yefeme 0.8.0 → 0.8.2

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: 4c10667a79facce4f6c0401d78f2a6fed47ab957ade81c0da3a5865f66585d6e
4
- data.tar.gz: 641245bffcdfdcd4ecf72c654ba8fe489dbb684db33fc18b81a1d0864ee4f2bf
3
+ metadata.gz: ed0199dd1d91cedd1a8a0120ea61490ebad253d41eec07af9be7c95352307363
4
+ data.tar.gz: 2d97f48217a11e8b30b55f21b43cb23a355c87956e5b37cdfe35420481021cf2
5
5
  SHA512:
6
- metadata.gz: 86fcff51ffad9b8eae7d5b6e3266acc9280f298361ff5c35c2d9907504e8ade0c097095ab971222cba354f61463d894154b5df636fceda66e5ebca3e56381654
7
- data.tar.gz: 8c153aa81fd90395bb749534c6322865c6865a7c8c76c910d8f67265447c0de415160d6387b84e7705713a217ff948be50d67588459b70c480fc515f380350d3
6
+ metadata.gz: ef18f21c9cccd8092fdcd8da3ba93b47f678b2dad695a6e57cc37712da1a551ee8c63b58f5dc4503ab0a5a94ed3cc814d38b427e7d38f8822489f2ef98304c82
7
+ data.tar.gz: c180febcd4ec4b6c676f8e4ce25e447dfaee3d1f89bbbc85abfcebe8347f2e3d576ed8557668518e24d0318877ffa126f271a0229e4fb749c75170141862299a
data/README.md CHANGED
@@ -58,7 +58,7 @@ cf_images_variants:
58
58
  xl: xl
59
59
  ```
60
60
 
61
- Each document in `_shoots` should use the `shoot` layout and provide a title, date, and list of Cloudflare image IDs:
61
+ Each document in `_shoots` should use the `shoot` layout and provide a title, date, and list of Cloudflare images. Include the intrinsic dimensions of the `thumb` variant so browsers can reserve each image's aspect ratio before it loads:
62
62
 
63
63
  ```yaml
64
64
  ---
@@ -66,11 +66,19 @@ layout: shoot
66
66
  title: Marseille
67
67
  date: 2025-11-25
68
68
  images:
69
- - image-id-one
70
- - image-id-two
69
+ - id: image-id-one
70
+ width: 1366
71
+ height: 910
72
+ blurhash: "LSD+;-t74.NF~XRiD%t7_3WBM_oe"
73
+ - id: image-id-two
74
+ width: 910
75
+ height: 1366
76
+ blurhash: "LaEV$vM_4ns:~qM{D%of-;M{WBof"
71
77
  ---
72
78
  ```
73
79
 
80
+ The theme decodes each BlurHash in the browser and displays it as an immediate placeholder behind the full thumbnail. Scalar image IDs remain supported for existing sites, but do not provide intrinsic sizing or placeholder information.
81
+
74
82
  ## Contributing
75
83
 
76
84
  Bug reports and pull requests are welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
data/_includes/head.html CHANGED
@@ -36,4 +36,5 @@
36
36
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
37
37
  <link href="https://fonts.googleapis.com/css2?family=Inconsolata:wght@200..900&family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
38
38
  <link rel="stylesheet" href="{{ "/assets/main.css" | relative_url }}">
39
+ <script defer src="{{ "/assets/blurhash.js" | relative_url }}"></script>
39
40
  </head>
@@ -0,0 +1,17 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ <header>
6
+ <div class="logo">
7
+ {% include logo.svg width=46 height=61 %}
8
+ <div class="under under-small">
9
+ <h2 itemprop="name headline"><em>{{ page.title | escape }}</em></h2>
10
+ <p class="date"><time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">{{ page.date | date: "%b %-d, %Y" }}</time></p>
11
+ <a class="back accent" href="{{ "/" | relative_url }}">~ back home</a>
12
+ </div>
13
+ </div>
14
+ <hr />
15
+ </header>
16
+
17
+ {{ content }}
data/_layouts/home.html CHANGED
@@ -38,11 +38,11 @@ layout: default
38
38
 
39
39
  <main>
40
40
  {% for entry in entries %}
41
- {% if entry.collection == "shoots" %}
42
- <a class="post shoot-block" href="{{ entry.url | relative_url }}">
43
- <span class="accent fixed-date">{{ entry.date | date: "%b %-d, %Y" }}</span>
44
- <div class="post-details shoot-details">
45
- <h2>{{ entry.title | escape }}</h2>
41
+ <a class="post{% if entry.collection == "shoots" %} shoot-block{% endif %}" href="{{ entry.url | relative_url }}">
42
+ <span class="accent fixed-date">{{ entry.date | date: "%b %-d, %Y" }}</span>
43
+ <div class="post-details{% if entry.collection == "shoots" %} shoot-details{% endif %}">
44
+ <h2>{{ entry.title | escape }}</h2>
45
+ {% if entry.collection == "shoots" %}
46
46
  {% assign total = entry.images | size %}
47
47
  {% if total > 3 %}
48
48
  {% assign preview_count = 4 %}
@@ -51,11 +51,19 @@ layout: default
51
51
  {% endif %}
52
52
 
53
53
  <div class="shoot-thumbs shoot-thumbs-{{ preview_count }}">
54
- {% for id in entry.images limit:3 %}
55
- {% assign base = "https://imagedelivery.net/" | append: site.cf_images_account_hash | append: "/" | append: id | append: "/" %}
54
+ {% for image in entry.images limit:3 %}
55
+ {% assign image_id = image["id"] | default: image %}
56
+ {% assign base = "https://imagedelivery.net/" | append: site.cf_images_account_hash | append: "/" | append: image_id | append: "/" %}
56
57
  <img
57
58
  class="shoot-thumb"
58
59
  src="{{ base }}{{ site.cf_images_variants.thumb }}"
60
+ {% if image["width"] and image["height"] %}
61
+ width="{{ image["width"] }}"
62
+ height="{{ image["height"] }}"
63
+ {% endif %}
64
+ {% if image["blurhash"] %}
65
+ data-blurhash="{{ image["blurhash"] | escape }}"
66
+ {% endif %}
59
67
  loading="lazy"
60
68
  decoding="async"
61
69
  alt="Photo from {{ entry.title | escape }}"
@@ -63,11 +71,19 @@ layout: default
63
71
  {% endfor %}
64
72
 
65
73
  {% if total > 3 %}
66
- {% assign fourth_id = entry.images[3] %}
74
+ {% assign fourth_image = entry.images[3] %}
75
+ {% assign fourth_id = fourth_image["id"] | default: fourth_image %}
67
76
  {% assign fourth_base = "https://imagedelivery.net/" | append: site.cf_images_account_hash | append: "/" | append: fourth_id | append: "/" %}
68
77
  <div class="shoot-thumb shoot-more-thumb">
69
78
  <img
70
79
  src="{{ fourth_base }}{{ site.cf_images_variants.thumb }}"
80
+ {% if fourth_image["width"] and fourth_image["height"] %}
81
+ width="{{ fourth_image["width"] }}"
82
+ height="{{ fourth_image["height"] }}"
83
+ {% endif %}
84
+ {% if fourth_image["blurhash"] %}
85
+ data-blurhash="{{ fourth_image["blurhash"] | escape }}"
86
+ {% endif %}
71
87
  alt=""
72
88
  loading="lazy"
73
89
  decoding="async"
@@ -76,16 +92,10 @@ layout: default
76
92
  </div>
77
93
  {% endif %}
78
94
  </div>
79
- </div>
80
- </a>
81
- {% else %}
82
- <a class="post" href="{{ entry.url | relative_url }}">
83
- <span class="accent fixed-date">{{ entry.date | date: "%b %-d, %Y" }}</span>
84
- <div class="post-details">
85
- <h2>{{ entry.title | escape }}</h2>
95
+ {% else %}
86
96
  <p class="excerpt">{{ entry.excerpt | strip_html | normalize_whitespace | truncate: 160 | escape }}</p>
87
- </div>
88
- </a>
89
- {% endif %}
97
+ {% endif %}
98
+ </div>
99
+ </a>
90
100
  {% endfor %}
91
101
  </main>
data/_layouts/post.html CHANGED
@@ -1,19 +1,7 @@
1
1
  ---
2
- layout: default
2
+ layout: entry
3
3
  ---
4
4
 
5
- <header>
6
- <div class="logo">
7
- {% include logo.svg width=46 height=61 %}
8
- </svg>
9
- <div class="under under-small">
10
- <h2 itemprop="name headline"><em>{{ page.title | escape }}</em></h2>
11
- <p class="date"><time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">{{ page.date | date: "%b %-d, %Y" }}</time></p>
12
- <a class="back accent" href="{{ "/" | relative }}">~ back to blog</a>
13
- </div>
14
- </div>
15
- <hr />
16
- </header>
17
5
  <main>
18
6
  <article itemprop="articleBody">
19
7
  {{ content }}
data/_layouts/shoot.html CHANGED
@@ -1,29 +1,25 @@
1
1
  ---
2
- layout: default
2
+ layout: entry
3
3
  ---
4
4
 
5
- <header>
6
- <div class="logo">
7
- {% include logo.svg width=46 height=61 %}
8
- <div class="under under-small">
9
- <h2 itemprop="name headline"><em>{{ page.title | escape }}</em></h2>
10
- <p class="date"><time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">{{ page.date | date: "%b %-d, %Y" }}</time></p>
11
- <a class="back accent" href="{{ "/" | relative_url }}">~ back home</a>
12
- </div>
13
- </div>
14
- <hr />
15
- </header>
16
-
17
5
  <main class="portfolio-day">
18
6
  {% assign thumb = site.cf_images_variants.thumb %}
19
7
  {% assign xl = site.cf_images_variants.xl %}
20
8
 
21
9
  <div class="grid">
22
- {% for id in page.images %}
23
- {% assign base = "https://imagedelivery.net/" | append: site.cf_images_account_hash | append: "/" | append: id | append: "/" %}
10
+ {% for image in page.images %}
11
+ {% assign image_id = image["id"] | default: image %}
12
+ {% assign base = "https://imagedelivery.net/" | append: site.cf_images_account_hash | append: "/" | append: image_id | append: "/" %}
24
13
  <a class="tile" href="{{ base }}{{ xl }}" target="_blank" rel="noopener">
25
14
  <img
26
15
  src="{{ base }}{{ thumb }}"
16
+ {% if image["width"] and image["height"] %}
17
+ width="{{ image["width"] }}"
18
+ height="{{ image["height"] }}"
19
+ {% endif %}
20
+ {% if image["blurhash"] %}
21
+ data-blurhash="{{ image["blurhash"] | escape }}"
22
+ {% endif %}
27
23
  loading="lazy"
28
24
  decoding="async"
29
25
  alt="Photo from {{ page.title | escape }}"
data/_sass/yefeme.scss CHANGED
@@ -163,7 +163,7 @@ main {
163
163
  }
164
164
 
165
165
  > img.shoot-thumb {
166
- background: #eee;
166
+ background-color: #eee;
167
167
  border-radius: 4px;
168
168
  height: var(--shoot-thumb-height);
169
169
  object-fit: cover;
@@ -379,6 +379,12 @@ article {
379
379
  }
380
380
  }
381
381
 
382
+ img[data-blurhash] {
383
+ background-position: center;
384
+ background-repeat: no-repeat;
385
+ background-size: cover;
386
+ }
387
+
382
388
  .social {
383
389
  display: flex;
384
390
  gap: 16px;
@@ -0,0 +1,130 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ var BASE83 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
5
+ var cache = new Map();
6
+
7
+ function decode83(value) {
8
+ var result = 0;
9
+
10
+ for (var index = 0; index < value.length; index += 1) {
11
+ result = result * 83 + BASE83.indexOf(value[index]);
12
+ }
13
+
14
+ return result;
15
+ }
16
+
17
+ function srgbToLinear(value) {
18
+ value /= 255;
19
+ return value <= 0.04045 ? value / 12.92 : Math.pow((value + 0.055) / 1.055, 2.4);
20
+ }
21
+
22
+ function linearToSrgb(value) {
23
+ value = Math.max(0, Math.min(1, value));
24
+ value = value <= 0.0031308 ? value * 12.92 : 1.055 * Math.pow(value, 1 / 2.4) - 0.055;
25
+ return Math.floor(value * 255 + 0.5);
26
+ }
27
+
28
+ function signPow(value, exponent) {
29
+ return (value < 0 ? -1 : 1) * Math.pow(Math.abs(value), exponent);
30
+ }
31
+
32
+ function decodeDC(value) {
33
+ return [
34
+ srgbToLinear(value >> 16),
35
+ srgbToLinear((value >> 8) & 255),
36
+ srgbToLinear(value & 255)
37
+ ];
38
+ }
39
+
40
+ function decodeAC(value, maximum) {
41
+ return [
42
+ signPow(Math.floor(value / (19 * 19)) / 9 - 1, 2) * maximum,
43
+ signPow((Math.floor(value / 19) % 19) / 9 - 1, 2) * maximum,
44
+ signPow((value % 19) / 9 - 1, 2) * maximum
45
+ ];
46
+ }
47
+
48
+ function decode(hash, width, height) {
49
+ var sizeFlag = decode83(hash[0]);
50
+ var componentsY = Math.floor(sizeFlag / 9) + 1;
51
+ var componentsX = (sizeFlag % 9) + 1;
52
+ var expectedLength = 4 + 2 * componentsX * componentsY;
53
+
54
+ if (hash.length !== expectedLength) return null;
55
+
56
+ var maximum = (decode83(hash[1]) + 1) / 166;
57
+ var colors = [decodeDC(decode83(hash.slice(2, 6)))];
58
+
59
+ for (var component = 1; component < componentsX * componentsY; component += 1) {
60
+ colors.push(decodeAC(decode83(hash.slice(4 + component * 2, 6 + component * 2)), maximum));
61
+ }
62
+
63
+ var pixels = new Uint8ClampedArray(width * height * 4);
64
+
65
+ for (var y = 0; y < height; y += 1) {
66
+ for (var x = 0; x < width; x += 1) {
67
+ var red = 0;
68
+ var green = 0;
69
+ var blue = 0;
70
+
71
+ for (var componentY = 0; componentY < componentsY; componentY += 1) {
72
+ for (var componentX = 0; componentX < componentsX; componentX += 1) {
73
+ var basis =
74
+ Math.cos(Math.PI * x * componentX / width) *
75
+ Math.cos(Math.PI * y * componentY / height);
76
+ var color = colors[componentX + componentY * componentsX];
77
+ red += color[0] * basis;
78
+ green += color[1] * basis;
79
+ blue += color[2] * basis;
80
+ }
81
+ }
82
+
83
+ var offset = 4 * (x + y * width);
84
+ pixels[offset] = linearToSrgb(red);
85
+ pixels[offset + 1] = linearToSrgb(green);
86
+ pixels[offset + 2] = linearToSrgb(blue);
87
+ pixels[offset + 3] = 255;
88
+ }
89
+ }
90
+
91
+ return pixels;
92
+ }
93
+
94
+ function placeholderFor(image) {
95
+ var hash = image.dataset.blurhash;
96
+ var intrinsicWidth = Number(image.getAttribute("width"));
97
+ var intrinsicHeight = Number(image.getAttribute("height"));
98
+
99
+ if (!hash || !intrinsicWidth || !intrinsicHeight) return;
100
+
101
+ var width = intrinsicWidth >= intrinsicHeight ? 32 : Math.max(1, Math.round(32 * intrinsicWidth / intrinsicHeight));
102
+ var height = intrinsicHeight >= intrinsicWidth ? 32 : Math.max(1, Math.round(32 * intrinsicHeight / intrinsicWidth));
103
+ var cacheKey = hash + ":" + width + "x" + height;
104
+ var dataUrl = cache.get(cacheKey);
105
+
106
+ if (!dataUrl) {
107
+ var pixels = decode(hash, width, height);
108
+ if (!pixels) return;
109
+
110
+ var canvas = document.createElement("canvas");
111
+ var context = canvas.getContext("2d");
112
+ if (!context) return;
113
+
114
+ canvas.width = width;
115
+ canvas.height = height;
116
+ var imageData = context.createImageData(width, height);
117
+ imageData.data.set(pixels);
118
+ context.putImageData(imageData, 0, 0);
119
+ dataUrl = canvas.toDataURL("image/jpeg", 0.7);
120
+ cache.set(cacheKey, dataUrl);
121
+ }
122
+
123
+ image.style.backgroundImage = "url(\"" + dataUrl + "\")";
124
+ image.style.backgroundPosition = "center";
125
+ image.style.backgroundRepeat = "no-repeat";
126
+ image.style.backgroundSize = "cover";
127
+ }
128
+
129
+ document.querySelectorAll("img[data-blurhash]").forEach(placeholderFor);
130
+ }());
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yefeme
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yefim Vedernikoff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-27 00:00:00.000000000 Z
11
+ date: 2026-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -39,11 +39,13 @@ files:
39
39
  - _includes/logo.svg
40
40
  - _includes/twitter.svg
41
41
  - _layouts/default.html
42
+ - _layouts/entry.html
42
43
  - _layouts/home.html
43
44
  - _layouts/post.html
44
45
  - _layouts/shoot.html
45
46
  - _sass/highlight.scss
46
47
  - _sass/yefeme.scss
48
+ - assets/blurhash.js
47
49
  - assets/main.scss
48
50
  - index.md
49
51
  homepage: https://github.com/yefim/yefeme