From 53c95ab94cab5163424646d4a798a7ea7fb13ec7 Mon Sep 17 00:00:00 2001
From: Kyle Gunger <kgunger12@gmail.com>
Date: Tue, 12 Nov 2024 23:49:21 -0500
Subject: Toggle, Checkbox, and Slider widgets

---
 index.html                    |  12 +-
 scripts/gui-common/color.js   |  66 ++++++
 scripts/gui-common/widgets.js | 541 +++++++++++++++++++++++++++++++++++++++---
 scripts/main.js               |  36 ++-
 styles/widgets.css            | 109 +++++----
 5 files changed, 674 insertions(+), 90 deletions(-)
 create mode 100644 scripts/gui-common/color.js

diff --git a/index.html b/index.html
index d34b98d..23578ff 100644
--- a/index.html
+++ b/index.html
@@ -40,17 +40,19 @@
         <div class="widget color-temp" style="--percent: 0.3;"></div>
         <div class="widget color-light" style="--percent: 0.3;"></div>
         <div class="widget thermostat" style="--percent: 0.3; --arch-color: #0084ff;">
-            <div class="arch"></div>
-            <div class="gague">68</div>
-            <div class="temp">72°</div>
+            <arch></arch>
+            <gague>68</gague>
+            <temp>72°</temp>
         </div>
-        <div class="widget select h">
+        <div class="widget sel-button h">
             <div class="widget button">Heat</div>
             <div class="widget button">Cool</div>
             <div class="widget button" selected>Eco</div>
         </div>
     </content>
 
-    <script type="module" src="scripts/main.js"></script>
+    <script src="scripts/gui-common/color.js"></script>
+    <script src="scripts/gui-common/widgets.js"></script>
+    <script src="scripts/main.js"></script>
 </body>
 </html>
diff --git a/scripts/gui-common/color.js b/scripts/gui-common/color.js
new file mode 100644
index 0000000..90eac14
--- /dev/null
+++ b/scripts/gui-common/color.js
@@ -0,0 +1,66 @@
+class Color
+{
+    /** @type {Array<number>} */
+    channels = []
+
+    /**
+     * Construct a color
+     * @param  {...number} nums 
+     */
+    constructor(...nums)
+    {
+        this.channels = nums;
+    }
+
+    /**
+     * @param {Color} b
+     * @param {number} i
+     * @returns {Color}
+     */
+    interpolate(b, i)
+    {
+        let out = new Color();
+        for (let c = 0; c < this.channels.length && c < b.channels.length; c++)
+        {
+            out.channels.push(b.channels[c] * i + this.channels[c] * (1 - i))
+        }
+        return out;
+    }
+
+    /** Get the CSS string representing rgb
+     * @returns {string}
+     */
+    rgb()
+    {
+        return `rgb(${Math.trunc(this.channels[0] * 255)}, ${Math.trunc(this.channels[1] * 255)}, ${Math.trunc(this.channels[2] * 255)})`;
+    }
+
+    /** Get the CSS string representing rgba
+     * @returns {string}
+     */
+    rgba()
+    {
+        return `rgba(${Math.trunc(this.channels[0] * 255)}, ${Math.trunc(this.channels[1] * 255)}, ${Math.trunc(this.channels[2] * 255)}, ${this.channels[3]})`;
+    }
+
+    static from_rgb(r, g, b)
+    {
+        return new Color(r / 255, g / 255, b / 255);
+    }
+
+    static from_rgba(r, g, b)
+    {
+        return new Color(r / 255, g / 255, b / 255, a);
+    }
+}
+
+/**
+ * Interpolate between two colors
+ * @param {Color} a 
+ * @param {Color} b 
+ * @param {number} p 
+ */
+function interpolate(a, b, p)
+{
+
+}
\ No newline at end of file
diff --git a/scripts/gui-common/widgets.js b/scripts/gui-common/widgets.js
index e365f56..88f1db8 100644
--- a/scripts/gui-common/widgets.js
+++ b/scripts/gui-common/widgets.js
@@ -1,37 +1,524 @@
 /**
- * @typedef {{
- *  el: HTMLElement;
- *  value: T;
- *  set: (value: T) => void;
- *  get: () => T}} Widget<T>
- * @template {any} T
+ * @typedef {"button" | "toggle" | "slider" | "checkbox" | "color-wheel" | "color-temp" | "color-light" | "thermostat" | "sel-button"} WidgetType
  */
 
-/**
- * @typedef {"button" | "toggle" | "slider" | "checkbox"} WidgetType
- */
+/** @template {*} T */
+class Widget extends EventTarget{
+    /** @type {T} */
+    #value = null;
 
-/**
- * @template {any} T
- * @param {WidgetType} type
- * @returns {Widget<T>}
- */
-function Widget (type = "button") {
-    /** @type {Widget<Number>} */
-    let out = {
-        el: document.createElement("div"),
-        value: 0,
-        set: (e) => {value = e},
-        get: () => this.value,
-    };
+    /** @type {HTMLElement} */
+    element = null;
+
+    /** @type {boolean} */
+    inactive = false;
+    
+    /**
+     * Construct a new widget
+     */
+    constructor ()
+    {
+        super();
+        this.element = document.createElement("div");
+        this.element.classList.add("widget");
+
+        this.element.addEventListener("mousedown", this.#emitMouseEvent.bind(this));
+        this.element.addEventListener("mouseup", this.#emitMouseEvent.bind(this));
+        this.element.addEventListener("mousemove", this.#emitMouseEvent.bind(this));
+        this.element.addEventListener("mouseleave", this.#emitMouseEvent.bind(this));
+        this.element.addEventListener("mouseenter", this.#emitMouseEvent.bind(this));
+
+        this.element.addEventListener("touchstart", this.#emitTouchEvent.bind(this));
+        this.element.addEventListener("touchend", this.#emitTouchEvent.bind(this));
+        this.element.addEventListener("touchmove", this.#emitTouchEvent.bind(this));
+        this.element.addEventListener("touchcancel", this.#emitTouchEvent.bind(this));
+
+        this.element.addEventListener("contextmenu", this.#emitContextEvent.bind(this));
+    }
+
+    /** @returns {T} */
+    get()
+    {
+        return this.#value;
+    }
+
+    /** @param {T} v */
+    set(v)
+    {
+        this.#value = v;
+        this.#emitChangeEvent();
+    }
+
+    /** @param {boolean} i */
+    setInactive (i)
+    {
+        this.element.classList.toggle("inactive", i);
+        this.inactive = i;
+    }
+
+    /** @returns {boolean} */
+    getInactive ()
+    {
+        return this.inactive;
+    }
+
+    #emitChangeEvent()
+    {
+        this.dispatchEvent(new CustomEvent("change", {detail: this}));
+    }
+
+    /** @param {MouseEvent} event */
+    #emitMouseEvent(event)
+    {
+        if (this.inactive)
+            this.dispatchEvent(new CustomEvent("inactive", {detail: {widget: this, event: new MouseEvent(event.type, event)}}));
+        else
+            this.dispatchEvent(new MouseEvent(event.type, event));
+    }
+
+    /** @param {TouchEvent} event */
+    #emitTouchEvent(event)
+    {
+        if (this.inactive)
+            this.dispatchEvent(new CustomEvent("inactive", {detail: {widget: this, event: new TouchEvent(event.type, event)}}));
+        else
+            this.dispatchEvent(new TouchEvent(event.type, event));
+    }
+
+    /** @param {Event} event */
+    #emitContextEvent(event)
+    {
+        if (this.inactive)
+            this.dispatchEvent(new CustomEvent("inactive", {detail: {widget: this, event: new Event(event.type, event)}}));
+        else
+            this.dispatchEvent(new Event(event.type, event));
+        event.preventDefault();
+    }
+
+    handleClick() {}
+}
+
+/** @typedef {Widget<boolean>} WidgetButton */
+class WidgetButton extends Widget
+{
+    /** @type {number} */
+    pressing = 0;
+
+    gone = 0;
+
+    #bound = null;
+
+    constructor ()
+    {
+        super();
+        this.element.classList.add("button");
+        this.addEventListener("mousedown", this.#press);
+        this.addEventListener("mouseup", this.#unpress);
+        this.addEventListener("mouseleave", this.#leave);
+        this.addEventListener("mouseenter", this.#enter);
+        this.#bound = this.#unpress.bind(this);
+    }
+
+    /** @param {MouseEvent} event */
+    #press(event)
+    {
+        this.pressing |= (1 << event.button);
+        this.set(true);
+    }
+
+    #unpress (event)
+    {
+        if (((1 << event.button) & this.pressing) == 0)
+            return;
+
+        this.pressing -= (1 << event.button);
+        if (this.pressing == 0)
+        {
+            this.set(false);
+            if (this.gone)
+            {
+                this.gone = 0;
+                window.removeEventListener("mouseup", this.#bound);
+            }
+        }
+    }
+
+    /** @param {MouseEvent} event */
+    #leave(event)
+    {
+        if (this.pressing == 0)
+            return;
+        this.gone = 1;
+        window.addEventListener("mouseup", this.#bound);
+    }
+
+    /** @param {MouseEvent} event */
+    #enter(event)
+    {
+        if (this.primed == 0)
+            return;
+        this.gone = 0;
+        window.removeEventListener("mouseup", this.#bound);
+    }
+}
+
+/** @typedef {Widget<boolean>} WidgetToggle */
+class WidgetToggle extends Widget
+{
+    /** @type {number} */
+    primed = 0;
+
+    /** @type {number} */
+    gone = 0;
+
+    #bound = null;
+
+    constructor ()
+    {
+        super();
+        this.element.classList.add("button");
+        this.element.classList.add("toggle");
+        this.addEventListener("mousedown", this.#prime);
+        this.addEventListener("mouseup", this.#toggle);
+        this.addEventListener("mouseleave", this.#leave);
+        this.addEventListener("mouseenter", this.#enter);
+        this.#bound = this.#toggle.bind(this);
+    }
+
+    /** @param {MouseEvent} event */
+    #prime(event)
+    {
+        this.primed |= (1 << event.button);
+    }
+
+    /** @param {MouseEvent} event */
+    #toggle(event)
+    {
+        if (this.gone)
+        {
+            this.primed = 0;
+            this.gone = 0;
+            window.removeEventListener("mouseup", this.#bound);
+        }
+        if (((1 << event.button) & this.primed) == 0)
+            return;
+        this.set(!this.get());
+        this.element.classList.toggle("active", this.get());
+        this.primed -= (1 << event.button);
+    }
+
+    /** @param {MouseEvent} event */
+    #leave(event)
+    {
+        if (this.primed == 0)
+            return;
+        this.gone = 1;
+        window.addEventListener("mouseup", this.#bound);
+    }
+
+    /** @param {MouseEvent} event */
+    #enter(event)
+    {
+        if (this.primed == 0)
+            return;
+        this.gone = 0;
+        window.removeEventListener("mouseup", this.#bound);
+    }
+}
+
+class WidgetCheckbox extends WidgetToggle {
+
+    constructor()
+    {
+        super();
+        this.element.classList.remove("toggle");
+        this.element.classList.add("checkbox");
+    }
+}
+
+class WidgetSlider extends Widget
+{
+    /** @type {number} */
+    #primed = 0;
+
+    /** @type {number} */
+    #gone = 0;
+
+    #boundUp = null;
+    #boundMove = null;
+
+    /** @type {HTMLElement} */
+    #detail = null;
+    /** @type {(e: HTMLElement, v: number, p: number) => void} */
+    #detailUpdater = null;
+
+    /** @type {number} */
+    #tmpNum = 0;
+
+    /** @type {number} */
+    #max;
+    /** @type {number} */
+    #min;
+    /** @type {number} */
+    #step;
+    /** @type {number} */
+    #precision;
+    /** @type {boolean} */
+    #percent;
+
+
+    /**
+     * Constructor
+     * @param {number} [max] Value the slider represents at maximum
+     * @param {number} [min] Value the slider represents at minimum
+     * @param {number} [step] Step amount
+     * @param {number} [trunc] Decimal places to keep
+     * @param {boolean} [percent] Whether to show a percentage instead of the raw number
+     */
+    constructor (max = 10, min = 1, step = 0.1, trunc = 1, percent = false)
+    {
+        super();
+        this.element.classList.add("slider");
+        let fill = document.createElement("div");
+        fill.classList.add("fill");
+        this.element.appendChild(fill);
+
+        this.#detail = document.createElement("div");
+        this.#detail.classList.add("detail");
+        this.element.appendChild(this.#detail);
+        
+        this.addEventListener("mousedown", this.#press);
+        this.addEventListener("mousemove", this.#move);
+        this.addEventListener("mouseup", this.#unpress);
+        this.addEventListener("mouseleave", this.#leave);
+        this.addEventListener("mouseenter", this.#enter);
+        this.addEventListener("change", this.#change);
+        
+        this.#boundUp = this.#unpress.bind(this);
+        this.#boundMove = this.#move.bind(this);
+        this.#max = max;
+        this.#min = min;
+        this.#step = step;
+        this.#precision = trunc;
+        this.#percent = percent;
+
+        this.#detailUpdater = this.update_detail.bind(this);
+    }
+
+    /** @param {MouseEvent} event */
+    #press(event)
+    {
+        this.#primed |= (1 << event.button);
+        this.#move(event);
+    }
+
+    /** @param {MouseEvent} event */
+    #move(event)
+    {
+        if (this.#primed == 0)
+            return;
+
+        let rect = this.element.getBoundingClientRect();
+        let top = 0, bot = 0, point = 0;
+        if (this.element.classList.contains("h"))
+        {
+            top = rect.right;
+            bot = rect.left;
+            point = event.clientX;
+        }
+        else
+        {
+            top = rect.bottom;
+            bot = rect.top;
+            point = top - event.clientY + bot;
+        }
+
+        if (point < bot && this.#tmpNum != this.#min)
+            this.#tmpNum = this.#min;
+        else if (point > top && this.#tmpNum != this.#max)
+            this.#tmpNum = this.#max;
+        else if (bot < point && point < top)
+        {
+            let v = ((point - bot) / (top - bot)) * (this.#max - this.#min) + this.#min;
+            let r = v % this.#step;
+            v -= v % this.#step;
+            if (r >= this.#step / 2)
+                v += this.#step;
+            this.#tmpNum = v;
+        }
+        this.#update_ui();
+    }
+
+    #change ()
+    {
+        this.#tmpNum = this.get();
+        this.#update_ui();
+    }
+
+    #update_ui()
+    {
+        if (this.#tmpNum < this.#min || this.#max < this.#tmpNum)
+            this.#tmpNum = Math.min(Math.max(this.#tmpNum, this.#min), this.#max);
+        let percent = (this.#tmpNum - this.#min) / (this.#max - this.#min);
+
+        this.#detailUpdater(this.#detail, this.#tmpNum, percent, this.#percent);
+        this.element.style.setProperty("--percent", percent);
+    }
+
+    /** @param {MouseEvent} event */
+    #unpress(event)
+    {
+        if (((1 << event.button) & this.#primed) == 0)
+            return;
+        this.#primed -= (1 << event.button);
+        if (this.#primed == 0)
+        {
+            this.set(this.#tmpNum);
+            if (this.#gone)
+            {
+                this.#gone = 0;
+                window.removeEventListener("mouseup", this.#boundUp);
+                window.removeEventListener("mousemove", this.#boundMove);
+            }
+        }
+    }
 
-    out.el.classList = ["widget", type];
-    if (type == "checkbox" || type == "toggle")
+    /** @param {MouseEvent} event */
+    #leave(event)
     {
-        out.el.classList.add("button");
+        if (this.#primed == 0)
+            return;
+        this.#gone = 1;
+        window.addEventListener("mouseup", this.#boundUp);
+        window.addEventListener("mousemove", this.#boundMove);
     }
 
-    return out;
+    /** @param {MouseEvent} event */
+    #enter(event)
+    {
+        if (this.#primed == 0)
+            return;
+        this.#gone = 0;
+        window.removeEventListener("mouseup", this.#boundUp);
+        window.removeEventListener("mousemove", this.#boundMove);
+    }
+
+    update_detail(el, val, percent)
+    {
+        if (this.#percent)
+            el.innerText = `${Math.trunc(percent * 100)}%`;
+        else
+            el.innerText = `${Math.trunc(Math.pow(10, this.#precision) * val) / Math.pow(10, this.#precision)}`
+    }
+
+    setDetailUpdater(updater)
+    {
+        this.#detailUpdater = updater;
+    }
+
+    /** @param {number} m */
+    setMin(m)
+    {
+        this.#min = m;
+        this.#update_ui();
+    }
+
+    /** @param {number} m */
+    setMax(m)
+    {
+        this.#max = m;
+        this.#update_ui();
+    }
+
+    /** @param {number} s */
+    setStep(s)
+    {
+        this.#step = s;
+        this.#update_ui();
+    }
+}
+
+class WidgetColorTemp extends WidgetSlider 
+{
+    /** @type {Color} */
+    ORANGE = Color.from_rgb(250, 160, 100);
+    /** @type {Color} */
+    WHITE = new Color(1, 1, 1);
+    /** @type {Color} */
+    BLUE = Color.from_rgb(190, 200, 255);
+
+    constructor ()
+    {
+        super(6000, 2700, 100);
+        this.element.classList.replace("slider", "color-temp");
+        
+        let fills = this.element.getElementsByClassName("fill");
+        for(let f of fills)
+        {
+            f.remove();
+        }
+
+        this.setDetailUpdater(this.update_detail.bind(this));
+        this.set(2700);
+    }
+
+    /**
+     * Update the detail for the color temp slider
+     * @param {HTMLElement} el 
+     * @param {number} val 
+     * @param {number} percent 
+     */
+    update_detail(el, val, percent)
+    {
+        let out = null;
+        if (percent < 0.7)
+            out = this.ORANGE.interpolate(this.WHITE, percent / 0.7);
+        else
+            out = this.WHITE.interpolate(this.BLUE, (percent - 0.7) / 0.3);
+        el.style.setProperty("--detail", out.rgb());
+    }
+}
+
+class WidgetColorLight extends WidgetSlider 
+{
+    /** @type {Color} */
+    WHITE = new Color(1, 1, 1);
+    /** @type {Color} */
+    BLACK = new Color(0, 0, 0);
+
+    constructor ()
+    {
+        super(1, 0, 0.01, 0, 1);
+        this.element.classList.replace("slider", "color-light");
+        
+        let fills = this.element.getElementsByClassName("fill");
+        for(let f of fills)
+        {
+            f.remove();
+        }
+
+        this.setDetailUpdater(this.update_detail.bind(this));
+        this.set(0);
+    }
+
+    /**
+     * Update the detail for the color temp slider
+     * @param {HTMLElement} el 
+     * @param {number} val 
+     * @param {number} percent 
+     */
+    update_detail(el, val, percent)
+    {
+        let out = this.BLACK.interpolate(this.WHITE, percent);
+        el.style.setProperty("--detail", out.rgb());
+    }
 }
 
-export { Widget };
\ No newline at end of file
+/** @typedef {Widget<number>} WidgetSlider */
+/** @typedef {Widget<string>} WidgetColorWheel */
+/** @typedef {Widget<number>} WidgetColorTemp */
+/** @typedef {Widget<number>} WidgetColorLight */
+/** @typedef {Widget<number> & {getGague: () => number, setGague: (value: number) => void}} WidgetThermostat */
+/** @typedef {Widget<any> & {addSelection: (name: string, value: any) => void, setSelection: (name: string) => boolean, removeSelection: (name: string) => void, getSelection: () => string}} WidgetSelectButton */
+
+// export { Widget };
\ No newline at end of file
diff --git a/scripts/main.js b/scripts/main.js
index ce0dfe4..ac6dbe9 100644
--- a/scripts/main.js
+++ b/scripts/main.js
@@ -1,3 +1,35 @@
-import * as w from './gui-common/widgets.js';
+class Client {
 
-let a = w.Widget();
+    
+
+    /**
+     * @param {HTMLElement} content The base element where page content is placed
+     */
+    constructor (content)
+    {
+        /** @type {WidgetToggle} */
+        this.toggle = new WidgetCheckbox();
+        content.appendChild(this.toggle.element);
+        this.slider = new WidgetSlider();
+        content.appendChild(this.slider.element);
+        this.temp = new WidgetColorTemp();
+        content.appendChild(this.temp.element);
+        this.light = new WidgetColorLight();
+        content.appendChild(this.light.element);
+        // content.appendChild(Widget("button").el);
+        // content.appendChild(Widget("checkbox").el);
+        // content.appendChild(Widget("slider").el);
+        // content.appendChild(Widget("color-wheel").el);
+        // content.appendChild(Widget("color-temp").el);
+        // content.appendChild(Widget("color-light").el);
+        // content.appendChild(Widget("thermostat").el);
+        // content.appendChild(Widget("sel-button").el);
+        this.content = content;
+    }
+}
+
+let contents = document.getElementsByTagName("content");
+if (contents.length > 0)
+    OSmClient = new Client(contents[0]);
+else
+    console.error("Unable to find content tag, OSm stopping client.");
diff --git a/styles/widgets.css b/styles/widgets.css
index 8503364..5a29507 100644
--- a/styles/widgets.css
+++ b/styles/widgets.css
@@ -26,6 +26,8 @@ input {
 
     cursor:pointer;
     position: relative;
+
+    user-select: none;
 }
 
 .widget:hover {
@@ -127,7 +129,17 @@ input {
     width: calc(3 * var(--base-unit));
 }
 
-.slider::before {
+.slider > .fill {
+    overflow: hidden;
+    width: 100%;
+    height: 100%;
+    border-radius: 10px;
+    position: absolute;
+    top: 0;
+    left: 0;
+}
+
+.slider > .fill::before {
     border-radius: 10px;
     position: absolute;
     bottom: 0;
@@ -136,21 +148,19 @@ input {
     width: 100%;
     background-color: var(--w-sl-fill);
 
-    transition-duration: 0.15s;
-
     content: '';
 }
 
-.slider.h::before {
-    width: calc(100% * var(--fill-percent));
+.slider.h > .fill::before {
+    width: calc(100% * var(--percent));
     height: 100%;
 }
 
-.slider:hover::before {
+.slider:hover > .fill::before {
     background-color: var(--w-sl-fill-hover);
 }
 
-.slider:active::before {
+.slider:active > .fill::before {
     background-color: var(--w-sl-fill-active);
 }
 
@@ -175,7 +185,7 @@ input {
     border-bottom: var(--w-sl-dots);
 }
 
-.slider > .detail {
+.slider > .detail, .color-light > .detail, .color-temp > .detail {
     display: block;
     position: absolute;
 
@@ -190,15 +200,18 @@ input {
     z-index: 2;
 
     font-weight: bold;
-    min-width: var(--base-unit);
+    width: calc(1.2 * var(--base-unit));
     height: var(--base-unit);
-    transition-duration: 0.15s;
     pointer-events: none;
 
     align-content: center;
     text-align: center;
-    padding-left: 3px;
-    padding-right: 3px;
+    padding: 2px;
+}
+
+.slider.h > .detail, .color-light.h > .detail, .color-temp.h > .detail {
+    top: calc(100% + 20px);
+    left: calc(-1.2 * var(--base-unit) / 2 + 100% * var(--percent));
 }
 
 .slider:active > .detail {
@@ -206,7 +219,11 @@ input {
     color: rgba(255, 255, 255, 1);
 }
 
-.slider.inactive > .detail {
+.color-light:active > .detail, .color-temp:active > .detail {
+    background-color: var(--detail);
+}
+
+.inactive > .detail {
     display: none;
 }
 
@@ -359,60 +376,37 @@ input {
     overflow: unset;
 }
 
-.color-temp::after {
-    content: '';
-    width: calc(100% + 8px);
-    height: 14px;
-    border: 5px solid #444;
-    position: absolute;
-    bottom: calc(100% * var(--percent) - 7px);
-    left: -4px;
-
-    box-sizing: border-box;
-
-    border-radius: 7px;
-    transition-duration: 0.15s;
-}
-
-.color-temp:hover::after {
-    border-color: #888;
-}
-
-.color-temp.inactive::after {
-    border-color: rgb(68, 68, 68, 0);
-}
-
 .color-light {
-    height: var(--base-unit);
-    width: calc(3 * var(--base-unit));
-    background: linear-gradient(to left, white, black);
+    width: var(--base-unit);
+    height: calc(3 * var(--base-unit));
+    background: linear-gradient(white, black);
     --side-color: #aaa;
     overflow: unset;
 }
 
-.color-light::after {
+.color-temp::after, .color-light::after {
     content: '';
-    height: calc(100% + 8px);
-    width: 14px;
+    width: calc(100% + 8px);
+    height: 14px;
     border: 5px solid #444;
     position: absolute;
-    left: calc(100% * var(--percent) - 7px);
-    top: -4px;
+    bottom: calc(100% * var(--percent) - 7px);
+    left: -4px;
 
     box-sizing: border-box;
 
     border-radius: 7px;
-    transition-duration: 0.15s;
 }
 
-.color-light:hover::after {
+.color-temp:hover::after, .color-light:hover::after {
     border-color: #888;
 }
 
-.color-light.inactive::after {
+.color-temp.inactive::after, .color-light.inactive::after {
     border-color: rgb(68, 68, 68, 0);
 }
 
+
 /**
     Thermostat
 */
@@ -425,7 +419,8 @@ input {
     height: calc(3 * var(--base-unit));
 }
 
-.thermostat > .arch {
+.thermostat > arch {
+    display: block;
     position: absolute;
 
     height: 100px;
@@ -439,7 +434,7 @@ input {
     background-color: #222;
 }
 
-.thermostat > .arch::after {
+.thermostat > arch::after {
     content: '';
     display: block;
     height: 100px;
@@ -450,7 +445,8 @@ input {
     transition-duration: 0.5s;
 }
 
-.thermostat > .gague {
+.thermostat > gague {
+    display: block;
     position: absolute;
     bottom: calc(-13px + ((100% - 100px) / 2) + 90px);
     left: calc(-13px + 50%);
@@ -474,7 +470,8 @@ input {
     font-weight: bold;
 }
 
-.thermostat > .temp {
+.thermostat > temp {
+    display: block;
     position: absolute;
     bottom: calc((100% - 100px) / 2);
     left: calc(50% - 65px);
@@ -487,7 +484,7 @@ input {
     Button Select
 */
 
-.select {
+.sel-button {
     display: inline-flex;
     max-height: calc(2 *  var(--base-unit));
     max-width: calc(8 *  var(--base-unit));
@@ -497,7 +494,7 @@ input {
     overflow-x: auto;
 }
 
-.select.h {
+.sel-button.h {
     flex-direction: column;
     height: 100%;
     overflow-y: auto;
@@ -506,7 +503,7 @@ input {
     overflow-x: hidden;
 }
 
-.select > div {
+.sel-button > div {
     margin: 5px 5px;
     padding: 7px;
 
@@ -521,13 +518,13 @@ input {
     width: fit-content;
 }
 
-.select.h > div {
+.sel-button.h > div {
     height: auto;
     width: calc(100% - 12px);
 }
 
 
-.select > div[selected] {
+.sel-button > div[selected] {
     --w-bg: var(--w-sel-button-selected);
     --w-bg-hover: var(--w-sel-button-selected-hover);
     --w-bg-active: var(--w-sel-button-selected-active);
-- 
cgit v1.2.3