imdhemy-jekyll-theme 1.2.1 → 1.3.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 +4 -4
- data/README.md +13 -0
- data/_includes/post-header.html +1 -1
- data/_includes/post-navigation.html +29 -6
- data/_sass/components/_core.scss +159 -116
- data/exe/imdhemy-image +6 -0
- data/lib/imdhemy/jekyll/theme/image_cli.rb +280 -0
- metadata +7 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69e9d0e1c1d690401607c1367a5bc466299ad39b138bec2da357e728051de11b
|
|
4
|
+
data.tar.gz: fadd0e06e18d3555ceabba16456bfcae609feea59ac9d60e60d311c8ccf53b0f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bcb7c70dcd2aa0c7a7455a15f5c5cf75a4e473a63ff16539825c2651cf25397d7011f95315cf2f80bf5307d6a8ba1f08af5075e76a321a8ee54bcc56a389e1fa
|
|
7
|
+
data.tar.gz: bbe0c712aa2bce20a32a6ba998a29dccf93be925dd9427da01b4413fde456305f83363201a1a983a162b85bdf5c8b6ea64affd7c86b1459c43468b24181526f1
|
data/README.md
CHANGED
|
@@ -10,10 +10,23 @@ All documentation lives in the [`docs/`](./docs) directory.
|
|
|
10
10
|
- [Getting Started](./docs/getting-started.md)
|
|
11
11
|
- [Configuration Reference](./docs/configuration.md)
|
|
12
12
|
- [Customization Guide](./docs/customization.md)
|
|
13
|
+
- [AI Design System Guide](./docs/ai-design-guide.md)
|
|
13
14
|
- [Components and Layouts](./docs/components.md)
|
|
14
15
|
- [Content Elements](./docs/content-elements.md)
|
|
15
16
|
- [Upgrade Guide](./UPGRADE.md)
|
|
16
17
|
|
|
18
|
+
## Optional Tooling
|
|
19
|
+
|
|
20
|
+
The gem also ships an optional image optimization executable for downstream sites:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bundle exec imdhemy-image path/to/image.jpg
|
|
24
|
+
bundle exec imdhemy-image --recursive assets/images
|
|
25
|
+
bundle exec imdhemy-image --dry-run path/to/images
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The tool is opt-in and intended for projects using the theme. It is not part of the theme render pipeline.
|
|
29
|
+
|
|
17
30
|
## Example Site
|
|
18
31
|
|
|
19
32
|
A complete runnable example is available in [`example/`](./example).
|
data/_includes/post-header.html
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
{% assign back_to_posts_label = site.theme_text.back_to_posts_label | default: "Back to all posts" %}
|
|
3
3
|
<div class="post-header">
|
|
4
4
|
<a class="post-back-link" href="{{ site.baseurl }}/blog">← {{ back_to_posts_label }}</a>
|
|
5
|
-
{% include post-series.html %}
|
|
6
5
|
<h1 class="post-title">{{ page.title }}</h1>
|
|
7
6
|
{% include post-meta.html date=page.date reading_minutes=read_minutes class_name="post-header__meta post-meta" %}
|
|
8
7
|
<div class="post-header__tags">
|
|
9
8
|
{% assign tags = page.tags %}
|
|
10
9
|
{% include post-tags.html %}
|
|
11
10
|
</div>
|
|
11
|
+
{% include post-series.html %}
|
|
12
12
|
|
|
13
13
|
{% if page.image %}
|
|
14
14
|
<div class="post-image-wrap">
|
|
@@ -1,17 +1,40 @@
|
|
|
1
1
|
{% assign previous_article_label = site.theme_text.previous_article_label | default: "Previous article" %}
|
|
2
2
|
{% assign next_article_label = site.theme_text.next_article_label | default: "Next article" %}
|
|
3
|
+
{% assign effective_previous_post = page.previous %}
|
|
4
|
+
{% assign effective_next_post = page.next %}
|
|
5
|
+
{% if page.list %}
|
|
6
|
+
{% assign series_posts = site.posts | where: "list", page.list | sort: "date" %}
|
|
7
|
+
{% assign current_series_index = -1 %}
|
|
8
|
+
{% for post in series_posts %}
|
|
9
|
+
{% if post.url == page.url %}
|
|
10
|
+
{% assign current_series_index = forloop.index0 %}
|
|
11
|
+
{% endif %}
|
|
12
|
+
{% endfor %}
|
|
13
|
+
{% if current_series_index >= 0 %}
|
|
14
|
+
{% assign effective_previous_post = page.previous %}
|
|
15
|
+
{% assign effective_next_post = page.next %}
|
|
16
|
+
{% assign previous_series_index = current_series_index | minus: 1 %}
|
|
17
|
+
{% assign next_series_index = current_series_index | plus: 1 %}
|
|
18
|
+
{% if previous_series_index >= 0 %}
|
|
19
|
+
{% assign effective_previous_post = series_posts[previous_series_index] %}
|
|
20
|
+
{% endif %}
|
|
21
|
+
{% if next_series_index < series_posts.size %}
|
|
22
|
+
{% assign effective_next_post = series_posts[next_series_index] %}
|
|
23
|
+
{% endif %}
|
|
24
|
+
{% endif %}
|
|
25
|
+
{% endif %}
|
|
3
26
|
<section class="post-navigation-shell">
|
|
4
27
|
<div class="post-navigation-shell__inner post-navigation">
|
|
5
|
-
{% if
|
|
6
|
-
<a class="post-navigation__item" href="{{ site.baseurl }}{{
|
|
28
|
+
{% if effective_previous_post.url %}
|
|
29
|
+
<a class="post-navigation__item" href="{{ site.baseurl }}{{ effective_previous_post.url }}">
|
|
7
30
|
<span class="post-navigation__label">{{ previous_article_label }}</span>
|
|
8
|
-
<span class="post-navigation__title">{{
|
|
31
|
+
<span class="post-navigation__title">{{ effective_previous_post.title }}</span>
|
|
9
32
|
</a>
|
|
10
33
|
{% endif %}
|
|
11
|
-
{% if
|
|
12
|
-
<a class="post-navigation__item post-navigation__item--next" href="{{ site.baseurl }}{{
|
|
34
|
+
{% if effective_next_post.url %}
|
|
35
|
+
<a class="post-navigation__item post-navigation__item--next" href="{{ site.baseurl }}{{ effective_next_post.url }}">
|
|
13
36
|
<span class="post-navigation__label">{{ next_article_label }}</span>
|
|
14
|
-
<span class="post-navigation__title">{{
|
|
37
|
+
<span class="post-navigation__title">{{ effective_next_post.title }}</span>
|
|
15
38
|
</a>
|
|
16
39
|
{% endif %}
|
|
17
40
|
</div>
|
data/_sass/components/_core.scss
CHANGED
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
.hero-section {
|
|
200
|
-
padding:
|
|
200
|
+
padding: 2.25rem 0 2.5rem;
|
|
201
201
|
background:
|
|
202
202
|
radial-gradient(circle at 8% 10%, color-mix(in srgb, var(--color-brand) 10%, transparent), transparent 42%),
|
|
203
203
|
var(--color-surface);
|
|
@@ -231,9 +231,12 @@
|
|
|
231
231
|
|
|
232
232
|
.hero-title {
|
|
233
233
|
margin-bottom: 2rem;
|
|
234
|
-
font-size: 1.
|
|
234
|
+
font-size: clamp(1.95rem, 4.2vw, 3rem);
|
|
235
235
|
font-weight: 700;
|
|
236
|
-
line-height: 1.
|
|
236
|
+
line-height: 1.12;
|
|
237
|
+
letter-spacing: -0.015em;
|
|
238
|
+
text-wrap: balance;
|
|
239
|
+
overflow-wrap: break-word;
|
|
237
240
|
}
|
|
238
241
|
|
|
239
242
|
.hero-title__accent {
|
|
@@ -304,13 +307,20 @@
|
|
|
304
307
|
padding: 1.25rem;
|
|
305
308
|
}
|
|
306
309
|
|
|
310
|
+
.latest-posts__inner {
|
|
311
|
+
padding: 0.75rem 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
307
314
|
.latest-posts__title,
|
|
308
315
|
.contributions__title,
|
|
309
316
|
.testimonials__title {
|
|
310
317
|
margin-bottom: 2rem;
|
|
311
318
|
text-align: center;
|
|
312
|
-
font-size: 1.
|
|
319
|
+
font-size: clamp(1.5rem, 2.8vw, 2rem);
|
|
313
320
|
font-weight: 700;
|
|
321
|
+
line-height: 1.2;
|
|
322
|
+
letter-spacing: -0.01em;
|
|
323
|
+
text-wrap: balance;
|
|
314
324
|
}
|
|
315
325
|
|
|
316
326
|
.section-subtitle,
|
|
@@ -334,11 +344,20 @@
|
|
|
334
344
|
margin-inline: auto;
|
|
335
345
|
}
|
|
336
346
|
|
|
347
|
+
.latest-posts__container {
|
|
348
|
+
width: 100%;
|
|
349
|
+
margin-inline: 0;
|
|
350
|
+
}
|
|
351
|
+
|
|
337
352
|
.latest-posts__cta {
|
|
338
353
|
margin-top: 4rem;
|
|
339
354
|
text-align: center;
|
|
340
355
|
}
|
|
341
356
|
|
|
357
|
+
.latest-posts__list {
|
|
358
|
+
width: min(100% - 2rem, 880px);
|
|
359
|
+
}
|
|
360
|
+
|
|
342
361
|
.post-listing,
|
|
343
362
|
.content-shell,
|
|
344
363
|
.post-header,
|
|
@@ -455,8 +474,11 @@
|
|
|
455
474
|
margin-bottom: 0.85rem;
|
|
456
475
|
font-family: "Manrope", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
457
476
|
font-size: 1.875rem;
|
|
458
|
-
font-weight:
|
|
477
|
+
font-weight: 800;
|
|
459
478
|
line-height: 1.2;
|
|
479
|
+
letter-spacing: -0.01em;
|
|
480
|
+
text-wrap: balance;
|
|
481
|
+
overflow-wrap: break-word;
|
|
460
482
|
}
|
|
461
483
|
|
|
462
484
|
.post-card__title-link {
|
|
@@ -500,8 +522,12 @@
|
|
|
500
522
|
|
|
501
523
|
.post-title {
|
|
502
524
|
font-family: "Manrope", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
503
|
-
font-size: 1.
|
|
525
|
+
font-size: clamp(1.45rem, 3vw, 2.1rem);
|
|
504
526
|
font-weight: 900;
|
|
527
|
+
line-height: 1.18;
|
|
528
|
+
letter-spacing: -0.012em;
|
|
529
|
+
text-wrap: balance;
|
|
530
|
+
overflow-wrap: break-word;
|
|
505
531
|
}
|
|
506
532
|
|
|
507
533
|
.post-header__meta {
|
|
@@ -577,7 +603,7 @@
|
|
|
577
603
|
|
|
578
604
|
.post-page .post-header {
|
|
579
605
|
margin-top: 0.4rem;
|
|
580
|
-
padding: 1.
|
|
606
|
+
padding: 1.2rem 1rem 0.95rem;
|
|
581
607
|
border: 1px solid color-mix(in srgb, var(--color-border) 86%, #c9d6ff 14%);
|
|
582
608
|
border-bottom: 0;
|
|
583
609
|
border-radius: 1rem 1rem 0 0;
|
|
@@ -769,9 +795,9 @@
|
|
|
769
795
|
|
|
770
796
|
.post-page .post-title {
|
|
771
797
|
margin-bottom: 0.95rem;
|
|
772
|
-
font-size:
|
|
773
|
-
line-height: 1.
|
|
774
|
-
letter-spacing: -0.
|
|
798
|
+
font-size: 1.875rem;
|
|
799
|
+
line-height: 1.2;
|
|
800
|
+
letter-spacing: -0.014em;
|
|
775
801
|
}
|
|
776
802
|
|
|
777
803
|
.post-page .post-image-wrap {
|
|
@@ -790,7 +816,7 @@
|
|
|
790
816
|
|
|
791
817
|
.post-page .content-shell {
|
|
792
818
|
margin-top: 0;
|
|
793
|
-
padding:
|
|
819
|
+
padding: 1.25rem 1rem 1.1rem;
|
|
794
820
|
border: 1px solid color-mix(in srgb, var(--color-border) 84%, #c9d6ff 16%);
|
|
795
821
|
border-top: 0;
|
|
796
822
|
border-radius: 0 0 1rem 1rem;
|
|
@@ -808,20 +834,20 @@
|
|
|
808
834
|
.post-page .post-navigation-shell__inner,
|
|
809
835
|
.post-page .related-posts-shell__inner {
|
|
810
836
|
margin-inline: auto;
|
|
811
|
-
padding:
|
|
837
|
+
padding: 0.9rem;
|
|
812
838
|
border: 1px solid color-mix(in srgb, var(--color-border) 84%, #c9d6ff 16%);
|
|
813
839
|
border-radius: 1rem;
|
|
814
840
|
background: #fff;
|
|
815
841
|
}
|
|
816
842
|
|
|
817
843
|
.post-page .comments-shell__inner {
|
|
818
|
-
padding:
|
|
844
|
+
padding: 1.25rem 1rem 1.1rem;
|
|
819
845
|
}
|
|
820
846
|
|
|
821
847
|
.comments-shell__heading {
|
|
822
848
|
display: block;
|
|
823
849
|
margin-bottom: 0.3rem;
|
|
824
|
-
font-size: 1.
|
|
850
|
+
font-size: 1.16rem;
|
|
825
851
|
font-weight: 800;
|
|
826
852
|
}
|
|
827
853
|
|
|
@@ -857,8 +883,8 @@
|
|
|
857
883
|
display: inline-flex;
|
|
858
884
|
align-items: center;
|
|
859
885
|
justify-content: center;
|
|
860
|
-
width:
|
|
861
|
-
height:
|
|
886
|
+
width: 1.85rem;
|
|
887
|
+
height: 1.85rem;
|
|
862
888
|
border: 1px solid color-mix(in srgb, var(--color-border) 84%, #c9d6ff 16%);
|
|
863
889
|
border-radius: 999px;
|
|
864
890
|
color: var(--color-muted);
|
|
@@ -892,7 +918,7 @@
|
|
|
892
918
|
}
|
|
893
919
|
|
|
894
920
|
.post-page .post-navigation {
|
|
895
|
-
grid-template-columns:
|
|
921
|
+
grid-template-columns: 1fr;
|
|
896
922
|
gap: 0.9rem;
|
|
897
923
|
}
|
|
898
924
|
|
|
@@ -916,7 +942,7 @@
|
|
|
916
942
|
|
|
917
943
|
.page-article__header {
|
|
918
944
|
margin-top: 0.4rem;
|
|
919
|
-
padding: 1.
|
|
945
|
+
padding: 1.35rem 1.25rem 1rem;
|
|
920
946
|
border: 1px solid color-mix(in srgb, var(--color-border) 86%, #c9d6ff 14%);
|
|
921
947
|
border-bottom: 0;
|
|
922
948
|
border-radius: 1rem 1rem 0 0;
|
|
@@ -926,17 +952,17 @@
|
|
|
926
952
|
}
|
|
927
953
|
|
|
928
954
|
.page-article__header-layout {
|
|
929
|
-
display:
|
|
930
|
-
justify-content: space-between;
|
|
931
|
-
gap: 2rem;
|
|
955
|
+
display: block;
|
|
932
956
|
}
|
|
933
957
|
|
|
934
958
|
.page-article__title {
|
|
935
959
|
margin: 0 0 1rem;
|
|
936
|
-
font-size: clamp(
|
|
960
|
+
font-size: clamp(1.35rem, 8.2vw, 1.7rem);
|
|
937
961
|
font-weight: 800;
|
|
938
|
-
line-height: 1.
|
|
939
|
-
letter-spacing: -0.
|
|
962
|
+
line-height: 1.2;
|
|
963
|
+
letter-spacing: -0.014em;
|
|
964
|
+
text-wrap: balance;
|
|
965
|
+
overflow-wrap: break-word;
|
|
940
966
|
}
|
|
941
967
|
|
|
942
968
|
.page-article__description {
|
|
@@ -950,7 +976,7 @@
|
|
|
950
976
|
|
|
951
977
|
.page-article .content-shell {
|
|
952
978
|
margin-top: 0;
|
|
953
|
-
padding:
|
|
979
|
+
padding: 1.35rem 1.25rem 1.2rem;
|
|
954
980
|
border: 1px solid color-mix(in srgb, var(--color-border) 84%, #c9d6ff 16%);
|
|
955
981
|
border-top: 0;
|
|
956
982
|
border-radius: 0 0 1rem 1rem;
|
|
@@ -976,7 +1002,7 @@
|
|
|
976
1002
|
.archive-hub .page-header--archive-hub {
|
|
977
1003
|
margin-top: 0.4rem;
|
|
978
1004
|
margin-bottom: 0;
|
|
979
|
-
padding:
|
|
1005
|
+
padding: 1.05rem 1rem 0.8rem;
|
|
980
1006
|
border: 1px solid color-mix(in srgb, var(--color-border) 86%, #c9d6ff 14%);
|
|
981
1007
|
border-bottom: 0;
|
|
982
1008
|
border-radius: 1rem 1rem 0 0;
|
|
@@ -988,7 +1014,7 @@
|
|
|
988
1014
|
.blog-hub .blog-hub__listing,
|
|
989
1015
|
.archive-hub .archive-hub__listing {
|
|
990
1016
|
margin-top: 0;
|
|
991
|
-
padding: 0.
|
|
1017
|
+
padding: 0.3rem 0.65rem 0.7rem;
|
|
992
1018
|
border: 1px solid color-mix(in srgb, var(--color-border) 84%, #c9d6ff 16%);
|
|
993
1019
|
border-top: 0;
|
|
994
1020
|
background: #fff;
|
|
@@ -1005,7 +1031,7 @@
|
|
|
1005
1031
|
|
|
1006
1032
|
.blog-hub .pagination-shell {
|
|
1007
1033
|
margin-top: 0;
|
|
1008
|
-
padding: 0
|
|
1034
|
+
padding: 0 0.65rem 0.75rem;
|
|
1009
1035
|
border: 1px solid color-mix(in srgb, var(--color-border) 84%, #c9d6ff 16%);
|
|
1010
1036
|
border-top: 0;
|
|
1011
1037
|
border-radius: 0 0 1rem 1rem;
|
|
@@ -1022,6 +1048,13 @@
|
|
|
1022
1048
|
margin-bottom: 0.5rem;
|
|
1023
1049
|
}
|
|
1024
1050
|
|
|
1051
|
+
.blog-hub .page-header__description,
|
|
1052
|
+
.archive-hub .page-header__description {
|
|
1053
|
+
max-width: none;
|
|
1054
|
+
margin-top: 0.6rem;
|
|
1055
|
+
font-size: 1.05rem;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1025
1058
|
.page-header-wrap {
|
|
1026
1059
|
padding: 0;
|
|
1027
1060
|
background: #fff;
|
|
@@ -1032,6 +1065,12 @@
|
|
|
1032
1065
|
background: transparent;
|
|
1033
1066
|
}
|
|
1034
1067
|
|
|
1068
|
+
.page-header-wrap--blog .page-header-wrap__container,
|
|
1069
|
+
.page-header-wrap--archive .page-header-wrap__container {
|
|
1070
|
+
width: 100%;
|
|
1071
|
+
margin-inline: 0;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1035
1074
|
.page-header {
|
|
1036
1075
|
margin-inline: auto;
|
|
1037
1076
|
margin-bottom: 2rem;
|
|
@@ -1048,9 +1087,7 @@
|
|
|
1048
1087
|
}
|
|
1049
1088
|
|
|
1050
1089
|
.page-header__layout {
|
|
1051
|
-
display:
|
|
1052
|
-
justify-content: space-between;
|
|
1053
|
-
gap: 2rem;
|
|
1090
|
+
display: block;
|
|
1054
1091
|
padding-inline: 1.25rem;
|
|
1055
1092
|
}
|
|
1056
1093
|
|
|
@@ -1060,10 +1097,12 @@
|
|
|
1060
1097
|
|
|
1061
1098
|
.page-header__title {
|
|
1062
1099
|
margin-bottom: 2rem;
|
|
1063
|
-
font-size:
|
|
1100
|
+
font-size: clamp(1.65rem, 10vw, 2.25rem);
|
|
1064
1101
|
font-weight: 700;
|
|
1065
|
-
line-height: 1;
|
|
1102
|
+
line-height: 1.1;
|
|
1066
1103
|
letter-spacing: -0.02em;
|
|
1104
|
+
text-wrap: balance;
|
|
1105
|
+
overflow-wrap: break-word;
|
|
1067
1106
|
}
|
|
1068
1107
|
|
|
1069
1108
|
.page-header__title--centered {
|
|
@@ -1400,8 +1439,8 @@
|
|
|
1400
1439
|
.content {
|
|
1401
1440
|
color: color-mix(in srgb, var(--color-text) 90%, #1e293b 10%);
|
|
1402
1441
|
font-family: "Source Serif 4", Georgia, Cambria, "Times New Roman", Times, serif;
|
|
1403
|
-
font-size: 1.
|
|
1404
|
-
line-height: 1.
|
|
1442
|
+
font-size: 1.12rem;
|
|
1443
|
+
line-height: 1.8;
|
|
1405
1444
|
|
|
1406
1445
|
p {
|
|
1407
1446
|
margin-bottom: 1.5rem;
|
|
@@ -1672,7 +1711,33 @@
|
|
|
1672
1711
|
}
|
|
1673
1712
|
}
|
|
1674
1713
|
|
|
1714
|
+
@media (min-width: 421px) {
|
|
1715
|
+
.page-header__title {
|
|
1716
|
+
font-size: clamp(1.85rem, 9vw, 2.8rem);
|
|
1717
|
+
line-height: 1.08;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
.post-card__title {
|
|
1721
|
+
font-size: 3rem;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
.post-page .post-title {
|
|
1725
|
+
font-size: 3rem;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
.page-article__title {
|
|
1729
|
+
font-size: clamp(1.55rem, 7.2vw, 2.25rem);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1675
1733
|
@media (min-width: 1024px) {
|
|
1734
|
+
.page-header-wrap--blog .page-header-wrap__container,
|
|
1735
|
+
.page-header-wrap--archive .page-header-wrap__container,
|
|
1736
|
+
.latest-posts__container {
|
|
1737
|
+
width: min(100% - 2rem, 1280px);
|
|
1738
|
+
margin-inline: auto;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1676
1741
|
.site-header__inner {
|
|
1677
1742
|
padding: 1.5rem 0;
|
|
1678
1743
|
}
|
|
@@ -1690,8 +1755,8 @@
|
|
|
1690
1755
|
display: none;
|
|
1691
1756
|
}
|
|
1692
1757
|
|
|
1693
|
-
.hero-
|
|
1694
|
-
|
|
1758
|
+
.hero-section {
|
|
1759
|
+
padding: 4rem 0;
|
|
1695
1760
|
}
|
|
1696
1761
|
|
|
1697
1762
|
.hero-description {
|
|
@@ -1699,6 +1764,12 @@
|
|
|
1699
1764
|
line-height: 2.5rem;
|
|
1700
1765
|
}
|
|
1701
1766
|
|
|
1767
|
+
.latest-posts__inner,
|
|
1768
|
+
.contributions__inner,
|
|
1769
|
+
.testimonials__inner {
|
|
1770
|
+
padding: 1.25rem;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1702
1773
|
.post-card {
|
|
1703
1774
|
flex-direction: row;
|
|
1704
1775
|
}
|
|
@@ -1711,136 +1782,108 @@
|
|
|
1711
1782
|
margin-bottom: 0;
|
|
1712
1783
|
}
|
|
1713
1784
|
|
|
1714
|
-
.post-
|
|
1715
|
-
font-size: 2.25rem;
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
.page-header__layout {
|
|
1719
|
-
padding-inline: 0;
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
.contributions__grid,
|
|
1723
|
-
.testimonials__grid {
|
|
1724
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
@media (max-width: 1023px) {
|
|
1729
|
-
.page-header-wrap--blog .page-header-wrap__container,
|
|
1730
|
-
.page-header-wrap--archive .page-header-wrap__container,
|
|
1731
|
-
.latest-posts__container {
|
|
1732
|
-
width: 100%;
|
|
1733
|
-
margin-inline: 0;
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
|
-
.hero-section {
|
|
1737
|
-
padding-top: 2.25rem;
|
|
1738
|
-
padding-bottom: 2.5rem;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
.page-header__title {
|
|
1785
|
+
.post-card__title {
|
|
1742
1786
|
font-size: 3rem;
|
|
1743
|
-
line-height: 1.
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
.content {
|
|
1747
|
-
font-size: 1.12rem;
|
|
1748
|
-
line-height: 1.8;
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
.page-header__layout {
|
|
1752
|
-
display: block;
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
.latest-posts__inner {
|
|
1756
|
-
padding: 0.75rem 0;
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
.latest-posts__list {
|
|
1760
|
-
width: min(100% - 2rem, 880px);
|
|
1787
|
+
line-height: 1.25;
|
|
1761
1788
|
}
|
|
1762
1789
|
|
|
1763
1790
|
.post-page .post-header {
|
|
1764
|
-
padding: 1.
|
|
1791
|
+
padding: 1.6rem 1.75rem 1.35rem;
|
|
1765
1792
|
}
|
|
1766
1793
|
|
|
1767
|
-
.post-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
padding-left: 0.95rem;
|
|
1771
|
-
padding-right: 0.95rem;
|
|
1794
|
+
.post-page .post-title {
|
|
1795
|
+
font-size: 3rem;
|
|
1796
|
+
line-height: 1.16;
|
|
1772
1797
|
}
|
|
1773
1798
|
|
|
1774
1799
|
.post-page .content-shell {
|
|
1775
|
-
padding:
|
|
1776
|
-
}
|
|
1777
|
-
|
|
1778
|
-
.post-page .post-header,
|
|
1779
|
-
.post-page .content-shell,
|
|
1780
|
-
.post-page .comments-shell__inner,
|
|
1781
|
-
.post-page .post-navigation-shell__inner,
|
|
1782
|
-
.post-page .related-posts-shell__inner {
|
|
1783
|
-
width: min(100% - 2rem, 880px);
|
|
1800
|
+
padding: 2rem 2rem 1.8rem;
|
|
1784
1801
|
}
|
|
1785
1802
|
|
|
1786
1803
|
.post-page .comments-shell__inner,
|
|
1787
1804
|
.post-page .post-navigation-shell__inner,
|
|
1788
1805
|
.post-page .related-posts-shell__inner {
|
|
1789
|
-
padding:
|
|
1806
|
+
padding: 1.1rem;
|
|
1790
1807
|
}
|
|
1791
1808
|
|
|
1792
1809
|
.post-page .comments-shell__inner {
|
|
1793
|
-
padding:
|
|
1810
|
+
padding: 2rem 2rem 1.8rem;
|
|
1794
1811
|
}
|
|
1795
1812
|
|
|
1796
1813
|
.comments-shell__heading {
|
|
1797
|
-
font-size: 1.
|
|
1814
|
+
font-size: 1.3rem;
|
|
1798
1815
|
}
|
|
1799
1816
|
|
|
1800
1817
|
.comments-collapsible__icon-wrap,
|
|
1801
1818
|
.series-collapsible__icon-wrap {
|
|
1802
|
-
width:
|
|
1803
|
-
height:
|
|
1819
|
+
width: 2rem;
|
|
1820
|
+
height: 2rem;
|
|
1804
1821
|
}
|
|
1805
1822
|
|
|
1806
1823
|
.post-page .post-navigation {
|
|
1807
|
-
grid-template-columns: 1fr;
|
|
1824
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1808
1825
|
}
|
|
1809
1826
|
|
|
1810
1827
|
.page-article__header {
|
|
1811
|
-
padding: 1.
|
|
1828
|
+
padding: 1.75rem 1.75rem 1.25rem;
|
|
1812
1829
|
}
|
|
1813
1830
|
|
|
1814
1831
|
.page-article__header-layout {
|
|
1815
|
-
display:
|
|
1832
|
+
display: flex;
|
|
1833
|
+
justify-content: space-between;
|
|
1834
|
+
gap: 2rem;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
.page-article__title {
|
|
1838
|
+
font-size: clamp(1.9rem, 4.9vw, 3.2rem);
|
|
1839
|
+
line-height: 1.16;
|
|
1816
1840
|
}
|
|
1817
1841
|
|
|
1818
1842
|
.page-article .content-shell {
|
|
1819
|
-
padding:
|
|
1843
|
+
padding: 2rem 2rem 1.8rem;
|
|
1820
1844
|
}
|
|
1821
1845
|
|
|
1822
1846
|
.blog-hub .page-header--blog,
|
|
1823
1847
|
.archive-hub .page-header--archive-hub {
|
|
1824
|
-
|
|
1825
|
-
padding: 1.05rem 1rem 0.8rem;
|
|
1848
|
+
padding: 2rem 1.75rem 1.15rem;
|
|
1826
1849
|
}
|
|
1827
1850
|
|
|
1828
1851
|
.blog-hub .blog-hub__listing,
|
|
1829
1852
|
.archive-hub .archive-hub__listing {
|
|
1830
|
-
|
|
1831
|
-
padding: 0.3rem 0.65rem 0.7rem;
|
|
1853
|
+
padding: 0.65rem 1.35rem 1.1rem;
|
|
1832
1854
|
}
|
|
1833
1855
|
|
|
1834
1856
|
.blog-hub .pagination-shell {
|
|
1835
|
-
|
|
1836
|
-
padding: 0 0.65rem 0.75rem;
|
|
1857
|
+
padding: 0 1.35rem 1.2rem;
|
|
1837
1858
|
}
|
|
1838
1859
|
|
|
1839
1860
|
.blog-hub .page-header__description,
|
|
1840
1861
|
.archive-hub .page-header__description {
|
|
1841
|
-
max-width:
|
|
1842
|
-
margin-top: 0
|
|
1843
|
-
font-size: 1.
|
|
1862
|
+
max-width: 640px;
|
|
1863
|
+
margin-top: 0;
|
|
1864
|
+
font-size: 1.25rem;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
.page-header__layout {
|
|
1868
|
+
display: flex;
|
|
1869
|
+
justify-content: space-between;
|
|
1870
|
+
gap: 2rem;
|
|
1871
|
+
padding-inline: 0;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
.page-header__title {
|
|
1875
|
+
font-size: clamp(2.05rem, 7vw, 4.2rem);
|
|
1876
|
+
line-height: 1.05;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
.contributions__grid,
|
|
1880
|
+
.testimonials__grid {
|
|
1881
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
.content {
|
|
1885
|
+
font-size: 1.23rem;
|
|
1886
|
+
line-height: 1.9;
|
|
1844
1887
|
}
|
|
1845
1888
|
}
|
|
1846
1889
|
|
data/exe/imdhemy-image
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "optparse"
|
|
5
|
+
require "pathname"
|
|
6
|
+
require "shellwords"
|
|
7
|
+
require "tmpdir"
|
|
8
|
+
|
|
9
|
+
module Imdhemy
|
|
10
|
+
module Jekyll
|
|
11
|
+
module Theme
|
|
12
|
+
class ImageCLI
|
|
13
|
+
SUPPORTED_EXTENSIONS = %w[.jpg .jpeg .png].freeze
|
|
14
|
+
|
|
15
|
+
Result = Struct.new(:path, :status, :original_size, :optimized_size, :message, keyword_init: true)
|
|
16
|
+
|
|
17
|
+
def self.run(argv)
|
|
18
|
+
new(argv).run
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(argv)
|
|
22
|
+
@argv = argv.dup
|
|
23
|
+
@options = {
|
|
24
|
+
recursive: false,
|
|
25
|
+
dry_run: false
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def run
|
|
30
|
+
parser.parse!(@argv)
|
|
31
|
+
|
|
32
|
+
if @argv.empty?
|
|
33
|
+
warn parser.to_s
|
|
34
|
+
return 1
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
paths = expand_inputs(@argv)
|
|
38
|
+
if paths.empty?
|
|
39
|
+
warn "No supported image files found."
|
|
40
|
+
return 1
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
results = paths.map { |path| optimize_path(path) }
|
|
44
|
+
print_results(results)
|
|
45
|
+
|
|
46
|
+
results.any? { |result| result.status == :error } ? 1 : 0
|
|
47
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
|
48
|
+
warn e.message
|
|
49
|
+
warn parser.to_s
|
|
50
|
+
1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def parser
|
|
56
|
+
@parser ||= OptionParser.new do |opts|
|
|
57
|
+
opts.banner = "Usage: imdhemy-image [options] PATH [PATH ...]"
|
|
58
|
+
|
|
59
|
+
opts.on("-r", "--recursive", "Scan directories recursively") do
|
|
60
|
+
@options[:recursive] = true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
opts.on("-n", "--dry-run", "Print what would change without writing files") do
|
|
64
|
+
@options[:dry_run] = true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
opts.on("-h", "--help", "Show this help") do
|
|
68
|
+
puts opts
|
|
69
|
+
exit 0
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def expand_inputs(inputs)
|
|
75
|
+
files = inputs.flat_map do |input|
|
|
76
|
+
path = Pathname(input).expand_path
|
|
77
|
+
if path.file?
|
|
78
|
+
supported_file?(path) ? [path] : []
|
|
79
|
+
elsif path.directory?
|
|
80
|
+
collect_directory(path)
|
|
81
|
+
else
|
|
82
|
+
warn "Skipping missing path: #{path}"
|
|
83
|
+
[]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
files.uniq
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def collect_directory(directory)
|
|
91
|
+
pattern = @options[:recursive] ? "**/*" : "*"
|
|
92
|
+
directory.glob(pattern).select { |path| path.file? && supported_file?(path) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def supported_file?(path)
|
|
96
|
+
SUPPORTED_EXTENSIONS.include?(path.extname.downcase)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def optimize_path(path)
|
|
100
|
+
optimizer = optimizer_for(path)
|
|
101
|
+
return Result.new(path: path, status: :skipped, message: "No optimizer available for #{path.extname.downcase}") unless optimizer
|
|
102
|
+
|
|
103
|
+
original_size = path.size
|
|
104
|
+
if @options[:dry_run]
|
|
105
|
+
return Result.new(
|
|
106
|
+
path: path,
|
|
107
|
+
status: :dry_run,
|
|
108
|
+
original_size: original_size,
|
|
109
|
+
optimized_size: original_size,
|
|
110
|
+
message: "Would run #{optimizer.label}"
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
optimized = optimizer.optimize(path)
|
|
115
|
+
return Result.new(path: path, status: :skipped, original_size: original_size, optimized_size: original_size, message: optimized[:message]) unless optimized[:changed]
|
|
116
|
+
|
|
117
|
+
Result.new(
|
|
118
|
+
path: path,
|
|
119
|
+
status: :optimized,
|
|
120
|
+
original_size: original_size,
|
|
121
|
+
optimized_size: path.size,
|
|
122
|
+
message: optimized[:message]
|
|
123
|
+
)
|
|
124
|
+
rescue StandardError => e
|
|
125
|
+
Result.new(path: path, status: :error, original_size: path.exist? ? path.size : nil, message: e.message)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def optimizer_for(path)
|
|
129
|
+
case path.extname.downcase
|
|
130
|
+
when ".jpg", ".jpeg"
|
|
131
|
+
jpeg_optimizer
|
|
132
|
+
when ".png"
|
|
133
|
+
png_optimizer
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def jpeg_optimizer
|
|
138
|
+
@jpeg_optimizer ||= begin
|
|
139
|
+
if command_available?("jpegoptim")
|
|
140
|
+
ExternalOptimizer.new(
|
|
141
|
+
label: "jpegoptim",
|
|
142
|
+
command_builder: lambda { |path, temp_path|
|
|
143
|
+
["jpegoptim", "--strip-all", "--all-progressive", "--dest=#{temp_path.dirname}", path.to_s]
|
|
144
|
+
},
|
|
145
|
+
temp_output_builder: lambda { |path, temp_dir|
|
|
146
|
+
temp_dir.join(path.basename.to_s)
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
elsif command_available?("sips")
|
|
150
|
+
SipsJpegOptimizer.new
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def png_optimizer
|
|
156
|
+
@png_optimizer ||= begin
|
|
157
|
+
if command_available?("oxipng")
|
|
158
|
+
ExternalOptimizer.new(
|
|
159
|
+
label: "oxipng",
|
|
160
|
+
command_builder: lambda { |path, temp_path|
|
|
161
|
+
["oxipng", "--strip", "safe", "--opt", "2", "--out", temp_path.to_s, path.to_s]
|
|
162
|
+
},
|
|
163
|
+
temp_output_builder: ->(_path, temp_dir) { temp_dir.join("optimized.png") }
|
|
164
|
+
)
|
|
165
|
+
elsif command_available?("pngcrush")
|
|
166
|
+
ExternalOptimizer.new(
|
|
167
|
+
label: "pngcrush",
|
|
168
|
+
command_builder: lambda { |path, temp_path|
|
|
169
|
+
["pngcrush", "-q", "-reduce", "-brute", path.to_s, temp_path.to_s]
|
|
170
|
+
},
|
|
171
|
+
temp_output_builder: ->(_path, temp_dir) { temp_dir.join("optimized.png") }
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def command_available?(command)
|
|
178
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |entry|
|
|
179
|
+
executable = File.join(entry, command)
|
|
180
|
+
File.file?(executable) && File.executable?(executable)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def print_results(results)
|
|
185
|
+
results.each do |result|
|
|
186
|
+
case result.status
|
|
187
|
+
when :optimized
|
|
188
|
+
saved = result.original_size - result.optimized_size
|
|
189
|
+
percent = percentage(saved, result.original_size)
|
|
190
|
+
puts "optimized #{result.path}: #{human_size(result.original_size)} -> #{human_size(result.optimized_size)} (saved #{percent}%) via #{result.message}"
|
|
191
|
+
when :dry_run
|
|
192
|
+
puts "dry-run #{result.path}: #{result.message}"
|
|
193
|
+
when :skipped
|
|
194
|
+
puts "skipped #{result.path}: #{result.message}"
|
|
195
|
+
when :error
|
|
196
|
+
puts "error #{result.path}: #{result.message}"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def percentage(saved, original)
|
|
202
|
+
return 0 if original.to_i <= 0
|
|
203
|
+
|
|
204
|
+
((saved.to_f / original) * 100).round(1)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def human_size(bytes)
|
|
208
|
+
units = %w[B KB MB GB].freeze
|
|
209
|
+
value = bytes.to_f
|
|
210
|
+
unit = units.shift
|
|
211
|
+
while value >= 1024 && !units.empty?
|
|
212
|
+
value /= 1024
|
|
213
|
+
unit = units.shift
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
format(value >= 10 || unit == "B" ? "%.0f %s" : "%.1f %s", value, unit)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
class ExternalOptimizer
|
|
221
|
+
def initialize(label:, command_builder:, temp_output_builder:)
|
|
222
|
+
@label = label
|
|
223
|
+
@command_builder = command_builder
|
|
224
|
+
@temp_output_builder = temp_output_builder
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
attr_reader :label
|
|
228
|
+
|
|
229
|
+
def optimize(path)
|
|
230
|
+
Dir.mktmpdir("imdhemy-image") do |tmpdir|
|
|
231
|
+
temp_dir = Pathname(tmpdir)
|
|
232
|
+
temp_output = @temp_output_builder.call(path, temp_dir)
|
|
233
|
+
command = @command_builder.call(path, temp_output)
|
|
234
|
+
|
|
235
|
+
success = system(*command, out: File::NULL, err: File::NULL)
|
|
236
|
+
raise "Optimizer failed: #{Shellwords.join(command)}" unless success
|
|
237
|
+
|
|
238
|
+
if !temp_output.exist? || temp_output.size >= path.size
|
|
239
|
+
return { changed: false, message: "#{label} ran but produced no smaller output" }
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
FileUtils.cp(temp_output, path)
|
|
243
|
+
{ changed: true, message: label }
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
class SipsJpegOptimizer
|
|
249
|
+
attr_reader :label
|
|
250
|
+
|
|
251
|
+
def initialize
|
|
252
|
+
@label = "sips"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def optimize(path)
|
|
256
|
+
Dir.mktmpdir("imdhemy-image") do |tmpdir|
|
|
257
|
+
temp_output = Pathname(tmpdir).join(path.basename.to_s)
|
|
258
|
+
success = system(
|
|
259
|
+
"sips",
|
|
260
|
+
"-s", "format", "jpeg",
|
|
261
|
+
"-s", "formatOptions", "best",
|
|
262
|
+
path.to_s,
|
|
263
|
+
"--out", temp_output.to_s,
|
|
264
|
+
out: File::NULL,
|
|
265
|
+
err: File::NULL
|
|
266
|
+
)
|
|
267
|
+
raise "Optimizer failed: sips" unless success
|
|
268
|
+
|
|
269
|
+
if !temp_output.exist? || temp_output.size >= path.size
|
|
270
|
+
return { changed: false, message: "#{label} ran but produced no smaller output" }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
FileUtils.cp(temp_output, path)
|
|
274
|
+
{ changed: true, message: label }
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: imdhemy-jekyll-theme
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mohamad Eldhemy
|
|
8
8
|
autorequire:
|
|
9
|
-
bindir:
|
|
9
|
+
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -69,7 +69,8 @@ dependencies:
|
|
|
69
69
|
description:
|
|
70
70
|
email:
|
|
71
71
|
- imdhemy@gmail.com
|
|
72
|
-
executables:
|
|
72
|
+
executables:
|
|
73
|
+
- imdhemy-image
|
|
73
74
|
extensions: []
|
|
74
75
|
extra_rdoc_files: []
|
|
75
76
|
files:
|
|
@@ -116,6 +117,8 @@ files:
|
|
|
116
117
|
- assets/css/main.scss
|
|
117
118
|
- assets/images/social.png
|
|
118
119
|
- assets/js/dist/main.js
|
|
120
|
+
- exe/imdhemy-image
|
|
121
|
+
- lib/imdhemy/jekyll/theme/image_cli.rb
|
|
119
122
|
homepage: https://imdhemy.com
|
|
120
123
|
licenses:
|
|
121
124
|
- MIT
|