CSS
/* ─── 03 Nexus — cool blue-gray smart home control panel ─────────── */
@import url('https://fonts.googleapis.com/css2?family=Syne:wght@400;500;700&family=Syne+Mono&display=swap');
.nm-nex {
--nm-nex-bg: #dce4ee;
--nm-nex-dark: #b8c4d2;
--nm-nex-light: #ffffff;
--nm-nex-text: #5a6880;
--nm-nex-text-dim: #96a4b8;
--nm-nex-on: #5b8def;
--nm-nex-warm: #f0a855;
--nm-nex-green: #5bb87e;
--nm-nex-red: #e07070;
--nm-nex-inset: #d4dce8;
position: relative;
width: 100%;
min-height: 720px;
background: var(--nm-nex-bg);
font-family: 'Syne', system-ui, sans-serif;
display: flex;
align-items: center;
justify-content: center;
padding: 32px 16px;
overflow: hidden;
box-sizing: border-box;
}
.nm-nex *,
.nm-nex *::before,
.nm-nex *::after { box-sizing: border-box; }
/* The inner card holds the neumorphic shadow */
.nm-nex .card {
position: relative;
width: 100%;
max-width: 440px;
background: var(--nm-nex-bg);
border-radius: 36px;
padding: 36px 34px 34px;
box-shadow:
14px 14px 30px var(--nm-nex-dark),
-12px -12px 26px var(--nm-nex-light);
}
/* Room pills */
.nm-nex .room-row {
display: flex;
gap: 8px;
margin-bottom: 26px;
overflow-x: auto;
scrollbar-width: none;
}
.nm-nex .room-row::-webkit-scrollbar { display: none; }
.nm-nex .room-pill {
flex-shrink: 0;
padding: 7px 16px;
border-radius: 20px;
border: none;
background: var(--nm-nex-bg);
font-family: 'Syne', system-ui, sans-serif;
font-size: 11px;
font-weight: 500;
letter-spacing: 0.5px;
color: var(--nm-nex-text-dim);
cursor: pointer;
box-shadow:
4px 4px 10px var(--nm-nex-dark),
-3px -3px 8px var(--nm-nex-light);
transition: all 0.2s;
}
.nm-nex .room-pill.active {
background: var(--nm-nex-on);
color: #fff;
box-shadow:
4px 4px 12px rgba(91, 141, 239, 0.35),
-2px -2px 6px rgba(255, 255, 255, 0.5),
inset 1px 1px 3px rgba(255, 255, 255, 0.2);
}
/* Temp dial */
.nm-nex .temp-section {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 22px;
background: var(--nm-nex-bg);
border-radius: 24px;
padding: 18px;
box-shadow:
inset 5px 5px 12px var(--nm-nex-dark),
inset -4px -4px 10px var(--nm-nex-light);
}
.nm-nex .dial-wrap {
position: relative;
width: 90px;
height: 90px;
flex-shrink: 0;
}
.nm-nex .dial-wrap svg {
width: 100%;
height: 100%;
transform: rotate(-220deg);
}
.nm-nex .dial-wrap svg .track {
fill: none;
stroke: var(--nm-nex-dark);
stroke-width: 5;
stroke-linecap: round;
opacity: 0.5;
}
.nm-nex .dial-wrap svg .fill {
fill: none;
stroke: url(#nm-nex-tempgrad);
stroke-width: 5;
stroke-linecap: round;
stroke-dasharray: 220;
stroke-dashoffset: 55;
transition: stroke-dashoffset 0.5s;
}
.nm-nex .dial-center {
position: absolute;
inset: 14px;
border-radius: 50%;
background: var(--nm-nex-bg);
box-shadow:
3px 3px 8px var(--nm-nex-dark),
-2px -2px 6px var(--nm-nex-light);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.nm-nex .dial-val {
font-family: 'Syne Mono', ui-monospace, monospace;
font-size: 18px;
color: var(--nm-nex-text);
line-height: 1;
}
.nm-nex .dial-unit {
font-size: 8px;
color: var(--nm-nex-text-dim);
letter-spacing: 1px;
}
.nm-nex .temp-info { flex: 1; }
.nm-nex .temp-label {
font-size: 12px;
font-weight: 700;
color: var(--nm-nex-text);
margin-bottom: 4px;
letter-spacing: 0.5px;
}
.nm-nex .temp-sub {
font-size: 10px;
color: var(--nm-nex-text-dim);
margin-bottom: 14px;
}
.nm-nex .temp-btns { display: flex; gap: 8px; }
.nm-nex .adj-btn {
width: 30px;
height: 30px;
border-radius: 50%;
border: none;
background: var(--nm-nex-bg);
cursor: pointer;
font-size: 16px;
color: var(--nm-nex-text);
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow:
3px 3px 7px var(--nm-nex-dark),
-2px -2px 5px var(--nm-nex-light);
transition: all 0.15s;
font-weight: 500;
font-family: inherit;
}
.nm-nex .adj-btn:active {
box-shadow:
inset 2px 2px 5px var(--nm-nex-dark),
inset -1px -1px 4px var(--nm-nex-light);
color: var(--nm-nex-on);
}
/* Device grid */
.nm-nex .devices-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
margin-bottom: 20px;
}
.nm-nex .device-card {
background: var(--nm-nex-bg);
border: 0;
border-radius: 20px;
padding: 18px 16px;
box-shadow:
6px 6px 14px var(--nm-nex-dark),
-5px -5px 12px var(--nm-nex-light);
cursor: pointer;
transition: all 0.2s;
position: relative;
overflow: hidden;
text-align: left;
font-family: inherit;
}
.nm-nex .device-card.active::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 3px;
border-radius: 20px 20px 0 0;
background: var(--device-color, var(--nm-nex-on));
}
.nm-nex .device-icon {
width: 36px;
height: 36px;
border-radius: 12px;
background: var(--nm-nex-bg);
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 12px;
font-size: 16px;
box-shadow:
3px 3px 8px var(--nm-nex-dark),
-2px -2px 6px var(--nm-nex-light);
transition: all 0.2s;
}
.nm-nex .device-card.active .device-icon {
background: var(--device-color, var(--nm-nex-on));
box-shadow:
2px 2px 6px rgba(0, 0, 0, 0.15),
inset 1px 1px 3px rgba(255, 255, 255, 0.25);
color: #fff;
}
.nm-nex .device-name {
font-size: 11px;
font-weight: 700;
color: var(--nm-nex-text);
margin-bottom: 2px;
letter-spacing: 0.3px;
}
.nm-nex .device-status {
font-size: 9px;
color: var(--nm-nex-text-dim);
letter-spacing: 0.5px;
font-family: 'Syne Mono', ui-monospace, monospace;
}
.nm-nex .device-card.active .device-status { color: var(--device-color, var(--nm-nex-on)); }
/* Toggle switch */
.nm-nex .toggle {
position: absolute;
top: 16px;
right: 14px;
width: 28px;
height: 16px;
border-radius: 8px;
background: var(--nm-nex-bg);
box-shadow:
inset 2px 2px 5px var(--nm-nex-dark),
inset -1px -1px 4px var(--nm-nex-light);
cursor: pointer;
transition: all 0.2s;
}
.nm-nex .toggle::after {
content: '';
position: absolute;
width: 11px;
height: 11px;
border-radius: 50%;
background: var(--nm-nex-text-dim);
top: 2.5px;
left: 3px;
transition: all 0.25s;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2);
}
.nm-nex .device-card.active .toggle {
background: var(--device-color, var(--nm-nex-on));
box-shadow:
inset 1px 1px 3px rgba(0, 0, 0, 0.25),
inset -1px -1px 2px rgba(255, 255, 255, 0.2);
}
.nm-nex .device-card.active .toggle::after {
left: calc(100% - 14px);
background: #fff;
}
/* Security row */
.nm-nex .security-row {
background: var(--nm-nex-bg);
border-radius: 18px;
padding: 16px 18px;
box-shadow:
inset 4px 4px 10px var(--nm-nex-dark),
inset -3px -3px 8px var(--nm-nex-light);
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.nm-nex .sec-left { display: flex; align-items: center; gap: 12px; }
.nm-nex .sec-icon {
width: 36px;
height: 36px;
border-radius: 12px;
background: var(--nm-nex-bg);
box-shadow:
3px 3px 7px var(--nm-nex-dark),
-2px -2px 5px var(--nm-nex-light);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: var(--nm-nex-green);
}
.nm-nex .sec-title {
font-size: 12px;
font-weight: 700;
color: var(--nm-nex-text);
margin-bottom: 2px;
}
.nm-nex .sec-sub {
font-size: 9px;
color: var(--nm-nex-green);
font-family: 'Syne Mono', ui-monospace, monospace;
letter-spacing: 0.5px;
}
.nm-nex .status-dots { display: flex; gap: 5px; }
.nm-nex .sdot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--nm-nex-bg);
box-shadow:
1px 1px 3px var(--nm-nex-dark),
-1px -1px 2px var(--nm-nex-light);
}
.nm-nex .sdot.green {
background: var(--nm-nex-green);
box-shadow: 0 0 6px rgba(91, 184, 126, 0.5);
}
.nm-nex .sdot.pulse { animation: nm-nex-dot 2s ease-in-out infinite; }
@keyframes nm-nex-dot {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
@media (prefers-reduced-motion: reduce) {
.nm-nex .sdot.pulse { animation: none; }
.nm-nex .dial-wrap svg .fill { transition: none; }
} JS
(() => {
const root = document.querySelector('.nm-nex');
if (!root) return;
const statusMap = {
light: { on: 'On · 70%', off: 'Off' },
air: { on: 'Running', off: 'Off' },
tv: { on: 'On', off: 'Off' },
speaker: { on: 'Playing', off: 'Off' },
};
root.querySelectorAll('.device-card').forEach(card => {
card.addEventListener('click', () => {
card.classList.toggle('active');
const key = card.dataset.nmNexDevice;
const status = card.querySelector('.device-status');
const labels = statusMap[key];
if (status && labels) {
status.textContent = card.classList.contains('active') ? labels.on : labels.off;
}
});
});
let temp = 22;
const tempVal = root.querySelector('#nm-nex-tempval');
const dialFill = root.querySelector('#nm-nex-dialfill');
root.querySelectorAll('[data-nm-nex-adj]').forEach(btn => {
btn.addEventListener('click', () => {
const delta = parseInt(btn.dataset.nmNexAdj, 10) || 0;
temp = Math.max(16, Math.min(30, temp + delta));
if (tempVal) tempVal.textContent = temp;
if (dialFill) {
const pct = (temp - 16) / 14;
dialFill.style.strokeDashoffset = String(220 - pct * 220);
}
});
});
root.querySelectorAll('.room-pill').forEach(p => {
p.addEventListener('click', () => {
root.querySelectorAll('.room-pill').forEach(o => {
o.classList.remove('active');
o.setAttribute('aria-selected', 'false');
});
p.classList.add('active');
p.setAttribute('aria-selected', 'true');
});
});
})();