├── script.js
├── index.html
└── style.css
/script.js:
--------------------------------------------------------------------------------
1 | console.clear();
2 |
3 | const cardsContainer = document.querySelector(".cards");
4 | const cardsContainerInner = document.querySelector(".cards__inner");
5 | const cards = Array.from(document.querySelectorAll(".card"));
6 | const overlay = document.querySelector(".overlay");
7 |
8 | const applyOverlayMask = (e) => {
9 | const overlayEl = e.currentTarget;
10 | const x = e.pageX - cardsContainer.offsetLeft;
11 | const y = e.pageY - cardsContainer.offsetTop;
12 |
13 | overlayEl.style = `--opacity: 1; --x: ${x}px; --y:${y}px;`;
14 | };
15 |
16 | const createOverlayCta = (overlayCard, ctaEl) => {
17 | const overlayCta = document.createElement("div");
18 | overlayCta.classList.add("cta");
19 | overlayCta.textContent = ctaEl.textContent;
20 | overlayCta.setAttribute("aria-hidden", true);
21 | overlayCard.append(overlayCta);
22 | };
23 |
24 | const observer = new ResizeObserver((entries) => {
25 | entries.forEach((entry) => {
26 | const cardIndex = cards.indexOf(entry.target);
27 | let width = entry.borderBoxSize[0].inlineSize;
28 | let height = entry.borderBoxSize[0].blockSize;
29 |
30 | if (cardIndex >= 0) {
31 | overlay.children[cardIndex].style.width = `${width}px`;
32 | overlay.children[cardIndex].style.height = `${height}px`;
33 | }
34 | });
35 | });
36 |
37 | const initOverlayCard = (cardEl) => {
38 | const overlayCard = document.createElement("div");
39 | overlayCard.classList.add("card");
40 | createOverlayCta(overlayCard, cardEl.lastElementChild);
41 | overlay.append(overlayCard);
42 | observer.observe(cardEl);
43 | };
44 |
45 | cards.forEach(initOverlayCard);
46 | document.body.addEventListener("pointermove", applyOverlayMask);
47 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 | Pricing
14 |
15 |
16 |
17 |
Basic
18 |
$9.99
19 |
20 | - Access to standard workouts and nutrition plans
21 | - Email support
22 |
23 |
Get Started
24 |
25 |
26 |
27 |
Pro
28 |
$19.99
29 |
30 | - Access to advanced workouts and nutrition plans
31 | - Priority Email support
32 | - Exclusive access to live Q&A sessions
33 |
34 |
Upgrade to Pro
35 |
36 |
37 |
38 |
Ultimate
39 |
$29.99
40 |
41 | - Access to all premium workouts and nutrition plans
42 | - 24/7 Priority support
43 | - 1-on-1 virtual coaching session every month
44 | - Exclusive content and early access to new features
45 |
46 |
Go Ultimate
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=League+Spartan:wght@400;500;600;700;800;900&display=swap");
2 |
3 | *,
4 | *::after,
5 | *::before {
6 | box-sizing: border-box;
7 | margin: 0;
8 | padding: 0;
9 | }
10 |
11 | html,
12 | body {
13 | height: 100%;
14 | min-height: 100vh;
15 | }
16 |
17 | body {
18 | display: grid;
19 | place-items: center;
20 | font-family: "League Spartan", system-ui, sans-serif;
21 | font-size: 1.1rem;
22 | line-height: 1.2;
23 | background-color: #212121;
24 | color: #ddd;
25 | }
26 |
27 | ul {
28 | list-style: none;
29 | }
30 |
31 | .main {
32 | max-width: 75rem;
33 | padding: 3em 1.5em;
34 | }
35 |
36 | .main__heading {
37 | font-weight: 600;
38 | font-size: 2.25em;
39 | margin-bottom: 0.75em;
40 | text-align: center;
41 | color: #eceff1;
42 | }
43 |
44 | .cards {
45 | position: relative;
46 | }
47 |
48 | .cards__inner {
49 | display: flex;
50 | flex-wrap: wrap;
51 | gap: 2.5em;
52 | }
53 |
54 | .card {
55 | --flow-space: 0.5em;
56 | --hsl: var(--hue), var(--saturation), var(--lightness);
57 | flex: 1 1 14rem;
58 | padding: 1.5em 2em;
59 | display: grid;
60 | grid-template-rows: auto auto auto 1fr;
61 | align-items: start;
62 | gap: 1.25em;
63 | color: #eceff1;
64 | background-color: #2b2b2b;
65 | border: 1px solid #eceff133;
66 | border-radius: 15px;
67 | }
68 |
69 | .card:nth-child(1) {
70 | --hue: 165;
71 | --saturation: 82.26%;
72 | --lightness: 51.37%;
73 | }
74 |
75 | .card:nth-child(2) {
76 | --hue: 291.34;
77 | --saturation: 95.9%;
78 | --lightness: 61.76%;
79 | }
80 |
81 | .card:nth-child(3) {
82 | --hue: 338.69;
83 | --saturation: 100%;
84 | --lightness: 48.04%;
85 | }
86 |
87 | .card__bullets {
88 | line-height: 1.4;
89 | }
90 |
91 | .card__bullets li::before {
92 | display: inline-block;
93 | content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512' width='16' title='check' fill='%23dddddd'%3E%3Cpath d='M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z' /%3E%3C/svg%3E");
94 | transform: translatey(0.25ch);
95 | margin-right: 1ch;
96 | }
97 |
98 | .card__heading {
99 | font-size: 1.05em;
100 | font-weight: 600;
101 | }
102 |
103 | .card__price {
104 | font-size: 1.75em;
105 | font-weight: 700;
106 | }
107 |
108 | .flow>*+* {
109 | margin-top: var(--flow-space, 1.25em);
110 | }
111 |
112 | .cta {
113 | display: block;
114 | align-self: end;
115 | margin: 1em 0 0.5em 0;
116 | text-align: center;
117 | text-decoration: none;
118 | color: #fff;
119 | background-color: #0d0d0d;
120 | padding: 0.7em;
121 | border-radius: 10px;
122 | font-size: 1rem;
123 | font-weight: 600;
124 | }
125 |
126 | .overlay {
127 | position: absolute;
128 | inset: 0;
129 | pointer-events: none;
130 | user-select: none;
131 | opacity: var(--opacity, 0);
132 | -webkit-mask: radial-gradient(25rem 25rem at var(--x) var(--y),
133 | #000 1%,
134 | transparent 50%);
135 | mask: radial-gradient(25rem 25rem at var(--x) var(--y),
136 | #000 1%,
137 | transparent 50%);
138 | transition: 400ms mask ease;
139 | will-change: mask;
140 | }
141 |
142 | .overlay .card {
143 | background-color: hsla(var(--hsl), 0.15);
144 | border-color: hsla(var(--hsl), 1);
145 | box-shadow: 0 0 0 1px inset hsl(var(--hsl));
146 | }
147 |
148 | .overlay .cta {
149 | display: block;
150 | grid-row: -1;
151 | width: 100%;
152 | background-color: hsl(var(--hsl));
153 | box-shadow: 0 0 0 1px hsl(var(--hsl));
154 | }
155 |
156 | :not(.overlay)>.card {
157 | transition: 400ms background ease;
158 | will-change: background;
159 | }
160 |
161 | :not(.overlay)>.card:hover {
162 | --lightness: 95%;
163 | background: hsla(var(--hsl), 0.1);
164 | }
--------------------------------------------------------------------------------