import { app } from "../../scripts/app.js"; /** * 📸 Camera Forensic Realism Engine — Premium UI Dashboard (v4) * ============================================================== * Gold/Black themed DOM widget replacing ALL default drawing. * Matches the v4 iPhone 17 pipeline parameter set. */ const NODE_NAME = "CameraForensicRealismEngine"; const NODE_MIN_WIDTH = 420; const NODE_TITLE_HEIGHT = 40; const SECTIONS = { master: { icon: "⚡", label: "Master", toggle: null }, wb: { icon: "☀️", label: "White Balance", toggle: "enable_white_balance" }, tone: { icon: "🌓", label: "Global Tone", toggle: "enable_tone" }, hdr: { icon: "✨", label: "Smart HDR", toggle: "enable_smart_hdr" }, color: { icon: "🎨", label: "Color Science (Oklab)", toggle: "enable_color" }, detail: { icon: "🔪", label: "Detail", toggle: "enable_detail" }, optics: { icon: "📷", label: "Optics & Sensor", toggle: "enable_optics" }, }; const WIDGET_META = { photographic_style: { type: "combo", section: "master", label: "Style", desc: "iPhone 17 Photographic Style preset. Undertones steer warmth, moods steer tone+color. Applied as offsets on top of your sliders." }, master_strength: { type: "slider", section: "master", label: "Master Strength", min: 0, max: 1, step: 0.01, desc: "Final blend between original and fully processed image." }, seed: { type: "number", section: "master", label: "Seed", desc: "Random seed for grain reproducibility." }, control_after_generate: { type: "combo", section: "master", label: "Seed Mode", desc: "Change seed behavior after generation." }, enable_white_balance: { type: "toggle", section: "wb", label: "Enable", desc: "Channel gains in linear light, luminance-preserving." }, wb_temperature: { type: "slider", section: "wb", label: "Temperature", min: -1, max: 1, step: 0.01, desc: "Blue-Orange axis. iPhone AWB sits slightly warm (~0.1)." }, wb_tint: { type: "slider", section: "wb", label: "Tint", min: -1, max: 1, step: 0.01, desc: "Green-Magenta axis. 0 = neutral." }, enable_tone: { type: "toggle", section: "tone", label: "Enable", desc: "Linear-light global tone mapping." }, exposure: { type: "slider", section: "tone", label: "Exposure", min: -1, max: 1, step: 0.01, desc: "Exposure in EV. iPhone meters slightly bright." }, contrast: { type: "slider", section: "tone", label: "Contrast", min: -1, max: 1, step: 0.01, desc: "Midtone contrast around the 0.18 photographic pivot." }, shadows: { type: "slider", section: "tone", label: "Shadows", min: 0, max: 1, step: 0.01, desc: "Luminance-masked shadow lift. iPhone never crushes blacks." }, highlights: { type: "slider", section: "tone", label: "Highlights", min: 0, max: 1, step: 0.01, desc: "Reinhard soft-knee rolloff — highlights compress smoothly, never clip." }, enable_smart_hdr: { type: "toggle", section: "hdr", label: "Enable", desc: "Local tone mapping in log-luminance (Smart HDR 5 style fusion look)." }, hdr_strength: { type: "slider", section: "hdr", label: "Strength", min: 0, max: 1, step: 0.01, desc: "Local dynamic range compression — shadows up, highlights down, detail untouched." }, enable_color: { type: "toggle", section: "color", label: "Enable", desc: "Perceptual color rendering in Oklab space." }, vibrance: { type: "slider", section: "color", label: "Vibrance", min: 0, max: 1, step: 0.01, desc: "Smart saturation: boosts muted colors more than saturated ones. Hue-preserving." }, skin_protection: { type: "slider", section: "color", label: "Skin Protection", min: 0, max: 1, step: 0.01, desc: "Shields skin tones from vibrance and caps skin chroma — no orange faces." }, shadow_tint: { type: "slider", section: "color", label: "Shadow Tint", min: 0, max: 1, step: 0.01, desc: "Subtle cool/blue tint in shadows — the iPhone blue-black signature." }, highlight_warmth: { type: "slider", section: "color", label: "Highlight Warmth", min: 0, max: 1, step: 0.01, desc: "Golden warmth in bright areas, complements the cool shadows." }, enable_detail: { type: "toggle", section: "detail", label: "Enable", desc: "Two-scale luminance detail with halo suppression." }, texture: { type: "slider", section: "detail", label: "Texture", min: 0, max: 1, step: 0.01, desc: "Fine-scale detail (~1px): pores, fabric, hair." }, clarity: { type: "slider", section: "detail", label: "Clarity", min: 0, max: 1, step: 0.01, desc: "Mid-scale local punch. Keep low for a natural look." }, enable_optics: { type: "toggle", section: "optics", label: "Enable", desc: "Lens vignette + sensor grain." }, grain: { type: "slider", section: "optics", label: "Grain", min: 0, max: 1, step: 0.01, desc: "Photon-weighted sensor noise, stronger in shadows." }, vignette: { type: "slider", section: "optics", label: "Vignette", min: 0, max: 1, step: 0.01, desc: "Natural illumination falloff applied in linear light." }, }; /* ── CSS injection ───────────────────────────────────────────────────── */ function injectCSS() { if (document.getElementById("cfr-styles")) return; const style = document.createElement("style"); style.id = "cfr-styles"; style.textContent = ` .cfr-root { font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; background: #0d0d08; border-radius: 8px; color: #d0d0c0; user-select: none; width: 100%; height: 100%; box-sizing: border-box; overflow-y: auto; overflow-x: hidden; scrollbar-width: thin; scrollbar-color: #333322 transparent; } .cfr-root::-webkit-scrollbar { width: 6px; } .cfr-root::-webkit-scrollbar-thumb { background: #333322; border-radius: 3px; } .cfr-inner { padding: 12px; box-sizing: border-box; } .cfr-header { text-align: center; padding: 4px 0 12px; border-bottom: 1px solid rgba(255, 215, 0, 0.15); margin-bottom: 12px; } .cfr-title { font-size: 16px; font-weight: 800; letter-spacing: 2px; text-transform: uppercase; background: linear-gradient(135deg, #FFD700, #b89b00); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin: 0; } .cfr-subtitle { font-size: 9px; color: #887; letter-spacing: 2px; text-transform: uppercase; margin-top: 4px; font-weight: 600; } .cfr-info { background: rgba(255, 215, 0, 0.04); border: 1px solid rgba(255, 215, 0, 0.1); border-radius: 8px; padding: 10px 12px; margin-bottom: 12px; font-size: 10px; line-height: 1.4; } .cfr-info-badge { display: inline-block; font-size: 8px; font-weight: 800; letter-spacing: 1.5px; padding: 3px 6px; border-radius: 4px; background: rgba(255, 215, 0, 0.15); color: #FFD700; margin-bottom: 6px; } .cfr-info-desc { color: #bbb; margin: 4px 0; } .cfr-info-tip { color: #887; font-size: 9px; font-style: italic; margin-top: 6px; } .cfr-section { display: flex; align-items: center; gap: 8px; margin: 16px 0 8px; font-size: 10px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: #FFD700; } .cfr-section::after { content: ""; flex: 1; height: 1px; background: linear-gradient(90deg, rgba(255,215,0,0.2), transparent); } .cfr-row { display: flex; align-items: center; gap: 10px; margin: 6px 0; height: 24px; transition: opacity 0.2s, max-height 0.2s; } .cfr-row.hidden { opacity: 0; max-height: 0; margin: 0; pointer-events: none; overflow: hidden; } .cfr-label { min-width: 100px; font-size: 11px; color: #aaa; text-align: right; cursor: help; font-weight: 500; } .cfr-label:hover { color: #FFD700; } .cfr-slider { -webkit-appearance: none; appearance: none; flex: 1; height: 6px; border-radius: 3px; background: linear-gradient(90deg, #1f1f14, #333322); outline: none; cursor: pointer; box-shadow: inset 0 1px 2px rgba(0,0,0,0.5); } .cfr-slider:hover { background: linear-gradient(90deg, #1f1f14, #444422); } .cfr-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; border-radius: 50%; background: radial-gradient(circle, #fff 10%, #FFD700 80%); border: 1px solid #b89b00; cursor: grab; box-shadow: 0 0 6px rgba(255, 215, 0, 0.4); margin-top: -4px; transition: transform 0.1s; } .cfr-slider::-webkit-slider-thumb:hover { transform: scale(1.2); box-shadow: 0 0 10px rgba(255, 215, 0, 0.6); } .cfr-slider-val { min-width: 40px; text-align: right; font-size: 11px; font-family: 'Consolas','Monaco',monospace; color: #FFD700; font-weight: 700; } .cfr-tooltip { position: relative; } .cfr-tooltip::after { content: attr(data-tip); position: absolute; bottom: 125%; left: 0%; background: #1a1a14; color: #ccc; border: 1px solid rgba(255,215,0,0.3); padding: 6px 10px; border-radius: 6px; font-size: 10px; width: 220px; white-space: normal; pointer-events: none; text-align: left; opacity: 0; transition: opacity 0.2s; z-index: 999; line-height: 1.4; box-shadow: 0 4px 12px rgba(0,0,0,0.5); font-weight: normal; font-family: sans-serif; } .cfr-tooltip:hover::after { opacity: 1; } .cfr-switch { display: flex; align-items: center; gap: 8px; cursor: pointer; flex: 1; } .cfr-switch-text { font-size: 10px; font-weight: 700; color: #666; width: 26px; text-align: right; letter-spacing: 0.5px; } .cfr-switch input { display: none; } .cfr-switch-track { width: 32px; height: 16px; background: #222; border-radius: 8px; position: relative; transition: 0.2s; border: 1px solid #333; box-shadow: inset 0 1px 3px rgba(0,0,0,0.5); } .cfr-switch-thumb { width: 12px; height: 12px; background: #777; border-radius: 50%; position: absolute; top: 1px; left: 2px; transition: 0.2s; } .cfr-switch input:checked + .cfr-switch-text { color: #FFD700; } .cfr-switch input:checked ~ .cfr-switch-track { background: rgba(255,215,0,0.15); border-color: rgba(255,215,0,0.4); } .cfr-switch input:checked ~ .cfr-switch-track .cfr-switch-thumb { background: #FFD700; transform: translateX(14px); box-shadow: 0 0 8px rgba(255,215,0,0.5); } .cfr-input { flex: 1; background: #1a1a14; border: 1px solid rgba(255,215,0,0.2); color: #FFD700; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-family: inherit; font-weight: 600; outline: none; } .cfr-input:focus { border-color: #FFD700; } `; document.head.appendChild(style); } /* ── Build DOM ───────────────────────────────────────────────────────── */ function buildUI() { const el = document.createElement("div"); el.className = "cfr-root"; const inner = document.createElement("div"); inner.className = "cfr-inner"; el.appendChild(inner); // Header & Description let html = `