HASH 3beadbaa9293
DATE 2026-06-15
SUBJECT forgejo: custom colorscheme
FILES 4 CHANGED
HASH 3beadbaa9293
DATE 2026-06-15
SUBJECT forgejo: custom colorscheme
FILES 4 CHANGED
┌─ hosts/linode/default.nix ─────────────────────────────────────────────────┐
│ diff --git a/hosts/linode/default.nix b/hosts/linode/default.nix │
│ index b89bfdf..aaa8981 100644 │
│ --- a/hosts/linode/default.nix │
│ +++ b/hosts/linode/default.nix │
│ @@ -4,5 +4,10 @@ │
│ ./base │
│ ]; │
│ │
│ + ooknet.server.forgejo = { │
│ + customTheme.enable = true; │
│ + hideLogo = true; │
│ + }; │
│ + │
│ system.stateVersion = "24.11"; │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ hosts/linode/default.nix ───────────┐
│ diff --git a/hosts/linode/default.nix b/host │
│ s/linode/default.nix │
│ index b89bfdf..aaa8981 100644 │
│ --- a/hosts/linode/default.nix │
│ +++ b/hosts/linode/default.nix │
│ @@ -4,5 +4,10 @@ │
│ ./base │
│ ]; │
│ │
│ + ooknet.server.forgejo = { │
│ + customTheme.enable = true; │
│ + hideLogo = true; │
│ + }; │
│ + │
│ system.stateVersion = "24.11"; │
│ } │
└──────────────────────────────────────────────┘
┌─ modules/nixos/server/services/forgejo/default.nix ────────────────────────┐
│ diff --git a/modules/nixos/server/services/forgejo/default.nix b/modules/nixos/ser │
│ ver/services/forgejo/default.nix │
│ index 4c6ac05..fffffb3 100644 │
│ --- a/modules/nixos/server/services/forgejo/default.nix │
│ +++ b/modules/nixos/server/services/forgejo/default.nix │
│ @@ -2,20 +2,53 @@ │
│ config, │
│ lib, │
│ pkgs, │
│ + ook, │
│ ... │
│ }: let │
│ - inherit (lib) mkIf elem getExe; │
│ + inherit (lib) mkIf elem getExe optionalAttrs optionals; │
│ │
│ inherit (config.ooknet.server) services domain; │
│ inherit (config.services) fail2ban; │
│ │
│ + forgejoCfg = config.ooknet.server.forgejo; │
│ + themeCfg = forgejoCfg.customTheme; │
│ + themeFile = pkgs.writeText "theme-${themeCfg.name}.css" ( │
│ + ook.lib.color.export.toForgejoTheme {scheme = ook.themes.dark;} │
│ + ); │
│ + │
│ + blankLogoSvg = pkgs.writeText "logo.svg" ''<svg xmlns="http://www.w3.org/2000/s │
│ vg" viewBox="0 0 1 1"></svg>''; │
│ + blankLogoPng = pkgs.runCommand "logo.png" {nativeBuildInputs = [pkgs.imagemagic │
│ k];} '' │
│ + magick -size 1x1 xc:transparent PNG32:$out │
│ + ''; │
│ + │
│ settingsFormat = lib.generators.toINI { │
│ mkKeyValue = lib.generators.mkKeyValueDefault {} " = "; │
│ }; │
│ in { │
│ + imports = [./options.nix]; │
│ + │
│ config = mkIf (elem "forgejo" services) { │
│ networking.firewall.allowedTCPPorts = [2222]; │
│ │
│ + systemd.tmpfiles.rules = let │
│ + fj = config.services.forgejo; │
│ + pub = "${fj.customDir}/public"; │
│ + mkdir = p: "d ${p} 0750 ${fj.user} ${fj.group} - -"; │
│ + in │
│ + optionals (themeCfg.enable || forgejoCfg.hideLogo) [ │
│ + (mkdir pub) │
│ + (mkdir "${pub}/assets") │
│ + ] │
│ + ++ optionals themeCfg.enable [ │
│ + (mkdir "${pub}/assets/css") │
│ + "L+ ${pub}/assets/css/theme-${themeCfg.name}.css - - - - ${themeFile}" │
│ + ] │
│ + ++ optionals forgejoCfg.hideLogo [ │
│ + (mkdir "${pub}/assets/img") │
│ + "L+ ${pub}/assets/img/logo.svg - - - - ${blankLogoSvg}" │
│ + "L+ ${pub}/assets/img/logo.png - - - - ${blankLogoPng}" │
│ + ]; │
│ + │
│ ooknet.server = { │
│ webserver.caddy.enable = true; │
│ database.postgresql.enable = true; │
│ @@ -45,6 +78,12 @@ in { │
│ security = { │
│ INSTALL_LOCK = true; │
│ }; │
│ + ui = mkIf themeCfg.enable ({ │
│ + THEMES = "forgejo-auto,forgejo-light,forgejo-dark,${themeCfg.name}" │
│ ; │
│ + } │
│ + // optionalAttrs themeCfg.default { │
│ + DEFAULT_THEME = themeCfg.name; │
│ + }); │
│ }; │
│ }; │
│ caddy.virtualHosts = { │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../services/forgejo/default.nix ───┐
│ diff --git a/modules/nixos/server/services/f │
│ orgejo/default.nix b/modules/nixos/server/se │
│ rvices/forgejo/default.nix │
│ index 4c6ac05..fffffb3 100644 │
│ --- a/modules/nixos/server/services/forgejo/ │
│ default.nix │
│ +++ b/modules/nixos/server/services/forgejo/ │
│ default.nix │
│ @@ -2,20 +2,53 @@ │
│ config, │
│ lib, │
│ pkgs, │
│ + ook, │
│ ... │
│ }: let │
│ - inherit (lib) mkIf elem getExe; │
│ + inherit (lib) mkIf elem getExe optionalAt │
│ trs optionals; │
│ │
│ inherit (config.ooknet.server) services d │
│ omain; │
│ inherit (config.services) fail2ban; │
│ │
│ + forgejoCfg = config.ooknet.server.forgejo │
│ ; │
│ + themeCfg = forgejoCfg.customTheme; │
│ + themeFile = pkgs.writeText "theme-${theme │
│ Cfg.name}.css" ( │
│ + ook.lib.color.export.toForgejoTheme {sc │
│ heme = ook.themes.dark;} │
│ + ); │
│ + │
│ + blankLogoSvg = pkgs.writeText "logo.svg" │
│ ''<svg xmlns="http://www.w3.org/2000/svg" vi │
│ ewBox="0 0 1 1"></svg>''; │
│ + blankLogoPng = pkgs.runCommand "logo.png" │
│ {nativeBuildInputs = [pkgs.imagemagick];} ' │
│ ' │
│ + magick -size 1x1 xc:transparent PNG32:$ │
│ out │
│ + ''; │
│ + │
│ settingsFormat = lib.generators.toINI { │
│ mkKeyValue = lib.generators.mkKeyValueD │
│ efault {} " = "; │
│ }; │
│ in { │
│ + imports = [./options.nix]; │
│ + │
│ config = mkIf (elem "forgejo" services) { │
│ networking.firewall.allowedTCPPorts = [ │
│ 2222]; │
│ │
│ + systemd.tmpfiles.rules = let │
│ + fj = config.services.forgejo; │
│ + pub = "${fj.customDir}/public"; │
│ + mkdir = p: "d ${p} 0750 ${fj.user} ${ │
│ fj.group} - -"; │
│ + in │
│ + optionals (themeCfg.enable || forgejo │
│ Cfg.hideLogo) [ │
│ + (mkdir pub) │
│ + (mkdir "${pub}/assets") │
│ + ] │
│ + ++ optionals themeCfg.enable [ │
│ + (mkdir "${pub}/assets/css") │
│ + "L+ ${pub}/assets/css/theme-${theme │
│ Cfg.name}.css - - - - ${themeFile}" │
│ + ] │
│ + ++ optionals forgejoCfg.hideLogo [ │
│ + (mkdir "${pub}/assets/img") │
│ + "L+ ${pub}/assets/img/logo.svg - - │
│ - - ${blankLogoSvg}" │
│ + "L+ ${pub}/assets/img/logo.png - - │
│ - - ${blankLogoPng}" │
│ + ]; │
│ + │
│ ooknet.server = { │
│ webserver.caddy.enable = true; │
│ database.postgresql.enable = true; │
│ @@ -45,6 +78,12 @@ in { │
│ security = { │
│ INSTALL_LOCK = true; │
│ }; │
│ + ui = mkIf themeCfg.enable ({ │
│ + THEMES = "forgejo-auto,forgej │
│ o-light,forgejo-dark,${themeCfg.name}"; │
│ + } │
│ + // optionalAttrs themeCfg.defau │
│ lt { │
│ + DEFAULT_THEME = themeCfg.name │
│ ; │
│ + }); │
│ }; │
│ }; │
│ caddy.virtualHosts = { │
└──────────────────────────────────────────────┘
┌─ modules/nixos/server/services/forgejo/options.nix ────────────────────────┐
│ diff --git a/modules/nixos/server/services/forgejo/options.nix b/modules/nixos/ser │
│ ver/services/forgejo/options.nix │
│ index e17cd82..f9e048e 100644 │
│ --- a/modules/nixos/server/services/forgejo/options.nix │
│ +++ b/modules/nixos/server/services/forgejo/options.nix │
│ @@ -3,18 +3,28 @@ │
│ ... │
│ }: let │
│ inherit (lib) mkEnableOption mkOption; │
│ - inherit (lib.types) str; │
│ - mkColorOption = description: │
│ - mkOption { │
│ - type = str; │
│ - default = ""; │
│ - inherit description; │
│ - }; │
│ + inherit (lib.types) str bool; │
│ in { │
│ - options.ooknet.server.forgejo.customTheme = { │
│ - enable = mkEnableOption; │
│ - light = { │
│ - primary = mkColorOption; │
│ + options.ooknet.server.forgejo = { │
│ + # blanks the logo asset (custom/public/assets/img/logo.{svg,png}); the │
│ + # supported way to customize forgejo branding, no app.ini flag exists │
│ + hideLogo = mkOption { │
│ + type = bool; │
│ + default = false; │
│ + description = "Replace the Forgejo logo with a blank asset."; │
│ + }; │
│ + customTheme = { │
│ + enable = mkEnableOption "ooknet-generated Forgejo color theme (from ook.the │
│ mes.dark)"; │
│ + name = mkOption { │
│ + type = str; │
│ + default = "ooknet"; │
│ + description = "Theme name, served as theme-<name>.css and selectable in t │
│ he UI."; │
│ + }; │
│ + default = mkOption { │
│ + type = bool; │
│ + default = true; │
│ + description = "Set this theme as the instance DEFAULT_THEME."; │
│ + }; │
│ }; │
│ }; │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ .../services/forgejo/options.nix ───┐
│ diff --git a/modules/nixos/server/services/f │
│ orgejo/options.nix b/modules/nixos/server/se │
│ rvices/forgejo/options.nix │
│ index e17cd82..f9e048e 100644 │
│ --- a/modules/nixos/server/services/forgejo/ │
│ options.nix │
│ +++ b/modules/nixos/server/services/forgejo/ │
│ options.nix │
│ @@ -3,18 +3,28 @@ │
│ ... │
│ }: let │
│ inherit (lib) mkEnableOption mkOption; │
│ - inherit (lib.types) str; │
│ - mkColorOption = description: │
│ - mkOption { │
│ - type = str; │
│ - default = ""; │
│ - inherit description; │
│ - }; │
│ + inherit (lib.types) str bool; │
│ in { │
│ - options.ooknet.server.forgejo.customTheme │
│ = { │
│ - enable = mkEnableOption; │
│ - light = { │
│ - primary = mkColorOption; │
│ + options.ooknet.server.forgejo = { │
│ + # blanks the logo asset (custom/public/ │
│ assets/img/logo.{svg,png}); the │
│ + # supported way to customize forgejo br │
│ anding, no app.ini flag exists │
│ + hideLogo = mkOption { │
│ + type = bool; │
│ + default = false; │
│ + description = "Replace the Forgejo lo │
│ go with a blank asset."; │
│ + }; │
│ + customTheme = { │
│ + enable = mkEnableOption "ooknet-gener │
│ ated Forgejo color theme (from ook.themes.da │
│ rk)"; │
│ + name = mkOption { │
│ + type = str; │
│ + default = "ooknet"; │
│ + description = "Theme name, served a │
│ s theme-<name>.css and selectable in the UI. │
│ "; │
│ + }; │
│ + default = mkOption { │
│ + type = bool; │
│ + default = true; │
│ + description = "Set this theme as th │
│ e instance DEFAULT_THEME."; │
│ + }; │
│ }; │
│ }; │
│ } │
└──────────────────────────────────────────────┘
┌─ outputs/ook/lib/color/export.nix ─────────────────────────────────────────┐
│ diff --git a/outputs/ook/lib/color/export.nix b/outputs/ook/lib/color/export.nix │
│ index 88c04af..445b2d9 100644 │
│ --- a/outputs/ook/lib/color/export.nix │
│ +++ b/outputs/ook/lib/color/export.nix │
│ @@ -1,5 +1,5 @@ │
│ {lib}: let │
│ - inherit (lib) concatStringsSep mapAttrsToList flatten; │
│ + inherit (lib) concatStringsSep mapAttrsToList flatten concatMap; │
│ │
│ # Convert a flat color to CSS custom property │
│ colorToCss = name: value: " --ooknet-${name}: #${value};"; │
│ @@ -21,7 +21,11 @@ │
│ ]; │
│ │
│ # Convert semantic colors to CSS custom properties │
│ - semanticToCss = {layout, border, typography}: let │
│ + semanticToCss = { │
│ + layout, │
│ + border, │
│ + typography, │
│ + }: let │
│ layoutVars = mapAttrsToList (key: color: colorToCss "layout-${key}" color) la │
│ yout; │
│ borderVars = mapAttrsToList (key: color: colorToCss "border-${key}" color) bo │
│ rder; │
│ typographyVars = mapAttrsToList (key: color: colorToCss "typography-${key}" c │
│ olor) typography; │
│ @@ -40,49 +44,61 @@ │
│ ]; │
│ │
│ # Neutrals section │
│ - neutralsSection = [ │
│ - "" │
│ - " /* Neutrals */" │
│ - ] ++ (neutralsToCss scheme.neutrals); │
│ + neutralsSection = │
│ + [ │
│ + "" │
│ + " /* Neutrals */" │
│ + ] │
│ + ++ (neutralsToCss scheme.neutrals); │
│ │
│ # Named colors section │
│ namedColors = ["red" "orange" "yellow" "olive" "green" "teal" "blue" "violet" │
│ "purple" "pink" "brown" "primary"]; │
│ - namedColorsSection = [ │
│ - "" │
│ - " /* Named colors */" │
│ - ] ++ (flatten (map (color: scaleToCss color scheme.${color}) namedColors)); │
│ + namedColorsSection = │
│ + [ │
│ + "" │
│ + " /* Named colors */" │
│ + ] │
│ + ++ (flatten (map (color: scaleToCss color scheme.${color}) namedColors)); │
│ │
│ # Secondary section │
│ - secondarySection = [ │
│ - "" │
│ - " /* Secondary */" │
│ - ] ++ (scaleToCss "secondary" scheme.secondary); │
│ + secondarySection = │
│ + [ │
│ + "" │
│ + " /* Secondary */" │
│ + ] │
│ + ++ (scaleToCss "secondary" scheme.secondary); │
│ │
│ # Alert section │
│ - alertSection = [ │
│ - "" │
│ - " /* Alerts */" │
│ - ] ++ (flatten [ │
│ - (alertToCss "error" scheme.error) │
│ - (alertToCss "warning" scheme.warning) │
│ - (alertToCss "success" scheme.success) │
│ - (alertToCss "note" scheme.note) │
│ - (alertToCss "tip" scheme.tip) │
│ - ]); │
│ + alertSection = │
│ + [ │
│ + "" │
│ + " /* Alerts */" │
│ + ] │
│ + ++ (flatten [ │
│ + (alertToCss "error" scheme.error) │
│ + (alertToCss "warning" scheme.warning) │
│ + (alertToCss "success" scheme.success) │
│ + (alertToCss "note" scheme.note) │
│ + (alertToCss "tip" scheme.tip) │
│ + ]); │
│ │
│ # Semantic section │
│ - semanticSection = [ │
│ - "" │
│ - " /* Semantic */" │
│ - ] ++ (semanticToCss { │
│ - inherit (scheme) layout border typography; │
│ - }); │
│ + semanticSection = │
│ + [ │
│ + "" │
│ + " /* Semantic */" │
│ + ] │
│ + ++ (semanticToCss { │
│ + inherit (scheme) layout border typography; │
│ + }); │
│ │
│ # Syntax section │
│ - syntaxSection = [ │
│ - "" │
│ - " /* Syntax */" │
│ - ] ++ (syntaxToCss scheme.syntax); │
│ + syntaxSection = │
│ + [ │
│ + "" │
│ + " /* Syntax */" │
│ + ] │
│ + ++ (syntaxToCss scheme.syntax); │
│ │
│ # Base16 section │
│ base16Section = [ │
│ @@ -131,6 +147,249 @@ │
│ ]; │
│ in │
│ concatStringsSep "\n" allSections; │
│ + │
│ + # TODO: move to ooknet-style │
│ + # generate a full Forgejo theme from a scheme. imports a built-in base │
│ + # for the bits we dont recolor (chroma syntax, codemirror, markup) then │
│ + # overrides the whole UI palette. nearly everything in forgejos theme │
│ + # references --steel-* so redefining those cascades to body/box/text/etc │
│ + toForgejoTheme = { │
│ + scheme, │
│ + base ? "forgejo-dark", │
│ + }: let │
│ + inherit (scheme) neutrals primary; │
│ + v = name: hex: " --${name}: #${hex};"; │
│ + ref = name: target: " --${name}: var(--${target});"; │
│ + │
│ + # forgejos neutral ramp (900 darkest -> 100 lightest), 1:1 with ook neutrals │
│ + steelKeys = ["900" "850" "800" "750" "700" "650" "600" "550" "500" "450" "400 │
│ " "350" "300" "250" "200" "150" "100"]; │
│ + steel = map (k: v "steel-${k}" neutrals.${k}) steelKeys; │
│ + │
│ + # dark theme: primary-dark-N is lighter (hard), primary-light-N is darker (so │
│ ft) │
│ + alphas = [ │
│ + { │
│ + p = "10"; │
│ + h = "19"; │
│ + } │
│ + { │
│ + p = "20"; │
│ + h = "33"; │
│ + } │
│ + { │
│ + p = "30"; │
│ + h = "4b"; │
│ + } │
│ + { │
│ + p = "40"; │
│ + h = "66"; │
│ + } │
│ + { │
│ + p = "50"; │
│ + h = "80"; │
│ + } │
│ + { │
│ + p = "60"; │
│ + h = "99"; │
│ + } │
│ + { │
│ + p = "70"; │
│ + h = "b3"; │
│ + } │
│ + { │
│ + p = "80"; │
│ + h = "cc"; │
│ + } │
│ + { │
│ + p = "90"; │
│ + h = "e1"; │
│ + } │
│ + ]; │
│ + primaryVars = │
│ + [ │
│ + (v "color-primary" primary.base) │
│ + (ref "color-primary-contrast" "steel-900") │
│ + (v "color-primary-dark-1" primary.hard1) │
│ + (v "color-primary-dark-2" primary.hard1) │
│ + (v "color-primary-dark-3" primary.hard2) │
│ + (v "color-primary-dark-4" primary.hard2) │
│ + (v "color-primary-dark-5" primary.hard3) │
│ + (v "color-primary-dark-6" primary.hard3) │
│ + (v "color-primary-dark-7" primary.hard4) │
│ + (v "color-primary-light-1" primary.soft1) │
│ + (v "color-primary-light-2" primary.soft2) │
│ + (v "color-primary-light-3" primary.soft3) │
│ + (v "color-primary-light-4" primary.soft4) │
│ + (v "color-primary-light-5" primary.soft4) │
│ + (v "color-primary-light-6" primary.soft4) │
│ + (v "color-primary-light-7" primary.soft4) │
│ + (ref "color-primary-hover" "color-primary-light-1") │
│ + (ref "color-primary-active" "color-primary-light-2") │
│ + ] │
│ + ++ (map (a: v "color-primary-alpha-${a.p}" "${primary.base}${a.h}") alphas) │
│ ; │
│ + │
│ + named = ["red" "orange" "yellow" "olive" "green" "teal" "blue" "violet" "purp │
│ le" "pink" "brown"]; │
│ + namedVars = │
│ + (concatMap (n: let │
│ + s = scheme.${n}; │
│ + in [ │
│ + (v "color-${n}" s.base) │
│ + (v "color-${n}-light" s.hard1) │
│ + (v "color-${n}-dark-1" s.soft1) │
│ + (v "color-${n}-dark-2" s.soft2) │
│ + ]) │
│ + named) │
│ + ++ [ │
│ + (ref "color-grey" "steel-500") │
│ + (ref "color-grey-light" "steel-300") │
│ + (ref "color-black" "steel-900") │
│ + (ref "color-black-light" "steel-850") │
│ + ]; │
│ + │
│ + badgeVars = concatMap (n: let │
│ + b = scheme.${n}.base; │
│ + in [ │
│ + (v "color-${n}-badge" b) │
│ + (v "color-${n}-badge-bg" "${b}22") │
│ + (v "color-${n}-badge-hover-bg" "${b}44") │
│ + ]) ["red" "green" "yellow" "orange"]; │
│ + │
│ + consoleVars = [ │
│ + (ref "color-console-fg" "steel-100") │
│ + (ref "color-console-fg-subtle" "steel-300") │
│ + (ref "color-console-bg" "steel-850") │
│ + (ref "color-console-border" "steel-700") │
│ + " --color-console-hover-bg: #ffffff16;" │
│ + (ref "color-console-active-bg" "steel-650") │
│ + (ref "color-console-menu-bg" "steel-700") │
│ + (ref "color-console-menu-border" "steel-550") │
│ + (ref "color-ansi-black" "steel-800") │
│ + (v "color-ansi-red" scheme.red.base) │
│ + (v "color-ansi-green" scheme.green.base) │
│ + (v "color-ansi-yellow" scheme.yellow.base) │
│ + (v "color-ansi-blue" scheme.blue.base) │
│ + (v "color-ansi-magenta" scheme.purple.base) │
│ + (v "color-ansi-cyan" scheme.teal.base) │
│ + (ref "color-ansi-white" "steel-200") │
│ + (ref "color-ansi-bright-black" "steel-500") │
│ + (v "color-ansi-bright-red" scheme.red.hard1) │
│ + (v "color-ansi-bright-green" scheme.green.hard1) │
│ + (v "color-ansi-bright-yellow" scheme.yellow.hard1) │
│ + (v "color-ansi-bright-blue" scheme.blue.hard1) │
│ + (v "color-ansi-bright-magenta" scheme.purple.hard1) │
│ + (v "color-ansi-bright-cyan" scheme.teal.hard1) │
│ + (ref "color-ansi-bright-white" "steel-100") │
│ + ]; │
│ + │
│ + # syntax highlighting (chroma). forgejo ships these as hardcoded │
│ + # `.chroma .X { color }` rules; we re-emit them from the semantic │
│ + # syntax palette (gruvbox-material assignments). class -> token type │
│ + # per https://github.com/alecthomas/chroma types.go │
│ + syn = scheme.syntax; │
│ + hx = h: "#${h}"; │
│ + text = "var(--color-text)"; │
│ + crule = class: color: " .chroma .${class} { color: ${color}; }"; │
│ + chromaRules = [ │
│ + (crule "k" (hx syn.statement)) # keyword │
│ + (crule "kc" (hx syn.boolean)) # keyword constant │
│ + (crule "kd" (hx syn.storageClass)) # keyword declaration │
│ + (crule "kn" (hx syn.include)) # keyword namespace │
│ + (crule "kp" (hx syn.statement)) # keyword pseudo │
│ + (crule "kr" (hx syn.statement)) # keyword reserved │
│ + (crule "kt" (hx syn.type)) # keyword type │
│ + (crule "n" text) # name │
│ + (crule "na" (hx syn.identifier)) # name attribute │
│ + (crule "nb" (hx syn.function)) # name builtin │
│ + (crule "bp" (hx syn.function)) # name builtin pseudo │
│ + (crule "nc" (hx syn.type)) # name class │
│ + (crule "nd" (hx syn.macro)) # name decorator │
│ + (crule "ne" (hx syn.exception)) # name exception │
│ + (crule "nf" (hx syn.function)) # name function │
│ + (crule "fm" (hx syn.function)) # name function magic │
│ + (crule "ni" (hx syn.special)) # name entity │
│ + (crule "nl" (hx syn.label)) # name label │
│ + (crule "nn" text) # name namespace │
│ + (crule "no" (hx syn.constant)) # name constant │
│ + (crule "nt" (hx syn.tag)) # name tag │
│ + (crule "nv" (hx syn.identifier)) # name variable │
│ + (crule "vc" (hx syn.identifier)) # name variable class │
│ + (crule "vg" (hx syn.identifier)) # name variable global │
│ + (crule "vi" (hx syn.identifier)) # name variable instance │
│ + (crule "nx" text) # name other │
│ + (crule "s" (hx syn.string)) # string │
│ + (crule "s1" (hx syn.string)) # string single │
│ + (crule "s2" (hx syn.string)) # string double │
│ + (crule "sb" (hx syn.string)) # string backtick │
│ + (crule "sc" (hx syn.string)) # string char │
│ + (crule "sd" (hx syn.string)) # string doc │
│ + (crule "sh" (hx syn.string)) # string heredoc │
│ + (crule "sx" (hx syn.string)) # string other │
│ + (crule "sa" (hx syn.storageClass)) # string affix │
│ + (crule "dl" (hx syn.string)) # string delimiter │
│ + (crule "se" (hx syn.specialChar)) # string escape │
│ + (crule "ss" (hx syn.specialChar)) # string symbol │
│ + (crule "si" (hx syn.special)) # string interpol │
│ + (crule "sr" (hx syn.special)) # string regex │
│ + (crule "m" (hx syn.number)) # number │
│ + (crule "mb" (hx syn.number)) # number bin │
│ + (crule "mf" (hx syn.number)) # number float │
│ + (crule "mh" (hx syn.number)) # number hex │
│ + (crule "mi" (hx syn.number)) # number int │
│ + (crule "mo" (hx syn.number)) # number oct │
│ + (crule "il" (hx syn.number)) # number int long │
│ + (crule "o" (hx syn.operator)) # operator │
│ + (crule "ow" (hx syn.statement)) # operator word │
│ + (crule "p" text) # punctuation │
│ + (crule "c" (hx syn.comment)) # comment │
│ + (crule "c1" (hx syn.comment)) # comment single │
│ + (crule "ch" (hx syn.comment)) # comment hashbang │
│ + (crule "cm" (hx syn.comment)) # comment multiline │
│ + (crule "cs" (hx syn.comment)) # comment special │
│ + (crule "cp" (hx syn.preproc)) # comment preproc │
│ + (crule "cpf" (hx syn.preproc)) # comment preproc file │
│ + (crule "go" (hx syn.comment)) # generic output │
│ + (crule "gp" (hx syn.comment)) # generic prompt │
│ + (crule "gh" (hx syn.identifier)) # generic heading │
│ + (crule "gu" (hx syn.identifier)) # generic subheading │
│ + (crule "ge" (hx syn.identifier)) # generic emph │
│ + (crule "gs" text) # generic strong │
│ + (crule "gr" (hx syn.exception)) # generic error │
│ + (crule "gt" (hx syn.exception)) # generic traceback │
│ + (crule "gd" (hx syn.exception)) # generic deleted (bg kept from base) │
│ + (crule "gi" (hx syn.function)) # generic inserted (bg kept from base) │
│ + (crule "w" (hx syn.comment)) # whitespace │
│ + ]; │
│ + │
│ + lines = flatten [ │
│ + "/* ooknet generated forgejo theme: ${scheme.slug} */" │
│ + "@import url(\"theme-${base}.css\");" │
│ + "" │
│ + ":root {" │
│ + " --is-dark-theme: true;" │
│ + "" │
│ + " /* neutrals */" │
│ + steel │
│ + "" │
│ + " /* primary */" │
│ + primaryVars │
│ + "" │
│ + " /* named colors */" │
│ + namedVars │
│ + "" │
│ + " /* badges */" │
│ + badgeVars │
│ + "" │
│ + " /* console + ansi */" │
│ + consoleVars │
│ + "" │
│ + " color-scheme: dark;" │
│ + " accent-color: var(--color-accent);" │
│ + "}" │
│ + "" │
│ + "/* syntax highlighting (chroma) */" │
│ + chromaRules │
│ + ]; │
│ + in │
│ + concatStringsSep "\n" lines; │
│ in { │
│ - inherit toScss; │
│ + inherit toScss toForgejoTheme; │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ outputs/ook/lib/color/export.nix ───┐
│ diff --git a/outputs/ook/lib/color/export.ni │
│ x b/outputs/ook/lib/color/export.nix │
│ index 88c04af..445b2d9 100644 │
│ --- a/outputs/ook/lib/color/export.nix │
│ +++ b/outputs/ook/lib/color/export.nix │
│ @@ -1,5 +1,5 @@ │
│ {lib}: let │
│ - inherit (lib) concatStringsSep mapAttrsTo │
│ List flatten; │
│ + inherit (lib) concatStringsSep mapAttrsTo │
│ List flatten concatMap; │
│ │
│ # Convert a flat color to CSS custom prop │
│ erty │
│ colorToCss = name: value: " --ooknet-${n │
│ ame}: #${value};"; │
│ @@ -21,7 +21,11 @@ │
│ ]; │
│ │
│ # Convert semantic colors to CSS custom p │
│ roperties │
│ - semanticToCss = {layout, border, typograp │
│ hy}: let │
│ + semanticToCss = { │
│ + layout, │
│ + border, │
│ + typography, │
│ + }: let │
│ layoutVars = mapAttrsToList (key: color │
│ : colorToCss "layout-${key}" color) layout; │
│ borderVars = mapAttrsToList (key: color │
│ : colorToCss "border-${key}" color) border; │
│ typographyVars = mapAttrsToList (key: c │
│ olor: colorToCss "typography-${key}" color) │
│ typography; │
│ @@ -40,49 +44,61 @@ │
│ ]; │
│ │
│ # Neutrals section │
│ - neutralsSection = [ │
│ - "" │
│ - " /* Neutrals */" │
│ - ] ++ (neutralsToCss scheme.neutrals); │
│ + neutralsSection = │
│ + [ │
│ + "" │
│ + " /* Neutrals */" │
│ + ] │
│ + ++ (neutralsToCss scheme.neutrals); │
│ │
│ # Named colors section │
│ namedColors = ["red" "orange" "yellow" │
│ "olive" "green" "teal" "blue" "violet" "purp │
│ le" "pink" "brown" "primary"]; │
│ - namedColorsSection = [ │
│ - "" │
│ - " /* Named colors */" │
│ - ] ++ (flatten (map (color: scaleToCss c │
│ olor scheme.${color}) namedColors)); │
│ + namedColorsSection = │
│ + [ │
│ + "" │
│ + " /* Named colors */" │
│ + ] │
│ + ++ (flatten (map (color: scaleToCss c │
│ olor scheme.${color}) namedColors)); │
│ │
│ # Secondary section │
│ - secondarySection = [ │
│ - "" │
│ - " /* Secondary */" │
│ - ] ++ (scaleToCss "secondary" scheme.sec │
│ ondary); │
│ + secondarySection = │
│ + [ │
│ + "" │
│ + " /* Secondary */" │
│ + ] │
│ + ++ (scaleToCss "secondary" scheme.sec │
│ ondary); │
│ │
│ # Alert section │
│ - alertSection = [ │
│ - "" │
│ - " /* Alerts */" │
│ - ] ++ (flatten [ │
│ - (alertToCss "error" scheme.error) │
│ - (alertToCss "warning" scheme.warning) │
│ - (alertToCss "success" scheme.success) │
│ - (alertToCss "note" scheme.note) │
│ - (alertToCss "tip" scheme.tip) │
│ - ]); │
│ + alertSection = │
│ + [ │
│ + "" │
│ + " /* Alerts */" │
│ + ] │
│ + ++ (flatten [ │
│ + (alertToCss "error" scheme.error) │
│ + (alertToCss "warning" scheme.warnin │
│ g) │
│ + (alertToCss "success" scheme.succes │
│ s) │
│ + (alertToCss "note" scheme.note) │
│ + (alertToCss "tip" scheme.tip) │
│ + ]); │
│ │
│ # Semantic section │
│ - semanticSection = [ │
│ - "" │
│ - " /* Semantic */" │
│ - ] ++ (semanticToCss { │
│ - inherit (scheme) layout border typogr │
│ aphy; │
│ - }); │
│ + semanticSection = │
│ + [ │
│ + "" │
│ + " /* Semantic */" │
│ + ] │
│ + ++ (semanticToCss { │
│ + inherit (scheme) layout border typo │
│ graphy; │
│ + }); │
│ │
│ # Syntax section │
│ - syntaxSection = [ │
│ - "" │
│ - " /* Syntax */" │
│ - ] ++ (syntaxToCss scheme.syntax); │
│ + syntaxSection = │
│ + [ │
│ + "" │
│ + " /* Syntax */" │
│ + ] │
│ + ++ (syntaxToCss scheme.syntax); │
│ │
│ # Base16 section │
│ base16Section = [ │
│ @@ -131,6 +147,249 @@ │
│ ]; │
│ in │
│ concatStringsSep "\n" allSections; │
│ + │
│ + # TODO: move to ooknet-style │
│ + # generate a full Forgejo theme from a sc │
│ heme. imports a built-in base │
│ + # for the bits we dont recolor (chroma sy │
│ ntax, codemirror, markup) then │
│ + # overrides the whole UI palette. nearly │
│ everything in forgejos theme │
│ + # references --steel-* so redefining thos │
│ e cascades to body/box/text/etc │
│ + toForgejoTheme = { │
│ + scheme, │
│ + base ? "forgejo-dark", │
│ + }: let │
│ + inherit (scheme) neutrals primary; │
│ + v = name: hex: " --${name}: #${hex};"; │
│ + ref = name: target: " --${name}: var(- │
│ -${target});"; │
│ + │
│ + # forgejos neutral ramp (900 darkest -> │
│ 100 lightest), 1:1 with ook neutrals │
│ + steelKeys = ["900" "850" "800" "750" "7 │
│ 00" "650" "600" "550" "500" "450" "400" "350 │
│ " "300" "250" "200" "150" "100"]; │
│ + steel = map (k: v "steel-${k}" neutrals │
│ .${k}) steelKeys; │
│ + │
│ + # dark theme: primary-dark-N is lighter │
│ (hard), primary-light-N is darker (soft) │
│ + alphas = [ │
│ + { │
│ + p = "10"; │
│ + h = "19"; │
│ + } │
│ + { │
│ + p = "20"; │
│ + h = "33"; │
│ + } │
│ + { │
│ + p = "30"; │
│ + h = "4b"; │
│ + } │
│ + { │
│ + p = "40"; │
│ + h = "66"; │
│ + } │
│ + { │
│ + p = "50"; │
│ + h = "80"; │
│ + } │
│ + { │
│ + p = "60"; │
│ + h = "99"; │
│ + } │
│ + { │
│ + p = "70"; │
│ + h = "b3"; │
│ + } │
│ + { │
│ + p = "80"; │
│ + h = "cc"; │
│ + } │
│ + { │
│ + p = "90"; │
│ + h = "e1"; │
│ + } │
│ + ]; │
│ + primaryVars = │
│ + [ │
│ + (v "color-primary" primary.base) │
│ + (ref "color-primary-contrast" "stee │
│ l-900") │
│ + (v "color-primary-dark-1" primary.h │
│ ard1) │
│ + (v "color-primary-dark-2" primary.h │
│ ard1) │
│ + (v "color-primary-dark-3" primary.h │
│ ard2) │
│ + (v "color-primary-dark-4" primary.h │
│ ard2) │
│ + (v "color-primary-dark-5" primary.h │
│ ard3) │
│ + (v "color-primary-dark-6" primary.h │
│ ard3) │
│ + (v "color-primary-dark-7" primary.h │
│ ard4) │
│ + (v "color-primary-light-1" primary. │
│ soft1) │
│ + (v "color-primary-light-2" primary. │
│ soft2) │
│ + (v "color-primary-light-3" primary. │
│ soft3) │
│ + (v "color-primary-light-4" primary. │
│ soft4) │
│ + (v "color-primary-light-5" primary. │
│ soft4) │
│ + (v "color-primary-light-6" primary. │
│ soft4) │
│ + (v "color-primary-light-7" primary. │
│ soft4) │
│ + (ref "color-primary-hover" "color-p │
│ rimary-light-1") │
│ + (ref "color-primary-active" "color- │
│ primary-light-2") │
│ + ] │
│ + ++ (map (a: v "color-primary-alpha-${ │
│ a.p}" "${primary.base}${a.h}") alphas); │
│ + │
│ + named = ["red" "orange" "yellow" "olive │
│ " "green" "teal" "blue" "violet" "purple" "p │
│ ink" "brown"]; │
│ + namedVars = │
│ + (concatMap (n: let │
│ + s = scheme.${n}; │
│ + in [ │
│ + (v "color-${n}" s.base) │
│ + (v "color-${n}-light" s.hard1) │
│ + (v "color-${n}-dark-1" s.soft1) │
│ + (v "color-${n}-dark-2" s.soft2) │
│ + ]) │
│ + named) │
│ + ++ [ │
│ + (ref "color-grey" "steel-500") │
│ + (ref "color-grey-light" "steel-300" │
│ ) │
│ + (ref "color-black" "steel-900") │
│ + (ref "color-black-light" "steel-850 │
│ ") │
│ + ]; │
│ + │
│ + badgeVars = concatMap (n: let │
│ + b = scheme.${n}.base; │
│ + in [ │
│ + (v "color-${n}-badge" b) │
│ + (v "color-${n}-badge-bg" "${b}22") │
│ + (v "color-${n}-badge-hover-bg" "${b}4 │
│ 4") │
│ + ]) ["red" "green" "yellow" "orange"]; │
│ + │
│ + consoleVars = [ │
│ + (ref "color-console-fg" "steel-100") │
│ + (ref "color-console-fg-subtle" "steel │
│ -300") │
│ + (ref "color-console-bg" "steel-850") │
│ + (ref "color-console-border" "steel-70 │
│ 0") │
│ + " --color-console-hover-bg: #ffffff1 │
│ 6;" │
│ + (ref "color-console-active-bg" "steel │
│ -650") │
│ + (ref "color-console-menu-bg" "steel-7 │
│ 00") │
│ + (ref "color-console-menu-border" "ste │
│ el-550") │
│ + (ref "color-ansi-black" "steel-800") │
│ + (v "color-ansi-red" scheme.red.base) │
│ + (v "color-ansi-green" scheme.green.ba │
│ se) │
│ + (v "color-ansi-yellow" scheme.yellow. │
│ base) │
│ + (v "color-ansi-blue" scheme.blue.base │
│ ) │
│ + (v "color-ansi-magenta" scheme.purple │
│ .base) │
│ + (v "color-ansi-cyan" scheme.teal.base │
│ ) │
│ + (ref "color-ansi-white" "steel-200") │
│ + (ref "color-ansi-bright-black" "steel │
│ -500") │
│ + (v "color-ansi-bright-red" scheme.red │
│ .hard1) │
│ + (v "color-ansi-bright-green" scheme.g │
│ reen.hard1) │
│ + (v "color-ansi-bright-yellow" scheme. │
│ yellow.hard1) │
│ + (v "color-ansi-bright-blue" scheme.bl │
│ ue.hard1) │
│ + (v "color-ansi-bright-magenta" scheme │
│ .purple.hard1) │
│ + (v "color-ansi-bright-cyan" scheme.te │
│ al.hard1) │
│ + (ref "color-ansi-bright-white" "steel │
│ -100") │
│ + ]; │
│ + │
│ + # syntax highlighting (chroma). forgejo │
│ ships these as hardcoded │
│ + # `.chroma .X { color }` rules; we re-e │
│ mit them from the semantic │
│ + # syntax palette (gruvbox-material assi │
│ gnments). class -> token type │
│ + # per https://github.com/alecthomas/chr │
│ oma types.go │
│ + syn = scheme.syntax; │
│ + hx = h: "#${h}"; │
│ + text = "var(--color-text)"; │
│ + crule = class: color: " .chroma .${cla │
│ ss} { color: ${color}; }"; │
│ + chromaRules = [ │
│ + (crule "k" (hx syn.statement)) # keyw │
│ ord │
│ + (crule "kc" (hx syn.boolean)) # keywo │
│ rd constant │
│ + (crule "kd" (hx syn.storageClass)) # │
│ keyword declaration │
│ + (crule "kn" (hx syn.include)) # keywo │
│ rd namespace │
│ + (crule "kp" (hx syn.statement)) # key │
│ word pseudo │
│ + (crule "kr" (hx syn.statement)) # key │
│ word reserved │
│ + (crule "kt" (hx syn.type)) # keyword │
│ type │
│ + (crule "n" text) # name │
│ + (crule "na" (hx syn.identifier)) # na │
│ me attribute │
│ + (crule "nb" (hx syn.function)) # name │
│ builtin │
│ + (crule "bp" (hx syn.function)) # name │
│ builtin pseudo │
│ + (crule "nc" (hx syn.type)) # name cla │
│ ss │
│ + (crule "nd" (hx syn.macro)) # name de │
│ corator │
│ + (crule "ne" (hx syn.exception)) # nam │
│ e exception │
│ + (crule "nf" (hx syn.function)) # name │
│ function │
│ + (crule "fm" (hx syn.function)) # name │
│ function magic │
│ + (crule "ni" (hx syn.special)) # name │
│ entity │
│ + (crule "nl" (hx syn.label)) # name la │
│ bel │
│ + (crule "nn" text) # name namespace │
│ + (crule "no" (hx syn.constant)) # name │
│ constant │
│ + (crule "nt" (hx syn.tag)) # name tag │
│ + (crule "nv" (hx syn.identifier)) # na │
│ me variable │
│ + (crule "vc" (hx syn.identifier)) # na │
│ me variable class │
│ + (crule "vg" (hx syn.identifier)) # na │
│ me variable global │
│ + (crule "vi" (hx syn.identifier)) # na │
│ me variable instance │
│ + (crule "nx" text) # name other │
│ + (crule "s" (hx syn.string)) # string │
│ + (crule "s1" (hx syn.string)) # string │
│ single │
│ + (crule "s2" (hx syn.string)) # string │
│ double │
│ + (crule "sb" (hx syn.string)) # string │
│ backtick │
│ + (crule "sc" (hx syn.string)) # string │
│ char │
│ + (crule "sd" (hx syn.string)) # string │
│ doc │
│ + (crule "sh" (hx syn.string)) # string │
│ heredoc │
│ + (crule "sx" (hx syn.string)) # string │
│ other │
│ + (crule "sa" (hx syn.storageClass)) # │
│ string affix │
│ + (crule "dl" (hx syn.string)) # string │
│ delimiter │
│ + (crule "se" (hx syn.specialChar)) # s │
│ tring escape │
│ + (crule "ss" (hx syn.specialChar)) # s │
│ tring symbol │
│ + (crule "si" (hx syn.special)) # strin │
│ g interpol │
│ + (crule "sr" (hx syn.special)) # strin │
│ g regex │
│ + (crule "m" (hx syn.number)) # number │
│ + (crule "mb" (hx syn.number)) # number │
│ bin │
│ + (crule "mf" (hx syn.number)) # number │
│ float │
│ + (crule "mh" (hx syn.number)) # number │
│ hex │
│ + (crule "mi" (hx syn.number)) # number │
│ int │
│ + (crule "mo" (hx syn.number)) # number │
│ oct │
│ + (crule "il" (hx syn.number)) # number │
│ int long │
│ + (crule "o" (hx syn.operator)) # opera │
│ tor │
│ + (crule "ow" (hx syn.statement)) # ope │
│ rator word │
│ + (crule "p" text) # punctuation │
│ + (crule "c" (hx syn.comment)) # commen │
│ t │
│ + (crule "c1" (hx syn.comment)) # comme │
│ nt single │
│ + (crule "ch" (hx syn.comment)) # comme │
│ nt hashbang │
│ + (crule "cm" (hx syn.comment)) # comme │
│ nt multiline │
│ + (crule "cs" (hx syn.comment)) # comme │
│ nt special │
│ + (crule "cp" (hx syn.preproc)) # comme │
│ nt preproc │
│ + (crule "cpf" (hx syn.preproc)) # comm │
│ ent preproc file │
│ + (crule "go" (hx syn.comment)) # gener │
│ ic output │
│ + (crule "gp" (hx syn.comment)) # gener │
│ ic prompt │
│ + (crule "gh" (hx syn.identifier)) # ge │
│ neric heading │
│ + (crule "gu" (hx syn.identifier)) # ge │
│ neric subheading │
│ + (crule "ge" (hx syn.identifier)) # ge │
│ neric emph │
│ + (crule "gs" text) # generic strong │
│ + (crule "gr" (hx syn.exception)) # gen │
│ eric error │
│ + (crule "gt" (hx syn.exception)) # gen │
│ eric traceback │
│ + (crule "gd" (hx syn.exception)) # gen │
│ eric deleted (bg kept from base) │
│ + (crule "gi" (hx syn.function)) # gene │
│ ric inserted (bg kept from base) │
│ + (crule "w" (hx syn.comment)) # whites │
│ pace │
│ + ]; │
│ + │
│ + lines = flatten [ │
│ + "/* ooknet generated forgejo theme: $ │
│ {scheme.slug} */" │
│ + "@import url(\"theme-${base}.css\");" │
│ + "" │
│ + ":root {" │
│ + " --is-dark-theme: true;" │
│ + "" │
│ + " /* neutrals */" │
│ + steel │
│ + "" │
│ + " /* primary */" │
│ + primaryVars │
│ + "" │
│ + " /* named colors */" │
│ + namedVars │
│ + "" │
│ + " /* badges */" │
│ + badgeVars │
│ + "" │
│ + " /* console + ansi */" │
│ + consoleVars │
│ + "" │
│ + " color-scheme: dark;" │
│ + " accent-color: var(--color-accent); │
│ " │
│ + "}" │
│ + "" │
│ + "/* syntax highlighting (chroma) */" │
│ + chromaRules │
│ + ]; │
│ + in │
│ + concatStringsSep "\n" lines; │
│ in { │
│ - inherit toScss; │
│ + inherit toScss toForgejoTheme; │
│ } │
└──────────────────────────────────────────────┘
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET