A .config/code/colors/vacme.lua => .config/code/colors/vacme.lua +345 -0
@@ 0,0 1,345 @@
+vim.cmd('hi clear')
+
+if vim.fn.exists("syntax_on") then
+ vim.cmd('syntax reset')
+end
+
+vim.g.colors_name = 'vacme'
+vim.o.background = 'light'
+
+local vcolors = {
+ -- whites
+ w1 = {term='15',hex='#FFFFEC'},
+ w2 = {term='11',hex='#EEEEA7'},
+ w3 = {term='7',hex='#999957'},
+ w4 = {term='0',hex='#424242'},
+
+ -- reds
+ r1 = {term='9',hex='#F2ACAA'},
+ r2 = {term='1',hex='#B85C57'},
+
+ -- greens
+ g1 = {term='193',hex='#EFFEEC'},
+ g2 = {term='10',hex='#98CE8F'},
+ g3 = {term='2',hex='#57864E'},
+
+ -- yellows
+ y1 = {term='187',hex='#EAEBDB'},
+ y2 = {term='8',hex='#B7B19C'},
+ y3 = {term='3',hex='#8F7634'},
+
+ -- blues
+ b1 = {term='195',hex='#E2F1F8'},
+ b2 = {term='12',hex='#A6DCF8'},
+ b3 = {term='4',hex='#2A8DC5'},
+
+ -- magentas
+ m1 = {term='13',hex='#D0D0F7'},
+ m2 = {term='5',hex='#8888C7'},
+
+ -- cyans
+ c1 = {term='194',hex='#EAFFFF'},
+ c2 = {term='14',hex='#B0ECED'},
+ c3 = {term='6',hex='#6AA7A8'},
+
+ -- misc (accent)
+ a1 = {term='195',hex='#030093'}
+}
+local vstyles = {
+ normal = {fg=vcolors.w4,bg=vcolors.w1},
+ ghostly = {fg=vcolors.y3,bg=vcolors.w1},
+ hilited = {fg=vcolors.w4,bg=vcolors.w2},
+ justbold = {style='bold'},
+ justuline = {style='underline'},
+ justit = {style='italic'}
+}
+
+function syntax(group, rule)
+ vim.cmd(string.format("hi! %s term=NONE cterm=NONE ctermfg=NONE ctermbg=NONE gui=NONE guifg=NONE guibg=NONE", group))
+ local fg = ""
+ if rule.fg then fg = string.format(' guifg=%s ctermfg=%s', rule.fg['hex'], rule.fg['term']) end
+ local bg = ""
+ if rule.bg then bg = string.format(' guibg=%s ctermbg=%s', rule.bg['hex'], rule.bg['term']) end
+ local style = ""
+ if rule.style then style = string.format(' gui=%s cterm=%s', rule.style, rule.style) end
+ vim.cmd(string.format( 'hi! %s%s%s%s', group, fg, bg, style))
+end
+function link(group, model)
+ vim.cmd(string.format("hi! link %s %s", group, model))
+end
+
+-- nvim UI elements
+syntax('Normal', vstyles.normal)
+syntax('Folded', vstyles.ghostly)
+syntax('FoldColumn', vstyles.ghostly)
+syntax('Terminal', vstyles.normal)
+syntax('ToolbarButton', vstyles.normal)
+syntax('ToolbarLine', vstyles.normal)
+syntax('CursorLine', {bg=vcolors.g1})
+syntax('LineNr', {fg=vcolors.w3,bg=vcolors.y1})
+link('LineNrAbove', 'LineNr')
+link('LineNrBelow', 'LineNr')
+syntax('FloatBorder', {fg=vcolors.g1,bg=vcolors.g3})
+syntax('NormalFloat', {fg=vcolors.g3,bg=vcolors.g1})
+syntax('DiffAdd', {fg=vcolors.w4, bg=vcolors.g2})
+syntax('DiffChange', {fg=vcolors.w4, bg=vcolors.c2})
+syntax('DiffDelete', {fg=vcolors.w4, bg=vcolors.r1})
+syntax('DiffText', {fg=vcolors.w4, bg=vcolors.g2})
+syntax('StatusLine', {fg=vcolors.w4, bg=vcolors.c1, style='bold,underline'})
+syntax('StatusLineNC', {fg=vcolors.w4, bg=vcolors.c1})
+link('TabLine', 'StatusLineNC')
+link('TabLineFill', 'StatusLineNC')
+link('WinSeparator', 'StatusLineNC')
+syntax('TabLineSel', {fg=vcolors.w1, bg=vcolors.m2})
+syntax('CursorLineNr', {fg=vcolors.w1,bg=vcolors.m2})
+syntax('NonText', {fg=vcolors.w3})
+syntax('SpecialKey', vstyles.justbold)
+syntax('SpellBad', {fg=vcolors.r2,style='underline'})
+syntax('SpellCap', vstyles.justuline)
+syntax('SpellLocal', vstyles.justuline)
+syntax('SpellRare', vstyles.justuline)
+syntax('Title', vstyles.justbold)
+syntax('ColorColumn', vstyles.hilited)
+syntax('Conceal', vstyles.ghostly)
+syntax('CursorColumn', {bg=vcolors.g1})
+syntax('Directory', vstyles.justbold)
+syntax('EndOfBuffer', vstyles.ghostly)
+syntax('ErrorMsg', vstyles.justbold)
+syntax('IncSearch', {fg=vcolors.w1,bg=vcolors.m2})
+syntax('MatchParen', {bg=vcolors.w2,style='bold'})
+syntax('ModeMsg', vstyles.justbold)
+syntax('MoreMsg', vstyles.justbold)
+syntax('Pmenu', {fg=vcolors.g3,bg=vcolors.g1})
+syntax('PmenuSbar', {fg=vcolors.g1,bg=vcolors.g3})
+syntax('PmenuSel', {fg=vcolors.w4,bg=vcolors.g2,style='underline'})
+syntax('PmenuKindSel', {fg=vcolors.w4,bg=vcolors.g2})
+link('PmenuExtraSel', 'PmenuKindSel')
+syntax('PmenuThumb', {fg=vcolors.g1,bg=vcolors.w4})
+syntax('Question', vstyles.justbold)
+syntax('Search', vstyles.hilited)
+link('SignColumn', 'LineNr')
+syntax('Visual', vstyles.hilited)
+syntax('VisualNOS', vstyles.hilited)
+syntax('WarningMsg', vstyles.justbold)
+syntax('WildMenu', {fg=vcolors.w1,bg=vcolors.m2})
+syntax('QuickFixLine', vstyles.justbold)
+
+-- language syntax (acme philosophy: almost no color, just bold comments)
+syntax('Comment', vstyles.justbold)
+
+syntax('Constant', vstyles.normal)
+syntax('String', vstyles.normal)
+syntax('Character', vstyles.normal)
+syntax('Number', vstyles.normal)
+syntax('Boolean', vstyles.normal)
+syntax('Float', vstyles.normal)
+
+syntax('Identifier', vstyles.normal)
+syntax('Function', vstyles.normal)
+
+syntax('Statement', vstyles.normal)
+syntax('Conditional', vstyles.normal)
+syntax('Repeat', vstyles.normal)
+syntax('Label', vstyles.normal)
+syntax('Operator', vstyles.normal)
+syntax('Keyword', vstyles.normal)
+syntax('Exception', vstyles.normal)
+
+syntax('PreProc', vstyles.normal)
+syntax('Include', vstyles.normal)
+syntax('Define', vstyles.normal)
+syntax('Macro', vstyles.normal)
+syntax('PreCondit', vstyles.normal)
+
+syntax('Type', vstyles.normal)
+syntax('StorageClass', vstyles.normal)
+syntax('Structure', vstyles.normal)
+syntax('Typedef', vstyles.normal)
+
+syntax('Special', vstyles.normal)
+syntax('SpecialChar', vstyles.normal)
+syntax('Tag', vstyles.justuline)
+syntax('Delimiter', vstyles.normal)
+syntax('SpecialComment', vstyles.normal)
+syntax('Debug', vstyles.normal)
+
+syntax('Underlined', vstyles.justuline)
+
+syntax('Ignore', vstyles.justbold)
+
+syntax('Error', vstyles.normal)
+
+syntax('Todo', vstyles.hilited)
+
+-- TreeSitter (legacy names)
+link('TSAnnotation', 'Normal')
+link('TSBoolean', 'Normal')
+link('TSCharacter', 'Normal')
+link('TSComment', 'Comment')
+link('TSConditional', 'Normal')
+link('TSConstant', 'Normal')
+link('TSConstBuiltin', 'Normal')
+link('TSConstMacro', 'Normal')
+link('TSError', 'Error')
+link('TSException', 'Normal')
+link('TSField', 'Normal')
+link('TSFloat', 'Normal')
+link('TSFunction', 'Normal')
+link('TSFuncBuiltin', 'Normal')
+link('TSFuncMacro', 'Normal')
+link('TSInclude', 'Normal')
+link('TSKeyword', 'Normal')
+link('TSLabel', 'Normal')
+link('TSMethod', 'Normal')
+link('TSNamespace', 'Normal')
+link('TSNumber', 'Normal')
+link('TSOperator', 'Normal')
+link('TSParameterReference', 'Normal')
+link('TSProperty', 'Normal')
+link('TSPunctDelimiter', 'Normal')
+link('TSPunctBracket', 'Normal')
+link('TSPunctSpecial', 'Normal')
+link('TSRepeat', 'Normal')
+link('TSString', 'Normal')
+link('TSStringRegex', 'Normal')
+link('TSStringEscape', 'Normal')
+link('TSStrong', 'Normal')
+link('TSConstructor', 'Normal')
+link('TSKeywordFunction', 'Normal')
+link('TSLiteral', 'Normal')
+link('TSParameter', 'Normal')
+link('TSVariable', 'Normal')
+link('TSVariableBuiltin', 'Normal')
+link('TSTag', 'Normal')
+link('TSTagDelimiter', 'Normal')
+link('TSTitle', 'Normal')
+link('TSType', 'Normal')
+link('TSTypeBuiltin', 'Normal')
+link('TSEmphasis', 'Normal')
+
+-- Neovim >= 0.8 treesitter captures
+link('@comment', 'Comment')
+link('@error', 'Error')
+link('@none', 'Normal')
+link('@preproc', 'Normal')
+link('@keyword.directive', 'Normal')
+link('@define', 'Normal')
+link('@keyword.directive.define', 'Normal')
+link('@operator', 'Normal')
+link('@punctuation.delimiter', 'Normal')
+link('@markup.raw.delimiter', 'Normal')
+link('@punctuation.bracket', 'Normal')
+link('@punctuation.special', 'Normal')
+link('@markup.list', 'Normal')
+link('@string', 'Normal')
+link('@string.regex', 'Normal')
+link('@string.regexp', 'Normal')
+link('@string.escape', 'Normal')
+link('@string.special', 'Normal')
+link('@markup.link.label', 'Normal')
+link('@character', 'Normal')
+link('@character.special', 'Normal')
+link('@boolean', 'Normal')
+link('@number', 'Normal')
+link('@float', 'Normal')
+link('@number.float', 'Normal')
+link('@function', 'Normal')
+link('@function.call', 'Normal')
+link('@function.builtin', 'Normal')
+link('@function.macro', 'Normal')
+link('@method', 'Normal')
+link('@method.call', 'Normal')
+link('@function.method', 'Normal')
+link('@function.method.call', 'Normal')
+link('@constructor', 'Normal')
+link('@parameter', 'Normal')
+link('@variable.parameter', 'Normal')
+link('@keyword', 'Normal')
+link('@keyword.function', 'Normal')
+link('@keyword.operator', 'Normal')
+link('@keyword.return', 'Normal')
+link('@conditional', 'Normal')
+link('@repeat', 'Normal')
+link('@debug', 'Normal')
+link('@keyword.conditional', 'Normal')
+link('@keyword.repeat', 'Normal')
+link('@keyword.debug', 'Normal')
+link('@label', 'Normal')
+link('@include', 'Normal')
+link('@exception', 'Normal')
+link('@keyword.import', 'Normal')
+link('@keyword.exception', 'Normal')
+link('@type', 'Normal')
+link('@type.builtin', 'Normal')
+link('@type.qualifier', 'Normal')
+link('@type.definition', 'Normal')
+link('@storageclass', 'Normal')
+link('@keyword.storage', 'Normal')
+link('@attribute', 'Normal')
+link('@field', 'Normal')
+link('@variable.member', 'Normal')
+link('@property', 'Normal')
+link('@variable', 'Normal')
+link('@variable.builtin', 'Normal')
+link('@constant', 'Normal')
+link('@constant.builtin', 'Normal')
+link('@constant.macro', 'Normal')
+link('@namespace', 'Normal')
+link('@module', 'Normal')
+link('@symbol', 'Normal')
+link('@string.special.symbol', 'Normal')
+link('@text', 'Normal')
+link('@text.strong', 'Normal')
+link('@text.emphasis', 'Normal')
+link('@text.underline', 'Underlined')
+link('@text.strike', 'Normal')
+link('@markup.strong', 'Normal')
+link('@markup.emphasis', 'Normal')
+link('@markup.italic', 'Normal')
+link('@markup.underline', 'Underlined')
+link('@markup.strike', 'Normal')
+link('@markup.strikethrough', 'Normal')
+link('@text.title', 'Title')
+link('@text.literal', 'Normal')
+link('@markup.heading', 'Title')
+link('@markup.raw', 'Normal')
+link('@text.uri', 'Underlined')
+link('@string.special.url', 'Underlined')
+link('@markup.link.url', 'Underlined')
+link('@text.math', 'Normal')
+link('@text.environment', 'Normal')
+link('@text.environment.name', 'Normal')
+link('@markup.math', 'Normal')
+link('@markup.environment', 'Normal')
+link('@markup.environment.name', 'Normal')
+link('@text.reference', 'Normal')
+link('@markup.link', 'Normal')
+link('@text.todo', 'Todo')
+link('@markup.list.checked', 'Todo')
+link('@markup.list.unchecked', 'Todo')
+link('@comment.todo', 'Todo')
+link('@text.note', 'WarningMsg')
+link('@comment.info', 'WarningMsg')
+link('@comment.hint', 'WarningMsg')
+link('@text.warning', 'WarningMsg')
+link('@comment.warning', 'WarningMsg')
+link('@text.danger', 'ErrorMsg')
+link('@comment.danger', 'ErrorMsg')
+link('@tag', 'Normal')
+link('@tag.attribute', 'Normal')
+link('@tag.delimiter', 'Normal')
+
+-- Diagnostics
+syntax('DiagnosticError', {fg=vcolors.r2})
+syntax('DiagnosticWarn', {fg=vcolors.y3})
+syntax('DiagnosticInfo', {fg=vcolors.b3})
+syntax('DiagnosticHint', {fg=vcolors.g3})
+syntax('DiagnosticUnderlineError', {style='underline'})
+syntax('DiagnosticUnderlineWarn', {style='underline'})
+syntax('DiagnosticUnderlineInfo', {style='underline'})
+syntax('DiagnosticUnderlineHint', {style='underline'})
+
+-- Git signs
+link('GitSignsAdd', 'DiffAdd')
+link('GitSignsChange', 'DiffChange')
+link('GitSignsDelete', 'DiffDelete')
A .config/code/init.lua => .config/code/init.lua +149 -0
@@ 0,0 1,149 @@
+-- nvim coding config
+-- Launch: NVIM_APPNAME=code nvim
+
+vim.g.mapleader = " "
+
+-- Disable netrw (snacks explorer replaces it)
+vim.g.loaded_netrw = 1
+vim.g.loaded_netrwPlugin = 1
+
+-- Appearance
+vim.opt.number = true
+vim.opt.relativenumber = true
+vim.opt.signcolumn = "yes"
+vim.opt.termguicolors = true
+vim.opt.cursorline = true
+vim.opt.showmode = false
+vim.opt.laststatus = 2
+vim.opt.fillchars = { eob = " " }
+
+-- Kill syntax highlighting
+vim.cmd("syntax off")
+vim.cmd("filetype plugin indent on")
+
+-- Colorscheme
+vim.cmd("colorscheme vacme")
+
+-- Indentation
+vim.opt.expandtab = true
+vim.opt.tabstop = 4
+vim.opt.shiftwidth = 4
+vim.opt.smartindent = true
+vim.opt.autoindent = true
+
+-- Search
+vim.opt.ignorecase = true
+vim.opt.smartcase = true
+vim.opt.hlsearch = false
+vim.opt.incsearch = true
+
+-- Wrapping
+vim.opt.wrap = false
+vim.opt.scrolloff = 8
+vim.opt.sidescrolloff = 8
+
+-- Misc
+vim.opt.swapfile = false
+vim.opt.backup = false
+vim.opt.undofile = true
+vim.opt.undodir = vim.fn.stdpath("data") .. "/undo"
+vim.opt.clipboard = "unnamedplus"
+vim.opt.mouse = "a"
+vim.opt.updatetime = 250
+
+-- snacks.nvim (explorer + project picker)
+require("snacks").setup({
+ explorer = { enabled = true },
+ picker = {
+ enabled = true,
+ sources = {
+ projects = {
+ dev = { "~/dev" },
+ recent = false,
+ },
+ },
+ },
+})
+
+-- gitsigns
+require("gitsigns").setup({
+ signs = {
+ add = { text = "+" },
+ change = { text = "~" },
+ delete = { text = "_" },
+ topdelete = { text = "-" },
+ changedelete = { text = "~" },
+ },
+})
+
+-- lualine (statusline)
+require("lualine").setup({
+ options = {
+ theme = {
+ normal = {
+ a = { fg = "#424242", bg = "#EAFFFF", gui = "bold" },
+ b = { fg = "#424242", bg = "#EAEBDB" },
+ c = { fg = "#424242", bg = "#FFFFEC" },
+ },
+ insert = {
+ a = { fg = "#424242", bg = "#EFFEEC", gui = "bold" },
+ },
+ visual = {
+ a = { fg = "#424242", bg = "#EEEEA7", gui = "bold" },
+ },
+ command = {
+ a = { fg = "#424242", bg = "#D0D0F7", gui = "bold" },
+ },
+ inactive = {
+ c = { fg = "#999957", bg = "#FFFFEC" },
+ },
+ },
+ icons_enabled = false,
+ component_separators = { left = "|", right = "|" },
+ section_separators = { left = "", right = "" },
+ },
+ sections = {
+ lualine_a = { "mode" },
+ lualine_b = { "branch" },
+ lualine_c = { "filename" },
+ lualine_x = { "filetype" },
+ lualine_y = { "progress" },
+ lualine_z = { "location" },
+ },
+})
+
+-- Keymaps
+local map = vim.keymap.set
+
+-- Explorer
+map("n", "<leader>e", function() Snacks.explorer.open() end, { desc = "Toggle file explorer" })
+
+-- Project switcher (browse ~/dev directories)
+map("n", "<leader>fp", function()
+ local dev_root = vim.fn.expand("~/dev")
+ Snacks.picker.files({
+ cwd = dev_root,
+ cmd = "fd",
+ args = { "--type", "d", "--max-depth", "1" },
+ confirm = function(picker, item)
+ picker:close()
+ if item then
+ local dir = dev_root .. "/" .. item.text:gsub("/$", "")
+ vim.cmd("cd " .. vim.fn.fnameescape(dir))
+ Snacks.picker.files({ cwd = dir })
+ end
+ end,
+ })
+end, { desc = "Switch project" })
+
+-- File search
+map("n", "<leader>sf", function() Snacks.picker.files() end, { desc = "Search files" })
+map("n", "<leader>fg", function() Snacks.picker.grep() end, { desc = "Grep" })
+map("n", "<leader>ff", function() Snacks.picker.smart() end, { desc = "Smart find" })
+map("n", "<leader>s.", function() Snacks.picker.recent() end, { desc = "Recent files" })
+map("n", "<leader><leader>", function() Snacks.picker.buffers() end, { desc = "Buffers" })
+map("n", "<leader>/", function() Snacks.picker.lines() end, { desc = "Search in buffer" })
+
+-- Basics
+map("n", "<leader>w", "<cmd>w<cr>", { desc = "Save" })
+map("n", "<leader>q", "<cmd>q<cr>", { desc = "Quit" })
A .config/conky/clock.conf => .config/conky/clock.conf +46 -0
@@ 0,0 1,46 @@
+-- =============================================================================
+-- Conky Clock - Plan 9 Analog Clock
+-- Author: Kris Yotam
+-- =============================================================================
+
+conky.config = {
+ alignment = 'top_right',
+ background = false,
+ border_width = 0,
+ border_inner_margin = 0,
+
+ lua_load = '/home/krisyotam/.config/conky/clock.lua',
+ lua_draw_hook_post = 'draw_clock',
+
+ default_color = '1a2028',
+
+ double_buffer = true,
+ draw_borders = false,
+ draw_graph_borders = false,
+ draw_outline = false,
+ draw_shades = false,
+ use_xft = true,
+ font = 'JetBrainsMono Nerd Font:size=9',
+
+ xinerama_head = 0,
+ gap_x = 57,
+ gap_y = 55,
+
+ minimum_width = 180,
+ maximum_width = 180,
+ minimum_height = 180,
+
+ own_window = true,
+ own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
+ own_window_class = 'ConkyClock',
+ own_window_type = 'override',
+ own_window_transparent = false,
+ own_window_argb_visual = true,
+ own_window_argb_value = 190,
+ own_window_colour = '1a2028',
+
+ update_interval = 1,
+}
+
+conky.text = [[
+]]
A .config/conky/clock.lua => .config/conky/clock.lua +106 -0
@@ 0,0 1,106 @@
+require "cairo"
+
+-- Plan 9 style analog clock
+-- Cream face, cyan border, simple black hands
+
+local border_width = 8
+local border_color = { 0.918, 1.0, 1.0, 1.0 } -- #EAFFFF
+local face_color = { 1.0, 1.0, 0.918, 1.0 } -- #FFFFEA (Plan 9 cream)
+local hand_color = { 0.05, 0.05, 0.05, 1.0 } -- near black
+local tick_color = { 0.3, 0.3, 0.3, 1.0 } -- dark grey
+local second_color = { 0.83, 0.42, 0.42, 1.0 } -- #D46A6A muted red
+local center_color = { 0.3, 0.3, 0.3, 1.0 }
+
+function conky_draw_clock()
+ if conky_window == nil then return end
+
+ local cs = cairo_xlib_surface_create(
+ conky_window.display,
+ conky_window.drawable,
+ conky_window.visual,
+ conky_window.width,
+ conky_window.height
+ )
+ local cr = cairo_create(cs)
+
+ local w = conky_window.width
+ local h = conky_window.height
+ local cx = w / 2
+ local cy = h / 2
+ local radius = math.min(w, h) / 2 - border_width - 8
+
+ -- Border (4 filled rectangles)
+ local bw = border_width
+ cairo_set_source_rgba(cr, table.unpack(border_color))
+ cairo_rectangle(cr, 0, 0, w, bw)
+ cairo_rectangle(cr, 0, h - bw, w, bw)
+ cairo_rectangle(cr, 0, 0, bw, h)
+ cairo_rectangle(cr, w - bw, 0, bw, h)
+ cairo_fill(cr)
+
+ -- Clock face
+ cairo_set_source_rgba(cr, table.unpack(face_color))
+ cairo_arc(cr, cx, cy, radius, 0, 2 * math.pi)
+ cairo_fill(cr)
+
+ -- Face outline
+ cairo_set_source_rgba(cr, table.unpack(tick_color))
+ cairo_set_line_width(cr, 1.5)
+ cairo_arc(cr, cx, cy, radius, 0, 2 * math.pi)
+ cairo_stroke(cr)
+
+ -- Hour ticks
+ for i = 0, 11 do
+ local angle = (i * 30 - 90) * math.pi / 180
+ local inner = radius * 0.85
+ local outer = radius * 0.95
+ local tick_w = 2.0
+ if i % 3 == 0 then
+ inner = radius * 0.78
+ tick_w = 3.0
+ end
+ cairo_set_line_width(cr, tick_w)
+ cairo_set_source_rgba(cr, table.unpack(tick_color))
+ cairo_move_to(cr, cx + inner * math.cos(angle), cy + inner * math.sin(angle))
+ cairo_line_to(cr, cx + outer * math.cos(angle), cy + outer * math.sin(angle))
+ cairo_stroke(cr)
+ end
+
+ -- Get time
+ local hours = tonumber(os.date("%I"))
+ local mins = tonumber(os.date("%M"))
+ local secs = tonumber(os.date("%S"))
+
+ -- Hour hand
+ local hour_angle = ((hours + mins / 60) * 30 - 90) * math.pi / 180
+ cairo_set_source_rgba(cr, table.unpack(hand_color))
+ cairo_set_line_width(cr, 4)
+ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND)
+ cairo_move_to(cr, cx, cy)
+ cairo_line_to(cr, cx + radius * 0.5 * math.cos(hour_angle), cy + radius * 0.5 * math.sin(hour_angle))
+ cairo_stroke(cr)
+
+ -- Minute hand
+ local min_angle = ((mins + secs / 60) * 6 - 90) * math.pi / 180
+ cairo_set_source_rgba(cr, table.unpack(hand_color))
+ cairo_set_line_width(cr, 2.5)
+ cairo_move_to(cr, cx, cy)
+ cairo_line_to(cr, cx + radius * 0.72 * math.cos(min_angle), cy + radius * 0.72 * math.sin(min_angle))
+ cairo_stroke(cr)
+
+ -- Second hand
+ local sec_angle = (secs * 6 - 90) * math.pi / 180
+ cairo_set_source_rgba(cr, table.unpack(second_color))
+ cairo_set_line_width(cr, 1.2)
+ cairo_move_to(cr, cx, cy)
+ cairo_line_to(cr, cx + radius * 0.78 * math.cos(sec_angle), cy + radius * 0.78 * math.sin(sec_angle))
+ cairo_stroke(cr)
+
+ -- Center dot
+ cairo_set_source_rgba(cr, table.unpack(center_color))
+ cairo_arc(cr, cx, cy, 4, 0, 2 * math.pi)
+ cairo_fill(cr)
+
+ cairo_destroy(cr)
+ cairo_surface_destroy(cs)
+end
M .config/nvim/colors/acme.lua => .config/nvim/colors/acme.lua +2 -2
@@ 12,8 12,8 @@ local colors = {
bg_dim = "#ffffca",
bg_dark = "#eeee9e",
cursor = "#98ce8f",
- tagbar = "#aeeeee",
- tagbar_inactive = "#eaffff",
+ tagbar = "#eaffff",
+ tagbar_inactive = "#bdc1bb",
error = "#b85c57",
warn = "#8f7634",
M .config/wal/postrun => .config/wal/postrun +2 -0
@@ 32,4 32,6 @@ fix_sequences() {
fix_sequences <"${XDG_CACHE_HOME:-$HOME/.cache}/wal/sequences"
+xrdb -merge "${XDG_CACHE_HOME:-$HOME/.cache}/wal/colors-dwm-xresources"
+
pkill dunst; setsid -f dunst=
\ No newline at end of file
D .config/wal/templates/btop.theme => .config/wal/templates/btop.theme +0 -62
@@ 1,62 0,0 @@
-# btop pywal theme - auto-generated by pywal
-# Background is transparent (set theme_background = False in btop.conf)
-
-# Main background and foreground
-theme[main_bg]="{background}"
-theme[main_fg]="{foreground}"
-
-# Title and highlighted text
-theme[title]="{foreground}"
-theme[hi_fg]="{color4}"
-
-# Selected item
-theme[selected_bg]="{color1}"
-theme[selected_fg]="{foreground}"
-
-# Inactive/disabled text
-theme[inactive_fg]="{color8}"
-
-# Graph colors
-theme[proc_misc]="{color3}"
-
-# Box borders
-theme[cpu_box]="{color1}"
-theme[mem_box]="{color2}"
-theme[net_box]="{color3}"
-theme[proc_box]="{color4}"
-
-# CPU graph gradient (low to high)
-theme[cpu_start]="{color2}"
-theme[cpu_mid]="{color1}"
-theme[cpu_end]="{color4}"
-
-# Memory graph colors
-theme[free_start]="{color2}"
-theme[free_mid]="{color3}"
-theme[free_end]="{color4}"
-
-theme[cached_start]="{color1}"
-theme[cached_mid]="{color2}"
-theme[cached_end]="{color3}"
-
-theme[available_start]="{color6}"
-theme[available_mid]="{color5}"
-theme[available_end]="{color8}"
-
-theme[used_start]="{color4}"
-theme[used_mid]="{color1}"
-theme[used_end]="{color3}"
-
-# Network graph
-theme[download_start]="{color1}"
-theme[download_mid]="{color2}"
-theme[download_end]="{color4}"
-
-theme[upload_start]="{color3}"
-theme[upload_mid]="{color2}"
-theme[upload_end]="{color6}"
-
-# Process graph
-theme[process_start]="{color4}"
-theme[process_mid]="{color1}"
-theme[process_end]="{color3}"
A .config/wal/templates/colors-dwm-xresources => .config/wal/templates/colors-dwm-xresources +33 -0
@@ 0,0 1,33 @@
+! dwm xresources generated by pywal
+dwm.normfgcolor: {foreground}
+dwm.normbgcolor: {background}
+dwm.normbordercolor: {color8}
+dwm.normfloatcolor: {color8}
+dwm.selfgcolor: {foreground}
+dwm.selbgcolor: {color2}
+dwm.selbordercolor: {foreground}
+dwm.selfloatcolor: {color2}
+dwm.titlenormfgcolor: {foreground}
+dwm.titlenormbgcolor: {background}
+dwm.titlenormbordercolor: {color8}
+dwm.titlenormfloatcolor: {color8}
+dwm.titleselfgcolor: {foreground}
+dwm.titleselbgcolor: {color2}
+dwm.titleselbordercolor: {foreground}
+dwm.titleselfloatcolor: {color2}
+dwm.tagsnormfgcolor: {foreground}
+dwm.tagsnormbgcolor: {background}
+dwm.tagsnormbordercolor: {color8}
+dwm.tagsnormfloatcolor: {color8}
+dwm.tagsselfgcolor: {foreground}
+dwm.tagsselbgcolor: {color2}
+dwm.tagsselbordercolor: {foreground}
+dwm.tagsselfloatcolor: {color2}
+dwm.hidnormfgcolor: {color2}
+dwm.hidnormbgcolor: {background}
+dwm.hidselfgcolor: {color6}
+dwm.hidselbgcolor: {background}
+dwm.urgfgcolor: {foreground}
+dwm.urgbgcolor: {color1}
+dwm.urgbordercolor: {color1}
+dwm.urgfloatcolor: {color1}
M .config/wal/templates/zathurarc => .config/wal/templates/zathurarc +1 -1
@@ 43,4 43,4 @@ set completion-fg "{color4}"
set completion-highlight-bg "{color3}"
set completion-highlight-fg "{color4}"
set recolor-lightcolor "{background}"
-set recolor-darkcolor "{foreground}">
\ No newline at end of file
+set recolor-darkcolor "{foreground}"
A .config/write/colors/writing.lua => .config/write/colors/writing.lua +99 -0
@@ 0,0 1,99 @@
+vim.cmd("highlight clear")
+vim.g.colors_name = "writing"
+
+local bg = "#1a1a1a"
+local fg = "#c8c8c8"
+local dim = "#585858"
+local cursorline = "#222222"
+
+local hi = function(group, opts)
+ vim.api.nvim_set_hl(0, group, opts)
+end
+
+-- Core
+hi("Normal", { bg = bg, fg = fg })
+hi("NormalFloat", { bg = bg, fg = fg })
+hi("NormalNC", { bg = bg, fg = fg })
+hi("EndOfBuffer", { bg = bg, fg = bg })
+hi("CursorLine", { bg = cursorline })
+hi("CursorLineNr", { bg = cursorline, fg = fg })
+hi("LineNr", { fg = dim })
+hi("SignColumn", { bg = bg })
+hi("VertSplit", { bg = bg, fg = dim })
+hi("WinSeparator", { bg = bg, fg = dim })
+hi("StatusLine", { bg = bg, fg = bg })
+hi("StatusLineNC", { bg = bg, fg = bg })
+hi("TabLine", { bg = bg, fg = dim })
+hi("TabLineFill", { bg = bg })
+hi("TabLineSel", { bg = bg, fg = fg })
+
+-- Popup/float
+hi("Pmenu", { bg = "#222222", fg = fg })
+hi("PmenuSel", { bg = "#333333", fg = fg })
+hi("PmenuSbar", { bg = "#222222" })
+hi("PmenuThumb", { bg = dim })
+
+-- Search
+hi("Search", { bg = "#333333", fg = fg })
+hi("IncSearch", { bg = "#444444", fg = fg })
+
+-- Visual
+hi("Visual", { bg = "#333333" })
+hi("VisualNOS", { bg = "#333333" })
+
+-- Spelling
+hi("SpellBad", { undercurl = true, sp = "#cc6666" })
+hi("SpellCap", { undercurl = true, sp = "#6699cc" })
+hi("SpellRare", { undercurl = true, sp = "#b294bb" })
+hi("SpellLocal", { undercurl = true, sp = "#8abeb7" })
+
+-- Diff
+hi("DiffAdd", { bg = "#1d2e1d" })
+hi("DiffChange", { bg = "#2e2e1d" })
+hi("DiffDelete", { bg = "#2e1d1d" })
+hi("DiffText", { bg = "#3e3e1d" })
+
+-- Folding
+hi("Folded", { bg = "#222222", fg = dim })
+hi("FoldColumn", { bg = bg, fg = dim })
+
+-- Misc UI
+hi("Directory", { fg = fg })
+hi("Title", { fg = fg, bold = true })
+hi("Question", { fg = fg })
+hi("MoreMsg", { fg = fg })
+hi("WarningMsg", { fg = "#cc6666" })
+hi("ErrorMsg", { fg = "#cc6666", bg = bg })
+hi("NonText", { fg = dim })
+hi("SpecialKey", { fg = dim })
+hi("Conceal", { fg = dim })
+hi("MatchParen", { bg = "#444444" })
+hi("ColorColumn", { bg = "#222222" })
+hi("Cursor", { bg = fg, fg = bg })
+
+-- Everything text-like is just fg (no syntax highlighting)
+for _, group in ipairs({
+ "Comment", "Constant", "String", "Character", "Number", "Boolean", "Float",
+ "Identifier", "Function", "Statement", "Conditional", "Repeat", "Label",
+ "Operator", "Keyword", "Exception", "PreProc", "Include", "Define", "Macro",
+ "PreCondit", "Type", "StorageClass", "Structure", "Typedef", "Special",
+ "SpecialChar", "Tag", "Delimiter", "SpecialComment", "Debug", "Underlined",
+ "Error", "Todo",
+}) do
+ hi(group, { fg = fg, bg = "NONE" })
+end
+
+-- NvimTree
+hi("NvimTreeNormal", { bg = bg, fg = fg })
+hi("NvimTreeNormalNC", { bg = bg, fg = fg })
+hi("NvimTreeEndOfBuffer", { bg = bg, fg = bg })
+hi("NvimTreeWinSeparator", { bg = bg, fg = bg })
+hi("NvimTreeFolderIcon", { fg = dim })
+hi("NvimTreeFolderName", { fg = fg })
+hi("NvimTreeOpenedFolderName", { fg = fg })
+hi("NvimTreeRootName", { fg = fg, bold = true })
+
+-- Goyo padding windows
+hi("NormalNC", { bg = bg, fg = bg })
+hi("WinBar", { bg = bg, fg = bg })
+hi("WinBarNC", { bg = bg, fg = bg })
A .config/write/init.lua => .config/write/init.lua +234 -0
@@ 0,0 1,234 @@
+-- nvim writing config
+-- Launch: NVIM_APPNAME=write nvim
+-- Plugins: native pack/ (no plugin manager)
+
+vim.g.mapleader = " "
+
+-- Disable netrw (snacks explorer replaces it)
+vim.g.loaded_netrw = 1
+vim.g.loaded_netrwPlugin = 1
+
+-- Plugin settings (must be set before they load)
+vim.g.goyo_width = 80
+vim.g["goyo_height"] = "90%"
+vim.g.limelight_conceal_ctermfg = 240
+vim.g.limelight_conceal_guifg = "#585858"
+vim.g["pencil#wrapModeDefault"] = "soft"
+vim.g["pencil#textwidth"] = 80
+
+-- Appearance
+vim.opt.number = false
+vim.opt.relativenumber = false
+vim.opt.signcolumn = "no"
+vim.opt.showmode = false
+vim.opt.ruler = false
+vim.opt.laststatus = 0
+vim.opt.cmdheight = 1
+vim.opt.fillchars = { eob = " " }
+vim.opt.termguicolors = true
+
+-- Kill all syntax highlighting
+vim.cmd("syntax off")
+vim.cmd("filetype plugin on")
+vim.cmd("filetype indent off")
+
+-- Colorscheme
+vim.opt.background = "dark"
+vim.cmd("colorscheme writing")
+
+-- Writing defaults
+vim.opt.wrap = true
+vim.opt.linebreak = true
+vim.opt.breakindent = true
+vim.opt.textwidth = 0
+vim.opt.wrapmargin = 0
+vim.opt.spell = true
+vim.opt.spelllang = "en_us"
+vim.opt.cursorline = true
+vim.opt.scrolloff = 8
+vim.opt.sidescrolloff = 8
+
+-- Tabs
+vim.opt.expandtab = true
+vim.opt.tabstop = 4
+vim.opt.shiftwidth = 4
+
+-- Search
+vim.opt.ignorecase = true
+vim.opt.smartcase = true
+vim.opt.hlsearch = false
+vim.opt.incsearch = true
+
+-- Misc
+vim.opt.swapfile = false
+vim.opt.backup = false
+vim.opt.undofile = true
+vim.opt.undodir = vim.fn.stdpath("data") .. "/undo"
+vim.opt.clipboard = "unnamedplus"
+vim.opt.mouse = "a"
+
+-- snacks.nvim (explorer + project picker)
+require("snacks").setup({
+ explorer = { enabled = true },
+ picker = {
+ enabled = true,
+ sources = {
+ projects = {
+ dev = { "~/.corpus/content" },
+ recent = false,
+ },
+ },
+ },
+})
+
+-- Word count in statusline (shown in cmdline area)
+vim.api.nvim_create_autocmd({ "BufEnter", "CursorHold", "InsertLeave" }, {
+ pattern = "*",
+ callback = function()
+ local wc = vim.fn.wordcount()
+ local words = wc.visual_words or wc.words or 0
+ vim.opt.rulerformat = string.format("%%=%d words", words)
+ vim.opt.ruler = true
+ end,
+})
+
+-- Goyo enter/leave hooks
+vim.api.nvim_create_autocmd("User", {
+ pattern = "GoyoEnter",
+ callback = function()
+ vim.cmd("colorscheme writing")
+ for _, win in ipairs(vim.api.nvim_list_wins()) do
+ vim.api.nvim_win_set_option(win, "winhighlight", "Normal:Normal,NormalNC:Normal,EndOfBuffer:EndOfBuffer")
+ end
+ vim.cmd("Limelight")
+ vim.opt.showmode = false
+ vim.opt.showcmd = false
+ end,
+})
+
+vim.api.nvim_create_autocmd("User", {
+ pattern = "GoyoLeave",
+ callback = function()
+ vim.cmd("Limelight!")
+ vim.opt.showmode = false
+ vim.opt.showcmd = false
+ end,
+})
+
+-- Goyo/explorer coordination
+local goyo_was_on = false
+
+local function goyo_is_active()
+ return vim.fn.exists("#goyo") == 1
+end
+
+local function goyo_suspend()
+ if goyo_is_active() then
+ goyo_was_on = true
+ vim.cmd("Goyo!")
+ end
+end
+
+local function goyo_restore()
+ if goyo_was_on then
+ goyo_was_on = false
+ vim.defer_fn(function()
+ vim.cmd("Goyo")
+ end, 50)
+ end
+end
+
+-- Keymaps
+local map = vim.keymap.set
+
+-- Explorer (suspend/restore Goyo around it)
+map("n", "<leader>e", function()
+ local explorers = Snacks.picker.get({ source = "explorer" })
+ if #explorers > 0 then
+ explorers[1]:close()
+ goyo_restore()
+ else
+ goyo_suspend()
+ Snacks.explorer.open()
+ end
+end, { desc = "Toggle file explorer" })
+
+-- Project switcher (content directories + extras)
+map("n", "<leader>fp", function()
+ local content_root = vim.fn.expand("~/.corpus/content")
+ local dirs = {}
+
+ -- Grab subdirs from ~/.corpus/content
+ local handle = vim.loop.fs_scandir(content_root)
+ if handle then
+ while true do
+ local name, typ = vim.loop.fs_scandir_next(handle)
+ if not name then break end
+ if typ == "directory" and name:sub(1, 1) ~= "." then
+ table.insert(dirs, { name = name, path = content_root .. "/" .. name, text = name })
+ end
+ end
+ end
+
+ -- Extra content dirs outside .corpus/content
+ table.insert(dirs, { name = "slipbox", path = vim.fn.expand("~/dev/slipbox"), text = "slipbox" })
+
+ table.sort(dirs, function(a, b) return a.name < b.name end)
+
+ Snacks.picker({
+ title = "Content",
+ items = dirs,
+ format = function(item) return { { item.name } } end,
+ confirm = function(picker, item)
+ picker:close()
+ if item then
+ vim.cmd("cd " .. vim.fn.fnameescape(item.path))
+ Snacks.picker.files({ cwd = item.path })
+ end
+ end,
+ })
+end, { desc = "Switch content project" })
+
+-- File search
+map("n", "<leader>sf", function() Snacks.picker.files() end, { desc = "Search files" })
+map("n", "<leader>fg", function() Snacks.picker.grep() end, { desc = "Grep" })
+map("n", "<leader>ff", function() Snacks.picker.smart() end, { desc = "Smart find" })
+map("n", "<leader>s.", function() Snacks.picker.recent() end, { desc = "Recent files" })
+map("n", "<leader><leader>", function() Snacks.picker.buffers() end, { desc = "Buffers" })
+map("n", "<leader>/", function() Snacks.picker.lines() end, { desc = "Search in buffer" })
+
+-- Writing tools
+map("n", "<leader>g", "<cmd>Goyo<cr>", { desc = "Toggle Goyo" })
+map("n", "<leader>l", "<cmd>Limelight!!<cr>", { desc = "Toggle Limelight" })
+map("n", "<leader>s", "<cmd>set spell!<cr>", { desc = "Toggle spell check" })
+map("n", "<leader>w", "<cmd>w<cr>", { desc = "Save" })
+map("n", "<leader>q", "<cmd>q<cr>", { desc = "Quit" })
+
+-- Navigate wrapped lines naturally
+map("n", "j", "gj")
+map("n", "k", "gk")
+
+-- Pencil + Goyo auto-init for prose files
+vim.api.nvim_create_autocmd("FileType", {
+ pattern = { "markdown", "text", "tex" },
+ callback = function()
+ vim.cmd("PencilSoft")
+ end,
+})
+
+-- Autosave on focus loss, idle, or leaving insert mode
+vim.api.nvim_create_autocmd({ "FocusLost", "CursorHold", "InsertLeave" }, {
+ pattern = "*",
+ callback = function()
+ if vim.bo.modified and vim.bo.buftype == "" and vim.fn.expand("%") ~= "" then
+ vim.cmd("silent! write")
+ end
+ end,
+})
+
+-- Goyo on by default
+vim.api.nvim_create_autocmd("VimEnter", {
+ callback = function()
+ vim.cmd("Goyo")
+ end,
+})
M .config/x11/xprofile => .config/x11/xprofile +11 -8
@@ 3,28 3,31 @@
# This file runs when a DM logs you into a graphical session.
# If you use startx/xinit like a Chad, this file will also be sourced.
-# Load environment variables from login profile
-. "$HOME/.profile"
+# Add local bin to PATH for scripts
+export PATH="$HOME/.local/bin:$HOME/.local/bin/statusbar:$PATH"
# Auto-detect device type and set DPI accordingly
-DEVICE_TYPE_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/lazykris/device-type"
+DEVICE_TYPE_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/srice/device-type"
if [ -f "$DEVICE_TYPE_FILE" ] && [ "$(cat "$DEVICE_TYPE_FILE")" = "laptop" ]; then
xrandr --dpi 150 # Laptop HiDPI
else
xrandr --dpi 128 # Desktop 1440p scaled
fi
~/.screenlayout/main.sh # Set up monitor layout
+setxkbmap -option caps:none # Completely disable Caps Lock
xrdb -merge ${XDG_CONFIG_HOME:-$HOME/.config}/x11/xresources # Load base Xresources
-setbg -s -c "$HOME/.local/share/wallpapers/cs-winter-1920x960.webp" "$HOME/.local/share/wallpapers/p9blue-2560x1440.png" # solid bg + plan9 colorscheme
+xset s off -dpms s noblank # Disable DPMS/screensaver (freezes with dual-GPU + 4 monitors)
+setbg -s -c "$HOME/.local/share/wallpapers/plan9theme.png" "$HOME/.local/share/wallpapers/plan9.png" # plan9 solid bg + plan9 winter colorscheme
+setfan & # Set case fan RGB (ice blue)
-# Start gnome-keyring-daemon and export its environment variables
-eval $(gnome-keyring-daemon --start --components=pkcs11,secrets,ssh)
+# Start gnome-keyring-daemon with unlocked login keyring (needed for ProtonMail Bridge)
+eval $(echo -n "kris" | gnome-keyring-daemon --replace --unlock --components=pkcs11,secrets,ssh)
export SSH_AUTH_SOCK
-autostart="mpd picom dunst unclutter pipewire wireplumber conky"
+autostart="mpd picom dunst unclutter pipewire wireplumber conky protonmail-bridge"
for program in $autostart; do
pgrep -x "$program" >/dev/null 2>&1 || "$program" &
done >/dev/null 2>&1
-# dwmblocks is started by dwm's cool_autostart patch
+# dwmblocks is started by dwm autostart
M .config/x11/xresources => .config/x11/xresources +56 -0
@@ 4,6 4,62 @@
!! Set a default font and font size as below:
*.font: JetBrainsMono Nerd Font:size=14
+!! Plan 9 defaults
+*.foreground: #000000
+*.background: #737573
+*.color0: #000000
+*.color1: #aa0000
+*.color2: #448844
+*.color3: #99994c
+*.color4: #005dbb
+*.color5: #8888cc
+*.color6: #008888
+*.color7: #ffffea
+*.color8: #737573
+*.color9: #ff0000
+*.color10: #88cc88
+*.color11: #eeee9e
+*.color12: #4993dd
+*.color13: #ff00ff
+*.color14: #eaffff
+*.color15: #ffffff
+*.color256: #737573
+*.color257: #ffffea
+
+!! DWM Plan 9 defaults
+dwm.normfgcolor: #000000
+dwm.normbgcolor: #ffffea
+dwm.normbordercolor: #eaffff
+dwm.normfloatcolor: #eaffff
+dwm.selfgcolor: #000000
+dwm.selbgcolor: #eaffff
+dwm.selbordercolor: #eaffff
+dwm.selfloatcolor: #eaffff
+dwm.titlenormfgcolor: #000000
+dwm.titlenormbgcolor: #ffffea
+dwm.titlenormbordercolor: #eaffff
+dwm.titlenormfloatcolor: #eaffff
+dwm.titleselfgcolor: #000000
+dwm.titleselbgcolor: #eaffff
+dwm.titleselbordercolor: #eaffff
+dwm.titleselfloatcolor: #eaffff
+dwm.tagsnormfgcolor: #000000
+dwm.tagsnormbgcolor: #ffffea
+dwm.tagsnormbordercolor: #eaffff
+dwm.tagsnormfloatcolor: #eaffff
+dwm.tagsselfgcolor: #000000
+dwm.tagsselbgcolor: #eaffff
+dwm.tagsselbordercolor: #eaffff
+dwm.tagsselfloatcolor: #eaffff
+dwm.hidnormfgcolor: #737573
+dwm.hidnormbgcolor: #ffffea
+dwm.hidselfgcolor: #000000
+dwm.hidselbgcolor: #eaffff
+dwm.urgfgcolor: #000000
+dwm.urgbgcolor: #eeee9e
+dwm.urgbordercolor: #eaffff
+dwm.urgfloatcolor: #eaffff
+
/* name dark light */
/* black 0 8 */
/* red 1 9 */
A .local/bin/code => .local/bin/code +2 -0
@@ 0,0 1,2 @@
+#!/bin/sh
+NVIM_APPNAME=code exec nvim "$@"
M .local/bin/media/setbg => .local/bin/media/setbg +60 -7
@@ 12,9 12,14 @@
# setbg <wallpaper> -c <colorimg> # wallpaper from first, colors from second
# setbg -c <colorimg> <wallpaper> # same as above (order doesn't matter)
# setbg -s <wallpaper> # silent mode (no notifications)
+# setbg -w <wallpaper> # wallpaper only (no colorscheme)
+# setbg -d # default Plan 9 theme + solid grey root
# Location of link to wallpaper link.
bgloc="${XDG_DATA_HOME:-$HOME/.local/share}/bg"
+default_bg="#737573"
+default_wallpaper="${XDG_DATA_HOME:-$HOME/.local/share}/wallpapers/plan9-grey.png"
+xresources="${XDG_CONFIG_HOME:-$HOME/.config}/x11/xresources"
# Configuration files of applications that have their themes changed by pywal.
dunstconf="${XDG_CONFIG_HOME:-$HOME/.config}/dunst/dunstrc"
@@ 23,14 28,62 @@ zathuraconf="${XDG_CONFIG_HOME:-$HOME/.config}/zathura/zathurarc"
# Parse options
silent=""
colorimg=""
+wallonly=""
+default_theme=""
-while getopts "sc:" o; do case "${o}" in
+while getopts "sdwc:" o; do case "${o}" in
s) silent='1' ;;
+ d) default_theme='1' ;;
+ w) wallonly='1' ;;
c) colorimg="$OPTARG" ;;
esac done
shift $((OPTIND - 1))
+refreshdwm() {
+ pidof dwm >/dev/null && xdotool key super+F5
+}
+
+set_default_terminal_colors() {
+ for term in /dev/pts/[0-9]*; do
+ [ -w "$term" ] || continue
+ {
+ printf '\033]10;#000000\007'
+ printf '\033]11;%s\007' "$default_bg"
+ printf '\033]12;#000000\007'
+ printf '\033]4;0;#000000\007'
+ printf '\033]4;1;#aa0000\007'
+ printf '\033]4;2;#448844\007'
+ printf '\033]4;3;#99994c\007'
+ printf '\033]4;4;#005dbb\007'
+ printf '\033]4;5;#8888cc\007'
+ printf '\033]4;6;#008888\007'
+ printf '\033]4;7;#ffffea\007'
+ printf '\033]4;8;%s\007' "$default_bg"
+ printf '\033]4;9;#ff0000\007'
+ printf '\033]4;10;#88cc88\007'
+ printf '\033]4;11;#eeee9e\007'
+ printf '\033]4;12;#4993dd\007'
+ printf '\033]4;13;#ff00ff\007'
+ printf '\033]4;14;#eaffff\007'
+ printf '\033]4;15;#ffffff\007'
+ printf '\033]4;256;#000000\007'
+ printf '\033]4;258;%s\007' "$default_bg"
+ printf '\033]4;259;#000000\007'
+ } >"$term" 2>/dev/null
+ done
+}
+
+if [ -n "$default_theme" ]; then
+ [ -f "$xresources" ] && xrdb -merge "$xresources"
+ [ -f "$default_wallpaper" ] && ln -sf "$default_wallpaper" "$bgloc"
+ xwallpaper --tile "$default_wallpaper"
+ set_default_terminal_colors
+ [ -z "$silent" ] && notify-send "Default Plan 9 theme" "$default_bg"
+ refreshdwm
+ exit 0
+fi
+
# If no argument, just apply existing wallpaper
if [ -z "$1" ]; then
[ -f "$bgloc" ] && xwallpaper --zoom "$bgloc"
@@ 38,7 91,7 @@ if [ -z "$1" ]; then
fi
trueloc="$(readlink -f "$1")" &&
-case "$(file --mime-type -b "$trueloc")" in
+case "$(/usr/bin/file --mime-type -b "$trueloc")" in
image/* ) ln -sf "$trueloc" "$bgloc" && [ -z "$silent" ] && notify-send -i "$bgloc" "Changing wallpaper..." ;;
inode/directory ) ln -sf "$(find "$trueloc" -iregex '.*.\(jpg\|jpeg\|png\|gif\)' -type f | shuf -n 1)" "$bgloc" && [ -z "$silent" ] && notify-send -i "$bgloc" "Random Wallpaper chosen." ;;
*) [ -z "$silent" ] && notify-send "🖼️ Error" "Not a valid image or directory." ; exit 1;;
@@ 48,7 101,7 @@ esac
if [ -n "$colorimg" ]; then
colorloc="$(readlink -f "$colorimg")"
# Verify it's a valid image
- case "$(file --mime-type -b "$colorloc")" in
+ case "$(/usr/bin/file --mime-type -b "$colorloc")" in
image/* ) ;;
*) [ -z "$silent" ] && notify-send "🎨 Error" "Color image not valid." ; colorloc="" ;;
esac
@@ 56,16 109,16 @@ else
colorloc="$(readlink -f "$bgloc")"
fi
-# If pywal is installed, use it.
-if command -v wal >/dev/null 2>&1 ; then
+# If pywal is installed and wallpaper-only mode is not set, use it.
+if [ -z "$wallonly" ] && command -v wal >/dev/null 2>&1 ; then
wal -n -i "$colorloc" -o "${XDG_CONFIG_HOME:-$HOME/.config}/wal/postrun" >/dev/null 2>&1
[ -z "$silent" ] && [ -n "$colorimg" ] && notify-send -i "$colorloc" "Colors from separate image"
# If pywal is removed, return config files to normal.
-else
+elif [ -z "$wallonly" ]; then
[ -f "$dunstconf.bak" ] && unlink "$dunstconf" && mv "$dunstconf.bak" "$dunstconf"
[ -f "$zathuraconf.bak" ] && unlink "$zathuraconf" && mv "$zathuraconf.bak" "$zathuraconf"
fi
xwallpaper --zoom "$bgloc"
# If running, dwm hit the key to refresh the color scheme.
-pidof dwm >/dev/null && xdotool key super+F5
+refreshdwm
M .local/bin/misc/sync => .local/bin/misc/sync +1 -1
@@ 17,7 17,7 @@ set -euo pipefail
# sync --quiet # suppress per-repo output (for git.js integration)
###############################################################################
-CONTENT_DIR="$HOME/content"
+CONTENT_DIR="$HOME/.corpus/content"
CONTENT_TYPES=(blog diary essays fiction news notes ocs papers progymnasmata reviews verse)
QUIET=false
A .local/bin/nnn-write => .local/bin/nnn-write +6 -0
@@ 0,0 1,6 @@
+#!/bin/sh
+# nnn-write -- open .mdx/.md files in write (nvim writing config)
+case "$1" in
+ *.mdx|*.md) write "$1" ;;
+ *) ${NNN_FALLBACK_OPENER:-xdg-open} "$1" >/dev/null 2>&1 & disown ;;
+esac
A .local/bin/sds => .local/bin/sds +33 -0
@@ 0,0 1,33 @@
+#!/bin/sh
+# sds - subdomain scanner via Certificate Transparency logs (crt.sh)
+# Usage: sds <domain>
+
+[ -z "$1" ] && echo "Usage: sds <domain>" && exit 1
+
+domain="$(echo "$1" | sed 's|^https\?://||;s|/.*||' | tr '[:upper:]' '[:lower:]')"
+
+printf "Scanning CT logs for *.%s ...\n\n" "$domain"
+
+curl -sf "https://crt.sh/?q=%25.${domain}&output=json" |
+ jq -r '.[].common_name, .[].name_value' 2>/dev/null |
+ tr '\n' '\n' |
+ sed 's/\r//g' |
+ tr '[:upper:]' '[:lower:]' |
+ grep -v "^${domain}$" |
+ grep "\.${domain}$\|^\*\.${domain}$" |
+ sort -u |
+ awk -v dom="$domain" '
+ /^\*\./ { wild[NR] = $0; next }
+ { reg[NR] = $0 }
+ END {
+ n = 0
+ for (i in reg) n++
+ for (i in wild) n++
+ printf "%d subdomains found for %s\n\n", n, dom
+ printf "%-4s %s\n", "#", "SUBDOMAIN"
+ printf "%-4s %s\n", "---", "---"
+ c = 0
+ for (i in reg) { c++; printf "%-4d %s\n", c, reg[i] }
+ for (i in wild) { c++; printf "%-4d %s\n", c, wild[i] }
+ }
+ '
A .local/bin/tome => .local/bin/tome +1068 -0
@@ 0,0 1,1068 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+###############################################################################
+# TOME -- Content manager for krisyotam.com
+#
+# Maintainer: Kris Yotam <krisyotam@pm.me>
+# License: MIT
+# Created: 2026-05-18
+# Description: Unified content creator, editor, and metadata manager.
+# Three actions (create, edit, data) with two interfaces
+# (nnn/fzf TUI or dmenu).
+#
+# Usage:
+# tome -nnn -create Create new content entry (TUI)
+# tome -dmenu -create Create new content entry (dmenu)
+# tome -nnn -edit Browse content, open in write (TUI)
+# tome -dmenu -edit Browse content, open in write (dmenu)
+# tome -nnn -data Edit content metadata (TUI)
+# tome -dmenu -data Edit content metadata (dmenu)
+###############################################################################
+
+###############################################################################
+# config
+###############################################################################
+
+DB="$HOME/dev/krisyotam.com/data/content.db"
+CONTENT_DIR="$HOME/.corpus/content"
+TIL_DIR="$HOME/.corpus/til"
+
+# Menu entries (order matters -- displayed as-is)
+# Indented entries are news publications
+MENU_ENTRIES=(
+ "papers"
+ "essays"
+ "blog"
+ "diary"
+ "reviews"
+ "verse"
+ "news"
+ " the-soapbox"
+ " field-notes"
+ " off-the-record"
+ "til"
+ "fiction"
+ "ocs"
+ "prayers"
+ "progymnasmata"
+)
+
+# Types that are actual content directories
+ALL_TYPES=(papers essays blog diary reviews verse news til fiction ocs prayers progymnasmata)
+
+# News publications
+NEWS_PUBS=("the-soapbox" "field-notes" "off-the-record")
+
+# Diary has no status/confidence/importance columns
+DIARY_FIELDS=(title preview category_slug state)
+STANDARD_FIELDS=(title preview category_slug status confidence importance state)
+
+VALID_STATUSES=("Notes" "Draft" "In Progress" "Finished" "Abandoned")
+VALID_CONFIDENCES=(
+ "certain" "highly likely" "likely" "possible"
+ "unlikely" "highly unlikely" "remote" "impossible"
+)
+VALID_STATES=("active" "hidden")
+
+# nerd font icon map
+declare -A TYPE_ICONS=(
+ [papers]="" [essays]="" [blog]=""
+ [diary]="" [reviews]="" [verse]=""
+ [news]="" [fiction]="" [ocs]=""
+ [prayers]="" [progymnasmata]="" [til]=""
+ [the-soapbox]="" [field-notes]="" [off-the-record]=""
+)
+
+###############################################################################
+# helpers
+###############################################################################
+
+have_cmd() { command -v "$1" >/dev/null 2>&1; }
+die() { echo "ERROR: $1" >&2; exit 1; }
+sql() { sqlite3 "$DB" "$1"; }
+notify() { notify-send "tome" "$1" 2>/dev/null || true; }
+
+slugify() {
+ echo "$1" |
+ tr '[:upper:]' '[:lower:]' |
+ sed 's/[^a-z0-9 -]//g' |
+ sed 's/ */ /g; s/ /-/g; s/--*/-/g; s/^-//; s/-$//'
+}
+
+check_slug_globally() {
+ local slug="$1"
+ for dir in "$CONTENT_DIR"/*/; do
+ [ -d "$dir" ] || continue
+ if [ -f "$dir/${slug}.mdx" ]; then
+ echo "$(basename "$dir"):${slug}.mdx"
+ return
+ fi
+ done
+ # Also check til
+ for subdir in "$TIL_DIR"/*/; do
+ [ -d "$subdir" ] || continue
+ if [ -f "$subdir/${slug}.mdx" ]; then
+ echo "til/$(basename "$subdir"):${slug}.mdx"
+ return
+ fi
+ done
+}
+
+# Get content directory for a type (or news publication)
+content_dir_for() {
+ local selection="$1"
+ case "$selection" in
+ the-soapbox|field-notes|off-the-record)
+ echo "$CONTENT_DIR/news"
+ ;;
+ til)
+ echo "$TIL_DIR"
+ ;;
+ *)
+ echo "$CONTENT_DIR/$selection"
+ ;;
+ esac
+}
+
+# Resolve a menu selection to its content type (for DB operations)
+db_type_for() {
+ local selection="$1"
+ case "$selection" in
+ the-soapbox|field-notes|off-the-record) echo "news" ;;
+ *) echo "$selection" ;;
+ esac
+}
+
+today() { date +%Y-%m-%d; }
+
+###############################################################################
+# argument parsing
+###############################################################################
+
+UI_MODE=""
+ACTION_MODE=""
+
+for arg in "$@"; do
+ case "$arg" in
+ -nnn) UI_MODE="nnn" ;;
+ -dmenu) UI_MODE="dmenu" ;;
+ -create) ACTION_MODE="create" ;;
+ -edit) ACTION_MODE="edit" ;;
+ -data) ACTION_MODE="data" ;;
+ *)
+ echo "Unknown flag: $arg" >&2
+ echo "Usage: tome -nnn|-dmenu -create|-edit|-data" >&2
+ exit 1
+ ;;
+ esac
+done
+
+if [[ -z "$UI_MODE" || -z "$ACTION_MODE" ]]; then
+ echo "Usage: tome -nnn|-dmenu -create|-edit|-data" >&2
+ exit 1
+fi
+
+###############################################################################
+# dependency checks
+###############################################################################
+
+check_deps_nnn() {
+ have_cmd fzf || die "fzf not found"
+ have_cmd nnn || die "nnn not found"
+}
+
+check_deps_dmenu() {
+ have_cmd dmenu || die "dmenu not found"
+}
+
+check_deps_data() {
+ have_cmd sqlite3 || die "sqlite3 not found"
+ [[ -f "$DB" ]] || die "content.db not found at $DB"
+}
+
+###############################################################################
+# menu rendering
+###############################################################################
+
+# Build the fzf menu string
+fzf_menu() {
+ for entry in "${MENU_ENTRIES[@]}"; do
+ echo "$entry"
+ done
+ echo "quit"
+}
+
+# Build the dmenu menu string (with icons)
+dmenu_menu() {
+ for entry in "${MENU_ENTRIES[@]}"; do
+ local trimmed="${entry## }"
+ local indent="${entry%%[! ]*}"
+ local icon="${TYPE_ICONS[$trimmed]:-}"
+ echo "${indent}${icon} ${trimmed}"
+ done
+}
+
+# Parse a menu selection back to a clean type/publication name
+parse_selection() {
+ echo "$1" | sed 's/^[[:space:]]*//' | sed 's/^[^ ]* *//'
+}
+
+###############################################################################
+# banners
+###############################################################################
+
+print_banner_main() {
+ local cols
+ cols="$(tput cols 2>/dev/null || echo 80)"
+
+ if have_cmd figlet; then
+ figlet -w "$cols" -f big "KRISYOTAM.COM" 2>/dev/null \
+ || figlet -w "$cols" "KRISYOTAM.COM"
+ else
+ echo "KRISYOTAM.COM"
+ fi
+
+ echo
+ echo " est. 2025"
+ echo " author: Kris Yotam"
+ echo " script: tome"
+ echo
+ echo " \"Do I contradict myself? Very well then I contradict myself,"
+ echo " (I am large, I contain multitudes.)\""
+ echo " -- Walt Whitman"
+ echo
+}
+
+print_banner_data() {
+ local cols
+ cols="$(tput cols 2>/dev/null || echo 80)"
+
+ if have_cmd figlet; then
+ figlet -w "$cols" -f big "DATA" 2>/dev/null \
+ || figlet -w "$cols" "DATA"
+ else
+ echo "=== DATA ==="
+ fi
+
+ echo
+ echo " Content metadata editor for krisyotam.com"
+ echo
+}
+
+###############################################################################
+# create: write frontmatter + .mdx file (pure shell)
+###############################################################################
+
+# Collect metadata and write the .mdx file
+write_new_entry() {
+ local selection="$1"
+ local type
+ type="$(db_type_for "$selection")"
+ local target_dir
+ target_dir="$(content_dir_for "$selection")"
+
+ mkdir -p "$target_dir"
+
+ # For news publications browsing into subdirectory is not needed
+ # since all news lives flat in content/news/
+
+ local title slug preview category status confidence importance
+ local verse_type="" prayer_type="" form="" publication=""
+
+ # Title
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ title="$(echo "" | dmenu -p "title:")"
+ else
+ read -rp " Title: " title
+ fi
+ [[ -z "$title" ]] && return
+
+ # Slug
+ slug="$(slugify "$title")"
+ local collision
+ collision="$(check_slug_globally "$slug")"
+ if [[ -n "$collision" ]]; then
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ notify "Slug collision: $slug ($collision)"
+ slug="$(echo "$slug" | dmenu -p "collision! alt slug:")"
+ else
+ echo " Slug collision: $slug ($collision)"
+ read -rp " Alt slug: " slug
+ fi
+ [[ -z "$slug" ]] && return
+ slug="$(slugify "$slug")"
+ collision="$(check_slug_globally "$slug")"
+ if [[ -n "$collision" ]]; then
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ notify "Still colliding: $slug. Aborting."
+ else
+ echo " Still colliding: $slug. Aborting."
+ fi
+ return
+ fi
+ fi
+
+ # Preview
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ preview="$(echo "" | dmenu -p "preview:")"
+ else
+ read -rp " Preview: " preview
+ fi
+
+ # Category
+ check_deps_data
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ category="$(sql "SELECT slug FROM categories ORDER BY slug" | dmenu -i -l 20 -p "category:")"
+ else
+ category="$(sql "SELECT slug FROM categories ORDER BY slug" | fzf --prompt="category> " --height=20 --reverse)" || category=""
+ fi
+ [[ -z "$category" ]] && return
+
+ # Status (skip for diary)
+ if [[ "$type" == "diary" ]]; then
+ status="" confidence="" importance=""
+ else
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ status="$(printf "%s\n" "${VALID_STATUSES[@]}" | dmenu -i -l 5 -p "status:")"
+ confidence="$(printf "%s\n" "${VALID_CONFIDENCES[@]}" | dmenu -i -l 8 -p "confidence:")"
+ importance="$(seq 1 10 | dmenu -i -l 10 -p "importance:")"
+ else
+ status="$(printf "%s\n" "${VALID_STATUSES[@]}" | fzf --prompt="status> " --height=8 --reverse)" || status="Draft"
+ confidence="$(printf "%s\n" "${VALID_CONFIDENCES[@]}" | fzf --prompt="confidence> " --height=10 --reverse)" || confidence="possible"
+ read -rp " Importance (1-10): " importance
+ fi
+ [[ -z "$status" ]] && status="Draft"
+ [[ -z "$confidence" ]] && confidence="possible"
+ [[ -z "$importance" ]] && importance="5"
+ fi
+
+ # Type-specific fields
+ case "$type" in
+ verse)
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ verse_type="$(printf 'haiku\nsonnet\nfree-verse\node\nlimerick\nvillanelle\nother' | dmenu -i -l 7 -p "verse type:")"
+ else
+ verse_type="$(printf 'haiku\nsonnet\nfree-verse\node\nlimerick\nvillanelle\nother' | fzf --prompt="verse type> " --height=10 --reverse)" || verse_type=""
+ fi
+ ;;
+ prayers)
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ prayer_type="$(echo "" | dmenu -p "prayer type:")"
+ form="$(echo "" | dmenu -p "form:")"
+ else
+ read -rp " Prayer type: " prayer_type
+ read -rp " Form: " form
+ fi
+ ;;
+ news)
+ # Publication is determined by the menu selection
+ case "$selection" in
+ the-soapbox|field-notes|off-the-record)
+ publication="$selection"
+ ;;
+ news)
+ # Selected the parent "news" -- ask which publication
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ publication="$(printf '%s\n' "${NEWS_PUBS[@]}" | dmenu -i -l 3 -p "publication:")"
+ else
+ publication="$(printf '%s\n' "${NEWS_PUBS[@]}" | fzf --prompt="publication> " --height=5 --reverse)" || publication="the-soapbox"
+ fi
+ [[ -z "$publication" ]] && publication="the-soapbox"
+ ;;
+ esac
+ ;;
+ esac
+
+ # Tags
+ local tags=""
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ # Collect up to 5 tags
+ local tag_list=()
+ for _ in 1 2 3 4 5; do
+ local tag
+ tag="$(sql "SELECT slug FROM tags ORDER BY slug" | dmenu -i -l 20 -p "tag (empty=done):")" || break
+ [[ -z "$tag" ]] && break
+ tag_list+=("$tag")
+ done
+ tags="$(printf '%s\n' "${tag_list[@]}" 2>/dev/null | paste -sd',' -)"
+ else
+ echo " Tags (comma-separated, or empty):"
+ read -rp " > " tags
+ fi
+
+ # Build the .mdx file
+ local target_file="$target_dir/${slug}.mdx"
+ local date
+ date="$(today)"
+
+ {
+ echo "---"
+ echo "title: \"$title\""
+ echo "slug: $slug"
+ echo "type: $type"
+ echo "category: $category"
+ echo "start_date: '$date'"
+ echo "end_date: ''"
+ echo "state: active"
+ [[ -n "$preview" ]] && echo "preview: \"$preview\""
+
+ if [[ "$type" != "diary" ]]; then
+ echo "status: $status"
+ echo "confidence: $confidence"
+ echo "importance: $importance"
+ fi
+
+ # Type-specific fields
+ [[ -n "$verse_type" ]] && echo "verse_type: $verse_type"
+ [[ -n "$prayer_type" ]] && echo "prayer_type: $prayer_type"
+ [[ -n "$form" ]] && echo "form: $form"
+ [[ -n "$publication" ]] && echo "publication: $publication"
+
+ # Tags as inline YAML array
+ if [[ -n "$tags" ]]; then
+ local tag_arr
+ tag_arr="$(echo "$tags" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | awk '{printf "%s\"%s\"", (NR>1 ? ", " : ""), $0}')"
+ echo "tags: [$tag_arr]"
+ else
+ echo "tags: []"
+ fi
+
+ echo "---"
+ echo ""
+ } > "$target_file"
+
+ if [[ "$UI_MODE" == "dmenu" ]]; then
+ notify "Created: $title ($slug)"
+ # Open in write
+ write "$target_file" &
+ disown
+ else
+ echo
+ echo " Created: $target_file"
+ echo
+ read -rp " Open in write? [Y/n] " yn
+ case "$yn" in
+ n|N) ;;
+ *) write "$target_file" ;;
+ esac
+ fi
+}
+
+###############################################################################
+# mode: -nnn -create
+###############################################################################
+
+run_nnn_create() {
+ check_deps_nnn
+
+ clear
+ print_banner_main
+
+ local selection
+ selection="$(fzf_menu | fzf --prompt="create> " --height=20 --reverse)" || exit 0
+
+ [[ "$selection" == "quit" ]] && exit 0
+
+ # Trim whitespace (for indented news publications)
+ selection="${selection## }"
+
+ # If they selected "news" (parent), let them pick publication or create generic
+ write_new_entry "$selection"
+}
+
+###############################################################################
+# mode: -dmenu -create
+###############################################################################
+
+run_dmenu_create() {
+ check_deps_dmenu
+
+ local raw
+ raw="$(dmenu_menu | dmenu -i -l 16 -p "create:")"
+ [[ -z "$raw" ]] && exit 0
+
+ local selection
+ selection="$(parse_selection "$raw")"
+ [[ -z "$selection" ]] && exit 0
+
+ write_new_entry "$selection"
+}
+
+###############################################################################
+# mode: -nnn -edit (browse and open existing content in write)
+###############################################################################
+
+run_nnn_edit() {
+ check_deps_nnn
+
+ local nnn_write_helper="$HOME/.local/bin/nnn-write"
+ [[ ! -x "$nnn_write_helper" ]] && die "nnn-write helper not found at $nnn_write_helper"
+
+ clear
+ print_banner_main
+
+ while true; do
+ local selection
+ selection="$(fzf_menu | fzf --prompt="edit> " --height=20 --reverse)" || exit 0
+
+ [[ "$selection" == "quit" ]] && exit 0
+
+ selection="${selection## }"
+ local target_dir
+ target_dir="$(content_dir_for "$selection")"
+
+ if [[ ! -d "$target_dir" ]]; then
+ echo "Directory not found: $target_dir"
+ read -rp "Press Enter..." _
+ continue
+ fi
+
+ # For news publications, filter to only show matching files
+ case "$selection" in
+ the-soapbox|field-notes|off-the-record)
+ # Show only files matching this publication
+ local tmpdir
+ tmpdir="$(mktemp -d)"
+ # Symlink matching files into tmpdir for nnn browsing
+ for f in "$target_dir"/*.mdx; do
+ [[ -f "$f" ]] || continue
+ if grep -q "^publication: $selection" "$f" 2>/dev/null; then
+ ln -s "$f" "$tmpdir/$(basename "$f")"
+ fi
+ done
+ NNN_OPENER="$nnn_write_helper" \
+ NNN_FALLBACK_OPENER="${NNN_OPENER:-xdg-open}" \
+ nnn "$tmpdir"
+ rm -rf "$tmpdir"
+ ;;
+ til)
+ # TIL has subdirectories
+ NNN_OPENER="$nnn_write_helper" \
+ NNN_FALLBACK_OPENER="${NNN_OPENER:-xdg-open}" \
+ nnn "$target_dir"
+ ;;
+ *)
+ NNN_OPENER="$nnn_write_helper" \
+ NNN_FALLBACK_OPENER="${NNN_OPENER:-xdg-open}" \
+ nnn "$target_dir"
+ ;;
+ esac
+
+ clear
+ print_banner_main
+ done
+}
+
+###############################################################################
+# mode: -dmenu -edit
+###############################################################################
+
+run_dmenu_edit() {
+ check_deps_dmenu
+
+ local raw
+ raw="$(dmenu_menu | dmenu -i -l 16 -p "edit:")"
+ [[ -z "$raw" ]] && exit 0
+
+ local selection
+ selection="$(parse_selection "$raw")"
+ [[ -z "$selection" ]] && exit 0
+
+ local target_dir
+ target_dir="$(content_dir_for "$selection")"
+ [[ ! -d "$target_dir" ]] && { notify "Directory not found: $target_dir"; exit 1; }
+
+ local file
+
+ case "$selection" in
+ the-soapbox|field-notes|off-the-record)
+ # Filter news files by publication
+ file="$(
+ for f in "$target_dir"/*.mdx; do
+ [[ -f "$f" ]] || continue
+ if grep -q "^publication: $selection" "$f" 2>/dev/null; then
+ basename "$f" .mdx
+ fi
+ done | sort | dmenu -i -l 20 -p "$selection:"
+ )"
+ [[ -z "$file" ]] && exit 0
+ write "$target_dir/$file.mdx"
+ ;;
+ til)
+ # TIL has subdirs -- first pick subdir, then file
+ local subdir
+ subdir="$(ls "$target_dir" | dmenu -i -l 10 -p "til topic:")"
+ [[ -z "$subdir" ]] && exit 0
+ file="$(ls "$target_dir/$subdir"/*.mdx 2>/dev/null | xargs -I{} basename {} .mdx | sort | dmenu -i -l 20 -p "$subdir:")"
+ [[ -z "$file" ]] && exit 0
+ write "$target_dir/$subdir/$file.mdx"
+ ;;
+ *)
+ file="$(
+ ls "$target_dir"/*.mdx 2>/dev/null |
+ xargs -I{} basename {} .mdx |
+ sort |
+ dmenu -i -l 20 -p "$selection:"
+ )"
+ [[ -z "$file" ]] && exit 0
+ write "$target_dir/$file.mdx"
+ ;;
+ esac
+}
+
+###############################################################################
+# metadata display (for -data mode)
+###############################################################################
+
+show_metadata() {
+ local type="$1" slug="$2"
+
+ local title preview category state tags id
+
+ title="$(sql "SELECT title FROM $type WHERE slug='$slug'")"
+ preview="$(sql "SELECT preview FROM $type WHERE slug='$slug'")"
+ category="$(sql "SELECT category_slug FROM $type WHERE slug='$slug'")"
+ state="$(sql "SELECT state FROM $type WHERE slug='$slug'")"
+
+ id="$(sql "SELECT id FROM $type WHERE slug='$slug'")"
+ tags="$(sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id = t.id WHERE ct.content_type = '$type' AND ct.content_id = $id ORDER BY t.slug" | paste -sd', ' -)"
+
+ echo
+ echo " ┌─ $slug ─────────────────────────────────"
+ printf " │ %-14s %s\n" "Title:" "$title"
+ printf " │ %-14s %s\n" "Preview:" "${preview:0:60}"
+ printf " │ %-14s %s\n" "Category:" "$category"
+ printf " │ %-14s %s\n" "Tags:" "$tags"
+
+ if [[ "$type" != "diary" ]]; then
+ local status confidence importance
+ status="$(sql "SELECT status FROM $type WHERE slug='$slug'")"
+ confidence="$(sql "SELECT confidence FROM $type WHERE slug='$slug'")"
+ importance="$(sql "SELECT importance FROM $type WHERE slug='$slug'")"
+ printf " │ %-14s %s\n" "Status:" "$status"
+ printf " │ %-14s %s\n" "Confidence:" "$confidence"
+ printf " │ %-14s %s\n" "Importance:" "$importance"
+ fi
+
+ if [[ "$type" == "news" ]]; then
+ local pub
+ pub="$(sql "SELECT publication FROM $type WHERE slug='$slug'")"
+ printf " │ %-14s %s\n" "Publication:" "$pub"
+ fi
+
+ printf " │ %-14s %s\n" "State:" "$state"
+ echo " └──────────────────────────────────────────"
+ echo
+}
+
+###############################################################################
+# field editing -- nnn/fzf mode (for -data)
+###############################################################################
+
+edit_tags_nnn() {
+ local type="$1" slug="$2"
+
+ local id
+ id="$(sql "SELECT id FROM $type WHERE slug='$slug'")"
+
+ echo
+ echo " Current tags:"
+ sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id = t.id WHERE ct.content_type = '$type' AND ct.content_id = $id ORDER BY t.slug" | while read -r tag; do
+ echo " - $tag"
+ done
+
+ echo
+ echo " Actions: [a]dd tag, [r]emove tag, [d]one"
+ while true; do
+ read -rp " tag> " action
+ case "$action" in
+ a|add)
+ local new_tag
+ new_tag="$(
+ sql "SELECT slug FROM tags ORDER BY slug" |
+ fzf --prompt="add tag> " --height=20 --reverse
+ )" || continue
+
+ local existing
+ existing="$(sql "SELECT COUNT(*) FROM content_tags WHERE content_type='$type' AND content_id=$id AND tag_id=(SELECT id FROM tags WHERE slug='$new_tag')")"
+ if [[ "$existing" -gt 0 ]]; then
+ echo " Tag '$new_tag' already linked."
+ continue
+ fi
+
+ local tag_id
+ tag_id="$(sql "SELECT id FROM tags WHERE slug='$new_tag'")"
+ sql "INSERT INTO content_tags (content_type, content_id, tag_id) VALUES ('$type', $id, $tag_id)"
+ echo " Added tag: $new_tag"
+ ;;
+ r|remove)
+ local rm_tag
+ rm_tag="$(
+ sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id = t.id WHERE ct.content_type = '$type' AND ct.content_id = $id ORDER BY t.slug" |
+ fzf --prompt="remove tag> " --height=10 --reverse
+ )" || continue
+
+ sql "DELETE FROM content_tags WHERE content_type='$type' AND content_id=$id AND tag_id=(SELECT id FROM tags WHERE slug='$rm_tag')"
+ echo " Removed tag: $rm_tag"
+ ;;
+ d|done|q|quit)
+ break
+ ;;
+ *)
+ echo " Unknown action. Use [a]dd, [r]emove, or [d]one."
+ ;;
+ esac
+ done
+}
+
+edit_field_nnn() {
+ local type="$1" slug="$2" field="$3"
+ local new_value=""
+
+ case "$field" in
+ title|preview)
+ local current
+ current="$(sql "SELECT $field FROM $type WHERE slug='$slug'")"
+ echo " Current $field: $current"
+ read -rp " New $field: " new_value
+ [[ -z "$new_value" ]] && return
+ new_value="${new_value//\'/\'\'}"
+ sql "UPDATE $type SET $field='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ echo " Updated $field."
+ ;;
+ category_slug)
+ new_value="$(
+ sql "SELECT slug FROM categories ORDER BY slug" |
+ fzf --prompt="category> " --height=20 --reverse
+ )" || return
+ sql "UPDATE $type SET category_slug='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ echo " Updated category to: $new_value"
+ ;;
+ tags)
+ edit_tags_nnn "$type" "$slug"
+ ;;
+ status)
+ new_value="$(
+ printf "%s\n" "${VALID_STATUSES[@]}" |
+ fzf --prompt="status> " --height=10 --reverse
+ )" || return
+ sql "UPDATE $type SET status='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ echo " Updated status to: $new_value"
+ ;;
+ confidence)
+ new_value="$(
+ printf "%s\n" "${VALID_CONFIDENCES[@]}" |
+ fzf --prompt="confidence> " --height=12 --reverse
+ )" || return
+ sql "UPDATE $type SET confidence='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ echo " Updated confidence to: $new_value"
+ ;;
+ importance)
+ read -rp " Importance (1-10): " new_value
+ if [[ "$new_value" =~ ^[0-9]+$ ]] && (( new_value >= 1 && new_value <= 10 )); then
+ sql "UPDATE $type SET importance=$new_value, updated_at=datetime('now') WHERE slug='$slug'"
+ echo " Updated importance to: $new_value"
+ else
+ echo " Invalid. Must be 1-10."
+ fi
+ ;;
+ publication)
+ new_value="$(
+ printf "%s\n" "${NEWS_PUBS[@]}" |
+ fzf --prompt="publication> " --height=5 --reverse
+ )" || return
+ sql "UPDATE $type SET publication='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ echo " Updated publication to: $new_value"
+ ;;
+ state)
+ new_value="$(
+ printf "%s\n" "${VALID_STATES[@]}" |
+ fzf --prompt="state> " --height=5 --reverse
+ )" || return
+ sql "UPDATE $type SET state='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ echo " Updated state to: $new_value"
+ ;;
+ *)
+ echo " Unknown field: $field"
+ ;;
+ esac
+}
+
+###############################################################################
+# field editing -- dmenu mode (for -data)
+###############################################################################
+
+edit_field_dmenu() {
+ local type="$1" slug="$2" field="$3"
+ local current new_value
+
+ case "$field" in
+ title|preview)
+ current="$(sql "SELECT $field FROM $type WHERE slug='$slug'")"
+ new_value="$(echo "$current" | dmenu -p "$field:")"
+ [[ -z "$new_value" ]] && return
+ new_value="${new_value//\'/\'\'}"
+ sql "UPDATE $type SET $field='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ notify "Updated $field"
+ ;;
+ category_slug)
+ new_value="$(sql "SELECT slug FROM categories ORDER BY slug" | dmenu -i -l 15 -p "category:")"
+ [[ -z "$new_value" ]] && return
+ sql "UPDATE $type SET category_slug='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ notify "Category: $new_value"
+ ;;
+ tags)
+ local id
+ id="$(sql "SELECT id FROM $type WHERE slug='$slug'")"
+ local action
+ action="$(printf "add\nremove" | dmenu -i -p "tags:")"
+ [[ -z "$action" ]] && return
+
+ if [[ "$action" == "add" ]]; then
+ local tag
+ tag="$(sql "SELECT slug FROM tags ORDER BY slug" | dmenu -i -l 20 -p "add tag:")"
+ [[ -z "$tag" ]] && return
+ local tag_id
+ tag_id="$(sql "SELECT id FROM tags WHERE slug='$tag'")"
+ [[ -z "$tag_id" ]] && return
+ sql "INSERT OR IGNORE INTO content_tags (content_type, content_id, tag_id) VALUES ('$type', $id, $tag_id)"
+ notify "Added tag: $tag"
+ else
+ local tag
+ tag="$(sql "SELECT t.slug FROM tags t JOIN content_tags ct ON ct.tag_id=t.id WHERE ct.content_type='$type' AND ct.content_id=$id ORDER BY t.slug" | dmenu -i -l 10 -p "remove:")"
+ [[ -z "$tag" ]] && return
+ sql "DELETE FROM content_tags WHERE content_type='$type' AND content_id=$id AND tag_id=(SELECT id FROM tags WHERE slug='$tag')"
+ notify "Removed tag: $tag"
+ fi
+ ;;
+ status)
+ new_value="$(printf "Notes\nDraft\nIn Progress\nFinished\nAbandoned" | dmenu -i -l 5 -p "status:")"
+ [[ -z "$new_value" ]] && return
+ sql "UPDATE $type SET status='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ notify "Status: $new_value"
+ ;;
+ confidence)
+ new_value="$(printf "certain\nhighly likely\nlikely\npossible\nunlikely\nhighly unlikely\nremote\nimpossible" | dmenu -i -l 8 -p "confidence:")"
+ [[ -z "$new_value" ]] && return
+ sql "UPDATE $type SET confidence='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ notify "Confidence: $new_value"
+ ;;
+ importance)
+ new_value="$(seq 1 10 | dmenu -i -l 10 -p "importance:")"
+ [[ -z "$new_value" ]] && return
+ sql "UPDATE $type SET importance=$new_value, updated_at=datetime('now') WHERE slug='$slug'"
+ notify "Importance: $new_value"
+ ;;
+ publication)
+ new_value="$(printf '%s\n' "${NEWS_PUBS[@]}" | dmenu -i -l 3 -p "publication:")"
+ [[ -z "$new_value" ]] && return
+ sql "UPDATE $type SET publication='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ notify "Publication: $new_value"
+ ;;
+ state)
+ new_value="$(printf "active\nhidden" | dmenu -i -l 2 -p "state:")"
+ [[ -z "$new_value" ]] && return
+ sql "UPDATE $type SET state='$new_value', updated_at=datetime('now') WHERE slug='$slug'"
+ notify "State: $new_value"
+ ;;
+ esac
+}
+
+###############################################################################
+# entry selection helpers
+###############################################################################
+
+select_entry_fzf() {
+ local type="$1" filter_pub="${2:-}"
+
+ local query="SELECT title || ' (' || slug || ')' || char(9) || slug FROM $type WHERE state != '' "
+ [[ -n "$filter_pub" ]] && query+="AND publication = '$filter_pub' "
+ query+="ORDER BY title"
+
+ sql "$query" |
+ fzf --prompt="$type> " --delimiter=$'\t' --with-nth=1 \
+ --preview="sqlite3 '$DB' \"SELECT 'Title: ' || title || char(10) || 'Preview: ' || COALESCE(preview,'') || char(10) || 'Category: ' || COALESCE(category_slug,'') || char(10) || 'State: ' || COALESCE(state,'active') FROM $type WHERE slug='{2}'\"" \
+ --height=80% --reverse |
+ awk -F'\t' '{print $2}'
+}
+
+select_entry_nnn_browse() {
+ local type="$1"
+ local content_path="$CONTENT_DIR/$type"
+
+ [[ ! -d "$content_path" ]] && { echo ""; return; }
+
+ local tmpfile
+ tmpfile="$(mktemp)"
+
+ NNN_TMPFILE="$tmpfile" nnn -p "$tmpfile" "$content_path"
+
+ if [[ -s "$tmpfile" ]]; then
+ local selected
+ selected="$(cat "$tmpfile")"
+ rm -f "$tmpfile"
+ basename "$selected" .mdx
+ else
+ rm -f "$tmpfile"
+ echo ""
+ fi
+}
+
+###############################################################################
+# mode: -nnn -data
+###############################################################################
+
+run_nnn_data() {
+ check_deps_nnn
+ check_deps_data
+
+ clear
+ print_banner_data
+
+ while true; do
+ local selection
+ selection="$(fzf_menu | fzf --prompt="data> " --height=20 --reverse)" || exit 0
+
+ [[ "$selection" == "quit" ]] && exit 0
+
+ selection="${selection## }"
+ local type
+ type="$(db_type_for "$selection")"
+
+ # Determine publication filter for news
+ local pub_filter=""
+ case "$selection" in
+ the-soapbox|field-notes|off-the-record) pub_filter="$selection" ;;
+ esac
+
+ local mode
+ mode="$(
+ printf "%s\n" "fzf (search by title)" "nnn (browse files)" "back" |
+ fzf --prompt="select via> " --height=5 --reverse
+ )" || continue
+
+ [[ "$mode" == "back" ]] && continue
+
+ local slug=""
+ if [[ "$mode" == *"fzf"* ]]; then
+ slug="$(select_entry_fzf "$type" "$pub_filter")" || continue
+ else
+ slug="$(select_entry_nnn_browse "$type")" || continue
+ fi
+
+ [[ -z "$slug" ]] && continue
+
+ local count
+ count="$(sql "SELECT COUNT(*) FROM $type WHERE slug='$slug'")"
+ if [[ "$count" -eq 0 ]]; then
+ echo " Slug '$slug' not found in $type table."
+ read -rp " Press Enter..." _
+ continue
+ fi
+
+ while true; do
+ clear
+ print_banner_data
+ show_metadata "$type" "$slug"
+
+ local fields
+ if [[ "$type" == "diary" ]]; then
+ fields=("${DIARY_FIELDS[@]}" "tags" "done")
+ elif [[ "$type" == "news" ]]; then
+ fields=("${STANDARD_FIELDS[@]}" "publication" "tags" "done")
+ else
+ fields=("${STANDARD_FIELDS[@]}" "tags" "done")
+ fi
+
+ local field
+ field="$(
+ printf "%s\n" "${fields[@]}" |
+ fzf --prompt="edit field> " --height=14 --reverse
+ )" || break
+
+ [[ "$field" == "done" ]] && break
+
+ edit_field_nnn "$type" "$slug" "$field"
+ read -rp " Press Enter..." _
+ done
+ done
+}
+
+###############################################################################
+# mode: -dmenu -data
+###############################################################################
+
+run_dmenu_data() {
+ check_deps_dmenu
+ check_deps_data
+
+ local raw
+ raw="$(dmenu_menu | dmenu -i -l 16 -p "data:")"
+ [[ -z "$raw" ]] && exit 0
+
+ local selection
+ selection="$(parse_selection "$raw")"
+ [[ -z "$selection" ]] && exit 0
+
+ local type
+ type="$(db_type_for "$selection")"
+
+ # Publication filter for news subs
+ local pub_filter=""
+ case "$selection" in
+ the-soapbox|field-notes|off-the-record) pub_filter="$selection" ;;
+ esac
+
+ # Select entry
+ local query="SELECT title || ' [' || slug || ']' FROM $type "
+ [[ -n "$pub_filter" ]] && query+="WHERE publication = '$pub_filter' "
+ query+="ORDER BY title"
+
+ local slug
+ slug="$(
+ sql "$query" |
+ dmenu -i -l 20 -p "$type:" |
+ sed 's/.*\[\(.*\)\]/\1/'
+ )"
+ [[ -z "$slug" ]] && exit 0
+
+ # Edit loop
+ while true; do
+ local fields="title
+preview
+category_slug
+tags
+state"
+
+ if [[ "$type" != "diary" ]]; then
+ fields+="
+status
+confidence
+importance"
+ fi
+
+ if [[ "$type" == "news" ]]; then
+ fields+="
+publication"
+ fi
+
+ local field
+ field="$(echo "$fields" | dmenu -i -l 12 -p "field:")"
+ [[ -z "$field" ]] && break
+ edit_field_dmenu "$type" "$slug" "$field"
+ done
+}
+
+###############################################################################
+# dispatch
+###############################################################################
+
+case "${UI_MODE}-${ACTION_MODE}" in
+ nnn-create) run_nnn_create ;;
+ dmenu-create) run_dmenu_create ;;
+ nnn-edit) run_nnn_edit ;;
+ dmenu-edit) run_dmenu_edit ;;
+ nnn-data) run_nnn_data ;;
+ dmenu-data) run_dmenu_data ;;
+esac
A .local/bin/write => .local/bin/write +2 -0
@@ 0,0 1,2 @@
+#!/bin/sh
+NVIM_APPNAME=write exec nvim "$@"
A .local/share/wallpapers/d1e2f1-horizontal.png => .local/share/wallpapers/d1e2f1-horizontal.png +0 -0
A .local/share/wallpapers/plan9-grey.png => .local/share/wallpapers/plan9-grey.png +0 -0
A .local/share/wallpapers/plan9.png => .local/share/wallpapers/plan9.png +0 -0
A .local/share/wallpapers/plan9theme.png => .local/share/wallpapers/plan9theme.png +0 -0
A .local/share/wallpapers/winter-color-scheme.png => .local/share/wallpapers/winter-color-scheme.png +0 -0
M .mkshrc => .mkshrc +14 -0
@@ 49,6 49,20 @@ alias top='btop'
alias htop='btop'
alias vim='nvim'
+# Nvim profiles
+alias write='NVIM_APPNAME=write nvim'
+alias code='NVIM_APPNAME=code nvim'
+
+# Corpus directories
+alias content='cd $HOME/.corpus/content'
+alias til='cd $HOME/.corpus/til'
+alias sb='cd $HOME/.corpus/slipbox'
+
+# Tome shortcuts
+alias tc='tome -nnn -create'
+alias te='tome -nnn -edit'
+alias td='tome -nnn -data'
+
# Claude Code
alias claude='claude --dangerously-skip-permissions'
M .xprofile => .xprofile +20 -9
@@ 3,20 3,31 @@
# This file runs when a DM logs you into a graphical session.
# If you use startx/xinit like a Chad, this file will also be sourced.
-# Load environment variables from login profile
-. "$HOME/.profile"
+# Add local bin to PATH for scripts
+export PATH="$HOME/.local/bin:$HOME/.local/bin/statusbar:$PATH"
-xrandr --dpi 128 # Set DPI. Scaled up for 1440p to match 1080p UI size.
+# Auto-detect device type and set DPI accordingly
+DEVICE_TYPE_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/srice/device-type"
+if [ -f "$DEVICE_TYPE_FILE" ] && [ "$(cat "$DEVICE_TYPE_FILE")" = "laptop" ]; then
+ xrandr --dpi 150 # Laptop HiDPI
+else
+ xrandr --dpi 128 # Desktop 1440p scaled
+fi
~/.screenlayout/main.sh # Set up monitor layout
+setxkbmap -option caps:none # Completely disable Caps Lock
xrdb -merge ${XDG_CONFIG_HOME:-$HOME/.config}/x11/xresources # Load base Xresources
-setbg -s # set the background and colorscheme (sync, not background)
+xset s off -dpms s noblank # Disable DPMS/screensaver (freezes with dual-GPU + 4 monitors)
+setbg -s -c "$HOME/.local/share/wallpapers/plan9theme.png" "$HOME/.local/share/wallpapers/plan9.png" # plan9 solid bg + plan9 winter colorscheme
+setfan & # Set case fan RGB (ice blue)
-autostart="mpd picom dunst unclutter pipewire"
+# Start gnome-keyring-daemon with unlocked login keyring (needed for ProtonMail Bridge)
+eval $(echo -n "kris" | gnome-keyring-daemon --replace --unlock --components=pkcs11,secrets,ssh)
+export SSH_AUTH_SOCK
+
+autostart="mpd picom dunst unclutter pipewire wireplumber conky protonmail-bridge"
for program in $autostart; do
- pidof -sx "$program" || "$program" &
+ pgrep -x "$program" >/dev/null 2>&1 || "$program" &
done >/dev/null 2>&1
-# Start conky (lean-conky-config)
-pidof -sx conky || ~/.config/conky/lean-conky/start.sh &
-
+# dwmblocks is started by dwm autostart