From 7c2a5367a34df73d3c7ddadc420e11341a16e5d4 Mon Sep 17 00:00:00 2001 From: Olivier Date: Thu, 25 Sep 2025 20:32:10 -0400 Subject: [PATCH] - Changed specification mechanism from "class" to "layout" for built-in menu styles - Introduced three new layouts (default, minimal, slate) replacing class-based themes - Added comprehensive CSS variables for full customization of colors, fonts, borders, and link types - Updated README with new usage syntax, layout examples, and removed outdated todos --- README.md | 236 ++++++++++++++++++++++-------------------- main.js | 28 +++-- main.ts | 56 ++++++++-- styles.css | 295 +++++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 434 insertions(+), 181 deletions(-) diff --git a/README.md b/README.md index f913e12..543eefb 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,14 @@ # Menu Plugin for Obsidian -A flexible Obsidian plugin that allows you to create custom menus using code blocks with support for internal links, external links, and local file links. +Create custom navigation menus in Obsidian using simple code blocks. Supports internal links, external URLs, and local file links with built-in themes and full customization. +## Basic Usage -## Todo -- [ ] Maybe change the format from using "class" to using "layout" and have them numbered? Like grid, flex, etc. - - -## Features - -- ๐Ÿ”— **Multiple Link Types**: Support for Obsidian internal links, external web links, and local file links -- ๐ŸŽจ **Three Built-in Themes**: Default, minimal, and enhanced styling options -- โš™๏ธ **Custom Styling**: Full control over appearance with custom CSS classes -- ๐Ÿ“ **File System Integration**: Click to open local files and folders with system default applications -- ๐Ÿ”„ **Hot Reload Support**: Development-friendly with automatic reloading - -## Usage - -Create a menu using a `menu` code block: +Create a menu using a `menu` code block with one of the built-in layouts: ````markdown ```menu -class: default +layout: default [[Home]] [[Projects|My Projects]] [Google](https://google.com) @@ -29,118 +16,149 @@ class: default ``` ```` -### Syntax - -- **First line**: `class: {theme-name}` - Specifies the styling theme -- **Subsequent lines**: Links in various formats - -### Link Types - -#### Internal Links (Obsidian Notes) -``` -[[Note Name]] # Links to "Note Name.md" -[[Note Name|Display]] # Links to "Note Name.md" but displays "Display" -``` - -#### External Links (Web URLs) -``` -[Display Text](https://example.com) -``` - -#### File Links (Local Files/Folders) -``` -[My Documents](file:///C:/Users/YourName/Documents) -[Project Folder](file:///C:/Users/YourName/Projects) -[PDF File](file:///C:/Users/YourName/document.pdf) -``` - -## Built-in Themes - -### Default Theme -The standard theme with borders, backgrounds, and distinct styling for each link type. +## Built-in Layouts +### `default` +Standard buttons with borders and backgrounds ````markdown ```menu -class: default -[[Home]] -[Google](https://google.com) -[Documents](file:///C:/Users/YourName/Documents) +layout: default +[[Dashboard]] +[GitHub](https://github.com) +[Files](file:///C:/Users/Documents) ``` ```` -### Minimal Theme -Clean, text-only appearance with subtle color differentiation. - +### `minimal` +Clean text links with subtle colors ````markdown ```menu -class: minimal -[[Home]] -[Google](https://google.com) -[Documents](file:///C:/Users/YourName/Documents) +layout: minimal +[[Notes]] +[Web](https://example.com) +[Folder](file:///C:/Projects) ``` ```` - - -## Custom Styling - -Create your own themes by adding CSS to your vault's snippets: - -```css -.menu-container.my-custom-theme { - display: flex; - gap: 2em; - padding: 1em; - background: linear-gradient(45deg, #ff6b6b, #4ecdc4); - border-radius: 10px; -} - -.menu-container.my-custom-theme a { - color: white; - text-decoration: none; - padding: 0.5em 1em; - border-radius: 5px; - background: rgba(255, 255, 255, 0.2); - transition: all 0.3s ease; -} - -.menu-container.my-custom-theme a:hover { - background: rgba(255, 255, 255, 0.3); - transform: scale(1.05); -} -``` - -Then use it in your menu: - +### `slate` +Solid background buttons ````markdown ```menu -class: my-custom-theme +layout: slate [[Home]] -[Google](https://google.com) +[Links](https://obsidian.md) ``` ```` -## Development - -### Setup -```bash -npm install -npm run dev # Start development with auto-rebuild -npm run build # Build for production +### `horizon` +Modern outlined style +````markdown +```menu +layout: horizon +[[Dashboard]] +[Resources](https://example.com) ``` +```` -## Technical Details +### `aether` +Grid layout with equal-width items +````markdown +```menu +layout: aether +[[Projects]] +[GitHub](https://github.com) +[Documents](file:///C:/Users/Documents) +``` +```` -### Link Processing -- **Internal links**: Use Obsidian's `data-href` attribute for proper navigation -- **External links**: Open in new tab with security attributes -- **File links**: Use Electron's shell API to open with system default applications +## Link Types -### CSS Architecture -- Base class: `.menu-container` -- Theme classes: `.menu-container.{theme-name}` -- Link type classes: `.menu-internal-link`, `.menu-external-link`, `.menu-file-link` +- **Internal**: `[[Note Name]]` or `[[Note Name|Display Text]]` +- **External**: `[Display Text](https://example.com)` +- **Files**: `[Display Text](file:///C:/path/to/file)` -## License +## Color Customization -MIT License - feel free to modify and distribute. +Add color properties using YAML syntax: + +````markdown +```menu +layout: default +bg: #1a1a1a +text: #ffffff +accent: #ff6b6b +border: #333333 +[[Home]] +[GitHub](https://github.com) +``` +```` + +### Global Color Variables +- `bg`: Background color +- `text`: Text color +- `accent`: Hover/accent color +- `border`: Border color +- `hover-bg`: Hover background +- `hover-border`: Hover border color +- `font`: Font family + +### Link-Type Specific Colors +Customize each link type individually: + +````markdown +```menu +layout: minimal +internal-text: #00ff00 +external-text: #ff6600 +file-text: #0066ff +internal-font: "Arial" +external-font: "Georgia" +[[Internal Link]] +[External Link](https://example.com) +[File Link](file:///C:/Documents) +``` +```` + +**Available for each type** (`internal`, `external`, `file`): +- `{type}-text`: Text color +- `{type}-bg`: Background color +- `{type}-border`: Border color +- `{type}-font`: Font family +- `{type}-accent`: Hover text color +- `{type}-hover-bg`: Hover background +- `{type}-hover-border`: Hover border color + +## Advanced Examples + +### Gradient Background +````markdown +```menu +layout: horizon +bg: linear-gradient(45deg, #667eea, #764ba2) +text: white +accent: #ffd700 +[[Dashboard]] +[Projects](https://github.com) +``` +```` + +### Different Fonts Per Link Type +````markdown +```menu +layout: default +font: "Inter" +internal-font: "Fira Code" +external-font: "Georgia" +file-font: "Arial" +[[Code Notes]] +[Articles](https://medium.com) +[Local Files](file:///C:/Documents) +``` +```` + +## Notes + +- Use either `layout:` or `class:` (both work the same) +- File paths use `file://` protocol +- Colors support hex, rgb, hsl, CSS variables, and gradients +- Font names with spaces need quotes: `font: "Work Sans"` \ No newline at end of file diff --git a/main.js b/main.js index 26ff191..b9ab40c 100644 --- a/main.js +++ b/main.js @@ -33,18 +33,33 @@ var MenuPlugin = class extends import_obsidian.Plugin { async onload() { this.registerMarkdownCodeBlockProcessor("menu", (source, el, ctx) => { const lines = source.trim().split("\n"); - let cssClass = ""; + let layout = ""; + let colors = {}; const links = []; for (const line of lines) { const trimmed = line.trim(); - if (trimmed.startsWith("class:")) { - cssClass = trimmed.substring(6).trim().replace(/[{}]/g, ""); - } else if (trimmed) { + if (trimmed.startsWith("layout:") || trimmed.startsWith("class:")) { + const colonIndex = trimmed.indexOf(":"); + layout = trimmed.substring(colonIndex + 1).trim(); + } else if (trimmed.includes(":") && !trimmed.startsWith("[") && !trimmed.startsWith("[[")) { + const [key, ...valueParts] = trimmed.split(":"); + const value = valueParts.join(":").trim(); + if (key && value && !key.includes("//") && !key.includes("http")) { + colors[key.trim()] = value; + } + } else if (trimmed && !trimmed.includes(":")) { + links.push(trimmed); + } else if (trimmed.startsWith("[")) { links.push(trimmed); } } - const finalClass = cssClass || "default"; - const container = el.createEl("div", { cls: `menu-container ${finalClass}` }); + const finalLayout = layout || "default"; + const container = el.createEl("div", { cls: `menu-container ${finalLayout}` }); + if (Object.keys(colors).length > 0) { + for (const [key, value] of Object.entries(colors)) { + container.style.setProperty(`--${key}`, value); + } + } for (const link of links) { if (link.startsWith("[[") && link.endsWith("]]")) { const linkContent = link.slice(2, -2); @@ -100,4 +115,3 @@ var MenuPlugin = class extends import_obsidian.Plugin { }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = {}); -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsibWFpbi50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiaW1wb3J0IHsgUGx1Z2luIH0gZnJvbSAnb2JzaWRpYW4nO1xyXG5pbXBvcnQgeyBmaWxlVVJMVG9QYXRoIH0gZnJvbSAndXJsJztcclxuXHJcbmNvbnN0IHsgc2hlbGwgfSA9IHJlcXVpcmUoJ2VsZWN0cm9uJyk7XHJcblxyXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBNZW51UGx1Z2luIGV4dGVuZHMgUGx1Z2luIHtcclxuXHRhc3luYyBvbmxvYWQoKSB7XHJcblx0XHR0aGlzLnJlZ2lzdGVyTWFya2Rvd25Db2RlQmxvY2tQcm9jZXNzb3IoJ21lbnUnLCAoc291cmNlLCBlbCwgY3R4KSA9PiB7XHJcblx0XHRcdC8vIFBhcnNlIHRoZSBzb3VyY2VcclxuXHRcdFx0Y29uc3QgbGluZXMgPSBzb3VyY2UudHJpbSgpLnNwbGl0KCdcXG4nKTtcclxuXHRcdFx0bGV0IGNzc0NsYXNzID0gJyc7XHJcblx0XHRcdGNvbnN0IGxpbmtzOiBzdHJpbmdbXSA9IFtdO1xyXG5cclxuXHRcdFx0Zm9yIChjb25zdCBsaW5lIG9mIGxpbmVzKSB7XHJcblx0XHRcdFx0Y29uc3QgdHJpbW1lZCA9IGxpbmUudHJpbSgpO1xyXG5cdFx0XHRcdGlmICh0cmltbWVkLnN0YXJ0c1dpdGgoJ2NsYXNzOicpKSB7XHJcblx0XHRcdFx0XHRjc3NDbGFzcyA9IHRyaW1tZWQuc3Vic3RyaW5nKDYpLnRyaW0oKS5yZXBsYWNlKC9be31dL2csICcnKTtcclxuXHRcdFx0XHR9IGVsc2UgaWYgKHRyaW1tZWQpIHtcclxuXHRcdFx0XHRcdGxpbmtzLnB1c2godHJpbW1lZCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvLyBDcmVhdGUgdGhlIGNvbnRhaW5lciAtIGlmIG5vIGNsYXNzIHNwZWNpZmllZCwgdXNlICdkZWZhdWx0J1xyXG5cdFx0XHRjb25zdCBmaW5hbENsYXNzID0gY3NzQ2xhc3MgfHwgJ2RlZmF1bHQnO1xyXG5cdFx0XHRjb25zdCBjb250YWluZXIgPSBlbC5jcmVhdGVFbCgnZGl2JywgeyBjbHM6IGBtZW51LWNvbnRhaW5lciAke2ZpbmFsQ2xhc3N9YCB9KTtcclxuXHJcblx0XHRcdC8vIFByb2Nlc3MgZWFjaCBsaW5rXHJcblx0XHRcdGZvciAoY29uc3QgbGluayBvZiBsaW5rcykge1xyXG5cdFx0XHRcdGlmIChsaW5rLnN0YXJ0c1dpdGgoJ1tbJykgJiYgbGluay5lbmRzV2l0aCgnXV0nKSkge1xyXG5cdFx0XHRcdFx0Ly8gSW50ZXJuYWwgbGlua1xyXG5cdFx0XHRcdFx0Y29uc3QgbGlua0NvbnRlbnQgPSBsaW5rLnNsaWNlKDIsIC0yKTtcclxuXHRcdFx0XHRcdGxldCBocmVmID0gbGlua0NvbnRlbnQ7XHJcblx0XHRcdFx0XHRsZXQgdGV4dCA9IGxpbmtDb250ZW50O1xyXG5cdFx0XHRcdFx0aWYgKGxpbmtDb250ZW50LmluY2x1ZGVzKCd8JykpIHtcclxuXHRcdFx0XHRcdFx0W2hyZWYsIHRleHRdID0gbGlua0NvbnRlbnQuc3BsaXQoJ3wnKTtcclxuXHRcdFx0XHRcdH1cclxuXHRcdFx0XHRcdGNvbnN0IGEgPSBjb250YWluZXIuY3JlYXRlRWwoJ2EnLCB7XHJcblx0XHRcdFx0XHRcdHRleHQ6IHRleHQsXHJcblx0XHRcdFx0XHRcdGF0dHI6IHsgJ2RhdGEtaHJlZic6IGhyZWYgfVxyXG5cdFx0XHRcdFx0fSk7XHJcblx0XHRcdFx0XHRhLmFkZENsYXNzKCdtZW51LWludGVybmFsLWxpbmsnKTtcclxuXHRcdFx0XHR9IGVsc2UgaWYgKGxpbmsubWF0Y2goL15cXFsuKlxcXVxcKC4qXFwpJC8pKSB7XHJcblx0XHRcdFx0XHQvLyBFeHRlcm5hbCBsaW5rXHJcblx0XHRcdFx0XHRjb25zdCBtYXRjaCA9IGxpbmsubWF0Y2goL15cXFsoLiopXFxdXFwoKC4qKVxcKSQvKTtcclxuXHRcdFx0XHRcdGlmIChtYXRjaCkge1xyXG5cdFx0XHRcdFx0XHRjb25zdCB0ZXh0ID0gbWF0Y2hbMV07XHJcblx0XHRcdFx0XHRcdGNvbnN0IHVybCA9IG1hdGNoWzJdO1xyXG5cdFx0XHRcdFx0XHRjb25zdCBhID0gY29udGFpbmVyLmNyZWF0ZUVsKCdhJywge1xyXG5cdFx0XHRcdFx0XHRcdHRleHQ6IHRleHQsXHJcblx0XHRcdFx0XHRcdFx0YXR0cjogdXJsLnN0YXJ0c1dpdGgoJ2ZpbGU6Ly8nKSA/IHt9IDogeyBocmVmOiB1cmwsIHRhcmdldDogJ19ibGFuaycsIHJlbDogJ25vb3BlbmVyIG5vcmVmZXJyZXInIH1cclxuXHRcdFx0XHRcdFx0fSk7XHJcblx0XHRcdFx0XHRcdGEuc3R5bGUuY3Vyc29yID0gJ3BvaW50ZXInO1xyXG5cdFx0XHRcdFx0XHRcclxuXHRcdFx0XHRcdFx0Ly8gQWRkIGFwcHJvcHJpYXRlIGNsYXNzIGJhc2VkIG9uIGxpbmsgdHlwZVxyXG5cdFx0XHRcdFx0XHRpZiAodXJsLnN0YXJ0c1dpdGgoJ2ZpbGU6Ly8nKSkge1xyXG5cdFx0XHRcdFx0XHRcdGEuYWRkQ2xhc3MoJ21lbnUtZmlsZS1saW5rJyk7XHJcblx0XHRcdFx0XHRcdH0gZWxzZSB7XHJcblx0XHRcdFx0XHRcdFx0YS5hZGRDbGFzcygnbWVudS1leHRlcm5hbC1saW5rJyk7XHJcblx0XHRcdFx0XHRcdH1cclxuXHRcdFx0XHRcdFx0YS5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIChlKSA9PiB7XHJcblx0XHRcdFx0XHRcdFx0ZS5wcmV2ZW50RGVmYXVsdCgpO1xyXG5cdFx0XHRcdFx0XHRcdGlmICh1cmwuc3RhcnRzV2l0aCgnZmlsZTovLycpKSB7XHJcblx0XHRcdFx0XHRcdFx0XHR0cnkge1xyXG5cdFx0XHRcdFx0XHRcdFx0XHQvLyBDb252ZXJ0IGZpbGUgVVJMIHRvIHBhdGggYW5kIGhhbmRsZSBXaW5kb3dzIHBhdGhzXHJcblx0XHRcdFx0XHRcdFx0XHRcdGxldCBmaWxlUGF0aCA9IGRlY29kZVVSSUNvbXBvbmVudCh1cmwuc3Vic3RyaW5nKDcpKTsgLy8gUmVtb3ZlICdmaWxlOi8vJ1xyXG5cdFx0XHRcdFx0XHRcdFx0XHQvLyBIYW5kbGUgV2luZG93cyBwYXRocyB0aGF0IHN0YXJ0IHdpdGggL0M6XHJcblx0XHRcdFx0XHRcdFx0XHRcdGlmIChmaWxlUGF0aC5zdGFydHNXaXRoKCcvJykgJiYgZmlsZVBhdGguY2hhckF0KDIpID09PSAnOicpIHtcclxuXHRcdFx0XHRcdFx0XHRcdFx0XHRmaWxlUGF0aCA9IGZpbGVQYXRoLnN1YnN0cmluZygxKTtcclxuXHRcdFx0XHRcdFx0XHRcdFx0fVxyXG5cdFx0XHRcdFx0XHRcdFx0XHRjb25zb2xlLmxvZygnT3BlbmluZyBmaWxlIHBhdGg6JywgZmlsZVBhdGgpO1xyXG5cdFx0XHRcdFx0XHRcdFx0XHRzaGVsbC5vcGVuUGF0aChmaWxlUGF0aCk7XHJcblx0XHRcdFx0XHRcdFx0XHR9IGNhdGNoIChlcnJvcikge1xyXG5cdFx0XHRcdFx0XHRcdFx0XHRjb25zb2xlLmVycm9yKCdGYWlsZWQgdG8gb3BlbiBmaWxlOicsIGVycm9yKTtcclxuXHRcdFx0XHRcdFx0XHRcdH1cclxuXHRcdFx0XHRcdFx0XHR9IGVsc2Uge1xyXG5cdFx0XHRcdFx0XHRcdFx0d2luZG93Lm9wZW4odXJsLCAnX2JsYW5rJywgJ25vb3BlbmVyLG5vcmVmZXJyZXInKTtcclxuXHRcdFx0XHRcdFx0XHR9XHJcblx0XHRcdFx0XHRcdH0pO1xyXG5cdFx0XHRcdFx0fVxyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cdFx0fSk7XHJcblx0fVxyXG5cclxuXHRvbnVubG9hZCgpIHtcclxuXHRcdC8vIENsZWFudXAgaWYgbmVlZGVkXHJcblx0fVxyXG59XHJcbiJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxzQkFBdUI7QUFHdkIsSUFBTSxFQUFFLE1BQU0sSUFBSSxRQUFRLFVBQVU7QUFFcEMsSUFBcUIsYUFBckIsY0FBd0MsdUJBQU87QUFBQSxFQUM5QyxNQUFNLFNBQVM7QUFDZCxTQUFLLG1DQUFtQyxRQUFR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 diff --git a/main.ts b/main.ts index 20f5c61..2480fec 100644 --- a/main.ts +++ b/main.ts @@ -3,27 +3,61 @@ import { fileURLToPath } from 'url'; const { shell } = require('electron'); +// Simple YAML parser for color properties +function parseYAML(text: string) { + const result: Record = {}; + const lines = text.split('\n'); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed && trimmed.includes(':')) { + const [key, ...valueParts] = trimmed.split(':'); + const value = valueParts.join(':').trim(); + if (key && value) { + result[key.trim()] = value; + } + } + } + return result; +} + export default class MenuPlugin extends Plugin { async onload() { this.registerMarkdownCodeBlockProcessor('menu', (source, el, ctx) => { - // Parse the source const lines = source.trim().split('\n'); - let cssClass = ''; + let layout = ''; + let colors: Record = {}; const links: string[] = []; - + + // Parse YAML-like properties and links for (const line of lines) { const trimmed = line.trim(); - if (trimmed.startsWith('class:')) { - cssClass = trimmed.substring(6).trim().replace(/[{}]/g, ''); - } else if (trimmed) { + if (trimmed.startsWith('layout:') || trimmed.startsWith('class:')) { + const colonIndex = trimmed.indexOf(':'); + layout = trimmed.substring(colonIndex + 1).trim(); + } else if (trimmed.includes(':') && !trimmed.startsWith('[') && !trimmed.startsWith('[[')) { + // Parse color properties + const [key, ...valueParts] = trimmed.split(':'); + const value = valueParts.join(':').trim(); + if (key && value && !key.includes('//') && !key.includes('http')) { + colors[key.trim()] = value; + } + } else if (trimmed && !trimmed.includes(':')) { + links.push(trimmed); + } else if (trimmed.startsWith('[')) { links.push(trimmed); } } - - // Create the container - if no class specified, use 'default' - const finalClass = cssClass || 'default'; - const container = el.createEl('div', { cls: `menu-container ${finalClass}` }); - + + const finalLayout = layout || 'default'; + const container = el.createEl('div', { cls: `menu-container ${finalLayout}` }); + + // Apply custom colors as CSS variables + if (Object.keys(colors).length > 0) { + for (const [key, value] of Object.entries(colors)) { + container.style.setProperty(`--${key}`, value); + } + } + // Process each link for (const link of links) { if (link.startsWith('[[') && link.endsWith(']]')) { diff --git a/styles.css b/styles.css index bb2f6b1..376ed52 100644 --- a/styles.css +++ b/styles.css @@ -22,6 +22,17 @@ } } +.menu-container, +.menu-container a { + transition: 125ms; +} + +/* + โ€ขยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทโ€ขยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทโ€ข + | Menu Container Styles | + โ€ขยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทโ€ขยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทโ€ข +*/ + /* Default style variant */ .menu-container.default { @@ -29,7 +40,8 @@ gap: 1em; flex-wrap: wrap; border-radius: 6px; - background-color: var(--background-primary); + background-color: var(--bg, var(--background-primary)); + font-family: var(--font, inherit); a { padding: 0.6em 1em; @@ -37,14 +49,59 @@ border-radius: 4px; transition: all 0.2s ease; font-weight: 500; - border: 1px solid var(--background-modifier-border); - color: var(--text-normal); + border: 1px solid var(--border, var(--background-modifier-border)); + color: var(--text, var(--text-normal)); + font-family: var(--font, inherit); &:hover { - color: var(--text-accent); - /* background-color: var(--background-modifier-hover); */ - border-color: var(--text-accent); + color: var(--accent, var(--text-accent)); + border-color: var(--hover-border, var(--accent, var(--text-accent))); } + + &.menu-internal-link { + color: var(--internal-text, var(--text, var(--text-normal))); + border-color: var(--internal-border, var(--border, var(--background-modifier-border))); + background-color: var(--internal-bg, transparent); + font-family: var(--internal-font, var(--font, inherit)); + + &:hover { + color: var(--internal-accent, var(--accent, var(--text-accent))); + border-color: var(--internal-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); + background-color: var(--internal-hover-bg, var(--hover-bg, transparent)); + } + } + + &.menu-external-link { + color: var(--external-text, var(--text, var(--text-normal))); + border-color: var(--external-border, var(--border, var(--background-modifier-border))); + background-color: var(--external-bg, transparent); + font-family: var(--external-font, var(--font, inherit)); + + &:hover { + color: var(--external-accent, var(--accent, var(--text-accent))); + border-color: var(--external-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); + background-color: var(--external-hover-bg, var(--hover-bg, transparent)); + } + } + + &.menu-file-link { + color: var(--file-text, var(--text, var(--text-normal))); + border-color: var(--file-border, var(--border, var(--background-modifier-border))); + background-color: var(--file-bg, transparent); + font-family: var(--file-font, var(--font, inherit)); + + &:hover { + color: var(--file-accent, var(--accent, var(--text-accent))); + border-color: var(--file-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); + background-color: var(--file-hover-bg, var(--hover-bg, transparent)); + } + } + } + + &.wide a { + flex-grow: 1; + flex-basis: 150px; + text-align: center; } } @@ -52,13 +109,7 @@ .menu-external-link .menu-internal-link */ -.menu-container { - transition: 125ms; - a { - transition: 125ms; - } -} /* Minimal style variant */ .menu-container.minimal { @@ -66,7 +117,8 @@ gap: 0.2em; flex-wrap: wrap; transition: 125ms; - + background-color: var(--bg, transparent); + font-family: var(--font, inherit); a { padding: 0.4em 0.8em; @@ -75,34 +127,42 @@ transition: color 0.15s ease; font-size: 0.9em; font-weight: normal; + color: var(--text, var(--text-normal)); + font-family: var(--font, inherit); &:hover { - background-color: var(--background-secondary); - color: var(--text-accent); + background-color: var(--hover-bg, var(--background-secondary)); + color: var(--accent, var(--text-accent)); } - } - a.menu-internal-link { - color: var(--text-accent); + &.menu-internal-link { + color: var(--internal-text, var(--internal, var(--text-accent))); + font-family: var(--internal-font, var(--font, inherit)); - &:hover { - color: var(--text-accent-hover); + &:hover { + color: var(--internal-accent, var(--internal-hover, var(--text-accent-hover))); + background-color: var(--internal-hover-bg, var(--hover-bg, var(--background-secondary))); + } } - } - a.menu-external-link { - color: var(--text-faint); + &.menu-external-link { + color: var(--external-text, var(--external, var(--text-faint))); + font-family: var(--external-font, var(--font, inherit)); - &:hover { - color: var(--text-normal); + &:hover { + color: var(--external-accent, var(--external-hover, var(--text-normal))); + background-color: var(--external-hover-bg, var(--hover-bg, var(--background-secondary))); + } } - } - a.menu-file-link { - color: var(--text-faint); + &.menu-file-link { + color: var(--file-text, var(--file, var(--text-faint))); + font-family: var(--file-font, var(--font, inherit)); - &:hover { - color: var(--text-normal); + &:hover { + color: var(--file-accent, var(--file-hover, var(--text-normal))); + background-color: var(--file-hover-bg, var(--hover-bg, var(--background-secondary))); + } } } } @@ -112,7 +172,8 @@ display: flex; gap: 0.2em; flex-wrap: wrap; - + background-color: var(--bg, transparent); + font-family: var(--font, inherit); a { padding: 0.4em 0.8em; @@ -121,18 +182,56 @@ transition: color 0.15s ease; font-size: 0.9em; font-weight: normal; - background-color: var(--background-secondary); - border: 1px solid var(--background-secondary); - color: var(--text-faint); - + background-color: var(--item-bg, var(--background-secondary)); + border: 1px solid var(--border, var(--background-secondary)); + color: var(--text, var(--text-faint)); + font-family: var(--font, inherit); &:hover { - background-color: var(--background-secondary); - color: var(--text-accentl); - border: 1px solid var(--text-accent); + background-color: var(--hover-bg, var(--background-secondary)); + color: var(--accent, var(--text-accent)); + border: 1px solid var(--hover-border, var(--accent, var(--text-accent))); + } + + &.menu-internal-link { + color: var(--internal-text, var(--text, var(--text-faint))); + border-color: var(--internal-border, var(--border, var(--background-secondary))); + background-color: var(--internal-bg, var(--item-bg, var(--background-secondary))); + font-family: var(--internal-font, var(--font, inherit)); + + &:hover { + color: var(--internal-accent, var(--accent, var(--text-accent))); + border-color: var(--internal-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); + background-color: var(--internal-hover-bg, var(--hover-bg, var(--background-secondary))); + } + } + + &.menu-external-link { + color: var(--external-text, var(--text, var(--text-faint))); + border-color: var(--external-border, var(--border, var(--background-secondary))); + background-color: var(--external-bg, var(--item-bg, var(--background-secondary))); + font-family: var(--external-font, var(--font, inherit)); + + &:hover { + color: var(--external-accent, var(--accent, var(--text-accent))); + border-color: var(--external-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); + background-color: var(--external-hover-bg, var(--hover-bg, var(--background-secondary))); + } + } + + &.menu-file-link { + color: var(--file-text, var(--text, var(--text-faint))); + border-color: var(--file-border, var(--border, var(--background-secondary))); + background-color: var(--file-bg, var(--item-bg, var(--background-secondary))); + font-family: var(--file-font, var(--font, inherit)); + + &:hover { + color: var(--file-accent, var(--accent, var(--text-accent))); + border-color: var(--file-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); + background-color: var(--file-hover-bg, var(--hover-bg, var(--background-secondary))); + } } } - } @@ -141,38 +240,80 @@ gap: 0.5em; flex-wrap: wrap; border-radius: 8px; - background-color: var(--background-primary); + background-color: var(--bg, var(--background-primary)); + font-family: var(--font, 'Space Grotesk', Inter, sans-serif); a { - font-family: 'Space Grotesk', Inter, sans-serif; - font-size: var(--fs, 1.1rem); + font-family: var(--font, 'Space Grotesk', Inter, sans-serif); + font-size: var(--fs, 0.9rem); white-space: nowrap; - color: var(--cyan-500, darkcyan); + color: var(--text, var(--cyan-500, darkcyan)); text-align: center; text-decoration: none; - text-align: center; - - border: 1px solid var(--cyan-500, darkcyan); + border: 1px solid var(--border, var(--cyan-500, darkcyan)); border-radius: 3px; padding-block: var(--size-2, 0.5em); padding-inline: var(--size-3, 2em); transition: 250ms; &:hover { - color: var(--text-accent); - /* background-color: var(--background-modifier-hover); */ - border: 1px solid var(--orange-500, darkorange); - color: var(--orange-500, darkorange); + color: var(--accent, var(--orange-500, darkorange)); + border: 1px solid var(--hover-border, var(--accent, var(--orange-500, darkorange))); } + + &.menu-internal-link { + color: var(--internal-text, var(--text, var(--cyan-500, darkcyan))); + border-color: var(--internal-border, var(--border, var(--cyan-500, darkcyan))); + background-color: var(--internal-bg, transparent); + font-family: var(--internal-font, var(--font, 'Space Grotesk', Inter, sans-serif)); + + &:hover { + color: var(--internal-accent, var(--accent, var(--orange-500, darkorange))); + border-color: var(--internal-hover-border, var(--hover-border, var(--accent, var(--orange-500, darkorange)))); + background-color: var(--internal-hover-bg, var(--hover-bg, transparent)); + } + } + + &.menu-external-link { + color: var(--external-text, var(--text, var(--cyan-500, darkcyan))); + border-color: var(--external-border, var(--border, var(--cyan-500, darkcyan))); + background-color: var(--external-bg, transparent); + font-family: var(--external-font, var(--font, 'Space Grotesk', Inter, sans-serif)); + + &:hover { + color: var(--external-accent, var(--accent, var(--orange-500, darkorange))); + border-color: var(--external-hover-border, var(--hover-border, var(--accent, var(--orange-500, darkorange)))); + background-color: var(--external-hover-bg, var(--hover-bg, transparent)); + } + } + + &.menu-file-link { + color: var(--file-text, var(--text, var(--cyan-500, darkcyan))); + border-color: var(--file-border, var(--border, var(--cyan-500, darkcyan))); + background-color: var(--file-bg, transparent); + font-family: var(--file-font, var(--font, 'Space Grotesk', Inter, sans-serif)); + + &:hover { + color: var(--file-accent, var(--accent, var(--orange-500, darkorange))); + border-color: var(--file-hover-border, var(--hover-border, var(--accent, var(--orange-500, darkorange)))); + background-color: var(--file-hover-bg, var(--hover-bg, transparent)); + } + } + } + + &.wide a { + flex-grow: 1; + flex-basis: 150px; + text-align: center; } } .menu-container.aether { display: grid; - grid-template-columns: - repeat(auto-fit, minmax(150px, 1fr)); - + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 0.5em; + background-color: var(--bg, transparent); + font-family: var(--font, inherit); a { text-align: center; @@ -180,7 +321,53 @@ font-weight: 600; padding: 0.5em 1em; border-radius: 8px; + color: var(--text, var(--text-normal)); + border: 1px solid var(--border, var(--background-modifier-border)); + font-family: var(--font, inherit); - border: 1px solid var(--background-modifier-border); + &:hover { + color: var(--accent, var(--text-accent)); + border-color: var(--hover-border, var(--accent, var(--text-accent))); + background-color: var(--hover-bg, var(--background-modifier-hover)); + } + + &.menu-internal-link { + color: var(--internal-text, var(--text, var(--text-normal))); + border-color: var(--internal-border, var(--border, var(--background-modifier-border))); + background-color: var(--internal-bg, transparent); + font-family: var(--internal-font, var(--font, inherit)); + + &:hover { + color: var(--internal-accent, var(--accent, var(--text-accent))); + border-color: var(--internal-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); + background-color: var(--internal-hover-bg, var(--hover-bg, var(--background-modifier-hover))); + } + } + + &.menu-external-link { + color: var(--external-text, var(--text, var(--text-normal))); + border-color: var(--external-border, var(--border, var(--background-modifier-border))); + background-color: var(--external-bg, transparent); + font-family: var(--external-font, var(--font, inherit)); + + &:hover { + color: var(--external-accent, var(--accent, var(--text-accent))); + border-color: var(--external-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); + background-color: var(--external-hover-bg, var(--hover-bg, var(--background-modifier-hover))); + } + } + + &.menu-file-link { + color: var(--file-text, var(--text, var(--text-normal))); + border-color: var(--file-border, var(--border, var(--background-modifier-border))); + background-color: var(--file-bg, transparent); + font-family: var(--file-font, var(--font, inherit)); + + &:hover { + color: var(--file-accent, var(--accent, var(--text-accent))); + border-color: var(--file-hover-border, var(--hover-border, var(--accent, var(--text-accent)))); + background-color: var(--file-hover-bg, var(--hover-bg, var(--background-modifier-hover))); + } + } } } \ No newline at end of file