Reshape the landing so each project anchors to a slot on one of the two
helix strands. Strand A = Projects (durable, in-production), strand B =
Innovations (experiments, prototypes). Cards alternate L/R based on the
strand's instantaneous position at the slot — strand identity is shown
by colour + badge, not by side.
Schema
- ALTER projects ADD strand TEXT NOT NULL DEFAULT 'A' CHECK ('A','B').
Generated as drizzle/0001_*.sql.
Component
- New VerticalHelix.svelte replaces Helix.svelte on the landing.
- Slot Y = 120 + i * 240; period = 480, so each strand is at an extreme
at every slot. Node sits at the strand's actual x; card hugs that side.
- Pulsing node halos and animated gradient stops give "alive" feeling
without moving the strand geometry — projects stay anchored.
- prefers-reduced-motion disables the pulse.
- <760px: SVG hides, cards stack full-width.
Authoring
- /projects/new form gets a Project / Innovation radio picker.
- Seed adds an "DNA Scout" example on strand B so the helix renders with
both strands populated on first boot.
Landing
- Hero shrinks (60vh, no helix backdrop) and gains a strand legend.
- Top 12 projects bind to the helix; "See all N →" appears if there's
more.
- Posts band becomes compact (3 most recent), below the helix.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
171 lines
5.9 KiB
JavaScript
171 lines
5.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Seeds the database with a couple of example projects + posts so the landing
|
|
* page has something to render on first boot. Safe to re-run — uses INSERT OR
|
|
* IGNORE semantics via fixed IDs.
|
|
*
|
|
* node scripts/seed.js
|
|
*/
|
|
import Database from 'better-sqlite3';
|
|
|
|
const url = process.env.DATABASE_URL ?? './helix.db';
|
|
const db = new Database(url);
|
|
db.pragma('foreign_keys = ON');
|
|
|
|
const now = Math.floor(Date.now() / 1000);
|
|
|
|
const projects = [
|
|
{
|
|
id: 'prj_evolv',
|
|
slug: 'evolv',
|
|
title: 'EVOLV',
|
|
summary:
|
|
'ISA-88 Node-RED nodes for wastewater treatment plant automation. The flagship R&D stack.',
|
|
body_md: [
|
|
'# EVOLV',
|
|
'',
|
|
'EVOLV is a Node-RED custom-nodes package implementing the **ISA-88 (S88)** batch-control standard for wastewater treatment plants.',
|
|
'',
|
|
'Each node follows a three-layer architecture: Node-RED wrapper → adapter → pure domain logic. Configuration is JSON-driven; measurement series flow through a chainable container; outputs are normalised for InfluxDB and Grafana.',
|
|
'',
|
|
'## Highlights',
|
|
'',
|
|
'- Reactors, settlers, pumping stations, valves, rotating machines, machine groups',
|
|
'- Physics-checked: hydraulics, mass balance, biology, rotating-equipment envelopes',
|
|
'- Telemetry-first: every state surfaces on Ports 0/1/2 with declared output manifests',
|
|
'- Used at Waterschap Brabantse Delta for plant simulation and live control'
|
|
].join('\n'),
|
|
cover_url: null,
|
|
strand: 'A',
|
|
status: 'published'
|
|
},
|
|
{
|
|
id: 'prj_helix',
|
|
slug: 'helix',
|
|
title: 'HELIX',
|
|
summary:
|
|
'This very site — the R&D showcase platform. EVOLV and every R&D strand, one helix.',
|
|
body_md: [
|
|
'# HELIX',
|
|
'',
|
|
'HELIX is the R&D showcase platform of Waterschap Brabantse Delta. It collects projects, innovations, and updates from across the team in one place — with deep links to the actual repos, dashboards, and demos.',
|
|
'',
|
|
'## Stack',
|
|
'',
|
|
'- **SvelteKit 2** + **Svelte 5** + TypeScript',
|
|
'- **Tailwind v3** + CSS-first design tokens',
|
|
'- **SQLite** + **Drizzle ORM** — single-file, easy to back up',
|
|
'- **Gitea OAuth** for authoring',
|
|
'- Pure SVG + CSS for the helix animation — no WebGL',
|
|
'',
|
|
'## Why?',
|
|
'',
|
|
'Innovations were scattered: Gitea repos here, Grafana dashboards there, slide decks elsewhere. HELIX is the strand they share.'
|
|
].join('\n'),
|
|
cover_url: null,
|
|
strand: 'A',
|
|
status: 'published'
|
|
},
|
|
{
|
|
id: 'prj_dna_scout',
|
|
slug: 'dna-scout',
|
|
title: 'DNA Scout',
|
|
summary:
|
|
'Experimental anomaly-detection prototype that fingerprints reactor telemetry. R&D scout, not yet plant-ready.',
|
|
body_md: [
|
|
'# DNA Scout',
|
|
'',
|
|
"An experimental innovation: build short \"fingerprints\" of reactor telemetry to spot anomalies before the operator does. Think of it as a sequence read on the plant's metabolism.",
|
|
'',
|
|
'## Status',
|
|
'',
|
|
"Prototype. Runs on bench data from one reactor at the WBD test site. Not production-ready — accuracy on overflow events is still 60-something percent.",
|
|
'',
|
|
'## Idea',
|
|
'',
|
|
'- Slide a 5-minute window across all telemetry channels',
|
|
'- Hash each window into a compact descriptor',
|
|
'- Cluster descriptors → anomalies fall outside the dense clusters',
|
|
'',
|
|
'Belongs on strand B (Innovation) — it might never ship, and that\'s fine. If it does, it graduates to a Project.'
|
|
].join('\n'),
|
|
cover_url: null,
|
|
strand: 'B',
|
|
status: 'published'
|
|
}
|
|
];
|
|
|
|
const links = [
|
|
{
|
|
id: 'lnk_evolv_repo',
|
|
project_id: 'prj_evolv',
|
|
kind: 'gitea',
|
|
label: 'gitea.wbd-rd.nl/RnD/EVOLV',
|
|
url: 'https://gitea.wbd-rd.nl/RnD/EVOLV',
|
|
position: 0
|
|
},
|
|
{
|
|
id: 'lnk_helix_repo',
|
|
project_id: 'prj_helix',
|
|
kind: 'gitea',
|
|
label: 'gitea.wbd-rd.nl/RnD/helix',
|
|
url: 'https://gitea.wbd-rd.nl/RnD/helix',
|
|
position: 0
|
|
}
|
|
];
|
|
|
|
const posts = [
|
|
{
|
|
id: 'pst_welcome',
|
|
slug: 'welcome-to-helix',
|
|
title: 'Welcome to HELIX',
|
|
summary: 'Why this site exists and how to contribute.',
|
|
body_md: [
|
|
'# Welcome to HELIX',
|
|
'',
|
|
'HELIX is the home of R&D output at Waterschap Brabantse Delta. If you have a project, a dashboard, or a one-off experiment worth showing — it belongs here.',
|
|
'',
|
|
'## How to post',
|
|
'',
|
|
'Sign in with your Gitea account (top-right). Then:',
|
|
'',
|
|
'- **Projects** are durable showcases. Title, summary, markdown body, links to repos / dashboards / demos.',
|
|
'- **Posts** are updates, write-ups, notes. Anything blog-shaped.',
|
|
'',
|
|
'Both render with full markdown and can link to live dashboards. Iframe embeds are gated by an allowlist in `src/lib/config.ts`.',
|
|
'',
|
|
"Don't see your dashboard host in the allowlist? Add it in a PR — one line."
|
|
].join('\n'),
|
|
published_at: now
|
|
}
|
|
];
|
|
|
|
const insertProject = db.prepare(`
|
|
INSERT OR IGNORE INTO projects
|
|
(id, slug, title, summary, body_md, cover_url, strand, author_id, status, created_at, updated_at)
|
|
VALUES
|
|
(@id, @slug, @title, @summary, @body_md, @cover_url, @strand, NULL, @status, ${now}, ${now})
|
|
`);
|
|
const insertLink = db.prepare(`
|
|
INSERT OR IGNORE INTO project_links
|
|
(id, project_id, kind, label, url, position)
|
|
VALUES
|
|
(@id, @project_id, @kind, @label, @url, @position)
|
|
`);
|
|
const insertPost = db.prepare(`
|
|
INSERT OR IGNORE INTO posts
|
|
(id, slug, title, summary, body_md, author_id, published_at, created_at, updated_at)
|
|
VALUES
|
|
(@id, @slug, @title, @summary, @body_md, NULL, @published_at, ${now}, ${now})
|
|
`);
|
|
|
|
const tx = db.transaction(() => {
|
|
for (const p of projects) insertProject.run(p);
|
|
for (const l of links) insertLink.run(l);
|
|
for (const p of posts) insertPost.run(p);
|
|
});
|
|
tx();
|
|
|
|
console.log(`[helix] seeded ${projects.length} projects, ${links.length} links, ${posts.length} posts → ${url}`);
|
|
db.close();
|