#app
View Compiled
/* 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;
}
View Compiled
import gsap from 'https://cdn.skypack.dev/gsap@3.11.0'
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 (infinite)
* 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)
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.