ReviewOS

also looking at this

stacks/bunpress

fix(spa): repair client-side router and polish default theme

#71
Merged glennmichael123 wants to merge fix/spa-nav-and-theme-polish into main
9 files +1037 -170
packages/bunpress/src/themes/vitepress/index.tsmodified+640-0
Changes to packages/bunpress/src/themes/vitepress/index.ts
@@ -1901,6 +1901,645 @@ const codeGroupCSS = `/**
19011901 border: none;
19021902}`
19031903
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.
1906export 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
19042543/**
19052544 * Get all VitePress theme CSS combined
19062545 */
@@ -1909,6 +2548,7 @@ export function getVitePressThemeCSS(): string {
19092548/* VitePress Theme for BunPress */
19102549${varsCSS}
19112550${baseCSS}
2551${layoutCSS}
19122552${customBlockCSS}
19132553${codeGroupCSS}
19142554`