methanol 0.0.18 → 0.0.20

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.
@@ -28,6 +28,7 @@ let initPromise = null
28
28
  let pages = []
29
29
  let pagesContext = null
30
30
  let components = null
31
+ let mdxPageIds = new Set()
31
32
 
32
33
  const ensureInit = async () => {
33
34
  if (initPromise) return initPromise
@@ -112,31 +113,27 @@ const logPageError = (phase, page, error) => {
112
113
  const handleSetPages = async (message) => {
113
114
  const { pages: nextPages, excludedRoutes = [], excludedDirs = [] } = message || {}
114
115
  pages = Array.isArray(nextPages) ? nextPages : []
116
+ mdxPageIds = new Set()
115
117
  await rebuildPagesContext(new Set(excludedRoutes), new Set(excludedDirs))
116
118
  }
117
119
 
118
120
  const handleSyncUpdates = async (message) => {
119
- const { updates = [], titles = null, excludedRoutes = null, excludedDirs = null } = message || {}
120
- if (Array.isArray(titles)) {
121
- for (let i = 0; i < titles.length; i += 1) {
122
- const page = pages[i]
123
- if (!page) continue
124
- if (titles[i] !== undefined) {
125
- page.title = titles[i]
126
- }
127
- }
128
- }
121
+ const { updates = [], excludedRoutes = null, excludedDirs = null } = message || {}
129
122
  for (const update of updates) {
130
123
  const page = pages[update.id]
131
124
  if (!page) continue
132
125
  if (update.title !== undefined) page.title = update.title
133
- if (update.toc !== undefined) page.toc = update.toc
134
126
  }
135
- await rebuildPagesContext(
136
- excludedRoutes ? new Set(excludedRoutes) : pagesContext?.excludedRoutes || new Set(),
137
- excludedDirs ? new Set(excludedDirs) : pagesContext?.excludedDirs || new Set()
138
- )
139
- for (const page of pages) {
127
+ if (!pagesContext || excludedRoutes || excludedDirs) {
128
+ await rebuildPagesContext(
129
+ excludedRoutes ? new Set(excludedRoutes) : pagesContext?.excludedRoutes || new Set(),
130
+ excludedDirs ? new Set(excludedDirs) : pagesContext?.excludedDirs || new Set()
131
+ )
132
+ } else {
133
+ pagesContext.refreshPagesTree?.(true)
134
+ }
135
+ for (const id of mdxPageIds) {
136
+ const page = pages[id]
140
137
  if (!page?.mdxCtx) continue
141
138
  refreshMdxCtx(page)
142
139
  }
@@ -159,6 +156,7 @@ const handleCompile = async (message) => {
159
156
  lazyPagesTree: true,
160
157
  refreshPagesTree: false
161
158
  })
159
+ mdxPageIds.add(id)
162
160
  updates.push({ id, title: page.title, toc: page.toc || null })
163
161
  } catch (error) {
164
162
  logPageError('MDX compile', page, error)
@@ -173,7 +171,6 @@ const handleCompile = async (message) => {
173
171
  const handleRender = async (message) => {
174
172
  const { ids = [], stage } = message || {}
175
173
  const { renderHtml } = await import('../mdx.js')
176
- const results = []
177
174
  let completed = 0
178
175
  for (const id of ids) {
179
176
  const page = pages[id]
@@ -190,7 +187,7 @@ const handleRender = async (message) => {
190
187
  pagesContext,
191
188
  pageMeta: page
192
189
  })
193
- results.push({ id, html })
190
+ parentPort?.postMessage({ type: 'result', stage, result: { id, html } })
194
191
  } catch (error) {
195
192
  logPageError('MDX render', page, error)
196
193
  throw error
@@ -198,7 +195,35 @@ const handleRender = async (message) => {
198
195
  completed += 1
199
196
  parentPort?.postMessage({ type: 'progress', stage, completed })
200
197
  }
201
- return results
198
+ }
199
+
200
+ const handleRss = async (message) => {
201
+ const { ids = [], stage } = message || {}
202
+ const { renderPageContent } = await import('../mdx.js')
203
+ let completed = 0
204
+ for (const id of ids) {
205
+ const page = pages[id]
206
+ if (!page) {
207
+ completed += 1
208
+ parentPort?.postMessage({ type: 'progress', stage, completed })
209
+ continue
210
+ }
211
+ try {
212
+ const content = await renderPageContent({
213
+ routePath: page.routePath,
214
+ path: page.path,
215
+ components,
216
+ pagesContext,
217
+ pageMeta: page
218
+ })
219
+ parentPort?.postMessage({ type: 'result', stage, result: { id, content } })
220
+ } catch (error) {
221
+ logPageError('RSS render', page, error)
222
+ throw error
223
+ }
224
+ completed += 1
225
+ parentPort?.postMessage({ type: 'progress', stage, completed })
226
+ }
202
227
  }
203
228
 
204
229
  parentPort?.on('message', async (message) => {
@@ -221,8 +246,13 @@ parentPort?.on('message', async (message) => {
221
246
  return
222
247
  }
223
248
  if (type === 'render') {
224
- const results = await handleRender(message)
225
- parentPort?.postMessage({ type: 'done', stage, results })
249
+ await handleRender(message)
250
+ parentPort?.postMessage({ type: 'done', stage })
251
+ return
252
+ }
253
+ if (type === 'rss') {
254
+ await handleRss(message)
255
+ parentPort?.postMessage({ type: 'done', stage })
226
256
  return
227
257
  }
228
258
  } catch (error) {
@@ -0,0 +1,52 @@
1
+ # Themes
2
+
3
+ Methanol ships with built-in themes under `themes/`.
4
+
5
+ ## Built-in themes
6
+
7
+ - `default`: Documentation-style theme (sidebar + ToC).
8
+ - `blog`: Blog theme (post list, tags/categories UI).
9
+
10
+ ## Using a theme
11
+
12
+ CLI:
13
+
14
+ ```bash
15
+ methanol build --theme blog
16
+ methanol dev --theme default
17
+ ```
18
+
19
+ Config (`methanol.config.*`):
20
+
21
+ ```js
22
+ export default () => ({
23
+ theme: 'blog'
24
+ })
25
+ ```
26
+
27
+ ## Using a local theme (in your project)
28
+
29
+ Theme name resolution only applies when `theme` is a string (built-in or `methanol-theme-xxx` from `node_modules`).
30
+ If your theme lives inside your project, import it and pass the theme object/factory:
31
+
32
+ ```js
33
+ import createTheme from './themes/my-theme/index.js'
34
+
35
+ export default () => ({
36
+ theme: createTheme()
37
+ })
38
+ ```
39
+
40
+ ## Publishing a theme
41
+
42
+ If you publish a theme as an npm package named `methanol-theme-xxx`, users can enable it via `--theme xxx` or `theme: 'xxx'`.
43
+
44
+ ## Theme structure (convention)
45
+
46
+ - `index.js`: entrypoint that exports a theme object or a factory function (recommended).
47
+ - `src/`: theme runtime/template modules (e.g. `src/page.jsx`).
48
+ - `components/`: theme components (used by MDX).
49
+ - `pages/`: theme-provided pages (e.g. `_404.mdx`, `offline.mdx`).
50
+ - `public/`: theme static assets (merged with user `public/`).
51
+ - `sources/`: extra source mappings exposed via `theme.sources`.
52
+
@@ -1,26 +1,46 @@
1
1
  # Blog Theme
2
2
 
3
- A simple, clean blog theme for Methanol.
3
+ A blog theme for Methanol.
4
4
 
5
5
  ## Features
6
- - Clean typography
7
- - Post list on homepage
8
- - Responsive design
9
- - Dark mode support (via system preference)
6
+
7
+ - Post list and post pages
8
+ - Category/collection views (theme-provided client UI)
9
+ - Responsive layout
10
10
 
11
11
  ## Usage
12
12
 
13
- To use this theme, configure your Methanol project to point to this directory.
13
+ CLI:
14
+
15
+ ```bash
16
+ methanol build --theme blog
17
+ methanol dev --theme blog
18
+ ```
19
+
20
+ Config (`methanol.config.*`):
14
21
 
15
22
  ```js
16
- // methanol.config.js
17
- export default {
18
- theme: './themes/blog',
19
- // ...
20
- }
23
+ export default () => ({
24
+ theme: 'blog'
25
+ })
21
26
  ```
22
27
 
23
28
  ## Structure
24
- - `src/page.jsx`: Main layout template.
25
- - `sources/style.css`: Stylesheet.
26
- - `pages/`: Default pages (Home, 404, Offline).
29
+
30
+ - `src/page.jsx`: main layout template
31
+ - `pages/`: theme pages (including special pages like `_404.mdx` and `offline.mdx` when present)
32
+ - `components/`: theme components used by MDX
33
+ - `public/`: theme static assets
34
+ - `sources/`: theme source mappings (used by `theme.sources`)
35
+
36
+ ## Local development
37
+
38
+ If you want to use the theme from a local folder (instead of built-in name / npm package), import it in config:
39
+
40
+ ```js
41
+ import createBlogTheme from './themes/blog/index.js'
42
+
43
+ export default () => ({
44
+ theme: createBlogTheme()
45
+ })
46
+ ```
@@ -98,9 +98,31 @@ a {
98
98
  flex-shrink: 0;
99
99
  }
100
100
 
101
+ .header-actions .rss-link {
102
+ color: var(--text-muted);
103
+ text-decoration: none;
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: center;
107
+ width: 2.25rem;
108
+ height: 2.25rem;
109
+ border-radius: 999px;
110
+ transition: all 0.2s;
111
+ }
112
+
113
+ .header-actions .rss-link:hover {
114
+ color: var(--text);
115
+ background: var(--bg-soft);
116
+ }
117
+
118
+ .rss-label {
119
+ display: none;
120
+ }
121
+
101
122
  .blog-nav {
102
123
  display: flex;
103
124
  gap: 1.25rem;
125
+ align-items: center;
104
126
  }
105
127
 
106
128
  .blog-nav a {
@@ -551,11 +573,29 @@ h6:hover .heading-anchor, h6:active .heading-anchor {
551
573
  visibility: visible;
552
574
  }
553
575
 
554
- .nav-toggle:checked ~ .nav-toggle-label {
555
- color: var(--primary);
576
+ .nav-toggle:checked ~ .nav-toggle-label {
577
+ color: var(--primary);
578
+ }
579
+
580
+ .header-actions .rss-link {
581
+ width: 100%;
582
+ height: auto;
583
+ border-radius: 0;
584
+ justify-content: flex-start;
585
+ padding: 0.75rem var(--container-px);
586
+ background: transparent !important;
587
+ font-weight: 500;
588
+ }
589
+
590
+ .header-actions .rss-link:hover {
591
+ color: var(--primary);
592
+ }
593
+
594
+ .rss-label {
595
+ display: inline;
596
+ margin-left: 0.5rem;
597
+ }
556
598
  }
557
- }
558
-
559
599
  @media (max-width: 640px) {
560
600
  .blog-header { padding: 1rem 0; }
561
601
  .header-container { gap: 1rem; }
@@ -40,9 +40,9 @@ const renderPostCards = (posts = []) =>
40
40
  )
41
41
  })
42
42
 
43
- export const LayoutCategories = ({ PageContent, title, pages, navLinks, components }) => {
43
+ export const LayoutCategories = ({ PageContent, title, pages, navLinks, components, currentRoutePath, hiddenPrefixes }) => {
44
44
  const { CategoryView } = components || {}
45
- const filteredPosts = filterBlogPosts(pages, navLinks)
45
+ const filteredPosts = filterBlogPosts(pages, navLinks, { currentRoutePath, hiddenPrefixes })
46
46
  const staticPosts = mapStaticPosts(filteredPosts)
47
47
  const categories = collectCategories(filteredPosts)
48
48
  const visiblePosts = staticPosts.slice(0, 10)
@@ -39,9 +39,9 @@ const renderPostCards = (posts = [], collectionTitles = {}) =>
39
39
  )
40
40
  })
41
41
 
42
- export const LayoutCollections = ({ PageContent, title, pages, pagesByRoute, navLinks, components }) => {
42
+ export const LayoutCollections = ({ PageContent, title, pages, pagesByRoute, navLinks, components, currentRoutePath, hiddenPrefixes }) => {
43
43
  const { CollectionView } = components || {}
44
- const filteredPosts = filterBlogPosts(pages, navLinks)
44
+ const filteredPosts = filterBlogPosts(pages, navLinks, { currentRoutePath, hiddenPrefixes })
45
45
  const staticPosts = mapStaticPosts(filteredPosts)
46
46
  const collectionTitles = collectCollectionTitles(staticPosts, pagesByRoute)
47
47
  const visiblePosts = staticPosts.slice(0, 10)
@@ -35,9 +35,9 @@ const renderPostCards = (posts = []) =>
35
35
  )
36
36
  })
37
37
 
38
- export const LayoutHome = ({ PageContent, pages, navLinks, components }) => {
38
+ export const LayoutHome = ({ PageContent, pages, navLinks, components, currentRoutePath, hiddenPrefixes }) => {
39
39
  const { PostList } = components || {}
40
- const filteredPosts = filterBlogPosts(pages, navLinks)
40
+ const filteredPosts = filterBlogPosts(pages, navLinks, { currentRoutePath, hiddenPrefixes })
41
41
  const staticPosts = mapStaticPosts(filteredPosts)
42
42
  const visiblePosts = staticPosts.slice(0, 10)
43
43
  const hasMore = staticPosts.length > visiblePosts.length
@@ -24,7 +24,7 @@ import { LayoutCategories } from './layout-categories.jsx'
24
24
  import { LayoutCollections } from './layout-collections.jsx'
25
25
  import { LayoutPost } from './layout-post.jsx'
26
26
 
27
- const Header = ({ siteName, navLinks, components, pagefindOptions }) => {
27
+ const Header = ({ siteName, navLinks, components, pagefindOptions, rssHref, feedLabel }) => {
28
28
  const { ThemeSearchBox } = components || {}
29
29
  return (
30
30
  <header class="blog-header">
@@ -34,7 +34,6 @@ const Header = ({ siteName, navLinks, components, pagefindOptions }) => {
34
34
  </a>
35
35
  <div class="header-actions">
36
36
  {ThemeSearchBox && <ThemeSearchBox options={pagefindOptions} />}
37
-
38
37
  <input type="checkbox" id="nav-toggle" class="nav-toggle" />
39
38
  <label for="nav-toggle" class="nav-toggle-label">
40
39
  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -50,6 +49,25 @@ const Header = ({ siteName, navLinks, components, pagefindOptions }) => {
50
49
  {link.label}
51
50
  </a>
52
51
  ))}
52
+ {rssHref ? (
53
+ <a class="rss-link" href={rssHref} aria-label={feedLabel} title={feedLabel}>
54
+ <svg
55
+ width="20"
56
+ height="20"
57
+ viewBox="0 0 24 24"
58
+ fill="none"
59
+ stroke="currentColor"
60
+ stroke-width="2"
61
+ stroke-linecap="round"
62
+ stroke-linejoin="round"
63
+ >
64
+ <path d="M4 11a9 9 0 0 1 9 9"></path>
65
+ <path d="M4 4a16 16 0 0 1 16 16"></path>
66
+ <circle cx="5" cy="19" r="1"></circle>
67
+ </svg>
68
+ <span class="rss-label">{feedLabel}</span>
69
+ </a>
70
+ ) : null}
53
71
  </nav>
54
72
  </div>
55
73
  </div>
@@ -57,11 +75,11 @@ const Header = ({ siteName, navLinks, components, pagefindOptions }) => {
57
75
  )
58
76
  }
59
77
 
60
- const Footer = ({ siteName }) => (
78
+ const Footer = ({ siteName, owner }) => (
61
79
  <footer class="blog-footer">
62
80
  <div class="container">
63
81
  <p>
64
- &copy; {new Date().getFullYear()} {siteName}. Powered by <a href="https://methanol.sudoMaker.com">Methanol</a>.
82
+ &copy; {new Date().getFullYear()} {owner || siteName}. Powered by <a href="https://methanol.sudoMaker.com">Methanol</a>.
65
83
  </p>
66
84
  </div>
67
85
  </footer>
@@ -70,6 +88,7 @@ const Footer = ({ siteName }) => (
70
88
  const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) => {
71
89
  const page = ctx.page
72
90
  const siteName = ctx.site.name || 'Methanol Blog'
91
+ const siteOwner = ctx.site.owner || null
73
92
  const title = page.title || siteName
74
93
 
75
94
  const isHome = page.routePath === '/'
@@ -90,11 +109,27 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
90
109
  return link
91
110
  })
92
111
 
112
+ const currentRoutePath = page.routePath || '/'
93
113
  const isCategoriesPage = page.routePath === '/categories'
94
114
  const isCollectionsPage = page.routePath === '/collections'
115
+ const hiddenPrefixes = (ctx.pages || [])
116
+ .filter(
117
+ (entry) =>
118
+ entry.isIndex &&
119
+ entry.hidden &&
120
+ entry.routePath &&
121
+ entry.routePath !== '/' &&
122
+ entry.routePath !== '/404' &&
123
+ entry.routePath !== '/offline'
124
+ )
125
+ .map((entry) => (entry.routePath.endsWith('/') ? entry.routePath : `${entry.routePath}/`))
95
126
 
96
127
  const pagefindEnabled = ctx.site.pagefind?.enabled !== false
97
128
  const pagefindOptions = ctx.site.pagefind?.options || null
129
+ const feedInfo = ctx.site.feed
130
+ const rssHref = feedInfo?.enabled ? feedInfo.href : null
131
+ const feedType = feedInfo?.atom ? 'application/atom+xml' : 'application/rss+xml'
132
+ const feedLabel = feedInfo?.atom ? 'Atom' : 'RSS'
98
133
 
99
134
  return (
100
135
  <>
@@ -107,6 +142,7 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
107
142
  {title} | {siteName}
108
143
  </title>
109
144
  <link rel="stylesheet" href="/.methanol_theme_blog/style.css" />
145
+ {rssHref ? <link rel="alternate" type={feedType} title={`${siteName} ${feedLabel}`} href={rssHref} /> : null}
110
146
  <ExtraHead />
111
147
  </head>
112
148
  <body>
@@ -116,10 +152,19 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
116
152
  navLinks={resolvedNavLinks}
117
153
  components={pagefindEnabled ? components : {}}
118
154
  pagefindOptions={pagefindOptions}
155
+ rssHref={rssHref}
156
+ feedLabel={feedLabel}
119
157
  />
120
158
  <main class="container main-content">
121
159
  {isHome ? (
122
- <LayoutHome PageContent={PageContent} pages={ctx.pages} navLinks={navLinks} components={components} />
160
+ <LayoutHome
161
+ PageContent={PageContent}
162
+ pages={ctx.pages}
163
+ navLinks={navLinks}
164
+ components={components}
165
+ currentRoutePath={currentRoutePath}
166
+ hiddenPrefixes={hiddenPrefixes}
167
+ />
123
168
  ) : isCategoriesPage ? (
124
169
  <LayoutCategories
125
170
  PageContent={PageContent}
@@ -127,6 +172,8 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
127
172
  pages={ctx.pages}
128
173
  navLinks={navLinks}
129
174
  components={components}
175
+ currentRoutePath={currentRoutePath}
176
+ hiddenPrefixes={hiddenPrefixes}
130
177
  />
131
178
  ) : isCollectionsPage ? (
132
179
  <LayoutCollections
@@ -136,12 +183,14 @@ const PAGE_TEMPLATE = ({ PageContent, ExtraHead, components, ctx, withBase }) =>
136
183
  pagesByRoute={ctx.pagesByRoute}
137
184
  navLinks={navLinks}
138
185
  components={components}
186
+ currentRoutePath={currentRoutePath}
187
+ hiddenPrefixes={hiddenPrefixes}
139
188
  />
140
189
  ) : (
141
190
  <LayoutPost PageContent={PageContent} title={title} page={page} />
142
191
  )}
143
192
  </main>
144
- <Footer siteName={siteName} />
193
+ <Footer siteName={siteName} owner={siteOwner} />
145
194
  </div>
146
195
  </body>
147
196
  </html>
@@ -29,15 +29,25 @@ const isBlogPost = (page) => {
29
29
  return true
30
30
  }
31
31
 
32
- export const filterBlogPosts = (pages, navLinks = []) => {
32
+ export const filterBlogPosts = (pages, navLinks = [], options = {}) => {
33
33
  const excluded = new Set((navLinks || []).map((link) => link?.href).filter(Boolean))
34
34
  const list = (pages || []).filter((page) => isBlogPost(page) && !excluded.has(page.routeHref))
35
- list.sort((a, b) => {
35
+ const { currentRoutePath, hiddenPrefixes } = options || {}
36
+ const activeRoute = typeof currentRoutePath === 'string' ? currentRoutePath : ''
37
+ const hiddenScopes = Array.isArray(hiddenPrefixes) ? hiddenPrefixes.filter(Boolean) : []
38
+ const filtered = hiddenScopes.length
39
+ ? list.filter((page) => {
40
+ const hiddenScope = hiddenScopes.find((prefix) => page.routePath?.startsWith(prefix))
41
+ if (!hiddenScope) return true
42
+ return activeRoute.startsWith(hiddenScope)
43
+ })
44
+ : list
45
+ filtered.sort((a, b) => {
36
46
  const dateA = new Date(a.frontmatter?.date || a.stats?.createdAt || 0)
37
47
  const dateB = new Date(b.frontmatter?.date || b.stats?.createdAt || 0)
38
48
  return dateB - dateA
39
49
  })
40
- return list
50
+ return filtered
41
51
  }
42
52
 
43
53
  export const getExcerpt = (page) => extractExcerpt(page)
@@ -0,0 +1,26 @@
1
+ # Default Theme
2
+
3
+ The default Methanol theme is designed for documentation sites (sidebar navigation + table of contents).
4
+
5
+ ## Enable
6
+
7
+ CLI:
8
+
9
+ ```bash
10
+ methanol build --theme default
11
+ methanol dev --theme default
12
+ ```
13
+
14
+ Config:
15
+
16
+ ```js
17
+ export default () => ({
18
+ theme: 'default'
19
+ })
20
+ ```
21
+
22
+ ## Notes
23
+
24
+ - User `public/` assets override theme-provided `public/` assets.
25
+ - Theme pages under `pages/` can provide special routes like `_404.mdx` and `offline.mdx`.
26
+
@@ -75,6 +75,19 @@ export default function () {
75
75
  localStorage.setItem('methanol-theme', theme.value)
76
76
  document.documentElement.classList.toggle('light', theme.value === 'light')
77
77
  document.documentElement.classList.toggle('dark', theme.value === 'dark')
78
+
79
+ const metas = document.querySelectorAll('meta[name="theme-color"]')
80
+ let meta = metas[0]
81
+ if (!meta) {
82
+ meta = document.createElement('meta')
83
+ meta.name = 'theme-color'
84
+ document.head.appendChild(meta)
85
+ }
86
+ for (let i = 1; i < metas.length; i++) {
87
+ metas[i].remove()
88
+ }
89
+ meta.content = theme.value === 'light' ? '#ffffff' : '#09090b'
90
+ meta.removeAttribute('media')
78
91
  }
79
92
 
80
93
  const CurrentIcon = $(() => {
@@ -470,6 +470,33 @@ a {
470
470
  flex-shrink: 0; /* Prevent footer from shrinking */
471
471
  }
472
472
 
473
+ .sidebar-footer .rss-link {
474
+ display: flex;
475
+ align-items: center;
476
+ justify-content: center;
477
+ width: 2.25rem;
478
+ height: 2.25rem;
479
+ background: var(--surface-muted);
480
+ border: 1px solid var(--border);
481
+ border-radius: var(--radius);
482
+ color: var(--text);
483
+ transition: all 0.2s ease;
484
+ flex-shrink: 0;
485
+ }
486
+
487
+ .sidebar-footer .rss-link:hover {
488
+ background: var(--hover-bg);
489
+ border-color: var(--muted);
490
+ }
491
+
492
+ .sidebar-footer .rss-link:active {
493
+ transform: scale(0.96);
494
+ }
495
+
496
+ .sidebar-footer .rss-link svg {
497
+ opacity: 0.8;
498
+ }
499
+
473
500
  .lang-switch-wrapper {
474
501
  flex: 1;
475
502
  position: relative;
@@ -1825,4 +1852,3 @@ a {
1825
1852
  size: auto;
1826
1853
  }
1827
1854
  }
1828
-
@@ -25,6 +25,19 @@
25
25
  document.documentElement.classList.toggle('light', theme === 'light')
26
26
  document.documentElement.classList.toggle('dark', theme === 'dark')
27
27
 
28
+ const metas = document.querySelectorAll('meta[name="theme-color"]')
29
+ let meta = metas[0]
30
+ if (!meta) {
31
+ meta = document.createElement('meta')
32
+ meta.name = 'theme-color'
33
+ document.head.appendChild(meta)
34
+ }
35
+ for (let i = 1; i < metas.length; i++) {
36
+ metas[i].remove()
37
+ }
38
+ meta.content = theme === 'light' ? '#ffffff' : '#09090b'
39
+ meta.removeAttribute('media')
40
+
28
41
  const savedAccent = localStorage.getItem('methanol-accent')
29
42
  if (savedAccent && savedAccent !== 'default') {
30
43
  document.documentElement.classList.add('accent-' + savedAccent)