package theme
import "strings"
// Family is a light theme and a dark one made to go together, as most
// editor themes ship. A reader picks the family; the page follows the
// light/dark choice (System, Light or Dark) and draws whichever side
// matches, so a light page never carries a dark code block. A theme
// with no partner is not offered.
type Family struct {
ID, Name string
Light, Dark Theme
}
// familyTable pairs the generated themes. Where a family has several
// dark variants and one light side (Catppuccin, Kanagawa, Material),
// each dark variant is its own family over the same light theme.
var familyTable = []struct{ id, name, light, dark string }{
{"ayu", "Ayu", "ayu-light", "ayu-dark"},
{"ayu-mirage", "Ayu Mirage", "ayu-light", "ayu-mirage"},
{"catppuccin-mocha", "Catppuccin Mocha", "catppuccin-latte", "catppuccin-mocha"},
{"catppuccin-macchiato", "Catppuccin Macchiato", "catppuccin-latte", "catppuccin-macchiato"},
{"catppuccin-frappe", "Catppuccin Frappé", "catppuccin-latte", "catppuccin-frappe"},
{"everforest", "Everforest", "everforest-light", "everforest-dark"},
{"github", "GitHub", "github-light", "github-dark"},
{"github-default", "GitHub Default", "github-light-default", "github-dark-default"},
{"github-high-contrast", "GitHub High Contrast", "github-light-high-contrast", "github-dark-high-contrast"},
{"github-dimmed", "GitHub Dimmed", "github-light", "github-dark-dimmed"},
{"gruvbox-hard", "Gruvbox Hard", "gruvbox-light-hard", "gruvbox-dark-hard"},
{"gruvbox-medium", "Gruvbox Medium", "gruvbox-light-medium", "gruvbox-dark-medium"},
{"gruvbox-soft", "Gruvbox Soft", "gruvbox-light-soft", "gruvbox-dark-soft"},
{"horizon", "Horizon", "horizon-bright", "horizon"},
{"kanagawa-wave", "Kanagawa Wave", "kanagawa-lotus", "kanagawa-wave"},
{"kanagawa-dragon", "Kanagawa Dragon", "kanagawa-lotus", "kanagawa-dragon"},
{"material", "Material", "material-theme-lighter", "material-theme"},
{"material-darker", "Material Darker", "material-theme-lighter", "material-theme-darker"},
{"material-ocean", "Material Ocean", "material-theme-lighter", "material-theme-ocean"},
{"material-palenight", "Material Palenight", "material-theme-lighter", "material-theme-palenight"},
{"min", "Min", "min-light", "min-dark"},
{"night-owl", "Night Owl", "night-owl-light", "night-owl"},
{"one", "One", "one-light", "one-dark-pro"},
{"rose-pine", "Rosé Pine", "rose-pine-dawn", "rose-pine"},
{"rose-pine-moon", "Rosé Pine Moon", "rose-pine-dawn", "rose-pine-moon"},
{"slack", "Slack", "slack-ochin", "slack-dark"},
{"solarized", "Solarized", "solarized-light", "solarized-dark"},
{"vitesse", "Vitesse", "vitesse-light", "vitesse-dark"},
{"vitesse-black", "Vitesse Black", "vitesse-light", "vitesse-black"},
{"vscode", "VS Code", "light-plus", "dark-plus"},
}
// Families is familyTable resolved against All. A row naming a theme
// the generator no longer makes is skipped; a test holds every row to
// resolving.
var Families = func() []Family {
var out []Family
for _, r := range familyTable {
l, okL := ByID(r.light)
d, okD := ByID(r.dark)
if okL && okD {
out = append(out, Family{ID: r.id, Name: r.name, Light: l, Dark: d})
}
}
return out
}()
// FamilyByID finds a family by its id. A single theme's id finds the
// first family it belongs to, so a choice saved before themes came in
// pairs ("gruvbox-dark-hard") still holds.
func FamilyByID(id string) (Family, bool) {
for _, f := range Families {
if f.ID == id {
return f, true
}
}
for _, f := range Families {
if f.Light.ID == id || f.Dark.ID == id {
return f, true
}
}
return Family{}, false
}
// The dark side applies where paletteCSS's dark tokens do: an explicit
// Dark, or System on a dark device. Each selector is one class more
// specific than the light side's and than paletteCSS's own, so the
// sides never mix and the family always wins over quarry's palette.
const (
darkSystem = `@media (prefers-color-scheme: dark){`
notLight = `:not([data-theme="light"])`
darkStamp = `[data-theme="dark"]`
)
// FamilySiteCSS is a site family's sheet: the light side on the root,
// the dark side on the root in dark mode.
func FamilySiteCSS(f Family) string {
var b strings.Builder
b.WriteString(":root:root{" + siteBody(f.Light) + "}\n")
b.WriteString(darkSystem + ":root:root" + notLight + "{" + siteBody(f.Dark) + "}}\n")
b.WriteString(":root:root" + darkStamp + "{" + siteBody(f.Dark) + "}\n")
return b.String()
}
// FamilyCodeCSS is a code family's sheet, on the elements that draw code.
func FamilyCodeCSS(f Family) string {
var b strings.Builder
b.WriteString(codeSelector + "{" + codeBody(f.Light) + "}\n")
b.WriteString(darkSystem + ":root" + notLight + " :is(" + codeSelector + "){" + codeBody(f.Dark) + "}}\n")
b.WriteString(":root" + darkStamp + " :is(" + codeSelector + "){" + codeBody(f.Dark) + "}\n")
return b.String()
}
// FamilySiteURL and FamilyCodeURL are a family's sheets, hashed like a
// single theme's.
func FamilySiteURL(f Family) string { return assetURL("site", FamilySiteCSS(f), f.ID) }
func FamilyCodeURL(f Family) string { return assetURL("code", FamilyCodeCSS(f), f.ID) }