Skip to content

Instantly share code, notes, and snippets.

@fgouin2014
Created July 11, 2024 02:00
Show Gist options
  • Save fgouin2014/b96953dc27811cf69ba4e0dc5d65df6c to your computer and use it in GitHub Desktop.
Save fgouin2014/b96953dc27811cf69ba4e0dc5d65df6c to your computer and use it in GitHub Desktop.
LightFluidRC
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multi-Bulb Smart Light Control</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jscolor/2.4.5/jscolor.min.js"></script>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Multi-Bulb Smart Light Control</h1>
<div class="quick-access"></div>
<div id="masterControlSection" class="control-section">
<h2>Control Panel</h2>
<div class="control-layout">
<div class="switch-column">
<label class="switch vertical">
<input type="checkbox" id="masterToggle">
<span class="slider round"></span>
</label>
<svg class="bulb-icon" viewBox="0 0 24 24">
<path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z" fill="#000000"/>
<path class="bulb-inner" d="M11 4c-2.76 0-5 2.24-5 5c0 1.63.8 3.16 2.15 4.1l.85.6V16h4v-2.3l.85-.6C15.2 12.16 16 10.63 16 9c0-2.76-2.24-5-5-5z" fill="#FFEB3B"/>
</svg>
</div>
<div class="options-column">
<div class="control-item">
<label for="masterBrightness">Brightness:</label>
<input type="range" id="masterBrightness" min="0" max="100" value="50">
<span id="masterBrightnessValue">50%</span>
</div>
<div class="control-item">
<label for="masterColor">Color:</label>
<input type="text" id="masterColor" data-jscolor="" value="FFFFFF">
</div>
<div class="color-presets" id="masterColorPresets"></div>
</div>
</div>
</div>
<div id="bulbControls"></div>
<div class="settings-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
</div>
<script src="bulb-controls.js"></script>
<script src="color-presets.js"></script>
</body>
</html>
const bulbCount = 5;
const bulbControls = document.getElementById("bulbControls");
const masterToggle = document.getElementById("masterToggle");
const masterBrightness = document.getElementById("masterBrightness");
const masterBrightnessValue = document.getElementById("masterBrightnessValue");
const masterColor = document.getElementById("masterColor");
const masterBulbIcon = document.querySelector("#masterControlSection .bulb-icon");
const quickAccess = document.querySelector(".quick-access");
function createBulbControl(id) {
const bulbDiv = document.createElement("div");
bulbDiv.className = "bulb-control control-section";
bulbDiv.innerHTML = `
<h2>Bulb ${id}</h2>
<div class="control-layout">
<div class="switch-column">
<label class="switch vertical">
<input type="checkbox" class="bulbToggle" data-id="${id}">
<span class="slider round"></span>
</label>
<svg class="bulb-icon" viewBox="0 0 24 24">
<path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z" fill="#000000"/>
<path class="bulb-inner" d="M11 4c-2.76 0-5 2.24-5 5c0 1.63.8 3.16 2.15 4.1l.85.6V16h4v-2.3l.85-.6C15.2 12.16 16 10.63 16 9c0-2.76-2.24-5-5-5z" fill="#FFEB3B"/>
</svg>
</div>
<div class="options-column">
<div class="control-item">
<label for="brightness${id}">Brightness:</label>
<input type="range" id="brightness${id}" class="brightnessSlider" data-id="${id}" min="0" max="100" value="50">
<span class="brightnessValue" data-id="${id}">50%</span>
</div>
<div class="control-item">
<label for="color${id}">Color:</label>
<input type="text" id="color${id}" class="colorPicker" data-jscolor="" data-id="${id}" value="FFFFFF">
</div>
<div class="color-presets" id="colorPresets${id}"></div>
</div>
</div>
`;
bulbControls.appendChild(bulbDiv);
}
function createQuickAccessSwitch(id) {
const switchDiv = document.createElement("div");
switchDiv.className = "quick-access-switch";
switchDiv.innerHTML = `
<label class="switch vertical">
<input type="checkbox" class="quickBulbToggle" data-id="${id}">
<span class="slider round"></span>
</label>
`;
quickAccess.appendChild(switchDiv);
}
function initializeBulbs() {
for (let i = 1; i <= bulbCount; i++) {
createBulbControl(i);
createQuickAccessSwitch(i);
}
jscolor.install();
}
function updateBulbVisualState(id, isOn) {
const bulbToggle = document.querySelector(`.bulbToggle[data-id="${id}"]`);
const quickBulbToggle = document.querySelector(`.quickBulbToggle[data-id="${id}"]`);
const bulbIcon = document.querySelector(`.bulb-control:nth-child(${id}) .bulb-icon`);
const switchLabel = bulbToggle.closest(".switch");
const quickSwitchLabel = quickBulbToggle.closest(".switch");
if (masterToggle.checked) {
bulbIcon.classList.toggle("on", isOn);
bulbIcon.classList.toggle("off", !isOn);
switchLabel.classList.remove("disabled");
quickSwitchLabel.classList.remove("disabled");
} else {
bulbIcon.classList.remove("on");
bulbIcon.classList.add("off");
switchLabel.classList.add("disabled");
quickSwitchLabel.classList.add("disabled");
}
bulbToggle.checked = isOn;
quickBulbToggle.checked = isOn;
}
function updateAllBulbsVisualState(masterIsOn) {
document.querySelectorAll(".bulbToggle").forEach(toggle => {
const id = toggle.dataset.id;
updateBulbVisualState(id, masterIsOn && toggle.checked);
});
}
function updateMasterBulbIcon(isOn) {
masterBulbIcon.classList.toggle("on", isOn);
masterBulbIcon.classList.toggle("off", !isOn);
}
function updateBulbInnerColor(id, color) {
const bulbInner = document.querySelector(`.bulb-control:nth-child(${id}) .bulb-inner`);
if (bulbInner) {
bulbInner.style.fill = `#${color}`;
}
}
function syncMasterBrightness(brightness) {
document.querySelectorAll(".brightnessSlider").forEach(slider => {
slider.value = brightness;
const id = slider.dataset.id;
document.querySelector(`.brightnessValue[data-id="${id}"]`).textContent = `${brightness}%`;
});
}
function syncMasterColor(color) {
document.querySelectorAll(".colorPicker").forEach(picker => {
if (picker.jscolor) {
picker.jscolor.fromString(color);
}
updateBulbInnerColor(picker.dataset.id, color);
});
}
function updateMasterToggle() {
const anyBulbOn = Array.from(document.querySelectorAll(".bulbToggle")).some(toggle => toggle.checked);
masterToggle.checked = anyBulbOn;
updateMasterBulbIcon(anyBulbOn);
}
function saveSettings() {
const settings = {
masterToggle: masterToggle.checked,
masterBrightness: masterBrightness.value,
masterColor: masterColor.value,
bulbs: {}
};
document.querySelectorAll(".bulb-control").forEach((bulb, index) => {
const id = index + 1;
settings.bulbs[id] = {
isOn: bulb.querySelector(".bulbToggle").checked,
brightness: bulb.querySelector(".brightnessSlider").value,
color: bulb.querySelector(".colorPicker").value
};
});
localStorage.setItem("bulbSettings", JSON.stringify(settings));
}
function loadSettings() {
const settings = JSON.parse(localStorage.getItem("bulbSettings"));
if (settings) {
masterToggle.checked = settings.masterToggle;
masterBrightness.value = settings.masterBrightness;
masterBrightnessValue.textContent = `${settings.masterBrightness}%`;
masterColor.jscolor.fromString(settings.masterColor);
Object.entries(settings.bulbs).forEach(([id, bulbSettings]) => {
const bulbControl = document.querySelector(`.bulb-control:nth-child(${id})`);
if (bulbControl) {
const bulbToggle = bulbControl.querySelector(".bulbToggle");
bulbToggle.checked = bulbSettings.isOn;
updateBulbVisualState(id, settings.masterToggle && bulbSettings.isOn);
const brightnessSlider = bulbControl.querySelector(".brightnessSlider");
brightnessSlider.value = bulbSettings.brightness;
bulbControl.querySelector(".brightnessValue").textContent = `${bulbSettings.brightness}%`;
const colorPicker = bulbControl.querySelector(".colorPicker");
colorPicker.jscolor.fromString(bulbSettings.color);
updateBulbInnerColor(id, bulbSettings.color);
}
});
updateMasterToggle();
updateAllBulbsVisualState(settings.masterToggle);
}
}
document.addEventListener("DOMContentLoaded", function () {
initializeBulbs();
loadSettings();
document.querySelectorAll(".bulbToggle, .quickBulbToggle").forEach(toggle => {
toggle.addEventListener("change", (e) => {
const id = e.target.dataset.id;
const isOn = e.target.checked;
console.log(`Toggle bulb ${id} ${isOn ? "on" : "off"}`);
updateBulbVisualState(id, isOn);
updateMasterToggle();
saveSettings();
});
});
document.querySelectorAll(".brightnessSlider").forEach(slider => {
slider.addEventListener("input", (e) => {
const id = e.target.dataset.id;
const brightness = e.target.value;
document.querySelector(`.brightnessValue[data-id="${id}"]`).textContent = `${brightness}%`;
console.log(`Set bulb ${id} brightness to ${brightness}%`);
saveSettings();
});
});
document.querySelectorAll(".colorPicker").forEach(picker => {
picker.addEventListener("change", (e) => {
const id = e.target.dataset.id;
const color = e.target.jscolor.toString();
console.log(`Set bulb ${id} color to #${color}`);
updateBulbInnerColor(id, color);
saveSettings();
});
});
masterToggle.addEventListener("change", (e) => {
const isOn = e.target.checked;
console.log(`Master toggle ${isOn ? "on" : "off"}`);
updateAllBulbsVisualState(isOn);
updateMasterBulbIcon(isOn);
saveSettings();
});
masterBrightness.addEventListener("input", (e) => {
const brightness = e.target.value;
masterBrightnessValue.textContent = `${brightness}%`;
console.log(`Set all bulbs brightness to ${brightness}%`);
syncMasterBrightness(brightness);
saveSettings();
});
masterColor.addEventListener("change", (e) => {
const color = e.target.jscolor.toString();
console.log(`Set all bulbs color to #${color}`);
syncMasterColor(color);
saveSettings();
});
});
const colorPresets = {
"White Tones": [
{ name: "Cool White", color: "F0F8FF" },
{ name: "Daylight", color: "F5F5F5" },
{ name: "Neutral White", color: "F5DEB3" },
{ name: "Warm White", color: "FFD700" },
{ name: "Candlelight", color: "FFA07A" }
],
"Basic Colors": [
{ name: "Red", color: "FF0000" },
{ name: "Green", color: "00FF00" },
{ name: "Blue", color: "0000FF" },
{ name: "Yellow", color: "FFFF00" },
{ name: "Magenta", color: "FF00FF" }
],
"Pastel Colors": [
{ name: "Pastel Pink", color: "FFB6C1" },
{ name: "Pastel Blue", color: "ADD8E6" },
{ name: "Pastel Green", color: "98FB98" },
{ name: "Pastel Yellow", color: "FFFACD" },
{ name: "Pastel Lavender", color: "E6E6FA" }
]
};
function createColorPresets(container, colorPickerId) {
const presetsToggle = document.createElement("div");
presetsToggle.className = "color-presets-toggle";
presetsToggle.textContent = "Color Presets";
container.appendChild(presetsToggle);
const presetsContent = document.createElement("div");
presetsContent.className = "color-presets-content";
Object.entries(colorPresets).forEach(([category, presets]) => {
const categoryDiv = document.createElement("div");
categoryDiv.className = "preset-category";
const labelDiv = document.createElement("div");
labelDiv.className = "preset-label";
labelDiv.textContent = category;
categoryDiv.appendChild(labelDiv);
const presetRow = document.createElement("div");
presetRow.className = "preset-row";
presets.forEach(preset => createPresetButton(preset, presetRow, colorPickerId));
categoryDiv.appendChild(presetRow);
presetsContent.appendChild(categoryDiv);
});
container.appendChild(presetsContent);
presetsToggle.addEventListener("click", () => {
presetsContent.style.display = presetsContent.style.display === "none" ? "block" : "none";
});
}
function createPresetButton(preset, container, colorPickerId) {
const presetElement = document.createElement("div");
presetElement.className = "color-preset";
presetElement.style.backgroundColor = `#${preset.color}`;
presetElement.title = preset.name;
presetElement.addEventListener("click", () => {
const colorPicker = document.getElementById(colorPickerId);
if (colorPicker.jscolor) {
colorPicker.jscolor.fromString(preset.color);
updateBulbColor(colorPickerId, preset.color);
}
});
container.appendChild(presetElement);
}
function updateBulbColor(colorPickerId, color) {
const bulbId = colorPickerId.replace("color", "");
console.log(`Set bulb ${bulbId} color to #${color}`);
if (colorPickerId === "masterColor") {
syncMasterColor(color);
}
saveSettings();
}
document.addEventListener("DOMContentLoaded", function () {
const masterColorPresets = document.getElementById("masterColorPresets");
createColorPresets(masterColorPresets, "masterColor");
document.querySelectorAll(".bulb-control").forEach((bulb, index) => {
const id = index + 1;
const colorPresets = bulb.querySelector(`#colorPresets${id}`);
createColorPresets(colorPresets, `color${id}`);
});
});
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f0f0f0;
}
h1, h2 {
text-align: center;
color: #333;
}
.control-section {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 20px;
margin-bottom: 20px;
}
.control-layout {
display: flex;
align-items: flex-start;
}
.switch-column {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 20px;
}
.options-column {
flex-grow: 1;
}
.control-item {
margin-bottom: 15px;
}
.control-item label {
display: block;
margin-bottom: 5px;
}
input[type="text"], input[type="range"] {
width: 100%;
height: 30px;
padding: 5px;
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
}
input[type="text"].jscolor {
padding-right: 30px;
}
.control-item input[type="text"] {
width: calc(100% - 40px);
}
.jscolor-picker {
z-index: 1000;
}
.color-presets {
position: relative;
}
.color-presets-toggle {
background-color: #f0f0f0;
border: 1px solid #ccc;
padding: 5px 10px;
cursor: pointer;
}
.color-presets-content {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: white;
border: 1px solid #ccc;
padding: 10px;
z-index: 10;
}
.color-presets:hover .color-presets-content {
display: block;
}
.preset-category {
margin-bottom: 10px;
}
.preset-label {
font-weight: bold;
margin-bottom: 5px;
color: #555;
}
.preset-row {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.color-preset {
width: calc(20% - 4px);
aspect-ratio: 1;
border-radius: 4px;
cursor: pointer;
transition: transform 0.2s;
}
.color-preset:hover {
transform: scale(1.1);
}
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.switch.vertical {
width: 34px;
height: 60px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.switch.vertical .slider {
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
.switch.vertical .slider:before {
left: 4px;
bottom: 4px;
}
input:checked + .slider {
background-color: #2196F3;
}
input:checked + .slider:before {
transform: translateX(26px);
}
.switch.vertical input:checked + .slider:before {
transform: translateY(-26px);
}
.switch.disabled .slider {
background-color: #ddd;
cursor: not-allowed;
}
.switch.disabled input:checked + .slider {
background-color: #999;
}
.bulb-icon {
width: 34px;
height: 34px;
transition: all 0.3s ease;
margin-top: 10px;
}
.bulb-icon .bulb-inner {
transition: fill 0.3s ease;
}
.bulb-icon.on .bulb-inner {
fill: #FFEB3B;
filter: drop-shadow(0 0 10px #FFEB3B);
}
.bulb-icon.off .bulb-inner {
fill: #D3D3D3;
}
input[type="range"] {
-webkit-appearance: none;
background: #d3d3d3;
outline: none;
opacity: 0.7;
transition: opacity .2s;
}
input[type="range"]:hover {
opacity: 1;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
border-radius: 50%;
background: #2196F3;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 25px;
height: 25px;
border-radius: 50%;
background: #2196F3;
cursor: pointer;
}
.quick-access {
display: flex;
justify-content: space-around;
margin-bottom: 20px;
}
.settings-icon {
position: fixed;
bottom: 20px;
right: 20px;
width: 40px;
height: 40px;
cursor: pointer;
}
.settings-icon svg {
width: 100%;
height: 100%;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment