diff --git a/docs/assets/control-plane-dispatch-fallback-dark.svg b/docs/assets/control-plane-dispatch-fallback-dark.svg
new file mode 100644
index 0000000..bb87d64
--- /dev/null
+++ b/docs/assets/control-plane-dispatch-fallback-dark.svg
@@ -0,0 +1,92 @@
+
\ No newline at end of file
diff --git a/docs/assets/control-plane-dispatch-fallback.svg b/docs/assets/control-plane-dispatch-fallback.svg
index e93d20f..8fc8bfd 100644
--- a/docs/assets/control-plane-dispatch-fallback.svg
+++ b/docs/assets/control-plane-dispatch-fallback.svg
@@ -8,7 +8,6 @@
[fill="#1f883d"]{fill:var(--success)} [fill="#cf222e"]{fill:var(--danger)} [fill="#bf8700"]{fill:var(--attention)} [fill="#8250df"]{fill:var(--accent)} [fill="#0969da"]{fill:var(--info)}
[stroke="#ffffff"]{stroke:var(--surface)} [stroke="#d0d7de"]{stroke:var(--border)} [stroke="#afb8c1"]{stroke:var(--connector)} [stroke="#8250df"]{stroke:var(--accent)}
[stroke="#1f883d"]{stroke:var(--success)} [stroke="#cf222e"]{stroke:var(--danger)} [stroke="#bf8700"]{stroke:var(--attention)} [stroke="#0969da"]{stroke:var(--info)}
- @media(prefers-color-scheme:dark){:root{--surface:#161b22;--foreground:#f0f6fc;--muted:#8b949e;--border:#30363d;--connector:#484f58;--success:#3fb950;--danger:#f85149;--attention:#d29922;--accent:#a371f7;--info:#58a6ff;--shadow:#010409}}
diff --git a/docs/assets/control-plane-dispatch-mobile-dark.svg b/docs/assets/control-plane-dispatch-mobile-dark.svg
new file mode 100644
index 0000000..ea17011
--- /dev/null
+++ b/docs/assets/control-plane-dispatch-mobile-dark.svg
@@ -0,0 +1,88 @@
+
\ No newline at end of file
diff --git a/docs/assets/control-plane-dispatch-mobile.svg b/docs/assets/control-plane-dispatch-mobile.svg
index 0f11fa2..c180ef5 100644
--- a/docs/assets/control-plane-dispatch-mobile.svg
+++ b/docs/assets/control-plane-dispatch-mobile.svg
@@ -8,7 +8,6 @@
[fill="#1f883d"]{fill:var(--success)} [fill="#cf222e"]{fill:var(--danger)} [fill="#bf8700"]{fill:var(--attention)} [fill="#8250df"]{fill:var(--accent)} [fill="#0969da"]{fill:var(--info)}
[stroke="#ffffff"]{stroke:var(--surface)} [stroke="#d0d7de"]{stroke:var(--border)} [stroke="#afb8c1"]{stroke:var(--connector)} [stroke="#8250df"]{stroke:var(--accent)}
[stroke="#1f883d"]{stroke:var(--success)} [stroke="#cf222e"]{stroke:var(--danger)} [stroke="#bf8700"]{stroke:var(--attention)} [stroke="#0969da"]{stroke:var(--info)}
- @media(prefers-color-scheme:dark){:root{--surface:#161b22;--foreground:#f0f6fc;--muted:#8b949e;--border:#30363d;--connector:#484f58;--success:#3fb950;--danger:#f85149;--attention:#d29922;--accent:#a371f7;--info:#58a6ff;--shadow:#010409}}
diff --git a/docs/assets/control-plane-dispatch.animation.mjs b/docs/assets/control-plane-dispatch.animation.mjs
index 6059324..02f21df 100644
--- a/docs/assets/control-plane-dispatch.animation.mjs
+++ b/docs/assets/control-plane-dispatch.animation.mjs
@@ -1,19 +1,37 @@
+// Colors are resolved to a concrete light/dark palette in JS (rather than relying on an
+// embedded `@media (prefers-color-scheme: dark)` stylesheet inside each SVG) because Safari
+// does not evaluate that media query for SVGs referenced via a data URI or ``/`
`
+// element: https://bugs.webkit.org/show_bug.cgi?id=199134
const SPARKLE_ASSET_ID = "octicon-sparkle-fill";
const SPARKLE_SIZE = 16;
-const SPARKLE_SVG = ``;
-const SPARKLE_DATA_URI = `data:image/svg+xml,${encodeURIComponent(SPARKLE_SVG)}`;
+const sparkleSvg = (dark) =>
+ ``;
+const sparkleDataUri = (dark) => `data:image/svg+xml,${encodeURIComponent(sparkleSvg(dark))}`;
const STATUS_PENDING_ASSET_ID = "status-pending";
const STATUS_SUCCESS_ASSET_ID = "status-success";
const STATUS_SIZE = 20;
-const STATUS_PENDING_SVG = ``;
-const STATUS_SUCCESS_SVG = ``;
-const STATUS_PENDING_DATA_URI = `data:image/svg+xml,${encodeURIComponent(STATUS_PENDING_SVG)}`;
-const STATUS_SUCCESS_DATA_URI = `data:image/svg+xml,${encodeURIComponent(STATUS_SUCCESS_SVG)}`;
+const statusPendingSvg = (dark) =>
+ ``;
+const statusSuccessSvg = (dark) =>
+ ``;
+const statusPendingDataUri = (dark) => `data:image/svg+xml,${encodeURIComponent(statusPendingSvg(dark))}`;
+const statusSuccessDataUri = (dark) => `data:image/svg+xml,${encodeURIComponent(statusSuccessSvg(dark))}`;
const REPORT_UPDATED_ASSET_ID = "report-updated";
const REPORT_WIDTH = 274;
const REPORT_HEIGHT = 272;
-const REPORT_UPDATED_SVG = ``;
-const REPORT_UPDATED_DATA_URI = `data:image/svg+xml,${encodeURIComponent(REPORT_UPDATED_SVG)}`;
+const reportUpdatedSvg = (dark) => {
+ const surface = dark ? "#161b22" : "#fff";
+ const foreground = dark ? "#f0f6fc" : "#1f2328";
+ const muted = dark ? "#8b949e" : "#656d76";
+ const border = dark ? "#30363d" : "#d0d7de";
+ const success = dark ? "#3fb950" : "#1f883d";
+ const danger = dark ? "#f85149" : "#cf222e";
+ const attention = dark ? "#d29922" : "#bf8700";
+ const accent = dark ? "#a371f7" : "#8250df";
+ const info = dark ? "#58a6ff" : "#0969da";
+ return ``;
+};
+const reportUpdatedDataUri = (dark) => `data:image/svg+xml,${encodeURIComponent(reportUpdatedSvg(dark))}`;
const linear = { i: { x: 0.667, y: 1 }, o: { x: 0.333, y: 0 } };
const staticValue = (value) => ({ a: 0, k: value });
@@ -236,7 +254,16 @@ function createMobileLayers() {
return layers;
}
-export default function createAnimationData() {
+function pipelineAssets(prefersDark) {
+ return [
+ { id: SPARKLE_ASSET_ID, w: SPARKLE_SIZE, h: SPARKLE_SIZE, u: "", p: sparkleDataUri(prefersDark), e: 1 },
+ { id: STATUS_PENDING_ASSET_ID, w: STATUS_SIZE, h: STATUS_SIZE, u: "", p: statusPendingDataUri(prefersDark), e: 1 },
+ { id: STATUS_SUCCESS_ASSET_ID, w: STATUS_SIZE, h: STATUS_SIZE, u: "", p: statusSuccessDataUri(prefersDark), e: 1 },
+ { id: REPORT_UPDATED_ASSET_ID, w: REPORT_WIDTH, h: REPORT_HEIGHT, u: "", p: reportUpdatedDataUri(prefersDark), e: 1 },
+ ];
+}
+
+export default function createAnimationData(prefersDark = false) {
return {
v: "5.12.2",
fr: 60,
@@ -246,18 +273,13 @@ export default function createAnimationData() {
h: 560,
nm: "Central Agentic Ops pipeline",
ddd: 0,
- assets: [
- { id: SPARKLE_ASSET_ID, w: SPARKLE_SIZE, h: SPARKLE_SIZE, u: "", p: SPARKLE_DATA_URI, e: 1 },
- { id: STATUS_PENDING_ASSET_ID, w: STATUS_SIZE, h: STATUS_SIZE, u: "", p: STATUS_PENDING_DATA_URI, e: 1 },
- { id: STATUS_SUCCESS_ASSET_ID, w: STATUS_SIZE, h: STATUS_SIZE, u: "", p: STATUS_SUCCESS_DATA_URI, e: 1 },
- { id: REPORT_UPDATED_ASSET_ID, w: REPORT_WIDTH, h: REPORT_HEIGHT, u: "", p: REPORT_UPDATED_DATA_URI, e: 1 },
- ],
+ assets: pipelineAssets(prefersDark),
layers: createLayers(),
markers: [],
};
}
-export function createMobileAnimationData() {
+export function createMobileAnimationData(prefersDark = false) {
return {
v: "5.12.2",
fr: 60,
@@ -267,12 +289,7 @@ export function createMobileAnimationData() {
h: 886,
nm: "Central Agentic Ops mobile pipeline",
ddd: 0,
- assets: [
- { id: SPARKLE_ASSET_ID, w: SPARKLE_SIZE, h: SPARKLE_SIZE, u: "", p: SPARKLE_DATA_URI, e: 1 },
- { id: STATUS_PENDING_ASSET_ID, w: STATUS_SIZE, h: STATUS_SIZE, u: "", p: STATUS_PENDING_DATA_URI, e: 1 },
- { id: STATUS_SUCCESS_ASSET_ID, w: STATUS_SIZE, h: STATUS_SIZE, u: "", p: STATUS_SUCCESS_DATA_URI, e: 1 },
- { id: REPORT_UPDATED_ASSET_ID, w: REPORT_WIDTH, h: REPORT_HEIGHT, u: "", p: REPORT_UPDATED_DATA_URI, e: 1 },
- ],
+ assets: pipelineAssets(prefersDark),
layers: createMobileLayers(),
markers: [],
};
diff --git a/docs/components/HierarchyHero.astro b/docs/components/HierarchyHero.astro
index 969f7b7..294803c 100644
--- a/docs/components/HierarchyHero.astro
+++ b/docs/components/HierarchyHero.astro
@@ -1,8 +1,9 @@
---
-import { Image } from "astro:assets";
import type { HTMLAttributes } from "astro/types";
import dispatchFallback from "../assets/control-plane-dispatch-fallback.svg";
+import dispatchFallbackDark from "../assets/control-plane-dispatch-fallback-dark.svg";
import mobileDispatchFallback from "../assets/control-plane-dispatch-mobile.svg";
+import mobileDispatchFallbackDark from "../assets/control-plane-dispatch-mobile-dark.svg";
import OpsWizard from "./OpsWizard.astro";
const PAGE_TITLE_ID = "_top";
@@ -58,12 +59,34 @@ const { title = data.title, tagline, actions = [] } = data.hero || {};
-
+
+
+
+
-
+
+
+
+
@@ -78,18 +101,24 @@ const { title = data.title, tagline, actions = [] } = data.hero || {};
const animations = new WeakMap>();
const observers = new WeakMap();
+ // Safari does not evaluate `prefers-color-scheme` inside SVGs referenced from a data URI, so
+ // the animation's embedded icon colors are resolved here (in the host document) instead:
+ // https://bugs.webkit.org/show_bug.cgi?id=199134
+ const darkSchemeQuery = window.matchMedia("(prefers-color-scheme: dark)");
+
function initializeHierarchyAnimations() {
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
document.querySelectorAll(".dispatch-motion").forEach((container) => {
if (animations.has(container)) return;
+ const isMobile = container instanceof HTMLElement && container.dataset.layout === "mobile";
const animation = lottie.loadAnimation({
container,
renderer: "svg",
loop: true,
autoplay: false,
- animationData: container instanceof HTMLElement && container.dataset.layout === "mobile" ? createMobileAnimationData() : createAnimationData(),
+ animationData: isMobile ? createMobileAnimationData(darkSchemeQuery.matches) : createAnimationData(darkSchemeQuery.matches),
rendererSettings: {
preserveAspectRatio: "xMidYMid meet",
progressiveLoad: true,
@@ -119,6 +148,10 @@ const { title = data.title, tagline, actions = [] } = data.hero || {};
initializeHierarchyAnimations();
document.addEventListener("astro:page-load", initializeHierarchyAnimations);
document.addEventListener("astro:before-swap", destroyHierarchyAnimations);
+ darkSchemeQuery.addEventListener("change", () => {
+ destroyHierarchyAnimations();
+ initializeHierarchyAnimations();
+ });