Skip to content
quarry Artifact [f50625a517]
Login

Artifact [f50625a517]

Artifact f50625a517278411e235510ff88c3f03c1e6f0df783403b4ddae74b7688bfd97:


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) }