feat: rebrand to R&D-lab + WBD palette + lab-slide cards
Identity refresh aligned to Waterschap Brabantse Delta.
Brand
- HELIX → R&D-lab everywhere user-facing (SITE.name; literal "HELIX"s
swept across routes, app.html, login, error messages, seed).
- New tagline: "Projects, innovations, and every strand between."
- Site description updated.
Palette (sourced from the official WSBD-logo.svg)
- Primary #0d4f9e, secondary #1fa0db, accent #bed137 — added as
--wbd-blue / --wbd-cyan / --wbd-lime CSS vars and as wbd.* in
tailwind.config.js. helix.* aliases now point to the WBD palette.
- Strand A (Projects) → #1fa0db cyan. Strand B (Innovations) → #bed137 lime.
- Body vignette + scroll-bar + legend dots repainted accordingly.
Composite logo
- New 24px nav glyph + favicon.svg: WBD-style tilted-square mark in WBD
blues at the centre, helix strands (lime + cyan) wrapping it, lime
"active site" dot at the crossing. Says "R&D-lab × Brabantse Delta"
in one mark.
Lab-slide cards (VerticalHelix)
- Frosted-glass surface (backdrop-filter blur+saturate).
- Thick 5px strand-coloured stripe on the helix-facing edge (gradient,
glowing shadow). Slide rounds the stripe corners; the rest is square.
- Slide header has the strand badge and a monospace serial number
(01/03 etc) — lab-specimen feel.
- Dashed footer rule + "Open detail →" CTA.
- Inline link chips (Gitea / Dashboard / Demo / Docs / Paper / Video)
with inline SVG icons + short labels. Hover lights up in the strand
colour. Capped at 5 visible, "+N" overflow indicator.
- Real <a> chips inside the card without nested <a>: overlay-link
pattern (transparent slide-link absolute fills the card, chips sit on
z-index: 2 above it).
Server load
- + Page now fetches each project's links in one Drizzle relational
query (db.query.projects.findMany with: { links }), capped at 12.
- + Form: strand picker (Project / Innovation radios) reads + persists
the new column.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,13 +42,13 @@ const projects = [
|
||||
{
|
||||
id: 'prj_helix',
|
||||
slug: 'helix',
|
||||
title: 'HELIX',
|
||||
title: 'R&D-lab',
|
||||
summary:
|
||||
'This very site — the R&D showcase platform. EVOLV and every R&D strand, one helix.',
|
||||
'This very site — the R&D lab of Waterschap Brabantse Delta. Projects, innovations, and every strand between.',
|
||||
body_md: [
|
||||
'# HELIX',
|
||||
'# R&D-lab',
|
||||
'',
|
||||
'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.',
|
||||
'The R&D-lab is the 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',
|
||||
'',
|
||||
@@ -60,7 +60,7 @@ const projects = [
|
||||
'',
|
||||
'## Why?',
|
||||
'',
|
||||
'Innovations were scattered: Gitea repos here, Grafana dashboards there, slide decks elsewhere. HELIX is the strand they share.'
|
||||
'Innovations were scattered: Gitea repos here, Grafana dashboards there, slide decks elsewhere. R&D-lab is the strand they share.'
|
||||
].join('\n'),
|
||||
cover_url: null,
|
||||
strand: 'A',
|
||||
@@ -117,13 +117,13 @@ const links = [
|
||||
const posts = [
|
||||
{
|
||||
id: 'pst_welcome',
|
||||
slug: 'welcome-to-helix',
|
||||
title: 'Welcome to HELIX',
|
||||
slug: 'welcome-to-rd-lab',
|
||||
title: 'Welcome to R&D-lab',
|
||||
summary: 'Why this site exists and how to contribute.',
|
||||
body_md: [
|
||||
'# Welcome to HELIX',
|
||||
'# Welcome to R&D-lab',
|
||||
'',
|
||||
'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.',
|
||||
'R&D-lab 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',
|
||||
'',
|
||||
|
||||
43
src/app.css
43
src/app.css
@@ -5,14 +5,21 @@
|
||||
/* Design tokens exposed as CSS vars so Svelte component <style> blocks
|
||||
can use them without going through Tailwind. */
|
||||
:root {
|
||||
/* S88-inspired hierarchy palette (mirrors EVOLV) */
|
||||
--color-helix-area: #0f52a5;
|
||||
--color-helix-process: #0c99d9;
|
||||
--color-helix-unit: #50a8d9;
|
||||
--color-helix-equipment: #86bbdd;
|
||||
--color-helix-control: #a9daee;
|
||||
/* Waterschap Brabantse Delta brand palette.
|
||||
Sourced from the official WSBD logo SVG (fd-cdn.nl/.../WSBD-logo.svg). */
|
||||
--wbd-deep: #0a3d80; /* deeper shade derived from primary, for hover/depth */
|
||||
--wbd-blue: #0d4f9e; /* WBD primary blue */
|
||||
--wbd-cyan: #1fa0db; /* WBD secondary blue */
|
||||
--wbd-lime: #bed137; /* WBD accent lime/green */
|
||||
|
||||
/* Surfaces */
|
||||
/* Hierarchy palette (semantic) — points back to the WBD palette */
|
||||
--color-helix-area: var(--wbd-deep);
|
||||
--color-helix-process: var(--wbd-blue);
|
||||
--color-helix-unit: var(--wbd-cyan);
|
||||
--color-helix-equipment: #6fc3ec;
|
||||
--color-helix-control: #b8dff5;
|
||||
|
||||
/* Surfaces (dark UI, on-brand) */
|
||||
--color-helix-bg: #07111d;
|
||||
--color-helix-bg-2: #0c1c30;
|
||||
--color-helix-bg-3: #122842;
|
||||
@@ -23,9 +30,17 @@
|
||||
--color-helix-ink-dim: #8fa6b8;
|
||||
--color-helix-ink-faint: #5b7388;
|
||||
|
||||
/* Accent (helix glow / R&D signal) */
|
||||
--color-helix-accent: #4dd0c2;
|
||||
--color-helix-accent-2: #c084fc;
|
||||
/* Accents
|
||||
accent = WBD lime — used for primary CTAs, focus, and Strand B (Innovations)
|
||||
accent2 = a lighter lime, used for highlights */
|
||||
--color-helix-accent: var(--wbd-lime);
|
||||
--color-helix-accent-2: #d8e36a;
|
||||
|
||||
/* Strand colors (semantic) — pick from the WBD palette */
|
||||
--strand-a-primary: var(--wbd-cyan); /* Projects */
|
||||
--strand-a-secondary: var(--wbd-blue);
|
||||
--strand-b-primary: var(--wbd-lime); /* Innovations */
|
||||
--strand-b-secondary: #8fa024;
|
||||
|
||||
--font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
|
||||
@@ -40,15 +55,15 @@ body {
|
||||
font-feature-settings: 'cv11', 'ss01', 'ss03';
|
||||
}
|
||||
|
||||
/* Subtle radial vignette anchoring the landing page */
|
||||
/* Subtle radial vignette anchoring the landing page (WBD palette) */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(1200px 800px at 20% 0%, rgba(12, 153, 217, 0.18), transparent 60%),
|
||||
radial-gradient(900px 700px at 90% 20%, rgba(77, 208, 194, 0.12), transparent 60%),
|
||||
radial-gradient(700px 500px at 50% 100%, rgba(192, 132, 252, 0.08), transparent 60%);
|
||||
radial-gradient(1200px 800px at 20% 0%, rgba(31, 160, 219, 0.18), transparent 60%),
|
||||
radial-gradient(900px 700px at 90% 20%, rgba(13, 79, 158, 0.16), transparent 60%),
|
||||
radial-gradient(700px 500px at 50% 100%, rgba(190, 209, 55, 0.08), transparent 60%);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#0c99d9" />
|
||||
<meta name="description" content="HELIX — the R&D showcase platform of Waterschap Brabantse Delta. EVOLV and every R&D strand, one helix." />
|
||||
<meta name="description" content="R&D-lab — the R&D platform of Waterschap Brabantse Delta. Projects, innovations, and every strand between." />
|
||||
<link rel="preconnect" href="https://rsms.me/" />
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
|
||||
%sveltekit.head%
|
||||
|
||||
123
src/lib/components/LinkChip.svelte
Normal file
123
src/lib/components/LinkChip.svelte
Normal file
@@ -0,0 +1,123 @@
|
||||
<script lang="ts">
|
||||
import { LINK_KIND_SHORT, type LinkKind } from '$lib/config';
|
||||
|
||||
let {
|
||||
kind,
|
||||
label,
|
||||
url
|
||||
}: {
|
||||
kind: string;
|
||||
label: string;
|
||||
url: string;
|
||||
} = $props();
|
||||
|
||||
// Display short kind name; fall back to label for unknown kinds
|
||||
const shortKind = $derived(
|
||||
(LINK_KIND_SHORT as Record<string, string>)[kind] ?? label.slice(0, 6)
|
||||
);
|
||||
</script>
|
||||
|
||||
<a
|
||||
class="chip"
|
||||
data-kind={kind}
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
aria-label={`${label} (${kind})`}
|
||||
title={label}
|
||||
>
|
||||
<span class="ico" aria-hidden="true">
|
||||
{#if kind === 'gitea'}
|
||||
<!-- code-branch -->
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="4" cy="3" r="1.5" />
|
||||
<circle cx="4" cy="13" r="1.5" />
|
||||
<circle cx="12" cy="6" r="1.5" />
|
||||
<path d="M4 4.5v7" />
|
||||
<path d="M4 8c0-1.5 1-3 4-3v0" />
|
||||
</svg>
|
||||
{:else if kind === 'dashboard'}
|
||||
<!-- grid -->
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="2" width="5" height="5" rx="0.6" />
|
||||
<rect x="9" y="2" width="5" height="5" rx="0.6" />
|
||||
<rect x="2" y="9" width="5" height="5" rx="0.6" />
|
||||
<rect x="9" y="9" width="5" height="5" rx="0.6" />
|
||||
</svg>
|
||||
{:else if kind === 'demo'}
|
||||
<!-- play -->
|
||||
<svg viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M5 3.5v9l8-4.5z" />
|
||||
</svg>
|
||||
{:else if kind === 'docs'}
|
||||
<!-- doc lines -->
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3.5 2.5h7l2.5 2.5v8.5h-9.5z" />
|
||||
<path d="M10 2.5v3h3" />
|
||||
<path d="M5 8h6" />
|
||||
<path d="M5 10.5h6" />
|
||||
<path d="M5 13h4" />
|
||||
</svg>
|
||||
{:else if kind === 'paper'}
|
||||
<!-- paper / page -->
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 2.5h10v11h-10z" />
|
||||
<path d="M5.5 5.5h5" />
|
||||
<path d="M5.5 8h5" />
|
||||
<path d="M5.5 10.5h3" />
|
||||
</svg>
|
||||
{:else if kind === 'video'}
|
||||
<!-- video rect with play -->
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="3.5" width="12" height="9" rx="1" />
|
||||
<path d="M7 6v4l3.5-2z" fill="currentColor" stroke="none" />
|
||||
</svg>
|
||||
{:else}
|
||||
<!-- generic external link -->
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M9.5 3h3.5v3.5" />
|
||||
<path d="M13 3l-5.5 5.5" />
|
||||
<path d="M11 9v3.5h-7v-7h3.5" />
|
||||
</svg>
|
||||
{/if}
|
||||
</span>
|
||||
<span class="lbl">{shortKind}</span>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.25rem 0.6rem 0.25rem 0.45rem;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid var(--color-helix-border);
|
||||
color: var(--color-helix-ink-dim);
|
||||
text-decoration: none;
|
||||
font-size: 0.72rem;
|
||||
font-family: var(--font-mono);
|
||||
letter-spacing: 0.04em;
|
||||
transition: color 160ms ease, border-color 160ms ease, background 160ms ease;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.chip:hover {
|
||||
color: var(--color-helix-ink);
|
||||
border-color: var(--card-accent, var(--color-helix-accent));
|
||||
background: color-mix(in oklab, var(--card-accent, var(--color-helix-accent)) 14%, transparent);
|
||||
}
|
||||
.ico {
|
||||
display: inline-flex;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--card-accent, var(--color-helix-accent));
|
||||
}
|
||||
.ico svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.lbl {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@@ -20,21 +20,20 @@
|
||||
<div class="inner">
|
||||
<a href="/" class="brand">
|
||||
<span class="mark" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" width="22" height="22">
|
||||
<path
|
||||
d="M4 4 C 4 10, 20 14, 20 20"
|
||||
fill="none"
|
||||
stroke="#4dd0c2"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M4 20 C 4 14, 20 10, 20 4"
|
||||
fill="none"
|
||||
stroke="#0c99d9"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<svg viewBox="0 0 24 24" width="24" height="24">
|
||||
<!-- WBD tilted-square core -->
|
||||
<g transform="rotate(45 12 12)">
|
||||
<rect x="5.5" y="5.5" width="13" height="13" rx="0.5" fill="#0d4f9e" fill-opacity="0.9"/>
|
||||
<rect x="8.2" y="8.2" width="7.6" height="7.6" rx="0.4" fill="#1fa0db"/>
|
||||
</g>
|
||||
<!-- Helix strands wrapping the core -->
|
||||
<path d="M4 4 C 4 10, 20 14, 20 20"
|
||||
fill="none" stroke="#bed137" stroke-width="2.1" stroke-linecap="round"/>
|
||||
<path d="M4 20 C 4 14, 20 10, 20 4"
|
||||
fill="none" stroke="#1fa0db" stroke-width="2.1" stroke-linecap="round" stroke-opacity="0.9"/>
|
||||
<!-- Active-site lime dot -->
|
||||
<circle cx="12" cy="12" r="1.9" fill="#bed137"/>
|
||||
<circle cx="12" cy="12" r="0.9" fill="#07111d"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="wordmark">{SITE.name}</span>
|
||||
@@ -105,8 +104,8 @@
|
||||
}
|
||||
.wordmark {
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: 0.02em;
|
||||
font-size: 0.98rem;
|
||||
}
|
||||
.links {
|
||||
display: flex;
|
||||
|
||||
@@ -7,23 +7,36 @@
|
||||
*
|
||||
* Each project sits at slot Y = TOP_PAD + i * SLOT_HEIGHT, at the
|
||||
* instantaneous x of its strand. Card sits on whichever side the node is.
|
||||
* Strand colour + badge encode strand identity regardless of L/R position.
|
||||
*
|
||||
* Animation: gradient stops drift along the strands + node pulse.
|
||||
* The strand geometry itself is static — keeps project anchors stable.
|
||||
* Cards are lab-slides: frosted glass surface with a thick strand-coloured
|
||||
* stripe on the helix-facing edge. Inline link chips per project are real
|
||||
* anchor elements stacked above an overlay link to the detail page
|
||||
* (HTML doesn't allow nested <a>, so we use the overlay pattern).
|
||||
*
|
||||
* Palette: Waterschap Brabantse Delta — #0d4f9e, #1fa0db, #bed137.
|
||||
*/
|
||||
|
||||
import LinkChip from './LinkChip.svelte';
|
||||
|
||||
type ProjectLink = {
|
||||
kind: string;
|
||||
label: string;
|
||||
url: string;
|
||||
position: number;
|
||||
};
|
||||
|
||||
type StrandProject = {
|
||||
slug: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
strand: 'A' | 'B';
|
||||
coverUrl: string | null;
|
||||
links: ProjectLink[];
|
||||
};
|
||||
|
||||
let { projects }: { projects: StrandProject[] } = $props();
|
||||
|
||||
// Geometry constants (user-space units)
|
||||
// Geometry (user-space units inside the SVG)
|
||||
const W = 1000;
|
||||
const CX = W / 2;
|
||||
const AMP = 140;
|
||||
@@ -102,8 +115,7 @@
|
||||
<section
|
||||
class="vhelix"
|
||||
style:--vh-h="{H}px"
|
||||
style:--vh-w="{W}px"
|
||||
aria-label="Projects bound to the HELIX strands"
|
||||
aria-label="Projects bound to the R&D-lab strands"
|
||||
>
|
||||
<svg
|
||||
class="vhelix-svg"
|
||||
@@ -113,24 +125,24 @@
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="vstrand-a" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#0f52a5">
|
||||
<stop offset="0%" stop-color="#0d4f9e">
|
||||
<animate attributeName="offset" values="-0.3;1.3" dur="18s" repeatCount="indefinite" />
|
||||
</stop>
|
||||
<stop offset="50%" stop-color="#0c99d9">
|
||||
<stop offset="50%" stop-color="#1fa0db">
|
||||
<animate attributeName="offset" values="0.0;1.6" dur="18s" repeatCount="indefinite" />
|
||||
</stop>
|
||||
<stop offset="100%" stop-color="#4dd0c2">
|
||||
<stop offset="100%" stop-color="#6fc3ec">
|
||||
<animate attributeName="offset" values="0.3;1.9" dur="18s" repeatCount="indefinite" />
|
||||
</stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="vstrand-b" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#c084fc">
|
||||
<stop offset="0%" stop-color="#bed137">
|
||||
<animate attributeName="offset" values="-0.3;1.3" dur="22s" repeatCount="indefinite" />
|
||||
</stop>
|
||||
<stop offset="50%" stop-color="#7e6ce8">
|
||||
<stop offset="50%" stop-color="#d8e36a">
|
||||
<animate attributeName="offset" values="0.0;1.6" dur="22s" repeatCount="indefinite" />
|
||||
</stop>
|
||||
<stop offset="100%" stop-color="#50a8d9">
|
||||
<stop offset="100%" stop-color="#8fa024">
|
||||
<animate attributeName="offset" values="0.3;1.9" dur="22s" repeatCount="indefinite" />
|
||||
</stop>
|
||||
</linearGradient>
|
||||
@@ -154,8 +166,8 @@
|
||||
y1={r.y}
|
||||
x2={r.x2}
|
||||
y2={r.y}
|
||||
stroke="#86bbdd"
|
||||
stroke-opacity={0.08 + 0.32 * r.depth}
|
||||
stroke="#6fc3ec"
|
||||
stroke-opacity={0.08 + 0.30 * r.depth}
|
||||
stroke-width={0.6 + 1.0 * r.depth}
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
@@ -181,8 +193,8 @@
|
||||
y1={r.y}
|
||||
x2={r.x2}
|
||||
y2={r.y}
|
||||
stroke="#a9daee"
|
||||
stroke-opacity={0.15 + 0.50 * r.depth}
|
||||
stroke="#b8dff5"
|
||||
stroke-opacity={0.18 + 0.50 * r.depth}
|
||||
stroke-width={1.0 + 1.6 * r.depth}
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
@@ -206,7 +218,7 @@
|
||||
cx={s.nodeX}
|
||||
cy={s.y}
|
||||
r="22"
|
||||
fill={s.project.strand === 'A' ? '#0c99d9' : '#c084fc'}
|
||||
fill={s.project.strand === 'A' ? '#1fa0db' : '#bed137'}
|
||||
fill-opacity="0.25"
|
||||
filter="url(#vnode-glow)"
|
||||
class="vnode-halo"
|
||||
@@ -215,49 +227,78 @@
|
||||
cx={s.nodeX}
|
||||
cy={s.y}
|
||||
r="12"
|
||||
fill={s.project.strand === 'A' ? '#0c99d9' : '#c084fc'}
|
||||
fill={s.project.strand === 'A' ? '#1fa0db' : '#bed137'}
|
||||
/>
|
||||
<circle cx={s.nodeX} cy={s.y} r="6" fill="#07111d" />
|
||||
<circle
|
||||
cx={s.nodeX}
|
||||
cy={s.y}
|
||||
r="3.5"
|
||||
fill={s.project.strand === 'A' ? '#4dd0c2' : '#c084fc'}
|
||||
fill={s.project.strand === 'A' ? '#bed137' : '#1fa0db'}
|
||||
/>
|
||||
</g>
|
||||
{/each}
|
||||
|
||||
<!-- Connector lines from each node out to its card edge -->
|
||||
<!-- Connector dash lines from node to card -->
|
||||
{#each slots as s}
|
||||
<line
|
||||
x1={s.nodeX}
|
||||
y1={s.y}
|
||||
x2={s.side === 'right' ? W - 80 : 80}
|
||||
y2={s.y}
|
||||
stroke={s.project.strand === 'A' ? '#0c99d9' : '#c084fc'}
|
||||
stroke-opacity="0.35"
|
||||
stroke={s.project.strand === 'A' ? '#1fa0db' : '#bed137'}
|
||||
stroke-opacity="0.32"
|
||||
stroke-width="1"
|
||||
stroke-dasharray="2 4"
|
||||
/>
|
||||
{/each}
|
||||
</svg>
|
||||
|
||||
<!-- HTML card layer absolutely positioned over the SVG -->
|
||||
<!-- HTML lab-slide cards layered over the SVG -->
|
||||
<div class="vhelix-cards">
|
||||
{#each slots as s}
|
||||
<article
|
||||
class="slide side-{s.side} strand-{s.project.strand}"
|
||||
style:top="{((s.y / H) * 100).toFixed(2)}%"
|
||||
>
|
||||
<!-- Strand-coloured edge stripe (lab-slide signature) -->
|
||||
<span class="stripe" aria-hidden="true"></span>
|
||||
|
||||
<!-- Overlay link covers the body but not the chips -->
|
||||
<a
|
||||
href="/projects/{s.project.slug}"
|
||||
class="vcard side-{s.side} strand-{s.project.strand}"
|
||||
style:top="{(s.y / H * 100).toFixed(2)}%"
|
||||
>
|
||||
class="slide-link"
|
||||
aria-label={`Open ${s.project.title}`}
|
||||
></a>
|
||||
|
||||
<header class="slide-head">
|
||||
<span class="badge">
|
||||
<span class="dot" aria-hidden="true"></span>
|
||||
{s.project.strand === 'A' ? 'Project' : 'Innovation'}
|
||||
</span>
|
||||
<h3>{s.project.title}</h3>
|
||||
<p>{s.project.summary}</p>
|
||||
<span class="cta">Open <span class="arrow">→</span></span>
|
||||
</a>
|
||||
<span class="serial" aria-hidden="true">
|
||||
{String(s.index + 1).padStart(2, '0')}/{String(slots.length).padStart(2, '0')}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<h3 class="title">{s.project.title}</h3>
|
||||
<p class="summary">{s.project.summary}</p>
|
||||
|
||||
{#if s.project.links.length > 0}
|
||||
<div class="chips">
|
||||
{#each s.project.links.slice(0, 5) as l}
|
||||
<LinkChip kind={l.kind} label={l.label} url={l.url} />
|
||||
{/each}
|
||||
{#if s.project.links.length > 5}
|
||||
<span class="more-chips">+{s.project.links.length - 5}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<footer class="slide-foot">
|
||||
<span class="open">Open detail <span class="arrow">→</span></span>
|
||||
</footer>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
@@ -286,41 +327,90 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vcard {
|
||||
/* ========== LAB SLIDE CARD ========== */
|
||||
.slide {
|
||||
position: absolute;
|
||||
width: 280px;
|
||||
max-width: calc(50% - 110px);
|
||||
padding: 1rem 1.15rem 1.1rem;
|
||||
width: 300px;
|
||||
max-width: calc(50% - 90px);
|
||||
padding: 1.1rem 1.25rem 0.95rem;
|
||||
border-radius: 12px;
|
||||
background: color-mix(in oklab, var(--color-helix-bg-2) 90%, transparent);
|
||||
background: color-mix(in oklab, var(--color-helix-bg-2) 88%, transparent);
|
||||
border: 1px solid var(--color-helix-border);
|
||||
backdrop-filter: blur(6px);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
backdrop-filter: blur(10px) saturate(140%);
|
||||
-webkit-backdrop-filter: blur(10px) saturate(140%);
|
||||
transform: translateY(-50%);
|
||||
transition: border-color 200ms ease, background 200ms ease, transform 200ms ease;
|
||||
transition: border-color 220ms ease, background 220ms ease, transform 220ms ease,
|
||||
box-shadow 220ms ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
.vcard:hover {
|
||||
.slide:hover {
|
||||
border-color: var(--card-accent);
|
||||
background: var(--color-helix-bg-3);
|
||||
background: color-mix(in oklab, var(--color-helix-bg-3) 90%, transparent);
|
||||
transform: translateY(calc(-50% - 2px));
|
||||
box-shadow: 0 16px 40px -20px color-mix(in oklab, var(--card-accent) 60%, transparent);
|
||||
}
|
||||
.vcard.side-right {
|
||||
.slide.side-right {
|
||||
right: 1.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
.vcard.side-left {
|
||||
.slide.side-left {
|
||||
left: 1.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.vcard.strand-A {
|
||||
--card-accent: var(--color-helix-process);
|
||||
--card-accent-soft: rgba(12, 153, 217, 0.18);
|
||||
.slide.strand-A {
|
||||
--card-accent: #1fa0db;
|
||||
--card-accent-soft: rgba(31, 160, 219, 0.18);
|
||||
}
|
||||
.vcard.strand-B {
|
||||
--card-accent: var(--color-helix-accent-2);
|
||||
--card-accent-soft: rgba(192, 132, 252, 0.18);
|
||||
.slide.strand-B {
|
||||
--card-accent: #bed137;
|
||||
--card-accent-soft: rgba(190, 209, 55, 0.22);
|
||||
}
|
||||
|
||||
/* Thick coloured stripe on the helix-facing edge */
|
||||
.stripe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 5px;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--card-accent) 0%,
|
||||
color-mix(in oklab, var(--card-accent) 60%, transparent) 100%
|
||||
);
|
||||
box-shadow: 0 0 12px var(--card-accent-soft);
|
||||
}
|
||||
.slide.side-right .stripe {
|
||||
left: 0;
|
||||
border-radius: 12px 0 0 12px;
|
||||
}
|
||||
.slide.side-left .stripe {
|
||||
right: 0;
|
||||
border-radius: 0 12px 12px 0;
|
||||
}
|
||||
|
||||
/* Invisible overlay link: covers the card except the chips row */
|
||||
.slide-link {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
text-indent: -9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slide-head,
|
||||
.title,
|
||||
.summary,
|
||||
.slide-foot {
|
||||
position: relative;
|
||||
z-index: 0; /* below the overlay link */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.slide-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
@@ -328,14 +418,13 @@
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.68rem;
|
||||
letter-spacing: 0.16em;
|
||||
font-size: 0.66rem;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--card-accent);
|
||||
padding: 0.18rem 0.55rem;
|
||||
border-radius: 999px;
|
||||
background: var(--card-accent-soft);
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
.badge .dot {
|
||||
width: 6px;
|
||||
@@ -345,37 +434,73 @@
|
||||
box-shadow: 0 0 8px var(--card-accent);
|
||||
}
|
||||
|
||||
.vcard h3 {
|
||||
.serial {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.66rem;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-helix-ink-faint);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
margin: 0 0 0.35rem;
|
||||
padding-left: 0.45rem; /* clear the stripe a hair */
|
||||
}
|
||||
.vcard p {
|
||||
.slide.side-left .title {
|
||||
padding-left: 0;
|
||||
padding-right: 0.45rem;
|
||||
}
|
||||
|
||||
.summary {
|
||||
color: var(--color-helix-ink-dim);
|
||||
font-size: 0.92rem;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
margin: 0 0 0.55rem;
|
||||
margin: 0 0 0.7rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cta {
|
||||
|
||||
/* Chips ROW — above the overlay link */
|
||||
.chips {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
margin: 0 0 0.6rem;
|
||||
}
|
||||
.more-chips {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.72rem;
|
||||
color: var(--color-helix-ink-faint);
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.slide-foot {
|
||||
border-top: 1px dashed color-mix(in oklab, var(--card-accent) 30%, transparent);
|
||||
padding-top: 0.55rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.open {
|
||||
color: var(--card-accent);
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.arrow {
|
||||
display: inline-block;
|
||||
transition: transform 200ms ease;
|
||||
}
|
||||
.vcard:hover .arrow {
|
||||
.slide:hover .arrow {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
/* Node pulse (subtle "alive" feeling, no rotation) */
|
||||
/* Node pulse */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:global(.vnode-halo) {
|
||||
animation: vnode-pulse 3.5s ease-in-out infinite;
|
||||
@@ -395,14 +520,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile: helix becomes a thin centerline; cards stack full-width below each node */
|
||||
/* Mobile: helix hides, cards stack full-width */
|
||||
@media (max-width: 760px) {
|
||||
.vhelix {
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
.vhelix-svg {
|
||||
display: none; /* hide the wide-helix SVG on narrow screens */
|
||||
display: none;
|
||||
}
|
||||
.vhelix-cards {
|
||||
display: flex;
|
||||
@@ -411,7 +535,7 @@
|
||||
height: auto;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
.vcard {
|
||||
.slide {
|
||||
position: relative;
|
||||
top: auto !important;
|
||||
right: auto !important;
|
||||
@@ -420,7 +544,7 @@
|
||||
max-width: 100%;
|
||||
transform: none;
|
||||
}
|
||||
.vcard:hover {
|
||||
.slide:hover {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
* Site-wide configuration. Edit here to rebrand without touching components.
|
||||
*/
|
||||
export const SITE = {
|
||||
name: 'HELIX',
|
||||
tagline: 'EVOLV and every R&D strand, one helix.',
|
||||
name: 'R&D-lab',
|
||||
shortName: 'R&D-lab',
|
||||
tagline: 'Projects, innovations, and every strand between.',
|
||||
description:
|
||||
'The R&D showcase platform of Waterschap Brabantse Delta. EVOLV at its core, every innovation along the strands.',
|
||||
organization: 'Waterschap Brabantse Delta R&D',
|
||||
'The R&D lab of Waterschap Brabantse Delta. EVOLV at its core, every project and innovation along the strands.',
|
||||
organization: 'Waterschap Brabantse Delta',
|
||||
giteaOrg: 'RnD',
|
||||
giteaBaseUrl: 'https://gitea.wbd-rd.nl'
|
||||
} as const;
|
||||
@@ -32,3 +33,13 @@ export const LINK_KIND_LABEL: Record<LinkKind, string> = {
|
||||
paper: 'Paper',
|
||||
video: 'Video'
|
||||
};
|
||||
|
||||
/** Short labels used inline on cards (≤ 6 chars). */
|
||||
export const LINK_KIND_SHORT: Record<LinkKind, string> = {
|
||||
gitea: 'Repo',
|
||||
dashboard: 'Dash',
|
||||
demo: 'Demo',
|
||||
docs: 'Docs',
|
||||
paper: 'Paper',
|
||||
video: 'Video'
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ marked.setOptions({
|
||||
*
|
||||
* Trust model: authoring is gated to members of the configured Gitea org,
|
||||
* so we render markdown as-is (raw HTML in markdown is passed through).
|
||||
* If HELIX is opened to untrusted authors, swap this for a DOMPurify pass.
|
||||
* If authoring is opened to untrusted authors, swap this for a DOMPurify pass.
|
||||
*/
|
||||
export function renderMarkdown(md: string): string {
|
||||
return marked.parse(md) as string;
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { projects, posts } from '$lib/server/db/schema';
|
||||
import { desc, eq, isNotNull } from 'drizzle-orm';
|
||||
import { projects, posts, projectLinks } from '$lib/server/db/schema';
|
||||
import { asc, desc, eq, isNotNull } from 'drizzle-orm';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
const helixProjects = db
|
||||
.select({
|
||||
slug: projects.slug,
|
||||
title: projects.title,
|
||||
summary: projects.summary,
|
||||
strand: projects.strand,
|
||||
coverUrl: projects.coverUrl
|
||||
})
|
||||
.from(projects)
|
||||
.where(eq(projects.status, 'published'))
|
||||
.orderBy(desc(projects.updatedAt))
|
||||
.limit(12)
|
||||
.all();
|
||||
// Top 12 projects + their links (one round-trip via Drizzle relational query)
|
||||
const helixProjects = await db.query.projects.findMany({
|
||||
where: eq(projects.status, 'published'),
|
||||
orderBy: [desc(projects.updatedAt)],
|
||||
limit: 12,
|
||||
columns: {
|
||||
slug: true,
|
||||
title: true,
|
||||
summary: true,
|
||||
strand: true,
|
||||
coverUrl: true
|
||||
},
|
||||
with: {
|
||||
links: {
|
||||
columns: { kind: true, label: true, url: true, position: true },
|
||||
orderBy: [asc(projectLinks.position)]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const totalPublished = db
|
||||
.select({ slug: projects.slug })
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="hero-content">
|
||||
<p class="eyebrow">R&D · {SITE.organization}</p>
|
||||
<h1 class="title">
|
||||
<span class="word">HELIX</span>
|
||||
<span class="word">{SITE.name}</span>
|
||||
</h1>
|
||||
<p class="tagline">{SITE.tagline}</p>
|
||||
<p class="lede">{SITE.description}</p>
|
||||
@@ -37,7 +37,7 @@
|
||||
{#if data.helixProjects.length === 0}
|
||||
<div class="empty">
|
||||
<h2>The strands are empty</h2>
|
||||
<p>HELIX needs its first project. <a href="/projects/new">Add one →</a></p>
|
||||
<p>R&D-lab needs its first project. <a href="/projects/new">Add one →</a></p>
|
||||
</div>
|
||||
{:else}
|
||||
<header class="section-head">
|
||||
@@ -167,12 +167,12 @@
|
||||
border-radius: 50%;
|
||||
}
|
||||
.leg-a {
|
||||
background: var(--color-helix-process);
|
||||
box-shadow: 0 0 10px var(--color-helix-process);
|
||||
background: #1fa0db;
|
||||
box-shadow: 0 0 10px #1fa0db;
|
||||
}
|
||||
.leg-b {
|
||||
background: var(--color-helix-accent-2);
|
||||
box-shadow: 0 0 10px var(--color-helix-accent-2);
|
||||
background: #bed137;
|
||||
box-shadow: 0 0 10px #bed137;
|
||||
}
|
||||
|
||||
.scroll-hint {
|
||||
@@ -208,7 +208,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- HELIX SECTION ---------- */
|
||||
/* ---------- STRAND SECTION ---------- */
|
||||
.helix-anchor {
|
||||
margin-top: 4rem;
|
||||
padding: 0 1rem;
|
||||
|
||||
@@ -45,7 +45,7 @@ export const GET = async ({ url, cookies }) => {
|
||||
if (GITEA_ALLOWED_ORG) {
|
||||
const allowed = await isUserInOrg(accessToken, giteaUser.login, GITEA_ALLOWED_ORG);
|
||||
if (!allowed) {
|
||||
error(403, `HELIX is restricted to members of the "${GITEA_ALLOWED_ORG}" Gitea organisation.`);
|
||||
error(403, `R&D-lab is restricted to members of the "${GITEA_ALLOWED_ORG}" Gitea organisation.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="rounded-2xl border border-helix-border bg-helix-bg-2/60 p-10 backdrop-blur">
|
||||
<h1 class="text-3xl font-semibold tracking-tight">Sign in to {SITE.name}</h1>
|
||||
<p class="mt-3 text-helix-ink-dim">
|
||||
HELIX uses your <strong class="text-helix-ink">Gitea</strong> account at
|
||||
{SITE.name} uses your <strong class="text-helix-ink">Gitea</strong> account at
|
||||
<code class="font-mono text-sm">{SITE.giteaBaseUrl.replace('https://', '')}</code>.
|
||||
Anyone can read; authoring is restricted to the
|
||||
<code class="font-mono text-sm">{SITE.giteaOrg}</code> organisation.
|
||||
@@ -30,7 +30,7 @@
|
||||
</a>
|
||||
|
||||
<p class="mt-6 text-xs text-helix-ink-faint">
|
||||
You'll be redirected to Gitea to approve, then back here. No password is stored by HELIX.
|
||||
You'll be redirected to Gitea to approve, then back here. No password is stored by {SITE.name}.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import PostCard from '$lib/components/PostCard.svelte';
|
||||
import { SITE } from '$lib/config';
|
||||
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Posts · HELIX</title>
|
||||
<title>Posts · {SITE.name}</title>
|
||||
</svelte:head>
|
||||
|
||||
<section class="page">
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { SITE } from '$lib/config';
|
||||
|
||||
let { data } = $props();
|
||||
const published = $derived(
|
||||
data.post.publishedAt
|
||||
@@ -12,7 +14,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{data.post.title} · HELIX</title>
|
||||
<title>{data.post.title} · {SITE.name}</title>
|
||||
<meta name="description" content={data.post.summary || data.post.title} />
|
||||
</svelte:head>
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { SITE } from '$lib/config';
|
||||
|
||||
let { form } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>New post · HELIX</title>
|
||||
<title>New post · {SITE.name}</title>
|
||||
</svelte:head>
|
||||
|
||||
<section class="page">
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import ProjectCard from '$lib/components/ProjectCard.svelte';
|
||||
import { SITE } from '$lib/config';
|
||||
|
||||
let { data } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Projects · HELIX</title>
|
||||
<title>Projects · {SITE.name}</title>
|
||||
</svelte:head>
|
||||
|
||||
<section class="page">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import LinkChips from '$lib/components/LinkChips.svelte';
|
||||
import DashboardEmbed from '$lib/components/DashboardEmbed.svelte';
|
||||
import { SITE } from '$lib/config';
|
||||
|
||||
let { data } = $props();
|
||||
const updated = $derived(
|
||||
@@ -13,7 +14,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{data.project.title} · HELIX</title>
|
||||
<title>{data.project.title} · {SITE.name}</title>
|
||||
<meta name="description" content={data.project.summary} />
|
||||
</svelte:head>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { LINK_KINDS, LINK_KIND_LABEL } from '$lib/config';
|
||||
import { LINK_KINDS, LINK_KIND_LABEL, SITE } from '$lib/config';
|
||||
import { enhance } from '$app/forms';
|
||||
|
||||
let { form } = $props();
|
||||
@@ -16,7 +16,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>New project · HELIX</title>
|
||||
<title>New project · {SITE.name}</title>
|
||||
</svelte:head>
|
||||
|
||||
<section class="page">
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<!-- Dark backplate -->
|
||||
<rect width="24" height="24" rx="5" fill="#07111d"/>
|
||||
<path d="M5 4 C 5 10, 19 14, 19 20" fill="none" stroke="#4dd0c2" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<path d="M5 20 C 5 14, 19 10, 19 4" fill="none" stroke="#0c99d9" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<line x1="6" y1="6" x2="6" y2="6.5" stroke="#a9daee" stroke-width="1.2"/>
|
||||
<line x1="12" y1="12" x2="12" y2="12.5" stroke="#a9daee" stroke-width="1.2"/>
|
||||
<line x1="18" y1="18" x2="18" y2="18.5" stroke="#a9daee" stroke-width="1.2"/>
|
||||
|
||||
<!-- WBD-style tilted square mark (echo of the Waterschap diamond) -->
|
||||
<g transform="rotate(45 12 12)">
|
||||
<rect x="5.5" y="5.5" width="13" height="13" rx="0.5" fill="#0d4f9e" fill-opacity="0.9"/>
|
||||
<rect x="8.2" y="8.2" width="7.6" height="7.6" rx="0.4" fill="#1fa0db"/>
|
||||
</g>
|
||||
|
||||
<!-- Helix strand A (front) — WBD lime, wraps around -->
|
||||
<path d="M4 4 C 4 10, 20 14, 20 20"
|
||||
fill="none" stroke="#bed137" stroke-width="2.1" stroke-linecap="round"/>
|
||||
<!-- Helix strand B (back) — softer cyan -->
|
||||
<path d="M4 20 C 4 14, 20 10, 20 4"
|
||||
fill="none" stroke="#1fa0db" stroke-width="2.1" stroke-linecap="round" stroke-opacity="0.9"/>
|
||||
|
||||
<!-- Center "active site" lime dot -->
|
||||
<circle cx="12" cy="12" r="1.9" fill="#bed137"/>
|
||||
<circle cx="12" cy="12" r="0.9" fill="#07111d"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 581 B After Width: | Height: | Size: 950 B |
@@ -4,12 +4,19 @@ export default {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// Waterschap Brabantse Delta brand palette (from WSBD-logo.svg)
|
||||
wbd: {
|
||||
deep: '#0a3d80',
|
||||
blue: '#0d4f9e',
|
||||
cyan: '#1fa0db',
|
||||
lime: '#bed137'
|
||||
},
|
||||
helix: {
|
||||
area: '#0f52a5',
|
||||
process: '#0c99d9',
|
||||
unit: '#50a8d9',
|
||||
equipment: '#86bbdd',
|
||||
control: '#a9daee',
|
||||
area: '#0a3d80',
|
||||
process: '#0d4f9e',
|
||||
unit: '#1fa0db',
|
||||
equipment: '#6fc3ec',
|
||||
control: '#b8dff5',
|
||||
bg: '#07111d',
|
||||
'bg-2': '#0c1c30',
|
||||
'bg-3': '#122842',
|
||||
@@ -17,8 +24,8 @@ export default {
|
||||
ink: '#e6f1fb',
|
||||
'ink-dim': '#8fa6b8',
|
||||
'ink-faint': '#5b7388',
|
||||
accent: '#4dd0c2',
|
||||
'accent-2': '#c084fc'
|
||||
accent: '#bed137',
|
||||
'accent-2': '#d8e36a'
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
|
||||
Reference in New Issue
Block a user