docyard 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +41 -1
  4. data/LICENSE.vscode-icons +42 -0
  5. data/README.md +57 -8
  6. data/lib/docyard/asset_handler.rb +33 -0
  7. data/lib/docyard/components/base_processor.rb +24 -0
  8. data/lib/docyard/components/callout_processor.rb +121 -0
  9. data/lib/docyard/components/code_block_processor.rb +55 -0
  10. data/lib/docyard/components/code_detector.rb +59 -0
  11. data/lib/docyard/components/icon_detector.rb +57 -0
  12. data/lib/docyard/components/icon_processor.rb +51 -0
  13. data/lib/docyard/components/registry.rb +34 -0
  14. data/lib/docyard/components/tabs_parser.rb +60 -0
  15. data/lib/docyard/components/tabs_processor.rb +44 -0
  16. data/lib/docyard/config/validator.rb +171 -0
  17. data/lib/docyard/config.rb +133 -0
  18. data/lib/docyard/constants.rb +28 -0
  19. data/lib/docyard/errors.rb +54 -0
  20. data/lib/docyard/file_watcher.rb +2 -2
  21. data/lib/docyard/icons/LICENSE.phosphor +21 -0
  22. data/lib/docyard/icons/file_types.rb +92 -0
  23. data/lib/docyard/icons/phosphor.rb +63 -0
  24. data/lib/docyard/icons.rb +40 -0
  25. data/lib/docyard/initializer.rb +27 -2
  26. data/lib/docyard/language_mapping.rb +52 -0
  27. data/lib/docyard/logging.rb +43 -0
  28. data/lib/docyard/markdown.rb +14 -3
  29. data/lib/docyard/rack_application.rb +100 -13
  30. data/lib/docyard/renderer.rb +40 -5
  31. data/lib/docyard/router.rb +13 -8
  32. data/lib/docyard/routing/resolution_result.rb +31 -0
  33. data/lib/docyard/server.rb +5 -2
  34. data/lib/docyard/sidebar/file_system_scanner.rb +77 -0
  35. data/lib/docyard/sidebar/renderer.rb +110 -0
  36. data/lib/docyard/sidebar/title_extractor.rb +25 -0
  37. data/lib/docyard/sidebar/tree_builder.rb +59 -0
  38. data/lib/docyard/sidebar_builder.rb +58 -0
  39. data/lib/docyard/templates/assets/css/code.css +362 -0
  40. data/lib/docyard/templates/assets/css/components/callout.css +169 -0
  41. data/lib/docyard/templates/assets/css/components/code-block.css +196 -0
  42. data/lib/docyard/templates/assets/css/components/icon.css +16 -0
  43. data/lib/docyard/templates/assets/css/components/logo.css +44 -0
  44. data/lib/docyard/templates/assets/css/components/navigation.css +258 -0
  45. data/lib/docyard/templates/assets/css/components/tabs.css +298 -0
  46. data/lib/docyard/templates/assets/css/components/theme-toggle.css +61 -0
  47. data/lib/docyard/templates/assets/css/layout.css +283 -0
  48. data/lib/docyard/templates/assets/css/main.css +10 -4
  49. data/lib/docyard/templates/assets/css/markdown.css +200 -0
  50. data/lib/docyard/templates/assets/css/reset.css +63 -0
  51. data/lib/docyard/templates/assets/css/typography.css +97 -0
  52. data/lib/docyard/templates/assets/css/variables.css +205 -0
  53. data/lib/docyard/templates/assets/favicon.svg +16 -0
  54. data/lib/docyard/templates/assets/js/components/code-block.js +162 -0
  55. data/lib/docyard/templates/assets/js/components/tabs.js +338 -0
  56. data/lib/docyard/templates/assets/js/theme.js +209 -1
  57. data/lib/docyard/templates/assets/logo-dark.svg +4 -0
  58. data/lib/docyard/templates/assets/logo.svg +12 -0
  59. data/lib/docyard/templates/config/docyard.yml.erb +20 -0
  60. data/lib/docyard/templates/layouts/default.html.erb +69 -19
  61. data/lib/docyard/templates/markdown/components/callouts.md.erb +204 -0
  62. data/lib/docyard/templates/markdown/components/icons.md.erb +125 -0
  63. data/lib/docyard/templates/markdown/components/tabs.md.erb +686 -0
  64. data/lib/docyard/templates/markdown/configuration.md.erb +202 -0
  65. data/lib/docyard/templates/markdown/core-concepts/file-structure.md.erb +61 -0
  66. data/lib/docyard/templates/markdown/core-concepts/markdown.md.erb +90 -0
  67. data/lib/docyard/templates/markdown/getting-started/installation.md.erb +43 -0
  68. data/lib/docyard/templates/markdown/getting-started/introduction.md.erb +30 -0
  69. data/lib/docyard/templates/markdown/getting-started/quick-start.md.erb +56 -0
  70. data/lib/docyard/templates/markdown/index.md.erb +78 -14
  71. data/lib/docyard/templates/partials/_callout.html.erb +11 -0
  72. data/lib/docyard/templates/partials/_code_block.html.erb +6 -0
  73. data/lib/docyard/templates/partials/_icon.html.erb +1 -0
  74. data/lib/docyard/templates/partials/_icon_file_extension.html.erb +1 -0
  75. data/lib/docyard/templates/partials/_icons.html.erb +11 -0
  76. data/lib/docyard/templates/partials/_nav_group.html.erb +7 -0
  77. data/lib/docyard/templates/partials/_nav_item.html.erb +3 -0
  78. data/lib/docyard/templates/partials/_nav_leaf.html.erb +1 -0
  79. data/lib/docyard/templates/partials/_nav_list.html.erb +3 -0
  80. data/lib/docyard/templates/partials/_nav_section.html.erb +6 -0
  81. data/lib/docyard/templates/partials/_sidebar.html.erb +6 -0
  82. data/lib/docyard/templates/partials/_sidebar_footer.html.erb +11 -0
  83. data/lib/docyard/templates/partials/_tabs.html.erb +40 -0
  84. data/lib/docyard/templates/partials/_theme_toggle.html.erb +13 -0
  85. data/lib/docyard/utils/path_resolver.rb +30 -0
  86. data/lib/docyard/utils/text_formatter.rb +22 -0
  87. data/lib/docyard/version.rb +1 -1
  88. data/lib/docyard.rb +16 -4
  89. metadata +71 -3
  90. data/lib/docyard/templates/assets/css/syntax.css +0 -116
  91. data/lib/docyard/templates/markdown/getting-started.md.erb +0 -40
@@ -0,0 +1,338 @@
1
+ /**
2
+ * TabsManager - Manages tab component interactions
3
+ *
4
+ * @class TabsManager
5
+ */
6
+ class TabsManager {
7
+ /**
8
+ * Create a TabsManager instance
9
+ * @param {HTMLElement} container - The .docyard-tabs container element
10
+ */
11
+ constructor(container) {
12
+ if (!container) return;
13
+
14
+ this.container = container;
15
+ this.tabListWrapper = container.querySelector('.docyard-tabs__list-wrapper');
16
+ this.tabList = container.querySelector('[role="tablist"]');
17
+ this.tabs = Array.from(container.querySelectorAll('[role="tab"]'));
18
+ this.panels = Array.from(container.querySelectorAll('[role="tabpanel"]'));
19
+ this.indicator = container.querySelector('.docyard-tabs__indicator');
20
+ this.activeIndex = 0;
21
+ this.groupId = container.getAttribute('data-tabs');
22
+
23
+ this.handleTabClick = this.handleTabClick.bind(this);
24
+ this.handleKeyDown = this.handleKeyDown.bind(this);
25
+ this.handleResize = this.handleResize.bind(this);
26
+ this.handleScroll = this.handleScroll.bind(this);
27
+
28
+ this.init();
29
+ }
30
+
31
+ /**
32
+ * Initialize the tabs component
33
+ */
34
+ init() {
35
+ if (!this.tabList || this.tabs.length === 0 || this.panels.length === 0) {
36
+ return;
37
+ }
38
+
39
+ this.createScrollIndicators();
40
+ this.loadPreference();
41
+ this.attachEventListeners();
42
+ this.activateTab(this.activeIndex, false);
43
+ this.updateIndicator();
44
+ this.updateScrollIndicators();
45
+ }
46
+
47
+ /**
48
+ * Create scroll indicator elements
49
+ */
50
+ createScrollIndicators() {
51
+ if (!this.tabListWrapper || !this.tabList) return;
52
+
53
+ this.leftIndicator = document.createElement('div');
54
+ this.leftIndicator.className = 'docyard-tabs__scroll-indicator docyard-tabs__scroll-indicator--left';
55
+ this.tabListWrapper.insertBefore(this.leftIndicator, this.tabList);
56
+
57
+ this.rightIndicator = document.createElement('div');
58
+ this.rightIndicator.className = 'docyard-tabs__scroll-indicator docyard-tabs__scroll-indicator--right';
59
+ this.tabListWrapper.appendChild(this.rightIndicator);
60
+ }
61
+
62
+ /**
63
+ * Attach all event listeners
64
+ */
65
+ attachEventListeners() {
66
+ this.tabs.forEach((tab, index) => {
67
+ tab.addEventListener('click', () => this.handleTabClick(index));
68
+ });
69
+
70
+ this.tabList.addEventListener('keydown', this.handleKeyDown);
71
+
72
+ this.tabList.addEventListener('scroll', this.handleScroll);
73
+
74
+ window.addEventListener('resize', this.handleResize);
75
+ }
76
+
77
+ /**
78
+ * Handle tab click
79
+ * @param {number} index - Index of clicked tab
80
+ */
81
+ handleTabClick(index) {
82
+ if (index === this.activeIndex) return;
83
+
84
+ this.activateTab(index, true);
85
+ this.savePreference(index);
86
+ }
87
+
88
+ /**
89
+ * Handle keyboard navigation
90
+ * @param {KeyboardEvent} event - Keyboard event
91
+ */
92
+ handleKeyDown(event) {
93
+ const { key } = event;
94
+
95
+ if (key === 'ArrowLeft' || key === 'ArrowRight') {
96
+ event.preventDefault();
97
+
98
+ if (key === 'ArrowLeft') {
99
+ this.activatePreviousTab();
100
+ } else {
101
+ this.activateNextTab();
102
+ }
103
+
104
+ this.tabs[this.activeIndex].focus();
105
+ }
106
+
107
+ if (key === 'Home') {
108
+ event.preventDefault();
109
+ this.activateTab(0, true);
110
+ this.tabs[0].focus();
111
+ }
112
+
113
+ if (key === 'End') {
114
+ event.preventDefault();
115
+ const lastIndex = this.tabs.length - 1;
116
+ this.activateTab(lastIndex, true);
117
+ this.tabs[lastIndex].focus();
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Handle scroll event - update scroll indicators
123
+ */
124
+ handleScroll() {
125
+ if (this.scrollTimeout) {
126
+ cancelAnimationFrame(this.scrollTimeout);
127
+ }
128
+
129
+ this.scrollTimeout = requestAnimationFrame(() => {
130
+ this.updateScrollIndicators();
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Handle window resize - update indicator and scroll indicators
136
+ */
137
+ handleResize() {
138
+ if (this.resizeTimeout) {
139
+ cancelAnimationFrame(this.resizeTimeout);
140
+ }
141
+
142
+ this.resizeTimeout = requestAnimationFrame(() => {
143
+ this.updateIndicator(false);
144
+ this.updateScrollIndicators();
145
+ });
146
+ }
147
+
148
+ /**
149
+ * Activate a specific tab
150
+ * @param {number} index - Index of tab to activate
151
+ * @param {boolean} animate - Whether to animate the transition
152
+ */
153
+ activateTab(index, animate = true) {
154
+ if (index < 0 || index >= this.tabs.length) return;
155
+
156
+ const previousIndex = this.activeIndex;
157
+ this.activeIndex = index;
158
+
159
+ this.tabs.forEach((tab, i) => {
160
+ const isActive = i === index;
161
+ tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
162
+ tab.setAttribute('tabindex', isActive ? '0' : '-1');
163
+ });
164
+
165
+ this.panels.forEach((panel, i) => {
166
+ const isActive = i === index;
167
+ panel.setAttribute('aria-hidden', isActive ? 'false' : 'true');
168
+ });
169
+
170
+ this.updateIndicator(animate);
171
+
172
+ if (previousIndex !== index) {
173
+ this.savePreference(index);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Activate the next tab (wraps around)
179
+ */
180
+ activateNextTab() {
181
+ const nextIndex = (this.activeIndex + 1) % this.tabs.length;
182
+ this.activateTab(nextIndex, true);
183
+ }
184
+
185
+ /**
186
+ * Activate the previous tab (wraps around)
187
+ */
188
+ activatePreviousTab() {
189
+ const prevIndex = (this.activeIndex - 1 + this.tabs.length) % this.tabs.length;
190
+ this.activateTab(prevIndex, true);
191
+ }
192
+
193
+ /**
194
+ * Update the visual indicator position
195
+ * @param {boolean} animate - Whether to animate the transition
196
+ */
197
+ updateIndicator(animate = true) {
198
+ if (!this.indicator || !this.tabs[this.activeIndex]) return;
199
+
200
+ const activeTab = this.tabs[this.activeIndex];
201
+ const tabListRect = this.tabList.getBoundingClientRect();
202
+ const activeTabRect = activeTab.getBoundingClientRect();
203
+
204
+ const left = activeTabRect.left - tabListRect.left + this.tabList.scrollLeft;
205
+ const width = activeTabRect.width;
206
+
207
+ this.indicator.style.width = `${width}px`;
208
+ this.indicator.style.transform = `translateX(${left}px)`;
209
+
210
+ if (!animate) {
211
+ this.indicator.style.transition = 'none';
212
+ void this.indicator.offsetWidth;
213
+ this.indicator.style.transition = '';
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Update scroll indicators visibility based on scroll position
219
+ */
220
+ updateScrollIndicators() {
221
+ if (!this.tabList || !this.leftIndicator || !this.rightIndicator) return;
222
+
223
+ const { scrollLeft, scrollWidth, clientWidth } = this.tabList;
224
+ const hasOverflow = scrollWidth > clientWidth;
225
+
226
+ if (!hasOverflow) {
227
+ this.leftIndicator.classList.remove('is-visible');
228
+ this.rightIndicator.classList.remove('is-visible');
229
+ return;
230
+ }
231
+
232
+ const canScrollLeft = scrollLeft > 5;
233
+ if (canScrollLeft) {
234
+ this.leftIndicator.classList.add('is-visible');
235
+ } else {
236
+ this.leftIndicator.classList.remove('is-visible');
237
+ }
238
+
239
+ const canScrollRight = scrollLeft < scrollWidth - clientWidth - 5;
240
+ if (canScrollRight) {
241
+ this.rightIndicator.classList.add('is-visible');
242
+ } else {
243
+ this.rightIndicator.classList.remove('is-visible');
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Load user preference from localStorage
249
+ */
250
+ loadPreference() {
251
+ try {
252
+ const preferredTab = localStorage.getItem('docyard-preferred-pm');
253
+ if (!preferredTab) return;
254
+
255
+ const index = this.tabs.findIndex(tab =>
256
+ tab.textContent.trim().toLowerCase() === preferredTab.toLowerCase()
257
+ );
258
+
259
+ if (index !== -1) {
260
+ this.activeIndex = index;
261
+ }
262
+ } catch (error) {
263
+ console.warn('Could not load tab preference:', error);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Save user preference to localStorage
269
+ * @param {number} index - Index of active tab
270
+ */
271
+ savePreference(index) {
272
+ if (index < 0 || index >= this.tabs.length) return;
273
+
274
+ try {
275
+ const tabName = this.tabs[index].textContent.trim().toLowerCase();
276
+ localStorage.setItem('docyard-preferred-pm', tabName);
277
+ } catch (error) {
278
+ console.warn('Could not save tab preference:', error);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Activate tab by name
284
+ * @param {string} name - Name of tab to activate
285
+ */
286
+ activateTabByName(name) {
287
+ const index = this.tabs.findIndex(tab =>
288
+ tab.textContent.trim().toLowerCase() === name.toLowerCase()
289
+ );
290
+
291
+ if (index !== -1) {
292
+ this.activateTab(index, true);
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Cleanup - remove event listeners
298
+ */
299
+ destroy() {
300
+ this.tabs.forEach((tab, index) => {
301
+ tab.removeEventListener('click', () => this.handleTabClick(index));
302
+ });
303
+
304
+ this.tabList.removeEventListener('keydown', this.handleKeyDown);
305
+ this.tabList.removeEventListener('scroll', this.handleScroll);
306
+ window.removeEventListener('resize', this.handleResize);
307
+
308
+ if (this.resizeTimeout) {
309
+ cancelAnimationFrame(this.resizeTimeout);
310
+ }
311
+
312
+ if (this.scrollTimeout) {
313
+ cancelAnimationFrame(this.scrollTimeout);
314
+ }
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Auto-initialize all tabs on page load
320
+ */
321
+ function initializeTabs() {
322
+ const tabsContainers = document.querySelectorAll('.docyard-tabs');
323
+
324
+ tabsContainers.forEach(container => {
325
+ new TabsManager(container);
326
+ });
327
+ }
328
+
329
+ // Initialize on DOM ready
330
+ if (document.readyState === 'loading') {
331
+ document.addEventListener('DOMContentLoaded', initializeTabs);
332
+ } else {
333
+ initializeTabs();
334
+ }
335
+
336
+ if (typeof module !== 'undefined' && module.exports) {
337
+ module.exports = { TabsManager };
338
+ }
@@ -1 +1,209 @@
1
- console.log('Docyard loaded');
1
+ // Docyard Theme JavaScript
2
+
3
+ (function() {
4
+ 'use strict';
5
+
6
+ function initThemeToggle() {
7
+ const toggle = document.querySelector('.theme-toggle');
8
+ if (!toggle) return;
9
+
10
+ toggle.addEventListener('click', function() {
11
+ const html = document.documentElement;
12
+ const isDark = html.classList.contains('dark');
13
+ const newTheme = isDark ? 'light' : 'dark';
14
+
15
+ html.classList.toggle('dark', !isDark);
16
+ localStorage.setItem('theme', newTheme);
17
+ });
18
+ }
19
+
20
+ function initMobileMenu() {
21
+ const toggle = document.querySelector('.secondary-header-menu');
22
+ const sidebar = document.querySelector('.sidebar');
23
+ const overlay = document.querySelector('.mobile-overlay');
24
+
25
+ if (!toggle || !sidebar || !overlay) return;
26
+
27
+ function openMenu() {
28
+ sidebar.classList.add('is-open');
29
+ overlay.classList.add('is-visible');
30
+ toggle.setAttribute('aria-expanded', 'true');
31
+ document.body.style.overflow = 'hidden';
32
+ }
33
+
34
+ function closeMenu() {
35
+ sidebar.classList.remove('is-open');
36
+ overlay.classList.remove('is-visible');
37
+ toggle.setAttribute('aria-expanded', 'false');
38
+ document.body.style.overflow = '';
39
+ }
40
+
41
+ function toggleMenu() {
42
+ if (sidebar.classList.contains('is-open')) {
43
+ closeMenu();
44
+ } else {
45
+ openMenu();
46
+ }
47
+ }
48
+
49
+ toggle.addEventListener('click', toggleMenu);
50
+ overlay.addEventListener('click', closeMenu);
51
+
52
+ document.addEventListener('keydown', function(e) {
53
+ if (e.key === 'Escape' && sidebar.classList.contains('is-open')) {
54
+ closeMenu();
55
+ }
56
+ });
57
+
58
+ sidebar.querySelectorAll('a').forEach(function(link) {
59
+ link.addEventListener('click', closeMenu);
60
+ });
61
+ }
62
+
63
+ function initAccordion() {
64
+ const toggles = document.querySelectorAll('.nav-group-toggle');
65
+
66
+ toggles.forEach(function(toggle) {
67
+ toggle.addEventListener('click', function() {
68
+ const expanded = toggle.getAttribute('aria-expanded') === 'true';
69
+ const children = toggle.nextElementSibling;
70
+
71
+ if (!children || !children.classList.contains('nav-group-children')) {
72
+ return;
73
+ }
74
+
75
+ toggle.setAttribute('aria-expanded', !expanded);
76
+ children.classList.toggle('collapsed');
77
+
78
+ if (expanded) {
79
+ children.style.maxHeight = '0';
80
+ } else {
81
+ children.style.maxHeight = children.scrollHeight + 'px';
82
+ }
83
+ });
84
+ });
85
+
86
+ document.querySelectorAll('.nav-group-children.collapsed').forEach(function(el) {
87
+ el.style.maxHeight = '0';
88
+ });
89
+ }
90
+
91
+ function initSidebarScroll() {
92
+ const scrollContainer = document.querySelector('.sidebar nav');
93
+ if (!scrollContainer) return;
94
+
95
+ const STORAGE_KEY = 'docyard_sidebar_scroll';
96
+ const savedPosition = sessionStorage.getItem(STORAGE_KEY);
97
+
98
+ if (savedPosition) {
99
+ const position = parseInt(savedPosition, 10);
100
+ scrollContainer.scrollTop = position;
101
+
102
+ setTimeout(function() {
103
+ scrollContainer.scrollTop = position;
104
+ }, 100);
105
+ } else {
106
+ const activeLink = scrollContainer.querySelector('a.active');
107
+ if (activeLink) {
108
+ setTimeout(function() {
109
+ activeLink.scrollIntoView({
110
+ behavior: 'instant',
111
+ block: 'center'
112
+ });
113
+ }, 50);
114
+ }
115
+ }
116
+
117
+ scrollContainer.querySelectorAll('a').forEach(function(link) {
118
+ link.addEventListener('click', function() {
119
+ sessionStorage.setItem(STORAGE_KEY, scrollContainer.scrollTop);
120
+ });
121
+ });
122
+
123
+ let scrollTimeout;
124
+ scrollContainer.addEventListener('scroll', function() {
125
+ clearTimeout(scrollTimeout);
126
+ scrollTimeout = setTimeout(function() {
127
+ sessionStorage.setItem(STORAGE_KEY, scrollContainer.scrollTop);
128
+ }, 150);
129
+ });
130
+
131
+ const logo = document.querySelector('.header-logo');
132
+ if (logo) {
133
+ logo.addEventListener('click', function() {
134
+ sessionStorage.removeItem(STORAGE_KEY);
135
+ scrollContainer.scrollTop = 0;
136
+ });
137
+ }
138
+ }
139
+
140
+ function initScrollBehavior() {
141
+ const header = document.querySelector('.header');
142
+ const secondaryHeader = document.querySelector('.secondary-header');
143
+
144
+ if (!header || !secondaryHeader) return;
145
+
146
+ let lastScrollTop = 0;
147
+ let ticking = false;
148
+
149
+ function isMobile() {
150
+ return window.innerWidth <= 1024;
151
+ }
152
+
153
+ function updateHeaders() {
154
+ if (!isMobile()) {
155
+ header.classList.remove('hide-on-scroll');
156
+ secondaryHeader.classList.remove('shift-up');
157
+ ticking = false;
158
+ return;
159
+ }
160
+
161
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
162
+
163
+ if (scrollTop > lastScrollTop && scrollTop > 100) {
164
+ header.classList.add('hide-on-scroll');
165
+ secondaryHeader.classList.add('shift-up');
166
+ } else if (scrollTop < lastScrollTop) {
167
+ header.classList.remove('hide-on-scroll');
168
+ secondaryHeader.classList.remove('shift-up');
169
+ }
170
+
171
+ lastScrollTop = scrollTop <= 0 ? 0 : scrollTop;
172
+ ticking = false;
173
+ }
174
+
175
+ window.addEventListener('scroll', function() {
176
+ if (!ticking) {
177
+ window.requestAnimationFrame(updateHeaders);
178
+ ticking = true;
179
+ }
180
+ });
181
+
182
+ window.addEventListener('resize', function() {
183
+ if (!isMobile()) {
184
+ header.classList.remove('hide-on-scroll');
185
+ secondaryHeader.classList.remove('shift-up');
186
+ }
187
+ });
188
+ }
189
+
190
+ if ('scrollRestoration' in history) {
191
+ history.scrollRestoration = 'manual';
192
+ }
193
+
194
+ if (document.readyState === 'loading') {
195
+ document.addEventListener('DOMContentLoaded', function() {
196
+ initThemeToggle();
197
+ initMobileMenu();
198
+ initAccordion();
199
+ initSidebarScroll();
200
+ initScrollBehavior();
201
+ });
202
+ } else {
203
+ initThemeToggle();
204
+ initMobileMenu();
205
+ initAccordion();
206
+ initSidebarScroll();
207
+ initScrollBehavior();
208
+ }
209
+ })();
@@ -0,0 +1,4 @@
1
+ <svg width="531" height="769" viewBox="0 0 531 769" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M359.643 59.1798C402.213 89.4398 449.713 123.6 502.063 160.99C510.793 167.23 515.873 170.31 519.293 178.05C523.253 187.02 521.733 198.11 515.883 205.77C513.77 208.536 510.93 211.2 507.363 213.76C379.643 305.353 309.413 355.73 296.673 364.89C287.987 371.136 282.07 374.8 278.923 375.88C269.703 379.026 260.263 378.636 250.603 374.71C248.243 373.75 244.497 371.416 239.363 367.71C199.963 339.29 177.32 322.99 171.433 318.81C128.863 288.54 81.3733 254.39 29.0233 216.99C20.2833 210.75 15.2033 207.67 11.7833 199.93C7.82332 190.96 9.34332 179.87 15.1933 172.21C17.3067 169.443 20.1467 166.78 23.7133 164.22C151.433 72.6264 221.663 22.2498 234.403 13.0898C243.09 6.84309 249.007 3.17976 252.153 2.09976C261.373 -1.04691 270.813 -0.656912 280.473 3.26976C282.833 4.22976 286.58 6.56309 291.713 10.2698C331.113 38.6898 353.757 54.9931 359.643 59.1798Z" fill="#DC2626"/>
3
+ <path d="M467.383 298.01C483.943 286.23 505.033 289.93 519.063 303.51C524.457 308.723 528.033 314.713 529.793 321.48C530.433 323.92 530.733 330.946 530.693 342.56C530.647 356.206 530.657 427.233 530.723 555.64C530.723 566.633 530.513 573 530.093 574.74C527.033 587.29 518.333 592.61 506.693 601.06C504.313 602.786 430.877 656.346 286.383 761.74C275.623 769.59 261.793 770.79 250.113 764.36C249.18 763.846 245.86 761.513 240.153 757.36C150.56 692.066 74.8667 637.046 13.0733 592.3C6.70001 587.68 2.65667 581.73 0.943337 574.45C0.316671 571.783 0.00333476 564.803 0.00333476 553.51C-0.00333191 421.323 -4.06895e-06 348.98 0.0133293 336.48C0.0133293 332.84 -0.0766665 327.18 0.783334 323.18C4.59333 305.51 20.1033 293.29 37.4533 291.15C42.9467 290.476 48.8667 291.276 55.2133 293.55C58.28 294.643 63.3533 297.8 70.4333 303.02C75.98 307.113 82.4433 311.78 89.8233 317.02C128.563 344.526 178.703 380.303 240.243 424.35C242.73 426.13 245.853 428.246 249.613 430.7C257.443 435.8 268.453 436.24 277.213 433.14C279.8 432.22 284.54 429.283 291.433 424.33C394.46 350.276 453.11 308.17 467.383 298.01Z" fill="#E5E5E5"/>
4
+ </svg>
@@ -0,0 +1,12 @@
1
+ <svg width="531" height="769" viewBox="0 0 531 769" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <style>
4
+ @media (prefers-color-scheme: dark) {
5
+ path[fill="#1E1E1D"] { fill: #E5E5E5; }
6
+ path[fill="#AB333D"] { fill: #DC2626; }
7
+ }
8
+ </style>
9
+ </defs>
10
+ <path d="M359.643 59.1798C402.213 89.4398 449.713 123.6 502.063 160.99C510.793 167.23 515.873 170.31 519.293 178.05C523.253 187.02 521.733 198.11 515.883 205.77C513.77 208.536 510.93 211.2 507.363 213.76C379.643 305.353 309.413 355.73 296.673 364.89C287.987 371.136 282.07 374.8 278.923 375.88C269.703 379.026 260.263 378.636 250.603 374.71C248.243 373.75 244.497 371.416 239.363 367.71C199.963 339.29 177.32 322.99 171.433 318.81C128.863 288.54 81.3733 254.39 29.0233 216.99C20.2833 210.75 15.2033 207.67 11.7833 199.93C7.82332 190.96 9.34332 179.87 15.1933 172.21C17.3067 169.443 20.1467 166.78 23.7133 164.22C151.433 72.6264 221.663 22.2498 234.403 13.0898C243.09 6.84309 249.007 3.17976 252.153 2.09976C261.373 -1.04691 270.813 -0.656912 280.473 3.26976C282.833 4.22976 286.58 6.56309 291.713 10.2698C331.113 38.6898 353.757 54.9931 359.643 59.1798Z" fill="#AB333D"/>
11
+ <path d="M467.383 298.01C483.943 286.23 505.033 289.93 519.063 303.51C524.457 308.723 528.033 314.713 529.793 321.48C530.433 323.92 530.733 330.946 530.693 342.56C530.647 356.206 530.657 427.233 530.723 555.64C530.723 566.633 530.513 573 530.093 574.74C527.033 587.29 518.333 592.61 506.693 601.06C504.313 602.786 430.877 656.346 286.383 761.74C275.623 769.59 261.793 770.79 250.113 764.36C249.18 763.846 245.86 761.513 240.153 757.36C150.56 692.066 74.8667 637.046 13.0733 592.3C6.70001 587.68 2.65667 581.73 0.943337 574.45C0.316671 571.783 0.00333476 564.803 0.00333476 553.51C-0.00333191 421.323 -4.06895e-06 348.98 0.0133293 336.48C0.0133293 332.84 -0.0766665 327.18 0.783334 323.18C4.59333 305.51 20.1033 293.29 37.4533 291.15C42.9467 290.476 48.8667 291.276 55.2133 293.55C58.28 294.643 63.3533 297.8 70.4333 303.02C75.98 307.113 82.4433 311.78 89.8233 317.02C128.563 344.526 178.703 380.303 240.243 424.35C242.73 426.13 245.853 428.246 249.613 430.7C257.443 435.8 268.453 436.24 277.213 433.14C279.8 432.22 284.54 429.283 291.433 424.33C394.46 350.276 453.11 308.17 467.383 298.01Z" fill="#1E1E1D"/>
12
+ </svg>
@@ -0,0 +1,20 @@
1
+ # Docyard Configuration
2
+ # All fields are optional - remove what you don't need
3
+
4
+ site:
5
+ title: "My Documentation"
6
+ description: "Documentation for my project"
7
+
8
+ # Custom branding (paths relative to docs/ directory, or use URLs)
9
+ branding:
10
+ # logo: "assets/logo.svg" # or "https://cdn.example.com/logo.svg" (defaults to Docyard logo)
11
+ # logo_dark: "assets/logo-dark.svg" # Dark mode logo (optional)
12
+ # favicon: "assets/favicon.svg" # Browser tab icon (defaults to Docyard favicon)
13
+ # appearance:
14
+ # logo: true # Show logo (true/false)
15
+ # title: true # Show site title (true/false)
16
+
17
+ build:
18
+ output_dir: "dist"
19
+ base_url: "/"
20
+ clean: true
@@ -1,29 +1,79 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title><%= @page_title || 'Documentation' %></title>
7
- <link rel="stylesheet" href="/assets/css/main.css">
8
- <link rel="stylesheet" href="/assets/css/syntax.css">
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="description" content="<%= @site_description %>">
7
+ <title><%= @page_title %> | <%= @site_title %></title>
8
+ <link rel="icon" href="<%= asset_path(@favicon) %>" type="image/svg+xml">
9
+
10
+ <!-- Prevent flash of wrong theme -->
11
+ <script>
12
+ (function() {
13
+ const theme = localStorage.getItem('theme') ||
14
+ (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
15
+ document.documentElement.classList.toggle('dark', theme === 'dark');
16
+ })();
17
+ </script>
18
+
19
+ <link rel="stylesheet" href="/assets/css/main.css">
9
20
  </head>
10
21
  <body>
11
- <header>
12
- <h1>Documentation</h1>
13
- </header>
22
+ <!-- Skip to main content for accessibility -->
23
+ <a href="#main-content" class="skip-link">Skip to main content</a>
24
+
25
+ <!-- Primary Header -->
26
+ <header class="header">
27
+ <div class="header-content">
28
+ <a href="/" class="header-logo">
29
+ <% if @display_logo %>
30
+ <div class="site-logo-container">
31
+ <% if @logo %>
32
+ <img src="<%= asset_path(@logo) %>" alt="<%= @site_title %>" class="site-logo site-logo-light">
33
+ <% end %>
34
+ <% if @logo_dark %>
35
+ <img src="<%= asset_path(@logo_dark) %>" alt="<%= @site_title %>" class="site-logo site-logo-dark">
36
+ <% else %>
37
+ <img src="<%= asset_path(@logo) %>" alt="<%= @site_title %>" class="site-logo site-logo-dark">
38
+ <% end %>
39
+ </div>
40
+ <% end %>
41
+ <% if @display_title %>
42
+ <span class="header-title"><%= @site_title %></span>
43
+ <% end %>
44
+ </a>
45
+
46
+ <%= render_partial('_theme_toggle') %>
47
+ </div>
48
+ </header>
49
+
50
+ <!-- Secondary Header (Mobile Navigation) -->
51
+ <div class="secondary-header">
52
+ <button class="secondary-header-menu" aria-label="Toggle navigation menu" aria-expanded="false">
53
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="currentColor" width="18" height="18">
54
+ <path d="M32,64a8,8,0,0,1,8-8H216a8,8,0,0,1,0,16H40A8,8,0,0,1,32,64Zm8,48H168a8,8,0,0,0,0-16H40a8,8,0,0,0,0,16Zm176,24H40a8,8,0,0,0,0,16H216a8,8,0,0,0,0-16Zm-48,40H40a8,8,0,0,0,0,16H168a8,8,0,0,0,0-16Z"></path>
55
+ </svg>
56
+ <span>Menu</span>
57
+ </button>
58
+ </div>
14
59
 
15
- <nav>
16
- <!-- Sidebar will go here -->
17
- </nav>
60
+ <!-- Mobile overlay -->
61
+ <div class="mobile-overlay" aria-hidden="true"></div>
18
62
 
19
- <main>
20
- <%= @content %>
21
- </main>
63
+ <div class="layout">
64
+ <!-- Sidebar navigation -->
65
+ <%= @sidebar_html %>
22
66
 
23
- <footer>
24
- <p>Built with Docyard</p>
25
- </footer>
67
+ <!-- Main content area -->
68
+ <div class="layout-main">
69
+ <main id="main-content" class="content">
70
+ <%= @content %>
71
+ </main>
72
+ </div>
73
+ </div>
26
74
 
27
- <script src="/assets/js/reload.js"></script>
75
+ <script src="/assets/js/theme.js"></script>
76
+ <script src="/assets/js/components.js"></script>
77
+ <script src="/assets/js/reload.js"></script>
28
78
  </body>
29
- </html>
79
+ </html>