explorer / index.html
Bielefelder's picture
Upload 2 files
6751998 verified
Raw History Blame Contribute Delete
38.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Wearables Explorer — Compare Smartwatches, Rings & Trackers</title>
<meta
name="description"
content="Discover and compare selected wearables by features, pros, cons, compatibility and use case. An independent curated wearable explorer."
/>
<meta name="robots" content="index,follow" />
<meta name="theme-color" content="#0b1020" />
<meta property="og:title" content="Wearables Explorer" />
<meta
property="og:description"
content="Compare selected wearables: features, pros, cons and compatibility."
/>
<meta property="og:type" content="website" />
<meta property="og:image" content="wearables.png" />
<link rel="icon" href="wearables.png" />
<link rel="apple-touch-icon" href="wearables.png" />
<style>
:root {
--bg: #07101d;
--bg-soft: #0b1525;
--card: rgba(16, 28, 48, 0.78);
--card-strong: #101c30;
--line: rgba(255, 255, 255, 0.09);
--text: #f4f7fb;
--muted: #9daabc;
--accent: #8c67ff;
--accent-2: #2fc7a5;
--accent-3: #6ea8ff;
--good: #57d6a6;
--bad: #ff8f9b;
--shadow: 0 22px 70px rgba(0,0,0,.28);
--radius: 22px;
--radius-sm: 14px;
--max: 1220px;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at 16% 0%, rgba(140,103,255,.18), transparent 34%),
radial-gradient(circle at 86% 6%, rgba(47,199,165,.13), transparent 32%),
linear-gradient(180deg, #07101d 0%, #08111f 55%, #07101d 100%);
color: var(--text);
font-family:
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
line-height: 1.55;
}
button, input, select { font: inherit; }
a { color: inherit; }
.shell {
width: min(calc(100% - 32px), var(--max));
margin: 0 auto;
}
.topbar {
position: sticky;
top: 0;
z-index: 30;
backdrop-filter: blur(18px);
background: rgba(7,16,29,.78);
border-bottom: 1px solid var(--line);
}
.topbar-inner {
min-height: 72px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.brand img {
width: 40px;
height: 40px;
border-radius: 12px;
object-fit: cover;
background: #111b2d;
border: 1px solid var(--line);
}
.brand-title {
font-weight: 780;
letter-spacing: -.02em;
}
.brand-sub {
color: var(--muted);
font-size: .78rem;
margin-top: -2px;
}
.status-pill {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 11px;
border: 1px solid var(--line);
border-radius: 999px;
color: #cbd5e1;
background: rgba(255,255,255,.035);
font-size: .78rem;
white-space: nowrap;
}
.status-dot {
width: 7px;
height: 7px;
background: var(--accent-2);
border-radius: 50%;
box-shadow: 0 0 0 5px rgba(47,199,165,.12);
}
.hero {
padding: 76px 0 36px;
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(280px, .65fr);
gap: 44px;
align-items: end;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
padding: 7px 11px;
border: 1px solid rgba(140,103,255,.24);
border-radius: 999px;
background: rgba(140,103,255,.09);
color: #cfc3ff;
font-size: .8rem;
font-weight: 650;
}
h1 {
margin: 0;
max-width: 850px;
font-size: clamp(2.45rem, 6vw, 5.2rem);
line-height: .98;
letter-spacing: -.06em;
}
.gradient {
background: linear-gradient(90deg, #b59cff 0%, #7fb2ff 45%, #55d9b7 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.hero p {
max-width: 760px;
margin: 24px 0 0;
color: #b8c3d2;
font-size: clamp(1rem, 2vw, 1.16rem);
}
.hero-note {
padding: 20px;
border: 1px solid var(--line);
border-radius: var(--radius);
background: linear-gradient(180deg, rgba(255,255,255,.045), rgba(255,255,255,.018));
box-shadow: var(--shadow);
}
.hero-note strong {
display: block;
font-size: .95rem;
margin-bottom: 8px;
}
.hero-note p {
margin: 0;
font-size: .86rem;
color: var(--muted);
}
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-top: 18px;
}
.stat {
padding: 13px;
border: 1px solid var(--line);
border-radius: 14px;
background: rgba(0,0,0,.12);
}
.stat b {
display: block;
font-size: 1.08rem;
}
.stat span {
color: var(--muted);
font-size: .72rem;
}
.controls-wrap {
position: relative;
z-index: 5;
margin: 18px 0 28px;
padding: 16px;
border: 1px solid var(--line);
border-radius: 20px;
background: rgba(10,20,35,.8);
backdrop-filter: blur(12px);
}
.controls {
display: grid;
grid-template-columns: minmax(220px, 1.5fr) minmax(160px, .7fr) minmax(160px, .7fr);
gap: 10px;
}
.input, .select {
width: 100%;
min-height: 46px;
border: 1px solid var(--line);
border-radius: 13px;
outline: none;
background: rgba(255,255,255,.04);
color: var(--text);
padding: 0 14px;
}
.input:focus, .select:focus {
border-color: rgba(140,103,255,.55);
box-shadow: 0 0 0 4px rgba(140,103,255,.10);
}
.select option {
background: #101c30;
color: white;
}
.quick-filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.chip {
border: 1px solid var(--line);
border-radius: 999px;
background: rgba(255,255,255,.025);
color: #c8d2df;
padding: 8px 12px;
cursor: pointer;
transition: .18s ease;
}
.chip:hover, .chip.active {
border-color: rgba(140,103,255,.42);
background: rgba(140,103,255,.12);
color: white;
}
.section-head {
display: flex;
justify-content: space-between;
align-items: end;
gap: 20px;
margin: 34px 0 17px;
}
.section-head h2 {
margin: 0;
font-size: 1.55rem;
letter-spacing: -.03em;
}
.section-head p {
margin: 5px 0 0;
color: var(--muted);
font-size: .9rem;
}
#resultCount {
color: var(--muted);
font-size: .86rem;
white-space: nowrap;
}
.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0,1fr));
gap: 16px;
padding-bottom: 100px;
}
.card {
min-width: 0;
display: flex;
flex-direction: column;
border: 1px solid var(--line);
border-radius: var(--radius);
overflow: hidden;
background:
linear-gradient(180deg, rgba(255,255,255,.045), rgba(255,255,255,.018)),
var(--card);
box-shadow: 0 12px 40px rgba(0,0,0,.13);
transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.card:hover {
transform: translateY(-3px);
border-color: rgba(255,255,255,.16);
box-shadow: 0 18px 50px rgba(0,0,0,.23);
}
.card-top {
padding: 20px 20px 16px;
border-bottom: 1px solid var(--line);
min-height: 174px;
background:
radial-gradient(circle at 88% 0%, var(--glow, rgba(140,103,255,.12)), transparent 42%);
}
.card-meta {
display: flex;
justify-content: space-between;
gap: 10px;
align-items: center;
margin-bottom: 18px;
}
.brand-tag, .new-tag {
font-size: .72rem;
font-weight: 720;
letter-spacing: .03em;
text-transform: uppercase;
color: #d6deea;
}
.new-tag {
padding: 5px 8px;
border-radius: 999px;
color: #adf1dd;
background: rgba(47,199,165,.10);
border: 1px solid rgba(47,199,165,.18);
}
.device-name {
margin: 0;
font-size: 1.42rem;
line-height: 1.14;
letter-spacing: -.035em;
}
.device-type {
margin: 8px 0 0;
color: var(--muted);
font-size: .85rem;
}
.card-body {
display: flex;
flex-direction: column;
flex: 1;
padding: 18px 20px 20px;
}
.summary {
color: #c4cedb;
font-size: .91rem;
min-height: 68px;
margin: 0 0 16px;
}
.keyline {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
margin-bottom: 16px;
}
.key {
padding: 10px;
border-radius: 12px;
background: rgba(255,255,255,.03);
border: 1px solid var(--line);
}
.key span {
display: block;
color: var(--muted);
font-size: .68rem;
margin-bottom: 3px;
}
.key b {
display: block;
font-size: .8rem;
line-height: 1.25;
}
.pc {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin: 3px 0 18px;
}
.pc-title {
font-size: .74rem;
text-transform: uppercase;
letter-spacing: .06em;
font-weight: 760;
margin-bottom: 7px;
}
.pros .pc-title { color: var(--good); }
.cons .pc-title { color: var(--bad); }
.pc ul {
padding-left: 16px;
margin: 0;
color: #b9c3d0;
font-size: .78rem;
}
.pc li + li { margin-top: 4px; }
.card-actions {
display: grid;
grid-template-columns: 1fr auto;
gap: 8px;
margin-top: auto;
}
.btn {
min-height: 42px;
border: 1px solid var(--line);
border-radius: 12px;
padding: 0 13px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
text-decoration: none;
cursor: pointer;
transition: .16s ease;
background: rgba(255,255,255,.035);
color: var(--text);
font-weight: 650;
font-size: .82rem;
}
.btn:hover {
border-color: rgba(255,255,255,.22);
background: rgba(255,255,255,.07);
}
.btn.primary {
background: linear-gradient(135deg, rgba(140,103,255,.25), rgba(47,199,165,.14));
border-color: rgba(140,103,255,.35);
}
.btn.selected {
background: rgba(47,199,165,.14);
border-color: rgba(47,199,165,.38);
color: #c9ffef;
}
.empty {
grid-column: 1 / -1;
padding: 60px 20px;
text-align: center;
color: var(--muted);
border: 1px dashed var(--line);
border-radius: var(--radius);
}
.compare-bar {
position: fixed;
z-index: 45;
left: 50%;
bottom: 18px;
transform: translateX(-50%) translateY(140%);
width: min(calc(100% - 28px), 760px);
padding: 12px;
display: flex;
align-items: center;
gap: 12px;
border: 1px solid rgba(255,255,255,.13);
border-radius: 18px;
background: rgba(13,24,41,.94);
backdrop-filter: blur(20px);
box-shadow: 0 20px 70px rgba(0,0,0,.45);
transition: transform .25s ease;
}
.compare-bar.show {
transform: translateX(-50%) translateY(0);
}
.compare-info {
flex: 1;
min-width: 0;
}
.compare-info b {
display: block;
font-size: .87rem;
}
.compare-info span {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--muted);
font-size: .74rem;
}
.overlay {
position: fixed;
z-index: 70;
inset: 0;
display: none;
align-items: center;
justify-content: center;
padding: 20px;
background: rgba(1,5,12,.74);
backdrop-filter: blur(10px);
}
.overlay.open { display: flex; }
.modal {
width: min(1120px, 100%);
max-height: min(860px, 92vh);
overflow: auto;
background: #0b1627;
border: 1px solid rgba(255,255,255,.12);
border-radius: 24px;
box-shadow: var(--shadow);
}
.modal-head {
position: sticky;
top: 0;
z-index: 3;
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
padding: 18px 20px;
background: rgba(11,22,39,.95);
backdrop-filter: blur(15px);
border-bottom: 1px solid var(--line);
}
.modal-head h3 {
margin: 0;
font-size: 1.18rem;
}
.icon-btn {
width: 40px;
height: 40px;
border: 1px solid var(--line);
border-radius: 12px;
background: rgba(255,255,255,.04);
color: white;
cursor: pointer;
}
.table-wrap {
overflow-x: auto;
padding: 8px 20px 22px;
}
table {
width: 100%;
min-width: 760px;
border-collapse: collapse;
}
th, td {
border-bottom: 1px solid var(--line);
padding: 15px 14px;
vertical-align: top;
text-align: left;
font-size: .85rem;
}
th:first-child, td:first-child {
width: 160px;
color: var(--muted);
font-weight: 600;
}
th {
color: white;
font-size: .92rem;
}
.footer {
padding: 8px 0 54px;
border-top: 1px solid var(--line);
color: var(--muted);
}
.footer-grid {
display: grid;
grid-template-columns: 1.2fr .8fr;
gap: 32px;
padding-top: 28px;
}
.footer strong {
color: #dbe4ef;
}
.footer p {
margin: 8px 0 0;
font-size: .82rem;
}
.footer a {
color: #bdc9d8;
text-underline-offset: 3px;
}
.tiny {
margin-top: 22px !important;
color: #76869a;
font-size: .74rem !important;
}
@media (max-width: 980px) {
.hero {
grid-template-columns: 1fr;
gap: 22px;
padding-top: 54px;
}
.hero-note { max-width: 720px; }
.grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
.controls { grid-template-columns: 1fr 1fr; }
.controls .input { grid-column: 1 / -1; }
}
@media (max-width: 670px) {
.shell { width: min(calc(100% - 20px), var(--max)); }
.topbar-inner { min-height: 64px; }
.brand-sub { display: none; }
.status-pill { font-size: 0; padding: 9px; }
.status-dot { margin: 0; }
.hero { padding-top: 40px; }
h1 { font-size: clamp(2.6rem, 14vw, 4.4rem); }
.stats { grid-template-columns: 1fr; }
.controls { grid-template-columns: 1fr; }
.controls .input { grid-column: auto; }
.section-head {
align-items: start;
flex-direction: column;
gap: 8px;
}
.grid { grid-template-columns: 1fr; }
.card-top { min-height: auto; }
.summary { min-height: 0; }
.pc { grid-template-columns: 1fr; }
.compare-bar {
align-items: stretch;
flex-wrap: wrap;
}
.compare-info { flex-basis: 100%; }
.footer-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<header class="topbar">
<div class="shell topbar-inner">
<div class="brand">
<img src="wearables.png" alt="Wearables logo" onerror="this.style.display='none'" />
<div>
<div class="brand-title">Wearables Explorer</div>
<div class="brand-sub">Independent curated comparison</div>
</div>
</div>
<div class="status-pill" title="Curated snapshot">
<span class="status-dot"></span>
Updated 14 Sep 2026
</div>
</div>
</header>
<main>
<section class="shell hero">
<div>
<div class="eyebrow">⌁ Selected wearables, clearly explained</div>
<h1>
Find the wearable that
<span class="gradient">fits your life.</span>
</h1>
<p>
Compare selected smartwatches, sports watches, smart rings and trackers
by compatibility, strengths, trade-offs and intended use — without
drowning in specifications.
</p>
</div>
<aside class="hero-note">
<strong>A curated snapshot, not a paid ranking.</strong>
<p>
No sponsored placement. Every device links to an official source.
Features can vary by country, phone, software version and regulatory approval.
</p>
<div class="stats">
<div class="stat"><b id="statDevices">8</b><span>devices</span></div>
<div class="stat"><b>5</b><span>brands</span></div>
<div class="stat"><b>3</b><span>compare at once</span></div>
</div>
</aside>
</section>
<section class="shell">
<div class="controls-wrap">
<div class="controls">
<input
id="searchInput"
class="input"
type="search"
placeholder="Search Apple, Garmin, ring, outdoor..."
aria-label="Search wearables"
/>
<select id="typeFilter" class="select" aria-label="Filter by device type">
<option value="all">All device types</option>
<option value="Smartwatch">Smartwatch</option>
<option value="Sports watch">Sports watch</option>
<option value="Smart ring">Smart ring</option>
<option value="Smart band">Smart band</option>
</select>
<select id="sortFilter" class="select" aria-label="Sort devices">
<option value="newest">Newest first</option>
<option value="name">Name A–Z</option>
<option value="brand">Brand A–Z</option>
</select>
</div>
<div class="quick-filters" aria-label="Compatibility filters">
<button class="chip active" data-compat="all">All phones</button>
<button class="chip" data-compat="iPhone">iPhone</button>
<button class="chip" data-compat="Android">Android</button>
<button class="chip" data-use="outdoor">Outdoor</button>
<button class="chip" data-use="sleep">Sleep</button>
<button class="chip" data-use="fitness">Fitness</button>
</div>
</div>
<div class="section-head">
<div>
<h2>Selected recent wearables</h2>
<p>Short explanations first. Specifications second.</p>
</div>
<div id="resultCount">8 devices</div>
</div>
<div id="deviceGrid" class="grid"></div>
</section>
</main>
<footer class="footer">
<div class="shell footer-grid">
<div>
<strong>Independent information project</strong>
<p>
Wearables Explorer is not affiliated with or endorsed by the manufacturers
listed here. Manufacturer claims are summarized from official product,
newsroom or support sources.
</p>
<p class="tiny">
Health and wellness features are informational and are not a substitute
for professional medical advice, diagnosis or treatment.
</p>
</div>
<div>
<strong>About this version</strong>
<p>
Static Hugging Face Space. No account, tracking profile or API key is
required for the comparison interface.
</p>
<p class="tiny">Explore the signals. Understand the limits. Choose more clearly.</p>
</div>
</div>
</footer>
<div id="compareBar" class="compare-bar" aria-live="polite">
<div class="compare-info">
<b id="compareCount">0 selected</b>
<span id="compareNames">Choose up to three devices</span>
</div>
<button id="clearCompare" class="btn">Clear</button>
<button id="openCompare" class="btn primary">Compare now</button>
</div>
<div id="compareOverlay" class="overlay" role="dialog" aria-modal="true" aria-labelledby="compareTitle">
<div class="modal">
<div class="modal-head">
<h3 id="compareTitle">Wearable comparison</h3>
<button id="closeCompare" class="icon-btn" aria-label="Close comparison">✕</button>
</div>
<div id="compareTable" class="table-wrap"></div>
</div>
</div>
<script>
const devices = [
{
id: "apple-watch-series-12",
brand: "Apple",
name: "Apple Watch Series 12",
type: "Smartwatch",
released: "2026-09-09",
newLabel: "New · Sep 2026",
compatibility: ["iPhone"],
use: ["fitness", "sleep", "everyday"],
ecosystem: "iPhone",
battery: "Varies by use",
bestFor: "Health + everyday use",
summary: "Apple’s newest mainstream watch adds a new Health Sensing System, higher-frequency heart-rate and HRV sensing, and a readiness score.",
features: "S11 chip; Health Sensing System; readiness; ECG; sleep and fitness tracking",
pros: [
"Deep iPhone integration",
"Broad health, fitness and safety feature set"
],
cons: [
"Requires iPhone",
"Some features vary by region and eligibility"
],
source: "https://www.apple.com/newsroom/2026/09/introducing-apple-watch-series-12-with-the-all-new-health-sensing-system/",
glow: "rgba(110,168,255,.18)"
},
{
id: "apple-watch-ultra-4",
brand: "Apple",
name: "Apple Watch Ultra 4",
type: "Sports watch",
released: "2026-09-09",
newLabel: "New · Sep 2026",
compatibility: ["iPhone"],
use: ["fitness", "sleep", "outdoor"],
ecosystem: "iPhone",
battery: "Up to 45h outdoor workout tracking",
bestFor: "Sport + adventure",
summary: "A rugged Apple Watch built for athletes and outdoor use, with the new Health Sensing System, readiness insights, satellite communication and longer endurance.",
features: "S11; readiness; satellite communication; advanced GPS; health and workout tracking",
pros: [
"Strong outdoor and safety feature set",
"Longer endurance than the standard Apple Watch"
],
cons: [
"Requires iPhone",
"Large, premium sports-watch format"
],
source: "https://www.apple.com/newsroom/2026/09/apple-unveils-apple-watch-ultra-4/",
glow: "rgba(255,181,84,.16)"
},
{
id: "galaxy-watch9",
brand: "Samsung",
name: "Galaxy Watch9",
type: "Smartwatch",
released: "2026-07-22",
newLabel: "2026",
compatibility: ["Android"],
use: ["fitness", "sleep", "everyday"],
ecosystem: "Android · best with Galaxy",
battery: "Up to ~40h, usage dependent",
bestFor: "Android everyday use",
summary: "Samsung’s current mainstream Galaxy Watch focuses on a comfortable fit, improved battery capacity, faster performance and practical Samsung Health insights.",
features: "Wear OS; Samsung Health; Gemini; sleep, activity and wellness tracking",
pros: [
"Strong Android smartwatch experience",
"Useful health and ecosystem integration"
],
cons: [
"Not compatible with iPhone",
"Some features work best with Samsung Galaxy phones"
],
source: "https://news.samsung.com/us/samsung-whats-new-galaxy-watch9/",
glow: "rgba(91,137,255,.18)"
},
{
id: "galaxy-watch-ultra2",
brand: "Samsung",
name: "Galaxy Watch Ultra2",
type: "Sports watch",
released: "2026-07-22",
newLabel: "2026",
compatibility: ["Android"],
use: ["fitness", "sleep", "outdoor"],
ecosystem: "Android · best with Galaxy",
battery: "Usage dependent",
bestFor: "Android sport + outdoor",
summary: "Samsung’s rugged flagship wearable is aimed at runners, training and outdoor use while retaining the connected features of the Galaxy Watch ecosystem.",
features: "Titanium design; advanced fitness; Samsung Health; connected smartwatch features",
pros: [
"Rugged premium build",
"Strong match for Galaxy users who train outdoors"
],
cons: [
"Not compatible with iPhone",
"Bulky compared with mainstream smartwatches"
],
source: "https://www.samsung.com/us/watches/",
glow: "rgba(80,205,214,.15)"
},
{
id: "pixel-watch-5",
brand: "Google",
name: "Pixel Watch 5",
type: "Smartwatch",
released: "2026-08-01",
newLabel: "New · 2026",
compatibility: ["Android"],
use: ["fitness", "sleep", "everyday"],
ecosystem: "Android · optimized for Pixel",
battery: "Up to 40h",
bestFor: "Google + health ecosystem",
summary: "Google’s newest Pixel Watch combines Google Health tracking with Gemini-powered coaching and expanded wellness sensing in a compact smartwatch format.",
features: "Gemini; Google Health; ECG; advanced wellness sensors; up to 40h battery",
pros: [
"Tight Google and Android integration",
"Strong health and coaching focus"
],
cons: [
"Not compatible with iPhone",
"Some health capabilities depend on region or rollout"
],
source: "https://store.google.com/product/pixel_watch_5?hl=en-US",
glow: "rgba(129,216,174,.16)"
},
{
id: "garmin-fenix-9-pro",
brand: "Garmin",
name: "fēnix 9 Pro",
type: "Sports watch",
released: "2026-08-25",
newLabel: "New · Aug 2026",
compatibility: ["iPhone", "Android"],
use: ["fitness", "sleep", "outdoor"],
ecosystem: "iPhone + Android",
battery: "Multi-day · varies by mode",
bestFor: "Training + expeditions",
summary: "Garmin’s flagship performance watch targets athletes and adventurers with extensive multisport tools, advanced navigation and connected outdoor capabilities.",
features: "Multisport GPS; stamina metrics; navigation; satellite + LTE on Pro models",
pros: [
"Excellent outdoor and endurance focus",
"Works with both iPhone and Android"
],
cons: [
"Complex feature set for casual users",
"Premium performance-watch positioning"
],
source: "https://www.garmin.com/en-US/newsroom/press-release/outdoor/garmin-expands-its-flagship-performance-smartwatch-lineup-with-fenix9-and-fenix9-pro/",
glow: "rgba(64,221,176,.16)"
},
{
id: "garmin-cirqa",
brand: "Garmin",
name: "CIRQA Smart Band",
type: "Smart band",
released: "2026-07-21",
newLabel: "New · Jul 2026",
compatibility: ["iPhone", "Android"],
use: ["fitness", "sleep"],
ecosystem: "iPhone + Android",
battery: "Up to 10 days",
bestFor: "Screen-free health tracking",
summary: "Garmin’s first screen-free smart band is designed for distraction-free 24/7 wellness and activity tracking with data viewed in Garmin Connect.",
features: "No display; 24/7 health tracking; activity detection; Garmin Connect; no subscription required",
pros: [
"Screen-free and low distraction",
"Up to 10 days of battery life"
],
cons: [
"No on-device display",
"Less suitable for people wanting rich smartwatch apps"
],
source: "https://www.garmin.com/en-US/newsroom/press-release/wearables-health/meet-cirqa-smart-band-the-screen-free-health-and-fitness-tracker-from-garmin/",
glow: "rgba(69,231,194,.16)"
},
{
id: "oura-ring-5",
brand: "Oura",
name: "Oura Ring 5",
type: "Smart ring",
released: "2026-05-28",
newLabel: "2026",
compatibility: ["iPhone", "Android"],
use: ["sleep", "fitness"],
ecosystem: "iPhone + Android",
battery: "Typically 6–9 days",
bestFor: "Sleep + recovery",
summary: "Oura’s newest generation is a slimmer titanium smart ring focused on continuous health signals, sleep, recovery and long-term wellness insights.",
features: "Titanium; temperature sensing; heart-rate sensing; sleep and recovery insights",
pros: [
"Compact, screen-free form factor",
"Strong sleep and recovery focus"
],
cons: [
"Active membership is needed for the full software experience",
"No smartwatch-style screen or apps"
],
source: "https://support.ouraring.com/hc/en-us/articles/50997181300243-Discover-Oura-Ring-5",
glow: "rgba(190,150,255,.17)"
}
];
let activeCompat = "all";
let activeUse = null;
let selected = new Set();
const grid = document.getElementById("deviceGrid");
const searchInput = document.getElementById("searchInput");
const typeFilter = document.getElementById("typeFilter");
const sortFilter = document.getElementById("sortFilter");
const resultCount = document.getElementById("resultCount");
const compareBar = document.getElementById("compareBar");
const compareCount = document.getElementById("compareCount");
const compareNames = document.getElementById("compareNames");
const compareOverlay = document.getElementById("compareOverlay");
const compareTable = document.getElementById("compareTable");
document.getElementById("statDevices").textContent = devices.length;
function safeText(str) {
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function getFiltered() {
const query = searchInput.value.trim().toLowerCase();
const type = typeFilter.value;
let list = devices.filter(d => {
const text = [
d.brand, d.name, d.type, d.summary, d.features,
d.ecosystem, d.bestFor, ...d.use
].join(" ").toLowerCase();
const matchesQuery = !query || text.includes(query);
const matchesType = type === "all" || d.type === type;
const matchesCompat = activeCompat === "all" || d.compatibility.includes(activeCompat);
const matchesUse = !activeUse || d.use.includes(activeUse);
return matchesQuery && matchesType && matchesCompat && matchesUse;
});
if (sortFilter.value === "newest") {
list.sort((a,b) => b.released.localeCompare(a.released));
} else if (sortFilter.value === "name") {
list.sort((a,b) => a.name.localeCompare(b.name));
} else if (sortFilter.value === "brand") {
list.sort((a,b) => a.brand.localeCompare(b.brand) || a.name.localeCompare(b.name));
}
return list;
}
function render() {
const list = getFiltered();
resultCount.textContent = `${list.length} ${list.length === 1 ? "device" : "devices"}`;
if (!list.length) {
grid.innerHTML = `<div class="empty">No matching wearables. Try a broader search or reset a filter.</div>`;
return;
}
grid.innerHTML = list.map(d => {
const isSelected = selected.has(d.id);
return `
<article class="card">
<div class="card-top" style="--glow:${d.glow}">
<div class="card-meta">
<span class="brand-tag">${safeText(d.brand)}</span>
<span class="new-tag">${safeText(d.newLabel)}</span>
</div>
<h3 class="device-name">${safeText(d.name)}</h3>
<p class="device-type">${safeText(d.type)} · ${safeText(d.ecosystem)}</p>
</div>
<div class="card-body">
<p class="summary">${safeText(d.summary)}</p>
<div class="keyline">
<div class="key">
<span>Battery</span>
<b>${safeText(d.battery)}</b>
</div>
<div class="key">
<span>Best for</span>
<b>${safeText(d.bestFor)}</b>
</div>
</div>
<div class="pc">
<div class="pros">
<div class="pc-title">Pros</div>
<ul>
${d.pros.map(x => `<li>${safeText(x)}</li>`).join("")}
</ul>
</div>
<div class="cons">
<div class="pc-title">Cons</div>
<ul>
${d.cons.map(x => `<li>${safeText(x)}</li>`).join("")}
</ul>
</div>
</div>
<div class="card-actions">
<a
class="btn"
href="${d.source}"
target="_blank"
rel="noopener noreferrer"
aria-label="Open official source for ${safeText(d.name)}"
>
Official source ↗
</a>
<button
class="btn ${isSelected ? "selected" : "primary"}"
data-compare="${d.id}"
aria-pressed="${isSelected}"
aria-label="${isSelected ? "Remove from" : "Add to"} comparison: ${safeText(d.name)}"
>
${isSelected ? "✓ Added" : "+ Compare"}
</button>
</div>
</div>
</article>
`;
}).join("");
document.querySelectorAll("[data-compare]").forEach(btn => {
btn.addEventListener("click", () => toggleCompare(btn.dataset.compare));
});
}
function toggleCompare(id) {
if (selected.has(id)) {
selected.delete(id);
} else {
if (selected.size >= 3) {
alert("You can compare up to three wearables at once.");
return;
}
selected.add(id);
}
updateCompareBar();
render();
}
function updateCompareBar() {
const list = devices.filter(d => selected.has(d.id));
compareBar.classList.toggle("show", list.length > 0);
compareCount.textContent = `${list.length} selected`;
compareNames.textContent = list.length
? list.map(d => d.name).join(" · ")
: "Choose up to three devices";
}
function openComparison() {
const list = devices.filter(d => selected.has(d.id));
if (list.length < 2) {
alert("Select at least two wearables to compare.");
return;
}
const rows = [
["Type", d => d.type],
["Compatibility", d => d.compatibility.join(" + ")],
["Ecosystem", d => d.ecosystem],
["Battery", d => d.battery],
["Best for", d => d.bestFor],
["Key features", d => d.features],
["Pros", d => d.pros.join(" • ")],
["Cons", d => d.cons.join(" • ")]
];
compareTable.innerHTML = `
<table>
<thead>
<tr>
<th>Compare</th>
${list.map(d => `<th>${safeText(d.name)}</th>`).join("")}
</tr>
</thead>
<tbody>
${rows.map(([label, getter]) => `
<tr>
<td>${safeText(label)}</td>
${list.map(d => `<td>${safeText(getter(d))}</td>`).join("")}
</tr>
`).join("")}
<tr>
<td>Official source</td>
${list.map(d => `
<td>
<a href="${d.source}" target="_blank" rel="noopener noreferrer">Open source ↗</a>
</td>
`).join("")}
</tr>
</tbody>
</table>
`;
compareOverlay.classList.add("open");
document.body.style.overflow = "hidden";
}
function closeComparison() {
compareOverlay.classList.remove("open");
document.body.style.overflow = "";
}
searchInput.addEventListener("input", render);
typeFilter.addEventListener("change", render);
sortFilter.addEventListener("change", render);
document.querySelectorAll(".chip").forEach(chip => {
chip.addEventListener("click", () => {
if (chip.dataset.compat) {
activeCompat = chip.dataset.compat;
activeUse = null;
document.querySelectorAll(".chip").forEach(c => c.classList.remove("active"));
chip.classList.add("active");
} else if (chip.dataset.use) {
const clickedUse = chip.dataset.use;
const wasActive = activeUse === clickedUse;
activeUse = wasActive ? null : clickedUse;
activeCompat = "all";
document.querySelectorAll(".chip").forEach(c => c.classList.remove("active"));
const allPhones = document.querySelector('[data-compat="all"]');
if (activeUse) {
chip.classList.add("active");
} else {
allPhones.classList.add("active");
}
}
render();
});
});
document.getElementById("openCompare").addEventListener("click", openComparison);
document.getElementById("clearCompare").addEventListener("click", () => {
selected.clear();
updateCompareBar();
render();
});
document.getElementById("closeCompare").addEventListener("click", closeComparison);
compareOverlay.addEventListener("click", e => {
if (e.target === compareOverlay) closeComparison();
});
document.addEventListener("keydown", e => {
if (e.key === "Escape" && compareOverlay.classList.contains("open")) {
closeComparison();
}
});
render();
updateCompareBar();
</script>
</body>
</html>