- Reveal: in-viewport [data-sc-reveal]/schematic elements now reveal on the
first animation frame (getBoundingClientRect pass) and the safety-net timer
drops 1600ms -> 400ms (under the 1500ms capture settle). Fixes the ~10%
opacity above-the-fold KPI row + benchmark table and the washed-out About
"Recent releases" console, in both modes and for no-JS/no-scroll.
- Fonts: @font-face for Space Grotesk / Inter / JetBrains Mono against the
served /templates/scifi-clean/fonts/*.woff2 paths + typography refs in all
four presets (bundled_fonts is CMS dead code). --font-heading now resolves
to Space Grotesk live.
- Lexer trap: contact-form CSS `){#` -> `){ #` (was failing whole-theme
registration: "Newline not permitted in a single-line comment").
- Seed: set `title` so the navbar/site title shows "Halcyon Robotics" (was
leaking the gallery instance name); expanded the control-loop article to
~10 paragraphs to kill the footer dead space.
- SVG guard: explicit width/height on the <use> icon SVGs.
- Wave B gallery screenshots (home/about/article x light+dark), preview
= light home (light-canonical).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
123 lines
13 KiB
JSON
123 lines
13 KiB
JSON
{
|
|
"demo": true,
|
|
"settings": {
|
|
"merge": {
|
|
"title": "Halcyon Robotics",
|
|
"site_name": "Halcyon Robotics",
|
|
"tagline": "Autonomy systems for the field."
|
|
}
|
|
},
|
|
"data_tables": [
|
|
{
|
|
"key": "contact_submissions",
|
|
"name": "Contact submissions",
|
|
"description": "Messages sent from the Halcyon Robotics contact form.",
|
|
"primary_key": "id",
|
|
"columns": [
|
|
{ "key": "name", "label": "Name", "type": "text", "required": true },
|
|
{ "key": "email", "label": "Email", "type": "email", "required": true },
|
|
{ "key": "organisation", "label": "Organisation", "type": "text", "required": false },
|
|
{ "key": "message", "label": "Message", "type": "text", "required": true }
|
|
]
|
|
}
|
|
],
|
|
"pages": [
|
|
{
|
|
"slug": "/",
|
|
"title": "Halcyon Robotics",
|
|
"template_key": "full-width",
|
|
"reconcile_blocks": true,
|
|
"blocks": [
|
|
{ "block_key": "hero", "title": "Hero", "slot": "main", "sort_order": 1, "content": { "template": "left-aligned", "eyebrow": { "text": "Field autonomy" }, "headline": { "text": "Robots that hold their own position" }, "subheadline": { "text": "Halcyon builds the perception and control stack for machines that work where the map runs out. One runtime, from sensor to actuator." }, "ctaPrimary": { "text": "Read the spec", "url": "/about" }, "ctaSecondary": { "text": "Book a briefing", "url": "/contact" } } },
|
|
{ "block_key": "scifi-clean:telemetry_grid", "title": "Telemetry", "slot": "main", "sort_order": 2, "content": { "eyebrow": "Runtime", "heading": "What the stack holds", "columns": 4, "tiles": [ { "label": "Control loop", "value": "1000", "unit": "Hz", "delta": "deterministic" }, { "label": "Localisation drift", "value": "4", "unit": "cm", "delta": "over 1 km" }, { "label": "Onboard latency", "value": "18", "unit": "ms", "delta": "sensor to command" }, { "label": "Power draw", "value": "42", "unit": "W", "delta": "full autonomy" } ] } },
|
|
{ "block_key": "scifi-clean:benchmark_table", "title": "Benchmarks", "slot": "main", "sort_order": 3, "content": { "eyebrow": "Perception", "heading": "Detection recall by surface", "rows": [ { "label": "Paved road", "value": "99.2", "unit": "%", "bar": 99, "highlight": false }, { "label": "Gravel and dirt", "value": "97.8", "unit": "%", "bar": 98, "highlight": false }, { "label": "Low light", "value": "94.1", "unit": "%", "bar": 94, "highlight": true }, { "label": "Heavy dust", "value": "91.6", "unit": "%", "bar": 92, "highlight": false } ], "note": "Measured on the internal Halcyon field set. Numbers are illustrative product data for this demo site." } },
|
|
{ "block_key": "stats", "title": "Deployment", "slot": "main", "sort_order": 4, "content": { "heading": "Built for the field", "columns": 3, "countUp": false, "items": [ { "value": "40", "suffix": " C", "label": "Rated operating ceiling" }, { "value": "8", "suffix": " h", "label": "Continuous mission time" }, { "value": "IP67", "label": "Sealed enclosure rating" } ] } },
|
|
{ "block_key": "scifi-clean:tech_spec", "title": "Reference platform", "slot": "main", "sort_order": 5, "content": { "caption": "Halcyon H1 reference unit", "rows": [ { "label": "Mass", "value": "28", "unit": "kg" }, { "label": "Top speed", "value": "3.2", "unit": "m/s" }, { "label": "Compute", "value": "128", "unit": "TOPS" }, { "label": "Sensors", "value": "6", "unit": "channels" } ] } },
|
|
{ "block_key": "cta", "title": "Closing CTA", "slot": "main", "sort_order": 6, "content": { "background": "band", "heading": "Request a technical briefing", "text": "Thirty minutes with an engineer. Bring your hardest deployment.", "primaryLabel": "Book a briefing", "primaryUrl": "/contact" } }
|
|
]
|
|
},
|
|
{
|
|
"slug": "/about",
|
|
"title": "About",
|
|
"template_key": "default",
|
|
"reconcile_blocks": true,
|
|
"blocks": [
|
|
{ "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "We build one autonomy stack", "level": 1 } },
|
|
{ "block_key": "text", "title": "Intro", "slot": "main", "sort_order": 2, "content": { "text": "<p>Halcyon Robotics builds a single runtime that carries a machine from raw sensor data to a motor command. We do not sell a demo. We sell the part that has to work at three in the morning in the rain.</p><p>The team came out of field robotics and controls. We test on real ground before we ship a line of code.</p>" } },
|
|
{ "block_key": "team", "title": "Team", "slot": "main", "sort_order": 3, "content": { "heading": "The founding engineers", "layout": "grid", "columns": 3, "members": [ { "name": "R. Novak", "role": "Perception", "bio": "Sensor fusion and detection for off-road ground vehicles." }, { "name": "S. Adeyemi", "role": "Controls", "bio": "Real-time control and state estimation on embedded targets." }, { "name": "T. Winters", "role": "Systems", "bio": "Runtime, safety cases, and field deployment." } ] } },
|
|
{ "block_key": "scifi-clean:changelog_console", "title": "Runtime changelog", "slot": "main", "sort_order": 4, "content": { "eyebrow": "Runtime", "heading": "Recent releases", "title": "halcyon-runtime", "entries": [ { "version": "v2.4.0", "date": "2026-06-28", "note": "Dust-robust detection model, retrained on the winter field set." }, { "version": "v2.3.1", "date": "2026-06-09", "note": "Localisation drift cut to 4 cm over a kilometre." }, { "version": "v2.3.0", "date": "2026-05-22", "note": "Control loop moved to a deterministic 1000 Hz scheduler." } ] } }
|
|
]
|
|
},
|
|
{
|
|
"slug": "/blog",
|
|
"title": "Field notes",
|
|
"template_key": "blog-index",
|
|
"reconcile_blocks": true,
|
|
"blocks": [
|
|
{ "block_key": "card", "title": "Post 1", "slot": "main", "sort_order": 1, "content": { "title": "Why we run the control loop at 1000 Hz", "text": "<p>A slower loop looks fine on a bench and fails on gravel. Here is the reasoning.</p>", "link": "/blog/control-loop", "linkLabel": "Read" } },
|
|
{ "block_key": "card", "title": "Post 2", "slot": "main", "sort_order": 2, "content": { "title": "Measuring localisation drift honestly", "text": "<p>Drift over one clean kilometre is easy. Drift over a dusty one is the number that matters.</p>", "link": "/blog/localisation-drift", "linkLabel": "Read" } },
|
|
{ "block_key": "card", "title": "Post 3", "slot": "main", "sort_order": 3, "content": { "title": "Testing on real ground first", "text": "<p>Simulation earns a machine the right to a field test. It does not replace one.</p>", "link": "/blog/real-ground", "linkLabel": "Read" } }
|
|
]
|
|
},
|
|
{
|
|
"slug": "/blog/control-loop",
|
|
"parent_slug": "/blog",
|
|
"title": "Why we run the control loop at 1000 Hz",
|
|
"template_key": "article",
|
|
"reconcile_blocks": true,
|
|
"blocks": [
|
|
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Why we run the control loop at 1000 Hz", "level": 1 } },
|
|
{ "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "<p>On a paved test track a 200 Hz loop tracks a path cleanly. On loose gravel the wheels slip between updates and the estimate lags the truth. The machine corrects late and starts to hunt.</p><p>At 1000 Hz the loop sees the slip inside a single wheel rotation. The correction is small and the path stays smooth. The cost is a tighter compute budget, which is why the runtime is deterministic rather than best effort.</p><p>The first version of the platform ran the controller at 250 Hz. It looked fine in the lab and on the paved apron behind the workshop. The trouble started the first time we drove it across a dry creek bed.</p><p>On that surface the tyres broke traction for tens of milliseconds at a time. Between control updates the vehicle rotated a few degrees more than the model expected, and by the time the next update landed the correction it applied was already stale. The machine oscillated around the path instead of settling on it.</p><p>We spent a week convinced the problem was the estimator. It was not. The state estimate was accurate to within the sensor noise floor. The gap was time: the world was changing faster than we were reading it back.</p><p>Raising the loop rate is not free. Every doubling halves the time budget for the estimator, the planner, and the control solve. At 1000 Hz the whole chain has to finish in under a millisecond, every millisecond, with no exceptions and no pauses to collect memory.</p><p>That constraint is why the runtime is written the way it is. Memory is allocated once at start-up. The control thread is pinned to an isolated core. The scheduler is deterministic rather than best effort, so a slow frame is a bug we can catch instead of a statistic we learn to tolerate.</p><p>The payoff shows up on exactly the surfaces that used to break it. On gravel and loose dirt the corrections are now small and continuous. The path traces stay inside a few centimetres of the reference even when the tyres are slipping the whole way through a turn.</p><p>There is a ceiling to this. Past a certain rate the actuators cannot respond any faster and the extra updates buy nothing but heat. For our drivetrain that ceiling sits a little above 1000 Hz, which is why we stopped there rather than chase a larger number for its own sake.</p><p>If you are specifying a system like this, do not ask a vendor for their loop rate in isolation. Ask what surface they measured on, what the worst-case frame time is, and what happens on the frame that runs long. Those three answers tell you far more than the headline figure.</p>" } }
|
|
]
|
|
},
|
|
{
|
|
"slug": "/blog/localisation-drift",
|
|
"parent_slug": "/blog",
|
|
"title": "Measuring localisation drift honestly",
|
|
"template_key": "article",
|
|
"reconcile_blocks": true,
|
|
"blocks": [
|
|
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Measuring localisation drift honestly", "level": 1 } },
|
|
{ "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "<p>Every vendor quotes a drift figure. Few say what surface it came from. Drift on a flat paved loop tells you almost nothing about a field run.</p><p>We report drift over a kilometre of mixed dirt and gravel with dust in the air. The number is worse and it is the one you can trust. Ask any supplier for the surface behind the figure.</p>" } }
|
|
]
|
|
},
|
|
{
|
|
"slug": "/blog/real-ground",
|
|
"parent_slug": "/blog",
|
|
"title": "Testing on real ground first",
|
|
"template_key": "article",
|
|
"reconcile_blocks": true,
|
|
"blocks": [
|
|
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Testing on real ground first", "level": 1 } },
|
|
{ "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "<p>Simulation is where a change proves it will not do something stupid. It is fast and it is cheap and it catches the obvious failures. It does not catch the ground.</p><p>Real dirt has ruts the model never saw and light the camera never met. A change earns a field test in simulation. It earns a release on the field.</p>" } }
|
|
]
|
|
},
|
|
{
|
|
"slug": "/contact",
|
|
"title": "Contact",
|
|
"template_key": "contact",
|
|
"reconcile_blocks": true,
|
|
"blocks": [
|
|
{ "block_key": "contact-form", "title": "Contact form", "slot": "main", "sort_order": 1, "content": { "heading": "Request a briefing", "description": "Tell us the deployment. We reply within one business day.", "submitLabel": "Send message", "successMessage": "Received. We will be in touch within one business day.", "formConfig": { "targetTable": "contact_submissions", "enabled": true, "captchaEnabled": false }, "fields": [ { "name": "name", "label": "Name", "type": "text", "required": true, "width": "half" }, { "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" }, { "name": "organisation", "label": "Organisation", "type": "text", "required": false, "width": "full" }, { "name": "message", "label": "What are you deploying?", "type": "textarea", "required": true, "width": "full" } ] } },
|
|
{ "block_key": "contact-card", "title": "Contact details", "slot": "aside", "sort_order": 1, "content": { "heading": "Direct", "columns": 1, "channels": [ { "icon": "lucide:mail", "label": "Email", "value": "field@halcyon.example", "link": "mailto:field@halcyon.example" }, { "icon": "lucide:phone", "label": "Phone", "value": "+61 2 5550 0142", "link": "tel:+61255500142" } ] } },
|
|
{ "block_key": "hours-location", "title": "Lab hours", "slot": "aside", "sort_order": 2, "content": { "heading": "Lab", "address": "Unit 4, 22 Fabrication Way, Fremantle WA 6160", "hours": [ { "day": "Monday to Friday", "hours": "8:00 to 18:00" }, { "day": "Saturday and Sunday", "closed": true } ] } }
|
|
]
|
|
},
|
|
{
|
|
"slug": "/login",
|
|
"title": "Sign in",
|
|
"template_key": "auth",
|
|
"reconcile_blocks": true,
|
|
"blocks": [
|
|
{ "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } }
|
|
]
|
|
}
|
|
],
|
|
"menu_items": [
|
|
{ "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 },
|
|
{ "menu": "main", "label": "About", "page_slug": "/about", "sort_order": 2 },
|
|
{ "menu": "main", "label": "Field notes", "page_slug": "/blog", "sort_order": 3 },
|
|
{ "menu": "main", "label": "Contact", "page_slug": "/contact", "sort_order": 4 }
|
|
]
|
|
}
|