13 CSS Neumorphism & Soft UI Designs
Nexus
A cool blue-gray light-neumorphic room control dashboard. Features a live arc-dial temperature control with +/− buttons, a 2×2 device card grid (each with inset toggle switches, active color-stripe headers, and individual accent colors), room selector pills, and a security status bar with pulsing indicator dots. Every device card is fully toggleable. Syne typeface for a clean modern edge. Best for smart-home apps, IoT dashboards, control panels.
The code
<section class="nm-nex" aria-label="Smart home control panel">
<div class="card">
<div class="room-row" role="tablist">
<button type="button" class="room-pill active" role="tab" aria-selected="true">Living Room</button>
<button type="button" class="room-pill" role="tab" aria-selected="false">Bedroom</button>
<button type="button" class="room-pill" role="tab" aria-selected="false">Kitchen</button>
<button type="button" class="room-pill" role="tab" aria-selected="false">Office</button>
</div>
<div class="temp-section">
<div class="dial-wrap" aria-hidden="true">
<svg viewBox="0 0 90 90">
<defs>
<linearGradient id="nm-nex-tempgrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#5b8def"/>
<stop offset="100%" style="stop-color:#f0a855"/>
</linearGradient>
</defs>
<circle class="track" cx="45" cy="45" r="35"/>
<circle class="fill" cx="45" cy="45" r="35" id="nm-nex-dialfill"/>
</svg>
<div class="dial-center">
<div class="dial-val" id="nm-nex-tempval">22</div>
<div class="dial-unit">°C</div>
</div>
</div>
<div class="temp-info">
<div class="temp-label">Climate Control</div>
<div class="temp-sub">Humidity 48% · Feels 23°</div>
<div class="temp-btns">
<button type="button" class="adj-btn" data-nm-nex-adj="-1" aria-label="Decrease temperature">−</button>
<button type="button" class="adj-btn" data-nm-nex-adj="1" aria-label="Increase temperature">+</button>
</div>
</div>
</div>
<div class="devices-grid">
<button type="button" class="device-card active" data-nm-nex-device="light" style="--device-color: #f0a855;">
<span class="toggle" aria-hidden="true"></span>
<span class="device-icon" aria-hidden="true">💡</span>
<div class="device-name">Main Light</div>
<div class="device-status">On · 70%</div>
</button>
<button type="button" class="device-card active" data-nm-nex-device="air" style="--device-color: #5b8def;">
<span class="toggle" aria-hidden="true"></span>
<span class="device-icon" aria-hidden="true">❄️</span>
<div class="device-name">Air Purifier</div>
<div class="device-status">Running</div>
</button>
<button type="button" class="device-card" data-nm-nex-device="tv" style="--device-color: #5bb87e;">
<span class="toggle" aria-hidden="true"></span>
<span class="device-icon" aria-hidden="true">📺</span>
<div class="device-name">Smart TV</div>
<div class="device-status">Off</div>
</button>
<button type="button" class="device-card active" data-nm-nex-device="speaker" style="--device-color: #5bb87e;">
<span class="toggle" aria-hidden="true"></span>
<span class="device-icon" aria-hidden="true">🔊</span>
<div class="device-name">Speaker</div>
<div class="device-status">Playing</div>
</button>
</div>
<div class="security-row">
<div class="sec-left">
<span class="sec-icon" aria-hidden="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>
</span>
<div class="sec-text">
<div class="sec-title">Security System</div>
<div class="sec-sub">All Clear · Armed</div>
</div>
</div>
<div class="status-dots" aria-hidden="true">
<span class="sdot green pulse"></span>
<span class="sdot green"></span>
<span class="sdot"></span>
</div>
</div>
</div>
</section> <section class="nm-nex" aria-label="Smart home control panel">
<div class="card">
<div class="room-row" role="tablist">
<button type="button" class="room-pill active" role="tab" aria-selected="true">Living Room</button>
<button type="button" class="room-pill" role="tab" aria-selected="false">Bedroom</button>
<button type="button" class="room-pill" role="tab" aria-selected="false">Kitchen</button>
<button type="button" class="room-pill" role="tab" aria-selected="false">Office</button>
</div>
<div class="temp-section">
<div class="dial-wrap" aria-hidden="true">
<svg viewBox="0 0 90 90">
<defs>
<linearGradient id="nm-nex-tempgrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#5b8def"/>
<stop offset="100%" style="stop-color:#f0a855"/>
</linearGradient>
</defs>
<circle class="track" cx="45" cy="45" r="35"/>
<circle class="fill" cx="45" cy="45" r="35" id="nm-nex-dialfill"/>
</svg>
<div class="dial-center">
<div class="dial-val" id="nm-nex-tempval">22</div>
<div class="dial-unit">°C</div>
</div>
</div>
<div class="temp-info">
<div class="temp-label">Climate Control</div>
<div class="temp-sub">Humidity 48% · Feels 23°</div>
<div class="temp-btns">
<button type="button" class="adj-btn" data-nm-nex-adj="-1" aria-label="Decrease temperature">−</button>
<button type="button" class="adj-btn" data-nm-nex-adj="1" aria-label="Increase temperature">+</button>
</div>
</div>
</div>
<div class="devices-grid">
<button type="button" class="device-card active" data-nm-nex-device="light" style="--device-color: #f0a855;">
<span class="toggle" aria-hidden="true"></span>
<span class="device-icon" aria-hidden="true">💡</span>
<div class="device-name">Main Light</div>
<div class="device-status">On · 70%</div>
</button>
<button type="button" class="device-card active" data-nm-nex-device="air" style="--device-color: #5b8def;">
<span class="toggle" aria-hidden="true"></span>
<span class="device-icon" aria-hidden="true">❄️</span>
<div class="device-name">Air Purifier</div>
<div class="device-status">Running</div>
</button>
<button type="button" class="device-card" data-nm-nex-device="tv" style="--device-color: #5bb87e;">
<span class="toggle" aria-hidden="true"></span>
<span class="device-icon" aria-hidden="true">📺</span>
<div class="device-name">Smart TV</div>
<div class="device-status">Off</div>
</button>
<button type="button" class="device-card active" data-nm-nex-device="speaker" style="--device-color: #5bb87e;">
<span class="toggle" aria-hidden="true"></span>
<span class="device-icon" aria-hidden="true">🔊</span>
<div class="device-name">Speaker</div>
<div class="device-status">Playing</div>
</button>
</div>
<div class="security-row">
<div class="sec-left">
<span class="sec-icon" aria-hidden="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>
</span>
<div class="sec-text">
<div class="sec-title">Security System</div>
<div class="sec-sub">All Clear · Armed</div>
</div>
</div>
<div class="status-dots" aria-hidden="true">
<span class="sdot green pulse"></span>
<span class="sdot green"></span>
<span class="sdot"></span>
</div>
</div>
</div>
</section>/* ─── 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; }
} /* ─── 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; }
}(() => {
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');
});
});
})(); (() => {
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');
});
});
})();