bulma-turbo-themes 0.7.4

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.
@@ -0,0 +1,489 @@
1
+ const THEMES = [
2
+ {
3
+ id: 'bulma-light',
4
+ name: 'Bulma Light',
5
+ cssFile: 'assets/css/themes/bulma-light.css',
6
+ icon: 'assets/img/bulma-logo.png',
7
+ },
8
+ {
9
+ id: 'bulma-dark',
10
+ name: 'Bulma Dark',
11
+ cssFile: 'assets/css/themes/bulma-dark.css',
12
+ icon: 'assets/img/bulma-logo-dark.png',
13
+ },
14
+ {
15
+ id: 'catppuccin-latte',
16
+ name: 'Catppuccin Latte',
17
+ cssFile: 'assets/css/themes/catppuccin-latte.css',
18
+ icon: 'assets/img/catppuccin-logo-latte.png',
19
+ },
20
+ {
21
+ id: 'catppuccin-frappe',
22
+ name: 'Catppuccin Frappé',
23
+ cssFile: 'assets/css/themes/catppuccin-frappe.css',
24
+ icon: 'assets/img/catppuccin-logo-latte.png',
25
+ },
26
+ {
27
+ id: 'catppuccin-macchiato',
28
+ name: 'Catppuccin Macchiato',
29
+ cssFile: 'assets/css/themes/catppuccin-macchiato.css',
30
+ icon: 'assets/img/catppuccin-logo-macchiato.png',
31
+ },
32
+ {
33
+ id: 'catppuccin-mocha',
34
+ name: 'Catppuccin Mocha',
35
+ cssFile: 'assets/css/themes/catppuccin-mocha.css',
36
+ icon: 'assets/img/catppuccin-logo-macchiato.png',
37
+ },
38
+ {
39
+ id: 'dracula',
40
+ name: 'Dracula',
41
+ cssFile: 'assets/css/themes/dracula.css',
42
+ icon: 'assets/img/dracula-logo.png',
43
+ },
44
+ {
45
+ id: 'github-light',
46
+ name: 'GitHub Light',
47
+ cssFile: 'assets/css/themes/github-light.css',
48
+ icon: 'assets/img/github-logo-light.png',
49
+ },
50
+ {
51
+ id: 'github-dark',
52
+ name: 'GitHub Dark',
53
+ cssFile: 'assets/css/themes/github-dark.css',
54
+ icon: 'assets/img/github-logo-dark.png',
55
+ },
56
+ ];
57
+ const STORAGE_KEY = 'bulma-theme-flavor';
58
+ const DEFAULT_THEME = 'catppuccin-mocha';
59
+ function getBaseUrl(doc) {
60
+ const baseElement = doc.documentElement;
61
+ const raw = baseElement?.getAttribute('data-baseurl') || '';
62
+ try {
63
+ const u = new URL(raw, 'http://localhost');
64
+ // Only allow same-origin relative paths; strip origin used for parsing
65
+ return u.origin === 'http://localhost' ? u.pathname.replace(/\/$/, '') : '';
66
+ }
67
+ catch {
68
+ return '';
69
+ }
70
+ }
71
+ function applyTheme(doc, themeId) {
72
+ const theme = THEMES.find((t) => t.id === themeId) || THEMES.find((t) => t.id === DEFAULT_THEME);
73
+ const baseUrl = getBaseUrl(doc);
74
+ const flavorLink = doc.getElementById('theme-flavor-css');
75
+ if (flavorLink) {
76
+ // Build a safe URL relative to base by prepending baseUrl to relative path
77
+ try {
78
+ const fullPath = baseUrl ? `${baseUrl}/${theme.cssFile}` : theme.cssFile;
79
+ const url = new URL(fullPath, 'http://localhost');
80
+ flavorLink.href = url.pathname;
81
+ }
82
+ catch {
83
+ // Ignore invalid URL
84
+ }
85
+ }
86
+ doc.documentElement.setAttribute('data-flavor', themeId);
87
+ // Update trigger button icon
88
+ const triggerIcon = doc.getElementById('theme-flavor-trigger-icon');
89
+ if (triggerIcon) {
90
+ // Clear existing content first
91
+ while (triggerIcon.firstChild) {
92
+ triggerIcon.removeChild(triggerIcon.firstChild);
93
+ }
94
+ if (theme.icon) {
95
+ // Create and append img element (CSP-friendly)
96
+ const img = doc.createElement('img');
97
+ try {
98
+ const fullPath = baseUrl ? `${baseUrl}/${theme.icon}` : theme.icon;
99
+ const url = new URL(fullPath, 'http://localhost');
100
+ img.src = url.pathname;
101
+ }
102
+ catch {
103
+ // Ignore invalid URL
104
+ }
105
+ img.alt = theme.name;
106
+ img.title = theme.name; // Tooltip on hover
107
+ img.width = 28;
108
+ img.height = 28;
109
+ triggerIcon.appendChild(img);
110
+ }
111
+ else {
112
+ // Fallback: show first two letters with circular background
113
+ const span = doc.createElement('span');
114
+ span.textContent = theme.name.substring(0, 2).toUpperCase();
115
+ span.style.fontSize = '12px';
116
+ span.style.fontWeight = 'bold';
117
+ span.style.color = 'var(--theme-text, inherit)';
118
+ span.style.display = 'flex';
119
+ span.style.alignItems = 'center';
120
+ span.style.justifyContent = 'center';
121
+ span.style.width = '28px';
122
+ span.style.height = '28px';
123
+ span.style.borderRadius = '50%';
124
+ span.style.backgroundColor = 'var(--theme-surface-1, #f5f5f5)';
125
+ span.style.border = '1px solid var(--theme-border, #ddd)';
126
+ span.title = theme.name; // Tooltip on hover
127
+ triggerIcon.appendChild(span);
128
+ }
129
+ }
130
+ // Update active state in dropdown
131
+ const dropdownItems = doc.querySelectorAll('#theme-flavor-items .dropdown-item');
132
+ dropdownItems.forEach((item) => {
133
+ if (item.getAttribute('data-theme-id') === themeId) {
134
+ item.classList.add('is-active');
135
+ item.setAttribute('aria-checked', 'true');
136
+ }
137
+ else {
138
+ item.classList.remove('is-active');
139
+ item.setAttribute('aria-checked', 'false');
140
+ }
141
+ });
142
+ }
143
+ export function initTheme(documentObj, windowObj) {
144
+ const savedTheme = windowObj.localStorage.getItem(STORAGE_KEY) || DEFAULT_THEME;
145
+ applyTheme(documentObj, savedTheme);
146
+ }
147
+ export function initNavbar(documentObj) {
148
+ const currentPath = documentObj.location.pathname;
149
+ const navbarItems = documentObj.querySelectorAll('.navbar-item');
150
+ // Find the matching link first
151
+ let matchingItem = null;
152
+ const checkedItems = new Set();
153
+ navbarItems.forEach((item) => {
154
+ const link = item;
155
+ if (link.href) {
156
+ try {
157
+ const linkPath = new URL(link.href).pathname;
158
+ // Remove trailing slashes for comparison
159
+ const normalizedCurrentPath = currentPath.replace(/\/$/, '') || '/';
160
+ const normalizedLinkPath = linkPath.replace(/\/$/, '') || '/';
161
+ checkedItems.add(item);
162
+ if (normalizedCurrentPath === normalizedLinkPath) {
163
+ matchingItem = item;
164
+ }
165
+ }
166
+ catch {
167
+ // Ignore invalid URLs - don't add to checkedItems
168
+ }
169
+ }
170
+ });
171
+ // Clear all active states except the matching one (only for items that were checked)
172
+ navbarItems.forEach((item) => {
173
+ if (item !== matchingItem && checkedItems.has(item)) {
174
+ item.classList.remove('is-active');
175
+ const link = item;
176
+ // Check if removeAttribute exists (for test mocks that might not have it)
177
+ if (link && 'removeAttribute' in link && typeof link.removeAttribute === 'function') {
178
+ link.removeAttribute('aria-current');
179
+ }
180
+ }
181
+ });
182
+ // Set active state for the matching link
183
+ if (matchingItem) {
184
+ matchingItem.classList.add('is-active');
185
+ const link = matchingItem;
186
+ // Check if setAttribute exists (for test mocks that might not have it)
187
+ if (link && 'setAttribute' in link && typeof link.setAttribute === 'function') {
188
+ link.setAttribute('aria-current', 'page');
189
+ }
190
+ }
191
+ // Handle Reports dropdown highlighting
192
+ const reportsLink = documentObj.querySelector('[data-testid="nav-reports"]');
193
+ if (reportsLink) {
194
+ const reportPaths = ['/coverage', '/playwright', '/lighthouse'];
195
+ const normalizedCurrentPath = currentPath.replace(/\/$/, '') || '/';
196
+ const isOnReportsPage = reportPaths.some((path) => normalizedCurrentPath === path || normalizedCurrentPath.startsWith(path + '/'));
197
+ if (isOnReportsPage) {
198
+ reportsLink.classList.add('is-active');
199
+ }
200
+ else {
201
+ reportsLink.classList.remove('is-active');
202
+ }
203
+ }
204
+ }
205
+ window.initNavbar = initNavbar;
206
+ export function wireFlavorSelector(documentObj, windowObj) {
207
+ const abortController = new AbortController();
208
+ const baseUrl = getBaseUrl(documentObj);
209
+ const dropdownContent = documentObj.getElementById('theme-flavor-items');
210
+ const trigger = documentObj.querySelector('.theme-flavor-trigger');
211
+ const dropdown = documentObj.getElementById('theme-flavor-dd');
212
+ if (!dropdownContent || !trigger || !dropdown) {
213
+ return {
214
+ cleanup: () => {
215
+ abortController.abort();
216
+ },
217
+ };
218
+ }
219
+ let currentIndex = -1;
220
+ const menuItems = [];
221
+ // Get current theme to set initial aria-checked state
222
+ const currentThemeId = windowObj.localStorage.getItem(STORAGE_KEY) ||
223
+ documentObj.documentElement.getAttribute('data-flavor') ||
224
+ DEFAULT_THEME;
225
+ // Populate dropdown with theme options
226
+ THEMES.forEach((theme) => {
227
+ const item = documentObj.createElement('a');
228
+ item.href = '#';
229
+ item.className = 'dropdown-item';
230
+ item.setAttribute('data-theme-id', theme.id);
231
+ item.setAttribute('role', 'menuitemradio');
232
+ item.setAttribute('aria-label', theme.name);
233
+ item.setAttribute('tabindex', '-1');
234
+ const isActive = theme.id === currentThemeId;
235
+ item.setAttribute('aria-checked', String(isActive));
236
+ if (isActive) {
237
+ item.classList.add('is-active');
238
+ }
239
+ if (theme.icon) {
240
+ const img = documentObj.createElement('img');
241
+ try {
242
+ const fullPath = baseUrl ? `${baseUrl}/${theme.icon}` : theme.icon;
243
+ const url = new URL(fullPath, 'http://localhost');
244
+ img.src = url.pathname;
245
+ }
246
+ catch {
247
+ // Ignore invalid URL
248
+ }
249
+ img.alt = theme.name;
250
+ img.title = theme.name;
251
+ img.width = 28;
252
+ img.height = 28;
253
+ item.appendChild(img);
254
+ }
255
+ else {
256
+ // Fallback: show first two letters with styled background
257
+ const span = documentObj.createElement('span');
258
+ span.textContent = theme.name.substring(0, 2);
259
+ span.style.fontSize = '14px';
260
+ span.style.fontWeight = 'bold';
261
+ span.style.color = 'var(--theme-text, inherit)';
262
+ item.appendChild(span);
263
+ }
264
+ // Always include a visually hidden full name for screen readers
265
+ const srOnly = documentObj.createElement('span');
266
+ srOnly.textContent = theme.name;
267
+ srOnly.style.position = 'absolute';
268
+ srOnly.style.width = '1px';
269
+ srOnly.style.height = '1px';
270
+ srOnly.style.padding = '0';
271
+ srOnly.style.margin = '-1px';
272
+ srOnly.style.overflow = 'hidden';
273
+ srOnly.style.clip = 'rect(0, 0, 0, 0)';
274
+ srOnly.style.whiteSpace = 'nowrap';
275
+ srOnly.style.border = '0';
276
+ item.appendChild(srOnly);
277
+ item.addEventListener('click', (e) => {
278
+ e.preventDefault();
279
+ applyTheme(documentObj, theme.id);
280
+ windowObj.localStorage.setItem(STORAGE_KEY, theme.id);
281
+ closeDropdown({ restoreFocus: true });
282
+ });
283
+ menuItems.push(item);
284
+ dropdownContent.appendChild(item);
285
+ });
286
+ // Update aria-expanded on trigger
287
+ const updateAriaExpanded = (expanded) => {
288
+ trigger.setAttribute('aria-expanded', String(expanded));
289
+ };
290
+ // Focus management
291
+ const focusMenuItem = (index) => {
292
+ if (index < 0 || index >= menuItems.length)
293
+ return;
294
+ const item = menuItems[index];
295
+ // Set tabindex to -1 for all items
296
+ menuItems.forEach((menuItem) => {
297
+ menuItem.setAttribute('tabindex', '-1');
298
+ });
299
+ // Focus and set tabindex to 0 on current item
300
+ item.setAttribute('tabindex', '0');
301
+ item.focus();
302
+ currentIndex = index;
303
+ };
304
+ const openDropdown = () => {
305
+ dropdown.classList.add('is-active');
306
+ updateAriaExpanded(true);
307
+ currentIndex = -1;
308
+ };
309
+ const closeDropdown = (options = {}) => {
310
+ const { restoreFocus = true } = options;
311
+ dropdown.classList.remove('is-active');
312
+ updateAriaExpanded(false);
313
+ menuItems.forEach((menuItem) => {
314
+ menuItem.setAttribute('tabindex', '-1');
315
+ });
316
+ currentIndex = -1;
317
+ if (restoreFocus) {
318
+ // Only restore focus to trigger when explicitly requested (e.g., selection or Esc)
319
+ trigger.focus();
320
+ }
321
+ };
322
+ // Open dropdown on hover
323
+ dropdown.addEventListener('mouseenter', () => {
324
+ openDropdown();
325
+ }, { signal: abortController.signal });
326
+ // Close dropdown when mouse leaves
327
+ dropdown.addEventListener('mouseleave', () => {
328
+ // Only close if not keyboard navigating
329
+ if (currentIndex === -1) {
330
+ closeDropdown();
331
+ }
332
+ }, { signal: abortController.signal });
333
+ // Toggle dropdown helper function
334
+ const toggleDropdown = (focusFirst = false) => {
335
+ const isActive = dropdown.classList.toggle('is-active');
336
+ updateAriaExpanded(isActive);
337
+ if (!isActive) {
338
+ currentIndex = -1;
339
+ menuItems.forEach((menuItem) => {
340
+ menuItem.setAttribute('tabindex', '-1');
341
+ menuItem.setAttribute('aria-checked', String(menuItem.classList.contains('is-active')));
342
+ });
343
+ }
344
+ else if (focusFirst && menuItems.length > 0) {
345
+ // ...rest of the existing logic
346
+ // When opening via keyboard, focus first item
347
+ focusMenuItem(0);
348
+ }
349
+ };
350
+ // Toggle dropdown on trigger click (for touch devices)
351
+ trigger.addEventListener('click', (e) => {
352
+ e.preventDefault();
353
+ toggleDropdown();
354
+ }, { signal: abortController.signal });
355
+ // Close dropdown when clicking outside
356
+ documentObj.addEventListener('click', (e) => {
357
+ if (!dropdown.contains(e.target)) {
358
+ // Close on any outside click; do not steal focus from the newly clicked element
359
+ closeDropdown({ restoreFocus: false });
360
+ }
361
+ }, { signal: abortController.signal });
362
+ // Handle Escape key globally to close dropdown
363
+ documentObj.addEventListener('keydown', (e) => {
364
+ if (e.key === 'Escape' && dropdown.classList.contains('is-active')) {
365
+ closeDropdown({ restoreFocus: true });
366
+ }
367
+ }, { signal: abortController.signal });
368
+ // Keyboard navigation
369
+ trigger.addEventListener('keydown', (e) => {
370
+ const key = e.key;
371
+ if (key === 'Enter' || key === ' ') {
372
+ e.preventDefault();
373
+ const wasActive = dropdown.classList.contains('is-active');
374
+ if (wasActive) {
375
+ // If already open, close it
376
+ toggleDropdown(false);
377
+ }
378
+ else {
379
+ // If closed, open and focus first item
380
+ toggleDropdown(true);
381
+ }
382
+ }
383
+ else if (key === 'ArrowDown') {
384
+ e.preventDefault();
385
+ if (!dropdown.classList.contains('is-active')) {
386
+ dropdown.classList.add('is-active');
387
+ updateAriaExpanded(true);
388
+ focusMenuItem(0); // Focus first item when opening
389
+ }
390
+ else {
391
+ // If currentIndex is -1 (dropdown opened via mouse or not yet initialized), focus first item
392
+ if (currentIndex < 0) {
393
+ focusMenuItem(0);
394
+ }
395
+ else {
396
+ const nextIndex = currentIndex < menuItems.length - 1 ? currentIndex + 1 : 0;
397
+ focusMenuItem(nextIndex);
398
+ }
399
+ }
400
+ }
401
+ else if (key === 'ArrowUp') {
402
+ e.preventDefault();
403
+ if (!dropdown.classList.contains('is-active')) {
404
+ dropdown.classList.add('is-active');
405
+ updateAriaExpanded(true);
406
+ // Start from last item when opening with ArrowUp
407
+ focusMenuItem(menuItems.length - 1);
408
+ }
409
+ else {
410
+ // If currentIndex is -1 (dropdown opened via mouse), start from last
411
+ const startIndex = currentIndex < 0 ? menuItems.length - 1 : currentIndex;
412
+ const prevIndex = startIndex > 0 ? startIndex - 1 : menuItems.length - 1;
413
+ focusMenuItem(prevIndex);
414
+ }
415
+ }
416
+ }, { signal: abortController.signal });
417
+ // Keyboard navigation on menu items
418
+ menuItems.forEach((item, index) => {
419
+ item.addEventListener('keydown', (e) => {
420
+ const key = e.key;
421
+ if (key === 'ArrowDown') {
422
+ e.preventDefault();
423
+ const nextIndex = index < menuItems.length - 1 ? index + 1 : 0;
424
+ focusMenuItem(nextIndex);
425
+ }
426
+ else if (key === 'ArrowUp') {
427
+ e.preventDefault();
428
+ const prevIndex = index > 0 ? index - 1 : menuItems.length - 1;
429
+ focusMenuItem(prevIndex);
430
+ }
431
+ else if (key === 'Escape') {
432
+ e.preventDefault();
433
+ closeDropdown();
434
+ }
435
+ else if (key === 'Enter' || key === ' ') {
436
+ e.preventDefault();
437
+ item.click();
438
+ }
439
+ else if (key === 'Home') {
440
+ e.preventDefault();
441
+ focusMenuItem(0);
442
+ }
443
+ else if (key === 'End') {
444
+ e.preventDefault();
445
+ focusMenuItem(menuItems.length - 1);
446
+ }
447
+ }, { signal: abortController.signal });
448
+ });
449
+ // Initialize aria-expanded
450
+ updateAriaExpanded(false);
451
+ return {
452
+ cleanup: () => {
453
+ abortController.abort();
454
+ },
455
+ };
456
+ }
457
+ export function enhanceAccessibility(documentObj) {
458
+ const pres = documentObj.querySelectorAll('.highlight > pre');
459
+ pres.forEach((pre) => {
460
+ if (!pre.hasAttribute('tabindex'))
461
+ pre.setAttribute('tabindex', '0');
462
+ if (!pre.hasAttribute('role'))
463
+ pre.setAttribute('role', 'region');
464
+ if (!pre.hasAttribute('aria-label'))
465
+ pre.setAttribute('aria-label', 'Code block');
466
+ });
467
+ }
468
+ // Auto-initialize on DOMContentLoaded
469
+ if (typeof document !== 'undefined' && typeof window !== 'undefined') {
470
+ document.addEventListener('DOMContentLoaded', () => {
471
+ console.warn('Theme switcher initializing...');
472
+ try {
473
+ initTheme(document, window);
474
+ const { cleanup } = wireFlavorSelector(document, window);
475
+ enhanceAccessibility(document);
476
+ // Register cleanup to run on teardown
477
+ const pagehideHandler = () => {
478
+ cleanup();
479
+ window.removeEventListener('pagehide', pagehideHandler);
480
+ };
481
+ window.addEventListener('pagehide', pagehideHandler);
482
+ console.warn('Theme switcher initialized successfully');
483
+ }
484
+ catch (error) {
485
+ console.error('Theme switcher initialization failed:', error);
486
+ }
487
+ });
488
+ }
489
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BulmaTurboThemes
4
+ VERSION = "0.7.4"
5
+ end
6
+
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "bulma-turbo-themes/version"
4
+
5
+ # Jekyll theme for Bulma Turbo Themes
6
+ # Provides accessible theme packs and theme selector for Bulma CSS framework
7
+ module BulmaTurboThemes
8
+ end
9
+
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bulma-turbo-themes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.4
5
+ platform: ruby
6
+ authors:
7
+ - Turbo Coder
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-11-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jekyll
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.5'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.5'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ description: Bulma Turbo Themes provides multiple color schemes (Catppuccin, Dracula,
34
+ GitHub) and an accessible theme selector component for Jekyll sites.
35
+ email:
36
+ - turbocoder13@users.noreply.github.com
37
+ executables: []
38
+ extensions: []
39
+ extra_rdoc_files: []
40
+ files:
41
+ - CHANGELOG.md
42
+ - LICENSE
43
+ - README.md
44
+ - assets/css/custom.css
45
+ - assets/css/themes/bulma-dark.css
46
+ - assets/css/themes/bulma-light.css
47
+ - assets/css/themes/catppuccin-frappe.css
48
+ - assets/css/themes/catppuccin-latte.css
49
+ - assets/css/themes/catppuccin-macchiato.css
50
+ - assets/css/themes/catppuccin-mocha.css
51
+ - assets/css/themes/dracula.css
52
+ - assets/css/themes/github-dark.css
53
+ - assets/css/themes/github-light.css
54
+ - assets/css/themes/global.css
55
+ - assets/img/bulma-logo-dark.png
56
+ - assets/img/bulma-logo.png
57
+ - assets/img/catppuccin-logo-latte.png
58
+ - assets/img/catppuccin-logo-macchiato.png
59
+ - assets/img/dracula-logo.png
60
+ - assets/img/github-logo-dark.png
61
+ - assets/img/github-logo-light.png
62
+ - assets/js/theme-selector.js
63
+ - lib/bulma-turbo-themes.rb
64
+ - lib/bulma-turbo-themes/version.rb
65
+ homepage: https://github.com/TurboCoder13/bulma-turbo-themes
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ bug_tracker_uri: https://github.com/TurboCoder13/bulma-turbo-themes/issues
70
+ changelog_uri: https://github.com/TurboCoder13/bulma-turbo-themes/blob/main/CHANGELOG.md
71
+ documentation_uri: https://turbocoder13.github.io/bulma-turbo-themes/
72
+ homepage_uri: https://github.com/TurboCoder13/bulma-turbo-themes
73
+ source_code_uri: https://github.com/TurboCoder13/bulma-turbo-themes
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 2.6.0
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.5.22
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Modern, accessible theme packs and a drop-in theme selector for Bulma 1.x
93
+ test_files: []