on 2026-10-29.
// help-circle's published name is circle-question-mark: the address
// lucide.dev/icons/circle-help now lands on that page.
var lucideNames = map[string]string{
"kebab": "ellipsis-vertical",
"alert-triangle": "triangle-alert",
"check-circle": "circle-check",
"help-circle": "circle-question-mark",
"x-circle": "circle-x",
}
// outboundLinks is the whole list of addresses the tree may point at
// that are not in it, each with the reason it is there. The table is
// exact strings, not prefixes, and every entry has to be linked by some
// page; TestEveryOutboundLinkIsAllowedAndUsed holds both halves, so it
// cannot become a licence for the next link somebody feels like adding.
var outboundLinks = map[string]string{
lucideHome: "where the glyphs come from: the set the icons are copied from, under the ISC licence, and the only place a reader can see the rest of it",
}
// lucideName is what lucide.dev calls the icon Fiesta calls slug.
func lucideName(slug string) string {
if name, ok := lucideNames[slug]; ok {
return name
}
return slug
}
// iconView is one chip: its anchor, its slug, the glyph, and its
// provenance, an address rather than a sentence (lucide.dev/icons/…)
// so it needs no translation and a reader can type it.
type iconView struct {
ID, Slug, Provenance string
Markup template.HTML
}
// iconsView is the page's data. Total and Renamed are counted from
// the set rather than written into the sentence, so the sentence
// cannot be wrong about the set it describes. Lucide is the line that
// links the set, with the link in it.
type iconsView struct {
List []iconView
Total, Renamed string
Lucide template.HTML
}
// buildIcons reads the set for one locale's page.
func (s *site) buildIcons(locale string) iconsView {
out := iconsView{Total: fmt.Sprint(len(s.icons))}
renamed := 0
for _, ic := range s.icons {
name := lucideName(ic.Slug)
if name != ic.Slug {
renamed++
}
out.List = append(out.List, iconView{
ID: anchorID("icon", ic.Slug),
Slug: ic.Slug,
Provenance: "lucide.dev/icons/" + name,
Markup: ic.Markup,
})
}
out.Renamed = fmt.Sprint(renamed)
link := `lucide.dev (` +
template.HTMLEscapeString(s.say(locale, "gallery.newtab")) + `)`
out.Lucide = template.HTML(strings.Replace(template.HTMLEscapeString(s.say(locale, "gallery.icons.lucide")), "{link}", link, 1))
return out
}
// iconsBody is every slug, drawn at the size tokens.css gives .icon,
// each with its file and its lucide.dev address; then the names that
// are Fiesta's own, with the count; then where the glyphs come from,
// with the one link that leaves the tree.
const iconsBody = `{{define "ds-body-icons"}}{{say "gallery.icons.lead" "total" .Icons.Total}}
{{range .Icons.List}}
- {{.Markup}}{{.Slug}}icons/{{.Slug}}.svg{{.Provenance}}
{{end}}
{{say "gallery.icons.names.heading"}}
{{say "gallery.icons.names.body" "renamed" .Icons.Renamed "total" .Icons.Total}}
{{say "gallery.icons.kebab"}}
{{say "gallery.icons.source"}}
{{say "gallery.icons.source.body"}}
{{.Icons.Lucide}}
{{end}}`
// iconsBody renders the Icons page's body for p.
func (s *site) iconsBody(p page) (template.HTML, error) {
return s.body(p.Locale, "ds-body-icons", struct {
page
Icons iconsView
}{p, s.buildIcons(p.Locale)})
}
// iconMarkup is one of Fiesta's icons, inline, for the gallery's own
// furniture; the icon template func.
func (s *site) iconMarkup(slug string) template.HTML {
for _, ic := range s.icons {
if ic.Slug == slug {
return ic.Markup
}
}
return ""
}
func init() { registerTemplate(iconsBody) }