mirror of
https://github.com/bucknatt/mitsu-plugin.git
synced 2026-07-22 07:05:32 +00:00
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:
parent
927667cd7d
commit
9636bb8366
19 changed files with 150 additions and 2567 deletions
26
.gitignore
vendored
26
.gitignore
vendored
|
|
@ -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
58
CONTRIBUTING.md
Normal 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`) |
|
||||
27
README.md
27
README.md
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
Hatsune Miku–inspired 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).
|
||||
|
|
|
|||
|
|
@ -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
33
docs/RELEASE.md
Normal 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."
|
||||
```
|
||||
|
|
@ -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).
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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 plugin’s `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
6
package-lock.json
generated
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "mitsu-plugin",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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."
|
||||
|
|
@ -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()
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
Loading…
Reference in a new issue