jekyll-color-copy-tag 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d8aaa3496ae7cf44d0642325182fe3261784a91533947295a267b71ceae81cce
4
+ data.tar.gz: e3f42023f7175ece0c86e6c383a225414d0cf21ada9bf9e195c67c4f170706ac
5
+ SHA512:
6
+ metadata.gz: 4f2293a821bf7a4f0f1f36f55ffea009111835efd2ae89e1e77417ada412115b10ac062fcb2208217a473ff48e9d76a85fec7df2803c0f6955668837ad7ddeda
7
+ data.tar.gz: 1b747d74ef5aaec88d4050ec5e3c225248d1fa86af1ad51bb90446522d29b400bcdce1b424f350f38dc997505ef496940c1c9d5ae6ca6ca218c52dcd12ec4b0f
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-08-29
9
+
10
+ ### Added
11
+
12
+ - Initial release of jekyll-color-copy-tag
13
+ - `{% color_copy '#HEX' %}` Liquid tag for rendering clickable copy buttons
14
+ - Button sizing: small, medium (default), large
15
+ - Paint swatches with `swatch` modifier and custom dimensions via `swatch_WxH` syntax
16
+ - WCAG contrast ratio calculation for automatic black/white text selection
17
+ - Copy-to-clipboard feedback: background color change and icon swap
18
+ - Configurable copied-to-clipboard feedback color via `_config.yml`
19
+ - Configurable default swatch dimensions in `_config.yml`
20
+ - Embedded Bootstrap Icons (copy icon, check-lg icon)
21
+ - No external dependencies (FontAwesome, etc.)
22
+ - Full accessibility support with title attributes
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jason Chance
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # jekyll-color-copy-tag
2
+
3
+ A Jekyll Liquid tag that renders clickable hex color copy buttons and paint swatches with copy-to-clipboard functionality. Uses WCAG contrast ratio math to automatically choose black or white text for optimal accessible contrast.
4
+
5
+ - **No dependencies** — includes its own SVG icons
6
+ - **WCAG accessible** — automatic contrast calculation
7
+ - **Flexible sizing** — three button sizes, configurable swatch dimensions, custom per-tag overrides
8
+ - **Copy feedback** — visual confirmation with background color change and icon swap
9
+ - **Paint swatches** — render clickable color chips with hex label positioned in corner
10
+
11
+ ## Installation
12
+
13
+ Add the gem to your `Gemfile`:
14
+
15
+ ```ruby
16
+ gem "jekyll-color-copy-tag"
17
+ ```
18
+
19
+ Enable it in `_config.yml`:
20
+
21
+ ```yaml
22
+ plugins:
23
+ - jekyll-color-copy-tag
24
+ ```
25
+
26
+ Add the JavaScript include near the end of your layout, just before `</body>`:
27
+
28
+ ```liquid
29
+ {% include color_copy.js %}
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ### Copy Buttons
35
+
36
+ Render clickable buttons in three sizes:
37
+
38
+ ```liquid
39
+ {% color_copy '#2BB3B1' %} {# medium (default) #}
40
+ {% color_copy '#2BB3B1', sm %} {# small #}
41
+ {% color_copy '#2BB3B1', lg %} {# large #}
42
+ ```
43
+
44
+ Text color is automatically chosen (black or white) based on WCAG contrast ratio math against the button background.
45
+
46
+ ### Paint Swatches
47
+
48
+ Render clickable color swatches with hex value displayed in the corner:
49
+
50
+ ```liquid
51
+ {% color_copy '#2BB3B1', swatch %} {# uses config dimensions (default 100×100) #}
52
+ {% color_copy '#2BB3B1', swatch_150x150 %} {# override to 150×150 #}
53
+ {% color_copy '#2BB3B1', swatch_200x100 %} {# custom width×height #}
54
+ ```
55
+
56
+ The hex label has semi-transparent background and adapts text color for readability on any swatch color.
57
+
58
+ ## Configuration
59
+
60
+ Customize the copied-to-clipboard feedback color and default swatch dimensions in `_config.yml`:
61
+
62
+ ```yaml
63
+ color_copy:
64
+ copied_color: "#2BB3B1"
65
+ swatch_width: 100
66
+ swatch_height: 100
67
+ ```
68
+
69
+ - `copied_color` — Background color shown when button/swatch is clicked (defaults to `#2BB3B1`)
70
+ - `swatch_width` — Default paint swatch width in pixels (defaults to `100px`)
71
+ - `swatch_height` — Default paint swatch height in pixels (defaults to `100px`)
72
+
73
+ Override per-tag with `swatch_WxH` syntax: `{% color_copy '#hex', swatch_200x150 %}`
74
+
75
+ ## How It Works
76
+
77
+ ### WCAG Contrast Ratio
78
+
79
+ The plugin calculates relative luminance using the sRGB-to-linear conversion algorithm, then computes contrast ratios against both black (#000) and white (#fff). It selects whichever provides higher contrast, ensuring text is always readable per WCAG standards.
80
+
81
+ ### Copy to Clipboard
82
+
83
+ Clicking a button or swatch triggers JavaScript that:
84
+ 1. Copies the hex value to clipboard
85
+ 2. Temporarily changes background to the configured `copied_color`
86
+ 3. Shows a checkmark icon (buttons show "✓ Copied", swatches show just the checkmark)
87
+ 4. Restores original background and content after 1.2 seconds
88
+
89
+ ### Icons
90
+
91
+ Both icons are embedded as inline SVG from Bootstrap Icons:
92
+ - Copy icon (16×16 viewBox)
93
+ - Check icon (check-lg variant)
94
+
95
+ ## Accessibility
96
+
97
+ - Text color automatically selected for WCAG AAA contrast on all backgrounds
98
+ - Semantic HTML with title attributes
99
+ - No reliance on external icon libraries
100
+ - Works without JavaScript (buttons render but copy won't function)
101
+
102
+ ## License
103
+
104
+ MIT License
105
+
106
+ ## Credits
107
+
108
+ Icons from [Bootstrap Icons](https://icons.getbootstrap.com/) (MIT License)
@@ -0,0 +1,29 @@
1
+ <script>
2
+ function copyToClipboard(text, element) {
3
+ if (!navigator.clipboard) return;
4
+
5
+ navigator.clipboard.writeText(text).then(function () {
6
+ if (!element) return;
7
+
8
+ const originalInnerHTML = element.innerHTML;
9
+ const originalBackground = window.getComputedStyle(element).backgroundColor;
10
+ const copiedColor = element.dataset.copiedColor || "#2BB3B1";
11
+
12
+ // Checkmark icon (check-lg from Bootstrap Icons)
13
+ const checkmarkIcon = '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 16 16" width="1em" height="1em" style="margin-right: 0.35em; vertical-align: -0.125em; fill: currentColor;"><path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z"/></svg>';
14
+
15
+ // Detect if this is a swatch (has class color-copy-swatch) or button
16
+ const isSwatch = element.classList.contains('color-copy-swatch');
17
+ const copiedContent = isSwatch ? checkmarkIcon : checkmarkIcon + ' Copied';
18
+
19
+ // Replace with checkmark (swatch) or checkmark + text (button)
20
+ element.innerHTML = copiedContent;
21
+ element.style.background = copiedColor;
22
+
23
+ setTimeout(function () {
24
+ element.innerHTML = originalInnerHTML;
25
+ element.style.background = originalBackground;
26
+ }, 1200);
27
+ });
28
+ }
29
+ </script>
@@ -0,0 +1,7 @@
1
+ require_relative "jekyll/color_copy_tag/version"
2
+ require_relative "jekyll/color_copy_tag"
3
+
4
+ module Jekyll
5
+ module ColorCopyTag
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Jekyll
2
+ module ColorCopyTag
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,169 @@
1
+ require "liquid"
2
+
3
+ # Liquid tag that renders a clickable copy-to-clipboard button or swatch for a hex color.
4
+ #
5
+ # Usage:
6
+ # {% color_copy '#RRGGBB' %} — medium button (default)
7
+ # {% color_copy '#RRGGBB', sm %} — small button (inline / notification lists)
8
+ # {% color_copy '#RRGGBB', lg %} — large button (palette grid)
9
+ # {% color_copy '#RRGGBB', swatch %} — 100x100 paint swatch (configurable)
10
+ #
11
+ # The tag uses WCAG contrast ratio math to choose black or white text for the
12
+ # highest-contrast accessible label, with a subtle border on light swatches.
13
+
14
+ module Jekyll
15
+ class ColorCopyTag < Liquid::Tag
16
+ def initialize(tag_name, markup, tokens)
17
+ super
18
+ parts = markup.strip.split(/\s*,\s*/)
19
+ @hex = parts[0].gsub(/['"]/, '').strip
20
+ @type = 'button'
21
+ @size = 'md'
22
+ @swatch_width = nil
23
+ @swatch_height = nil
24
+ parts[1..].to_a.each do |part|
25
+ part = part.strip
26
+ case part
27
+ when 'sm', 'md', 'lg'
28
+ @size = part
29
+ when 'swatch'
30
+ @type = 'swatch'
31
+ when /^swatch_(\d+)x(\d+)$/
32
+ @type = 'swatch'
33
+ @swatch_width = $1.to_i
34
+ @swatch_height = $2.to_i
35
+ end
36
+ end
37
+ end
38
+
39
+ def render(context)
40
+ hex = @hex
41
+ config = context&.registers&.[](:site)&.config&.[]('color_copy') || {}
42
+ copied_color = config['copied_color'] || '#2BB3B1'
43
+ swatch_width = @swatch_width || config['swatch_width'] || 100
44
+ swatch_height = @swatch_height || config['swatch_height'] || 100
45
+
46
+ if @type == 'swatch'
47
+ render_swatch(hex, copied_color, swatch_width, swatch_height)
48
+ else
49
+ render_button(hex, copied_color)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def render_button(hex, copied_color)
56
+ luminance = relative_luminance(hex)
57
+ contrast_with_white = contrast_ratio(luminance, 1.0)
58
+ contrast_with_black = contrast_ratio(luminance, 0.0)
59
+
60
+ if contrast_with_black >= contrast_with_white
61
+ text_color = '#000'
62
+ border_style = "border: 1px solid #{darken(hex)};"
63
+ else
64
+ text_color = 'white'
65
+ border_style = 'border: none;'
66
+ end
67
+
68
+ case @size
69
+ when 'sm'
70
+ padding = '0.2rem 0.45rem'
71
+ font_size = '0.75rem'
72
+ icon_spacing = '0.3em'
73
+ when 'lg'
74
+ padding = '0.8rem 1.45rem'
75
+ font_size = '1rem'
76
+ icon_spacing = '0.5em'
77
+ else
78
+ padding = '0.5rem 1rem'
79
+ font_size = '0.875rem'
80
+ icon_spacing = '0.35em'
81
+ end
82
+
83
+ style = [
84
+ "padding: #{padding}",
85
+ "background: #{hex}",
86
+ "color: #{text_color}",
87
+ border_style.chomp(';'),
88
+ 'border-radius: 4px',
89
+ 'cursor: pointer',
90
+ "font-size: #{font_size}",
91
+ 'font-weight: 500'
92
+ ].join('; ')
93
+
94
+ copy_icon = %(<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 16 16" width="1em" height="1em" style="margin-right: 0.35em; vertical-align: -0.125em; fill: currentColor;"><path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/></svg>)
95
+
96
+ %(<button class="color-copy-btn color-copy-btn--#{@size}" data-color-copy-button data-color="#{hex}" data-copied-color="#{copied_color}" data-copy-icon="#{ERB::Util.html_escape(copy_icon)}" data-copied-icon="#{ERB::Util.html_escape(copy_icon)}" onclick="copyToClipboard('#{hex}', this)" style="#{style};">#{copy_icon.sub('margin-right: 0.35em', "margin-right: #{icon_spacing}")} #{hex}</button>)
97
+ end
98
+
99
+ def render_swatch(hex, copied_color, width, height)
100
+ luminance = relative_luminance(hex)
101
+ contrast_with_white = contrast_ratio(luminance, 1.0)
102
+ contrast_with_black = contrast_ratio(luminance, 0.0)
103
+
104
+ text_color = contrast_with_black >= contrast_with_white ? '#000' : 'white'
105
+ border_style = contrast_with_black >= contrast_with_white ? "1px solid #{darken(hex)}" : 'none'
106
+
107
+ swatch_style = [
108
+ "width: #{width}px",
109
+ "height: #{height}px",
110
+ "background: #{hex}",
111
+ "border: #{border_style}",
112
+ 'border-radius: 4px',
113
+ 'cursor: pointer',
114
+ 'position: relative',
115
+ 'display: inline-flex',
116
+ 'align-items: flex-end',
117
+ 'justify-content: flex-end',
118
+ 'line-height: 1',
119
+ 'vertical-align: top'
120
+ ].join('; ')
121
+
122
+ hex_style = [
123
+ "color: #{text_color}",
124
+ 'font-size: 0.65rem',
125
+ 'font-weight: 600',
126
+ 'padding: 4px 6px',
127
+ 'background: rgba(0, 0, 0, 0.1)',
128
+ 'border-radius: 2px',
129
+ 'line-height: 1'
130
+ ].join('; ')
131
+
132
+ %(<div class="color-copy-swatch" data-color="#{hex}" data-copied-color="#{copied_color}" onclick="copyToClipboard('#{hex}', this)" style="#{swatch_style};" title="Click to copy #{hex}"><span style="#{hex_style}">#{hex}</span></div>)
133
+ end
134
+
135
+ def relative_luminance(hex)
136
+ r, g, b = hex_to_rgb(hex).map { |channel| srgb_to_linear(channel) }
137
+ 0.2126 * r + 0.7152 * g + 0.0722 * b
138
+ end
139
+
140
+ def contrast_ratio(luminance, other_luminance)
141
+ lighter = [luminance, other_luminance].max
142
+ darker = [luminance, other_luminance].min
143
+ (lighter + 0.05) / (darker + 0.05)
144
+ end
145
+
146
+ def hex_to_rgb(hex)
147
+ [hex[1, 2], hex[3, 2], hex[5, 2]].map { |channel| channel.to_i(16) / 255.0 }
148
+ end
149
+
150
+ def srgb_to_linear(channel)
151
+ if channel <= 0.04045
152
+ channel / 12.92
153
+ else
154
+ ((channel + 0.055) / 1.055) ** 2.4
155
+ end
156
+ end
157
+
158
+ # Produce a slightly darker hex border from the swatch color
159
+ def darken(hex)
160
+ r, g, b = hex_to_rgb(hex).map { |channel| (channel * 255).to_i }
161
+ dr = [(r - 40), 0].max
162
+ dg = [(g - 40), 0].max
163
+ db = [(b - 40), 0].max
164
+ format('#%02X%02X%02X', dr, dg, db)
165
+ end
166
+ end
167
+ end
168
+
169
+ Liquid::Template.register_tag('color_copy', Jekyll::ColorCopyTag)
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-color-copy-tag
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jason Chance
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-08-29 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: jekyll
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '3.5'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '5'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '3.5'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '5'
32
+ - !ruby/object:Gem::Dependency
33
+ name: liquid
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '3.0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '3.0'
46
+ description: Renders clickable buttons and swatches to copy hex colors to clipboard.
47
+ Automatically selects black or white text using WCAG contrast math. Includes custom
48
+ SVG icons, no dependencies.
49
+ email:
50
+ - jason@jasonchance.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - CHANGELOG.md
56
+ - LICENSE.txt
57
+ - README.md
58
+ - _includes/color_copy.js
59
+ - lib/color_copy_tag.rb
60
+ - lib/jekyll/color_copy_tag.rb
61
+ - lib/jekyll/color_copy_tag/version.rb
62
+ homepage: https://jasonchance.com/projects/jekyll-color-copy-tag/
63
+ licenses:
64
+ - MIT
65
+ metadata:
66
+ homepage_uri: https://jasonchance.com/projects/jekyll-color-copy-tag/
67
+ source_code_uri: https://github.com/jchance/jekyll-color-copy-tag
68
+ changelog_uri: https://github.com/jchance/jekyll-color-copy-tag/blob/master/CHANGELOG.md
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 3.6.2
84
+ specification_version: 4
85
+ summary: A Jekyll Liquid tag for clickable hex color copy buttons and paint swatches
86
+ test_files: []