Skip to content

Commit fff31a6

Browse files
authored
feat: add social sharing support and clean up assets (#3)
- Add Open Graph & Twitter Card meta tags for rich link previews - Create 1200×630 og-image.jpg for social sharing (Facebook, LinkedIn, WhatsApp, X) - Regenerate all hero images from hero_selections/ with optimized compression - Move icon sources to gallery/icons/ and update Makefile - Delete orphaned assets (banner-*, single_article_hero_*, unused logos) - Add og-image generation to 📷
1 parent 116e5ab commit fff31a6

File tree

66 files changed

+493
-245
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+493
-245
lines changed

Makefile

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Digital Bazaar — Makefile
22
# Simplifies common development tasks
33

4-
.PHONY: help install update assets images images-hero clean serve serve-drafts build setup-hooks prod-build
4+
.PHONY: help install update assets images hero clean serve serve-drafts build setup-hooks prod-build
55

66
# Homebrew Ruby paths (macOS)
77
RUBY_BIN := /opt/homebrew/opt/ruby/bin
@@ -27,7 +27,7 @@ help: ## Show this help message
2727
@echo " $(CYAN)update$(RESET) Update all gem dependencies"
2828
@echo " $(CYAN)assets$(RESET) Download fonts + mermaid.js"
2929
@echo " $(CYAN)images$(RESET) Generate logos, favicons, icons"
30-
@echo " $(CYAN)images-hero$(RESET) Show hero banner generation guide"
30+
@echo " $(CYAN)hero$(RESET) Generate hero banners (make hero SRC=x OUT=y)"
3131
@echo " $(CYAN)clean$(RESET) Remove generated site and caches"
3232
@echo ""
3333
@echo "$(YELLOW)Development:$(RESET)"
@@ -58,39 +58,79 @@ images: ## Generate all site images from gallery sources
5858
@echo "📷 Generating site images from assets/gallery/..."
5959
@echo ""
6060
@echo " → Logo sizes..."
61-
sips -z 128 128 assets/gallery/bazaar_2025.jpg --out assets/img/logo-128.jpg
62-
sips -z 256 256 assets/gallery/bazaar_2025.jpg --out assets/img/logo-256.jpg
63-
sips -z 512 512 assets/gallery/bazaar_2025.jpg --out assets/img/logo-512.jpg
61+
sips -z 128 128 assets/gallery/icons/bazaar_2025.jpg --out assets/img/logo-128.jpg
62+
sips -z 256 256 assets/gallery/icons/bazaar_2025.jpg --out assets/img/logo-256.jpg
63+
sips -z 512 512 assets/gallery/icons/bazaar_2025.jpg --out assets/img/logo-512.jpg
6464
@echo " → Favicons and icons..."
65-
sips -z 16 16 assets/gallery/bazaar_2025.png --out assets/img/favicon-16.png
66-
sips -z 32 32 assets/gallery/bazaar_2025.png --out assets/img/favicon-32.png
67-
sips -z 180 180 assets/gallery/bazaar_2025.png --out assets/img/apple-touch-icon.png
68-
sips -z 192 192 assets/gallery/bazaar_2025.png --out assets/img/icon-192.png
65+
sips -z 16 16 assets/gallery/icons/bazaar_2025.png --out assets/img/favicon-16.png
66+
sips -z 32 32 assets/gallery/icons/bazaar_2025.png --out assets/img/favicon-32.png
67+
sips -z 180 180 assets/gallery/icons/bazaar_2025.png --out assets/img/apple-touch-icon.png
68+
sips -z 192 192 assets/gallery/icons/bazaar_2025.png --out assets/img/icon-192.png
6969
@echo " → Converting to WebP..."
7070
cwebp -q 80 assets/img/logo-128.jpg -o assets/img/logo-128.webp
7171
cwebp -q 80 assets/img/logo-256.jpg -o assets/img/logo-256.webp
7272
cwebp -q 90 assets/img/logo-512.jpg -o assets/img/logo-512-hq.webp
73+
@echo " → Social share image (OG)..."
74+
sips -Z 1200 assets/gallery/hero_selections/hero_home_light.png --out /tmp/og_temp.png
75+
sips -c 630 1200 /tmp/og_temp.png --out /tmp/og_cropped.png
76+
sips -s format jpeg -s formatOptions 80 /tmp/og_cropped.png --out assets/img/og-image.jpg
77+
rm /tmp/og_temp.png /tmp/og_cropped.png
7378
@echo ""
7479
@echo "✅ Site images generated in assets/img/"
7580

76-
images-hero: ## Show hero banner generation guide
77-
@echo "📷 Hero Banner Generator"
81+
hero: ## Generate responsive hero images from a gallery source
82+
@# ═══════════════════════════════════════════════════════════════════════════
83+
@# HERO IMAGE GENERATOR
84+
@# ═══════════════════════════════════════════════════════════════════════════
85+
@#
86+
@# Usage:
87+
@# make hero SRC=bazaar_conversations_1.png OUT=articles_hero_banner
88+
@#
89+
@# This generates 3 responsive WebP images with optimized compression:
90+
@# - assets/img/$(OUT)-1200.webp (desktop)
91+
@# - assets/img/$(OUT)-800.webp (tablet)
92+
@# - assets/img/$(OUT)-600.webp (mobile)
93+
@#
94+
@# Compression settings: -q 80 -m 6 -sharp_yuv
95+
@# - q 80: High quality, good compression balance
96+
@# - m 6: Maximum compression effort
97+
@# - sharp_yuv: Better detail preservation
98+
@#
99+
@# Examples:
100+
@# make hero SRC=bazaar_conversations_dark_5.png OUT=articles_hero_banner_dark
101+
@# make hero SRC=bazaar_archives_2.png OUT=archive_hero_banner
102+
@# make hero SRC=bazaar_guidelines_2.png OUT=rules_hero_banner
103+
@#
104+
@# ═══════════════════════════════════════════════════════════════════════════
105+
ifndef SRC
106+
@echo "❌ Error: SRC is required"
78107
@echo ""
79-
@echo "Run these commands to generate responsive hero images:"
108+
@echo "Usage: make hero SRC=<gallery_image.png> OUT=<output_prefix>"
80109
@echo ""
81-
@echo " # Replace SOURCE.png with your gallery image name"
82-
@echo " # Replace PREFIX with: home_hero, articles_hero, or archive_hero"
110+
@echo "Example: make hero SRC=bazaar_conversations_1.png OUT=articles_hero_banner"
111+
@exit 1
112+
endif
113+
ifndef OUT
114+
@echo "❌ Error: OUT is required"
83115
@echo ""
84-
@echo " sips -Z 1200 assets/gallery/SOURCE.png --out /tmp/hero.png"
85-
@echo " cwebp -q 82 /tmp/hero.png -o assets/img/PREFIX_banner-1200.webp"
116+
@echo "Usage: make hero SRC=<gallery_image.png> OUT=<output_prefix>"
86117
@echo ""
87-
@echo " sips -Z 800 assets/gallery/SOURCE.png --out /tmp/hero.png"
88-
@echo " cwebp -q 82 /tmp/hero.png -o assets/img/PREFIX_banner-800.webp"
118+
@echo "Example: make hero SRC=bazaar_conversations_1.png OUT=articles_hero_banner"
119+
@exit 1
120+
endif
121+
@echo "📷 Generating hero images from $(SRC)..."
122+
@echo " Output: assets/img/$(OUT)-{1200,800,600}.webp"
89123
@echo ""
90-
@echo " sips -Z 600 assets/gallery/SOURCE.png --out /tmp/hero.png"
91-
@echo " cwebp -q 82 /tmp/hero.png -o assets/img/PREFIX_banner-600.webp"
124+
@sips -Z 1200 assets/gallery/$(SRC) --out /tmp/hero_1200.png
125+
@cwebp -q 80 -m 6 -sharp_yuv /tmp/hero_1200.png -o assets/img/$(OUT)-1200.webp
126+
@sips -Z 800 assets/gallery/$(SRC) --out /tmp/hero_800.png
127+
@cwebp -q 80 -m 6 -sharp_yuv /tmp/hero_800.png -o assets/img/$(OUT)-800.webp
128+
@sips -Z 600 assets/gallery/$(SRC) --out /tmp/hero_600.png
129+
@cwebp -q 80 -m 6 -sharp_yuv /tmp/hero_600.png -o assets/img/$(OUT)-600.webp
130+
@rm -f /tmp/hero_1200.png /tmp/hero_800.png /tmp/hero_600.png
92131
@echo ""
93-
@echo " rm /tmp/hero.png"
132+
@echo "✅ Done! Generated:"
133+
@ls -lh assets/img/$(OUT)-*.webp
94134

95135
clean: ## Remove generated site and caches
96136
$(BUNDLE) exec jekyll clean

_data/strings_en.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ archive:
113113
# Articles Section
114114
articles:
115115
kicker: "Articles & field notes"
116-
title: "Recent writing from the Digital Bazaar"
116+
title: "Recent writings from the Digital Bazaar"
117117
subtitle: "Essays, reflections and notes from people in the Bazaar – on engineering, product, management, markets, careers and life. These are the latest pieces; older work lives in the archive."
118118
hero_alt: "Groups of people and robots talking in the Digital Bazaar"
119119
section_title: "Latest essays"

_includes/footer.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@
1212
</div>
1313

1414
<div>
15-
<div class="footer-title">{{ strings.footer.sections_title | default: 'Sections' }}</div>
15+
<div class="footer-title" data-i18n="footer_sections" data-ar="{{ strings_ar.footer.sections_title }}" data-en="{{ strings_en.footer.sections_title | default: 'Sections' }}">{{ strings.footer.sections_title | default: 'Sections' }}</div>
1616
<div class="footer-links">
17-
<a href="/{{ lang }}/">{{ strings.nav.home }}</a>
18-
<a href="/{{ lang }}/articles/">{{ strings.footer.links.articles | default: strings.nav.articles }}</a>
19-
<a href="/{{ lang }}/archive/">{{ strings.footer.links.archive | default: strings.nav.archive }}</a>
20-
<a href="/{{ lang }}/rules/">{{ strings.footer.links.rules | default: strings.nav.rules }}</a>
17+
<a href="/{{ lang }}/" data-i18n="nav_home" data-ar="{{ strings_ar.nav.home }}" data-en="{{ strings_en.nav.home }}">{{ strings.nav.home }}</a>
18+
<a href="/{{ lang }}/articles/" data-i18n="footer_articles" data-ar="{{ strings_ar.footer.links.articles }}" data-en="{{ strings_en.footer.links.articles | default: 'Articles & field notes' }}">{{ strings.footer.links.articles | default: strings.nav.articles }}</a>
19+
<a href="/{{ lang }}/archive/" data-i18n="footer_archive" data-ar="{{ strings_ar.footer.links.archive }}" data-en="{{ strings_en.footer.links.archive | default: 'Archive' }}">{{ strings.footer.links.archive | default: strings.nav.archive }}</a>
20+
<a href="/{{ lang }}/rules/" data-i18n="footer_rules" data-ar="{{ strings_ar.footer.links.rules }}" data-en="{{ strings_en.footer.links.rules | default: 'Community rules' }}">{{ strings.footer.links.rules | default: strings.nav.rules }}</a>
2121
</div>
2222
</div>
2323

2424
<div>
25-
<div class="footer-title">{{ strings.footer.community_title | default: 'Community' }}</div>
25+
<div class="footer-title" data-i18n="footer_community" data-ar="{{ strings_ar.footer.community_title }}" data-en="{{ strings_en.footer.community_title | default: 'Community' }}">{{ strings.footer.community_title | default: 'Community' }}</div>
2626
<div class="footer-links">
27-
<a href="{{ site.social.whatsapp }}" target="_blank" rel="noopener">{{ strings.footer.links.join_whatsapp | default: 'Join WhatsApp ↗' }}</a>
28-
<a href="{{ site.social.github }}" target="_blank" rel="noopener">{{ strings.footer.links.visit_github | default: 'Visit GitHub org ↗' }}</a>
27+
<a href="{{ site.social.whatsapp }}" target="_blank" rel="noopener" data-i18n="footer_whatsapp" data-ar="{{ strings_ar.footer.links.join_whatsapp }}" data-en="{{ strings_en.footer.links.join_whatsapp | default: 'Join us on WhatsApp ↗' }}">{{ strings.footer.links.join_whatsapp | default: 'Join WhatsApp ↗' }}</a>
28+
<a href="{{ site.social.github }}" target="_blank" rel="noopener" data-i18n="footer_github" data-ar="{{ strings_ar.footer.links.visit_github }}" data-en="{{ strings_en.footer.links.visit_github | default: 'Visit us on GitHub ↗' }}">{{ strings.footer.links.visit_github | default: 'Visit GitHub org ↗' }}</a>
2929
</div>
3030
</div>
3131
</footer>

_includes/language-toggle.html

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
{%- endif -%}
2525
{%- endif -%}
2626

27-
<div class="lang-toggle" data-is-article="{{ is_article }}">
28-
<a href="{{ ar_url }}" class="{% if lang == 'ar' %}active{% endif %}" data-lang="ar">AR</a>
29-
<span>|</span>
30-
<a href="{{ en_url }}" class="{% if lang == 'en' %}active{% endif %}" data-lang="en">EN</a>
27+
<div class="lang-theme-toggle" data-is-article="{{ is_article }}">
28+
<a href="{{ ar_url }}" class="lang-btn {% if lang == 'ar' %}active{% endif %}" data-lang="ar">عربي</a>
29+
<span class="separator">|</span>
30+
<a href="{{ en_url }}" class="lang-btn {% if lang == 'en' %}active{% endif %}" data-lang="en">English</a>
31+
<span class="separator">|</span>
32+
<button class="theme-btn" id="themeToggle" aria-label="Toggle dark mode" title="Toggle dark mode">🌑</button>
3133
</div>

_layouts/archive.html

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
{%- assign strings = site.data[strings_key] -%}
77
{%- assign categories = site.data.categories -%}
88

9-
<!-- Archive hero with banner -->
9+
<!-- Archive hero with banner (light/dark variants) -->
1010
<section class="hero">
11-
<picture>
11+
<!-- Light mode hero image -->
12+
<picture class="hero-image-light">
1213
<source media="(max-width: 600px)" srcset="{{ '/assets/img/archive_hero_banner-600.webp' | relative_url }}" type="image/webp">
1314
<source media="(max-width: 900px)" srcset="{{ '/assets/img/archive_hero_banner-800.webp' | relative_url }}" type="image/webp">
1415
<source srcset="{{ '/assets/img/archive_hero_banner-1200.webp' | relative_url }}" type="image/webp">
@@ -22,6 +23,20 @@
2223
fetchpriority="high"
2324
>
2425
</picture>
26+
<!-- Dark mode hero image -->
27+
<picture class="hero-image-dark">
28+
<source media="(max-width: 600px)" srcset="{{ '/assets/img/archive_hero_banner_dark-600.webp' | relative_url }}" type="image/webp">
29+
<source media="(max-width: 900px)" srcset="{{ '/assets/img/archive_hero_banner_dark-800.webp' | relative_url }}" type="image/webp">
30+
<source srcset="{{ '/assets/img/archive_hero_banner_dark-1200.webp' | relative_url }}" type="image/webp">
31+
<img
32+
src="{{ '/assets/img/archive_hero_banner_dark-1200.webp' | relative_url }}"
33+
alt="{{ strings.archive.hero_alt | default: 'Ancient archive shelves of scrolls mixed with glowing digital artifacts' }}"
34+
class="hero-image archive-hero-image"
35+
width="1200"
36+
height="400"
37+
loading="lazy"
38+
>
39+
</picture>
2540
<div class="hero-text">
2641
<div class="hero-kicker">{{ strings.archive.kicker | default: 'Full archive' }}</div>
2742
<h1 class="hero-title">{{ strings.archive.title | default: 'The shelves of the Digital Bazaar' }}</h1>

_layouts/default.html

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@
88
{%- assign strings = site.data[strings_key] -%}
99
<html lang="{{ lang }}" dir="{{ text_direction }}">
1010
<head>
11+
<!-- Prevent flash of wrong theme -->
12+
<script>
13+
(function() {
14+
var theme = localStorage.getItem('theme');
15+
if (theme === 'dark') {
16+
document.documentElement.setAttribute('data-theme', 'dark');
17+
}
18+
})();
19+
</script>
1120
<meta charset="utf-8">
1221
<meta name="viewport" content="width=device-width, initial-scale=1">
1322

@@ -19,11 +28,23 @@
1928

2029
<meta name="description" content="{{ page.excerpt | default: site.description }}">
2130

22-
<!-- Open Graph -->
31+
<!-- Open Graph (Facebook, WhatsApp, LinkedIn, Telegram) -->
2332
<meta property="og:title" content="{{ page.title | default: strings.site_title }}">
2433
<meta property="og:description" content="{{ page.excerpt | default: strings.site_tagline }}">
25-
<meta property="og:type" content="website">
34+
<meta property="og:type" content="{% if page.layout == 'article' %}article{% else %}website{% endif %}">
2635
<meta property="og:locale" content="{{ lang }}">
36+
<meta property="og:url" content="{{ page.url | absolute_url }}">
37+
<meta property="og:site_name" content="{{ strings.site_title }}">
38+
<meta property="og:image" content="{{ '/assets/img/og-image.jpg' | absolute_url }}">
39+
<meta property="og:image:width" content="1200">
40+
<meta property="og:image:height" content="630">
41+
<meta property="og:image:alt" content="{{ strings.site_title }}">
42+
43+
<!-- Twitter Card (X) - Large Image -->
44+
<meta name="twitter:card" content="summary_large_image">
45+
<meta name="twitter:title" content="{{ page.title | default: strings.site_title }}">
46+
<meta name="twitter:description" content="{{ page.excerpt | default: strings.site_tagline }}">
47+
<meta name="twitter:image" content="{{ '/assets/img/og-image.jpg' | absolute_url }}">
2748

2849
<!-- Theme Color -->
2950
<meta name="theme-color" content="#B34E36">
@@ -102,7 +123,7 @@
102123
}
103124

104125
// Update language toggle active state
105-
document.querySelectorAll('.lang-toggle a').forEach(item => {
126+
document.querySelectorAll('.lang-theme-toggle .lang-btn').forEach(item => {
106127
const itemLang = item.dataset.lang;
107128
if (itemLang === uiLang) {
108129
item.classList.add('active');
@@ -120,6 +141,31 @@
120141
const lang = params.get('lang') || el.dataset.lang || 'en';
121142
el.textContent = lang === 'ar' ? toArabicNumerals(year) : year;
122143
});
144+
145+
// Theme toggle (dark/light mode)
146+
(function() {
147+
var toggle = document.getElementById('themeToggle');
148+
if (!toggle) return;
149+
150+
var html = document.documentElement;
151+
var isDark = html.getAttribute('data-theme') === 'dark';
152+
153+
// Set initial icon (🌑 = dark moon for light mode, ☀️ = sun for dark mode)
154+
toggle.textContent = isDark ? '☀️' : '🌑';
155+
156+
toggle.addEventListener('click', function() {
157+
isDark = !isDark;
158+
if (isDark) {
159+
html.setAttribute('data-theme', 'dark');
160+
localStorage.setItem('theme', 'dark');
161+
toggle.textContent = '☀️';
162+
} else {
163+
html.removeAttribute('data-theme');
164+
localStorage.setItem('theme', 'light');
165+
toggle.textContent = '🌑';
166+
}
167+
});
168+
})();
123169
</script>
124170

125171
<!-- Mermaid Diagrams (loaded only when needed) -->

_layouts/rules.html

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,47 @@
55
{%- assign strings_key = 'strings_' | append: lang -%}
66
{%- assign strings = site.data[strings_key] -%}
77

8-
<header class="rules-header">
9-
<div class="kicker">{{ strings.rules_page.kicker }}</div>
10-
<h1>{{ strings.rules_page.title }}</h1>
11-
<p class="dek">{{ strings.rules_page.dek }}</p>
8+
<!-- Rules hero with banner (light/dark variants) -->
9+
<section class="hero">
10+
<!-- Light mode hero image -->
11+
<picture class="hero-image-light">
12+
<source media="(max-width: 600px)" srcset="{{ '/assets/img/rules_hero_banner-600.webp' | relative_url }}" type="image/webp">
13+
<source media="(max-width: 900px)" srcset="{{ '/assets/img/rules_hero_banner-800.webp' | relative_url }}" type="image/webp">
14+
<source srcset="{{ '/assets/img/rules_hero_banner-1200.webp' | relative_url }}" type="image/webp">
15+
<img
16+
src="{{ '/assets/img/rules_hero_banner-1200.webp' | relative_url }}"
17+
alt="{{ strings.rules_page.hero_alt | default: 'Ancient scrolls with community guidelines in the Digital Bazaar' }}"
18+
class="hero-image rules-hero-image"
19+
width="1200"
20+
height="400"
21+
loading="eager"
22+
fetchpriority="high"
23+
>
24+
</picture>
25+
<!-- Dark mode hero image -->
26+
<picture class="hero-image-dark">
27+
<source media="(max-width: 600px)" srcset="{{ '/assets/img/rules_hero_banner_dark-600.webp' | relative_url }}" type="image/webp">
28+
<source media="(max-width: 900px)" srcset="{{ '/assets/img/rules_hero_banner_dark-800.webp' | relative_url }}" type="image/webp">
29+
<source srcset="{{ '/assets/img/rules_hero_banner_dark-1200.webp' | relative_url }}" type="image/webp">
30+
<img
31+
src="{{ '/assets/img/rules_hero_banner_dark-1200.webp' | relative_url }}"
32+
alt="{{ strings.rules_page.hero_alt | default: 'Ancient scrolls with community guidelines in the Digital Bazaar' }}"
33+
class="hero-image rules-hero-image"
34+
width="1200"
35+
height="400"
36+
loading="lazy"
37+
>
38+
</picture>
39+
<div class="hero-text">
40+
<div class="hero-kicker">{{ strings.rules_page.kicker }}</div>
41+
<h1 class="hero-title">{{ strings.rules_page.title }}</h1>
42+
<p class="hero-sub">{{ strings.rules_page.dek }}</p>
43+
</div>
44+
</section>
45+
46+
<div class="rules-intro">
1247
<p class="meta-note">{{ strings.rules_page.meta_note }}</p>
13-
</header>
48+
</div>
1449

1550
<div class="highlight-box">
1651
<strong>{{ strings.rules_page.short_version_label | default: 'Short version:' }}</strong> {{ strings.rules_page.short_version }}

0 commit comments

Comments
 (0)