jekyll-bear-theme 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ecc530b679d4282ee1b024826ab66867981de210b25bc59772758990f1f9fa3
4
- data.tar.gz: 89b663c633e425b61c4ec0c25de78dc6082027bc89c0ecbe8a435c49ac8786bf
3
+ metadata.gz: 321a64217ca032b2568fb793d950fe0f8e795b0672f15b9f4432bff72c6c48e3
4
+ data.tar.gz: 5725640312e7f03e468c8b61b64ea828d6b4777dbe32043e098a01e14f0ea671
5
5
  SHA512:
6
- metadata.gz: 821809b58d18a4f4f5c2af63725552a224cb0cd8b0419b5d8641a9697a7902aed86013601a818518116796f76252b3b05fa17ce543779fea3a659039317d2683
7
- data.tar.gz: 276a81deb6a733f2a10c891bb111e4a821b3af99e2fa1dd354b5e9f77df56ff5d3aa307fe7f4d185082a188a61ad21aa8eaf0dcc6b3c18d29c1e6cbfcf17d6c3
6
+ metadata.gz: 07b29e2279a7fb91b778950c5bcaa8d8a5a6b8e03fe6719a29539aa3ff5c52d800084cfa871da546b252b10cf4f5a5d59c010a73d15c1e6491077fc36d89b194
7
+ data.tar.gz: b17d6e3ecdc6359f9328200e124855b86d2592c1ec3d2f9b553f3404ec33330605585ddefcc061ce352f883bed69b153908785c591e0c9ac57489990713f4c90
data/README.md CHANGED
@@ -122,6 +122,18 @@ Use them as templates for your front matter.
122
122
  >To test your theme, run `bundle exec jekyll serve` and open your browser at `http://localhost:4000`.
123
123
 
124
124
 
125
+ ## Recent Updates: Life in Weeks Interactive Carousel
126
+
127
+ - The **Life in Weeks** page now features an interactive carousel blockquote above the grid, showing a key event from your life.
128
+ - Up/down arrows let you cycle through events. The week number, event name, and description are shown, with the week number highlighted.
129
+ - As you change the quote, the corresponding week in the grid pulses, visually connecting the event to its week.
130
+ - The quote area has a fixed height and is vertically centered, so the grid never shifts as you browse events.
131
+ - The grid is fully non-interactive (no tooltips, no pointer/question mark cursor), for a clean and minimal look.
132
+ - The carousel arrows are styled to match the theme and do not show a highlight on click.
133
+ - All code is lean and efficient: no extra network requests, no heavy JS, and all data is embedded at build time for instant loading.
134
+
135
+ See `life.md`, `_layouts/life-in-weeks.html`, and `assets/life-carousel.js` for implementation details.
136
+
125
137
  ## Contributing
126
138
 
127
139
  Bug reports and pull requests are welcome on GitHub at https://github.com/knhash/jekyllBear. 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.
@@ -20,7 +20,34 @@ layout: compress
20
20
  {% assign start_day = split_start[2] %}
21
21
  {% assign end_year = page.end_year | plus: 0 %}
22
22
 
23
- <div class="weeks-grid">
23
+ <!-- Carousel Blockquote Section -->
24
+ <div id="life-carousel-container" style="display: flex; align-items: center; margin: 2.2em 0 1.7em 0;" data-start-date="{{ page.start_date }}">
25
+ <div style="display: flex; flex-direction: column; align-items: center; margin-right: 1em;">
26
+ <button id="life-carousel-up" aria-label="Previous event" style="background: none; border: none; cursor: pointer; font-size: 1.5em; line-height: 1; padding: 0.2em;">
27
+ <span aria-hidden="true">&#9650;</span>
28
+ </button>
29
+ <button id="life-carousel-down" aria-label="Next event" style="background: none; border: none; cursor: pointer; font-size: 1.5em; line-height: 1; padding: 0.2em;">
30
+ <span aria-hidden="true">&#9660;</span>
31
+ </button>
32
+ </div>
33
+ <blockquote style="flex: 1; margin: 0; font-size: 1.1em; height: 7.5em; max-width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: flex-start; line-height: 1.5; overflow: hidden; font-style: normal;">
34
+ <span id="life-carousel-quote"></span>
35
+ <span id="life-carousel-meta" style="font-size: 0.95em; color: var(--main-color); margin-top: 0.5em;"></span>
36
+ </blockquote>
37
+ </div>
38
+ {% capture events_json %}[
39
+ {% assign first = true %}
40
+ {% for event_date in data %}
41
+ {% assign date = event_date[0] %}
42
+ {% for event in event_date[1] %}
43
+ {% unless first %},{% endunless %}{"name":{{ event.name | jsonify }},"desc":{{ event.desc | jsonify }},"date":{{ date | jsonify }}}
44
+ {% assign first = false %}
45
+ {% endfor %}
46
+ {% endfor %}
47
+ ]{% endcapture %}
48
+ <script id="life-in-weeks-events-json" type="application/json">{{ events_json | strip }}</script>
49
+ <script src="{{ "/assets/life-carousel.js" | relative_url }}"></script>
50
+ <div class="weeks-grid" style="margin-top: 1.7em;">
24
51
  {% assign total_years = end_year | minus: start_year | plus: 1 %}
25
52
  {% assign total_weeks = total_years | times: 52 %}
26
53
  {% if total_weeks > 0 %}
@@ -67,9 +94,9 @@ layout: compress
67
94
  {% assign is_current = true %}
68
95
  {% endif %}
69
96
  {% assign decade_mod = decade | modulo: 2 %}
70
- <div class="week decade-{{ decade_mod }}{% if week_has_events %} has-events{% endif %}{% if is_future %} future{% endif %}{% if is_current %} current{% endif %}"
71
- {% if week_has_events %}data-event="{{ week_tooltip | strip }}"{% endif %}>
72
- {% if week_has_events %}{{ week_events | strip }}{% endif %}
97
+ <div class="week decade-{{ decade_mod }}{% if week_has_events %} has-events{% endif %}{% if is_future %} future{% endif %}{% if is_current %} current{% endif %}"
98
+ data-week-number="{{ week_index }}">
99
+ <!-- No event text, just highlight -->
73
100
  </div>
74
101
  {% assign week_index = week_index | plus: 1 %}
75
102
  {% endfor %}
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,93 @@
1
+ // Carousel logic for life-in-weeks blockquote
2
+ (function() {
3
+ let currentIndex = 0;
4
+
5
+ // Parse events from embedded JSON
6
+ function loadEventsFromJson() {
7
+ var jsonTag = document.getElementById('life-in-weeks-events-json');
8
+ if (jsonTag) {
9
+ try {
10
+ var jsonStr = jsonTag.textContent.trim();
11
+ window.lifeInWeeksEvents = JSON.parse(jsonStr);
12
+ } catch (e) {
13
+ window.lifeInWeeksEvents = [];
14
+ }
15
+ }
16
+ }
17
+
18
+ function getWeekNumberSinceBirth(eventDateStr) {
19
+ // Get start_date from a data attribute on the container
20
+ const container = document.getElementById('life-carousel-container');
21
+ const startDateStr = container ? container.getAttribute('data-start-date') : null;
22
+ if (!startDateStr) return '?';
23
+ const start = new Date(startDateStr);
24
+ const eventDate = new Date(eventDateStr);
25
+ if (isNaN(start) || isNaN(eventDate)) return '?';
26
+ const diffMs = eventDate - start;
27
+ const weekNum = Math.floor(diffMs / (1000 * 60 * 60 * 24 * 7)) + 1;
28
+ return weekNum;
29
+ }
30
+
31
+ function updateCarousel() {
32
+ const events = window.lifeInWeeksEvents || [];
33
+ const quote = document.getElementById('life-carousel-quote');
34
+ const meta = document.getElementById('life-carousel-meta');
35
+ // Remove pulse from all weeks
36
+ document.querySelectorAll('.week.carousel-pulse').forEach(el => {
37
+ el.classList.remove('carousel-pulse');
38
+ });
39
+ if (!events.length) {
40
+ quote.textContent = 'No events to display.';
41
+ meta.textContent = '';
42
+ return;
43
+ }
44
+ if (currentIndex >= events.length) currentIndex = 0;
45
+ if (currentIndex < 0) currentIndex = events.length - 1;
46
+ const event = events[currentIndex];
47
+ const weekNum = getWeekNumberSinceBirth(event.date);
48
+ // Highlight the corresponding week in the grid
49
+ const weekDiv = document.querySelector('.week[data-week-number="' + (weekNum - 1) + '"]');
50
+ if (weekDiv) {
51
+ weekDiv.classList.add('carousel-pulse');
52
+ }
53
+ let main = '';
54
+ if (event.name && event.desc) {
55
+ main = `<span class="carousel-weeknum">Week ${weekNum}</span>: ${event.name}`;
56
+ quote.innerHTML = main;
57
+ meta.textContent = event.desc;
58
+ } else if (event.name) {
59
+ main = `<span class="carousel-weeknum">Week ${weekNum}</span>: ${event.name}`;
60
+ quote.innerHTML = main;
61
+ meta.textContent = '';
62
+ } else if (event.desc) {
63
+ main = `<span class="carousel-weeknum">Week ${weekNum}</span>`;
64
+ quote.innerHTML = main;
65
+ meta.textContent = event.desc;
66
+ } else {
67
+ quote.innerHTML = `<span class="carousel-weeknum">Week ${weekNum}</span>`;
68
+ meta.textContent = '';
69
+ }
70
+ }
71
+ window.updateLifeCarousel = updateCarousel;
72
+
73
+ function setupCarousel() {
74
+ loadEventsFromJson();
75
+ document.getElementById('life-carousel-up').addEventListener('click', function() {
76
+ const events = window.lifeInWeeksEvents || [];
77
+ currentIndex = (currentIndex - 1 + events.length) % events.length;
78
+ updateCarousel();
79
+ });
80
+ document.getElementById('life-carousel-down').addEventListener('click', function() {
81
+ const events = window.lifeInWeeksEvents || [];
82
+ currentIndex = (currentIndex + 1) % events.length;
83
+ updateCarousel();
84
+ });
85
+ updateCarousel();
86
+ }
87
+
88
+ if (document.readyState === 'loading') {
89
+ document.addEventListener('DOMContentLoaded', setupCarousel);
90
+ } else {
91
+ setupCarousel();
92
+ }
93
+ })();
data/assets/style.css CHANGED
@@ -1,43 +1,144 @@
1
+ /* Highlighted week number in carousel quote */
2
+ .carousel-weeknum {
3
+ color: var(--link-color);
4
+ font-weight: bold;
5
+ font-style: normal;
6
+ }
7
+ /* Pulse effect for carousel-selected week */
8
+ .carousel-pulse {
9
+ animation: carousel-pulse 1.2s cubic-bezier(0.4,0,0.6,1) infinite;
10
+ border-width: 2px !important;
11
+ border-color: var(--link-color) !important;
12
+ box-shadow: 0 0 10px rgba(124, 64, 17, 0.5);
13
+ z-index: 2;
14
+ }
15
+
16
+ @keyframes carousel-pulse {
17
+ 0% {
18
+ transform: scale(1);
19
+ opacity: 1;
20
+ }
21
+ 50% {
22
+ transform: scale(1.18);
23
+ opacity: 0.7;
24
+ }
25
+ 100% {
26
+ transform: scale(1);
27
+ opacity: 1;
28
+ }
29
+ }
30
+ /* Carousel arrow button styles */
31
+ #life-carousel-up, #life-carousel-down {
32
+ background: none;
33
+ border: none;
34
+ cursor: pointer;
35
+ font-size: 1.5em;
36
+ line-height: 1;
37
+ padding: 0.2em;
38
+ color: var(--main-color);
39
+ outline: none;
40
+ user-select: none;
41
+ -webkit-tap-highlight-color: transparent;
42
+ transition: color 0.2s;
43
+ }
44
+ #life-carousel-up:focus, #life-carousel-down:focus, #life-carousel-up:active, #life-carousel-down:active {
45
+ outline: none;
46
+ box-shadow: none;
47
+ background: none;
48
+ color: var(--main-color);
49
+ }
50
+ #life-carousel-up span, #life-carousel-down span {
51
+ color: inherit;
52
+ }
1
53
  /* Tooltip for event squares */
54
+
2
55
  .week[data-event] {
3
56
  position: relative;
4
- cursor: pointer;
57
+ cursor: default !important;
5
58
  }
6
- .week[data-event]:hover::after {
7
- content: attr(data-event);
8
- position: absolute;
9
- background: var(--background-color);
10
- color: var(--text-color);
11
- padding: 0.6rem;
12
- border-radius: 8px;
13
- font-size: 1rem;
14
- font-family: var(--font-secondary);
15
- font-weight: normal;
16
- line-height: 1.4;
17
- white-space: pre-line;
18
- z-index: 100;
19
- box-shadow: 0 4px 16px rgba(0,0,0,0.18);
20
- top: 130%;
21
- left: 50%;
22
- transform: translateX(-50%);
23
- pointer-events: none;
24
- max-width: 50vw;
25
- min-width: max-content;
26
- width: max-content;
27
- text-align: left;
28
- display: flex;
29
- align-items: center;
30
- vertical-align: baseline;
59
+
60
+ .week {
61
+ cursor: default !important;
62
+ }
63
+
64
+ /* Desktop: show tooltip on hover */
65
+ @media (hover: hover) {
66
+ .week[data-event]:hover::after {
67
+ content: attr(data-event);
68
+ position: absolute;
69
+ background: var(--background-color);
70
+ color: var(--text-color);
71
+ padding: 0.6rem;
72
+ border-radius: 0.2rem;
73
+ border: 1px solid var(--blockquote-color);
74
+ font-size: 1rem;
75
+ font-family: var(--font-thin);
76
+ font-weight: normal;
77
+ line-height: 1.2;
78
+ white-space: pre-line;
79
+ z-index: 100;
80
+ box-shadow: 0 4px 16px rgba(0,0,0,0.18);
81
+ top: 130%;
82
+ left: 50%;
83
+ transform: translateX(-50%);
84
+ pointer-events: none;
85
+ max-width: 30vw;
86
+ min-width: max-content;
87
+ width: max-content;
88
+ text-align: left;
89
+ display: flex;
90
+ align-items: center;
91
+ vertical-align: baseline;
92
+ }
93
+ }
94
+
95
+ /* Mobile: show tooltip on active/focus */
96
+ @media (hover: none) {
97
+ .week[data-event]:active::after,
98
+ .week[data-event]:focus::after {
99
+ content: attr(data-event);
100
+ position: absolute;
101
+ background: var(--background-color);
102
+ color: var(--text-color);
103
+ padding: 0.6rem;
104
+ border-radius: 0.2rem;
105
+ border: 1px solid var(--blockquote-color);
106
+ font-size: 1rem;
107
+ font-family: var(--font-thin);
108
+ font-weight: normal;
109
+ line-height: 1.2;
110
+ white-space: pre-line;
111
+ z-index: 100;
112
+ box-shadow: 0 4px 16px rgba(0,0,0,0.18);
113
+ top: 130%;
114
+ left: 50%;
115
+ transform: translateX(-50%);
116
+ pointer-events: none;
117
+ max-width: 50vw;
118
+ min-width: max-content;
119
+ width: max-content;
120
+ text-align: left;
121
+ display: flex;
122
+ align-items: center;
123
+ vertical-align: baseline;
124
+ }
125
+
126
+ .week[data-event] {
127
+ -webkit-tap-highlight-color: transparent;
128
+ outline: none;
129
+ }
31
130
  }
32
131
 
33
132
 
34
133
 
35
- @import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@100;200;300;400;500;600;700;800;900&display=swap');
134
+ @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
36
135
 
37
136
  :root {
38
137
  --width: 900px;
39
- --font-main: 'Fira Code', monospace;
40
- --font-secondary: 'Fira Code', monospace;
138
+ --font-main: 'IBM Plex Sans', sans-serif;
139
+ --font-thin: 'IBM Plex Sans Thin', sans-serif;
140
+ --font-secondary: 'IBM Plex Sans', sans-serif;
141
+ --font-monospace: 'IBM Plex Mono', monospace;
41
142
  --font-scale: .95em;
42
143
  --background-color: #F8F1E5; /* Lighter cream */
43
144
  --heading-color: #2C1E15; /* Darker roast coffee */
@@ -140,7 +241,7 @@ img {
140
241
  }
141
242
 
142
243
  code {
143
- font-family: monospace;
244
+ font-family: var(--font-monospace);
144
245
  padding: 2px;
145
246
  background-color: var(--code-background-color);
146
247
  color: var(--code-color);
@@ -225,7 +326,8 @@ td {
225
326
 
226
327
  /* external link marker */
227
328
  a[href^="http"]:where(:not([href*="knhash.in/"])):not(:has(img))::after {
228
- content: "↗"
329
+ content: "↗";
330
+ font-family: var(--font-monospace);
229
331
  }
230
332
 
231
333
  /* Form elements */
@@ -280,7 +382,6 @@ input[type=button], input[type=submit], input[type=reset] {
280
382
 
281
383
  .weeks-grid {
282
384
  margin: 60px 0;
283
- font-family: var(--font-main);
284
385
  display: flex;
285
386
  flex-wrap: wrap;
286
387
  gap: 2px;
@@ -288,28 +389,11 @@ input[type=button], input[type=submit], input[type=reset] {
288
389
  align-items: flex-start;
289
390
  }
290
391
 
291
- /* Make weeks grid wider on larger screens while maintaining padding */
292
- @media (min-width: 1200px) {
293
- .weeks-grid {
294
- max-width: calc(100vw - 10vw);
295
- width: calc(100vw - 10vw);
296
- margin-left: calc((var(--width) - 100vw + 10vw) / 2);
297
- margin-right: calc((var(--width) - 100vw + 10vw) / 2);
298
- }
299
- }
300
-
301
- @media (min-width: 1400px) {
302
- .weeks-grid {
303
- max-width: calc(100vw - 10vw);
304
- width: calc(100vw - 10vw);
305
- margin-left: calc((var(--width) - 100vw + 10vw) / 2);
306
- margin-right: calc((var(--width) - 100vw + 10vw) / 2);
307
- }
308
- }
392
+ /* Removed wide-screen overrides for .weeks-grid to always fit its container */
309
393
 
310
394
  .week {
311
- min-width: 30px;
312
- height: 30px;
395
+ width: 16px;
396
+ height: 16px;
313
397
  border: 1px solid var(--main-color);
314
398
  background-color: var(--background-color);
315
399
  display: inline-flex;
@@ -317,9 +401,10 @@ input[type=button], input[type=submit], input[type=reset] {
317
401
  justify-content: center;
318
402
  position: relative;
319
403
  cursor: help;
404
+ font-family: var(--font-thin);
320
405
  font-size: var(--font-scale);
321
406
  line-height: 1.5;
322
- padding: 4px 8px;
407
+ padding: 2px 2px;
323
408
  white-space: nowrap;
324
409
  box-sizing: border-box;
325
410
  }
@@ -346,11 +431,10 @@ input[type=button], input[type=submit], input[type=reset] {
346
431
 
347
432
  .week.has-events {
348
433
  border-color: var(--visited-color);
349
- color: var(--text-color);
434
+ background-color: var(--blockquote-color);
435
+ color: inherit;
350
436
  font-size: var(--font-scale);
351
- min-width: auto;
352
- width: auto;
353
- padding: 4px 10px;
437
+ padding: 0;
354
438
  }
355
439
 
356
440
  .week.has-events.future {
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-bear-theme
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - knhash
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-07-16 00:00:00.000000000 Z
10
+ date: 2025-07-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: jekyll
@@ -107,6 +107,7 @@ files:
107
107
  - assets/images/circle-about-tr.png
108
108
  - assets/images/circle-about.ico
109
109
  - assets/images/circle-about.png
110
+ - assets/life-carousel.js
110
111
  - assets/style.css
111
112
  homepage: https://knhash.in/jekyllBear
112
113
  licenses: