package nsview import ( "strings" "time" "gno.land/p/nt/ufmt/v0" "gno.land/r/nym-mnsrehearsal001/nsdata/v1" ) /* SKINS — the card becomes something else entirely for a day. The decoration calendar already draws snow and embers ON TOP of a card. A skin REPLACES it: on 22 May the token is a pizza, on 3 January it is a newspaper. That is a different kind of change, so it is a different field, and any date without one renders exactly as it always has. HOW A CONTRACT KNOWS THE DATE AT ALL. It does not read a block height and guess. Every block carries a timestamp in its header, agreed by consensus the same way its transactions are, and `time.Now()` inside a realm returns that block time. So a realm has a real clock, accurate to within a block interval — which for a decoration that changes once a year is precision nobody will ever notice. This is the same mechanism the seasonal overlay has always used. WHY THIS COSTS NOTHING TO RUN. The card is generated on every read rather than fixed at mint, so a skin needs no transaction, no migration and nobody awake at midnight. It applies to names sold years ago. Turning it off is deleting one field from the calendar. NO FONTS. Every renderer that shows these — wallets, marketplaces — puts the SVG in a sandbox with no network, so a web font simply fails and silently substitutes. Only the generic families resolve, which is why the existing card says monospace and why these say serif or sans-serif. The display faces in the mockups are a later job: an alphabet drawn as paths and stored once, which is the only way to get identical letterforms on every device. */ // skinFor returns the skin named by whichever calendar rule is active // today, or "" for the ordinary card. Reusing pickRule means a skin // obeys the same window rules, the same first-match-wins ordering and // the same year wrapping as every other season. func skinFor(now int64) string { return skinFromSpec(nsdata.GetGlobal(keyFX), now) } // Split out so the calendar logic can be tested against a spec directly, // without standing up a vault to hold one. func skinFromSpec(spec string, now int64) string { rule := pickRule(spec, now) if rule == "" { return "" } return ruleField(rule, "skin") } // hasSkin reports whether a name is renderable as this skin. Unknown // names render nothing rather than a broken card — the same defensive // stance the overlay parser takes, and for the same reason: this markup // lands inside a token that other people's software renders. func hasSkin(name string) bool { switch name { case "pizza", "paper", "cal", "fog", "slash", "pi", "heart": return true } return false } // seedOf turns a token id into a small number, so that a card can differ // from the one beside it without any randomness — which a deterministic // machine does not have. Every node computes the same answer for the same // name, which is the requirement; looking arbitrary is merely the effect. func seedOf(tid string) int { h := 2166136261 for i := 0; i < len(tid); i++ { h ^= int(tid[i]) h *= 16777619 h &= 0x7fffffff } return h } // skinSVG renders a complete 400x400 card. Same frame as the ordinary // one, so nothing downstream has to know which it received. func skinSVG(name, tid string, now int64) string { label := tid if at := strings.Index(tid, "*"); at > 0 { label = tid } size := 34 if len(label) > 14 { size = 22 } else if len(label) > 10 { size = 27 } switch name { case "pizza": return skinPizza(label, size, seedOf(tid)) case "paper": return skinPaper(label, size, now) case "cal": return skinCal(label, size) case "fog": return skinFog(label, size) case "slash": return skinSlash(label, size) case "pi": return skinPi(label, size) case "heart": return skinHeart(label, size) } return "" } func head(bg string) string { return `` + `` } func foot() string { return `MINTED ON GNO.LAND` } /* ---------------- 22 May: the pizza ---------------- * * Eight wedges with a gap between them, so it reads as CUT rather than * merely divided, and each wedge carries its own toppings inside its own * group — that is what makes a slice leave with its pepperoni still on * it instead of leaving them floating over the board. * * EVERY NAME EATS IN ITS OWN ORDER. The seed decides which slice goes * first and how long each one waits, so a marketplace full of these is a * room full of pizzas being eaten from different sides at different * rates rather than one animation repeated. It is derived from the token * id, so it is the same on every node and different for every name. */ func skinPizza(label string, size, seed int) string { var b strings.Builder b.WriteString(head("#2a2018")) b.WriteString(``) order := make([]int, 8) for i := range order { order[i] = i } // A deterministic shuffle: the seed walks the list and swaps. s := seed for i := 7; i > 0; i-- { s = (s*1103515245 + 12345) & 0x7fffffff j := s % (i + 1) order[i], order[j] = order[j], order[i] } for pos, idx := range order { a0 := float64(idx)*45 + 1.8 a1 := float64(idx+1)*45 - 1.8 // Each slice waits a little longer than the last, with the gap // itself varying by name so two cards never keep step. begin := 3 + pos*3 + (seed>>uint(pos))%4 b.WriteString(``) b.WriteString(``, 2, begin)) b.WriteString(wedge(200, 200, 162, a0, a1, "#d9822b")) b.WriteString(wedge(200, 200, 143, a0+1.2, a1-1.2, "#f0b23c")) // Three toppings inside this wedge: one pepperoni, two herbs. for t := 0; t < 3; t++ { s = (s*1103515245 + 12345) & 0x7fffffff ang := a0 + 4 + float64(s%int(a1-a0-8)) s = (s*1103515245 + 12345) & 0x7fffffff rad := 46 + float64(s%86) x, y := polar(200, 200, rad, ang) if t == 0 { b.WriteString(ufmt.Sprintf(``, x, y)) } else { b.WriteString(ufmt.Sprintf( ``, x-6, y-6)) } } b.WriteString(``) } b.WriteString(ufmt.Sprintf( `%s`, size, esc(label))) b.WriteString(foot()) return b.String() } func wedge(cx, cy int, r float64, a0, a1 float64, fill string) string { x0, y0 := polar(cx, cy, r, a0) x1, y1 := polar(cx, cy, r, a1) return ufmt.Sprintf(``, cx, cy, x0, y0, int(r), int(r), x1, y1, fill) } // Degrees clockwise from twelve o'clock, which is how a pizza is cut. // Integer maths throughout: a realm has no floating point worth trusting // across nodes, and a pizza does not need sub-pixel accuracy. func polar(cx, cy int, r float64, deg float64) (int, int) { rad := (deg - 90) * 3.14159265 / 180 return cx + int(r*cosApprox(rad)), cy + int(r*sinApprox(rad)) } func sinApprox(x float64) float64 { for x > 3.14159265 { x -= 6.2831853 } for x < -3.14159265 { x += 6.2831853 } x2 := x * x return x * (1 - x2/6 + x2*x2/120 - x2*x2*x2/5040) } func cosApprox(x float64) float64 { return sinApprox(x + 1.5707963) } /* ---------------- 3 Jan: the front page ---------------- */ func skinPaper(label string, size int, now int64) string { year := time.Unix(now, 0).UTC().Year() return head("#f4f0e4") + `THE LEDGER` + `` + ufmt.Sprintf(`3 JANUARY %d · BLOCK ZERO`, year) + ufmt.Sprintf(`%s`, size+6, esc(label)) + `The first block is mined. A chain begins.` + `Years later somebody registers a name` + `on it that does nothing at all.` + `` + `MINTED ON GNO.LAND` } /* ---------------- 29 Feb: the calendar page ---------------- */ func skinCal(label string, size int) string { return head("#ffffff") + `` + `FEBRUARY` + `` + `` + `29` + ufmt.Sprintf(`%s`, size, esc(label)) + `MINTED ON GNO.LAND` } /* ---------------- 20 Apr: the fog ---------------- */ func skinFog(label string, size int) string { return head("#1d4028") + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + `` + ufmt.Sprintf(`%s`, size, esc(label)) + foot() } /* ---------------- Friday 13th: the poster ---------------- */ func skinSlash(label string, size int) string { return head("#15080a") + `` + `` + ufmt.Sprintf(`%s`, size+4, esc(label)) + foot() } /* ---------------- 14 Mar: pi ---------------- */ func skinPi(label string, size int) string { digits := []string{ "3.14159265358979323846", "26433832795028841971", "69399375105820974944", "59230781640628620899", "86280348253421170679", "82148086513282306647", } var b strings.Builder b.WriteString(head("#101418")) for i, row := range digits { b.WriteString(ufmt.Sprintf( `%s`, 70+i*56, row)) } b.WriteString(``) b.WriteString(ufmt.Sprintf( `%s`, size, esc(label))) b.WriteString(foot()) return b.String() } /* ---------------- 14 Feb: the heart ---------------- * * A real double thump, not one pulse: squeeze, small recoil, second * smaller squeeze, then a long rest. The rest is most of what makes it * read as a heartbeat rather than a throb. */ func skinHeart(label string, size int) string { return head("#fbe3ec") + `` + `` + `` + ufmt.Sprintf(`%s`, size, esc(strings.ToUpper(label))) + `MINTED ON GNO.LAND` } // Markup safety, same as everywhere else that puts a name into SVG: a // label is validated on registration, but this is the last gate before // it lands inside somebody else's renderer. func esc(s string) string { s = strings.ReplaceAll(s, "&", "&") s = strings.ReplaceAll(s, "<", "<") s = strings.ReplaceAll(s, ">", ">") return s }