code wiki / _hdl_build / nx_ad_banner.nx

nx_ad_banner.nx source

↩ module page · 205 lines · 14578 B

1// nx_ad_banner.nx -- LIB: the BANNER GENERATOR for the performance ad engine. Advertisers generate real 2// STATIC or DYNAMIC display banners (IAB-sized, themed) for placement in the one-ad-per-page slot. 3// EXTENDS nx_ad_serve's clean "Brought to you by" model from a text byline to a full creative, while 4// KEEPING every privacy/security invariant BY CONSTRUCTION: 5// * ZERO third-party JS -- no <script>/<iframe>/<object>/<embed>/src= ; DYNAMIC = pure CSS @keyframes 6// animation (no JS), so a banner can NEVER carry a surveillance auction or third-party script. 7// * SELF-CONTAINED -- no external fetch (no src=, no url(http...)) -> no off-site beacon. Visuals 8// are CSS (solid + accent) + text only; no <img src>. No per-user targeting -- "systems not people". 9// * BOUNDARY-ESCAPED -- sponsor/headline/body/cta are external input -> HTML-escaped (rule 12); the 10// click LINK is scheme-validated (https/http/ relative only) so a `javascript:`/`data:` URI XSS is 11// neutralised to '#'. 12// * SCOPED -- a per-banner class suffix scopes the inline <style> so multiple previews on 13// one builder page never collide. 14// DRY: composes nx_ad_serve (as_append / as_append_escaped / as_has_thirdparty_js / as_len / as_contains). 15// IAB sizes + themes are NAMED here (code-default tier of the config hierarchy); migrate to 16// adcfg:bannersizes in the sovereign store (nx_ad_store) as a follow-on. license_tier: ORIGINAL 17import "nx_ad_serve.nx" 18import "nx_syscalls.nx" 19 20const AB_STATIC: i64 = 0 21const AB_DYNAMIC: i64 = 1 22 23func ab_startswith(buf: *u8, pfx: *u8) -> i64 { 24 var i: i64 = 0 25 while pfx[i] != (0 as u8) { if buf[i] != pfx[i] { return 0 } i = i + 1 } 26 return 1 27} 28// append unsigned decimal v to out at off; return new offset. 29func ab_uitoa(out: *u8, off: i64, v: i64) -> i64 { 30 let t: *u8 = sys_mmap(24); var m: i64 = v; var k: i64 = 0 31 if m == 0 { t[0] = 48 as u8; k = 1 } 32 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 33 var o: i64 = off; var q: i64 = k - 1 34 while q >= 0 { out[o] = t[q]; o = o + 1; q = q - 1 } 35 return o 36} 37// click-link safety: only https:// , http:// , or a site-relative '/path' (defeats javascript:/data: URIs). 38func ab_safe_link(link: *u8) -> i64 { 39 if ab_startswith(link, "https://" as *u8) == 1 { return 1 } 40 if ab_startswith(link, "http://" as *u8) == 1 { return 1 } 41 if link[0] == (47 as u8) { return 1 } // '/' 42 return 0 43} 44// 16-bit djb2 over business||headline -> 4 hex chars into out (NUL-terminated). Scopes the <style>. 45func ab_suffix(a: *u8, b: *u8, out: *u8) -> i64 { 46 var h: i64 = 5381 47 var i: i64 = 0 48 while a[i] != (0 as u8) { h = ((h * 33) + (a[i] as i64)) & 0xffffff; i = i + 1 } 49 var j: i64 = 0 50 while b[j] != (0 as u8) { h = ((h * 33) + (b[j] as i64)) & 0xffffff; j = j + 1 } 51 h = h & 0xffff 52 let hx: *u8 = "0123456789abcdef" as *u8 53 out[0] = hx[(h >> 12) & 15]; out[1] = hx[(h >> 8) & 15]; out[2] = hx[(h >> 4) & 15]; out[3] = hx[h & 15] 54 out[4] = 0 as u8 55 return 4 56} 57 58func ab_upper(c: i64) -> i64 { if c >= 97 { if c <= 122 { return c - 32 } } return c } 59// up to 2 uppercase initials from the business name (first letter of the first 2 words) -> out (NUL-term). 60func ab_initials(business: *u8, out: *u8) -> i64 { 61 var o: i64 = 0; var i: i64 = 0; var atword: i64 = 1; var go: i64 = 1 62 while go == 1 { 63 let c: i64 = business[i] as i64 64 if c == 0 { go = 0 } else { 65 if c == 32 { atword = 1 } else { 66 if atword == 1 { 67 var isl: i64 = 0 68 if c >= 65 { if c <= 90 { isl = 1 } } 69 if c >= 97 { if c <= 122 { isl = 1 } } 70 if isl == 1 { if o < 2 { out[o] = ab_upper(c) as u8; o = o + 1 } } 71 atword = 0 72 } 73 } 74 i = i + 1 75 if o >= 2 { go = 0 } 76 } 77 } 78 if o == 0 { out[0] = 78 as u8; o = 1 } // 'N' fallback 79 out[o] = 0 as u8 80 return o 81} 82 83// THE GENERATOR. Emit a slot-ready banner (NUL-terminated) into out; return byte length. 84// text args NUL-terminated; w/h = pixel size (IAB, supplied by caller/config); kind = AB_STATIC|AB_DYNAMIC; 85// theme_id 0=light 1=dark 2=warm. 86func ab_emit_banner(business: *u8, headline: *u8, body: *u8, cta: *u8, link: *u8, 87 w: i64, h: i64, kind: i64, theme_id: i64, out: *u8) -> i64 { 88 var bg: *u8 = "#ffffff"; var fg: *u8 = "#16202e"; var accent: *u8 = "#0b2545"; var actxt: *u8 = "#ffffff" 89 if theme_id == 1 { bg = "#0b2545"; fg = "#eaf1fb"; accent = "#4da3ff"; actxt = "#06122b" } 90 if theme_id == 2 { bg = "#fff7ed"; fg = "#7c2d12"; accent = "#ea580c"; actxt = "#ffffff" } 91 92 let sfx: *u8 = sys_mmap(8) 93 ab_suffix(business, headline, sfx) 94 var safe: i64 = ab_safe_link(link) 95 var o: i64 = 0 96 97 o = as_append(out, o, "<a class='nb-" as *u8); o = as_append(out, o, sfx) 98 o = as_append(out, o, "' href='" as *u8) 99 if safe == 1 { o = as_append_escaped(out, o, link, as_len(link)) } else { o = as_append(out, o, "#" as *u8) } 100 o = as_append(out, o, "' rel='nofollow noopener' role='note' aria-label='Advertisement'>" as *u8) 101 102 o = as_append(out, o, "<style>.nb-" as *u8); o = as_append(out, o, sfx) 103 o = as_append(out, o, "{display:flex;flex-direction:column;justify-content:center;box-sizing:border-box;width:" as *u8) 104 o = ab_uitoa(out, o, w); o = as_append(out, o, "px;height:" as *u8); o = ab_uitoa(out, o, h) 105 o = as_append(out, o, "px;padding:12px 14px;gap:6px;border-radius:10px;overflow:hidden;text-decoration:none;font-family:system-ui,-apple-system,Segoe UI,sans-serif;background:" as *u8) 106 o = as_append(out, o, bg); o = as_append(out, o, ";color:" as *u8); o = as_append(out, o, fg) 107 o = as_append(out, o, ";border:1px solid rgba(0,0,0,.12);position:relative}" as *u8) 108 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-spon{font-size:11px;letter-spacing:.04em;opacity:.7;text-transform:uppercase}" as *u8) 109 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-head{font-size:18px;font-weight:700;line-height:1.15}" as *u8) 110 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-body{font-size:13px;opacity:.9;line-height:1.3}" as *u8) 111 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-cta{align-self:flex-start;margin-top:4px;padding:6px 12px;border-radius:6px;font-size:13px;font-weight:600;background:" as *u8) 112 o = as_append(out, o, accent); o = as_append(out, o, ";color:" as *u8); o = as_append(out, o, actxt); o = as_append(out, o, "}" as *u8) 113 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-logo{position:absolute;top:10px;right:12px}" as *u8) 114 if kind == AB_DYNAMIC { 115 o = as_append(out, o, "@keyframes nbp-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, "{0%,100%{transform:scale(1)}50%{transform:scale(1.06)}}" as *u8) 116 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-cta{animation:nbp-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " 1.6s ease-in-out infinite}" as *u8) 117 o = as_append(out, o, "@media (prefers-reduced-motion:reduce){.nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-cta{animation:none}}" as *u8) 118 } 119 o = as_append(out, o, "</style>" as *u8) 120 121 // sovereign inline-SVG sponsor monogram (a real logo element; NO external fetch, NO script -> invariants hold) 122 let ini: *u8 = sys_mmap(8) 123 ab_initials(business, ini) 124 o = as_append(out, o, "<svg class='nb-logo' width='32' height='32' viewBox='0 0 36 36' aria-hidden='true'><circle cx='18' cy='18' r='18' fill='" as *u8) 125 o = as_append(out, o, accent) 126 o = as_append(out, o, "'/><text x='18' y='23' text-anchor='middle' font-family='system-ui,sans-serif' font-size='15' font-weight='700' fill='" as *u8) 127 o = as_append(out, o, actxt) 128 o = as_append(out, o, "'>" as *u8) 129 o = as_append(out, o, ini) 130 o = as_append(out, o, "</text></svg>" as *u8) 131 132 o = as_append(out, o, "<span class='nb-spon'>Brought to you by " as *u8) 133 o = as_append_escaped(out, o, business, as_len(business)); o = as_append(out, o, "</span>" as *u8) 134 o = as_append(out, o, "<span class='nb-head'>" as *u8); o = as_append_escaped(out, o, headline, as_len(headline)); o = as_append(out, o, "</span>" as *u8) 135 o = as_append(out, o, "<span class='nb-body'>" as *u8); o = as_append_escaped(out, o, body, as_len(body)); o = as_append(out, o, "</span>" as *u8) 136 o = as_append(out, o, "<span class='nb-cta'>" as *u8); o = as_append_escaped(out, o, cta, as_len(cta)); o = as_append(out, o, " &rarr;</span>" as *u8) 137 o = as_append(out, o, "</a>" as *u8) 138 out[o] = 0 as u8 139 return o 140} 141 142// ===== data-driven emitter consumption (the builder USES catalogued emitters) ===== 143// substitution engine: {ACCENT}/{ACTXT}/{INI}/{S} in a template -> values. 144func ab_pl(tpl: *u8, i: i64, pat: *u8) -> i64 { var j: i64 = 0; while pat[j] != (0 as u8) { if tpl[i+j] != pat[j] { return 0 } j = j + 1 } return 1 } 145func ab_apply_tpl(out: *u8, off: i64, tpl: *u8, accent: *u8, actxt: *u8, ini: *u8, sfx: *u8) -> i64 { 146 var o: i64 = off; var i: i64 = 0 147 while tpl[i] != (0 as u8) { 148 var done: i64 = 0 149 if tpl[i] == (123 as u8) { 150 if ab_pl(tpl, i, "{ACCENT}" as *u8) == 1 { o = as_append(out, o, accent); i = i + 8; done = 1 } 151 if done == 0 { if ab_pl(tpl, i, "{ACTXT}" as *u8) == 1 { o = as_append(out, o, actxt); i = i + 7; done = 1 } } 152 if done == 0 { if ab_pl(tpl, i, "{INI}" as *u8) == 1 { o = as_append(out, o, ini); i = i + 5; done = 1 } } 153 if done == 0 { if ab_pl(tpl, i, "{S}" as *u8) == 1 { o = as_append(out, o, sfx); i = i + 3; done = 1 } } 154 } 155 if done == 0 { out[o] = tpl[i]; o = o + 1; i = i + 1 } 156 } 157 return o 158} 159// THE CORE RENDERER: explicit colors + an accent TEMPLATE + an animation TEMPLATE (data-driven; placeholders 160// substituted). The registry-driven path (nx_ad_emitter_apply) renders grown emitters through THIS one engine. 161// (ab_emit_banner above is the back-compat theme_id/monogram/pulse path; FOLD it onto this core next.) 162func ab_emit_banner_c(business: *u8, headline: *u8, body: *u8, cta: *u8, link: *u8, 163 w: i64, h: i64, bg: *u8, fg: *u8, accent: *u8, actxt: *u8, 164 accent_tpl: *u8, anim_tpl: *u8, out: *u8) -> i64 { 165 let sfx: *u8 = sys_mmap(8); ab_suffix(business, headline, sfx) 166 let ini: *u8 = sys_mmap(8); ab_initials(business, ini) 167 var safe: i64 = ab_safe_link(link) 168 var o: i64 = 0 169 o = as_append(out, o, "<a class='nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, "' href='" as *u8) 170 if safe == 1 { o = as_append_escaped(out, o, link, as_len(link)) } else { o = as_append(out, o, "#" as *u8) } 171 o = as_append(out, o, "' rel='nofollow noopener' role='note' aria-label='Advertisement'>" as *u8) 172 o = as_append(out, o, "<style>.nb-" as *u8); o = as_append(out, o, sfx) 173 o = as_append(out, o, "{display:flex;flex-direction:column;justify-content:center;box-sizing:border-box;width:" as *u8) 174 o = ab_uitoa(out, o, w); o = as_append(out, o, "px;height:" as *u8); o = ab_uitoa(out, o, h) 175 o = as_append(out, o, "px;padding:12px 14px;gap:6px;border-radius:10px;overflow:hidden;text-decoration:none;font-family:system-ui,-apple-system,Segoe UI,sans-serif;background:" as *u8) 176 o = as_append(out, o, bg); o = as_append(out, o, ";color:" as *u8); o = as_append(out, o, fg) 177 o = as_append(out, o, ";border:1px solid rgba(0,0,0,.12);position:relative}" as *u8) 178 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-spon{font-size:11px;letter-spacing:.04em;opacity:.7;text-transform:uppercase}" as *u8) 179 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-head{font-size:18px;font-weight:700;line-height:1.15}" as *u8) 180 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-body{font-size:13px;opacity:.9;line-height:1.3}" as *u8) 181 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-cta{align-self:flex-start;margin-top:4px;padding:6px 12px;border-radius:6px;font-size:13px;font-weight:600;background:" as *u8) 182 o = as_append(out, o, accent); o = as_append(out, o, ";color:" as *u8); o = as_append(out, o, actxt); o = as_append(out, o, "}" as *u8) 183 o = as_append(out, o, ".nb-" as *u8); o = as_append(out, o, sfx); o = as_append(out, o, " .nb-logo{position:absolute;top:10px;right:12px}" as *u8) 184 o = ab_apply_tpl(out, o, anim_tpl, accent, actxt, ini, sfx) 185 o = as_append(out, o, "</style>" as *u8) 186 o = ab_apply_tpl(out, o, accent_tpl, accent, actxt, ini, sfx) 187 o = as_append(out, o, "<span class='nb-spon'>Brought to you by " as *u8); o = as_append_escaped(out, o, business, as_len(business)); o = as_append(out, o, "</span>" as *u8) 188 o = as_append(out, o, "<span class='nb-head'>" as *u8); o = as_append_escaped(out, o, headline, as_len(headline)); o = as_append(out, o, "</span>" as *u8) 189 o = as_append(out, o, "<span class='nb-body'>" as *u8); o = as_append_escaped(out, o, body, as_len(body)); o = as_append(out, o, "</span>" as *u8) 190 o = as_append(out, o, "<span class='nb-cta'>" as *u8); o = as_append_escaped(out, o, cta, as_len(cta)); o = as_append(out, o, " &rarr;</span>" as *u8) 191 o = as_append(out, o, "</a>" as *u8) 192 out[o] = 0 as u8 193 return o 194} 195 196// IAB standard size names (code-default tier; w via *wout, h via *hout). 1 if known, 0 if not (defaults 300x250). 197func ab_iab_size(name: *u8, wout: *i64, hout: *i64) -> i64 { 198 if as_contains(name, as_len(name), "leaderboard" as *u8) == 1 { wout[0] = 728; hout[0] = 90; return 1 } 199 if as_contains(name, as_len(name), "rectangle" as *u8) == 1 { wout[0] = 300; hout[0] = 250; return 1 } 200 if as_contains(name, as_len(name), "mobile" as *u8) == 1 { wout[0] = 320; hout[0] = 50; return 1 } 201 if as_contains(name, as_len(name), "skyscraper" as *u8) == 1 { wout[0] = 160; hout[0] = 600; return 1 } 202 if as_contains(name, as_len(name), "halfpage" as *u8) == 1 { wout[0] = 300; hout[0] = 600; return 1 } 203 if as_contains(name, as_len(name), "billboard" as *u8) == 1 { wout[0] = 970; hout[0] = 250; return 1 } 204 wout[0] = 300; hout[0] = 250; return 0 205}