diff --git a/sass/components/_image_compare.scss b/sass/components/_image_compare.scss
new file mode 100644
index 0000000000..496eabdd08
--- /dev/null
+++ b/sass/components/_image_compare.scss
@@ -0,0 +1,62 @@
+div.image-compare {
+ display: flex;
+ align-items: center;
+ position: relative;
+ border-radius: 10px;
+ width: 100%;
+ aspect-ratio: 16 / 9; // fallback
+ background-color: black;
+ --slider-value: 50%;
+
+ &::before,
+ &::after {
+ position: absolute;
+ font-weight: bolder;
+ font-size: 175%;
+ writing-mode: vertical-rl;
+ }
+ &::before {
+ content: attr(data-title-a);
+ z-index: 1;
+ }
+ &::after {
+ content: attr(data-title-b);
+ width: 100%;
+ }
+
+ & img {
+ width: 100%;
+ }
+ & .image-a {
+ position: absolute;
+ -webkit-clip-path: inset(0 calc(100% - var(--slider-value) + 1.5px) 0 0);
+ clip-path: inset(0 calc(100% - var(--slider-value) + 1.5px) 0 0);
+ }
+ & .image-b {
+ float: right;
+ -webkit-clip-path: inset(0 0 0 calc(var(--slider-value) + 1.5px));
+ clip-path: inset(0 0 0 calc(var(--slider-value) + 1.5px));
+ }
+
+ & input[type="range"] {
+ position: absolute;
+ padding: 0;
+ margin: 0;
+ width: inherit;
+ height: 100%;
+ background-color: transparent;
+ -webkit-appearance: none;
+ appearance: none;
+ z-index: 2;
+
+ &::-moz-range-thumb {
+ border: none;
+ background-color: transparent;
+ }
+ &::-webkit-slider-thumb {
+ width: 0; // no clue why this is required, but it is
+ -webkit-appearance: none;
+ appearance: none;
+ }
+ }
+}
diff --git a/sass/site.scss b/sass/site.scss
index addc659007..8cda020aad 100644
--- a/sass/site.scss
+++ b/sass/site.scss
@@ -42,6 +42,7 @@
@import "components/syntax-theme";
@import "components/tree-menu";
@import "components/asset-card";
+@import "components/image_compare";
// Pages
// - Page specific CSS
diff --git a/static/components.js b/static/components.js
new file mode 100644
index 0000000000..cb13ee8d51
--- /dev/null
+++ b/static/components.js
@@ -0,0 +1,42 @@
+
+// clamp a number to the given range
+function clamp(val, min, max) {
+ return Math.min(Math.max(val, min), max)
+}
+
+// inserts the input element required to activate image_compare components
+//
+// Usage in a document should look like:
+// ```html
+//