howdy-jekyll-theme 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.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/AGENTS.md +49 -0
  3. data/Gemfile +11 -0
  4. data/LICENSE +21 -0
  5. data/README.md +431 -0
  6. data/_includes/analytics.html +19 -0
  7. data/_includes/blog-pagination.html +31 -0
  8. data/_includes/comments.html +46 -0
  9. data/_includes/dark-mode-toggle.html +18 -0
  10. data/_includes/head.html +137 -0
  11. data/_includes/hero-carousel.html +80 -0
  12. data/_includes/logo.html +5 -0
  13. data/_includes/navigation.html +24 -0
  14. data/_includes/newsletter.html +10 -0
  15. data/_includes/share-buttons.html +70 -0
  16. data/_includes/social-links.html +37 -0
  17. data/_includes/toc.html +29 -0
  18. data/_layouts/autopage_collection.html +43 -0
  19. data/_layouts/blog.html +42 -0
  20. data/_layouts/default.html +86 -0
  21. data/_layouts/home.html +5 -0
  22. data/_layouts/page.html +33 -0
  23. data/_layouts/post.html +76 -0
  24. data/_layouts/project.html +61 -0
  25. data/_plugins/sort_projects.rb +6 -0
  26. data/_plugins/user_colors.rb +68 -0
  27. data/_posts/2026-01-01-beyond-pixels.md +21 -0
  28. data/_posts/2026-01-02-collaboration.md +25 -0
  29. data/_posts/2026-01-03-design-feeling.md +25 -0
  30. data/_posts/2026-01-04-finding-inspiration.md +25 -0
  31. data/_posts/2026-01-05-freebies.md +35 -0
  32. data/_posts/2026-01-06-ux-research-methods.md +25 -0
  33. data/_posts/2026-01-07-color-theory.md +25 -0
  34. data/_posts/2026-01-08-responsive-design.md +25 -0
  35. data/_posts/2026-01-09-web-performance.md +25 -0
  36. data/_posts/2026-01-10-mobile-first.md +25 -0
  37. data/_posts/2026-04-15-crafting-visual-hierarchy.md +27 -0
  38. data/_posts/2026-04-16-building-design-systems.md +27 -0
  39. data/_posts/2026-04-18-accessibility-matters.md +25 -0
  40. data/_posts/2026-04-19-motion-design.md +29 -0
  41. data/_posts/2026-04-20-typography-basics.md +25 -0
  42. data/_posts/2026-04-22-product-design-best-practices.md +239 -0
  43. data/_projects/atlas.md +26 -0
  44. data/_projects/luminous.md +26 -0
  45. data/_projects/nova.md +31 -0
  46. data/_projects/osaka.md +26 -0
  47. data/_sass/_base.scss +181 -0
  48. data/_sass/_components.scss +767 -0
  49. data/_sass/_dark-mode.scss +39 -0
  50. data/_sass/_layout.scss +1033 -0
  51. data/_sass/_typography.scss +394 -0
  52. data/_sass/_user-colors.scss +1 -0
  53. data/_sass/_variables.scss +165 -0
  54. data/about.md +62 -0
  55. data/assets/css/main.scss +10 -0
  56. data/assets/css/swiper-bundle.min.css +13 -0
  57. data/assets/fonts/ChaumontScript-Regular.otf +0 -0
  58. data/assets/fonts/DMSans-Italic-Variable.ttf +0 -0
  59. data/assets/fonts/DMSans-Variable.ttf +0 -0
  60. data/assets/illustrations/analytics.svg +218 -0
  61. data/assets/illustrations/business-strategy.svg +161 -0
  62. data/assets/illustrations/designer.svg +267 -0
  63. data/assets/illustrations/email.svg +123 -0
  64. data/assets/illustrations/friends-taking-selfie.svg +189 -0
  65. data/assets/illustrations/launch.svg +230 -0
  66. data/assets/illustrations/love-animals.svg +83 -0
  67. data/assets/illustrations/meeting.svg +292 -0
  68. data/assets/illustrations/mobile-shopping.svg +113 -0
  69. data/assets/illustrations/online-shopping.svg +148 -0
  70. data/assets/illustrations/oops-something-went-wrong.svg +97 -0
  71. data/assets/illustrations/podcast.svg +199 -0
  72. data/assets/illustrations/presentation.svg +138 -0
  73. data/assets/illustrations/programmer.svg +240 -0
  74. data/assets/illustrations/task-management.svg +174 -0
  75. data/assets/illustrations/tasks-complete.svg +161 -0
  76. data/assets/illustrations/travel-booking-hotel.svg +214 -0
  77. data/assets/illustrations/video-call.svg +178 -0
  78. data/assets/images/apple-touch-icon.png +0 -0
  79. data/assets/images/favicon-dark.png +0 -0
  80. data/assets/images/favicon-light.png +0 -0
  81. data/assets/images/howdy-theme-dark.png +0 -0
  82. data/assets/images/howdy-theme-light.png +0 -0
  83. data/assets/images/og-image.png +0 -0
  84. data/assets/js/hero-carousel.js +36 -0
  85. data/assets/js/mobile-nav.js +62 -0
  86. data/assets/js/swiper-bundle.min.js +13 -0
  87. data/assets/js/swiper-bundle.min.js.map +1 -0
  88. data/assets/js/theme-toggle.js +54 -0
  89. data/assets/resume.pdf +683 -0
  90. data/contact.md +74 -0
  91. data/howdy-jekyll-theme.gemspec +25 -0
  92. data/lib/howdy-jekyll-theme.rb +19 -0
  93. metadata +245 -0
@@ -0,0 +1,137 @@
1
+ <meta charset="utf-8">
2
+ <meta name="viewport" content="width=device-width, initial-scale=1">
3
+
4
+ <meta name="description" content="{% if page.description %}{{ page.description }}{% elsif page.excerpt %}{{ page.excerpt | strip_html | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
5
+
6
+ <!-- Theme color -->
7
+ {% if site.theme_color %}
8
+ <meta name="theme-color" content="{{ site.theme_color }}">
9
+ {% endif %}
10
+
11
+ <!-- Favicons -->
12
+ {% if site.favicon_dark %}
13
+ <link rel="icon" href="{{ site.favicon_dark | relative_url }}" media="(prefers-color-scheme: dark)">
14
+ {% endif %}
15
+ {% if site.favicon %}
16
+ <link rel="icon" href="{{ site.favicon | relative_url }}" media="(prefers-color-scheme: light)">
17
+ {% endif %}
18
+ {% if site.apple_touch_icon %}
19
+ <link rel="apple-touch-icon" href="{{ site.apple_touch_icon | relative_url }}">
20
+ {% endif %}
21
+
22
+ <!-- Open Graph / Facebook -->
23
+ <meta property="og:type" content="website">
24
+ <meta property="og:description" content="{% if page.description %}{{ page.description }}{% elsif page.excerpt %}{{ page.excerpt | strip_html | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
25
+ {% assign og_img = page.image | default: site.og_image %}
26
+ {% if og_img %}
27
+ <meta property="og:image" content="{{ og_img | absolute_url }}">
28
+ {% endif %}
29
+
30
+ <!-- Twitter -->
31
+ <meta name="twitter:card" content="summary_large_image">
32
+ <meta name="twitter:description" content="{% if page.description %}{{ page.description }}{% elsif page.excerpt %}{{ page.excerpt | strip_html | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
33
+ {% if og_img %}
34
+ <meta name="twitter:image" content="{{ og_img | absolute_url }}">
35
+ {% endif %}
36
+
37
+ <!-- RSS Feed -->
38
+ {% feed_meta %}
39
+
40
+ <!-- SEO Tag -->
41
+ {% seo %}
42
+
43
+
44
+
45
+ <!-- Local fonts -->
46
+ <style>
47
+ @font-face {
48
+ font-family: 'Chaumont Script';
49
+ src: url('{{ '/assets/fonts/ChaumontScript-Regular.otf' | relative_url }}') format('opentype');
50
+ font-weight: 400;
51
+ font-style: normal;
52
+ font-display: swap;
53
+ }
54
+
55
+ @font-face {
56
+ font-family: 'DM Sans';
57
+ src: url('{{ '/assets/fonts/DMSans-Variable.ttf' | relative_url }}') format('truetype');
58
+ font-weight: 100 900;
59
+ font-style: normal;
60
+ font-display: swap;
61
+ }
62
+
63
+ @font-face {
64
+ font-family: 'DM Sans';
65
+ src: url('{{ '/assets/fonts/DMSans-Italic-Variable.ttf' | relative_url }}') format('truetype');
66
+ font-weight: 100 900;
67
+ font-style: italic;
68
+ font-display: swap;
69
+ }
70
+
71
+ </style>
72
+
73
+ <!-- Custom CSS injection -->
74
+ {% if site.custom_css %}
75
+ <link rel="stylesheet" href="{{ site.custom_css | relative_url }}">
76
+ {% endif %}
77
+
78
+ <!-- Swiper CSS -->
79
+ <link rel="stylesheet" href="{{ '/assets/css/swiper-bundle.min.css' | relative_url }}">
80
+
81
+ <!-- Stylesheet -->
82
+ <link rel="stylesheet" href="{{ '/assets/css/main.css' | relative_url }}">
83
+
84
+ <!-- Dark mode script (inline for immediate execution) -->
85
+ <script>
86
+ (function() {
87
+ const root = document.documentElement;
88
+ const saved = localStorage.getItem('howdy-theme');
89
+ {% if site.default_theme and site.default_theme != "auto" %}
90
+ const defaultMode = '{{ site.default_theme }}';
91
+ if (saved === 'dark') {
92
+ root.classList.add('dark-mode');
93
+ root.classList.remove('light-mode');
94
+ } else if (saved === 'light') {
95
+ root.classList.add('light-mode');
96
+ root.classList.remove('dark-mode');
97
+ } else {
98
+ root.classList.add('{{ site.default_theme }}-mode');
99
+ }
100
+ {% else %}
101
+ if (saved === 'dark') {
102
+ root.classList.add('dark-mode');
103
+ } else if (saved === 'light') {
104
+ root.classList.add('light-mode');
105
+ } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
106
+ root.classList.add('dark-mode');
107
+ }
108
+ {% endif %}
109
+ })();
110
+ </script>
111
+
112
+ <!-- Theme toggle icon state — set before first paint to prevent flash -->
113
+ <style>
114
+ .sun-icon { display: none; }
115
+ .moon-icon { display: block; }
116
+ html.dark-mode .sun-icon { display: block; }
117
+ html.dark-mode .moon-icon { display: none; }
118
+ </style>
119
+
120
+ <!-- Page reveal — content starts hidden, revealed after first paint -->
121
+ <style>
122
+ .hero-content,
123
+ .content-area > *,
124
+ .company-logos,
125
+ .stats,
126
+ .hero-carousel,
127
+ .logo-overlay {
128
+ opacity: 0;
129
+ }
130
+ </style>
131
+ <script>
132
+ requestAnimationFrame(function() {
133
+ requestAnimationFrame(function() {
134
+ document.body.classList.add('loaded');
135
+ });
136
+ });
137
+ </script>
@@ -0,0 +1,80 @@
1
+ {% comment %}
2
+ Hero Carousel Include
3
+ Supports single `hero_image` (backward compatible) and array `hero_images`
4
+ or auto-gathering from collection featured images on index pages.
5
+ Fallback to placeholder if no images exist.
6
+ {% endcomment %}
7
+
8
+ {% assign images = "" | split: "" %}
9
+
10
+ {% comment %}Priority: page.hero_images > collection images > page.hero_image > site.hero_images > site.hero_image{% endcomment %}
11
+ {% if page.hero_images and page.hero_images != empty %}
12
+ {% for img in page.hero_images %}
13
+ {% assign images = images | push: img %}
14
+ {% endfor %}
15
+
16
+ {% comment %}Projects index — gather from all projects{% endcomment %}
17
+ {% elsif page.url == '/projects/' %}
18
+ {% assign sorted_projects = site.projects | sort: 'year' | reverse %}
19
+ {% for project in sorted_projects %}
20
+ {% if project.image %}
21
+ {% assign images = images | push: project.image %}
22
+ {% endif %}
23
+ {% endfor %}
24
+
25
+ {% comment %}Blog index — use config or auto-gather{% endcomment %}
26
+ {% elsif page.layout == 'blog' %}
27
+ {% if site.blog.hero_images %}
28
+ {% for img in site.blog.hero_images %}
29
+ {% assign images = images | push: img %}
30
+ {% endfor %}
31
+ {% else %}
32
+ {% assign limit = site.blog.hero_image_limit | default: site.posts.size %}
33
+ {% assign counter = 0 %}
34
+ {% for post in site.posts limit: limit %}
35
+ {% if post.image %}
36
+ {% assign images = images | push: post.image %}
37
+ {% assign counter = counter | plus: 1 %}
38
+ {% endif %}
39
+ {% endfor %}
40
+ {% endif %}
41
+ {% endif %}
42
+
43
+ {% comment %}Fallback to single image if no collection images found{% endcomment %}
44
+ {% if images == empty %}
45
+ {% if page.hero_image and page.hero_image != "" %}
46
+ {% assign images = images | push: page.hero_image %}
47
+ {% elsif page.image and page.image != "" %}
48
+ {% assign images = images | push: page.image %}
49
+ {% elsif site.hero_images and site.hero_images != empty %}
50
+ {% for img in site.hero_images %}
51
+ {% assign images = images | push: img %}
52
+ {% endfor %}
53
+ {% elsif site.hero_image and site.hero_image != "" %}
54
+ {% assign images = images | push: site.hero_image %}
55
+ {% endif %}
56
+ {% endif %}
57
+
58
+ {% assign image_count = images | size %}
59
+
60
+ {% if image_count == 0 %}
61
+ <div class="hero-placeholder"></div>
62
+ {% elsif image_count == 1 %}
63
+ <img src="{{ images.first | relative_url }}" alt="Hero" class="hero-image">
64
+ {% else %}
65
+ <div class="hero-carousel swiper" aria-label="Hero image carousel">
66
+ <div class="swiper-wrapper">
67
+ {% for img in images %}
68
+ <div class="swiper-slide">
69
+ <img
70
+ src="{{ img | relative_url }}"
71
+ alt="Hero slide {{ forloop.index }}"
72
+ class="hero-carousel__slide"
73
+ loading="{% if forloop.first %}eager{% else %}lazy{% endif %}"
74
+ >
75
+ </div>
76
+ {% endfor %}
77
+ </div>
78
+ <div class="hero-carousel__dots"></div>
79
+ </div>
80
+ {% endif %}
@@ -0,0 +1,5 @@
1
+ <a href="{{ '/' | relative_url }}" class="site-logo" aria-label="{{ site.title | default: 'Home' }}">
2
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="160" height="80" class="site-logo-svg" role="img" aria-hidden="true">
3
+ <text x="50%" y="60%" text-anchor="middle" dominant-baseline="middle" class="logo-text">{{ site.logo_text | default: "Howdy" }}</text>
4
+ </svg>
5
+ </a>
@@ -0,0 +1,24 @@
1
+ <button class="nav-toggle" aria-label="Toggle navigation" aria-expanded="false">
2
+ <span class="nav-toggle-icon"></span>
3
+ </button>
4
+
5
+ <div class="nav-overlay"></div>
6
+
7
+ <nav class="site-nav">
8
+ {% for item in site.navigation %}
9
+ {% assign item_url = item.url | relative_url %}
10
+ {% assign page_url = page.url | relative_url %}
11
+ {% assign is_active = false %}
12
+
13
+ {% if item.url == "/" %}
14
+ {% if page_url == "/" or page_url == "" %}
15
+ {% assign is_active = true %}
16
+ {% endif %}
17
+ {% elsif page_url contains item_url %}
18
+ {% assign is_active = true %}
19
+ {% endif %}
20
+
21
+ <a href="{{ item_url }}" {% if is_active %}class="active"{% endif %} {% if item.external %}target="_blank" rel="noopener noreferrer"{% endif %}>{{ item.title }}</a>
22
+ {% endfor %}
23
+ {% include dark-mode-toggle.html %}
24
+ </nav>
@@ -0,0 +1,10 @@
1
+ {% if site.newsletter and site.newsletter.enabled %}
2
+ <div class="newsletter">
3
+ <h3>{{ site.newsletter.title | default: "Stay in the loop" }}</h3>
4
+ <p>{{ site.newsletter.description | default: "Get occasional updates — no spam, ever." }}</p>
5
+ <form action="{{ site.newsletter.action_url }}" method="post" target="_blank" class="newsletter-form">
6
+ <input type="email" name="EMAIL" placeholder="{{ site.newsletter.placeholder | default: 'your@email.com' }}" required aria-label="Email address">
7
+ <button type="submit">{{ site.newsletter.button_text | default: "Subscribe" }}</button>
8
+ </form>
9
+ </div>
10
+ {% endif %}
@@ -0,0 +1,70 @@
1
+ {% if site.share_buttons and site.share_buttons.enabled %}
2
+ <div class="share-buttons">
3
+ <span class="share-label">Share</span>
4
+ {% assign page_url = page.url | absolute_url | url_encode %}
5
+ {% assign page_title = page.title | url_encode %}
6
+
7
+ {% if site.share_buttons.platforms contains "twitter" %}
8
+ <a href="https://twitter.com/intent/tweet?url={{ page_url }}&text={{ page_title }}" target="_blank" rel="noopener noreferrer" class="share-btn" aria-label="Share on Twitter">
9
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
10
+ </a>
11
+ {% endif %}
12
+
13
+ {% if site.share_buttons.platforms contains "linkedin" %}
14
+ <a href="https://www.linkedin.com/sharing/share-offsite/?url={{ page_url }}" target="_blank" rel="noopener noreferrer" class="share-btn" aria-label="Share on LinkedIn">
15
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
16
+ </a>
17
+ {% endif %}
18
+
19
+ {% if site.share_buttons.platforms contains "facebook" %}
20
+ <a href="https://www.facebook.com/sharer/sharer.php?u={{ page_url }}" target="_blank" rel="noopener noreferrer" class="share-btn" aria-label="Share on Facebook">
21
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
22
+ </a>
23
+ {% endif %}
24
+
25
+ {% if site.share_buttons.platforms contains "reddit" %}
26
+ <a href="https://reddit.com/submit?url={{ page_url }}&title={{ page_title }}" target="_blank" rel="noopener noreferrer" class="share-btn" aria-label="Share on Reddit">
27
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0A12 12 0 0012 24 12 12 0 0012 0zM18.94 8.21a1.87 1.87 0 011.87 1.87c0 .77-.47 1.43-1.13 1.72.04.24.06.48.06.72 0 3.14-3.2 5.68-7.14 5.68S5.46 15.66 5.46 12.52c0-.24.02-.48.06-.72a1.87 1.87 0 01-.92-1.62A1.9 1.9 0 016.5 8.28a1.9 1.9 0 011.73 1.05 5.6 5.6 0 013.33-1.06l.63-2.98.01-.01.01-.01a.1.1 0 01.07-.06l2.1-.44a1.35 1.35 0 011.23 2.1c.4.2.68.61.68 1.09a1.35 1.35 0 01-1.35 1.35 1.35 1.35 0 01-1.35-1.35c0-.4.18-.76.46-1.01l-1.63.34-.51 2.41c1.39.03 2.66.39 3.68 1.06a1.9 1.9 0 011.66-.95zm-8.2 4.78a1.31 1.31 0 00-1.31 1.31 1.31 1.31 0 001.31 1.31 1.31 1.31 0 001.31-1.31 1.3 1.3 0 00-1.31-1.31zm3.91 2.83c-.3.3-.87.43-1.55.43s-1.24-.12-1.54-.43a.39.39 0 00-.55 0 .39.39 0 000 .55c.52.52 1.2.5 2.09.5s1.58.03 2.1-.5a.39.39 0 000-.55.39.39 0 00-.55 0zm-.22-1.52a1.31 1.31 0 001.31-1.31 1.31 1.31 0 00-1.31-1.31 1.31 1.31 0 00-1.31 1.31 1.3 1.3 0 001.31 1.31z"/></svg>
28
+ </a>
29
+ {% endif %}
30
+
31
+ {% if site.share_buttons.platforms contains "hackernews" %}
32
+ <a href="https://news.ycombinator.com/submitlink?u={{ page_url }}&t={{ page_title }}" target="_blank" rel="noopener noreferrer" class="share-btn" aria-label="Share on Hacker News">
33
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M0 24V0h24v24H0zM6.951 5.896l4.112 7.708v5.064h1.583v-5.064l4.148-7.708h-1.63l-2.608 5.064a85.07 85.07 0 00-.625 1.288 119.28 119.28 0 00-.515-1.072l-2.7-5.28H6.95z"/></svg>
34
+ </a>
35
+ {% endif %}
36
+
37
+ {% if site.share_buttons.platforms contains "whatsapp" %}
38
+ <a href="https://wa.me/?text={{ page_title | url_encode }}%20{{ page_url }}" target="_blank" rel="noopener noreferrer" class="share-btn" aria-label="Share on WhatsApp">
39
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.76-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg>
40
+ </a>
41
+ {% endif %}
42
+
43
+ {% if site.share_buttons.platforms contains "telegram" %}
44
+ <a href="https://t.me/share/url?url={{ page_url }}&text={{ page_title }}" target="_blank" rel="noopener noreferrer" class="share-btn" aria-label="Share on Telegram">
45
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M11.944 0A12 12 0 000 12a12 12 0 0012 12 12 12 0 0012-12A12 12 0 0012 0a12 12 0 00-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 01.171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/></svg>
46
+ </a>
47
+ {% endif %}
48
+
49
+ {% if site.share_buttons.platforms contains "email" %}
50
+ <a href="mailto:?subject={{ page_title }}&body={{ page_url }}" target="_blank" rel="noopener noreferrer" class="share-btn" aria-label="Share via Email">
51
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/></svg>
52
+ </a>
53
+ {% endif %}
54
+
55
+ <button class="share-btn share-copy" aria-label="Copy link" data-url="{{ page.url | absolute_url }}">
56
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg>
57
+ </button>
58
+ </div>
59
+
60
+ <script>
61
+ document.querySelector('.share-copy')?.addEventListener('click', function() {
62
+ const url = this.dataset.url;
63
+ navigator.clipboard.writeText(url).then(() => {
64
+ const original = this.innerHTML;
65
+ this.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>';
66
+ setTimeout(() => { this.innerHTML = original; }, 2000);
67
+ });
68
+ });
69
+ </script>
70
+ {% endif %}
@@ -0,0 +1,37 @@
1
+ <div class="social-links">
2
+ {% if site.social.github %}
3
+ <a href="{{ site.social.github }}" target="_blank" rel="noopener noreferrer" aria-label="GitHub">
4
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
5
+ <path d="M8 0c-4.42 0-8 3.58-8 8a8 8 0 0 0 5.47 7.59c.4.07.55-.17.55-.38s-.01-.86-.01-1.7c-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.6 7.6 0 0 1 4 0c1.52-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8 8 0 0 0 16 8c0-4.42-3.58-8-8-8"/>
6
+ </svg>
7
+ </a>
8
+ {% endif %}
9
+ {% if site.social.twitter or site.social.x %}
10
+ <a href="{{ site.social.x | default: site.social.twitter }}" target="_blank" rel="noopener noreferrer" aria-label="X (Twitter)">
11
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
12
+ <path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865l8.875 11.633Z"/>
13
+ </svg>
14
+ </a>
15
+ {% endif %}
16
+ {% if site.social.linkedin %}
17
+ <a href="{{ site.social.linkedin }}" target="_blank" rel="noopener noreferrer" aria-label="LinkedIn">
18
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
19
+ <path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854zm4.943 12.248V6.169H2.542v7.225zm-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248S2.4 3.226 2.4 3.934c0 .694.521 1.248 1.327 1.248zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016l.016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225z"/>
20
+ </svg>
21
+ </a>
22
+ {% endif %}
23
+ {% if site.social.instagram %}
24
+ <a href="{{ site.social.instagram }}" target="_blank" rel="noopener noreferrer" aria-label="Instagram">
25
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
26
+ <path d="M8 0C5.829 0 5.556.01 4.703.048 3.85.088 3.269.222 2.76.42a3.917 3.917 0 0 0-1.417.923A3.927 3.927 0 0 0 .42 2.76C.222 3.268.087 3.85.048 4.7.01 5.555 0 5.827 0 8.001c0 2.172.01 2.444.048 3.297.04.852.174 1.433.372 1.942.205.526.478.972.923 1.417.444.445.89.719 1.416.923.51.198 1.09.333 1.942.372C5.555 15.99 5.827 16 8 16s2.444-.01 3.298-.048c.851-.04 1.434-.174 1.943-.372a3.916 3.916 0 0 0 1.416-.923c.445-.445.718-.891.923-1.417.197-.509.332-1.09.372-1.942C15.99 10.445 16 10.173 16 8s-.01-2.445-.048-3.299c-.04-.851-.175-1.433-.372-1.941a3.926 3.926 0 0 0-.923-1.417A3.911 3.911 0 0 0 13.24.42c-.51-.198-1.092-.333-1.943-.372C10.443.01 10.172 0 7.998 0h.003zm-.717 1.442h.718c2.136 0 2.389.007 3.232.046.78.035 1.204.166 1.486.275.373.145.64.319.92.599.28.28.453.546.598.92.11.281.24.705.275 1.485.039.843.047 1.096.047 3.231s-.008 2.389-.047 3.232c-.035.78-.166 1.203-.275 1.485a2.47 2.47 0 0 1-.599.919c-.28.28-.546.453-.92.598-.28.11-.704.24-1.485.276-.843.038-1.096.047-3.232.047s-2.39-.009-3.233-.047c-.78-.036-1.203-.166-1.485-.276a2.478 2.478 0 0 1-.92-.598 2.48 2.48 0 0 1-.6-.92c-.109-.281-.24-.705-.275-1.485-.038-.843-.046-1.096-.046-3.233 0-2.136.008-2.388.046-3.231.036-.78.166-1.204.276-1.486.145-.373.319-.64.599-.92.28-.28.546-.453.92-.598.282-.11.705-.24 1.485-.276.738-.034 1.024-.044 2.515-.045v.002zm4.988 1.328a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92zm-4.27 1.122a4.109 4.109 0 1 0 0 8.217 4.109 4.109 0 0 0 0-8.217zm0 1.441a2.667 2.667 0 1 1 0 5.334 2.667 2.667 0 0 1 0-5.334z"/>
27
+ </svg>
28
+ </a>
29
+ {% endif %}
30
+ {% if site.social.dribbble %}
31
+ <a href="{{ site.social.dribbble }}" target="_blank" rel="noopener noreferrer" aria-label="Dribbble">
32
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
33
+ <path fill-rule="evenodd" d="M8 0C3.584 0 0 3.584 0 8s3.584 8 8 8c4.408 0 8-3.584 8-8s-3.592-8-8-8m5.284 3.688a6.8 6.8 0 0 1 1.545 4.251c-.226-.043-2.482-.503-4.755-.217-.052-.112-.096-.234-.148-.355-.139-.33-.295-.668-.451-.99 2.516-1.023 3.662-2.498 3.81-2.69zM8 1.18c1.735 0 3.323.65 4.53 1.718-.122.174-1.155 1.553-3.584 2.464-1.12-2.056-2.36-3.74-2.551-4A7 7 0 0 1 8 1.18m-2.907.642A43 43 0 0 1 7.627 5.77c-3.193.85-6.013.833-6.317.833a6.87 6.87 0 0 1 3.783-4.78zM1.163 8.01V7.8c.295.01 3.61.053 7.02-.971.199.381.381.772.555 1.162l-.27.078c-3.522 1.137-5.396 4.243-5.553 4.504a6.82 6.82 0 0 1-1.752-4.564zM8 14.837a6.8 6.8 0 0 1-4.19-1.44c.12-.252 1.509-2.924 5.361-4.269.018-.009.026-.009.044-.017a28.3 28.3 0 0 1 1.457 5.18A6.7 6.7 0 0 1 8 14.837m3.81-1.171c-.07-.417-.435-2.412-1.328-4.868 2.143-.338 4.017.217 4.251.295a6.77 6.77 0 0 1-2.924 4.573z"/>
34
+ </svg>
35
+ </a>
36
+ {% endif %}
37
+ </div>
@@ -0,0 +1,29 @@
1
+ {% assign min_h = include.min_heading | default: 2 %}
2
+ {% assign max_h = include.max_heading | default: 3 %}
3
+ {% assign raw_content = include.content %}
4
+
5
+ {% assign heading_items = raw_content | split: '<h' | where_exp: "item", "item != ''" %}
6
+
7
+ {% assign toc_items = "" | split: "" %}
8
+
9
+ {% for item in heading_items %}
10
+ {% assign level = item | slice: 0 | plus: 0 %}
11
+ {% if level >= min_h and level <= max_h %}
12
+ {% assign tag_attrs = item | split: '>' | first %}
13
+ {% assign id = tag_attrs | split: 'id="' | last | split: '"' | first %}
14
+ {% assign content_after_tag = item | remove_first: tag_attrs | remove_first: '>' %}
15
+ {% assign text = content_after_tag | split: '</h' | first %}
16
+ {% assign indent = level | minus: min_h | times: 16 %}
17
+ {% assign toc_item = '<a href="#' | append: id | append: '" style="padding-left:' | append: indent | append: 'px;">' | append: text | append: '</a>' %}
18
+ {% assign toc_items = toc_items | push: toc_item %}
19
+ {% endif %}
20
+ {% endfor %}
21
+
22
+ {% if toc_items.size > 0 %}
23
+ <nav class="toc" aria-label="Table of contents">
24
+ <span class="toc-title">On this page</span>
25
+ {% for item in toc_items %}
26
+ {{ item }}
27
+ {% endfor %}
28
+ </nav>
29
+ {% endif %}
@@ -0,0 +1,43 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ <section class="two-column">
6
+ <div class="left-section">
7
+ <div class="left-container">
8
+ <div class="image-wrapper">
9
+ <div class="hero-content">
10
+ {% include hero-carousel.html %}
11
+ <div class="logo-overlay">{% include logo.html %}</div>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </div>
16
+
17
+ <div class="right-section">
18
+ <div class="content-wrapper">
19
+ {% include navigation.html %}
20
+ <div class="content-area">
21
+ <h1>{{ page.title | default: "Blog" }}</h1>
22
+ <p class="body-large" style="color: var(--howdy-text-secondary);">{{ page.description | default: page.autopage_display_name | default: "Welcome to the blog." }}</p>
23
+
24
+ <div class="posts-grid" style="margin-top: 48px;">
25
+ {% for post in paginator.posts %}
26
+ <a href="{{ post.url | relative_url }}" class="post-card">
27
+ {% if post.image %}
28
+ <img src="{{ post.image | relative_url }}" alt="{{ post.title }}" class="post-image">
29
+ {% endif %}
30
+ <div class="post-category category-{{ post.category | downcase }}">{{ post.category }}</div>
31
+ <h3 class="post-title">{{ post.title }}</h3>
32
+ <p class="post-excerpt">{{ post.excerpt | strip_html | truncate: 120 }}</p>
33
+ <span class="post-date">{{ post.date | date: "%b %d, %Y" }}</span>
34
+ </a>
35
+ {% endfor %}
36
+ </div>
37
+
38
+ <!-- Pagination -->
39
+ {% include blog-pagination.html %}
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </section>
@@ -0,0 +1,42 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ <section class="two-column">
6
+ <div class="left-section">
7
+ <div class="left-container">
8
+ <div class="image-wrapper">
9
+ <div class="hero-content">
10
+ {% include hero-carousel.html %}
11
+ <div class="logo-overlay">{% include logo.html %}</div>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </div>
16
+
17
+ <div class="right-section">
18
+ <div class="content-wrapper">
19
+ {% include navigation.html %}
20
+ <div class="content-area">
21
+ <h1>Blog</h1>
22
+ <p class="body-large" style="color: var(--howdy-text-secondary);">{{ page.description | default: "Thoughts, ideas, and inspiration." }}</p>
23
+
24
+ <div class="posts-grid" style="margin-top: 48px;">
25
+ {% for post in paginator.posts %}
26
+ <a href="{{ post.url | relative_url }}" class="post-card">
27
+ {% if post.image %}
28
+ <img src="{{ post.image | relative_url }}" alt="{{ post.title }}" class="post-image">
29
+ {% endif %}
30
+ <div class="post-category category-{{ post.category | downcase }}">{{ post.category }}</div>
31
+ <h3 class="post-title">{{ post.title }}</h3>
32
+ <p class="post-excerpt">{{ post.excerpt | strip_html | truncate: 120 }}</p>
33
+ <span class="post-date">{{ post.date | date: "%b %d, %Y" }}</span>
34
+ </a>
35
+ {% endfor %}
36
+ </div>
37
+
38
+ {% include blog-pagination.html %}
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </section>
@@ -0,0 +1,86 @@
1
+ <!DOCTYPE html>
2
+ <html lang="{{ page.lang | default: site.lang | default: 'en' }}">
3
+ <head>
4
+ {% include head.html %}
5
+ </head>
6
+ <body>
7
+ <div class="main-container">
8
+ {{ content }}
9
+ </div>
10
+
11
+
12
+ <script src="{{ '/assets/js/theme-toggle.js' | relative_url }}"></script>
13
+ <script src="{{ '/assets/js/mobile-nav.js' | relative_url }}"></script>
14
+ <script src="{{ '/assets/js/swiper-bundle.min.js' | relative_url }}"></script>
15
+ <script src="{{ '/assets/js/hero-carousel.js' | relative_url }}"></script>
16
+
17
+ <!-- Analytics -->
18
+ {% include analytics.html %}
19
+
20
+ <!-- Custom JS injection -->
21
+ {% if site.custom_js %}
22
+ <script src="{{ site.custom_js | relative_url }}"></script>
23
+ {% endif %}
24
+
25
+ <script>
26
+ // Smooth page transitions: fade out content only, then navigate
27
+ document.addEventListener('click', (e) => {
28
+ const link = e.target.closest('a');
29
+ if (!link) return;
30
+
31
+ const href = link.getAttribute('href');
32
+ if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:') || link.target === '_blank') return;
33
+
34
+ const url = new URL(href, window.location.href);
35
+ if (url.origin === window.location.origin && url.pathname !== window.location.pathname) {
36
+ e.preventDefault();
37
+
38
+ if (typeof window.howdyNavIsClosing === 'function' && window.howdyNavIsClosing()) {
39
+ setTimeout(() => {
40
+ document.body.classList.add('howdy-navigating');
41
+ setTimeout(() => { window.location.href = href; }, 150);
42
+ }, 300);
43
+ } else {
44
+ document.body.classList.add('howdy-navigating');
45
+ setTimeout(() => { window.location.href = href; }, 150);
46
+ }
47
+ }
48
+ });
49
+ </script>
50
+
51
+ <script>
52
+ // Fix pagination links - add blog path prefix if missing
53
+ const blogPath = '{{ "/blog/" | relative_url }}';
54
+ document.querySelectorAll('.pagination a[href^="/page"]').forEach(link => {
55
+ if (!link.href.includes(blogPath)) {
56
+ link.href = blogPath + link.getAttribute('href').substring(1);
57
+ }
58
+ });
59
+ </script>
60
+
61
+ <script>
62
+ // TOC smooth scroll with offset and highlight
63
+ document.addEventListener('click', (e) => {
64
+ const link = e.target.closest('.toc a');
65
+ if (!link) return;
66
+
67
+ const href = link.getAttribute('href');
68
+ if (!href || !href.startsWith('#')) return;
69
+
70
+ const target = document.querySelector(href);
71
+ if (!target) return;
72
+
73
+ e.preventDefault();
74
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
75
+
76
+ target.style.transition = 'color 0.3s ease';
77
+ target.style.color = 'var(--howdy-accent-green)';
78
+ setTimeout(() => {
79
+ target.style.color = '';
80
+ }, 1500);
81
+
82
+ window.history.pushState(null, null, href);
83
+ });
84
+ </script>
85
+ </body>
86
+ </html>
@@ -0,0 +1,5 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ {{ content }}
@@ -0,0 +1,33 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ <section class="two-column">
6
+ <div class="left-section">
7
+ <div class="left-container">
8
+ <div class="image-wrapper">
9
+ <div class="hero-content">
10
+ {% include hero-carousel.html %}
11
+ <div class="logo-overlay">{% include logo.html %}</div>
12
+ </div>
13
+ </div>
14
+ </div>
15
+ </div>
16
+
17
+ <div class="right-section">
18
+ <div class="content-wrapper">
19
+ {% include navigation.html %}
20
+ <div class="content-area">
21
+ <h1>{{ page.title }}</h1>
22
+
23
+ {% if page.subtitle %}
24
+ <p class="body-large" style="margin-top: 16px;">{{ page.subtitle }}</p>
25
+ {% endif %}
26
+
27
+ <div class="page-content" style="margin-top: 32px;">
28
+ {{ content }}
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </section>