├── 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 | } --------------------------------------------------------------------------------