305 lines
14 KiB
Markdown
305 lines
14 KiB
Markdown
# Takemi — Persona × Mocha
|
||
|
||
A Persona 5 flavoured Quickshell desktop for Hyprland, painted in Catppuccin
|
||
Mocha. Blue for anything live, mauve for anything wrong, the Mocha greys for
|
||
everything else, and no gradients anywhere. Everything leans by the same 14°,
|
||
corners are chopped rather than rounded, and every surface is one continuous
|
||
slab divided by hairlines rather than a cluster of floating cards.
|
||
|
||
Replaces Waybar, dunst and hyprlock.
|
||
|
||
## Running
|
||
|
||
```sh
|
||
qs # starts the shell (Hyprland does this via autostart.lua)
|
||
qs -p lockpreview.qml # the lock screen in an ordinary window, for styling
|
||
```
|
||
|
||
Hyprland starts it from `~/.config/hypr/lua/autostart.lua`.
|
||
|
||
## Layout
|
||
|
||
Two surfaces. Status moves off the bar and onto a vertical rail, because on a
|
||
1080p panel horizontal space is cheap and vertical space is not: a 48px rail
|
||
costs 2.5% of the width, where another 48px strip along the bottom would have
|
||
cost 4.4% of the height on top of what the bar already takes.
|
||
|
||
```
|
||
┌────────────────────────────────────────────┐
|
||
│ ◆ │ 1 2 3 │ window title ♪ TRACK │ 42% │ 00:09 │ bar — 48px exclusive
|
||
├────────────────────────────────────────────┤
|
||
│ ▐█▌│ rail — 48px exclusive
|
||
│ windows ▐ ▌│
|
||
│ ▐ ▌│
|
||
│ ▐ ▌│
|
||
└────────────────────────────────────────────┘
|
||
```
|
||
|
||
- **bar** — one slab: identity and workspaces on the left; the focused window,
|
||
the player, the CPU/MEM/GPU readout and the clock on the right.
|
||
- **rail** — one slab: network, bluetooth, audio, mic, backlight, battery, tray,
|
||
notifications, power. Popouts open leftward, into the screen.
|
||
|
||
Each permanent surface is a single continuous `ChromeSurface` with modules
|
||
divided by sheared hairlines. `BarPill` is chromeless by default — a module is
|
||
*content inside* a surface, not a surface of its own — and marks hover with an
|
||
accent underline. Pass `standalone: true` for the rare thing that really is its
|
||
own panel.
|
||
|
||
The shell uses three deliberate surface tiers. **Chrome** (`ChromeSurface`) is
|
||
the calm, texture-free bar, rail, and tooltip layer. **Transient**
|
||
(`PopoutSurface`) is reserved for expressive menus and detail panels, with one
|
||
clear composition and stronger separation from the desktop. **Cinematic**
|
||
(`P5Backdrop`) belongs only to scene-scale experiences such as power and lock;
|
||
it is never used as decoration inside ordinary chrome or popouts.
|
||
|
||
```
|
||
shell.qml entrypoint — bar, rail, power menu, toasts, OSD per screen
|
||
config/ palette, type scale, geometry, motion curves
|
||
components/ the P5 primitives: panels, sliders, list rows, print-shadowed text
|
||
services/ data sources, all singletons
|
||
bar/ the two surfaces and their modules
|
||
popouts/ hover panels hanging off rail and bar modules
|
||
overlays/ power menu, notification toasts, volume/brightness OSD
|
||
lock/ session lock
|
||
assets/ halftone and stripe tiles, generated with ImageMagick
|
||
```
|
||
|
||
`P5Panel` is the base surface everything sits on. It does not auto-size —
|
||
callers set implicit sizes from their own layout (`row.implicitWidth +
|
||
contentPad * 2`), which keeps the shape maths out of binding loops.
|
||
|
||
## Modules
|
||
|
||
| Module | Where | Left click | Right click | Middle | Scroll | Hover |
|
||
|---|---|---|---|---|---|---|
|
||
| Sigil | bar | app menu | reload shell | | | |
|
||
| Workspaces | bar | switch | | | | |
|
||
| Clock | bar | pin calendar | | | | calendar |
|
||
| Network | rail | pin details | | | | state + networks |
|
||
| Bluetooth | rail | pin details | | | | state + devices |
|
||
| Volume | rail | pin audio | | | volume | state + scroll hint + audio |
|
||
| Mic | rail | pin audio | | | mic volume | state + scroll hint + audio |
|
||
| Brightness | rail | | | | brightness | state + scroll hint |
|
||
| Battery | rail | pin panel | | | | power detail |
|
||
| Tray | rail | activate | app menu | secondary | | |
|
||
| Bell | rail | pin history + mark read | | | | state + history |
|
||
| Power | rail | power menu | | | | |
|
||
| Media | bar | play/pause | next | raise player | next/prev | full player |
|
||
| CPU/MEM/GPU | bar | btop | | | | resource dashboard |
|
||
|
||
Popouts open on a short dwell and stay while the pointer is over the trigger or
|
||
the panel. Hover openings remain passive. Clicking remaps a detail panel as a
|
||
keyboard-focused popup: Tab moves through its controls, Escape dismisses it,
|
||
and an outside click closes it. A shell-wide coordinator keeps only one
|
||
transient surface mapped at a time, including tray context menus, so sweeping
|
||
across the rail cannot stack several animated panels over one another.
|
||
|
||
Audio is the reference vertical-slice popout: current output percentage leads,
|
||
output and input have distinct bands, and application streams form a subordinate
|
||
mixer list. Output/input mute actions and **Open audio mixer** are explicit
|
||
buttons inside the panel; scrolling the rail icons remains the fast level
|
||
adjustment.
|
||
|
||
The power menu is fully keyboard-driven as well as clickable: `Up`/`Down` or
|
||
`J`/`K` moves the active slab, `Enter`/`Space` confirms it, and `Escape`
|
||
dismisses the screen.
|
||
|
||
## Keybinds
|
||
|
||
Set in `~/.config/hypr/lua/binds/shell.lua`:
|
||
|
||
- `Super+Ctrl+L` — lock
|
||
- `Super+P` — power menu
|
||
- `Super+Shift+N` — toggle do not disturb
|
||
- `Super+Ctrl+Shift+N` — clear notifications
|
||
- `Super+A` — ask panel
|
||
- `Super+Shift+R` — reload the shell
|
||
|
||
All of these go through Quickshell's IPC, so they work from any script:
|
||
|
||
```sh
|
||
qs ipc call shell lock
|
||
qs ipc call shell power
|
||
qs ipc call shell toggleDnd
|
||
qs ipc call shell ask
|
||
qs ipc call shell askAbout "why is the sky blue"
|
||
qs ipc call shell clearNotifications
|
||
qs ipc call shell reloadConfig
|
||
```
|
||
|
||
## Ask
|
||
|
||
`Super+A` drops a panel off the top edge and hands whatever you type to
|
||
`claude -p`, streamed token by token. It is the subscription that answers —
|
||
the CLI already holds the credentials, so nothing here has an API key and
|
||
nothing here has to be given one.
|
||
|
||
The panel is a chat, not a coding session. Settings sources, MCP servers and
|
||
project files are switched off by default, which keeps a question asked from
|
||
the desktop from arriving with the last project's hooks, `CLAUDE.md` and tone
|
||
attached. The thread survives closing the panel; `Ctrl+L` starts a new one.
|
||
|
||
| Key | Does |
|
||
|---|---|
|
||
| `Enter` | ask |
|
||
| `Shift+Enter` | newline |
|
||
| `Ctrl+M` | next model, written back to `ask.json` |
|
||
| `Ctrl+Y` | copy the last answer |
|
||
| `Ctrl+L` | new conversation |
|
||
| `Ctrl+S` | stop the answer |
|
||
| `Escape` | hide, keeping the thread |
|
||
|
||
Configured in `ask.json`, watched and reloaded live:
|
||
|
||
```json
|
||
{
|
||
"model": "sonnet",
|
||
"systemPrompt": "…",
|
||
"keepContext": true,
|
||
"settingSources": "",
|
||
"allowedTools": ["WebSearch", "WebFetch"],
|
||
"disallowedTools": ["Bash", "Edit", "Write"],
|
||
"workingDirectory": "/home/narl",
|
||
"maxTurns": 6
|
||
}
|
||
```
|
||
|
||
`settingSources` is the escape hatch: set it to `"user,project,local"` and the
|
||
panel becomes your ordinary Claude Code, hooks and all.
|
||
|
||
## Data sources
|
||
|
||
Hardware telemetry comes from `fluxo`, whose hardware modules are configured to
|
||
emit raw pipe-delimited values rather than display strings — `services/Sys.qml`
|
||
parses them and does its own formatting. The previous human-readable Waybar
|
||
formats are preserved in `~/.config/fluxo/config.toml.waybar-bak`.
|
||
|
||
Everything else is native: PipeWire for audio, BlueZ for bluetooth,
|
||
NetworkManager for Wi-Fi, UPower for battery, MPRIS for media, and Quickshell's
|
||
own notification server. These are event-driven, so the bar and OSD react the
|
||
instant a volume key is pressed instead of on the next poll.
|
||
|
||
## Lock screen
|
||
|
||
Authenticates through `/etc/pam.d/hyprlock`, which on this box is just
|
||
`auth include login`. The visuals live in `lock/LockFace.qml` so they can be
|
||
styled in an ordinary window (`qs -p lockpreview.qml`) without locking the
|
||
session.
|
||
|
||
If the shell ever dies while the session is locked, the compositor keeps the
|
||
session secured by protocol and there is no unlocker left to talk to — recovery
|
||
means restarting Hyprland from a TTY.
|
||
|
||
## What was retired
|
||
|
||
- `waybar.service` — disabled. Its config is still in `~/.config/waybar`.
|
||
- `dunst.service` — masked, so it cannot be D-Bus activated and steal
|
||
`org.freedesktop.Notifications`. Undo with
|
||
`systemctl --user unmask dunst.service`.
|
||
- `hyprlock` — no longer bound. `hyprlock.conf` is untouched.
|
||
|
||
## Optional
|
||
|
||
`power-profiles-daemon` is not installed (TLP is), so the power-profile
|
||
switching in `services/Battery.qml` stays dormant. Nothing else depends on it.
|
||
|
||
The display face is Archivo Black, set italic almost everywhere. Change
|
||
`Theme.fontDisplay` and everything follows.
|
||
|
||
## Palette discipline
|
||
|
||
Catppuccin Mocha, spent on two hues. **Hue is meaning**; nothing gets a colour
|
||
for decoration.
|
||
|
||
- **Blue (`accent`, Mocha blue)** means live: the focused workspace, the
|
||
selected row, the hovered control, a channel that is on, an unread badge.
|
||
- **Mauve (`alarm`, Mocha mauve)** means wrong or destructive: critical battery,
|
||
an urgent workspace, a critical notification, reboot and shut down.
|
||
- **Grey** is everything else, and that includes *off*. A muted speaker, a
|
||
muted mic and Do Not Disturb drop to `muted` grey with their off glyph. They
|
||
used to be painted in the accent, which made a silenced channel look the most
|
||
alive thing on the rail.
|
||
- **Crust (`ink`)** is structure: the hard keyline, the offset under display
|
||
type and panels, the text on an accent slab.
|
||
|
||
**Selection is an inverted slab.** Focus and selection fill with the accent and
|
||
print their text in crust (about 9:1). Hover stays on the surface tone with an
|
||
accent mark. Destructive-active is the same slab in mauve. The `treatment*`
|
||
tokens in `Theme.qml` encode all of this; use them rather than picking colours
|
||
per widget.
|
||
|
||
**Print, not blur.** Display text (`SplitText`) carries one hard crust offset.
|
||
The blue and mauve ghosts appear only while `glitch()` runs. Drawn permanently
|
||
1.6px off the body, they read as a focus problem on Mocha's pastels rather than
|
||
as misregistered print. The lock screen and the power menu are the exceptions;
|
||
they are the cinematic surfaces and keep their split.
|
||
|
||
**Texture is tiered.** Chrome (bar, rail, tooltips) and transient panels
|
||
(popouts, OSD, toasts) are flat. Stripes and halftone live only in
|
||
`P5Backdrop`, behind the lock screen and the power menu.
|
||
|
||
**Type has a floor.** Nothing renders below `Theme.fsMicro` (10px).
|
||
|
||
**Changing the palette.** The colours are written once per program:
|
||
|
||
| File | Covers |
|
||
|---|---|
|
||
| `quickshell/config/Theme.qml` | the shell: `accent`, `alarm` and the neutral ramp |
|
||
| `hypr/lua/palette.lua` | window borders, groups, shadows |
|
||
| `alacritty/catppuccin-mocha.toml` | the terminal (stock Mocha, cursor in the accent) |
|
||
|
||
Everything else derives. `accentDim` and `accentSoft` come off `accent`, `glow`
|
||
*is* `accent`, and `primary` is plain `text`. `Theme.heat()` is a continuous
|
||
text → blue → mauve ramp, so a CPU figure that has gone mauve means the same
|
||
thing as a battery that has gone mauve. Tray icons are flattened to one colour
|
||
(subtext, the accent on hover) for the same reason: app brand colours wreck the
|
||
palette faster than anything else in the shell. nm-applet and blueman are
|
||
hidden from the tray, since the rail already shows network and bluetooth, but
|
||
keep running as the secret and pairing agents.
|
||
|
||
## The rest of the desktop
|
||
|
||
- **Hyprland** — `hypr/lua/look.lua`. No rounding, no blur, both opacities at
|
||
`1.0`. The active border runs blue into lavender and the inactive border is
|
||
`surfaceAlt`. The window shadow is a sharp 4px crust offset, the same printed
|
||
drop the shell's panels carry, not a soft glow.
|
||
- **Alacritty** — `alacritty/catppuccin-mocha.toml`, selected by the `import`
|
||
in `alacritty.toml`. Stock Mocha except for the cursor, which is the accent.
|
||
`takemi.toml` is left in place, unreferenced.
|
||
- **Neovim** — the `catppuccin` plugin, flavour mocha, configured in
|
||
`nvim/lua/plugins/ui.lua`. Blue is the accent there too. `colors/takemi.lua`
|
||
is left in place, unused.
|
||
- **Fish** — `fish/conf.d/takemi_theme.fish` is Catppuccin Mocha's fish theme
|
||
with the Takemi selection rows: a solid accent block with crust punched out.
|
||
- **The launcher** — no external theming to keep in step, because it is part of
|
||
this shell (`launcher/`). Fuzzel is gone; `takemi-dmenu` stands in for
|
||
`fuzzel --dmenu` where a script needs a picker.
|
||
- **No glass anywhere.** Alacritty is `opacity = 1.0, blur = false`, and
|
||
Hyprland's `decoration:blur` is disabled. Blur is the signature of the
|
||
frosted-glass look, which is the opposite of flat ink on flat ground — and
|
||
with nothing translucent left it was costing three render passes for no
|
||
visible result.
|
||
|
||
There are **no gradients in this shell**; every fill is flat. `Theme.sheen`
|
||
still exists as a no-op property because a teal-to-transparent wash on every
|
||
panel was once the biggest source of murk in the design.
|
||
|
||
## Geometry discipline
|
||
|
||
**One angle, one chamfer, everywhere.** `Theme.skew` is `tan(14°)` and every
|
||
edge in the shell leans by exactly that, in the same direction; every corner is
|
||
chopped by exactly `Theme.cut`. A vertical panel (the rail) leans its horizontal
|
||
edges by a fraction of its *width*, which works out to the same 14°.
|
||
|
||
This is deliberate and was learned the hard way. An earlier pass gave every
|
||
module a different lean, chamfer and height from per-index variance tables, on
|
||
the theory that Persona geometry is "chaotic". It isn't — Persona UI is
|
||
meticulously clean, and the chaos lives in composition and motion, not in each
|
||
element being a different shape. Randomised geometry just reads as noise. If you
|
||
find yourself adding a variance table, don't.
|
||
|
||
Each surface is **one continuous slab**, with modules divided by sheared
|
||
hairline rules rather than by gaps. A row of separate floating cards reads as
|
||
clutter; that is what the dividers exist to avoid.
|