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 +4 -4
- data/README.md +11 -3
- data/_includes/head.html +1 -0
- data/_layouts/entry.html +17 -0
- data/_layouts/home.html +28 -18
- data/_layouts/post.html +1 -13
- data/_layouts/shoot.html +11 -15
- data/_sass/yefeme.scss +7 -1
- data/assets/blurhash.js +130 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ed0199dd1d91cedd1a8a0120ea61490ebad253d41eec07af9be7c95352307363
|
|
4
|
+
data.tar.gz: 2d97f48217a11e8b30b55f21b43cb23a355c87956e5b37cdfe35420481021cf2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
|
|
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>
|
data/_layouts/entry.html
ADDED
|
@@ -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
|
-
<
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
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
|
|
55
|
-
{% assign
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
</
|
|
89
|
-
|
|
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:
|
|
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:
|
|
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
|
|
23
|
-
{% assign
|
|
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;
|
data/assets/blurhash.js
ADDED
|
@@ -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.
|
|
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-
|
|
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
|