jekyll-before-after 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a21883c118e8086f562f69af0804ec131bf49ff1166485abe8e1f4b23c657e0
4
- data.tar.gz: 724bb4a84e30680bd90f347a1c6ce4596790d49bf113bc5243dc5376976903b7
3
+ metadata.gz: 95018375cf799be55f8ea6697b6e75001a0273bf053d6cfd70f584a6c0e31a9e
4
+ data.tar.gz: 517c6093fc0789f13d357322d2ec9eeacba39df886e55fc5f054474bfe7fe182
5
5
  SHA512:
6
- metadata.gz: ee01f6091029ec8d399a8331babc95b098135433ea150ebfae8cd7b5c77fc285e4cc6f03716e28d6d21ccf5947285c5473fab2878d102241b8315ecc2fb134fd
7
- data.tar.gz: 33bae04da7fe2bcd3ecb8762a75ade05ee4d32e885f99630b06853328bd7f418b241ac1eb461b1383f4ca915f3a91b16dd32d693ee447d595fb56696124f2c16
6
+ metadata.gz: ebc5888a4e142f4247ba391cc4528b3a718830bc12fd2a0358a00d30ce307a9b72e723e5600cd0d45a1d9b103ee8e68a8a8277bf87e1db72d1324397596fe47e
7
+ data.tar.gz: ae8c0aaf89ee1c78345cfd4c80039dacfa4e32be35a6a6449c7431804726225cca8cc112f8e440a71e043862ae356f65696de0c79a67958b1cbb00310c22a90f
data/CHANGELOG.md CHANGED
@@ -3,6 +3,32 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project adheres to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
5
 
6
+ ## [1.0.0] - 2026-08-29
7
+
8
+ ### Changed
9
+ - **MAJOR: Refactored to use [img-comparison-slider](https://github.com/sneas/img-comparison-slider) web component**
10
+ - Replaced custom JavaScript implementation with proven, battle-tested web component
11
+ - Dramatically simplified codebase (no custom JS initialization needed)
12
+ - Improved performance and reliability
13
+ - Better accessibility with native web component semantics
14
+
15
+ ### Added
16
+ - `orientation="vertical"` attribute for vertical comparisons
17
+ - Automatic CDN injection of img-comparison-slider library (no manual setup required)
18
+ - Full CSS custom property theming support via img-comparison-slider
19
+
20
+ ### Removed
21
+ - Custom JavaScript slider implementation
22
+ - `_before-after.scss` (styling now via CSS custom properties)
23
+ - Configurable start position, overlay labels, srcset attributes (use CSS for customization)
24
+ - ResizeObserver logic (handled by web component)
25
+
26
+ ### Migration Guide
27
+ If upgrading from 0.1.0:
28
+ - All existing `{% before_after %}` tags work unchanged
29
+ - Custom styling should migrate to CSS custom properties (see README)
30
+ - No configuration changes needed — it just works
31
+
6
32
  ## [0.1.0] - 2026-08-22
7
33
 
8
34
  ### Added
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # jekyll-before-after
2
2
 
3
- A Jekyll plugin providing a `{% before_after %}` Liquid tag for rendering a polished, accessible drag-to-reveal image comparison slider. Supports horizontal and vertical orientations, overlay labels, responsive images, keyboard navigation, and full CSS custom property theming. Pure vanilla JS no external dependencies, GitHub Pages compatible.
3
+ A Jekyll plugin providing a `{% before_after %}` Liquid tag for rendering a drag-to-reveal image comparison slider. Built on the proven [img-comparison-slider](https://github.com/sneas/img-comparison-slider) web component. Supports horizontal and vertical orientations, responsive images, keyboard navigation, touch support, and full CSS custom property theming. Pure vanilla JS with zero external dependencies GitHub Pages compatible.
4
4
 
5
5
  ## Installation
6
6
 
@@ -17,18 +17,14 @@ plugins:
17
17
  - jekyll-before-after
18
18
  ```
19
19
 
20
- Copy `_before-after.scss` to your site's CSS directory and import it.
21
-
22
20
  Run `bundle install`.
23
21
 
24
22
  ## Usage
25
23
 
26
24
  ```liquid
27
25
  {% before_after
28
- before="/images/main-street-2018.jpg"
29
- after="/images/main-street-2024.jpg"
30
- before_label="2018"
31
- after_label="2024"
26
+ before="/images/before.jpg"
27
+ after="/images/after.jpg"
32
28
  %}
33
29
  ```
34
30
 
@@ -38,96 +34,69 @@ Run `bundle install`.
38
34
  |--------|---------|-------------|
39
35
  | `before` | _(required)_ | Path to the "before" image |
40
36
  | `after` | _(required)_ | Path to the "after" image |
41
- | `before_label` | `"Before"` | Label overlaid on the before side |
42
- | `after_label` | `"After"` | Label overlaid on the after side |
43
- | `start` | `50` | Initial slider position (0–100) |
44
37
  | `orientation` | `horizontal` | `horizontal` or `vertical` |
45
- | `alt_before` | `""` | Alt text for the before image |
46
- | `alt_after` | `""` | Alt text for the after image |
47
- | `srcset_before` | `""` | `srcset` value for responsive before image |
48
- | `srcset_after` | `""` | `srcset` value for responsive after image |
49
- | `sizes` | `"(max-width: 768px) 100vw, 100vw"` | `sizes` attribute when using srcset |
50
38
 
51
- ### Responsive images
39
+ ### Examples
52
40
 
53
- Pass `srcset_before` and `srcset_after` to render a `<picture>` element:
41
+ #### Horizontal comparison
54
42
 
55
43
  ```liquid
56
44
  {% before_after
57
- before="/images/street-before.jpg"
58
- after="/images/street-after.jpg"
59
- srcset_before="/images/street-before-480.jpg 480w, /images/street-before-960.jpg 960w"
60
- srcset_after="/images/street-after-480.jpg 480w, /images/street-after-960.jpg 960w"
61
- sizes="(max-width: 600px) 100vw, 80vw"
45
+ before="/images/main-street-2018.jpg"
46
+ after="/images/main-street-2024.jpg"
62
47
  %}
63
48
  ```
64
49
 
65
- ### Vertical orientation
50
+ #### Vertical comparison
66
51
 
67
52
  ```liquid
68
53
  {% before_after
69
54
  before="/images/building-2015.jpg"
70
55
  after="/images/building-2025.jpg"
71
- orientation=vertical
72
- before_label="2015"
73
- after_label="2025"
56
+ orientation="vertical"
74
57
  %}
75
58
  ```
76
59
 
77
- ### Multiple sliders
78
-
79
- Multiple sliders on the same page are fully supported. The JavaScript block is emitted only once per page regardless of how many sliders you include.
60
+ #### Multiple sliders
80
61
 
81
- ## Configuration
82
-
83
- Set site-wide defaults in `_config.yml`:
84
-
85
- ```yaml
86
- before_after:
87
- start: 50
88
- orientation: horizontal
89
- before_label: "Before"
90
- after_label: "After"
91
- ```
62
+ Multiple sliders on the same page are fully supported. The library script is injected only once per page.
92
63
 
93
64
  ## Styling
94
65
 
95
- Copy `_before-after.scss` to your project and import it. All visual properties are controlled via CSS custom properties:
96
-
97
- | Variable | Default | Description |
98
- |----------|---------|-------------|
99
- | `--ba-handle-color` | `#fff` | Handle line and knob color |
100
- | `--ba-knob-size` | `2.75rem` | Drag knob diameter |
101
- | `--ba-knob-icon-color` | `#333` | Arrow icon color inside knob |
102
- | `--ba-line-width` | `2px` | Divider line thickness |
103
- | `--ba-label-bg` | `rgba(0,0,0,0.55)` | Label background |
104
- | `--ba-label-color` | `#fff` | Label text color |
105
- | `--ba-label-radius` | `3px` | Label border radius |
106
- | `--ba-radius` | `4px` | Container border radius |
107
- | `--ba-focus-ring` | `rgba(255,255,255,0.8)` | Keyboard focus ring color |
108
-
109
- ### Example override
110
-
111
- ```scss
112
- .before-after {
113
- --ba-handle-color: #e67e22;
114
- --ba-knob-size: 3rem;
115
- --ba-label-bg: rgba(0, 0, 0, 0.75);
66
+ All visual properties are controlled via CSS custom properties. Target the `img-comparison-slider` element:
67
+
68
+ ```css
69
+ img-comparison-slider {
70
+ --divider-width: 4px;
71
+ --divider-color: #fff;
72
+ --handle-width: 60px;
73
+ --handle-height: 60px;
74
+ --handle-opacity: 0.3;
75
+ --handle-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
116
76
  }
117
77
  ```
118
78
 
79
+ See [img-comparison-slider docs](https://img-comparison-slider.sneas.io/) for all available CSS custom properties.
80
+
119
81
  ## Interaction
120
82
 
121
- - **Drag** the handle to reveal
83
+ - **Drag** the divider to reveal
122
84
  - **Click** anywhere on the image to jump the slider to that position
123
- - **Keyboard**: focus the handle and use arrow keys (5% steps; hold Shift for 1% steps)
85
+ - **Keyboard**: focus the slider and use arrow keys
124
86
  - **Touch** and stylus supported via Pointer Events API
125
87
 
88
+ ## How it works
89
+
90
+ This plugin wraps the lightweight [img-comparison-slider](https://github.com/sneas/img-comparison-slider) web component, loading it from CDN. The Liquid tag converts your `{% before_after %}` markup into the web component format, and the plugin injects the library script into the page once per build.
91
+
126
92
  ## Requirements
127
93
 
128
94
  - Jekyll >= 3.9
129
95
  - Ruby >= 2.7
96
+ - Modern browser with Web Components support (all modern browsers)
130
97
 
131
98
  ## License
132
99
 
133
100
  MIT — see [LICENSE.txt](LICENSE.txt).
101
+
102
+ The plugin wraps [img-comparison-slider](https://github.com/sneas/img-comparison-slider) by sneas, also licensed under MIT.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "jekyll-before-after"
5
- spec.version = "0.1.0"
5
+ spec.version = "1.0.0"
6
6
  spec.authors = ["Jason Chance"]
7
7
  spec.email = ["jason@jasonchance.com"]
8
8
 
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ class BeforeAfterGenerator < Generator
5
+ safe true
6
+ priority :highest
7
+
8
+ def generate(site)
9
+ # Create the CSS as a virtual file that can be included
10
+ css_content = %q{<style>
11
+ .before-after{position:relative;overflow:hidden;cursor:col-resize;user-select:none;touch-action:none;border-radius:var(--ba-radius, 4px);background:var(--ba-bg, #000);margin-bottom:2rem}
12
+ .before-after[data-orientation="vertical"]{cursor:row-resize}
13
+ .before-after--dragging{cursor:grabbing}
14
+ .before-after__after{position:relative;display:block}
15
+ .before-after__after img,.before-after__after picture img{display:block;width:100%;height:auto;pointer-events:none}
16
+ .before-after__before{position:absolute;inset:0;z-index:1;clip-path:inset(0 50% 0 0)}
17
+ .before-after__before img,.before-after__before picture img{display:block;width:100%;height:100%;object-fit:cover;pointer-events:none}
18
+ .before-after__label{position:absolute;bottom:.75rem;z-index:2;padding:.25rem .6rem;font-size:.75rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:var(--ba-label-bg, rgba(0, 0, 0, 0.55));color:var(--ba-label-color, #fff);border-radius:var(--ba-label-radius, 3px);pointer-events:none}
19
+ .before-after__label--before{left:.75rem}
20
+ .before-after__label--after{right:.75rem}
21
+ .before-after__handle{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:10;display:flex;align-items:center;justify-content:center;outline:0}
22
+ .before-after__handle:focus-visible .before-after__handle-knob{box-shadow:0 0 0 3px var(--ba-focus-ring, rgba(255, 255, 255, 0.8))}
23
+ .before-after__handle-line{position:absolute;background:var(--ba-handle-color, #fff);opacity:.85}
24
+ .before-after:not([data-orientation="vertical"]) .before-after__handle-line{width:var(--ba-line-width, 2px);top:-200%;bottom:-200%;left:50%;transform:translateX(-50%)}
25
+ [data-orientation="vertical"] .before-after__handle-line{height:var(--ba-line-width, 2px);left:-200%;right:-200%;top:50%;transform:translateY(-50%)}
26
+ .before-after__handle-knob{position:relative;z-index:1;width:var(--ba-knob-size, 2.75rem);height:var(--ba-knob-size, 2.75rem);border-radius:50%;background:var(--ba-handle-color, #fff);color:var(--ba-knob-icon-color, #333);display:flex;align-items:center;justify-content:center;box-shadow:0 2px 8px rgba(0, 0, 0, 0.35);transition:transform .15s ease, box-shadow .15s ease;cursor:grab}
27
+ .before-after__handle-knob svg{width:1.1rem;height:1.1rem}
28
+ .before-after--dragging .before-after__handle-knob{cursor:grabbing;transform:scale(1.12);box-shadow:0 4px 16px rgba(0, 0, 0, 0.45)}
29
+ [data-orientation="vertical"] .before-after__handle-knob svg{transform:rotate(90deg)}
30
+ </style>}
31
+
32
+ # Store in site data so layouts can access it
33
+ site.data['before_after_css'] = css_content
34
+ end
35
+ end
36
+ end
@@ -13,9 +13,6 @@ module Jekyll
13
13
  attrs
14
14
  end
15
15
 
16
- # Dedup script: only emit the JS block once per page
17
- BEFORE_AFTER_SCRIPT_FLAG = :before_after_script_emitted
18
-
19
16
  class BeforeAfterTag < Liquid::Tag
20
17
  def initialize(tag_name, markup, tokens)
21
18
  super
@@ -23,170 +20,18 @@ module Jekyll
23
20
  end
24
21
 
25
22
  def render(context)
26
- config = context.registers[:site]&.config&.dig("before_after") || {}
27
23
  before = @attributes["before"] || ""
28
24
  after = @attributes["after"] || ""
29
- b_label = @attributes["before_label"] || config["before_label"] || "Before"
30
- a_label = @attributes["after_label"] || config["after_label"] || "After"
31
- start = (@attributes["start"] || config["start"] || 50).to_i
32
- orient = @attributes["orientation"] || config["orientation"] || "horizontal"
33
- alt_b = @attributes["alt_before"] || ""
34
- alt_a = @attributes["alt_after"] || ""
35
- srcset_b = @attributes["srcset_before"] || ""
36
- srcset_a = @attributes["srcset_after"] || ""
37
- sizes = @attributes["sizes"] || "(max-width: 768px) 100vw, 100vw"
38
-
39
- before_img = build_img(before, alt_b, srcset_b, sizes)
40
- after_img = build_img(after, alt_a, srcset_a, sizes)
41
-
42
- script_block = emit_script(context)
25
+ orient = @attributes["orientation"] || "horizontal"
26
+ direction = orient == "vertical" ? ' direction="vertical"' : ""
43
27
 
44
28
  <<~HTML
45
- <div class="before-after" data-orientation="#{orient}" data-start="#{start}">
46
- <div class="before-after__before">
47
- #{before_img}
48
- #{b_label.empty? ? "" : %(<span class="before-after__label before-after__label--before">#{b_label}</span>)}
49
- </div>
50
- <div class="before-after__after">
51
- #{after_img}
52
- #{a_label.empty? ? "" : %(<span class="before-after__label before-after__label--after">#{a_label}</span>)}
53
- </div>
54
- <div class="before-after__handle"
55
- role="slider"
56
- aria-label="Image comparison slider"
57
- aria-valuenow="#{start}"
58
- aria-valuemin="0"
59
- aria-valuemax="100"
60
- tabindex="0">
61
- <div class="before-after__handle-line"></div>
62
- <div class="before-after__handle-knob">
63
- <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
64
- <path d="M8 5l-5 7 5 7M16 5l5 7-5 7" stroke="currentColor" stroke-width="2"
65
- stroke-linecap="round" stroke-linejoin="round" fill="none"/>
66
- </svg>
67
- </div>
68
- </div>
69
- </div>
70
- #{script_block}
29
+ <img-comparison-slider#{direction}>
30
+ <img slot="first" src="#{before}" alt="Before">
31
+ <img slot="second" src="#{after}" alt="After">
32
+ </img-comparison-slider>
71
33
  HTML
72
34
  end
73
-
74
- private
75
-
76
- def build_img(src, alt, srcset, sizes)
77
- if srcset && !srcset.empty?
78
- %(<picture><source srcset="#{srcset}" sizes="#{sizes}"><img src="#{src}" alt="#{alt}" loading="lazy" draggable="false"></picture>)
79
- else
80
- %(<img src="#{src}" alt="#{alt}" loading="lazy" draggable="false">)
81
- end
82
- end
83
-
84
- def emit_script(context)
85
- return "" if context.registers[BEFORE_AFTER_SCRIPT_FLAG]
86
- context.registers[BEFORE_AFTER_SCRIPT_FLAG] = true
87
- <<~JS
88
- <script>
89
- (function() {
90
- function initSlider(el) {
91
- var orientation = el.dataset.orientation === "vertical" ? "vertical" : "horizontal";
92
- var handle = el.querySelector(".before-after__handle");
93
- var beforePane = el.querySelector(".before-after__before");
94
- var start = parseFloat(el.dataset.start) || 50;
95
- var dragging = false;
96
-
97
- function setPosition(pct) {
98
- pct = Math.min(100, Math.max(0, pct));
99
- if (orientation === "vertical") {
100
- beforePane.style.clipPath = "inset(0 0 " + (100 - pct) + "% 0)";
101
- handle.style.top = pct + "%";
102
- handle.style.left = "";
103
- } else {
104
- beforePane.style.clipPath = "inset(0 " + (100 - pct) + "% 0 0)";
105
- handle.style.left = pct + "%";
106
- handle.style.top = "";
107
- }
108
- handle.setAttribute("aria-valuenow", Math.round(pct));
109
- }
110
-
111
- function pctFromEvent(e) {
112
- var rect = el.getBoundingClientRect();
113
- if (orientation === "vertical") {
114
- return ((e.clientY - rect.top) / rect.height) * 100;
115
- } else {
116
- return ((e.clientX - rect.left) / rect.width) * 100;
117
- }
118
- }
119
-
120
- handle.addEventListener("pointerdown", function(e) {
121
- dragging = true;
122
- handle.setPointerCapture(e.pointerId);
123
- el.classList.add("before-after--dragging");
124
- e.preventDefault();
125
- });
126
-
127
- handle.addEventListener("pointermove", function(e) {
128
- if (!dragging) return;
129
- setPosition(pctFromEvent(e));
130
- });
131
-
132
- handle.addEventListener("pointerup", function() {
133
- dragging = false;
134
- el.classList.remove("before-after--dragging");
135
- });
136
-
137
- handle.addEventListener("pointercancel", function() {
138
- dragging = false;
139
- el.classList.remove("before-after--dragging");
140
- });
141
-
142
- // Keyboard support
143
- handle.addEventListener("keydown", function(e) {
144
- var step = e.shiftKey ? 1 : 5;
145
- var cur = parseFloat(handle.getAttribute("aria-valuenow")) || 50;
146
- if (e.key === "ArrowRight" || e.key === "ArrowDown") {
147
- setPosition(cur + step);
148
- e.preventDefault();
149
- } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
150
- setPosition(cur - step);
151
- e.preventDefault();
152
- }
153
- });
154
-
155
- // Click anywhere on container to jump
156
- el.addEventListener("pointerdown", function(e) {
157
- if (e.target === handle || handle.contains(e.target)) return;
158
- setPosition(pctFromEvent(e));
159
- });
160
-
161
- // Recalculate on resize
162
- if (window.ResizeObserver) {
163
- new ResizeObserver(function() {
164
- var cur = parseFloat(handle.getAttribute("aria-valuenow")) || start;
165
- setPosition(cur);
166
- }).observe(el);
167
- }
168
-
169
- setPosition(start);
170
- }
171
-
172
- function init() {
173
- document.querySelectorAll(".before-after").forEach(function(el) {
174
- if (!el.dataset.baInit) {
175
- el.dataset.baInit = "1";
176
- initSlider(el);
177
- }
178
- });
179
- }
180
-
181
- if (document.readyState === "loading") {
182
- document.addEventListener("DOMContentLoaded", init);
183
- } else {
184
- init();
185
- }
186
- })();
187
- </script>
188
- JS
189
- end
190
35
  end
191
36
  end
192
37
 
@@ -1,3 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "jekyll/before_after_tag"
4
+
5
+ module Jekyll
6
+ class BeforeAfterScriptInjector
7
+ def call(doc)
8
+ return if doc.output.nil? || !doc.output.include?("<img-comparison-slider>")
9
+ return if doc.output.include?("<!-- img-comparison-slider-injected -->")
10
+
11
+ script = <<~HTML
12
+ <!-- img-comparison-slider-injected -->
13
+ <script type="module" src="https://unpkg.com/img-comparison-slider@7/dist/index.js"></script>
14
+ <link rel="stylesheet" href="https://unpkg.com/img-comparison-slider@7/dist/styles.css">
15
+ HTML
16
+
17
+ doc.output.sub!("</head>", "#{script}</head>")
18
+ end
19
+ end
20
+
21
+ Jekyll::Hooks.register :pages, :post_render do |doc|
22
+ Jekyll::BeforeAfterScriptInjector.new.call(doc)
23
+ end
24
+
25
+ Jekyll::Hooks.register :documents, :post_render do |doc|
26
+ Jekyll::BeforeAfterScriptInjector.new.call(doc)
27
+ end
28
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-before-after
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Chance
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-08-22 00:00:00.000000000 Z
10
+ date: 2026-08-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: jekyll
@@ -44,9 +44,9 @@ files:
44
44
  - CONTRIBUTING.md
45
45
  - LICENSE.txt
46
46
  - README.md
47
- - _before-after.scss
48
47
  - jekyll-before-after.gemspec
49
48
  - lib/jekyll-before-after.rb
49
+ - lib/jekyll/before_after_generator.rb
50
50
  - lib/jekyll/before_after_tag.rb
51
51
  homepage: https://jasonchance.com/projects/jekyll-before-after/
52
52
  licenses:
data/_before-after.scss DELETED
@@ -1,144 +0,0 @@
1
- // jekyll-before-after — copy to your site's CSS/SCSS directory and @import or @use it.
2
-
3
- .before-after {
4
- position: relative;
5
- overflow: hidden;
6
- cursor: col-resize;
7
- user-select: none;
8
- touch-action: none;
9
- border-radius: var(--ba-radius, 4px);
10
- background: var(--ba-bg, #000);
11
-
12
- &[data-orientation="vertical"] {
13
- cursor: row-resize;
14
- }
15
-
16
- &--dragging {
17
- cursor: grabbing;
18
- }
19
- }
20
-
21
- // After pane: normal flow — its img drives the container height
22
- .before-after__after {
23
- position: relative;
24
- display: block;
25
-
26
- img,
27
- picture img {
28
- display: block;
29
- width: 100%;
30
- height: auto;
31
- pointer-events: none;
32
- }
33
- }
34
-
35
- // Before pane: absolutely stacked on top, clipped by JS
36
- .before-after__before {
37
- position: absolute;
38
- inset: 0;
39
- z-index: 1;
40
- clip-path: inset(0 50% 0 0); // overridden by JS on init
41
-
42
- img,
43
- picture img {
44
- display: block;
45
- width: 100%;
46
- height: 100%;
47
- object-fit: cover;
48
- pointer-events: none;
49
- }
50
- }
51
-
52
- // Labels
53
- .before-after__label {
54
- position: absolute;
55
- bottom: 0.75rem;
56
- z-index: 2;
57
- padding: 0.25rem 0.6rem;
58
- font-size: 0.75rem;
59
- font-weight: 600;
60
- letter-spacing: 0.04em;
61
- text-transform: uppercase;
62
- background: var(--ba-label-bg, rgba(0, 0, 0, 0.55));
63
- color: var(--ba-label-color, #fff);
64
- border-radius: var(--ba-label-radius, 3px);
65
- pointer-events: none;
66
-
67
- &--before {
68
- left: 0.75rem;
69
- }
70
-
71
- &--after {
72
- right: 0.75rem;
73
- }
74
- }
75
-
76
- // Handle
77
- .before-after__handle {
78
- position: absolute;
79
- top: 50%;
80
- left: 50%; // overridden by JS
81
- transform: translate(-50%, -50%);
82
- z-index: 10;
83
- display: flex;
84
- align-items: center;
85
- justify-content: center;
86
- outline: none;
87
-
88
- &:focus-visible .before-after__handle-knob {
89
- box-shadow: 0 0 0 3px var(--ba-focus-ring, rgba(255, 255, 255, 0.8));
90
- }
91
- }
92
-
93
- .before-after__handle-line {
94
- position: absolute;
95
- background: var(--ba-handle-color, #fff);
96
- opacity: 0.85;
97
-
98
- .before-after:not([data-orientation="vertical"]) & {
99
- width: var(--ba-line-width, 2px);
100
- top: -200%;
101
- bottom: -200%;
102
- left: 50%;
103
- transform: translateX(-50%);
104
- }
105
-
106
- [data-orientation="vertical"] & {
107
- height: var(--ba-line-width, 2px);
108
- left: -200%;
109
- right: -200%;
110
- top: 50%;
111
- transform: translateY(-50%);
112
- }
113
- }
114
-
115
- .before-after__handle-knob {
116
- position: relative;
117
- z-index: 1;
118
- width: var(--ba-knob-size, 2.75rem);
119
- height: var(--ba-knob-size, 2.75rem);
120
- border-radius: 50%;
121
- background: var(--ba-handle-color, #fff);
122
- color: var(--ba-knob-icon-color, #333);
123
- display: flex;
124
- align-items: center;
125
- justify-content: center;
126
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
127
- transition: transform 0.15s ease, box-shadow 0.15s ease;
128
- cursor: grab;
129
-
130
- svg {
131
- width: 1.1rem;
132
- height: 1.1rem;
133
- }
134
-
135
- .before-after--dragging & {
136
- cursor: grabbing;
137
- transform: scale(1.12);
138
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
139
- }
140
-
141
- [data-orientation="vertical"] & svg {
142
- transform: rotate(90deg);
143
- }
144
- }