aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-12 19:35:04 +0200
committeruser <user@clank>2026-08-12 19:35:04 +0200
commitcd2e81d50569271f69d7f3c99aaf8a0295087f33 (patch)
treed7e286625611ebaf298bd2e2327d37f66a751d19
parentinstallers: sync scripts/ CLI helpers into ~/bin (diff)
downloadsl0ppi-cd2e81d50569271f69d7f3c99aaf8a0295087f33.tar.gz
sl0ppi-cd2e81d50569271f69d7f3c99aaf8a0295087f33.tar.xz
sl0ppi-cd2e81d50569271f69d7f3c99aaf8a0295087f33.zip
config: snapshot the live environment as source of truth
The most opinionated part of sl0ppi was never in any repo: it lived in ~/.config/zellij as dotfiles, versioned by ~20 hand-made `config.kdl.pre-*` backups. Imported verbatim so the packaging work has something to install. Deliberately not imported: - config.kdl.pre-* / layouts/*.pre-* / themes/*.pre-* backups (that is what git is for now) - plugins/pane-finder.wasm (a build output of sl0ppij/sl0p/zj-pane-finder) - plugins/zjstatus.wasm (a third-party blob no longer referenced by config.kdl -- dropped as dead weight) The snapshot is faithful, so it still hardcodes development-machine paths; config/README.md lists what the rename work has to parameterise.
-rw-r--r--config/README.md28
-rw-r--r--config/answers.example.md14
-rw-r--r--config/config.kdl380
-rw-r--r--config/layouts/default.kdl137
-rw-r--r--config/layouts/default.swap.kdl131
-rw-r--r--config/themes/gruber-darker-pop.kdl159
-rw-r--r--config/themes/templeos.kdl133
7 files changed, 982 insertions, 0 deletions
diff --git a/config/README.md b/config/README.md
new file mode 100644
index 0000000..350384b
--- /dev/null
+++ b/config/README.md
@@ -0,0 +1,28 @@
+# config/ — the opinionated default environment
+
+These files are the **source of truth** for sl0ppij's shipped configuration.
+Until this snapshot they existed only as live dotfiles in `~/.config/zellij`,
+versioned by hand-rolled `config.kdl.pre-<thing>` backups.
+
+| file | what it is |
+|---|---|
+| `config.kdl` | "unlock-first" keybinds (`Ctrl g` leader), pane frames, plugin bindings, sl0p fork options |
+| `layouts/default.kdl` | the default tab layout |
+| `layouts/default.swap.kdl` | responsive swap layouts (stacked-pane behaviour) |
+| `themes/gruber-darker-pop.kdl` | the default theme |
+| `themes/templeos.kdl` | alternate 16-color VGA theme |
+| `answers.example.md` | seed for a project's `.pi-answers.md` (answer-picker buttons) |
+
+## Editing workflow
+
+Edit **here**, then `make install-config` to sync into your live config dir.
+Do not edit the live dotfiles directly any more — they are now a build output.
+
+## Known rewrites still pending (see docs/rename.md)
+
+This is a *faithful* snapshot, so it still contains development-machine paths
+that the rename + packaging work has to parameterise:
+
+- `file:~/.config/zellij/plugins/pane-finder.wasm` (x2) → sl0ppij's own config dir
+- header comments referencing `~/.config/zellij/...` and `~/lol/zellij`
+- `theme "gruber-darker-pop"` assumes `themes/` is installed alongside
diff --git a/config/answers.example.md b/config/answers.example.md
new file mode 100644
index 0000000..85dd160
--- /dev/null
+++ b/config/answers.example.md
@@ -0,0 +1,14 @@
+# pi answer buttons
+
+<!-- Clickable buttons in this pane's zellij frame row (OSC 5577), also alt+1..9.
+ Format: - `label` — text sent to the agent
+ This file is authoritative: it was seeded with answer-picker.ts defaults,
+ so deleting a line removes that button. `/answers reload` re-reads it. -->
+
+- `continue` — continue
+- `yes` — yes, go ahead
+- `no` — no — don't do that
+- `explain` — explain what you just did, and why
+- `tests` — run the tests and fix anything that fails
+- `commit` — commit this with a clear message
+- `push` — push to git
diff --git a/config/config.kdl b/config/config.kdl
new file mode 100644
index 0000000..7f15c8c
--- /dev/null
+++ b/config/config.kdl
@@ -0,0 +1,380 @@
+// ~/.config/zellij/config.kdl
+// "unlock-first" (non-colliding) setup: Zellij grabs almost no keys by default.
+// Leader: Ctrl g -> unlocks into Normal mode, then one letter picks a mode:
+// p pane t tab r resize m move s scroll/search o session
+// Esc / Enter (or Ctrl g again) -> back to Locked.
+// Everything else goes straight to your shell/vim/etc.
+
+default_mode "locked"
+
+keybinds clear-defaults=true {
+ normal {
+ }
+ locked {
+ bind "Ctrl g" { SwitchToMode "Normal"; }
+ }
+ resize {
+ bind "r" { SwitchToMode "Normal"; }
+ bind "h" "Left" { Resize "Increase Left"; }
+ bind "j" "Down" { Resize "Increase Down"; }
+ bind "k" "Up" { Resize "Increase Up"; }
+ bind "l" "Right" { Resize "Increase Right"; }
+ bind "H" { Resize "Decrease Left"; }
+ bind "J" { Resize "Decrease Down"; }
+ bind "K" { Resize "Decrease Up"; }
+ bind "L" { Resize "Decrease Right"; }
+ bind "=" "+" { Resize "Increase"; }
+ bind "-" { Resize "Decrease"; }
+ }
+ pane {
+ bind "p" { SwitchToMode "Normal"; }
+ bind "h" "Left" { MoveFocus "Left"; }
+ bind "l" "Right" { MoveFocus "Right"; }
+ bind "j" "Down" { MoveFocus "Down"; }
+ bind "k" "Up" { MoveFocus "Up"; }
+ bind "Tab" { SwitchFocus; }
+ bind "n" { NewPane; SwitchToMode "Locked"; }
+ bind "d" { NewPane "Down"; SwitchToMode "Locked"; }
+ bind "r" { NewPane "Right"; SwitchToMode "Locked"; }
+ bind "s" { NewPane "stacked"; SwitchToMode "Locked"; }
+ bind "x" { CloseFocus; SwitchToMode "Locked"; }
+ bind "f" { ToggleFocusFullscreen; SwitchToMode "Locked"; }
+ bind "Shift f" { ToggleFocusNoUiFullscreen; SwitchToMode "Locked"; }
+ bind "z" { TogglePaneFrames; SwitchToMode "Locked"; }
+ bind "w" { ToggleFloatingPanes; SwitchToMode "Locked"; }
+ bind "e" { TogglePaneEmbedOrFloating; SwitchToMode "Locked"; }
+ bind "c" { SwitchToMode "RenamePane"; PaneNameInput 0;}
+ bind "i" { TogglePanePinned; SwitchToMode "Locked"; }
+ }
+ move {
+ bind "m" { SwitchToMode "Normal"; }
+ bind "n" "Tab" { MovePane; }
+ bind "p" { MovePaneBackwards; }
+ bind "h" "Left" { MovePane "Left"; }
+ bind "j" "Down" { MovePane "Down"; }
+ bind "k" "Up" { MovePane "Up"; }
+ bind "l" "Right" { MovePane "Right"; }
+ }
+ tab {
+ bind "t" { SwitchToMode "Normal"; }
+ bind "r" { SwitchToMode "RenameTab"; TabNameInput 0; }
+ bind "h" "Left" "Up" "k" { GoToPreviousTab; }
+ bind "l" "Right" "Down" "j" { GoToNextTab; }
+ bind "n" { NewTab; SwitchToMode "Locked"; }
+ bind "x" { CloseTab; SwitchToMode "Locked"; }
+ bind "s" { ToggleActiveSyncTab; SwitchToMode "Locked"; }
+ bind "b" { BreakPane; SwitchToMode "Locked"; }
+ bind "]" { BreakPaneRight; SwitchToMode "Locked"; }
+ bind "[" { BreakPaneLeft; SwitchToMode "Locked"; }
+ bind "1" { GoToTab 1; SwitchToMode "Locked"; }
+ bind "2" { GoToTab 2; SwitchToMode "Locked"; }
+ bind "3" { GoToTab 3; SwitchToMode "Locked"; }
+ bind "4" { GoToTab 4; SwitchToMode "Locked"; }
+ bind "5" { GoToTab 5; SwitchToMode "Locked"; }
+ bind "6" { GoToTab 6; SwitchToMode "Locked"; }
+ bind "7" { GoToTab 7; SwitchToMode "Locked"; }
+ bind "8" { GoToTab 8; SwitchToMode "Locked"; }
+ bind "9" { GoToTab 9; SwitchToMode "Locked"; }
+ bind "Tab" { ToggleTab; }
+ }
+ scroll {
+ bind "s" { SwitchToMode "Normal"; }
+ bind "e" { EditScrollback; SwitchToMode "Locked"; }
+ bind "f" { SwitchToMode "EnterSearch"; SearchInput 0; }
+ bind "Ctrl c" { ScrollToBottom; SwitchToMode "Locked"; }
+ bind "j" "Down" { ScrollDown; }
+ bind "k" "Up" { ScrollUp; }
+ bind "Ctrl f" "PageDown" "Right" "l" { PageScrollDown; }
+ bind "Ctrl b" "PageUp" "Left" "h" { PageScrollUp; }
+ bind "d" { HalfPageScrollDown; }
+ bind "u" { HalfPageScrollUp; }
+ bind "Alt left" { MoveFocusOrTab "left"; SwitchToMode "locked"; }
+ bind "Alt down" { MoveFocus "down"; SwitchToMode "locked"; }
+ bind "Alt up" { MoveFocus "up"; SwitchToMode "locked"; }
+ bind "Alt right" { MoveFocusOrTab "right"; SwitchToMode "locked"; }
+ bind "Alt h" { MoveFocusOrTab "left"; SwitchToMode "locked"; }
+ bind "Alt j" { MoveFocus "down"; SwitchToMode "locked"; }
+ bind "Alt k" { MoveFocus "up"; SwitchToMode "locked"; }
+ bind "Alt l" { MoveFocusOrTab "right"; SwitchToMode "locked"; }
+ }
+ search {
+ bind "Ctrl c" { ScrollToBottom; SwitchToMode "Locked"; }
+ bind "j" "Down" { ScrollDown; }
+ bind "k" "Up" { ScrollUp; }
+ bind "Ctrl f" "PageDown" "Right" "l" { PageScrollDown; }
+ bind "Ctrl b" "PageUp" "Left" "h" { PageScrollUp; }
+ bind "d" { HalfPageScrollDown; }
+ bind "u" { HalfPageScrollUp; }
+ bind "n" { Search "down"; }
+ bind "p" { Search "up"; }
+ bind "c" { SearchToggleOption "CaseSensitivity"; }
+ bind "w" { SearchToggleOption "Wrap"; }
+ bind "o" { SearchToggleOption "WholeWord"; }
+ }
+ entersearch {
+ bind "Ctrl c" "Esc" { SwitchToMode "Scroll"; }
+ bind "Enter" { SwitchToMode "Search"; }
+ }
+ renametab {
+ bind "Ctrl c" "Enter" { SwitchToMode "Locked"; }
+ bind "Esc" { UndoRenameTab; SwitchToMode "Tab"; }
+ }
+ renamepane {
+ bind "Ctrl c" "Enter" { SwitchToMode "Locked"; }
+ bind "Esc" { UndoRenamePane; SwitchToMode "Pane"; }
+ }
+ session {
+ bind "o" { SwitchToMode "Normal"; }
+ // pane finder (fuzzy find any pane across tabs, with live preview).
+ // skip_plugin_cache: always load the .wasm fresh from disk, so a rebuild
+ // takes effect on the next launch instead of being masked by the server's
+ // in-memory module cache. Costs a wasm recompile (~100-300ms) per launch.
+ bind "g" {
+ LaunchOrFocusPlugin "file:~/.config/zellij/plugins/pane-finder.wasm" {
+ floating true
+ move_to_focused_tab true
+ skip_plugin_cache true
+ };
+ SwitchToMode "Locked"
+ }
+ bind "d" { Detach; }
+ bind "]" { FocusHostSession; SwitchToMode "Normal"; }
+ bind "[" { FocusGuestSession; SwitchToMode "Normal"; }
+ bind "f" { ToggleHostFullscreen; SwitchToMode "Normal"; }
+ bind "w" {
+ LaunchOrFocusPlugin "session-manager" {
+ floating true
+ move_to_focused_tab true
+ };
+ SwitchToMode "Locked"
+ }
+ bind "c" {
+ LaunchOrFocusPlugin "configuration" {
+ floating true
+ move_to_focused_tab true
+ };
+ SwitchToMode "Locked"
+ }
+ bind "p" {
+ LaunchOrFocusPlugin "plugin-manager" {
+ floating true
+ move_to_focused_tab true
+ };
+ SwitchToMode "Locked"
+ }
+ bind "a" {
+ LaunchOrFocusPlugin "zellij:about" {
+ floating true
+ move_to_focused_tab true
+ };
+ SwitchToMode "Locked"
+ }
+ bind "s" {
+ LaunchOrFocusPlugin "zellij:share" {
+ floating true
+ move_to_focused_tab true
+ };
+ SwitchToMode "Locked"
+ }
+ bind "l" {
+ LaunchOrFocusPlugin "zellij:layout-manager" {
+ floating true
+ move_to_focused_tab true
+ };
+ SwitchToMode "Locked"
+ }
+ }
+ shared_except "locked" "renametab" "renamepane" {
+ bind "Ctrl g" { SwitchToMode "Locked"; }
+ bind "Ctrl q" { Quit; }
+ }
+ shared_except "renamepane" "renametab" "entersearch" "locked" {
+ bind "esc" { SwitchToMode "locked"; }
+ }
+ // The only keys Zellij still eats while locked: Alt-based quick actions.
+ // Comment this block out if your shell/editor wants Alt for itself.
+ shared_among "normal" "locked" {
+ bind "Alt n" { NewPane; }
+ bind "Alt f" { ToggleFloatingPanes; }
+ // Alt-f shows/hides the floating LAYER; Alt-Shift-F floats (or re-embeds)
+ // the pane you are on. Two easily-confused actions, so they share a key
+ // with Shift as the "act on this pane" variant -- same shape as
+ // Alt-p / Alt-Shift-P below.
+ bind "Alt Shift f" { TogglePaneEmbedOrFloating; }
+ // Ctrl-' does the same, next to Ctrl-; for the pane finder: same hand
+ // position, and both are keys nothing else claims.
+ //
+ // NOT Ctrl-f, which looks free but is not: nvim's page-forward, readline's
+ // forward-char, less/man's page-forward, and this config's own scroll and
+ // search modes all use it. Binding it here would shadow all of them in
+ // every pane.
+ bind "Ctrl '" { TogglePaneEmbedOrFloating; }
+ bind "Alt i" { MoveTab "Left"; }
+ bind "Alt o" { MoveTab "Right"; }
+ bind "Alt h" "Alt Left" { MoveFocusOrTab "Left"; }
+ bind "Alt l" "Alt Right" { MoveFocusOrTab "Right"; }
+ bind "Alt j" "Alt Down" { MoveFocus "Down"; }
+ bind "Alt k" "Alt Up" { MoveFocus "Up"; }
+ bind "Alt =" "Alt +" { Resize "Increase"; }
+ bind "Alt -" { Resize "Decrease"; }
+ bind "Alt [" { PreviousSwapLayout; }
+ bind "Alt ]" { NextSwapLayout; }
+ // tab cycling. Ctrl-= / Ctrl-- are only distinguishable from plain =/-
+ // under the kitty keyboard protocol, which zellij enables and kitty
+ // speaks; on a terminal without it these bindings simply never fire.
+ bind "Ctrl =" { GoToNextTab; }
+ bind "Ctrl -" { GoToPreviousTab; }
+ bind "Alt p" { TogglePaneInGroup; }
+ bind "Alt Shift p" { ToggleGroupMarking; }
+ // pane finder, straight from Locked -- the ^g o g route is too many
+ // keys for something used this often. Ctrl-; is deliberately obscure:
+ // ';' has no control-code meaning, readline does not bind it, and vim
+ // only binds bare ';' (repeat f/t), never Ctrl-;. Like Ctrl-= / Ctrl--
+ // above it needs the kitty keyboard protocol to be distinguishable.
+ bind "Ctrl ;" {
+ LaunchOrFocusPlugin "file:~/.config/zellij/plugins/pane-finder.wasm" {
+ floating true
+ move_to_focused_tab true
+ skip_plugin_cache true
+ // sl0p fork only: pressing the chord again dismisses it, so one
+ // key toggles. A plugin cannot do this itself -- keybindings are
+ // resolved into actions before a key can reach a pane, so the
+ // launching chord never arrives at the plugin.
+ close_if_focused true
+ };
+ SwitchToMode "Locked"
+ }
+ }
+ shared_except "locked" "renametab" "renamepane" {
+ bind "Enter" { SwitchToMode "Locked"; }
+ }
+ shared_except "pane" "locked" "renametab" "renamepane" "entersearch" {
+ bind "p" { SwitchToMode "Pane"; }
+ }
+ shared_except "resize" "locked" "renametab" "renamepane" "entersearch" {
+ bind "r" { SwitchToMode "Resize"; }
+ }
+ shared_except "scroll" "locked" "renametab" "renamepane" "entersearch" {
+ bind "s" { SwitchToMode "Scroll"; }
+ }
+ shared_except "session" "locked" "renametab" "renamepane" "entersearch" {
+ bind "o" { SwitchToMode "Session"; }
+ }
+ shared_except "tab" "locked" "renametab" "renamepane" "entersearch" {
+ bind "t" { SwitchToMode "Tab"; }
+ }
+ shared_except "move" "locked" "renametab" "renamepane" "entersearch" {
+ bind "m" { SwitchToMode "Move"; }
+ }
+}
+
+// ── look & feel ────────────────────────────────────────────────────────────
+// ~/.config/zellij/themes/templeos.kdl — 16-color VGA, white desktop, blue task
+// frames. Run `templeos-term on` to put the *terminal* into the same palette.
+// gruber-darker-pop = stock gruber-darker + a `frame_unselected` block, so the
+// FOCUSED pane's frame (gruber yellow) pops against recessive gray inactive ones.
+// Defined in ~/.config/zellij/themes/gruber-darker-pop.kdl — tune `frame_unselected`
+// `base` there: lower = more recessive, higher = louder. Revert with "gruber-darker".
+theme "gruber-darker-pop"
+// Fallbacks if you get tired of God's own colors: catppuccin-mocha, gruvbox-dark,
+// tokyo-night-storm, nord, kanagawa, cyber-noir, retro-wave, gruber-darker, ansi.
+
+ui {
+ pane_frames {
+ rounded_corners true // (was false: "TempleOS does not do rounded. 640x480 or death.")
+ hide_session_name false
+ // sl0p fork only (~/lol/zellij, branch sl0p/base): "left" | "center" | "right".
+ // Upstream zellij hardcodes full-frame titles flush left.
+ title_alignment "center"
+ // sl0p fork only.
+ // gap - uniform space around every pane edge, in ROWS. A pane
+ // insets right/bottom always and left/top only at the
+ // screen edge, so two neighbours show exactly `gap`
+ // between them (not 2x) and screen edges match.
+ // padding - breathing room between a pane's frame and its
+ // content, also in ROWS.
+ // gap_aspect - columns per row (default 2). Terminal cells are ~2x
+ // taller than wide, so 1 row needs ~2 columns to look
+ // like the same distance. Set to 1 to disable.
+ gap 1
+ padding 0 0 0 0
+ gap_aspect 2
+ // Desaturate the CONTENT of panes you are not focused on, 0-100.
+ // 0 = off. Colours blend toward their own luminance, so an unfocused
+ // pane keeps its brightness but loses its colour.
+ inactive_grayscale 85
+ // Show no pane title at all until something names the pane: either you
+ // rename it (double-click its top row) or the running program sets a
+ // title. Upstream shows a "Pane #N" placeholder.
+ hide_default_pane_names true
+ // sl0p fork only. Collapse to the swap layout named by
+ // `responsive_layout` when the tab is narrower than this many columns,
+ // and restore the previous layout when it grows back. 0 = off.
+ //
+ // Restore only happens above min_cols + 10 (hysteresis): without a dead
+ // band, dragging an edge that lands on the threshold flips the layout on
+ // every motion event, and each flip resizes every PTY in the tab.
+ //
+ // Selects BY NAME, so it needs a matching `swap_tiled_layout name=...`
+ // in layouts/default.swap.kdl -- and that layout's own min_panes/
+ // max_panes constraint still has to be satisfied, or nothing happens.
+ //
+ // Stops acting once you have rearranged panes by hand, same principle
+ // as auto_layout: an explicit arrangement outranks an automatic one.
+ responsive_min_cols 90
+ responsive_layout "stacked"
+ // Cap how many entries the stacked-pane picker shows at once. It windows
+ // around the focused entry and reports the rest as "+N more" in the row
+ // it already reserves. 0 = show every member.
+ stack_list_max_rows 3
+ // "grid" packs picker entries several per row, so the cap above becomes a
+ // space budget rather than a count. "rows" (default) is one per row.
+ stack_list_layout "grid"
+ // "boxed" (default) brackets each entry and marks the active one with
+ // "> "; "minimal" drops both and inverts the active entry instead.
+ stack_list_style "minimal"
+ // Status bar action buttons (OSC 5577): "boxed" renders [Approve],
+ // "indexed" renders 1:Approve -- useful when something else binds those
+ // numbers to the same actions.
+ status_bar_button_style "indexed"
+ }
+}
+
+pane_frames true
+pane_frame_style "full"
+
+// ── behaviour ──────────────────────────────────────────────────────────────
+scroll_buffer_size 50000
+copy_on_select true
+scrollback_editor "/usr/bin/nvim"
+
+// keep sessions (tabs/panes/cwds/commands) resurrectable after a crash/reboot
+session_serialization true
+serialize_pane_viewport true
+scrollback_lines_to_serialize 10000
+
+// smarter resizing / stacked panes
+stacked_resize true
+stacked_pane_list true
+
+// mouse quality-of-life
+mouse_mode true
+advanced_mouse_actions true
+mouse_hover_effects true
+// sl0p fork only: the "Ctrl <MouseScroll> or <drag borders> to resize" hint that
+// appears on hover. Upstream bundles it with mouse_hover_effects, which also
+// governs the hover frame highlight -- this separates them, so the reminder can
+// go while the highlight stays.
+mouse_hover_help_text false
+mouse_scroll_resize true
+// focus panes on hover (no click needed)
+focus_follows_mouse true
+
+// less chrome on startup
+show_startup_tips false
+show_release_notes false
+
+// detach (don't kill) when the terminal window goes away
+on_force_close "detach"
+
+styled_underlines true
diff --git a/config/layouts/default.kdl b/config/layouts/default.kdl
new file mode 100644
index 0000000..aa86a86
--- /dev/null
+++ b/config/layouts/default.kdl
@@ -0,0 +1,137 @@
+// ~/.config/zellij/layouts/default.kdl
+// zjstatus bar, palette-matched to themes/gruber-darker-pop.kdl.
+//
+// WHY THIS FILE EXISTS: a layout named `default` is what zellij applies to every
+// new tab/session, so dropping zjstatus in here replaces the stock tab-bar +
+// status-bar everywhere without touching config.kdl.
+//
+// NOTE ON FRAMES: `hide_frame_for_single_pane` is deliberately NOT set. That
+// option makes zjstatus toggle pane frames itself, which would fight the
+// `frame_unselected` / `frame_selected` colors tuned in gruber-darker-pop.kdl.
+// config.kdl keeps `pane_frames true` and stays authoritative over frames.
+//
+// NOTE ON SWAP LAYOUTS: overriding the default layout also overrides zellij's
+// default swap layouts, so they are restored in default.swap.kdl next to this
+// file. Alt-[ / Alt-] keep working.
+//
+// PERMISSIONS: on first launch zjstatus asks for ReadApplicationState,
+// ChangeApplicationState and RunCommands (the last one is for the git widget).
+// Click the bar, press `y`. Must be repeated whenever zjstatus.wasm is updated.
+
+layout {
+ default_tab_template {
+ children
+ pane size=1 borderless=true {
+ plugin location="file:~/.config/zellij/plugins/zjstatus.wasm" {
+
+ // ── palette: lifted verbatim from gruber-darker-pop.kdl ──────
+ color_bg "#181818" // 24 24 24 theme background
+ color_bg_alt "#282828" // 40 40 40 ribbon_unselected bg
+ color_dim "#484848" // 72 72 72 frame_unselected base
+ color_fg "#e4e4ef" // 228 228 239 text base
+ color_sage "#95a99f" // 149 169 159 emphasis_0
+ color_yellow "#ffdd33" // 255 221 51 frame_selected / gruber yellow
+ color_green "#73c936" // 115 201 54 frame_highlight / success
+ color_blue "#96a6c8" // 150 166 200
+ color_purple "#9e95c7" // 158 149 199
+ color_brown "#cc8c3c" // 204 140 60
+ color_red "#ff4f58" // 255 79 88 exit_code_error
+
+ format_left "{mode}{session}"
+ format_center "{tabs}"
+ format_right "{command_git} {datetime}"
+ format_space "#[bg=$bg]"
+
+ // hide the least-important part instead of letting the three
+ // sections collide on a narrow terminal. Priority: center
+ // (tabs) > left (mode) > right (git/clock is the first to go).
+ format_hide_on_overlength "true"
+ format_precedence "clr"
+
+ // ── mode ────────────────────────────────────────────────────
+ // config.kdl is `default_mode "locked"` with Ctrl-g as leader,
+ // so LOCKED is the resting state and is styled to recede.
+ // Every *unlocked* mode is loud on purpose: with a leader-key
+ // setup the expensive mistake is typing into a pane while a
+ // mode is still live, so the bar should be impossible to miss.
+ // Powerline caps: U+E0B6 left-round, U+E0B4 right-round.
+ // These live in the Powerline Symbols block and exist in every
+ // Nerd Font AND every powerline-patched font, so they are the
+ // safe part. The lock/unlock glyphs below are Font Awesome
+ // range and are Nerd-Font-only: if ONLY those show as tofu,
+ // you have a powerline font rather than a Nerd Font.
+ // Pre-Nerd-Font version saved at default.kdl.pre-nerdfont.
+ mode_locked "#[bg=$dim,fg=$bg,bold] LOCKED #[bg=$bg,fg=$dim]#[bg=$bg,fg=$dim,italic] ^g "
+ mode_normal "#[bg=$yellow,fg=$bg,bold] NORMAL #[bg=$bg,fg=$yellow]#[bg=$bg,fg=$sage,italic] p t r m s o "
+ mode_pane "#[bg=$blue,fg=$bg,bold] PANE #[bg=$bg,fg=$blue]#[bg=$bg,fg=$sage,italic] hjkl n d r s x f w e c "
+ mode_tab "#[bg=$purple,fg=$bg,bold] TAB #[bg=$bg,fg=$purple]#[bg=$bg,fg=$sage,italic] hl n x r b 1-9 "
+ mode_resize "#[bg=$brown,fg=$bg,bold] RESIZE #[bg=$bg,fg=$brown]#[bg=$bg,fg=$sage,italic] hjkl HJKL +- "
+ mode_move "#[bg=$brown,fg=$bg,bold] MOVE #[bg=$bg,fg=$brown]#[bg=$bg,fg=$sage,italic] hjkl n p "
+ mode_scroll "#[bg=$green,fg=$bg,bold] SCROLL #[bg=$bg,fg=$green]#[bg=$bg,fg=$sage,italic] jk du f=find e=edit "
+ mode_enter_search "#[bg=$green,fg=$bg,bold] SEARCH: #[bg=$bg,fg=$green]#[bg=$bg,fg=$sage,italic] type, Enter "
+ mode_search "#[bg=$green,fg=$bg,bold] FOUND #[bg=$bg,fg=$green]#[bg=$bg,fg=$sage,italic] n p c w o "
+ mode_session "#[bg=$sage,fg=$bg,bold] SESSION #[bg=$bg,fg=$sage]#[bg=$bg,fg=$sage,italic] g=panes w d c p "
+ mode_rename_tab "#[bg=$red,fg=$bg,bold] RENAME TAB #[bg=$bg,fg=$red]"
+ mode_rename_pane "#[bg=$red,fg=$bg,bold] RENAME PANE #[bg=$bg,fg=$red]"
+ mode_prompt "#[bg=$red,fg=$bg,bold] PROMPT #[bg=$bg,fg=$red]"
+ mode_tmux "#[bg=$brown,fg=$bg,bold] TMUX #[bg=$bg,fg=$brown]"
+ mode_default_to_mode "locked"
+
+ // ── session ─────────────────────────────────────────────────
+ session "#[bg=$bg,fg=$blue,bold] {name} #[bg=$bg,fg=$dim] "
+
+ // ── tabs ────────────────────────────────────────────────────
+ // ▐ / ▌ are half-block glyphs: they fake a hard-edged powerline
+ // pill without needing a Nerd Font, and stay square-cornered to
+ // match `rounded_corners false`.
+ tab_normal "#[bg=$bg,fg=$dim] {index}#[bg=$bg,fg=$sage] {name} {floating_indicator}#[bg=$bg,fg=$dim] "
+ tab_normal_fullscreen "#[bg=$bg,fg=$dim] {index}#[bg=$bg,fg=$sage] {name} {fullscreen_indicator}#[bg=$bg,fg=$dim] "
+ tab_normal_sync "#[bg=$bg,fg=$dim] {index}#[bg=$bg,fg=$sage] {name} {sync_indicator}#[bg=$bg,fg=$dim] "
+
+ tab_active "#[bg=$yellow,fg=$bg,bold] {index} {name} {floating_indicator}#[bg=$bg,fg=$yellow]"
+ tab_active_fullscreen "#[bg=$yellow,fg=$bg,bold] {index} {name} {fullscreen_indicator}#[bg=$bg,fg=$yellow]"
+ tab_active_sync "#[bg=$yellow,fg=$bg,bold] {index} {name} {sync_indicator}#[bg=$bg,fg=$yellow]"
+
+ tab_separator "#[bg=$bg] "
+ tab_rename "#[bg=$red,fg=$bg,bold] {index} {name} #[bg=$bg,fg=$red]"
+
+ tab_sync_indicator "<> "
+ tab_fullscreen_indicator "[] "
+ tab_floating_indicator "^ "
+
+ // ── git branch of the FOCUSED pane's cwd ────────────────────
+ // {focused_pane_cwd} (zjstatus 0.24.0+) makes this track
+ // whichever pane you are in rather than the session's cwd.
+ //
+ // The `bash -c` wrapper is REQUIRED, not cosmetic: zjstatus
+ // execs the command directly with no shell, so a bare
+ // `git ... 2>/dev/null` hands `2>/dev/null` to git as a literal
+ // revision argument and git dies with "ambiguous argument".
+ // The redirect also matters: outside a git repo git writes to
+ // stderr, and without it that noise can reach the bar.
+ //
+ // rendermode "static" = format with the config's format string
+ // and ignore any zjstatus directives in the command's own output.
+ command_git_command "bash -c \"git rev-parse --abbrev-ref HEAD 2>/dev/null\""
+ command_git_format "#[bg=$bg,fg=$green] {stdout}"
+ command_git_interval "10"
+ command_git_rendermode "static"
+ command_git_cwd "{focused_pane_cwd}"
+
+ // ── clock ───────────────────────────────────────────────────
+ // Keep `#[..]` directives OUT of datetime_format: that string is
+ // strftime output substituted into {format} after the outer
+ // format is parsed, so a directive in here renders literally.
+ datetime "#[bg=$bg,fg=$sage]{format}"
+ datetime_format "%a %d %b %H:%M"
+ datetime_timezone "Europe/Amsterdam"
+
+ // ── thin rule above the bar, so it reads as chrome ──────────
+ border_enabled "false"
+ border_char "─"
+ border_format "#[fg=$dim]{char}"
+ border_position "top"
+ }
+ }
+ }
+}
diff --git a/config/layouts/default.swap.kdl b/config/layouts/default.swap.kdl
new file mode 100644
index 0000000..4e97b79
--- /dev/null
+++ b/config/layouts/default.swap.kdl
@@ -0,0 +1,131 @@
+// ~/.config/zellij/layouts/default.swap.kdl
+// Zellij's stock swap layouts, restored.
+//
+// Defining layouts/default.kdl overrides the built-in default layout, which
+// silently takes the built-in swap layouts with it and leaves Alt-[ / Alt-]
+// doing nothing. This file is `zellij setup --dump-swap-layout default` with
+// the hardcoded `ui` tab_template stripped, so each swap layout inherits the
+// zjstatus `default_tab_template` from default.kdl instead of re-adding
+// zellij's own tab-bar/status-bar.
+//
+// Regenerate with:
+// zellij setup --dump-swap-layout default
+// then delete the `tab_template name="ui"` block and s/^ui /tab /.
+
+swap_tiled_layout name="vertical" {
+ tab max_panes=5 {
+ pane split_direction="vertical" {
+ pane
+ pane { children; }
+ }
+ }
+ tab max_panes=8 {
+ pane split_direction="vertical" {
+ pane { children; }
+ pane { pane; pane; pane; pane; }
+ }
+ }
+ tab max_panes=12 {
+ pane split_direction="vertical" {
+ pane { children; }
+ pane { pane; pane; pane; pane; }
+ pane { pane; pane; pane; pane; }
+ }
+ }
+}
+
+swap_tiled_layout name="horizontal" {
+ tab max_panes=4 {
+ pane
+ pane
+ }
+ tab max_panes=8 {
+ pane {
+ pane split_direction="vertical" { children; }
+ pane split_direction="vertical" { pane; pane; pane; pane; }
+ }
+ }
+ tab max_panes=12 {
+ pane {
+ pane split_direction="vertical" { children; }
+ pane split_direction="vertical" { pane; pane; pane; pane; }
+ pane split_direction="vertical" { pane; pane; pane; pane; }
+ }
+ }
+}
+
+// min_panes lowered 4 -> 2 for the responsive-layout collapse: the switch finds
+// this layout by name, but still has to satisfy this constraint, so at 4 a
+// narrow 2- or 3-pane tab would silently refuse to collapse.
+swap_tiled_layout name="stacked" {
+ tab min_panes=2 {
+ pane stacked=true { children; }
+ }
+}
+
+swap_tiled_layout name="half-stacked" {
+ tab min_panes=5 {
+ pane split_direction="vertical" {
+ pane
+ pane stacked=true { children; }
+ }
+ }
+}
+
+swap_floating_layout name="staggered" {
+ floating_panes
+}
+
+swap_floating_layout name="enlarged" {
+ floating_panes max_panes=10 {
+ pane { x "5%"; y 1; width "90%"; height "90%"; }
+ pane { x "5%"; y 2; width "90%"; height "90%"; }
+ pane { x "5%"; y 3; width "90%"; height "90%"; }
+ pane { x "5%"; y 4; width "90%"; height "90%"; }
+ pane { x "5%"; y 5; width "90%"; height "90%"; }
+ pane { x "5%"; y 6; width "90%"; height "90%"; }
+ pane { x "5%"; y 7; width "90%"; height "90%"; }
+ pane { x "5%"; y 8; width "90%"; height "90%"; }
+ pane { x "5%"; y 9; width "90%"; height "90%"; }
+ pane { x 10; y 10; width "90%"; height "90%"; }
+ }
+}
+
+swap_floating_layout name="spread" {
+ floating_panes max_panes=1 {
+ pane {y "50%"; x "50%"; }
+ }
+ floating_panes max_panes=2 {
+ pane { x "1%"; y "25%"; width "45%"; }
+ pane { x "50%"; y "25%"; width "45%"; }
+ }
+ floating_panes max_panes=3 {
+ pane { y "55%"; width "45%"; height "45%"; }
+ pane { x "1%"; y "1%"; width "45%"; }
+ pane { x "50%"; y "1%"; width "45%"; }
+ }
+ floating_panes max_panes=4 {
+ pane { x "1%"; y "55%"; width "45%"; height "45%"; }
+ pane { x "50%"; y "55%"; width "45%"; height "45%"; }
+ pane { x "1%"; y "1%"; width "45%"; height "45%"; }
+ pane { x "50%"; y "1%"; width "45%"; height "45%"; }
+ }
+}
+
+// sl0p fork: `arrangement` builds a tree for N panes, instead of the default
+// "every extra pane becomes a flat sibling".
+swap_tiled_layout name="grid" {
+ tab min_panes=3 {
+ pane arrangement="grid" {
+ children
+ }
+ }
+}
+
+swap_tiled_layout name="master-stack" {
+ tab min_panes=2 {
+ pane arrangement="master-stack" {
+ children
+ }
+ }
+}
diff --git a/config/themes/gruber-darker-pop.kdl b/config/themes/gruber-darker-pop.kdl
new file mode 100644
index 0000000..1a65ad7
--- /dev/null
+++ b/config/themes/gruber-darker-pop.kdl
@@ -0,0 +1,159 @@
+// gruber-darker-pop — stock zellij `gruber-darker` (copied verbatim from
+// ~/lol/zellij/zellij-utils/assets/themes/gruber-darker.kdl) with ONE addition:
+// a `frame_unselected` block. Stock gruber-darker omits it, and zellij defaults
+// it to None (data.rs:1501), so unfocused frames get no explicit color.
+// focused frame = 255 221 51 (gruber yellow, from frame_selected)
+// unfocused frame= 72 72 72 (muted gray, below)
+// mouse hover = 115 201 54 (green, from frame_highlight)
+themes {
+ gruber-darker-pop {
+ text_unselected {
+ base 228 228 239
+ background 24 24 24
+ emphasis_0 149 169 159
+ emphasis_1 150 166 200
+ emphasis_2 115 201 54
+ emphasis_3 255 221 51
+ }
+ text_selected {
+ base 228 228 239
+ background 40 40 40
+ emphasis_0 255 255 255
+ emphasis_1 255 221 51
+ emphasis_2 115 201 54
+ emphasis_3 150 166 200
+ }
+ ribbon_unselected {
+ base 228 228 239
+ background 40 40 40
+ emphasis_0 149 169 159
+ emphasis_1 204 140 60
+ emphasis_2 150 166 200
+ emphasis_3 158 149 199
+ }
+ ribbon_selected {
+ base 0 0 0
+ background 255 221 51
+ emphasis_0 244 244 255
+ emphasis_1 255 255 255
+ emphasis_2 115 201 54
+ emphasis_3 150 166 200
+ }
+ table_title {
+ base 255 221 51
+ background 24 24 24
+ emphasis_0 255 255 255
+ emphasis_1 244 244 255
+ emphasis_2 150 166 200
+ emphasis_3 115 201 54
+ }
+ table_cell_unselected {
+ base 228 228 239
+ background 24 24 24
+ emphasis_0 149 169 159
+ emphasis_1 150 166 200
+ emphasis_2 204 140 60
+ emphasis_3 115 201 54
+ }
+ table_cell_selected {
+ base 228 228 239
+ background 69 61 65
+ emphasis_0 255 255 255
+ emphasis_1 255 221 51
+ emphasis_2 115 201 54
+ emphasis_3 150 166 200
+ }
+ list_unselected {
+ base 228 228 239
+ background 24 24 24
+ emphasis_0 149 169 159
+ emphasis_1 150 166 200
+ emphasis_2 204 140 60
+ emphasis_3 115 201 54
+ }
+ list_selected {
+ base 244 244 255
+ background 72 72 72
+ emphasis_0 255 255 255
+ emphasis_1 255 221 51
+ emphasis_2 115 201 54
+ emphasis_3 150 166 200
+ }
+ // ── added: inactive pane frames recede so the focused one pops ──
+ // base = the border color of every UNFOCUSED pane. Tune this one number:
+ // lower = more recessive (24 24 24 is invisible against the bg), higher = louder.
+ // Terminals cannot do WM-style pane gaps (no gap/margin/padding option
+ // exists in zellij at all). Every cell is content or chrome, so the frame
+ // IS the only possible separator between two panes. Going the other way
+ // -- pane_frame_style "titles" -- removed the separator and read as MORE
+ // cramped, not less. So: louder frames = more perceived separation.
+ // Was base 72 / emphasis 90. Keep emphasis ~24 above base or the
+ // hierarchy inverts and frame accents go darker than the frame itself.
+ frame_unselected {
+ base 96 96 96
+ background 24 24 24
+ emphasis_0 114 114 114
+ emphasis_1 114 114 114
+ emphasis_2 114 114 114
+ emphasis_3 114 114 114
+ }
+ // ── focused pane frame ──
+ // base = the border color of the FOCUSED pane. Stock gruber yellow
+ // (255 221 51) sits at 4.68:1 contrast against the unfocused frame
+ // (96 96 96) -- roughly double what's needed to read as "focused", which
+ // is why it glares. What actually signals focus is contrast against the
+ // UNFOCUSED frame plus a hue shift, not raw brightness, so this drops to
+ // a calm desaturated blue at 2.57:1 -- still unmistakable, no glare.
+ // It also stays clear of frame_highlight's green (mouse hover) in hue.
+ // The theme keeps its yellow identity in the status bar / ribbons.
+ // Alternatives (swap base, all measured vs the 96 96 96 unfocused frame):
+ // 199 164 74 muted gold 2.65:1 <- keeps the gruber yellow hue
+ // 158 149 199 theme violet 2.27:1
+ // 149 169 159 theme sage 2.53:1
+ // 255 221 51 stock yellow 4.68:1 <- original
+ frame_selected {
+ base 150 166 200
+ background 24 24 24
+ emphasis_0 255 255 255
+ emphasis_1 244 244 255
+ emphasis_2 115 201 54
+ emphasis_3 150 166 200
+ }
+ frame_highlight {
+ base 115 201 54
+ background 24 24 24
+ emphasis_0 255 255 255
+ emphasis_1 255 221 51
+ emphasis_2 150 166 200
+ emphasis_3 158 149 199
+ }
+ exit_code_success {
+ base 115 201 54
+ background 24 24 24
+ emphasis_0 255 255 255
+ emphasis_1 255 221 51
+ emphasis_2 150 166 200
+ emphasis_3 158 149 199
+ }
+ exit_code_error {
+ base 255 79 88
+ background 24 24 24
+ emphasis_0 255 255 255
+ emphasis_1 255 221 51
+ emphasis_2 150 166 200
+ emphasis_3 158 149 199
+ }
+ multiplayer_user_colors {
+ player_1 255 221 51
+ player_2 115 201 54
+ player_3 150 166 200
+ player_4 158 149 199
+ player_5 149 169 159
+ player_6 204 140 60
+ player_7 255 79 88
+ player_8 244 56 65
+ player_9 199 60 63
+ player_10 255 255 255
+ }
+ }
+}
diff --git a/config/themes/templeos.kdl b/config/themes/templeos.kdl
new file mode 100644
index 0000000..b6848d1
--- /dev/null
+++ b/config/themes/templeos.kdl
@@ -0,0 +1,133 @@
+// TempleOS — "God's temple" 640x480 16-color VGA look for Zellij.
+// Palette is the exact TempleOS/VGA 16:
+// BLACK 0,0,0 BLUE 0,0,170 GREEN 0,170,0 CYAN 0,170,170
+// RED 170,0,0 PURPLE 170,0,170 BROWN 170,85,0 LTGRAY 170,170,170
+// DKGRAY 85,85,85 LTBLUE 85,85,255 LTGREEN 85,255,85 LTCYAN 85,255,255
+// LTRED 255,85,85 LTPURPLE 255,85,255 YELLOW 255,255,85 WHITE 255,255,255
+// Desktop is WHITE with BLACK text, selected things are white-on-BLUE,
+// like a highlighted line in a TempleOS window.
+
+themes {
+ templeos {
+ // normal UI text: black on white, TempleOS desktop
+ text_unselected {
+ base 0 0 0
+ background 255 255 255
+ emphasis_0 170 0 0 // RED
+ emphasis_1 0 0 170 // BLUE
+ emphasis_2 0 170 0 // GREEN
+ emphasis_3 170 0 170 // PURPLE
+ }
+ text_selected {
+ base 255 255 255
+ background 0 0 170 // white on BLUE, the TempleOS cursor line
+ emphasis_0 255 255 85 // YELLOW
+ emphasis_1 85 255 255 // LTCYAN
+ emphasis_2 85 255 85 // LTGREEN
+ emphasis_3 255 85 255 // LTPURPLE
+ }
+ // tabs / mode ribbons
+ ribbon_selected {
+ base 255 255 255
+ background 0 0 170 // BLUE "active task"
+ emphasis_0 255 255 85
+ emphasis_1 85 255 255
+ emphasis_2 85 255 85
+ emphasis_3 255 85 255
+ }
+ ribbon_unselected {
+ base 0 0 0
+ background 170 170 170 // LTGRAY inactive
+ emphasis_0 170 0 0
+ emphasis_1 255 255 255
+ emphasis_2 0 0 170
+ emphasis_3 170 0 170
+ }
+ table_title {
+ base 170 0 0 // RED headings, like TempleOS docs
+ background 0
+ emphasis_0 0 0 170
+ emphasis_1 0 170 0
+ emphasis_2 170 85 0
+ emphasis_3 170 0 170
+ }
+ table_cell_unselected {
+ base 0 0 0
+ background 255 255 255
+ emphasis_0 170 0 0
+ emphasis_1 0 0 170
+ emphasis_2 0 170 0
+ emphasis_3 170 0 170
+ }
+ table_cell_selected {
+ base 255 255 255
+ background 0 0 170
+ emphasis_0 255 255 85
+ emphasis_1 85 255 255
+ emphasis_2 85 255 85
+ emphasis_3 255 85 255
+ }
+ list_unselected {
+ base 0 0 0
+ background 255 255 255
+ emphasis_0 170 0 0
+ emphasis_1 0 0 170
+ emphasis_2 0 170 0
+ emphasis_3 170 0 170
+ }
+ list_selected {
+ base 255 255 255
+ background 0 0 170
+ emphasis_0 255 255 85
+ emphasis_1 85 255 255
+ emphasis_2 85 255 85
+ emphasis_3 255 85 255
+ }
+ // window borders: focused task gets a BLUE frame, like a TempleOS window
+ frame_selected {
+ base 0 0 170
+ background 0
+ emphasis_0 170 0 0
+ emphasis_1 0 170 0
+ emphasis_2 170 0 170
+ emphasis_3 0
+ }
+ frame_highlight {
+ base 170 0 0 // RED alert border
+ background 0
+ emphasis_0 255 85 85
+ emphasis_1 170 0 0
+ emphasis_2 170 0 0
+ emphasis_3 170 0 0
+ }
+ exit_code_success {
+ base 0 170 0
+ background 0
+ emphasis_0 0 170 170
+ emphasis_1 0 0 0
+ emphasis_2 170 0 170
+ emphasis_3 0 0 170
+ }
+ exit_code_error {
+ base 170 0 0
+ background 0
+ emphasis_0 255 255 85
+ emphasis_1 0
+ emphasis_2 0
+ emphasis_3 0
+ }
+ // the 16-color rotation TempleOS uses for its "God words"
+ multiplayer_user_colors {
+ player_1 170 0 0
+ player_2 0 0 170
+ player_3 0 170 0
+ player_4 170 85 0
+ player_5 170 0 170
+ player_6 0 170 170
+ player_7 255 85 85
+ player_8 85 85 255
+ player_9 85 255 85
+ player_10 85 85 85
+ }
+ }
+}