color: #4F8FE6;
}
-.updater {
+.color-editor {
+ position: relative;
+ height: 230px;
+}
+
+.color {
width: 200px;
height: 200px;
background: #fff;
- border: 10px solid #0F261F;
+ margin-left: 20px;
+}
+
+.light {
+ width: 20px;
+ height: 20px;
+ background: #FA2B00;
+ position: absolute;
+ left: 220px;
+}
+
+.x-label, .y-label, .z-label {
+ margin: 0;
+ padding: 0;
+ color: #FA2B00;
+ width: 100px;
+ position: absolute;
+ top: 200px;
+}
+
+.y-label {
+ transform: rotate(-90deg);
+ left: -45px;
+ top: 135px;
+}
+
+.z-label {
+ left: 125px;
+}
+
+.timeline {
+ width: 200px;
+ height: 50px;
+ background: black;
+ margin: 0 0 0 20px;
}
@media (prefers-color-scheme: dark) {
color: #fff;
background: #0F261F;
}
-
- .updater {
- background: #0F261F;
- border: 10px solid #fff;
- }
}
<link rel="icon" type="image/png" sizes="96x96" href="/images/icon96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/icon16.png">
- <script type="module" src="/js/updater.js"></script>
+ <script type="module" src="/js/fyr.js"></script>
</head>
<body>
<div class="content-wrapper">
<h1><img alt="" src="/images/logo.png" />Fyr.</h1>
<p>Communicate through light.</p>
<h2>Update Status</h2>
- <div class="updater" />
+ <p>Move your mouse around the color box to change hue and saturation. Scroll inside the box to adjust lightness. Press to start recording the color in the timeline, release to stop.</p>
+ <div class="timeline"></div>
+ <div class="color-editor">
+ <div class="color"></div>
+ <div class="light"></div>
+ <p class="x-label">Hue →</p>
+ <p class="z-label">Lightness ⇈</p>
+ <p class="y-label">Saturation →</p>
+ </div>
+ <button class="post">Post</button>
+ <button class="clear">Clear</button>
</main>
</div>
</body>
--- /dev/null
+var color = document.querySelector('.color'),
+ light = document.querySelector('.light'),
+ timeline = document.querySelector('.timeline'),
+ clear = document.querySelector('.clear'),
+ colors = [],
+ selecting = false,
+ recording = false,
+ animationTime = 0,
+ lastFrame = 0,
+ t = null,
+ h = 0,
+ s = 100,
+ l = 50;
+
+function render() {
+ color.style.backgroundColor = `hsl(${h} ${s}% ${l}%)`;
+ light.style.transform = `translateY(-${l*2.2}px)`;
+}
+
+function renderTimeline() {
+ var background = 'linear-gradient(90deg,',
+ last = 0;
+ for (var {h, s, l, t: now} of colors) {
+ background += `hsl(${h} ${s}% ${l}%) ${Math.round(100*last/3000)}%,`;
+ background += `hsl(${h} ${s}% ${l}%) ${Math.round(100*(last+now)/3000)}%,`;
+ last += now;
+ }
+ background += `black ${Math.round(100*last/3000)}%, black 101%)`;
+ timeline.style.background = background;
+}
+
+function length() {
+ return colors.map((c) => c.t).reduce((s, t) => s + t, 0);
+}
+
+function addColor() {
+ t = Date.now();
+ colors.push({h, s, l, t: 0});
+}
+
+function record() {
+ if (recording) setTimeout(record, 100);
+ var c = colors[colors.length - 1],
+ l = length();
+ if (c.h !== h || c.s !== s || c.l !== l) {
+ c.t = Math.min(3000, Date.now() - t);
+ addColor();
+ c = colors[colors.length - 1];
+ }
+ if (l >= 3000) return;
+ c.t = Math.min(3000, Date.now() - t);
+ renderTimeline();
+}
+
+function preview(current) {
+ if (selecting || recording) return;
+ window.requestAnimationFrame(preview);
+ var dt = current - lastFrame,
+ last = 0;
+ if (dt > 32) {
+ animationTime = (animationTime + dt) % 3000
+ color.style.background = `hsl(0 0% 0%)`;
+ for (var {h, s, l, t: now} of colors) {
+ if (animationTime >= last && animationTime < last+now) {
+ color.style.background = `hsl(${h} ${s}% ${l}%)`;
+ break;
+ }
+ last += now;
+ }
+ lastFrame = current;
+ }
+}
+
+color.addEventListener('mousemove', ({offsetX: x, offsetY: y}) => {
+ h = Math.round(360 * x / 200);
+ s = Math.round(100 * (200 - y) / 200);
+ render();
+});
+
+color.addEventListener('wheel', ({deltaY: y}) => {
+ selecting = true;
+ l = Math.min(Math.max(0, l + y/4), 100)
+ render();
+});
+
+color.addEventListener('mousedown', () => {
+ addColor();
+ recording = true;
+ setTimeout(record, 100);
+});
+
+color.addEventListener('mouseup', () => {
+ recording = false;
+});
+
+color.addEventListener('mouseenter', () => {
+ selecting = true;
+});
+
+color.addEventListener('mouseout', () => {
+ recording = false;
+ selecting = false;
+ window.requestAnimationFrame(preview);
+});
+
+clear.addEventListener('click', () => {
+ colors = [];
+ renderTimeline();
+});
+
+render();
+renderTimeline();
+++ /dev/null
-function getColors({x, y}, [{x: ax, y: ay}, {x: bx, y: by}, {x: cx, y: cy}]) {
-
- if (x < 50 && y < 50) return {r: 255, g: 255, b: 255};
- if (x > 150 && y < 50) return {r: 0, g: 0, b: 0};
-
- var { abs, round, min } = Math,
- area = abs((bx - ax) * (cy - ay) - (cx - ax) * (by - ay)),
- R = abs((bx - x) * (cy - y) - (cx - x) * (by - y)),
- G = abs((ax - x) * (cy - y) - (cx - x) * (ay - y)),
- B = abs((ax - x) * (by - y) - (bx - x) * (ay - y));
-
- // Normalize the areas to get the weights
- const weights = {
- r: round(255 * min(1, R / area)),
- g: round(255 * min(1, G / area)),
- b: round(255 * min(1, B / area))
- };
-
- return weights;
-}
-
-var updater = document.querySelector('.updater'),
- color = {r: 0, g: 0, b: 0};
-updater.addEventListener('mousemove', ({offsetX: x, offsetY: y}) => {
- var {r, g, b} = color = getColors(
- {x, y},
- [{x: 0, y: 200}, {x: 200, y: 200}, {x: 100, y: 0}]
- )
- updater.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
-});
-updater.addEventListener('mousedown', () => {
- console.log('Adding Color', color);
-});
-updater.addEventListener('mouseup', () => {
- console.log('Stop Adding Color', color);
-});