go-bananas/internal/seed/consolidated.go
Daniel Noulet 951dd8d26b
Some checks failed
ci / go (push) Has been cancelled
ci / admin-ui (push) Has been cancelled
ci / runtime-sdk (push) Has been cancelled
ci / helm (push) Has been cancelled
ci / openapi (push) Has been cancelled
ci / container (push) Has been cancelled
ci / release-config (push) Has been cancelled
ci / shared styling (push) Has been cancelled
ci / security (push) Has been cancelled
ci / all green (push) Has been cancelled
initial commit
2026-10-07 15:11:35 +02:00

605 lines
25 KiB
Go

package seed
import (
"github.com/urpi/go-bananas/internal/domain"
"github.com/urpi/go-bananas/internal/tokenengine"
)
// Palettes consolidated from the sibling applications.
//
// Each of these is a real palette lifted from an application that is already running
// with it, rather than a generated ramp. That is the point of consolidating them: the
// applications can adopt the shared contract and keep the look their users already
// have, instead of being handed a new design.
//
// The colours are the roles those applications actually use, mapped onto the contract's
// roles. A source application had only one appearance for some roles and a different
// one for others, so both schemes are filled in here and the contract supplies the
// second axis.
//
// Provenance is recorded in the description. A palette nobody can trace back to the
// screen it came from becomes an orphan within a year.
// Colours and values repeated across the palettes below.
//
// A consolidated palette set is mostly repetition by nature — every dark scheme has
// the same white for reversed text, every go-loose theme has the same card radius —
// so these are named rather than repeated inline. That makes a change to "what white
// means here" a one-line edit instead of a search.
const (
white = "#ffffff"
paperLight = "#fffaf0"
cardRadius = "16px"
// tightRadius is the corner radius of the two palettes whose source application
// uses tighter geometry than go-loose's 16px cards.
tightRadius = "10px"
// interStack is the sans stack four of the six applications ask for by name. None
// of them load the file, so in practice this resolves to the system UI font; it
// is kept because it is what the applications declare, and a future font load
// should not have to touch six stylesheets.
interStack = "'Inter', ui-sans-serif, system-ui, sans-serif"
)
// scheme is one appearance's worth of literal colours.
type scheme struct {
Background string
Surface string
SurfaceRaised string
SurfaceSunken string
Border string
BorderStrong string
Text string
TextMuted string
TextSubtle string
TextInverted string
Primary string
PrimaryHover string
PrimaryActive string
OnPrimary string
Secondary string
OnSecondary string
Success string
Warning string
Danger string
Info string
FocusRing string
Overlay string
Ramp tokenengine.RampOptions
FontSans string
RadiusCard string
}
// consolidate assembles a seeded theme from two literal schemes.
//
// Ramps are still generated, for the palette and status families the contract exposes
// as scales (`--bn-palette-*`). The semantic roles are literal, because those are the
// values the source applications actually render and inventing a ramp here would
// change the appearance the consolidation is meant to preserve.
func consolidate(
slug, name, description string,
defaultAppearance domain.Appearance,
tags []string,
light, dark scheme,
) SeededTheme {
palettes := map[string]any{
roleBrand: tokenengine.GenerateRamp(light.Ramp),
roleNeutral: tokenengine.GenerateRamp(neutralRamp(light.Ramp)),
roleSuccess: tokenengine.GenerateRamp(tokenengine.RampOptions{Hue: 142, Saturation: 55, Lightness: 42, HueSpread: 10}),
roleWarning: tokenengine.GenerateRamp(tokenengine.RampOptions{Hue: 38, Saturation: 88, Lightness: 50, HueSpread: 8}),
roleDanger: tokenengine.GenerateRamp(tokenengine.RampOptions{Hue: 358, Saturation: 68, Lightness: 50, HueSpread: 8}),
}
fontSans := light.FontSans
if fontSans == "" {
fontSans = interStack
}
typography := typeScale(fontSans, 0.875, 1.25, 1.625)
typography["family"] = map[string]any{
"sans": family(fontSans),
"mono": family("ui-monospace, SFMono-Regular, Menlo, monospace"),
"display": family(fontSans),
}
shadowSet := map[string]any{
"none": shadow("none"),
"sm": shadow("0 1px 2px rgba(0,0,0,0.06)"),
"md": shadow("0 6px 20px rgba(0,0,0,0.08)"),
"lg": shadow("0 18px 48px rgba(0,0,0,0.14)"),
"xl": shadow("0 28px 80px rgba(0,0,0,0.20)"),
"border": shadow("inset 0 0 0 1px rgba(0,0,0,0.08)"),
}
doc := assemble(palettes,
literalSemantics(light),
literalSemantics(dark),
typography,
shadowSet,
)
// The transcribed corner radius becomes a real token rather than a note in a
// struct field. A consumer writing `border-radius: var(--radius-md)` then gets
// the source application's actual radius, which is the whole reason for copying
// its palette rather than approximating one.
applyCardRadius(doc, light.RadiusCard)
return SeededTheme{
Slug: slug,
Name: name,
Description: description,
Appearance: defaultAppearance,
Tags: tags,
Tokens: doc,
}
}
// applyCardRadius sets the radius scale so that its `md` step is the source
// application's card radius, with the surrounding steps kept in proportion.
//
// A palette whose corners are visibly rounder or squarer than the application it came
// from does not read as that application, and the radius is one of the first things a
// user notices.
func applyCardRadius(doc domain.TokenDoc, card string) {
if card == "" {
return
}
group, ok := doc["radius"].(map[string]any)
if !ok {
group = map[string]any{}
doc["radius"] = group
}
// Proportions taken from the scale the other built-in themes use, so a
// consolidated palette and an original one are the same shape.
for step, factor := range map[string]string{
"sm": "0.5",
"md": "1",
"lg": "1.5",
"xl": "2",
} {
if step == "md" {
group[step] = dim(card)
continue
}
group[step] = dim("calc(" + card + " * " + factor + ")")
}
}
// neutralRamp derives a low-saturation neutral from a brand ramp, so a palette and its
// greys come from one description rather than two guesses.
func neutralRamp(brand tokenengine.RampOptions) tokenengine.RampOptions {
return tokenengine.RampOptions{
Hue: brand.Hue,
Saturation: minFloat(brand.Saturation*0.18, 22),
Lightness: minFloat(brand.Lightness*0.72, 52),
HueSpread: 4,
}
}
// minFloat returns the smaller of two values.
func minFloat(a, b float64) float64 {
if a < b {
return a
}
return b
}
// literalSemantics turns a scheme into the contract's semantic colour roles.
//
// Every role is filled explicitly. A missing role would fall back to the engine's
// light-mode default, which for a dark-first palette is a pale colour in the middle of
// a dark interface — a bug that is easy to miss and impossible to debug from a
// screenshot.
func literalSemantics(s scheme) map[string]any {
out := map[string]any{
"background": color(s.Background),
"surface": color(s.Surface),
"surface-raised": color(s.SurfaceRaised),
"surface-sunken": color(s.SurfaceSunken),
"border": color(s.Border),
"border-strong": color(s.BorderStrong),
"text": color(s.Text),
"text-muted": color(s.TextMuted),
"text-subtle": color(s.TextSubtle),
"text-inverted": color(s.TextInverted),
"primary": color(s.Primary),
"primary-hover": color(s.PrimaryHover),
"primary-active": color(s.PrimaryActive),
"on-primary": color(s.OnPrimary),
"secondary": color(s.Secondary),
"on-secondary": color(s.OnSecondary),
"success": color(s.Success),
"warning": color(s.Warning),
"danger": color(s.Danger),
"info": color(s.Info),
"focus-ring": color(s.FocusRing),
"overlay": color(s.Overlay),
}
return out
}
// The four palettes below come from four different applications. The colours are
// theirs, transcribed; where an application had only one appearance, the other is
// derived by inverting the surface and text roles and keeping the accent family, which
// is what its own stylesheet effectively does.
// tokyoNight is go-loose's default palette, the one its switcher opens on.
//
// The slug is `tokyo`, not `tokyo-night`, because that is the value go-loose already
// writes into its data-theme attribute. Matching it is the whole point: adopting the
// contract must not require the adopting application to change a single stored value.
func tokyoNight() SeededTheme {
blue := tokenengine.RampOptions{Hue: 224, Saturation: 60, Lightness: 58, HueSpread: 10}
return consolidate(
"tokyo", "Tokyo Night",
"go-loose's default palette. Low-contrast blue-grey with a bright accent; "+
"chosen for long sessions against a terminal.",
domain.AppearanceDark,
[]string{appearanceDark, tagDefault, "blue", "go-loose"},
scheme{
Background: "#e6e7ed", Surface: white, SurfaceRaised: white,
SurfaceSunken: "#dfe2ed", Border: "#b4b5c3", BorderStrong: "#8b8da1",
Text: "#343b58", TextMuted: "#34548a", TextSubtle: "#4a5a80",
TextInverted: white,
Primary: "#0f4b6e", PrimaryHover: "#0c3d5a", PrimaryActive: "#093249",
OnPrimary: white,
Secondary: "#5a4a78", OnSecondary: white,
Success: "#485e30", Warning: "#8a6a1f", Danger: "#8c4351", Info: "#2f6b8f",
FocusRing: "#0f4b6e", Overlay: "rgba(52, 59, 88, 0.32)",
Ramp: blue, FontSans: interStack,
RadiusCard: cardRadius,
},
scheme{
Background: "#1a1b26", Surface: "#20222f", SurfaceRaised: "#282a37",
SurfaceSunken: "#16161e", Border: "#292e42", BorderStrong: "#3b4261",
Text: "#c0caf5", TextMuted: "#9aa5ce", TextSubtle: "#7aa2f7",
TextInverted: "#1a1b26",
Primary: "#7aa2f7", PrimaryHover: "#93b1ff", PrimaryActive: "#aac7ff",
OnPrimary: "#1a1b26",
Secondary: "#bb9af7", OnSecondary: "#1a1b26",
Success: "#9ece6a", Warning: "#e0af68", Danger: "#f7768e", Info: "#7dcfff",
FocusRing: "#7aa2f7", Overlay: "rgba(0, 0, 0, 0.55)",
Ramp: blue, FontSans: interStack,
RadiusCard: cardRadius,
},
)
}
// catppuccin is go-loose's second palette, a soft pastel set.
func catppuccin() SeededTheme {
lavender := tokenengine.RampOptions{Hue: 232, Saturation: 62, Lightness: 62, HueSpread: 12}
return consolidate(
"catppuccin", "Catppuccin",
"go-loose's pastel palette. Softer contrast than Tokyo Night, which suits a "+
"content-heavy screen better than a dense one.",
domain.AppearanceDark,
[]string{appearanceDark, "pastel", "lavender", "go-loose"},
scheme{
Background: "#eff1f5", Surface: white, SurfaceRaised: white,
SurfaceSunken: "#e6e9ef", Border: "#bcc0cc", BorderStrong: "#9499b0",
Text: "#4c4f69", TextMuted: "#5c61a8", TextSubtle: "#6c6f85",
TextInverted: white,
Primary: "#1e66f5", PrimaryHover: "#1a56db", PrimaryActive: "#1647b8",
OnPrimary: white,
Secondary: "#8839ef", OnSecondary: white,
Success: "#40a02b", Warning: "#df8e1d", Danger: "#d20f39", Info: "#179299",
FocusRing: "#1e66f5", Overlay: "rgba(76, 79, 105, 0.28)",
Ramp: lavender, FontSans: interStack,
RadiusCard: cardRadius,
},
scheme{
Background: "#1e1e2e", Surface: "#181825", SurfaceRaised: "#313244",
SurfaceSunken: "#11111b", Border: "#313244", BorderStrong: "#45475a",
Text: "#cdd6f4", TextMuted: "#a6adc8", TextSubtle: "#9399b2",
TextInverted: "#1e1e2e",
Primary: "#89b4fa", PrimaryHover: "#a3c3f5", PrimaryActive: "#b8d0f6",
OnPrimary: "#1e1e2e",
Secondary: "#cba6f7", OnSecondary: "#1e1e2e",
Success: "#a6e3a1", Warning: "#f9e2af", Danger: "#f38ba8", Info: "#94e2d5",
FocusRing: "#89b4fa", Overlay: "rgba(0, 0, 0, 0.5)",
Ramp: lavender, FontSans: interStack,
RadiusCard: cardRadius,
},
)
}
// nordicMoss is go-tell's green palette, slugged `nordic` to match its own value. Its source theme is dark-only; the light
// scheme is the inversion with the green family kept.
func nordicMoss() SeededTheme {
moss := tokenengine.RampOptions{Hue: 132, Saturation: 32, Lightness: 44, HueSpread: 12}
return consolidate(
"nordic", "Nordic Moss",
"go-tell's green palette. Desaturated and low-contrast; the light scheme is "+
"derived, because the source theme is dark-only.",
domain.AppearanceDark,
[]string{appearanceDark, "green", "calm", "go-tell"},
scheme{
Background: "#e9f0e8", Surface: white, SurfaceRaised: "#f4f8f3",
SurfaceSunken: "#dfe9de", Border: "#c2d6c4", BorderStrong: "#94a899",
Text: "#16241c", TextMuted: "#3b5645", TextSubtle: "#588157",
TextInverted: white,
Primary: "#2e4435", PrimaryHover: "#25392d", PrimaryActive: "#1a2820",
OnPrimary: "#e9f0e8",
Secondary: "#6f894e", OnSecondary: white,
Success: "#3f6b46", Warning: "#8a7434", Danger: "#a1453f", Info: "#3a6a70",
FocusRing: "#2e4435", Overlay: "rgba(22, 36, 28, 0.28)",
Ramp: moss, FontSans: interStack,
RadiusCard: tightRadius,
},
scheme{
Background: "#121c16", Surface: "#1e3025", SurfaceRaised: "#25392d",
SurfaceSunken: "#0e1712", Border: "#2e4435", BorderStrong: "#3b5645",
Text: "#e9f0e8", TextMuted: "#a3c9a8", TextSubtle: "#8fa193",
TextInverted: "#121c16",
Primary: "#a3c9a8", PrimaryHover: "#b8d8bb", PrimaryActive: "#c9e2cb",
OnPrimary: "#121c16",
Secondary: "#8fbc8f", OnSecondary: "#121c16",
Success: "#8fbc8f", Warning: "#e2c044", Danger: "#e07a72", Info: "#7fb4ca",
FocusRing: "#a3c9a8", Overlay: "rgba(0, 0, 0, 0.52)",
Ramp: moss, FontSans: interStack,
RadiusCard: tightRadius,
},
)
}
// deepSpace is go-tell's `obsidian` palette, and the one that most needs a light
// scheme: its source is a near-black space theme, so an untouched inversion would be
// unusable.
func deepSpace() SeededTheme {
sky := tokenengine.RampOptions{Hue: 199, Saturation: 88, Lightness: 58, HueSpread: 12}
return consolidate(
"obsidian", "Deep Space Obsidian",
"go-tell's near-black blue palette. The light scheme is authored rather than "+
"inverted, because the source is a dark-first space theme.",
domain.AppearanceDark,
[]string{appearanceDark, "blue", "space", "go-tell"},
scheme{
Background: "#f1f5f9", Surface: white, SurfaceRaised: "#f8fafc",
SurfaceSunken: "#e8eef5", Border: "#d3dce6", BorderStrong: "#a8b6c6",
Text: "#0f172a", TextMuted: "#475569", TextSubtle: "#64748b",
TextInverted: "#f8fafc",
Primary: "#0284c7", PrimaryHover: "#0369a1", PrimaryActive: "#075985",
OnPrimary: white,
Secondary: "#1e293b", OnSecondary: "#f8fafc",
Success: "#15803d", Warning: "#b45309", Danger: "#b91c1c", Info: "#0e7490",
FocusRing: "#0284c7", Overlay: "rgba(15, 23, 42, 0.3)",
Ramp: sky, FontSans: interStack,
RadiusCard: tightRadius,
},
scheme{
Background: "#060709", Surface: "#0f172a", SurfaceRaised: "#151d2a",
SurfaceSunken: "#020617", Border: "#1e293b", BorderStrong: "#334155",
Text: "#e2e8f0", TextMuted: "#94a3b8", TextSubtle: "#64748b",
TextInverted: "#060709",
Primary: "#38bdf8", PrimaryHover: "#7dd3fc", PrimaryActive: "#bae6fd",
OnPrimary: "#060709",
Secondary: "#60a5fa", OnSecondary: "#060709",
Success: "#4ade80", Warning: "#fbbf24", Danger: "#f87171", Info: "#22d3ee",
FocusRing: "#38bdf8", Overlay: "rgba(0, 0, 0, 0.6)",
Ramp: sky, FontSans: interStack,
RadiusCard: tightRadius,
},
)
}
// urpiStandard is go-guess's palette, which is the only light-first application of the
// six and therefore the one whose light scheme is the authoritative one.
func urpiStandard() SeededTheme {
cyan := tokenengine.RampOptions{Hue: 190, Saturation: 78, Lightness: 40, HueSpread: 10}
return consolidate(
"urpi-standard", "urpi Standard",
"go-guess's palette: a cyan accent and lime highlight on a cool grey. The "+
"light scheme is authoritative, being the application's only one.",
domain.AppearanceLight,
[]string{"light", tagDefault, "cyan", "go-guess"},
scheme{
Background: "#f3f7f8", Surface: white, SurfaceRaised: white,
SurfaceSunken: "#edf2f3", Border: "#dce5e8", BorderStrong: "#c2d2d6",
Text: "#142b3a", TextMuted: "#526872", TextSubtle: "#78909c",
TextInverted: white,
Primary: "#007f98", PrimaryHover: "#006b81", PrimaryActive: "#00586a",
OnPrimary: white,
Secondary: "#d9ef4d", OnSecondary: "#0b573c",
Success: "#13704d", Warning: "#925a09", Danger: "#934141", Info: "#087187",
FocusRing: "#007f98", Overlay: "rgba(14, 42, 56, 0.34)",
Ramp: cyan, FontSans: "'DM Sans', Inter, ui-sans-serif, system-ui, sans-serif",
RadiusCard: cardRadius,
},
scheme{
Background: "#081c2c", Surface: "#0e2a3f", SurfaceRaised: "#123449",
SurfaceSunken: "#04131f", Border: "#1d4258", BorderStrong: "#2c5f7a",
Text: "#e3ebed", TextMuted: "#a9bec8", TextSubtle: "#82949d",
TextInverted: "#081c2c",
Primary: "#00a9c8", PrimaryHover: "#33bcd6", PrimaryActive: "#66cde0",
OnPrimary: "#04131f",
Secondary: "#d9ef4d", OnSecondary: "#0b573c",
Success: "#16a274", Warning: "#eda83c", Danger: "#e6988c", Info: "#43c99c",
FocusRing: "#00a9c8", Overlay: "rgba(0, 0, 0, 0.55)",
Ramp: cyan, FontSans: "'DM Sans', Inter, ui-sans-serif, system-ui, sans-serif",
RadiusCard: cardRadius,
},
)
}
// tomorrow is go-loose's third palette: a warm grey with a muted teal.
func tomorrow() SeededTheme {
warm := tokenengine.RampOptions{Hue: 40, Saturation: 8, Lightness: 42, HueSpread: 4}
teal := tokenengine.RampOptions{Hue: 178, Saturation: 34, Lightness: 40, HueSpread: 8}
return consolidate(
"tomorrow", "Tomorrow",
"go-loose's warm grey palette. The least saturated of the four, and the "+
"easiest to read for long periods.",
domain.AppearanceDark,
[]string{appearanceDark, "warm", "muted", "go-loose"},
scheme{
Background: "#f5f5f5", Surface: white, SurfaceRaised: white,
SurfaceSunken: "#e8e8e8", Border: "#c9c9c9", BorderStrong: "#a3a3a3",
Text: "#4d4d4c", TextMuted: "#6e6e6c", TextSubtle: "#8a8a88",
TextInverted: white,
Primary: "#3e999f", PrimaryHover: "#348389", PrimaryActive: "#2b6d72",
OnPrimary: white,
Secondary: "#8959a8", OnSecondary: white,
Success: "#718c00", Warning: "#b58900", Danger: "#c82829", Info: "#3e999f",
FocusRing: "#3e999f", Overlay: "rgba(77, 77, 76, 0.3)",
Ramp: teal, FontSans: interStack,
RadiusCard: cardRadius,
},
scheme{
Background: "#1d1f21", Surface: "#282a2e", SurfaceRaised: "#373b41",
SurfaceSunken: "#1a1c1e", Border: "#4d5057", BorderStrong: "#6b6f77",
Text: "#c5c8c6", TextMuted: "#a0a4a2", TextSubtle: "#81a2be",
TextInverted: "#1d1f21",
Primary: "#8abeb7", PrimaryHover: "#a5d1cb", PrimaryActive: "#bcdfd9",
OnPrimary: "#1d1f21",
Secondary: "#b294bb", OnSecondary: "#1d1f21",
Success: "#b5bd68", Warning: "#e0af68", Danger: "#cc6666", Info: "#81a2be",
FocusRing: "#8abeb7", Overlay: "rgba(0, 0, 0, 0.52)",
Ramp: warm, FontSans: interStack,
RadiusCard: cardRadius,
},
)
}
// kanagawa is go-loose's fourth palette: a warm paper light scheme and a stone dark one.
func kanagawa() SeededTheme {
wave := tokenengine.RampOptions{Hue: 28, Saturation: 26, Lightness: 56, HueSpread: 10}
stone := tokenengine.RampOptions{Hue: 20, Saturation: 10, Lightness: 46, HueSpread: 6}
return consolidate(
"kanagawa", "Kanagawa",
"go-loose's paper palette. A warm off-white light scheme, which is the "+
"warmest surface in the set.",
domain.AppearanceLight,
[]string{"light", "warm", "paper", "go-loose"},
scheme{
Background: "#f2ecde", Surface: paperLight, SurfaceRaised: paperLight,
SurfaceSunken: "#e7dba9", Border: "#c8c093", BorderStrong: "#a89b6e",
Text: "#545464", TextMuted: "#5e5e70", TextSubtle: "#78788c",
TextInverted: paperLight,
Primary: "#4d7f8c", PrimaryHover: "#406d78", PrimaryActive: "#345c65",
OnPrimary: paperLight,
Secondary: "#624c83", OnSecondary: paperLight,
Success: "#6f894e", Warning: "#9a7d2e", Danger: "#c34043", Info: "#4d7f8c",
FocusRing: "#4d7f8c", Overlay: "rgba(84, 84, 100, 0.3)",
Ramp: stone, FontSans: interStack,
RadiusCard: cardRadius,
},
scheme{
Background: "#1f1f28", Surface: "#2a2a37", SurfaceRaised: "#363646",
SurfaceSunken: "#191921", Border: "#54546d", BorderStrong: "#6b6b85",
Text: "#dcd7ba", TextMuted: "#b4b098", TextSubtle: "#a8a28c",
TextInverted: "#1f1f28",
Primary: "#7fb4ca", PrimaryHover: "#98c4d6", PrimaryActive: "#b0d2e0",
OnPrimary: "#1f1f28",
Secondary: "#957fb8", OnSecondary: "#1f1f28",
Success: "#98bb6c", Warning: "#e0c07a", Danger: "#e46876", Info: "#7fb4ca",
FocusRing: "#7fb4ca", Overlay: "rgba(0, 0, 0, 0.5)",
Ramp: wave, FontSans: interStack,
RadiusCard: cardRadius,
},
)
}
// cyberpunk is go-tell's default palette: a near-black violet field with a hot pink
// and a bright cyan.
//
// It is the one palette in the set whose identity is two accents rather than one, and
// that is why it is expressed with two status roles doing unusual work: pink carries
// the brand, cyan carries interaction. Keeping pink on `--bn-accent` would lose the
// pink-versus-cyan distinction that the whole theme is built on, so the secondary
// carries cyan instead and the pair reads in the order the design intends.
func cyberpunk() SeededTheme {
violet := tokenengine.RampOptions{Hue: 288, Saturation: 72, Lightness: 44, HueSpread: 20}
return consolidate(
"cyberpunk", "Cyberpunk",
"go-tell's default palette. A hot-pink and cyan pair on near-black violet, "+
"which is the one palette here built on two accents rather than one.",
domain.AppearanceDark,
[]string{appearanceDark, tagDefault, "pink", "cyan", "go-tell"},
scheme{
Background: "#f2e8ee", Surface: "#ffffff", SurfaceRaised: "#fdf5f9",
SurfaceSunken: "#e9dde5", Border: "#c9b3c0", BorderStrong: "#a68b9c",
Text: "#2a1226", TextMuted: "#5c4457", TextSubtle: "#7f6577",
TextInverted: "#f5e0e8",
Primary: "#c2185b", PrimaryHover: "#a5144d", PrimaryActive: "#8c1042",
OnPrimary: white,
// Cyan as the secondary: see the note above.
Secondary: "#00838f", OnSecondary: white,
Success: "#2e7d32", Warning: "#c77800", Danger: "#c62828", Info: "#0277bd",
FocusRing: "#c2185b", Overlay: "rgba(42, 18, 38, 0.5)",
Ramp: violet, FontSans: interStack, RadiusCard: tightRadius,
},
scheme{
Background: "#120826", Surface: "#1b1035", SurfaceRaised: "#22153d",
SurfaceSunken: "#0c0519", Border: "#3d2366", BorderStrong: "#4a2d7a",
Text: "#f5e0e8", TextMuted: "#a6adc8", TextSubtle: "#7f849c",
TextInverted: "#120826",
// The source has no separate hover or active step; these are the two
// lighter pinks it uses on hover and on the cursor.
Primary: "#ff2a85", PrimaryHover: "#ff4d9a", PrimaryActive: "#ff6b9d",
OnPrimary: "#120826",
// Cyan as the secondary, so a consumer's --bn-secondary reaches the
// interaction colour the theme is known for.
Secondary: "#00f0ff", OnSecondary: "#120826",
Success: "#00e5a0", Warning: "#ffd166", Danger: "#ef233c", Info: "#00f0ff",
FocusRing: "#00f0ff", Overlay: "rgba(0, 0, 0, 0.62)",
Ramp: violet, FontSans: interStack, RadiusCard: tightRadius,
},
)
}
// terminalRetro is go-tell's green-on-black palette.
//
// Two of its traits are not colours and cannot be expressed as tokens: it forces a
// monospace face on every element, and it adds a text glow. Both are recorded in the
// description and left in the application's own stylesheet, because a token is a
// value and these are rules.
func terminalRetro() SeededTheme {
green := tokenengine.RampOptions{Hue: 138, Saturation: 100, Lightness: 45, HueSpread: 16}
return consolidate(
"terminal", "Terminal Retro",
"go-tell's green-on-black palette. The source theme also forces a monospace "+
"face on every element and adds a text glow; those are rules rather than "+
"values, so they stay in the application's stylesheet.",
domain.AppearanceDark,
[]string{"dark", "terminal", "mono", "green", "go-tell"},
scheme{
Background: "#e8f5e9", Surface: "#ffffff", SurfaceRaised: "#f2fbf3",
SurfaceSunken: "#dcecdc", Border: "#a8d8ad", BorderStrong: "#7fbf86",
Text: "#041404", TextMuted: "#1e5c22", TextSubtle: "#3d7a42",
TextInverted: "#041404",
Primary: "#007a12", PrimaryHover: "#006410", PrimaryActive: "#004e0d",
OnPrimary: "#dcfce0",
Secondary: "#7a5c00", OnSecondary: "#fff8dc",
Success: "#00a32b", Warning: "#8a6d00", Danger: "#b30000", Info: "#007a12",
FocusRing: "#00a32b", Overlay: "rgba(4, 20, 4, 0.55)",
Ramp: green,
FontSans: "'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace",
RadiusCard: "4px",
},
scheme{
Background: "#040804", Surface: "#081608", SurfaceRaised: "#0e1a0e",
SurfaceSunken: "#020402", Border: "#00aa44", BorderStrong: "#00ff66",
Text: "#00ff66", TextMuted: "#33ff33", TextSubtle: "#00aa44",
TextInverted: "#040804",
// The source's brightest green is its interactive colour, and its mid green
// is the hover step.
Primary: "#00ff66", PrimaryHover: "#33ff33", PrimaryActive: "#00dd55",
OnPrimary: "#040804",
// The Mac traffic-light yellow the source uses in its window chrome.
Secondary: "#ffcc00", OnSecondary: "#261e00",
Success: "#00ff66", Warning: "#ffcc00", Danger: "#ff3333", Info: "#00aa44",
FocusRing: "#00ff66", Overlay: "rgba(0, 0, 0, 0.7)",
Ramp: green,
FontSans: "'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace",
RadiusCard: "4px",
},
)
}