Static safety/lint runner for the BlockNinja codebase. ~25 invariant
checks across Go and frontend sources. Was at git.dev.alexdunmow.com:block/ninja
in backend/cmd/check-safety/ until the 2026-06-06 consolidation moved
the BlockNinja repos under a shared ~/src/blockninja/ parent.
This repo is the standalone extraction:
- Own go.mod (git.dev.alexdunmow.com/block/check-safety, go 1.26.4)
- Vendored internal/{helpers,theme} from CMS (Go's internal/ rule
blocks cross-module imports; vendoring is the workaround)
- CLI contract unchanged: `check-safety <target-dir> [--flags]`
- CMS Makefile shells into ../check-safety for safety-check /
install-safety-checker targets
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
326 lines
12 KiB
Go
326 lines
12 KiB
Go
package theme
|
|
|
|
// DefaultTheme returns the default theme settings
|
|
func DefaultTheme() *Theme {
|
|
return &Theme{
|
|
LightColors: &ColorScheme{
|
|
Background: "0 0% 100%",
|
|
Foreground: "0 0% 3.9%",
|
|
Card: "0 0% 100%",
|
|
CardForeground: "0 0% 3.9%",
|
|
Popover: "0 0% 100%",
|
|
PopoverForeground: "0 0% 3.9%",
|
|
Primary: "0 0% 9%",
|
|
PrimaryForeground: "0 0% 98%",
|
|
Secondary: "0 0% 96.1%",
|
|
SecondaryForeground: "0 0% 9%",
|
|
Muted: "0 0% 96.1%",
|
|
MutedForeground: "0 0% 45.1%",
|
|
Accent: "0 0% 96.1%",
|
|
AccentForeground: "0 0% 9%",
|
|
Destructive: "0 84.2% 60.2%",
|
|
DestructiveForeground: "0 0% 98%",
|
|
Border: "0 0% 89.8%",
|
|
Input: "0 0% 89.8%",
|
|
Ring: "0 0% 3.9%",
|
|
},
|
|
DarkColors: &ColorScheme{
|
|
Background: "0 0% 3.9%",
|
|
Foreground: "0 0% 98%",
|
|
Card: "0 0% 3.9%",
|
|
CardForeground: "0 0% 98%",
|
|
Popover: "0 0% 3.9%",
|
|
PopoverForeground: "0 0% 98%",
|
|
Primary: "0 0% 98%",
|
|
PrimaryForeground: "0 0% 9%",
|
|
Secondary: "0 0% 14.9%",
|
|
SecondaryForeground: "0 0% 98%",
|
|
Muted: "0 0% 14.9%",
|
|
MutedForeground: "0 0% 63.9%",
|
|
Accent: "0 0% 14.9%",
|
|
AccentForeground: "0 0% 98%",
|
|
Destructive: "0 62.8% 30.6%",
|
|
DestructiveForeground: "0 0% 98%",
|
|
Border: "0 0% 14.9%",
|
|
Input: "0 0% 14.9%",
|
|
Ring: "0 0% 83.1%",
|
|
},
|
|
Typography: &ThemeTypography{
|
|
FontHeading: "system",
|
|
FontBody: "system",
|
|
FontMono: "system",
|
|
FontSizeBase: "16",
|
|
LineHeightBase: "1.5",
|
|
FontWeightBase: "normal",
|
|
},
|
|
Spacing: &ThemeSpacing{
|
|
Radius: "0.5",
|
|
ButtonRadius: "0.375",
|
|
CardRadius: "0.75",
|
|
InputRadius: "0.375",
|
|
PillButtons: false,
|
|
BorderWidth: "normal",
|
|
ContainerWidth: "normal",
|
|
SpacingScale: "normal",
|
|
},
|
|
Effects: &ThemeEffects{
|
|
ShadowIntensity: 50,
|
|
ShadowColor: "0 0% 0%",
|
|
ShadowBlur: "normal",
|
|
ElevationPreset: "raised",
|
|
TransitionSpeed: "normal",
|
|
ReduceMotion: false,
|
|
HoverIntensity: "normal",
|
|
},
|
|
Mode: "system",
|
|
Buttons: &ThemeButtons{
|
|
Primary: &ButtonTypeConfig{
|
|
BgColor: "var(--primary)",
|
|
TextColor: "var(--primary-foreground)",
|
|
BorderWidth: "0",
|
|
Radius: "0.375",
|
|
Shadow: "none",
|
|
HoverBg: "var(--primary)/90",
|
|
},
|
|
Secondary: &ButtonTypeConfig{
|
|
BgColor: "var(--secondary)",
|
|
TextColor: "var(--secondary-foreground)",
|
|
BorderWidth: "0",
|
|
Radius: "0.375",
|
|
Shadow: "none",
|
|
HoverBg: "var(--secondary)/80",
|
|
},
|
|
Outline: &ButtonTypeConfig{
|
|
BgColor: "transparent",
|
|
TextColor: "var(--primary)",
|
|
BorderColor: "var(--border)",
|
|
BorderWidth: "1",
|
|
Radius: "0.375",
|
|
Shadow: "none",
|
|
HoverBg: "var(--accent)",
|
|
},
|
|
Ghost: &ButtonTypeConfig{
|
|
BgColor: "transparent",
|
|
TextColor: "var(--foreground)",
|
|
BorderWidth: "0",
|
|
Radius: "0.375",
|
|
Shadow: "none",
|
|
HoverBg: "var(--accent)",
|
|
},
|
|
Link: &ButtonTypeConfig{
|
|
BgColor: "transparent",
|
|
TextColor: "var(--primary)",
|
|
BorderWidth: "0",
|
|
Radius: "0",
|
|
Shadow: "none",
|
|
},
|
|
Destructive: &ButtonTypeConfig{
|
|
BgColor: "var(--destructive)",
|
|
TextColor: "var(--destructive-foreground)",
|
|
BorderWidth: "0",
|
|
Radius: "0.375",
|
|
Shadow: "none",
|
|
HoverBg: "var(--destructive)/90",
|
|
},
|
|
},
|
|
Inputs: &ThemeInputs{
|
|
Default: &InputVariantConfig{
|
|
BgColor: "var(--background)",
|
|
BorderColor: "var(--input)",
|
|
BorderWidth: "1",
|
|
TextColor: "var(--foreground)",
|
|
FocusRing: "var(--ring)",
|
|
FocusBg: "",
|
|
FocusBorder: "",
|
|
HoverBg: "",
|
|
PlaceholderColor: "var(--muted-foreground)",
|
|
},
|
|
Filled: &InputVariantConfig{
|
|
BgColor: "var(--muted)",
|
|
BorderColor: "transparent",
|
|
BorderWidth: "1",
|
|
TextColor: "var(--foreground)",
|
|
FocusRing: "var(--ring)",
|
|
FocusBg: "var(--background)",
|
|
FocusBorder: "var(--input)",
|
|
HoverBg: "",
|
|
PlaceholderColor: "var(--muted-foreground)",
|
|
},
|
|
Outlined: &InputVariantConfig{
|
|
BgColor: "transparent",
|
|
BorderColor: "var(--input)",
|
|
BorderWidth: "2",
|
|
TextColor: "var(--foreground)",
|
|
FocusRing: "var(--ring)",
|
|
FocusBg: "",
|
|
FocusBorder: "var(--ring)",
|
|
HoverBg: "",
|
|
PlaceholderColor: "var(--muted-foreground)",
|
|
},
|
|
Ghost: &InputVariantConfig{
|
|
BgColor: "transparent",
|
|
BorderColor: "transparent",
|
|
BorderWidth: "1",
|
|
TextColor: "var(--foreground)",
|
|
FocusRing: "var(--ring)",
|
|
FocusBg: "var(--background)",
|
|
FocusBorder: "var(--input)",
|
|
HoverBg: "var(--muted)",
|
|
PlaceholderColor: "var(--muted-foreground)",
|
|
},
|
|
},
|
|
Cards: &ThemeCards{
|
|
BorderWidth: "1",
|
|
BorderStyle: "solid",
|
|
BorderColor: "var(--border)",
|
|
Shadow: "sm",
|
|
ShadowColor: "",
|
|
BgOpacity: "100",
|
|
BackdropBlur: "none",
|
|
HoverEffect: "none",
|
|
},
|
|
}
|
|
}
|
|
|
|
// Theme represents the complete theme configuration
|
|
type Theme struct {
|
|
LightColors *ColorScheme `json:"lightColors"`
|
|
DarkColors *ColorScheme `json:"darkColors"`
|
|
Typography *ThemeTypography `json:"typography"`
|
|
Spacing *ThemeSpacing `json:"spacing"`
|
|
Effects *ThemeEffects `json:"effects"`
|
|
Mode string `json:"mode"`
|
|
Buttons *ThemeButtons `json:"buttons,omitempty"`
|
|
Inputs *ThemeInputs `json:"inputs,omitempty"`
|
|
Cards *ThemeCards `json:"cards,omitempty"`
|
|
CustomColors []*CustomColor `json:"customColors,omitempty"`
|
|
}
|
|
|
|
// CustomColor represents a user-defined color variable with light and dark mode values
|
|
type CustomColor struct {
|
|
Name string `json:"name"` // Variable name (e.g., "brand") - becomes --color-{name}
|
|
LightValue string `json:"lightValue"` // HSL value for light mode
|
|
DarkValue string `json:"darkValue"` // HSL value for dark mode
|
|
Source string `json:"source,omitempty"` // "" = user-created, "pluginname" = plugin-provisioned
|
|
}
|
|
|
|
// ColorScheme represents colors for a single mode (light or dark)
|
|
type ColorScheme struct {
|
|
Background string `json:"background"`
|
|
Foreground string `json:"foreground"`
|
|
Card string `json:"card"`
|
|
CardForeground string `json:"cardForeground"`
|
|
Popover string `json:"popover"`
|
|
PopoverForeground string `json:"popoverForeground"`
|
|
Primary string `json:"primary"`
|
|
PrimaryForeground string `json:"primaryForeground"`
|
|
Secondary string `json:"secondary"`
|
|
SecondaryForeground string `json:"secondaryForeground"`
|
|
Muted string `json:"muted"`
|
|
MutedForeground string `json:"mutedForeground"`
|
|
Accent string `json:"accent"`
|
|
AccentForeground string `json:"accentForeground"`
|
|
Destructive string `json:"destructive"`
|
|
DestructiveForeground string `json:"destructiveForeground"`
|
|
Border string `json:"border"`
|
|
Input string `json:"input"`
|
|
Ring string `json:"ring"`
|
|
}
|
|
|
|
// ThemeTypography represents typography settings
|
|
type ThemeTypography struct {
|
|
FontHeading string `json:"fontHeading"`
|
|
FontBody string `json:"fontBody"`
|
|
FontMono string `json:"fontMono"`
|
|
FontSizeBase string `json:"fontSizeBase"`
|
|
LineHeightBase string `json:"lineHeightBase"`
|
|
FontWeightBase string `json:"fontWeightBase"`
|
|
}
|
|
|
|
// ThemeSpacing represents spacing settings
|
|
type ThemeSpacing struct {
|
|
Radius string `json:"radius"`
|
|
ButtonRadius string `json:"buttonRadius"`
|
|
CardRadius string `json:"cardRadius"`
|
|
InputRadius string `json:"inputRadius"`
|
|
PillButtons bool `json:"pillButtons"`
|
|
BorderWidth string `json:"borderWidth"`
|
|
ContainerWidth string `json:"containerWidth"`
|
|
SpacingScale string `json:"spacingScale"`
|
|
}
|
|
|
|
// ThemeEffects represents shadow, motion, and effect settings
|
|
type ThemeEffects struct {
|
|
ShadowIntensity int `json:"shadowIntensity"`
|
|
ShadowColor string `json:"shadowColor"`
|
|
ShadowBlur string `json:"shadowBlur"`
|
|
ElevationPreset string `json:"elevationPreset"`
|
|
TransitionSpeed string `json:"transitionSpeed"`
|
|
ReduceMotion bool `json:"reduceMotion"`
|
|
HoverIntensity string `json:"hoverIntensity"`
|
|
}
|
|
|
|
// ButtonTypeConfig represents configuration for a single button type
|
|
type ButtonTypeConfig struct {
|
|
BgColor string `json:"bgColor"` // CSS value: "var(--primary)" | "transparent" | HSL
|
|
TextColor string `json:"textColor"` // CSS value: "var(--primary-foreground)" | HSL
|
|
BorderColor string `json:"borderColor"` // CSS value or empty
|
|
BorderWidth string `json:"borderWidth"` // "0" | "1" | "2" (px)
|
|
Radius string `json:"radius"` // rem value or "pill"
|
|
Shadow string `json:"shadow"` // "none" | "sm" | "md" | "lg"
|
|
HoverBg string `json:"hoverBg"` // CSS value for hover
|
|
HoverText string `json:"hoverText"` // CSS value for hover
|
|
Size string `json:"size"` // "sm" | "md" | "lg" - default button size
|
|
}
|
|
|
|
// ThemeButtons represents all button type configurations
|
|
type ThemeButtons struct {
|
|
Primary *ButtonTypeConfig `json:"primary"`
|
|
Secondary *ButtonTypeConfig `json:"secondary"`
|
|
Outline *ButtonTypeConfig `json:"outline"`
|
|
Ghost *ButtonTypeConfig `json:"ghost"`
|
|
Link *ButtonTypeConfig `json:"link"`
|
|
Destructive *ButtonTypeConfig `json:"destructive"`
|
|
Custom map[string]*ButtonTypeConfig `json:"custom,omitempty"`
|
|
}
|
|
|
|
// InputVariantConfig represents configuration for a single input variant
|
|
type InputVariantConfig struct {
|
|
BgColor string `json:"bgColor"` // CSS value: "var(--background)" | "var(--muted)" | transparent
|
|
BorderColor string `json:"borderColor"` // CSS value: "var(--input)" | "var(--border)"
|
|
BorderWidth string `json:"borderWidth"` // "0" | "1" | "2" (px)
|
|
TextColor string `json:"textColor"` // CSS value: "var(--foreground)"
|
|
FocusRing string `json:"focusRing"` // CSS value: "var(--ring)"
|
|
FocusBg string `json:"focusBg"` // Background when focused
|
|
FocusBorder string `json:"focusBorder"` // Border when focused
|
|
HoverBg string `json:"hoverBg"` // Background on hover (for ghost variant)
|
|
PlaceholderColor string `json:"placeholderColor"` // Placeholder text color
|
|
}
|
|
|
|
// ThemeInputs represents all input variant configurations
|
|
type ThemeInputs struct {
|
|
Default *InputVariantConfig `json:"default"`
|
|
Filled *InputVariantConfig `json:"filled"`
|
|
Outlined *InputVariantConfig `json:"outlined"`
|
|
Ghost *InputVariantConfig `json:"ghost"`
|
|
}
|
|
|
|
// ThemeCards represents card styling configuration
|
|
type ThemeCards struct {
|
|
// Border
|
|
BorderWidth string `json:"borderWidth"` // "0", "1", "2", "4" (px)
|
|
BorderStyle string `json:"borderStyle"` // "solid", "dashed", "dotted"
|
|
BorderColor string `json:"borderColor"` // CSS var or HSL value
|
|
|
|
// Shadow
|
|
Shadow string `json:"shadow"` // "none", "sm", "md", "lg", "xl"
|
|
ShadowColor string `json:"shadowColor"` // HSL for colored shadows
|
|
|
|
// Glass Effects
|
|
BgOpacity string `json:"bgOpacity"` // "100", "90", "80", "70" (percent)
|
|
BackdropBlur string `json:"backdropBlur"` // "none", "sm", "md", "lg"
|
|
|
|
// Hover Effects
|
|
HoverEffect string `json:"hoverEffect"` // "none", "lift", "glow", "scale"
|
|
}
|