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

              
                <div class="panel" id="panel">
  <div class="frame rim"></div>
  <div class="frame accent"></div>
  <div class="frame accent glow"></div>
  <div class="circle icon">🌭</div>
  <div class="content">
    Lorem ipsum dolor sit, amet consectetur adipisicing elit. Magni harum vitae, ex ad aut corrupti corporis molestias velit exercitationem numquam cupiditate fuga, esse maxime officiis beatae molestiae odio in dolore!
  </div>
</div>
              
            
!

CSS

              
                .panel {
  position: relative;
  padding: 1em;
  box-sizing: border-box;
  width: fit-content;
}

.panel .frame,
.panel .frame::before {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  box-sizing: border-box;
}

.panel .icon {
  position: absolute;
  top: 100%;
  left: 50%;
  width: 45px;
  height: 45px;
  background-color: #111216;
  box-shadow: inset 0 0 0 1px #8bc34a;
  border-radius: 50%;
  transform: translateX(-50%) translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Gray border */
.frame.rim {
  border: 1px solid #303030;
  box-shadow:
    inset 0 0 0 1px #0a0a0a,
    0 0 0 1px #0a0a0a;
  background-color: #111216;

  clip-path: var(--fill-path);
}

/* Green accent edges */
.frame.accent::before {
  content: '';
  background-image: linear-gradient(
    to top,
    #8bc34a,
    #558b2f99 25%,
    transparent 70%
  );
  clip-path: var(--outline-path);
}

/* Glow is just a copy of the accent frame with a blur filter */
.frame.accent.glow {
  filter: blur(4px);
}

.panel .content {
  min-width: 100px;
  min-height: 100px;
  width: 280px;
  height: 120px;
  position: relative;
  z-index: 1;
  font-size: smaller;
  resize: both;
  overflow: auto;
  /* Add some cushion at the bottom to avoid spilling over the icon */
  margin-bottom: 1.5em;
}

body {
  background-color: #0b0c0d;
  color: silver;
  font-family: Segoe UI,Helvetica Neue,Tahoma,Geneva,Verdana,sans-serif;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  margin: 0;
}
              
            
!

JS

              
                // Gap between the icon and panel's borders
const gutter = 4

/**
 * Recomputes the clip paths for the panel: a "fill path" for the background
 * and an "outline" path for the border. Punches a hole on the bottom edge
 * to make space for the icon. This expects the given panel to have a child
 * node with the "icon" class.
 * @param {HTMLElement} $el
 */
const updateClipPath = ($el) => {
  const $icon = $el.querySelector('.icon')
  const iconBox = $icon.getBoundingClientRect()
  const panelBox = $el.getBoundingClientRect()

  const iconWidth = iconBox.width
  const iconHeight = iconBox.height

  // Sort out where we need to go: the top, left, and right sides of the
  // panel are simple straight lines, but we need to stop at the sides of
  // the icon to create an arc around it.
  const w = panelBox.width
  const h = panelBox.height
  const rx = iconWidth / 2 + (gutter / 2)
  const ry = iconHeight / 2 + (gutter / 2)
  const ix1 = iconBox.x - panelBox.x - gutter
  const ix2 = ix1 + iconWidth + gutter * 2

  // Start with the fill path: trace around the edges of the panel clockwise
  // until we meet back at the top left corner
  const fillPath = [
    'M0,0',
    `L${w},0`,
    `L${w},${h}`,
    `L${ix2},${h}`,
    `A ${rx} ${ry} 0 0 0 ${ix1},${h}`,
    `L0,${h}`,
    'L0,0',
  ]

  // The outline path is an extension of the fill path: move 1px inward, then
  // move back around counter-clockwise until we come back to 1,1
  const outlinePath = fillPath.concat(
    'L1,1',
    `L1,${h - 1}`,
    `L${ix1 - 1},${h - 1}`,
    `A ${rx + 1} ${ry + 1} 0 0 1 ${ix2 + 1},${h - 1}`,
    `L${w - 1},${h - 1}`,
    `L${w - 1},1`,
    'L1,1',
  )

  // Set the fill and outline paths as CSS variables on the panel element
  $el.style.setProperty('--fill-path', `path('${fillPath.join(' ')}')`)
  $el.style.setProperty('--outline-path', `path('${outlinePath.join(' ')}')`)
}

document.addEventListener('DOMContentLoaded', () => {
  // Set up a ResizeObserver and attach it to the panel: any time the panel's
  // dimensions change, we'll recompute the path
  const $panel = document.getElementById('panel')
  const observer = new ResizeObserver(() => updateClipPath($panel))
  observer.observe($panel)

  updateClipPath($panel)
})
              
            
!
999px

Console