Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ 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

Save Automatically?

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

              
                #app
              
            
!

CSS

              
                /* PrismJS 1.23.0
https://prismjs.com/download.html#themes=prism-tomorrow&languages=css+css-extras&plugins=line-numbers+inline-color+toolbar+copy-to-clipboard */
/**
 * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
 * Based on https://github.com/chriskempson/tomorrow-theme
 * @author Rose Pritchard
 */

code[class*="language-"],
pre[class*="language-"] {
  color: #ccc;
  background: none;
  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
  font-size: 1em;
  text-align: left;
  white-space: pre;
  word-spacing: normal;
  word-break: normal;
  word-wrap: normal;
  line-height: 1.5;

  -moz-tab-size: 4;
  -o-tab-size: 4;
  tab-size: 4;

  -webkit-hyphens: none;
  -moz-hyphens: none;
  -ms-hyphens: none;
  hyphens: none;

}

/* Code blocks */
pre[class*="language-"] {
  padding: 2rem;
  margin: 0;
  overflow: auto;
  outline: transparent;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
  background: #2d2d2d;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
  padding: .1em;
  border-radius: .3em;
  white-space: normal;
}

.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
  color: #999;
}

.token.punctuation {
  color: #ccc;
}

.token.tag,
.token.attr-name,
.token.namespace,
.token.deleted {
  color: #e2777a;
}

.token.function-name {
  color: #6196cc;
}

.token.boolean,
.token.number,
.token.function {
  color: #f08d49;
}

.token.property,
.token.class-name,
.token.constant,
.token.symbol {
  color: #f8c555;
}

.token.selector,
.token.important,
.token.atrule,
.token.keyword,
.token.builtin {
  color: #cc99cd;
}

.token.string,
.token.char,
.token.attr-value,
.token.regex,
.token.variable {
  color: #7ec699;
}

.token.operator,
.token.entity,
.token.url {
  color: #67cdcc;
}

.token.important,
.token.bold {
  font-weight: bold;
}
.token.italic {
  font-style: italic;
}

.token.entity {
  cursor: help;
}

.token.inserted {
  color: green;
}

pre[class*="language-"].line-numbers {
  position: relative;
  padding-left: 3.8em;
  counter-reset: linenumber;
}

pre[class*="language-"].line-numbers > code {
  position: relative;
  white-space: inherit;
}

.line-numbers .line-numbers-rows {
  position: absolute;
  pointer-events: none;
  top: 0;
  font-size: 100%;
  left: -3.8em;
  width: 3em; /* works for line-numbers below 1000 lines */
  letter-spacing: -1px;
  border-right: 1px solid #999;

  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;

}

  .line-numbers-rows > span {
    display: block;
    counter-increment: linenumber;
  }

    .line-numbers-rows > span:before {
      content: counter(linenumber);
      color: #999;
      display: block;
      padding-right: 0.8em;
      text-align: right;
    }

span.inline-color-wrapper {
  /*
   * The background image is the following SVG inline in base 64:
   *
   * <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2">
   *     <path fill="gray" d="M0 0h2v2H0z"/>
   *     <path fill="white" d="M0 0h1v1H0zM1 1h1v1H1z"/>
   * </svg>
   *
   * SVG-inlining explained:
   * https://stackoverflow.com/a/21626701/7595472
   */
  background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyIDIiPjxwYXRoIGZpbGw9ImdyYXkiIGQ9Ik0wIDBoMnYySDB6Ii8+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0wIDBoMXYxSDB6TTEgMWgxdjFIMXoiLz48L3N2Zz4=");
  /* This is to prevent visual glitches where one pixel from the repeating pattern could be seen. */
  background-position: center;
  background-size: 110%;

  display: inline-block;
  height: 1.333ch;
  width: 1.333ch;
  margin: 0 .333ch;
  box-sizing: border-box;
  border: 1px solid white;
  outline: 1px solid rgba(0,0,0,.5);
  overflow: hidden;
}

span.inline-color {
  display: block;
  /* To prevent visual glitches again */
  height: 120%;
  width: 120%;
}

div.code-toolbar {
  position: relative;
}

div.code-toolbar > .toolbar {
  position: absolute;
  top: .3em;
  right: .2em;
  opacity: 1;
}

div.code-toolbar:hover > .toolbar {
  opacity: 1;
}

/* Separate line b/c rules are thrown out if selector is invalid.
   IE11 and old Edge versions don't support :focus-within. */
div.code-toolbar:focus-within > .toolbar {
  opacity: 1;
}

div.code-toolbar > .toolbar .toolbar-item {
  display: inline-block;
}

div.code-toolbar > .toolbar a {
  cursor: pointer;
}

div.code-toolbar > .toolbar button {
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  line-height: normal;
  overflow: visible;
  padding: 0;
  -webkit-user-select: none; /* for button */
  -moz-user-select: none;
  -ms-user-select: none;
}

div.code-toolbar > .toolbar a,
div.code-toolbar > .toolbar button {
  color: #bbb;
  font-size: 1rem;
  padding: 0.5rem;
  font-family: sans-serif;
  background: hsl(0, 0%, 25%);
  border-radius: .5em;
  outline: transparent;
  cursor: pointer;
}

div.code-toolbar > .toolbar a:hover,
div.code-toolbar > .toolbar a:focus,
div.code-toolbar > .toolbar button:hover,
div.code-toolbar > .toolbar button:focus,
div.code-toolbar > .toolbar span:hover,
div.code-toolbar > .toolbar span:focus {
  background: hsl(0, 0%, 40%);
  text-decoration: none;
}

* {
  box-sizing: border-box;
}

body {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  background: hsl(210, 30%, 60%);
  overflow-x: hidden;
  overflow-y: scroll;
}

pre {
  border-radius: 6px;
}

.watch {
  height: 300px;
  align-self: center;
  justify-self: center;
}

.container {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-rows: auto auto;
}

.slider-thumb {
  background: transparent;
  position: relative;

  &:after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    background: hsl(210, 80%, 70%);
    height: 24px;
    width: 24px;
    border: '2px solid hsl(%s, 80%, 50%)' % var(--hue, 210)
  }

  &:active {
    border: '2px solid hsl(%s, 80%, 50%)' % var(--hue, 210);
  }
}

.slider-track {
  height: 10px;
  width: 100%;
  min-width: 100px;
  background: hsl(0, 0%, 90%);
  border: 1px solid hsl(0, 0%, 70%);
}

.watch {
  grid-columm: 2;
}

label {
  font-weight: bold;
  font-family: sans-serif;
  color: hsl(0, 0, 10%);
}

.code-block {
  grid-row: 1 / -1;
}

.controls {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: 1fr 1fr;
  align-items: center;
  padding: 1rem;
  grid-gap: 1rem 2rem;
}
              
            
!

JS

              
                import gsap from 'https://cdn.skypack.dev/gsap'
import React, {
  useEffect,
  useRef,
  useState,
} from 'https://cdn.skypack.dev/react'
import T from 'https://cdn.skypack.dev/prop-types'
import { Range } from 'https://cdn.skypack.dev/react-range'
import { render } from 'https://cdn.skypack.dev/react-dom'
import Prism from 'https://cdn.skypack.dev/prismjs'

const ROOT_NODE = document.querySelector('#app')
const SPEED = 12
const getCode = (timeWindow, duration) => `/**
 * LOOP is a timeline moving the hands
 * It has repeat: -1 set
 * Use "fromTo" to animate a time window
 * This is "Meta GSAP" 😎
 */
gsap.fromTo(LOOP, {
  totalTime: ${timeWindow[0]},
}, {
  totalTime: ${timeWindow[1]},
  duration: ${duration[0]},
  ease: 'none',
  repeat: -1,
})
`

const getCodeMarkup = (timeWindow, duration) => {
  const code = getCode(timeWindow, duration)
  return Prism.highlight(code, Prism.languages.javascript, 'javascript')
}

const Watch = ({ timeWindow, duration }) => {
  const minuteRef = useRef(null)
  const hourRef = useRef(null)
  const rawRef = useRef(null)
  const loopRef = useRef(null)
  // Sets up the timeline.
  useEffect(() => {
    rawRef.current = gsap
      .timeline({
        repeat: -1,
        paused: true,
      })
      .to(
        minuteRef.current,
        {
          rotate: 360,
          repeat: 12,
          duration: SPEED / 13,
          transformOrigin: '50% 100%',
          ease: 'none',
          immediateRender: false,
        },
        0
      )
      .to(
        hourRef.current,
        {
          rotate: 360,
          immediateRender: false,
          duration: SPEED,
          transformOrigin: '50% 100%',
          ease: 'none',
        },
        0
      )
  }, [])
  useEffect(() => {
    loopRef.current = gsap.fromTo(
      rawRef.current,
      {
        totalTime: timeWindow[0],
      },
      {
        duration: duration[0],
        totalTime: timeWindow[1],
        ease: 'none',
        repeat: -1,
      }
    )
  }, [duration, timeWindow])
  // useEffect(() => {})
  return (
    <svg
      className="watch"
      viewBox="0 0 574 707"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      xmlnsXlink="http://www.w3.org/1999/xlink">
      <rect
        x={526}
        y={328}
        width={41}
        height={53}
        rx={9}
        fill="hsl(35, 90%, 65%)"
      />
      <rect x={168} width={238} height={707} rx={12} fill="hsl(0, 0%, 85%)" />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M168 608.916V169.084C203.384 149.897 243.92 139 287 139s83.616 10.897 119 30.084v439.832C370.616 628.103 330.08 639 287 639s-83.616-10.897-119-30.084z"
        fill="hsla(0, 0%, 45%, 0.65)"
      />
      <circle
        cx={287}
        cy={354}
        r={249}
        fill="hsl(0, 0%, 74%)"
        stroke="#fff"
        strokeWidth={2}
      />
      <circle
        cx={287}
        cy={354}
        r={207.5}
        fill="hsl(0, 0%, 42%)"
        stroke="#4F4F4F"
        strokeWidth={5}
      />
      <circle
        cx={287}
        cy={354}
        r={207.5}
        fill="hsl(0, 0%, 42%)"
        stroke="#4F4F4F"
        strokeWidth={5}
      />
      <path
        d="M285 176h4v15h-4v-15zM374.268 198.847l3.464 2-7.5 12.991-3.464-2 7.5-12.991zM440.153 263.268l2 3.464-12.991 7.5-2-3.464 12.991-7.5zM465 352v4h-15v-4h15zM442.153 441.268l-2 3.464-12.991-7.5 2-3.464 12.991 7.5zM377.732 507.153l-3.464 2-7.5-12.991 3.464-2 7.5 12.991zM289 532h-4v-15h4v15zM199.732 509.153l-3.464-2 7.5-12.991 3.464 2-7.5 12.991zM133.847 444.732l-2-3.464 12.991-7.5 2 3.464-12.991 7.5zM109 356v-4h15v4h-15zM131.847 266.732l2-3.464 12.991 7.5-2 3.464-12.991-7.5zM196.268 200.847l3.464-2 7.5 12.991-3.464 2-7.5-12.991z"
        fill="#000"
      />
      <path fill="url(#prefix__pattern0)" d="M267 234h40v40h-40z" />
      <rect
        ref={minuteRef}
        x={283}
        y={245}
        width={8}
        height={109}
        rx={4}
        fill="hsl(0, 0%, 0%)"
      />
      <rect
        ref={hourRef}
        x={281}
        y={263}
        width={12}
        height={91}
        rx={6}
        fill="hsl(0, 0%, 0%)"
      />
      <circle cx={287} cy={354} r={12} fill="#000" />
      <defs>
        <pattern
          id="prefix__pattern0"
          patternContentUnits="objectBoundingBox"
          width={1}
          height={1}>
          <use xlinkHref="#prefix__image0" transform="scale(.00333)" />
        </pattern>
        <image
          id="prefix__image0"
          width={300}
          height={300}
          xlinkHref="https://assets.codepen.io/605876/watch-branding-bear.png"
        />
      </defs>
    </svg>
  )
}
Watch.propTypes = {
  timeWindow: T.array,
  duration: T.array,
}

const App = () => {
  const [timeWindow, setTimeWindow] = useState([0, 12])
  const [duration, setDuration] = useState([12])
  const jsRef = useRef(getCodeMarkup(timeWindow, duration))
  return (
    <div className="container">
      <div className="code-block">
        <pre>
          <code
            className="language-javascript"
            dangerouslySetInnerHTML={{ __html: jsRef.current }}
          />
        </pre>
      </div>
      <Watch timeWindow={timeWindow} duration={duration} />
      <div className="controls">
        <label>Time Window</label>
        <Range
          step={1}
          min={0}
          max={36}
          values={timeWindow}
          onChange={values => {
            jsRef.current = getCodeMarkup(values, duration)
            setTimeWindow(values)
          }}
          renderTrack={({ props, children }) => (
            <div
              {...props}
              className="slider-track"
              style={{
                ...props.style,
              }}>
              {children}
            </div>
          )}
          renderThumb={({ props }) => (
            <div
              {...props}
              className="slider-thumb"
              style={{
                ...props.style,
                borderRadius: '50%',
                outline: 'transparent',
                height: '44px',
                width: '44px',
              }}
            />
          )}
        />
        <label>Duration</label>
        <Range
          step={1}
          min={1}
          max={36}
          values={duration}
          onChange={values => {
            jsRef.current = getCodeMarkup(timeWindow, values)
            setDuration(values)
          }}
          renderTrack={({ props, children }) => (
            <div
              {...props}
              className="slider-track"
              style={{
                ...props.style,
              }}>
              {children}
            </div>
          )}
          renderThumb={({ props }) => (
            <div
              {...props}
              className="slider-thumb"
              style={{
                ...props.style,
                borderRadius: '50%',
                outline: 'transparent',
                height: '44px',
                width: '44px',
              }}
            />
          )}
        />
      </div>
    </div>
  )
}

render(<App />, ROOT_NODE)

              
            
!
999px

Console