-
-
-
Live
-
Browse XML Schemas
-
- Interactive documentation for all UnitsML schema versions —
- from the current 1.0 release back to the original 0.9 draft.
-
-
@@ -410,8 +266,8 @@ const tickerUnits = [
inset: 0;
pointer-events: none;
background-image:
- linear-gradient(rgba(45,44,105,0.04) 1px, transparent 1px),
- linear-gradient(90deg, rgba(45,44,105,0.04) 1px, transparent 1px);
+ linear-gradient(rgba(45,44,105,0.06) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(45,44,105,0.06) 1px, transparent 1px);
background-size: 48px 48px;
mask-image: radial-gradient(ellipse 80% 70% at 50% 40%, black, transparent);
}
@@ -437,134 +293,67 @@ const tickerUnits = [
pointer-events: none;
}
-/* ── Unit ticker ── */
-.hero-ticker {
- position: relative;
- width: 100%;
- max-width: 600px;
- margin: 0 auto 2rem;
- overflow: hidden;
- -webkit-mask-image: linear-gradient(90deg, transparent, black 15%, black 85%, transparent);
- mask-image: linear-gradient(90deg, transparent, black 15%, black 85%, transparent);
-}
-.ticker-track {
- display: flex;
- gap: 1.5rem;
- animation: ticker-scroll 30s linear infinite;
- width: max-content;
-}
-.ticker-item {
- flex-shrink: 0;
- font-size: 0.8125rem;
- color: var(--vp-c-text-3);
- white-space: nowrap;
- opacity: 0.6;
-}
-.ticker-item code {
- font-size: 0.75rem;
- font-weight: 700;
- color: var(--vp-c-text-2);
- background: var(--vp-c-default-soft);
- padding: 0.1em 0.35em;
- border-radius: 3px;
- margin-right: 0.25rem;
-}
-@keyframes ticker-scroll {
- 0% { transform: translateX(0); }
- 100% { transform: translateX(-50%); }
-}
-
-.hero-shapes {
+/* ── SI dimensional symbols ── */
+.hero-dims {
position: absolute;
inset: 0;
pointer-events: none;
overflow: hidden;
}
-.hero-shape {
+.hero-dim {
position: absolute;
- border: 2px solid var(--unitsml-navy);
- animation: float 6s ease-in-out infinite;
+ font-family: Georgia, 'Times New Roman', serif;
+ font-weight: 300;
+ line-height: 1;
+ color: var(--unitsml-navy);
+ animation: float-dim 8s ease-in-out infinite;
+ user-select: none;
}
-.hero-shape.circle {
- border-radius: 50%;
+@keyframes float-dim {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(-10px); }
}
-.hero-shape.square {
- border-radius: 4px;
- transform: rotate(45deg);
+/* ── Static unit pills ── */
+.hero-pills {
+ display: flex;
+ gap: 0.5rem;
+ justify-content: center;
+ flex-wrap: wrap;
+ margin-top: 2rem;
+ position: relative;
}
-.hero-shape.diamond {
+.hero-pill {
+ font-size: 0.6875rem;
+ font-weight: 700;
+ padding: 0.2rem 0.55rem;
border-radius: 4px;
- transform: rotate(45deg);
-}
-
-/* ── Stats section ── */
-.stats-section {
- padding: 2rem 0;
-}
-
-.stats-grid {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 1.5rem;
-}
-
-.stat-card {
- text-align: center;
- padding: 2rem 1rem;
- background: var(--vp-c-bg-soft);
- border: 1px solid var(--vp-c-divider);
- border-radius: 16px;
- transition: all 0.4s ease;
- opacity: 0;
- transform: translateY(16px);
-}
-
-.stat-card.visible {
- opacity: 1;
- transform: translateY(0);
+ background: var(--vp-c-default-soft);
+ color: var(--vp-c-text-3);
+ opacity: 0.5;
+ transition: opacity 0.2s;
}
-.stat-card:nth-child(2) { transition-delay: 80ms; }
-.stat-card:nth-child(3) { transition-delay: 160ms; }
-.stat-card:nth-child(4) { transition-delay: 240ms; }
-
-.stat-card:hover {
- border-color: var(--vp-c-brand-1);
- box-shadow: 0 4px 20px rgba(45, 44, 105, 0.08);
+.hero-pill:hover {
+ opacity: 0.8;
}
-.stat-icon {
- color: var(--vp-c-brand-1);
- margin-bottom: 0.75rem;
- display: flex;
- justify-content: center;
-}
-
-.stat-number {
- font-size: 2.5rem;
- font-weight: 800;
- line-height: 1;
- margin-bottom: 0.5rem;
- background: linear-gradient(135deg, var(--unitsml-navy), var(--unitsml-teal-dark));
+/* ── Hero accent gradient animation ── */
+.home-hero h1 .accent {
+ background: linear-gradient(135deg, var(--unitsml-navy) 0%, var(--unitsml-teal-dark) 50%, var(--unitsml-blue) 100%);
+ background-size: 200% 200%;
+ animation: gradient-shift 8s ease infinite;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
- font-variant-numeric: tabular-nums;
-}
-
-.stat-suffix {
- font-size: 1.5rem;
- font-weight: 600;
}
-.stat-label {
- font-size: 0.8125rem;
- color: var(--vp-c-text-2);
- font-weight: 500;
+@keyframes gradient-shift {
+ 0%, 100% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
}
/* ── UnitsDB CTA ── */
@@ -748,6 +537,12 @@ const tickerUnits = [
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
+ animation: step-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+@keyframes step-pop {
+ 0% { transform: scale(0.9); }
+ 100% { transform: scale(1); }
}
.step-tab-text {
@@ -780,6 +575,12 @@ const tickerUnits = [
min-height: 260px;
}
+/* Step fade transition */
+.step-fade-enter-active,
+.step-fade-leave-active { transition: opacity 0.2s ease; }
+.step-fade-enter-from,
+.step-fade-leave-to { opacity: 0; }
+
.code-header {
display: flex;
align-items: center;
@@ -796,6 +597,25 @@ const tickerUnits = [
letter-spacing: 0.05em;
}
+.code-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.code-copy-btn {
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 2px;
+ color: rgba(255,255,255,0.3);
+ transition: color 0.15s;
+}
+
+.code-copy-btn:hover {
+ color: rgba(255,255,255,0.7);
+}
+
.code-dots {
display: flex;
gap: 5px;
@@ -829,52 +649,71 @@ const tickerUnits = [
.step-code :deep(.xml-val) { color: #98c379; }
.step-code :deep(.xml-comment) { color: #5c6370; font-style: italic; }
-/* ── Schema card ── */
-.schema-card {
- position: relative;
- overflow: hidden;
+/* ── CTA Strip ── */
+.cta-strip {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1.25rem;
}
-.schema-grid-bg {
- position: absolute;
- inset: 0;
- background-image:
- linear-gradient(rgba(255,255,255,0.02) 1px, transparent 1px),
- linear-gradient(90deg, rgba(255,255,255,0.02) 1px, transparent 1px);
- background-size: 32px 32px;
- pointer-events: none;
+.cta-strip-card {
+ display: flex;
+ flex-direction: column;
+ padding: 2rem;
+ background: var(--vp-c-bg-soft);
+ border: 1px solid var(--vp-c-divider);
+ border-radius: 16px;
+ text-decoration: none;
+ color: inherit;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
-.schema-content {
- position: relative;
- z-index: 1;
+.cta-strip-card:hover {
+ border-color: var(--unitsml-navy);
+ box-shadow: 0 8px 30px rgba(45, 44, 105, 0.10);
+ transform: translateY(-2px);
}
-.schema-badge {
- display: inline-block;
- font-size: 0.6875rem;
- font-weight: 700;
- text-transform: uppercase;
- letter-spacing: 0.08em;
- padding: 0.25rem 0.75rem;
- border-radius: 4px;
- background: rgba(48, 223, 192, 0.15);
- color: var(--unitsml-teal-light);
+.cta-strip-icon {
+ width: 44px;
+ height: 44px;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
margin-bottom: 1rem;
+ background: linear-gradient(135deg, rgba(45, 44, 105, 0.08), rgba(87, 160, 254, 0.08));
+ color: var(--unitsml-navy);
}
-.schema-url {
- display: flex;
- align-items: center;
- gap: 0.5rem;
+.cta-strip-card h4 {
+ font-size: 1.0625rem;
+ font-weight: 600;
+ color: var(--vp-c-text-1);
+ margin-bottom: 0.5rem;
+}
+
+.cta-strip-card p {
+ font-size: 0.875rem;
+ color: var(--vp-c-text-2);
+ line-height: 1.6;
+ margin-bottom: 1rem;
+ flex: 1;
+}
+
+.cta-strip-link {
+ font-size: 0.8125rem;
+ font-weight: 600;
+ color: var(--vp-c-brand-1);
+ transition: color 0.2s;
+}
+
+.cta-strip-card:hover .cta-strip-link {
+ color: var(--unitsml-teal-dark);
}
/* ── Responsive ── */
@media (max-width: 768px) {
- .stats-grid {
- grid-template-columns: repeat(2, 1fr);
- }
-
.steps-layout {
grid-template-columns: 1fr;
}
@@ -883,11 +722,15 @@ const tickerUnits = [
position: static;
overflow: hidden;
}
-}
-@media (max-width: 480px) {
- .stats-grid {
+ .cta-strip {
grid-template-columns: 1fr;
}
}
+
+@media (prefers-reduced-motion: reduce) {
+ .hero-dim { animation: none !important; }
+ .home-hero h1 .accent { animation: none !important; }
+ .step-tab.active .step-tab-num { animation: none !important; }
+}
diff --git a/.vitepress/theme/components/NavScrollHandler.vue b/.vitepress/theme/components/NavScrollHandler.vue
index 552ae9c..fbd8bfd 100644
--- a/.vitepress/theme/components/NavScrollHandler.vue
+++ b/.vitepress/theme/components/NavScrollHandler.vue
@@ -76,9 +76,21 @@ function init() {
handleScroll()
}
+let scrollRevealObserver: IntersectionObserver | null = null
+
onMounted(() => {
init()
window.addEventListener('scroll', handleScroll, { passive: true })
+
+ scrollRevealObserver = new IntersectionObserver((entries) => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ entry.target.classList.add('visible')
+ scrollRevealObserver?.unobserve(entry.target)
+ }
+ })
+ }, { threshold: 0.1 })
+ document.querySelectorAll('.reveal').forEach(el => scrollRevealObserver!.observe(el))
})
onUnmounted(() => {
diff --git a/.vitepress/theme/components/UnitsDBBrowser.vue b/.vitepress/theme/components/UnitsDBBrowser.vue
index a93c584..6b72870 100644
--- a/.vitepress/theme/components/UnitsDBBrowser.vue
+++ b/.vitepress/theme/components/UnitsDBBrowser.vue
@@ -187,6 +187,7 @@ async function dlDataset() {
function onKeydown(e: KeyboardEvent) {
if (e.key === '/' && document.activeElement?.tagName !== 'INPUT') { e.preventDefault(); searchInput.value?.focus() }
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); searchInput.value?.focus() }
if (e.key === 'Escape' && q.value) { q.value = ''; searchInput.value?.blur() }
}
onMounted(() => window.addEventListener('keydown', onKeydown))
@@ -213,8 +214,8 @@ onUnmounted(() => {
-
-
+
+
{{ cnt(tab.key) }}
{{ tab.label }}
@@ -246,7 +247,7 @@ onUnmounted(() => {
-
+
Symbol Name UnitsML ID Dimension Type Quantity
@@ -291,7 +292,7 @@ onUnmounted(() => {
-
+
Name UnitsML ID Type Dimension Units
@@ -307,7 +308,7 @@ onUnmounted(() => {
-
+
Name UnitsML ID Expression Quantities Dim-less
@@ -323,7 +324,7 @@ onUnmounted(() => {
-
+
Symbol Name UnitsML ID Factor Value
@@ -339,7 +340,7 @@ onUnmounted(() => {
-
+
Name UnitsML ID Properties
@@ -353,7 +354,7 @@ onUnmounted(() => {
-
+
Name UnitsML ID Short Units Acceptable
@@ -421,6 +422,12 @@ onUnmounted(() => {
}
.stat-pill:hover { background: var(--vp-c-bg); border-color: var(--vp-c-divider); color: var(--vp-c-text-2); }
.stat-pill.active { background: var(--vp-c-brand-1); color: #fff; border-color: var(--vp-c-brand-1); }
+.stat-pill[data-type="units"].active { background: #14b8a6; border-color: #14b8a6; }
+.stat-pill[data-type="quantities"].active { background: #57a0fe; border-color: #57a0fe; }
+.stat-pill[data-type="dimensions"].active { background: #2d2c69; border-color: #2d2c69; }
+.stat-pill[data-type="prefixes"].active { background: #8b5cf6; border-color: #8b5cf6; }
+.stat-pill[data-type="scales"].active { background: #f59e0b; border-color: #f59e0b; color: #1f1e4a; }
+.stat-pill[data-type="unit_systems"].active { background: #6b7280; border-color: #6b7280; }
.sp-val { font-weight: 700; font-size: 0.875rem; }
.sp-lbl { font-size: 0.6875rem; }
.stat-divider { display: none; width: 1px; height: 1.5rem; background: var(--vp-c-divider); flex-shrink: 0; }
diff --git a/.vitepress/theme/components/UnitsDBEntityDetail.vue b/.vitepress/theme/components/UnitsDBEntityDetail.vue
index fd2f8c5..50587b2 100644
--- a/.vitepress/theme/components/UnitsDBEntityDetail.vue
+++ b/.vitepress/theme/components/UnitsDBEntityDetail.vue
@@ -545,6 +545,11 @@ async function copyLink() {
+
+
+ ← {{ TYPE_PLURAL[entityType] }}
+
+
@@ -778,6 +783,8 @@ async function copyLink() {
.ep-auth-ucum { border-color: rgba(87,160,254,0.3); }
.ep-auth-qudt { border-color: rgba(139,92,246,0.3); }
.ep-auth-nist { background: rgba(45,44,105,0.1); border-color: rgba(45,44,105,0.3); color: #2d2c69; }
+.dark .ep-auth-badge { background: var(--vp-c-bg-soft); }
+.dark .ep-auth-nist { color: var(--vp-c-brand-1); background: rgba(110,109,186,0.15); }
.ep-auth-label { font-size: 0.75rem; font-weight: 500; color: var(--vp-c-text-1); }
/* Names */
@@ -801,6 +808,23 @@ async function copyLink() {
.ep-json-view { margin-top: 0.5rem; border: 1px solid var(--vp-c-divider); border-radius: 6px; overflow: hidden; }
.ep-json-view pre { margin: 0; padding: 0.75rem; font-size: 0.6875rem; line-height: 1.5; overflow-x: auto; background: var(--vp-c-bg-soft); }
+/* Floating back button (mobile only) */
+.ep-back-float {
+ display: none;
+ position: fixed; bottom: 1rem; left: 50%; transform: translateX(-50%);
+ padding: 0.5rem 1.25rem; border-radius: 999px;
+ background: var(--vp-c-brand-1); color: white;
+ font-size: 0.8125rem; font-weight: 600; text-decoration: none;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 50;
+ align-items: center; gap: 0.3rem;
+ transition: background 0.2s;
+}
+.ep-back-float:hover { background: var(--vp-c-brand-2); color: white; }
+
+@media (max-width: 768px) {
+ .ep-back-float { display: inline-flex; }
+}
+
/* ── Desktop overrides ── */
@media (min-width: 640px) {
diff --git a/.vitepress/theme/custom.css b/.vitepress/theme/custom.css
index e9db4fa..3355596 100644
--- a/.vitepress/theme/custom.css
+++ b/.vitepress/theme/custom.css
@@ -88,12 +88,19 @@
}
.home-hero h1 .accent {
- background: linear-gradient(135deg, var(--unitsml-navy) 0%, var(--unitsml-teal-dark) 100%);
+ background: linear-gradient(135deg, var(--unitsml-navy) 0%, var(--unitsml-teal-dark) 50%, var(--unitsml-blue) 100%);
+ background-size: 200% 200%;
+ animation: gradient-shift 8s ease infinite;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
+@keyframes gradient-shift {
+ 0%, 100% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+}
+
.home-hero .tagline {
font-size: 1.25rem;
color: var(--vp-c-text-2);
@@ -178,7 +185,7 @@
========================================================================== */
.section {
- margin: 5rem 0;
+ margin: 6rem 0;
}
.section-header {
@@ -508,16 +515,6 @@
Animations & Motion
========================================================================== */
-@keyframes float {
- 0%, 100% { transform: translateY(0) rotate(0deg); }
- 50% { transform: translateY(-12px) rotate(3deg); }
-}
-
-@keyframes float-delayed {
- 0%, 100% { transform: translateY(0) rotate(0deg); }
- 50% { transform: translateY(-8px) rotate(-2deg); }
-}
-
@keyframes pulse-glow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
@@ -731,3 +728,62 @@ body {
.unitsdb-full .vp-doc > div > h1 {
display: none;
}
+
+/* ==========================================================================
+ Scroll Reveal
+ ========================================================================== */
+
+.reveal {
+ opacity: 0;
+ transform: translateY(20px);
+ transition: opacity 0.5s ease-out, transform 0.5s ease-out;
+}
+
+.reveal.visible {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+/* ==========================================================================
+ Focus Indicators
+ ========================================================================== */
+
+.btn:focus-visible,
+.step-tab:focus-visible,
+.tab-btn:focus-visible,
+.cta-strip-card:focus-visible {
+ outline: 2px solid var(--vp-c-brand-1);
+ outline-offset: 2px;
+}
+
+/* ==========================================================================
+ Reduced Motion
+ ========================================================================== */
+
+@media (prefers-reduced-motion: reduce) {
+ .home-hero h1 .accent,
+ .hero-dim,
+ .step-tab.active .step-tab-num,
+ .stat-card,
+ .feature-card,
+ .project-card,
+ .section[data-revealed],
+ .ticker-track {
+ animation: none !important;
+ }
+
+ .btn,
+ .step-tab,
+ .cta-strip-card,
+ .project-card,
+ .feature-card {
+ transition: none !important;
+ }
+
+ .stat-card,
+ .section[data-revealed],
+ .reveal {
+ opacity: 1 !important;
+ transform: none !important;
+ }
+}
diff --git a/TODO.improve/01-hero-polish.md b/TODO.improve/01-hero-polish.md
new file mode 100644
index 0000000..7dbc5dc
--- /dev/null
+++ b/TODO.improve/01-hero-polish.md
@@ -0,0 +1,117 @@
+# 01 — Hero Polish
+
+## Problem
+
+The hero has visual noise that breaks reading flow and dilutes the primary message.
+
+### Issues
+
+1. **Ticker above headline breaks reading flow** — The scrolling unit ticker sits *between* the logo and the ``, so the natural scan path (logo → headline → tagline → CTA) is interrupted by a peripheral animation competing for attention. It adds motion without adding comprehension.
+
+2. **Three CTAs dilute the primary action** — "Explore UnitsDB", "View Schemas", "Learn about UnitsML". Three equal-weight buttons give no clear next step. The user's primary question is "what is this?" — not "take me to the database".
+
+3. **Floating shapes are generic noise** — Circles, squares, and diamonds don't connect to UnitsML's domain (scientific units). They add animation for animation's sake and make the hero feel like a generic SaaS template.
+
+4. **Grid background is too subtle to notice** — The 48px grid lines at 4% opacity are nearly invisible. Either commit to the grid aesthetic or remove it.
+
+## Plan
+
+### Step 1: Restructure hero visual hierarchy
+
+Reorder: logo → headline → tagline → CTAs → ambient decoration. The ticker moves below CTAs as a subtle "pills" strip (not scrolling — static chips showing `m kg s A K mol cd N J W Pa`).
+
+**File**: `HomePage.vue` lines ~146–201 (template), ~440–475 (ticker CSS)
+
+- Remove `hero-ticker` scrolling ticker from between logo and ``
+- Replace with a static "SI unit pills" strip *below* the CTAs:
+ ```
+ m · kg · s · A · K · mol · cd · N · J · W · Pa · Hz · V · Ω
+ ```
+ Rendered as small inline `` chips, horizontally centered, subtle opacity (0.5), no animation. This communicates "we deal with scientific units" without breaking flow.
+
+### Step 2: Reduce CTAs to 2
+
+**File**: `HomePage.vue` lines ~193–200
+
+Replace:
+```html
+Explore UnitsDB
+View Schemas
+Learn about UnitsML
+```
+
+With:
+```html
+
+ Learn about UnitsML
+
+
+Explore UnitsDB
+```
+
+Rationale: First-time visitors need context before they explore a database. "Learn about UnitsML" is the natural first step; "Explore UnitsDB" is the secondary action for returning users or the curious. "View Schemas" is reachable from Learn dropdown and from the Schema Browser CTA at the bottom.
+
+### Step 3: Replace floating shapes with domain-specific ambient decoration
+
+**File**: `HomePage.vue` lines ~107–114 (shapes data), ~477–502 (shapes CSS)
+
+Replace the 6 generic geometric shapes with 7 subtle SI base quantity symbols floating at low opacity:
+
+```
+L M T I Θ N J
+```
+
+These are the actual dimensional symbols (Length, Mass, Time, Electric current, Temperature, Amount, Luminous intensity) — they're meaningful to the domain and signal "this is about scientific dimensions" even before the user reads a single word.
+
+- Render as large, thin, serif text (font: Georgia or similar, weight 300, size ~40px)
+- Very low opacity (0.04–0.08)
+- Gentle float animation (existing `float` keyframes, slower — 8–12s)
+- No border, just text
+
+### Step 4: Strengthen or remove grid background
+
+**File**: `HomePage.vue` lines ~408–417
+
+Option A (strengthen): Increase grid opacity from 0.04 → 0.06, add a subtle radial gradient overlay that creates a "blueprint paper" feel.
+
+Option B (remove): Delete the grid entirely. The glow effects + dimensional symbols provide enough visual interest.
+
+Recommendation: Option A — the grid subtly reinforces "precision/standards" messaging.
+
+### Step 5: Add gradient text animation to headline accent
+
+**File**: `HomePage.vue` or `custom.css`
+
+Add a slow gradient shift on `.home-hero h1 .accent`:
+
+```css
+.home-hero h1 .accent {
+ background: linear-gradient(135deg, var(--unitsml-navy) 0%, var(--unitsml-teal-dark) 50%, var(--unitsml-blue) 100%);
+ background-size: 200% 200%;
+ animation: gradient-shift 8s ease infinite;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+}
+@keyframes gradient-shift {
+ 0%, 100% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+}
+```
+
+This adds a subtle, living quality to the hero without being distracting. The gradient shifts between navy → teal → blue, reinforcing the brand palette.
+
+## Files Modified
+
+| File | Change |
+|------|--------|
+| `HomePage.vue` | Remove ticker, add static pills, reduce CTAs, replace shapes with dimensional symbols |
+| `custom.css` | Add `gradient-shift` keyframe animation for hero accent text |
+
+## Verification
+
+1. Hero loads cleanly: logo → headline (with animated gradient accent) → tagline → 2 CTAs → unit pills
+2. No scrolling animation above the fold
+3. Floating dimensional symbols visible at very low opacity
+4. Both CTAs are clearly differentiated (brand primary + teal secondary)
+5. Mobile: unit pills wrap, CTAs stack vertically (already handled)
diff --git a/TODO.improve/02-homepage-section-reduction.md b/TODO.improve/02-homepage-section-reduction.md
new file mode 100644
index 0000000..0342730
--- /dev/null
+++ b/TODO.improve/02-homepage-section-reduction.md
@@ -0,0 +1,124 @@
+# 02 — Homepage Section Reduction
+
+## Problem
+
+The homepage has 8 sections, which creates scroll fatigue and buries important content. Users arriving at the homepage need a concise narrative arc, not an encyclopedic tour.
+
+### Current sections (in order)
+
+1. Hero (logo, ticker, headline, tagline, 3 CTAs)
+2. Stats (4 animated counter cards)
+3. UnitsDB CTA (dark card with preview window)
+4. Ecosystem Diagram (interactive SVG)
+5. How UnitsML Works (3-step tabbed interface)
+6. Software (project cards — currently only unitsml-ruby)
+7. Why UnitsML? (4 feature cards)
+8. Schema Browser CTA (dark card linking to schema.unitsml.org)
+
+### Issues
+
+1. **Stats (#2) and UnitsDB CTA (#3) are redundant** — both prominently display unit counts (380+, 199, 92, etc.). The stats section exists only to show animated counters; the UnitsDB CTA already communicates the same information with more context.
+
+2. **Software (#6) is anticlimactic** — showing a single card ("unitsml-ruby") in a dedicated "Software" section feels thin. This belongs on `/software/`.
+
+3. **Why UnitsML? (#7) duplicates learn content** — the 4 feature cards restate content from `/learn/what-is-unitsml`. Homepage visitors don't need to read the same arguments twice.
+
+4. **Schema Browser CTA (#8) is heavyweight for an external link** — the full-width dark card is visually on par with the UnitsDB CTA (#3), but it just links to schema.unitsml.org. It should be lighter.
+
+5. **No clear narrative arc** — the sections don't build on each other. It reads like a collection of promotional blocks, not a guided story.
+
+## Plan
+
+### Target: 5 sections
+
+1. **Hero** (cleaned up per TODO 01)
+2. **UnitsDB CTA** (absorbs stats data)
+3. **Ecosystem Diagram**
+4. **How UnitsML Works**
+5. **Closing CTA strip** (replaces Software, Why UnitsML, and Schema Browser)
+
+### Step 1: Remove Stats section, absorb into UnitsDB CTA
+
+**File**: `HomePage.vue`
+
+- Delete the entire `` section (lines ~206–221) and its CSS
+- Remove `statsRef`, `statsVisible`, `displayValues`, `animateCounters`, `statsObserver` from script
+- The UnitsDB CTA already has `cta-types` chips showing counts (380 Units, 199 Quantities, 92 Dimensions, 33 Prefixes, 7 Systems). No data loss.
+
+### Step 2: Remove Software section
+
+**File**: `HomePage.vue`
+
+- Delete the `` section (lines ~313–335)
+- Remove `projects` import
+- Software lives at `/software/` — accessible from nav bar. Homepage doesn't need to repeat it.
+
+### Step 3: Remove Why UnitsML section
+
+**File**: `HomePage.vue`
+
+- Delete the `` section (lines ~340–376)
+- The 4 feature card arguments (Unambiguous Encoding, Composable, SI & Non-SI, Standards-Based) are already in `/learn/what-is-unitsml.html`
+
+### Step 4: Replace Schema Browser CTA with a compact closing CTA strip
+
+**File**: `HomePage.vue`
+
+Replace the full-width dark `schema-card` (lines ~381–403) with a lightweight 3-column CTA strip:
+
+```
+┌─────────────────┬─────────────────┬─────────────────┐
+│ 📄 Schemas │ 💎 Software │ 📖 Learn │
+│ Browse XML & │ unitsml-ruby │ What is UnitsML│
+│ YAML schemas │ and more │ and more │
+│ [Open →] │ [View →] │ [Read →] │
+└─────────────────┴─────────────────┴─────────────────┘
+```
+
+- Light background (`var(--vp-c-bg-soft)`)
+- 1px border, rounded corners
+- Each column: icon + title + 1-line description + text link
+- Columns link to: `/schemas.html`, `/software/`, `/learn/what-is-unitsml.html`
+- This gives visitors 3 clear next steps at the bottom without heavy visual weight
+
+### Step 5: Adjust section spacing
+
+With fewer sections, increase spacing between remaining sections for breathing room:
+
+- Change `.section { margin: 5rem 0; }` → `.section { margin: 6rem 0; }` in `custom.css`
+- The homepage should feel spacious, not cramped
+
+## Resulting homepage structure
+
+```
+┌─────────────────────────────────────┐
+│ HERO │ ← Cleaned per TODO 01
+│ Logo + headline + tagline + 2 CTAs │
+│ Static unit pills strip │
+├─────────────────────────────────────┤
+│ UNITSDB CTA │ ← Dark card with preview
+│ 380+ units, 199 quantities... │ (stats absorbed here)
+├─────────────────────────────────────┤
+│ ECOSYSTEM DIAGRAM │ ← Unchanged
+├─────────────────────────────────────┤
+│ HOW UNITSML WORKS │ ← Unchanged (3-step tabs)
+├─────────────────────────────────────┤
+│ CTA STRIP │ ← NEW: Schemas | Software | Learn
+└─────────────────────────────────────┘
+```
+
+## Files Modified
+
+| File | Change |
+|------|--------|
+| `HomePage.vue` | Remove Stats, Software, Why UnitsML, Schema Browser sections; add CTA strip |
+| `custom.css` | Adjust section spacing |
+
+## Verification
+
+1. Homepage has exactly 5 distinct sections
+2. No duplicate information (stats vs UnitsDB counts)
+3. Scroll depth is manageable (~3 viewport heights total)
+4. Every section serves a distinct purpose in the narrative arc
+5. CTA strip provides 3 clear navigation paths
+6. Mobile: CTA strip stacks vertically
diff --git a/TODO.improve/03-navigation-restructure.md b/TODO.improve/03-navigation-restructure.md
new file mode 100644
index 0000000..ffaab3a
--- /dev/null
+++ b/TODO.improve/03-navigation-restructure.md
@@ -0,0 +1,123 @@
+# 03 — Navigation & Information Architecture
+
+## Problem
+
+The nav bar is flat (6 items), Schemas needs prominent placement, and the sidebar doesn't cover UnitsDB or Schemas pages well. Users need clear paths to the two most important resources: UnitsDB and Schemas.
+
+## Current Nav
+
+```
+UnitsDB · Schemas · Learn ▾ · Software · Blog · About
+```
+
+### Issues
+
+1. **Schemas page has no sidebar** — the sidebar config has `/schemas` but it's a single-item sidebar. The schemas page covers both UnitsML XML Schemas and UnitsDB YAML Schemas, with subsections that would benefit from a sidebar TOC.
+2. **UnitsDB page has no sidebar** — by design (full-width layout), but once inside an entity detail page, there's no way to navigate to another entity type except the breadcrumb back to UnitsDB index.
+3. **Blog has no sidebar** — blog posts use the default VitePress behavior. Not critical, but the blog index could benefit from a "Recent posts" sidebar.
+
+## Plan
+
+### Step 1: Keep current nav bar as-is
+
+```
+UnitsDB · Schemas · Learn ▾ · Software · Blog · About
+```
+
+Schemas stays top-level because:
+- Schema users arrive at the site specifically to understand or reference schemas
+- Schemas is a core product alongside UnitsDB
+- It's a high-value destination for developers integrating UnitsML
+
+### Step 2: Improve Schemas sidebar
+
+**File**: `.vitepress/config.ts`
+
+Expand the `/schemas` sidebar to include anchor links for page sections:
+
+```ts
+'/schemas': [
+ {
+ text: 'Schemas',
+ items: [
+ { text: 'Overview', link: '/schemas' },
+ { text: 'UnitsML XML Schemas', link: '/schemas#unitsml-xml-schemas' },
+ { text: 'UnitsDB YAML Schemas', link: '/schemas#unitsdb-yaml-schemas' },
+ { text: 'Schema Browser', link: '/schemas#schema-browser' },
+ ]
+ }
+]
+```
+
+### Step 3: Add Schemas page anchor IDs
+
+**File**: `schemas.md`
+
+Ensure each major section has a proper heading with an ID that the sidebar can link to:
+- `## UnitsML XML Schemas` (already exists)
+- `## UnitsDB YAML Schemas` (already exists)
+- `## Schema Browser` or similar for the external schema browser section
+
+### Step 4: Improve UnitsDB sub-navigation
+
+The UnitsDB browser already has entity-type tabs (Units, Quantities, Dimensions, Prefixes, Scales, Systems). For entity detail pages, the breadcrumb provides navigation back. No structural change needed, but add a subtle "back to listing" floating button on mobile for easier navigation.
+
+**File**: `UnitsDBEntityDetail.vue`
+
+Add a floating back button visible on mobile:
+```html
+
+ ← {{ TYPE_PLURAL[entityType] }}
+
+```
+
+```css
+.ep-back-float {
+ display: none; /* hidden on desktop */
+ position: fixed; bottom: 1rem; left: 50%; transform: translateX(-50%);
+ padding: 0.5rem 1.25rem; border-radius: 999px;
+ background: var(--vp-c-brand-1); color: white;
+ font-size: 0.8125rem; font-weight: 600; text-decoration: none;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 50;
+}
+@media (max-width: 768px) {
+ .ep-back-float { display: inline-flex; }
+}
+```
+
+### Step 5: Add "Get Started" to the top-level nav or hero
+
+"Get Started" is currently buried in the Learn dropdown. Since it's a key conversion page, ensure it's prominent:
+- Keep in Learn dropdown (current)
+- Add as a secondary CTA in the hero (done in TODO 01 — "Learn about UnitsML" links to `/learn/what-is-unitsml.html`)
+- Add a "Quick Start" link in the footer
+
+**File**: `.vitepress/config.ts` footer
+
+```ts
+footer: {
+ message: `UnitsDB · Schemas · Learn · Get Started · GitHub `,
+}
+```
+
+### Step 6: Ensure footer links are comprehensive
+
+Current footer: `UnitsDB · Schemas · Learn · GitHub`
+Updated footer: `UnitsDB · Schemas · Learn · Get Started · Software · GitHub`
+
+**File**: `.vitepress/config.ts`
+
+## Files Modified
+
+| File | Change |
+|------|--------|
+| `.vitepress/config.ts` | Expand schemas sidebar, update footer links |
+| `schemas.md` | Verify anchor IDs exist for sidebar linking |
+| `UnitsDBEntityDetail.vue` | Add mobile floating back button |
+
+## Verification
+
+1. Schemas page has a sidebar with section links
+2. Footer has all key navigation links
+3. Mobile: floating back button appears on entity detail pages
+4. Nav bar unchanged (Schemas stays top-level)
diff --git a/TODO.improve/04-unitsdb-browser-ux.md b/TODO.improve/04-unitsdb-browser-ux.md
new file mode 100644
index 0000000..17ee3c5
--- /dev/null
+++ b/TODO.improve/04-unitsdb-browser-ux.md
@@ -0,0 +1,158 @@
+# 04 — UnitsDB Browser UX
+
+## Problem
+
+The UnitsDB browser is functional but lacks polish that would make it feel like a professional data explorer. Specific friction points: search has no affordances, table rows are informationally dense but visually flat, and the stats bar doesn't convey the richness of the dataset.
+
+## Current State
+
+- Top: stats bar showing entity type counts + search box
+- Tabs: Units | Quantities | Dimensions | Prefixes | Scales | Systems
+- Content: scrollable data tables with sortable-looking (but not sortable) columns
+- Each row links to an entity detail page
+
+## Plan
+
+### Step 1: Search UX improvements
+
+**File**: `UnitsDBBrowser.vue`
+
+1. **Add a clear button** (×) inside the search input when it has a value:
+
+```html
+
+
+
+ ×
+
+```
+
+```css
+.search-clear {
+ position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
+ background: none; border: none; cursor: pointer;
+ color: var(--vp-c-text-3); font-size: 1rem; padding: 0 4px;
+}
+.search-clear:hover { color: var(--vp-c-text-1); }
+```
+
+2. **Add keyboard shortcut hint** — show `⌘K` / `Ctrl+K` placeholder text:
+ ```
+ placeholder="Search units, quantities… (⌘K)"
+ ```
+
+3. **Add keyboard listener** — `Cmd/Ctrl+K` focuses the search box:
+```ts
+onMounted(() => {
+ document.addEventListener('keydown', (e) => {
+ if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
+ e.preventDefault()
+ searchInputRef.value?.focus()
+ }
+ })
+})
+```
+
+### Step 2: Table row hover preview tooltip
+
+**File**: `UnitsDBBrowser.vue`
+
+On hover over a table row, show a subtle tooltip with the entity's dimension formula (for units) or description snippet:
+
+```html
+
+```
+
+This is a zero-JS enhancement — just the native `title` attribute. For richer tooltips, use the existing `.tooltip::after` CSS pattern from `custom.css`.
+
+### Step 3: Inline dimension formula rendering in Units table
+
+**File**: `UnitsDBBrowser.vue`
+
+The units table currently shows: Symbol | Name | ID | Type
+
+Add a "Dimension" column that shows the expression (e.g., `L·M·T⁻²`) inline. This is the most useful piece of information for units — it tells you at a glance what physical quantity the unit measures.
+
+The expression data is already in the JSON (from `dimensions.json` via the dimension reference). Need to resolve the cross-reference at render time:
+
+```ts
+const dimensionMap = computed(() => {
+ const dims = store.value.dimensions ?? []
+ const map: Record = {}
+ for (const d of dims) map[d.id] = d.expression
+ return map
+})
+```
+
+Then in the units table template, add a column:
+```html
+{{ dimensionMap[item.dimension?.id] || '—' }}
+```
+
+### Step 4: Visual differentiation for entity types
+
+**File**: `UnitsDBBrowser.vue`
+
+The 6 tab buttons look identical. Add a subtle color accent per type:
+- Units: teal
+- Quantities: blue
+- Dimensions: navy
+- Prefixes: purple
+- Scales: orange
+- Systems: gray
+
+Apply via a class per tab:
+```css
+.tab-btn[data-type="units"] { --tab-accent: var(--unitsml-teal); }
+.tab-btn[data-type="quantities"] { --tab-accent: var(--unitsml-blue); }
+.tab-btn[data-type="dimensions"] { --tab-accent: var(--unitsml-navy); }
+/* etc. */
+.tab-btn.active { border-bottom-color: var(--tab-accent); color: var(--tab-accent); }
+```
+
+### Step 5: Results count indicator
+
+**File**: `UnitsDBBrowser.vue`
+
+Show "Showing X of Y units" below the search box when a search is active:
+```html
+
+ Showing {{ filteredItems.length }} of {{ items.length }} {{ tabLabel }}
+
+```
+
+```css
+.search-results-count {
+ font-size: 0.75rem; color: var(--vp-c-text-3);
+ margin-top: 0.5rem; text-align: center;
+}
+```
+
+### Step 6: Empty state for search
+
+**File**: `UnitsDBBrowser.vue`
+
+When search yields no results, show a friendly empty state:
+```html
+
+
No {{ tabLabel.toLowerCase() }} matching "{{ search }} "
+
Clear search
+
+```
+
+## Files Modified
+
+| File | Change |
+|------|--------|
+| `UnitsDBBrowser.vue` | Search clear button, keyboard shortcut, dimension column, type colors, results count, empty state |
+
+## Verification
+
+1. `⌘K` / `Ctrl+K` focuses search
+2. Search clear button appears when text is entered
+3. Units table shows dimension expression column
+4. Tab buttons have per-type color accents
+5. "Showing X of Y" appears during search
+6. Empty state shows when no results match
+7. Mobile: all features work with touch
diff --git a/TODO.improve/05-interactivity-animation.md b/TODO.improve/05-interactivity-animation.md
new file mode 100644
index 0000000..eef9656
--- /dev/null
+++ b/TODO.improve/05-interactivity-animation.md
@@ -0,0 +1,166 @@
+# 05 — Interactivity & Animation
+
+## Problem
+
+The site has some animation but lacks consistency. Steps swap without transitions, ecosystem diagram has no pointer feedback, and content pages feel static. Motion should guide attention, not decorate.
+
+## Plan
+
+### Step 1: Fade transition on How It Works code panel
+
+**File**: `HomePage.vue`
+
+The step code panel swaps instantly when tabs change. Add a Vue `` with a 200ms fade:
+
+```html
+
+
+
+```
+
+```css
+.step-fade-enter-active,
+.step-fade-leave-active { transition: opacity 0.2s ease; }
+.step-fade-enter-from,
+.step-fade-leave-to { opacity: 0; }
+```
+
+Also animate the tab indicator — the large step number text should have a subtle scale pulse when selected:
+```css
+.step-tab.active .step-tab-num {
+ animation: step-pop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+@keyframes step-pop {
+ 0% { transform: scale(0.9); }
+ 100% { transform: scale(1); }
+}
+```
+
+### Step 2: EcosystemDiagram pointer cursor + tooltip
+
+**File**: `EcosystemDiagram.vue`
+
+1. Add `cursor: pointer` to clickable nodes:
+
+In the SVG node rendering, add a conditional class:
+```html
+
+```
+
+```css
+.node.clickable { cursor: pointer; }
+```
+
+2. Add a tooltip on hover showing the description:
+
+```html
+
+
+ {{ node.desc }}
+
+```
+
+Style the tooltip rect with a subtle shadow and the text with small font.
+
+### Step 3: Scroll-reveal on content pages
+
+**File**: `custom.css`
+
+Add a universal scroll-reveal animation class that can be applied to sections on content pages:
+
+```css
+.reveal {
+ opacity: 0;
+ transform: translateY(20px);
+ transition: opacity 0.5s ease-out, transform 0.5s ease-out;
+}
+
+.reveal.visible {
+ opacity: 1;
+ transform: translateY(0);
+}
+```
+
+Then add an IntersectionObserver in a `NavScrollHandler.vue` or directly in the VitePress theme's `index.ts` that activates `.reveal` elements as they enter the viewport.
+
+### Step 4: Stat cards — add a subtle pulse on hover
+
+**File**: `custom.css`
+
+The stat cards already animate in. Add a hover state:
+```css
+.stat-card:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 8px 24px rgba(45, 44, 105, 0.12);
+}
+```
+
+The card already has `transition: all 0.4s ease;` so this works without adding a new transition.
+
+### Step 5: Ecosystem diagram — animate connection lines on hover
+
+**File**: `EcosystemDiagram.vue`
+
+The connection paths already use SVG stroke-dasharray. Animate the stroke when a node is hovered:
+
+```css
+.connection path {
+ stroke-dasharray: 6;
+ animation: dash-flow 1s linear infinite;
+ animation-play-state: paused;
+}
+.connection.active path {
+ animation-play-state: running;
+}
+```
+
+When `hoveredNode` is set, mark connected paths as active:
+```ts
+function isPathActive(from: string, to: string): boolean {
+ return !!(hoveredNode.value && (hoveredNode.value === from || hoveredNode.value === to))
+}
+```
+
+### Step 6: Code blocks — add copy button
+
+**File**: VitePress handles this via its built-in `:construct` code blocks. But custom code blocks in Vue templates (like the How It Works step panel) need a copy button added manually.
+
+In the step code panel header:
+```html
+
+ {{ copied ? 'Copied!' : 'Copy' }}
+
+```
+
+```ts
+const copied = ref(false)
+function copyCode() {
+ navigator.clipboard.writeText(steps[activeStep].code.replace(/<[^>]+>/g, ''))
+ copied.value = true
+ setTimeout(() => copied.value = false, 2000)
+}
+```
+
+## Files Modified
+
+| File | Change |
+|------|--------|
+| `HomePage.vue` | Step code fade transition, step number pop animation, copy button |
+| `EcosystemDiagram.vue` | Pointer cursor, tooltip, animated connection paths |
+| `custom.css` | Scroll-reveal class, stat card hover lift |
+| `index.ts` (theme) | Global scroll-reveal observer |
+
+## Verification
+
+1. Step code panel fades between tabs
+2. Clickable diagram nodes show pointer cursor and tooltip on hover
+3. Connection lines animate when node is hovered
+4. Content page sections fade in on scroll
+5. Copy button on code panel works
+6. All animations respect `prefers-reduced-motion`
diff --git a/TODO.improve/06-content-pages-polish.md b/TODO.improve/06-content-pages-polish.md
new file mode 100644
index 0000000..65411db
--- /dev/null
+++ b/TODO.improve/06-content-pages-polish.md
@@ -0,0 +1,185 @@
+# 06 — Content Pages Polish
+
+## Problem
+
+Learn pages, About, and Schemas are walls of text with minimal visual breaks. The About page is extremely long (ecosystem, governance, timeline, people, orgs, FAQ). Schemas page lacks visual hierarchy. Blog posts have no reading-time indicator.
+
+## Plan
+
+### Step 1: Schemas page — visual cards for each schema type
+
+**File**: `schemas.md`
+
+The current schemas page uses markdown headings and code blocks. Restructure into visual cards:
+
+```html
+
+
+
+
UnitsML XML Schemas
+
Authoritative XML schemas (XSD) for encoding units of measure...
+
+ Format: XSD
+ Hosted: schema.unitsml.org
+
+
+
+
+
+
UnitsDB YAML Schemas
+
YAML schemas defining the structure of UnitsDB entries...
+
+ Format: YAML
+ Repo: unitsdb
+
+
+
+
+```
+
+Style as side-by-side cards with:
+- Icon (file icon with XML/YAML badge)
+- Title + description
+- Meta chips (format, hosting location)
+- Action links
+
+### Step 2: Schemas page — add a "Schema Browser" embed/preview
+
+Below the cards, add a compact preview of the schema browser experience. This could be:
+- A screenshot/mockup of schema.unitsml.org in a browser frame (similar to the UnitsDB CTA preview window on the homepage)
+- Or a simple CTA card linking to schema.unitsml.org
+
+Reuse the `preview-window` pattern from the homepage:
+```html
+
+
+
+
https://schema.unitsml.org/unitsml/1.0
+
+
UnitSet
+ ├── Unit (xml:id, dimensionURL)
+ │ ├── UnitName
+ │ ├── UnitSymbol
+ │ └── RootUnits
+ ├── QuantitySet
+ └── DimensionSet
+
+
+```
+
+### Step 3: Learn pages — add visual callout patterns
+
+**Files**: `learn/*.md`
+
+The "What is UnitsML" page has a good pattern with `callout-grid` (Mars Climate Orbiter story). Extend this pattern to other learn pages:
+
+- `how-it-works.md`: Add a callout for the BIPM/UCUM/QUDT cross-reference table — wrap it in a styled card with authority logos
+- `incorporating-unitsml.md`: Add a "real-world example" callout showing how UnitsML fits into a specific domain (e.g., materials science with MatML, chemistry with CML)
+- `who-is-it-for.md`: Add persona callout cards with icons for each audience (Metrologist, Software Developer, Standards Body, Researcher)
+
+These are scoped CSS additions in each page's `