package home import ( "strconv" "gno.land/p/nym-alexiscolin000/gnoradio/svg/v0" "gno.land/p/nym-alexiscolin000/gnoradio/text/v0" ) // Sober Swiss artwork, drawn on-chain as SVG and embedded by gnoweb as // data-URI images: black and off-white, warm greys and the app's red, blue // and yellow accents. Cards with text sit on ink so they read in light and // dark mode alike. // Written with a strings.Builder to keep vm/qrender gas low. const ( ink = "#0d0d0d" paper = "#fafaf8" cream = "#f2efe9" grey = "#c4c4c0" stone = "#8f8b84" line = "#e3e3df" red = "#e5432f" blue = "#2445c9" amber = "#f2b51d" // the app's yellow dim = "#3a3a37" // unplayed ticks on ink font = "font-family:Geist,'Helvetica Neue',Helvetica,Arial,sans-serif" ) func newCanvas(w, h int) *svg.Canvas { return svg.New(w, h, "text{"+font+"}") } // text writes s (escaped); weight 400–800; anchor start|middle|end. func drawText(c *svg.Canvas, x, y int, s, fill string, size, weight int, anchor, extra string) { c.Text(x, y, s, fill, size, ` font-weight="`+text.Itoa(weight)+`" text-anchor="`+anchor+`"`+extra) } // coverSVG draws one of six geometric compositions and one of three accents, // picked from the track and its artist so neighbours differ. func coverSVG(id, artist int, title string, size int) string { h := svg.FNV(strconv.Itoa(id) + "/" + strconv.Itoa(artist) + title) accent := [3]string{red, blue, amber}[h/6%3] c := newCanvas(size, size) s := size switch h % 6 { case 0: c.Rect(0, 0, s, s, s/12, ink, "") c.Circle(s/2, s/2, s*28/100, accent) case 1: c.Rect(0, 0, s, s, s/12, grey, "") c.Circle(s/5, s*9/10, s*45/100, stone) c.Circle(s*68/100, s*32/100, s*15/100, ink) case 2: c.Rect(0, 0, s, s, s/12, accent, "") c.Raw(``) c.Circle(s/2, s/2, s*15/100, ink) case 3: c.Rect(0, 0, s, s, s/12, cream, "") c.Rect(s*14/100, s*14/100, s*44/100, s*72/100, s*22/100, stone, "") c.Circle(s*74/100, s*27/100, s*13/100, accent) case 4: c.Rect(0, 0, s, s, s/12, stone, "") c.Raw(``) c.Circle(s/2, s/2, s/5, cream) default: c.Rect(0, 0, s, s, s/12, ink, "") for i := 0; i < 21; i++ { th := s / 14 if i%5 == 0 { th = s / 7 } c.Rect(s/10+i*(s*8/10)/20, s*62/100-th, 2, th, 0, paper, "") } c.Rect(s/10+int(h%17)*(s*8/10)/20, s*36/100, 3, s*30/100, 0, accent, "") } return c.Image("cover of " + title) } // onAirSVG is the station banner: huge time numerals and a tick ruler with // the red needle, as in the app, on ink so it reads in dark mode too. func onAirSVG(station, title, artist string, offset, dur int64) string { const w, h = 720, 330 c := newCanvas(w, h) c.Rect(0, 0, w, h, 26, ink, "") c.Rect(28, 26, 104, 30, 10, red, "") drawText(c, 80, 46, "ON AIR", "#fff", 13, 600, "middle", ` letter-spacing="1.5"`) c.Rect(142, 26, 18+len(station)*9, 30, 10, dim, "") drawText(c, 151, 46, station, paper, 13, 500, "start", "") if title == "" { drawText(c, w/2, 170, "Silence. Publish the first track.", grey, 26, 600, "middle", "") return c.Image("nothing on air") } t := clock(offset) if offset < 600 { t = "0" + t } lead := 0 for lead < len(t) && (t[lead] == '0' || t[lead] == ':') { lead++ } c.Raw(`` + `` + t[:lead] + `` + t[lead:] + ``) drawText(c, w/2, 178, "of "+clock(dur)+" · synced to the last block", stone, 13, 400, "middle", "") frac := 0 if dur > 0 { frac = int(offset * 1000 / dur) } for i := 0; i <= 80; i++ { th, col := 10, dim if i%10 == 0 { th = 26 } else if i%5 == 0 { th = 17 } if i*1000/80 <= frac { col = paper } c.Rect(40+i*8, 238-th, 2, th, 0, col, "") } c.Rect(40+frac*640/1000, 196, 2, 50, 0, red, "") // Large enough to read once the card shrinks to a phone's width. drawText(c, 40, 284, svg.Clip(title, 36), paper, 28, 600, "start", "") drawText(c, 40, 312, svg.Clip(artist, 48), grey, 18, 400, "start", "") return c.Image("on air: " + title) } // avatarSVG is a symmetric identicon in the sober palette. func avatarSVG(seed string, size int) string { h := svg.FNV(seed) c := newCanvas(size, size) c.Rect(0, 0, size, size, size/2, cream, "") cell := size / 9 for y := 0; y < 5; y++ { for x := 0; x < 3; x++ { h ^= uint32(y*3 + x + 1) h *= 16777619 if (h>>24)%2 == 0 { continue } col := ink if (h>>20)%7 == 0 { col = red } c.Rect(cell*2+x*cell, cell*2+y*cell, cell, cell, 2, col, "") if x < 2 { c.Rect(cell*2+(4-x)*cell, cell*2+y*cell, cell, cell, 2, col, "") } } } return c.Image("avatar") } // badgeSVG is the early-listener badge. func badgeSVG(title string, rank int) string { c := newCanvas(120, 120) c.Circle(60, 60, 58, ink) c.Raw(``) drawText(c, 60, 50, "EARLY", grey, 11, 600, "middle", ` letter-spacing="2"`) drawText(c, 60, 82, "#"+text.Itoa(rank), paper, 30, 500, "middle", "") c.Circle(60, 100, 4, red) return c.Image("early listener #" + text.Itoa(rank) + " of " + title) } type bar struct { label string value int64 } // barsSVG draws a horizontal bar chart. func barsSVG(rows []bar, unit func(int64) string) string { h := 30 + len(rows)*40 c := newCanvas(640, h) c.Rect(0, 0, 640, h, 20, ink, "") var max int64 = 1 for _, r := range rows { if r.value > max { max = r.value } } for i, r := range rows { y := 20 + i*40 w := int(r.value * 300 / max) if w < 4 { w = 4 } col := paper if i == 0 { col = red } drawText(c, 20, y+20, svg.Clip(r.label, 20), paper, 14, 500, "start", "") c.Rect(210, y+6, w, 18, 9, col, "") drawText(c, 220+w, y+20, unit(r.value), grey, 13, 400, "start", "") } return c.Image("chart") }