opus-oss_obsidian-opus/theme.css
2026-07-20 10:12:28 -05:00

3029 lines
110 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* =============================================================
OPUS — an Obsidian theme · The Great Work
six alchemical palette flavors on one engine (Prima Materia + the opus stages)
cold slate ground · monospace headers · tempered-amber accent
teal = confirmed · clay = warning · v2.6.5
============================================================= */
/* @settings
name: Opus
id: opus
settings:
-
id: opus-head-palette
title: Palette
type: heading
level: 2
collapsed: false
-
id: opus-flavor
title: Flavor
description: The palette family. Prima Materia (cool slate / warm latte) is the default; Nigredo is stark true-black; Citrinitas is warm and cozy (Gruvbox-spirited, one accent). Each is a full, contrast-audited palette in light and dark.
type: class-select
allowEmpty: false
default: opus-flavor-prima
options:
-
label: 'Prima Materia — the first matter (default)'
value: opus-flavor-prima
-
label: 'Nigredo — the blackening'
value: opus-flavor-nigredo
-
label: 'Citrinitas — the yellowing'
value: opus-flavor-citrinitas
-
id: opus-head-accent
title: Accent
type: heading
level: 2
collapsed: false
-
id: opus-accent
title: Accent color
description: The tempered-amber accent — drives headings, links, callouts, tabs, and the active-file rail. Teal (good) and clay (warning) stay fixed.
type: variable-color
format: hex
opacity: false
default: '#d98a3d'
-
id: opus-head-type
title: Typography
type: heading
level: 2
collapsed: true
-
id: opus-sans-headings
title: Sans-serif headings
description: Off = monospace headings (the Opus signature). On = the body sans face.
type: class-toggle
default: false
-
id: opus-font-text
title: Body font
description: Reading-text font family. Leave blank for the theme default.
type: variable-text
default: ''
quotes: false
-
id: opus-font-mono
title: Monospace font
description: Font for code and headings. Leave blank for the theme default.
type: variable-text
default: ''
quotes: false
-
id: opus-head-features
title: Features
type: heading
level: 2
collapsed: true
-
id: opus-no-nav-rail
title: Hide active-file rail
description: Removes the amber rail on the selected file in the sidebar.
type: class-toggle
default: false
-
id: opus-no-tab-rail
title: Hide active-tab rail
description: Removes the amber rail on the active editor tab.
type: class-toggle
default: false
-
id: opus-flat-code
title: Flat code blocks
description: Removes the shadow and amber top-sheen from code blocks.
type: class-toggle
default: false
-
id: opus-light-terminal
title: Dark terminal code in Light mode
description: Keep code blocks dark even in light mode — the original signature look.
type: class-toggle
default: false
-
id: opus-head-layout
title: Layout & motion
type: heading
level: 2
collapsed: true
-
title: Heading rhythm
id: opus-heading-rhythm
description: Vertical spacing unit between headings and body. Lower = denser, tighter documents. Default matches the v2.2 scale.
type: variable-number-slider
default: 1.6
min: 1
max: 2.4
step: 0.05
format: rem
-
id: opus-compact
title: Compact / density mode
description: Condensed operator profile — tightens body and heading line-height, list spacing and indent, callout padding, native and Dataview table cell padding, tab height, and heading margins. Off by default; changes nothing until enabled.
type: class-toggle
default: false
-
id: opus-no-motion
title: Disable motion
description: Off = a single 130ms ease-out transition on the rails, hover, checkbox, callout fold, and link states (the v2.2 default). On = instant, no transitions. Reduced-motion is always respected regardless of this toggle.
type: class-toggle
default: false
-
id: opus-head-measure
title: Measure
type: heading
level: 2
collapsed: true
-
id: opus-measure-width
title: Readable line width
description: Max width of the reading column when Obsidian's "Readable line length" is on (Settings → Editor). Default 44rem matches the current column.
type: variable-number-slider
default: 44
min: 30
max: 60
step: 1
format: rem
-
id: opus-measure-font-size
title: Editor font size
description: Reading/editing text size in pixels.
type: variable-number-slider
default: 16
min: 13
max: 20
step: 1
format: px
-
id: opus-measure-line-height
title: Line height
description: Body line spacing. Denser at 1.4, airier at 1.9.
type: variable-number-slider
default: 1.62
min: 1.4
max: 1.9
step: 0.02
-
id: opus-table-states-heading
title: Table row states
type: heading
level: 2
collapsed: true
-
id: opus-table-glow
title: Active-row glow
description: Accent tint intensity on the active (cursor) table row. 8% is the shipping default; 0 disables the accent glow (neutral hover + leading rail remain).
type: variable-number-slider
default: 8
min: 0
max: 16
step: 1
format: '%'
-
id: opus-head-oled
title: OLED and contrast
type: heading
level: 3
collapsed: false
-
id: opus-oled
title: OLED true black
description: Dark mode only. Forces the background to pure #000 and drops panels to a flavor-tinted near-black across every flavor. Saves power and kills glow halos on OLED displays.
type: class-toggle
default: false
-
id: opus-contrast
title: Higher contrast
description: Dark mode only. Brightens ink and strengthens rules for low-light reading. Pairs well with OLED true black.
type: class-toggle
default: false
-
id: opus-head-mobile
title: Mobile
description: Applies on phone and tablet only. Desktop is unaffected.
type: heading
level: 2
collapsed: true
-
id: opus-mobile-font-scale
title: Mobile text scale
description: Multiplies the reading/editing text size on mobile only. 1.0 = the Measure size as-is; nudge up for phone legibility.
type: variable-number-slider
default: 1
min: 0.85
max: 1.3
step: 0.05
-
id: opus-mobile-flat-toolbar
title: Dock keyboard toolbar
description: Off = the keyboard formatting toolbar floats as a rounded panel inset from the edges (default). On = it docks flush to the bottom edge.
type: class-toggle
default: false
-
id: opus-mobile-lean-header
title: Lean mobile note header
description: Trims the per-note top header height on phones to reclaim vertical space. Off by default.
type: class-toggle
default: false
-
id: opus-canvas-vivid
title: Vivid canvas colors
description: Canvas only. Off = the muted, operator-grade 6-color set that harmonizes with the slate world (default). On = punchier versions of the three fixed hues (sage, slate-blue, plum) for higher separation on dense diagrams. The three semantic slots (clay/amber/teal) follow the flavor either way.
type: class-toggle
default: false
*/
/* -------------------------------------------------------------
0. TYPOGRAPHY + GLOBAL TOKENS (theme-local, prefixed --ts-)
------------------------------------------------------------- */
body {
--font-text-theme:
-apple-system, "SF Pro Text", "Inter", system-ui, "Segoe UI", Roboto, sans-serif;
--font-monospace-theme:
"SF Mono", "JetBrains Mono", "Menlo", ui-monospace, "Consolas", monospace;
--font-interface-theme: var(--font-text-theme);
--ts-mono: var(--opus-font-mono, "SF Mono", "JetBrains Mono", "Menlo", ui-monospace, monospace);
/* NB: --opus-accent is intentionally NOT defaulted here. The Style
Settings accent picker sets it; when untouched, --ts-amber falls
through to each flavor's --flavor-accent (see .theme-dark / .theme-light).
A default at this level would freeze every flavor to the Prima Materia accent. */
/* type scale */
--font-text-size: 16px;
--line-height-normal: 1.62;
--line-height-tight: 1.15;
--p-spacing: 1rem;
--h1-size: 2rem;
--h2-size: 1.36rem;
--h3-size: 1.1rem;
--h4-size: 0.98rem;
--h5-size: 0.88rem;
--h6-size: 0.74rem;
--h1-weight: 600;
--h2-weight: 600;
--h3-weight: 600;
--h4-weight: 600;
--h5-weight: 600;
--h6-weight: 600;
--bold-weight: 650;
--heading-spacing: 1.8em;
--radius-s: 6px;
--radius-m: 9px;
--radius-l: 12px;
--code-size: 0.86em;
--code-radius: 10px;
--tab-radius: 8px;
--nav-item-size: 0.95em;
--nav-item-weight: 450;
}
/* -------------------------------------------------------------
1. DARK — Prima Materia (base palette)
------------------------------------------------------------- */
.theme-dark {
/* accent HSL drives Obsidian's interactive system (amber #d98a3d) */
--accent-h: 31;
--accent-s: 66%;
--accent-l: 55%;
--ts-ground: #0c1113;
--ts-panel: #12181a;
--ts-panel-2: #171f21;
--ts-raised: #1b2426;
--ts-ink: #dbe4e0;
--ts-ink-soft: #a3b0ab;
--ts-ink-faint: #74827d;
--ts-line: #212b2c;
--ts-line-strong:#2c3839;
--flavor-accent: #d98a3d;
--ts-amber: var(--opus-accent, var(--flavor-accent));
--ts-amber-hi: color-mix(in srgb, var(--ts-amber), #ffffff 16%);
--ts-teal: #4bbfa6;
--ts-clay: #d0655c;
--ts-code-bg: #080f0e; /* deeper than the ground so the well reads */
--ts-code-ink: #cfe0da;
--ts-code-line: #2b3d38; /* the edge does the work — two near-black fills can't separate on their own */
--ts-selection: color-mix(in srgb, var(--ts-amber) 22%, transparent);
--ts-highlight: color-mix(in srgb, var(--ts-amber) 32%, transparent);
--ts-shadow: 0 18px 44px -24px rgba(0,0,0,0.85);
}
/* -------------------------------------------------------------
2. LIGHT — Prima Materia (base palette)
------------------------------------------------------------- */
.theme-light {
--accent-h: 28;
--accent-s: 71%;
--accent-l: 40%;
/* warm latte light — cream ground + warm ink; teal/clay stay cool for
contrast, amber accent unchanged. Warmth is in the hue, not lowered
contrast (ink #2a251d on #f4eee1 is ~12:1), so it reads easier without
going dim. */
--ts-ground: #f4eee1;
--ts-panel: #fbf6ec;
--ts-panel-2: #ece3d2;
--ts-raised: #fdfbf5;
--ts-ink: #2a251d;
--ts-ink-soft: #5c5445;
--ts-ink-faint: #877d69;
--ts-line: #e3dac8;
--ts-line-strong:#cdc1a9;
--flavor-accent: #b0631f;
--ts-amber: var(--opus-accent, var(--flavor-accent));
--ts-amber-hi: color-mix(in srgb, var(--ts-amber), #000000 26%);
--ts-teal: #14776a;
--ts-clay: #b23e37;
--ts-code-bg: #efe6d3; /* warm cream code panel in light (matches §20 override) */
--ts-code-ink: #322e27;
--ts-code-line: #e0d6c2;
--ts-selection: color-mix(in srgb, var(--ts-amber) 18%, transparent);
--ts-highlight: color-mix(in srgb, var(--ts-amber) 26%, transparent);
--ts-shadow: 0 12px 34px -22px rgba(60,45,25,0.32);
}
/* -------------------------------------------------------------
3. MAP THEME TOKENS -> OBSIDIAN VARIABLES (both modes)
------------------------------------------------------------- */
body {
--font-text: var(--opus-font-text, var(--font-text-theme));
--font-monospace: var(--opus-font-mono, var(--font-monospace-theme));
--font-interface: var(--opus-font-text, var(--font-interface-theme));
--background-primary: var(--ts-ground);
--background-primary-alt: var(--ts-panel);
--background-secondary: var(--ts-panel);
--background-secondary-alt: var(--ts-panel-2);
--background-modifier-border: var(--ts-line);
--background-modifier-border-hover: var(--ts-line-strong);
--background-modifier-border-focus: var(--ts-amber);
--background-modifier-hover: var(--ts-panel-2);
--background-modifier-active-hover: var(--ts-raised);
--background-modifier-form-field: var(--ts-panel-2);
--background-modifier-success: var(--ts-teal);
--background-modifier-error: var(--ts-clay);
--background-modifier-error-rgb: 208, 101, 92;
--text-normal: var(--ts-ink);
--text-muted: var(--ts-ink-soft);
--text-faint: var(--ts-ink-faint);
--text-on-accent: var(--ts-ground); /* auto-flips: dark text on light amber, light text on dark amber */
--text-accent: var(--ts-amber);
--text-accent-hover: var(--ts-amber-hi);
--text-error: var(--ts-clay);
--text-success: var(--ts-teal);
--text-selection: var(--ts-selection);
--text-highlight-bg: var(--ts-highlight);
--interactive-normal: var(--ts-panel-2);
--interactive-hover: var(--ts-raised);
--interactive-accent: var(--ts-amber);
--interactive-accent-hover: var(--ts-amber-hi);
--interactive-accent-hsl: var(--accent-h), var(--accent-s), var(--accent-l);
--divider-color: var(--ts-line);
--scrollbar-bg: transparent;
--scrollbar-thumb-bg: var(--ts-line-strong);
--scrollbar-active-thumb-bg: var(--ts-amber);
--hr-color: var(--ts-line);
--hr-thickness: 1px;
--caret-color: var(--ts-amber);
}
/* -------------------------------------------------------------
4. APP CHROME — titlebar, ribbon, tabs, sidebars, status bar
------------------------------------------------------------- */
body {
--titlebar-background: var(--ts-ground);
--titlebar-background-focused: var(--ts-ground);
--titlebar-text-color: var(--ts-ink-soft);
--titlebar-border-width: 0;
--ribbon-background: var(--ts-panel);
--ribbon-background-collapsed: var(--ts-ground);
--tab-background-active: var(--ts-ground);
--tab-text-color-focused-active-current: var(--ts-ink);
--tab-outline-width: 0;
--status-bar-background: var(--ts-panel);
--status-bar-border-color: var(--ts-line);
--status-bar-text-color: var(--ts-ink-faint);
--sidebar-tab-text-color: var(--ts-ink-soft);
--titlebar-background: var(--ts-ground);
}
/* left/right sidebars slightly recessed from the editor */
.workspace-split.mod-left-split,
.workspace-split.mod-right-split {
background-color: var(--ts-panel);
}
.workspace-tabs .workspace-leaf {
background-color: var(--background-primary);
}
/* all tab titles: mono, compact */
.workspace-tab-header-inner-title {
font-family: var(--ts-mono);
font-size: 0.82em;
}
/* --- TABS — flat, fully-square amber active tab, no corner artifacts ---
The "black squares" were Obsidian's --tab-curve flare: ::before/::after
quarter-circles painted in --tab-background-active (left at the default
dark). Fix = zero the curve/radii, point --tab-background-active at the
amber, and display:none the flares. (Researched against Obsidian's CSS
variables reference for Tabs.) */
/* 1. neutralize the base tab geometry on the strip */
.workspace-tab-header-container,
.workspace-split .workspace-tab-header-container-inner,
.workspace-tabs {
--tab-curve: 0px;
--tab-radius: 0px;
--tab-radius-active: 0px;
--tab-outline-width: 0px;
--tab-background-active: color-mix(in srgb, var(--ts-amber) 16%, var(--ts-ground));
}
/* 2. kill the concave-corner flare pseudo-elements outright */
.workspace-tab-header::before,
.workspace-tab-header::after,
.workspace-tab-header-inner::before,
.workspace-tab-header-inner::after {
display: none !important;
}
/* 3. inactive tabs — recessed, square */
.workspace-tab-header:not(.is-active),
.workspace-tab-header:not(.is-active) .workspace-tab-header-inner {
background-color: var(--ts-panel-2);
border-radius: 0;
}
.workspace-tab-header:not(.is-active) .workspace-tab-header-inner-title { color: var(--ts-ink-faint); }
.workspace-tab-header:not(.is-active) .workspace-tab-header-inner-icon,
.workspace-tab-header:not(.is-active) .workspace-tab-header-inner-close-button { color: var(--ts-ink-faint); }
.workspace-tab-header:not(.is-active):hover .workspace-tab-header-inner-title { color: var(--ts-ink-soft); }
/* 4. active tab — flat, fully square, cleanly amber-filled */
.workspace-tab-header.is-active,
.workspace-tab-header.is-active .workspace-tab-header-inner {
background-color: color-mix(in srgb, var(--ts-amber) 16%, var(--ts-ground));
border-radius: 0 !important;
box-shadow: none;
}
/* 2px inset amber rail across the top of the active tab */
.workspace-tab-header.is-active .workspace-tab-header-inner {
box-shadow: inset 0 2px 0 0 var(--ts-amber);
}
.workspace-tab-header.is-active .workspace-tab-header-inner-title {
color: var(--ts-ink);
font-weight: 600;
font-family: var(--ts-mono);
letter-spacing: 0.01em;
}
.workspace-tab-header.is-active .workspace-tab-header-inner-icon { color: var(--ts-amber); }
/* status bar reads like a console footer */
.status-bar {
font-family: var(--ts-mono);
font-size: 0.72em;
letter-spacing: 0.04em;
border-top: 1px solid var(--ts-line);
}
/* -------------------------------------------------------------
5. FILE EXPLORER / NAV
------------------------------------------------------------- */
body {
--nav-item-color: var(--ts-ink-soft);
--nav-item-color-hover: var(--ts-ink);
--nav-item-color-active: var(--ts-ink);
--nav-item-background-hover: var(--ts-panel-2);
--nav-item-background-active: var(--ts-panel-2);
--nav-indentation-guide-color: var(--ts-line);
--nav-collapse-icon-color: var(--ts-ink-faint);
}
.nav-file-title.is-active,
.nav-folder-title.is-active {
box-shadow: inset 2px 0 0 0 var(--ts-amber);
}
/* light mode: the amber rail reads faint on a bright sidebar — darken to
amber-hi, thicken to 3px, and tint the active row so it's easy to track. */
.theme-light .nav-file-title.is-active,
.theme-light .nav-folder-title.is-active {
box-shadow: inset 3px 0 0 0 var(--ts-amber-hi);
background-color: color-mix(in srgb, var(--ts-amber) 9%, var(--ts-panel-2));
}
.nav-folder-title-content,
.nav-file-title-content {
font-size: 0.9em;
}
/* -------------------------------------------------------------
6. HEADINGS — monospace, tempered
------------------------------------------------------------- */
body {
--h1-color: var(--ts-ink);
--h2-color: var(--ts-ink);
--h3-color: var(--ts-amber);
--h4-color: var(--ts-ink-soft);
--h5-color: var(--ts-ink-soft);
--h6-color: var(--ts-amber);
--heading-formatting: var(--ts-ink-faint);
}
.markdown-rendered :is(h1, h2, h3, h4, h5, h6),
.cm-s-obsidian .HyperMD-header,
.inline-title {
font-family: var(--ts-mono);
letter-spacing: -0.01em;
line-height: var(--line-height-tight);
}
.inline-title { font-weight: 600; color: var(--ts-ink); }
.markdown-rendered h1, .cm-s-obsidian .HyperMD-header-1 { letter-spacing: -0.015em; }
.markdown-rendered h1 {
border-bottom: 1px solid var(--ts-line);
padding-bottom: 0.3em;
}
.markdown-rendered h3, .cm-s-obsidian .HyperMD-header-3 { color: var(--ts-amber); }
/* h6 becomes a letter-spaced eyebrow label (write ###### PHASE 02) */
.markdown-rendered h6, .cm-s-obsidian .HyperMD-header-6 {
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--ts-amber);
}
/* -------------------------------------------------------------
7. BODY TEXT, LINKS, MARKS
------------------------------------------------------------- */
.markdown-rendered strong { color: var(--ts-ink); font-weight: var(--bold-weight); }
.markdown-rendered em { color: var(--ts-ink-soft); }
.markdown-rendered a,
.cm-s-obsidian span.cm-link,
.cm-s-obsidian span.cm-url,
.cm-s-obsidian span.cm-hmd-internal-link {
color: var(--ts-amber);
text-decoration-thickness: 1px;
text-underline-offset: 2px;
}
.markdown-rendered a:hover { color: var(--ts-amber-hi); }
.markdown-rendered mark {
background: var(--ts-highlight);
color: var(--ts-ink);
border-radius: 3px;
padding: 0 0.15em;
}
/* -------------------------------------------------------------
8. CODE — inline + block + syntax tokens
------------------------------------------------------------- */
body {
--code-background: var(--ts-code-bg);
--code-normal: var(--ts-code-ink);
--code-comment: var(--ts-ink-faint);
--code-function: var(--ts-amber-hi);
--code-keyword: var(--ts-clay);
--code-property: var(--ts-amber);
--code-tag: var(--ts-clay);
--code-string: var(--ts-teal);
--code-value: var(--ts-teal);
--code-operator: var(--ts-ink-soft);
--code-punctuation: var(--ts-ink-faint);
--code-important: var(--ts-amber-hi);
}
.markdown-rendered code { font-family: var(--font-monospace); font-size: var(--code-size); }
.markdown-rendered :not(pre) > code {
background: var(--ts-panel-2);
color: var(--ts-ink);
border: 1px solid var(--ts-line);
border-radius: 4px;
padding: 0.08em 0.38em;
}
.markdown-rendered pre {
background: var(--ts-code-bg);
border: 1px solid var(--ts-code-line);
border-radius: var(--code-radius);
box-shadow: var(--ts-shadow);
}
.markdown-rendered pre > code { color: var(--ts-code-ink); line-height: 1.7; }
.markdown-rendered pre > code::-webkit-scrollbar { height: 8px; }
/* editor code (live preview / source) matches */
.cm-s-obsidian .HyperMD-codeblock,
.cm-s-obsidian .cm-inline-code {
font-family: var(--font-monospace);
}
/* ...and matches the READING-mode panel. CodeMirror renders a block as a
stack of per-line divs, not one element, so the panel is assembled:
sides on every line, top/bottom + radius on the fence lines, and the
amber accent on the opening fence so edit mode reads like read mode. */
.markdown-source-view.mod-cm6 .HyperMD-codeblock {
background: var(--ts-code-bg);
border-left: 1px solid var(--ts-code-line);
border-right: 1px solid var(--ts-code-line);
}
.markdown-source-view.mod-cm6 .HyperMD-codeblock-begin {
border-top: 1px solid var(--ts-code-line);
border-top-left-radius: var(--code-radius);
border-top-right-radius: var(--code-radius);
box-shadow: inset 0 1px 0 rgba(217, 138, 61, 0.38);
}
.markdown-source-view.mod-cm6 .HyperMD-codeblock-end {
border-bottom: 1px solid var(--ts-code-line);
border-bottom-left-radius: var(--code-radius);
border-bottom-right-radius: var(--code-radius);
}
/* light mode keeps the softer sheen, same as Reading mode */
.theme-light .markdown-source-view.mod-cm6 .HyperMD-codeblock-begin {
box-shadow: inset 0 1px 0 rgba(176, 99, 31, 0.10);
}
.markdown-source-view.mod-cm6 .cm-inline-code {
color: var(--ts-ink);
}
/* -------------------------------------------------------------
9. BLOCKQUOTES + CALLOUTS (the amber-rail "note" system)
------------------------------------------------------------- */
body {
--blockquote-border-thickness: 3px;
--blockquote-border-color: var(--ts-amber);
--blockquote-color: var(--ts-ink-soft);
--callout-border-width: 0px;
--callout-border-opacity: 0;
--callout-padding: 0.85em 1.1em;
--callout-radius: var(--radius-m);
--callout-blend-mode: normal;
}
.markdown-rendered blockquote {
background: var(--ts-panel);
border-left: var(--blockquote-border-thickness) solid var(--ts-amber);
border-radius: 0 var(--radius-m) var(--radius-m) 0;
padding: 0.7em 1.1em;
color: var(--ts-ink-soft);
}
/* callout styling moved to the accent-following surface below (color-mix, themes across flavors) */
/* -------------------------------------------------------------
10. TABLES
------------------------------------------------------------- */
body {
--table-header-color: var(--ts-ink-soft);
--table-border-color: var(--ts-line);
--table-header-background: var(--ts-panel);
--table-row-alt-background: var(--ts-panel);
--table-cell-vertical-alignment: baseline;
}
.markdown-rendered th {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.72rem;
color: var(--ts-ink-soft);
font-weight: 600;
}
.markdown-rendered td, .markdown-rendered th {
border-color: var(--ts-line);
}
/* -------------------------------------------------------------
11. TAGS, CHECKBOXES, LISTS
------------------------------------------------------------- */
body {
--tag-color: var(--ts-amber);
--tag-background: color-mix(in srgb, var(--ts-amber) 12%, transparent);
--tag-background-hover: color-mix(in srgb, var(--ts-amber) 22%, transparent);
--tag-border-color: color-mix(in srgb, var(--ts-amber) 26%, transparent);
--tag-border-width: 1px;
--tag-radius: 999px;
--tag-weight: 500;
--checkbox-color: var(--ts-amber);
--checkbox-color-hover: var(--ts-amber-hi);
--checkbox-border-color: var(--ts-line-strong);
--checkbox-marker-color: var(--ts-ground);
--list-marker-color: var(--ts-ink-faint);
}
.markdown-rendered .tag {
font-family: var(--ts-mono);
font-size: 0.74em;
}
/* -------------------------------------------------------------
12. INPUTS, BUTTONS, PROMPTS, SETTINGS
------------------------------------------------------------- */
input, textarea, select, .dropdown {
font-family: var(--font-interface);
}
button.mod-cta, .mod-cta {
color: var(--text-on-accent);
font-weight: 600;
}
.prompt {
border: 1px solid var(--ts-line);
border-radius: var(--radius-l);
background: var(--ts-panel);
box-shadow: var(--ts-shadow);
}
.suggestion-item.is-selected {
background: var(--ts-panel-2);
box-shadow: inset 2px 0 0 0 var(--ts-amber);
}
/* search result match highlight */
.search-result-file-matched-text {
background: var(--ts-highlight);
color: var(--ts-ink);
}
/* -------------------------------------------------------------
13. GRAPH VIEW
------------------------------------------------------------- */
.theme-dark, .theme-light {
--graph-line: var(--ts-line-strong);
--graph-node: var(--ts-ink-soft);
--graph-text: var(--ts-ink);
--graph-node-unresolved: var(--ts-ink-faint);
--graph-node-focused: var(--ts-amber);
--graph-node-tag: var(--ts-teal);
--graph-node-attachment: var(--ts-clay);
}
/* -------------------------------------------------------------
14. MISC POLISH
------------------------------------------------------------- */
/* thinner, quieter scrollbars */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
background: var(--ts-line-strong);
border-radius: 999px;
border: 3px solid transparent;
background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--ts-amber); background-clip: content-box; }
/* vault name / workspace drag region a touch quieter */
.titlebar-inner { font-family: var(--ts-mono); letter-spacing: 0.03em; }
/* selection */
::selection { background: var(--ts-selection); }
/* reduced motion respect for any theme transitions */
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; }
}
/* -------------------------------------------------------------
15. EDITOR SYNTAX (Live Preview / Source view)
Reading view uses Prism (--code-* vars, section 8). The editor
uses CodeMirror, which ignores those — so its tokens must be
coloured separately, or comments fall back to the CM default.
Scoped to code blocks so markdown source is never touched.
------------------------------------------------------------- */
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-comment {
color: var(--code-comment); /* muted slate, matches the doc */
}
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-string,
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-string-2 { color: var(--code-string); }
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-keyword,
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-atom { color: var(--code-keyword); }
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-number { color: var(--code-string); }
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-def,
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-variable-2,
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-property { color: var(--code-property); }
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-operator { color: var(--code-operator); }
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-meta { color: var(--code-important); }
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-tag { color: var(--code-tag); }
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-builtin,
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-type,
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-qualifier { color: var(--code-function); }
.markdown-source-view.mod-cm6 .HyperMD-codeblock span.cm-variable { color: var(--ts-code-ink); }
/* active line + gutter — quiet console feel */
.markdown-source-view.mod-cm6 .cm-active-line {
background: color-mix(in srgb, var(--ts-panel-2) 38%, transparent);
}
.cm-s-obsidian .cm-gutters { background: transparent; border: none; }
.cm-s-obsidian .cm-lineNumbers .cm-gutterElement {
color: var(--ts-ink-faint);
font-family: var(--ts-mono);
font-size: 0.82em;
}
.cm-s-obsidian .cm-active-line-gutter { color: var(--ts-amber); background: transparent; }
/* -------------------------------------------------------------
16. LINKS — keep the whole family in the amber lane
(kills the default reddish tint on unresolved links)
------------------------------------------------------------- */
body {
--link-color: var(--ts-amber);
--link-color-hover: var(--ts-amber-hi);
--link-external-color: var(--ts-amber);
--link-external-color-hover: var(--ts-amber-hi);
--link-unresolved-color: var(--ts-ink-faint);
--link-unresolved-opacity: 0.8;
--link-unresolved-decoration-color: var(--ts-line-strong);
--link-unresolved-decoration-style: dashed;
--link-decoration: underline;
--link-decoration-hover: underline;
}
/* -------------------------------------------------------------
17. TASKS — completed items recede: fade/dull, NO strikethrough,
NO per-state color-coding (the colored "board" is removed because
clicking a box rewrites the marker to [x]/[ ], so colors can't stick).
------------------------------------------------------------- */
/* checked text dulls back; override Obsidian's default strikethrough */
.markdown-rendered li[data-task="x"],
.markdown-rendered li[data-task="X"],
.markdown-rendered .task-list-item.is-checked,
.markdown-rendered .task-list-item.is-checked .task-list-item-inner,
.HyperMD-task-line[data-task="x"],
.HyperMD-task-line[data-task="X"] {
color: var(--ts-ink-faint);
text-decoration: none !important;
}
.markdown-rendered li[data-task="x"] a,
.markdown-rendered li[data-task="X"] a { color: var(--ts-ink-faint); }
/* nested children of a checked parent stay full strength */
.markdown-rendered li[data-task="x"] > :is(ul, ol),
.markdown-rendered li[data-task="X"] > :is(ul, ol) { color: var(--text-normal); }
/* the checked box recedes too — muted, not a bright accent pop */
.markdown-rendered .task-list-item.is-checked input.task-list-item-checkbox,
.markdown-rendered li[data-task="x"] input.task-list-item-checkbox,
.markdown-rendered li[data-task="X"] input.task-list-item-checkbox,
.HyperMD-task-line[data-task="x"] .task-list-item-checkbox,
.HyperMD-task-line[data-task="X"] .task-list-item-checkbox {
background-color: var(--ts-ink-faint) !important;
border-color: var(--ts-ink-faint) !important;
}
/* light mode: ink-faint fill leaves the checked box nearly invisible on a
bright ground — use the darker ink-soft so "done" still reads clearly. */
.theme-light .markdown-rendered .task-list-item.is-checked input.task-list-item-checkbox,
.theme-light .markdown-rendered li[data-task="x"] input.task-list-item-checkbox,
.theme-light .markdown-rendered li[data-task="X"] input.task-list-item-checkbox,
.theme-light .HyperMD-task-line[data-task="x"] .task-list-item-checkbox,
.theme-light .HyperMD-task-line[data-task="X"] .task-list-item-checkbox {
background-color: var(--ts-ink-soft) !important;
border-color: var(--ts-ink-soft) !important;
}
/* -------------------------------------------------------------
18. SIGNATURE POLISH — amber sheen on code, mono chrome
------------------------------------------------------------- */
.markdown-rendered pre {
box-shadow: var(--ts-shadow), inset 0 1px 0 rgba(217, 138, 61, 0.38);
}
.view-header-title,
.view-header-breadcrumb {
font-family: var(--ts-mono);
letter-spacing: 0.01em;
}
/* -------------------------------------------------------------
19. v1.2.0 — bulletproof comment colour + task-state board
------------------------------------------------------------- */
/* The v1.1.0 rule required a .HyperMD-codeblock wrapper, which only
exists in Live Preview — Source mode never got it, so comments fell
back to the CM default. Target the token directly instead. The
!important guarantees it out-ranks Obsidian's default in every view.
Covers both the legacy (.cm-comment) and Lezer (.tok-comment) class. */
.markdown-source-view.mod-cm6 .cm-comment,
.markdown-source-view.mod-cm6 .tok-comment {
color: var(--code-comment) !important;
font-style: normal;
}
/* Per-state task color-coding was removed in v2.0.1: clicking a checkbox
rewrites its marker to [x]/[ ], so custom-state colors could never
persist. Completed tasks now simply recede (section 17); every other
marker uses the default checkbox fill. */
/* -------------------------------------------------------------
20. v1.3.0 — LIGHT MODE pass ("cold spec sheet")
Dark was the first-class build; this brings daylight up to par.
Code blocks go light + cohesive with syntax re-tuned for a light
ground; accent colours already darken for contrast in .theme-light.
------------------------------------------------------------- */
.theme-light {
/* code panel: warm cream, distinct from the latte page (no cool cast) */
--ts-code-bg: #efe6d3;
--ts-code-ink: #322e27;
--ts-code-line: #e0d6c2;
}
/* the dark-mode amber sheen reads heavy on a light panel — soften it */
.theme-light .markdown-rendered pre {
box-shadow: var(--ts-shadow), inset 0 1px 0 rgba(176, 99, 31, 0.10);
}
/* -------------------------------------------------------------
21. v1.4.0 — Style Settings toggles (opt-in overrides)
The signature look above is unconditional, so the theme is
complete WITHOUT the Style Settings plugin. Each class below
only MODIFIES; the plugin adds it to <body> when toggled on.
------------------------------------------------------------- */
/* Sans-serif headings */
body.opus-sans-headings .markdown-rendered :is(h1, h2, h3, h4, h5, h6),
body.opus-sans-headings .cm-s-obsidian .HyperMD-header,
body.opus-sans-headings .inline-title {
font-family: var(--font-text);
letter-spacing: 0;
}
body.opus-sans-headings .markdown-rendered h6,
body.opus-sans-headings .cm-s-obsidian .HyperMD-header-6 {
text-transform: none;
letter-spacing: 0;
}
/* Hide the active-file rail */
body.opus-no-nav-rail .nav-file-title.is-active,
body.opus-no-nav-rail .nav-folder-title.is-active { box-shadow: none; }
/* Hide the active-tab rail */
body.opus-no-tab-rail .workspace-tab-header.is-active { box-shadow: none; }
/* Flat code blocks (drop shadow + amber sheen) */
body.opus-flat-code .markdown-rendered pre { box-shadow: none; }
/* Keep code blocks dark in Light mode — the original terminal look */
body.opus-light-terminal.theme-light {
--ts-code-bg: #10201d;
--ts-code-ink: #cfe0da;
--ts-code-line: #21332f;
--code-normal: #cfe0da;
--code-comment: #6f817b;
--code-string: #58c3a9;
--code-value: #58c3a9;
--code-keyword: #d0655c;
--code-tag: #d0655c;
--code-property: #d98a3d;
--code-function: #e6a15c;
--code-important: #e6a15c;
--code-operator: var(--ts-ink-soft);
--code-punctuation: var(--ts-ink-faint);
}
/* -------------------------------------------------------------
22. FLAVORS — The Great Work (Style Settings "Flavor" dropdown)
Each flavor overrides the palette tokens only; --ts-amber
re-derives from --flavor-accent, and code syntax / selection /
highlight / callouts all follow automatically. Prima Materia is
the base default (no class). Every palette is WCAG-audited in both
light and dark.
------------------------------------------------------------- */
/* ---- Nigredo ---- */
.theme-dark.opus-flavor-nigredo {
--ts-ground: #05070c;
--ts-panel: #0b0e15;
--ts-panel-2: #12161f;
--ts-raised: #181d28;
--ts-ink: #e7e9ee;
--ts-ink-soft: #9ba2af;
--ts-ink-faint: #6d7583;
--ts-line: #1c222d;
--ts-line-strong: #2c3340;
--flavor-accent: #b8af7a;
--ts-teal: #84a97e;
--ts-clay: #c9695e;
--ts-code-bg: #070910;
--ts-code-ink: #d5d9e0;
--ts-code-line: #1a1f2a;
--accent-h: 51; --accent-s: 30%; --accent-l: 60%;
}
.theme-light.opus-flavor-nigredo {
--ts-ground: #eef0f4;
--ts-panel: #e4e7ee;
--ts-panel-2: #d9dde6;
--ts-raised: #ffffff;
--ts-ink: #1b1f27;
--ts-ink-soft: #4c5361;
--ts-ink-faint: #6e7683;
--ts-line: #d5d9e1;
--ts-line-strong: #c1c6d0;
--flavor-accent: #716728;
--ts-teal: #376c37;
--ts-clay: #a9392d;
--ts-code-bg: #e8eaf0;
--ts-code-ink: #242832;
--ts-code-line: #d2d6de;
--accent-h: 52; --accent-s: 48%; --accent-l: 30%;
}
/* ---- Citrinitas ---- */
.theme-dark.opus-flavor-citrinitas {
/* Gruvbox-spirited warm dark — cozy brown ground + cream ink, medium
contrast, but only amber/teal/clay carry color (no rainbow). */
--ts-ground: #22201b;
--ts-panel: #2a2822;
--ts-panel-2: #37332b;
--ts-raised: #433c32;
--ts-ink: #e9ddc5;
--ts-ink-soft: #bbac94;
--ts-ink-faint: #8a806e;
--ts-line: #39342b;
--ts-line-strong: #4c4637;
--flavor-accent: #d79921;
--ts-teal: #8ec07c;
--ts-clay: #d65d4e;
--ts-code-bg: #1b1912;
--ts-code-ink: #e8d9b5;
--ts-code-line: #322c1c;
--accent-h: 40; --accent-s: 73%; --accent-l: 49%;
}
.theme-light.opus-flavor-citrinitas {
/* Gruvbox-light warm cream — mild, cozy, medium contrast. */
--ts-ground: #f4eed9;
--ts-panel: #f8f4e4;
--ts-panel-2: #ebe2ca;
--ts-raised: #fefcf1;
--ts-ink: #3c3327;
--ts-ink-soft: #6b5a3f;
--ts-ink-faint: #927f5f;
--ts-line: #e5dcc5;
--ts-line-strong: #cec3a6;
--flavor-accent: #b5741a;
--ts-teal: #427b58;
--ts-clay: #af3a2b;
/* warm cream panel, NOT the dark espresso: this flavor's syntax colors
are the light-mode set (dark green/red/gold), so a dark panel put dark
tokens on a dark ground and killed legibility. */
--ts-code-bg: #ece3ca;
--ts-code-ink: #3a3227;
--ts-code-line: #ddd1b0;
--accent-h: 35; --accent-s: 75%; --accent-l: 41%;
}
/* =============================================================
23-28. PLUGIN & CONTENT SURFACES (v2.0.0)
All token-based, so they re-theme across every flavor + mode.
============================================================= */
/* ===== Dataview plugin ===== */
/* ============================================================
DATAVIEW — Opus theme
Operator-grade tables, inline-field pills, task/list queries.
Uses only --ts-* tokens (+ standard Obsidian vars). Light/dark
flip automatically via the tokens.
============================================================ */
/* ---- Table query -------------------------------------------------- */
/* Dataview renders into a table carrying BOTH .dataview and
.table-view-table. Scope to that pair so we don't touch native
Markdown tables. */
.markdown-rendered table.dataview.table-view-table,
.dataview.table-view-table {
border-collapse: separate;
border-spacing: 0;
width: 100%;
font-variant-numeric: tabular-nums;
font-feature-settings: "tnum" 1;
border: 1px solid var(--ts-line);
border-radius: 8px;
overflow: hidden; /* clip zebra/hover corners to the radius */
background: var(--ts-panel);
color: var(--ts-ink);
}
/* Header row — mono uppercase, quiet */
.dataview.table-view-table > thead > tr > th,
.dataview.table-view-table thead th {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.72rem;
font-weight: 600;
text-align: left;
color: var(--ts-ink-soft);
background: var(--ts-panel-2);
padding: 8px 12px;
border-bottom: 1px solid var(--ts-line-strong);
border-right: 1px solid var(--ts-line);
vertical-align: bottom;
white-space: nowrap;
}
.dataview.table-view-table > thead > tr > th:last-child,
.dataview.table-view-table thead th:last-child {
border-right: none;
}
/* Header sort affordance (Dataview adds a caret span) */
.dataview.table-view-table thead th .dataview-header-sort {
color: var(--ts-ink-faint);
}
/* Body cells */
.dataview.table-view-table > tbody > tr > td,
.dataview.table-view-table tbody td {
padding: 7px 12px;
color: var(--ts-ink);
border-top: 1px solid var(--ts-line);
border-right: 1px solid var(--ts-line);
border-bottom: none; /* native-table cell rule leaks border-bottom here; suppress to keep the border-top model */
vertical-align: top;
font-variant-numeric: tabular-nums;
}
.dataview.table-view-table > tbody > tr > td:last-child,
.dataview.table-view-table tbody td:last-child {
border-right: none;
}
/* First body row sits directly under the header divider; drop its
extra top border so we don't double up. */
.dataview.table-view-table > tbody > tr:first-child > td {
border-top: none;
}
/* Zebra striping — even rows on the base panel, odd lifted */
.dataview.table-view-table > tbody > tr:nth-child(odd) > td {
background: var(--ts-panel);
}
.dataview.table-view-table > tbody > tr:nth-child(even) > td {
background: var(--ts-panel-2);
}
/* Row hover — raise the whole row. Kept AFTER the zebra rules and at
matching specificity so it wins on source order. */
.dataview.table-view-table > tbody > tr:hover > td {
background: var(--ts-raised);
color: var(--ts-ink);
}
/* Links inside cells pick up the accent */
.dataview.table-view-table td a.internal-link,
.dataview.table-view-table td a.external-link {
color: var(--ts-amber);
text-decoration-color: var(--ts-line-strong);
}
.dataview.table-view-table td a.internal-link:hover,
.dataview.table-view-table td a.external-link:hover {
color: var(--ts-amber-hi);
}
/* ---- List / bullet query ------------------------------------------ */
.dataview.list-view-ul,
ul.dataview.list-view-ul {
margin-block: 0.4em;
padding-left: 1.3em;
}
.dataview.list-view-ul > li {
color: var(--ts-ink);
margin-block: 0.15em;
}
.dataview.list-view-ul > li::marker {
color: var(--ts-amber);
}
/* ---- Task query --------------------------------------------------- */
/* Dataview task results render as .contains-task-list with .task-list-item.
The `dataview` class sits on the CONTAINER (ul), NOT on each <li>, so the
accent/fade rules must reach the item as a DESCENDANT of .dataview — a
compound `.dataview.task-list-item` (both classes on one element) never
matches. Keep the container quiet and let the accent live on metadata. */
.dataview .task-list-item,
ul.contains-task-list.dataview li.task-list-item {
color: var(--ts-ink);
}
/* Inline metadata chips that Dataview appends to tasks/lists
(e.g. due dates) share the inline-field styling below. */
.dataview .task-list-item-checkbox {
accent-color: var(--ts-amber);
}
/* Completed tasks fade back (no strikethrough) */
.dataview .task-list-item[data-task="x"],
.dataview .task-list-item.is-checked {
color: var(--ts-ink-faint);
text-decoration: none;
}
/* ---- Inline fields (pills) ---------------------------------------- */
/* Rendered as .dataview.inline-field wrapping .inline-field-key +
.inline-field-value. Style as a single bordered pill. */
.dataview.inline-field,
span.dataview.inline-field {
display: inline-flex;
align-items: stretch;
border: 1px solid var(--ts-line);
border-radius: 6px;
overflow: hidden;
background: var(--ts-panel-2);
line-height: 1.4;
vertical-align: baseline;
font-size: 0.85em;
}
.dataview.inline-field > .inline-field-key {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.9em;
font-weight: 600;
color: var(--ts-amber);
background: transparent;
padding: 1px 7px;
border-right: 1px solid var(--ts-line);
}
.dataview.inline-field > .inline-field-value {
color: var(--ts-ink);
background: transparent;
padding: 1px 7px;
font-variant-numeric: tabular-nums;
}
/* Key-less inline value (list-style inline field: [[ ::value ]]).
The .inline-field wrapper already paints border/background/radius, so
the standalone child must stay transparent/borderless to avoid a
double-pill (nested border). Just carry the mono treatment. */
.dataview.inline-field > .inline-field-standalone-value {
font-family: var(--ts-mono);
color: var(--ts-ink);
background: transparent;
border: none;
padding: 1px 7px;
font-variant-numeric: tabular-nums;
}
/* ---- Errors ------------------------------------------------------- */
.dataview.dataview-error,
.dataview-error {
color: var(--ts-clay);
font-family: var(--ts-mono);
font-size: 0.82rem;
background: var(--ts-panel);
border: 1px solid var(--ts-clay);
border-radius: 6px;
padding: 8px 12px;
white-space: pre-wrap;
}
.dataview-error-box {
border-color: var(--ts-clay);
}
/* ---- Misc: small result count / refresh label --------------------- */
.dataview.small-text,
.dataview .dataview-error-message {
color: var(--ts-ink-faint);
font-family: var(--ts-mono);
font-size: 0.72rem;
letter-spacing: 0.04em;
}
/* ===== Kanban plugin ===== */
/* =============================================================
KANBAN PLUGIN — calm operator board
plugin: mgmeyers/obsidian-kanban (stable BEM API .kanban-plugin__*)
Child rules are prefixed with `.kanban-plugin ` to win the
specificity tie against the plugin's own single-class styles.
============================================================= */
/* --- BOARD GROUND ------------------------------------------------ */
.kanban-plugin,
.kanban-plugin .kanban-plugin__board {
background: var(--ts-ground);
}
.kanban-plugin .kanban-plugin__board {
padding: 12px 10px;
}
/* --- LANE / COLUMN ---------------------------------------------- */
.kanban-plugin .kanban-plugin__lane {
background: var(--ts-panel);
border: 1px solid var(--ts-line);
border-radius: var(--radius-m); /* 9px */
box-shadow: none;
}
.kanban-plugin .kanban-plugin__lane-items {
background: transparent;
}
/* collapsed lane keeps the same skin — current plugin puts the
collapse state on the *lane-wrapper* via .collapse-horizontal /
.collapse-vertical, not `.is-collapsed` on the lane */
.kanban-plugin .kanban-plugin__lane-wrapper.collapse-horizontal .kanban-plugin__lane,
.kanban-plugin .kanban-plugin__lane-wrapper.collapse-vertical .kanban-plugin__lane {
background: var(--ts-panel);
border-color: var(--ts-line);
}
/* --- LANE HEADER ------------------------------------------------- */
.kanban-plugin .kanban-plugin__lane-header-wrapper {
border-bottom: 1px solid var(--ts-line);
}
/* mono uppercase lane title */
.kanban-plugin .kanban-plugin__lane-title,
.kanban-plugin .kanban-plugin__lane-title-text,
.kanban-plugin .kanban-plugin__lane-input {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.09em;
font-size: 0.74rem;
font-weight: 600;
color: var(--ts-ink-soft);
}
.kanban-plugin .kanban-plugin__lane-input::placeholder {
color: var(--ts-ink-faint);
}
/* item-count badge — amber, tabular digits */
.kanban-plugin .kanban-plugin__lane-title-count {
font-family: var(--ts-mono);
font-variant-numeric: tabular-nums;
font-size: 0.68rem;
font-weight: 600;
line-height: 1.4;
color: var(--ts-amber);
background: color-mix(in srgb, var(--ts-amber) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--ts-amber) 26%, transparent);
border-radius: 999px;
padding: 1px 7px;
}
/* grip + settings — faint chrome, amber on hover */
.kanban-plugin .kanban-plugin__lane-grip,
.kanban-plugin .kanban-plugin__lane-settings-button {
color: var(--ts-ink-faint);
}
.kanban-plugin .kanban-plugin__lane-settings-button:hover {
color: var(--ts-amber);
background: var(--ts-panel-2);
border-radius: var(--radius-s);
}
/* --- CARDS ------------------------------------------------------- */
.kanban-plugin .kanban-plugin__item {
background: var(--ts-panel-2);
border: 1px solid var(--ts-line);
border-radius: 8px;
color: var(--ts-ink);
box-shadow: none;
transition:
border-color 120ms ease,
box-shadow 120ms ease,
transform 120ms ease,
background 120ms ease;
}
/* the plugin paints var(--background-primary) on the inner title +
content wrappers, which would sit *on top* of the card skin above
and hide it — neutralize so --ts-panel-2 actually shows */
.kanban-plugin .kanban-plugin__item-title-wrapper,
.kanban-plugin .kanban-plugin__item-content-wrapper {
background: transparent;
}
.kanban-plugin .kanban-plugin__item-title,
.kanban-plugin .kanban-plugin__markdown-preview-view {
color: var(--ts-ink);
}
/* Collapse the phantom line inside kanban cards, desktop AND mobile.
The gap is Obsidian's paragraph spacing. Instead of chasing which
element carries the margin (the phone nests the card markdown
differently than desktop, which is why the earlier selector fixes
landed on desktop but missed mobile), kill it at the SOURCE:
--p-spacing is the variable the paragraph margin reads, so zeroing it
on the whole card collapses the gap no matter how the DOM is nested.
The element rules are belt-and-suspenders, and scoped to the whole
item (not just -title) so they catch the mobile container too. */
.kanban-plugin .kanban-plugin__item {
--p-spacing: 0;
}
.kanban-plugin .kanban-plugin__item :is(p, div[class^="el-"], .markdown-preview-view, .markdown-rendered) {
margin-top: 0;
margin-bottom: 0;
}
/* keep real separation between stacked blocks on multi-line cards */
.kanban-plugin .kanban-plugin__item :is(p, div[class^="el-"]) + :is(p, div[class^="el-"]) {
margin-top: 0.4em;
}
/* MOBILE: the surviving gap is NOT paragraph margin — the card title row
is a flexbox, and on the phone touch-target/checkbox sizing inflates
one of its children (the prefix checkbox or the buttons), stretching
the whole row so the text floats in a tall box. Neutralize the size
inflation, scoped to kanban cards on mobile so nothing else moves. */
body.is-mobile .kanban-plugin .kanban-plugin__item-title-wrapper {
padding-block: 4px;
min-height: 0;
}
body.is-mobile .kanban-plugin .kanban-plugin__item-title {
min-height: 0;
line-height: 1.4;
}
body.is-mobile .kanban-plugin .kanban-plugin__item :is(
input[type="checkbox"],
.kanban-plugin__item-prefix-button,
.kanban-plugin__item-postfix-button,
.kanban-plugin__item-prefix-button-wrapper,
.kanban-plugin__item-postfix-button-wrapper) {
min-height: 0;
min-width: 0;
}
/* quiet raise on hover */
.kanban-plugin .kanban-plugin__item:hover {
background: var(--ts-raised);
border-color: var(--ts-line-strong);
box-shadow: var(--ts-shadow);
transform: translateY(-1px);
}
/* dragging clone — the current plugin renders the dragged card inside
.kanban-plugin__drag-container (appended outside the board root, so
this rule is intentionally NOT prefixed with .kanban-plugin) */
.kanban-plugin__drag-container .kanban-plugin__item {
border-color: var(--ts-amber);
box-shadow: var(--ts-shadow);
}
.kanban-plugin__placeholder {
background: color-mix(in srgb, var(--ts-amber) 8%, transparent);
border: 1px dashed color-mix(in srgb, var(--ts-amber) 40%, transparent);
border-radius: 8px;
}
/* --- ADD-CARD BUTTON — subdued --------------------------------- */
.kanban-plugin .kanban-plugin__new-item-button {
color: var(--ts-ink-faint);
background: transparent;
border: 1px solid transparent;
border-radius: var(--radius-s);
box-shadow: none;
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.7rem;
font-weight: 600;
}
.kanban-plugin .kanban-plugin__new-item-button:hover {
color: var(--ts-amber);
background: var(--ts-panel-2);
border-color: var(--ts-line);
}
/* --- ADD-CARD / TITLE INPUT FORM -------------------------------- */
/* the editable field is a native <textarea> inside
.kanban-plugin__item-input-wrapper / .kanban-plugin__item-form —
there is no .kanban-plugin__item-input element */
.kanban-plugin .kanban-plugin__item-input-wrapper textarea,
.kanban-plugin .kanban-plugin__item-form textarea {
background: var(--ts-panel-2);
border: 1px solid var(--ts-line);
border-radius: var(--radius-s);
color: var(--ts-ink);
}
.kanban-plugin .kanban-plugin__item-input-wrapper textarea:focus,
.kanban-plugin .kanban-plugin__item-form textarea:focus {
border-color: var(--ts-amber);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--ts-amber) 40%, transparent);
outline: none;
}
/* ===== Canvas: the old duplicate block was removed in v2.4.3.
All canvas styling now lives in the single consolidated block
below (search ".canvas-wrapper"). The duplicate second-painted
the edge paths, which read as glitchy lines. ===== */
/* ============================================================
GRAPH VIEW (local + global)
Map the graph engine's own vars onto theme tokens so both
canvas-rendered nodes/lines and DOM text follow the flavor.
Obsidian defines these vars per-mode already, so the leaf
selector alone covers light + dark; theme prefixes dropped
to avoid needless specificity.
============================================================ */
.graph-view.color-fill,
.workspace-leaf-content[data-type="graph"],
.workspace-leaf-content[data-type="localgraph"] {
--graph-line: var(--ts-line-strong);
--graph-node: var(--ts-ink-soft);
--graph-text: var(--ts-ink);
--graph-node-focused: var(--ts-amber);
--graph-node-tag: var(--ts-teal);
--graph-node-attachment: var(--ts-clay);
--graph-node-unresolved: var(--ts-ink-faint);
}
/* Graph canvas backdrop */
.workspace-leaf-content[data-type="graph"] .view-content,
.workspace-leaf-content[data-type="localgraph"] .view-content {
background-color: var(--ts-ground);
}
/* Graph controls panel (search / filter / display) */
.graph-controls {
background: var(--ts-panel);
border: 1px solid var(--ts-line);
border-radius: 8px;
color: var(--ts-ink);
}
.graph-controls .graph-control-section-header,
.graph-controls .setting-item-name {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--ts-ink-soft);
font-size: 0.72em;
}
.graph-controls .clickable-icon:hover {
color: var(--ts-amber);
}
/* ===== Properties, native tables, embeds ===== */
/* ============================================================
OPUS — Properties, Native Tables, Embeds
Six-flavor / light+dark safe. Tokens only, no hardcoded hex.
============================================================ */
/* ------------------------------------------------------------
1. FRONTMATTER PROPERTIES (metadata-container)
------------------------------------------------------------ */
/* Panel shell around the whole properties block */
.markdown-source-view.mod-cm6 .metadata-container,
.markdown-reading-view .metadata-container,
.metadata-container {
background: var(--ts-panel);
border: 1px solid var(--ts-line);
border-radius: 8px;
padding: 8px 10px;
margin-bottom: 1em;
}
/* Kill the base theme's own separators so we own the look */
.metadata-container .metadata-content,
.metadata-container .metadata-properties {
border: none;
}
/* "Properties" collapse header, if shown */
.metadata-properties-heading .metadata-properties-title {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.72em;
color: var(--ts-ink-faint);
}
/* One property row */
.metadata-property {
border-radius: 6px;
padding: 1px 2px;
}
.metadata-property:hover {
background: var(--ts-panel-2);
}
/* Property KEY — mono, faint, uppercase-ish operator label */
.metadata-property-key,
.metadata-property-key-input,
.metadata-property .metadata-property-key .metadata-property-key-input {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.74em;
color: var(--ts-ink-faint);
}
.metadata-property-key-input::placeholder {
color: var(--ts-ink-faint);
opacity: 0.6;
}
/* Property VALUES — primary ink */
.metadata-property-value,
.metadata-property-value .metadata-input-longtext,
.metadata-property-value input[type="text"],
.metadata-property-value input[type="number"],
.metadata-property-value input[type="date"],
.metadata-property-value input[type="datetime-local"],
.metadata-property-value .multi-select-input {
color: var(--ts-ink);
}
/* numeric property values align on the decimal */
.metadata-property[data-property-key] .metadata-property-value input[type="number"] {
font-variant-numeric: tabular-nums;
}
/* Property TYPE ICONS — faint, warm to amber on hover */
.metadata-property-icon,
.metadata-property-key .metadata-property-icon svg,
.metadata-property-icon svg {
color: var(--ts-ink-faint);
}
.metadata-property:hover .metadata-property-icon,
.metadata-property:hover .metadata-property-icon svg {
color: var(--ts-ink-soft);
}
.metadata-property.is-active .metadata-property-icon svg {
color: var(--ts-amber);
}
/* Multi-select / tag pills inside values */
.metadata-property-value .multi-select-pill {
background: var(--ts-panel-2);
border: 1px solid var(--ts-line);
border-radius: 6px;
color: var(--ts-ink);
}
.metadata-property-value .multi-select-pill:hover {
border-color: var(--ts-line-strong);
}
/* "Add property" affordance */
.metadata-add-button {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.72em;
color: var(--ts-ink-faint);
}
.metadata-add-button:hover {
color: var(--ts-amber);
}
/* ------------------------------------------------------------
2. NATIVE MARKDOWN TABLES
Reading view + Live Preview. Scope to rendered content so we
don't touch the plugin/settings tables in the app chrome.
NOTE: use border-collapse: separate (not collapse) so the
border-radius + overflow clip actually renders in Obsidian's
Chromium — collapsed borders ignore radius and show square
corners. Interior borders live on the cells (right + bottom);
the table element supplies the outer frame, keeping every
line single-width in both light and dark.
------------------------------------------------------------ */
.markdown-rendered table,
.markdown-source-view.mod-cm6 .cm-table-widget table {
border-collapse: separate;
border-spacing: 0;
border: 1px solid var(--ts-line);
border-radius: 8px;
overflow: hidden; /* clip zebra + header to radius */
font-variant-numeric: tabular-nums;
}
/* Header — mono uppercase operator label */
.markdown-rendered thead th,
.markdown-source-view.mod-cm6 .cm-table-widget thead th {
background: var(--ts-panel-2);
color: var(--ts-ink-soft);
font-family: var(--ts-mono);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.74em;
text-align: left;
border-bottom: 1px solid var(--ts-line-strong);
}
/* Cells — interior borders only (right + bottom) */
.markdown-rendered th,
.markdown-rendered td,
.markdown-source-view.mod-cm6 .cm-table-widget th,
.markdown-source-view.mod-cm6 .cm-table-widget td {
border-right: 1px solid var(--ts-line);
border-bottom: 1px solid var(--ts-line);
padding: 5px 10px;
color: var(--ts-ink);
}
/* Drop the doubled edges that meet the table's own outer border */
.markdown-rendered tr > th:last-child,
.markdown-rendered tr > td:last-child,
.markdown-source-view.mod-cm6 .cm-table-widget tr > th:last-child,
.markdown-source-view.mod-cm6 .cm-table-widget tr > td:last-child {
border-right: none;
}
.markdown-rendered tbody tr:last-child > td,
.markdown-source-view.mod-cm6 .cm-table-widget tbody tr:last-child > td {
border-bottom: none;
}
/* Zebra rows via panel tint */
.markdown-rendered tbody tr:nth-child(even) td,
.markdown-source-view.mod-cm6 .cm-table-widget tbody tr:nth-child(even) td {
background: var(--ts-panel);
}
.markdown-rendered tbody tr:nth-child(odd) td,
.markdown-source-view.mod-cm6 .cm-table-widget tbody tr:nth-child(odd) td {
background: var(--ts-ground);
}
/* Hover row lift */
.markdown-rendered tbody tr:hover td,
.markdown-source-view.mod-cm6 .cm-table-widget tbody tr:hover td {
background: var(--ts-panel-2);
}
/* Honor authored column alignment (| :--- | :---: | ---: |).
Current Obsidian emits inline text-align styles that already
win on specificity; these attribute rules are a compat fallback
for older output that used the align attribute. */
.markdown-rendered td[align="right"],
.markdown-rendered th[align="right"] {
text-align: right;
}
.markdown-rendered td[align="center"],
.markdown-rendered th[align="center"] {
text-align: center;
}
/* ------------------------------------------------------------
3. EMBEDDED / TRANSCLUDED NOTES (.markdown-embed)
------------------------------------------------------------ */
.markdown-embed {
position: relative;
border: 1px solid var(--ts-line);
border-left: 3px solid var(--ts-amber); /* amber rail */
border-radius: 8px;
background: var(--ts-panel);
padding: 6px 14px;
}
/* Neutralize the base theme's inherited embed border/shadow */
.markdown-embed,
.internal-embed .markdown-embed {
box-shadow: none;
}
/* Embed title — mono, soft ink label */
.markdown-embed-title,
.markdown-embed .markdown-embed-title {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.74em;
color: var(--ts-ink-soft);
font-weight: 600;
}
/* The "open link" arrow that sits in the embed corner */
.markdown-embed .markdown-embed-link {
color: var(--ts-ink-faint);
}
.markdown-embed:hover .markdown-embed-link {
color: var(--ts-amber);
}
/* Nested embed content shouldn't double-tint */
.markdown-embed .markdown-embed-content {
background: transparent;
}
.markdown-embed .markdown-preview-view {
padding: 0;
}
/* ===== Callouts + highlight (accent-following refactor) ===== */
/* ============================================================
CALLOUTS — color follows accent/flavor via var() + color-mix
============================================================ */
/* Base callout shell: neutral defaults, overridden per data-callout below.
Uses --_ac as an internal accent channel so the title + tint stay in sync. */
.markdown-rendered .callout,
.markdown-source-view .callout {
--_ac: var(--ts-amber);
border: 1px solid var(--ts-line);
border-left: 3px solid var(--_ac);
border-radius: 8px;
background-color: color-mix(in srgb, var(--_ac) 9%, var(--ts-panel));
/* neutralize base-theme accent so mix-blend/pseudo elements don't tint */
--callout-border-color: var(--_ac);
}
/* Title row: mono, uppercase, letter-spacing, accent-colored */
.markdown-rendered .callout .callout-title,
.markdown-source-view .callout .callout-title {
color: var(--_ac);
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.82em;
font-weight: 600;
padding: 6px 2px;
align-items: center;
/* Obsidian tints the title row background from --callout-color at low
alpha; keep it transparent so our shell tint isn't doubled. */
background-color: transparent;
}
.markdown-rendered .callout .callout-title-inner,
.markdown-source-view .callout .callout-title-inner {
color: var(--_ac);
font-weight: 600;
}
/* Icon inherits the accent */
.markdown-rendered .callout .callout-icon,
.markdown-source-view .callout .callout-icon,
.markdown-rendered .callout .callout-icon .svg-icon,
.markdown-source-view .callout .callout-icon .svg-icon {
color: var(--_ac);
}
/* Body text sits on the ink scale, not the accent */
.markdown-rendered .callout .callout-content,
.markdown-source-view .callout .callout-content {
color: var(--ts-ink-soft);
}
/* ---- TYPE -> TOKEN MAP -----------------------------------------
note/info/abstract/summary -> --ts-amber
tip/hint/success/check/done/example -> --ts-teal
warning/caution/attention/danger/error/fail/failure/missing/bug -> --ts-clay
quote/cite/question/help/faq/todo -> --ts-ink-faint
Only remap the accent channel; shell/title rules above inherit it.
Scoped to the rendered/source views so specificity matches the shell
rules above (0,3,0) and the accent actually cascades in. */
.markdown-rendered .callout[data-callout="note"],
.markdown-source-view .callout[data-callout="note"],
.markdown-rendered .callout[data-callout="info"],
.markdown-source-view .callout[data-callout="info"],
.markdown-rendered .callout[data-callout="abstract"],
.markdown-source-view .callout[data-callout="abstract"],
.markdown-rendered .callout[data-callout="summary"],
.markdown-source-view .callout[data-callout="summary"] {
--_ac: var(--ts-amber);
}
.markdown-rendered .callout[data-callout="tip"],
.markdown-source-view .callout[data-callout="tip"],
.markdown-rendered .callout[data-callout="hint"],
.markdown-source-view .callout[data-callout="hint"],
.markdown-rendered .callout[data-callout="success"],
.markdown-source-view .callout[data-callout="success"],
.markdown-rendered .callout[data-callout="check"],
.markdown-source-view .callout[data-callout="check"],
.markdown-rendered .callout[data-callout="done"],
.markdown-source-view .callout[data-callout="done"],
.markdown-rendered .callout[data-callout="example"],
.markdown-source-view .callout[data-callout="example"] {
--_ac: var(--ts-teal);
}
.markdown-rendered .callout[data-callout="warning"],
.markdown-source-view .callout[data-callout="warning"],
.markdown-rendered .callout[data-callout="caution"],
.markdown-source-view .callout[data-callout="caution"],
.markdown-rendered .callout[data-callout="attention"],
.markdown-source-view .callout[data-callout="attention"],
.markdown-rendered .callout[data-callout="danger"],
.markdown-source-view .callout[data-callout="danger"],
.markdown-rendered .callout[data-callout="error"],
.markdown-source-view .callout[data-callout="error"],
.markdown-rendered .callout[data-callout="fail"],
.markdown-source-view .callout[data-callout="fail"],
.markdown-rendered .callout[data-callout="failure"],
.markdown-source-view .callout[data-callout="failure"],
.markdown-rendered .callout[data-callout="missing"],
.markdown-source-view .callout[data-callout="missing"],
.markdown-rendered .callout[data-callout="bug"],
.markdown-source-view .callout[data-callout="bug"] {
--_ac: var(--ts-clay);
}
.markdown-rendered .callout[data-callout="quote"],
.markdown-source-view .callout[data-callout="quote"],
.markdown-rendered .callout[data-callout="cite"],
.markdown-source-view .callout[data-callout="cite"],
.markdown-rendered .callout[data-callout="question"],
.markdown-source-view .callout[data-callout="question"],
.markdown-rendered .callout[data-callout="help"],
.markdown-source-view .callout[data-callout="help"],
.markdown-rendered .callout[data-callout="faq"],
.markdown-source-view .callout[data-callout="faq"],
.markdown-rendered .callout[data-callout="todo"],
.markdown-source-view .callout[data-callout="todo"] {
--_ac: var(--ts-ink-faint);
}
/* ============================================================
==MARK== HIGHLIGHT — follows the accent too
============================================================ */
.markdown-rendered mark,
.markdown-source-view mark {
background: color-mix(in srgb, var(--ts-amber) 28%, transparent);
color: var(--ts-ink);
border-radius: 3px;
padding: 0 0.15em;
/* keep box tight to the glyphs */
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
}
/* ===== Print/PDF + side panes ===== */
/* =============================================================
OPUS — PRINT / PDF EXPORT
Deliberate exception to the "tokens only" rule: the theme
tokens flip with light/dark, so using them here would print
dark pages in dark mode. A clean paper export requires an
absolute white ground and black ink, so this ONE surface
hardcodes white/black/neutral-grey on purpose.
============================================================= */
@media print {
/* ---- force a clean light ground everywhere ---- */
:root,
body,
.app-container,
.horizontal-main-container,
.workspace,
.workspace-split,
.workspace-leaf,
.workspace-leaf-content,
.view-content,
.markdown-reading-view,
.markdown-preview-view,
.markdown-rendered,
.markdown-source-view,
.cm-editor,
.cm-scroller,
.print {
background: #ffffff !important;
color: #000000 !important;
box-shadow: none !important;
filter: none !important;
}
/* black ink, no shadows/haze on all rendered content */
.markdown-preview-view *,
.markdown-rendered * {
color: #000000 !important;
box-shadow: none !important;
text-shadow: none !important;
background-image: none !important;
}
/* links: black + underlined, no accent wash */
.markdown-preview-view a,
.markdown-rendered a,
a.internal-link,
a.external-link,
a.tag {
color: #000000 !important;
text-decoration: underline !important;
background: none !important;
border: 0 !important;
}
/* code: light fill, thin border, black text, wrap for paper */
.markdown-rendered code,
.markdown-rendered pre,
.markdown-rendered pre code,
.cm-inline-code {
background: #f4f4f4 !important;
color: #000000 !important;
border: 1px solid #cccccc !important;
box-shadow: none !important;
}
.markdown-rendered pre {
border-radius: 6px;
padding: 0.6em 0.8em;
white-space: pre-wrap !important;
word-break: break-word;
overflow: visible !important;
}
.markdown-rendered code { border-radius: 3px; }
/* flatten syntax highlighting so tokens read as plain black on paper */
.markdown-rendered pre code span,
.markdown-rendered pre code .token { color: #000000 !important; }
/* tables: hairline black rules, no zebra fill */
.markdown-rendered th,
.markdown-rendered td {
border: 1px solid #000000 !important;
background: #ffffff !important;
color: #000000 !important;
}
.markdown-rendered th { font-weight: 700; }
/* blockquotes / callouts: plain bordered boxes */
.markdown-rendered blockquote,
.markdown-rendered .callout {
background: #ffffff !important;
border: 1px solid #cccccc !important;
color: #000000 !important;
border-radius: 6px;
}
.markdown-rendered hr { border-color: #000000 !important; }
.markdown-rendered img { filter: none !important; }
/* ---- hide UI chrome ---- */
.titlebar,
.status-bar,
.workspace-ribbon,
.side-dock-ribbon,
.workspace-tab-header-container,
.workspace-tab-header,
.workspace-tab-container-before,
.workspace-tab-container-after,
.view-header,
.view-actions,
.workspace-split.mod-left-split,
.workspace-split.mod-right-split,
.mod-left-split,
.mod-right-split,
.workspace-leaf-resize-handle,
.sidebar-toggle-button,
.scroll-more,
.collapse-indicator,
.metadata-container .metadata-properties-heading {
display: none !important;
}
}
/* =============================================================
SIDE PANES — outline · backlinks · search · tags
Shared Obsidian 1.x .tree-item scaffold. Mono labels in
--ts-ink-soft, amber left rail on hover/active, amber-wash
search matches.
============================================================= */
/* mono labels, muted ink */
.outline .tree-item-inner,
.backlink-pane .tree-item-inner,
.tag-container .tree-item-inner,
.search-result-container .tree-item-inner,
.search-result-file-title,
.tag-pane-tag-text {
font-family: var(--ts-mono);
color: var(--ts-ink-soft);
letter-spacing: 0.01em;
}
/* file-title rows read like console labels */
.search-result-file-title {
font-size: 0.82em;
letter-spacing: 0.02em;
}
/* counts / flair — tabular mono so digits align */
.tag-pane-tag-count,
.search-result-count,
.backlink-pane .tree-item-flair,
.outline .tree-item-flair,
.tree-item-flair {
font-family: var(--ts-mono);
font-variant-numeric: tabular-nums;
font-feature-settings: "tnum" 1;
color: var(--ts-ink-faint);
}
/* hover + active: subtle fill + amber left accent rail */
.outline .tree-item-self:hover,
.backlink-pane .tree-item-self:hover,
.tag-container .tree-item-self:hover,
.search-result-container .tree-item-self:hover,
.search-result-file-title:hover {
background: var(--ts-panel-2);
color: var(--ts-ink);
box-shadow: inset 2px 0 0 0 var(--ts-amber);
}
.outline .tree-item-self.is-active,
.outline .tree-item-self.is-clicked,
.backlink-pane .tree-item-self.is-active,
.tag-container .tree-item-self.is-active,
.search-result-container .tree-item-self.is-active,
.search-result-file-title.is-active,
.search-result-file-title.is-selected {
color: var(--ts-ink);
box-shadow: inset 2px 0 0 0 var(--ts-amber);
}
.outline .tree-item-self.is-active .tree-item-inner,
.backlink-pane .tree-item-self.is-active .tree-item-inner,
.tag-container .tree-item-self.is-active .tree-item-inner {
color: var(--ts-ink);
}
/* matched search text — amber wash.
Scoped to the result containers (0,2,0) so it beats the base
theme's .search-result-file-matched-text --ts-highlight rule
regardless of load order. */
.search-result-container .search-result-file-matched-text,
.backlink-pane .search-result-file-matched-text {
background: color-mix(in srgb, var(--ts-amber) 26%, transparent);
color: var(--ts-ink);
border-radius: 3px;
}
/* (Section 29 removed in v2.0.1 — per-state checkbox coloring dropped.
The recede-on-complete treatment lives in section 17 and covers both
Reading and Live Preview.) */
/* -------------------------------------------------------------
30. Collapsible Code Blocks plugin (bwya77) — optional skin
Dormant unless that plugin is installed. Class names verified
from the plugin source (.code-block-toggle, pre.ccb-code-block,
.ccb-editor-codeblock).
------------------------------------------------------------- */
.code-block-toggle {
color: var(--ts-ink-faint);
background: transparent;
border: 1px solid transparent;
border-radius: var(--radius-s);
font-family: var(--ts-mono);
transition: color 0.12s ease, background 0.12s ease;
}
.code-block-toggle:hover { color: var(--ts-amber); background: var(--ts-panel-2); }
.code-block-toggle:focus-visible { outline: 2px solid var(--ts-amber); outline-offset: 2px; }
/* a folded block reads as a quiet bar with the amber "there's more" rail */
.markdown-preview-view pre.ccb-code-block.collapsed {
border-color: var(--ts-code-line);
border-left: 3px solid var(--ts-amber);
background: var(--ts-code-bg);
}
.markdown-preview-view pre.ccb-code-block.collapsed code { color: var(--ts-ink-faint); }
.ccb-editor-codeblock.code-block-folded,
.code-block-folded { border-left: 3px solid var(--ts-amber); }
/* ============================================================
OPUS v2.2 — heading-scale (BASE feature, always on)
Governed modular type scale (~1.21.25) + a MONOCHROME
structural heading hierarchy. Hierarchy is carried by size,
weight, tonal step, and a monochrome structural rule under H1.
The sole heading ACCENT is the H6 uppercase mono eyebrow.
Appended at end of theme.css: overrides §6 HEADINGS by source
order at matched (never inflated) specificity.
============================================================ */
body {
/* one vertical-rhythm unit — all heading margins snap to it.
Exposed as --opus-heading-rhythm so Style Settings can tune density. */
--opus-heading-rhythm: 1.6rem;
/* modular scale — explicit sizes (ratio ~1.201.30 per step) */
--h1-size: 2.1rem;
--h2-size: 1.62rem;
--h3-size: 1.3rem;
--h4-size: 1.08rem;
--h5-size: 0.92rem;
--h6-size: 0.78rem;
/* explicit weights — bold at the top, stepping down (mono-safe stops) */
--h1-weight: 700;
--h2-weight: 600;
--h3-weight: 600;
--h4-weight: 500;
--h5-weight: 500;
--h6-weight: 600;
/* MONOCHROME hierarchy: tonal step, no hue. H3 returns to ink;
H4/H5 recede to ink-soft; ONLY H6 keeps the amber accent. */
--h1-color: var(--ts-ink);
--h2-color: var(--ts-ink);
--h3-color: var(--ts-ink);
--h4-color: var(--ts-ink-soft);
--h5-color: var(--ts-ink-soft);
--h6-color: var(--ts-amber);
}
/* shared heading treatment — reading, editor (Live Preview), inline title */
.markdown-rendered :is(h1, h2, h3, h4, h5, h6),
.cm-s-obsidian .HyperMD-header,
.inline-title {
font-family: var(--ts-mono);
line-height: 1.2; /* tight, within 1.151.25 */
letter-spacing: -0.01em;
}
/* ---- per-level size + weight + monochrome color -------------------- */
.markdown-rendered h1,
.cm-s-obsidian .HyperMD-header-1,
.inline-title {
font-size: var(--h1-size);
font-weight: var(--h1-weight);
color: var(--ts-ink);
letter-spacing: -0.02em;
}
.markdown-rendered h2,
.cm-s-obsidian .HyperMD-header-2 {
font-size: var(--h2-size);
font-weight: var(--h2-weight);
color: var(--ts-ink);
}
/* H3: explicitly back to ink — removes the base amber (base §6, lines ~363/383) */
.markdown-rendered h3,
.cm-s-obsidian .HyperMD-header-3 {
font-size: var(--h3-size);
font-weight: var(--h3-weight);
color: var(--ts-ink);
}
.markdown-rendered h4,
.cm-s-obsidian .HyperMD-header-4 {
font-size: var(--h4-size);
font-weight: var(--h4-weight);
color: var(--ts-ink-soft);
}
.markdown-rendered h5,
.cm-s-obsidian .HyperMD-header-5 {
font-size: var(--h5-size);
font-weight: var(--h5-weight);
color: var(--ts-ink-soft);
}
/* ---- H1 signature: full-width MONOCHROME structural hairline (H1 ONLY)
Weight comes from --ts-line-strong, not hue — the accent stays
reserved for the H6 eyebrow. ------------------------------------ */
.markdown-rendered h1 {
border-bottom: 1px solid var(--ts-line-strong);
padding-bottom: calc(var(--opus-heading-rhythm) * 0.3);
}
.cm-s-obsidian .HyperMD-header-1 {
border-bottom: 1px solid var(--ts-line-strong);
padding-bottom: 0.2em;
}
/* ---- H6 eyebrow: the ONLY heading accent — re-asserted -------------- */
.markdown-rendered h6,
.cm-s-obsidian .HyperMD-header-6 {
font-size: var(--h6-size);
font-weight: var(--h6-weight);
color: var(--ts-amber);
text-transform: uppercase;
letter-spacing: 0.15em;
}
.inline-title {
color: var(--ts-ink);
border-bottom: 0; /* the note title carries no rule */
}
/* ---- vertical rhythm: margins snapped to the spacing unit ----------- */
/* Reading view only — Live Preview keeps CodeMirror's own line metrics
to avoid caret/line-height artifacts. */
.markdown-rendered :is(h1, h2, h3, h4, h5, h6) {
margin-top: var(--opus-heading-rhythm);
margin-bottom: calc(var(--opus-heading-rhythm) * 0.5);
}
.markdown-rendered h1 { margin-top: calc(var(--opus-heading-rhythm) * 2); }
.markdown-rendered h2 { margin-top: calc(var(--opus-heading-rhythm) * 1.5); } /* clear space-before */
.markdown-rendered :is(h1, h2, h3, h4, h5, h6):first-child { margin-top: 0; }
/* -------------------------------------------------------------
14. DENSITY — Compact operator profile (v2.3)
Class-toggle: body.opus-compact (Style Settings, default OFF).
Fully additive and scoped under body.opus-compact — nothing
changes until the toggle is on. A single --opus-density
multiplier drives the em/rem vertical rhythm (paragraph + heading
spacing); line-height is a ratio and the two-axis paddings are
tightened explicitly, so they don't get bent by the same factor.
Themes across all flavors automatically (tokens only).
------------------------------------------------------------- */
body.opus-compact {
--opus-density: 0.6; /* vertical-rhythm multiplier */
/* body + heading line-height (ratios — tightened, not multiplied) */
--line-height-normal: 1.42; /* was 1.62 */
--line-height-tight: 1.06; /* was 1.15 — drives heading leading */
/* paragraph + heading rhythm, driven by the one multiplier */
--p-spacing: calc(1rem * var(--opus-density)); /* 1rem -> 0.60rem */
--heading-spacing: calc(1.8em * var(--opus-density)); /* 1.8em -> 1.08em */
/* list vertical spacing + indent */
--list-spacing: 0.05em; /* Obsidian li margin driver */
--list-indent: 1.5em; /* reading-view <ul>/<ol> indent (consumed below) */
/* callout padding */
--callout-padding: 0.5em 0.9em; /* was 0.85em 1.1em */
}
/* Reading-view list indent + item spacing (belt-and-braces over the vars).
Indent is driven by --list-indent so the token isn't inert and the
value isn't duplicated; the var is always defined under this toggle. */
body.opus-compact .markdown-rendered :is(ul, ol) {
padding-inline-start: var(--list-indent);
}
body.opus-compact .markdown-rendered li {
margin-block: var(--list-spacing);
}
/* Blockquote padding follows the callout tightening */
body.opus-compact .markdown-rendered blockquote {
padding: 0.45em 0.9em;
}
/* Native reading-view table cells */
body.opus-compact .markdown-rendered td,
body.opus-compact .markdown-rendered th {
padding: 3px 8px;
}
/* Dataview table cells — the body.opus-compact prefix lifts specificity
over the base .dataview.table-view-table rules (which win on source
order + class count otherwise). */
body.opus-compact .dataview.table-view-table > thead > tr > th,
body.opus-compact .dataview.table-view-table thead th {
padding: 4px 10px; /* was 8px 12px */
}
body.opus-compact .dataview.table-view-table > tbody > tr > td,
body.opus-compact .dataview.table-view-table tbody td {
padding: 3px 10px; /* was 7px 12px */
}
/* Workspace tab height — trim the header inner padding (all tab strips) */
body.opus-compact .workspace-tab-header-inner {
padding-top: 3px;
padding-bottom: 3px;
}
/* -------------------------------------------------------------
TABLE STATES (v2.2 · base improvement)
Interactive row states for native Markdown AND Dataview tables,
in both Reading view (.markdown-rendered) and Live Preview
(.markdown-source-view.is-live-preview).
Tint is painted as an inset box-shadow OVERLAY, not a background,
so it layers over the existing zebra (--table-row-alt-background,
set on the row) and the cell borders (--ts-line, set on td) instead
of replacing them. `> td` scoping means header cells (th) never
light up. References only --ts- tokens, so it themes across all
flavors automatically.
hover row -> faint NEUTRAL raise (monochrome)
active/selected row -> accent glow at low alpha (:focus-within =
the row holding the cursor in Live Preview),
plus a single amber leading-edge tick.
------------------------------------------------------------- */
body {
--opus-table-hover: color-mix(in srgb, var(--ts-ink) 5%, transparent);
}
.markdown-rendered table td,
.markdown-source-view.is-live-preview table td {
transition: box-shadow 90ms ease;
}
/* hover feedback — neutral, leaves background + borders untouched */
.markdown-rendered table tr:hover > td,
.markdown-source-view.is-live-preview table tr:hover > td {
box-shadow: inset 0 0 0 100vmax var(--opus-table-hover);
}
/* active / selected row — accent at low alpha (the instrument glow).
Overrides hover by source order when a row is both hovered + active.
Slider-overridable via --opus-table-glow; falls back to 8%. */
.markdown-rendered table tr:focus-within > td,
.markdown-source-view.is-live-preview table tr:focus-within > td {
box-shadow: inset 0 0 0 100vmax
color-mix(in srgb, var(--ts-amber) var(--opus-table-glow, 8%), transparent);
}
/* leading-edge rail on the active row — one amber tick, echoing the
theme's active-file / active-tab rail language */
.markdown-rendered table tr:focus-within > td:first-child,
.markdown-source-view.is-live-preview table tr:focus-within > td:first-child {
box-shadow:
inset 2px 0 0 0 var(--ts-amber-hi),
inset 0 0 0 100vmax
color-mix(in srgb, var(--ts-amber) var(--opus-table-glow, 8%), transparent);
}
/* light mode: the 8% amber glow washes out on a bright row — use the
darker amber-hi and lift the default glow so the selection reads. */
.theme-light .markdown-rendered table tr:focus-within > td,
.theme-light .markdown-source-view.is-live-preview table tr:focus-within > td {
box-shadow: inset 0 0 0 100vmax
color-mix(in srgb, var(--ts-amber-hi) var(--opus-table-glow, 13%), transparent);
}
.theme-light .markdown-rendered table tr:focus-within > td:first-child,
.theme-light .markdown-source-view.is-live-preview table tr:focus-within > td:first-child {
box-shadow:
inset 2px 0 0 0 var(--ts-amber-hi),
inset 0 0 0 100vmax
color-mix(in srgb, var(--ts-amber-hi) var(--opus-table-glow, 13%), transparent);
}
/* =============================================================
19. MOTION — one functional token, precision only (v2.2)
A single ~130ms ease-out transition wired to state changes that
ALREADY exist in the theme: the active-file/active-tab rails,
hover brightening, active-tab color shift, checkbox toggle,
callout fold chevron, and link hover. No entrance animation,
no bounce, no decoration — the UI just stops snapping.
--opus-motion holds the WHOLE transition value (property list +
timing), so the "Disable motion" toggle can set it to `none` and
every wired rule collapses to `transition: none` cleanly.
Everything is emitted ONLY under prefers-reduced-motion:
no-preference, so reduced-motion users never receive a transition
in the first place (belt-and-suspenders alongside the existing
section-14 `* { transition: none !important }` reduce killer).
------------------------------------------------------------- */
/* Token lives on body so a body-class toggle can override it.
Defined outside the media query (harmless when unused), consumed
only inside it. Lists exactly the properties these state changes
touch — nothing animates that wasn't already changing. */
body {
--opus-motion:
color 130ms ease-out,
background-color 130ms ease-out,
box-shadow 130ms ease-out,
border-color 130ms ease-out,
transform 130ms ease-out;
}
/* opt-out: collapse the token to a valid no-op */
body.opus-no-motion { --opus-motion: none; }
@media (prefers-reduced-motion: no-preference) {
/* active-tab rail (inset amber top-rail) + active-tab title shift */
.workspace-tab-header,
.workspace-tab-header-inner-title,
/* active-file / active-folder rail (inset amber side-rail) + name */
.nav-file-title,
.nav-folder-title,
.nav-file-title-content,
.nav-folder-title-content,
/* hover brightening on sidebar list rows (outline/backlink/tag/search) */
.outline .tree-item-self,
.backlink-pane .tree-item-self,
.tag-container .tree-item-self,
.search-result-container .tree-item-self,
.search-result-file-title,
.tree-item-inner,
/* link hover: amber -> amber-hi, reading + live-preview */
.markdown-rendered a,
.cm-s-obsidian span.cm-link,
.cm-s-obsidian span.cm-url,
.cm-s-obsidian span.cm-hmd-internal-link,
/* checkbox toggle: box fill/border recede to ink-faint on check */
input.task-list-item-checkbox,
.task-list-item-checkbox,
/* callout fold chevron rotate + title hue on collapse */
.callout-fold,
.callout-fold .svg-icon,
.markdown-rendered .callout .callout-title,
.markdown-source-view .callout .callout-title {
transition: var(--opus-motion);
}
/* clickable icons already brighten on hover across the chrome */
.clickable-icon {
transition: var(--opus-motion);
}
}
/* =============================================================
OLED TRUE BLACK + HIGHER CONTRAST (v2.2)
Appended last so source order + specificity override flavors.
body.opus-oled.theme-dark (0,2,1) beats .theme-dark.opus-flavor-x
(0,2,0) and base .theme-dark (0,1,0) -> applies across all flavors.
Dark mode only. Panels derive from --ts-line (NOT overridden here)
so there is no self-reference cycle and each flavor keeps its tint.
============================================================= */
body.opus-oled.theme-dark {
--ts-ground: #000000;
--ts-panel: color-mix(in srgb, var(--ts-line), #000000 55%);
--ts-panel-2: color-mix(in srgb, var(--ts-line), #000000 40%);
--ts-raised: color-mix(in srgb, var(--ts-line), #000000 22%);
--ts-code-bg: #000000;
--ts-code-line: color-mix(in srgb, var(--ts-line), #000000 45%);
--ts-shadow: 0 0 0 1px var(--ts-line);
}
/* Higher contrast — self-contained token promotion (no base -src patch).
Lifts the dimmest ink and hairlines up the existing ladder; safe on
every flavor/mode because it only references defined tokens. */
body.opus-contrast.theme-dark {
--ts-ink-faint: var(--ts-ink-soft);
--ts-line: var(--ts-line-strong);
}
/* =============================================================
MEASURE — reader-controlled line width, text size, line-height
Additive: appended at end of theme.css. Each mapping falls back
to the theme's CURRENT value, so with Style Settings absent OR at
its defaults nothing changes. Style Settings' variable-number-slider
writes --opus-measure-* onto <body> (default value included), which
then overrides via the var() below (body specificity + later source
order beats the base --font-text-size / --line-height-normal set in
section 0). References Obsidian vars only — no palette tokens needed.
------------------------------------------------------------- */
body {
/* (a) readable line width -> Obsidian --file-line-width.
Fallback 44rem ~= Obsidian's default 700px (current behavior). */
--file-line-width: var(--opus-measure-width, 44rem);
/* (b) editor/reading font size -> --font-text-size (current: 16px) */
--font-text-size: var(--opus-measure-font-size, 16px);
/* (c) body line-height -> --line-height-normal (current: 1.62) */
--line-height-normal: var(--opus-measure-line-height, 1.62);
}
/* =============================================================
OPUS — MOBILE OPTIMIZATION (v2.4)
Every rule is gated on .is-mobile / .is-phone / .is-tablet, or a
phone-width @media whose inner selector is .is-phone. A desktop
<body> carries none of these classes, so nothing here can match it.
Color comes ONLY from --ts-* tokens (or standard Obsidian vars that
resolve to them). Elevation uses Obsidian's own --shadow-s/--shadow-l.
Safe-area via Obsidian's --safe-area-inset-* with an env() fallback.
Purely additive.
------------------------------------------------------------- */
/* ---- 1. mobile-local tokens: surfaces, touch metrics, insets ---- */
body.is-mobile {
--ts-mobile-chrome: color-mix(in srgb, var(--ts-panel) 60%, var(--ts-panel-2));
--ts-mobile-elev: inset 0 1px 0 0 var(--ts-line), var(--shadow-s);
--ts-touch: 44px;
--ts-sa-top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px));
--ts-sa-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
--font-text-size: calc(var(--opus-measure-font-size, 16px) * var(--opus-mobile-font-scale, 1));
--line-height-normal: var(--opus-measure-line-height, 1.66);
--file-line-width: 100%;
}
/* ---- 2. READABILITY: gentler heading scale + margins on phones ----
em (not rem) so headings track the mobile text-scale slider. */
body.is-phone {
--h1-size: 1.68em;
--h2-size: 1.26em;
--h3-size: 1.06em;
--file-margins: calc(var(--ts-sa-top) + 10px) 16px calc(var(--ts-sa-bottom) + 20px) 16px;
}
/* ---- 3. TOUCH TARGETS ---- */
body.is-mobile .clickable-icon {
--icon-size: var(--icon-m);
min-width: var(--ts-touch);
min-height: var(--ts-touch);
display: flex;
align-items: center;
justify-content: center;
}
body.is-mobile .nav-file-title,
body.is-mobile .nav-folder-title {
min-height: var(--ts-touch);
display: flex;
align-items: center;
padding-top: 6px;
padding-bottom: 6px;
}
body.is-mobile .markdown-rendered input[type="checkbox"] {
width: 1.4em;
height: 1.4em;
}
/* ---- 4-5. BOTTOM BAR / KEYBOARD TOOLBAR — left to Obsidian's stock styling.
Restyling the floating bottom bar produced hollow proportions on some
builds, and it's not worth fighting per-selector on a surface the theme
can't reliably target. The stock bar is a fine baseline. (The
"Dock keyboard toolbar" Style Settings toggle is now a no-op.) */
/* ---- 6. NAVIGATION DRAWERS (left / right slide-outs) ---- */
body.is-mobile .workspace-drawer,
body.is-mobile .workspace-drawer-inner {
background: var(--ts-panel);
}
body.is-mobile .workspace-drawer.mod-left { border-right: 1px solid var(--ts-line-strong); }
body.is-mobile .workspace-drawer.mod-right { border-left: 1px solid var(--ts-line-strong); }
body.is-mobile .workspace-drawer-backdrop {
-webkit-backdrop-filter: blur(3px);
backdrop-filter: blur(3px);
}
body.is-mobile .workspace-drawer-header {
padding: calc(var(--ts-sa-top) + 12px) 16px 8px 16px;
}
body.is-mobile .workspace-drawer-tab-option-item {
min-height: var(--ts-touch);
padding: 12px;
}
body.is-tablet .workspace-drawer:not(.is-pinned):not(.is-sticky) {
border-radius: var(--radius-l);
box-shadow: var(--shadow-l);
}
/* ---- 7. "YOU ARE HERE" amber rail — strengthened for touch legibility ---- */
body.is-mobile:not(.opus-no-nav-rail) .nav-file-title.is-active,
body.is-mobile:not(.opus-no-nav-rail) .nav-folder-title.is-active {
box-shadow: inset 3px 0 0 0 var(--ts-amber);
background: var(--ts-panel-2);
}
/* ---- 8. CODE BLOCKS: comfortable size + clean horizontal scroll ---- */
body.is-mobile .markdown-rendered pre {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
border-radius: var(--radius-m);
}
body.is-mobile .markdown-rendered pre > code {
font-size: 0.82em;
}
/* ---- 9. TABLES: scroll internally, never push the page sideways ---- */
body.is-mobile .markdown-rendered table {
display: block;
width: max-content;
max-width: 100%;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}
/* ---- 10. CALLOUTS: fit the narrow column ---- */
body.is-mobile .callout {
--callout-padding: 0.7em 0.9em;
}
body.is-mobile .callout-title { flex-wrap: wrap; }
/* ---- 11. LEAN NOTE HEADER (Style Settings, phone only) ---- */
body.is-phone.opus-mobile-lean-header .view-header {
height: calc(var(--ts-sa-top) + 34px);
min-height: calc(var(--ts-sa-top) + 34px);
}
/* ---- 12. PHONE-ONLY DENSITY (portrait phone widths) ---- */
@media (max-width: 400px) {
body.is-phone {
--border-width: 0.75px;
}
}
/* ============================================================
OPUS — CANVAS (operator-grade restyle) · APPEND-ONLY
Appended after the basic canvas rules earlier in this file;
wins purely on source order (selectors are equal-or-higher
specificity than the old block, so the later copy overrides).
Token-based; themes across all 6 flavors + light/dark.
No glow, no gradients, no noise.
Mechanism: Obsidian's preset color classes (.mod-canvas-color-1..6)
carry only a bare RGB-triplet --canvas-color; they can't carry our
token hexes. So each preset class sets a real-color custom prop
--cc. Every colored surface (node border, node tint, group tint,
group-label chip, edge stroke, arrowhead) reads var(--cc) with a
fallback to rgb(var(--canvas-color)) so USER CUSTOM-HEX nodes/edges
(which carry .is-themed + .mod-canvas-color-custom + inline
--canvas-color, but no .mod-canvas-color-N, so never get --cc)
still honor their literal color.
============================================================ */
/* ---- Curated 6-color canvas set --------------------------- *
3 semantic (follow the flavor) + 3 fixed cold desaturated hues.
Slots follow Obsidian's preset order: 1 red, 2 orange, 3 yellow,
4 green, 5 cyan, 6 purple — remapped so an existing canvas keeps
its hue family but loses the garish saturation. */
body {
--ts-canvas-1: var(--ts-clay); /* red -> clay (warning, semantic) */
--ts-canvas-2: var(--ts-amber); /* orange -> amber (accent, semantic) */
--ts-canvas-3: #869b7a; /* yellow -> muted sage (fixed) */
--ts-canvas-4: var(--ts-teal); /* green -> teal (good, semantic) */
--ts-canvas-5: #7089ad; /* cyan -> slate-blue (fixed) */
--ts-canvas-6: #9a7ea9; /* purple -> dusty plum (fixed) */
/* tight, quiet depth shadow (dark) */
--ts-canvas-shadow: 0 3px 12px -6px rgba(0,0,0,0.55);
}
/* Light mode: darken the 3 fixed hues so a 1px border reads on
the near-white ground; soften the shadow. (body.theme-light beats
the bare body rule above on specificity.) */
.theme-light {
--ts-canvas-3: #5c7150;
--ts-canvas-5: #45597e;
--ts-canvas-6: #6f5580;
--ts-canvas-shadow: 0 2px 10px -6px rgba(20,40,35,0.28);
}
/* Style-Settings toggle: punchier (still cohesive) fixed hues.
The 3 semantic slots already pop via their tokens.
body.opus-canvas-vivid (0,1,1) outranks .theme-light (0,1,0), so
these apply in both modes — deliberate: vivid = max separation. */
body.opus-canvas-vivid {
--ts-canvas-3: #93b47c;
--ts-canvas-5: #6f92cc;
--ts-canvas-6: #b586c9;
}
/* ---- Map preset classes -> --cc --------------------------- *
Scoped to .canvas-wrapper so it cascades to node containers,
group nodes AND the per-edge themed groups (all carry a
.mod-canvas-color-N). One assignment feeds border, tint, chip,
edge and arrowhead below. Custom-hex elements carry
.mod-canvas-color-custom (not -N), so --cc stays unset and the
rgb(var(--canvas-color)) fallback wins for them. */
.canvas-wrapper .mod-canvas-color-1 { --cc: var(--ts-canvas-1); }
.canvas-wrapper .mod-canvas-color-2 { --cc: var(--ts-canvas-2); }
.canvas-wrapper .mod-canvas-color-3 { --cc: var(--ts-canvas-3); }
.canvas-wrapper .mod-canvas-color-4 { --cc: var(--ts-canvas-4); }
.canvas-wrapper .mod-canvas-color-5 { --cc: var(--ts-canvas-5); }
.canvas-wrapper .mod-canvas-color-6 { --cc: var(--ts-canvas-6); }
/* ---- Node cards ------------------------------------------- */
.canvas-node .canvas-node-container {
background: var(--ts-panel);
border: 1px solid var(--ts-line);
border-radius: var(--radius-m);
/* No box-shadow on canvas nodes: it promotes each node to its own GPU
layer, which Obsidian scales for zoom and blurs the node text. The
border + faint tint carry the card instead. (v2.5.0 fuzzy-text fix.) */
}
/* Color-coded nodes: colored 1px border + a whisper of tint over
the panel. Fallback path keeps user custom-hex nodes correct. */
.canvas-node.is-themed .canvas-node-container {
border-color: var(--cc, rgb(var(--canvas-color)));
background:
color-mix(in srgb, var(--cc, rgb(var(--canvas-color))) 7%, var(--ts-panel));
}
/* Content + text/file/link nodes speak the same language */
.canvas-node-content { color: var(--ts-ink); }
.canvas-node-label {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--ts-ink-soft);
font-size: 0.72em;
}
/* Node connection anchor handles (kept off .canvas-node-container so
handles rendered as direct children of .canvas-node still match) */
.canvas-node .canvas-node-edge {
background: var(--cc, var(--ts-amber));
}
/* ---- Groups: labeled regions, not loud blocks ------------- *
A colored group is BOTH .is-themed and .canvas-node-group; this
rule sits AFTER the themed-node rule at equal specificity, so a
group correctly stays translucent instead of panel-filled. */
.canvas-node.canvas-node-group .canvas-node-container,
.canvas-node .canvas-node-container.canvas-node-group {
background: color-mix(in srgb, var(--cc, var(--ts-amber)) 7%, transparent);
border: 1px solid var(--cc, var(--ts-line));
border-radius: var(--radius-l);
box-shadow: none;
}
/* Group title -> small mono UPPERCASE chip (echoes tab/label lang) */
.canvas-node.canvas-node-group .canvas-node-label,
.canvas-node-group .canvas-node-label,
.canvas-node .canvas-node-container.canvas-node-group ~ .canvas-node-label {
font-family: var(--ts-mono);
text-transform: uppercase;
letter-spacing: 0.09em;
font-size: 0.66em;
font-weight: 600;
color: var(--ts-ink-soft);
background: var(--ts-panel);
border: 1px solid var(--cc, var(--ts-line));
border-radius: var(--radius-s);
padding: 2px 8px;
}
/* ---- Selection / focus: clean amber ring (wins over color) - *
0-blur ring = hard rule, not a glow. */
.canvas-node.is-themed.is-focused .canvas-node-container,
.canvas-node.is-themed.is-selected .canvas-node-container,
.canvas-node.is-focused .canvas-node-container,
.canvas-node.is-selected .canvas-node-container,
.canvas-node.canvas-node-group.is-focused .canvas-node-container,
.canvas-node.canvas-node-group.is-selected .canvas-node-container {
border-color: var(--ts-amber);
outline: 2px solid var(--ts-amber); /* outline, not box-shadow — no layer promotion, text stays crisp */
outline-offset: -1px;
}
/* ---- Edges ------------------------------------------------- *
Paint ONLY the thin display path. The wide interaction path
stays transparent (painting it draws fat grey ghost ribbons).
.canvas-path is kept as a legacy alias; harmless if absent. */
.canvas-edges .canvas-path,
.canvas-edges path.canvas-display-path {
stroke: var(--ts-line-strong);
stroke-width: 2px;
}
.canvas-edges path.canvas-interaction-path {
stroke: transparent;
}
/* Colored edges: the per-edge group carries .is-themed +
.mod-canvas-color-N, so --cc is inherited down to the path.
Custom-hex edges fall back to their inline --canvas-color.
Higher specificity than the base stroke above -> wins. */
.canvas-wrapper .canvas-edges .is-themed path.canvas-display-path,
.canvas-wrapper .canvas-edges .is-themed .canvas-path {
stroke: var(--cc, rgb(var(--canvas-color)));
}
/* Arrowheads + endpoint dots follow the edge color. Class name for
the end-caps has shifted across Obsidian builds, so target the
known variants (.canvas-line-end current; -path-end/-endpoint
legacy) — extras are inert no-ops. */
.canvas-edges .canvas-line-end,
.canvas-edges .canvas-path-end,
.canvas-edges .canvas-edge-endpoint {
fill: var(--ts-line-strong);
stroke: var(--ts-line-strong);
}
.canvas-wrapper .canvas-edges .is-themed .canvas-line-end,
.canvas-wrapper .canvas-edges .is-themed .canvas-path-end,
.canvas-wrapper .canvas-edges .is-themed .canvas-edge-endpoint {
fill: var(--cc, rgb(var(--canvas-color)));
stroke: var(--cc, rgb(var(--canvas-color)));
}
/* Hover / selection -> amber. Equal specificity to the themed
stroke but placed AFTER it, so it wins by source order; :hover
is only a fallback because the interaction path overlays the
display path and eats pointer events. */
.canvas-wrapper .canvas-edges .is-hovered path.canvas-display-path,
.canvas-wrapper .canvas-edges .is-focused path.canvas-display-path,
.canvas-wrapper .canvas-edges .canvas-path.is-hovered,
.canvas-wrapper .canvas-edges .canvas-path.is-focused,
.canvas-wrapper .canvas-edges .canvas-path:hover {
stroke: var(--ts-amber);
}
.canvas-wrapper .canvas-edges .is-hovered .canvas-line-end,
.canvas-wrapper .canvas-edges .is-focused .canvas-line-end,
.canvas-wrapper .canvas-edges .is-hovered .canvas-path-end,
.canvas-wrapper .canvas-edges .is-focused .canvas-path-end,
.canvas-wrapper .canvas-edges .is-hovered .canvas-edge-endpoint,
.canvas-wrapper .canvas-edges .is-focused .canvas-edge-endpoint {
fill: var(--ts-amber);
stroke: var(--ts-amber);
}
/* Edge label -> one tight mono chip, centered on the connector.
Obsidian nests it as .canvas-path-label-wrapper > .canvas-path-label:
the WRAPPER is a larger box that sits offset down-right of the text.
So make the wrapper a transparent, centering box and paint the chip
on the INNER label, which hugs the text and stays centered. (v2.4.5 —
the v2.4.3 attempt kept the wrong box, pinning the text top-left.) */
.canvas-path-label-wrapper {
background: none;
border: 0;
padding: 0;
box-shadow: none;
display: flex;
align-items: center;
justify-content: center;
}
.canvas-path-label {
font-family: var(--ts-mono);
font-size: 0.66em;
letter-spacing: 0.04em;
color: var(--ts-ink-soft);
background: var(--ts-panel);
border: 1px solid var(--ts-line);
border-radius: var(--radius-s);
padding: 1px 6px;
width: fit-content;
white-space: nowrap;
}
/* ---- Backdrop + dot grid ---------------------------------- *
Use Obsidian's own canvas vars for the ground + dot pattern;
background-color sits behind the dot background-image, so the
grid still renders. */
.canvas-wrapper,
.canvas-background,
.canvas {
background-color: var(--ts-ground);
--canvas-background: var(--ts-ground);
--canvas-dot-pattern: var(--ts-line);
}
/* ---- Controls cluster + card menu (app-chrome consistent) - */
.canvas-controls,
.canvas-card-menu {
background: var(--ts-panel-2);
border: 1px solid var(--ts-line);
border-radius: var(--radius-m);
box-shadow: var(--ts-canvas-shadow);
}
.canvas-controls .canvas-control-item,
.canvas-controls .clickable-icon,
.canvas-card-menu-button {
color: var(--ts-ink-soft);
font-family: var(--ts-mono);
border-radius: var(--radius-s);
}
.canvas-controls .canvas-control-item:hover,
.canvas-controls .clickable-icon:hover,
.canvas-card-menu-button:hover {
color: var(--ts-amber);
background: var(--ts-raised);
}
/* Per-node popover toolbar (color picker lives here) */
.canvas-menu {
background: var(--ts-panel-2);
border: 1px solid var(--ts-line);
border-radius: var(--radius-m);
box-shadow: var(--ts-canvas-shadow);
}
.canvas-menu .clickable-icon {
color: var(--ts-ink-soft);
border-radius: var(--radius-s);
}
.canvas-menu .clickable-icon:hover {
color: var(--ts-amber);
background: var(--ts-raised);
}
/* (v2.4.1 mobile bottom-bar sizing removed in v2.4.2 — bar left to stock) */
/* =============================================================
HEADING EMPHASIS — value ladder (v2.5.6)
-------------------------------------------------------------
SELF-CONTAINED AND FULLY REVERTIBLE: this is the last block in
the file, --ts-ink-hi is defined and used ONLY here, and
nothing above depends on any of it. Deleting this entire block
restores the pre-2.5.5 headings exactly.
Hierarchy by VALUE within one ink family, never by hue (hue
variation is the rainbow line). H1 lifts above body text; H3
and H5 step down, and H3 gives up its amber to become a neutral
rung. Structural accents were tried and rejected — see below.
============================================================= */
.theme-dark { --ts-ink-hi: #f2f7f5; }
.theme-light { --ts-ink-hi: #191510; }
body {
--h1-color: var(--ts-ink-hi);
--h2-color: var(--ts-ink);
--h3-color: color-mix(in srgb, var(--ts-ink) 72%, var(--ts-ink-soft));
--h4-color: var(--ts-ink-soft);
--h5-color: color-mix(in srgb, var(--ts-ink-soft) 60%, var(--ts-ink-faint));
--h6-color: var(--ts-amber);
}
/* A - the ladder. Explicit rules because §6 and §18 hardcode h1/h3. */
.markdown-rendered h1,
.cm-s-obsidian .HyperMD-header-1,
.inline-title { color: var(--ts-ink-hi); }
.markdown-rendered h2,
.cm-s-obsidian .HyperMD-header-2 { color: var(--ts-ink); }
.markdown-rendered h3,
.cm-s-obsidian .HyperMD-header-3 {
color: color-mix(in srgb, var(--ts-ink) 72%, var(--ts-ink-soft));
}
.markdown-rendered h4,
.cm-s-obsidian .HyperMD-header-4 { color: var(--ts-ink-soft); }
.markdown-rendered h5,
.cm-s-obsidian .HyperMD-header-5 {
color: color-mix(in srgb, var(--ts-ink-soft) 60%, var(--ts-ink-faint));
}
/* (The v2.5.5 structural marks — amber H1 rule + H2 pipe/rail — were cut
in v2.5.6. The muting earned its keep; the furniture didn't. Heading
accent is the H6 eyebrow and nothing else.) */
/* =============================================================
CITRINITAS — Gruvbox-spirited colored headings (v2.6.0)
-------------------------------------------------------------
Citrinitas is the Gruvbox-hearted flavor, so it carries the
colored-heading idea too. Prima Materia and Nigredo stay
monochrome — the flavor carries the philosophy, not a toggle.
The hues are TONALLY LEVELED: same rough lightness and
saturation across the ramp. That is the actual trick behind
Gruvbox reading as one earthy family instead of a circus, and
it's what separates this from a rainbow. Colour stays ADDITIVE
— size and weight still carry the hierarchy; hue is on top.
Specificity (0,3,1) intentionally out-ranks the monochrome
value ladder above. SELF-CONTAINED: delete this block and
Citrinitas falls straight back to the ladder.
============================================================= */
/* dark — Gruvbox "bright" set, punchiest hues muted to level them */
.theme-dark.opus-flavor-citrinitas :is(.markdown-rendered h1, .cm-s-obsidian .HyperMD-header-1, .inline-title) { color: #e3b34a; }
.theme-dark.opus-flavor-citrinitas :is(.markdown-rendered h2, .cm-s-obsidian .HyperMD-header-2) { color: #8ec07c; }
.theme-dark.opus-flavor-citrinitas :is(.markdown-rendered h3, .cm-s-obsidian .HyperMD-header-3) { color: #83a598; }
.theme-dark.opus-flavor-citrinitas :is(.markdown-rendered h4, .cm-s-obsidian .HyperMD-header-4) { color: #c98fa3; }
.theme-dark.opus-flavor-citrinitas :is(.markdown-rendered h5, .cm-s-obsidian .HyperMD-header-5) { color: #d99a5e; }
/* light — Gruvbox "faded" set, already leveled by design */
.theme-light.opus-flavor-citrinitas :is(.markdown-rendered h1, .cm-s-obsidian .HyperMD-header-1, .inline-title) { color: #b57614; }
.theme-light.opus-flavor-citrinitas :is(.markdown-rendered h2, .cm-s-obsidian .HyperMD-header-2) { color: #427b58; }
.theme-light.opus-flavor-citrinitas :is(.markdown-rendered h3, .cm-s-obsidian .HyperMD-header-3) { color: #076678; }
.theme-light.opus-flavor-citrinitas :is(.markdown-rendered h4, .cm-s-obsidian .HyperMD-header-4) { color: #8f3f71; }
.theme-light.opus-flavor-citrinitas :is(.markdown-rendered h5, .cm-s-obsidian .HyperMD-header-5) { color: #af3a03; }