Skip to content

About

technext.asia - website of TechNext Pte. Ltd., Odoo Partner in Singapore. Static site with a Python build, deployed on Vercel.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

738 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

technext.asia

Production website for TechNext Pte. Ltd. — Odoo Partner, Singapore. Static HTML/CSS/JS generated by a small Python build. No framework, no build step on the host.

Live: https://technext.asia · Hosting: Vercel project technext-website (auto-deploys master)

Edit → build → ship

# 1. edit a page partial (or assets/css/site.css, assets/js/*.js, _src/sitedata.py)
# 2. rebuild everything: pages, sitemap.xml, Nexi (nexi-app.html + assets/js/nexi-app.js) AND vercel.json
python _src/build.py
# 3. preview locally, exactly as Vercel serves it (clean URLs + the real security headers)
python _src/serve.py 3960        # http://127.0.0.1:3960
# 4. static audit: links, SEO, headings
python _src/audit.py
# 5. commit the partial AND the generated files, open a PR, merge -> Vercel deploys

Never edit the generated .html files at the repo root or in solutions/ industries/ odoo/ directly — the next build overwrites them. Edit the partial in _src/pages/.

What Where
Pages (one partial per page, <!--meta {...}--> header) _src/pages/
Layout, header, mega menus, footer, Google tags _src/build.py
Navigation, company facts, icons, tracking IDs _src/sitedata.py
Redirects + security headers (generated) _src/make_vercel.py → vercel.json
Design tokens and all styles assets/css/site.css
Menus, forms, reveals, image fallbacks assets/js/site.js
Cookie consent + first-party visitor context assets/js/consent.js
Nexi, the AI companion: the full chatbot on /nexi (knowledge base, 3D robot, tours) _src/nexi/app.html → _src/make_nexi.py → nexi-app.html + assets/js/nexi-app.js
Nexi roaming the home hero (desktop only) assets/js/nexi.js (loader) + assets/js/nexi-bot.js
Odoo module pages content python _src/fetch_odoo.py → _src/apps_content.json

Page meta: {"title", "desc", "out", "nav", "head"?, "body"?, "scripts"?} — scripts is a list of files. {{ROOT}} is the relative path to the site root; {{icon:name}} inlines an icon from sitedata.ICONS (an unknown name fails the build on purpose).

Things that will bite you

  • The CSP allows inline scripts by hash, not 'unsafe-inline'. vercel.json is regenerated on every build, so changing an inline snippet is fine — as long as you rebuild before committing. Do not add inline on*= handlers anywhere: the CSP blocks them. Use listeners in a .js file.
  • Google tags must stay in the homepage <head> in their standard form. Search Console verifies site ownership through the GA4 (G-E7P849N9JY) and Tag Manager snippets. Removing or rewriting them can cost you ownership of the Search Console property.
  • New third-party resources need a CSP entry in _src/make_vercel.py, or the browser blocks them. Test with python _src/serve.py — it applies the real headers.
  • Clean URLs. Links are extensionless (/solutions/ai), canonicals too. build.py strips .html from internal links; Vercel's cleanUrls resolves them.
  • Retired URLs (the previous site's blog, careers, gallery, /ai-article/…, /contact, /lp/, …) redirect to the closest v2 page via make_vercel.py. Never delete a redirect: those URLs are indexed and linked.
  • CNAME is a leftover from GitHub Pages hosting and is kept deliberately; it is not deployed.
  • App page URLs use Odoo's app names (/odoo/apps/inventory, not the module stock): the map is APP_SLUG in _src/sitedata.py. The build rewrites every internal link, make_vercel.py 308-redirects the old module-name URLs, and assets/js/app-slugs.js (generated) gives the scripts the same map. A new app whose Odoo name differs from its module needs an entry there.
  • The intro plays on the home page only (INTRO_HEAD / INTRO_BODY in _src/build.py), so visitors who land on any other page from search see it straight away.

How a page is put together

Every page is the same shell (LAYOUT in _src/build.py): the header with the mega menus, <main> with the page partial, the footer, then the fixed side tabs (Set up meeting · Let's Talk · Ask Nexi) and the Let's Talk panel. One <h1> per page; decorative layers (canvases, SVG scenes, the 3D robot) are aria-hidden.

The home hero is layered, bottom to top:

z Layer What it is
0 .hero-bg grid, three drifting auroras, the pointer spotlight, the particles canvas (hero.js)
1 .hero-track the three slides, which share one grid cell so the hero never changes height; the active one tilts toward the cursor
3 .nexi Nexi, the roaming companion (desktop only): a small WebGL window that travels with the robot
4 .hero-marquee the app marquee
5 .hero-ctl arrows and progress pills

Slide scenes (orbit, process map, launch path) are drawn by assets/js/hero-scenes.js; the carousel, camera moves, tilt and particles by assets/js/hero.js. Inner pages use a data-stage-hero hero with its own illustration (assets/css/stage.css, assets/js/stage.js) and interactive demos (assets/js/demo-*.js, all driven by demo-kit.js).

Nexi exists twice. On the home hero it only roams and introduces itself (nexi.js is a 2.8 KB loader that fetches Three.js and nexi-bot.js on desktop only; phones download nothing else). The full chatbot is /nexi: a normal page that frames nexi-app.html (noindex; opened directly it forwards to /nexi), built from _src/nexi/app.html by _src/make_nexi.py with the site's page index written in, so Nexi knows every page. Its Let's Talk button asks the page around it to open the form (postMessage, same origin only).

Industry worlds (/industries/*)

Industry pages are drawn as "worlds" in the look of the Nexi Explains videos: a live hero scene (canvas) with the industry's own room, props and two sample staff, Nexi in the industry's costume giving a guided tour of six hotspots, a six-station workflow with animated scenes, and every section skinned for the industry. Each world is self-contained and must look like no other world:

What Where
Engine (shared): hero canvas, cast, tour, caption, workflow, picker assets/js/industry-world.js, assets/css/industry-world.css
The world's scene, props, cast and toys assets/js/worlds/<key>.js
The world's palette and section designs assets/css/worlds/<key>.css
Six workflow scenes, sample records, phone copy (META) _src/vignettes/<key>.py (discovered by _src/worlds.py)
Nexi's poses in the costume (+ poses.json) assets/img/industries/<key>/
Hero markup (hotspots, cast, toys, lines) and the page _src/pages/industries/<key>.html

Phone copy: <span class="m-full">…</span><span class="m-short">…</span> shows the short version under 768px. Every name and figure in a world is sample data and is labelled as such.

Worlds so far: Medical (clinic), Travel (airport terminal), Retail (shop floor), Ecommerce (packing room), Construction (building site), F&B (restaurant with an open kitchen).

  • Beyond the frame: paintBack(g, ext) and paintFront(g, ext) get ext {l, r, t, b}, the whole hero in set units, and S.ext holds the same. Every world fills its margins (left of the frame, behind the title card, right of it, and the ground in front) with props and passers-by (K.walker), so wide desktops look busy.

  • "View the scene" (.ixw-peek, added by the engine, desktop only) hides the title card and the caption so the whole scene shows; pressing it again brings them back.

  • No indicator dots. Hotspots (data-hot), toys (data-toy) and staff (data-cast) are invisible areas over the drawn objects: --x/--y is the centre and --w/--h the size, in set units. Pointing at one glows the object (glow[key] in the world file) and pops its .ixw-hot-lab label. Pointing at a staff member makes them smile and hop. Small objects still get a 32 px touch area.

  • Every world has its own button family, in the spirit of the comic buttons on /nexi-explains: an ink outline and a hard lip you press onto, shaped by the theme. Medical uses capsules, Travel boarding passes and luggage tags, Retail price tags and receipts, Ecommerce taped parcels, shipping labels and bubble wrap. These rules are scoped to main so the site header keeps its own buttons. Labels take --lab-bg, --lab-bd, --lab-lip, --lab-tail and --lab-r.

  • The engine options a world can set:

    • windowBehind with paintFrame(): a view outside the glass (clouds, jets) passes behind the props.
    • motes: false: turns off the floating dust specks.
    • hit(x, y, S, t, onBtn): makes something moving in the canvas tappable even over a button.
  • The cast kit's outfit options:

    • outfits: scrubs, coat, blazer, tee, hoodie
    • hats: sunhat, cap
    • extras: apron, headset
    • things held: clipboard, passport, bags, box

Smoothness rules (keep these, the home page depends on them)

  • Animate transform and opacity only. A filter or backdrop-filter in the same animation keeps it off the compositor: the camera moves keep their blur in separate keyframes on the parent .slide.
  • No backdrop-filter over moving content (it re-blurs every frame); use a slightly more opaque background.
  • No big blur filters on moving blobs: the auroras bake their blur into a radial gradient.
  • Everything stops off screen: the hero gets .is-off (all its animations pause, particles and autoplay stop), demos get .is-offscreen, scenes and rAF loops check an IntersectionObserver.
  • Frame loops never read layout: paths are sampled into point tables once (no getPointAtLength per frame), pointer handlers cache the element's box and work once per frame, the spotlight moves by transform.
  • No lazy loading (owner's decision): every image loads with the page. Images below the fold carry fetchpriority="low" so they never delay the first screen.
  • Nexi renders into a canvas three times its own size that follows it, compiles its shaders one per frame before its first frame, parks only where it covers no text or button, and fades and lets clicks through while flying across text.

Forms → Odoo CRM

Every form (the Let's Talk panel on every page — Nexi on /nexi opens it too — /get-started, /free-website-with-odoo and the quotation builder) posts only to /api/lead (api/lead.py, a Vercel Python function); FormSubmit was removed on 16 Oct 2026 (with sales@ feeding the CRM it would have made duplicate leads). The function files a lead in the Odoo CRM at technext.odoo.com: no salesperson (leads are never auto-assigned), department (sales team) Marketing Dept, source Website (or Paid Ads after an ad click), medium Website, tags Website + Web form: <form>, and an internal note with the summary emailed to hello@technext.asia only (no @mentions; 4 Nov 2026). Vercel env vars: ODOO_API_KEY (required, never commit it — the repo is public), ODOO_URL. The thank-you shows only once the CRM accepted the lead; on failure the visitor is shown sales@technext.asia to email. An accepted submission fires the Google Ads lead conversion and a GA4 generate_lead event (only if the visitor accepted cookies — Consent Mode).

Brand rules

  • Brand blue #3167CA. Partner wording is Odoo Ready Partner, never "Certified".
  • Approved figures only: 10+ countries, 11+ enterprise clients, 4 core AI disciplines. No founding year, no project counts, no ISO claims.
  • Case studies are placeholders (noindex) until each client approves publication.

About

technext.asia - website of TechNext Pte. Ltd., Odoo Partner in Singapore. Static site with a Python build, deployed on Vercel.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages