This is an automated email from the ASF dual-hosted git repository.

sergehuber pushed a commit to branch UNOMI-932-unomi3-major-release-story
in repository https://gitbox.apache.org/repos/asf/unomi-site.git

commit fb5eba30d32a101f48b05717f430ecf9ae50cdc1
Author: Serge Huber <[email protected]>
AuthorDate: Fri Jul 17 14:11:43 2026 +0200

    feat: add Unomi 3 in numbers blog, CDN/Matomo charts, and Netlify preview 
(UNOMI-932)
    
    Add metrics post with download/visit charts, blog publish dates and archive,
    plus preview.sh for personal Netlify staging with prerequisite checks.
---
 .gitignore                                         |   1 +
 README.md                                          | 107 +++++++
 netlify.toml                                       |  13 +
 preview.sh                                         | 306 +++++++++++++++++++++
 .../assets/images/unomi3/numbers-contributors.svg  |  76 +++++
 .../assets/images/unomi3/numbers-downloads-map.svg |  75 +++++
 .../assets/images/unomi3/numbers-downloads.svg     |  53 ++++
 .../webapp/assets/images/unomi3/numbers-hero.svg   |  74 +++++
 .../webapp/assets/images/unomi3/numbers-loc.svg    |  78 ++++++
 .../webapp/assets/images/unomi3/numbers-tests.svg  |  83 ++++++
 .../images/unomi3/numbers-visits-countries.svg     |  60 ++++
 .../webapp/assets/images/unomi3/numbers-visits.svg |  31 +++
 src/main/webapp/blog/index.html                    | 214 +++++++++++++-
 .../webapp/blog/introducing-apache-unomi-3.html    |   7 +
 src/main/webapp/blog/migrating-to-unomi-3.html     |   7 +
 src/main/webapp/blog/unomi-3-ai-agents.html        |   7 +
 src/main/webapp/blog/unomi-3-build-sh.html         |   7 +
 src/main/webapp/blog/unomi-3-in-numbers.html       | 139 ++++++++++
 src/main/webapp/blog/unomi-3-it-execution.html     |   7 +
 src/main/webapp/blog/unomi-3-manual.html           |   7 +
 src/main/webapp/blog/unomi-3-multitenancy.html     |   7 +
 .../webapp/blog/unomi-3-openapi-enrichment.html    |   7 +
 src/main/webapp/blog/unomi-3-opensearch.html       |   7 +
 .../webapp/blog/unomi-3-platform-foundation.html   |   7 +
 src/main/webapp/blog/unomi-3-request-tracing.html  |   7 +
 src/main/webapp/blog/unomi-3-shell-commands.html   |   7 +
 src/main/webapp/blog/unomi-3-task-scheduler.html   |   7 +
 .../webapp/blog/unomi-3-unit-testing-harness.html  |   7 +
 .../blog/welcome-to-the-apache-unomi-blog.html     |   7 +
 src/main/webapp/sitemap.xml                        |   5 +
 30 files changed, 1419 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index ec2086a..60e1e40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ target/
 /.mvn/wrapper/maven-wrapper.jar
 Gemfile.lock
 .bundle/
+.netlify/
diff --git a/README.md b/README.md
index 3b173b4..7d95ee7 100644
--- a/README.md
+++ b/README.md
@@ -68,11 +68,118 @@ Or serve locally with live-reload:
 bundle exec jekyll serve
 ```
 
+## Staging preview (Netlify)
+
+`./preview.sh` builds the site with Docker Jekyll and can publish a 
**personal/community** staging URL on Netlify (same approach as the UNOMI-932 
preview at `https://unomi-v3-site.netlify.app/`).
+
+This is **not** how `https://unomi.apache.org/` is published. Production still 
uses Maven scm-publish (see [Publish](#publish) below). Do not commit Netlify 
tokens or `.netlify/` state into git.
+
+### Prerequisites
+
+| Requirement | Needed for | Notes |
+|---|---|---|
+| [Docker](https://docs.docker.com/get-docker/) + running daemon | All modes | 
Pulls `bretfisher/jekyll` / `bretfisher/jekyll-serve` |
+| [Node.js](https://nodejs.org/) (includes `npx`) **or** [Netlify 
CLI](https://docs.netlify.com/cli/get-started/) | Netlify deploy 
(`./preview.sh`, `--prod`) | Script uses `netlify` if installed, otherwise `npx 
netlify-cli` |
+| Netlify account | Netlify deploy | Free personal account is enough |
+| Network access | Image pull + Netlify API | First build downloads the Jekyll 
image |
+
+Optional:
+
+- `curl` — used in prerequisite checks for Netlify reachability
+- `open` (macOS) — used with `--open` to launch the preview URL
+- `lsof` — warns if port 4000 is already taken for `--local`
+
+Verify tooling before building:
+
+```shell
+./preview.sh --check-only
+```
+
+### Credentials and configuration
+
+**Auth (pick one):**
+
+1. **Interactive login** (simplest):
+
+   ```shell
+   npx netlify-cli login
+   # or: netlify login
+   ```
+
+   Opens a browser and stores credentials in your user Netlify CLI config 
(`~/.netlify` / `~/.config/netlify`).
+
+2. **Personal access token** (CI / non-interactive):
+
+   Create a token in the Netlify UI: **User settings → Applications → Personal 
access tokens**, then:
+
+   ```shell
+   export NETLIFY_AUTH_TOKEN=your_token_here
+   ```
+
+**Site linking (pick one):**
+
+| Variable / file | Purpose |
+|---|---|
+| (default) | Script creates/links a site named `unomi-v3-site` on first 
deploy |
+| `NETLIFY_SITE_NAME` | Override the Netlify site name (default: 
`unomi-v3-site`) |
+| `NETLIFY_SITE_ID` | Use an existing site id and skip interactive link |
+| `.netlify/state.json` | Created after a successful link; reused on later 
runs (gitignored) |
+
+Example:
+
+```shell
+export NETLIFY_AUTH_TOKEN=...          # optional if already logged in
+export NETLIFY_SITE_NAME=unomi-v3-site # optional
+export NETLIFY_SITE_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx  # optional
+```
+
+Other optional env vars:
+
+| Variable | Default | Purpose |
+|---|---|---|
+| `JEKYLL_IMAGE` | `bretfisher/jekyll` | Docker image used to build |
+| `JEKYLL_SERVE_IMAGE` | `bretfisher/jekyll-serve` | Docker image used for 
`--local` |
+
+`netlify.toml` in this repo sets `publish = "target/site"`. The script builds 
locally first; Netlify only hosts the static output.
+
+### Usage
+
+From the repository root:
+
+```shell
+chmod +x ./preview.sh   # once
+
+./preview.sh --check-only   # validate Docker, layout, Netlify tooling/auth 
hints
+./preview.sh                # build + Netlify *draft* deploy (unique preview 
URL)
+./preview.sh --prod         # build + deploy to the linked site’s production 
URL
+./preview.sh --local        # build + serve at http://localhost:4000 (no 
Netlify)
+./preview.sh --build-only   # build only → target/site
+./preview.sh --open         # after a Netlify deploy, open the URL (macOS)
+```
+
+Flags can be combined where it makes sense, e.g. `./preview.sh --prod --open`.
+
+Typical first-time flow:
+
+1. Start Docker Desktop (or your Docker daemon).
+2. `./preview.sh --check-only` and fix any `[FAIL]` lines.
+3. `./preview.sh` — complete Netlify login / site link if prompted.
+4. Share the printed `https://….netlify.app/` URL for review.
+5. Later deploys to the same linked site: `./preview.sh --prod`.
+
+### What the script does
+
+1. Runs prerequisite checks (`[ok]` / `[warn]` / `[FAIL]`).
+2. Builds with Docker Jekyll into `target/site`.
+3. For Netlify modes: authenticates, links/creates the site if needed, deploys 
`target/site`, prints the preview URL.
+
 ## Publish
 
 To publish the local website to the production location 
(https://unomi.apache.org/), you have to use:
 Do not use the `clean` maven goal to not remove the previous generated site.
 
+Credentials: your **ASF LDAP username and password** (not Netlify).
+
 ```shell
 mvn install scm-publish:publish-scm -Dusername=YOUR_APACHE_USERNAME 
-Dpassword=YOUR_APACHE_PASSWORD
 ```
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..5e35b2e
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,13 @@
+# Personal/community staging only (see preview.sh).
+# Production for unomi.apache.org is Maven scm-publish, not Netlify.
+
+[build]
+  publish = "target/site"
+  # Built by preview.sh (Docker Jekyll) before deploy — do not rely on Netlify 
CI.
+  command = "echo 'Build with ./preview.sh locally; Netlify only hosts the 
static output.'"
+
+[[headers]]
+  for = "/*"
+  [headers.values]
+    X-Frame-Options = "SAMEORIGIN"
+    X-Content-Type-Options = "nosniff"
diff --git a/preview.sh b/preview.sh
new file mode 100755
index 0000000..f74eda0
--- /dev/null
+++ b/preview.sh
@@ -0,0 +1,306 @@
+#!/usr/bin/env bash
+# Build the Apache Unomi website and publish a Netlify staging preview.
+#
+# This mirrors the earlier UNOMI-932 staging flow (unomi-v3-site.netlify.app).
+# It is for personal/community review only — production remains:
+#   mvn install scm-publish:publish-scm ...
+#
+# Usage:
+#   ./preview.sh                 # Docker build + Netlify draft deploy (unique 
URL)
+#   ./preview.sh --prod          # Docker build + deploy to the linked site URL
+#   ./preview.sh --local         # Docker build + serve at 
http://localhost:4000
+#   ./preview.sh --build-only    # Docker build only (output: target/site)
+#   ./preview.sh --check-only  # Prerequisite checks only (no build/deploy)
+#   ./preview.sh --open        # After deploy, open the preview URL (macOS)
+#
+# Optional environment:
+#   NETLIFY_AUTH_TOKEN   Personal access token (or run: npx netlify-cli login)
+#   NETLIFY_SITE_ID      Existing site id (skips interactive link)
+#   NETLIFY_SITE_NAME    Site name when creating/linking (default: 
unomi-v3-site)
+#   JEKYLL_IMAGE         Docker image (default: bretfisher/jekyll)
+#   JEKYLL_SERVE_IMAGE   Serve image (default: bretfisher/jekyll-serve)
+
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+cd "$ROOT"
+
+MODE="draft"          # draft | prod | local | build-only | check-only
+OPEN_URL=0
+JEKYLL_IMAGE="${JEKYLL_IMAGE:-bretfisher/jekyll}"
+JEKYLL_SERVE_IMAGE="${JEKYLL_SERVE_IMAGE:-bretfisher/jekyll-serve}"
+NETLIFY_SITE_NAME="${NETLIFY_SITE_NAME:-unomi-v3-site}"
+SITE_DIR="target/site"
+
+usage() {
+  sed -n '2,23p' "$0" | sed 's/^# \{0,1\}//'
+  exit "${1:-0}"
+}
+
+while [[ $# -gt 0 ]]; do
+  case "$1" in
+    --prod) MODE="prod"; shift ;;
+    --local) MODE="local"; shift ;;
+    --build-only) MODE="build-only"; shift ;;
+    --check-only) MODE="check-only"; shift ;;
+    --open) OPEN_URL=1; shift ;;
+    -h|--help) usage 0 ;;
+    *)
+      echo "Unknown option: $1" >&2
+      usage 1
+      ;;
+  esac
+done
+
+need_cmd() {
+  if ! command -v "$1" >/dev/null 2>&1; then
+    echo "Missing required command: $1" >&2
+    return 1
+  fi
+  return 0
+}
+
+have_cmd() {
+  command -v "$1" >/dev/null 2>&1
+}
+
+check_pass() { echo "  [ok] $1"; }
+check_fail() { echo "  [FAIL] $1" >&2; CHECKS_FAILED=1; }
+check_warn() { echo "  [warn] $1" >&2; }
+
+CHECKS_FAILED=0
+
+check_prerequisites() {
+  echo "==> Prerequisite checks (mode: ${MODE})"
+  CHECKS_FAILED=0
+
+  # --- Project layout ---
+  if [[ -f "${ROOT}/_config.yml" ]]; then
+    check_pass "_config.yml present"
+  else
+    check_fail "_config.yml missing — run from the unomi-site repo root"
+  fi
+
+  if [[ -d "${ROOT}/src/main/webapp" ]]; then
+    check_pass "src/main/webapp present"
+  else
+    check_fail "src/main/webapp missing — unexpected project layout"
+  fi
+
+  if [[ -f "${ROOT}/netlify.toml" ]]; then
+    check_pass "netlify.toml present"
+  else
+    check_warn "netlify.toml missing (deploy still works with 
--dir=${SITE_DIR})"
+  fi
+
+  # --- Docker (required for all modes that build) ---
+  if have_cmd docker; then
+    check_pass "docker found ($(command -v docker))"
+    if docker info >/dev/null 2>&1; then
+      check_pass "Docker daemon is running"
+    else
+      check_fail "Docker daemon is not running — start Docker Desktop / 
dockerd"
+    fi
+  else
+    check_fail "docker not found — install Docker (required to build with 
${JEKYLL_IMAGE})"
+  fi
+
+  # Writable output dir
+  if mkdir -p "${ROOT}/target" 2>/dev/null && [[ -w "${ROOT}/target" ]]; then
+    check_pass "target/ is writable"
+  else
+    check_fail "cannot create/write ${ROOT}/target"
+  fi
+
+  # --- Mode-specific ---
+  case "${MODE}" in
+    draft|prod|check-only)
+      if have_cmd netlify; then
+        check_pass "netlify CLI found ($(netlify --version 2>/dev/null | head 
-1 || echo ok))"
+      elif have_cmd npx; then
+        check_pass "npx found (will use: npx netlify-cli)"
+        if have_cmd node; then
+          check_pass "node found ($(node --version 2>/dev/null))"
+        else
+          check_warn "node not found — npx may still work depending on your 
Node install"
+        fi
+      else
+        check_fail "neither 'netlify' nor 'npx' found — install Netlify CLI or 
Node.js (npx)"
+      fi
+
+      if [[ -n "${NETLIFY_AUTH_TOKEN:-}" ]]; then
+        check_pass "NETLIFY_AUTH_TOKEN is set"
+      elif [[ -f "${HOME}/.netlify/config.json" ]] || [[ -d 
"${HOME}/.config/netlify" ]]; then
+        check_pass "Netlify CLI config present (will verify auth at deploy 
time)"
+      else
+        check_warn "no NETLIFY_AUTH_TOKEN / Netlify config yet — script will 
run 'netlify login' if needed"
+      fi
+
+      if [[ -n "${NETLIFY_SITE_ID:-}" ]]; then
+        check_pass "NETLIFY_SITE_ID is set"
+      elif [[ -f "${ROOT}/.netlify/state.json" ]]; then
+        check_pass "site already linked (.netlify/state.json)"
+      else
+        check_warn "site not linked yet — will create/link 
'${NETLIFY_SITE_NAME}'"
+      fi
+
+      if have_cmd curl; then
+        if curl -fsS --connect-timeout 5 -o /dev/null 
https://api.netlify.com/api/v1/ 2>/dev/null \
+          || curl -fsS --connect-timeout 5 -o /dev/null 
https://www.netlify.com/ 2>/dev/null; then
+          check_pass "network reachability to Netlify"
+        else
+          check_warn "could not reach Netlify APIs — deploy may fail offline"
+        fi
+      fi
+      ;;
+    local)
+      if have_cmd lsof && lsof -iTCP:4000 -sTCP:LISTEN >/dev/null 2>&1; then
+        check_warn "port 4000 is already in use — jekyll-serve may fail to 
bind"
+      else
+        check_pass "port 4000 appears free"
+      fi
+      ;;
+    build-only)
+      ;;
+  esac
+
+  if [[ "${OPEN_URL}" -eq 1 ]]; then
+    if have_cmd open; then
+      check_pass "open found (will open preview URL)"
+    else
+      check_warn "'open' not found — --open will be ignored"
+    fi
+  fi
+
+  if [[ "${CHECKS_FAILED}" -ne 0 ]]; then
+    echo "" >&2
+    echo "Prerequisite checks failed. Fix the items marked [FAIL] and re-run." 
>&2
+    exit 1
+  fi
+  echo "==> Prerequisites OK"
+  echo ""
+}
+
+build_site() {
+  echo "==> Building site with Docker (${JEKYLL_IMAGE})"
+  mkdir -p target
+  docker run --rm \
+    --volume="${ROOT}:/site" \
+    -w /site \
+    "${JEKYLL_IMAGE}" \
+    build
+
+  if [[ ! -d "${SITE_DIR}" ]] || [[ -z "$(ls -A "${SITE_DIR}" 2>/dev/null || 
true)" ]]; then
+    echo "Build failed: ${SITE_DIR} is missing or empty" >&2
+    exit 1
+  fi
+  echo "==> Build OK → ${SITE_DIR}"
+}
+
+netlify_cli() {
+  if have_cmd netlify; then
+    netlify "$@"
+  else
+    npx --yes netlify-cli "$@"
+  fi
+}
+
+ensure_netlify_auth() {
+  if [[ -n "${NETLIFY_AUTH_TOKEN:-}" ]]; then
+    return 0
+  fi
+  if netlify_cli status >/dev/null 2>&1; then
+    return 0
+  fi
+  echo "==> Netlify auth required (browser login)"
+  netlify_cli login
+}
+
+link_or_create_site() {
+  if [[ -n "${NETLIFY_SITE_ID:-}" ]]; then
+    echo "==> Using NETLIFY_SITE_ID=${NETLIFY_SITE_ID}"
+    return 0
+  fi
+  if [[ -f .netlify/state.json ]]; then
+    echo "==> Using existing Netlify link (.netlify/state.json)"
+    return 0
+  fi
+
+  echo "==> Linking/creating Netlify site '${NETLIFY_SITE_NAME}'"
+  if netlify_cli sites:list --json 2>/dev/null | grep -q 
"\"name\":\"${NETLIFY_SITE_NAME}\""; then
+    netlify_cli link --name "${NETLIFY_SITE_NAME}"
+  else
+    netlify_cli sites:create --name "${NETLIFY_SITE_NAME}" --disable-linking 
|| true
+    netlify_cli link --name "${NETLIFY_SITE_NAME}" || netlify_cli init
+  fi
+}
+
+deploy_netlify() {
+  ensure_netlify_auth
+  link_or_create_site
+
+  local args=(deploy --dir="${SITE_DIR}" --message "unomi-site preview $(date 
-u +%Y-%m-%dT%H:%MZ)")
+  if [[ "${MODE}" == "prod" ]]; then
+    args+=(--prod)
+    echo "==> Deploying to Netlify production URL for linked site"
+  else
+    echo "==> Deploying Netlify draft preview (unique URL)"
+  fi
+
+  local log
+  log="$(mktemp)"
+  if ! netlify_cli "${args[@]}" 2>&1 | tee "${log}"; then
+    echo "Netlify deploy failed" >&2
+    rm -f "${log}"
+    exit 1
+  fi
+
+  local url
+  url="$(grep -Eo 'https://[^ ]+\.netlify\.app/?[^ ]*' "${log}" | tail -1 || 
true)"
+  rm -f "${log}"
+
+  if [[ -n "${url}" ]]; then
+    echo ""
+    echo "==> Preview URL: ${url}"
+    if [[ "${OPEN_URL}" -eq 1 ]] && command -v open >/dev/null 2>&1; then
+      open "${url}"
+    fi
+  else
+    echo "==> Deploy finished (check netlify output above for the URL)"
+  fi
+}
+
+serve_local() {
+  echo "==> Serving with Docker (${JEKYLL_SERVE_IMAGE}) at 
http://localhost:4000/";
+  echo "    Ctrl+C to stop"
+  docker run --rm \
+    --volume="${ROOT}:/site" \
+    -p 4000:4000 \
+    -w /site \
+    "${JEKYLL_SERVE_IMAGE}"
+}
+
+check_prerequisites
+
+if [[ "${MODE}" == "check-only" ]]; then
+  echo "==> Done (check only)"
+  exit 0
+fi
+
+build_site
+
+case "${MODE}" in
+  build-only)
+    echo "==> Done (build only)"
+    ;;
+  local)
+    serve_local
+    ;;
+  draft|prod)
+    deploy_netlify
+    ;;
+  *)
+    echo "Internal error: unknown mode ${MODE}" >&2
+    exit 1
+    ;;
+esac
diff --git a/src/main/webapp/assets/images/unomi3/numbers-contributors.svg 
b/src/main/webapp/assets/images/unomi3/numbers-contributors.svg
new file mode 100644
index 0000000..d65d94d
--- /dev/null
+++ b/src/main/webapp/assets/images/unomi3/numbers-contributors.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 840 400" fill="none" xmlns="http://www.w3.org/2000/svg"; 
role="img"
+     aria-label="Line chart of cumulative unique contributors from Unomi 1 
through 3.1">
+  <defs>
+    <style>
+  svg {
+    --unomi-primary: #6d5ce7;
+    --unomi-primary-dark: #5a48c9;
+    --unomi-primary-light: #ede9fe;
+    --unomi-primary-subtle: #f5f3ff;
+    --unomi-accent: #00cec9;
+    --unomi-accent-dark: #0d9488;
+    --unomi-hero-accent: #a78bfa;
+    --unomi-text: #1e293b;
+    --unomi-text-muted: #64748b;
+    --unomi-white: #ffffff;
+    --unomi-bg-soft: #f8fafc;
+    --unomi-border: #e2e8f0;
+    --unomi-success: #059669;
+    --unomi-success-bg: #ecfdf5;
+    --unomi-amber: #d97706;
+    --unomi-amber-bg: #fef3c7;
+    --unomi-info: #2563eb;
+    --unomi-info-bg: #dbeafe;
+    font-family: Inter, system-ui, sans-serif;
+  }
+  @keyframes grow-bar { from { transform: scaleY(0); } to { transform: 
scaleY(1); } }
+  @keyframes fade-up { from { opacity: 0; transform: translateY(8px); } to { 
opacity: 1; transform: translateY(0); } }
+  @keyframes dash-flow { to { stroke-dashoffset: -20; } }
+  @keyframes pulse-dot { 0%,100%{opacity:.7;r:5} 50%{opacity:1;r:7} }
+  .bar { transform-box: fill-box; transform-origin: bottom; animation: 
grow-bar 0.9s ease-out both; }
+  .fade { animation: fade-up 0.7s ease-out both; }
+  .flow { stroke-dasharray: 5 4; animation: dash-flow 1.8s linear infinite; }
+  .dot { animation: pulse-dot 2.4s ease-in-out infinite; }
+  @media (prefers-reduced-motion: reduce) {
+    .bar,.fade,.flow,.dot { animation: none !important; }
+  }
+</style>
+    <linearGradient id="areaFill" x1="0" y1="100" x2="0" y2="300" 
gradientUnits="userSpaceOnUse">
+      <stop stop-color="#6d5ce7" stop-opacity="0.35"/>
+      <stop offset="1" stop-color="#6d5ce7" stop-opacity="0.02"/>
+    </linearGradient>
+  </defs>
+  <rect width="840" height="400" rx="20" fill="var(--unomi-bg-soft)"/>
+  <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" 
font-weight="700">Cumulative contributors</text>
+  <text class="fade" style="animation-delay:.08s" x="40" y="58" 
fill="var(--unomi-text-muted)" font-size="12">Unique git authors through each 
release tag (name-based; aliases not merged)</text>
+  <line x1="80" y1="300" x2="760" y2="300" stroke="var(--unomi-border)" 
stroke-width="1.5"/>
+  <polygon class="fade" style="animation-delay:.15s" points="120,300 120,220.0 
300,153.3 480,140.0 660,120.0 660,300" fill="url(#areaFill)"/>
+  <polyline class="fade flow" style="animation-delay:.2s" points="120,220.0 
300,153.3 480,140.0 660,120.0" fill="none" stroke="var(--unomi-primary)" 
stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
+  
+  <g class="fade" style="animation-delay:0.20s">
+    <circle class="dot" style="animation-delay:0.0s" cx="120" cy="220.0" r="6" 
fill="var(--unomi-primary)"/>
+    <text x="120" y="204.0" text-anchor="middle" 
fill="var(--unomi-primary-dark)" font-size="16" font-weight="800">62</text>
+    <text x="120" y="328" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 1</text>
+    <text x="120" y="346" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">2,255 commits</text>
+  </g>
+  <g class="fade" style="animation-delay:0.32s">
+    <circle class="dot" style="animation-delay:0.3s" cx="300" cy="153.3" r="6" 
fill="var(--unomi-primary)"/>
+    <text x="300" y="137.3" text-anchor="middle" 
fill="var(--unomi-primary-dark)" font-size="16" font-weight="800">72</text>
+    <text x="300" y="328" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 2</text>
+    <text x="300" y="346" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">2,657 commits</text>
+  </g>
+  <g class="fade" style="animation-delay:0.44s">
+    <circle class="dot" style="animation-delay:0.6s" cx="480" cy="140.0" r="6" 
fill="var(--unomi-primary)"/>
+    <text x="480" y="124.0" text-anchor="middle" 
fill="var(--unomi-primary-dark)" font-size="16" font-weight="800">74</text>
+    <text x="480" y="328" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 3.0</text>
+    <text x="480" y="346" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">2,682 commits</text>
+  </g>
+  <g class="fade" style="animation-delay:0.56s">
+    <circle class="dot" style="animation-delay:0.9s" cx="660" cy="120.0" r="6" 
fill="var(--unomi-primary)"/>
+    <text x="660" y="104.0" text-anchor="middle" 
fill="var(--unomi-primary-dark)" font-size="16" font-weight="800">77</text>
+    <text x="660" y="328" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 3.1</text>
+    <text x="660" y="346" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">2,769 commits</text>
+  </g>
+  <text class="fade" style="animation-delay:.8s" x="40" y="375" 
fill="var(--unomi-text-muted)" font-size="12">Steady community growth across a 
decade - 77 authors and 2,769 commits on master ancestry.</text>
+</svg>
diff --git a/src/main/webapp/assets/images/unomi3/numbers-downloads-map.svg 
b/src/main/webapp/assets/images/unomi3/numbers-downloads-map.svg
new file mode 100644
index 0000000..497224b
--- /dev/null
+++ b/src/main/webapp/assets/images/unomi3/numbers-downloads-map.svg
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 960 560" fill="none" xmlns="http://www.w3.org/2000/svg"; 
role="img"
+     aria-label="World map of human Unomi software package downloads by 
country, bots excluded">
+  <defs>
+    <style>
+  svg {
+    --unomi-primary: #6d5ce7; --unomi-text: #1e293b; --unomi-text-muted: 
#64748b;
+    --unomi-bg-soft: #f8fafc; font-family: Inter, system-ui, sans-serif;
+  }
+  path:hover { stroke: #6d5ce7; stroke-width: 1.2; }
+  @keyframes fade-up { from { opacity: 0; } to { opacity: 1; } }
+  .fade { animation: fade-up 0.7s ease-out both; }
+  @media (prefers-reduced-motion: reduce) { .fade { animation: none 
!important; } }
+</style>
+    <linearGradient id="dlScale" x1="0" y1="0" x2="1" y2="0"><stop 
offset="0.0" stop-color="#e2e8f0"/><stop offset="0.25" 
stop-color="#8878ee"/><stop offset="0.5" stop-color="#6453da"/><stop 
offset="0.75" stop-color="#5443bf"/><stop offset="1.0" 
stop-color="#3b2d8f"/></linearGradient>
+  </defs>
+  <rect width="960" height="560" rx="20" fill="var(--unomi-bg-soft)"/>
+  <text class="fade" x="28" y="34" fill="var(--unomi-text)" font-size="18" 
font-weight="700">Downloads by country (bots excluded)</text>
+  <text class="fade" style="animation-delay:.08s" x="28" y="54" 
fill="var(--unomi-text-muted)" font-size="12">Human browser share of software 
packages - ASF CDN geo - Jul 2023 to Jul 2026</text>
+  <g class="fade" style="animation-delay:.15s"><path 
d="M740.0,305.7L740.0,306.8L738.7,307.3L737.5,307.8L737.2,307.0L738.2,306.5L738.8,306.4L740.0,305.7Z
 
M736.3,308.9L736.7,308.5L737.4,309.2L737.1,310.3L735.9,310.6L734.8,310.4L734.6,309.4L735.3,308.6L736.3,308.9Z
 M20.4,305.6L20.2,306.7L20.0,306.8L20.0,305.7L20.4,305.6Z" fill="#e2e8f0" 
stroke="#ffffff" stroke-width="0.6" 
data-cc="FJ"><title>Fiji</title></path><path 
d="M447.8,272.1L448.1,272.4L455.4,276.9L455.5,278.2L458.4,280.4L457.5,283. [...]
+  <text x="750" y="62" fill="var(--unomi-text)" font-size="13" 
font-weight="700">Top countries</text>
+  
+    <text x="750" y="108" fill="var(--unomi-text-muted)" 
font-size="11">01</text>
+    <text x="772" y="108" fill="var(--unomi-text)" font-size="12" 
font-weight="600">Brazil</text>
+    <text x="944" y="108" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">3,627</text>
+    <rect x="772" y="113" width="120" height="4" rx="2" fill="#3b2d8f"/>
+    <text x="750" y="136" fill="var(--unomi-text-muted)" 
font-size="11">02</text>
+    <text x="772" y="136" fill="var(--unomi-text)" font-size="12" 
font-weight="600">China</text>
+    <text x="944" y="136" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">2,816</text>
+    <rect x="772" y="141" width="116" height="4" rx="2" fill="#3e2f94"/>
+    <text x="750" y="164" fill="var(--unomi-text-muted)" 
font-size="11">03</text>
+    <text x="772" y="164" fill="var(--unomi-text)" font-size="12" 
font-weight="600">Russia</text>
+    <text x="944" y="164" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">2,583</text>
+    <rect x="772" y="169" width="115" height="4" rx="2" fill="#3f3097"/>
+    <text x="750" y="192" fill="var(--unomi-text-muted)" 
font-size="11">04</text>
+    <text x="772" y="192" fill="var(--unomi-text)" font-size="12" 
font-weight="600">United States</text>
+    <text x="944" y="192" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">1,771</text>
+    <rect x="772" y="197" width="109" height="4" rx="2" fill="#44349f"/>
+    <text x="750" y="220" fill="var(--unomi-text-muted)" 
font-size="11">05</text>
+    <text x="772" y="220" fill="var(--unomi-text)" font-size="12" 
font-weight="600">Germany</text>
+    <text x="944" y="220" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">1,258</text>
+    <rect x="772" y="225" width="104" height="4" rx="2" fill="#4838a7"/>
+    <text x="750" y="248" fill="var(--unomi-text-muted)" 
font-size="11">06</text>
+    <text x="772" y="248" fill="var(--unomi-text)" font-size="12" 
font-weight="600">Netherlands</text>
+    <text x="944" y="248" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">912</text>
+    <rect x="772" y="253" width="99" height="4" rx="2" fill="#4c3caf"/>
+    <text x="750" y="276" fill="var(--unomi-text-muted)" 
font-size="11">07</text>
+    <text x="772" y="276" fill="var(--unomi-text)" font-size="12" 
font-weight="600">India</text>
+    <text x="944" y="276" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">280</text>
+    <rect x="772" y="281" width="82" height="4" rx="2" fill="#5a48ca"/>
+    <text x="750" y="304" fill="var(--unomi-text-muted)" 
font-size="11">08</text>
+    <text x="772" y="304" fill="var(--unomi-text)" font-size="12" 
font-weight="600">Austria</text>
+    <text x="944" y="304" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">246</text>
+    <rect x="772" y="309" width="80" height="4" rx="2" fill="#5b49cb"/>
+    <text x="750" y="332" fill="var(--unomi-text-muted)" 
font-size="11">09</text>
+    <text x="772" y="332" fill="var(--unomi-text)" font-size="12" 
font-weight="600">Singapore</text>
+    <text x="944" y="332" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">218</text>
+    <rect x="772" y="337" width="78" height="4" rx="2" fill="#5c4acc"/>
+    <text x="750" y="360" fill="var(--unomi-text-muted)" 
font-size="11">10</text>
+    <text x="772" y="360" fill="var(--unomi-text)" font-size="12" 
font-weight="600">France</text>
+    <text x="944" y="360" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">188</text>
+    <rect x="772" y="365" width="76" height="4" rx="2" fill="#5d4bce"/>
+    <text x="750" y="388" fill="var(--unomi-text-muted)" 
font-size="11">11</text>
+    <text x="772" y="388" fill="var(--unomi-text)" font-size="12" 
font-weight="600">Italy</text>
+    <text x="944" y="388" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">122</text>
+    <rect x="772" y="393" width="70" height="4" rx="2" fill="#604ed2"/>
+    <text x="750" y="416" fill="var(--unomi-text-muted)" 
font-size="11">12</text>
+    <text x="772" y="416" fill="var(--unomi-text)" font-size="12" 
font-weight="600">Spain</text>
+    <text x="944" y="416" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">91</text>
+    <rect x="772" y="421" width="66" height="4" rx="2" fill="#6250d5"/>
+  <rect x="30" y="505" width="220" height="12" rx="3" fill="url(#dlScale)"/>
+  <text x="30" y="499" fill="var(--unomi-text-muted)" 
font-size="10">Fewer</text>
+  <text x="250" y="499" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="10">More (log scale)</text>
+  <text x="30" y="533" fill="var(--unomi-text-muted)" font-size="10">Countries 
scaled by each file's human-browser UA share · ~15,345 attributed</text>
+</svg>
diff --git a/src/main/webapp/assets/images/unomi3/numbers-downloads.svg 
b/src/main/webapp/assets/images/unomi3/numbers-downloads.svg
new file mode 100644
index 0000000..457b388
--- /dev/null
+++ b/src/main/webapp/assets/images/unomi3/numbers-downloads.svg
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 840 460" fill="none" xmlns="http://www.w3.org/2000/svg"; 
role="img"
+     aria-label="Cumulative Unomi software package downloads: human browsers, 
all packages including bots, and known bots">
+  <defs>
+    <style>
+  svg {
+    --unomi-primary: #6d5ce7;
+    --unomi-primary-dark: #5a48c9;
+    --unomi-accent: #00cec9;
+    --unomi-accent-dark: #0d9488;
+    --unomi-bot: #8b83b8;
+    --unomi-text: #1e293b;
+    --unomi-text-muted: #64748b;
+    --unomi-bg-soft: #f8fafc;
+    --unomi-border: #e2e8f0;
+    --unomi-amber: #d97706;
+    font-family: Inter, system-ui, sans-serif;
+  }
+  @keyframes fade-up { from { opacity: 0; } to { opacity: 1; } }
+  .fade { animation: fade-up 0.8s ease-out both; }
+  @media (prefers-reduced-motion: reduce) { .fade { animation: none 
!important; } }
+</style>
+    <linearGradient id="softFill" x1="0" y1="80" x2="0" y2="360" 
gradientUnits="userSpaceOnUse">
+      <stop stop-color="#6d5ce7" stop-opacity="0.28"/>
+      <stop offset="1" stop-color="#6d5ce7" stop-opacity="0.02"/>
+    </linearGradient>
+  </defs>
+  <rect width="840" height="460" rx="20" fill="var(--unomi-bg-soft)"/>
+  <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" 
font-weight="700">Cumulative software-package downloads</text>
+  <text class="fade" style="animation-delay:.08s" x="40" y="58" 
fill="var(--unomi-text-muted)" font-size="12">Purple = human only · Teal = all 
package hits · Dotted = known bots - ASF CDN Jul 2023 to Jul 2026</text>
+  <line x1="70" y1="360.0" x2="800" y2="360.0" stroke="var(--unomi-border)" 
stroke-width="1"/><text x="62" y="364.0" text-anchor="end" 
fill="var(--unomi-text-muted)" font-size="10">0</text><line x1="70" y1="290.0" 
x2="800" y2="290.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="62" 
y="294.0" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="10">12k</text><line x1="70" y1="220.0" x2="800" y2="220.0" 
stroke="var(--unomi-border)" stroke-width="1"/><text x="62" y="224.0" [...]
+  
+  <line x1="597.2" y1="80" x2="597.2" y2="360" 
stroke="var(--unomi-accent-dark)" stroke-width="1.5" stroke-dasharray="4 3"/>
+  <text x="591.2" y="94" text-anchor="end" fill="var(--unomi-accent-dark)" 
font-size="11" font-weight="700">2.7.0</text>
+  <line x1="637.8" y1="80" x2="637.8" y2="360" stroke="var(--unomi-amber)" 
stroke-width="1.5" stroke-dasharray="4 3"/>
+  <text x="643.8" y="94" fill="var(--unomi-amber)" font-size="11" 
font-weight="700">3.0</text>
+  <line x1="698.6" y1="80" x2="698.6" y2="360" 
stroke="var(--unomi-primary-dark)" stroke-width="1.5" stroke-dasharray="4 3"/>
+  <text x="704.6" y="94" fill="var(--unomi-primary-dark)" font-size="11" 
font-weight="700">Site refresh</text>
+  <polygon class="fade" style="animation-delay:.15s" points="70.0,360.0 
70.0,360.0 90.3,359.2 110.6,358.4 130.8,357.5 151.1,355.9 171.4,354.6 
191.7,353.8 211.9,352.6 232.2,351.8 252.5,350.7 272.8,349.4 293.1,348.3 
313.3,347.2 333.6,346.0 353.9,344.7 374.2,343.7 394.4,342.5 414.7,340.7 
435.0,338.5 455.3,337.0 475.6,335.6 495.8,333.2 516.1,332.0 536.4,316.7 
556.7,306.3 576.9,303.3 597.2,287.0 617.5,282.5 637.8,278.9 658.1,276.4 
678.3,273.7 698.6,269.7 718.9,267.2 739.2,264.2 759.4,260.2 77 [...]
+  <polyline class="fade" style="animation-delay:.2s" points="70.0,358.0 
90.3,349.0 110.6,339.3 130.8,330.0 151.1,320.6 171.4,309.2 191.7,299.0 
211.9,289.6 232.2,284.0 252.5,278.5 272.8,270.1 293.1,262.9 313.3,258.3 
333.6,251.1 353.9,240.7 374.2,228.2 394.4,223.0 414.7,218.5 435.0,215.1 
455.3,213.5 475.6,212.0 495.8,209.3 516.1,208.1 536.4,191.5 556.7,180.4 
576.9,176.5 597.2,155.7 617.5,148.3 637.8,141.4 658.1,136.9 678.3,129.8 
698.6,124.2 718.9,116.9 739.2,112.7 759.4,108.5 779.7,102.5 8 [...]
+  <polyline class="fade" style="animation-delay:.22s" points="70.0,358.0 
90.3,349.9 110.6,341.0 130.8,332.3 151.1,324.0 171.4,314.0 191.7,304.7 
211.9,296.6 232.2,291.6 252.5,287.2 272.8,280.2 293.1,274.3 313.3,270.9 
333.6,265.2 353.9,256.0 374.2,244.8 394.4,241.4 414.7,238.6 435.0,236.5 
455.3,236.3 475.6,236.2 495.8,236.0 516.1,235.9 536.4,235.0 556.7,234.9 
576.9,233.7 597.2,229.8 617.5,227.0 637.8,223.6 658.1,222.1 678.3,219.1 
698.6,217.5 718.9,212.6 739.2,210.5 759.4,210.1 779.7,209.0  [...]
+  <polyline class="fade" style="animation-delay:.25s" points="70.0,360.0 
90.3,359.2 110.6,358.4 130.8,357.5 151.1,355.9 171.4,354.6 191.7,353.8 
211.9,352.6 232.2,351.8 252.5,350.7 272.8,349.4 293.1,348.3 313.3,347.2 
333.6,346.0 353.9,344.7 374.2,343.7 394.4,342.5 414.7,340.7 435.0,338.5 
455.3,337.0 475.6,335.6 495.8,333.2 516.1,332.0 536.4,316.7 556.7,306.3 
576.9,303.3 597.2,287.0 617.5,282.5 637.8,278.9 658.1,276.4 678.3,273.7 
698.6,269.7 718.9,267.2 739.2,264.2 759.4,260.2 779.7,255.1  [...]
+  <text x="70.0" y="382" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">Jul 23</text><line x1="70.0" y1="360" x2="70.0" y2="366" 
stroke="var(--unomi-border)" stroke-width="1"/><text x="191.7" y="382" 
text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Jan 
24</text><line x1="191.7" y1="360" x2="191.7" y2="366" 
stroke="var(--unomi-border)" stroke-width="1"/><text x="313.3" y="382" 
text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Jul 24</ 
[...]
+  <circle cx="800.0" cy="253.6" r="5" fill="var(--unomi-primary)"/>
+  <g class="fade" style="animation-delay:.5s">
+    <rect x="40" y="420" width="14" height="14" rx="3" 
fill="var(--unomi-primary)"/>
+    <text x="62" y="431" fill="var(--unomi-text)" font-size="12">Human 
browsers - 17,785</text>
+    <rect x="280" y="420" width="14" height="14" rx="3" 
fill="var(--unomi-accent)"/>
+    <text x="302" y="431" fill="var(--unomi-text)" font-size="12">All packages 
(incl. bots) - 43,320</text>
+    <line x1="500" y1="427" x2="526" y2="427" stroke="var(--unomi-bot)" 
stroke-width="2" stroke-dasharray="2 5" stroke-linecap="round"/>
+    <text x="534" y="431" fill="var(--unomi-text)" font-size="12">Known bots / 
scripts - 25,295</text>
+  </g>
+</svg>
diff --git a/src/main/webapp/assets/images/unomi3/numbers-hero.svg 
b/src/main/webapp/assets/images/unomi3/numbers-hero.svg
new file mode 100644
index 0000000..b7ef64b
--- /dev/null
+++ b/src/main/webapp/assets/images/unomi3/numbers-hero.svg
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 840 400" fill="none" xmlns="http://www.w3.org/2000/svg"; 
role="img"
+     aria-label="Unomi 3 in numbers: lines of code, tests, and contributors at 
a glance">
+  <defs><style>
+  svg {
+    --unomi-primary: #6d5ce7;
+    --unomi-primary-dark: #5a48c9;
+    --unomi-primary-light: #ede9fe;
+    --unomi-primary-subtle: #f5f3ff;
+    --unomi-accent: #00cec9;
+    --unomi-accent-dark: #0d9488;
+    --unomi-hero-accent: #a78bfa;
+    --unomi-text: #1e293b;
+    --unomi-text-muted: #64748b;
+    --unomi-white: #ffffff;
+    --unomi-bg-soft: #f8fafc;
+    --unomi-border: #e2e8f0;
+    --unomi-success: #059669;
+    --unomi-success-bg: #ecfdf5;
+    --unomi-amber: #d97706;
+    --unomi-amber-bg: #fef3c7;
+    --unomi-info: #2563eb;
+    --unomi-info-bg: #dbeafe;
+    font-family: Inter, system-ui, sans-serif;
+  }
+  @keyframes grow-bar { from { transform: scaleY(0); } to { transform: 
scaleY(1); } }
+  @keyframes fade-up { from { opacity: 0; transform: translateY(8px); } to { 
opacity: 1; transform: translateY(0); } }
+  @keyframes dash-flow { to { stroke-dashoffset: -20; } }
+  @keyframes pulse-dot { 0%,100%{opacity:.7;r:5} 50%{opacity:1;r:7} }
+  .bar { transform-box: fill-box; transform-origin: bottom; animation: 
grow-bar 0.9s ease-out both; }
+  .fade { animation: fade-up 0.7s ease-out both; }
+  .flow { stroke-dasharray: 5 4; animation: dash-flow 1.8s linear infinite; }
+  .dot { animation: pulse-dot 2.4s ease-in-out infinite; }
+  @media (prefers-reduced-motion: reduce) {
+    .bar,.fade,.flow,.dot { animation: none !important; }
+  }
+</style>
+    <linearGradient id="heroGrad" x1="0" y1="0" x2="840" y2="400" 
gradientUnits="userSpaceOnUse">
+      <stop stop-color="#f5f3ff"/><stop offset="1" stop-color="#ecfeff"/>
+    </linearGradient>
+  </defs>
+  <rect width="840" height="400" rx="20" fill="url(#heroGrad)"/>
+  <text class="fade" x="40" y="42" fill="var(--unomi-text)" font-size="20" 
font-weight="700">Unomi 3 in numbers</text>
+  <text class="fade" style="animation-delay:.1s" x="40" y="66" 
fill="var(--unomi-text-muted)" font-size="13">From 1.x to 3.1 - codebase, 
tests, and community growth</text>
+
+  <g class="fade" style="animation-delay:.15s">
+    <rect x="40" y="100" width="175" height="150" rx="16" 
fill="var(--unomi-white)" stroke="var(--unomi-primary)" stroke-width="2"/>
+    <text x="127" y="145" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="12" font-weight="600">JAVA LOC</text>
+    <text x="127" y="190" text-anchor="middle" 
fill="var(--unomi-primary-dark)" font-size="36" font-weight="800">181k</text>
+    <text x="127" y="220" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">3.1 WIP (was 54k in 1.x)</text>
+  </g>
+  <g class="fade" style="animation-delay:.25s">
+    <rect x="235" y="100" width="175" height="150" rx="16" 
fill="var(--unomi-white)" stroke="var(--unomi-accent)" stroke-width="2"/>
+    <text x="322" y="145" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="12" font-weight="600">UNIT @Test</text>
+    <text x="322" y="190" text-anchor="middle" fill="var(--unomi-accent-dark)" 
font-size="36" font-weight="800">1,148</text>
+    <text x="322" y="220" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">up from 34 in Unomi 1.x</text>
+  </g>
+  <g class="fade" style="animation-delay:.35s">
+    <rect x="430" y="100" width="175" height="150" rx="16" 
fill="var(--unomi-white)" stroke="var(--unomi-info)" stroke-width="2"/>
+    <text x="517" y="145" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="12" font-weight="600">IT @Test</text>
+    <text x="517" y="190" text-anchor="middle" fill="var(--unomi-info)" 
font-size="36" font-weight="800">388</text>
+    <text x="517" y="220" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">up from 128 in Unomi 1.x</text>
+  </g>
+  <g class="fade" style="animation-delay:.45s">
+    <rect x="625" y="100" width="175" height="150" rx="16" 
fill="var(--unomi-white)" stroke="var(--unomi-success)" stroke-width="2"/>
+    <text x="712" y="145" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="12" font-weight="600">CONTRIBUTORS</text>
+    <text x="712" y="190" text-anchor="middle" fill="var(--unomi-success)" 
font-size="36" font-weight="800">77</text>
+    <text x="712" y="220" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">cumulative unique authors</text>
+  </g>
+
+  <rect x="40" y="280" width="760" height="90" rx="14" 
fill="var(--unomi-white)" stroke="var(--unomi-border)" stroke-width="2"/>
+  <text x="60" y="318" fill="var(--unomi-primary-dark)" font-size="14" 
font-weight="700">The story in one line</text>
+  <text x="60" y="344" fill="var(--unomi-text)" font-size="13">Production code 
nearly tripled since 1.x - test code grew ~7x, with the steepest climb after 
Unomi 3.0 into 3.1.</text>
+</svg>
diff --git a/src/main/webapp/assets/images/unomi3/numbers-loc.svg 
b/src/main/webapp/assets/images/unomi3/numbers-loc.svg
new file mode 100644
index 0000000..7eebd24
--- /dev/null
+++ b/src/main/webapp/assets/images/unomi3/numbers-loc.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 840 420" fill="none" xmlns="http://www.w3.org/2000/svg"; 
role="img"
+     aria-label="Stacked bar chart of Java lines of code from Unomi 1 through 
Unomi 3.1">
+  <defs><style>
+  svg {
+    --unomi-primary: #6d5ce7;
+    --unomi-primary-dark: #5a48c9;
+    --unomi-primary-light: #ede9fe;
+    --unomi-primary-subtle: #f5f3ff;
+    --unomi-accent: #00cec9;
+    --unomi-accent-dark: #0d9488;
+    --unomi-hero-accent: #a78bfa;
+    --unomi-text: #1e293b;
+    --unomi-text-muted: #64748b;
+    --unomi-white: #ffffff;
+    --unomi-bg-soft: #f8fafc;
+    --unomi-border: #e2e8f0;
+    --unomi-success: #059669;
+    --unomi-success-bg: #ecfdf5;
+    --unomi-amber: #d97706;
+    --unomi-amber-bg: #fef3c7;
+    --unomi-info: #2563eb;
+    --unomi-info-bg: #dbeafe;
+    font-family: Inter, system-ui, sans-serif;
+  }
+  @keyframes grow-bar { from { transform: scaleY(0); } to { transform: 
scaleY(1); } }
+  @keyframes fade-up { from { opacity: 0; transform: translateY(8px); } to { 
opacity: 1; transform: translateY(0); } }
+  @keyframes dash-flow { to { stroke-dashoffset: -20; } }
+  @keyframes pulse-dot { 0%,100%{opacity:.7;r:5} 50%{opacity:1;r:7} }
+  .bar { transform-box: fill-box; transform-origin: bottom; animation: 
grow-bar 0.9s ease-out both; }
+  .fade { animation: fade-up 0.7s ease-out both; }
+  .flow { stroke-dasharray: 5 4; animation: dash-flow 1.8s linear infinite; }
+  .dot { animation: pulse-dot 2.4s ease-in-out infinite; }
+  @media (prefers-reduced-motion: reduce) {
+    .bar,.fade,.flow,.dot { animation: none !important; }
+  }
+</style></defs>
+  <rect width="840" height="420" rx="20" fill="var(--unomi-bg-soft)"/>
+  <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" 
font-weight="700">Java lines of code</text>
+  <text class="fade" style="animation-delay:.08s" x="40" y="58" 
fill="var(--unomi-text-muted)" font-size="12">Physical LOC at release tags 
(production stacked on tests)</text>
+  <line x1="70" y1="330.0" x2="780" y2="330.0" stroke="var(--unomi-border)" 
stroke-width="1"/><text x="64" y="334.0" text-anchor="end" 
fill="var(--unomi-text-muted)" font-size="10">0</text><line x1="70" y1="270.0" 
x2="780" y2="270.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="64" 
y="274.0" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="10">46k</text><line x1="70" y1="210.0" x2="780" y2="210.0" 
stroke="var(--unomi-border)" stroke-width="1"/><text x="64" y="214.0" [...]
+  
+  <g class="fade" style="animation-delay:0.10s">
+    <rect class="bar" style="animation-delay:0.10s" x="110" y="318.7" 
width="70" height="11.3" rx="4" fill="var(--unomi-accent)"/>
+    <rect class="bar" style="animation-delay:0.15s" x="110" y="260.1" 
width="70" height="58.6" rx="4" fill="var(--unomi-primary)"/>
+    <text x="145.0" y="250.1" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">54k</text>
+    <text x="145.0" y="354" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 1</text>
+    <text x="145.0" y="372" text-anchor="middle" 
fill="var(--unomi-text-muted)" font-size="11">1.9.1</text>
+  </g>
+  <g class="fade" style="animation-delay:0.22s">
+    <rect class="bar" style="animation-delay:0.22s" x="290" y="314.8" 
width="70" height="15.2" rx="4" fill="var(--unomi-accent)"/>
+    <rect class="bar" style="animation-delay:0.27s" x="290" y="223.9" 
width="70" height="90.9" rx="4" fill="var(--unomi-primary)"/>
+    <text x="325.0" y="213.9" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">82k</text>
+    <text x="325.0" y="354" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 2</text>
+    <text x="325.0" y="372" text-anchor="middle" 
fill="var(--unomi-text-muted)" font-size="11">2.7.0</text>
+  </g>
+  <g class="fade" style="animation-delay:0.34s">
+    <rect class="bar" style="animation-delay:0.34s" x="470" y="314.4" 
width="70" height="15.6" rx="4" fill="var(--unomi-accent)"/>
+    <rect class="bar" style="animation-delay:0.39s" x="470" y="221.7" 
width="70" height="92.6" rx="4" fill="var(--unomi-primary)"/>
+    <text x="505.0" y="211.7" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">83k</text>
+    <text x="505.0" y="354" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 3.0</text>
+    <text x="505.0" y="372" text-anchor="middle" 
fill="var(--unomi-text-muted)" font-size="11">3.0.1</text>
+  </g>
+  <g class="fade" style="animation-delay:0.46s">
+    <rect class="bar" style="animation-delay:0.46s" x="650" y="248.6" 
width="70" height="81.4" rx="4" fill="var(--unomi-accent)"/>
+    <rect class="bar" style="animation-delay:0.51s" x="650" y="95.6" 
width="70" height="153.0" rx="4" fill="var(--unomi-primary)"/>
+    <text x="685.0" y="85.6" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">181k</text>
+    <text x="685.0" y="354" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 3.1</text>
+    <text x="685.0" y="372" text-anchor="middle" 
fill="var(--unomi-text-muted)" font-size="11">HEAD</text>
+  </g>
+  <g class="fade" style="animation-delay:.6s">
+    <rect x="40" y="385" width="14" height="14" rx="3" 
fill="var(--unomi-primary)"/>
+    <text x="62" y="396" fill="var(--unomi-text)" font-size="12">Production 
(main)</text>
+    <rect x="200" y="385" width="14" height="14" rx="3" 
fill="var(--unomi-accent)"/>
+    <text x="222" y="396" fill="var(--unomi-text)" font-size="12">Tests</text>
+    <text x="780" y="396" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">~3.4x total Java since Unomi 1</text>
+  </g>
+</svg>
diff --git a/src/main/webapp/assets/images/unomi3/numbers-tests.svg 
b/src/main/webapp/assets/images/unomi3/numbers-tests.svg
new file mode 100644
index 0000000..2b39861
--- /dev/null
+++ b/src/main/webapp/assets/images/unomi3/numbers-tests.svg
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 840 420" fill="none" xmlns="http://www.w3.org/2000/svg"; 
role="img"
+     aria-label="Grouped bar chart of unit and integration @Test methods from 
Unomi 1 through 3.1">
+  <defs><style>
+  svg {
+    --unomi-primary: #6d5ce7;
+    --unomi-primary-dark: #5a48c9;
+    --unomi-primary-light: #ede9fe;
+    --unomi-primary-subtle: #f5f3ff;
+    --unomi-accent: #00cec9;
+    --unomi-accent-dark: #0d9488;
+    --unomi-hero-accent: #a78bfa;
+    --unomi-text: #1e293b;
+    --unomi-text-muted: #64748b;
+    --unomi-white: #ffffff;
+    --unomi-bg-soft: #f8fafc;
+    --unomi-border: #e2e8f0;
+    --unomi-success: #059669;
+    --unomi-success-bg: #ecfdf5;
+    --unomi-amber: #d97706;
+    --unomi-amber-bg: #fef3c7;
+    --unomi-info: #2563eb;
+    --unomi-info-bg: #dbeafe;
+    font-family: Inter, system-ui, sans-serif;
+  }
+  @keyframes grow-bar { from { transform: scaleY(0); } to { transform: 
scaleY(1); } }
+  @keyframes fade-up { from { opacity: 0; transform: translateY(8px); } to { 
opacity: 1; transform: translateY(0); } }
+  @keyframes dash-flow { to { stroke-dashoffset: -20; } }
+  @keyframes pulse-dot { 0%,100%{opacity:.7;r:5} 50%{opacity:1;r:7} }
+  .bar { transform-box: fill-box; transform-origin: bottom; animation: 
grow-bar 0.9s ease-out both; }
+  .fade { animation: fade-up 0.7s ease-out both; }
+  .flow { stroke-dasharray: 5 4; animation: dash-flow 1.8s linear infinite; }
+  .dot { animation: pulse-dot 2.4s ease-in-out infinite; }
+  @media (prefers-reduced-motion: reduce) {
+    .bar,.fade,.flow,.dot { animation: none !important; }
+  }
+</style></defs>
+  <rect width="840" height="420" rx="20" fill="var(--unomi-bg-soft)"/>
+  <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" 
font-weight="700">Unit and integration tests</text>
+  <text class="fade" style="animation-delay:.08s" x="40" y="58" 
fill="var(--unomi-text-muted)" font-size="12">Approximate @Test method counts 
(JUnit annotations)</text>
+  <text class="fade" style="animation-delay:.12s" x="40" y="78" 
fill="var(--unomi-text-muted)" font-size="11">Unomi 1: 11 unit / 25 IT classes  
|  Unomi 2: 12 unit / 44 IT classes  |  Unomi 3.0: 15 unit / 43 IT classes  |  
Unomi 3.1: 66 unit / 63 IT classes</text>
+  <line x1="70" y1="320.0" x2="780" y2="320.0" stroke="var(--unomi-border)" 
stroke-width="1"/><text x="64" y="324.0" text-anchor="end" 
fill="var(--unomi-text-muted)" font-size="10">0</text><line x1="70" y1="265.0" 
x2="780" y2="265.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="64" 
y="269.0" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="10">300</text><line x1="70" y1="210.0" x2="780" y2="210.0" 
stroke="var(--unomi-border)" stroke-width="1"/><text x="64" y="214.0" [...]
+  
+  <g class="fade" style="animation-delay:0.10s">
+    <rect class="bar" style="animation-delay:0.10s" x="100" y="313.8" 
width="36" height="6.2" rx="4" fill="var(--unomi-primary)"/>
+    <rect class="bar" style="animation-delay:0.15s" x="142" y="296.5" 
width="36" height="23.5" rx="4" fill="var(--unomi-info)"/>
+    <text x="118" y="305.8" text-anchor="middle" 
fill="var(--unomi-primary-dark)" font-size="11" font-weight="700">34</text>
+    <text x="160" y="288.5" text-anchor="middle" fill="var(--unomi-info)" 
font-size="11" font-weight="700">128</text>
+    <text x="139" y="344" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 1</text>
+    <text x="139" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">1.9.1</text>
+  </g>
+  <g class="fade" style="animation-delay:0.22s">
+    <rect class="bar" style="animation-delay:0.22s" x="280" y="312.3" 
width="36" height="7.7" rx="4" fill="var(--unomi-primary)"/>
+    <rect class="bar" style="animation-delay:0.27s" x="322" y="284.6" 
width="36" height="35.4" rx="4" fill="var(--unomi-info)"/>
+    <text x="298" y="304.3" text-anchor="middle" 
fill="var(--unomi-primary-dark)" font-size="11" font-weight="700">42</text>
+    <text x="340" y="276.6" text-anchor="middle" fill="var(--unomi-info)" 
font-size="11" font-weight="700">193</text>
+    <text x="319" y="344" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 2</text>
+    <text x="319" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">2.7.0</text>
+  </g>
+  <g class="fade" style="animation-delay:0.34s">
+    <rect class="bar" style="animation-delay:0.34s" x="460" y="303.3" 
width="36" height="16.7" rx="4" fill="var(--unomi-primary)"/>
+    <rect class="bar" style="animation-delay:0.39s" x="502" y="284.6" 
width="36" height="35.4" rx="4" fill="var(--unomi-info)"/>
+    <text x="478" y="295.3" text-anchor="middle" 
fill="var(--unomi-primary-dark)" font-size="11" font-weight="700">91</text>
+    <text x="520" y="276.6" text-anchor="middle" fill="var(--unomi-info)" 
font-size="11" font-weight="700">193</text>
+    <text x="499" y="344" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 3.0</text>
+    <text x="499" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">3.0.1</text>
+  </g>
+  <g class="fade" style="animation-delay:0.46s">
+    <rect class="bar" style="animation-delay:0.46s" x="640" y="109.5" 
width="36" height="210.5" rx="4" fill="var(--unomi-primary)"/>
+    <rect class="bar" style="animation-delay:0.51s" x="682" y="248.9" 
width="36" height="71.1" rx="4" fill="var(--unomi-info)"/>
+    <text x="658" y="101.5" text-anchor="middle" 
fill="var(--unomi-primary-dark)" font-size="11" font-weight="700">1,148</text>
+    <text x="700" y="240.9" text-anchor="middle" fill="var(--unomi-info)" 
font-size="11" font-weight="700">388</text>
+    <text x="679" y="344" text-anchor="middle" fill="var(--unomi-text)" 
font-size="13" font-weight="700">Unomi 3.1</text>
+    <text x="679" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">HEAD</text>
+  </g>
+  <g class="fade" style="animation-delay:.7s">
+    <rect x="40" y="385" width="14" height="14" rx="3" 
fill="var(--unomi-primary)"/>
+    <text x="62" y="396" fill="var(--unomi-text)" font-size="12">Unit @Test 
methods</text>
+    <rect x="230" y="385" width="14" height="14" rx="3" 
fill="var(--unomi-info)"/>
+    <text x="252" y="396" fill="var(--unomi-text)" font-size="12">Integration 
@Test methods</text>
+    <text x="780" y="396" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="11">Unit methods ~34x since 1.x</text>
+  </g>
+</svg>
diff --git a/src/main/webapp/assets/images/unomi3/numbers-visits-countries.svg 
b/src/main/webapp/assets/images/unomi3/numbers-visits-countries.svg
new file mode 100644
index 0000000..ae3c884
--- /dev/null
+++ b/src/main/webapp/assets/images/unomi3/numbers-visits-countries.svg
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 840 420" fill="none" xmlns="http://www.w3.org/2000/svg"; 
role="img"
+     aria-label="Top visitor countries for unomi.apache.org from ASF Matomo">
+  <defs><style>
+  svg {
+    --unomi-primary: #6d5ce7; --unomi-primary-dark: #5a48c9; --unomi-accent: 
#00cec9;
+    --unomi-text: #1e293b; --unomi-text-muted: #64748b; --unomi-bg-soft: 
#f8fafc;
+    --unomi-border: #e2e8f0; --unomi-amber: #d97706;
+    font-family: Inter, system-ui, sans-serif;
+  }
+  @keyframes fade-up { from { opacity: 0; } to { opacity: 1; } }
+  .fade { animation: fade-up 0.7s ease-out both; }
+  @media (prefers-reduced-motion: reduce) { .fade { animation: none 
!important; } }
+</style></defs>
+  <rect width="840" height="420" rx="20" fill="var(--unomi-bg-soft)"/>
+  <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" 
font-weight="700">Visitors by country</text>
+  <text class="fade" style="animation-delay:.08s" x="40" y="56" 
fill="var(--unomi-text-muted)" font-size="12">ASF Matomo geo for 
unomi.apache.org - tracking window through Jul 2026</text>
+  <g class="fade" style="animation-delay:.15s">
+  <text x="40" y="90" fill="var(--unomi-text)" font-size="13" 
font-weight="600">United States</text>
+  <text x="800" y="90" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="12">12,941 (45%)</text>
+  <rect x="40" y="96" width="520.0" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.85"/>
+  <rect x="40" y="96" width="520" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.08"/>
+
+  <text x="40" y="126" fill="var(--unomi-text)" font-size="13" 
font-weight="600">China</text>
+  <text x="800" y="126" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="12">2,127 (7%)</text>
+  <rect x="40" y="132" width="85.5" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.85"/>
+  <rect x="40" y="132" width="520" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.08"/>
+
+  <text x="40" y="162" fill="var(--unomi-text)" font-size="13" 
font-weight="600">India</text>
+  <text x="800" y="162" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="12">1,658 (6%)</text>
+  <rect x="40" y="168" width="66.6" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.85"/>
+  <rect x="40" y="168" width="520" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.08"/>
+
+  <text x="40" y="198" fill="var(--unomi-text)" font-size="13" 
font-weight="600">United Kingdom</text>
+  <text x="800" y="198" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="12">1,320 (5%)</text>
+  <rect x="40" y="204" width="53.0" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.85"/>
+  <rect x="40" y="204" width="520" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.08"/>
+
+  <text x="40" y="234" fill="var(--unomi-text)" font-size="13" 
font-weight="600">Vietnam</text>
+  <text x="800" y="234" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="12">809 (3%)</text>
+  <rect x="40" y="240" width="32.5" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.85"/>
+  <rect x="40" y="240" width="520" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.08"/>
+
+  <text x="40" y="270" fill="var(--unomi-text)" font-size="13" 
font-weight="600">Russia</text>
+  <text x="800" y="270" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="12">707 (2%)</text>
+  <rect x="40" y="276" width="28.4" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.85"/>
+  <rect x="40" y="276" width="520" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.08"/>
+
+  <text x="40" y="306" fill="var(--unomi-text)" font-size="13" 
font-weight="600">France</text>
+  <text x="800" y="306" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="12">700 (2%)</text>
+  <rect x="40" y="312" width="28.1" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.85"/>
+  <rect x="40" y="312" width="520" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.08"/>
+
+  <text x="40" y="342" fill="var(--unomi-text)" font-size="13" 
font-weight="600">Germany</text>
+  <text x="800" y="342" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="12">672 (2%)</text>
+  <rect x="40" y="348" width="27.0" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.85"/>
+  <rect x="40" y="348" width="520" height="10" rx="5" 
fill="var(--unomi-primary)" fill-opacity="0.08"/>
+</g>
+  <text x="40" y="400" fill="var(--unomi-text-muted)" 
font-size="11">Desktop-heavy traffic (~88%) · mostly direct and search · Matomo 
bot filtering differs from CDN download logs</text>
+</svg>
diff --git a/src/main/webapp/assets/images/unomi3/numbers-visits.svg 
b/src/main/webapp/assets/images/unomi3/numbers-visits.svg
new file mode 100644
index 0000000..bfc473c
--- /dev/null
+++ b/src/main/webapp/assets/images/unomi3/numbers-visits.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg viewBox="0 0 840 420" fill="none" xmlns="http://www.w3.org/2000/svg"; 
role="img"
+     aria-label="Monthly visits to unomi.apache.org from Matomo, January 2025 
through July 2026">
+  <defs>
+    <style>
+  svg {
+    --unomi-primary: #6d5ce7; --unomi-primary-dark: #5a48c9; --unomi-accent: 
#00cec9;
+    --unomi-text: #1e293b; --unomi-text-muted: #64748b; --unomi-bg-soft: 
#f8fafc;
+    --unomi-border: #e2e8f0; --unomi-amber: #d97706;
+    font-family: Inter, system-ui, sans-serif;
+  }
+  @keyframes fade-up { from { opacity: 0; } to { opacity: 1; } }
+  .fade { animation: fade-up 0.7s ease-out both; }
+  @media (prefers-reduced-motion: reduce) { .fade { animation: none 
!important; } }
+</style>
+    <linearGradient id="visitFill" x1="0" y1="80" x2="0" y2="340" 
gradientUnits="userSpaceOnUse">
+      <stop stop-color="#6d5ce7" stop-opacity="0.25"/>
+      <stop offset="1" stop-color="#6d5ce7" stop-opacity="0.02"/>
+    </linearGradient>
+  </defs>
+  <rect width="840" height="420" rx="20" fill="var(--unomi-bg-soft)"/>
+  <text class="fade" x="40" y="36" fill="var(--unomi-text)" font-size="18" 
font-weight="700">Website visits</text>
+  <text class="fade" style="animation-delay:.08s" x="40" y="56" 
fill="var(--unomi-text-muted)" font-size="12">unomi.apache.org via ASF Matomo - 
monthly series Jan 2025 to Jul 2026</text>
+  <line x1="70" y1="340.0" x2="800" y2="340.0" stroke="var(--unomi-border)" 
stroke-width="1"/><text x="62" y="344.0" text-anchor="end" 
fill="var(--unomi-text-muted)" font-size="10">0</text><line x1="70" y1="275.0" 
x2="800" y2="275.0" stroke="var(--unomi-border)" stroke-width="1"/><text x="62" 
y="279.0" text-anchor="end" fill="var(--unomi-text-muted)" 
font-size="10">542</text><line x1="70" y1="210.0" x2="800" y2="210.0" 
stroke="var(--unomi-border)" stroke-width="1"/><text x="62" y="214.0" [...]
+  <g class="fade" style="animation-delay:.12s"><rect x="57.5" y="269.3" 
width="25.0" height="70.7" rx="3" fill="var(--unomi-primary)" 
fill-opacity="0.35"/><rect x="98.1" y="141.2" width="25.0" height="198.8" 
rx="3" fill="var(--unomi-primary)" fill-opacity="0.35"/><rect x="138.6" 
y="113.9" width="25.0" height="226.1" rx="3" fill="var(--unomi-primary)" 
fill-opacity="0.35"/><rect x="179.2" y="133.3" width="25.0" height="206.7" 
rx="3" fill="var(--unomi-primary)" fill-opacity="0.35"/><rect x= [...]
+  <polygon class="fade" style="animation-delay:.18s" points="70.0,340.0 
70.0,269.3 110.6,141.2 151.1,113.9 191.7,133.3 232.2,141.2 272.8,148.7 
313.3,155.8 353.9,146.7 394.4,135.6 435.0,151.9 475.6,148.7 516.1,163.6 
556.7,142.2 597.2,166.9 637.8,123.3 678.3,182.8 718.9,158.6 759.4,141.6 
800.0,241.6 800.0,340.0" fill="url(#visitFill)"/>
+  <polyline class="fade" style="animation-delay:.22s" points="70.0,269.3 
110.6,141.2 151.1,113.9 191.7,133.3 232.2,141.2 272.8,148.7 313.3,155.8 
353.9,146.7 394.4,135.6 435.0,151.9 475.6,148.7 516.1,163.6 556.7,142.2 
597.2,166.9 637.8,123.3 678.3,182.8 718.9,158.6 759.4,141.6 800.0,241.6" 
fill="none" stroke="var(--unomi-primary)" stroke-width="3" 
stroke-linecap="round" stroke-linejoin="round"/>
+  <text x="70.0" y="362" text-anchor="middle" fill="var(--unomi-text-muted)" 
font-size="11">Jan 25</text><text x="191.7" y="362" text-anchor="middle" 
fill="var(--unomi-text-muted)" font-size="11">Apr 25</text><text x="313.3" 
y="362" text-anchor="middle" fill="var(--unomi-text-muted)" font-size="11">Jul 
25</text><text x="435.0" y="362" text-anchor="middle" 
fill="var(--unomi-text-muted)" font-size="11">Oct 25</text><text x="556.7" 
y="362" text-anchor="middle" fill="var(--unomi-text-muted)" [...]
+  <circle cx="800.0" cy="241.6" r="5" fill="var(--unomi-primary)"/>
+  <text x="40" y="402" fill="var(--unomi-text-muted)" font-size="12">28,795 
visits total · 62,762 actions · ~91s avg · bounce 67%</text>
+</svg>
diff --git a/src/main/webapp/blog/index.html b/src/main/webapp/blog/index.html
index c83f6e5..1f861d8 100644
--- a/src/main/webapp/blog/index.html
+++ b/src/main/webapp/blog/index.html
@@ -213,6 +213,22 @@ layout: default
             </div>
         </div>
         <div class="row g-4">
+            <div class="col-md-6 col-lg-3">
+                <article class="feature-card h-100 overflow-hidden p-0 
position-relative">
+                    <a href="/blog/unomi-3-in-numbers.html" class="d-block" 
aria-label="Read Unomi 3 in numbers">
+                        <img src="/assets/images/unomi3/numbers-hero.svg" 
class="img-fluid w-100" alt="Unomi 3 metrics charts" loading="lazy">
+                    </a>
+                    <div class="p-3">
+                        <span class="badge badge-primary mb-2">Metrics</span>
+                        <h3 class="h6 fw-semibold mb-2">
+                            <a href="/blog/unomi-3-in-numbers.html" 
class="text-decoration-none text-body">Unomi 3 in numbers</a>
+                        </h3>
+                        <p class="small text-muted mb-0">LOC, tests, and 
contributors &mdash; 1.x to 3.1.</p>
+                        <a href="/blog/unomi-3-in-numbers.html" 
class="stretched-link" aria-label="Read Unomi 3 in numbers"></a>
+                    </div>
+                </article>
+            </div>
+
             <div class="col-md-6 col-lg-3">
                 <article class="feature-card h-100 overflow-hidden p-0 
position-relative">
                     <a href="/blog/unomi-3-platform-foundation.html" 
class="d-block" aria-label="Read platform foundation">
@@ -339,8 +355,11 @@ layout: default
             <div class="col-lg-5">
                 <div class="feature-card h-100">
                     <p class="section-label mb-3">Browse by topic</p>
+                    <p class="small mb-3"><a href="#all-posts">View all posts 
by date</a></p>
                     <div class="d-flex flex-wrap gap-2 mb-4">
                         <a href="/blog/introducing-apache-unomi-3.html" 
class="badge badge-primary text-decoration-none px-3 py-2">Unomi 3</a>
+                        <a href="/blog/unomi-3-in-numbers.html" class="badge 
badge-primary text-decoration-none px-3 py-2">In numbers</a>
+                        
                         <a href="/blog/unomi-3-multitenancy.html" class="badge 
badge-primary text-decoration-none px-3 py-2">Multi-tenancy</a>
                         <a href="/blog/unomi-3-ai-agents.html" class="badge 
badge-primary text-decoration-none px-3 py-2">AI agents</a>
                         <a href="/blog/migrating-to-unomi-3.html" class="badge 
badge-primary text-decoration-none px-3 py-2">Migration</a>
@@ -364,6 +383,198 @@ layout: default
     </div>
 </section>
 
+
+
+
+<!-- Chronological archive (featured layout above stays as-is) -->
+<section class="section section-alt" id="all-posts">
+    <div class="container">
+        <div class="row justify-content-center">
+            <div class="col-lg-9">
+                <div class="bg-white rounded-3 shadow-sm overflow-hidden">
+                    <button class="btn btn-link text-decoration-none text-body 
w-100 text-start px-4 py-3 d-flex align-items-center justify-content-between 
collapsed"
+                            type="button"
+                            data-bs-toggle="collapse"
+                            data-bs-target="#blog-archive-list"
+                            aria-expanded="false"
+                            aria-controls="blog-archive-list">
+                        <span>
+                            <span class="section-label d-block 
mb-1">Archive</span>
+                            <span class="h5 mb-0 fw-semibold">All posts by 
date</span>
+                            <span class="d-block small text-muted mt-1">Newest 
first &middot; 16 posts</span>
+                        </span>
+                        <i class="bi bi-chevron-down fs-5 text-muted" 
aria-hidden="true"></i>
+                    </button>
+                    <div class="collapse" id="blog-archive-list">
+                        <div class="list-group list-group-flush border-top">
+                    <a href="/blog/unomi-3-in-numbers.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">16</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Unomi 3 in 
numbers</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-07-17T10:00:00Z">Jul 17, 2026 &middot; 10:00 UTC</time>
+                            </div>
+                            <div class="small text-muted">LOC, tests, 
downloads, visitors</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-openapi-enrichment.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">15</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Richer OpenAPI docs 
in Unomi 3.1</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-07-15T13:58:00Z">Jul 15, 2026 &middot; 13:58 UTC</time>
+                            </div>
+                            <div class="small text-muted">Javadoc-enriched 
OpenAPI</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-manual.html" class="list-group-item 
list-group-item-action d-flex gap-3 py-3 border-0 border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">14</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Unomi 3 manual 
improvements</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-07-13T12:51:00Z">Jul 13, 2026 &middot; 12:51 UTC</time>
+                            </div>
+                            <div class="small text-muted">Branded docs and 
PlantUML</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-build-sh.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">13</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">One entry point — 
Unomi 3 build.sh</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-07-11T11:44:00Z">Jul 11, 2026 &middot; 11:44 UTC</time>
+                            </div>
+                            <div class="small text-muted">One script for build 
and deploy</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-it-execution.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">12</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Clearer integration 
test runs in Unomi 3</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-07-09T10:37:00Z">Jul 09, 2026 &middot; 10:37 UTC</time>
+                            </div>
+                            <div class="small text-muted">Clearer IT progress 
and CI</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-unit-testing-harness.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">11</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Faster Unomi service 
unit tests</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-07-07T09:30:00Z">Jul 07, 2026 &middot; 09:30 UTC</time>
+                            </div>
+                            <div class="small text-muted">Fast in-memory unit 
tests</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-request-tracing.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">10</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Request tracing with 
explain=true in Unomi 3</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-07-05T13:23:00Z">Jul 05, 2026 &middot; 13:23 UTC</time>
+                            </div>
+                            <div class="small text-muted">Debug with 
explain=true</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-task-scheduler.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">09</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Cluster-aware task 
scheduler in Unomi 3.1</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-07-03T12:16:00Z">Jul 03, 2026 &middot; 12:16 UTC</time>
+                            </div>
+                            <div class="small text-muted">Cluster-aware 
scheduled work</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-shell-commands.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">08</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Unomi 3 Karaf shell 
commands</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-07-01T11:59:00Z">Jul 01, 2026 &middot; 11:59 UTC</time>
+                            </div>
+                            <div class="small text-muted">Ops via Karaf 
shell</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-opensearch.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">07</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Elasticsearch 9 and 
OpenSearch in Unomi 3</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-06-29T10:52:00Z">Jun 29, 2026 &middot; 10:52 UTC</time>
+                            </div>
+                            <div class="small text-muted">Dual search 
backends</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-platform-foundation.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">06</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Unomi 3 platform 
foundation</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-06-27T09:45:00Z">Jun 27, 2026 &middot; 09:45 UTC</time>
+                            </div>
+                            <div class="small text-muted">Java 17, Karaf 4.4, 
ES9, clustering</div>
+                        </div>
+                    </a>
+                    <a href="/blog/migrating-to-unomi-3.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">05</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Migrating from Unomi 
2.x to Unomi 3</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-06-25T13:38:00Z">Jun 25, 2026 &middot; 13:38 UTC</time>
+                            </div>
+                            <div class="small text-muted">Upgrade path from 
2.x</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-ai-agents.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">04</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Apache Unomi 3 for 
AI agents</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-06-23T12:31:00Z">Jun 23, 2026 &middot; 12:31 UTC</time>
+                            </div>
+                            <div class="small text-muted">Privacy-first 
customer memory for agents</div>
+                        </div>
+                    </a>
+                    <a href="/blog/unomi-3-multitenancy.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">03</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Multi-tenancy in 
Apache Unomi 3</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-06-21T11:24:00Z">Jun 21, 2026 &middot; 11:24 UTC</time>
+                            </div>
+                            <div class="small text-muted">Platform-wide tenant 
isolation</div>
+                        </div>
+                    </a>
+                    <a href="/blog/introducing-apache-unomi-3.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">02</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Introducing Apache 
Unomi 3</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-06-19T10:17:00Z">Jun 19, 2026 &middot; 10:17 UTC</time>
+                            </div>
+                            <div class="small text-muted">The major Unomi 3 
release story</div>
+                        </div>
+                    </a>
+                    <a href="/blog/welcome-to-the-apache-unomi-blog.html" 
class="list-group-item list-group-item-action d-flex gap-3 py-3 border-0 
border-bottom rounded-0">
+                        <span class="text-primary fw-bold" 
style="min-width:2rem">01</span>
+                        <div class="flex-grow-1">
+                            <div class="d-flex flex-wrap 
justify-content-between gap-2">
+                                <strong class="text-body">Welcome to the 
Apache Unomi Blog</strong>
+                                <time class="small text-muted text-nowrap" 
datetime="2026-06-17T09:10:00Z">Jun 17, 2026 &middot; 09:10 UTC</time>
+                            </div>
+                            <div class="small text-muted">Why this Blog 
exists</div>
+                        </div>
+                    </a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</section>
+
 <!-- Reading order -->
 <section class="section">
     <div class="container">
@@ -404,7 +615,8 @@ layout: default
             </div>
         </div>
         <p class="text-center small text-muted mt-4 mb-0">
-            Then: <a href="/blog/migrating-to-unomi-3.html">Migrating from 
2.x</a> &middot;
+            Then: <a href="/blog/unomi-3-in-numbers.html">In numbers</a> 
&middot;
+            <a href="/blog/migrating-to-unomi-3.html">Migrating from 2.x</a> 
&middot;
             <a href="/blog/unomi-3-platform-foundation.html">Platform</a> 
&middot;
             <a href="/blog/unomi-3-opensearch.html">OpenSearch</a> &middot;
             <a href="/blog/unomi-3-shell-commands.html">Shell</a> &middot;
diff --git a/src/main/webapp/blog/introducing-apache-unomi-3.html 
b/src/main/webapp/blog/introducing-apache-unomi-3.html
index 1bb690e..e0e0525 100644
--- a/src/main/webapp/blog/introducing-apache-unomi-3.html
+++ b/src/main/webapp/blog/introducing-apache-unomi-3.html
@@ -3,6 +3,7 @@ title: Introducing Apache Unomi 3
 description: "Apache Unomi 3 is a major open-source CDP release — 
Elasticsearch 9 / OpenSearch, multi-tenancy, stable REST, quality investment, 
and AI-ready customer memory."
 keywords: "Apache Unomi 3, open source CDP, multi-tenancy, OpenSearch, V2 API 
compatibility, CDP tests"
 layout: default
+date: 2026-06-19 10:17:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3</p>
         <h1>Introducing Apache Unomi 3</h1>
         <p class="lead">A major Customer Data Platform release &mdash; 
platform foundation (3.0) and product leap (3.1) as one story.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 <section class="section">
diff --git a/src/main/webapp/blog/migrating-to-unomi-3.html 
b/src/main/webapp/blog/migrating-to-unomi-3.html
index 83520ad..c0d31de 100644
--- a/src/main/webapp/blog/migrating-to-unomi-3.html
+++ b/src/main/webapp/blog/migrating-to-unomi-3.html
@@ -3,6 +3,7 @@ title: Migrating from Unomi 2.x to Unomi 3
 description: "Unomi 3 retains 100% of documented 2.x OpenAPI paths and offers 
V2 compatibility mode for phased client auth migration."
 keywords: "Unomi 3 migration, V2 compatibility mode, OpenAPI stability, 
upgrade from Unomi 2"
 layout: default
+date: 2026-06-25 13:38:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Upgrades</p>
         <h1>Migrating from Unomi 2.x to Unomi 3</h1>
         <p class="lead">Familiar REST map, additive APIs, and an optional V2 
compatibility bridge.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 <section class="section">
diff --git a/src/main/webapp/blog/unomi-3-ai-agents.html 
b/src/main/webapp/blog/unomi-3-ai-agents.html
index 2a9d3e1..b6f4a33 100644
--- a/src/main/webapp/blog/unomi-3-ai-agents.html
+++ b/src/main/webapp/blog/unomi-3-ai-agents.html
@@ -3,6 +3,7 @@ title: Apache Unomi 3 for AI agents — privacy-first customer 
memory
 description: "Apache Unomi 3 is an open-source CDP AI agents can query and 
update — retrieve customer context, store observations, summarize profiles, and 
build segments with built-in consent."
 keywords: "Apache Unomi 3, AI agents CDP, LLM customer context, open source 
CDP for AI, agentic personalization, consent-aware AI data layer"
 layout: default
+date: 2026-06-23 12:31:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">AI agents &middot; Second 
message</p>
         <h1>Apache Unomi 3 for AI agents</h1>
         <p class="lead">Privacy-first customer memory &mdash; retrieve, store, 
summarize, and build segments. Unomi is not an LLM; it is the data layer agents 
plug into.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 <section class="section">
diff --git a/src/main/webapp/blog/unomi-3-build-sh.html 
b/src/main/webapp/blog/unomi-3-build-sh.html
index e06759e..c835d1e 100644
--- a/src/main/webapp/blog/unomi-3-build-sh.html
+++ b/src/main/webapp/blog/unomi-3-build-sh.html
@@ -3,6 +3,7 @@ title: One entry point — Unomi 3 build.sh
 description: "How Apache Unomi 3 replaced scattered build scripts with a 
single build.sh — preflight checks, CI mode, integration tests, deploy, and 
Windows build.ps1 parity."
 keywords: "Unomi build.sh, Apache Unomi developer experience, UNOMI-887, Maven 
CI, OpenSearch integration tests"
 layout: default
+date: 2026-07-11 11:44:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3 &middot; Developer 
experience</p>
         <h1>One entry point: <code>build.sh</code></h1>
         <p class="lead">Preflight, build, test, and deploy &mdash; the same 
script locally and on GitHub Actions.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/unomi-3-in-numbers.html 
b/src/main/webapp/blog/unomi-3-in-numbers.html
new file mode 100644
index 0000000..f6a6254
--- /dev/null
+++ b/src/main/webapp/blog/unomi-3-in-numbers.html
@@ -0,0 +1,139 @@
+---
+title: Unomi 3 in numbers
+description: "Unomi 1 vs 2 vs 3 in numbers — lines of code, tests, 
contributors, ASF downloads, and unomi.apache.org Matomo visits."
+keywords: "Unomi 3 metrics, lines of code, unit tests, integration tests, 
contributors, downloads, website visits, Matomo, Apache Unomi statistics"
+layout: default
+date: 2026-07-17 10:00:00 +0000
+---
+
+<section class="page-header">
+    <div class="container">
+        <p class="section-label text-hero-label">Unomi 3 &middot; Metrics</p>
+        <h1>Unomi 3 in numbers</h1>
+        <p class="lead">How the codebase, tests, community, downloads, and 
website traffic grew from Unomi 1 through Unomi 3.1.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
+    </div>
+</section>
+
+<section class="section">
+    <div class="container col-lg-8">
+        <p>Unomi 3 is not only new features &mdash; multi-tenancy, OpenSearch, 
shell CRUD, tracing, and the task scheduler.
+            It is also a step-change in how much of the platform is covered by 
automated tests, and how the Java surface area evolved across a decade of 
releases.</p>
+        <p>The charts below use <strong>release tags</strong> as waypoints:
+            Unomi&nbsp;1 = <code>unomi-root-1.9.1</code>,
+            Unomi&nbsp;2 = <code>unomi-root-2.7.0</code>,
+            Unomi&nbsp;3.0 = <code>unomi-root-3.0.1</code>,
+            Unomi&nbsp;3.1 = current <code>master</code> (work in progress 
after 3.0.1).
+            Counts are physical lines of code and approximate JUnit 
<code>@Test</code> annotations from the git tree at each tag.</p>
+
+        <img src="/assets/images/unomi3/numbers-hero.svg" class="img-fluid 
my-4" alt="Unomi 3 headline metrics: 181k Java LOC, 1148 unit tests, 388 IT 
tests, 77 contributors"/>
+
+        <h2 class="h4 mt-5 mb-3">Lines of code</h2>
+        <p>Production Java grew from about <strong>45k</strong> lines in late 
Unomi&nbsp;1 to about <strong>70k</strong> in Unomi&nbsp;2 and 
<strong>71k</strong> at Unomi&nbsp;3.0.1 &mdash; then jumped to about 
<strong>118k</strong> on the 3.1 line.
+            Test Java stayed modest through 3.0 (~12k) and then leapt to about 
<strong>63k</strong> with the unit harness, multi-tenancy coverage, and related 
work.</p>
+        <img src="/assets/images/unomi3/numbers-loc.svg" class="img-fluid 
my-4" alt="Stacked bar chart of Java production and test lines of code across 
Unomi 1, 2, 3.0, and 3.1"/>
+        <p class="small text-muted">Methodology: physical line counts over 
<code>.java</code> (and primary companion sources for totals elsewhere) from 
<code>git archive</code> at each tag; paths under <code>target/</code> excluded.
+            &ldquo;Main&rdquo; is everything outside <code>src/test/</code> 
and <code>itests/</code>; &ldquo;Tests&rdquo; is those trees.</p>
+
+        <h2 class="h4 mt-5 mb-3">Unit and integration tests</h2>
+        <p>Integration tests have been Unomi&rsquo;s historical strength 
&mdash; growing from roughly <strong>128</strong> IT <code>@Test</code> methods 
in 1.9.1 to about <strong>388</strong> on 3.1.
+            The dramatic change is on the unit side: from a few dozen methods 
through Unomi&nbsp;2 to about <strong>1,148</strong> on 3.1, driven by the 
in-memory persistence harness and denser service-level coverage.</p>
+        <img src="/assets/images/unomi3/numbers-tests.svg" class="img-fluid 
my-4" alt="Grouped bars of unit versus integration @Test method counts across 
Unomi versions"/>
+        <ul>
+            <li><strong>Unit classes</strong> &mdash; 11 &rarr; 12 &rarr; 15 
&rarr; <strong>66</strong></li>
+            <li><strong>IT classes</strong> &mdash; 25 &rarr; 44 &rarr; 43 
&rarr; <strong>63</strong></li>
+            <li><strong>Test / main Java ratio</strong> &mdash; ~0.19 in 1.x, 
~0.17 in 2.x, ~<strong>0.53</strong> on 3.1</li>
+        </ul>
+        <p>That ratio shift is intentional: Unomi&nbsp;3 invests in fast 
feedback for rules, persistence, and tenancy while keeping the heavier IT suite 
for end-to-end confidence.
+            See also
+            <a href="/blog/unomi-3-unit-testing-harness.html">the unit testing 
harness</a>
+            and
+            <a href="/blog/unomi-3-it-execution.html">clearer IT 
execution</a>.</p>
+
+        <h2 class="h4 mt-5 mb-3">Contributors</h2>
+        <p>The project has grown to <strong>77</strong> unique authors on the 
master ancestry (name-based; some people appear under multiple spellings), with 
about <strong>2,769</strong> commits through current <code>master</code>.
+            Late Unomi&nbsp;1 already had a broad base (~62 authors); 
Unomi&nbsp;2 and&nbsp;3 continue that trajectory rather than restarting it.</p>
+        <img src="/assets/images/unomi3/numbers-contributors.svg" 
class="img-fluid my-4" alt="Line chart of cumulative unique contributors from 
Unomi 1 through 3.1"/>
+        <p class="small text-muted">Top committers historically include Serge 
Huber, Thomas Draier, Abdelkader Midani, Sergiy Shyrkov, and many others who 
shaped incubating 1.x through the 3.x platform leap.</p>
+
+        <h2 class="h4 mt-5 mb-3">Downloads</h2>
+        <p>Apache Infra publishes monthly CDN hit logs for Unomi on
+            <a 
href="https://infra-reports.apache.org/download_stats/unomi/";>infra-reports.apache.org</a>
+            (hosts <code>dlcdn.apache.org</code> and 
<code>downloads.apache.org</code>).
+            From July&nbsp;2023 through mid-July&nbsp;2026 that is about
+            <strong>43k</strong> software-package hits in total (binary + 
source archives) &mdash;
+            that figure <em>includes</em> bots.
+            Of those, about <strong>25k</strong> (~58%) are identifiable as 
bots or scripts
+            (Python Requests, scrapers, GPTBot, curl/wget, HeadlessChrome, and 
similar),
+            leaving about <strong>18k</strong> from recognizable human browsers
+            (the purple series below).
+            ASF&rsquo;s coarse UA labels will miss some bots, so the human 
count is likely still a bit high.</p>
+        <img src="/assets/images/unomi3/numbers-downloads.svg" 
class="img-fluid my-4" alt="Cumulative Unomi software package downloads: human 
browsers only, all packages including bots, and known bots, July 2023 through 
July 2026"/>
+        <p class="small text-muted">Methodology: classify each file&rsquo;s 
<code>useragents</code> labels into human browsers vs known bots/scripts
+            (plus a small unknown bucket), then cumulate over the available 
months.
+            Solid purple = human browsers only (bots excluded);
+            solid teal = all software-package hits (bots included);
+            dotted muted purple = identified bots/scripts only.
+            Manuals/PDFs are excluded from all three series. Stats start 
mid-2023; July&nbsp;2026 is partial.
+            Vertical markers: teal <strong>2.7.0</strong> (Sep&nbsp;2025 
release), amber <strong>3.0</strong> (Nov&nbsp;2025),
+            and purple <strong>Site refresh</strong> (Feb&nbsp;2026 website 
modernization).
+            Bot detection is only as good as the coarse UA labels ASF 
publishes &mdash; some automated clients
+            (or bots spoofing a normal browser) will not be flagged, so the 
human series is a best-effort lower-noise estimate,
+            not a guarantee of bot-free traffic.</p>
+
+        <h3 class="h5 mt-4 mb-3">Where downloads come from</h3>
+        <p>Country totals use the same bot filter: each file&rsquo;s 
<code>cca2</code> hits are scaled by that file&rsquo;s human-browser share
+            (ASF does not publish a full country&times;UA cross-tab).
+            After filtering, the densest human-attributed package volume is
+            <strong>Brazil</strong> (~24%), <strong>China</strong> (~18%), 
<strong>Russia</strong> (~17%),
+            the <strong>United States</strong> (~12%), and 
<strong>Germany</strong> (~8%),
+            with a long tail across Europe and Asia (~102 countries with 
attributed human traffic).</p>
+        <img src="/assets/images/unomi3/numbers-downloads-map.svg" 
class="img-fluid my-4" alt="World map of human Unomi software package downloads 
by country with bots excluded"/>
+        <p class="small text-muted">Map colors use a log scale. Geo still 
comes from CDN IP attribution, so mirrors, cloud egress, and VPNs can skew 
rankings
+            even after UA filtering. Hover a country in a supporting browser 
for its estimate.</p>
+
+        <h2 class="h4 mt-5 mb-3">Website visitors</h2>
+        <p>Separately from CDN package logs, ASF runs
+            <a 
href="https://analytics.apache.org/index.php?module=CoreHome&amp;action=index&amp;idSite=61&amp;period=day&amp;date=yesterday";>Matomo
 analytics</a>
+            for <a href="https://unomi.apache.org";>unomi.apache.org</a> (site 
id&nbsp;61).
+            Across the available reporting window that is about
+            <strong>29k</strong> visits and <strong>63k</strong> actions,
+            with roughly <strong>2.2</strong> actions per visit, about
+            <strong>91</strong> seconds average time on site, and a
+            <strong>67%</strong> bounce rate.
+            Monthly series in Matomo are populated from January&nbsp;2025 
onward
+            (the site record was created mid-2024; earlier months are empty in 
the API).</p>
+        <img src="/assets/images/unomi3/numbers-visits.svg" class="img-fluid 
my-4" alt="Monthly visits to unomi.apache.org from ASF Matomo, January 2025 
through July 2026"/>
+        <p>Traffic is mostly <strong>desktop</strong> (~88%), arrives 
primarily via
+            <strong>direct entry</strong> and <strong>search engines</strong>,
+            and is led by visitors attributed to the <strong>United 
States</strong> (~45%),
+            then China, India, and the United Kingdom.
+            Top content is the home page, Getting Started, Documentation, Use 
Cases, and Download.</p>
+        <img src="/assets/images/unomi3/numbers-visits-countries.svg" 
class="img-fluid my-4" alt="Top visitor countries for unomi.apache.org from ASF 
Matomo"/>
+        <p class="small text-muted">Source:
+            <a 
href="https://analytics.apache.org/index.php?module=CoreHome&amp;action=index&amp;idSite=61";>analytics.apache.org</a>
+            (public Matomo Reporting API). Matomo applies its own bot 
filtering, so country mix here will not match CDN download geography.
+            Figures are visits (not unique visitors for the full range &mdash; 
Matomo disables that metric on long ranges).</p>
+
+        <h2 class="h4 mt-5 mb-3">What the numbers mean</h2>
+        <ul>
+            <li><strong>3.0 was the platform cut</strong> &mdash; LOC and IT 
counts look close to late 2.x; the runtime floor (Java&nbsp;17, ES9, 
clustering) landed without ballooning the tree.</li>
+            <li><strong>3.1 is the product + quality leap</strong> &mdash; 
multi-tenancy and ops features arrive together with a much denser unit 
suite.</li>
+            <li><strong>Community continuity</strong> &mdash; contributor 
growth is cumulative; Unomi remains a long-running ASF codebase with steady 
authorship.</li>
+            <li><strong>Steady distribution</strong> &mdash; after excluding 
known bots/scripts (~58% of package hits), human browser downloads still show 
continuous uptake across three years of logged CDN data.</li>
+            <li><strong>Site interest holds</strong> &mdash; Matomo shows a 
steady ~1.5&ndash;1.9k visits/month through 2025&ndash;2026, concentrated on 
getting-started and docs paths.</li>
+        </ul>
+
+        <p class="mt-4">
+            <a class="btn btn-primary" 
href="/blog/introducing-apache-unomi-3.html">Introducing Unomi 3</a>
+            <a class="btn btn-outline-primary ms-2" 
href="/blog/unomi-3-unit-testing-harness.html">Unit harness</a>
+            <a class="btn btn-outline-primary ms-2" 
href="/blog/unomi-3-platform-foundation.html">Platform foundation</a>
+        </p>
+        <p><a href="/blog/">&larr; All posts</a></p>
+    </div>
+</section>
diff --git a/src/main/webapp/blog/unomi-3-it-execution.html 
b/src/main/webapp/blog/unomi-3-it-execution.html
index e620d92..8592d44 100644
--- a/src/main/webapp/blog/unomi-3-it-execution.html
+++ b/src/main/webapp/blog/unomi-3-it-execution.html
@@ -3,6 +3,7 @@ title: Clearer integration test runs in Unomi 3
 description: "How Unomi 3 improved IT execution — live progress bars with ETA, 
quieter Maven and Docker output, and tooling for diagnosing long CI suites."
 keywords: "Unomi 3 integration tests, ProgressListener, CI logs, Apache Unomi 
IT, build.sh"
 layout: default
+date: 2026-07-09 10:37:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3 &middot; Quality</p>
         <h1>Clearer integration test runs in Unomi 3</h1>
         <p class="lead">Progress bars, quieter CI output, and tools that turn 
a 90&ndash;minute suite into something you can actually follow.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/unomi-3-manual.html 
b/src/main/webapp/blog/unomi-3-manual.html
index cebe055..258b8e2 100644
--- a/src/main/webapp/blog/unomi-3-manual.html
+++ b/src/main/webapp/blog/unomi-3-manual.html
@@ -3,6 +3,7 @@ title: Unomi 3 manual — PlantUML, branded theme, deeper docs
 description: "How the Apache Unomi 3 manual gained PlantUML/GraphViz diagrams, 
an Unomi-branded HTML/PDF theme, and major content expansions for migrations, 
multi-tenancy, and platform upgrades."
 keywords: "Apache Unomi manual, PlantUML AsciiDoc, Unomi 3 documentation, 
AsciiDoctor diagrams, Unomi CSS theme"
 layout: default
+date: 2026-07-13 12:51:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3 &middot; 
Documentation</p>
         <h1>Unomi 3 manual: diagrams, theme, and depth</h1>
         <p class="lead">PlantUML in AsciiDoc, a brand-aligned HTML/PDF look, 
and chapters that finally match the Unomi 3 platform.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/unomi-3-multitenancy.html 
b/src/main/webapp/blog/unomi-3-multitenancy.html
index 9b32c47..96f2563 100644
--- a/src/main/webapp/blog/unomi-3-multitenancy.html
+++ b/src/main/webapp/blog/unomi-3-multitenancy.html
@@ -3,6 +3,7 @@ title: Multi-tenancy in Apache Unomi 3
 description: "Unomi 3 multi-tenancy was a major platform refactor — tenant 
isolation for profiles, events, segments, rules, and schemas with public and 
private API keys."
 keywords: "Unomi 3 multi-tenancy, tenant API keys, SaaS CDP, Apache Unomi 
tenants, multi-tenant CDP"
 layout: default
+date: 2026-06-21 11:24:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3 &middot; 
Multi-tenancy</p>
         <h1>Multi-tenancy in Apache Unomi 3</h1>
         <p class="lead">A major platform refactoring &mdash; not a bolt-on 
flag. Isolate customer spaces with API keys on one cluster.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/unomi-3-openapi-enrichment.html 
b/src/main/webapp/blog/unomi-3-openapi-enrichment.html
index e400418..4abe8bd 100644
--- a/src/main/webapp/blog/unomi-3-openapi-enrichment.html
+++ b/src/main/webapp/blog/unomi-3-openapi-enrichment.html
@@ -3,6 +3,7 @@ title: Richer OpenAPI docs in Unomi 3.1
 description: "How Unomi 3.1 enriches /cxs/openapi.json from JAX-RS Javadoc — 
summaries, statuses, examples, and schemas without duplicating Swagger 
annotations (UNOMI-963)."
 keywords: "Unomi OpenAPI, openapi.json, UNOMI-963, CXF OpenAPI, Javadoc 
enrichment, Unomi 3.1 REST"
 layout: default
+date: 2026-07-15 13:58:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3.1 &middot; Developer 
experience</p>
         <h1>Richer OpenAPI docs from Javadoc</h1>
         <p class="lead">Unomi 3.1 ships an enriched 
<code>/cxs/openapi.json</code> &mdash; summaries, status codes, examples, and 
schema notes merged from endpoint Javadoc.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/unomi-3-opensearch.html 
b/src/main/webapp/blog/unomi-3-opensearch.html
index 02db882..537b1e0 100644
--- a/src/main/webapp/blog/unomi-3-opensearch.html
+++ b/src/main/webapp/blog/unomi-3-opensearch.html
@@ -3,6 +3,7 @@ title: Elasticsearch 9 and OpenSearch in Unomi 3
 description: "Unomi 3 runs on Elasticsearch 9 by default and ships a 
first-class OpenSearch persistence distribution — same SPI, migrate guide, and 
build flags."
 keywords: "Unomi OpenSearch, Elasticsearch 9, Apache Unomi persistence, 
UNOMI-828, migrate Elasticsearch to OpenSearch"
 layout: default
+date: 2026-06-29 10:52:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3 &middot; 
Operations</p>
         <h1>Elasticsearch 9 and OpenSearch in Unomi 3</h1>
         <p class="lead">One CDP runtime, two supported search backends &mdash; 
pick the distribution that matches your ops constraints.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/unomi-3-platform-foundation.html 
b/src/main/webapp/blog/unomi-3-platform-foundation.html
index cc67d10..57243a3 100644
--- a/src/main/webapp/blog/unomi-3-platform-foundation.html
+++ b/src/main/webapp/blog/unomi-3-platform-foundation.html
@@ -3,6 +3,7 @@ title: Unomi 3 platform foundation
 description: "What Unomi 3.0 changed under the hood — Java 17, Karaf 4.4, 
Elasticsearch 9, and persistence-based clustering without Cellar or Hazelcast."
 keywords: "Unomi 3 platform, Java 17, Karaf 4.4, UNOMI-877, persistence 
clustering, Elasticsearch 9"
 layout: default
+date: 2026-06-27 09:45:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3 &middot; Platform</p>
         <h1>Unomi 3 platform foundation</h1>
         <p class="lead">The 3.0 runtime floor &mdash; Java 17, Karaf 4.4, 
Elasticsearch 9, and clustering without Cellar or Hazelcast.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/unomi-3-request-tracing.html 
b/src/main/webapp/blog/unomi-3-request-tracing.html
index faf1858..b1513e5 100644
--- a/src/main/webapp/blog/unomi-3-request-tracing.html
+++ b/src/main/webapp/blog/unomi-3-request-tracing.html
@@ -3,6 +3,7 @@ title: Request tracing with explain=true in Unomi 3
 description: "Debug context and eventcollector requests with explain=true 
&mdash; admin-gated requestTracing trees, verified curl examples, and what 403 
means for public API keys."
 keywords: "Unomi explain, requestTracing, Unomi tracing, UNOMI-873, 
eventcollector explain, context.json explain"
 layout: default
+date: 2026-07-05 13:23:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3 &middot; Debugging</p>
         <h1>Request tracing with <code>explain=true</code></h1>
         <p class="lead">See condition evaluations, rule actions, and timings 
on <code>/cxs/context.json</code> and <code>/cxs/eventcollector</code>.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/unomi-3-shell-commands.html 
b/src/main/webapp/blog/unomi-3-shell-commands.html
index 83497da..73a874d 100644
--- a/src/main/webapp/blog/unomi-3-shell-commands.html
+++ b/src/main/webapp/blog/unomi-3-shell-commands.html
@@ -3,6 +3,7 @@ title: Unomi 3 Karaf shell commands
 description: "Unomi 3 expands the unomi:* Karaf shell &mdash; unified 
unomi:crud, tenant session context, cache ops, task scheduler commands, and 
list-invalid-objects (UNOMI-879 / 880)."
 keywords: "Unomi shell, unomi:crud, Karaf shell, unomi:cache, unomi:task-list, 
tenant-set, UNOMI-879, UNOMI-880"
 layout: default
+date: 2026-07-01 11:59:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3 &middot; Operator 
experience</p>
         <h1>Karaf shell commands in Unomi 3</h1>
         <p class="lead"><code>unomi:crud</code>, tenant session context, cache 
tools, and task commands &mdash; operate without leaving SSH.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/unomi-3-task-scheduler.html 
b/src/main/webapp/blog/unomi-3-task-scheduler.html
index 47be3ac..648a754 100644
--- a/src/main/webapp/blog/unomi-3-task-scheduler.html
+++ b/src/main/webapp/blog/unomi-3-task-scheduler.html
@@ -3,6 +3,7 @@ title: Cluster-aware task scheduler in Unomi 3.1
 description: "Unomi 3.1 durable task scheduler — full capability list: 
persistence, cluster locks, lifecycle, fluent builder, REST ops, shell, 
metrics, resume, and built-in jobs."
 keywords: "Unomi task scheduler, /cxs/tasks, unomi:task-list, Unomi 3.1, 
profile-purge, cluster tasks, TaskExecutor"
 layout: default
+date: 2026-07-03 12:16:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3.1 &middot; 
Operations</p>
         <h1>Cluster-aware task scheduler</h1>
         <p class="lead">Durable background work with persistence, locks, 
retry/resume, REST, shell, and a plugin-friendly fluent API.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/unomi-3-unit-testing-harness.html 
b/src/main/webapp/blog/unomi-3-unit-testing-harness.html
index 9e4135b..2b09332 100644
--- a/src/main/webapp/blog/unomi-3-unit-testing-harness.html
+++ b/src/main/webapp/blog/unomi-3-unit-testing-harness.html
@@ -3,6 +3,7 @@ title: Faster Unomi service unit tests — 
InMemoryPersistenceServiceImpl
 description: "How Unomi 3’s in-memory PersistenceService test harness lets 
Profile, Event, Rules, and Segment services run real unit tests in seconds 
without Karaf or Elasticsearch."
 keywords: "Unomi unit tests, InMemoryPersistenceServiceImpl, UNOMI-881, Unomi 
TestHelper, Surefire"
 layout: default
+date: 2026-07-07 09:30:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Unomi 3 &middot; Quality</p>
         <h1>Faster service unit tests</h1>
         <p class="lead"><code>InMemoryPersistenceServiceImpl</code> &mdash; a 
full PersistenceService on the test classpath so service logic no longer waits 
on Karaf.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 
diff --git a/src/main/webapp/blog/welcome-to-the-apache-unomi-blog.html 
b/src/main/webapp/blog/welcome-to-the-apache-unomi-blog.html
index 380ce95..0e815b4 100644
--- a/src/main/webapp/blog/welcome-to-the-apache-unomi-blog.html
+++ b/src/main/webapp/blog/welcome-to-the-apache-unomi-blog.html
@@ -3,6 +3,7 @@ title: Welcome to the Apache Unomi Blog
 description: "The Apache Unomi project launches an official blog — Unomi 3 
deep dives, AI/CDP guidance, and community updates."
 keywords: "Apache Unomi blog, Unomi 3, community"
 layout: default
+date: 2026-06-17 09:10:00 +0000
 ---
 
 <section class="page-header">
@@ -10,6 +11,12 @@ layout: default
         <p class="section-label text-hero-label">Blog</p>
         <h1>Welcome to the Apache Unomi Blog</h1>
         <p class="lead">Longer-form writing for Unomi 3, AI/CDP builders, and 
the community.</p>
+        {% if page.date %}
+        <p class="small text-muted mt-2 mb-0">
+            <time datetime="{{ page.date | date_to_xmlschema }}">Published {{ 
page.date | date: "%B %d, %Y at %H:%M UTC" }}</time>
+        </p>
+        {% endif %}
+
     </div>
 </section>
 <section class="section">
diff --git a/src/main/webapp/sitemap.xml b/src/main/webapp/sitemap.xml
index e2030eb..baf988f 100644
--- a/src/main/webapp/sitemap.xml
+++ b/src/main/webapp/sitemap.xml
@@ -245,6 +245,11 @@
     <priority>0.75</priority>
   </url>
   <url>
+  <url>
+    <loc>https://unomi.apache.org/blog/unomi-3-in-numbers.html</loc>
+    <changefreq>monthly</changefreq>
+    <priority>0.85</priority>
+  </url>
     <loc>https://unomi.apache.org/blog/unomi-3-shell-commands.html</loc>
     <changefreq>monthly</changefreq>
     <priority>0.8</priority>

Reply via email to