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 &mdash; 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}