also looking at this
fix(spa): repair client-side router and polish default theme
#71
9 files
+1037
-170
| @@ -39,12 +39,10 @@ async function generateSidebar(config: BunPressConfig, currentPath: string): Pro | ||
| 39 | 39 | const sectionsHtml = await Promise.all(sidebarSections.map(async (section) => { |
| 40 | 40 | const itemsHtml = section.items |
| 41 | 41 | ? section.items.map((item: SidebarItem) => { |
| 42 | // Use the link as-is (no /docs/ prefix needed) | |
| 43 | 42 | const link = item.link || '/' |
| 44 | ||
| 45 | 43 | const isActive = link === currentPath || item.link === currentPath |
| 46 | const activeStyle = isActive ? 'color: var(--bp-c-brand-1); font-weight: 500;' : '' | |
| 47 | return `<li><a href="${link}" style="display: block; padding: 6px 24px; color: var(--bp-c-text-2); text-decoration: none; font-size: 14px; transition: color 0.25s; ${activeStyle}" onmouseover="this.style.color='var(--bp-c-brand-1)'" onmouseout="this.style.color='${isActive ? 'var(--bp-c-brand-1)' : 'var(--bp-c-text-2)'}'">${item.text}</a></li>` | |
| 44 | const cls = isActive ? 'VPSidebarItem-link is-active' : 'VPSidebarItem-link' | |
| 45 | return `<li><a class="${cls}" href="${link}">${item.text}</a></li>` | |
| 48 | 46 | }).join('') |
| 49 | 47 | : '' |
| 50 | 48 | |
| @@ -149,132 +147,300 @@ function injectSPARouter(html: string): string { | ||
| 149 | 147 | * browser back/forward. No framework dependencies. |
| 150 | 148 | */ |
| 151 | 149 | function generateSPARouterScript(): string { |
| 152 | return `<script> | |
| 150 | return `<script data-bp-router="1"> | |
| 153 | 151 | (function(){ |
| 154 | var cache = {}; | |
| 152 | if (history.scrollRestoration) history.scrollRestoration = 'manual'; | |
| 153 | ||
| 154 | var cache = Object.create(null); | |
| 155 | 155 | var parser = new DOMParser(); |
| 156 | var scrollPositions = Object.create(null); | |
| 157 | var historyIndex = (history.state && typeof history.state.idx === 'number') ? history.state.idx : 0; | |
| 158 | var lastPathname = location.pathname; | |
| 159 | var lastSearch = location.search; | |
| 160 | ||
| 161 | // Wrap pushState/replaceState so any caller (incl. inline scripts) gets a tracked idx | |
| 162 | var origPush = history.pushState.bind(history); | |
| 163 | var origReplace = history.replaceState.bind(history); | |
| 164 | history.pushState = function(state, title, url) { | |
| 165 | state = state || {}; | |
| 166 | if (typeof state.idx !== 'number') { | |
| 167 | historyIndex++; | |
| 168 | state.idx = historyIndex; | |
| 169 | } else { | |
| 170 | historyIndex = state.idx; | |
| 171 | } | |
| 172 | return origPush(state, title, url); | |
| 173 | }; | |
| 174 | history.replaceState = function(state, title, url) { | |
| 175 | state = state || {}; | |
| 176 | if (typeof state.idx !== 'number') state.idx = historyIndex; | |
| 177 | else historyIndex = state.idx; | |
| 178 | return origReplace(state, title, url); | |
| 179 | }; | |
| 180 | if (!history.state || typeof history.state.idx !== 'number') { | |
| 181 | origReplace({ idx: historyIndex }, '', location.href); | |
| 182 | } | |
| 156 | 183 | |
| 157 | function isInternal(url) { | |
| 158 | try { | |
| 159 | var u = new URL(url, location.origin); | |
| 160 | return u.origin === location.origin && !u.pathname.match(/\\.[a-z]+$/i); | |
| 161 | } catch(e) { return false; } | |
| 184 | function getScroller() { | |
| 185 | var el = document.querySelector('.VPContent'); | |
| 186 | if (el) { | |
| 187 | var cs = getComputedStyle(el); | |
| 188 | if (cs.position === 'fixed' && (cs.overflowY === 'auto' || cs.overflowY === 'scroll')) { | |
| 189 | return el; | |
| 190 | } | |
| 191 | } | |
| 192 | return window; | |
| 193 | } | |
| 194 | ||
| 195 | function readScroll() { | |
| 196 | var s = getScroller(); | |
| 197 | return s === window ? window.scrollY : s.scrollTop; | |
| 198 | } | |
| 199 | ||
| 200 | function applyScroll(y) { | |
| 201 | var s = getScroller(); | |
| 202 | if (s === window) window.scrollTo(0, y); | |
| 203 | else s.scrollTop = y; | |
| 204 | } | |
| 205 | ||
| 206 | function saveScroll() { | |
| 207 | scrollPositions[historyIndex] = readScroll(); | |
| 208 | } | |
| 209 | ||
| 210 | function isInternalNavigable(u) { | |
| 211 | return u.origin === location.origin && !u.pathname.match(/\\.[a-z]+$/i); | |
| 162 | 212 | } |
| 163 | 213 | |
| 164 | 214 | function updateActiveLinks() { |
| 165 | 215 | var path = location.pathname; |
| 216 | document.querySelectorAll('a.is-active').forEach(function(a) { | |
| 217 | a.classList.remove('is-active'); | |
| 218 | }); | |
| 166 | 219 | document.querySelectorAll('a[href]').forEach(function(a) { |
| 167 | 220 | var href = a.getAttribute('href'); |
| 168 | if (href === path || (href !== '/' && path.startsWith(href))) { | |
| 169 | a.style.color = 'var(--bp-c-brand-1)'; | |
| 170 | a.style.fontWeight = '500'; | |
| 221 | if (!href || href.startsWith('#')) return; | |
| 222 | var u; | |
| 223 | try { u = new URL(href, location.origin); } catch(_) { return; } | |
| 224 | if (u.origin !== location.origin) return; | |
| 225 | var p = u.pathname; | |
| 226 | // eslint-disable-next-line general/prefer-template -- inner JS string; template literals would clash with the outer TS template | |
| 227 | if (p === path || (p !== '/' && (path === p || path.startsWith(p + '/')))) { | |
| 228 | a.classList.add('is-active'); | |
| 171 | 229 | } |
| 172 | 230 | }); |
| 173 | 231 | } |
| 174 | 232 | |
| 175 | async function navigate(url, push) { | |
| 176 | if (push !== false) history.pushState(null, '', url); | |
| 233 | function executeScripts(root) { | |
| 234 | if (!root) return; | |
| 235 | root.querySelectorAll('script').forEach(function(s) { | |
| 236 | if (s.dataset && s.dataset.bpRouter) return; | |
| 237 | var ns = document.createElement('script'); | |
| 238 | for (var i = 0; i < s.attributes.length; i++) { | |
| 239 | var attr = s.attributes[i]; | |
| 240 | ns.setAttribute(attr.name, attr.value); | |
| 241 | } | |
| 242 | if (!s.src) ns.textContent = s.textContent; | |
| 243 | s.parentNode.replaceChild(ns, s); | |
| 244 | }); | |
| 245 | } | |
| 246 | ||
| 247 | function scrollToHash(hash) { | |
| 248 | if (!hash) return false; | |
| 249 | var id; | |
| 250 | try { id = decodeURIComponent(hash.slice(1)); } catch(_) { id = hash.slice(1); } | |
| 251 | if (!id) return false; | |
| 252 | var el = document.getElementById(id); | |
| 253 | if (!el) { | |
| 254 | try { el = document.querySelector('[name="' + (window.CSS && CSS.escape ? CSS.escape(id) : id) + '"]'); } catch(_) {} | |
| 255 | } | |
| 256 | if (el) { | |
| 257 | el.scrollIntoView({ behavior: 'auto', block: 'start' }); | |
| 258 | return true; | |
| 259 | } | |
| 260 | return false; | |
| 261 | } | |
| 262 | ||
| 263 | function detectLayout(root) { | |
| 264 | if (root.querySelector('.VPHome')) return 'home'; | |
| 265 | if (root.querySelector('.VPContent--doc') || root.querySelector('.VPSidebar')) return 'doc'; | |
| 266 | if (root.querySelector('.VPContent--page') || root.querySelector('.VPPage')) return 'page'; | |
| 267 | return 'page'; | |
| 268 | } | |
| 269 | ||
| 270 | async function navigate(href, opts) { | |
| 271 | opts = opts || {}; | |
| 272 | var target = new URL(href, location.origin); | |
| 273 | var key = target.pathname + target.search; | |
| 274 | ||
| 275 | if (opts.push) { | |
| 276 | saveScroll(); | |
| 277 | history.pushState({}, '', target.pathname + target.search + target.hash); | |
| 278 | } | |
| 177 | 279 | |
| 178 | var html = cache[url]; | |
| 280 | var html = cache[key]; | |
| 179 | 281 | if (!html) { |
| 180 | 282 | try { |
| 181 | var res = await fetch(url); | |
| 283 | var res = await fetch(key, { headers: { 'X-BP-SPA': '1' } }); | |
| 284 | if (!res.ok) { | |
| 285 | // 404 / 5xx — fall through to a real navigation so the browser shows the actual error page | |
| 286 | location.href = target.href; | |
| 287 | return; | |
| 288 | } | |
| 182 | 289 | html = await res.text(); |
| 183 | cache[url] = html; | |
| 184 | } catch(e) { | |
| 185 | location.href = url; | |
| 290 | cache[key] = html; | |
| 291 | } catch(_) { | |
| 292 | location.href = target.href; | |
| 186 | 293 | return; |
| 187 | 294 | } |
| 188 | 295 | } |
| 189 | 296 | |
| 190 | 297 | var doc = parser.parseFromString(html, 'text/html'); |
| 191 | 298 | |
| 192 | // Update title | |
| 193 | 299 | var newTitle = doc.querySelector('title'); |
| 194 | 300 | if (newTitle) document.title = newTitle.textContent; |
| 195 | 301 | |
| 196 | // Update meta description | |
| 197 | var newDesc = doc.querySelector('meta[name="description"]'); | |
| 198 | var curDesc = document.querySelector('meta[name="description"]'); | |
| 199 | if (newDesc && curDesc) curDesc.setAttribute('content', newDesc.getAttribute('content')); | |
| 302 | // Sync description, og:*, twitter:* meta tags | |
| 303 | doc.querySelectorAll('meta[name="description"], meta[property^="og:"], meta[name^="twitter:"]').forEach(function(nm) { | |
| 304 | var sel; | |
| 305 | if (nm.getAttribute('property')) sel = 'meta[property="' + nm.getAttribute('property') + '"]'; | |
| 306 | else sel = 'meta[name="' + nm.getAttribute('name') + '"]'; | |
| 307 | var existing = document.querySelector(sel); | |
| 308 | if (existing) existing.setAttribute('content', nm.getAttribute('content') || ''); | |
| 309 | else document.head.appendChild(nm.cloneNode(true)); | |
| 310 | }); | |
| 311 | var newCanonical = doc.querySelector('link[rel="canonical"]'); | |
| 312 | var curCanonical = document.querySelector('link[rel="canonical"]'); | |
| 313 | if (newCanonical && curCanonical) curCanonical.setAttribute('href', newCanonical.getAttribute('href') || ''); | |
| 314 | else if (newCanonical && !curCanonical) document.head.appendChild(newCanonical.cloneNode(true)); | |
| 200 | 315 | |
| 201 | // Detect layout type: home vs doc vs page | |
| 202 | var curLayout = document.querySelector('.VPHome') ? 'home' : (document.querySelector('.VPContent') ? 'doc' : 'page'); | |
| 203 | var newLayout = doc.querySelector('.VPHome') ? 'home' : (doc.querySelector('.VPContent') ? 'doc' : 'page'); | |
| 316 | var curLayout = detectLayout(document); | |
| 317 | var newLayout = detectLayout(doc); | |
| 204 | 318 | |
| 205 | 319 | if (curLayout !== newLayout) { |
| 206 | // Layout changed — full body swap | |
| 207 | 320 | document.body.innerHTML = doc.body.innerHTML; |
| 208 | // Re-execute scripts so sidebar toggle, theme toggle etc. work | |
| 209 | doc.body.querySelectorAll('script').forEach(function(s) { | |
| 210 | var ns = document.createElement('script'); | |
| 211 | if (s.src) ns.src = s.src; else ns.textContent = s.textContent; | |
| 212 | document.body.appendChild(ns); | |
| 213 | }); | |
| 321 | executeScripts(document.body); | |
| 322 | } else if (curLayout === 'home') { | |
| 323 | var newHome = doc.querySelector('.VPHome'); | |
| 324 | var curHome = document.querySelector('.VPHome'); | |
| 325 | if (newHome && curHome) { | |
| 326 | curHome.innerHTML = newHome.innerHTML; | |
| 327 | executeScripts(curHome); | |
| 328 | } | |
| 329 | } else if (curLayout === 'doc') { | |
| 330 | var newMain = doc.querySelector('.VPDoc'); | |
| 331 | var curMain = document.querySelector('.VPDoc'); | |
| 332 | if (newMain && curMain) { | |
| 333 | curMain.innerHTML = newMain.innerHTML; | |
| 334 | executeScripts(curMain); | |
| 335 | } | |
| 336 | var newSidebar = doc.querySelector('.VPSidebar'); | |
| 337 | var curSidebar = document.querySelector('.VPSidebar'); | |
| 338 | if (newSidebar && curSidebar) { | |
| 339 | curSidebar.innerHTML = newSidebar.innerHTML; | |
| 340 | executeScripts(curSidebar); | |
| 341 | } | |
| 342 | var newAside = doc.querySelector('.VPDocAside'); | |
| 343 | var curAside = document.querySelector('.VPDocAside'); | |
| 344 | if (newAside && curAside) { | |
| 345 | curAside.innerHTML = newAside.innerHTML; | |
| 346 | executeScripts(curAside); | |
| 347 | } | |
| 214 | 348 | } else { |
| 215 | // Same layout — swap content + sidebar + TOC | |
| 216 | if (curLayout === 'home') { | |
| 217 | var newHome = doc.querySelector('.VPHome'); | |
| 218 | var curHome = document.querySelector('.VPHome'); | |
| 219 | if (newHome && curHome) curHome.innerHTML = newHome.innerHTML; | |
| 220 | } else { | |
| 221 | // Doc/page layout: swap content area | |
| 222 | var newMain = doc.querySelector('.VPDoc') || doc.querySelector('main'); | |
| 223 | var curMain = document.querySelector('.VPDoc') || document.querySelector('main'); | |
| 224 | if (newMain && curMain) curMain.innerHTML = newMain.innerHTML; | |
| 225 | ||
| 226 | // Update sidebar | |
| 227 | var newSidebar = doc.querySelector('.VPSidebar'); | |
| 228 | var curSidebar = document.querySelector('.VPSidebar'); | |
| 229 | if (newSidebar && curSidebar) curSidebar.innerHTML = newSidebar.innerHTML; | |
| 230 | ||
| 231 | // Update TOC | |
| 232 | var newToc = doc.querySelector('.VPDocAside'); | |
| 233 | var curToc = document.querySelector('.VPDocAside'); | |
| 234 | if (newToc && curToc) curToc.innerHTML = newToc.innerHTML; | |
| 349 | // page layout | |
| 350 | var newPage = doc.querySelector('.VPPage') || doc.querySelector('.VPContent'); | |
| 351 | var curPage = document.querySelector('.VPPage') || document.querySelector('.VPContent'); | |
| 352 | if (newPage && curPage) { | |
| 353 | curPage.innerHTML = newPage.innerHTML; | |
| 354 | executeScripts(curPage); | |
| 235 | 355 | } |
| 236 | 356 | } |
| 237 | 357 | |
| 238 | // Update <style> tags (CSS may differ between pages) | |
| 239 | 358 | var newStyles = doc.querySelectorAll('style[data-crosswind]'); |
| 240 | var curStyles = document.querySelectorAll('style[data-crosswind]'); | |
| 241 | if (newStyles.length && curStyles.length) { | |
| 242 | curStyles.forEach(function(s) { s.remove(); }); | |
| 359 | if (newStyles.length) { | |
| 360 | document.querySelectorAll('style[data-crosswind]').forEach(function(s) { s.remove(); }); | |
| 243 | 361 | newStyles.forEach(function(s) { document.head.appendChild(s.cloneNode(true)); }); |
| 244 | 362 | } |
| 245 | 363 | |
| 364 | lastPathname = target.pathname; | |
| 365 | lastSearch = target.search; | |
| 246 | 366 | updateActiveLinks(); |
| 247 | window.scrollTo(0, 0); | |
| 367 | ||
| 368 | if (typeof opts.restoreScroll === 'number') { | |
| 369 | applyScroll(opts.restoreScroll); | |
| 370 | } else if (target.hash) { | |
| 371 | if (!scrollToHash(target.hash)) { | |
| 372 | requestAnimationFrame(function(){ scrollToHash(target.hash); }); | |
| 373 | } | |
| 374 | } else { | |
| 375 | applyScroll(0); | |
| 376 | } | |
| 248 | 377 | } |
| 249 | 378 | |
| 250 | // Intercept clicks on internal links | |
| 251 | 379 | document.addEventListener('click', function(e) { |
| 380 | if (e.defaultPrevented) return; | |
| 381 | if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return; | |
| 252 | 382 | var a = e.target.closest('a[href]'); |
| 253 | 383 | if (!a) return; |
| 384 | if (a.target && a.target !== '_self') return; | |
| 385 | if (a.hasAttribute('download')) return; | |
| 386 | var rel = a.getAttribute('rel') || ''; | |
| 387 | if (/\\bexternal\\b/.test(rel)) return; | |
| 254 | 388 | var href = a.getAttribute('href'); |
| 255 | if (!href || href.startsWith('#') || a.target === '_blank') return; | |
| 256 | var url = new URL(href, location.origin); | |
| 257 | if (!isInternal(href)) return; | |
| 389 | if (!href || href.startsWith('#')) return; | |
| 390 | var u; | |
| 391 | try { u = new URL(href, location.origin); } catch(_) { return; } | |
| 392 | if (!isInternalNavigable(u)) return; | |
| 393 | ||
| 258 | 394 | e.preventDefault(); |
| 259 | if (url.pathname === location.pathname) return; | |
| 260 | navigate(url.pathname); | |
| 395 | ||
| 396 | if (u.pathname === location.pathname && u.search === location.search) { | |
| 397 | // Same page; update hash + scroll without re-render | |
| 398 | saveScroll(); | |
| 399 | if (u.hash) { | |
| 400 | history.pushState({}, '', u.pathname + u.search + u.hash); | |
| 401 | scrollToHash(u.hash); | |
| 402 | } else { | |
| 403 | history.pushState({}, '', u.pathname + u.search); | |
| 404 | applyScroll(0); | |
| 405 | } | |
| 406 | lastPathname = u.pathname; | |
| 407 | lastSearch = u.search; | |
| 408 | return; | |
| 409 | } | |
| 410 | navigate(u.href, { push: true }); | |
| 261 | 411 | }); |
| 262 | 412 | |
| 263 | // Handle back/forward | |
| 264 | window.addEventListener('popstate', function() { | |
| 265 | navigate(location.pathname, false); | |
| 413 | window.addEventListener('popstate', function(e) { | |
| 414 | var newIdx = (e.state && typeof e.state.idx === 'number') ? e.state.idx : historyIndex; | |
| 415 | var savedScroll = scrollPositions[newIdx]; | |
| 416 | saveScroll(); // capture scroll for the page being left, under the *current* historyIndex | |
| 417 | historyIndex = newIdx; | |
| 418 | ||
| 419 | if (location.pathname === lastPathname && location.search === lastSearch) { | |
| 420 | // Hash-only or no-op change — skip re-render | |
| 421 | if (typeof savedScroll === 'number') applyScroll(savedScroll); | |
| 422 | else if (location.hash) { if (!scrollToHash(location.hash)) applyScroll(0); } | |
| 423 | else applyScroll(0); | |
| 424 | return; | |
| 425 | } | |
| 426 | navigate(location.href, { push: false, restoreScroll: typeof savedScroll === 'number' ? savedScroll : 0 }); | |
| 266 | 427 | }); |
| 267 | 428 | |
| 268 | // Prefetch on hover | |
| 269 | 429 | document.addEventListener('mouseover', function(e) { |
| 270 | 430 | var a = e.target.closest('a[href]'); |
| 271 | 431 | if (!a) return; |
| 272 | 432 | var href = a.getAttribute('href'); |
| 273 | if (href && isInternal(href) && !cache[href]) { | |
| 274 | fetch(href).then(function(r) { return r.text(); }).then(function(h) { cache[href] = h; }).catch(function(){}); | |
| 275 | } | |
| 433 | if (!href || href.startsWith('#')) return; | |
| 434 | var u; | |
| 435 | try { u = new URL(href, location.origin); } catch(_) { return; } | |
| 436 | if (!isInternalNavigable(u)) return; | |
| 437 | var key = u.pathname + u.search; | |
| 438 | if (cache[key]) return; | |
| 439 | fetch(key).then(function(r){ return r.ok ? r.text() : null; }).then(function(h){ if (h) cache[key] = h; }).catch(function(){}); | |
| 276 | 440 | }); |
| 277 | 441 | |
| 442 | window.addEventListener('beforeunload', saveScroll); | |
| 443 | ||
| 278 | 444 | updateActiveLinks(); |
| 279 | 445 | })(); |
| 280 | 446 | <` + `/script>` |
| @@ -298,25 +464,22 @@ function generateNav(config: BunPressConfig): string { | ||
| 298 | 464 | } |
| 299 | 465 | |
| 300 | 466 | const links = navConfig.map((item) => { |
| 301 | // Handle items with sub-items (dropdown) | |
| 302 | 467 | if (item.items && item.items.length > 0) { |
| 303 | return `<div class="relative group"> | |
| 304 | <button class="flex gap-1 items-center font-medium text-[#213547] text-[14px] hover:text-[#5672cd] transition-colors cursor-pointer"> | |
| 305 | ${item.text} | |
| 306 | <svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| 468 | return `<div class="VPNavBarMenu-group"> | |
| 469 | <button class="VPNavBarMenu-group-button" type="button"> | |
| 470 | <span>${item.text}</span> | |
| 471 | <svg class="chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| 307 | 472 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path> |
| 308 | 473 | </svg> |
| 309 | 474 | </button> |
| 310 | <div class="hidden group-hover:block absolute right-0 top-full mt-2 py-2 min-w-[160px] bg-white border border-[#e2e2e3] rounded-lg shadow-lg"> | |
| 475 | <div class="VPNavBarMenu-group-items"> | |
| 311 | 476 | ${item.items.map(subItem => |
| 312 | `<a href="${fixNavLink(subItem.link)}" class="block px-4 py-2 text-[#213547] text-[13px] hover:text-[#5672cd] hover:bg-[#f6f6f7] transition-colors">${subItem.text}</a>`, | |
| 477 | `<a href="${fixNavLink(subItem.link)}">${subItem.text}</a>`, | |
| 313 | 478 | ).join('')} |
| 314 | 479 | </div> |
| 315 | 480 | </div>` |
| 316 | 481 | } |
| 317 | else { | |
| 318 | return `<a href="${fixNavLink(item.link)}" class="font-medium text-[#213547] text-[14px] hover:text-[#5672cd] transition-colors">${item.text}</a>` | |
| 319 | } | |
| 482 | return `<a class="VPNavBarMenu-link" href="${fixNavLink(item.link)}">${item.text}</a>` | |
| 320 | 483 | }).join('') |
| 321 | 484 | |
| 322 | 485 | return links |
| @@ -777,25 +940,22 @@ async function generateHero(hero: any): Promise<string> { | ||
| 777 | 940 | return '' |
| 778 | 941 | |
| 779 | 942 | const name = hero.name |
| 780 | ? `<p style="font-size: 20px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; background: linear-gradient(135deg, #5672cd 0%, #8b9cf7 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin: 0 0 8px;">${hero.name}</p>` | |
| 943 | ? `<p class="VPHero-name">${hero.name}</p>` | |
| 781 | 944 | : '' |
| 782 | 945 | const text = hero.text |
| 783 | ? `<h1 style="font-size: 40px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.1; color: var(--bp-c-text-1); margin: 0 0 8px;">${hero.text}</h1>` | |
| 946 | ? `<h1 class="VPHero-text">${hero.text}</h1>` | |
| 784 | 947 | : '' |
| 785 | 948 | const tagline = hero.tagline |
| 786 | ? `<p style="font-size: 18px; font-weight: 400; line-height: 1.6; color: var(--bp-c-text-2); margin: 12px 0 0;">${hero.tagline}</p>` | |
| 949 | ? `<p class="VPHero-tagline">${hero.tagline}</p>` | |
| 787 | 950 | : '' |
| 788 | 951 | |
| 789 | 952 | let actions = '' |
| 790 | 953 | if (hero.actions) { |
| 791 | 954 | const actionButtons = hero.actions.map((action: any) => { |
| 792 | const isPrimary = action.theme === 'brand' | |
| 793 | if (isPrimary) { | |
| 794 | return `<a href="${action.link}" style="display: inline-block; padding: 10px 24px; font-size: 14px; font-weight: 600; color: #fff; background: #5672cd; border-radius: 20px; text-decoration: none; transition: background 0.25s; border: 1px solid transparent;" onmouseover="this.style.background='#4558b8'" onmouseout="this.style.background='#5672cd'">${action.text}</a>` | |
| 795 | } | |
| 796 | return `<a href="${action.link}" style="display: inline-block; padding: 10px 24px; font-size: 14px; font-weight: 600; color: var(--bp-c-text-1); background: transparent; border: 1px solid var(--bp-c-divider); border-radius: 20px; text-decoration: none; transition: border-color 0.25s;" onmouseover="this.style.borderColor='var(--bp-c-text-2)'" onmouseout="this.style.borderColor='var(--bp-c-divider)'">${action.text}</a>` | |
| 955 | const cls = action.theme === 'brand' ? 'VPButton VPButton-brand' : 'VPButton VPButton-alt' | |
| 956 | return `<a class="${cls}" href="${action.link}">${action.text}</a>` | |
| 797 | 957 | }).join('\n ') |
| 798 | actions = `<div style="display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px;">${actionButtons}</div>` | |
| 958 | actions = `<div class="VPHero-actions">${actionButtons}</div>` | |
| 799 | 959 | } |
| 800 | 960 | |
| 801 | 961 | const image = hero.image |
| @@ -831,7 +991,7 @@ function getFeatureIcon(icon: string): string { | ||
| 831 | 991 | // If it's an emoji or HTML, pass through |
| 832 | 992 | if (icon.startsWith('<') || /\p{Emoji}/u.test(icon)) return icon |
| 833 | 993 | // Check icon map |
| 834 | return featureIconMap[icon] || `<span style="font-size: 24px; font-weight: 700;">${icon.charAt(0)}</span>` | |
| 994 | return featureIconMap[icon] || `<span class="VPFeature-icon-text">${icon.charAt(0)}</span>` | |
| 835 | 995 | } |
| 836 | 996 | |
| 837 | 997 | async function generateFeatures(features: any[]): Promise<string> { |
| @@ -840,15 +1000,16 @@ async function generateFeatures(features: any[]): Promise<string> { | ||
| 840 | 1000 | |
| 841 | 1001 | const items = features.map(feature => { |
| 842 | 1002 | const icon = feature.icon ? getFeatureIcon(feature.icon) : '' |
| 843 | const iconHtml = icon | |
| 844 | ? `<div style="width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border-radius: 8px; background: rgba(86, 114, 205, 0.1); color: #5672cd; margin-bottom: 12px;">${icon}</div>` | |
| 845 | : '' | |
| 1003 | const iconHtml = icon ? `<div class="VPFeature-icon">${icon}</div>` : '' | |
| 1004 | const link = feature.link | |
| 1005 | const tag = link ? 'a' : 'div' | |
| 1006 | const linkAttr = link ? ` href="${link}"` : '' | |
| 846 | 1007 | return ` |
| 847 | <div style="padding: 24px; background: var(--bp-c-bg-soft, var(--bp-c-bg-alt)); border: 1px solid var(--bp-c-divider); border-radius: 12px; transition: border-color 0.25s, box-shadow 0.25s;" onmouseover="this.style.borderColor='#5672cd';this.style.boxShadow='0 2px 12px rgba(86,114,205,0.08)'" onmouseout="this.style.borderColor='var(--bp-c-divider)';this.style.boxShadow='none'"> | |
| 1008 | <${tag} class="VPFeature"${linkAttr}> | |
| 848 | 1009 | ${iconHtml} |
| 849 | <h3 style="margin: 0 0 8px; font-size: 16px; font-weight: 600; color: var(--bp-c-text-1);">${feature.title || ''}</h3> | |
| 850 | <p style="margin: 0; font-size: 14px; line-height: 1.6; color: var(--bp-c-text-2);">${feature.details || ''}</p> | |
| 851 | </div>` | |
| 1010 | <h3 class="VPFeature-title">${feature.title || ''}</h3> | |
| 1011 | <p class="VPFeature-details">${feature.details || ''}</p> | |
| 1012 | </${tag}>` | |
| 852 | 1013 | }).join('') |
| 853 | 1014 | |
| 854 | 1015 | return await render('features', { |
| @@ -1106,24 +1267,11 @@ function processBadges(content: string): string { | ||
| 1106 | 1267 | const badgeRegex = /<Badge([^/>]+)\/>/gi |
| 1107 | 1268 | |
| 1108 | 1269 | return content.replace(badgeRegex, (_match, attributes) => { |
| 1109 | // Extract type and text attributes | |
| 1110 | 1270 | const typeMatch = attributes.match(/type="(info|tip|warning|danger)"/i) |
| 1111 | 1271 | const textMatch = attributes.match(/text="([^"]+)"/) |
| 1112 | ||
| 1113 | 1272 | const type = typeMatch ? typeMatch[1].toLowerCase() : 'info' |
| 1114 | 1273 | const text = textMatch ? textMatch[1] : '' |
| 1115 | ||
| 1116 | // Badge color schemes matching VitePress | |
| 1117 | const colors: Record<string, { bg: string, text: string, border: string }> = { | |
| 1118 | info: { bg: '#e0f2fe', text: '#0c4a6e', border: '#0ea5e9' }, | |
| 1119 | tip: { bg: '#dcfce7', text: '#14532d', border: '#22c55e' }, | |
| 1120 | warning: { bg: '#fef3c7', text: '#78350f', border: '#f59e0b' }, | |
| 1121 | danger: { bg: '#fee2e2', text: '#7f1d1d', border: '#ef4444' }, | |
| 1122 | } | |
| 1123 | ||
| 1124 | const color = colors[type] || colors.info | |
| 1125 | ||
| 1126 | return `<span class="badge badge-${type}" style="display: inline-block; padding: 2px 8px; font-size: 0.85em; font-weight: 600; border-radius: 4px; background: ${color.bg}; color: ${color.text}; border: 1px solid ${color.border}; margin: 0 4px; vertical-align: middle;">${text}</span>` | |
| 1274 | return `<span class="bp-badge bp-badge-${type}">${text}</span>` | |
| 1127 | 1275 | }) |
| 1128 | 1276 | } |
| 1129 | 1277 | |
| @@ -1161,7 +1309,7 @@ function addExternalLinkIcons(html: string): string { | ||
| 1161 | 1309 | if (match.includes('external-link-icon')) |
| 1162 | 1310 | return match |
| 1163 | 1311 | |
| 1164 | const externalIcon = '<svg class="external-link-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: inline-block; margin-left: 4px; vertical-align: middle;"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>' | |
| 1312 | const externalIcon = '<svg class="external-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>' | |
| 1165 | 1313 | return match.replace('</a>', `${externalIcon}</a>`) |
| 1166 | 1314 | }) |
| 1167 | 1315 | } |
| @@ -1,20 +1,7 @@ | ||
| 1 | <div class="VPHomeFeatures" style="padding: 64px 24px; border-top: 1px solid var(--bp-c-divider);"> | |
| 2 | <div style="max-width: 1152px; margin: 0 auto;"> | |
| 3 | <div class="VPFeatures" style="display: grid; grid-template-columns: repeat(1, 1fr); gap: 24px;"> | |
| 1 | <div class="VPHomeFeatures"> | |
| 2 | <div class="VPHomeFeatures-inner"> | |
| 3 | <div class="VPFeatures"> | |
| 4 | 4 | {!! items !!} |
| 5 | 5 | </div> |
| 6 | 6 | </div> |
| 7 | 7 | </div> |
| 8 | ||
| 9 | <style> | |
| 10 | @media (min-width: 640px) { | |
| 11 | .VPFeatures { | |
| 12 | grid-template-columns: repeat(2, 1fr) !important; | |
| 13 | } | |
| 14 | } | |
| 15 | @media (min-width: 960px) { | |
| 16 | .VPFeatures { | |
| 17 | grid-template-columns: repeat(3, 1fr) !important; | |
| 18 | } | |
| 19 | } | |
| 20 | </style> | |
| @@ -14,25 +14,29 @@ | ||
| 14 | 14 | <body> |
| 15 | 15 | <div class="Layout"> |
| 16 | 16 | <!-- VPNav - Header --> |
| 17 | <header class="VPNav" style="position: fixed; top: 0; left: 0; right: 0; height: var(--bp-nav-height, 64px); background-color: var(--bp-nav-bg-color, var(--bp-c-bg)); border-bottom: 1px solid var(--bp-c-divider); z-index: var(--bp-z-index-nav, 30);"> | |
| 18 | <div style="max-width: var(--bp-layout-max-width, 1440px); margin: 0 auto; width: 100%; height: 100%; display: flex; align-items: center; justify-content: space-between; padding: 0 24px;"> | |
| 19 | <!-- Left side: Logo/Title + Search --> | |
| 20 | <div style="display: flex; align-items: center; gap: 24px; flex: 1; min-width: 0;"> | |
| 21 | <a href="/" class="VPNavBarTitle" style="font-size: 16px; font-weight: 600; color: var(--bp-c-text-1); white-space: nowrap; text-decoration: none;">{{ title }}</a> | |
| 22 | <div class="VPNavBarSearch" style="position: relative; display: flex; align-items: center; max-width: 260px; width: 100%;"> | |
| 23 | <svg style="position: absolute; left: 12px; width: 16px; height: 16px; color: var(--bp-c-text-3);" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| 17 | <header class="VPNav"> | |
| 18 | <div class="VPNavBar"> | |
| 19 | <div class="VPNavBarStart"> | |
| 20 | <button class="VPNavBarHamburger" type="button" aria-label="Toggle navigation" onclick="toggleSidebar()"> | |
| 21 | <svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| 22 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path> | |
| 23 | </svg> | |
| 24 | </button> | |
| 25 | <a href="/" class="VPNavBarTitle">{{ title }}</a> | |
| 26 | <div class="VPNavBarSearch"> | |
| 27 | <svg class="VPNavBarSearch-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| 24 | 28 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> |
| 25 | 29 | </svg> |
| 26 | <input type="text" placeholder="Search" style="width: 100%; height: 32px; padding-left: 36px; padding-right: 48px; font-size: 13px; background-color: var(--bp-c-bg-alt); border: 1px solid var(--bp-c-divider); border-radius: 4px; color: var(--bp-c-text-1); outline: none; transition: border-color 0.25s, background-color 0.25s;" /> | |
| 27 | <kbd style="position: absolute; right: 8px; padding: 2px 6px; font-size: 11px; font-weight: 600; color: var(--bp-c-text-3); background-color: var(--bp-c-bg); border: 1px solid var(--bp-c-divider); border-radius: 4px;">⌘ K</kbd> | |
| 30 | <input type="text" class="VPNavBarSearch-input" placeholder="Search" /> | |
| 31 | <kbd class="VPNavBarSearch-kbd">⌘ K</kbd> | |
| 28 | 32 | </div> |
| 29 | 33 | </div> |
| 30 | 34 | |
| 31 | <!-- Right side: Navigation + Icons --> | |
| 32 | <div style="display: flex; align-items: center; gap: 24px;"> | |
| 33 | {!! nav !!} | |
| 34 | <div class="VPSocialLinks" style="display: flex; align-items: center; gap: 12px; padding-left: 24px; border-left: 1px solid var(--bp-c-divider);"> | |
| 35 | <!-- Theme Toggle --> | |
| 35 | <div class="VPNavBarEnd"> | |
| 36 | <div class="VPNavBarMenu"> | |
| 37 | {!! nav !!} | |
| 38 | </div> | |
| 39 | <div class="VPSocialLinks"> | |
| 36 | 40 | <button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle dark mode" title="Toggle dark mode"> |
| 37 | 41 | <svg class="sun-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| 38 | 42 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path> |
| @@ -41,8 +45,8 @@ | ||
| 41 | 45 | <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path> |
| 42 | 46 | </svg> |
| 43 | 47 | </button> |
| 44 | <a href="https://github.com/stacksjs/bunpress" target="_blank" style="color: var(--bp-c-text-2); transition: color 0.25s;" onmouseover="this.style.color='var(--bp-c-text-1)'" onmouseout="this.style.color='var(--bp-c-text-2)'"> | |
| 45 | <svg style="width: 20px; height: 20px;" fill="currentColor" viewBox="0 0 24 24"> | |
| 48 | <a href="https://github.com/stacksjs/bunpress" target="_blank" rel="noopener external" aria-label="GitHub"> | |
| 49 | <svg fill="currentColor" viewBox="0 0 24 24"> | |
| 46 | 50 | <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/> |
| 47 | 51 | </svg> |
| 48 | 52 | </a> |
| @@ -55,9 +59,9 @@ | ||
| 55 | 59 | {!! sidebar !!} |
| 56 | 60 | |
| 57 | 61 | <!-- VPContent - Main content area --> |
| 58 | <main class="VPContent" style="position: fixed; top: var(--bp-nav-height, 64px); bottom: 0; overflow-y: auto; left: max(var(--bp-sidebar-width, 272px), calc((100vw - var(--bp-layout-max-width, 1440px)) / 2 + var(--bp-sidebar-width, 272px))); right: max(0px, calc((100vw - var(--bp-layout-max-width, 1440px)) / 2));"> | |
| 59 | <div class="VPDoc" style="padding: 32px 24px; padding-right: 32px;"> | |
| 60 | <div class="VPDocContent" style="max-width: 768px; margin: 0;"> | |
| 62 | <main class="VPContent VPContent--doc"> | |
| 63 | <div class="VPDoc"> | |
| 64 | <div class="VPDocContent"> | |
| 61 | 65 | <article class="bp-doc"> |
| 62 | 66 | {!! content !!} |
| 63 | 67 | </article> |
| @@ -14,20 +14,22 @@ | ||
| 14 | 14 | <body> |
| 15 | 15 | <div class="Layout"> |
| 16 | 16 | <!-- VPNav - Header --> |
| 17 | <header class="VPNav" style="position: fixed; top: 0; left: 0; right: 0; height: var(--bp-nav-height, 64px); background-color: var(--bp-nav-bg-color, var(--bp-c-bg)); border-bottom: 1px solid var(--bp-c-divider); z-index: var(--bp-z-index-nav, 30);"> | |
| 18 | <div style="max-width: var(--bp-layout-max-width, 1440px); margin: 0 auto; width: 100%; height: 100%; display: flex; align-items: center; justify-content: space-between; padding: 0 24px;"> | |
| 19 | <div style="display: flex; align-items: center; gap: 24px; flex: 1; min-width: 0;"> | |
| 20 | <a href="/" class="VPNavBarTitle" style="font-size: 16px; font-weight: 600; color: var(--bp-c-text-1); white-space: nowrap; text-decoration: none;">{{ title }}</a> | |
| 17 | <header class="VPNav"> | |
| 18 | <div class="VPNavBar"> | |
| 19 | <div class="VPNavBarStart"> | |
| 20 | <a href="/" class="VPNavBarTitle">{{ title }}</a> | |
| 21 | 21 | </div> |
| 22 | <div style="display: flex; align-items: center; gap: 24px;"> | |
| 23 | {!! nav !!} | |
| 22 | <div class="VPNavBarEnd"> | |
| 23 | <div class="VPNavBarMenu"> | |
| 24 | {!! nav !!} | |
| 25 | </div> | |
| 24 | 26 | </div> |
| 25 | 27 | </div> |
| 26 | 28 | </header> |
| 27 | 29 | |
| 28 | 30 | <!-- VPContent - Full-width content without sidebar --> |
| 29 | <main class="VPContent" style="padding-top: var(--bp-nav-height, 64px);"> | |
| 30 | <div class="VPPage" style="padding: 48px 24px; max-width: 768px; margin: 0 auto;"> | |
| 31 | <main class="VPContent VPContent--page"> | |
| 32 | <div class="VPPage"> | |
| 31 | 33 | <article class="bp-doc"> |
| 32 | 34 | {!! content !!} |
| 33 | 35 | </article> |
| @@ -234,6 +234,11 @@ function initPageTOC() { | ||
| 234 | 234 | }); |
| 235 | 235 | |
| 236 | 236 | // Listen to scroll events on the actual scrolling container |
| 237 | // Remove any prior listener first so SPA navigation doesn't stack duplicates | |
| 238 | if (scrollContainer.__bpTocScroll) { | |
| 239 | scrollContainer.removeEventListener('scroll', scrollContainer.__bpTocScroll); | |
| 240 | } | |
| 241 | scrollContainer.__bpTocScroll = onScroll; | |
| 237 | 242 | scrollContainer.addEventListener('scroll', onScroll); |
| 238 | 243 | |
| 239 | 244 | // Handle URL hash on page load |
| @@ -11,6 +11,8 @@ | ||
| 11 | 11 | * - Dark backgrounds: #0d0e11, #14151a, #282a36 |
| 12 | 12 | */ |
| 13 | 13 | |
| 14 | import { layoutCSS } from '../vitepress' | |
| 15 | ||
| 14 | 16 | // CSS Variables - Exact values from bun.sh |
| 15 | 17 | const varsCSS = `/** |
| 16 | 18 | * Bun Theme for BunPress |
| @@ -2036,6 +2038,7 @@ export function getBunThemeCSS(): string { | ||
| 2036 | 2038 | /* Colors extracted from bun.sh */ |
| 2037 | 2039 | ${varsCSS} |
| 2038 | 2040 | ${baseCSS} |
| 2041 | ${layoutCSS} | |
| 2039 | 2042 | ${customBlockCSS} |
| 2040 | 2043 | ${codeGroupCSS} |
| 2041 | 2044 | ${bunExtrasCSS} |
packages/bunpress/src/themes/vitepress/index.tsmodified+640-0
| @@ -1901,6 +1901,645 @@ const codeGroupCSS = `/** | ||
| 1901 | 1901 | border: none; |
| 1902 | 1902 | }` |
| 1903 | 1903 | |
| 1904 | // Layout / chrome styles — classes referenced by layout-doc.stx, layout-page.stx, layout-home.stx, | |
| 1905 | // and the nav/sidebar generators in serve.ts. Keeps templates declarative and dark-mode aware. | |
| 1906 | export const layoutCSS = `/** | |
| 1907 | * VitePress Theme for BunPress - Layout & Chrome | |
| 1908 | * -------------------------------------------------------------------------- */ | |
| 1909 | ||
| 1910 | .Layout { | |
| 1911 | min-height: 100vh; | |
| 1912 | } | |
| 1913 | ||
| 1914 | /* VPNav — top bar */ | |
| 1915 | .VPNav { | |
| 1916 | position: fixed; | |
| 1917 | top: 0; | |
| 1918 | left: 0; | |
| 1919 | right: 0; | |
| 1920 | height: var(--bp-nav-height, 64px); | |
| 1921 | background-color: var(--bp-nav-bg-color, var(--bp-c-bg)); | |
| 1922 | border-bottom: 1px solid var(--bp-c-divider); | |
| 1923 | z-index: var(--bp-z-index-nav, 30); | |
| 1924 | backdrop-filter: saturate(50%) blur(8px); | |
| 1925 | } | |
| 1926 | ||
| 1927 | .VPNavBar { | |
| 1928 | max-width: var(--bp-layout-max-width, 1440px); | |
| 1929 | margin: 0 auto; | |
| 1930 | width: 100%; | |
| 1931 | height: 100%; | |
| 1932 | display: flex; | |
| 1933 | align-items: center; | |
| 1934 | justify-content: space-between; | |
| 1935 | padding: 0 24px; | |
| 1936 | gap: 24px; | |
| 1937 | } | |
| 1938 | ||
| 1939 | .VPNavBarStart { | |
| 1940 | display: flex; | |
| 1941 | align-items: center; | |
| 1942 | gap: 24px; | |
| 1943 | flex: 1; | |
| 1944 | min-width: 0; | |
| 1945 | } | |
| 1946 | ||
| 1947 | .VPNavBarEnd { | |
| 1948 | display: flex; | |
| 1949 | align-items: center; | |
| 1950 | gap: 24px; | |
| 1951 | flex-shrink: 0; | |
| 1952 | } | |
| 1953 | ||
| 1954 | .VPNavBarTitle { | |
| 1955 | font-size: 16px; | |
| 1956 | font-weight: 600; | |
| 1957 | color: var(--bp-c-text-1); | |
| 1958 | white-space: nowrap; | |
| 1959 | text-decoration: none; | |
| 1960 | transition: color 0.25s; | |
| 1961 | } | |
| 1962 | ||
| 1963 | .VPNavBarTitle:hover { | |
| 1964 | color: var(--bp-c-brand-1); | |
| 1965 | } | |
| 1966 | ||
| 1967 | .VPNavBarMenu { | |
| 1968 | display: flex; | |
| 1969 | align-items: center; | |
| 1970 | gap: 24px; | |
| 1971 | } | |
| 1972 | ||
| 1973 | .VPNavBarMenu a, | |
| 1974 | .VPNavBarMenu .VPNavBarMenu-link { | |
| 1975 | font-size: 14px; | |
| 1976 | font-weight: 500; | |
| 1977 | color: var(--bp-c-text-1); | |
| 1978 | text-decoration: none; | |
| 1979 | transition: color 0.25s; | |
| 1980 | cursor: pointer; | |
| 1981 | } | |
| 1982 | ||
| 1983 | .VPNavBarMenu a:hover, | |
| 1984 | .VPNavBarMenu .VPNavBarMenu-link:hover, | |
| 1985 | .VPNavBarMenu a.is-active { | |
| 1986 | color: var(--bp-c-brand-1); | |
| 1987 | } | |
| 1988 | ||
| 1989 | .VPNavBarMenu-group { | |
| 1990 | position: relative; | |
| 1991 | } | |
| 1992 | ||
| 1993 | .VPNavBarMenu-group-button { | |
| 1994 | display: inline-flex; | |
| 1995 | align-items: center; | |
| 1996 | gap: 4px; | |
| 1997 | background: transparent; | |
| 1998 | border: none; | |
| 1999 | padding: 0; | |
| 2000 | font: inherit; | |
| 2001 | font-size: 14px; | |
| 2002 | font-weight: 500; | |
| 2003 | color: var(--bp-c-text-1); | |
| 2004 | cursor: pointer; | |
| 2005 | transition: color 0.25s; | |
| 2006 | } | |
| 2007 | ||
| 2008 | .VPNavBarMenu-group-button:hover { | |
| 2009 | color: var(--bp-c-brand-1); | |
| 2010 | } | |
| 2011 | ||
| 2012 | .VPNavBarMenu-group-button .chevron { | |
| 2013 | width: 12px; | |
| 2014 | height: 12px; | |
| 2015 | flex-shrink: 0; | |
| 2016 | transition: transform 0.25s; | |
| 2017 | } | |
| 2018 | ||
| 2019 | .VPNavBarMenu-group:hover > .VPNavBarMenu-group-button .chevron { | |
| 2020 | transform: rotate(180deg); | |
| 2021 | } | |
| 2022 | ||
| 2023 | .VPNavBarMenu-group-items { | |
| 2024 | display: none; | |
| 2025 | position: absolute; | |
| 2026 | right: 0; | |
| 2027 | top: 100%; | |
| 2028 | margin-top: 8px; | |
| 2029 | padding: 8px 0; | |
| 2030 | min-width: 192px; | |
| 2031 | background: var(--bp-c-bg); | |
| 2032 | border: 1px solid var(--bp-c-divider); | |
| 2033 | border-radius: 8px; | |
| 2034 | box-shadow: var(--bp-shadow-3, 0 12px 32px rgba(0, 0, 0, 0.1)); | |
| 2035 | z-index: 10; | |
| 2036 | } | |
| 2037 | ||
| 2038 | .VPNavBarMenu-group:hover > .VPNavBarMenu-group-items { | |
| 2039 | display: block; | |
| 2040 | } | |
| 2041 | ||
| 2042 | .VPNavBarMenu-group-items a { | |
| 2043 | display: block; | |
| 2044 | padding: 6px 16px; | |
| 2045 | font-size: 13px; | |
| 2046 | color: var(--bp-c-text-1); | |
| 2047 | text-decoration: none; | |
| 2048 | transition: color 0.25s, background-color 0.25s; | |
| 2049 | } | |
| 2050 | ||
| 2051 | .VPNavBarMenu-group-items a:hover { | |
| 2052 | color: var(--bp-c-brand-1); | |
| 2053 | background-color: var(--bp-c-bg-soft); | |
| 2054 | } | |
| 2055 | ||
| 2056 | /* Search */ | |
| 2057 | .VPNavBarSearch { | |
| 2058 | position: relative; | |
| 2059 | display: flex; | |
| 2060 | align-items: center; | |
| 2061 | max-width: 260px; | |
| 2062 | width: 100%; | |
| 2063 | } | |
| 2064 | ||
| 2065 | .VPNavBarSearch-icon { | |
| 2066 | position: absolute; | |
| 2067 | left: 12px; | |
| 2068 | width: 16px; | |
| 2069 | height: 16px; | |
| 2070 | color: var(--bp-c-text-3); | |
| 2071 | pointer-events: none; | |
| 2072 | } | |
| 2073 | ||
| 2074 | .VPNavBarSearch-input { | |
| 2075 | width: 100%; | |
| 2076 | height: 32px; | |
| 2077 | padding: 0 48px 0 36px; | |
| 2078 | font: inherit; | |
| 2079 | font-size: 13px; | |
| 2080 | background-color: var(--bp-c-bg-alt); | |
| 2081 | border: 1px solid var(--bp-c-divider); | |
| 2082 | border-radius: 4px; | |
| 2083 | color: var(--bp-c-text-1); | |
| 2084 | outline: none; | |
| 2085 | transition: border-color 0.25s, background-color 0.25s; | |
| 2086 | } | |
| 2087 | ||
| 2088 | .VPNavBarSearch-input::placeholder { | |
| 2089 | color: var(--bp-c-text-3); | |
| 2090 | } | |
| 2091 | ||
| 2092 | .VPNavBarSearch-input:hover, | |
| 2093 | .VPNavBarSearch-input:focus { | |
| 2094 | border-color: var(--bp-c-brand-1); | |
| 2095 | background-color: var(--bp-c-bg); | |
| 2096 | } | |
| 2097 | ||
| 2098 | .VPNavBarSearch-kbd { | |
| 2099 | position: absolute; | |
| 2100 | right: 8px; | |
| 2101 | padding: 2px 6px; | |
| 2102 | font-size: 11px; | |
| 2103 | font-weight: 600; | |
| 2104 | font-family: inherit; | |
| 2105 | color: var(--bp-c-text-3); | |
| 2106 | background-color: var(--bp-c-bg); | |
| 2107 | border: 1px solid var(--bp-c-divider); | |
| 2108 | border-radius: 4px; | |
| 2109 | pointer-events: none; | |
| 2110 | } | |
| 2111 | ||
| 2112 | /* Social/icon links cluster */ | |
| 2113 | .VPSocialLinks { | |
| 2114 | display: flex; | |
| 2115 | align-items: center; | |
| 2116 | gap: 12px; | |
| 2117 | padding-left: 24px; | |
| 2118 | border-left: 1px solid var(--bp-c-divider); | |
| 2119 | } | |
| 2120 | ||
| 2121 | .VPSocialLinks > a { | |
| 2122 | display: inline-flex; | |
| 2123 | align-items: center; | |
| 2124 | justify-content: center; | |
| 2125 | width: 32px; | |
| 2126 | height: 32px; | |
| 2127 | color: var(--bp-c-text-2); | |
| 2128 | border-radius: 4px; | |
| 2129 | transition: color 0.25s, background-color 0.25s; | |
| 2130 | } | |
| 2131 | ||
| 2132 | .VPSocialLinks > a:hover { | |
| 2133 | color: var(--bp-c-text-1); | |
| 2134 | background-color: var(--bp-c-bg-soft); | |
| 2135 | } | |
| 2136 | ||
| 2137 | .VPSocialLinks > a svg { | |
| 2138 | width: 20px; | |
| 2139 | height: 20px; | |
| 2140 | } | |
| 2141 | ||
| 2142 | /* Content area */ | |
| 2143 | .VPContent { | |
| 2144 | flex: 1; | |
| 2145 | } | |
| 2146 | ||
| 2147 | .VPContent--doc { | |
| 2148 | position: fixed; | |
| 2149 | top: var(--bp-nav-height, 64px); | |
| 2150 | bottom: 0; | |
| 2151 | overflow-y: auto; | |
| 2152 | left: max(var(--bp-sidebar-width, 272px), calc((100vw - var(--bp-layout-max-width, 1440px)) / 2 + var(--bp-sidebar-width, 272px))); | |
| 2153 | right: max(0px, calc((100vw - var(--bp-layout-max-width, 1440px)) / 2)); | |
| 2154 | } | |
| 2155 | ||
| 2156 | .VPContent--page { | |
| 2157 | padding-top: var(--bp-nav-height, 64px); | |
| 2158 | } | |
| 2159 | ||
| 2160 | .VPDoc { | |
| 2161 | padding: 32px 24px; | |
| 2162 | padding-right: 32px; | |
| 2163 | } | |
| 2164 | ||
| 2165 | @media (min-width: 1280px) { | |
| 2166 | .VPDoc { | |
| 2167 | padding: 48px 32px 128px; | |
| 2168 | } | |
| 2169 | } | |
| 2170 | ||
| 2171 | .VPDocContent { | |
| 2172 | max-width: 768px; | |
| 2173 | margin: 0; | |
| 2174 | } | |
| 2175 | ||
| 2176 | .VPPage { | |
| 2177 | padding: 48px 24px; | |
| 2178 | max-width: 768px; | |
| 2179 | margin: 0 auto; | |
| 2180 | } | |
| 2181 | ||
| 2182 | @media (min-width: 1280px) { | |
| 2183 | .VPPage { | |
| 2184 | padding: 64px 32px; | |
| 2185 | } | |
| 2186 | } | |
| 2187 | ||
| 2188 | /* Mobile sidebar drawer behaviour */ | |
| 2189 | @media (max-width: 959px) { | |
| 2190 | .VPNavBarSearch { | |
| 2191 | display: none; | |
| 2192 | } | |
| 2193 | .VPSocialLinks { | |
| 2194 | padding-left: 12px; | |
| 2195 | gap: 8px; | |
| 2196 | } | |
| 2197 | .VPNavBarMenu { | |
| 2198 | display: none; | |
| 2199 | } | |
| 2200 | } | |
| 2201 | ||
| 2202 | /** | |
| 2203 | * Hero (home layout) — used by hero.stx + serve.ts generateHero | |
| 2204 | * -------------------------------------------------------------------------- */ | |
| 2205 | ||
| 2206 | .VPHero-name { | |
| 2207 | font-size: 20px; | |
| 2208 | font-weight: 700; | |
| 2209 | letter-spacing: -0.02em; | |
| 2210 | line-height: 1.2; | |
| 2211 | margin: 0 0 8px; | |
| 2212 | background: linear-gradient(135deg, var(--bp-c-brand-1, #5672cd) 0%, var(--bp-c-brand-2, #8b9cf7) 100%); | |
| 2213 | -webkit-background-clip: text; | |
| 2214 | -webkit-text-fill-color: transparent; | |
| 2215 | background-clip: text; | |
| 2216 | } | |
| 2217 | ||
| 2218 | .VPHero-text { | |
| 2219 | font-size: 40px; | |
| 2220 | font-weight: 800; | |
| 2221 | letter-spacing: -0.02em; | |
| 2222 | line-height: 1.1; | |
| 2223 | color: var(--bp-c-text-1); | |
| 2224 | margin: 0 0 8px; | |
| 2225 | } | |
| 2226 | ||
| 2227 | .VPHero-tagline { | |
| 2228 | font-size: 18px; | |
| 2229 | font-weight: 400; | |
| 2230 | line-height: 1.6; | |
| 2231 | color: var(--bp-c-text-2); | |
| 2232 | margin: 12px 0 0; | |
| 2233 | } | |
| 2234 | ||
| 2235 | @media (min-width: 960px) { | |
| 2236 | .VPHero-name { | |
| 2237 | font-size: 24px; | |
| 2238 | } | |
| 2239 | .VPHero-text { | |
| 2240 | font-size: 56px; | |
| 2241 | } | |
| 2242 | .VPHero-tagline { | |
| 2243 | font-size: 20px; | |
| 2244 | } | |
| 2245 | } | |
| 2246 | ||
| 2247 | .VPHero-actions { | |
| 2248 | display: flex; | |
| 2249 | flex-wrap: wrap; | |
| 2250 | gap: 12px; | |
| 2251 | margin-top: 28px; | |
| 2252 | } | |
| 2253 | ||
| 2254 | .VPButton { | |
| 2255 | display: inline-flex; | |
| 2256 | align-items: center; | |
| 2257 | justify-content: center; | |
| 2258 | padding: 0 24px; | |
| 2259 | height: 40px; | |
| 2260 | font-size: 14px; | |
| 2261 | font-weight: 600; | |
| 2262 | border-radius: 20px; | |
| 2263 | text-decoration: none; | |
| 2264 | transition: background-color 0.25s, color 0.25s, border-color 0.25s; | |
| 2265 | border: 1px solid transparent; | |
| 2266 | white-space: nowrap; | |
| 2267 | cursor: pointer; | |
| 2268 | } | |
| 2269 | ||
| 2270 | .VPButton-brand { | |
| 2271 | color: var(--bp-button-brand-text, #fff); | |
| 2272 | background-color: var(--bp-c-brand-1); | |
| 2273 | border-color: var(--bp-c-brand-1); | |
| 2274 | } | |
| 2275 | ||
| 2276 | .VPButton-brand:hover { | |
| 2277 | background-color: var(--bp-c-brand-2); | |
| 2278 | border-color: var(--bp-c-brand-2); | |
| 2279 | } | |
| 2280 | ||
| 2281 | .VPButton-alt { | |
| 2282 | color: var(--bp-c-text-1); | |
| 2283 | background-color: transparent; | |
| 2284 | border-color: var(--bp-c-divider); | |
| 2285 | } | |
| 2286 | ||
| 2287 | .VPButton-alt:hover { | |
| 2288 | border-color: var(--bp-c-text-2); | |
| 2289 | color: var(--bp-c-brand-1); | |
| 2290 | } | |
| 2291 | ||
| 2292 | /** | |
| 2293 | * Features grid — used by features.stx + serve.ts generateFeatures | |
| 2294 | * -------------------------------------------------------------------------- */ | |
| 2295 | ||
| 2296 | .VPHomeFeatures { | |
| 2297 | padding: 48px 24px; | |
| 2298 | border-top: 1px solid var(--bp-c-divider); | |
| 2299 | } | |
| 2300 | ||
| 2301 | .VPHomeFeatures-inner { | |
| 2302 | max-width: 1152px; | |
| 2303 | margin: 0 auto; | |
| 2304 | } | |
| 2305 | ||
| 2306 | .VPFeatures { | |
| 2307 | display: grid; | |
| 2308 | grid-template-columns: repeat(1, 1fr); | |
| 2309 | gap: 16px; | |
| 2310 | } | |
| 2311 | ||
| 2312 | @media (min-width: 640px) { | |
| 2313 | .VPFeatures { | |
| 2314 | grid-template-columns: repeat(2, 1fr); | |
| 2315 | gap: 24px; | |
| 2316 | } | |
| 2317 | } | |
| 2318 | ||
| 2319 | @media (min-width: 960px) { | |
| 2320 | .VPHomeFeatures { | |
| 2321 | padding: 64px 32px; | |
| 2322 | } | |
| 2323 | .VPFeatures { | |
| 2324 | grid-template-columns: repeat(3, 1fr); | |
| 2325 | } | |
| 2326 | } | |
| 2327 | ||
| 2328 | .VPFeature { | |
| 2329 | display: block; | |
| 2330 | padding: 24px; | |
| 2331 | background-color: var(--bp-c-bg-soft, var(--bp-c-bg-alt)); | |
| 2332 | border: 1px solid var(--bp-c-divider); | |
| 2333 | border-radius: 12px; | |
| 2334 | transition: border-color 0.25s, box-shadow 0.25s; | |
| 2335 | text-decoration: none; | |
| 2336 | color: inherit; | |
| 2337 | } | |
| 2338 | ||
| 2339 | .VPFeature:hover { | |
| 2340 | border-color: var(--bp-c-brand-1); | |
| 2341 | box-shadow: 0 2px 12px rgba(86, 114, 205, 0.08); | |
| 2342 | } | |
| 2343 | ||
| 2344 | .VPFeature-icon { | |
| 2345 | width: 40px; | |
| 2346 | height: 40px; | |
| 2347 | display: flex; | |
| 2348 | align-items: center; | |
| 2349 | justify-content: center; | |
| 2350 | border-radius: 8px; | |
| 2351 | background: var(--bp-c-brand-soft, rgba(86, 114, 205, 0.1)); | |
| 2352 | color: var(--bp-c-brand-1); | |
| 2353 | margin-bottom: 12px; | |
| 2354 | } | |
| 2355 | ||
| 2356 | .VPFeature-icon svg { | |
| 2357 | width: 24px; | |
| 2358 | height: 24px; | |
| 2359 | } | |
| 2360 | ||
| 2361 | .VPFeature-icon-text { | |
| 2362 | font-size: 24px; | |
| 2363 | font-weight: 700; | |
| 2364 | } | |
| 2365 | ||
| 2366 | .VPFeature-title { | |
| 2367 | margin: 0 0 8px; | |
| 2368 | font-size: 16px; | |
| 2369 | font-weight: 600; | |
| 2370 | line-height: 1.4; | |
| 2371 | color: var(--bp-c-text-1); | |
| 2372 | } | |
| 2373 | ||
| 2374 | .VPFeature-details { | |
| 2375 | margin: 0; | |
| 2376 | font-size: 14px; | |
| 2377 | line-height: 1.6; | |
| 2378 | color: var(--bp-c-text-2); | |
| 2379 | } | |
| 2380 | ||
| 2381 | /** | |
| 2382 | * Inline content — badges, external link icons | |
| 2383 | * -------------------------------------------------------------------------- */ | |
| 2384 | ||
| 2385 | .bp-badge { | |
| 2386 | display: inline-block; | |
| 2387 | padding: 2px 8px; | |
| 2388 | font-size: 0.85em; | |
| 2389 | font-weight: 600; | |
| 2390 | border-radius: 4px; | |
| 2391 | margin: 0 4px; | |
| 2392 | vertical-align: middle; | |
| 2393 | border: 1px solid transparent; | |
| 2394 | line-height: 1.5; | |
| 2395 | } | |
| 2396 | ||
| 2397 | .bp-badge-tip { | |
| 2398 | background: var(--bp-c-tip-soft, rgba(16, 185, 129, 0.14)); | |
| 2399 | color: var(--bp-c-tip-1, var(--bp-c-green-1)); | |
| 2400 | border-color: var(--bp-c-tip-2, var(--bp-c-green-2)); | |
| 2401 | } | |
| 2402 | ||
| 2403 | .bp-badge-info { | |
| 2404 | background: var(--bp-c-default-soft, var(--bp-c-gray-soft)); | |
| 2405 | color: var(--bp-c-text-1); | |
| 2406 | border-color: var(--bp-c-divider); | |
| 2407 | } | |
| 2408 | ||
| 2409 | .bp-badge-warning { | |
| 2410 | background: var(--bp-c-warning-soft, rgba(234, 179, 8, 0.14)); | |
| 2411 | color: var(--bp-c-warning-1, var(--bp-c-yellow-1)); | |
| 2412 | border-color: var(--bp-c-warning-2, var(--bp-c-yellow-2)); | |
| 2413 | } | |
| 2414 | ||
| 2415 | .bp-badge-danger { | |
| 2416 | background: var(--bp-c-danger-soft, rgba(244, 63, 94, 0.14)); | |
| 2417 | color: var(--bp-c-danger-1, var(--bp-c-red-1)); | |
| 2418 | border-color: var(--bp-c-danger-2, var(--bp-c-red-2)); | |
| 2419 | } | |
| 2420 | ||
| 2421 | .external-link-icon { | |
| 2422 | display: inline-block; | |
| 2423 | margin-left: 4px; | |
| 2424 | vertical-align: middle; | |
| 2425 | width: 12px; | |
| 2426 | height: 12px; | |
| 2427 | } | |
| 2428 | ||
| 2429 | /** | |
| 2430 | * Polish: prose elements | |
| 2431 | * -------------------------------------------------------------------------- */ | |
| 2432 | ||
| 2433 | /* Subtle row hover on tables */ | |
| 2434 | .bp-doc tr:hover { | |
| 2435 | background-color: var(--bp-c-bg-soft); | |
| 2436 | } | |
| 2437 | ||
| 2438 | /* Mobile-safe table scroll */ | |
| 2439 | .bp-doc table { | |
| 2440 | width: 100%; | |
| 2441 | max-width: 100%; | |
| 2442 | } | |
| 2443 | ||
| 2444 | /* Slightly stronger blockquote with brand-tinted accent */ | |
| 2445 | .bp-doc blockquote { | |
| 2446 | border-left-color: var(--bp-c-brand-1); | |
| 2447 | padding: 8px 0 8px 16px; | |
| 2448 | } | |
| 2449 | ||
| 2450 | /* Code-block scrollbar */ | |
| 2451 | .bp-doc [class*='language-'] pre::-webkit-scrollbar, | |
| 2452 | .bp-doc pre[data-lang]::-webkit-scrollbar { | |
| 2453 | height: 6px; | |
| 2454 | width: 6px; | |
| 2455 | } | |
| 2456 | .bp-doc [class*='language-'] pre::-webkit-scrollbar-thumb, | |
| 2457 | .bp-doc pre[data-lang]::-webkit-scrollbar-thumb { | |
| 2458 | background-color: var(--bp-c-divider); | |
| 2459 | border-radius: 3px; | |
| 2460 | } | |
| 2461 | .bp-doc [class*='language-'] pre::-webkit-scrollbar-thumb:hover, | |
| 2462 | .bp-doc pre[data-lang]::-webkit-scrollbar-thumb:hover { | |
| 2463 | background-color: var(--bp-c-text-3); | |
| 2464 | } | |
| 2465 | ||
| 2466 | /* Heading anchor: keep it subtle */ | |
| 2467 | .bp-doc .header-anchor { | |
| 2468 | color: var(--bp-c-text-3); | |
| 2469 | } | |
| 2470 | .bp-doc .header-anchor:hover, | |
| 2471 | .bp-doc .header-anchor:focus { | |
| 2472 | color: var(--bp-c-brand-1); | |
| 2473 | } | |
| 2474 | ||
| 2475 | /** | |
| 2476 | * Mobile nav / sidebar drawer | |
| 2477 | * -------------------------------------------------------------------------- */ | |
| 2478 | ||
| 2479 | .VPNavBarHamburger { | |
| 2480 | display: none; | |
| 2481 | width: 32px; | |
| 2482 | height: 32px; | |
| 2483 | background: transparent; | |
| 2484 | border: none; | |
| 2485 | cursor: pointer; | |
| 2486 | align-items: center; | |
| 2487 | justify-content: center; | |
| 2488 | color: var(--bp-c-text-2); | |
| 2489 | border-radius: 4px; | |
| 2490 | padding: 0; | |
| 2491 | margin-left: -4px; | |
| 2492 | } | |
| 2493 | ||
| 2494 | .VPNavBarHamburger:hover { | |
| 2495 | color: var(--bp-c-text-1); | |
| 2496 | background-color: var(--bp-c-bg-soft); | |
| 2497 | } | |
| 2498 | ||
| 2499 | .VPNavBarHamburger svg { | |
| 2500 | width: 20px; | |
| 2501 | height: 20px; | |
| 2502 | } | |
| 2503 | ||
| 2504 | @media (max-width: 959px) { | |
| 2505 | .VPNavBarHamburger { | |
| 2506 | display: inline-flex; | |
| 2507 | } | |
| 2508 | ||
| 2509 | .VPSidebar { | |
| 2510 | transform: translateX(-100%); | |
| 2511 | transition: transform 0.25s ease, box-shadow 0.25s ease; | |
| 2512 | z-index: var(--bp-z-index-sidebar, 25); | |
| 2513 | background-color: var(--bp-c-bg-alt); | |
| 2514 | box-shadow: none; | |
| 2515 | } | |
| 2516 | ||
| 2517 | .VPSidebar.is-open { | |
| 2518 | transform: translateX(0); | |
| 2519 | box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18); | |
| 2520 | } | |
| 2521 | ||
| 2522 | .VPSidebar-backdrop { | |
| 2523 | position: fixed; | |
| 2524 | inset: 0; | |
| 2525 | background-color: rgba(0, 0, 0, 0.32); | |
| 2526 | opacity: 0; | |
| 2527 | pointer-events: none; | |
| 2528 | transition: opacity 0.2s ease; | |
| 2529 | z-index: calc(var(--bp-z-index-sidebar, 25) - 1); | |
| 2530 | } | |
| 2531 | ||
| 2532 | .VPSidebar-backdrop.is-open { | |
| 2533 | opacity: 1; | |
| 2534 | pointer-events: auto; | |
| 2535 | } | |
| 2536 | ||
| 2537 | .VPContent--doc { | |
| 2538 | left: 0; | |
| 2539 | } | |
| 2540 | } | |
| 2541 | ` | |
| 2542 | ||
| 1904 | 2543 | /** |
| 1905 | 2544 | * Get all VitePress theme CSS combined |
| 1906 | 2545 | */ |
| @@ -1909,6 +2548,7 @@ export function getVitePressThemeCSS(): string { | ||
| 1909 | 2548 | /* VitePress Theme for BunPress */ |
| 1910 | 2549 | ${varsCSS} |
| 1911 | 2550 | ${baseCSS} |
| 2551 | ${layoutCSS} | |
| 1912 | 2552 | ${customBlockCSS} |
| 1913 | 2553 | ${codeGroupCSS} |
| 1914 | 2554 | ` |