diff --git a/CHANGELOG.md b/CHANGELOG.md
index b4abc4f..06f1468 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## 0.8.6 - 2026-07-26
+
+- added contextual animated code maps to unused diff-canvas space;
+- made illustrations respond to file type, diff size and pointer depth without obscuring code;
+- enriched changed-file rows with clearer state chips, active hierarchy and premium interaction feedback;
+- added responsive and reduced-motion safeguards for focused, accessible workspaces.
+
## 0.8.5 - 2026-07-26
- fixed packaged update downloads when Gitea reports an asset URL with a different public origin;
diff --git a/SOURCE_MANIFEST.txt b/SOURCE_MANIFEST.txt
index 63a3589..c5c199a 100644
--- a/SOURCE_MANIFEST.txt
+++ b/SOURCE_MANIFEST.txt
@@ -1,4 +1,4 @@
-ForgeFlow 0.8.5 source manifest
+ForgeFlow 0.8.6 source manifest
SHA-256 BYTES PATH
(The manifest excludes itself, dependencies and generated release artifacts.)
755f4db7d76bfec0963ef051748a82810c0d58acd4ffd823aa6928a5167fceb4 58 .gitignore
@@ -12,7 +12,7 @@ ca32a76e708d565c4af659f0f4d2615fc32114c3f75aec1454862a3ed1e72c41 2263
4633990a4b055bb3d00fef915ee29e85be5ee8413f809334728ad9688973c183 3364 build/icon-64.png
25048ed854e8ce8fece115e555c98d25507b002f8019b6ae717b54604c868c50 46223 build/icon.ico
16efd2fca83004f781eae40ae0f706a004ce0bddf338dd087b8adf7eb10c1d84 85704 build/icon.png
-ddd7faeae0a1700c3459b48ea086da1103c41efaf87f05a50e349389169f5fed 9288 CHANGELOG.md
+d73523f71016ac34bcf227d0222c9008fbd9c6489e2711dd3ad1da37de796369 9665 CHANGELOG.md
21cb96e7afe71b1dc791c818dedd244d92f9a6ed4d9ffbb3022ccb187e1bdf0f 852 docs/ACCEPTANCE.md
a17f95d96d3c9fbc69d870874e6fbb7472091adefc454b24f835db1279511d72 8296 docs/ARCHITECTURE.md
30a92bcf5daadb019efa2f82cb820ea302490dd1d68fb772674dc3faccd3e594 2045 docs/DEPLOYMENT_SETUP.md
@@ -43,6 +43,7 @@ d7bdc61d9b617ad5acf0b2d468eda547fd7509d4af08f33d2661393f25bdcb5a 576
c2802fa5dbff392c846b82b55e84a8bfb8e1625546fd1eba39f7129318bece96 654 docs/RELEASE_NOTES_0.8.3.md
8c13279987672314332f648889f52338bcdcb243249f9e1c20fb09d85d7808f5 505 docs/RELEASE_NOTES_0.8.4.md
b516db97a0353babc810c24a87a971d30d72a8021d809e6b833ff7ae0458f442 538 docs/RELEASE_NOTES_0.8.5.md
+838d196f3fbbfeee8df375a0502107f56b6df28babca30aa09ef1c7aa5196d09 738 docs/RELEASE_NOTES_0.8.6.md
2b631b9d6d973bdd70869d84886ff339da351e29e17598970b3b27915674661d 4175 docs/ROADMAP.md
1ccde232c060395d7aedce27e89a7647b77afe28ab71de0a5a3efeded57369d3 140415 docs/screenshots/deploy-confirmation.png
b39506254ffa2c73c389fb4795b3a745368bbeb7d8514cc47a636316d6d9a6aa 107166 docs/screenshots/deployment-run.png
@@ -66,8 +67,8 @@ c230b931abf2293d2d44b7a69b94c35f1142c093cc46b88739a0de5cbd6d1896 1532
4a561ead5ba7cdfaf4efce91842a4308c5f2a77980205879d83835efb8a579db 1067 LICENSE
6765015bdf27b288a250192272750b243c3cb8d1326b752d056d1e43317b6344 12935 main.cjs
91a984a89dd57a084b9a2331763cacdb061582fb590f13df379d92c1a77a2ee1 352 OVERLAY-INSTRUCTIONS.md
-3ffef38b3c056134c2ed8394e7bdcbb1c73e388024aa4f04a668e791060c600a 130466 package-lock.json
-ca3f27afd345ca2880f33f619e01bb161272d99f289f19f94831f4625f286006 3795 package.json
+59ab2691188bfde953a30c0ad2709f529c86ea9de7351aa98f53238ba50d7771 130466 package-lock.json
+8c137e3654c508000fa313429457e314c42a4b73abf6d14848e07445289515cf 3832 package.json
3d2ac366a13e9418e3ec6d13ce95b611f30f0228eb3a80ef9e7a936ce9578e24 9080 preload.cjs
b31c43d9355c13b5ae4efc0f3649d8cb8d509b2bb7ebb042ff546b7820fb7de8 8411 Publish-ForgeFlow-Release.ps1
a6d32a742412b7836606be00f17be0465f1b6f55d3911f6c73a14029787ba206 14037 README.md
@@ -80,7 +81,7 @@ f8359a69d20deb2dfe10042d1bec7b12a95e76e58e36bc5f265f073c3111d056 10287
74433d8a6b24afe368197a469e2fe0c5050c239d7250b84c2f3f598c304778b0 4736 scripts/publish-binary-release.cjs
444b397d515d65a7ee59d3088cba869cbb812d2b8cc18fc5d255105e3edb58c2 1468 scripts/serve-demo.mjs
42203f9e0fd4aae517284d387f265cf1b0b180379bc253a092b5c3c5c4caef0a 2992 scripts/validate-installed-connections.cjs
-c73c8bed917ee7dbf5f7fa57923456a4fcf43b1b60b3a67da7cfe9b5e2c223f5 12275 scripts/verify.mjs
+cd7650a307ce862c9dfc6c756efda5b10c75d3d1f63ce54191baaf7f008123bb 12308 scripts/verify.mjs
0079701b5acbfef07b71a9623613d1940805ccd20649d77e3f34c37e79df7655 735 scripts/write-release-checksums.mjs
619515f524cb89960370ffcbd3fafd3c0e178b95f69c5868b1dd44777f23ec1e 2081 setup-windows.ps1
dd613d04b366f2cd071a1685a414016a5fb008082ed1b4cb8b24b79c100f640a 2412 src/main/audit-service.cjs
@@ -100,14 +101,14 @@ e89b54e7e3174b4b0a1dcd9058d8344e29431f9d16d0e6bb8d11559b691440a0 2508
b31a63bf8cb1807b3e838e2bf8a0e742738f119d13de8ca9f42e471f072217d3 8328 src/main/ssh-service.cjs
8cf5013de91737dd9345b121586ceec38a6fc8518648975d60593d81fc225c4b 67376 src/main/unraid-deployment-service.cjs
27b9e15dd6530bc7bfab51414430eaef169e0cc4acd027257b1c63f891af8c85 20733 src/main/update-service.cjs
-ef029189791024421d78b3ac92981f03b20f2086fc4846eaa95d509f65e096a9 194049 src/renderer/app.js
+ac13e1222c7adcb664ff18c1de42c1f7bd25e6d90d2f02a18e4a4a68f5683bad 196260 src/renderer/app.js
16efd2fca83004f781eae40ae0f706a004ce0bddf338dd087b8adf7eb10c1d84 85704 src/renderer/assets/itworx-mark.png
813b8cdeecac43794166f3db9d3c5d2c441e0292f9ab7bd465ba136d6201e95d 82476 src/renderer/assets/itworx-wordmark-dark.png
094c1b71cc2482a9db250ac175f45f3de68f53277dfbde371a03e61923d00988 75240 src/renderer/assets/itworx-wordmark-light.png
813b8cdeecac43794166f3db9d3c5d2c441e0292f9ab7bd465ba136d6201e95d 82476 src/renderer/assets/itworx-wordmark.png
e1c463d6cda9f2b9b78c468845c0a7e8688f0362be5642074a1a5f7122dfe811 762 src/renderer/index.html
-34dd57cd916b3b2faabaf73d3063d5e6ed734393f13db795b51bff150f4bdc2d 50922 src/renderer/mock-bridge.js
-ebdd804b55c0b13cbf57e51c6d51a60dd838d67bb4c4eaf17ca34c86938a6865 67440 src/renderer/styles.css
+96e62ef31fd20954942dbe9dd4d44ecefdf869e9fb3144fe23d88c38fac0d9bf 50922 src/renderer/mock-bridge.js
+e30ea37ee00f34110d0d252c79f9d8e0bb127c4db0bd1bf8b374609dea433361 72160 src/renderer/styles.css
0a1e9d9d6cd4d190eb7f85dbc6668d80600b1cf2749cc0c2c51cc428f506f20d 1121 src/shared/clone-target.cjs
5d425d5c2f939d0f6beebee7ebb0c77146cb7e318535ba7286ec7081a4dc2269 2497 src/shared/deployment-policy.cjs
029e600229714d033c28e2dcb77817aa8269847001782ae0012960e83ffd183f 3057 src/shared/git-status.cjs
@@ -135,7 +136,7 @@ e7aebcc0d484a6a59d463d5cb26c11b3ad56e28f6535e7c38a0fe166a41565ea 13690
caf98cbd9de9b119dae610ee53fa333a7a11214f34762247452fbb85e8bbf725 2392 tests/log-redaction.test.mjs
96432a97d313f331694900bf0a2c21e38c20eac96d59147977aeed9055a9e3ad 2287 tests/partial-staging.test.mjs
c0f8f5a3784835f19d9ff1015185ccb385840b6fa1c9ec19f233393a7d952b65 3718 tests/preflight.test.mjs
-bc29c4e0c08305b2d00bf59ae9d486cad323c94879cf2a35d596a6b58fc17a0b 7784 tests/renderer-workflow.test.mjs
+185e96ff8fd029f06b77155117f492bc16684e08818a160148e00737dd8c220b 8395 tests/renderer-workflow.test.mjs
2b4956fa4df4624a04117737e57ba74020564330ff71303b5746d8ccc881e880 854 tests/repository-matching.test.mjs
f679072548554a64974f0452337ce5e7b0c567343c287223770cc0974b905348 1068 tests/repository-monitor.test.mjs
75b5b83836c75675bb9a48fe4363fcb8a24fc425e6af6f822d7955c6f3c79eac 2265 tests/repository-service.test.mjs
diff --git a/docs/RELEASE_NOTES_0.8.6.md b/docs/RELEASE_NOTES_0.8.6.md
new file mode 100644
index 0000000..7ba8a6a
--- /dev/null
+++ b/docs/RELEASE_NOTES_0.8.6.md
@@ -0,0 +1,14 @@
+# ForgeFlow 0.8.6
+
+ForgeFlow 0.8.6 gives the changes workspace a richer, more purposeful visual
+identity. Unused diff-canvas space now presents a contextual animated code map
+that reflects the selected file type and its additions and removals.
+
+Subtle travelling signals, floating status nodes and pointer-responsive depth
+bring the canvas to life while keeping every diff line fully readable. Dense
+diffs automatically reduce the illustration's presence, and narrow panes hide
+it entirely when there is no useful room.
+
+The changed-file list also gains clearer state chips, stronger active-file
+hierarchy and refined hover feedback. All effects support light and dark themes
+and respect the operating system's reduced-motion preference.
diff --git a/package-lock.json b/package-lock.json
index 52d60ba..a514af6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "forgeflow",
- "version": "0.8.5",
+ "version": "0.8.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "forgeflow",
- "version": "0.8.5",
+ "version": "0.8.6",
"dependencies": {
"ssh2": "1.17.0"
},
diff --git a/package.json b/package.json
index 9ebf588..55784a7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "forgeflow",
- "version": "0.8.5",
+ "version": "0.8.6",
"private": true,
"description": "Desktop release cockpit for local Git, Gitea Actions and controlled exact-commit deployments.",
"main": "main.cjs",
@@ -78,6 +78,7 @@
"docs/RELEASE_NOTES_0.8.3.md",
"docs/RELEASE_NOTES_0.8.4.md",
"docs/RELEASE_NOTES_0.8.5.md",
+ "docs/RELEASE_NOTES_0.8.6.md",
"docs/ACCEPTANCE.md"
],
"asarUnpack": [
diff --git a/scripts/verify.mjs b/scripts/verify.mjs
index 58503a4..2c11c94 100644
--- a/scripts/verify.mjs
+++ b/scripts/verify.mjs
@@ -66,6 +66,7 @@ const required = [
"docs/RELEASE_NOTES_0.8.3.md",
"docs/RELEASE_NOTES_0.8.4.md",
"docs/RELEASE_NOTES_0.8.5.md",
+ "docs/RELEASE_NOTES_0.8.6.md",
"docs/UPDATING.md",
"docs/DIAGNOSTICS.md",
"docs/DEPLOYMENT_SETUP.md",
@@ -104,9 +105,9 @@ for (const file of required) await access(path.join(root, file));
const packageJson = JSON.parse(
await readFile(path.join(root, "package.json"), "utf8"),
);
-if (packageJson.version !== "0.8.5")
+if (packageJson.version !== "0.8.6")
throw new Error(
- `Expected package version 0.8.5, got ${packageJson.version}.`,
+ `Expected package version 0.8.6, got ${packageJson.version}.`,
);
const sourceManifest = await readFile(
path.join(root, "SOURCE_MANIFEST.txt"),
diff --git a/src/renderer/app.js b/src/renderer/app.js
index e703662..d75502b 100644
--- a/src/renderer/app.js
+++ b/src/renderer/app.js
@@ -773,10 +773,25 @@ function releaseNode(label, value, description, tone = "") {
return `
${label}
${escapeHtml(value)}${escapeHtml(description)}
`;
}
+function diffAtmosphere(diff) {
+ if (!ui.selectedFile) return "";
+ const lines = String(diff || "").split("\n");
+ const additions = lines.filter(
+ (line) => line.startsWith("+") && !line.startsWith("+++"),
+ ).length;
+ const removals = lines.filter(
+ (line) => line.startsWith("-") && !line.startsWith("---"),
+ ).length;
+ const extension =
+ String(ui.selectedFile).split(".").pop()?.slice(0, 8).toUpperCase() ||
+ "FILE";
+ return `${escapeHtml(extension)} change map+${additions}−${removals}
`;
+}
+
function renderDiff(diff) {
if (!diff)
return '↔
No textual diff
Select another file or open the project folder for binary changes.
';
- return escapeHtml(diff)
+ const rendered = escapeHtml(diff)
.split("\n")
.map((line) => {
const type =
@@ -790,6 +805,7 @@ function renderDiff(diff) {
return `${line || " "}`;
})
.join("");
+ return `${rendered}${diffAtmosphere(diff)}`;
}
function fileStatusCode(file) {
if (file.conflict) return "U";
@@ -3259,23 +3275,46 @@ document.addEventListener("keydown", (event) => {
document.addEventListener("pointermove", (event) => {
const illustration = event.target.closest?.("[data-project-illustration]");
- if (!illustration) return;
- const bounds = illustration.getBoundingClientRect();
- illustration.style.setProperty(
- "--tilt-x",
- `${((event.clientY - bounds.top) / bounds.height - 0.5) * -7}deg`,
- );
- illustration.style.setProperty(
- "--tilt-y",
- `${((event.clientX - bounds.left) / bounds.width - 0.5) * 9}deg`,
- );
+ if (illustration) {
+ const bounds = illustration.getBoundingClientRect();
+ illustration.style.setProperty(
+ "--tilt-x",
+ `${((event.clientY - bounds.top) / bounds.height - 0.5) * -7}deg`,
+ );
+ illustration.style.setProperty(
+ "--tilt-y",
+ `${((event.clientX - bounds.left) / bounds.width - 0.5) * 9}deg`,
+ );
+ }
+
+ const diffPanel = event.target.closest?.(".diff-panel");
+ const atmosphere = diffPanel?.querySelector("[data-diff-atmosphere]");
+ if (atmosphere) {
+ const bounds = diffPanel.getBoundingClientRect();
+ atmosphere.style.setProperty(
+ "--diff-tilt-x",
+ `${((event.clientY - bounds.top) / bounds.height - 0.5) * -3}deg`,
+ );
+ atmosphere.style.setProperty(
+ "--diff-tilt-y",
+ `${((event.clientX - bounds.left) / bounds.width - 0.5) * 4}deg`,
+ );
+ }
});
document.addEventListener("pointerout", (event) => {
const illustration = event.target.closest?.("[data-project-illustration]");
- if (!illustration || illustration.contains(event.relatedTarget)) return;
- illustration.style.removeProperty("--tilt-x");
- illustration.style.removeProperty("--tilt-y");
+ if (illustration && !illustration.contains(event.relatedTarget)) {
+ illustration.style.removeProperty("--tilt-x");
+ illustration.style.removeProperty("--tilt-y");
+ }
+
+ const diffPanel = event.target.closest?.(".diff-panel");
+ if (diffPanel && !diffPanel.contains(event.relatedTarget)) {
+ const atmosphere = diffPanel.querySelector("[data-diff-atmosphere]");
+ atmosphere?.style.removeProperty("--diff-tilt-x");
+ atmosphere?.style.removeProperty("--diff-tilt-y");
+ }
});
window.addEventListener("error", (event) => {
diff --git a/src/renderer/mock-bridge.js b/src/renderer/mock-bridge.js
index 59218cb..dc7259f 100644
--- a/src/renderer/mock-bridge.js
+++ b/src/renderer/mock-bridge.js
@@ -564,7 +564,7 @@
await wait(80);
snapshot();
return {
- appVersion: "0.8.5-demo",
+ appVersion: "0.8.6-demo",
platform: "win32",
state: clone(state),
git: { available: true, version: "git version 2.47.3" },
diff --git a/src/renderer/styles.css b/src/renderer/styles.css
index 17e18be..82f7e9b 100644
--- a/src/renderer/styles.css
+++ b/src/renderer/styles.css
@@ -970,27 +970,56 @@ select:focus-visible {
scrollbar-gutter: stable;
}
.file-row {
+ position: relative;
width: 100%;
- min-height: 34px;
+ min-height: 38px;
display: grid;
grid-template-columns: 17px 17px minmax(0, 1fr) 16px;
gap: 7px;
align-items: center;
- padding: 3px 6px;
- border-radius: 4px;
+ padding: 4px 8px;
+ border: 1px solid transparent;
+ border-radius: 7px;
background: transparent;
color: var(--text-muted);
cursor: pointer;
text-align: left;
+ transition:
+ transform 160ms ease,
+ border-color 160ms ease,
+ background 160ms ease,
+ box-shadow 160ms ease;
}
.file-row:hover {
background: var(--surface-hover);
color: var(--text);
+ border-color: color-mix(in srgb, var(--primary) 18%, transparent);
+ transform: translateX(2px);
}
.file-row.active {
- background: var(--primary-soft);
+ background:
+ linear-gradient(90deg, var(--primary-soft), transparent 110%),
+ var(--surface-1);
+ border-color: color-mix(in srgb, var(--primary) 36%, var(--line));
+ box-shadow:
+ inset 3px 0 0 var(--primary),
+ 0 7px 22px rgba(0, 0, 0, 0.12);
color: var(--text);
}
+.file-row > span:last-child {
+ color: var(--text-faint);
+ filter: drop-shadow(0 0 5px transparent);
+ transition:
+ color 160ms ease,
+ filter 160ms ease;
+}
+.file-row:hover > span:last-child,
+.file-row.active > span:last-child {
+ color: var(--primary);
+ filter: drop-shadow(
+ 0 0 5px color-mix(in srgb, var(--primary) 45%, transparent)
+ );
+}
.file-row input {
margin: 0;
accent-color: var(--primary-strong);
@@ -1003,6 +1032,12 @@ select:focus-visible {
font-size: 11px;
}
.file-status {
+ display: inline-grid;
+ place-items: center;
+ width: 17px;
+ height: 17px;
+ border-radius: 5px;
+ background: color-mix(in srgb, currentColor 11%, transparent);
font-family: var(--font-mono);
font-size: 10px;
font-weight: 750;
@@ -1017,6 +1052,7 @@ select:focus-visible {
color: var(--danger);
}
.diff-panel {
+ container-type: inline-size;
min-width: 0;
min-height: 0;
display: grid;
@@ -1041,6 +1077,8 @@ select:focus-visible {
color: var(--text-muted);
}
.diff-view {
+ position: relative;
+ isolation: isolate;
overflow: auto;
padding: 8px 0 36px;
font-family: var(--font-mono);
@@ -1048,12 +1086,179 @@ select:focus-visible {
line-height: 19px;
white-space: pre;
tab-size: 2;
+ background:
+ radial-gradient(
+ circle at 84% 72%,
+ color-mix(in srgb, var(--primary) 8%, transparent),
+ transparent 28%
+ ),
+ radial-gradient(
+ circle at 72% 88%,
+ color-mix(in srgb, var(--success) 5%, transparent),
+ transparent 24%
+ );
}
.diff-line {
+ position: relative;
+ z-index: 2;
display: block;
min-height: 19px;
padding: 0 14px;
}
+.diff-atmosphere {
+ --diff-tilt-x: 0deg;
+ --diff-tilt-y: 0deg;
+ position: absolute;
+ right: clamp(24px, 7vw, 110px);
+ bottom: clamp(28px, 8vh, 90px);
+ z-index: 0;
+ width: min(360px, 34vw);
+ color: var(--primary);
+ opacity: 0.38;
+ pointer-events: none;
+ transform: perspective(850px) rotateX(var(--diff-tilt-x))
+ rotateY(var(--diff-tilt-y));
+ transform-style: preserve-3d;
+ transition:
+ transform 220ms ease-out,
+ opacity 180ms ease;
+}
+.diff-atmosphere.dense {
+ opacity: 0.14;
+}
+.diff-atmosphere svg {
+ display: block;
+ width: 100%;
+ overflow: visible;
+}
+.code-route {
+ fill: none;
+ stroke: currentColor;
+ stroke-width: 1.2;
+ stroke-dasharray: 4 8;
+ opacity: 0.42;
+}
+.code-route.route-b {
+ color: var(--success);
+}
+.code-card rect {
+ fill: color-mix(in srgb, var(--surface-2) 72%, transparent);
+ stroke: color-mix(in srgb, var(--primary) 60%, var(--line));
+ stroke-width: 1.2;
+ filter: drop-shadow(0 18px 28px rgba(0, 0, 0, 0.24));
+}
+.code-card path {
+ fill: none;
+ stroke: currentColor;
+ stroke-linecap: round;
+ stroke-width: 4;
+ opacity: 0.62;
+}
+.code-node circle {
+ fill: var(--surface-2);
+ stroke: currentColor;
+ stroke-width: 1.5;
+}
+.code-node path {
+ fill: none;
+ stroke: currentColor;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-width: 2;
+}
+.node-one {
+ color: var(--success);
+ animation: code-node-float 5s ease-in-out infinite;
+}
+.node-two {
+ animation: code-node-float 5s ease-in-out -2.5s infinite;
+}
+.code-packet {
+ fill: var(--primary);
+ filter: drop-shadow(0 0 8px currentColor);
+ offset-path: path("M38 195 C92 84 178 214 318 74");
+ animation: code-packet-travel 5.4s cubic-bezier(0.4, 0, 0.2, 1) infinite;
+}
+.code-packet.packet-two {
+ fill: var(--success);
+ offset-path: path("M52 74 C132 8 230 34 310 156");
+ animation-delay: -2.7s;
+}
+.diff-atmosphere-caption {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin: -10px 42px 0;
+ padding-top: 10px;
+ border-top: 1px solid color-mix(in srgb, var(--primary) 32%, transparent);
+ color: var(--text-muted);
+ font: 700 9px/1 var(--font-mono);
+ letter-spacing: 0.11em;
+ text-transform: uppercase;
+}
+.diff-atmosphere-caption strong {
+ display: flex;
+ gap: 8px;
+}
+.diff-atmosphere-caption i {
+ color: var(--success);
+ font-style: normal;
+}
+.diff-atmosphere-caption i + i {
+ color: var(--danger);
+}
+@keyframes code-packet-travel {
+ 0% {
+ offset-distance: 0%;
+ opacity: 0;
+ }
+ 12%,
+ 82% {
+ opacity: 1;
+ }
+ 100% {
+ offset-distance: 100%;
+ opacity: 0;
+ }
+}
+@keyframes code-node-float {
+ 0%,
+ 100% {
+ transform: translateY(0);
+ }
+ 50% {
+ transform: translateY(-6px);
+ }
+}
+html[data-theme="light"] .diff-view {
+ background:
+ radial-gradient(
+ circle at 84% 72%,
+ rgba(72, 92, 220, 0.12),
+ transparent 30%
+ ),
+ radial-gradient(
+ circle at 72% 88%,
+ rgba(15, 148, 108, 0.08),
+ transparent 25%
+ ),
+ linear-gradient(
+ 135deg,
+ rgba(248, 251, 255, 0.88),
+ rgba(239, 245, 255, 0.62)
+ );
+}
+html[data-theme="light"] .diff-atmosphere {
+ opacity: 0.46;
+}
+html[data-theme="light"] .diff-atmosphere.dense {
+ opacity: 0.18;
+}
+@container (max-width: 560px) {
+ .diff-atmosphere {
+ display: none;
+ }
+}
.diff-line.add {
background: rgba(38, 166, 115, 0.14);
color: #8ef0c6;
@@ -3408,6 +3613,9 @@ html[data-theme="light"] .visual-page-header {
.project-illustration svg {
transform: none !important;
}
+ .diff-atmosphere {
+ transform: none !important;
+ }
}
@media (max-width: 1180px) {
diff --git a/tests/renderer-workflow.test.mjs b/tests/renderer-workflow.test.mjs
index 720d0b6..6a4116a 100644
--- a/tests/renderer-workflow.test.mjs
+++ b/tests/renderer-workflow.test.mjs
@@ -226,3 +226,20 @@ test("interactive project illustrations are semantic, responsive and motion-safe
assert.match(styles, /prefers-reduced-motion/);
assert.match(styles, /transform: none !important/);
});
+
+test("the diff canvas uses a contextual and motion-safe code illustration", async () => {
+ const renderer = await readFile(
+ new URL("../src/renderer/app.js", import.meta.url),
+ "utf8",
+ );
+ const styles = await readFile(
+ new URL("../src/renderer/styles.css", import.meta.url),
+ "utf8",
+ );
+ assert.match(renderer, /function diffAtmosphere/);
+ assert.match(renderer, /data-diff-atmosphere/);
+ assert.match(renderer, /--diff-tilt-x/);
+ assert.match(styles, /\.diff-atmosphere/);
+ assert.match(styles, /@keyframes code-packet-travel/);
+ assert.match(styles, /prefers-reduced-motion/);
+});