feat: enhance learn-number with dynamic SVG icons, multi-page support, and modular UI
Build and Push Docker Image / build (push) Successful in 2m26s

This commit is contained in:
2026-02-25 23:29:35 -08:00
parent 0be94026c5
commit 7cd611140e
12 changed files with 4330 additions and 279 deletions
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 404 KiB

+69 -33
View File
@@ -1,41 +1,77 @@
package data
import (
"encoding/xml"
"fmt"
"os"
"strings"
)
type Icon struct {
Name string
Paths []string
}
// 升级版:更卡通、更饱满的简笔画
var CountingIcons = []Icon{
{Name: "Bear", Paths: []string{
"M 512 800 C 300 800 200 700 200 500 C 200 300 350 200 512 200 C 674 200 824 300 824 500 C 824 700 724 800 512 800 Z", // Body
"M 300 300 m -50 0 a 50 50 0 1 0 100 0 a 50 50 0 1 0 -100 0", // Ear L
"M 724 300 m -50 0 a 50 50 0 1 0 100 0 a 50 50 0 1 0 -100 0", // Ear R
"M 400 450 m -20 0 a 20 20 0 1 0 40 0 a 20 20 0 1 0 -40 0", // Eye L
"M 624 450 m -20 0 a 20 20 0 1 0 40 0 a 20 20 0 1 0 -40 0", // Eye R
"M 512 550 Q 512 650 400 650 M 512 550 Q 512 650 624 650", // Mouth
}},
{Name: "Cat", Paths: []string{
"M 200 800 L 300 400 L 400 200 L 512 350 L 624 200 L 724 400 L 824 800 Z", // Head
"M 400 550 m -15 0 a 15 15 0 1 0 30 0 a 15 15 0 1 0 -30 0", // Eye L
"M 624 550 m -15 0 a 15 15 0 1 0 30 0 a 15 15 0 1 0 -30 0", // Eye R
"M 512 650 L 450 700 M 512 650 L 574 700", // Nose
}},
{Name: "Car", Paths: []string{
"M 100 700 L 100 500 Q 100 400 300 400 L 700 400 Q 900 400 900 500 L 900 700 Z", // Body
"M 250 700 m -60 0 a 60 60 0 1 0 120 0 a 60 60 0 1 0 -120 0", // Wheel L
"M 750 700 m -60 0 a 60 60 0 1 0 120 0 a 60 60 0 1 0 -120 0", // Wheel R
"M 300 400 L 400 250 L 624 250 L 724 400", // Roof
}},
{Name: "Bird", Paths: []string{
"M 512 512 m -300 0 a 300 300 0 1 0 600 0 a 300 300 0 1 0 -600 0", // Body
"M 812 512 L 950 450 L 812 400 Z", // Beak
"M 400 400 m -20 0 a 20 20 0 1 0 40 0 a 20 20 0 1 0 -40 0", // Eye
"M 212 512 Q 100 400 212 300", // Wing
}},
{Name: "Rocket", Paths: []string{
"M 512 100 Q 700 400 700 800 L 324 800 Q 324 400 512 100 Z", // Body
"M 512 400 m -50 0 a 50 50 0 1 0 100 0 a 50 50 0 1 0 -100 0", // Window
"M 324 800 L 200 950 L 324 900 M 700 800 L 824 950 L 700 900", // Fins
}},
var IconCategories = map[string][]Icon{
"shapes": {
{Name: "Circle", Paths: []string{"M 512 112 C 291 112 112 291 112 512 C 112 733 291 912 512 912 C 733 912 912 733 912 512 C 912 291 733 112 512 112 Z"}},
{Name: "Square", Paths: []string{"M 150 150 L 874 150 L 874 874 L 150 874 Z"}},
{Name: "Rectangle", Paths: []string{"M 100 300 L 924 300 L 924 724 L 100 724 Z"}},
{Name: "Triangle", Paths: []string{"M 512 150 L 900 850 L 124 850 Z"}},
{Name: "Star", Paths: []string{"M 512 100 L 612 400 L 924 400 L 674 600 L 774 900 L 512 700 L 250 900 L 350 600 L 100 400 L 412 400 Z"}},
{Name: "Heart", Paths: []string{"M 512 900 C 200 700 100 500 100 300 C 100 100 400 100 512 250 C 624 100 924 100 924 300 C 924 500 824 700 512 900 Z"}},
{Name: "Diamond", Paths: []string{"M 512 100 L 900 512 L 512 924 L 124 512 Z"}},
{Name: "Oval", Paths: []string{"M 512 350 C 200 350 100 420 100 512 C 100 604 200 674 512 674 C 824 674 924 604 924 512 C 924 420 824 350 512 350 Z"}},
{Name: "Trapezoid", Paths: []string{"M 300 200 L 724 200 L 924 800 L 100 800 Z"}},
{Name: "Hexagon", Paths: []string{"M 512 100 L 858 300 L 858 724 L 512 924 L 166 724 L 166 300 Z"}},
},
"fruits": {},
}
type SVG struct {
Groups []G `xml:"g"`
}
type G struct {
ID string `xml:"id,attr"`
Groups []G `xml:"g"`
Paths []Path `xml:"path"`
}
type Path struct {
D string `xml:"d,attr"`
}
func LoadIcons(filePath string) error {
data, err := os.ReadFile(filePath)
if err != nil { return err }
var svg SVG
if err := xml.Unmarshal(data, &svg); err != nil { return err }
var objectsG *G
for i := range svg.Groups {
if svg.Groups[i].ID == "objects" { objectsG = &svg.Groups[i]; break }
}
if objectsG == nil { return nil }
var fruitIcons []Icon
for i, g := range objectsG.Groups {
paths := collectPaths(g)
if len(paths) > 0 {
fruitIcons = append(fruitIcons, Icon{
Name: fmt.Sprintf("Item %d", i+1),
Paths: paths,
})
}
}
IconCategories["fruits"] = fruitIcons
return nil
}
func collectPaths(g G) []string {
var paths []string
for _, p := range g.Paths {
d := strings.TrimSpace(p.D)
if d != "" { paths = append(paths, d) }
}
for _, subG := range g.Groups { paths = append(paths, collectPaths(subG)...) }
return paths
}