code wiki / _hdl_build / nx_ad_banner_builder.nx
nx_ad_banner_builder.nx source
↩ module page · 101 lines · 7112 B
1// nx_ad_banner_builder.nx -- LIB: the advertiser-facing BANNER BUILDER (the "iterating UI" for the banner
2// generator). SERVER-RENDERED, ZERO JavaScript even in the builder itself: GET /ad/banner-builder shows the
3// form; POST the same path re-renders the form (values repopulated) PLUS a live preview of the generated
4// banner and a copy-paste box with its HTML. Generation is the sovereign organ (ab_emit_banner) -- the
5// builder is just the form around it, so the produced banner keeps every invariant (0 third-party JS,
6// self-contained, escaped, link-validated). Form values are HTML-escaped on repopulation (rule 12).
7// DRY: composes nx_ad_banner (ab_emit_banner/ab_iab_size) + nx_ad_serve (as_append*/as_contains/as_len) +
8// nx_http_form (field parse). license_tier: ORIGINAL
9import "nx_ad_banner.nx"
10import "nx_ad_serve.nx"
11import "nx_http_form.nx"
12import "nx_syscalls.nx"
13const K_MAGIC_16384: i64 = 16384
14
15// read one urlencoded form field -> out (NUL-terminated), return its length (0 if absent).
16func abb_get(body: *u8, n: i64, name: *u8, namelen: i64, out: *u8, cap: i64) -> i64 {
17 let lp: *i64 = sys_mmap(16) as *i64; lp[0] = 0
18 out[0] = 0 as u8
19 nx_http_form_get_field(body, n, name, namelen, out, cap, lp)
20 return lp[0]
21}
22
23// map the submitted kind field -> AB_STATIC|AB_DYNAMIC ("dynamic" anywhere => dynamic).
24func abb_kind(s: *u8, n: i64) -> i64 { if as_contains(s, n, "dynamic" as *u8) == 1 { return AB_DYNAMIC } return AB_STATIC }
25// map theme field -> 0 light / 1 dark / 2 warm.
26func abb_theme(s: *u8, n: i64) -> i64 {
27 if as_contains(s, n, "dark" as *u8) == 1 { return 1 }
28 if as_contains(s, n, "warm" as *u8) == 1 { return 2 }
29 return 0
30}
31
32// generate the banner for a submitted form body -> out; returns length (0 if no business given).
33func abb_render(body: *u8, n: i64, out: *u8) -> i64 {
34 let business: *u8 = sys_mmap(256); let bn: i64 = abb_get(body, n, "business" as *u8, 8, business, 255)
35 if bn == 0 { return 0 }
36 let headline: *u8 = sys_mmap(256); abb_get(body, n, "headline" as *u8, 8, headline, 255)
37 let msg: *u8 = sys_mmap(512); abb_get(body, n, "body" as *u8, 4, msg, 511)
38 let cta: *u8 = sys_mmap(128); abb_get(body, n, "cta" as *u8, 3, cta, 127)
39 let link: *u8 = sys_mmap(512); abb_get(body, n, "link" as *u8, 4, link, 511)
40 let size: *u8 = sys_mmap(64); let sn: i64 = abb_get(body, n, "size" as *u8, 4, size, 63)
41 let theme: *u8 = sys_mmap(32); let tn: i64 = abb_get(body, n, "theme" as *u8, 5, theme, 31)
42 let kind: *u8 = sys_mmap(32); let kn: i64 = abb_get(body, n, "kind" as *u8, 4, kind, 31)
43 let wbox: *i64 = sys_mmap(16) as *i64; let hbox: *i64 = sys_mmap(16) as *i64
44 ab_iab_size(size, wbox, hbox)
45 return ab_emit_banner(business, headline, msg, cta, link, wbox[0], hbox[0], abb_kind(kind, kn), abb_theme(theme, tn), out)
46}
47
48// emit one <option> (value=label) -> out at off.
49func abb_opt(out: *u8, off: i64, val: *u8) -> i64 {
50 var o: i64 = as_append(out, off, "<option>" as *u8)
51 o = as_append(out, o, val)
52 o = as_append(out, o, "</option>" as *u8)
53 return o
54}
55// emit a repopulatable text input row -> out at off.
56func abb_input(out: *u8, off: i64, label: *u8, name: *u8, body: *u8, n: i64) -> i64 {
57 var o: i64 = as_append(out, off, "<label>" as *u8); o = as_append(out, o, label)
58 o = as_append(out, o, "<input name='" as *u8); o = as_append(out, o, name); o = as_append(out, o, "' value='" as *u8)
59 if n > 0 {
60 let cur: *u8 = sys_mmap(512); let nl: i64 = abb_get(body, n, name, as_len(name), cur, 511)
61 o = as_append_escaped(out, o, cur, nl)
62 }
63 o = as_append(out, o, "'></label>" as *u8)
64 return o
65}
66
67// THE PAGE. Renders the builder form; if body has a 'business' field, also renders the generated banner
68// preview + a copy-paste HTML box. NUL-terminated into out; returns length. Zero JavaScript.
69func abb_page(body: *u8, n: i64, out: *u8) -> i64 {
70 var o: i64 = 0
71 o = as_append(out, o, "<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>Nishi Banner Builder</title><style>body{font-family:system-ui,sans-serif;max-width:760px;margin:4vh auto;padding:0 18px;color:#16202e}h1{font-size:1.3rem}form{display:grid;gap:10px}label{display:grid;gap:3px;font-size:.85rem;color:#445}input,select{padding:8px;border:1px solid #b9c2d6;border-radius:6px;font-size:.95rem}fieldset{border:1px solid #d6deea;border-radius:8px}button{padding:10px 18px;background:#0b2545;color:#fff;border:0;border-radius:6px;font-size:1rem;cursor:pointer;justify-self:start}.prev{margin:18px 0;padding:20px;background:#eef2f7;border-radius:10px;display:flex;justify-content:center}textarea{width:100%;min-height:90px;font-family:ui-monospace,monospace;font-size:.8rem;border:1px solid #b9c2d6;border-radius:6px;padding:8px;box-sizing:border-box}</style></head><body><h1>Nishi Banner Builder</h1><p style='color:#667;font-size:.9rem'>Generate a static or animated banner for the one-ad-per-page slot. No third-party scripts, no trackers, no external images — ever.</p>" as *u8)
72 o = as_append(out, o, "<form method='post' action='/ad/banner-builder'>" as *u8)
73 o = abb_input(out, o, "Business / sponsor" as *u8, "business" as *u8, body, n)
74 o = abb_input(out, o, "Headline" as *u8, "headline" as *u8, body, n)
75 o = abb_input(out, o, "Body line" as *u8, "body" as *u8, body, n)
76 o = abb_input(out, o, "Call-to-action" as *u8, "cta" as *u8, body, n)
77 o = abb_input(out, o, "Click link (https:// or /path)" as *u8, "link" as *u8, body, n)
78 o = as_append(out, o, "<label>Size<select name='size'>" as *u8)
79 o = abb_opt(out, o, "rectangle 300x250" as *u8); o = abb_opt(out, o, "leaderboard 728x90" as *u8)
80 o = abb_opt(out, o, "mobile 320x50" as *u8); o = abb_opt(out, o, "skyscraper 160x600" as *u8)
81 o = abb_opt(out, o, "halfpage 300x600" as *u8); o = abb_opt(out, o, "billboard 970x250" as *u8)
82 o = as_append(out, o, "</select></label>" as *u8)
83 o = as_append(out, o, "<label>Theme<select name='theme'><option>light</option><option>dark</option><option>warm</option></select></label>" as *u8)
84 o = as_append(out, o, "<label>Kind<select name='kind'><option>static</option><option>dynamic (animated)</option></select></label>" as *u8)
85 o = as_append(out, o, "<button type='submit'>Generate banner</button></form>" as *u8)
86
87 // if a submission, render the preview + the copyable HTML
88 let banner: *u8 = sys_mmap(K_MAGIC_16384)
89 let blen: i64 = abb_render(body, n, banner)
90 if blen > 0 {
91 o = as_append(out, o, "<h2 style='font-size:1.05rem;margin-top:24px'>Preview</h2><div class='prev'>" as *u8)
92 var i: i64 = 0
93 while i < blen { out[o] = banner[i]; o = o + 1; i = i + 1 }
94 o = as_append(out, o, "</div><h2 style='font-size:1.05rem'>Banner HTML (copy into the ad slot)</h2><textarea readonly>" as *u8)
95 o = as_append_escaped(out, o, banner, blen)
96 o = as_append(out, o, "</textarea>" as *u8)
97 }
98 o = as_append(out, o, "</body></html>" as *u8)
99 out[o] = 0 as u8
100 return o
101}