jekyll-theme-zer0 1.19.1 → 1.20.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 +4 -4
- data/CHANGELOG.md +395 -0
- data/README.md +27 -19
- data/_data/authors.yml +154 -5
- data/_data/backlog.yml +5 -5
- data/_data/content_statistics.yml +273 -297
- data/_data/features.yml +4 -25
- data/_data/navigation/README.md +24 -0
- data/_data/navigation/about.yml +2 -0
- data/_data/navigation/main.yml +2 -7
- data/_data/roadmap.yml +86 -12
- data/_includes/components/author-avatar-url.html +28 -0
- data/_includes/components/author-bio.html +86 -0
- data/_includes/components/author-card.html +184 -121
- data/_includes/components/author-eeat.html +10 -4
- data/_includes/components/info-section.html +1 -1
- data/_includes/components/mermaid.html +0 -3
- data/_includes/components/post-card.html +19 -9
- data/_includes/content/giscus.html +3 -2
- data/_includes/core/footer-fabs.html +28 -0
- data/_includes/core/footer.html +7 -17
- data/_includes/core/head.html +2 -2
- data/_includes/navigation/breadcrumbs.html +20 -2
- data/_includes/navigation/local-graph.html +18 -2
- data/_includes/obsidian/full-graph.html +4 -6
- data/_layouts/article.html +44 -74
- data/_layouts/author.html +274 -0
- data/_layouts/authors.html +55 -0
- data/_layouts/news.html +3 -3
- data/_layouts/note.html +21 -6
- data/_layouts/notebook.html +21 -6
- data/_layouts/root.html +31 -17
- data/_layouts/section.html +3 -3
- data/_plugins/author_pages_generator.rb +121 -0
- data/_sass/components/_author.scss +219 -0
- data/_sass/components/_content-tables.scss +16 -1
- data/_sass/components/_notes-index.scss +102 -0
- data/_sass/components/_search-modal.scss +40 -0
- data/_sass/components/_ui-enhancements.scss +570 -0
- data/_sass/core/_docs-code-examples.scss +463 -0
- data/_sass/core/_docs-layout.scss +0 -453
- data/_sass/core/_navbar.scss +253 -0
- data/_sass/core/_sidebar-extras.scss +79 -0
- data/_sass/core/_toc.scss +87 -0
- data/_sass/core/_variables.scss +7 -142
- data/_sass/custom.scss +24 -1122
- data/_sass/layouts/_global-chrome.scss +59 -0
- data/assets/css/main.scss +19 -2
- data/assets/js/author-profile.js +190 -0
- data/assets/js/modules/navigation/navbar.js +104 -0
- data/assets/js/obsidian-graph.js +2 -2
- data/assets/js/obsidian-local-graph.js +11 -5
- data/assets/vendor/cytoscape/cytoscape.min.js +32 -0
- data/scripts/README.md +39 -0
- data/scripts/bin/validate +11 -1
- data/scripts/dev/css-diff.sh +49 -0
- data/scripts/dev/shot.js +37 -0
- data/scripts/features/generate-preview-images +110 -6
- data/scripts/features/pixelate-preview-images +126 -0
- data/scripts/features/pixelate_images.py +662 -0
- data/scripts/github-setup.sh +0 -0
- data/scripts/lib/preview_generator.py +47 -3
- data/scripts/pixelate-preview-images.sh +12 -0
- data/scripts/test/integration/auto-version +10 -8
- data/scripts/test/lib/run_tests.sh +2 -0
- data/scripts/test/lib/test_content_review.sh +205 -0
- data/scripts/test/lib/test_pixelate_images.sh +108 -0
- metadata +25 -20
- data/_data/hub.yml +0 -68
- data/_data/hub_index.yml +0 -203
- data/_data/navigation/hub.yml +0 -110
- data/assets/vendor/font-awesome/css/all.min.css +0 -9
- data/assets/vendor/font-awesome/webfonts/fa-brands-400.ttf +0 -0
- data/assets/vendor/font-awesome/webfonts/fa-brands-400.woff2 +0 -0
- data/assets/vendor/font-awesome/webfonts/fa-regular-400.ttf +0 -0
- data/assets/vendor/font-awesome/webfonts/fa-regular-400.woff2 +0 -0
- data/assets/vendor/font-awesome/webfonts/fa-solid-900.ttf +0 -0
- data/assets/vendor/font-awesome/webfonts/fa-solid-900.woff2 +0 -0
- data/assets/vendor/font-awesome/webfonts/fa-v4compatibility.ttf +0 -0
- data/assets/vendor/font-awesome/webfonts/fa-v4compatibility.woff2 +0 -0
- data/assets/vendor/jquery/jquery-3.7.1.min.js +0 -2
- data/scripts/lib/hub.rb +0 -208
- data/scripts/provision-org-sites.rb +0 -252
- data/scripts/provision-org-sites.sh +0 -23
- data/scripts/sync-hub-metadata.rb +0 -184
- data/scripts/sync-hub-metadata.sh +0 -22
data/_layouts/notebook.html
CHANGED
|
@@ -48,8 +48,7 @@ layout: default
|
|
|
48
48
|
<!-- Author -->
|
|
49
49
|
{% if page.author %}
|
|
50
50
|
<span class="notebook-author" itemprop="author" itemscope itemtype="https://schema.org/Person">
|
|
51
|
-
|
|
52
|
-
<span itemprop="name">{{ page.author }}</span>
|
|
51
|
+
{% include components/author-card.html author=page.author style="inline" name_itemprop="name" %}
|
|
53
52
|
</span>
|
|
54
53
|
<span class="mx-2">•</span>
|
|
55
54
|
{% endif %}
|
|
@@ -92,12 +91,21 @@ layout: default
|
|
|
92
91
|
</div>
|
|
93
92
|
|
|
94
93
|
<!-- Tags -->
|
|
94
|
+
{% comment %} Link tag badges only when a tags index exists (configurable via
|
|
95
|
+
`tags_page`); otherwise render plain badges so remote-theme consumers
|
|
96
|
+
without a /tags/ page don't get 404 links. See issue #204. {% endcomment %}
|
|
95
97
|
{% if page.tags and page.tags.size > 0 %}
|
|
98
|
+
{% assign _tags_base = site.tags_page | default: '/tags/' %}
|
|
99
|
+
{% assign _tags_page = site.html_pages | where: "url", _tags_base | first %}
|
|
96
100
|
<div class="notebook-tags mb-3">
|
|
97
101
|
{% for tag in page.tags %}
|
|
98
|
-
|
|
99
|
-
{{ tag }}
|
|
100
|
-
|
|
102
|
+
{% if _tags_page %}
|
|
103
|
+
<a href="{{ _tags_base | relative_url }}#{{ tag | slugify }}" class="badge bg-secondary text-decoration-none me-1">
|
|
104
|
+
{{ tag }}
|
|
105
|
+
</a>
|
|
106
|
+
{% else %}
|
|
107
|
+
<span class="badge bg-secondary me-1">{{ tag }}</span>
|
|
108
|
+
{% endif %}
|
|
101
109
|
{% endfor %}
|
|
102
110
|
</div>
|
|
103
111
|
{% endif %}
|
|
@@ -129,7 +137,14 @@ layout: default
|
|
|
129
137
|
<div class="notebook-content e-content" itemprop="articleBody">
|
|
130
138
|
{{ content }}
|
|
131
139
|
</div>
|
|
132
|
-
|
|
140
|
+
|
|
141
|
+
<!-- ================================ -->
|
|
142
|
+
<!-- AUTHOR BIO SECTION -->
|
|
143
|
+
<!-- ================================ -->
|
|
144
|
+
{% if page.author and page.author_profile != false %}
|
|
145
|
+
{% include components/author-bio.html author=page.author %}
|
|
146
|
+
{% endif %}
|
|
147
|
+
|
|
133
148
|
<!-- ================================ -->
|
|
134
149
|
<!-- NOTEBOOK FOOTER -->
|
|
135
150
|
<!-- ================================ -->
|
data/_layouts/root.html
CHANGED
|
@@ -54,7 +54,8 @@
|
|
|
54
54
|
<!-- ============================================== -->
|
|
55
55
|
|
|
56
56
|
<!-- Inline SVG symbols for consistent iconography throughout the site -->
|
|
57
|
-
|
|
57
|
+
<!-- Page-invariant: cached once per build via jekyll-include-cache. -->
|
|
58
|
+
{% include_cached components/svg.html %}
|
|
58
59
|
|
|
59
60
|
<!-- Google Tag Manager body code for analytics (if configured) -->
|
|
60
61
|
<!-- Note: Currently commented out - uncomment when GTM is needed -->
|
|
@@ -66,35 +67,45 @@
|
|
|
66
67
|
Enable with `navigation.unified_mobile_drawer: true` in _config.yml. -->
|
|
67
68
|
{% include navigation/unified-drawer.html %}
|
|
68
69
|
|
|
69
|
-
<!-- Search modal (site-wide) -->
|
|
70
|
-
{%
|
|
70
|
+
<!-- Search modal (site-wide) — page-invariant, cached once per build. -->
|
|
71
|
+
{% include_cached components/search-modal.html %}
|
|
71
72
|
|
|
72
|
-
<!-- Keyboard shortcuts help modal (triggered by `?`) -->
|
|
73
|
-
{%
|
|
73
|
+
<!-- Keyboard shortcuts help modal (triggered by `?`) — page-invariant. -->
|
|
74
|
+
{% include_cached components/shortcuts-modal.html %}
|
|
74
75
|
|
|
75
76
|
<!-- Site-wide information banner or announcement section -->
|
|
76
77
|
{% include components/info-section.html %}
|
|
77
78
|
|
|
78
|
-
<!-- Setup banner for unconfigured sites -->
|
|
79
|
-
{%
|
|
79
|
+
<!-- Setup banner for unconfigured sites — page-invariant. -->
|
|
80
|
+
{% include_cached components/setup-banner.html %}
|
|
80
81
|
|
|
81
82
|
<!-- ======================== -->
|
|
82
83
|
<!-- MAIN CONTENT AREA -->
|
|
83
84
|
<!-- ======================== -->
|
|
84
|
-
<!-- Skip-link target: keep a single, consistent #main-content anchor site-wide
|
|
85
|
-
|
|
85
|
+
<!-- Skip-link target: keep a single, consistent #main-content anchor site-wide.
|
|
86
|
+
Semantic <main> landmark (consistent with the default/section/news layouts)
|
|
87
|
+
so assistive tech, search engines, and AI content extractors can isolate the
|
|
88
|
+
primary content from the surrounding header/nav/footer chrome. -->
|
|
89
|
+
<main id="main-content">
|
|
86
90
|
{{ content }}
|
|
87
|
-
</
|
|
91
|
+
</main>
|
|
88
92
|
|
|
89
93
|
<!-- ======================== -->
|
|
90
94
|
<!-- FOOTER AND SCRIPTS -->
|
|
91
95
|
<!-- ======================== -->
|
|
92
96
|
|
|
93
|
-
<!-- Site footer with links, copyright, and additional navigation
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
<!-- Site footer with links, copyright, and additional navigation.
|
|
98
|
+
Page-invariant chrome is cached once per build; the page-dependent
|
|
99
|
+
FAB/local-graph tail renders fresh per page (see core/footer-fabs.html).
|
|
100
|
+
Build-time values inside (e.g. the copyright year from site.time) stay
|
|
101
|
+
fresh: jekyll-include-cache stores in memory only (Jekyll 3.x has no
|
|
102
|
+
Jekyll::Cache) and clears on every build's :pre_render hook — it never
|
|
103
|
+
persists to .jekyll-cache. -->
|
|
104
|
+
{%- include_cached core/footer.html -%}
|
|
105
|
+
{%- include core/footer-fabs.html -%}
|
|
106
|
+
|
|
107
|
+
<!-- Privacy-compliant cookie consent banner — page-invariant. -->
|
|
108
|
+
{%- include_cached components/cookie-consent.html -%}
|
|
98
109
|
|
|
99
110
|
<!-- AI Chat Assistant -->
|
|
100
111
|
{%- include components/ai-chat.html -%}
|
|
@@ -104,8 +115,11 @@
|
|
|
104
115
|
<!-- Search functionality -->
|
|
105
116
|
<!-- Note: Currently commented out - uncomment when search is implemented -->
|
|
106
117
|
|
|
107
|
-
<!-- External JavaScript libraries and custom scripts
|
|
108
|
-
|
|
118
|
+
<!-- External JavaScript libraries and custom scripts — page-invariant.
|
|
119
|
+
The "?v=" cache-buster (site.time) in js-cdn.html stays correct:
|
|
120
|
+
the include cache is in-memory and reset every build (see footer
|
|
121
|
+
note above), so v= advances on each build, never frozen across deploys. -->
|
|
122
|
+
{% include_cached components/js-cdn.html %}
|
|
109
123
|
</div>
|
|
110
124
|
|
|
111
125
|
<!-- Analytics Integration -->
|
data/_layouts/section.html
CHANGED
|
@@ -216,7 +216,7 @@ layout: root
|
|
|
216
216
|
</small>
|
|
217
217
|
{% if featured_in_section.author %}
|
|
218
218
|
<small class="text-muted">
|
|
219
|
-
|
|
219
|
+
{% include components/author-card.html author=featured_in_section.author style="inline" show_avatar=false %}
|
|
220
220
|
</small>
|
|
221
221
|
{% endif %}
|
|
222
222
|
</div>
|
|
@@ -425,7 +425,7 @@ layout: root
|
|
|
425
425
|
</span>
|
|
426
426
|
{% if gpost.author %}
|
|
427
427
|
<span>
|
|
428
|
-
|
|
428
|
+
{% include components/author-card.html author=gpost.author style="inline" show_avatar=false %}
|
|
429
429
|
</span>
|
|
430
430
|
{% endif %}
|
|
431
431
|
</div>
|
|
@@ -476,7 +476,7 @@ layout: root
|
|
|
476
476
|
<div class="d-flex gap-3 text-muted small">
|
|
477
477
|
<span><i class="bi bi-calendar me-1"></i>{{ lpost.date | date: "%B %d, %Y" }}</span>
|
|
478
478
|
{% if lpost.author %}
|
|
479
|
-
<span
|
|
479
|
+
<span>{% include components/author-card.html author=lpost.author style="inline" show_avatar=false %}</span>
|
|
480
480
|
{% endif %}
|
|
481
481
|
</div>
|
|
482
482
|
</div>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# File: author_pages_generator.rb
|
|
5
|
+
# Path: _plugins/author_pages_generator.rb
|
|
6
|
+
# Purpose: Auto-generate an author profile page (/authors/:key/) for every
|
|
7
|
+
# entry in _data/authors.yml, plus an /authors/ index, so author
|
|
8
|
+
# profiles work out of the box without authors hand-creating pages.
|
|
9
|
+
#
|
|
10
|
+
# Mirrors the pattern in search_and_sitemap_generator.rb (PageWithoutFile,
|
|
11
|
+
# safe true, low priority, idempotent, opt-out via _config.yml).
|
|
12
|
+
#
|
|
13
|
+
# Behaviour:
|
|
14
|
+
# - One /authors/:key/ page per author (layout: author).
|
|
15
|
+
# - One /authors/ index page (layout: authors).
|
|
16
|
+
# - If a page OR collection document already exists at the target permalink it
|
|
17
|
+
# is left untouched. This site's committed author pages live in
|
|
18
|
+
# pages/_about/authors/ (the `about` collection) with explicit
|
|
19
|
+
# /authors/:key/ permalinks, so they build under GitHub Pages safe mode.
|
|
20
|
+
# - An author entry with `profile: false` in _data/authors.yml is skipped.
|
|
21
|
+
# - Generation can be disabled via _config.yml:
|
|
22
|
+
# authors:
|
|
23
|
+
# generate_pages: false
|
|
24
|
+
#
|
|
25
|
+
# Note: like the other generators in this directory, this runs during a normal
|
|
26
|
+
# `jekyll build`. Sites consuming the theme remotely on GitHub Pages (which does
|
|
27
|
+
# not load custom plugins) can still create author pages by hand using the
|
|
28
|
+
# `author` / `authors` layouts shipped with the theme.
|
|
29
|
+
#
|
|
30
|
+
|
|
31
|
+
module Jekyll
|
|
32
|
+
class AuthorPagesGenerator < Generator
|
|
33
|
+
safe true
|
|
34
|
+
priority :low
|
|
35
|
+
|
|
36
|
+
def generate(site)
|
|
37
|
+
return if generation_disabled?(site)
|
|
38
|
+
|
|
39
|
+
authors = site.data["authors"]
|
|
40
|
+
return unless authors.is_a?(Hash)
|
|
41
|
+
|
|
42
|
+
generate_author_profiles(site, authors)
|
|
43
|
+
generate_authors_index(site)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def generate_author_profiles(site, authors)
|
|
49
|
+
authors.each do |key, data|
|
|
50
|
+
data ||= {}
|
|
51
|
+
next if data["profile"] == false
|
|
52
|
+
|
|
53
|
+
permalink = "/authors/#{Jekyll::Utils.slugify(key)}/"
|
|
54
|
+
next if page_exists?(site, permalink)
|
|
55
|
+
|
|
56
|
+
name = data["name"] || key
|
|
57
|
+
Jekyll.logger.info "AuthorPagesGenerator:", "Generating profile #{permalink}"
|
|
58
|
+
|
|
59
|
+
page = PageWithoutAFile.new(site, site.source, "", "#{key}.html")
|
|
60
|
+
page.data.merge!(
|
|
61
|
+
"layout" => "author",
|
|
62
|
+
"author_key" => key,
|
|
63
|
+
"title" => name,
|
|
64
|
+
"description" => data["bio"] || "Articles and content by #{name}.",
|
|
65
|
+
"permalink" => permalink,
|
|
66
|
+
"sitemap" => true,
|
|
67
|
+
"sidebar" => false,
|
|
68
|
+
"hide_intro" => true
|
|
69
|
+
)
|
|
70
|
+
page.content = ""
|
|
71
|
+
|
|
72
|
+
site.pages << page
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def generate_authors_index(site)
|
|
77
|
+
return if page_exists?(site, "/authors/")
|
|
78
|
+
|
|
79
|
+
Jekyll.logger.info "AuthorPagesGenerator:", "Generating /authors/ index"
|
|
80
|
+
|
|
81
|
+
page = PageWithoutAFile.new(site, site.source, "", "authors.html")
|
|
82
|
+
page.data.merge!(
|
|
83
|
+
"layout" => "authors",
|
|
84
|
+
"title" => "Authors",
|
|
85
|
+
"description" => "Meet the people behind the content.",
|
|
86
|
+
"permalink" => "/authors/",
|
|
87
|
+
"sidebar" => false,
|
|
88
|
+
"hide_intro" => true
|
|
89
|
+
)
|
|
90
|
+
page.content = ""
|
|
91
|
+
|
|
92
|
+
site.pages << page
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# ------------------------------------------------------------------
|
|
96
|
+
# Helpers
|
|
97
|
+
# ------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
def page_exists?(site, permalink)
|
|
100
|
+
normalized = permalink.chomp("/")
|
|
101
|
+
page_match = site.pages.any? do |p|
|
|
102
|
+
url = (p.url || "").chomp("/")
|
|
103
|
+
perm = (p.permalink || "").chomp("/")
|
|
104
|
+
url == normalized || url == permalink || perm == normalized || perm == permalink
|
|
105
|
+
end
|
|
106
|
+
return true if page_match
|
|
107
|
+
|
|
108
|
+
# Committed author pages now live in the `about` collection
|
|
109
|
+
# (pages/_about/authors/*.md), so also honour collection documents.
|
|
110
|
+
site.documents.any? do |d|
|
|
111
|
+
url = (d.url || "").chomp("/")
|
|
112
|
+
perm = (d.data["permalink"] || "").to_s.chomp("/")
|
|
113
|
+
url == normalized || url == permalink || perm == normalized || perm == permalink
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def generation_disabled?(site)
|
|
118
|
+
site.config.dig("authors", "generate_pages") == false
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Author — bylines, "About the Author" cards, and profile pages
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// Pairs with components/author-card.html, components/author-bio.html, and the
|
|
5
|
+
// author / authors layouts. Token-driven with Bootstrap fallbacks; dark-mode
|
|
6
|
+
// safe (avatars and links inherit surrounding color via `text-reset`).
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
// Inline byline — meta rows and card footers
|
|
10
|
+
.author-inline {
|
|
11
|
+
vertical-align: baseline;
|
|
12
|
+
|
|
13
|
+
&__avatar {
|
|
14
|
+
flex: 0 0 auto;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
&__link {
|
|
18
|
+
transition: color var(--zer0-motion-duration-fast, 120ms) var(--zer0-motion-ease-standard);
|
|
19
|
+
|
|
20
|
+
&:hover .author-name,
|
|
21
|
+
&:focus-visible .author-name {
|
|
22
|
+
text-decoration: underline;
|
|
23
|
+
text-underline-offset: 2px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&:focus-visible {
|
|
27
|
+
outline: 2px solid var(--zer0-color-primary, var(--bs-primary));
|
|
28
|
+
outline-offset: 2px;
|
|
29
|
+
border-radius: 0.25rem;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Circular fallback used when an author has no avatar image
|
|
35
|
+
.author-avatar-fallback {
|
|
36
|
+
flex: 0 0 auto;
|
|
37
|
+
background: var(--zer0-color-primary, var(--bs-primary));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// "About the Author" card + profile hero avatar ring
|
|
41
|
+
.author-card,
|
|
42
|
+
.author-card-compact {
|
|
43
|
+
.author-card__avatar,
|
|
44
|
+
img.rounded-circle {
|
|
45
|
+
border: 2px solid rgba(var(--zer0-color-primary-rgb, var(--bs-primary-rgb)), 0.25);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Expertise chips
|
|
50
|
+
.author-expertise .badge {
|
|
51
|
+
font-weight: 500;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Shared tagline styling (full "About the Author" card + profile hero)
|
|
55
|
+
.author-tagline {
|
|
56
|
+
color: var(--zer0-color-text-muted, var(--bs-secondary-color));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// AI author persona markers — a violet accent (the "AI" convention) chosen to
|
|
60
|
+
// stay legible across all skins independent of --zer0-color-primary.
|
|
61
|
+
.author-ai-badge {
|
|
62
|
+
background-color: rgba(111, 66, 193, 0.14);
|
|
63
|
+
color: #6f42c1;
|
|
64
|
+
border: 1px solid rgba(111, 66, 193, 0.4);
|
|
65
|
+
font-size: 0.7em;
|
|
66
|
+
font-weight: 600;
|
|
67
|
+
letter-spacing: 0.04em;
|
|
68
|
+
text-transform: uppercase;
|
|
69
|
+
vertical-align: middle;
|
|
70
|
+
|
|
71
|
+
[data-bs-theme="dark"] & {
|
|
72
|
+
color: #c8a2ff;
|
|
73
|
+
background-color: rgba(111, 66, 193, 0.28);
|
|
74
|
+
border-color: rgba(111, 66, 193, 0.55);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.author-ai-disclosure {
|
|
79
|
+
border-left: 3px solid rgba(111, 66, 193, 0.5);
|
|
80
|
+
padding-left: 0.65rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ----------------------------------------------------------------------------
|
|
84
|
+
// Author profile page (_layouts/author.html)
|
|
85
|
+
// ----------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
// Profile hero
|
|
88
|
+
.author-hero {
|
|
89
|
+
background: var(--zer0-color-bg-elevated, var(--bs-tertiary-bg));
|
|
90
|
+
|
|
91
|
+
&__avatar {
|
|
92
|
+
border: 3px solid rgba(var(--zer0-color-primary-rgb, var(--bs-primary-rgb)), 0.35);
|
|
93
|
+
box-shadow: var(--zer0-shadow-sm);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
&__tagline {
|
|
97
|
+
font-size: 1.05rem;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Interactive content hub
|
|
102
|
+
.author-content {
|
|
103
|
+
// Stats dashboard — doubles as the type-filter control group
|
|
104
|
+
.author-stats {
|
|
105
|
+
display: flex;
|
|
106
|
+
flex-wrap: wrap;
|
|
107
|
+
gap: 0.75rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.author-stat {
|
|
111
|
+
flex: 1 1 auto;
|
|
112
|
+
min-width: 5.5rem;
|
|
113
|
+
display: flex;
|
|
114
|
+
flex-direction: column;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 0.15rem;
|
|
117
|
+
padding: 0.75rem 1rem;
|
|
118
|
+
background: var(--zer0-color-bg-elevated, var(--bs-tertiary-bg));
|
|
119
|
+
border: 1px solid var(--zer0-color-border, var(--bs-border-color));
|
|
120
|
+
border-radius: 0.625rem;
|
|
121
|
+
color: inherit;
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
transition:
|
|
124
|
+
transform var(--zer0-motion-duration-fast, 120ms) var(--zer0-motion-ease-standard),
|
|
125
|
+
box-shadow var(--zer0-motion-duration-base, 200ms) var(--zer0-motion-ease-standard),
|
|
126
|
+
border-color var(--zer0-motion-duration-fast, 120ms) var(--zer0-motion-ease-standard),
|
|
127
|
+
background-color var(--zer0-motion-duration-fast, 120ms) var(--zer0-motion-ease-standard);
|
|
128
|
+
|
|
129
|
+
&:hover {
|
|
130
|
+
transform: translateY(-2px);
|
|
131
|
+
box-shadow: var(--zer0-shadow-md);
|
|
132
|
+
border-color: rgba(var(--zer0-color-primary-rgb, var(--bs-primary-rgb)), 0.5);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&:focus-visible {
|
|
136
|
+
outline: 2px solid var(--zer0-color-primary, var(--bs-primary));
|
|
137
|
+
outline-offset: 2px;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&.is-active {
|
|
141
|
+
background: var(--zer0-color-primary, var(--bs-primary));
|
|
142
|
+
border-color: var(--zer0-color-primary, var(--bs-primary));
|
|
143
|
+
color: #fff;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
&__num {
|
|
147
|
+
font-size: 1.5rem;
|
|
148
|
+
font-weight: 700;
|
|
149
|
+
line-height: 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&__label {
|
|
153
|
+
font-size: 0.8rem;
|
|
154
|
+
white-space: nowrap;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Search input with a leading icon
|
|
159
|
+
.author-search__icon {
|
|
160
|
+
position: absolute;
|
|
161
|
+
top: 50%;
|
|
162
|
+
left: 0.75rem;
|
|
163
|
+
transform: translateY(-50%);
|
|
164
|
+
color: var(--bs-secondary-color);
|
|
165
|
+
pointer-events: none;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.author-search .form-control {
|
|
169
|
+
padding-left: 2.25rem;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Topic / tag cloud
|
|
173
|
+
.author-tagcloud {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-wrap: wrap;
|
|
176
|
+
align-items: center;
|
|
177
|
+
gap: 0.35rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.author-tag {
|
|
181
|
+
border: 1px solid var(--zer0-color-border, var(--bs-border-color));
|
|
182
|
+
background: transparent;
|
|
183
|
+
color: var(--bs-body-color);
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
transition:
|
|
186
|
+
background-color var(--zer0-motion-duration-fast, 120ms) var(--zer0-motion-ease-standard),
|
|
187
|
+
color var(--zer0-motion-duration-fast, 120ms) var(--zer0-motion-ease-standard),
|
|
188
|
+
border-color var(--zer0-motion-duration-fast, 120ms) var(--zer0-motion-ease-standard);
|
|
189
|
+
|
|
190
|
+
&:hover { border-color: rgba(var(--zer0-color-primary-rgb, var(--bs-primary-rgb)), 0.6); }
|
|
191
|
+
&:focus-visible { outline: 2px solid var(--zer0-color-primary, var(--bs-primary)); outline-offset: 2px; }
|
|
192
|
+
|
|
193
|
+
&.is-active {
|
|
194
|
+
background: var(--zer0-color-primary, var(--bs-primary));
|
|
195
|
+
border-color: var(--zer0-color-primary, var(--bs-primary));
|
|
196
|
+
color: #fff;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Grid item entrance animation — only when motion is allowed. Re-runs each
|
|
201
|
+
// time an item returns from display:none, giving a gentle filter transition.
|
|
202
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
203
|
+
.author-item:not(.d-none) {
|
|
204
|
+
animation: authorItemIn var(--zer0-motion-duration-base, 220ms) var(--zer0-motion-ease-standard) both;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@keyframes authorItemIn {
|
|
210
|
+
from { opacity: 0; transform: translateY(6px); }
|
|
211
|
+
to { opacity: 1; transform: translateY(0); }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Authors index directory cards
|
|
215
|
+
.authors-index {
|
|
216
|
+
.author-card-compact {
|
|
217
|
+
width: 100%;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -149,7 +149,14 @@ $zer0-content-table-scopes: (
|
|
|
149
149
|
margin-bottom: var(--zer0-space-4);
|
|
150
150
|
border: 1px solid var(--zer0-color-border);
|
|
151
151
|
border-radius: 0.5rem;
|
|
152
|
-
|
|
152
|
+
// Wide tables must scroll WITHIN this card, not push the page wider. The
|
|
153
|
+
// wrapper sits between the content scope and the table, so the
|
|
154
|
+
// `#{$scope} > table` mobile fallback below no longer matches — without a
|
|
155
|
+
// scroll context here a wide markdown table forces a horizontal page
|
|
156
|
+
// scrollbar, which makes the fixed-top navbar look "cut off" on the right.
|
|
157
|
+
// (Pairs with the `html { overflow-x: clip }` safety net in _global-chrome.)
|
|
158
|
+
overflow-x: auto;
|
|
159
|
+
-webkit-overflow-scrolling: touch;
|
|
153
160
|
background-color: var(--zer0-color-bg);
|
|
154
161
|
|
|
155
162
|
> .content-table-toolbar {
|
|
@@ -256,5 +263,13 @@ $zer0-content-table-scopes: (
|
|
|
256
263
|
}
|
|
257
264
|
}
|
|
258
265
|
}
|
|
266
|
+
|
|
267
|
+
// Long unbroken inline-code tokens (URLs, hashes, flags) must wrap rather
|
|
268
|
+
// than force a horizontal page scrollbar on narrow viewports. Scoped to
|
|
269
|
+
// inline code only — block code (`pre code`) keeps its own scroll context.
|
|
270
|
+
:not(pre) > code {
|
|
271
|
+
overflow-wrap: break-word;
|
|
272
|
+
word-break: break-word;
|
|
273
|
+
}
|
|
259
274
|
}
|
|
260
275
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
|
|
3
|
+
// Notes & notebooks index grids + difficulty badges
|
|
4
|
+
|
|
5
|
+
// ----------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
// Extracted from custom.scss (Phase 5).
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
// ==============================================================================
|
|
12
|
+
// NOTES LAYOUT STYLES
|
|
13
|
+
// ==============================================================================
|
|
14
|
+
// MOVED → _sass/components/_notes.scss (token-aware, dark-mode-safe)
|
|
15
|
+
// The .note-header / .note-content / .related-notes / .note-navigation rules
|
|
16
|
+
// are now defined in the components partial. Difficulty badges remain here
|
|
17
|
+
// because they map to fixed semantic colors per the existing API.
|
|
18
|
+
|
|
19
|
+
// Difficulty badge colors (semantic — kept as fixed values)
|
|
20
|
+
.badge-beginner {
|
|
21
|
+
background-color: var(--zer0-color-success, #198754) !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.badge-intermediate {
|
|
25
|
+
background-color: var(--zer0-color-warning, #ffc107) !important;
|
|
26
|
+
color: #212529 !important;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.badge-advanced {
|
|
30
|
+
background-color: var(--zer0-color-danger, #dc3545) !important;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Share buttons
|
|
34
|
+
.share-buttons {
|
|
35
|
+
margin-top: 2rem;
|
|
36
|
+
padding-top: 1.5rem;
|
|
37
|
+
border-top: 1px solid #dee2e6;
|
|
38
|
+
|
|
39
|
+
.btn-share {
|
|
40
|
+
padding: 0.5rem 1rem;
|
|
41
|
+
border-radius: 0.375rem;
|
|
42
|
+
text-decoration: none;
|
|
43
|
+
display: inline-flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: 0.5rem;
|
|
46
|
+
transition: transform 0.2s ease;
|
|
47
|
+
|
|
48
|
+
&:hover {
|
|
49
|
+
transform: scale(1.05);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Notes index page styles
|
|
55
|
+
#notes-grid {
|
|
56
|
+
.note-card {
|
|
57
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
58
|
+
|
|
59
|
+
&[style*="display: none"] {
|
|
60
|
+
opacity: 0;
|
|
61
|
+
transform: scale(0.95);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.card {
|
|
66
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
67
|
+
|
|
68
|
+
&:hover {
|
|
69
|
+
transform: translateY(-4px);
|
|
70
|
+
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ==============================================================================
|
|
76
|
+
// NOTEBOOKS INDEX PAGE STYLES
|
|
77
|
+
// ==============================================================================
|
|
78
|
+
|
|
79
|
+
#notebooks-grid {
|
|
80
|
+
.notebook-card {
|
|
81
|
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
|
82
|
+
|
|
83
|
+
&[style*="display: none"] {
|
|
84
|
+
opacity: 0;
|
|
85
|
+
transform: scale(0.95);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.card {
|
|
90
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
91
|
+
border-left: 4px solid #0d6efd;
|
|
92
|
+
|
|
93
|
+
&:hover {
|
|
94
|
+
transform: translateY(-4px);
|
|
95
|
+
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.card-header {
|
|
99
|
+
border-bottom: none;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Search modal — quick-search overlay + collapsible folder tree
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// Extracted from custom.scss. Styles the search modal (triggered from the
|
|
5
|
+
// navbar Search button) and the nested folder list used in its results.
|
|
6
|
+
// Selectors are unique to this component, so import position is order-neutral.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
.search-modal .modal-content {
|
|
10
|
+
border-radius: 0.75rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.search-modal .input-group-text {
|
|
14
|
+
background-color: var(--bs-tertiary-bg);
|
|
15
|
+
border-color: var(--bs-border-color);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.search-modal kbd {
|
|
19
|
+
background-color: var(--bs-tertiary-bg);
|
|
20
|
+
color: var(--bs-body-color);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.search-modal .search-results {
|
|
24
|
+
max-height: 50vh;
|
|
25
|
+
overflow-y: auto;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.search-modal .list-group-item {
|
|
29
|
+
border-color: var(--bs-border-color-translucent);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.nested-list-group {
|
|
33
|
+
display: none;
|
|
34
|
+
}
|
|
35
|
+
.nested-list-group.show {
|
|
36
|
+
display: block;
|
|
37
|
+
}
|
|
38
|
+
.folder {
|
|
39
|
+
cursor: pointer;
|
|
40
|
+
}
|