Merge branch 'main' into chore/remove-barry-agent
This commit is contained in:
commit
7e0c325e20
|
|
@ -14,7 +14,9 @@
|
|||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
const fs = require('fs-extra');
|
||||
const { Installer } = require('../tools/installer/core/installer');
|
||||
const { ManifestGenerator } = require('../tools/installer/core/manifest-generator');
|
||||
const { OfficialModules } = require('../tools/installer/modules/official-modules');
|
||||
const { IdeManager } = require('../tools/installer/ide/manager');
|
||||
const { clearCache, loadPlatformCodes } = require('../tools/installer/ide/platform-codes');
|
||||
|
||||
|
|
@ -126,6 +128,56 @@ async function createSkillCollisionFixture() {
|
|||
return { root: fixtureRoot, bmadDir: fixtureDir };
|
||||
}
|
||||
|
||||
async function createCustomModuleManifestFixture() {
|
||||
const fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-custom-manifest-'));
|
||||
const bmadDir = path.join(fixtureRoot, '_bmad');
|
||||
const configDir = path.join(bmadDir, '_config');
|
||||
const moduleSourceDir = path.join(fixtureRoot, 'test-module-source');
|
||||
await fs.ensureDir(configDir);
|
||||
await fs.ensureDir(moduleSourceDir);
|
||||
|
||||
const minimalAgent = '<agent name="Test" title="T"><persona>p</persona></agent>';
|
||||
await fs.ensureDir(path.join(bmadDir, 'core', 'agents'));
|
||||
await fs.writeFile(path.join(bmadDir, 'core', 'agents', 'test.md'), minimalAgent);
|
||||
await fs.ensureDir(path.join(bmadDir, 'test-module', 'agents'));
|
||||
await fs.writeFile(path.join(bmadDir, 'test-module', 'agents', 'test.md'), minimalAgent);
|
||||
await fs.writeFile(path.join(moduleSourceDir, 'module.yaml'), ['code: test-module', 'name: Test Module', ''].join('\n'));
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(configDir, 'manifest.yaml'),
|
||||
[
|
||||
'installation:',
|
||||
' version: 6.2.2',
|
||||
' installDate: 2026-03-30T00:00:00.000Z',
|
||||
' lastUpdated: 2026-03-30T00:00:00.000Z',
|
||||
'modules:',
|
||||
' - name: core',
|
||||
' version: 6.2.2',
|
||||
' installDate: 2026-03-30T00:00:00.000Z',
|
||||
' lastUpdated: 2026-03-30T00:00:00.000Z',
|
||||
' source: built-in',
|
||||
' npmPackage: null',
|
||||
' repoUrl: null',
|
||||
' - name: test-module',
|
||||
' version: null',
|
||||
' installDate: 2026-03-30T00:00:00.000Z',
|
||||
' lastUpdated: 2026-03-30T00:00:00.000Z',
|
||||
' source: custom',
|
||||
' npmPackage: null',
|
||||
' repoUrl: null',
|
||||
'customModules:',
|
||||
' - id: test-module',
|
||||
' name: "Test Module"',
|
||||
` sourcePath: ${JSON.stringify(moduleSourceDir)}`,
|
||||
'ides:',
|
||||
' - codex',
|
||||
'',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
return { root: fixtureRoot, bmadDir, manifestPath: path.join(configDir, 'manifest.yaml'), moduleSourceDir };
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Suite
|
||||
*/
|
||||
|
|
@ -1713,6 +1765,107 @@ async function runTests() {
|
|||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Suite 33: Main manifest preserves active customModules only
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 33: Preserve active customModules in main manifest${colors.reset}\n`);
|
||||
|
||||
let customManifestFixture = null;
|
||||
try {
|
||||
customManifestFixture = await createCustomModuleManifestFixture();
|
||||
const yaml = require('yaml');
|
||||
const originalManifest = yaml.parse(await fs.readFile(customManifestFixture.manifestPath, 'utf8'));
|
||||
originalManifest.customModules.push({
|
||||
id: 'removed-module',
|
||||
name: 'Removed Module',
|
||||
sourcePath: path.join(customManifestFixture.root, 'removed-module-source'),
|
||||
});
|
||||
await fs.writeFile(customManifestFixture.manifestPath, yaml.stringify(originalManifest), 'utf8');
|
||||
|
||||
const generator33 = new ManifestGenerator();
|
||||
await generator33.generateManifests(customManifestFixture.bmadDir, ['core', 'test-module'], [], { ides: ['codex'] });
|
||||
|
||||
const updatedManifest = yaml.parse(await fs.readFile(customManifestFixture.manifestPath, 'utf8'));
|
||||
const customModule = updatedManifest.customModules?.find((entry) => entry.id === 'test-module');
|
||||
|
||||
assert(Array.isArray(updatedManifest.customModules), 'Main manifest keeps customModules array');
|
||||
assert(customModule !== undefined, 'Main manifest preserves existing custom module entry');
|
||||
assert(
|
||||
customModule && customModule.sourcePath === customManifestFixture.moduleSourceDir,
|
||||
'Main manifest preserves custom module sourcePath',
|
||||
);
|
||||
assert(
|
||||
!updatedManifest.customModules?.some((entry) => entry.id === 'removed-module'),
|
||||
'Main manifest drops stale custom module entries',
|
||||
);
|
||||
} catch (error) {
|
||||
assert(false, 'Main manifest preserves customModules test succeeds', error.message);
|
||||
} finally {
|
||||
if (customManifestFixture?.root) await fs.remove(customManifestFixture.root).catch(() => {});
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Suite 34: Quick update uses manifest-backed custom sources
|
||||
// ============================================================
|
||||
console.log(`${colors.yellow}Test Suite 34: Quick update uses manifest-backed custom module sources${colors.reset}\n`);
|
||||
|
||||
let quickUpdateFixture = null;
|
||||
const originalListAvailable34 = OfficialModules.prototype.listAvailable;
|
||||
const originalLoadExistingConfig34 = OfficialModules.prototype.loadExistingConfig;
|
||||
const originalCollectModuleConfigQuick34 = OfficialModules.prototype.collectModuleConfigQuick;
|
||||
try {
|
||||
quickUpdateFixture = await createCustomModuleManifestFixture();
|
||||
const installer34 = new Installer();
|
||||
installer34.externalModuleManager.hasModule = async () => false;
|
||||
installer34.externalModuleManager.listAvailable = async () => [];
|
||||
|
||||
let capturedInstallConfig34 = null;
|
||||
installer34.install = async (config) => {
|
||||
capturedInstallConfig34 = config;
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
OfficialModules.prototype.listAvailable = async function () {
|
||||
return { modules: [], customModules: [] };
|
||||
};
|
||||
OfficialModules.prototype.loadExistingConfig = async function () {
|
||||
this.collectedConfig = this.collectedConfig || {};
|
||||
};
|
||||
OfficialModules.prototype.collectModuleConfigQuick = async function (moduleName) {
|
||||
this.collectedConfig = this.collectedConfig || {};
|
||||
if (!this.collectedConfig[moduleName]) {
|
||||
this.collectedConfig[moduleName] = {};
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
await installer34.quickUpdate({
|
||||
directory: quickUpdateFixture.root,
|
||||
skipPrompts: true,
|
||||
});
|
||||
|
||||
const customModule34 = capturedInstallConfig34?._customModuleSources?.get('test-module');
|
||||
|
||||
assert(capturedInstallConfig34 !== null, 'Quick update forwards config to install');
|
||||
assert(customModule34 !== undefined, 'Quick update keeps manifest-backed custom module updateable');
|
||||
assert(customModule34 && customModule34.cached === false, 'Quick update uses manifest-backed source before cache');
|
||||
assert(
|
||||
customModule34 && customModule34.sourcePath === quickUpdateFixture.moduleSourceDir,
|
||||
'Quick update uses preserved manifest sourcePath for custom modules',
|
||||
);
|
||||
} catch (error) {
|
||||
assert(false, 'Quick update manifest-backed custom source test succeeds', error.message);
|
||||
} finally {
|
||||
OfficialModules.prototype.listAvailable = originalListAvailable34;
|
||||
OfficialModules.prototype.loadExistingConfig = originalLoadExistingConfig34;
|
||||
OfficialModules.prototype.collectModuleConfigQuick = originalCollectModuleConfigQuick34;
|
||||
if (quickUpdateFixture?.root) await fs.remove(quickUpdateFixture.root).catch(() => {});
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
// ============================================================
|
||||
// Summary
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -1144,59 +1144,12 @@ class Installer {
|
|||
const configuredIdes = existingInstall.ides;
|
||||
const projectRoot = path.dirname(bmadDir);
|
||||
|
||||
// Get custom module sources: first from --custom-content (re-cache from source), then from cache
|
||||
const customModuleSources = new Map();
|
||||
if (config.customContent?.sources?.length > 0) {
|
||||
for (const source of config.customContent.sources) {
|
||||
if (source.id && source.path && (await fs.pathExists(source.path))) {
|
||||
customModuleSources.set(source.id, {
|
||||
id: source.id,
|
||||
name: source.name || source.id,
|
||||
sourcePath: source.path,
|
||||
cached: false, // From CLI, will be re-cached
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
||||
if (await fs.pathExists(cacheDir)) {
|
||||
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
||||
|
||||
for (const cachedModule of cachedModules) {
|
||||
const moduleId = cachedModule.name;
|
||||
const cachedPath = path.join(cacheDir, moduleId);
|
||||
|
||||
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
|
||||
if (!(await fs.pathExists(cachedPath))) {
|
||||
continue;
|
||||
}
|
||||
if (!cachedModule.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if we already have this module from manifest
|
||||
if (customModuleSources.has(moduleId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is an external official module - skip cache for those
|
||||
const isExternal = await this.externalModuleManager.hasModule(moduleId);
|
||||
if (isExternal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is actually a custom module (has module.yaml)
|
||||
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
||||
if (await fs.pathExists(moduleYamlPath)) {
|
||||
customModuleSources.set(moduleId, {
|
||||
id: moduleId,
|
||||
name: moduleId,
|
||||
sourcePath: cachedPath,
|
||||
cached: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
const customModuleSources = await this.customModules.assembleQuickUpdateSources(
|
||||
config,
|
||||
existingInstall,
|
||||
bmadDir,
|
||||
this.externalModuleManager,
|
||||
);
|
||||
|
||||
// Get available modules (what we have source for)
|
||||
const availableModulesData = await new OfficialModules().listAvailable();
|
||||
|
|
|
|||
|
|
@ -377,10 +377,12 @@ class ManifestGenerator {
|
|||
*/
|
||||
async writeMainManifest(cfgDir) {
|
||||
const manifestPath = path.join(cfgDir, 'manifest.yaml');
|
||||
const installedModuleSet = new Set(this.modules);
|
||||
|
||||
// Read existing manifest to preserve install date
|
||||
let existingInstallDate = null;
|
||||
const existingModulesMap = new Map();
|
||||
let existingCustomModules = [];
|
||||
|
||||
if (await fs.pathExists(manifestPath)) {
|
||||
try {
|
||||
|
|
@ -402,6 +404,12 @@ class ManifestGenerator {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (existingManifest.customModules && Array.isArray(existingManifest.customModules)) {
|
||||
// We filter here so manifest regeneration preserves source metadata only for custom modules that
|
||||
// are still installed. Without that, customModules can retain stale entries for modules that were removed.
|
||||
existingCustomModules = existingManifest.customModules.filter((customModule) => installedModuleSet.has(customModule?.id));
|
||||
}
|
||||
} catch {
|
||||
// If we can't read existing manifest, continue with defaults
|
||||
}
|
||||
|
|
@ -437,6 +445,7 @@ class ManifestGenerator {
|
|||
lastUpdated: new Date().toISOString(),
|
||||
},
|
||||
modules: updatedModules,
|
||||
customModules: existingCustomModules,
|
||||
ides: this.selectedIdes,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -192,6 +192,111 @@ class CustomModules {
|
|||
|
||||
return this.paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble quick-update source candidates before install() hands them to discoverPaths().
|
||||
* This exists because discoverPaths() consumes already-prepared quick-update sources,
|
||||
* while quickUpdate() still has to build that source map from manifest, explicit inputs,
|
||||
* and cache conventions.
|
||||
* Precedence: manifest-backed paths, explicit sources override them, then cached modules.
|
||||
* @param {Object} config - Quick update configuration
|
||||
* @param {Object} existingInstall - Existing installation snapshot
|
||||
* @param {string} bmadDir - BMAD directory
|
||||
* @param {Object} externalModuleManager - External module manager
|
||||
* @returns {Promise<Map<string, Object>>} Map of custom module ID to source info
|
||||
*/
|
||||
async assembleQuickUpdateSources(config, existingInstall, bmadDir, externalModuleManager) {
|
||||
const projectRoot = path.dirname(bmadDir);
|
||||
const customModuleSources = new Map();
|
||||
|
||||
if (existingInstall.customModules) {
|
||||
for (const customModule of existingInstall.customModules) {
|
||||
// Skip if no ID - can't reliably track or re-cache without it
|
||||
if (!customModule?.id) continue;
|
||||
|
||||
let sourcePath = customModule.sourcePath;
|
||||
if (sourcePath && sourcePath.startsWith('_config')) {
|
||||
// Paths are relative to BMAD dir, but we want absolute paths for install
|
||||
sourcePath = path.join(bmadDir, sourcePath);
|
||||
} else if (!sourcePath && customModule.relativePath) {
|
||||
// Fall back to relativePath
|
||||
sourcePath = path.resolve(projectRoot, customModule.relativePath);
|
||||
} else if (sourcePath && !path.isAbsolute(sourcePath)) {
|
||||
// If we have a sourcePath but it's not absolute, resolve it relative to project root
|
||||
sourcePath = path.resolve(projectRoot, sourcePath);
|
||||
}
|
||||
|
||||
// If we still don't have a valid source path, skip this module
|
||||
if (!sourcePath || !(await fs.pathExists(sourcePath))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
customModuleSources.set(customModule.id, {
|
||||
id: customModule.id,
|
||||
name: customModule.name || customModule.id,
|
||||
sourcePath,
|
||||
relativePath: customModule.relativePath,
|
||||
cached: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (config.customContent?.sources?.length > 0) {
|
||||
for (const source of config.customContent.sources) {
|
||||
if (source.id && source.path) {
|
||||
customModuleSources.set(source.id, {
|
||||
id: source.id,
|
||||
name: source.name || source.id,
|
||||
sourcePath: source.path,
|
||||
cached: false, // From CLI, will be re-cached
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
||||
if (!(await fs.pathExists(cacheDir))) {
|
||||
return customModuleSources;
|
||||
}
|
||||
|
||||
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
||||
for (const cachedModule of cachedModules) {
|
||||
const moduleId = cachedModule.name;
|
||||
const cachedPath = path.join(cacheDir, moduleId);
|
||||
|
||||
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
|
||||
if (!(await fs.pathExists(cachedPath))) {
|
||||
continue;
|
||||
}
|
||||
if (!cachedModule.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if we already have this module from manifest
|
||||
if (customModuleSources.has(moduleId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is an external official module - skip cache for those
|
||||
const isExternal = await externalModuleManager.hasModule(moduleId);
|
||||
if (isExternal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is actually a custom module (has module.yaml)
|
||||
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
||||
if (await fs.pathExists(moduleYamlPath)) {
|
||||
customModuleSources.set(moduleId, {
|
||||
id: moduleId,
|
||||
name: moduleId,
|
||||
sourcePath: cachedPath,
|
||||
cached: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return customModuleSources;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { CustomModules };
|
||||
|
|
|
|||
|
|
@ -50,12 +50,6 @@ export default defineConfig({
|
|||
defaultLocale: 'root',
|
||||
locales,
|
||||
|
||||
logo: {
|
||||
light: './public/img/bmad-light.png',
|
||||
dark: './public/img/bmad-dark.png',
|
||||
alt: 'BMAD Method',
|
||||
replacesTitle: true,
|
||||
},
|
||||
favicon: '/favicon.ico',
|
||||
|
||||
// Social links
|
||||
|
|
|
|||
|
|
@ -12,16 +12,16 @@ const llmsFullUrl = `${getSiteUrl()}/llms-full.txt`;
|
|||
.ai-banner {
|
||||
width: 100%;
|
||||
height: var(--ai-banner-height, 2.75rem);
|
||||
background: #334155;
|
||||
color: #cbd5e1;
|
||||
background: #1a1a1a;
|
||||
color: #a1a1a1;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
border-bottom: 1px solid rgba(140, 140, 255, 0.15);
|
||||
border-bottom: 1px solid #262626;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
font-family: system-ui, sans-serif;
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
}
|
||||
|
||||
/* Truncate text on narrow screens */
|
||||
|
|
@ -32,15 +32,16 @@ const llmsFullUrl = `${getSiteUrl()}/llms-full.txt`;
|
|||
max-width: 100%;
|
||||
}
|
||||
.ai-banner a {
|
||||
color: #B9B9FF;
|
||||
color: #3b82f6;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
.ai-banner a:hover {
|
||||
color: #fafafa;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.ai-banner a:focus-visible {
|
||||
outline: 2px solid #B9B9FF;
|
||||
outline: 2px solid #3b82f6;
|
||||
outline-offset: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
/**
|
||||
* BMAD Method Documentation - Custom Styles for Starlight
|
||||
* Electric Blue theme optimized for dark mode
|
||||
* Dark theme matching bmadcode.com Ghost blog
|
||||
*
|
||||
* CSS Variable Mapping:
|
||||
* Docusaurus → Starlight
|
||||
* --ifm-color-primary → --sl-color-accent
|
||||
* --ifm-background-color → --sl-color-bg
|
||||
* --ifm-font-color-base → --sl-color-text
|
||||
* Design tokens from Ghost theme:
|
||||
* Background: #0a0a0a | Surface: #1a1a1a | Border: #262626
|
||||
* Accent: #3b82f6 | Gold: #d4a853 | Text: #fafafa/#a1a1a1/#666666
|
||||
*/
|
||||
|
||||
/* Google Fonts - match Ghost blog typography */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
||||
|
||||
/* ============================================
|
||||
COLOR PALETTE - Light Mode
|
||||
============================================ */
|
||||
|
|
@ -19,10 +20,10 @@
|
|||
/* Full-width content - override Starlight's default 45rem/67.5rem */
|
||||
--sl-content-width: 65rem;
|
||||
|
||||
/* Primary accent colors - purple to match Docusaurus */
|
||||
--sl-color-accent-low: #e0e0ff;
|
||||
--sl-color-accent: #5E5ED0;
|
||||
--sl-color-accent-high: #3333CC;
|
||||
/* Primary accent colors - blue to match Ghost blog */
|
||||
--sl-color-accent-low: #dbeafe;
|
||||
--sl-color-accent: #2563eb;
|
||||
--sl-color-accent-high: #1d4ed8;
|
||||
|
||||
/* Text colors */
|
||||
--sl-color-white: #1e293b;
|
||||
|
|
@ -35,13 +36,14 @@
|
|||
--sl-color-black: #f8fafc;
|
||||
|
||||
/* Font settings */
|
||||
--sl-font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
||||
--sl-font: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
|
||||
Arial, sans-serif;
|
||||
--sl-font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace;
|
||||
--sl-text-base: 1rem;
|
||||
--sl-line-height: 1.7;
|
||||
|
||||
/* Code highlighting */
|
||||
--sl-color-bg-inline-code: rgba(94, 94, 208, 0.1);
|
||||
--sl-color-bg-inline-code: rgba(59, 130, 246, 0.08);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
|
|
@ -51,35 +53,49 @@
|
|||
/* Full-width content - override Starlight's default */
|
||||
--sl-content-width: 65rem;
|
||||
|
||||
/* Primary accent colors - purple to match Docusaurus */
|
||||
--sl-color-accent-low: #2a2a5a;
|
||||
--sl-color-accent: #8C8CFF;
|
||||
--sl-color-accent-high: #B9B9FF;
|
||||
/* Primary accent colors - blue to match Ghost blog */
|
||||
--sl-color-accent-low: rgba(59, 130, 246, 0.12);
|
||||
--sl-color-accent: #3b82f6;
|
||||
--sl-color-accent-high: #60a5fa;
|
||||
|
||||
/* Background colors */
|
||||
--sl-color-bg: #1b1b1d;
|
||||
--sl-color-bg-nav: #1b1b1d;
|
||||
--sl-color-bg-sidebar: #1b1b1d;
|
||||
--sl-color-hairline-light: rgba(140, 140, 255, 0.1);
|
||||
--sl-color-hairline: rgba(140, 140, 255, 0.15);
|
||||
/* Background colors - match Ghost blog */
|
||||
--sl-color-bg: #0a0a0a;
|
||||
--sl-color-bg-nav: #0a0a0a;
|
||||
--sl-color-bg-sidebar: #0a0a0a;
|
||||
--sl-color-hairline-light: rgba(255, 255, 255, 0.06);
|
||||
--sl-color-hairline: #262626;
|
||||
|
||||
/* Text colors */
|
||||
--sl-color-white: #f8fafc;
|
||||
/* Text colors - match Ghost blog */
|
||||
--sl-color-white: #fafafa;
|
||||
--sl-color-gray-1: #e2e8f0;
|
||||
--sl-color-gray-2: #cbd5e1;
|
||||
--sl-color-gray-2: #a1a1a1;
|
||||
--sl-color-gray-3: #94a3b8;
|
||||
--sl-color-gray-4: #64748b;
|
||||
--sl-color-gray-4: #666666;
|
||||
--sl-color-gray-5: #475569;
|
||||
--sl-color-gray-6: #334155;
|
||||
--sl-color-black: #1b1b1d;
|
||||
--sl-color-gray-6: #262626;
|
||||
--sl-color-black: #0a0a0a;
|
||||
|
||||
/* Code highlighting */
|
||||
--sl-color-bg-inline-code: rgba(140, 140, 255, 0.15);
|
||||
--sl-color-bg-inline-code: rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TYPOGRAPHY
|
||||
============================================ */
|
||||
|
||||
/* Space Grotesk for all headings - match Ghost blog */
|
||||
.sl-markdown-content h1,
|
||||
.sl-markdown-content h2,
|
||||
.sl-markdown-content h3,
|
||||
.sl-markdown-content h4,
|
||||
.sl-markdown-content h5,
|
||||
.sl-markdown-content h6,
|
||||
.site-title,
|
||||
starlight-toc h2 {
|
||||
font-family: 'Space Grotesk', 'Inter', system-ui, sans-serif;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.sl-markdown-content h1 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
|
@ -138,14 +154,14 @@
|
|||
|
||||
/* Active state - thin left accent bar */
|
||||
.sidebar-content a[aria-current='page'] {
|
||||
background-color: rgba(94, 94, 208, 0.08);
|
||||
background-color: rgba(59, 130, 246, 0.08);
|
||||
color: var(--sl-color-accent);
|
||||
border-left-color: var(--sl-color-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .sidebar-content a[aria-current='page'] {
|
||||
background-color: rgba(140, 140, 255, 0.1);
|
||||
background-color: rgba(59, 130, 246, 0.1);
|
||||
color: var(--sl-color-accent-high);
|
||||
border-left-color: var(--sl-color-accent);
|
||||
}
|
||||
|
|
@ -232,7 +248,8 @@ header.header .header.sl-flex {
|
|||
}
|
||||
|
||||
:root[data-theme='dark'] header.header {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid #262626;
|
||||
}
|
||||
|
||||
.site-title {
|
||||
|
|
@ -281,20 +298,20 @@ header.header .header.sl-flex {
|
|||
.card:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: var(--sl-color-accent);
|
||||
box-shadow: 0 8px 24px rgba(94, 94, 208, 0.15);
|
||||
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .card {
|
||||
background: linear-gradient(145deg, rgba(30, 41, 59, 0.6), rgba(15, 23, 42, 0.8));
|
||||
border-color: rgba(140, 140, 255, 0.2);
|
||||
background: #1a1a1a;
|
||||
border-color: #262626;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .card:hover {
|
||||
border-color: rgba(140, 140, 255, 0.5);
|
||||
border-color: #3b82f6;
|
||||
box-shadow:
|
||||
0 8px 32px rgba(140, 140, 255, 0.2),
|
||||
0 0 0 1px rgba(140, 140, 255, 0.1);
|
||||
0 8px 32px rgba(59, 130, 246, 0.15),
|
||||
0 0 0 1px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* Starlight card grid */
|
||||
|
|
@ -313,11 +330,11 @@ header.header .header.sl-flex {
|
|||
}
|
||||
|
||||
:root[data-theme='dark'] .sl-link-card {
|
||||
border-color: rgba(140, 140, 255, 0.2);
|
||||
border-color: #262626;
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .sl-link-card:hover {
|
||||
border-color: rgba(140, 140, 255, 0.5);
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
|
|
@ -372,21 +389,21 @@ table {
|
|||
}
|
||||
|
||||
:root[data-theme='dark'] table {
|
||||
border-color: rgba(140, 140, 255, 0.1);
|
||||
border-color: #262626;
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] table th {
|
||||
background-color: rgba(140, 140, 255, 0.05);
|
||||
background-color: rgba(59, 130, 246, 0.05);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] table tr:nth-child(2n) {
|
||||
background-color: rgba(140, 140, 255, 0.02);
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
/* Blockquotes */
|
||||
blockquote {
|
||||
border-left-color: var(--sl-color-accent);
|
||||
background-color: rgba(94, 94, 208, 0.05);
|
||||
background-color: rgba(59, 130, 246, 0.05);
|
||||
border-radius: 0 8px 8px 0;
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
|
@ -423,19 +440,19 @@ blockquote {
|
|||
|
||||
/* Note aside */
|
||||
.starlight-aside--note {
|
||||
background-color: rgba(94, 94, 208, 0.08);
|
||||
background-color: rgba(59, 130, 246, 0.08);
|
||||
}
|
||||
|
||||
.starlight-aside--note .starlight-aside__title {
|
||||
color: #5C5CCC;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .starlight-aside--note {
|
||||
background-color: rgba(140, 140, 255, 0.12);
|
||||
background-color: rgba(59, 130, 246, 0.12);
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .starlight-aside--note .starlight-aside__title {
|
||||
color: #8C8CFF;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
/* Caution aside */
|
||||
|
|
@ -512,7 +529,7 @@ blockquote {
|
|||
ROADMAP STYLES
|
||||
============================================ */
|
||||
.roadmap-container {
|
||||
--color-planned: #6366f1;
|
||||
--color-planned: #3b82f6;
|
||||
--color-in-progress: #10b981;
|
||||
--color-exploring: #f59e0b;
|
||||
--color-bg-card: rgba(255, 255, 255, 0.03);
|
||||
|
|
@ -663,8 +680,8 @@ blockquote {
|
|||
}
|
||||
|
||||
.roadmap-badge.planned {
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
color: #6366f1;
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.roadmap-badge.exploring {
|
||||
|
|
@ -735,7 +752,7 @@ blockquote {
|
|||
.roadmap-future-card {
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, rgba(99, 102, 241, 0.1), rgba(245, 158, 11, 0.05));
|
||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.08), rgba(212, 168, 83, 0.05));
|
||||
border: 1px solid var(--color-border);
|
||||
transition: transform 0.2s ease;
|
||||
display: flex;
|
||||
|
|
|
|||
Loading…
Reference in New Issue