13 CSS Neumorphism & Soft UI Designs
Breathe
A warm sand-toned neumorphic wellness widget built around a breathing orb. Pulsing concentric-ring progress arc in terracotta gradient, three expanding pulse rings tied to the inhale/exhale cycle, animated phase-label transitions (Inhale → Hold → Exhale → Rest), a live countdown timer, cycle-progress dots, and preset duration pills. Cormorant Garamond for editorial calm. Best for meditation apps, wellness brands, mindfulness tools.
The code
<section class="nm-bre" aria-label="Breathing meditation timer">
<div class="card">
<span class="ambient" aria-hidden="true"></span>
<div class="phase-label" id="nm-bre-phase">Inhale slowly</div>
<div class="orb-wrap">
<div class="ring-track">
<svg viewBox="0 0 220 220" aria-hidden="true">
<defs>
<linearGradient id="nm-bre-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#c4856a"/>
<stop offset="100%" style="stop-color:#d9b8a8"/>
</linearGradient>
</defs>
<circle class="track" cx="110" cy="110" r="100"/>
<circle class="progress" cx="110" cy="110" r="100"/>
</svg>
<div class="orb-inner">
<span class="pulse-ring" aria-hidden="true"></span>
<span class="pulse-ring" aria-hidden="true"></span>
<span class="pulse-ring" aria-hidden="true"></span>
<span class="orb-symbol" aria-hidden="true">☽</span>
</div>
</div>
</div>
<div class="timer-display">
<div class="time" id="nm-bre-timer">05:00</div>
<div class="sub">minutes remaining</div>
</div>
<div class="cycles" aria-hidden="true">
<span class="cycle-dot done"></span>
<span class="cycle-dot done"></span>
<span class="cycle-dot done"></span>
<span class="cycle-dot"></span>
<span class="cycle-dot"></span>
<span class="cycle-dot"></span>
<span class="cycle-dot"></span>
<span class="cycle-dot"></span>
</div>
<div class="controls">
<button type="button" class="btn-sm" aria-label="Restart">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4"/>
</svg>
</button>
<button type="button" class="btn-main" id="nm-bre-play" aria-label="Play / Pause">
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<polygon points="6,4 20,12 6,20"/>
</svg>
</button>
<button type="button" class="btn-sm" aria-label="Settings">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.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-4 0v-.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-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.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 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.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 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
</button>
</div>
<div class="presets">
<button type="button" class="preset-pill active" data-mins="5">5 min</button>
<button type="button" class="preset-pill" data-mins="10">10 min</button>
<button type="button" class="preset-pill" data-mins="20">20 min</button>
</div>
</div>
</section> <section class="nm-bre" aria-label="Breathing meditation timer">
<div class="card">
<span class="ambient" aria-hidden="true"></span>
<div class="phase-label" id="nm-bre-phase">Inhale slowly</div>
<div class="orb-wrap">
<div class="ring-track">
<svg viewBox="0 0 220 220" aria-hidden="true">
<defs>
<linearGradient id="nm-bre-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#c4856a"/>
<stop offset="100%" style="stop-color:#d9b8a8"/>
</linearGradient>
</defs>
<circle class="track" cx="110" cy="110" r="100"/>
<circle class="progress" cx="110" cy="110" r="100"/>
</svg>
<div class="orb-inner">
<span class="pulse-ring" aria-hidden="true"></span>
<span class="pulse-ring" aria-hidden="true"></span>
<span class="pulse-ring" aria-hidden="true"></span>
<span class="orb-symbol" aria-hidden="true">☽</span>
</div>
</div>
</div>
<div class="timer-display">
<div class="time" id="nm-bre-timer">05:00</div>
<div class="sub">minutes remaining</div>
</div>
<div class="cycles" aria-hidden="true">
<span class="cycle-dot done"></span>
<span class="cycle-dot done"></span>
<span class="cycle-dot done"></span>
<span class="cycle-dot"></span>
<span class="cycle-dot"></span>
<span class="cycle-dot"></span>
<span class="cycle-dot"></span>
<span class="cycle-dot"></span>
</div>
<div class="controls">
<button type="button" class="btn-sm" aria-label="Restart">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 .49-4"/>
</svg>
</button>
<button type="button" class="btn-main" id="nm-bre-play" aria-label="Play / Pause">
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<polygon points="6,4 20,12 6,20"/>
</svg>
</button>
<button type="button" class="btn-sm" aria-label="Settings">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.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-4 0v-.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-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.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 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.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 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
</button>
</div>
<div class="presets">
<button type="button" class="preset-pill active" data-mins="5">5 min</button>
<button type="button" class="preset-pill" data-mins="10">10 min</button>
<button type="button" class="preset-pill" data-mins="20">20 min</button>
</div>
</div>
</section>/* ─── 01 Breathe — sand-toned meditation timer ──────────────────────
All :root vars from the source mock are scoped to .nm-bre so this
demo's warm sand palette never leaks. Body styles (centering,
background) are moved to the wrapper too. */
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;1,300&family=Jost:wght@200;300;400&display=swap');
.nm-bre {
--nm-bre-bg: #e8e0d8;
--nm-bre-shadow-dark: #c4bdb5;
--nm-bre-shadow-light: #ffffff;
--nm-bre-text-primary: #6b6260;
--nm-bre-text-muted: #a09896;
--nm-bre-accent: #c4856a;
--nm-bre-accent-soft: #d9a08a;
position: relative;
width: 100%;
min-height: 720px;
background: var(--nm-bre-bg);
font-family: 'Jost', system-ui, sans-serif;
display: flex;
align-items: center;
justify-content: center;
padding: 32px 16px;
overflow: hidden;
box-sizing: border-box;
}
.nm-bre *,
.nm-bre *::before,
.nm-bre *::after { box-sizing: border-box; }
/* The inner card holds the neumorphic shadow */
.nm-bre .card {
position: relative;
width: 100%;
max-width: 420px;
background: var(--nm-bre-bg);
border-radius: 40px;
padding: 50px 40px 44px;
box-shadow:
12px 12px 28px var(--nm-bre-shadow-dark),
-10px -10px 24px var(--nm-bre-shadow-light);
}
.nm-bre .ambient {
position: absolute;
width: 80px;
height: 80px;
border-radius: 50%;
top: -20px;
right: -20px;
background: radial-gradient(circle, rgba(196, 133, 106, 0.12), transparent 70%);
pointer-events: none;
}
/* Phase label */
.nm-bre .phase-label {
text-align: center;
font-family: 'Cormorant Garamond', Georgia, serif;
font-size: 13px;
font-weight: 300;
letter-spacing: 4px;
text-transform: uppercase;
color: var(--nm-bre-text-muted);
transition: opacity 0.8s;
}
/* Orb */
.nm-bre .orb-wrap {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 32px;
}
.nm-bre .ring-track {
width: 220px;
height: 220px;
border-radius: 50%;
background: var(--nm-bre-bg);
box-shadow:
8px 8px 18px var(--nm-bre-shadow-dark),
-7px -7px 16px var(--nm-bre-shadow-light);
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.nm-bre .ring-track svg {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
transform: rotate(-90deg);
}
.nm-bre .ring-track svg circle.track {
fill: none;
stroke: var(--nm-bre-shadow-dark);
stroke-width: 3;
opacity: 0.4;
}
.nm-bre .ring-track svg circle.progress {
fill: none;
stroke: url(#nm-bre-grad);
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray: 628;
stroke-dashoffset: 628;
animation: nm-bre-ring 16s linear infinite;
}
@keyframes nm-bre-ring {
0% { stroke-dashoffset: 628; }
50% { stroke-dashoffset: 0; }
100% { stroke-dashoffset: 628; }
}
.nm-bre .orb-inner {
width: 160px;
height: 160px;
border-radius: 50%;
background: radial-gradient(circle at 38% 38%, #f2ebe4, #ddd5cc);
box-shadow:
inset 5px 5px 12px var(--nm-bre-shadow-dark),
inset -4px -4px 10px var(--nm-bre-shadow-light);
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 1;
}
.nm-bre .pulse-ring {
position: absolute;
border-radius: 50%;
border: 1.5px solid var(--nm-bre-accent-soft);
opacity: 0;
animation: nm-bre-pulse 8s ease-in-out infinite;
}
.nm-bre .pulse-ring:nth-of-type(1) { width: 90px; height: 90px; animation-delay: 0s; }
.nm-bre .pulse-ring:nth-of-type(2) { width: 120px; height: 120px; animation-delay: 2s; }
.nm-bre .pulse-ring:nth-of-type(3) { width: 150px; height: 150px; animation-delay: 4s; }
@keyframes nm-bre-pulse {
0% { transform: scale(0.7); opacity: 0; }
30% { opacity: 0.5; }
100% { transform: scale(1.4); opacity: 0; }
}
.nm-bre .orb-symbol {
font-family: 'Cormorant Garamond', Georgia, serif;
font-size: 38px;
font-weight: 300;
color: var(--nm-bre-text-primary);
letter-spacing: -1px;
animation: nm-bre-breathe 8s ease-in-out infinite;
user-select: none;
}
@keyframes nm-bre-breathe {
0%, 100% { transform: scale(1); opacity: 0.7; }
50% { transform: scale(1.06); opacity: 1; }
}
/* Timer */
.nm-bre .timer-display { text-align: center; margin-bottom: 26px; }
.nm-bre .timer-display .time {
font-family: 'Cormorant Garamond', Georgia, serif;
font-size: 52px;
font-weight: 300;
color: var(--nm-bre-text-primary);
letter-spacing: -1px;
line-height: 1;
}
.nm-bre .timer-display .sub {
font-size: 10px;
font-weight: 300;
letter-spacing: 3px;
text-transform: uppercase;
color: var(--nm-bre-text-muted);
margin-top: 6px;
}
/* Cycle dots */
.nm-bre .cycles {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 30px;
}
.nm-bre .cycle-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--nm-bre-bg);
box-shadow:
2px 2px 5px var(--nm-bre-shadow-dark),
-2px -2px 4px var(--nm-bre-shadow-light);
transition: all 0.4s;
}
.nm-bre .cycle-dot.done {
background: var(--nm-bre-accent-soft);
box-shadow:
inset 1px 1px 3px rgba(0, 0, 0, 0.2),
inset -1px -1px 3px rgba(255, 255, 255, 0.4);
}
/* Controls */
.nm-bre .controls {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
}
.nm-bre .btn-sm {
width: 46px;
height: 46px;
border-radius: 50%;
border: none;
background: var(--nm-bre-bg);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow:
5px 5px 12px var(--nm-bre-shadow-dark),
-4px -4px 10px var(--nm-bre-shadow-light);
color: var(--nm-bre-text-muted);
font-size: 14px;
transition: all 0.2s;
}
.nm-bre .btn-sm:active {
box-shadow:
inset 3px 3px 8px var(--nm-bre-shadow-dark),
inset -2px -2px 6px var(--nm-bre-shadow-light);
}
.nm-bre .btn-main {
width: 68px;
height: 68px;
border-radius: 50%;
border: none;
background: var(--nm-bre-bg);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow:
8px 8px 18px var(--nm-bre-shadow-dark),
-7px -7px 16px var(--nm-bre-shadow-light);
color: var(--nm-bre-accent);
font-size: 22px;
transition: all 0.2s;
position: relative;
}
.nm-bre .btn-main::after {
content: '';
position: absolute;
inset: 4px;
border-radius: 50%;
background: linear-gradient(135deg, #f0e9e2, #dcd4cc);
z-index: 0;
}
.nm-bre .btn-main svg { position: relative; z-index: 1; }
.nm-bre .btn-main:active {
box-shadow:
inset 4px 4px 10px var(--nm-bre-shadow-dark),
inset -3px -3px 8px var(--nm-bre-shadow-light);
}
/* Presets */
.nm-bre .presets {
display: flex;
justify-content: center;
gap: 8px;
}
.nm-bre .preset-pill {
padding: 6px 16px;
border-radius: 20px;
border: none;
background: var(--nm-bre-bg);
font-family: 'Jost', system-ui, sans-serif;
font-size: 10px;
font-weight: 300;
letter-spacing: 2px;
text-transform: uppercase;
color: var(--nm-bre-text-muted);
cursor: pointer;
box-shadow:
3px 3px 8px var(--nm-bre-shadow-dark),
-2px -2px 6px var(--nm-bre-shadow-light);
transition: all 0.2s;
}
.nm-bre .preset-pill.active {
color: var(--nm-bre-accent);
box-shadow:
inset 2px 2px 5px var(--nm-bre-shadow-dark),
inset -2px -2px 4px var(--nm-bre-shadow-light);
}
@media (prefers-reduced-motion: reduce) {
.nm-bre .ring-track svg circle.progress,
.nm-bre .pulse-ring,
.nm-bre .orb-symbol { animation: none; }
} /* ─── 01 Breathe — sand-toned meditation timer ──────────────────────
All :root vars from the source mock are scoped to .nm-bre so this
demo's warm sand palette never leaks. Body styles (centering,
background) are moved to the wrapper too. */
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;1,300&family=Jost:wght@200;300;400&display=swap');
.nm-bre {
--nm-bre-bg: #e8e0d8;
--nm-bre-shadow-dark: #c4bdb5;
--nm-bre-shadow-light: #ffffff;
--nm-bre-text-primary: #6b6260;
--nm-bre-text-muted: #a09896;
--nm-bre-accent: #c4856a;
--nm-bre-accent-soft: #d9a08a;
position: relative;
width: 100%;
min-height: 720px;
background: var(--nm-bre-bg);
font-family: 'Jost', system-ui, sans-serif;
display: flex;
align-items: center;
justify-content: center;
padding: 32px 16px;
overflow: hidden;
box-sizing: border-box;
}
.nm-bre *,
.nm-bre *::before,
.nm-bre *::after { box-sizing: border-box; }
/* The inner card holds the neumorphic shadow */
.nm-bre .card {
position: relative;
width: 100%;
max-width: 420px;
background: var(--nm-bre-bg);
border-radius: 40px;
padding: 50px 40px 44px;
box-shadow:
12px 12px 28px var(--nm-bre-shadow-dark),
-10px -10px 24px var(--nm-bre-shadow-light);
}
.nm-bre .ambient {
position: absolute;
width: 80px;
height: 80px;
border-radius: 50%;
top: -20px;
right: -20px;
background: radial-gradient(circle, rgba(196, 133, 106, 0.12), transparent 70%);
pointer-events: none;
}
/* Phase label */
.nm-bre .phase-label {
text-align: center;
font-family: 'Cormorant Garamond', Georgia, serif;
font-size: 13px;
font-weight: 300;
letter-spacing: 4px;
text-transform: uppercase;
color: var(--nm-bre-text-muted);
transition: opacity 0.8s;
}
/* Orb */
.nm-bre .orb-wrap {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 32px;
}
.nm-bre .ring-track {
width: 220px;
height: 220px;
border-radius: 50%;
background: var(--nm-bre-bg);
box-shadow:
8px 8px 18px var(--nm-bre-shadow-dark),
-7px -7px 16px var(--nm-bre-shadow-light);
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.nm-bre .ring-track svg {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
transform: rotate(-90deg);
}
.nm-bre .ring-track svg circle.track {
fill: none;
stroke: var(--nm-bre-shadow-dark);
stroke-width: 3;
opacity: 0.4;
}
.nm-bre .ring-track svg circle.progress {
fill: none;
stroke: url(#nm-bre-grad);
stroke-width: 3;
stroke-linecap: round;
stroke-dasharray: 628;
stroke-dashoffset: 628;
animation: nm-bre-ring 16s linear infinite;
}
@keyframes nm-bre-ring {
0% { stroke-dashoffset: 628; }
50% { stroke-dashoffset: 0; }
100% { stroke-dashoffset: 628; }
}
.nm-bre .orb-inner {
width: 160px;
height: 160px;
border-radius: 50%;
background: radial-gradient(circle at 38% 38%, #f2ebe4, #ddd5cc);
box-shadow:
inset 5px 5px 12px var(--nm-bre-shadow-dark),
inset -4px -4px 10px var(--nm-bre-shadow-light);
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 1;
}
.nm-bre .pulse-ring {
position: absolute;
border-radius: 50%;
border: 1.5px solid var(--nm-bre-accent-soft);
opacity: 0;
animation: nm-bre-pulse 8s ease-in-out infinite;
}
.nm-bre .pulse-ring:nth-of-type(1) { width: 90px; height: 90px; animation-delay: 0s; }
.nm-bre .pulse-ring:nth-of-type(2) { width: 120px; height: 120px; animation-delay: 2s; }
.nm-bre .pulse-ring:nth-of-type(3) { width: 150px; height: 150px; animation-delay: 4s; }
@keyframes nm-bre-pulse {
0% { transform: scale(0.7); opacity: 0; }
30% { opacity: 0.5; }
100% { transform: scale(1.4); opacity: 0; }
}
.nm-bre .orb-symbol {
font-family: 'Cormorant Garamond', Georgia, serif;
font-size: 38px;
font-weight: 300;
color: var(--nm-bre-text-primary);
letter-spacing: -1px;
animation: nm-bre-breathe 8s ease-in-out infinite;
user-select: none;
}
@keyframes nm-bre-breathe {
0%, 100% { transform: scale(1); opacity: 0.7; }
50% { transform: scale(1.06); opacity: 1; }
}
/* Timer */
.nm-bre .timer-display { text-align: center; margin-bottom: 26px; }
.nm-bre .timer-display .time {
font-family: 'Cormorant Garamond', Georgia, serif;
font-size: 52px;
font-weight: 300;
color: var(--nm-bre-text-primary);
letter-spacing: -1px;
line-height: 1;
}
.nm-bre .timer-display .sub {
font-size: 10px;
font-weight: 300;
letter-spacing: 3px;
text-transform: uppercase;
color: var(--nm-bre-text-muted);
margin-top: 6px;
}
/* Cycle dots */
.nm-bre .cycles {
display: flex;
justify-content: center;
gap: 10px;
margin-bottom: 30px;
}
.nm-bre .cycle-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--nm-bre-bg);
box-shadow:
2px 2px 5px var(--nm-bre-shadow-dark),
-2px -2px 4px var(--nm-bre-shadow-light);
transition: all 0.4s;
}
.nm-bre .cycle-dot.done {
background: var(--nm-bre-accent-soft);
box-shadow:
inset 1px 1px 3px rgba(0, 0, 0, 0.2),
inset -1px -1px 3px rgba(255, 255, 255, 0.4);
}
/* Controls */
.nm-bre .controls {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
}
.nm-bre .btn-sm {
width: 46px;
height: 46px;
border-radius: 50%;
border: none;
background: var(--nm-bre-bg);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow:
5px 5px 12px var(--nm-bre-shadow-dark),
-4px -4px 10px var(--nm-bre-shadow-light);
color: var(--nm-bre-text-muted);
font-size: 14px;
transition: all 0.2s;
}
.nm-bre .btn-sm:active {
box-shadow:
inset 3px 3px 8px var(--nm-bre-shadow-dark),
inset -2px -2px 6px var(--nm-bre-shadow-light);
}
.nm-bre .btn-main {
width: 68px;
height: 68px;
border-radius: 50%;
border: none;
background: var(--nm-bre-bg);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow:
8px 8px 18px var(--nm-bre-shadow-dark),
-7px -7px 16px var(--nm-bre-shadow-light);
color: var(--nm-bre-accent);
font-size: 22px;
transition: all 0.2s;
position: relative;
}
.nm-bre .btn-main::after {
content: '';
position: absolute;
inset: 4px;
border-radius: 50%;
background: linear-gradient(135deg, #f0e9e2, #dcd4cc);
z-index: 0;
}
.nm-bre .btn-main svg { position: relative; z-index: 1; }
.nm-bre .btn-main:active {
box-shadow:
inset 4px 4px 10px var(--nm-bre-shadow-dark),
inset -3px -3px 8px var(--nm-bre-shadow-light);
}
/* Presets */
.nm-bre .presets {
display: flex;
justify-content: center;
gap: 8px;
}
.nm-bre .preset-pill {
padding: 6px 16px;
border-radius: 20px;
border: none;
background: var(--nm-bre-bg);
font-family: 'Jost', system-ui, sans-serif;
font-size: 10px;
font-weight: 300;
letter-spacing: 2px;
text-transform: uppercase;
color: var(--nm-bre-text-muted);
cursor: pointer;
box-shadow:
3px 3px 8px var(--nm-bre-shadow-dark),
-2px -2px 6px var(--nm-bre-shadow-light);
transition: all 0.2s;
}
.nm-bre .preset-pill.active {
color: var(--nm-bre-accent);
box-shadow:
inset 2px 2px 5px var(--nm-bre-shadow-dark),
inset -2px -2px 4px var(--nm-bre-shadow-light);
}
@media (prefers-reduced-motion: reduce) {
.nm-bre .ring-track svg circle.progress,
.nm-bre .pulse-ring,
.nm-bre .orb-symbol { animation: none; }
}(() => {
const root = document.querySelector('.nm-bre');
if (!root) return;
const phases = ['Inhale slowly', 'Hold gently', 'Exhale softly', 'Rest'];
const durations = [4000, 2000, 6000, 2000];
let phaseIdx = 0;
const phaseLabel = root.querySelector('#nm-bre-phase');
function nextPhase() {
phaseIdx = (phaseIdx + 1) % phases.length;
if (phaseLabel) {
phaseLabel.style.opacity = 0;
setTimeout(() => {
phaseLabel.textContent = phases[phaseIdx];
phaseLabel.style.opacity = 1;
}, 400);
}
setTimeout(nextPhase, durations[phaseIdx]);
}
setTimeout(nextPhase, durations[0]);
let totalSecs = 300;
let timerInterval = null;
let running = false;
const timerEl = root.querySelector('#nm-bre-timer');
const playBtn = root.querySelector('#nm-bre-play');
const fmt = (s) => String(Math.floor(s / 60)).padStart(2, '0') + ':' + String(s % 60).padStart(2, '0');
const playIcon = '<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><polygon points="6,4 20,12 6,20"/></svg>';
const pauseIcon = '<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>';
if (playBtn) playBtn.addEventListener('click', () => {
running = !running;
if (running) {
playBtn.innerHTML = pauseIcon;
timerInterval = setInterval(() => {
if (totalSecs > 0) {
totalSecs--;
if (timerEl) timerEl.textContent = fmt(totalSecs);
} else {
clearInterval(timerInterval);
running = false;
playBtn.innerHTML = playIcon;
}
}, 1000);
} else {
clearInterval(timerInterval);
playBtn.innerHTML = playIcon;
}
});
root.querySelectorAll('.preset-pill').forEach(btn => {
btn.addEventListener('click', () => {
root.querySelector('.preset-pill.active')?.classList.remove('active');
btn.classList.add('active');
const mins = parseInt(btn.dataset.mins, 10) || 5;
totalSecs = mins * 60;
if (timerEl) timerEl.textContent = fmt(totalSecs);
if (running) {
clearInterval(timerInterval);
running = false;
if (playBtn) playBtn.innerHTML = playIcon;
}
});
});
})(); (() => {
const root = document.querySelector('.nm-bre');
if (!root) return;
const phases = ['Inhale slowly', 'Hold gently', 'Exhale softly', 'Rest'];
const durations = [4000, 2000, 6000, 2000];
let phaseIdx = 0;
const phaseLabel = root.querySelector('#nm-bre-phase');
function nextPhase() {
phaseIdx = (phaseIdx + 1) % phases.length;
if (phaseLabel) {
phaseLabel.style.opacity = 0;
setTimeout(() => {
phaseLabel.textContent = phases[phaseIdx];
phaseLabel.style.opacity = 1;
}, 400);
}
setTimeout(nextPhase, durations[phaseIdx]);
}
setTimeout(nextPhase, durations[0]);
let totalSecs = 300;
let timerInterval = null;
let running = false;
const timerEl = root.querySelector('#nm-bre-timer');
const playBtn = root.querySelector('#nm-bre-play');
const fmt = (s) => String(Math.floor(s / 60)).padStart(2, '0') + ':' + String(s % 60).padStart(2, '0');
const playIcon = '<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><polygon points="6,4 20,12 6,20"/></svg>';
const pauseIcon = '<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>';
if (playBtn) playBtn.addEventListener('click', () => {
running = !running;
if (running) {
playBtn.innerHTML = pauseIcon;
timerInterval = setInterval(() => {
if (totalSecs > 0) {
totalSecs--;
if (timerEl) timerEl.textContent = fmt(totalSecs);
} else {
clearInterval(timerInterval);
running = false;
playBtn.innerHTML = playIcon;
}
}, 1000);
} else {
clearInterval(timerInterval);
playBtn.innerHTML = playIcon;
}
});
root.querySelectorAll('.preset-pill').forEach(btn => {
btn.addEventListener('click', () => {
root.querySelector('.preset-pill.active')?.classList.remove('active');
btn.classList.add('active');
const mins = parseInt(btn.dataset.mins, 10) || 5;
totalSecs = mins * 60;
if (timerEl) timerEl.textContent = fmt(totalSecs);
if (running) {
clearInterval(timerInterval);
running = false;
if (playBtn) playBtn.innerHTML = playIcon;
}
});
});
})();