A Pen by fgouin2014 on CodePen.
Created
July 11, 2024 02:00
-
-
Save fgouin2014/b96953dc27811cf69ba4e0dc5d65df6c to your computer and use it in GitHub Desktop.
LightFluidRC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}`); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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