code wiki / _hdl_build / nx_kit_market.nx

nx_kit_market.nx source

↩ module page · 193 lines · 15250 B

1// nx_kit_market.nx -- the MARKET design family: a real storefront grammar for commerce briefs (shop, 2// store, boutique, catalog, merch). Fourth first-class family after marketing, Counsel and Journal. 3// 4// What a commerce page actually needs and the marketing grammar never had: a UTILITY BAR (search + cart) 5// above the masthead, CATEGORY CHIPS for browsing, a PRODUCT GRID whose cards carry a visual, a name, a 6// short line, a REAL PRICE and an add-to-cart action, and a SHIPPING/RETURNS/SECURE trust row -- the row 7// that decides whether a stranger buys. What it must NOT have: the SaaS gradient hero, the 3-up feature 8// card grid, the pricing-TIER band (tiers are for SaaS plans, not for beans). 9// 10// ★ Product visuals are GENERATED, never stock: each card gets a deterministic two-stop gradient panel 11// derived from the seed + card index, so a catalog looks like a catalog before a single photograph exists 12// (and the gen-img pipeline can replace the panel later without touching this grammar). 13// Token-driven from the generated palette, both themes, reduced-motion, doc tier included from the start 14// (the round-9 two-languages lesson, applied up front). Pure emit. license_tier: ORIGINAL 15import "nx_uigen_l1.nx" 16import "nx_kit_shell.nx" 17 18func km_styles(buf: *u8, off: i64, hue: i64) -> i64 { 19 var o: i64 = he_puts(buf, off, "<style>:root{" as *u8) 20 o = l1_tok(buf, o, "--km-ink:" as *u8, hue, 18, 13) 21 o = l1_tok(buf, o, "--km-bg:" as *u8, hue, 10, 98) 22 o = l1_tok(buf, o, "--km-card:" as *u8, hue, 8, 100) 23 o = l1_tok(buf, o, "--km-line:" as *u8, hue, 14, 88) 24 o = l1_tok(buf, o, "--km-quiet:" as *u8, hue, 10, 44) 25 o = l1_tok(buf, o, "--km-buy:" as *u8, hue, 64, 34) 26 o = l1_tok(buf, o, "--km-buy2:" as *u8, hue, 60, 44) 27 o = he_puts(buf, o, "}@media(prefers-color-scheme:dark){:root{" as *u8) 28 o = l1_tok(buf, o, "--km-ink:" as *u8, hue, 14, 92) 29 o = l1_tok(buf, o, "--km-bg:" as *u8, hue, 16, 9) 30 o = l1_tok(buf, o, "--km-card:" as *u8, hue, 14, 14) 31 o = l1_tok(buf, o, "--km-line:" as *u8, hue, 14, 24) 32 o = l1_tok(buf, o, "--km-quiet:" as *u8, hue, 10, 66) 33 o = l1_tok(buf, o, "--km-buy:" as *u8, hue, 60, 60) 34 o = l1_tok(buf, o, "--km-buy2:" as *u8, hue, 56, 68) 35 o = he_puts(buf, o, "}}" as *u8) 36 o = he_puts(buf, o, "body{background:var(--km-bg);color:var(--km-ink);font:16px/1.6 system-ui,-apple-system,\"Segoe UI\",sans-serif}" as *u8) 37 // utility bar sits ABOVE the shell header; the shell header becomes the shop masthead 38 o = he_puts(buf, o, ".km-util{background:var(--km-ink);color:var(--km-bg);font-size:.78rem}.km-util .wrap{display:flex;gap:16px;align-items:center;justify-content:space-between;padding:9px 24px;flex-wrap:wrap}.km-util form{display:flex;gap:0;flex:1;max-width:420px}.km-util input{flex:1;min-width:120px;font:inherit;padding:7px 12px;border:0;border-radius:2px 0 0 2px;background:var(--km-bg);color:var(--km-ink)}.km-util button{font:inherit;font-weight:640;border:0;border-radius:0 2px 2px 0;padding:7px 15px;background:var(--km-buy);color:#fff;cursor:pointer}.km-util a{color:var(--km-bg);text-decoration:none;font-weight:640;letter-spacing:.04em}" as *u8) 39 o = kx_shell_reset(buf, o) 40 o = he_puts(buf, o, "header.site{background:var(--km-bg);border-bottom:1px solid var(--km-line)}header.site .bar{padding:16px 24px}header.site .brand{font-weight:800;letter-spacing:-.02em;color:var(--km-ink)}header.site .brand .dot{border-radius:3px;background:var(--km-buy)}header.site nav.top a{font-size:.86rem;font-weight:600;color:var(--km-quiet)}header.site nav.top a:hover{color:var(--km-ink)}" as *u8) 41 o = he_puts(buf, o, "footer.site{border-top:1px solid var(--km-line)}" as *u8) 42 // category chips 43 o = he_puts(buf, o, ".km-chips{padding:18px 0 4px}.km-chips .wrap{display:flex;gap:9px;flex-wrap:wrap}.km-chip{display:inline-block;border:1px solid var(--km-line);background:var(--km-card);border-radius:999px;padding:7px 15px;font-size:.82rem;font-weight:600;text-decoration:none;color:var(--km-ink)}.km-chip:hover{border-color:var(--km-buy);color:var(--km-buy)}" as *u8) 44 // promo strip -- one line of value, not a gradient hero 45 o = he_puts(buf, o, ".km-promo{background:var(--km-card);border-top:1px solid var(--km-line);border-bottom:1px solid var(--km-line);margin-top:14px}.km-promo .wrap{display:flex;gap:14px;align-items:baseline;flex-wrap:wrap;padding:16px 24px}.km-promo b{font-size:1.05rem}.km-promo span{color:var(--km-quiet);font-size:.92rem}" as *u8) 46 // product grid 47 o = he_puts(buf, o, ".km-shop{padding:clamp(24px,4vw,44px) 0}.km-shop h1{font-size:1.22rem;font-weight:750;letter-spacing:-.01em;margin:0 0 4px}.km-shop .km-sub{color:var(--km-quiet);margin:0 0 22px;font-size:.95rem}" as *u8) 48 o = he_puts(buf, o, ".km-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:clamp(14px,2vw,24px)}" as *u8) 49 o = he_puts(buf, o, ".km-item{background:var(--km-card);border:1px solid var(--km-line);border-radius:10px;overflow:hidden;display:flex;flex-direction:column}.km-shot{aspect-ratio:4/3;display:block}.km-item h2{font-size:1rem;font-weight:680;margin:14px 15px 3px}.km-item p{color:var(--km-quiet);font-size:.86rem;margin:0 15px 10px;flex:1}.km-row{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 15px 15px}.km-price{font-weight:750;font-size:1.06rem;font-variant-numeric:tabular-nums}.km-add{font:inherit;font-size:.83rem;font-weight:660;border:0;border-radius:6px;padding:9px 14px;background:var(--km-buy);color:#fff;cursor:pointer;text-decoration:none}.km-add:hover{background:var(--km-buy2)}.km-add:focus-visible{outline:3px solid var(--km-buy2);outline-offset:2px}" as *u8) 50 // shipping / returns / secure -- the row that closes the sale 51 o = he_puts(buf, o, ".km-trust{border-top:1px solid var(--km-line);margin-top:8px;padding:22px 0 30px}.km-trust .wrap{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:18px}.km-trust b{display:block;font-size:.93rem}.km-trust span{color:var(--km-quiet);font-size:.85rem}" as *u8) 52 o = he_puts(buf, o, "@media(prefers-reduced-motion:reduce){.km-item,.km-add{transition:none;animation:none}}</style>" as *u8) 53 return o 54} 55 56// TRUE when a caller-supplied destination is absent. An affordance with nowhere to go is NOT drawn. 57func km_absent(s: *u8) -> i64 { 58 if (s as i64) == 0 { return 1 } 59 if s[0] == (0 as u8) { return 1 } 60 if s[0] == (32 as u8) { if s[1] == (0 as u8) { return 1 } } 61 return 0 62} 63// utility bar: ship note + OPTIONAL search + OPTIONAL cart. 64// A CONTROL THAT GOES NOWHERE IS A FABRICATED FEATURE. This bar used to emit unconditionally a search 65// form whose action was index.html (the q was discarded) and the literal text "Cart (0)" -- a counter 66// that could never be anything but 0, linking to the contact page. Both are now caller-driven and are 67// OMITTED when no destination is given, so a storefront with no commerce backend renders no cart at 68// all rather than a picture of one. (operator 2026-08-10: "cart and a bunch of things havent been 69// built from the first bit up") 70func km_util2(buf: *u8, off: i64, ship: *u8, search_action: *u8, cart_href: *u8, cart_label: *u8) -> i64 { 71 var o: i64 = he_puts(buf, off, "<div class=\"km-util\"><div class=\"wrap\"><span>" as *u8) 72 o = he_puts(buf, o, ship) 73 o = he_puts(buf, o, "</span>" as *u8) 74 if km_absent(search_action) == 0 { 75 o = he_puts(buf, o, "<form role=\"search\" method=\"get\" action=\"" as *u8) 76 o = he_puts(buf, o, search_action) 77 o = he_puts(buf, o, "\"><input type=\"search\" name=\"q\" aria-label=\"Search the shop\" placeholder=\"Search the shop\"><button type=\"submit\">Search</button></form>" as *u8) 78 } 79 if km_absent(cart_href) == 0 { 80 o = he_puts(buf, o, "<a href=\"" as *u8) 81 o = he_puts(buf, o, cart_href) 82 o = he_puts(buf, o, "\">" as *u8) 83 if km_absent(cart_label) == 0 { o = he_puts(buf, o, cart_label) } 84 if km_absent(cart_label) == 1 { o = he_puts(buf, o, "Cart" as *u8) } 85 o = he_puts(buf, o, "</a>" as *u8) 86 } 87 return he_puts(buf, o, "</div></div>" as *u8) 88} 89// legacy arity (rule 19 additive). Resolves to the HONEST rendering, never the fabricated one. 90func km_util(buf: *u8, off: i64, ship: *u8) -> i64 { 91 return km_util2(buf, off, ship, "" as *u8, "" as *u8, "" as *u8) 92} 93func km_chips_open(buf: *u8, off: i64) -> i64 { return he_puts(buf, off, "<nav class=\"km-chips\" aria-label=\"Categories\"><div class=\"wrap\">" as *u8) } 94// ONE category chip. Every chip used to link to index.html, so four different labels all led to the 95// same unfiltered page -- navigation that reports a filter it does not apply. A chip is now emitted 96// ONLY when the caller supplies a real destination; with none it emits NOTHING, because an inert 97// control that looks clickable is the defect, not the absence of one. 98func km_chip2(buf: *u8, off: i64, label: *u8, href: *u8) -> i64 { 99 if km_absent(href) == 1 { return off } 100 var o: i64 = he_puts(buf, off, "<a class=\"km-chip\" href=\"" as *u8) 101 o = he_puts(buf, o, href) 102 o = he_puts(buf, o, "\">" as *u8) 103 o = he_puts(buf, o, label) 104 return he_puts(buf, o, "</a>" as *u8) 105} 106func km_chip(buf: *u8, off: i64, label: *u8) -> i64 { 107 return km_chip2(buf, off, label, "" as *u8) 108} 109func km_chips_close(buf: *u8, off: i64) -> i64 { return he_puts(buf, off, "</div></nav>" as *u8) } 110func km_promo(buf: *u8, off: i64, headline: *u8, sub: *u8) -> i64 { 111 var o: i64 = he_puts(buf, off, "<section class=\"km-promo\"><div class=\"wrap\"><b>" as *u8) 112 o = he_puts(buf, o, headline) 113 o = he_puts(buf, o, "</b><span>" as *u8) 114 o = he_puts(buf, o, sub) 115 return he_puts(buf, o, "</span></div></section>" as *u8) 116} 117func km_shop_open(buf: *u8, off: i64, h2: *u8, sub: *u8) -> i64 { 118 // THE PAGE'S ONE h1. A storefront index/catalog previously had NO h1 at all (nx_page_verify: 119 // "A11Y h1-count=0 (want exactly 1)", a11y-issues=1 on every market-family page ever emitted, 120 // while the doc-tier about/contact scored 0). The shop heading IS the page heading, so it takes 121 // h1 and the item cards drop h3->h2, giving h1 -> h2* : exactly one h1 and no skipped level. 122 var o: i64 = he_puts(buf, off, "<section class=\"km-shop\"><div class=\"wrap\"><h1>" as *u8) 123 o = he_puts(buf, o, h2) 124 o = he_puts(buf, o, "</h1><p class=\"km-sub\">" as *u8) 125 o = he_puts(buf, o, sub) 126 return he_puts(buf, o, "</p><div class=\"km-grid\">" as *u8) 127} 128// one product card. The visual is a GENERATED two-stop gradient derived from hue + index -- a catalog 129// that looks like a catalog before any photograph exists, and never a stock image. 130func km_item2(buf: *u8, off: i64, hue: i64, idx: i64, name: *u8, desc: *u8, price: *u8, buy_href: *u8, buy_label: *u8) -> i64 { 131 // a CATALOG must look like one shop, not a rainbow: drift the hue only slightly per card (±11deg) 132 // and carry the variation in LIGHTNESS instead. The render caught a coffee shop selling a teal bag. 133 var h1: i64 = hue + idx * 11 134 while h1 >= 360 { h1 = h1 - 360 } 135 var h2: i64 = h1 + 14 136 while h2 >= 360 { h2 = h2 - 360 } 137 let l1v: i64 = 76 - (idx % 3) * 7 138 let l2v: i64 = 50 - (idx % 2) * 6 139 var o: i64 = he_puts(buf, off, "<article class=\"km-item\"><svg class=\"km-shot\" viewBox=\"0 0 120 90\" role=\"img\" aria-label=\"\" preserveAspectRatio=\"none\"><defs><linearGradient id=\"g" as *u8) 140 o = l1_put_u8(buf, o, idx) 141 o = he_puts(buf, o, "\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\"><stop offset=\"0\" stop-color=\"" as *u8) 142 o = l1_rgb(buf, o, h1, 38, l1v) 143 o = he_puts(buf, o, "\"/><stop offset=\"1\" stop-color=\"" as *u8) 144 o = l1_rgb(buf, o, h2, 48, l2v) 145 o = he_puts(buf, o, "\"/></linearGradient></defs><rect width=\"120\" height=\"90\" fill=\"url(&#35;g" as *u8) 146 o = l1_put_u8(buf, o, idx) 147 o = he_puts(buf, o, ")\"/></svg><h2>" as *u8) 148 o = he_puts(buf, o, name) 149 o = he_puts(buf, o, "</h2><p>" as *u8) 150 o = he_puts(buf, o, desc) 151 o = he_puts(buf, o, "</p><div class=\"km-row\"><span class=\"km-price\">" as *u8) 152 o = he_puts(buf, o, price) 153 // THE BUY CONTROL. It used to be, unconditionally, a link to contact.html labelled "Add to cart" 154 // -- a button that added nothing to anything. Drawn only when the caller names a real destination. 155 if km_absent(buy_href) == 0 { 156 o = he_puts(buf, o, "</span><a class=\"km-add\" href=\"" as *u8) 157 o = he_puts(buf, o, buy_href) 158 o = he_puts(buf, o, "\">" as *u8) 159 if km_absent(buy_label) == 0 { o = he_puts(buf, o, buy_label) } 160 if km_absent(buy_label) == 1 { o = he_puts(buf, o, "View" as *u8) } 161 o = he_puts(buf, o, "</a>" as *u8) 162 } 163 if km_absent(buy_href) == 1 { o = he_puts(buf, o, "</span>" as *u8) } 164 o = he_puts(buf, o, "</div></article>" as *u8) 165 return o 166} 167// legacy arity (rule 19 additive): price, no buy control -- the honest rendering for a catalog whose 168// commerce backend does not exist yet. 169func km_item(buf: *u8, off: i64, hue: i64, idx: i64, name: *u8, desc: *u8, price: *u8) -> i64 { 170 // A STOREFRONT MUST HAVE A BUY CONTROL -- nx_uigen_site_gate T4/T13 count one per priced item and 171 // are RIGHT to. The defect was never that the control existed, it was that it LIED: labelled "Add 172 // to cart" with no cart anywhere in the system. Destination unchanged and still resolves; the LABEL 173 // now states what actually happens. When a real cart exists, callers pass buy_href to km_item2. 174 return km_item2(buf, off, hue, idx, name, desc, price, "contact.html" as *u8, "Enquire" as *u8) 175} 176func km_shop_close(buf: *u8, off: i64) -> i64 { return he_puts(buf, off, "</div></div></section>" as *u8) } 177func km_trust(buf: *u8, off: i64, a1: *u8, b1: *u8, a2: *u8, b2: *u8, a3: *u8, b3: *u8) -> i64 { 178 var o: i64 = he_puts(buf, off, "<section class=\"km-trust\"><div class=\"wrap\"><div><b>" as *u8) 179 o = he_puts(buf, o, a1); o = he_puts(buf, o, "</b><span>" as *u8); o = he_puts(buf, o, b1) 180 o = he_puts(buf, o, "</span></div><div><b>" as *u8) 181 o = he_puts(buf, o, a2); o = he_puts(buf, o, "</b><span>" as *u8); o = he_puts(buf, o, b2) 182 o = he_puts(buf, o, "</span></div><div><b>" as *u8) 183 o = he_puts(buf, o, a3); o = he_puts(buf, o, "</b><span>" as *u8); o = he_puts(buf, o, b3) 184 return he_puts(buf, o, "</span></div></div></section>" as *u8) 185} 186// doc tier so about/contact stay in-family 187func km_doc_styles(buf: *u8, off: i64) -> i64 { 188 var o: i64 = he_puts(buf, off, "<style>.doc h1{font-weight:800;letter-spacing:-.022em}.doc .lede{margin:0 0 1.1em;max-width:60ch;color:var(--km-quiet)}" as *u8) 189 o = he_puts(buf, o, ".doc .callout{background:var(--km-card);border-left:3px solid var(--km-buy);border-radius:0 8px 8px 0}" as *u8) 190 o = he_puts(buf, o, ".field input,.field textarea{border-radius:6px;border:1px solid var(--km-line);background:var(--km-card)}.field input:focus-visible,.field textarea:focus-visible{outline:none;border-color:var(--km-buy);box-shadow:0 0 0 3px var(--km-line)}" as *u8) 191 o = he_puts(buf, o, ".btnp{background:var(--km-buy);border-radius:6px;box-shadow:none}.btnp:hover{background:var(--km-buy2);transform:none}</style>" as *u8) 192 return o 193}