Data Model (Schema V3)
The project data model is defined in src/types/parallax.ts and validated/normalized by src/core/schema.ts. All import/export operations use this schema.
Object Graph
The following diagram shows the relationships between the main types in the project schema:
Compatibility
Schema v3 is a strict format. v2 payloads are intentionally rejected — there is no migration path.
Root Structure
type ParallaxDesignerProject = {
version: 3;
scene: SceneConfig;
harmonics: FloatHarmonics;
layers: ParallaxLayer[]; // max: 30
ui: UiState;
};Scene
type SceneConfig = {
maxRot: number; // 0 .. 10
containerLerp: number; // 0.005 .. 0.3
perspective: number; // 200 .. 2000
documentBackgroundColor: string; // CSS color
};Harmonics
type FloatHarmonics = {
freqA: number; // 0 .. 3
freqB: number; // 0 .. 3
ampB: number; // 0 .. 1
freqC: number; // 0 .. 3
freqD: number; // 0 .. 3
ampD: number; // 0 .. 1
};Layer
type ParallaxLayer = {
id: string;
presetKey?: string;
name: string;
imageSrc: string; // URL, relative path, or idb://asset/<id>
visible: boolean;
geometry: {
topPct: number; // -50 .. 50
leftPct: number; // -50 .. 50
widthPct: number; // 50 .. 180
heightPct: number; // 50 .. 180
zIndex: number; // 1 .. 30
baseZ: number; // -300 .. 100
baseScale: number; // 0.8 .. 1.5
};
background: {
position: {
anchorX: "left" | "center" | "right";
anchorY: "top" | "center" | "bottom";
offsetXPct: number; // -100 .. 100
offsetYPct: number; // -100 .. 100
};
size: {
mode: "cover" | "contain" | "auto" | "custom";
widthPct: number; // 1 .. 400
heightPct: number; // 1 .. 400
};
repeat: {
x: "repeat" | "no-repeat" | "round" | "space";
y: "repeat" | "no-repeat" | "round" | "space";
};
color: string; // CSS color
blendMode: BlendMode; // 16 CSS blend modes
origin: BoxModel; // border-box | padding-box | content-box
clip: BoxModel;
};
motion: {
lerp: number; // 0.005 .. 0.3
scaleBoost: number; // 0 .. 0.1
moveX: number; // 0 .. 80
moveY: number; // 0 .. 80
floatX: number; // 0 .. 20
floatY: number; // 0 .. 20
floatSpeed: number; // 0 .. 0.002
};
};UI State
type UiState = {
panelVisible: boolean;
fpsVisible: boolean;
mode: "tune" | "runtime";
selectedLayerId: string | null;
};Validation Rules
The schema validator (src/core/schema.ts) applies these rules on import/load:
| Rule | Behavior |
|---|---|
version must equal 3 | Rejected if not exactly 3 |
| Enum fields must match allowed values | Rejected on invalid enum |
| Numerics must be finite | NaN / Infinity rejected |
| Color strings must be valid CSS | Rejected on parse failure |
| Out-of-range numerics | Automatically clamped to schema-defined ranges |
| Layer count above 30 | Trimmed from the end |
| Numeric step values | Clamped only — no step snapping |
Result Pattern
Validation returns a Result<T, E> instead of throwing exceptions:
type Result<T, E> = { ok: true; data: T } | { ok: false; error: E };On success, the data field contains the validated and normalized project. On failure, the error field contains a descriptive message.
Normalization Summary
When values are clamped or layers trimmed during import, a ProjectNormalizationSummary is generated listing all adjustments. This is displayed to the user so they know exactly what changed.
Constraints
Numeric constraints (min, max, step) for every field are defined in src/core/projectConstraints.ts and shared between:
- The schema validator (for clamping)
- UI slider controls (for input ranges and step increments)
Step Values
| Category | Field | Step |
|---|---|---|
| Scene | maxRot | 0.1 |
| Scene | containerLerp | 0.005 |
| Scene | perspective | 10 |
| Harmonics | freq* | 0.05 |
| Harmonics | amp* | 0.05 |
| Geometry | top/left/width/height | 0.25 |
| Geometry | zIndex | 1 |
| Geometry | baseZ | 1 |
| Geometry | baseScale | 0.0025 |
| Motion | lerp | 0.005 |
| Motion | scaleBoost | 0.001 |
| Motion | moveX/Y | 1 |
| Motion | floatX/Y | 0.5 |
| Motion | floatSpeed | 0.00001 |
| Background | size width/height | 1 |
| Background | position offsets | 1 |
Storage & Share Keys
| Storage Location | Key / Path | Content |
|---|---|---|
| localStorage | parallax-designer-locale | Locale preference (app-level only) |
| IndexedDB | DB: parallax-designer-assets-v2 | Assets + custom presets |
| IndexedDB table | assets | Uploaded image blobs |
| IndexedDB table | customPresets | Named project snapshots |
Local Upload References
Uploaded images are stored as blobs in IndexedDB and referenced in project payloads as idb://asset/<id>. These references are valid only in the browser profile that contains the corresponding IndexedDB record.
Copy JSON preserves these references and surfaces a portability warning. Download HTML offers an IDB handling mode (inline data URLs or blank placeholders).
Custom Presets
Custom presets are stored in IndexedDB (customPresets table) and are not embedded in exported project JSON. Each record contains:
name— User-assigned preset name- Full
ParallaxDesignerProjectpayload createdAt,updatedAt— TimestampslayerCount— Number of layers in the snapshot