Clean up public repo: remove QA/debug tooling and personal paths.

Drop launch helpers, internal reports, and outdated release doc; add
CONTRIBUTING and generic RELEASE guide. Gitignore built main.js/styles.css;
sanitize docs to use placeholder vault paths.
This commit is contained in:
bucknatt 2026-05-15 16:48:44 +02:00
parent 927667cd7d
commit 9636bb8366
19 changed files with 150 additions and 2567 deletions

26
.gitignore vendored
View file

@ -1,16 +1,18 @@
# Dependencies
obsidian-miku-plugin/node_modules/
# Build outputs (produced by npm run build; shipped via GitHub Releases)
obsidian-miku-plugin/main.js
obsidian-miku-plugin/styles.css
# Local plugin dev
obsidian-miku-plugin/.obsidian/
qa/__pycache__/
# QA / local vaults (never commit vault data)
qa/vaults/
qa/__pycache__/
qa/reports/
# Editor and OS
.idea/
*.log
.idea
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

58
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,58 @@
# Contributing
Thanks for helping improve **Miku Hybrid**. This repo contains the community plugin source plus the theme CSS that is bundled at build time.
## Prerequisites
- [Node.js](https://nodejs.org/) 18+
- [Obsidian](https://obsidian.md/) for manual testing
## Setup
```bash
git clone https://github.com/bucknatt/mitsu-plugin.git
cd mitsu-plugin
npm install --prefix ./obsidian-miku-plugin
```
## Build
From the repository root:
```bash
npm run build --prefix ./obsidian-miku-plugin
npm run typecheck --prefix ./obsidian-miku-plugin
./qa/static-guards.sh
```
`npm run build` syncs root `manifest.json` / `versions.json` into `obsidian-miku-plugin/`, bundles TypeScript to `main.js`, and merges `obsidian-miku-theme/theme.css` into `styles.css`.
## Test in a vault (local install)
```bash
mkdir -p ./qa/vaults/example-vault
./qa/install-to-vault.sh ./qa/vaults/example-vault
```
Open that folder as a vault in Obsidian, then **Settings → Community plugins → Enable** **Miku Hybrid**.
Optional legacy split-theme test (CSS only in Appearance, not bundled):
```bash
MIKU_INSTALL_SPLIT_THEME=1 ./qa/install-to-vault.sh /path/to/your-vault
```
## Release
See [docs/RELEASE.md](docs/RELEASE.md) and [docs/COMMUNITY_SUBMISSION.md](docs/COMMUNITY_SUBMISSION.md).
Catalog metadata lives at the **repository root**: `manifest.json`, `versions.json`, `LICENSE`, `README.md`.
## Repository layout
| Path | Purpose |
|------|---------|
| `manifest.json` | Community directory manifest (source of truth) |
| `obsidian-miku-plugin/` | Plugin TypeScript, widget CSS, build scripts |
| `obsidian-miku-theme/` | Theme CSS source (merged into plugin `styles.css`) |
| `qa/` | Maintainer scripts (`install-to-vault.sh`, `static-guards.sh`) |

View file

@ -2,14 +2,14 @@
Hatsune Mikuinspired theme modes, optional dashboard widgets, and bundled hybrid styling for [Obsidian](https://obsidian.md/).
## Install (Community plugins)
## Install
1. Open **Settings → Community plugins**.
2. Turn off **Restricted mode** (first-time setup).
3. Select **Browse**, search for **Miku** or **Hybrid**.
4. Install **Miku Hybrid**, then **Enable**.
No separate Appearance theme is required: release builds bundle palette CSS into the plugin `styles.css`.
No separate Appearance theme is required: releases bundle palette CSS into the plugin `styles.css`.
## Features
@ -36,25 +36,10 @@ This plugin includes no audio assets, APIs, or dependencies.
- Do not redistribute official character media without permission.
- You are responsible for compliance with applicable trademark and copyright law.
## Development (monorepo)
## Development
Source layout:
See [CONTRIBUTING.md](CONTRIBUTING.md) for build, local vault install, and release steps.
- `obsidian-miku-plugin/` — TypeScript plugin (`npm run build` merges theme CSS)
- `obsidian-miku-theme/` — Theme CSS source (bundled at build time)
- `qa/` — Install helpers and guards
## License
```bash
npm install --prefix ./obsidian-miku-plugin
npm run build --prefix ./obsidian-miku-plugin
npm run typecheck --prefix ./obsidian-miku-plugin
./qa/install-to-vault.sh /path/to/vault
```
See [qa/test-environment.md](qa/test-environment.md) for vault install and launch notes.
## Publishing
- Catalog metadata: root `manifest.json`, `versions.json`, `LICENSE`
- Release: tag `x.y.z` (no `v` prefix) matching `manifest.json` `version`; GitHub Actions attaches `main.js`, `manifest.json`, `styles.css`
- Submit: [docs/COMMUNITY_SUBMISSION.md](docs/COMMUNITY_SUBMISSION.md)
MIT — see [LICENSE](LICENSE).

View file

@ -6,7 +6,7 @@ Submit **Miku Hybrid** at [community.obsidian.md](https://community.obsidian.md/
- [ ] GitHub repo is public (`bucknatt/mitsu-plugin` or your publish repo)
- [ ] Root `manifest.json`, `LICENSE`, and marketplace `README.md` on default branch
- [ ] Push `main` and tag `1.0.0` — see [RELEASE_1.0.0.md](RELEASE_1.0.0.md)
- [ ] Push `main` and a version tag — see [RELEASE.md](RELEASE.md)
- [ ] GitHub Release **`1.0.0`** published with assets: `main.js`, `manifest.json`, `styles.css`
- [ ] Release tag **equals** `manifest.json` `version` (no `v` prefix)
- [ ] Obsidian account + GitHub account linked on community.obsidian.md

33
docs/RELEASE.md Normal file
View file

@ -0,0 +1,33 @@
# Releasing
Releases are automated via [`.github/workflows/release.yml`](../.github/workflows/release.yml).
## Steps
1. Bump `version` in root [`manifest.json`](../manifest.json) and add the entry to [`versions.json`](../versions.json).
2. Commit and push `main`.
3. Create an annotated tag matching the version exactly (no `v` prefix):
```bash
git tag -a 1.0.2 -m "1.0.2"
git push origin 1.0.2
```
4. Open the **draft** release on GitHub, verify assets, publish:
- `obsidian-miku-plugin/main.js`
- `manifest.json` (repo root)
- `obsidian-miku-plugin/styles.css`
5. Confirm the release `manifest.json` has the same `"version"` as the tag.
## Manual release
```bash
npm run build --prefix ./obsidian-miku-plugin
gh release create 1.0.2 \
obsidian-miku-plugin/main.js \
manifest.json \
obsidian-miku-plugin/styles.css \
--title 1.0.2 \
--notes "Release notes here."
```

View file

@ -1,32 +0,0 @@
# Release 1.0.0 (local tag created)
The annotated tag `1.0.0` exists locally on commit `f78c072` (or later if rebased).
## Publish to GitHub
```bash
git push origin main
git push origin 1.0.0
```
GitHub Actions (`.github/workflows/release.yml`) will create a **draft** release with:
- `obsidian-miku-plugin/main.js`
- `manifest.json` (repo root)
- `obsidian-miku-plugin/styles.css`
Edit the draft on GitHub, add release notes, then **Publish release**.
## Manual release (if Actions unavailable)
```bash
npm run build --prefix ./obsidian-miku-plugin
gh release create 1.0.0 \
obsidian-miku-plugin/main.js \
manifest.json \
obsidian-miku-plugin/styles.css \
--title 1.0.0 \
--notes "Initial Community marketplace release with bundled hybrid CSS."
```
Tag name must match `manifest.json` `version` exactly (no `v` prefix).

View file

@ -1,56 +1,39 @@
# Obsidian Miku Hybrid
# Miku Hybrid (plugin source)
Runtime plugin for the Hatsune Miku hybrid experience.
TypeScript source for the **Miku Hybrid** Obsidian community plugin.
## Features
- Dynamic mode switching: `MinimalMiku`, `Concert`, `NightNeon`, `SnowMiku`
- Status bar widget
- Optional top banner widget
- Optional quote widget with interval control
- Optional profile card widget
- Ribbon action + command for quick mode switching
- Optional **original ASCII decorations** on the dashboard (presets: Minimal, Micro, Panel, Notes, Stage, Waves, Sparkle, Ribbon) and a compact motif on the settings screen
Visual palette for the companion **Miku Theme Hybrid Base** theme is driven by [Color-Hex palette 19601](https://www.color-hex.com/color-palette/19601) (`#373b3e`, `#bec8d1`, `#86cecb`, `#137a7f`, `#e12885`) mapped into CSS variables per mode.
## No-Audio Policy
This plugin intentionally includes no audio capabilities:
- no audio assets
- no audio APIs
- no audio settings
- no audio dependencies
- Status bar, banner, quote, and profile widgets (optional)
- Ribbon action and command for quick mode switching
- Original ASCII dashboard presets
## Development
```bash
npm install
npm run dev
npm run dev # watch build
npm run build # production bundle + theme CSS merge
npm run typecheck
```
Build, then copy artifacts into a vault from the **repo root** (see [../qa/test-environment.md](../qa/test-environment.md)):
From the **repository root**, install into a local vault:
```bash
cd /home/cachy/IdeaProjects/cachy/mitsu-plugin/obsidian-miku-plugin && npm run build
cd /home/cachy/IdeaProjects/cachy/mitsu-plugin
./qa/install-to-vault.sh /home/cachy/Documents/bfh4
npm run build --prefix ./obsidian-miku-plugin
./qa/install-to-vault.sh ./qa/vaults/example-vault
```
## Build
See [../CONTRIBUTING.md](../CONTRIBUTING.md).
`npm run build` bundles `main.js`, syncs root `manifest.json` / `versions.json`, and merges `../obsidian-miku-theme/theme.css` into `styles.css`.
## Build outputs
Widget-only CSS source: `styles.widgets.css`.
- `main.js` — bundled plugin (gitignored; attached to GitHub Releases)
- `styles.css` — widget CSS + merged theme from `../obsidian-miku-theme/theme.css`
- `styles.widgets.css` — hand-maintained widget styles (source)
- `manifest.json` — synced from repo root on build
## Release Artifacts
## No-audio policy
- `main.js`
- `styles.css` (widgets + bundled theme)
- `manifest.json` (synced from repo root for catalog)
## Compatibility
- `minAppVersion`: `1.5.0`
- `isDesktopOnly`: `false`
No audio assets, APIs, settings, or dependencies.

File diff suppressed because it is too large Load diff

View file

@ -1,989 +0,0 @@
.miku-plugin-enabled .miku-status-widget {
color: var(--miku-text, #d7e3ea);
opacity: 0.9;
}
.miku-status-widget.is-hidden,
.miku-top-banner.is-hidden,
.miku-quote-widget.is-hidden,
.miku-profile-card.is-hidden {
display: none;
}
.miku-top-banner {
position: fixed;
top: 2.2rem;
left: 50%;
right: auto;
transform: translateX(-50%);
z-index: 26;
max-width: min(22rem, calc(100vw - 3rem));
padding: 0.4rem 0.8rem;
border-radius: 999px;
color: var(--miku-text, #d7e3ea);
background: color-mix(in srgb, var(--miku-secondary-dark, #181c25) 80%, transparent);
border: 1px solid color-mix(in srgb, var(--miku-accent, #39c5bb) 52%, transparent);
box-shadow:
0 0 0.75rem color-mix(in srgb, var(--miku-glow, #b5f5ee) 42%, transparent),
0 0 1.5rem color-mix(in srgb, var(--miku-pop, #e12885) 28%, transparent);
cursor: grab;
user-select: none;
}
.miku-top-banner.is-dragging {
cursor: grabbing;
}
.miku-quote-widget {
position: fixed;
right: 1rem;
left: auto;
bottom: 2.25rem;
transform: none;
max-width: min(30rem, calc(100vw - 2rem));
z-index: 30;
padding: 0.7rem 0.8rem;
border-radius: 0.8rem;
line-height: 1.4;
color: var(--miku-text, #d7e3ea);
background: color-mix(in srgb, var(--miku-bg, #0f1117) 78%, transparent);
border: 1px solid color-mix(in srgb, var(--miku-glow, #b5f5ee) 52%, transparent);
box-shadow:
0 0 1rem color-mix(in srgb, var(--miku-glow, #b5f5ee) 38%, transparent),
0 0 2rem color-mix(in srgb, var(--miku-accent, #40e0d0) 15%, transparent);
cursor: grab;
user-select: none;
}
.miku-quote-widget.is-dragging {
cursor: grabbing;
}
@media (max-width: 900px) {
.miku-top-banner {
left: 1rem;
right: 1rem;
top: 2.2rem;
transform: none;
max-width: calc(100vw - 2rem);
}
.miku-quote-widget {
left: 1rem;
right: 1rem;
bottom: 2rem;
transform: none;
max-width: none;
}
}
/* Bottom-left: keeps the top of the right split (ASCII + dashboard) readable */
.miku-profile-card {
position: fixed;
left: 1rem;
right: auto;
top: auto;
bottom: 2.25rem;
width: 15rem;
max-width: min(15rem, calc(100vw - 2rem));
z-index: 30;
display: grid;
gap: 0.35rem;
padding: 0.8rem;
border-radius: 0.9rem;
cursor: grab;
color: var(--miku-text, #d7e3ea);
background: color-mix(in srgb, var(--miku-secondary-dark, #181c25) 76%, transparent);
border: 1px solid color-mix(in srgb, var(--miku-accent, #40e0d0) 50%, transparent);
box-shadow:
0 0 0.85rem color-mix(in srgb, var(--miku-glow, #b5f5ee) 35%, transparent),
0 0 1.6rem color-mix(in srgb, var(--miku-pop, #e12885) 22%, transparent);
transition:
transform 140ms ease,
box-shadow 180ms ease;
}
.miku-profile-card.is-dragging {
cursor: grabbing;
}
.miku-profile-card:hover {
transform: translateY(-1px);
box-shadow:
0 0 1rem color-mix(in srgb, var(--miku-glow, #b5f5ee) 45%, transparent),
0 0 2rem color-mix(in srgb, var(--miku-pop, #e12885) 30%, transparent);
}
.miku-profile-card:active {
transform: translateY(0);
}
.miku-profile-card:focus-visible {
outline: 2px solid color-mix(in srgb, var(--miku-accent, #40e0d0) 85%, transparent);
outline-offset: 2px;
}
.miku-profile-card.is-compact {
width: 12rem;
padding: 0.6rem;
gap: 0.25rem;
}
.miku-profile-card h4 {
margin: 0;
font-size: 1rem;
font-weight: 700;
letter-spacing: -0.02em;
background: linear-gradient(
120deg,
var(--miku-glow, #b5f5ee),
var(--miku-accent, #40e0d0),
var(--miku-pop, #e12885)
);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.miku-profile-card p {
margin: 0;
}
.miku-profile-card__header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 0.5rem;
}
.miku-profile-badge {
flex-shrink: 0;
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
padding: 0.15rem 0.42rem;
border-radius: 999px;
color: color-mix(in srgb, var(--miku-secondary-dark, #181c25) 12%, var(--miku-text, #d7e3ea));
background: color-mix(in srgb, var(--miku-accent, #40e0d0) 38%, transparent);
border: 1px solid color-mix(in srgb, var(--miku-pop, #e12885) 45%, transparent);
box-shadow: 0 0 10px color-mix(in srgb, var(--miku-glow, #b5f5ee) 28%, transparent);
}
.miku-profile-card__subtitle {
font-size: 0.78rem;
line-height: 1.35;
opacity: 0.92;
color: color-mix(in srgb, var(--miku-text, #d7e3ea) 88%, var(--miku-glow, #b5f5ee));
}
.miku-profile-card__chips {
display: flex;
flex-wrap: wrap;
gap: 0.28rem;
margin-top: 0.1rem;
}
.miku-profile-chip {
font-size: 0.65rem;
line-height: 1.2;
padding: 0.18rem 0.42rem;
border-radius: 0.42rem;
color: color-mix(in srgb, var(--miku-text, #d7e3ea) 92%, transparent);
background: color-mix(in srgb, var(--miku-secondary-dark, #181c25) 45%, transparent);
border: 1px solid color-mix(in srgb, var(--miku-glow, #b5f5ee) 28%, transparent);
}
.miku-profile-chip--glow {
border-color: color-mix(in srgb, var(--miku-pop, #e12885) 42%, transparent);
box-shadow: 0 0 8px color-mix(in srgb, var(--miku-glow, #b5f5ee) 22%, transparent);
}
.miku-profile-card__divider {
height: 1px;
margin: 0.05rem 0;
border-radius: 1px;
background: linear-gradient(
90deg,
transparent,
color-mix(in srgb, var(--miku-accent, #40e0d0) 55%, transparent),
color-mix(in srgb, var(--miku-pop, #e12885) 40%, transparent),
transparent
);
}
.miku-profile-card__tip {
font-size: 0.72rem;
line-height: 1.35;
font-style: italic;
opacity: 0.88;
}
.miku-profile-card__foot {
display: flex;
align-items: center;
gap: 0.45rem;
margin-top: 0.05rem;
padding-top: 0.2rem;
}
.miku-profile-card__cta {
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.03em;
color: color-mix(in srgb, var(--miku-glow, #b5f5ee) 82%, var(--text-accent, #fee440));
}
@media (prefers-reduced-motion: reduce) {
.miku-profile-card {
transition: none;
}
.miku-profile-card:hover {
transform: none;
}
}
.miku-profile-icon {
margin-top: 0.2rem;
color: var(--miku-glow, #b5f5ee);
opacity: 0.95;
filter: drop-shadow(0 0 0.35rem color-mix(in srgb, var(--miku-glow, #b5f5ee) 65%, transparent))
drop-shadow(0 0 0.85rem color-mix(in srgb, var(--miku-pop, #e12885) 35%, transparent));
}
.miku-dashboard {
padding: 1rem;
color: var(--miku-text, #d7e3ea);
}
.miku-dashboard.is-compact {
padding: 0.7rem;
}
.miku-dashboard-actions {
display: flex;
gap: 0.5rem;
margin: 0.8rem 0;
}
.miku-dashboard-actions button {
border-radius: 0.6rem;
border: 1px solid color-mix(in srgb, var(--miku-accent, #40e0d0) 58%, transparent);
box-shadow:
0 0 0 1px color-mix(in srgb, var(--miku-pop, #e12885) 42%, transparent),
0 0 22px color-mix(in srgb, var(--text-accent, #fee440) 32%, transparent),
0 0 42px color-mix(in srgb, var(--miku-glow, #b5f5ee) 22%, transparent);
padding: 0.35rem 0.7rem;
}
.miku-progress {
margin-top: 0.8rem;
padding: 0.7rem;
border-radius: 0.8rem;
background: color-mix(in srgb, var(--miku-secondary-dark, #181c25) 80%, transparent);
}
.miku-checklist-actions {
display: flex;
gap: 0.45rem;
margin: 0.5rem 0 0.35rem;
}
.miku-checklist-input {
min-width: 0;
flex: 1;
border-radius: 0.45rem;
padding: 0.22rem 0.45rem;
}
.miku-checklist-actions button {
border-radius: 0.45rem;
padding: 0.2rem 0.45rem;
border: 1px solid color-mix(in srgb, var(--miku-accent, #40e0d0) 42%, transparent);
background: color-mix(in srgb, var(--miku-bg, #0f1117) 70%, transparent);
}
.miku-checklist {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0.35rem;
}
.miku-checklist-item {
display: flex;
align-items: center;
}
.miku-checklist-label {
display: flex;
align-items: center;
gap: 0.45rem;
font-size: 0.86rem;
}
.miku-checklist-item.is-done span {
text-decoration: line-through;
opacity: 0.72;
}
.miku-checklist-empty {
font-size: 0.8rem;
opacity: 0.78;
}
.miku-equalizer {
margin-top: 1rem;
height: 2rem;
display: flex;
gap: 0.25rem;
align-items: end;
}
.miku-eq-bar {
width: 0.35rem;
height: 100%;
border-radius: 999px;
background: color-mix(in srgb, var(--miku-accent, #40e0d0) 82%, var(--miku-glow, #b5f5ee));
box-shadow: 0 0 0.55rem color-mix(in srgb, var(--miku-glow, #b5f5ee) 55%, transparent);
animation: mikuEq 1.1s ease-in-out infinite;
}
.miku-eq-bar:nth-child(2n) {
animation-duration: 0.9s;
}
.miku-eq-bar:nth-child(3n) {
animation-duration: 1.3s;
}
@keyframes mikuEq {
0%,
100% {
transform: scaleY(0.35);
}
50% {
transform: scaleY(0.95);
}
}
/* Original ASCII motifs — bundled theme supplies --miku-* when plugin is enabled */
.miku-ascii {
margin: 0 0 0.75rem;
padding: 0.5rem 0.75rem;
overflow-x: auto;
font-family: var(--font-monospace-theme), ui-monospace, "IBM Plex Mono", monospace;
font-size: 0.72rem;
line-height: 1.25;
color: var(--miku-accent, #86cecb);
text-shadow:
0 0 0.45rem color-mix(in srgb, var(--miku-glow, #b5f5ee) 62%, transparent),
0 0 1.1rem color-mix(in srgb, var(--miku-pop, #e12885) 45%, transparent);
background: color-mix(in srgb, var(--miku-secondary-dark, #1e2329) 55%, transparent);
border: 1px solid color-mix(in srgb, var(--miku-pop, #e12885) 45%, var(--miku-accent, #86cecb));
box-shadow:
0 0 1.1rem color-mix(in srgb, var(--miku-glow, #b5f5ee) 28%, transparent),
inset 0 0 1.25rem color-mix(in srgb, var(--miku-accent, #40e0d0) 12%, transparent);
border-radius: 0.65rem;
}
.miku-dashboard.is-compact .miku-ascii {
font-size: 0.62rem;
padding: 0.35rem 0.55rem;
margin-bottom: 0.5rem;
}
.miku-ascii--settings {
font-size: 0.68rem;
margin-bottom: 1rem;
}
/* --- Miku hybrid theme (bundled from obsidian-miku-theme/theme.css) --- */
/* Combined theme for distribution. Mirrors src/colors.css + chrome / editor rules. */
/*
* Base: Color-Hex palette 19601 (gmbl777) https://www.color-hex.com/color-palette/19601
* #373b3e #bec8d1 #86cecb #137a7f #e12885
*
* Extended pop / decora-style hues (approximate-inspired accent range; UI-tuned):
* teal/cyan hot #00e5ff · #40e0d0 · #00afb9 · pinks/magenta · sun yellow · violet · sky · plum
*/
:root {
--palette-19601-1: #373b3e;
--palette-19601-2: #bec8d1;
--palette-19601-3: #86cecb;
--palette-19601-4: #137a7f;
--palette-19601-5: #e12885;
--palette-pop-cyan: #40e0d0;
--palette-pop-cyan-hot: #00e5ff;
--palette-pop-teal-deep: #00afb9;
--palette-pop-pink-hot: #ff007f;
--palette-pop-pink-soft: #ff66cc;
--palette-pop-pink-muted: #f15bb5;
--palette-pop-sun: #fee440;
--palette-pop-sun-hot: #ffff00;
--palette-pop-violet: #9b5de5;
--palette-pop-plum: #540d6e;
--palette-pop-sky: #00bbf9;
--palette-pop-navy: #0081a7;
--miku-cyan-neon: color-mix(in srgb, var(--palette-pop-cyan-hot) 55%, var(--palette-19601-3));
--miku-hot-pink: color-mix(in srgb, var(--palette-pop-pink-hot) 35%, var(--palette-19601-5));
--miku-sun: color-mix(in srgb, var(--palette-pop-sun) 70%, white);
--miku-violet-bright: color-mix(in srgb, var(--palette-pop-violet) 80%, white);
--miku-plum-soft: color-mix(in srgb, var(--palette-pop-plum) 25%, var(--palette-19601-1));
--miku-accent: var(--palette-pop-cyan);
--miku-deep-teal: var(--palette-19601-4);
--miku-bg: #12151a;
--miku-secondary-dark: color-mix(in srgb, var(--miku-plum-soft) 18%, #1e2329);
--miku-glow: color-mix(in srgb, var(--palette-pop-cyan-hot) 42%, white);
--miku-text: var(--palette-19601-2);
--miku-pop: var(--miku-hot-pink);
--miku-accent-warm: var(--palette-pop-pink-soft);
--miku-muted: color-mix(in srgb, var(--palette-19601-2) 55%, var(--palette-19601-1));
--miku-border-subtle: color-mix(in srgb, var(--palette-pop-violet) 22%, var(--palette-19601-1));
--miku-gradient-surface: linear-gradient(
148deg,
color-mix(in srgb, var(--palette-pop-cyan-hot) 11%, var(--miku-bg)) 0%,
var(--miku-bg) 32%,
color-mix(in srgb, var(--palette-pop-violet) 9%, var(--miku-bg)) 58%,
color-mix(in srgb, var(--palette-pop-pink-muted) 7%, var(--miku-bg)) 82%,
color-mix(in srgb, var(--palette-pop-sun) 5%, var(--miku-bg)) 100%
);
--miku-tab-rail: color-mix(in srgb, var(--palette-pop-cyan-hot) 16%, transparent);
--miku-glow-intensity: 0.82;
--miku-link-hover: color-mix(in srgb, var(--miku-hot-pink) 50%, var(--palette-pop-sky));
--miku-heading-1: color-mix(in srgb, var(--palette-pop-cyan-hot) 75%, white);
--miku-heading-2: color-mix(in srgb, var(--palette-pop-violet) 82%, white);
--miku-heading-3: color-mix(in srgb, var(--palette-pop-sun) 45%, var(--miku-text));
--miku-heading-4: color-mix(in srgb, var(--palette-pop-sky) 58%, var(--miku-text));
--miku-heading-5: color-mix(in srgb, var(--palette-pop-pink-muted) 52%, var(--miku-text));
--miku-heading-6: color-mix(in srgb, var(--palette-pop-plum) 32%, var(--miku-text));
}
body[data-miku-theme="MinimalMiku"] {
--miku-accent: color-mix(in srgb, var(--palette-19601-3) 40%, var(--palette-pop-cyan));
--miku-deep-teal: var(--palette-19601-4);
--miku-bg: #12151a;
--miku-secondary-dark: color-mix(in srgb, var(--miku-plum-soft) 15%, #1f252c);
--miku-glow: color-mix(in srgb, var(--palette-pop-cyan-hot) 35%, white);
--miku-text: var(--palette-19601-2);
--miku-pop: var(--miku-hot-pink);
--miku-accent-warm: var(--palette-pop-pink-soft);
--miku-heading-3: color-mix(in srgb, var(--palette-pop-sun) 38%, var(--miku-text));
--miku-heading-4: color-mix(in srgb, var(--palette-pop-sky) 44%, var(--miku-text));
--miku-heading-5: color-mix(in srgb, var(--palette-19601-5) 42%, var(--miku-text));
--miku-heading-6: color-mix(in srgb, var(--palette-pop-violet) 36%, var(--miku-muted));
--miku-border-subtle: color-mix(in srgb, var(--palette-19601-3) 28%, var(--palette-pop-violet));
--miku-gradient-surface: linear-gradient(
148deg,
color-mix(in srgb, var(--palette-pop-cyan-hot) 13%, var(--miku-bg)) 0%,
var(--miku-bg) 38%,
color-mix(in srgb, var(--palette-pop-violet) 10%, var(--miku-bg)) 62%,
color-mix(in srgb, var(--palette-19601-5) 10%, var(--miku-bg)) 88%,
color-mix(in srgb, var(--palette-pop-sun) 6%, var(--miku-bg)) 100%
);
--miku-tab-rail: color-mix(in srgb, var(--palette-pop-cyan-hot) 20%, transparent);
}
body[data-miku-theme="Concert"] {
--miku-accent: color-mix(in srgb, var(--palette-pop-cyan-hot) 72%, white);
--miku-deep-teal: color-mix(in srgb, var(--palette-pop-teal-deep) 55%, var(--palette-19601-4));
--miku-bg: #0f141c;
--miku-secondary-dark: #171f2e;
--miku-glow: #e8fff8;
--miku-text: #eef4fc;
--miku-pop: #ff62c6;
--miku-accent-warm: color-mix(in srgb, var(--palette-pop-pink-hot) 52%, white);
--miku-muted: color-mix(in srgb, var(--miku-text) 58%, var(--palette-pop-plum));
--miku-border-subtle: color-mix(in srgb, var(--palette-pop-violet) 38%, var(--palette-19601-1));
--miku-gradient-surface: linear-gradient(
158deg,
color-mix(in srgb, var(--palette-pop-cyan-hot) 22%, var(--miku-bg)) 0%,
color-mix(in srgb, var(--palette-pop-pink-soft) 6%, var(--miku-bg)) 28%,
var(--miku-bg) 48%,
color-mix(in srgb, var(--palette-pop-sun-hot) 7%, var(--miku-bg)) 72%,
color-mix(in srgb, var(--palette-pop-violet) 14%, var(--miku-bg)) 100%
);
--miku-tab-rail: color-mix(in srgb, var(--palette-pop-pink-soft) 32%, transparent);
--miku-heading-1: color-mix(in srgb, var(--palette-pop-cyan-hot) 88%, white);
--miku-heading-2: color-mix(in srgb, var(--palette-pop-violet) 88%, white);
--miku-heading-3: color-mix(in srgb, var(--palette-pop-sun-hot) 42%, white);
--miku-heading-4: color-mix(in srgb, var(--palette-pop-sky) 82%, white);
--miku-heading-5: color-mix(in srgb, var(--palette-pop-pink-soft) 76%, white);
--miku-heading-6: color-mix(in srgb, var(--palette-pop-plum) 58%, white);
}
body[data-miku-theme="NightNeon"] {
--miku-accent: color-mix(in srgb, var(--palette-pop-cyan-hot) 48%, var(--palette-pop-teal-deep));
--miku-deep-teal: #065a61;
--miku-bg: #0b0e14;
--miku-secondary-dark: color-mix(in srgb, var(--palette-pop-plum) 22%, #141a22);
--miku-glow: color-mix(in srgb, var(--palette-pop-cyan-hot) 65%, cyan);
--miku-text: #d6e6f8;
--miku-pop: color-mix(in srgb, var(--palette-pop-pink-hot) 72%, magenta);
--miku-accent-warm: var(--palette-pop-pink-soft);
--miku-muted: color-mix(in srgb, var(--miku-text) 45%, var(--palette-pop-plum));
--miku-border-subtle: color-mix(in srgb, var(--palette-pop-violet) 45%, black);
--miku-gradient-surface: radial-gradient(
120% 80% at 50% -20%,
color-mix(in srgb, var(--palette-pop-cyan-hot) 14%, transparent),
transparent 55%
),
linear-gradient(
172deg,
var(--miku-bg) 0%,
color-mix(in srgb, var(--palette-pop-plum) 35%, var(--miku-bg)) 45%,
color-mix(in srgb, var(--palette-pop-pink-hot) 9%, var(--miku-bg)) 100%
);
--miku-tab-rail: color-mix(in srgb, var(--palette-pop-cyan-hot) 30%, transparent);
--miku-heading-1: color-mix(in srgb, var(--palette-pop-cyan-hot) 70%, white);
--miku-heading-2: color-mix(in srgb, var(--palette-pop-violet) 75%, white);
--miku-heading-3: color-mix(in srgb, var(--miku-pop) 52%, white);
--miku-heading-4: color-mix(in srgb, var(--palette-pop-cyan-hot) 64%, white);
--miku-heading-5: color-mix(in srgb, var(--palette-pop-pink-hot) 62%, white);
--miku-heading-6: color-mix(in srgb, var(--palette-pop-violet) 72%, white);
}
body[data-miku-theme="SnowMiku"] {
--miku-accent: color-mix(in srgb, #d4fffb 58%, var(--palette-pop-teal-deep));
--miku-deep-teal: color-mix(in srgb, var(--palette-pop-teal-deep) 30%, white);
--miku-bg: #1e2830;
--miku-secondary-dark: #2a3540;
--miku-glow: color-mix(in srgb, var(--palette-pop-cyan) 45%, white);
--miku-text: color-mix(in srgb, var(--palette-pop-sky) 8%, white);
--miku-pop: color-mix(in srgb, var(--palette-pop-pink-soft) 65%, pink);
--miku-accent-warm: color-mix(in srgb, var(--palette-pop-sun) 22%, white);
--miku-muted: color-mix(in srgb, var(--miku-text) 52%, var(--palette-pop-navy));
--miku-border-subtle: color-mix(in srgb, var(--palette-19601-2) 32%, var(--palette-pop-violet));
--miku-gradient-surface: linear-gradient(
158deg,
color-mix(in srgb, var(--palette-pop-sky) 12%, var(--miku-bg)) 0%,
var(--miku-bg) 40%,
color-mix(in srgb, white 10%, color-mix(in srgb, var(--palette-pop-pink-soft) 12%, var(--miku-bg))) 70%,
color-mix(in srgb, var(--palette-pop-sun) 8%, var(--miku-bg)) 100%
);
--miku-tab-rail: color-mix(in srgb, var(--palette-pop-sky) 24%, transparent);
--miku-heading-1: color-mix(in srgb, var(--palette-pop-cyan) 88%, white);
--miku-heading-2: color-mix(in srgb, var(--palette-pop-violet) 55%, white);
--miku-heading-3: color-mix(in srgb, var(--palette-pop-sun) 35%, white);
--miku-heading-4: color-mix(in srgb, var(--palette-pop-sky) 68%, white);
--miku-heading-5: color-mix(in srgb, var(--palette-pop-pink-soft) 62%, white);
--miku-heading-6: color-mix(in srgb, var(--palette-pop-plum) 40%, white);
}
body.miku-plugin-enabled {
--font-text-theme: "Inter", "Noto Sans", sans-serif;
--font-interface-theme: "Inter", "Noto Sans", sans-serif;
--line-height-normal: 1.7;
--background-primary: var(--miku-bg);
--background-secondary: var(--miku-secondary-dark);
--background-modifier-border: var(--miku-border-subtle);
--interactive-accent: var(--miku-accent);
--interactive-accent-hover: color-mix(in srgb, var(--miku-pop) 62%, var(--miku-accent));
--background-modifier-active: color-mix(in srgb, var(--miku-accent) 22%, transparent);
--text-normal: var(--miku-text);
--text-muted: var(--miku-muted);
--text-faint: color-mix(in srgb, var(--miku-muted) 85%, transparent);
--icon-color: color-mix(in srgb, var(--miku-accent) 72%, var(--miku-text));
--text-accent: var(--miku-sun);
--text-on-accent: #1a1620;
--link-color: color-mix(in srgb, var(--palette-pop-sky) 55%, white);
--link-color-hover: var(--miku-link-hover);
--miku-file-title: color-mix(
in srgb,
color-mix(in srgb, var(--miku-accent) 38%, var(--miku-pop)) 74%,
var(--miku-text)
);
--blockquote-border-color: color-mix(in srgb, var(--miku-accent-warm) 22%, transparent);
--italic-color: color-mix(
in srgb,
var(--palette-pop-sky) 38%,
color-mix(in srgb, var(--miku-accent-warm) 42%, var(--miku-text))
);
--bold-color: color-mix(
in srgb,
var(--palette-pop-pink-soft) 44%,
color-mix(in srgb, var(--miku-heading-2) 52%, white)
);
--miku-code-inline: color-mix(
in srgb,
var(--palette-pop-cyan-hot) 42%,
color-mix(in srgb, var(--miku-glow) 35%, var(--miku-text))
);
--miku-code-inline-bg: color-mix(
in srgb,
var(--palette-pop-violet) 22%,
var(--miku-secondary-dark)
);
--miku-code-block-fg: color-mix(
in srgb,
var(--miku-text) 72%,
color-mix(in srgb, var(--palette-pop-cyan) 48%, white)
);
--miku-code-block-bg: color-mix(
in srgb,
var(--palette-pop-plum) 16%,
color-mix(in srgb, var(--miku-bg) 92%, var(--miku-deep-teal))
);
--miku-code-block-border: color-mix(
in srgb,
var(--palette-pop-cyan-hot) 32%,
transparent
);
--code-normal: var(--miku-code-inline);
}
body.miku-plugin-enabled .markdown-preview-view {
color: var(--miku-text);
line-height: 1.8;
}
body.miku-plugin-enabled .markdown-preview-view h1,
body.miku-plugin-enabled .markdown-rendered h1 {
color: var(--miku-heading-1);
}
body.miku-plugin-enabled .markdown-preview-view h2,
body.miku-plugin-enabled .markdown-rendered h2 {
color: var(--miku-heading-2);
}
body.miku-plugin-enabled .markdown-preview-view h3,
body.miku-plugin-enabled .markdown-rendered h3 {
color: var(--miku-heading-3);
}
body.miku-plugin-enabled .markdown-preview-view h4,
body.miku-plugin-enabled .markdown-rendered h4 {
color: var(--miku-heading-4);
}
body.miku-plugin-enabled .markdown-preview-view h5,
body.miku-plugin-enabled .markdown-rendered h5 {
color: var(--miku-heading-5);
}
body.miku-plugin-enabled .markdown-preview-view h6,
body.miku-plugin-enabled .markdown-rendered h6 {
color: var(--miku-heading-6);
}
body.miku-plugin-enabled .markdown-preview-view em,
body.miku-plugin-enabled .markdown-rendered em {
color: var(--italic-color);
}
body.miku-plugin-enabled .markdown-preview-view strong,
body.miku-plugin-enabled .markdown-rendered strong {
color: var(--bold-color);
}
body.miku-plugin-enabled .markdown-preview-view :not(pre) > code,
body.miku-plugin-enabled .markdown-rendered :not(pre) > code {
color: var(--miku-code-inline);
background-color: var(--miku-code-inline-bg);
padding: 0.12em 0.38em;
border-radius: 0.35rem;
border: 1px solid color-mix(in srgb, var(--palette-pop-cyan-hot) 22%, transparent);
box-shadow: 0 0 0.55rem color-mix(in srgb, var(--miku-glow) 12%, transparent);
}
body.miku-plugin-enabled .markdown-preview-view pre,
body.miku-plugin-enabled .markdown-rendered pre {
color: var(--miku-code-block-fg);
background-color: var(--miku-code-block-bg);
border: 1px solid var(--miku-code-block-border);
border-radius: 0.55rem;
box-shadow:
0 0 0 1px color-mix(in srgb, var(--palette-pop-pink-muted) 12%, transparent),
0 0 1.1rem color-mix(in srgb, var(--palette-pop-violet) 10%, transparent);
}
body.miku-plugin-enabled .markdown-preview-view pre code,
body.miku-plugin-enabled .markdown-rendered pre code {
color: inherit;
background-color: transparent;
border: none;
padding: 0;
box-shadow: none;
}
body.miku-plugin-enabled .cm-content .cm-header-1,
body.miku-plugin-enabled .HyperMD-header-1 {
color: var(--miku-heading-1);
}
body.miku-plugin-enabled .cm-content .cm-header-2,
body.miku-plugin-enabled .HyperMD-header-2 {
color: var(--miku-heading-2);
}
body.miku-plugin-enabled .cm-content .cm-header-3,
body.miku-plugin-enabled .HyperMD-header-3 {
color: var(--miku-heading-3);
}
body.miku-plugin-enabled .cm-content .cm-header-4,
body.miku-plugin-enabled .HyperMD-header-4 {
color: var(--miku-heading-4);
}
body.miku-plugin-enabled .cm-content .cm-header-5,
body.miku-plugin-enabled .HyperMD-header-5 {
color: var(--miku-heading-5);
}
body.miku-plugin-enabled .cm-content .cm-header-6,
body.miku-plugin-enabled .HyperMD-header-6 {
color: var(--miku-heading-6);
}
body.miku-plugin-enabled .cm-content .cm-strong {
color: var(--bold-color);
}
body.miku-plugin-enabled .cm-content .cm-em {
color: var(--italic-color);
}
body.miku-plugin-enabled .cm-content .cm-inline-code {
color: var(--miku-code-inline);
background-color: var(--miku-code-inline-bg);
border-radius: 0.32rem;
padding: 0.05em 0.26em;
border: 1px solid color-mix(in srgb, var(--palette-pop-cyan-hot) 26%, transparent);
box-shadow: 0 0 0.5rem color-mix(in srgb, var(--miku-glow) 10%, transparent);
}
body.miku-plugin-enabled .markdown-source-view.mod-cm6 .HyperMD-codeblock {
color: var(--miku-code-block-fg);
background-color: color-mix(
in srgb,
var(--miku-code-block-bg) 88%,
var(--background-primary)
);
border-left: 3px solid color-mix(in srgb, var(--palette-pop-pink-soft) 45%, transparent);
border-radius: 0 0.4rem 0.4rem 0;
}
body.miku-plugin-enabled .markdown-source-view.mod-cm6 .HyperMD-codeblock-begin {
border-top-left-radius: 0.4rem;
border-top-right-radius: 0.4rem;
}
body.miku-plugin-enabled .markdown-source-view.mod-cm6 .HyperMD-codeblock-end {
border-bottom-left-radius: 0.4rem;
border-bottom-right-radius: 0.4rem;
}
body.miku-plugin-enabled .cm-content span.cm-highlight {
background-color: color-mix(in srgb, var(--palette-pop-sun) 35%, transparent);
color: color-mix(in srgb, var(--miku-text) 92%, black);
}
body.miku-plugin-enabled .markdown-preview-view mark {
background-color: color-mix(in srgb, var(--palette-pop-sun-hot) 22%, transparent);
color: var(--text-normal);
}
body.miku-plugin-enabled .markdown-preview-view hr {
border-color: color-mix(in srgb, var(--palette-pop-violet) 35%, transparent);
border-top-color: color-mix(in srgb, var(--miku-accent) 40%, transparent);
opacity: 0.85;
}
body.miku-plugin-enabled .markdown-preview-view a,
body.miku-plugin-enabled .cm-underline.cm-hmd-internal-link.cm-link,
body.miku-plugin-enabled .cm-underline.cm-link {
color: var(--link-color);
}
body.miku-plugin-enabled .markdown-preview-view .internal-link:hover,
body.miku-plugin-enabled .cm-link:hover {
color: var(--link-color-hover);
}
body.miku-plugin-enabled .markdown-rendered .tag {
border-width: 1px;
}
body.miku-plugin-enabled .markdown-rendered .tag:nth-of-type(3n + 1) {
background-color: color-mix(in srgb, var(--miku-accent) 18%, transparent);
border-color: color-mix(in srgb, var(--palette-pop-cyan-hot) 50%, transparent);
color: color-mix(in srgb, var(--miku-heading-1) 55%, white);
}
body.miku-plugin-enabled .markdown-rendered .tag:nth-of-type(3n + 2) {
background-color: color-mix(in srgb, var(--miku-pop) 22%, transparent);
border-color: color-mix(in srgb, var(--palette-pop-pink-soft) 45%, transparent);
color: color-mix(in srgb, white 82%, var(--miku-pop));
}
body.miku-plugin-enabled .markdown-rendered .tag:nth-of-type(3n + 3) {
background-color: color-mix(in srgb, var(--palette-pop-violet) 26%, transparent);
border-color: color-mix(in srgb, var(--miku-violet-bright) 45%, transparent);
color: color-mix(in srgb, white 88%, var(--palette-pop-violet));
}
body.miku-plugin-enabled .cm-content {
line-height: 1.7;
}
@keyframes mikuGlowPulse {
0%,
100% {
box-shadow: 0 0 0.65rem color-mix(in srgb, var(--miku-glow) 52%, transparent),
0 0 1.35rem color-mix(in srgb, var(--miku-pop) 22%, transparent),
0 0 2rem color-mix(in srgb, var(--miku-glow) 12%, transparent);
}
50% {
box-shadow: 0 0 1.05rem color-mix(in srgb, var(--miku-glow) 68%, transparent),
0 0 1.75rem color-mix(in srgb, var(--miku-pop) 38%, transparent),
0 0 2.6rem color-mix(in srgb, var(--miku-accent) 18%, transparent);
}
}
body.miku-plugin-enabled[data-miku-reduced-motion="true"] * {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
body.miku-plugin-enabled .workspace {
background: var(--miku-gradient-surface);
}
body.miku-plugin-enabled .workspace-split.mod-root .workspace-tab-header-container {
border-bottom: 1px solid var(--miku-tab-rail);
box-shadow: 0 1px 0 color-mix(in srgb, var(--miku-pop) 14%, transparent),
0 8px 24px color-mix(in srgb, var(--palette-pop-violet) 6%, transparent);
}
body.miku-plugin-enabled .workspace-tab-header.is-active .workspace-tab-header-inner-title {
color: var(--miku-file-title);
text-shadow:
0 0 0.55rem color-mix(in srgb, var(--miku-pop) 48%, transparent),
0 0 1rem color-mix(in srgb, var(--miku-glow) 26%, transparent);
}
body.miku-plugin-enabled .workspace-tab-header .workspace-tab-header-inner-title,
body.miku-plugin-enabled .nav-file-title-content,
body.miku-plugin-enabled .view-header-title {
color: var(--miku-file-title);
}
body.miku-plugin-enabled .workspace-tab-header.is-active {
box-shadow: 0 -2px 0 0 color-mix(in srgb, var(--miku-accent) 75%, transparent) inset,
0 -4px 0 0 color-mix(in srgb, var(--miku-pop) 35%, transparent) inset,
0 -7px 0 0 color-mix(in srgb, var(--miku-sun) 28%, transparent) inset;
}
body.miku-plugin-enabled .nav-folder-title:hover,
body.miku-plugin-enabled .vertical-tab-nav-item:hover {
background-color: color-mix(in srgb, var(--miku-accent) 10%, transparent);
}
body.miku-plugin-enabled .nav-folder .nav-folder-title-content {
font-weight: 600;
}
body.miku-plugin-enabled .nav-folder:nth-of-type(6n + 1) > .nav-folder-title .nav-folder-title-content {
color: color-mix(in srgb, var(--palette-pop-cyan-hot) 74%, var(--miku-text));
}
body.miku-plugin-enabled .nav-folder:nth-of-type(6n + 2) > .nav-folder-title .nav-folder-title-content {
color: color-mix(in srgb, var(--palette-pop-sky) 72%, var(--miku-text));
}
body.miku-plugin-enabled .nav-folder:nth-of-type(6n + 3) > .nav-folder-title .nav-folder-title-content {
color: color-mix(in srgb, var(--palette-pop-violet) 66%, var(--miku-text));
}
body.miku-plugin-enabled .nav-folder:nth-of-type(6n + 4) > .nav-folder-title .nav-folder-title-content {
color: color-mix(in srgb, var(--palette-pop-pink-soft) 64%, var(--miku-text));
}
body.miku-plugin-enabled .nav-folder:nth-of-type(6n + 5) > .nav-folder-title .nav-folder-title-content {
color: color-mix(in srgb, var(--palette-pop-sun) 56%, var(--miku-text));
}
body.miku-plugin-enabled .nav-folder:nth-of-type(6n + 6) > .nav-folder-title .nav-folder-title-content {
color: color-mix(in srgb, var(--palette-pop-teal-deep) 64%, var(--miku-text));
}
body.miku-plugin-enabled textarea,
body.miku-plugin-enabled input[type="text"],
body.miku-plugin-enabled .dropdown {
border-color: var(--miku-border-subtle);
}
body.miku-plugin-enabled .workspace-split.mod-root .workspace-tab-header-container,
body.miku-plugin-enabled .workspace-sidedock {
backdrop-filter: blur(14px);
background: linear-gradient(
178deg,
color-mix(in srgb, var(--miku-secondary-dark) 86%, transparent) 0%,
color-mix(in srgb, var(--miku-accent) 3%, transparent) 100%
);
}
body.miku-plugin-enabled .workspace-tab-header.is-active,
body.miku-plugin-enabled .clickable-icon:hover,
body.miku-plugin-enabled .nav-file-title:hover {
animation: mikuGlowPulse 2.6s ease-in-out infinite;
}
body.miku-plugin-enabled .markdown-preview-view .callout {
border-radius: 0.8rem;
border-style: solid;
border-width: 1px;
border-color: color-mix(in srgb, var(--miku-accent) 45%, var(--palette-pop-violet));
border-left-width: 4px;
border-left-color: var(--miku-accent-warm);
background: linear-gradient(
145deg,
color-mix(in srgb, var(--miku-secondary-dark) 88%, transparent) 0%,
color-mix(in srgb, var(--palette-pop-violet) 5%, var(--miku-secondary-dark)) 100%
);
box-shadow: 2px 0 0 color-mix(in srgb, var(--palette-pop-cyan-hot) 18%, transparent) inset,
-1px 0 0 color-mix(in srgb, var(--miku-pop) 12%, transparent) inset;
}
body.miku-plugin-enabled input[type="checkbox"]:checked {
background-color: var(--miku-accent);
border-color: color-mix(in srgb, var(--palette-pop-violet) 42%, transparent);
}
body.miku-plugin-enabled .graph-view.color-fill-highlight {
color: color-mix(in srgb, var(--miku-glow) calc(var(--miku-glow-intensity, 0.55) * 100%), var(--miku-pop));
}
body.miku-plugin-enabled .graph-view.color-line {
color: color-mix(in srgb, var(--miku-accent) 45%, var(--palette-pop-violet));
}
body.miku-plugin-enabled .graph-view.graph-controls .graph-expand-button svg {
color: color-mix(in srgb, var(--miku-accent) 60%, white);
}

View file

@ -1,29 +1,20 @@
# Obsidian Miku Theme Hybrid Base
# Theme CSS source (contributors)
Visual companion theme for the Miku plugin hybrid architecture.
Palette and workspace styling for **Miku Hybrid**. This folder is **not** a separate Community install for end users.
## Includes
Release builds merge [`theme.css`](theme.css) into the plugins `styles.css` via `obsidian-miku-plugin/scripts/merge-styles.mjs`.
- Core palette tokens: Color-Hex [19601](https://www.color-hex.com/color-palette/19601) plus extended **pop** hues (cyan `#00e5ff`/`#40e0d0`, hot pink `#ff007f`, sun `#fee440`, violet `#9b5de5`, plum `#540d6e`, sky `#00bbf9`) mapped into gradients, headings, tags, tabs, and callouts
- Typography tuning
- Glass-like sidebar and panel styling
- Subtle glow animation
- Callout and checkbox accents
- Graph view color treatment
## Editing
## Theme Modes
- Source modules: `src/*.css`
- Shipped bundle: `theme.css` (keep in sync when editing `src/`)
The theme reacts to `body[data-miku-theme]` values set by the plugin:
## Theme modes
- `MinimalMiku`
- `Concert`
- `NightNeon`
- `SnowMiku`
CSS reacts to `body[data-miku-theme]` set by the plugin:
## No-Audio Policy
- `MinimalMiku`, `Concert`, `NightNeon`, `SnowMiku`
This theme package contains no audio assets or audio-related functionality.
## No-audio policy
## Packaging
Ship `theme.css` and `manifest.json` for releases.
No audio assets or audio-related functionality.

6
package-lock.json generated
View file

@ -1,6 +0,0 @@
{
"name": "mitsu-plugin",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View file

@ -2,22 +2,21 @@
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VAULT_PATH="${1:-$ROOT_DIR/qa/vaults/clean-desktop}"
VAULT_PATH="${1:-$ROOT_DIR/qa/vaults/example-vault}"
PLUGIN_SRC="$ROOT_DIR/obsidian-miku-plugin"
PLUGIN_DST="$VAULT_PATH/.obsidian/plugins/miku-plugin-hybrid"
THEME_DST_DIR="$VAULT_PATH/.obsidian/themes"
THEME_DST_FILE="$THEME_DST_DIR/Miku Theme Hybrid Base.css"
ANCHOR_NOTE="$VAULT_PATH/README.md"
INSTALL_SPLIT_THEME="${MIKU_INSTALL_SPLIT_THEME:-0}"
mkdir -p "$PLUGIN_DST"
if [[ ! -f "$ANCHOR_NOTE" ]]; then
echo "# QA vault anchor" >>"$ANCHOR_NOTE"
echo "This file lets \`qa/launch-obsidian.sh\` open this folder via obsidian:// (see qa/test-environment.md)." >>"$ANCHOR_NOTE"
if [[ ! -f "$PLUGIN_SRC/main.js" || ! -f "$PLUGIN_SRC/styles.css" ]]; then
echo "Missing build artifacts. Run: npm run build --prefix ./obsidian-miku-plugin" >&2
exit 1
fi
mkdir -p "$PLUGIN_DST" "$VAULT_PATH"
MANIFEST_SRC="$ROOT_DIR/manifest.json"
if [[ ! -f "$MANIFEST_SRC" ]]; then
MANIFEST_SRC="$PLUGIN_SRC/manifest.json"
@ -36,11 +35,8 @@ echo "Installed plugin into: $VAULT_PATH"
echo "Plugin path: $PLUGIN_DST"
if [[ "$INSTALL_SPLIT_THEME" == "1" ]]; then
echo "Theme file (split dev mode): $THEME_DST_FILE"
echo " Settings → Appearance → Theme → \"Miku Theme Hybrid Base\""
else
echo "Bundled hybrid CSS in plugin styles.css (catalog-style install)."
echo "Bundled hybrid CSS in plugin styles.css."
fi
echo "URI anchor note: $ANCHOR_NOTE"
echo ""
echo "Next in Obsidian:"
echo " Settings → Community plugins → enable \"Miku Hybrid\"."
echo "Open the vault in Obsidian, then enable \"Miku Hybrid\" under Community plugins."

View file

@ -1,80 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <vault-path>"
exit 1
fi
VAULT_PATH="$(realpath "$1")"
if [[ ! -d "$VAULT_PATH" ]]; then
echo "Vault path does not exist: $VAULT_PATH"
exit 1
fi
ANCHOR="$VAULT_PATH/README.md"
EXTRA_ARGS=()
if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then
EXTRA_ARGS+=("--no-sandbox")
fi
# Obsidian's registered CLI (~/.local/bin/obsidian, often earlier on PATH) expects
# `obsidian <command> [options]`; the distro wrapper runs Electron with the same argv.
# Newer builds treat that first positional after flags as an Obsidian CLI *command*,
# so a vault path yields: Command ".../vault" not found.
# Opening by filesystem path reliably uses the URI handler instead.
launch_uri() {
local path="$1"
local encoded uri
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 is required to build obsidian://open URLs (URI percent-encoding)." >&2
return 1
fi
encoded="$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$path")"
uri="obsidian://open?path=${encoded}"
case "$(uname -s)" in
Darwin)
open "$uri"
;;
*)
if ! command -v xdg-open >/dev/null 2>&1; then
echo "xdg-open not found; cannot open URI on this OS." >&2
return 1
fi
xdg-open "$uri"
;;
esac
}
fallback_argv_gui() {
if [[ -n "${OBSIDIAN_GUI:-}" ]]; then
:
elif [[ -x "/usr/bin/obsidian" ]]; then
OBSIDIAN_GUI="/usr/bin/obsidian"
else
OBSIDIAN_GUI="obsidian"
fi
exec env -u ELECTRON_RUN_AS_NODE "${OBSIDIAN_GUI}" "${EXTRA_ARGS[@]}" "${VAULT_PATH}"
}
# Cursor/automation shells may export ELECTRON_RUN_AS_NODE=1, which breaks Electron
# when spawning the GUI launcher directly — not consulted for xdg-open URI opens.
if [[ "${MIKU_OBSIDIAN_LEGACY_ARGV_LAUNCH:-0}" == "1" ]]; then
fallback_argv_gui
fi
if [[ ! -f "$ANCHOR" ]]; then
echo "Missing $ANCHOR — run ./qa/install-to-vault.sh \"$VAULT_PATH\" first (needed for obsidian://open anchor)." >&2
exit 1
fi
if [[ "${MIKU_SKIP_OBSIDIAN_REGISTER:-0}" != "1" ]]; then
python3 "$SCRIPT_DIR/register-obsidian-vault.py" "$VAULT_PATH"
fi
launch_uri "$ANCHOR"
exit 0

View file

@ -1,29 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
echo "Running lifecycle/performance guard checks..."
# Ensure plugin lifecycle calls registry mount/unmount.
rg "mountAll\\(\\)" "$ROOT_DIR/obsidian-miku-plugin/src/main.ts" >/dev/null
rg "unmountAll\\(\\)" "$ROOT_DIR/obsidian-miku-plugin/src/main.ts" >/dev/null
# Ensure quote interval is cleaned up.
rg "clearInterval" "$ROOT_DIR/obsidian-miku-plugin/src/widgets.ts" >/dev/null
rg "stopRotation\\(\\)" "$ROOT_DIR/obsidian-miku-plugin/src/widgets.ts" >/dev/null
# Ensure dashboard leaves are detached on unmount path.
rg "detachLeavesOfType\\(MIKU_PANEL_VIEW\\)" "$ROOT_DIR/obsidian-miku-plugin/src/modules.ts" >/dev/null
# Basic anti-polling guard.
if rg "setInterval\\(|setTimeout\\(" "$ROOT_DIR/obsidian-miku-plugin/src" >/dev/null; then
# Allow quote widget interval only.
COUNT="$(rg "setInterval\\(|setTimeout\\(" "$ROOT_DIR/obsidian-miku-plugin/src" | wc -l | tr -d ' ')"
if [[ "$COUNT" -gt 1 ]]; then
echo "Unexpected repeated timers detected: $COUNT"
exit 1
fi
fi
echo "Lifecycle/performance guards passed."

View file

@ -1,109 +0,0 @@
#!/usr/bin/env python3
"""Ensure a vault folder is listed in ~/.config/obsidian/obsidian.json (Linux/XDG paths).
Obsidian rejects obsidian://open?path=... with \"Vault not found\" when the folder
was never opened via Manage Vaults, because path resolution uses the registered vault list.
"""
from __future__ import annotations
import json
import os
import platform
import secrets
import sys
import time
from pathlib import Path
def obsidian_config_file() -> Path:
home = Path.home()
system = platform.system()
if system == "Darwin":
return home / "Library" / "Application Support" / "obsidian" / "obsidian.json"
if system == "Windows":
appdata = os.environ.get("APPDATA")
if not appdata:
raise SystemExit("APPDATA not set; cannot locate Obsidian config on Windows")
return Path(appdata) / "Obsidian" / "obsidian.json"
cfg = Path(os.environ.get("XDG_CONFIG_HOME", home / ".config"))
return cfg / "obsidian" / "obsidian.json"
def main() -> None:
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <absolute-or-relative-vault-path>", file=sys.stderr)
raise SystemExit(2)
vault = Path(sys.argv[1]).expanduser().resolve(strict=True)
if not vault.is_dir():
print(f"Not a directory: {vault}", file=sys.stderr)
raise SystemExit(1)
config_path = obsidian_config_file()
config_path.parent.mkdir(parents=True, exist_ok=True)
if config_path.exists():
data = json.loads(config_path.read_text(encoding="utf-8"))
else:
data = {"vaults": {}}
vaults = data.setdefault("vaults", {})
if not isinstance(vaults, dict):
print("'vaults' in obsidian.json is not an object", file=sys.stderr)
raise SystemExit(1)
config_parent = config_path.parent
def ensure_per_vault_window_state(vault_id: str) -> None:
"""Obsidian reads ~/.config/obsidian/<vaultId>.json for window/chrome state on startup."""
p = config_parent / f"{vault_id}.json"
if p.exists():
return
p.write_text(
json.dumps(
{"isMaximized": True, "devTools": False, "zoom": -1},
separators=(",", ":"),
),
encoding="utf-8",
)
vault_str = str(vault)
target_id = None
for vid, entry in vaults.items():
if not isinstance(entry, dict):
continue
p = entry.get("path")
if not p:
continue
try:
if Path(p).expanduser().resolve() == vault:
target_id = vid
break
except OSError:
continue
if target_id is None:
target_id = secrets.token_hex(8)
vaults[target_id] = {
"path": vault_str,
"ts": int(time.time() * 1000),
"open": True,
}
else:
vaults[target_id]["path"] = vault_str
vaults[target_id]["ts"] = int(time.time() * 1000)
for vid, entry in vaults.items():
if isinstance(entry, dict):
entry["open"] = vid == target_id
for vid in vaults:
if isinstance(vid, str):
ensure_per_vault_window_state(vid)
config_path.write_text(json.dumps(data, separators=(",", ":")), encoding="utf-8")
print(f"Registered vault in {config_path}: {vault_str}")
if __name__ == "__main__":
main()

View file

@ -1,68 +0,0 @@
# Release Test Report (Desktop)
Date: 2026-05-15
## 1) Environment and Install Setup
- Clean test vault: `qa/vaults/clean-desktop`
- Install helper: `qa/install-to-vault.sh` (catalog-style: plugin only, bundled `styles.css`)
- Installed artifacts:
- `.obsidian/plugins/miku-plugin-hybrid/{main.js,manifest.json,styles.css}` (manifest `1.0.0`)
- No separate Appearance theme file (bundled hybrid CSS)
Status: PASS
## 2) Build and Static Checks
- `npm run typecheck --prefix ./obsidian-miku-plugin` -> PASS
- `npm run build --prefix ./obsidian-miku-plugin` -> PASS (esbuild + theme CSS merge)
- `./qa/static-guards.sh` -> PASS
- Hybrid DOM hooks in `theme-manager.ts`
- `body[data-miku-theme=` in theme source and merged `styles.css`
- Bundled theme marker in `styles.css`
- Module lifecycle and command registration
- No audio-policy violations
Status: PASS
## 3) Manual Matrix Execution
Obsidian GUI launch not run in this CI/agent environment (`Cannot find module 'electron'` when attempted previously).
Status: BLOCKED (environment-level) — run [manual matrix](#manual-matrix-checklist) on a desktop Obsidian install before marketplace sign-off.
## 4) Lifecycle and Performance Validation
- `./qa/lifecycle-perf-check.sh` -> PASS
- Theme manager cleanup, quote interval teardown, no audio paths (static guards)
Status: PASS
## 5) Release Sign-off Readiness
| Item | Status |
|------|--------|
| Root `manifest.json`, `LICENSE`, `README.md` | Ready |
| Merged `styles.css` in plugin package | Ready |
| GitHub Actions `release.yml` | Ready |
| GitHub Release `1.0.0` tag | Pending push (see below) |
| community.obsidian.md submission | Human step — [docs/COMMUNITY_SUBMISSION.md](../../docs/COMMUNITY_SUBMISSION.md) |
Status: CONDITIONAL PASS (automated); manual UI matrix required on host with Obsidian.
## Command Evidence Snapshot
- `npm run typecheck --prefix ./obsidian-miku-plugin` -> PASS
- `npm run build --prefix ./obsidian-miku-plugin` -> PASS
- `./qa/static-guards.sh` -> PASS
- `./qa/lifecycle-perf-check.sh` -> PASS
- `./qa/install-to-vault.sh ./qa/vaults/clean-desktop` -> PASS
## Manual Matrix Checklist
- Enable **Miku Hybrid** only (no Appearance theme); verify palette/modes.
- Cycle all theme modes; confirm `data-miku-theme` and status bar label.
- Toggle settings; persist after restart.
- Widget mount/unmount; no stale DOM after disable.
- Dashboard via ribbon and command.
- Optional: `MIKU_INSTALL_SPLIT_THEME=1 ./qa/install-to-vault.sh <vault>` for split theme dev test.

View file

@ -1,72 +0,0 @@
# Desktop Test Environment Setup
## Vault Targets
- Active desktop vault (detected): `/home/cachy/Documents/bfh4`
- Clean test vault (local): `/home/cachy/IdeaProjects/cachy/mitsu-plugin/qa/vaults/clean-desktop`
## Install Artifacts Into a Vault
**Run from the repository root.** Paths like `./qa/install-to-vault.sh` are relative to `mitsu-plugin/`, not to `mitsu-plugin/qa/`. If your shell is already inside `qa/`, `cd` to the root first (or call `./install-to-vault.sh` from `qa/`).
Build the plugin bundle first so `main.js` is up to date, then install from the repo root.
Active desktop vault:
```bash
cd /home/cachy/IdeaProjects/cachy/mitsu-plugin/obsidian-miku-plugin && npm run build
cd /home/cachy/IdeaProjects/cachy/mitsu-plugin
./qa/install-to-vault.sh /home/cachy/Documents/bfh4
```
Isolated clean test vault (still from repo root):
```bash
cd /home/cachy/IdeaProjects/cachy/mitsu-plugin/obsidian-miku-plugin && npm run build
cd /home/cachy/IdeaProjects/cachy/mitsu-plugin
./qa/install-to-vault.sh ./qa/vaults/clean-desktop
```
## Expected Install Paths
- Plugin: `<vault>/.obsidian/plugins/miku-plugin-hybrid/` (`main.js`, root or plugin `manifest.json`, merged `styles.css`)
Default install matches the **Community plugin** bundle: hybrid palette CSS is merged into plugin `styles.css`. Enable **Miku Hybrid** under Community plugins—no separate Appearance theme.
Split theme dev install (optional):
```bash
MIKU_INSTALL_SPLIT_THEME=1 ./qa/install-to-vault.sh /path/to/vault
```
That also copies `<vault>/.obsidian/themes/Miku Theme Hybrid Base.css`; pick it under **Settings → Appearance → Theme** when testing the legacy split layout.
## Notes
- This setup is desktop-focused and matches the release test plan scope.
- The script does not enable plugins/theme automatically; enable them from Obsidian settings.
- If `obsidian <vault>` fails with `Cannot find module 'electron'`, launch with:
```bash
./qa/launch-obsidian.sh /home/cachy/Documents/bfh4
```
This wrapper unsets `ELECTRON_RUN_AS_NODE` (common in automation shells) and adds `--no-sandbox` automatically when running as root.
### Opening a vault path from the terminal (URI vs argv)
Recent Obsidian builds treat the **first positional argument** after Chromium-style flags as an **Obsidian CLI subcommand** (same style of message as **`Command "/path/to/thing" not found`**), even when spawned via distro wrappers like **`/usr/bin/obsidian`** (Arch's script forwards **`electron … app.asar`** args). Passing **`/path/to/vault`** therefore no longer opens that folder reliably.
For QA, `./qa/launch-obsidian.sh` writes the vault folder into **`~/.config/obsidian/obsidian.json`** (unless `MIKU_SKIP_OBSIDIAN_REGISTER=1`). Obsidian otherwise answers **`Vault not found`** for `obsidian://open?path=...`, because URIs resolve only against **registered** vault paths. Then it opens **`xdg-open`** on **`obsidian://open?path=...`** using **`README.md`** at the vault root (created by `./qa/install-to-vault.sh` if missing). You need `python3` (encoding + registration), `xdg-open`, and a working `obsidian://` handler. Use `MIKU_OBSIDIAN_LEGACY_ARGV_LAUNCH=1 ./qa/launch-obsidian.sh ./path/to/vault` only to debug older installs that still honor folder-as-argv (`OBSIDIAN_GUI` selects the Electron wrapper in that fallback). Close Obsidian before first registration if you see config write conflicts.
If the terminal briefly logs **`ENOENT … ~/.config/obsidian/<vaultId>.json`**, Obsidian expects a small window-state JSON next to **`obsidian.json`** for each vault id. **`register-obsidian-vault.py`** now creates missing `<vaultId>.json` files automatically when you launch via **`./qa/launch-obsidian.sh`**.
### Community Plugins search versus side-loaded QA plugins
The **browse/search** modal lists plugins from Obsidian's **Community Plugins** catalog. Plugins installed only via **`./qa/install-to-vault.sh`** (files under `.obsidian/plugins/miku-plugin-hybrid/`) are **not** in that catalog, so **`Show installed only` + query** often shows **zero** hits even though the plugin is loaded. Confirm under **Settings → Community plugins**: the toggle list should include **Miku Hybrid**. The status bar **`Miku: MinimalMiku`** (or another mode name) confirms the bundle is executing.
After enabling **Settings → General → Command line interface**, Obsidian often adds **`~/.local/bin/obsidian`** ahead of the distro launcher on **`PATH`**; it only understands **`obsidian <command>`** automation when the GUI is already running, not cold-start **`obsidian /folder`**.
### Arch `user-flags.conf` trap
On Arch (and similar), `~/.config/obsidian/user-flags.conf` is inlined into the desktop launcher (`exec electron … app.asar $OBSIDIAN_USER_FLAGS "$@"`). A wrong token there is treated as a normal CLI argument—not an Electron/Chromium flag. For example `# -disable-gpu` can expand to `-disable-gpu` (missing the second hyphen), after which Obsidian may report something like **`Command "-disable-gpu" not found`**. Prefer valid flags (e.g. `--disable-gpu` if you really need GPU disable) or comment/remove the line entirely.