Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <main>
  <button aria-label="Say Hello">
    <div>
      <span class="inner">
        <div class="particles" aria-hidden="true"></div>
        <span class="text">Say hello</span>
        <span class="halo" aria-hidden="true"></span>
      </span>
    </div>
    <span class="shadow" aria-hidden="true"></span>
  </button>
</main>


<div id="info-box" data-info-chrome="Rendering in Chrome" data-info-safari="Rendering in Safari" data-info-firefox="Rendering in Firefox" data-presentation-width="50vw" data-presentation-height="50vh" data-inspiration="https://twitter.com/adrienrochet/status/1642127496823930882" data-hidden-onLoad="false"></div>
              
            
!

CSS

              
                @layer properties {
  @property --shadow-scale {
    syntax: "<number>";
    inherits: true;
    initial-value: 0;
  }
  @property --halo-scale {
    syntax: "<number>";
    inherits: true;
    initial-value: 0;
  }
  @property --halo-offset {
    syntax: "<number>";
    inherits: true;
    initial-value: 0;
  }
}

:root {
  --debug: 0;

  --bg-color: hsl(0, 0%, 8%);
  --svg-animation: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 122 116'%3E%3Cpath id='b' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M17.9256 115C17.434 111.774 13.1701 104.086 13.4282 95.6465C13.6862 87.207 18.6628 76.0721 17.9256 64.3628C17.1883 52.6535 8.7772 35.9512 9.00452 25.3907C9.23185 14.8302 16.2114 5.06512 17.9256 1'%3E%3C/path%3E%3Cpath id='d' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M84.1628 115C85.2376 112.055 94.5618 98.8394 93.9975 91.1338C93.4332 83.4281 82.5505 73.2615 84.1628 62.5704C85.775 51.8793 96.4803 35.4248 95.9832 25.7826C95.4861 16.1404 87.9113 4.71163 84.1628 1'%3E%3C/path%3E%3Cpath id='f' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M37.0913 115C37.9604 111.921 44.4347 99.4545 45.3816 92.9773C48.9305 68.7011 35.7877 73.9552 37.0913 62.7781C38.3949 51.6011 47.3889 36.9895 46.9869 26.9091C46.585 16.8286 40.1222 4.88034 37.0913 1'%3E%3C/path%3E%3Cpath id='h' stroke='%23fff' stroke-linecap='round' stroke-width='0' d='M112.443 115C111.698 112.235 108.25 106.542 107.715 93.7582C107.241 82.4286 107.229 83.9543 112.443 66.1429C116.085 44.0408 100.661 42.5908 101.006 33.539C101.35 24.4871 109.843 4.48439 112.443 1'%3E%3C/path%3E%3Cg%3E%3Ccircle r='1.5' fill='%23D9D9D9'%3E%3CanimateMotion dur='12s' repeatCount='indefinite'%3E%3Cmpath href='%23b'%3E%3C/mpath%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3Cg%3E%3Ccircle r='1' fill='%23fff' fill-opacity='1' shape-rendering='crispEdges'%3E%3CanimateMotion dur='8s' repeatCount='indefinite'%3E%3Cmpath href='%23d'%3E%3C/mpath%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3Cg%3E%3Ccircle r='.5' fill='%23fff' fill-opacity='1' shape-rendering='crispEdges'%3E%3CanimateMotion dur='10s' repeatCount='indefinite'%3E%3Cmpath href='%23f'%3E%3C/mpath%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3Cg%3E%3Ccircle r='.8' fill='%23fff' fill-opacity='1' shape-rendering='crispEdges'%3E%3CanimateMotion dur='6s' repeatCount='indefinite'%3E%3Cmpath href='%23h'%3E%3C/mpath%3E%3C/animateMotion%3E%3C/circle%3E%3C/g%3E%3C/svg%3E");

  --btn-radius: 12;
 
  --animation-timing-function: ease-in-out;

  --transitions: --shadow-scale 0.3s var(--animation-timing-function),
    --halo-scale 0.3s var(--animation-timing-function),
    --halo-offset 0.3s var(--animation-timing-function),
    transform 0.2s var(--animation-timing-function);

  --color-a: hsl(249.8 100% 50%);
  --color-b: hsl(259.15 100% 40%);
  --color-c: hsl(281.28 100% 56.01%);
}

@supports (color: color(display-p3 0 0 0)) {
  body {
    --color-a: color(display-p3 0.16 0.1 0.99);
    --color-b: color(display-p3 0.23 0 0.77);
    --color-c: color(display-p3 0.68 0.08 1);
  }
}

*,
*:before,
*:after {
  box-sizing: border-box;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  outline: calc(var(--debug) * 1px) dotted
    hsl(calc(var(--debug) * 10 * 1deg) 60% 60%);
}

html,
body,
main {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg-color);
  font-family: "Mona Sans", sans-serif;
}

main {
  display: flex;
  align-items: center;
  justify-content: center;

  transform: scale(2);
  position: relative;

  & button {
    --halo-scale: 0;
    --halo-offset: 100;
    --shadow-scale: 0;

    all: unset;
    font-family: "Inter", sans-serif;
    cursor: pointer;
    color: white;
    max-height: 46px;
    font-size: 22px;

    box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
    border-radius: calc(var(--btn-radius) * 1px);
    background: linear-gradient(
      180deg,
      rgba(255, 255, 255, 0.1) 0%,
      rgba(255, 255, 255, 0) 100%
    );

    position: relative;
    transition: var(--transitions);

    & > div {
      overflow: hidden;
      position: relative;
      border-radius: calc(var(--btn-radius) * 1px);
    }

    & .inner {
      display: block;
      background: hsl(300deg 0% 13% / 100%);
      position: relative;
      width: calc(100% - 1px);
      height: calc(100% - 1px);
      top: 1px;
      left: 1px;
      border-radius: calc(var(--btn-radius) * 1px);
      &:before,
      &:after {
        content: "";
        display: block;
        position: absolute;
        width: 70%;
        height: 1px;
        top: -1px;
        left: 50%;
        background: linear-gradient(
          90deg,
          rgba(255, 255, 255, 0) 0%,
          rgba(255, 255, 255, 0.25) 50%,
          rgba(255, 255, 255, 0) 100%
        );
        transform: translateX(-50%);
        opacity: 0.6;
      }
      &:before {
        mix-blend-mode: overlay;
      }
      &:after {
        top: calc(100% - 2px);
        opacity: var(--halo-scale);
        background: linear-gradient(
          90deg,
          rgba(255, 255, 255, 0) 0%,
          rgb(255 255 255 / 50%),
          rgba(255, 255, 255, 0) 100%
        );
        filter: brightness(200%) opacity(0.3);
      }
    }

    & .particles {
      background-image: var(--svg-animation), var(--svg-animation);
      background-repeat: no-repeat, repeat;
      background-position: center, center top 100%;
      background-size: 100% 100%, 50%;
      -webkit-mask-image: linear-gradient(
        to top,
        transparent,
        black,
        transparent
      );
      border-radius: calc(var(--btn-radius) * 1px);
      width: 100%;
      height: 100%;
      position: absolute;
      opacity: var(--halo-scale);

      /* Since having the blurry filter creates a weird artefact on safari
         I decided to doulbe the particles background in a pseudo-element
         And apply the blur filter on it.
      */
      &:before {
        content: "";
        display: block;
        position: absolute;
        width: 100%;
        height: 100%;
        background-image: var(--svg-animation), var(--svg-animation),
          var(--svg-animation); // The third one is to make a kind of bokeh effect
        background-repeat: no-repeat, repeat, repeat;
        background-position: center, center top 100%, center center;
        background-size: 100% 100%, 50%, 120%;
        filter: blur(1px);
        border-radius: calc(var(--btn-radius) * 1px);
        // Fix for safari
        will-change: filter;
      }
    }

    & .text {
      position: relative;
      z-index: 2;
      padding: 0.545em 1.455em;
      width: max-content;
      display: block;
      background: linear-gradient(
        180deg,
        #ffffff 0%,
        rgba(255, 255, 255, 0.7) 100%
      );
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      background-clip: text;
      text-fill-color: transparent;
      text-shadow: 0px 2px 2px rgba(0, 0, 0, 0.25);
    }

    & .halo {
      border-radius: calc(calc(var(--btn-radius)+1) * 1px);
      overflow: hidden;
      width: 100%;
      height: 100%;
      position: absolute;
      top: 0;
      left: 0;
      border-radius: calc(var(--btn-radius) * 1px);

      &:before {
        content: "";
        display: block;
        width: 80%;
        height: 60%;
        position: absolute;
        top: 80%;
        left: 50%;
        background: linear-gradient(
          180deg,
          var(--color-a) 0%,
          var(--color-b) 39.33%,
          var(--color-c) 59%
        );
        filter: blur(12px) saturate(1.2);
        transform: translateX(-50%) translateY(calc(var(--halo-offset) * 1%))
          scale(var(--halo-scale));
        // Fix for safari
        will-change: filter;
      }
    }

    //&:focus,
    &:hover {
      --halo-scale: 1;
      --shadow-scale: 1;
      --halo-offset: -70;
    }

    &:active {
      --halo-scale: 1.1;
      --halo-offset: -80;
      --shadow-scale: 1.2;

      transform: translateY(calc(var(--halo-scale) * 1px));
    }
  }

  /* I did not found a better way to achieve that with less dom. 
   *
   * Due to the button having a overflow: hidden;
   *
  */
  & .shadow {
    position: absolute;
    width: 60%;
    height: 20%;
    bottom: -26%;
    z-index: -1;
    left: 0;
    right: 0;
    margin: auto;
    border-radius: 1e5%;
    /* let's reuse the same variable */
    transform: scale(var(--shadow-scale));
    opacity: calc(var(--halo-scale) / 2);

    background: linear-gradient(
      to bottom,
      var(--color-c),
      var(--color-b),
      var(--color-a)
    );
    filter: blur(8px) opacity(0.7);
    // Fix for safari
    will-change: filter;
  }
}

/* 
  Fallback for browsers who do not support registerProperty.
  ( Which cannot use variables transitions )
*/

body.registerProperty-not-supported {
  & button .inner:after {
    transition: opacity 0.3s ease;
  }
  & button,
  & button .halo:before {
    transition: transform 0.3s ease;
  }

  & button .shadow {
    transition: transform 0.3s ease, opacity 0.3s ease;
  }
}

              
            
!

JS

              
                // No JS for this rendering 🙏

// The only JS files used are for the presentation deck 
// And to detect support for the CSS registerProperty.
              
            
!
999px

Console