code wiki / _hdl_build / nx_site_build.nx
nx_site_build.nx source
↩ module page · 531 lines · 27226 B
1// nx_site_build.nx -- R1 WHOLE-SITE emitter. From ONE .site blueprint, emit a multi-PAGE site with a SHARED
2// cross-page nav, INTERNAL links between pages, and its own sitemap.xml + robots.txt -- all through the gorgeous
3// S-class kit (nx_sclass_kit sk_*), so every page is s-class-by-construction. Closes the whole-site gaps the
4// factory census named: shared nav, internal <a href> links, and wired sitemap/robots EMISSION over our output.
5// Reuses the andelinwest .site blueprint verbatim (title|/header|brand|cta|href /hero|headline|sub|cta|href
6// /search|action|placeholder /cards+card|title|body /steps+step| /trust+sig| /footer|): the page LIST is derived
7// from the cards (like nx_site_pages), but upgraded to the premium kit + real navigation + a self-emitted sitemap.
8// usage: nx_site_build <config.site> <docroot> <domain> [theme:0|1]
9// emits: <docroot>/index.html + <docroot>/practice/<slug>.html (one per card) + sitemap.xml + robots.txt
10// SCALE: one buffer is mmap'd ONCE and reused per page (no per-iteration mmap -> no bump-allocator starvation).
11// license_tier: ORIGINAL
12import "nx_sclass_kit.nx"
13import "nx_nishi_editorial.nx"
14import "nx_syscalls.nx"
15const SB_MAGIC_1024: i64 = 1024
16const SB_MAGIC_131072: i64 = 131072
17const SB_MAGIC_262144: i64 = 262144
18
19const SB_ICON: *u8 = "<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 3l8 4v5c0 4-3.5 7-8 9-4.5-2-8-5-8-9V7l8-4z\"/></svg>"
20
21func sb_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
22func sb_wn(v: i64) -> i64 {
23 if v==0 { sys_write(1,"0" as *u8,1); return 0 }
24 var m: i64=v; if m<0 { sys_write(1,"-" as *u8,1); m=0-m }
25 let t: *u8=sys_mmap(24); var k: i64=0
26 while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 }
27 let o2: *u8=sys_mmap(24); var w: i64=0; var q: i64=k-1
28 while q>=0 { o2[w]=t[q]; w=w+1; q=q-1 }
29 sys_write(1,o2,w); return 0
30}
31func sb_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
32func sb_cat(out: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){out[o]=s[i]; o=o+1; i=i+1} return o }
33
34// copy text[start..end) into arena at ap (trim a trailing CR), NUL-terminate, return new ap.
35func sb_copy(arena: *u8, ap: i64, text: *u8, start: i64, end: i64) -> i64 {
36 var o: i64=ap; var j: i64=start
37 while j<end { arena[o]=text[j]; o=o+1; j=j+1 }
38 if o>ap { if (arena[o-1] as i64)==13 { o=o-1 } }
39 arena[o]=0 as u8; o=o+1
40 return o
41}
42// does text[ls..le) start with key|? return value-start idx, or -1.
43func sb_lm(text: *u8, ls: i64, le: i64, key: *u8, klen: i64) -> i64 {
44 if ls+klen+1 > le { return 0 - 1 }
45 var i: i64=0
46 while i<klen { if (text[ls+i] as i64)!=(key[i] as i64) { return 0 - 1 } i=i+1 }
47 if (text[ls+klen] as i64)!=124 { return 0 - 1 }
48 return ls+klen+1
49}
50// value of the first "key|value" line -> out (NUL-term, trailing CR trimmed). return len, or -1.
51func sb_cg(text: *u8, tn: i64, key: *u8, out: *u8, cap: i64) -> i64 {
52 let klen: i64 = sb_slen(key)
53 var ls: i64=0; var i: i64=0
54 while i<=tn {
55 var eol: i64=0
56 if i==tn { eol=1 }
57 if i<tn { if (text[i] as i64)==10 { eol=1 } }
58 if eol==1 {
59 let vs: i64 = sb_lm(text, ls, i, key, klen)
60 if vs>=0 {
61 var o: i64=0; var j: i64=vs
62 while j<i { if o<cap-1 { out[o]=text[j]; o=o+1 } j=j+1 }
63 if o>0 { if (out[o-1] as i64)==13 { o=o-1 } }
64 out[o]=0 as u8
65 return o
66 }
67 ls=i+1
68 }
69 i=i+1
70 }
71 out[0]=0 as u8
72 return 0 - 1
73}
74// idx-th '|'-separated field of NUL-term val -> out (NUL-term). return len.
75func sb_fld(val: *u8, idx: i64, out: *u8) -> i64 {
76 var f: i64=0; var i: i64=0; var o: i64=0
77 while val[i]!=(0 as u8) {
78 if (val[i] as i64)==124 { f=f+1 } else { if f==idx { out[o]=val[i]; o=o+1 } }
79 i=i+1
80 }
81 out[o]=0 as u8
82 return o
83}
84// slug: lowercase, space->'-', keep [a-z0-9-]. "Family Law" -> "family-law". return len.
85func sb_slug(title: *u8, out: *u8) -> i64 {
86 var o: i64=0; var i: i64=0
87 while title[i]!=(0 as u8) {
88 var c: i64 = title[i] as i64
89 if c>=65 { if c<=90 { c=c+32 } }
90 if c==32 { out[o]=45 as u8; o=o+1 } else {
91 var keep: i64=0
92 if c>=97 { if c<=122 { keep=1 } }
93 if c>=48 { if c<=57 { keep=1 } }
94 if c==45 { keep=1 }
95 if keep==1 { out[o]=c as u8; o=o+1 }
96 }
97 i=i+1
98 }
99 out[o]=0 as u8
100 return o
101}
102
103// collect every "card|title|body" line: fill ct/cb/cs (arena ptrs). return count.
104func sb_cards(text: *u8, tn: i64, ct: *i64, cb: *i64, cs: *i64, arena: *u8, apP: *i64, maxc: i64) -> i64 {
105 var cnt: i64=0; var ls: i64=0; var i: i64=0
106 while i<=tn {
107 var eol: i64=0
108 if i==tn { eol=1 }
109 if i<tn { if (text[i] as i64)==10 { eol=1 } }
110 if eol==1 {
111 let vs: i64 = sb_lm(text, ls, i, "card" as *u8, 4)
112 if vs>=0 { if cnt<maxc {
113 var p1: i64=vs
114 while p1<i { if (text[p1] as i64)==124 { break } p1=p1+1 }
115 var ap: i64 = apP[0]
116 ct[cnt] = (arena + ap) as i64
117 ap = sb_copy(arena, ap, text, vs, p1)
118 cb[cnt] = (arena + ap) as i64
119 ap = sb_copy(arena, ap, text, p1+1, i)
120 cs[cnt] = (arena + ap) as i64
121 let sl: i64 = sb_slug(ct[cnt] as *u8, (arena + ap) as *u8)
122 ap = ap + sl + 1
123 apP[0] = ap
124 cnt = cnt + 1
125 } }
126 ls=i+1
127 }
128 i=i+1
129 }
130 return cnt
131}
132// collect the single value of every "key|value" line (steps, sigs). return count.
133func sb_single(text: *u8, tn: i64, key: *u8, klen: i64, ptrs: *i64, arena: *u8, apP: *i64, maxc: i64) -> i64 {
134 var cnt: i64=0; var ls: i64=0; var i: i64=0
135 while i<=tn {
136 var eol: i64=0
137 if i==tn { eol=1 }
138 if i<tn { if (text[i] as i64)==10 { eol=1 } }
139 if eol==1 {
140 let vs: i64 = sb_lm(text, ls, i, key, klen)
141 if vs>=0 { if cnt<maxc {
142 var ap: i64 = apP[0]
143 ptrs[cnt] = (arena + ap) as i64
144 ap = sb_copy(arena, ap, text, vs, i)
145 apP[0] = ap
146 cnt = cnt + 1
147 } }
148 ls=i+1
149 }
150 i=i+1
151 }
152 return cnt
153}
154
155// build "/practice/<slug>.html" into out. return len.
156func sb_phref(out: *u8, slug: *u8) -> i64 {
157 var o: i64 = sb_cat(out, 0, "/practice/" as *u8)
158 o = sb_cat(out, o, slug); o = sb_cat(out, o, ".html" as *u8)
159 out[o]=0 as u8
160 return o
161}
162// emit the SHARED nav (Home + one item per card) into buf. hrefbuf is reused scratch. return new off.
163func emit_nav(buf: *u8, off: i64, ncards: i64, ct: *i64, cs: *i64, hrefbuf: *u8) -> i64 {
164 var o: i64 = sk_nav_item(buf, off, "Home" as *u8, "/" as *u8)
165 var i: i64=0
166 while i<ncards {
167 sb_phref(hrefbuf, cs[i] as *u8)
168 o = sk_nav_item(buf, o, ct[i] as *u8, hrefbuf)
169 i=i+1
170 }
171 return o
172}
173// emit the trust band from the sig[] signals. return new off.
174func emit_trust(buf: *u8, off: i64, nsigs: i64, sigs: *i64) -> i64 {
175 if nsigs<=0 { return off }
176 var o: i64 = sk_trust_open(buf, off, "Why us" as *u8, "Trusted, by construction" as *u8, "Every page ships accessible, fast, and sovereign -- by construction." as *u8)
177 var i: i64=0
178 while i<nsigs { o = sk_trust_item(buf, o, sigs[i] as *u8, "Verified." as *u8); i=i+1 }
179 o = sk_trust_close(buf, o)
180 return o
181}
182
183// sitemap.xml + robots.txt for the emitted page set. Extracted so BOTH presentation tiers emit the same
184// SEO surface from one place -- a second copy is how the two tiers would silently drift apart.
185func sb_emit_sitemap_robots(buf: *u8, pathbuf: *u8, docroot: *u8, domain: *u8, ncards: i64, cs: *i64) -> i64 {
186 var o: i64 = sb_cat(buf, 0, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n" as *u8)
187 o = sb_cat(buf, o, "<url><loc>https://" as *u8); o = sb_cat(buf, o, domain); o = sb_cat(buf, o, "/</loc><priority>1.0</priority></url>\n" as *u8)
188 var i: i64 = 0
189 while i < ncards {
190 o = sb_cat(buf, o, "<url><loc>https://" as *u8); o = sb_cat(buf, o, domain); o = sb_cat(buf, o, "/practice/" as *u8); o = sb_cat(buf, o, cs[i] as *u8); o = sb_cat(buf, o, ".html</loc><priority>0.8</priority></url>\n" as *u8)
191 i=i+1
192 }
193 o = sb_cat(buf, o, "</urlset>\n" as *u8)
194 var smo: i64 = sb_cat(pathbuf, 0, docroot); smo = sb_cat(pathbuf, smo, "/sitemap.xml" as *u8); pathbuf[smo]=0 as u8
195 let smfd: i64 = sys_openat_wr(pathbuf, 0x1a4)
196 if smfd >= 0 { sys_write(smfd, buf, o); sys_close(smfd) }
197
198 o = sb_cat(buf, 0, "User-agent: *\nAllow: /\nSitemap: https://" as *u8); o = sb_cat(buf, o, domain); o = sb_cat(buf, o, "/sitemap.xml\n" as *u8)
199 var rbo: i64 = sb_cat(pathbuf, 0, docroot); rbo = sb_cat(pathbuf, rbo, "/robots.txt" as *u8); pathbuf[rbo]=0 as u8
200 let rbfd: i64 = sys_openat_wr(pathbuf, 0x1a4)
201 if rbfd >= 0 { sys_write(rbfd, buf, o); sys_close(rbfd) }
202 return 0
203}
204
205// emit the shared editorial site nav (Home + one entry per practice area + Consult).
206func sb_ed_nav(buf: *u8, off: i64, brand: *u8, ncards: i64, ct: *i64, cs: *i64, hrefbuf: *u8, cur: i64) -> i64 {
207 var o: i64 = ed_firm_nav_open(buf, off, brand, "/" as *u8)
208 var home_cur: i64 = 0
209 if cur < 0 { home_cur = 1 }
210 o = ed_firm_nav_item(buf, o, "Home" as *u8, "/" as *u8, home_cur)
211 var i: i64 = 0
212 while i < ncards {
213 sb_phref(hrefbuf, cs[i] as *u8)
214 var isc: i64 = 0
215 if i == cur { isc = 1 }
216 o = ed_firm_nav_item(buf, o, ct[i] as *u8, hrefbuf, isc)
217 i = i + 1
218 }
219 o = ed_firm_nav_item(buf, o, "Consult" as *u8, "/#consult" as *u8, 0)
220 return ed_firm_nav_close(buf, o)
221}
222
223// THE WHOLE-SITE BUILD as a callable core (composed by the CLI main below AND the site-edit
224// daemon): read the .site blueprint at `config`, emit index + practice pages + sitemap + robots
225// into `docroot`. Returns ncards (>=1) on success, negative on failure. NO sys_exit here.
226func sb_build_site(config: *u8, docroot: *u8, domain: *u8, theme: i64) -> i64 {
227 let szp: *i64 = sys_mmap(16) as *i64
228 let text: *u8 = sys_read_file(config, szp)
229 if (text as i64) == 0 { sb_w("SITE-BUILD FAIL: cannot read config\n" as *u8); return 0 - 1 }
230 let tn: i64 = szp[0]
231
232 // ---- parse scalars (data-driven; content is the caller's) ----
233 let titlebase: *u8 = sys_mmap(512); sb_cg(text, tn, "title" as *u8, titlebase, 511)
234 let hdr: *u8 = sys_mmap(512); sb_cg(text, tn, "header" as *u8, hdr, 511)
235 let brand: *u8 = sys_mmap(160); sb_fld(hdr, 0, brand)
236 let hcta: *u8 = sys_mmap(160); sb_fld(hdr, 1, hcta)
237 let hhref: *u8 = sys_mmap(160); sb_fld(hdr, 2, hhref)
238 let hero: *u8 = sys_mmap(SB_MAGIC_1024); sb_cg(text, tn, "hero" as *u8, hero, 1023)
239 let hl: *u8 = sys_mmap(512); sb_fld(hero, 0, hl)
240 let hsub: *u8 = sys_mmap(512); sb_fld(hero, 1, hsub)
241 let search: *u8 = sys_mmap(512); sb_cg(text, tn, "search" as *u8, search, 511)
242 let saction: *u8 = sys_mmap(160); sb_fld(search, 0, saction)
243 let splace: *u8 = sys_mmap(256); sb_fld(search, 1, splace)
244 let footer: *u8 = sys_mmap(512); sb_cg(text, tn, "footer" as *u8, footer, 511)
245
246 // ---- collect cards / steps / sigs into one arena ----
247 let arena: *u8 = sys_mmap(SB_MAGIC_131072)
248 let apP: *i64 = sys_mmap(16) as *i64; apP[0]=0
249 let ct: *i64 = sys_mmap(8*32) as *i64
250 let cb: *i64 = sys_mmap(8*32) as *i64
251 let cs: *i64 = sys_mmap(8*32) as *i64
252 let ncards: i64 = sb_cards(text, tn, ct, cb, cs, arena, apP, 32)
253 let steps: *i64 = sys_mmap(8*8) as *i64
254 let nsteps: i64 = sb_single(text, tn, "step" as *u8, 4, steps, arena, apP, 3)
255 let sigs: *i64 = sys_mmap(8*16) as *i64
256 let nsigs: i64 = sb_single(text, tn, "sig" as *u8, 3, sigs, arena, apP, 12)
257
258 // ---- shared scratch (hoisted ONCE; reused per page) ----
259 let buf: *u8 = sys_mmap(SB_MAGIC_262144)
260 let pathbuf: *u8 = sys_mmap(512)
261 let hrefbuf: *u8 = sys_mmap(256)
262 let numbuf: *u8 = sys_mmap(8)
263 let stepttl: *u8 = sys_mmap(32)
264 let titlebuf2: *u8 = sys_mmap(320)
265
266 sys_mkdir(docroot, 0x1ed)
267 var pdo: i64 = sb_cat(pathbuf, 0, docroot); pdo = sb_cat(pathbuf, pdo, "/practice" as *u8); pathbuf[pdo]=0 as u8
268 sys_mkdir(pathbuf, 0x1ed)
269
270 var i: i64 = 0
271
272 // ================= EDITORIAL TIER =================
273 // `tier|editorial` selects the editorial presentation (nx_nishi_editorial -- the system behind /kitchen).
274 // ABSENT -> the original s-class path below runs untouched, so every other site this emitter builds keeps
275 // its current bytes. The site GRAPH (cards -> pages, nav, sitemap, robots) is shared: only presentation forks.
276 let tierv: *u8 = sys_mmap(64)
277 let has_tier: i64 = sb_cg(text, tn, "tier" as *u8, tierv, 63)
278 var ed_on: i64 = 0
279 if has_tier > 0 { if (tierv[0] as i64) == 101 { ed_on = 1 } }
280 if ed_on == 1 {
281 let eyeb: *u8 = sys_mmap(256); sb_cg(text, tn, "eyebrow" as *u8, eyeb, 255)
282 let hrow: *u8 = sys_mmap(SB_MAGIC_1024); sb_cg(text, tn, "hero" as *u8, hrow, 1023)
283 let eh1: *u8 = sys_mmap(512); sb_fld(hrow, 0, eh1)
284 let eem: *u8 = sys_mmap(256); sb_fld(hrow, 1, eem)
285 let ethe: *u8 = sys_mmap(768); sb_fld(hrow, 2, ethe)
286 let artv: *u8 = sys_mmap(512); sb_cg(text, tn, "art" as *u8, artv, 511)
287 let artk: *u8 = sys_mmap(16); sb_fld(artv, 0, artk)
288 let artl: *u8 = sys_mmap(256); sb_fld(artv, 1, artl)
289 var akind: i64 = 1
290 if (artk[0] as i64) >= 49 { akind = (artk[0] as i64) - 48 }
291 let ephone: *u8 = sys_mmap(128); sb_cg(text, tn, "phone" as *u8, ephone, 127)
292 let email: *u8 = sys_mmap(160); sb_cg(text, tn, "email" as *u8, email, 159)
293 let eaddr: *u8 = sys_mmap(256); sb_cg(text, tn, "address" as *u8, eaddr, 255)
294 let ehours: *u8 = sys_mmap(256); sb_cg(text, tn, "hours" as *u8, ehours, 255)
295 let eintake: *u8 = sys_mmap(256); sb_cg(text, tn, "intake" as *u8, eintake, 255)
296 let edisc: *u8 = sys_mmap(SB_MAGIC_1024); sb_cg(text, tn, "disclaimer" as *u8, edisc, 1023)
297 let ehint: *u8 = sys_mmap(256); sb_cg(text, tn, "hint" as *u8, ehint, 255)
298 // attorneys: name|role|bio|monogram|pending
299 let atts: *i64 = sys_mmap(8*12) as *i64
300 let natt: i64 = sb_single(text, tn, "attorney" as *u8, 8, atts, arena, apP, 12)
301 let afn: *u8 = sys_mmap(256)
302 let afr: *u8 = sys_mmap(256)
303 let afb: *u8 = sys_mmap(SB_MAGIC_1024)
304 let afm: *u8 = sys_mmap(16)
305 let afp: *u8 = sys_mmap(16)
306 let cbody: *u8 = sys_mmap(SB_MAGIC_1024)
307 let csubs: *u8 = sys_mmap(SB_MAGIC_1024)
308 let onesub: *u8 = sys_mmap(256)
309 let secn: *u8 = sys_mmap(8)
310 var sn: i64 = 1
311
312 // ---------------- index ----------------
313 // `paper|1` opts the whole site into the paper reading tier (measured-to-paper contrast, stillness,
314 // book measure). Absent -> the standard editorial palette, so no existing site changes.
315 let paperv: *u8 = sys_mmap(32)
316 let has_paper: i64 = sb_cg(text, tn, "paper" as *u8, paperv, 31)
317 var paper_on: i64 = 0
318 if has_paper > 0 { if (paperv[0] as i64) == 49 { paper_on = 1 } }
319
320 var o: i64 = ed_doc_open(buf, 0, titlebase)
321 o = ed_firm_css(buf, o)
322 if paper_on == 1 { o = ed_paper_css(buf, o) }
323 o = sb_ed_nav(buf, o, brand, ncards, ct, cs, hrefbuf, 0 - 1)
324 o = ed_firm_mast_open(buf, o, eyeb, eh1, eem, ethe, hcta, hhref, "Our expertise" as *u8, "#expertise" as *u8, ehint)
325 o = ed_artpanel(buf, o, akind, artl)
326 o = ed_firm_mast_close(buf, o)
327 o = ed_firm_search(buf, o, saction, splace, "Search this site" as *u8)
328
329 secn[0]=48 as u8; secn[1]=(48+sn) as u8; secn[2]=0 as u8; sn=sn+1
330 o = ed_sec_open(buf, o, secn, "How we help" as *u8)
331 o = ed_raw(buf, o, "<div class='grid' id='expertise'>" as *u8)
332 i = 0
333 while i < ncards {
334 sb_fld(cb[i] as *u8, 0, cbody)
335 sb_fld(cb[i] as *u8, 1, csubs)
336 sb_phref(hrefbuf, cs[i] as *u8)
337 o = ed_cell_open(buf, o, "Practice area" as *u8, ct[i] as *u8, cbody, hrefbuf)
338 // sub-items are a comma list in the card's 3rd field; emitted only when present, so a plain
339 // card never ships an empty <ul>.
340 if (csubs[0] as i64) != 0 {
341 o = ed_subs_open(buf, o)
342 var sp: i64 = 0; var so2: i64 = 0
343 while 1 == 1 {
344 let ch: i64 = csubs[sp] as i64
345 if ch == 44 { onesub[so2]=0 as u8; if so2>0 { o = ed_sub(buf, o, onesub, hrefbuf) } so2=0 }
346 if ch == 0 { onesub[so2]=0 as u8; if so2>0 { o = ed_sub(buf, o, onesub, hrefbuf) } break }
347 if ch != 44 { if ch != 32 { onesub[so2]=ch as u8; so2=so2+1 } }
348 if ch == 32 { if so2>0 { onesub[so2]=ch as u8; so2=so2+1 } }
349 sp=sp+1
350 }
351 o = ed_subs_close(buf, o)
352 }
353 o = ed_cell_close(buf, o, "Read more" as *u8, hrefbuf)
354 i=i+1
355 }
356 o = ed_raw(buf, o, "</div>" as *u8)
357 o = ed_sec_close(buf, o)
358
359 if nsteps > 0 {
360 secn[0]=48 as u8; secn[1]=(48+sn) as u8; secn[2]=0 as u8; sn=sn+1
361 o = ed_sec_open(buf, o, secn, "What working with us looks like" as *u8)
362 o = ed_raw(buf, o, "<ol class='steps'>" as *u8)
363 i = 0
364 while i < nsteps {
365 o = ed_raw(buf, o, "<li><span>" as *u8)
366 o = ed_esc(buf, o, steps[i] as *u8)
367 o = ed_raw(buf, o, "</span></li>" as *u8)
368 i=i+1
369 }
370 o = ed_raw(buf, o, "</ol>" as *u8)
371 o = ed_sec_close(buf, o)
372 }
373
374 if natt > 0 {
375 secn[0]=48 as u8; secn[1]=(48+sn) as u8; secn[2]=0 as u8; sn=sn+1
376 o = ed_sec_open(buf, o, secn, "Attorneys" as *u8)
377 o = ed_raw(buf, o, "<div class='roster' id='attorneys'>" as *u8)
378 i = 0
379 while i < natt {
380 sb_fld(atts[i] as *u8, 0, afn)
381 sb_fld(atts[i] as *u8, 1, afr)
382 sb_fld(atts[i] as *u8, 2, afb)
383 sb_fld(atts[i] as *u8, 3, afm)
384 sb_fld(atts[i] as *u8, 4, afp)
385 var pend: i64 = 0
386 if (afp[0] as i64) == 49 { pend = 1 }
387 o = ed_person(buf, o, afn, afr, afb, afm, pend)
388 i=i+1
389 }
390 o = ed_raw(buf, o, "</div>" as *u8)
391 o = ed_sec_close(buf, o)
392 }
393
394 secn[0]=48 as u8; secn[1]=(48+sn) as u8; secn[2]=0 as u8
395 o = ed_sec_open(buf, o, secn, "Request a consultation" as *u8)
396 o = ed_consult(buf, o, ephone, email, eaddr, ehours, eintake, "/" as *u8, "Request a consultation" as *u8, "We reply to every enquiry. Sending this does not create an attorney-client relationship." as *u8)
397 o = ed_sec_close(buf, o)
398 o = ed_ctabar(buf, o, hcta, "Free initial consultation" as *u8, "Request a consultation" as *u8, "#consult" as *u8, ephone)
399 o = ed_raw(buf, o, "<footer class='colophon'><p>" as *u8)
400 o = ed_esc(buf, o, footer)
401 o = ed_raw(buf, o, "</p><p>" as *u8)
402 o = ed_esc(buf, o, edisc)
403 o = ed_raw(buf, o, "</p></footer>" as *u8)
404 o = ed_doc_close(buf, o)
405 var eio: i64 = sb_cat(pathbuf, 0, docroot); eio = sb_cat(pathbuf, eio, "/index.html" as *u8); pathbuf[eio]=0 as u8
406 let eifd: i64 = sys_openat_wr(pathbuf, 0x1a4)
407 if eifd >= 0 { sys_write(eifd, buf, o); sys_close(eifd) }
408
409 // ---------------- one page per practice area ----------------
410 var epc: i64 = 0
411 while epc < ncards {
412 sb_fld(cb[epc] as *u8, 0, cbody)
413 var ept: i64 = sb_cat(titlebuf2, 0, ct[epc] as *u8); ept = sb_cat(titlebuf2, ept, " -- " as *u8); ept = sb_cat(titlebuf2, ept, brand); titlebuf2[ept]=0 as u8
414 o = ed_doc_open(buf, 0, titlebuf2)
415 o = ed_firm_css(buf, o)
416 if paper_on == 1 { o = ed_paper_css(buf, o) }
417 o = sb_ed_nav(buf, o, brand, ncards, ct, cs, hrefbuf, epc)
418 o = ed_firm_mast_open(buf, o, eyeb, ct[epc] as *u8, "" as *u8, cbody, hcta, "#consult" as *u8, "All practice areas" as *u8, "/#expertise" as *u8, ehint)
419 o = ed_artpanel(buf, o, akind, artl)
420 o = ed_firm_mast_close(buf, o)
421 o = ed_firm_search(buf, o, saction, splace, "Search this site" as *u8)
422 o = ed_sec_open(buf, o, "01" as *u8, "Other ways we help" as *u8)
423 o = ed_raw(buf, o, "<div class='grid'>" as *u8)
424 i = 0
425 while i < ncards {
426 if i != epc {
427 sb_fld(cb[i] as *u8, 0, csubs)
428 sb_phref(hrefbuf, cs[i] as *u8)
429 o = ed_cell_open(buf, o, "Practice area" as *u8, ct[i] as *u8, csubs, hrefbuf)
430 o = ed_cell_close(buf, o, "Read more" as *u8, hrefbuf)
431 }
432 i=i+1
433 }
434 o = ed_raw(buf, o, "</div>" as *u8)
435 o = ed_sec_close(buf, o)
436 o = ed_sec_open(buf, o, "02" as *u8, "Request a consultation" as *u8)
437 o = ed_consult(buf, o, ephone, email, eaddr, ehours, eintake, "/" as *u8, "Request a consultation" as *u8, "We reply to every enquiry. Sending this does not create an attorney-client relationship." as *u8)
438 o = ed_sec_close(buf, o)
439 o = ed_ctabar(buf, o, hcta, "Free initial consultation" as *u8, "Request a consultation" as *u8, "#consult" as *u8, ephone)
440 o = ed_raw(buf, o, "<footer class='colophon'><p>" as *u8)
441 o = ed_esc(buf, o, footer)
442 o = ed_raw(buf, o, "</p><p>" as *u8)
443 o = ed_esc(buf, o, edisc)
444 o = ed_raw(buf, o, "</p></footer>" as *u8)
445 o = ed_doc_close(buf, o)
446 var epo: i64 = sb_cat(pathbuf, 0, docroot); epo = sb_cat(pathbuf, epo, "/practice/" as *u8); epo = sb_cat(pathbuf, epo, cs[epc] as *u8); epo = sb_cat(pathbuf, epo, ".html" as *u8); pathbuf[epo]=0 as u8
447 let epfd: i64 = sys_openat_wr(pathbuf, 0x1a4)
448 if epfd >= 0 { sys_write(epfd, buf, o); sys_close(epfd) }
449 epc=epc+1
450 }
451
452 sb_emit_sitemap_robots(buf, pathbuf, docroot, domain, ncards, cs)
453 sb_w("SITE-BUILD OK [editorial]: index + " as *u8); sb_wn(ncards); sb_w(" practice pages + " as *u8); sb_wn(natt); sb_w(" attorneys + sitemap.xml + robots.txt -> " as *u8); sb_w(docroot); sb_w("\n" as *u8)
454 return ncards
455 }
456
457 // ================= INDEX =================
458 var o: i64 = sk_open_site(buf, 0, titlebase, brand, theme, "/" as *u8)
459 o = emit_nav(buf, o, ncards, ct, cs, hrefbuf)
460 o = sk_header_close(buf, o)
461 o = sk_hero(buf, o, brand, hl, " " as *u8, hsub, hcta, hhref, "Explore services" as *u8, "#services" as *u8, "Free consultation -- no obligation." as *u8)
462 o = sk_search(buf, o, saction, splace)
463 o = sk_section_open(buf, o, "services" as *u8, "What we do" as *u8, "How we help" as *u8, "Choose the area that fits your matter -- each opens a dedicated page." as *u8)
464 i = 0
465 while i < ncards { sb_phref(hrefbuf, cs[i] as *u8); o = sk_card_link(buf, o, SB_ICON, ct[i] as *u8, cb[i] as *u8, hrefbuf); i=i+1 }
466 o = sk_section_close(buf, o)
467 if nsteps > 0 {
468 o = sk_section_open(buf, o, "how" as *u8, "Simple process" as *u8, "Resolve it in 3 steps" as *u8, "From first contact to resolution." as *u8)
469 i = 0
470 while i < nsteps {
471 numbuf[0]=(49+i) as u8; numbuf[1]=0 as u8
472 var so: i64 = sb_cat(stepttl, 0, "Step " as *u8); so = sb_cat(stepttl, so, numbuf); stepttl[so]=0 as u8
473 o = sk_card(buf, o, numbuf, stepttl, steps[i] as *u8)
474 i=i+1
475 }
476 o = sk_section_close(buf, o)
477 }
478 o = emit_trust(buf, o, nsigs, sigs)
479 o = sk_cta(buf, o, "Get started" as *u8, hcta, "Most matters start with one short call." as *u8, hcta, hhref, "Browse services" as *u8, "#services" as *u8)
480 o = sk_close(buf, o, footer, brand)
481 var io: i64 = sb_cat(pathbuf, 0, docroot); io = sb_cat(pathbuf, io, "/index.html" as *u8); pathbuf[io]=0 as u8
482 let ifd: i64 = sys_openat_wr(pathbuf, 0x1a4)
483 if ifd >= 0 { sys_write(ifd, buf, o); sys_close(ifd) }
484
485 // ================= PRACTICE PAGES =================
486 var pc: i64 = 0
487 while pc < ncards {
488 o = 0
489 var pt: i64 = sb_cat(titlebuf2, 0, ct[pc] as *u8); pt = sb_cat(titlebuf2, pt, " -- " as *u8); pt = sb_cat(titlebuf2, pt, brand); titlebuf2[pt]=0 as u8
490 o = sk_open_site(buf, o, titlebuf2, brand, theme, "/" as *u8)
491 o = emit_nav(buf, o, ncards, ct, cs, hrefbuf)
492 o = sk_header_close(buf, o)
493 o = sk_hero(buf, o, brand, ct[pc] as *u8, " " as *u8, cb[pc] as *u8, hcta, hhref, "All services" as *u8, "/#services" as *u8, "Serving Utah -- free initial consultation." as *u8)
494 o = sk_search(buf, o, saction, splace)
495 o = sk_section_open(buf, o, "related" as *u8, "More help" as *u8, "Related services" as *u8, "Other ways we help Utah families and businesses." as *u8)
496 i = 0
497 while i < ncards {
498 if i != pc { sb_phref(hrefbuf, cs[i] as *u8); o = sk_card_link(buf, o, SB_ICON, ct[i] as *u8, cb[i] as *u8, hrefbuf) }
499 i=i+1
500 }
501 o = sk_section_close(buf, o)
502 o = emit_trust(buf, o, nsigs, sigs)
503 o = sk_cta(buf, o, "Get started" as *u8, hcta, "Tell us what happened -- we will call you back, usually the same day." as *u8, hcta, hhref, "Back to home" as *u8, "/" as *u8)
504 o = sk_close(buf, o, footer, brand)
505 var po: i64 = sb_cat(pathbuf, 0, docroot); po = sb_cat(pathbuf, po, "/practice/" as *u8); po = sb_cat(pathbuf, po, cs[pc] as *u8); po = sb_cat(pathbuf, po, ".html" as *u8); pathbuf[po]=0 as u8
506 let pfd: i64 = sys_openat_wr(pathbuf, 0x1a4)
507 if pfd >= 0 { sys_write(pfd, buf, o); sys_close(pfd) }
508 pc=pc+1
509 }
510
511 // ================= sitemap.xml + robots.txt =================
512 sb_emit_sitemap_robots(buf, pathbuf, docroot, domain, ncards, cs)
513
514 sb_w("SITE-BUILD OK: index + " as *u8); sb_wn(ncards); sb_w(" practice pages + sitemap.xml (" as *u8); sb_wn(ncards+1); sb_w(" urls) + robots.txt -> " as *u8); sb_w(docroot); sb_w("\n" as *u8)
515 return ncards
516}
517
518func main(argc: i64, argv: *i64) -> i64 {
519 if argc < 4 {
520 sb_w("usage: nx_site_build <config.site> <docroot> <domain> [theme:0|1]\n" as *u8)
521 sys_exit(2); return 2
522 }
523 let config: *u8 = argv[1] as *u8
524 let docroot: *u8 = argv[2] as *u8
525 let domain: *u8 = argv[3] as *u8
526 var theme: i64 = 0
527 if argc > 4 { let a4: *u8 = argv[4] as *u8; if (a4[0] as i64) == 49 { theme = 1 } }
528 let rc: i64 = sb_build_site(config, docroot, domain, theme)
529 if rc < 0 { sys_exit(1); return 1 }
530 sys_exit(0); return 0
531}