jekyll-calconnect-theme 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a8101b4b6ebc2ce836878b1bfb0bca4a0cf6a6e74dc1e35882437ae36f81dbd2
4
+ data.tar.gz: 9b23833d258824df5b7b5a2dd33f5491833d35ca2491f071ffff44e660586e98
5
+ SHA512:
6
+ metadata.gz: 6cad0f652b298dc0467a383c513e7f83384bc5d1f0c828fb7cff3d25cacde3ebba76e5cc97ddb7cea67980d9464815e545160aaa6fb8bf06e2ecd98ba40e6720
7
+ data.tar.gz: a4194106e49d4f06e26d6566529468a34fb7ea9250875e62155c08732ee0a6438d92373583b9d578ec3cc6baff52270cef6f2affa1c5da8f8688e5acd784770a
data/CLAUDE.md ADDED
@@ -0,0 +1,69 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ This is a shared Jekyll theme gem (`jekyll-calconnect-theme`) used across multiple CalConnect sites (calconnect.org, DEVGUIDE, standards.calconnect.org). Sites consume it via their Gemfile and override layouts/includes as needed.
8
+
9
+ ## Build & Development
10
+
11
+ ```bash
12
+ # Install dependencies
13
+ bundle install
14
+
15
+ # Build the gem
16
+ rake build # outputs to pkg/
17
+
18
+ # Install locally for development
19
+ rake install
20
+ ```
21
+
22
+ This is a theme gem only — there is no standalone site to serve. To preview changes, use the theme in a consumer site's `_config.yml` with a Gemfile pointing at the local path.
23
+
24
+ ## Architecture
25
+
26
+ ### Layout hierarchy
27
+
28
+ ```
29
+ base.html → HTML shell: head, header, footer, Vite JS entry
30
+ └─ default.html → Documentation layout: sidebar nav + article area
31
+ └─ page.html → Pass-through wrapper
32
+ ```
33
+
34
+ ### Frontend pipeline
35
+
36
+ CSS and JS live under `_frontend/` and are built by [jekyll-vite](https://github.com/elixir-vite/vite_ruby) in the consumer site. The theme provides:
37
+ - `_frontend/entrypoints/application.css` — Tailwind v4 config (`@import "tailwindcss"`), `@tailwindcss/typography` plugin, custom theme tokens (primary/accent colors, fonts)
38
+ - `_frontend/entrypoints/application.js` — imports `theme.js` (dark/light toggle with localStorage) and `navigation.js` (mobile menu, active state highlighting), plus sidebar collapsible toggle init
39
+ - `_sass/calconnect/` — supplementary SCSS for layout, navigation, typography, code blocks, tables, dark mode
40
+
41
+ ### Sidebar navigation (default.html layout)
42
+
43
+ The sidebar is fully data-driven from `site.data.navigation_sidebar.sections`. Each section has a `match` string matched against `page.url`. Item types: `link`, `collection` (iterates a Jekyll collection), `collapsible` (expandable with URL filtering), `group` (labeled sub-group), `years` (year list from `site.data.news_years`). The layout has no hardcoded page knowledge — all configuration is in the consumer site's `_data/navigation_sidebar.yml`.
44
+
45
+ ### Includes
46
+
47
+ - `head.html` — meta tags, Google Fonts (Inter + JetBrains Mono), critical inline CSS to prevent FOUC, `{% vite_stylesheet_tag %}`
48
+ - `custom-head.html` — Vite client + JS tag (for dev HMR in consumer sites)
49
+ - `header.html` — fixed top nav bar
50
+ - `footer.html` — copyright bar
51
+ - `breadcrumbs.html` — URL-derived breadcrumb trail
52
+ - `feedback.html` — "Was this helpful?" link
53
+ - `google-analytics.html` — conditional GA snippet (production only)
54
+
55
+ ### Dark mode
56
+
57
+ Class-based (`html.dark`). `theme.js` runs immediately (before DOM ready) to prevent flash, using localStorage > system preference. Tailwind custom variant: `@custom-variant dark (&:where(.dark, .dark *))`.
58
+
59
+ ### Ruby namespace
60
+
61
+ `Jekyll::CalconnectTheme` with version in `lib/jekyll/calconnect/theme/version.rb`. The gemspec requires the version constant from there.
62
+
63
+ ### Gem packaging
64
+
65
+ `gemspec` packages: `{_layouts,_includes,_sass,_frontend,lib}/**/*`, `*.md`, `*.gemspec`. Only runtime dependency is `jekyll ~> 4.3`.
66
+
67
+ ### Key Tailwind notes
68
+
69
+ Uses Tailwind v4 (CSS-first config with `@theme` blocks, not `tailwind.config.js`). Colors use `gray` not `slate` for class compatibility. Primary color scale is indigo-based; accent is cyan.
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # jekyll-calconnect-theme
2
+
3
+ Shared Jekyll theme gem for CalConnect sites (calconnect.org, DEVGUIDE, standards.calconnect.org).
4
+
5
+ ## Usage
6
+
7
+ Add to your site's `Gemfile`:
8
+
9
+ ```ruby
10
+ gem "jekyll-calconnect-theme", git: "git@github.com:calconnect/calconnect-theme.git"
11
+ ```
12
+
13
+ Add to your site's `_config.yml`:
14
+
15
+ ```yaml
16
+ theme: jekyll-calconnect-theme
17
+ ```
18
+
19
+ ## Site-specific overrides
20
+
21
+ Any layout or include can be overridden by placing a file with the same name in your site's own `_layouts/` or `_includes/` directory.
@@ -0,0 +1,80 @@
1
+ @import "tailwindcss";
2
+ @plugin "@tailwindcss/typography";
3
+
4
+ /* Enable class-based dark mode */
5
+ @custom-variant dark (&:where(.dark, .dark *));
6
+
7
+ /* Custom theme configuration */
8
+ @theme {
9
+ /* Font families */
10
+ --font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
11
+ --font-mono: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
12
+
13
+ /* Primary colors */
14
+ --color-primary-50: #eef2ff;
15
+ --color-primary-100: #e0e7ff;
16
+ --color-primary-200: #c7d2fe;
17
+ --color-primary-300: #a5b4fc;
18
+ --color-primary-400: #818cf8;
19
+ --color-primary-500: #6366f1;
20
+ --color-primary-600: #4f46e5;
21
+ --color-primary-700: #4338ca;
22
+ --color-primary-800: #3730a3;
23
+ --color-primary-900: #312e81;
24
+
25
+ /* Accent color */
26
+ --color-accent-400: #22d3ee;
27
+ --color-accent-500: #06b6d4;
28
+ }
29
+
30
+ /* Custom base styles */
31
+ @layer base {
32
+ html {
33
+ scroll-behavior: smooth;
34
+ }
35
+
36
+ body {
37
+ font-feature-settings: "kern" 1, "liga" 1;
38
+ -webkit-font-smoothing: antialiased;
39
+ -moz-osx-font-smoothing: grayscale;
40
+ }
41
+
42
+ main {
43
+ padding-top: 64px;
44
+ }
45
+
46
+ ::selection {
47
+ background-color: rgba(99, 102, 241, 0.2);
48
+ }
49
+
50
+ :focus-visible {
51
+ outline: 2px solid #6366f1;
52
+ outline-offset: 2px;
53
+ }
54
+ }
55
+
56
+ /* Custom component styles */
57
+ @layer components {
58
+ .sr-only {
59
+ position: absolute;
60
+ width: 1px;
61
+ height: 1px;
62
+ padding: 0;
63
+ margin: -1px;
64
+ overflow: hidden;
65
+ clip: rect(0, 0, 0, 0);
66
+ white-space: nowrap;
67
+ border-width: 0;
68
+ }
69
+
70
+ .sr-only:focus {
71
+ position: fixed;
72
+ width: auto;
73
+ height: auto;
74
+ padding: 0.5rem 1rem;
75
+ margin: 0;
76
+ overflow: visible;
77
+ clip: auto;
78
+ white-space: normal;
79
+ }
80
+ }
@@ -0,0 +1,12 @@
1
+ import '../js/theme.js'
2
+ import '../js/navigation.js'
3
+
4
+ document.addEventListener('DOMContentLoaded', function() {
5
+ document.querySelectorAll('.nav-toggle').forEach(function(toggle) {
6
+ toggle.addEventListener('click', function() {
7
+ this.classList.toggle('open');
8
+ var subsection = this.nextElementSibling;
9
+ if (subsection) subsection.classList.toggle('collapsed');
10
+ });
11
+ });
12
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Navigation Script
3
+ * Handles mobile menu toggle and active state highlighting
4
+ */
5
+
6
+ (function() {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Initialize mobile menu toggle
11
+ */
12
+ function initMobileMenu() {
13
+ var menuButton = document.getElementById('mobile-menu-btn');
14
+ var mobileMenu = document.getElementById('mobile-menu');
15
+
16
+ if (menuButton && mobileMenu) {
17
+ menuButton.addEventListener('click', function() {
18
+ var isExpanded = menuButton.getAttribute('aria-expanded') === 'true';
19
+ menuButton.setAttribute('aria-expanded', !isExpanded);
20
+ mobileMenu.classList.toggle('hidden');
21
+ });
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Highlight active navigation items
27
+ */
28
+ function initActiveNav() {
29
+ var currentPath = window.location.pathname;
30
+
31
+ // Sidebar navigation
32
+ var sidebarLinks = document.querySelectorAll('.docs-nav .nav-items a');
33
+ sidebarLinks.forEach(function(link) {
34
+ var href = link.getAttribute('href');
35
+ if (href) {
36
+ // Normalize paths for comparison
37
+ var linkPath = href.replace(/\/$/, '');
38
+ var pagePath = currentPath.replace(/\/$/, '');
39
+ if (linkPath === pagePath) {
40
+ link.classList.add('active');
41
+ }
42
+ }
43
+ });
44
+
45
+ // Header navigation
46
+ var headerLinks = document.querySelectorAll('nav a[href]');
47
+ headerLinks.forEach(function(link) {
48
+ var href = link.getAttribute('href');
49
+ if (href && href !== '/') {
50
+ var linkPath = href.replace(/\/$/, '');
51
+ var pagePath = currentPath.replace(/\/$/, '');
52
+ if (pagePath.indexOf(linkPath) === 0) {
53
+ link.classList.add('active');
54
+ }
55
+ }
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Initialize on DOM ready
61
+ */
62
+ document.addEventListener('DOMContentLoaded', function() {
63
+ initMobileMenu();
64
+ initActiveNav();
65
+ });
66
+ })();
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Theme Toggle Script
3
+ * Handles dark/light mode switching with localStorage persistence
4
+ */
5
+
6
+ (function() {
7
+ 'use strict';
8
+
9
+ /**
10
+ * Get the user's theme preference
11
+ * Priority: localStorage > system preference
12
+ */
13
+ function getThemePreference() {
14
+ if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
15
+ return localStorage.getItem('theme');
16
+ }
17
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
18
+ }
19
+
20
+ /**
21
+ * Set the theme on the document
22
+ */
23
+ function setTheme(theme) {
24
+ if (theme === 'dark') {
25
+ document.documentElement.classList.add('dark');
26
+ } else {
27
+ document.documentElement.classList.remove('dark');
28
+ }
29
+ localStorage.setItem('theme', theme);
30
+ }
31
+
32
+ /**
33
+ * Initialize theme immediately to prevent flash
34
+ */
35
+ setTheme(getThemePreference());
36
+
37
+ /**
38
+ * Handle DOM ready for toggle button
39
+ */
40
+ document.addEventListener('DOMContentLoaded', function() {
41
+ var toggle = document.getElementById('theme-toggle');
42
+ if (toggle) {
43
+ toggle.addEventListener('click', function() {
44
+ var currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
45
+ setTheme(currentTheme === 'dark' ? 'light' : 'dark');
46
+ });
47
+ }
48
+ });
49
+
50
+ /**
51
+ * Handle system preference changes
52
+ */
53
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
54
+ if (!localStorage.getItem('theme')) {
55
+ setTheme(e.matches ? 'dark' : 'light');
56
+ }
57
+ });
58
+ })();
@@ -0,0 +1,23 @@
1
+ <nav class="hidden lg:flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-4" aria-label="Breadcrumb">
2
+ <ol class="flex items-center flex-wrap gap-2 list-none p-0 m-0">
3
+ <li><a href="{{ '/' | relative_url }}" class="hover:text-gray-700 dark:hover:text-gray-300">Home</a></li>
4
+ {% assign crumbs = page.url | remove:'/index.html' | split: '/' %}
5
+ {% if page.collection == 'posts' %}
6
+ <li>/ <a href="{{ '/news' | relative_url }}" class="hover:text-gray-700 dark:hover:text-gray-300">News</a></li>
7
+ <li>/ {{ page.title }}</li>
8
+ {% else %}
9
+ {% for crumb in crumbs offset: 1 %}
10
+ {% if forloop.last %}
11
+ <li>/ {{ page.title }}</li>
12
+ {% else %}
13
+ {% assign crumb_limit = forloop.index | plus: 1 %}
14
+ {% assign crumb_url = '' %}
15
+ {% for crumb in crumbs limit: crumb_limit %}
16
+ {% assign crumb_url = crumb_url | append: crumb | append: '/' %}
17
+ {% endfor %}
18
+ <li>/ <a href="{{ crumb_url | absolute_url }}" class="hover:text-gray-700 dark:hover:text-gray-300">{{ crumb | replace: '-', ' ' | capitalize }}</a></li>
19
+ {% endif %}
20
+ {% endfor %}
21
+ {% endif %}
22
+ </ol>
23
+ </nav>
@@ -0,0 +1,2 @@
1
+ {% vite_client_tag %}
2
+ {% vite_javascript_tag application %}
@@ -0,0 +1,3 @@
1
+ <div class="border-t border-gray-200 dark:border-gray-700 mt-8 pt-6">
2
+ <p class="text-sm text-gray-500 dark:text-gray-400">Was this page helpful? Please give us <a href="https://goo.gl/forms/NqKna6d4lsqUtlgD3" class="text-indigo-500 hover:text-indigo-600 underline" target="_blank" rel="noopener">Feedback</a>.</p>
3
+ </div>
@@ -0,0 +1,10 @@
1
+ <footer class="bg-gray-50 dark:bg-gray-950 border-t border-gray-200 dark:border-white/10 py-12 transition-colors">
2
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
3
+ <div class="pt-8 border-t border-gray-200 dark:border-white/10">
4
+ <p class="text-sm text-gray-500 dark:text-gray-400">
5
+ &copy; {{ site.time | date: '%Y' }} CalConnect<sup>SM</sup>.
6
+ <a href="https://www.calconnect.org/about/policies/copyright-and-licensing" class="hover:text-gray-700 dark:hover:text-gray-300">Copyright</a>
7
+ </p>
8
+ </div>
9
+ </div>
10
+ </footer>
@@ -0,0 +1,9 @@
1
+ <script>
2
+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
3
+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
4
+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
5
+ })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
6
+
7
+ ga('create', '{{ site.google_analytics }}', 'auto');
8
+ ga('send', 'pageview');
9
+ </script>
@@ -0,0 +1,33 @@
1
+ <head>
2
+ <meta charset="UTF-8">
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
+ {% assign page_title = page.title | default: page.name %}
5
+ <title>{% if page_title %}{{ page_title | escape }} | {% endif %}{{ site.title | default: "CalConnect" }}</title>
6
+ <meta name="description"
7
+ content="{{ page.excerpt | default: site.description | strip_html | normalize_whitespace | truncate: 160 | escape }}" />
8
+
9
+ {% if site.favicon %}
10
+ <link rel="icon" type="image/png" href="{{ site.favicon | relative_url }}" sizes="96x96" />
11
+ {% endif %}
12
+
13
+ <!-- Google Fonts - Inter + JetBrains Mono -->
14
+ <link rel="preconnect" href="https://fonts.googleapis.com">
15
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
16
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
17
+
18
+ <!-- Critical CSS to prevent flash of unstyled content -->
19
+ <style>
20
+ html{scroll-behavior:smooth!important}
21
+ body{font-feature-settings:"kern" 1,"liga" 1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#fff!important;color:#1e293b!important;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,sans-serif!important;opacity:1!important}
22
+ html.dark body{background-color:#0f172a!important;color:#f1f5f9!important}
23
+ #nav-header{position:fixed!important;top:0!important;left:0!important;right:0!important;z-index:50!important;height:64px!important}
24
+ main{padding-top:64px!important}
25
+ </style>
26
+
27
+ <!-- Vite Stylesheet (Tailwind CSS) -->
28
+ {% vite_stylesheet_tag application %}
29
+
30
+ {% if jekyll.environment == 'production' and site.google_analytics %}
31
+ {% include google-analytics.html %}
32
+ {% endif %}
33
+ </head>
@@ -0,0 +1,9 @@
1
+ <header id="nav-header" class="fixed top-0 left-0 right-0 z-50 bg-white dark:bg-gray-950 backdrop-blur-md border-b border-gray-200 dark:border-white/10 transition-colors">
2
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
3
+ <div class="flex items-center justify-between h-16">
4
+ <a href="{{ '/' | relative_url }}" class="flex items-center">
5
+ <span class="text-lg font-semibold text-gray-900 dark:text-white">{{ site.title | default: "CalConnect" }}</span>
6
+ </a>
7
+ </div>
8
+ </div>
9
+ </header>
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="">
3
+
4
+ {% include head.html %}
5
+
6
+ <body class="bg-white dark:bg-gray-950 text-gray-900 dark:text-white">
7
+ <a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-indigo-500 focus:text-white focus:px-4 focus:py-2 focus:rounded-lg">
8
+ Skip to main content
9
+ </a>
10
+
11
+ {% include header.html %}
12
+
13
+ <main id="main-content">
14
+ {{ content }}
15
+ </main>
16
+
17
+ {% include footer.html %}
18
+
19
+ {% vite_javascript_tag application %}
20
+ </body>
21
+ </html>
@@ -0,0 +1,254 @@
1
+ ---
2
+ layout: base
3
+ ---
4
+
5
+ <section class="documentation">
6
+ <!-- Left Sidebar Navigation - Fully Data-Driven -->
7
+ <nav class="docs-nav" aria-label="Section navigation">
8
+ <div class="docs-nav-content">
9
+
10
+ {% comment %}
11
+ Find matching section based on page URL.
12
+ The layout has no knowledge of specific pages - all configuration is in _data/navigation_sidebar.yml
13
+ {% endcomment %}
14
+ {% assign current_section = nil %}
15
+ {% for section in site.data.navigation_sidebar.sections %}
16
+ {% if page.url contains section.match %}
17
+ {% assign current_section = section %}
18
+ {% break %}
19
+ {% endif %}
20
+ {% endfor %}
21
+
22
+ {% if current_section %}
23
+ <div class="nav-section">
24
+ <div class="nav-section-title">{{ current_section.title }}</div>
25
+ <ul class="nav-items">
26
+ {% for item in current_section.items %}
27
+
28
+ {% comment %} --- LINK TYPE --- {% endcomment %}
29
+ {% if item.type == 'link' or item.type == nil %}
30
+ {% assign is_active = false %}
31
+ {% if page.url == item.url %}
32
+ {% assign is_active = true %}
33
+ {% endif %}
34
+ <li>
35
+ <a href="{{ item.url | relative_url }}" {% if is_active %}class="active"{% endif %}>
36
+ {{ item.label }}
37
+ </a>
38
+ </li>
39
+
40
+ {% comment %} --- COLLECTION TYPE --- {% endcomment %}
41
+ {% elsif item.type == 'collection' %}
42
+ {% case item.collection %}
43
+ {% when 'about_pages' %}
44
+ {% assign docs = site.about_pages %}
45
+ {% when 'membership_pages' %}
46
+ {% assign docs = site.membership_pages %}
47
+ {% when 'events' %}
48
+ {% assign docs = site.events %}
49
+ {% when 'resources' %}
50
+ {% assign docs = site.resources %}
51
+ {% else %}
52
+ {% assign docs = site.posts %}
53
+ {% endcase %}
54
+ {% if item.sort_field == 'date' and item.sort_order == 'desc' %}
55
+ {% assign sorted_docs = docs | sort: 'date' | reverse %}
56
+ {% elsif item.sort_field %}
57
+ {% assign sorted_docs = docs | sort: item.sort_field %}
58
+ {% else %}
59
+ {% assign sorted_docs = docs %}
60
+ {% endif %}
61
+ {% for doc in sorted_docs %}
62
+ {% assign is_doc_active = false %}
63
+ {% if page.url == doc.url %}
64
+ {% assign is_doc_active = true %}
65
+ {% endif %}
66
+ <li>
67
+ <a href="{{ doc.url | relative_url }}" {% if is_doc_active %}class="active"{% endif %}>
68
+ {{ doc.title | default: doc.name }}
69
+ </a>
70
+ </li>
71
+ {% endfor %}
72
+
73
+ {% comment %} --- COLLAPSIBLE TYPE --- {% endcomment %}
74
+ {% elsif item.type == 'collapsible' %}
75
+ {% assign is_open = false %}
76
+ {% if page.url contains item.url_filter %}
77
+ {% assign is_open = true %}
78
+ {% endif %}
79
+ {% case item.collection %}
80
+ {% when 'about_pages' %}
81
+ {% assign child_docs = site.about_pages %}
82
+ {% when 'membership_pages' %}
83
+ {% assign child_docs = site.membership_pages %}
84
+ {% when 'events' %}
85
+ {% assign child_docs = site.events %}
86
+ {% when 'resources' %}
87
+ {% assign child_docs = site.resources %}
88
+ {% else %}
89
+ {% assign child_docs = site.posts %}
90
+ {% endcase %}
91
+ <li>
92
+ <span class="nav-toggle {% if is_open %}open{% endif %}">{{ item.label }}</span>
93
+ <ul class="nav-subsection {% unless is_open %}collapsed{% endunless %}">
94
+ {% if item.first_child_label %}
95
+ {% assign first_is_active = false %}
96
+ {% if page.url contains item.url_filter %}
97
+ {% assign first_is_active = true %}
98
+ {% endif %}
99
+ <li><a href="{{ item.url | relative_url }}" {% if first_is_active %}class="active"{% endif %}>{{ item.first_child_label }}</a></li>
100
+ {% endif %}
101
+ {% for doc in child_docs %}
102
+ {% assign url_matches = false %}
103
+ {% if doc.url contains item.url_filter %}
104
+ {% assign url_matches = true %}
105
+ {% endif %}
106
+ {% assign is_excluded = false %}
107
+ {% if doc.url == item.url %}
108
+ {% assign is_excluded = true %}
109
+ {% endif %}
110
+ {% if item.exclude_urls %}
111
+ {% for excl in item.exclude_urls %}
112
+ {% if doc.url == excl %}
113
+ {% assign is_excluded = true %}
114
+ {% endif %}
115
+ {% endfor %}
116
+ {% elsif item.exclude_url %}
117
+ {% if doc.url == item.exclude_url %}
118
+ {% assign is_excluded = true %}
119
+ {% endif %}
120
+ {% endif %}
121
+ {% if url_matches and is_excluded == false %}
122
+ {% assign child_is_active = false %}
123
+ {% if page.url == doc.url %}
124
+ {% assign child_is_active = true %}
125
+ {% endif %}
126
+ <li>
127
+ <a href="{{ doc.url | relative_url }}" {% if child_is_active %}class="active"{% endif %}>
128
+ {{ doc.title }}
129
+ </a>
130
+ </li>
131
+ {% endif %}
132
+ {% endfor %}
133
+ </ul>
134
+ </li>
135
+
136
+ {% comment %} --- YEARS TYPE --- {% endcomment %}
137
+ {% elsif item.type == 'years' %}
138
+ {% assign years = site.data.news_years %}
139
+ {% for year in years %}
140
+ {% assign is_active = false %}
141
+ {% if page.url contains year %}
142
+ {% assign is_active = true %}
143
+ {% endif %}
144
+ <li>
145
+ <a href="/news/{{ year }}/" {% if is_active %}class="active"{% endif %}>
146
+ {{ year }}
147
+ </a>
148
+ </li>
149
+ {% endfor %}
150
+
151
+ {% comment %} --- GROUP TYPE --- {% endcomment %}
152
+ {% elsif item.type == 'group' %}
153
+ </ul>
154
+ <div class="nav-group-title">{{ item.label }}</div>
155
+ <ul class="nav-items">
156
+ {% for child in item.children %}
157
+ {% if child.type == 'link' or child.type == nil %}
158
+ {% assign group_link_active = false %}
159
+ {% if page.url == child.url %}
160
+ {% assign group_link_active = true %}
161
+ {% endif %}
162
+ <li>
163
+ <a href="{{ child.url | relative_url }}" {% if group_link_active %}class="active"{% endif %}>
164
+ {{ child.label }}
165
+ </a>
166
+ </li>
167
+ {% elsif child.type == 'collapsible' %}
168
+ {% assign is_open = false %}
169
+ {% if page.url contains child.url_filter %}
170
+ {% assign is_open = true %}
171
+ {% endif %}
172
+ {% case child.collection %}
173
+ {% when 'about_pages' %}
174
+ {% assign child_docs = site.about_pages %}
175
+ {% when 'membership_pages' %}
176
+ {% assign child_docs = site.membership_pages %}
177
+ {% when 'events' %}
178
+ {% assign child_docs = site.events %}
179
+ {% when 'resources' %}
180
+ {% assign child_docs = site.resources %}
181
+ {% else %}
182
+ {% assign child_docs = site.posts %}
183
+ {% endcase %}
184
+ <li>
185
+ <span class="nav-toggle {% if is_open %}open{% endif %}">{{ child.label }}</span>
186
+ <ul class="nav-subsection {% unless is_open %}collapsed{% endunless %}">
187
+ {% if child.first_child_label %}
188
+ {% assign group_first_active = false %}
189
+ {% if page.url contains child.url_filter %}
190
+ {% assign group_first_active = true %}
191
+ {% endif %}
192
+ <li><a href="{{ child.url | relative_url }}" {% if group_first_active %}class="active"{% endif %}>{{ child.first_child_label }}</a></li>
193
+ {% endif %}
194
+ {% for doc in child_docs %}
195
+ {% assign url_matches = false %}
196
+ {% if doc.url contains child.url_filter %}
197
+ {% assign url_matches = true %}
198
+ {% endif %}
199
+ {% assign is_excluded = false %}
200
+ {% if doc.url == child.url %}
201
+ {% assign is_excluded = true %}
202
+ {% endif %}
203
+ {% if child.exclude_urls %}
204
+ {% for excl in child.exclude_urls %}
205
+ {% if doc.url == excl %}
206
+ {% assign is_excluded = true %}
207
+ {% endif %}
208
+ {% endfor %}
209
+ {% elsif child.exclude_url %}
210
+ {% if doc.url == child.exclude_url %}
211
+ {% assign is_excluded = true %}
212
+ {% endif %}
213
+ {% endif %}
214
+ {% if url_matches and is_excluded == false %}
215
+ {% assign group_doc_active = false %}
216
+ {% if page.url == doc.url %}
217
+ {% assign group_doc_active = true %}
218
+ {% endif %}
219
+ <li>
220
+ <a href="{{ doc.url | relative_url }}" {% if group_doc_active %}class="active"{% endif %}>
221
+ {{ doc.title }}
222
+ </a>
223
+ </li>
224
+ {% endif %}
225
+ {% endfor %}
226
+ </ul>
227
+ </li>
228
+ {% endif %}
229
+ {% endfor %}
230
+
231
+ {% endif %}
232
+
233
+ {% endfor %}
234
+ </ul>
235
+ </div>
236
+ {% endif %}
237
+
238
+ </div>
239
+ </nav>
240
+
241
+ <!-- Main Article -->
242
+ <article>
243
+ {% assign page_title = page.title | default: page.name %}
244
+ {% if page_title %}
245
+ <header>
246
+ <h1>{{ page_title }}</h1>
247
+ </header>
248
+ {% endif %}
249
+
250
+ <div class="body">
251
+ {{ content }}
252
+ </div>
253
+ </article>
254
+ </section>
@@ -0,0 +1,4 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+ {{ content }}
@@ -0,0 +1,23 @@
1
+ // Code blocks and inline code
2
+
3
+ .documentation .body pre {
4
+ background: #1e293b;
5
+ color: #e2e8f0;
6
+ padding: 1rem 1.25rem;
7
+ border-radius: 0.5rem;
8
+ overflow-x: auto;
9
+ margin: 1.5rem 0;
10
+ }
11
+
12
+ .documentation .body code {
13
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
14
+ font-size: 0.875rem;
15
+ }
16
+
17
+ .documentation .body :not(pre) > code {
18
+ background: #f1f5f9;
19
+ padding: 0.125rem 0.375rem;
20
+ border-radius: 0.25rem;
21
+ color: #1e293b;
22
+ font-size: 0.8125rem;
23
+ }
@@ -0,0 +1,91 @@
1
+ // Dark mode overrides
2
+
3
+ // Layout
4
+ .dark .documentation > article > header {
5
+ border-bottom-color: #334155;
6
+ }
7
+
8
+ .dark .documentation > article > header h1 {
9
+ color: #f8fafc;
10
+ }
11
+
12
+ // Navigation
13
+ .dark .docs-nav .nav-group-title {
14
+ color: #64748b;
15
+ border-top-color: #334155;
16
+ }
17
+
18
+ .dark .docs-nav .nav-items a:hover {
19
+ background: #1e293b;
20
+ color: #f1f5f9;
21
+ }
22
+
23
+ .dark .docs-nav .nav-items a.active {
24
+ background: #312e81;
25
+ color: #a5b4fc;
26
+ }
27
+
28
+ .dark .nav-toggle:hover {
29
+ background: #1e293b;
30
+ color: #f1f5f9;
31
+ }
32
+
33
+ .dark .nav-subsection {
34
+ border-left-color: #334155;
35
+ }
36
+
37
+ // Typography
38
+ .dark .documentation .body h2 {
39
+ color: #f1f5f9;
40
+ }
41
+
42
+ .dark .documentation .body h3 {
43
+ color: #e2e8f0;
44
+ }
45
+
46
+ .dark .documentation .body p {
47
+ color: #94a3b8;
48
+ }
49
+
50
+ .dark .documentation .body ul,
51
+ .dark .documentation .body ol,
52
+ .dark .documentation .body dl,
53
+ .dark .documentation .post-content ul,
54
+ .dark .documentation .post-content ol,
55
+ .dark .documentation .post-content dl,
56
+ .dark .documentation .sectionbody ul,
57
+ .dark .documentation .sectionbody ol,
58
+ .dark .documentation .sectionbody dl {
59
+ color: #94a3b8;
60
+ }
61
+
62
+ .dark .documentation .body li::marker,
63
+ .dark .documentation .post-content li::marker,
64
+ .dark .documentation .sectionbody li::marker {
65
+ color: #818cf8;
66
+ }
67
+
68
+ .dark .documentation .body dt {
69
+ color: #f1f5f9;
70
+ }
71
+
72
+ .dark .documentation .body dd {
73
+ color: #94a3b8;
74
+ }
75
+
76
+ // Code
77
+ .dark .documentation .body :not(pre) > code {
78
+ background: #334155;
79
+ color: #e2e8f0;
80
+ }
81
+
82
+ // Tables
83
+ .dark .documentation .body th,
84
+ .dark .documentation .body td {
85
+ border-bottom-color: #334155;
86
+ }
87
+
88
+ .dark .documentation .body th {
89
+ background: #1e293b;
90
+ color: #f1f5f9;
91
+ }
@@ -0,0 +1,57 @@
1
+ // Documentation layout: two-column flex with sidebar
2
+ .documentation {
3
+ display: flex;
4
+ max-width: 1400px;
5
+ margin: 0 auto;
6
+ margin-top: 64px;
7
+ padding: 2rem 1rem;
8
+ gap: 2rem;
9
+
10
+ @media (min-width: 1024px) {
11
+ padding: 2rem 2rem;
12
+ }
13
+ }
14
+
15
+ // Left sidebar
16
+ .docs-nav {
17
+ width: 220px;
18
+ flex-shrink: 0;
19
+ }
20
+
21
+ // Sticky sidebar content
22
+ @media (min-width: 1024px) {
23
+ .docs-nav-content {
24
+ position: sticky;
25
+ top: 5rem;
26
+ max-height: calc(100vh - 6rem);
27
+ overflow-y: auto;
28
+ }
29
+ }
30
+
31
+ // Main article
32
+ .documentation > article {
33
+ flex: 1;
34
+ min-width: 0;
35
+ max-width: 75ch;
36
+ }
37
+
38
+ .documentation > article > header {
39
+ margin-bottom: 2rem;
40
+ padding-bottom: 1.5rem;
41
+ border-bottom: 1px solid #e2e8f0;
42
+ }
43
+
44
+ .documentation > article > header h1 {
45
+ font-size: 2.25rem;
46
+ font-weight: 700;
47
+ color: #0f172a;
48
+ line-height: 1.2;
49
+ margin: 0;
50
+ }
51
+
52
+ // Hide sidebar on mobile
53
+ @media (max-width: 1023px) {
54
+ .docs-nav {
55
+ display: none;
56
+ }
57
+ }
@@ -0,0 +1,113 @@
1
+ // Sidebar navigation components
2
+
3
+ .docs-nav .nav-section {
4
+ margin-bottom: 1.5rem;
5
+ }
6
+
7
+ .docs-nav .nav-section-title {
8
+ font-size: 0.75rem;
9
+ font-weight: 600;
10
+ text-transform: uppercase;
11
+ letter-spacing: 0.05em;
12
+ color: #94a3b8;
13
+ margin-bottom: 0.5rem;
14
+ padding: 0 0.75rem;
15
+ }
16
+
17
+ // Group headers within navigation
18
+ .docs-nav .nav-group-title {
19
+ font-size: 0.6875rem;
20
+ font-weight: 600;
21
+ text-transform: uppercase;
22
+ letter-spacing: 0.05em;
23
+ color: #94a3b8;
24
+ margin-top: 1rem;
25
+ margin-bottom: 0.25rem;
26
+ padding: 0.25rem 0.75rem;
27
+ border-top: 1px solid #e2e8f0;
28
+ }
29
+
30
+ .docs-nav .nav-group-title:first-child {
31
+ margin-top: 0;
32
+ border-top: none;
33
+ }
34
+
35
+ .docs-nav .nav-items {
36
+ list-style: none;
37
+ padding: 0;
38
+ margin: 0;
39
+ }
40
+
41
+ .docs-nav .nav-items li {
42
+ margin-bottom: 0.125rem;
43
+ }
44
+
45
+ .docs-nav .nav-items a {
46
+ display: block;
47
+ padding: 0.5rem 0.75rem;
48
+ color: #64748b;
49
+ text-decoration: none;
50
+ border-radius: 0.375rem;
51
+ font-size: 0.875rem;
52
+ transition: all 0.15s ease;
53
+ }
54
+
55
+ .docs-nav .nav-items a:hover {
56
+ background: #f1f5f9;
57
+ color: #1e293b;
58
+ }
59
+
60
+ .docs-nav .nav-items a.active {
61
+ background: #eef2ff;
62
+ color: #4f46e5;
63
+ font-weight: 500;
64
+ }
65
+
66
+ // Collapsible subsections
67
+ .nav-subsection {
68
+ margin-left: 0.75rem;
69
+ border-left: 2px solid #e2e8f0;
70
+ padding-left: 0.75rem;
71
+ margin-top: 0.25rem;
72
+ }
73
+
74
+ // Toggle button
75
+ .nav-toggle {
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: space-between;
79
+ padding: 0.5rem 0.75rem;
80
+ color: #64748b;
81
+ font-size: 0.875rem;
82
+ border-radius: 0.375rem;
83
+ cursor: pointer;
84
+ transition: all 0.15s ease;
85
+ user-select: none;
86
+ }
87
+
88
+ .nav-toggle:hover {
89
+ background: #f1f5f9;
90
+ color: #1e293b;
91
+ }
92
+
93
+ // Arrow indicator
94
+ .nav-toggle::after {
95
+ content: '';
96
+ display: inline-block;
97
+ width: 5px;
98
+ height: 5px;
99
+ border-right: 2px solid currentColor;
100
+ border-bottom: 2px solid currentColor;
101
+ transform: rotate(-45deg);
102
+ transition: transform 0.2s ease;
103
+ margin-left: auto;
104
+ flex-shrink: 0;
105
+ }
106
+
107
+ .nav-toggle.open::after {
108
+ transform: rotate(45deg);
109
+ }
110
+
111
+ .nav-subsection.collapsed {
112
+ display: none;
113
+ }
@@ -0,0 +1,20 @@
1
+ // Table styling
2
+
3
+ .documentation .body table {
4
+ width: 100%;
5
+ border-collapse: collapse;
6
+ margin: 1.5rem 0;
7
+ }
8
+
9
+ .documentation .body th,
10
+ .documentation .body td {
11
+ padding: 0.75rem 1rem;
12
+ text-align: left;
13
+ border-bottom: 1px solid #e2e8f0;
14
+ }
15
+
16
+ .documentation .body th {
17
+ background: #f8fafc;
18
+ font-weight: 600;
19
+ color: #1e293b;
20
+ }
@@ -0,0 +1,112 @@
1
+ // Documentation body typography
2
+
3
+ .documentation .body {
4
+ font-size: 1rem;
5
+ line-height: 1.75;
6
+ }
7
+
8
+ .documentation .body h2 {
9
+ font-size: 1.5rem;
10
+ font-weight: 600;
11
+ margin-top: 2.5rem;
12
+ margin-bottom: 1rem;
13
+ color: #1e293b;
14
+ }
15
+
16
+ .documentation .body h3 {
17
+ font-size: 1.25rem;
18
+ font-weight: 600;
19
+ margin-top: 2rem;
20
+ margin-bottom: 0.75rem;
21
+ color: #334155;
22
+ }
23
+
24
+ .documentation .body p {
25
+ margin-bottom: 1rem;
26
+ color: #475569;
27
+ }
28
+
29
+ .documentation .body a {
30
+ color: #6366f1;
31
+ text-decoration: underline;
32
+ text-underline-offset: 2px;
33
+ }
34
+
35
+ .documentation .body a:hover {
36
+ color: #4f46e5;
37
+ }
38
+
39
+ // Lists
40
+ .documentation .body ul,
41
+ .documentation .body ol,
42
+ .documentation .body dl,
43
+ .documentation .post-content ul,
44
+ .documentation .post-content ol,
45
+ .documentation .post-content dl,
46
+ .documentation .sectionbody ul,
47
+ .documentation .sectionbody ol,
48
+ .documentation .sectionbody dl {
49
+ margin-bottom: 1.25rem;
50
+ padding-left: 1.75rem;
51
+ color: #475569;
52
+ list-style-position: outside;
53
+ }
54
+
55
+ .documentation .body ul,
56
+ .documentation .post-content ul,
57
+ .documentation .sectionbody ul {
58
+ list-style-type: disc !important;
59
+ }
60
+
61
+ .documentation .body ul ul,
62
+ .documentation .post-content ul ul,
63
+ .documentation .sectionbody ul ul {
64
+ list-style-type: circle !important;
65
+ margin-top: 0.5rem;
66
+ margin-bottom: 0.5rem;
67
+ }
68
+
69
+ .documentation .body ol,
70
+ .documentation .post-content ol,
71
+ .documentation .sectionbody ol {
72
+ list-style-type: decimal !important;
73
+ }
74
+
75
+ // Exception: don't number breadcrumb navigation
76
+ .body nav[aria-label="Breadcrumb"] ol,
77
+ .body nav ol.list-none,
78
+ ol.list-none {
79
+ list-style: none !important;
80
+ padding-left: 0 !important;
81
+ }
82
+
83
+ .body nav[aria-label="Breadcrumb"] li,
84
+ .body nav ol.list-none li {
85
+ margin-bottom: 0 !important;
86
+ }
87
+
88
+ .documentation .body li,
89
+ .documentation .post-content li,
90
+ .documentation .sectionbody li {
91
+ margin-bottom: 0.625rem;
92
+ line-height: 1.7;
93
+ }
94
+
95
+ .documentation .body li::marker,
96
+ .documentation .post-content li::marker,
97
+ .documentation .sectionbody li::marker {
98
+ color: #6366f1;
99
+ }
100
+
101
+ // Definition lists
102
+ .documentation .body dt {
103
+ font-weight: 600;
104
+ color: #1e293b;
105
+ margin-bottom: 0.25rem;
106
+ }
107
+
108
+ .documentation .body dd {
109
+ margin-left: 1.5rem;
110
+ margin-bottom: 0.75rem;
111
+ color: #475569;
112
+ }
@@ -0,0 +1,6 @@
1
+ @import 'calconnect/layout';
2
+ @import 'calconnect/navigation';
3
+ @import 'calconnect/typography';
4
+ @import 'calconnect/code';
5
+ @import 'calconnect/tables';
6
+ @import 'calconnect/dark-mode';
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/jekyll/calconnect/theme/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "jekyll-calconnect-theme"
7
+ spec.version = Jekyll::CalconnectTheme::VERSION
8
+ spec.authors = ["CalConnect"]
9
+ spec.email = ["info@calconnect.org"]
10
+ spec.summary = "Shared Jekyll theme for CalConnect sites"
11
+ spec.homepage = "https://github.com/calconnect/calconnect-theme"
12
+ spec.license = "MIT"
13
+
14
+ spec.files = Dir[
15
+ "{_layouts,_includes,_sass,_frontend,lib}/**/*",
16
+ "*.md",
17
+ "*.gemspec"
18
+ ]
19
+
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_runtime_dependency "jekyll", "~> 4.3"
23
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module CalconnectTheme
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "theme/version"
4
+
5
+ module Jekyll
6
+ module CalconnectTheme
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-calconnect-theme
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - CalConnect
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: jekyll
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '4.3'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '4.3'
26
+ email:
27
+ - info@calconnect.org
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - CLAUDE.md
33
+ - README.md
34
+ - _frontend/entrypoints/application.css
35
+ - _frontend/entrypoints/application.js
36
+ - _frontend/js/navigation.js
37
+ - _frontend/js/theme.js
38
+ - _includes/breadcrumbs.html
39
+ - _includes/custom-head.html
40
+ - _includes/feedback.html
41
+ - _includes/footer.html
42
+ - _includes/google-analytics.html
43
+ - _includes/head.html
44
+ - _includes/header.html
45
+ - _layouts/base.html
46
+ - _layouts/default.html
47
+ - _layouts/page.html
48
+ - _sass/calconnect.scss
49
+ - _sass/calconnect/_code.scss
50
+ - _sass/calconnect/_dark-mode.scss
51
+ - _sass/calconnect/_layout.scss
52
+ - _sass/calconnect/_navigation.scss
53
+ - _sass/calconnect/_tables.scss
54
+ - _sass/calconnect/_typography.scss
55
+ - jekyll-calconnect-theme.gemspec
56
+ - lib/jekyll/calconnect/theme.rb
57
+ - lib/jekyll/calconnect/theme/version.rb
58
+ homepage: https://github.com/calconnect/calconnect-theme
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.6.9
77
+ specification_version: 4
78
+ summary: Shared Jekyll theme for CalConnect sites
79
+ test_files: []