bluedither 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bluedither.config.json +14 -0
- package/cli/bin.js +60 -0
- package/cli/commands/extract.js +103 -0
- package/cli/commands/install.js +126 -0
- package/cli/commands/publish.js +85 -0
- package/cli/commands/tune.js +63 -0
- package/dist/bluedither-shader.js +4867 -0
- package/dist/bluedither-tuner.js +401 -0
- package/lib/render.js +108 -0
- package/package.json +40 -0
- package/skill.md +122 -0
- package/theme/generators/react.md +88 -0
- package/theme/generators/svelte.md +103 -0
- package/theme/generators/vanilla.md +54 -0
- package/theme/generators/vue.md +104 -0
- package/theme/rules.md +71 -0
- package/theme/shaders/bluedither-shader.js +92 -0
- package/theme/shaders/paper-shaders-bundle.js +6159 -0
- package/theme/structure.json +167 -0
- package/theme/template/index.html +235 -0
- package/theme/tokens.defaults.json +81 -0
- package/theme/tokens.json +81 -0
- package/theme/tokens.schema.json +166 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# React Generation
|
|
2
|
+
|
|
3
|
+
When the target project uses **React** (detected via `react` in package.json dependencies), follow these patterns.
|
|
4
|
+
|
|
5
|
+
## Detection
|
|
6
|
+
|
|
7
|
+
React is detected when `package.json` contains `react` in `dependencies` or `devDependencies`. Also check for:
|
|
8
|
+
- `react-dom` (standard React)
|
|
9
|
+
- `next` (Next.js — use `"use client"` directive)
|
|
10
|
+
- TypeScript: present if `typescript` is in devDependencies or `tsconfig.json` exists
|
|
11
|
+
|
|
12
|
+
## Component Structure
|
|
13
|
+
|
|
14
|
+
Generate separate component files matching `structure.json` nodes:
|
|
15
|
+
|
|
16
|
+
### `BlueDitherTheme.jsx` (or `.tsx`)
|
|
17
|
+
Root component wrapping the full layout. Imports and composes child components.
|
|
18
|
+
|
|
19
|
+
```jsx
|
|
20
|
+
import { BlueDitherHeader } from './BlueDitherHeader';
|
|
21
|
+
import { BlueDitherHero } from './BlueDitherHero';
|
|
22
|
+
import './bluedither.css';
|
|
23
|
+
|
|
24
|
+
export function BlueDitherTheme() {
|
|
25
|
+
return (
|
|
26
|
+
<div className="bd-root">
|
|
27
|
+
<BlueDitherHero />
|
|
28
|
+
<BlueDitherHeader />
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### `BlueDitherHeader.jsx`
|
|
35
|
+
Navbar component with logo, nav items, and CTA.
|
|
36
|
+
|
|
37
|
+
- Nav items come from `content.navItems` token array
|
|
38
|
+
- Use `<nav>` with `<a>` elements for nav items
|
|
39
|
+
- CTA is an `<a>` or `<button>` element
|
|
40
|
+
|
|
41
|
+
### `BlueDitherHero.jsx`
|
|
42
|
+
Hero section with shader and text content.
|
|
43
|
+
|
|
44
|
+
- **Shader initialization**: Use `useEffect` + `useRef` for the shader parent div:
|
|
45
|
+
```jsx
|
|
46
|
+
import { useEffect, useRef } from 'react';
|
|
47
|
+
|
|
48
|
+
const shaderRef = useRef(null);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
// Dynamic import to avoid SSR issues
|
|
52
|
+
import('./paper-shaders-bundle.js').then(({ ShaderMount, ditheringFragmentShader, getShaderColorFromString }) => {
|
|
53
|
+
const mount = new ShaderMount(shaderRef.current, ditheringFragmentShader, uniforms, opts, speed, 0, 2);
|
|
54
|
+
return () => mount.dispose();
|
|
55
|
+
});
|
|
56
|
+
}, []);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `bluedither.css`
|
|
60
|
+
All CSS custom properties and `.bd-*` class rules, identical to the vanilla `<style>` block but as an external file.
|
|
61
|
+
|
|
62
|
+
## TypeScript
|
|
63
|
+
|
|
64
|
+
If TypeScript is detected, use `.tsx` extensions and add minimal type annotations:
|
|
65
|
+
- Props interfaces where components accept props
|
|
66
|
+
- `useRef<HTMLDivElement>(null)` for refs
|
|
67
|
+
|
|
68
|
+
## Next.js
|
|
69
|
+
|
|
70
|
+
If Next.js is detected:
|
|
71
|
+
- Add `"use client"` at the top of components that use `useEffect` or `useRef`
|
|
72
|
+
- The CSS file can be imported directly in the component
|
|
73
|
+
|
|
74
|
+
## File Output
|
|
75
|
+
|
|
76
|
+
| File | Contents |
|
|
77
|
+
|------|----------|
|
|
78
|
+
| `BlueDitherTheme.jsx/tsx` | Root layout component |
|
|
79
|
+
| `BlueDitherHeader.jsx/tsx` | Navbar component |
|
|
80
|
+
| `BlueDitherHero.jsx/tsx` | Hero + shader component |
|
|
81
|
+
| `bluedither.css` | CSS custom properties + rules |
|
|
82
|
+
| `paper-shaders-bundle.js` | Shader library (copied) |
|
|
83
|
+
|
|
84
|
+
## Content Handling
|
|
85
|
+
|
|
86
|
+
- Inject content tokens directly as JSX text
|
|
87
|
+
- Headline newlines: use `{headline.split('\\n').map((line, i) => <Fragment key={i}>{i > 0 && <br />}{line}</Fragment>)}`
|
|
88
|
+
- Nav items: `{navItems.map(item => <a key={item} className="bd-nav-item" href="#">{item}</a>)}`
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Svelte Generation
|
|
2
|
+
|
|
3
|
+
When the target project uses **Svelte** (detected via `svelte` in package.json dependencies), follow these patterns.
|
|
4
|
+
|
|
5
|
+
## Detection
|
|
6
|
+
|
|
7
|
+
Svelte is detected when `package.json` contains `svelte` in `dependencies` or `devDependencies`. Also check for:
|
|
8
|
+
- `@sveltejs/kit` (SvelteKit)
|
|
9
|
+
- TypeScript: present if `typescript` is in devDependencies or `svelte.config.js` references TypeScript
|
|
10
|
+
|
|
11
|
+
## Component Structure
|
|
12
|
+
|
|
13
|
+
Generate Svelte components (`.svelte` files):
|
|
14
|
+
|
|
15
|
+
### `BlueDitherTheme.svelte`
|
|
16
|
+
Root component:
|
|
17
|
+
```svelte
|
|
18
|
+
<script>
|
|
19
|
+
import BlueDitherHeader from './BlueDitherHeader.svelte';
|
|
20
|
+
import BlueDitherHero from './BlueDitherHero.svelte';
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<div class="bd-root">
|
|
24
|
+
<BlueDitherHero />
|
|
25
|
+
<BlueDitherHeader />
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<style src="./bluedither.css"></style>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### `BlueDitherHeader.svelte`
|
|
32
|
+
Navbar with content tokens inlined:
|
|
33
|
+
```svelte
|
|
34
|
+
<script>
|
|
35
|
+
const navItems = ['HOME', 'FEATURES', 'FAQ', 'CONTACT'];
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<header class="bd-header">
|
|
39
|
+
<div class="bd-header-inner">
|
|
40
|
+
<div class="bd-logo">COMPANYLOGO</div>
|
|
41
|
+
<nav class="bd-nav">
|
|
42
|
+
{#each navItems as item}
|
|
43
|
+
<a class="bd-nav-item" href="#">{item}</a>
|
|
44
|
+
{/each}
|
|
45
|
+
</nav>
|
|
46
|
+
<a class="bd-cta" href="#">
|
|
47
|
+
<span class="bd-cta-text">SIGN UP</span>
|
|
48
|
+
</a>
|
|
49
|
+
</div>
|
|
50
|
+
</header>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `BlueDitherHero.svelte`
|
|
54
|
+
Hero section with shader initialization using `onMount`:
|
|
55
|
+
```svelte
|
|
56
|
+
<script>
|
|
57
|
+
import { onMount, onDestroy } from 'svelte';
|
|
58
|
+
|
|
59
|
+
let shaderParent;
|
|
60
|
+
let shaderMount = null;
|
|
61
|
+
|
|
62
|
+
onMount(async () => {
|
|
63
|
+
const { ShaderMount, ditheringFragmentShader, getShaderColorFromString } = await import('./paper-shaders-bundle.js');
|
|
64
|
+
shaderMount = new ShaderMount(shaderParent, ditheringFragmentShader, uniforms, opts, speed, 0, 2);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
onDestroy(() => {
|
|
68
|
+
shaderMount?.dispose();
|
|
69
|
+
});
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<div class="bd-hero">
|
|
73
|
+
<div class="bd-hero-inner">
|
|
74
|
+
<div bind:this={shaderParent} class="bd-shader-layer"></div>
|
|
75
|
+
<div class="bd-hero-content">
|
|
76
|
+
<div class="bd-headline">AN ABSOLUTELY<br>FANTASTIC HEADLINE.</div>
|
|
77
|
+
<div class="bd-subheadline">Sub-headline text here.</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Svelte 5 Runes
|
|
84
|
+
|
|
85
|
+
If Svelte 5 is detected (check `svelte` version in package.json):
|
|
86
|
+
- Use `$state()` instead of `let` for reactive variables
|
|
87
|
+
- Use `$effect()` instead of `onMount` for side effects with cleanup
|
|
88
|
+
|
|
89
|
+
## SvelteKit
|
|
90
|
+
|
|
91
|
+
If SvelteKit is detected:
|
|
92
|
+
- Place components in `src/lib/` directory
|
|
93
|
+
- Import shader bundle using `$lib/` alias
|
|
94
|
+
|
|
95
|
+
## File Output
|
|
96
|
+
|
|
97
|
+
| File | Contents |
|
|
98
|
+
|------|----------|
|
|
99
|
+
| `BlueDitherTheme.svelte` | Root layout component |
|
|
100
|
+
| `BlueDitherHeader.svelte` | Navbar component |
|
|
101
|
+
| `BlueDitherHero.svelte` | Hero + shader component |
|
|
102
|
+
| `bluedither.css` | CSS custom properties + rules |
|
|
103
|
+
| `paper-shaders-bundle.js` | Shader library (copied) |
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Vanilla HTML Generation
|
|
2
|
+
|
|
3
|
+
When the target project is **plain HTML/CSS/JS** (no framework detected), follow these patterns.
|
|
4
|
+
|
|
5
|
+
## Output Structure
|
|
6
|
+
|
|
7
|
+
Generate a single `index.html` file containing:
|
|
8
|
+
1. Inline `<style>` with CSS custom properties from tokens + all `.bd-*` class rules
|
|
9
|
+
2. Semantic HTML matching `structure.json` exactly
|
|
10
|
+
3. `<script type="module">` at the end of `<body>` that:
|
|
11
|
+
- Imports `bluedither-shader.js` (or the bundled version)
|
|
12
|
+
- Initializes `ShaderMount` on `#bd-shader-parent`
|
|
13
|
+
|
|
14
|
+
## CSS Custom Properties
|
|
15
|
+
|
|
16
|
+
Compute `clamp()` values from all `referencePx` and spacing tokens using:
|
|
17
|
+
```
|
|
18
|
+
clamp(max*0.55 rem, px/designWidth*100 vw, px/16 rem)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Emit all properties under `:root` using the `--bd-*` namespace. See `template/index.html` for the exact variable names.
|
|
22
|
+
|
|
23
|
+
## Google Fonts
|
|
24
|
+
|
|
25
|
+
Include `<link>` tags for `typography.primaryFont` and `typography.secondaryFont` via Google Fonts CDN:
|
|
26
|
+
```html
|
|
27
|
+
<link href="https://fonts.googleapis.com/css2?family=FONT_NAME:wght@400;700&display=swap" rel="stylesheet">
|
|
28
|
+
```
|
|
29
|
+
URL-encode the font family name.
|
|
30
|
+
|
|
31
|
+
## Shader Module
|
|
32
|
+
|
|
33
|
+
Copy `theme/shaders/bluedither-shader.js` and `theme/shaders/paper-shaders-bundle.js` to the target project.
|
|
34
|
+
|
|
35
|
+
Import and initialize inline:
|
|
36
|
+
```js
|
|
37
|
+
import { ShaderMount, ditheringFragmentShader, getShaderColorFromString } from './paper-shaders-bundle.js';
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Map shader tokens to uniforms as shown in `template/index.html`'s existing `<script type="module">` block.
|
|
41
|
+
|
|
42
|
+
## File Output
|
|
43
|
+
|
|
44
|
+
| File | Contents |
|
|
45
|
+
|------|----------|
|
|
46
|
+
| `index.html` | Complete rendered page |
|
|
47
|
+
| `bluedither-shader.js` | Shader module (copied) |
|
|
48
|
+
| `paper-shaders-bundle.js` | Shader library bundle (copied) |
|
|
49
|
+
|
|
50
|
+
## Content Handling
|
|
51
|
+
|
|
52
|
+
- Replace `{{content.*}}` placeholders with token values
|
|
53
|
+
- `{{content.headline}}` newlines become `<br>` tags
|
|
54
|
+
- `{{#each content.navItems}}` expands to repeated `<a>` elements
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Vue Generation
|
|
2
|
+
|
|
3
|
+
When the target project uses **Vue** (detected via `vue` in package.json dependencies), follow these patterns.
|
|
4
|
+
|
|
5
|
+
## Detection
|
|
6
|
+
|
|
7
|
+
Vue is detected when `package.json` contains `vue` in `dependencies` or `devDependencies`. Also check for:
|
|
8
|
+
- `nuxt` (Nuxt.js)
|
|
9
|
+
- TypeScript: present if `typescript` is in devDependencies or `tsconfig.json` exists
|
|
10
|
+
|
|
11
|
+
## Component Structure
|
|
12
|
+
|
|
13
|
+
Generate Vue Single File Components (SFCs) using `<script setup>` syntax (Vue 3):
|
|
14
|
+
|
|
15
|
+
### `BlueDitherTheme.vue`
|
|
16
|
+
Root component:
|
|
17
|
+
```vue
|
|
18
|
+
<script setup>
|
|
19
|
+
import BlueDitherHeader from './BlueDitherHeader.vue';
|
|
20
|
+
import BlueDitherHero from './BlueDitherHero.vue';
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div class="bd-root">
|
|
25
|
+
<BlueDitherHero />
|
|
26
|
+
<BlueDitherHeader />
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<style src="./bluedither.css"></style>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### `BlueDitherHeader.vue`
|
|
34
|
+
Navbar with logo, nav items, CTA:
|
|
35
|
+
```vue
|
|
36
|
+
<script setup>
|
|
37
|
+
const navItems = ['HOME', 'FEATURES', 'FAQ', 'CONTACT'];
|
|
38
|
+
const ctaText = 'SIGN UP';
|
|
39
|
+
const companyName = 'COMPANYLOGO';
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<header class="bd-header">
|
|
44
|
+
<div class="bd-header-inner">
|
|
45
|
+
<div class="bd-logo">{{ companyName }}</div>
|
|
46
|
+
<nav class="bd-nav">
|
|
47
|
+
<a v-for="item in navItems" :key="item" class="bd-nav-item" href="#">{{ item }}</a>
|
|
48
|
+
</nav>
|
|
49
|
+
<a class="bd-cta" href="#">
|
|
50
|
+
<span class="bd-cta-text">{{ ctaText }}</span>
|
|
51
|
+
</a>
|
|
52
|
+
</div>
|
|
53
|
+
</header>
|
|
54
|
+
</template>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `BlueDitherHero.vue`
|
|
58
|
+
Hero section with shader initialization using `onMounted`:
|
|
59
|
+
```vue
|
|
60
|
+
<script setup>
|
|
61
|
+
import { ref, onMounted, onUnmounted } from 'vue';
|
|
62
|
+
|
|
63
|
+
const shaderParent = ref(null);
|
|
64
|
+
let shaderMount = null;
|
|
65
|
+
|
|
66
|
+
onMounted(async () => {
|
|
67
|
+
const { ShaderMount, ditheringFragmentShader, getShaderColorFromString } = await import('./paper-shaders-bundle.js');
|
|
68
|
+
shaderMount = new ShaderMount(shaderParent.value, ditheringFragmentShader, uniforms, opts, speed, 0, 2);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
onUnmounted(() => {
|
|
72
|
+
shaderMount?.dispose();
|
|
73
|
+
});
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<template>
|
|
77
|
+
<div class="bd-hero">
|
|
78
|
+
<div class="bd-hero-inner">
|
|
79
|
+
<div ref="shaderParent" class="bd-shader-layer"></div>
|
|
80
|
+
<div class="bd-hero-content">
|
|
81
|
+
<div class="bd-headline">...</div>
|
|
82
|
+
<div class="bd-subheadline">...</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</template>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## TypeScript
|
|
90
|
+
|
|
91
|
+
If TypeScript is detected, add `lang="ts"` to `<script setup lang="ts">`. Type the template ref:
|
|
92
|
+
```ts
|
|
93
|
+
const shaderParent = ref<HTMLDivElement | null>(null);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## File Output
|
|
97
|
+
|
|
98
|
+
| File | Contents |
|
|
99
|
+
|------|----------|
|
|
100
|
+
| `BlueDitherTheme.vue` | Root layout SFC |
|
|
101
|
+
| `BlueDitherHeader.vue` | Navbar SFC |
|
|
102
|
+
| `BlueDitherHero.vue` | Hero + shader SFC |
|
|
103
|
+
| `bluedither.css` | CSS custom properties + rules |
|
|
104
|
+
| `paper-shaders-bundle.js` | Shader library (copied) |
|
package/theme/rules.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# BlueDither Design Language Rules
|
|
2
|
+
|
|
3
|
+
These rules govern how tokens should be applied when an AI agent generates or extends BlueDither-themed components. They encode the design intent behind the token values.
|
|
4
|
+
|
|
5
|
+
## Color Relationships
|
|
6
|
+
|
|
7
|
+
- **Background** (`colors.background`) is always the darkest value — near-black with a color tint (e.g., deep navy `#040037`).
|
|
8
|
+
- **Primary** (`colors.primary`) is a saturated accent used for the navbar background and shader foreground. It should be a bold, high-chroma color.
|
|
9
|
+
- **Text** (`colors.text`) is always light (white or near-white) for contrast against the dark background.
|
|
10
|
+
- **CTA** uses inverted contrast: light background (`colors.ctaBackground`) with dark text (`colors.ctaText`). The CTA must visually pop against both the navbar and the hero.
|
|
11
|
+
- **Shader colors**: `shaderFront` typically matches `colors.primary`. `shaderBack` is usually transparent (`#00000000`) so the page background shows through the dither pattern.
|
|
12
|
+
- When adjusting colors, maintain a minimum contrast ratio of 4.5:1 for body text and 3:1 for large text (headline).
|
|
13
|
+
|
|
14
|
+
## Type Scale
|
|
15
|
+
|
|
16
|
+
- **Two font families only**: a display/primary font (bold, impactful — e.g., Bebas Neue) and a secondary/mono font (readable, technical — e.g., Space Mono).
|
|
17
|
+
- **Primary font** is used for: logo, nav items, headline, CTA text. Always set to `primaryFontWeight` (typically 700).
|
|
18
|
+
- **Secondary font** is used for: sub-headline only. Always set to `secondaryFontWeight` (typically 400).
|
|
19
|
+
- **Size hierarchy** (from largest to smallest): headline → logo ≈ nav ≈ CTA → sub-headline. The headline should be dramatically larger (5-7x) than the sub-headline.
|
|
20
|
+
- All typography `referencePx` values are relative to `layout.designWidth`. At render time they become `clamp()` values for fluid responsiveness.
|
|
21
|
+
- The formula: `clamp(max*0.55 rem, px/designWidth*100 vw, px/16 rem)` — scales down to 55% on small screens.
|
|
22
|
+
|
|
23
|
+
## Spacing System
|
|
24
|
+
|
|
25
|
+
- All spacing values are in reference px at `designWidth`, converted to `clamp()` the same way as typography.
|
|
26
|
+
- **Header padding** is tight — just enough to frame the logo/nav/CTA row.
|
|
27
|
+
- **Hero padding** is generous at the bottom and sides, minimal at the top — content anchors to the bottom-left.
|
|
28
|
+
- **Nav gap** should be wide enough for clear separation but not so wide the items lose visual grouping.
|
|
29
|
+
- **CTA padding** is compact (button should feel tight/punchy, not bloated).
|
|
30
|
+
- **Border radius** is intentionally small (4px default). The design language is angular, not rounded.
|
|
31
|
+
|
|
32
|
+
## Font Usage
|
|
33
|
+
|
|
34
|
+
- All text in the navbar uses the primary font.
|
|
35
|
+
- Only the sub-headline uses the secondary font.
|
|
36
|
+
- Text transform (`uppercase`) is applied via CSS, not by modifying content strings. The `textTransform` token on sub-headline controls this.
|
|
37
|
+
- When generating content, write it in the natural case for the slot — CSS handles visual casing.
|
|
38
|
+
|
|
39
|
+
## Opacity Conventions
|
|
40
|
+
|
|
41
|
+
- `opacity.navLinks` dims the navigation row slightly (default 0.87) to create depth hierarchy. The logo and CTA remain at full opacity.
|
|
42
|
+
- Opacity should stay above 0.7 for accessibility. Values below 0.5 make text unreadable.
|
|
43
|
+
|
|
44
|
+
## Shader Integration
|
|
45
|
+
|
|
46
|
+
- The shader layer is always **full-bleed** behind hero content, positioned absolute.
|
|
47
|
+
- Shader `rotation` rotates the entire shader layer via CSS `rotate()`.
|
|
48
|
+
- The shader is a WebGL `<canvas>` managed by `ShaderMount` from `@paper-design/shaders`.
|
|
49
|
+
- Available shapes: simplex, warp, dots, wave, ripple, swirl, sphere. Default is `warp`.
|
|
50
|
+
- Available dither types: random, 2x2, 4x4, 8x8. Default is `2x2`.
|
|
51
|
+
- `speed` controls animation rate (0 = frozen, 0.18 = subtle motion, >1 = energetic).
|
|
52
|
+
- `scale` controls the zoom of the noise pattern. `size` controls dither pixel size.
|
|
53
|
+
|
|
54
|
+
## Layout Constraints
|
|
55
|
+
|
|
56
|
+
- The layout is **exactly two visual sections**: a header (navbar) and a hero. Nothing else.
|
|
57
|
+
- The header sits on top with `z-index: 10`, the hero is positioned absolute behind it.
|
|
58
|
+
- Hero content aligns to the **bottom-left** (`justify-content: end; align-items: start`).
|
|
59
|
+
- The root container is `min-height: 100vh` with `overflow: clip`.
|
|
60
|
+
- The design is **not scrollable** — it's a single-viewport statement piece.
|
|
61
|
+
|
|
62
|
+
## Extending the Theme
|
|
63
|
+
|
|
64
|
+
When the AI generates new components outside the locked template:
|
|
65
|
+
1. Use only CSS custom properties from the `--bd-*` namespace.
|
|
66
|
+
2. Follow the same two-font-family rule — never introduce a third font.
|
|
67
|
+
3. Maintain the dark-background + light-text paradigm.
|
|
68
|
+
4. Use `colors.primary` as the accent for interactive elements.
|
|
69
|
+
5. Use `colors.ctaBackground`/`colors.ctaText` for any additional buttons.
|
|
70
|
+
6. New spacing should derive from existing tokens (multiples or fractions of `heroPaddingX`).
|
|
71
|
+
7. Keep the angular, minimal aesthetic — no rounded corners > 8px, no shadows, no gradients (the shader IS the visual interest).
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlueDither Shader Module
|
|
3
|
+
*
|
|
4
|
+
* Uses the actual @paper-design/shaders library (ShaderMount + ditheringFragmentShader)
|
|
5
|
+
* for pixel-accurate rendering matching the Paper design tool.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
ShaderMount,
|
|
10
|
+
ditheringFragmentShader,
|
|
11
|
+
DitheringShapes,
|
|
12
|
+
DitheringTypes,
|
|
13
|
+
getShaderColorFromString
|
|
14
|
+
} from './paper-shaders-bundle.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Mount the dithering shader into a parent element.
|
|
18
|
+
* ShaderMount creates its own canvas inside the parent.
|
|
19
|
+
*
|
|
20
|
+
* @param {HTMLElement} parentEl - Container element (shader fills it)
|
|
21
|
+
* @param {object} params - Token-driven params
|
|
22
|
+
* @returns {{ updateParams, destroy }}
|
|
23
|
+
*/
|
|
24
|
+
export function createDitheringRenderer(parentEl, params) {
|
|
25
|
+
const shapeValue = DitheringShapes[params.shape] ?? DitheringShapes.warp;
|
|
26
|
+
const typeValue = DitheringTypes[params.type] ?? DitheringTypes['2x2'];
|
|
27
|
+
|
|
28
|
+
const uniforms = {
|
|
29
|
+
u_colorFront: getShaderColorFromString(params.colorFront),
|
|
30
|
+
u_colorBack: getShaderColorFromString(params.colorBack),
|
|
31
|
+
u_shape: shapeValue,
|
|
32
|
+
u_type: typeValue,
|
|
33
|
+
u_pxSize: params.size,
|
|
34
|
+
u_scale: params.scale,
|
|
35
|
+
u_rotation: parseFloat(params.rotation) || 180,
|
|
36
|
+
u_originX: 0.5,
|
|
37
|
+
u_originY: 0.5,
|
|
38
|
+
u_worldWidth: 0,
|
|
39
|
+
u_worldHeight: 0,
|
|
40
|
+
u_fit: 0,
|
|
41
|
+
u_offsetX: 0,
|
|
42
|
+
u_offsetY: 0,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const mount = new ShaderMount(
|
|
46
|
+
parentEl,
|
|
47
|
+
ditheringFragmentShader,
|
|
48
|
+
uniforms,
|
|
49
|
+
{ alpha: true, premultipliedAlpha: false },
|
|
50
|
+
params.speed, // speed
|
|
51
|
+
0, // frame
|
|
52
|
+
2, // minPixelRatio
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
updateParams(newParams) {
|
|
57
|
+
const updatedUniforms = {};
|
|
58
|
+
|
|
59
|
+
if (newParams.colorFront !== undefined) {
|
|
60
|
+
updatedUniforms.u_colorFront = getShaderColorFromString(newParams.colorFront);
|
|
61
|
+
}
|
|
62
|
+
if (newParams.colorBack !== undefined) {
|
|
63
|
+
updatedUniforms.u_colorBack = getShaderColorFromString(newParams.colorBack);
|
|
64
|
+
}
|
|
65
|
+
if (newParams.shape !== undefined) {
|
|
66
|
+
updatedUniforms.u_shape = DitheringShapes[newParams.shape] ?? shapeValue;
|
|
67
|
+
}
|
|
68
|
+
if (newParams.type !== undefined) {
|
|
69
|
+
updatedUniforms.u_type = DitheringTypes[newParams.type] ?? typeValue;
|
|
70
|
+
}
|
|
71
|
+
if (newParams.size !== undefined) {
|
|
72
|
+
updatedUniforms.u_pxSize = newParams.size;
|
|
73
|
+
}
|
|
74
|
+
if (newParams.scale !== undefined) {
|
|
75
|
+
updatedUniforms.u_scale = newParams.scale;
|
|
76
|
+
}
|
|
77
|
+
if (newParams.rotation !== undefined) {
|
|
78
|
+
updatedUniforms.u_rotation = parseFloat(newParams.rotation) || 180;
|
|
79
|
+
}
|
|
80
|
+
if (newParams.speed !== undefined) {
|
|
81
|
+
mount.setSpeed(newParams.speed);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (Object.keys(updatedUniforms).length > 0) {
|
|
85
|
+
mount.setUniforms(updatedUniforms);
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
destroy() {
|
|
89
|
+
mount.dispose();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|