diff --git a/flake.nix b/flake.nix index 3a02e3e..a84ead5 100644 --- a/flake.nix +++ b/flake.nix @@ -46,48 +46,31 @@ }; utils = import ./utils.nix {inherit inputs system sources;}; + + # Discover hosts: all subdirectories of hosts/ + hosts = builtins.attrNames (nixpkgs.lib.filterAttrs + (_: type: type == "directory") + (builtins.readDir ./hosts)); + + nixosConfigs = builtins.listToAttrs (map (host: { + name = host; + value = utils.mkHost ./hosts/${host}/configuration.nix; + }) hosts); in { - nixosConfigurations = { - # desktop - muon = utils.mkHost ./hosts/muon/configuration.nix; - - # laptop - muop = utils.mkHost ./hosts/muop/configuration.nix; - - # server - muho = utils.mkHost ./hosts/muho/configuration.nix; - - # vps - mups = utils.mkHost ./hosts/mups/configuration.nix; - - # vm - muvm = utils.mkHost ./hosts/muvm/configuration.nix; - - # work - murk = utils.mkHost ./hosts/murk/configuration.nix; - - # work desktop - musk = utils.mkHost ./hosts/musk/configuration.nix; - - # lenovo - muvo = utils.mkHost ./hosts/muvo/configuration.nix; - - # installer - muin = utils.mkHost ./hosts/muin/configuration.nix; - }; + nixosConfigurations = nixosConfigs; homeManagerModules.default = ./modules/home; - # Expose each host's HM activation package so `home-manager switch --flake .#muon@` - # works without a full NixOS rebuild. Extracted from the already-evaluated - # nixosConfiguration, so osConfig remains fully populated. - homeConfigurations = nixpkgs.lib.mapAttrs' (host: nixos: - nixpkgs.lib.nameValuePair "muon@${host}" { - activationPackage = nixos.config.home-manager.users.muon.home.activationPackage; - } - ) (nixpkgs.lib.filterAttrs - (_: nixos: nixos.config.home-manager.users ? muon) - inputs.self.outputs.nixosConfigurations); + # Standalone HM configurations — one per host. + # osConfig is injected so all modules using it continue to work. + # Use: home-manager switch --flake '.#muon@' + homeConfigurations = builtins.listToAttrs (map (host: { + name = "muon@${host}"; + value = utils.mkHome { + hostConfig = nixosConfigs.${host}; + homeFile = ./hosts/${host}/home.nix; + }; + }) hosts); colmena = { meta = { diff --git a/modules/home/terminal/wezterm.nix b/modules/home/terminal/wezterm.nix index 0314889..b778ff7 100644 --- a/modules/home/terminal/wezterm.nix +++ b/modules/home/terminal/wezterm.nix @@ -28,70 +28,155 @@ # vi directions: key, arrow alias, wezterm direction name viDirs = [ - {key = "h"; arrow = "LeftArrow"; dir = "Left";} - {key = "j"; arrow = "DownArrow"; dir = "Down";} - {key = "k"; arrow = "UpArrow"; dir = "Up";} - {key = "l"; arrow = "RightArrow"; dir = "Right";} + { + key = "h"; + arrow = "LeftArrow"; + dir = "Left"; + } + { + key = "j"; + arrow = "DownArrow"; + dir = "Down"; + } + { + key = "k"; + arrow = "UpArrow"; + dir = "Up"; + } + { + key = "l"; + arrow = "RightArrow"; + dir = "Right"; + } ]; # hjkl + arrow equivalents; mkAction :: dir -> lua-action-string - viDirKeys = {mods ? null, indent ? " ", mkAction}: + viDirKeys = { + mods ? null, + indent ? " ", + mkAction, + }: lib.concatMapStrings (d: - luaKey {inherit mods indent; key = d.key; action = mkAction d.dir;} + - luaKey {inherit mods indent; key = d.arrow; action = mkAction d.dir;}) + luaKey { + inherit mods indent; + key = d.key; + action = mkAction d.dir; + } + + luaKey { + inherit mods indent; + key = d.arrow; + action = mkAction d.dir; + }) viDirs; # 1-9 tab-jump bindings - tabJumpKeys = {mods ? null, indent ? " "}: + tabJumpKeys = { + mods ? null, + indent ? " ", + }: lib.concatStrings (builtins.genList (i: luaKey { inherit mods indent; - key = toString (i + 1); + key = toString (i + 1); action = "act.ActivateTab(${toString i})"; }) 9); # hjkl + arrows for AdjustPaneSize at a given step - resizeDirKeys = {step, indent ? " "}: + resizeDirKeys = { + step, + indent ? " ", + }: lib.concatMapStrings (d: - luaKey {inherit indent; key = d.key; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";} + - luaKey {inherit indent; key = d.arrow; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";}) + luaKey { + inherit indent; + key = d.key; + action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })"; + } + + luaKey { + inherit indent; + key = d.arrow; + action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })"; + }) viDirs; # Uppercase HJKL fine-tune resize - resizeShiftKeys = {step, indent ? " "}: + resizeShiftKeys = { + step, + indent ? " ", + }: lib.concatMapStrings (d: - luaKey {inherit indent; key = lib.toUpper d.key; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";}) + luaKey { + inherit indent; + key = lib.toUpper d.key; + action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })"; + }) viDirs; # Wrap a lua action string so it also pops the key table (return to locked) andPop = action: "act.Multiple({ ${action}, act.PopKeyTable })"; # Standard exit bindings, given the mode's own pop key - exitKeys = {selfKey, indent ? " "}: + exitKeys = { + selfKey, + indent ? " ", + }: lib.concatStrings [ - (luaKey {inherit indent; key = selfKey; action = "act.PopKeyTable";}) - (luaKey {inherit indent; key = "Escape"; action = "act.PopKeyTable";}) - (luaKey {inherit indent; key = "Enter"; action = "act.PopKeyTable";}) - (luaKey {inherit indent; key = "Space"; mods = "ALT"; action = "act.PopKeyTable";}) + (luaKey { + inherit indent; + key = selfKey; + action = "act.PopKeyTable"; + }) + (luaKey { + inherit indent; + key = "Escape"; + action = "act.PopKeyTable"; + }) + (luaKey { + inherit indent; + key = "Enter"; + action = "act.PopKeyTable"; + }) + (luaKey { + inherit indent; + key = "Space"; + mods = "ALT"; + action = "act.PopKeyTable"; + }) ]; # ── Status bar ─────────────────────────────────────────────────────────────── - # Per-mode accent colour (base16) and hint string shown on the left status bar + # Per-mode accent colour (base16) and hint string shown in the status bar + # Hints use nerd font glyphs: separators, icons, key labels modes = { - locked = {fg = color.base03; hint = "Alt+Space:enter Alt+hl:focus/tab Alt+1-9:tab Alt+n:split C-ud:scroll";}; - normal = {fg = color.base0D; hint = "p:pane t:tab r:resize s:scroll n:split Esc:exit";}; - pane = {fg = color.base0B; hint = "hjkl:focus n/r:split→ d:split↓ f:zoom x:close Esc:exit";}; - tab = {fg = color.base0C; hint = "hjkl:prev/next 1-9:jump n:new x:close r:rename Esc:exit";}; - resize = {fg = color.base0A; hint = "hjkl:+5 HJKL:+1 Esc:exit";}; - scroll = {fg = color.base0E; hint = "jk:line ud:half hl:page f:search Esc:exit";}; + locked = { + fg = color.base03; + hint = "⌥󱁐:󰌌 ⌥hl:󰿵 ⌥1-9:󰓩 ⌥n:󰤻 ⌥w:󰅙 ^ud:󱕷"; + }; + normal = { + fg = color.base0D; + hint = "t:󰐕 x:󰅙 r:󰩨 c:󰆏 s:󰍉 esc:󱊷"; + }; + resize = { + fg = color.base0A; + hint = "hjkl:󰁌 HJKL:󰁎 esc:󱊷"; + }; + copy_mode = { + fg = color.base0E; + hint = "hjkl:󰹳 v:󰒆 V:󰒇 ^v:󰒈 y:󰆏 /:󰍉 n/p:󰁹 esc:󱊷"; + }; + search_mode = { + fg = color.base08; + hint = "type:󰈙 ^n/^p:󰁹 ^r:󰑓 ↵/esc:󰆐"; + }; }; # Lua table literal mapping mode name -> { fg, hint } modeTableEntries = lib.concatStringsSep "\n " (lib.mapAttrsToList (name: m: '' - ["${name}"] = { fg = "${m.fg}", hint = "${m.hint}" }, - '') modes); + ["${name}"] = { fg = "${m.fg}", hint = "${m.hint}" }, + '') + modes); in { options.mods.terminal.wezterm.enable = lib.mkEnableOption "enables wezterm"; @@ -131,27 +216,51 @@ in { local bg_bar = "${color.base01}" local fg_text = "${color.base05}" + local fg_dim = "${color.base03}" local bg_main = "${color.base00}" + -- Render hints with the key in the mode accent colour and the icon dimmed. + -- Each hint token is "key:icon"; we colour them individually. + local function render_hints(hint, accent) + local cells = {} + for token in hint:gmatch("%S+") do + local key, icon = token:match("^(.-):(.)$") + if key and icon then + table.insert(cells, { Background = { Color = bg_bar } }) + table.insert(cells, { Foreground = { Color = accent } }) + table.insert(cells, { Text = key }) + table.insert(cells, { Foreground = { Color = fg_text } }) + table.insert(cells, { Text = ":" .. icon .. " " }) + else + table.insert(cells, { Background = { Color = bg_bar } }) + table.insert(cells, { Foreground = { Color = fg_dim } }) + table.insert(cells, { Text = token .. " " }) + end + end + return cells + end + wezterm.on("update-status", function(window, _pane) local kt = window:active_key_table() local mode = kt or "locked" local info = mode_info[mode] or mode_info["locked"] - -- Left: coloured mode badge + hint line - window:set_left_status(wezterm.format({ - { Background = { Color = info.fg } }, - { Foreground = { Color = bg_main } }, - { Attribute = { Intensity = "Bold" } }, - { Text = " " .. mode:upper() .. " " }, - "ResetAttributes", - { Background = { Color = bg_bar } }, - { Foreground = { Color = fg_text } }, - { Text = " " .. info.hint .. " " }, - "ResetAttributes", - })) + window:set_left_status("") - window:set_right_status("") + -- Right: coloured hints then mode badge + local cells = { { Text = " " } } + for _, c in ipairs(render_hints(info.hint, info.fg)) do + table.insert(cells, c) + end + table.insert(cells, "ResetAttributes") + table.insert(cells, { Background = { Color = info.fg } }) + table.insert(cells, { Foreground = { Color = bg_bar } }) + table.insert(cells, { Text = "" }) + table.insert(cells, { Foreground = { Color = bg_main } }) + table.insert(cells, { Attribute = { Intensity = "Bold" } }) + table.insert(cells, { Text = " " .. mode:upper() .. " " }) + table.insert(cells, "ResetAttributes") + window:set_right_status(wezterm.format(cells)) end) -- ─── Appearance ──────────────────────────────────────────────────────────── @@ -170,7 +279,18 @@ in { config.default_cursor_style = "BlinkingBar" config.hide_tab_bar_if_only_one_tab = false config.use_fancy_tab_bar = false - config.tab_bar_at_bottom = true + + config.colors = { + tab_bar = { + background = "${color.base01}", + active_tab = { bg_color = "${color.base0D}", fg_color = "${color.base00}", intensity = "Bold" }, + inactive_tab = { bg_color = "${color.base01}", fg_color = "${color.base03}" }, + inactive_tab_hover = { bg_color = "${color.base02}", fg_color = "${color.base04}" }, + new_tab = { bg_color = "${color.base01}", fg_color = "${color.base03}" }, + new_tab_hover = { bg_color = "${color.base02}", fg_color = "${color.base04}" }, + }, + } + config.tab_bar_at_bottom = false -- ─── Shell ───────────────────────────────────────────────────────────────── @@ -185,8 +305,8 @@ in { -- ─── Key bindings ────────────────────────────────────────────────────────── -- -- Default mode = "locked" (all keys pass through to the shell). - -- Alt+Space enters "normal" mode (like Zellij). - -- From normal: p→pane t→tab r→resize s→scroll + -- Alt+Space enters "normal" mode. + -- From normal: t→new tab x→close tab p/n→split r→resize mode -- Esc / Enter / Alt+Space → back to locked from any mode. config.disable_default_key_bindings = true @@ -195,15 +315,18 @@ in { -- Alt+h/l: move focus or switch tab at edge (Zellij MoveFocusOrTab) -- Alt+j/k: move focus between panes ${viDirKeys { - mods = "ALT"; - indent = " "; - mkAction = d: - if d == "Left" || d == "Right" - then "move_focus_or_tab(\"${d}\")" - else "act.ActivatePaneDirection(\"${d}\")"; - }} + mods = "ALT"; + indent = " "; + mkAction = d: + if d == "Left" || d == "Right" + then "move_focus_or_tab(\"${d}\")" + else "act.ActivatePaneDirection(\"${d}\")"; + }} -- Alt+1-9: jump to tab by number - ${tabJumpKeys {mods = "ALT"; indent = " ";}} + ${tabJumpKeys { + mods = "ALT"; + indent = " "; + }} -- Alt+n: new pane (split right) { key = "n", mods = "ALT", action = act.SplitHorizontal({ domain = "CurrentPaneDomain" }) }, -- Alt+f: toggle zoom @@ -218,6 +341,8 @@ in { -- Alt+i/o: reorder tabs { key = "i", mods = "ALT", action = act.MoveTabRelative(-1) }, { key = "o", mods = "ALT", action = act.MoveTabRelative(1) }, + -- Alt+w: close current pane + { key = "w", mods = "ALT", action = act.CloseCurrentPane({ confirm = true }) }, -- Alt+q: quit { key = "q", mods = "ALT", action = act.QuitApplication }, -- Ctrl+U/D: scroll half page (matches Alacritty) @@ -234,77 +359,86 @@ in { -- ── NORMAL ───────────────────────────────────────────────────────────── normal = { - { key = "p", action = act.ActivateKeyTable({ name = "pane", one_shot = false }) }, - { key = "t", action = act.ActivateKeyTable({ name = "tab", one_shot = false }) }, - { key = "r", action = act.ActivateKeyTable({ name = "resize", one_shot = false }) }, - { key = "s", action = act.ActivateKeyTable({ name = "scroll", one_shot = false }) }, - { key = "n", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} }, - ${exitKeys {selfKey = "Escape";}} }, - - -- ── PANE ─────────────────────────────────────────────────────────────── - pane = { - ${viDirKeys { - mkAction = d: - if d == "Left" || d == "Right" - then "move_focus_or_tab(\"${d}\")" - else "act.ActivatePaneDirection(\"${d}\")"; - }} - { key = "n", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} }, - { key = "r", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} }, - { key = "d", action = ${andPop "act.SplitVertical({ domain = \"CurrentPaneDomain\" })"} }, - { key = "f", action = ${andPop "act.TogglePaneZoomState"} }, - { key = "z", action = ${andPop "act.TogglePaneZoomState"} }, - { key = "x", action = ${andPop "act.CloseCurrentPane({ confirm = true })"} }, - { key = "Tab", action = act.ActivatePaneDirection("Next") }, - ${exitKeys {selfKey = "p";}} }, - - -- ── TAB ──────────────────────────────────────────────────────────────── - tab = { - { key = "h", action = act.ActivateTabRelative(-1) }, - { key = "k", action = act.ActivateTabRelative(-1) }, - { key = "j", action = act.ActivateTabRelative(1) }, - { key = "l", action = act.ActivateTabRelative(1) }, - { key = "LeftArrow", action = act.ActivateTabRelative(-1) }, - { key = "UpArrow", action = act.ActivateTabRelative(-1) }, - { key = "RightArrow", action = act.ActivateTabRelative(1) }, - { key = "DownArrow", action = act.ActivateTabRelative(1) }, - { key = "Tab", action = act.ActivateTabRelative(1) }, - ${tabJumpKeys {}} - { key = "n", action = ${andPop "act.SpawnTab(\"CurrentPaneDomain\")"} }, + { key = "t", action = ${andPop "act.SpawnTab(\"CurrentPaneDomain\")"} }, { key = "x", action = ${andPop "act.CloseCurrentTab({ confirm = true })"} }, - { key = "r", action = act.PromptInputLine({ - description = "Rename tab", - action = wezterm.action_callback(function(window, _, line) - if line then window:active_tab():set_title(line) end - window:perform_action(act.PopKeyTable, window:active_pane()) - end), - }) - }, - ${exitKeys {selfKey = "t";}} }, + { key = "r", action = act.ActivateKeyTable({ name = "resize", one_shot = false }) }, + { key = "c", action = ${andPop "act.ActivateCopyMode"} }, + { key = "s", action = ${andPop "act.Search({ CaseSensitiveString = \"\" })"} }, + ${exitKeys {selfKey = "Escape";}} }, -- ── RESIZE ───────────────────────────────────────────────────────────── resize = { - ${resizeDirKeys {step = 5;}}${resizeShiftKeys {step = 1;}} + ${resizeDirKeys {step = 5;}}${resizeShiftKeys {step = 1;}} { key = "+", action = act.AdjustPaneSize({ "Right", 5 }) }, { key = "-", action = act.AdjustPaneSize({ "Left", 5 }) }, { key = "=", action = act.AdjustPaneSize({ "Right", 5 }) }, ${exitKeys {selfKey = "r";}} }, - -- ── SCROLL ───────────────────────────────────────────────────────────── - scroll = { - { key = "j", action = act.ScrollByLine(1) }, - { key = "k", action = act.ScrollByLine(-1) }, - { key = "d", action = act.ScrollByPage(0.5) }, - { key = "u", action = act.ScrollByPage(-0.5) }, - { key = "h", action = act.ScrollByPage(-1) }, - { key = "l", action = act.ScrollByPage(1) }, - { key = "DownArrow", action = act.ScrollByLine(1) }, - { key = "UpArrow", action = act.ScrollByLine(-1) }, - { key = "PageDown", action = act.ScrollByPage(1) }, - { key = "PageUp", action = act.ScrollByPage(-1) }, - { key = "f", action = act.Search({ CaseSensitiveString = "" }) }, - { key = "c", mods = "CTRL", action = act.ScrollToBottom }, - ${exitKeys {selfKey = "s";}} }, + -- ── COPY MODE ────────────────────────────────────────────────────────── + copy_mode = { + -- movement + { key = "h", action = act.CopyMode("MoveLeft") }, + { key = "j", action = act.CopyMode("MoveDown") }, + { key = "k", action = act.CopyMode("MoveUp") }, + { key = "l", action = act.CopyMode("MoveRight") }, + { key = "LeftArrow", action = act.CopyMode("MoveLeft") }, + { key = "DownArrow", action = act.CopyMode("MoveDown") }, + { key = "UpArrow", action = act.CopyMode("MoveUp") }, + { key = "RightArrow", action = act.CopyMode("MoveRight") }, + { key = "w", action = act.CopyMode("MoveForwardWord") }, + { key = "b", action = act.CopyMode("MoveBackwardWord") }, + { key = "e", action = act.CopyMode("MoveForwardWordEnd") }, + { key = "0", action = act.CopyMode("MoveToStartOfLine") }, + { key = "^", action = act.CopyMode("MoveToStartOfLineContent") }, + { key = "$", action = act.CopyMode("MoveToEndOfLineContent") }, + { key = "g", action = act.CopyMode("MoveToScrollbackTop") }, + { key = "G", action = act.CopyMode("MoveToScrollbackBottom") }, + { key = "Enter", action = act.CopyMode("MoveToStartOfNextLine") }, + { key = "u", mods = "CTRL", action = act.CopyMode({ MoveByPage = -0.5 }) }, + { key = "d", mods = "CTRL", action = act.CopyMode({ MoveByPage = 0.5 }) }, + { key = "b", mods = "CTRL", action = act.CopyMode("PageUp") }, + { key = "f", mods = "CTRL", action = act.CopyMode("PageDown") }, + { key = "PageUp", action = act.CopyMode("PageUp") }, + { key = "PageDown", action = act.CopyMode("PageDown") }, + -- selection + { key = "v", action = act.CopyMode({ SetSelectionMode = "Cell" }) }, + { key = "V", action = act.CopyMode({ SetSelectionMode = "Line" }) }, + { key = "v", mods = "CTRL", action = act.CopyMode({ SetSelectionMode = "Block" }) }, + { key = "o", action = act.CopyMode("MoveToSelectionOtherEnd") }, + { key = "O", action = act.CopyMode("MoveToSelectionOtherEndHoriz") }, + -- yank and exit + { key = "y", action = act.Multiple({ + act.CopyTo("ClipboardAndPrimarySelection"), + act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }), + }) + }, + -- search within copy mode + { key = "/", action = act.Search({ CaseSensitiveString = "" }) }, + { key = "n", action = act.CopyMode("NextMatch") }, + { key = "p", action = act.CopyMode("PriorMatch") }, + -- exit + { key = "q", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) }, + { key = "Escape", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) }, + { key = "c", mods = "CTRL", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) }, + }, + + -- ── SEARCH MODE ──────────────────────────────────────────────────────── + search_mode = { + -- navigate matches + { key = "Enter", action = act.CopyMode("AcceptPattern") }, + { key = "n", mods = "CTRL", action = act.CopyMode("NextMatch") }, + { key = "p", mods = "CTRL", action = act.CopyMode("PriorMatch") }, + { key = "PageUp", action = act.CopyMode("PriorMatchPage") }, + { key = "PageDown", action = act.CopyMode("NextMatchPage") }, + { key = "UpArrow", action = act.CopyMode("PriorMatch") }, + { key = "DownArrow", action = act.CopyMode("NextMatch") }, + -- cycle match type (case-sensitive / insensitive / regex) + { key = "r", mods = "CTRL", action = act.CopyMode("CycleMatchType") }, + -- clear search input + { key = "u", mods = "CTRL", action = act.CopyMode("ClearPattern") }, + -- Escape: dismiss search bar, return to copy mode at current match + { key = "Escape", action = act.CopyMode("AcceptPattern") }, + }, } return config diff --git a/utils.nix b/utils.nix index fdb6c8b..b3c1b7d 100644 --- a/utils.nix +++ b/utils.nix @@ -1,4 +1,7 @@ -{ inputs, system, sources, ... }: { +{ inputs, system, sources, ... }: +let + pkgs = import inputs.nixpkgs { inherit system; }; +in { mkHost = host: inputs.nixpkgs.lib.nixosSystem { specialArgs = { inherit inputs system sources; }; @@ -11,4 +14,41 @@ inputs.impermanence.nixosModules.impermanence ]; }; + + # Build a standalone HM configuration for a given host NixOS config and + # home.nix file. osConfig is injected as a specialArg so all modules that + # reference it continue to work. + mkHome = { hostConfig, homeFile, username ? "muon" }: + inputs.home-manager.lib.homeManagerConfiguration { + inherit pkgs; + extraSpecialArgs = { + inherit inputs system sources; + osConfig = hostConfig.config; + }; + modules = [ + homeFile + inputs.self.outputs.homeManagerModules.default + inputs.stylix.homeManagerModules.stylix + ({ osConfig, ... }: { + home.username = username; + home.homeDirectory = "/home/${username}"; + # Mirror the stylix settings from the NixOS config so the standalone + # HM build has the same theme without re-declaring it. + stylix = { + enable = osConfig.stylix.enable; + autoEnable = osConfig.stylix.autoEnable; + base16Scheme = osConfig.stylix.base16Scheme; + image = osConfig.stylix.image; + cursor = osConfig.stylix.cursor; + # Forward individual font options; stylix derives fonts.packages + # internally as read-only so we must not forward that attr. + fonts.monospace = osConfig.stylix.fonts.monospace; + fonts.sansSerif = osConfig.stylix.fonts.sansSerif; + fonts.serif = osConfig.stylix.fonts.serif; + fonts.emoji = osConfig.stylix.fonts.emoji; + fonts.sizes = osConfig.stylix.fonts.sizes; + }; + }) + ]; + }; }