/* ============================================================ compose/schema.js — UI control descriptors for the composer, and the DEFAULT composition (two-point floor grid). Data only (no DOM). Each group: { id, label, transform?, enable?, controls:[ {path,label,type,...} ] } path = dotted path INTO the group's config. types: range {min,max,step} · color (#hex) · select {options} · toggle · text Groups with transform:true get an auto x/y/rotation/scale block. ============================================================ */ export const DEFAULT_COMPOSITION = { size: 1600, seed: 'MESON-5113', background: { color: 'rgb(229,222,203)', film: { opacity: 0.6, density: 0.5, seed: 8 }, aging: { opacity: 0.5, scratches: 5, dust: 0.45, foxing: 0.5, seed: 5 }, grain: { opacity: 0.42, intensity: 0.42, seed: 19 }, glow: { strength: 0.55, followSun: true }, // lit-page glow; centre tracks the disk vignette: { strength: 0.4, mode: 'radial', cx: 0.5, cy: 0.48, radius: 0.75, angle: 90, start: 0.5 }, }, fieldSea: { enabled: true, layerOpacity: 1, transform: { x: 0, y: 0, rotation: 0, scale: 1 }, seed: 'VACUUM-5113', color: { hueBack: 0.54, hueFront: 0.47, sat: 0.6, light: 0.36 }, // light > ~0.6 → waves lighter than ground layers: 3, chaos: 0.3, blips: 0.7, mound: 0.3, horizonY: 0.36, lines: 46, }, fieldGrid: { enabled: true, layerOpacity: 1, pos: 'over', style: 'floor', transform: { x: 0, y: 0, rotation: 0, scale: 1 }, color: { hue: 0.5, hue2: 0.55, sat: 0.32, lightNear: 0.34, lightFar: 0.62 }, opacity: 0.44, pitch: 0.45, yaw: 0.42, persp: 1.1, dist: 2.8, nx: 16, nz: 24, originY: 0.34, ripple: { amp: 0, freqI: 0.5, freqK: 0.35, phase: 0 }, resonance: { amp: 0, q: 14, axis: 'ties', hue: 0.06, sat: 0.7, light: 0.45 }, }, disk: { enabled: true, layerOpacity: 1, transform: { x: 0, y: -0.26, rotation: 0, scale: 0.78 }, hue: 0.06, sat: 0.82, size: 0.16, pressure: 0.85, intensity: 0.85, striations: 0.6, stain: 0.35, soften: 1.35, }, bubble: { enabled: true, layerOpacity: 1, transform: { x: 0.28, y: -0.1, rotation: 10, scale: 0.78 }, palette: 'magentarise', saturation: 1.05, traceHue: 0, transparentBase: true, primaries: 18, sweepers: 5, cosmics: 6, eloss: 0.34, bfield: 1.0, deltaRate: 0.6, showBoundary: true, boundaryR: 0.45, boundaryY: 0.35, boundaryOpacity: 0.4, instrument: 0.35, }, fiduciaries: { enabled: true, layerOpacity: 1, label: 'auto', pencil: '#39312a', width: 1.0, arrow: true, corners: false, caption: '', }, }; const R = (path, label, min, max, step) => ({ path, label, type: 'range', min, max, step }); export const GROUPS_SCHEMA = [ { id: 'background', label: 'Background', controls: [ { path: 'color', label: 'Paper colour', type: 'color' }, R('film.opacity', 'Film opacity', 0, 1, 0.01), R('film.density', 'Film density', 0, 1, 0.01), R('aging.opacity', 'Aging opacity', 0, 1, 0.01), R('aging.scratches', 'Scratches', 0, 20, 1), R('aging.foxing', 'Foxing', 0, 1, 0.01), R('aging.dust', 'Dust', 0, 1, 0.01), R('grain.opacity', 'Grain opacity', 0, 1, 0.01), R('grain.intensity', 'Grain intensity', 0, 1, 0.01), R('glow.strength', 'Page glow', 0, 1, 0.01), { path: 'glow.followSun', label: 'Glow follows sun', type: 'toggle' }, R('vignette.strength', 'Vignette', 0, 1, 0.01), { path: 'vignette.mode', label: 'Vignette mode', type: 'select', options: ['radial', 'linear'] }, R('vignette.cx', 'Vignette X', 0, 1, 0.01), R('vignette.cy', 'Vignette Y', 0, 1, 0.01), R('vignette.radius', 'Vignette radius', 0.3, 1.4, 0.01), R('vignette.angle', 'Vignette angle°', 0, 360, 1), ], }, { id: 'fieldSea', label: 'Field · Sea', transform: true, enable: true, controls: [ R('color.hueBack', 'Hue · far', 0, 1, 0.005), R('color.hueFront', 'Hue · near', 0, 1, 0.005), R('color.sat', 'Saturation', 0, 1, 0.01), R('color.light', 'Wave luminosity', 0, 1, 0.01), R('layers', 'Plate layers', 1, 3, 1), R('chaos', 'Chaos', 0, 1, 0.01), R('blips', 'Blips', 0, 2, 0.05), R('mound', 'Mound', 0, 1, 0.01), R('horizonY', 'Horizon Y', 0.2, 0.6, 0.01), R('lines', '# lines', 16, 80, 1), { path: 'seed', label: 'Seed', type: 'text' }, ], }, { id: 'fieldGrid', label: 'Field · Grid', transform: true, enable: true, controls: [ { path: 'style', label: 'Style', type: 'select', options: ['floor', 'radial'] }, { path: 'pos', label: 'Stack position', type: 'select', options: ['over', 'behind'] }, R('opacity', 'Opacity', 0, 1, 0.01), R('pitch', 'Pitch', 0, 1.4, 0.01), R('yaw', 'Yaw · two-point', -1, 1, 0.01), R('persp', 'Perspective', 0, 2, 0.01), R('dist', 'Camera distance', 1.2, 6, 0.05), R('nx', 'Width lines', 4, 36, 1), R('nz', 'Depth lines', 4, 48, 1), R('originY', 'Horizon Y', -0.4, 0.6, 0.01), R('color.hue', 'Hue', 0, 1, 0.005), R('color.sat', 'Saturation', 0, 1, 0.01), R('ripple.amp', 'Floor ripple', 0, 2, 0.02), R('resonance.amp', 'Resonance (high-Q)', 0, 1, 0.01), R('resonance.q', 'Resonance Q', 2, 40, 1), R('resonance.hue', 'Resonance hue', 0, 1, 0.005), ], }, { id: 'disk', label: 'Disk · sun', transform: true, enable: true, controls: [ R('hue', 'Hue', 0, 1, 0.005), R('sat', 'Saturation', 0, 1, 0.01), R('size', 'Size', 0.05, 0.4, 0.005), R('pressure', 'Pressure · dark core', 0, 1, 0.01), R('intensity', 'Intensity', 0, 1, 0.01), R('striations', 'Striations', 0, 1, 0.01), R('stain', 'Staining', 0, 1, 0.01), ], }, { id: 'bubble', label: 'Bubble · event', transform: true, enable: true, controls: [ { path: 'palette', label: 'Palette', type: 'select', options: ['magentarise', 'mono', 'kind', 'kindrise', 'kindlife', 'charge', 'beta', 'lifecycle', 'cyanotype'] }, { path: 'transparentBase', label: 'Transparent base', type: 'toggle' }, R('saturation', 'Saturation', 0, 1.5, 0.01), R('traceHue', 'Trace hue', 0, 1, 0.005), R('primaries', 'Primaries', 3, 40, 1), R('sweepers', 'Sweepers · arcs', 0, 12, 1), R('cosmics', 'Cosmics', 0, 16, 1), R('eloss', 'Energy loss', 0, 1.5, 0.01), R('bfield', 'B-field', 0.2, 3, 0.01), R('deltaRate', 'δ-ray rate', 0, 1, 0.01), { path: 'showBoundary', label: 'Chamber arc', type: 'toggle' }, R('boundaryR', 'Arc radius', 0.1, 1.0, 0.01), R('boundaryY', 'Arc position Y', -0.3, 0.7, 0.01), R('boundaryOpacity', 'Arc opacity', 0, 1, 0.01), R('instrument', 'Structural geometry', 0, 1, 0.01), ], }, { id: 'fiduciaries', label: 'Fiduciaries', transform: true, enable: true, controls: [ { path: 'label', label: 'Label', type: 'text' }, { path: 'pencil', label: 'Pencil', type: 'color' }, R('width', 'Line width', 0.3, 3, 0.1), { path: 'arrow', label: 'Arrow', type: 'toggle' }, { path: 'corners', label: 'Registration corners', type: 'toggle' }, { path: 'caption', label: 'Caption', type: 'text' }, ], }, ]; // the common transform block, generated for groups with transform:true export const COMMON_CONTROLS = [ R('layerOpacity', 'Opacity', 0, 1, 0.01), R('transform.x', 'Centre X', -1.6, 1.6, 0.01), // beyond ±1 → bleed off-canvas R('transform.y', 'Centre Y', -1.6, 1.6, 0.01), R('transform.rotation', 'Rotation°', -180, 180, 1), R('transform.scale', 'Scale', 0.1, 3, 0.01), // small ↔ overfill / eclipse ];