Skip to content

Instantly share code, notes, and snippets.

@danielslyman
Created December 5, 2024 15:10
Show Gist options
  • Select an option

  • Save danielslyman/bff190e76b9fb539d761e9f15ddbcca3 to your computer and use it in GitHub Desktop.

Select an option

Save danielslyman/bff190e76b9fb539d761e9f15ddbcca3 to your computer and use it in GitHub Desktop.

Revisions

  1. danielslyman created this gist Dec 5, 2024.
    1 change: 1 addition & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    <div id="app"></div>
    7 changes: 7 additions & 0 deletions react-glow-cards-minimal.markdown
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    React Glow Cards – Minimal
    --------------------------


    A [Pen](https://codepen.io/jh3y/pen/WNmQXyE) by [Jhey](https://codepen.io/jh3y) on [CodePen](https://codepen.io).

    [License](https://codepen.io/license/pen/WNmQXyE).
    57 changes: 57 additions & 0 deletions script.babel
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,57 @@
    import React from 'https://cdn.skypack.dev/react'
    import { render } from 'https://cdn.skypack.dev/react-dom'

    const ROOT_NODE = document.querySelector('#app')

    /**
    * Tiny hook that you can use where you need it
    */
    const usePointerGlow = () => {
    const [status, setStatus] = React.useState(null)
    React.useEffect(() => {
    const syncPointer = ({ x: pointerX, y: pointerY }) => {
    const x = pointerX.toFixed(2)
    const y = pointerY.toFixed(2)
    const xp = (pointerX / window.innerWidth).toFixed(2)
    const yp = (pointerY / window.innerHeight).toFixed(2)
    document.documentElement.style.setProperty('--x', x)
    document.documentElement.style.setProperty('--xp', xp)
    document.documentElement.style.setProperty('--y', y)
    document.documentElement.style.setProperty('--yp', yp)
    setStatus({ x, y, xp, yp })
    }
    document.body.addEventListener('pointermove', syncPointer)
    return () => {
    document.body.removeEventListener('pointermove', syncPointer)
    }
    }, [])
    return [status]
    }

    const App = () => {
    const [status] = usePointerGlow();
    return (
    <main>
    <article data-glow>
    <span data-glow />
    <button data-glow>
    <span>Glow Up</span>
    </button>
    </article>
    <article data-glow>
    <span data-glow />
    <button data-glow>
    <span>Glow Up</span>
    </button>
    </article>
    <article data-glow>
    <span data-glow />
    <button data-glow>
    <span>Glow Up</span>
    </button>
    </article>
    </main>
    )
    }

    render(<App/>, ROOT_NODE)
    172 changes: 172 additions & 0 deletions style.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,172 @@
    :root {
    --backdrop: hsl(0 0% 60% / 0.12);
    --radius: 14;
    --border: 3;
    --backup-border: var(--backdrop);
    --size: 200;
    }

    article:first-of-type {
    --base: 80;
    --spread: 500;
    --outer: 1;
    }
    article:last-of-type {
    --outer: 1;
    --base: 220;
    --spread: 200;
    }

    *,
    *:after,
    *:before {
    box-sizing: border-box;
    }
    body {
    display: grid;
    place-items: center;
    min-height: 100vh;
    overflow: hidden;
    background: hsl(0 0% 4%);
    }

    .wrapper {
    position: relative;
    }

    article {
    aspect-ratio: 3 / 4;
    border-radius: calc(var(--radius) * 1px);
    width: 260px;
    position: relative;
    grid-template-rows: 1fr auto;
    box-shadow: 0 1rem 2rem -1rem black;
    padding: 1rem;
    display: grid;
    border: 1px solid hsl(0 0% 100% / 0.15);
    backdrop-filter: blur(calc(var(--cardblur, 5) * 1px));
    /* For demo purposes. Means you get the effect on mobile */
    touch-action: none;
    }
    main {
    display: flex;
    gap: 2rem;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    width: 120ch;
    max-width: calc(100vw - 2rem);
    position: relative;
    }

    /* Glow specific styles */
    [data-glow] {
    --border-size: calc(var(--border, 2) * 1px);
    --spotlight-size: calc(var(--size, 150) * 1px);
    --hue: calc(var(--base) + (var(--xp, 0) * var(--spread, 0)));
    background-image: radial-gradient(
    var(--spotlight-size) var(--spotlight-size) at
    calc(var(--x, 0) * 1px)
    calc(var(--y, 0) * 1px),
    hsl(var(--hue, 210) calc(var(--saturation, 100) * 1%) calc(var(--lightness, 70) * 1%) / var(--bg-spot-opacity, 0.1)), transparent
    );
    background-color: var(--backdrop, transparent);
    background-size: calc(100% + (2 * var(--border-size))) calc(100% + (2 * var(--border-size)));
    background-position: 50% 50%;
    background-attachment: fixed;
    border: var(--border-size) solid var(--backup-border);
    position: relative;
    touch-action: none;
    }

    [data-glow]::before,
    [data-glow]::after {
    pointer-events: none;
    content: "";
    position: absolute;
    inset: calc(var(--border-size) * -1);
    border: var(--border-size) solid transparent;
    border-radius: calc(var(--radius) * 1px);
    background-attachment: fixed;
    background-size: calc(100% + (2 * var(--border-size))) calc(100% + (2 * var(--border-size)));
    background-repeat: no-repeat;
    background-position: 50% 50%;
    mask:
    linear-gradient(transparent, transparent),
    linear-gradient(white, white);
    mask-clip: padding-box, border-box;
    mask-composite: intersect;
    }

    /* This is the emphasis light */
    [data-glow]::before {
    background-image: radial-gradient(
    calc(var(--spotlight-size) * 0.75) calc(var(--spotlight-size) * 0.75) at
    calc(var(--x, 0) * 1px)
    calc(var(--y, 0) * 1px),
    hsl(var(--hue, 210) calc(var(--saturation, 100) * 1%) calc(var(--lightness, 50) * 1%) / var(--border-spot-opacity, 1)), transparent 100%
    );
    filter: brightness(2);
    }
    /* This is the spotlight */
    [data-glow]::after {
    background-image: radial-gradient(
    calc(var(--spotlight-size) * 0.5) calc(var(--spotlight-size) * 0.5) at
    calc(var(--x, 0) * 1px)
    calc(var(--y, 0) * 1px),
    hsl(0 100% 100% / var(--border-light-opacity, 1)), transparent 100%
    );
    }
    [data-glow] > [data-glow]:not(:is(a, button)) {
    position: absolute;
    inset: 0;
    will-change: filter;
    opacity: var(--outer, 1);
    }
    [data-glow] > [data-glow]:not(:is(a, button)) {
    border-radius: calc(var(--radius) * 1px);
    border-width: calc(var(--border-size) * 20);
    filter: blur(calc(var(--border-size) * 10));
    background: none;
    pointer-events: none;
    }
    [data-glow] > [data-glow]:not(:is(a, button))::before {
    inset: -10px;
    border-width: 10px;
    }
    [data-glow] > [data-glow] {
    border: none;
    }
    [data-glow] :is(a, button) {
    border-radius: calc(var(--radius) * 1px);
    border: var(--border-size) solid transparent;
    }
    [data-glow] :is(a, button) [data-glow] {
    background: none;
    }
    [data-glow] :is(a, button) [data-glow]::before {
    inset: calc(var(--border-size) * -1);
    border-width: calc(var(--border-size) * 1);
    }

    article button {
    padding: 0.75rem 2rem;
    align-self: end;
    color: hsl(0 0% 80%);
    }

    button[data-glow] span {
    font-weight: bold;
    background-image: radial-gradient(
    var(--spotlight-size) var(--spotlight-size) at
    calc(var(--x, 0) * 1px)
    calc(var(--y, 0) * 1px),
    hsl(var(--hue, 210) calc(var(--saturation, 100) * 1%) calc(var(--lightness, 70) * 1%) / var(--bg-spot-opacity, 1)), transparent
    );
    background-color: var(--backdrop, transparent);
    background-position: 50% 50%;
    background-attachment: fixed;
    background-clip: text;
    filter: brightness(1.5);
    color: transparent;
    }