Skip to content

Aura 360: Immersive 360° Image Editor

tailwind.config = { corePlugins: { preflight: false }, theme: { extend: { fontFamily: { sans: [‘Inter’, ‘sans-serif’] } } } } @import url(‘https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap’); #aura-root { font-family: ‘Inter’, sans-serif; background-color: #111827; position: relative; width: 100%; height: 85vh; min-height: 600px; overflow: hidden; border: 1px solid #374151; border-radius: 12px; box-sizing: border-box; color: white; } #aura-root * { box-sizing: border-box; border-width: 0; border-style: solid; border-color: #374151; } #aura-root input[type=”range”] { display: block; width: 100%; height: 4px; background: #4b5563; border-radius: 2px; -webkit-appearance: none; } #aura-root input[type=”range”]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; background: white; border-radius: 50%; cursor: pointer; box-shadow: 0 2px 6px rgba(0,0,0,0.3); } #aura-root button { cursor: pointer; transition: all 0.15s ease; } /* Scrollbar */ #aura-root .glass-panel::-webkit-scrollbar { width: 4px; } #aura-root .glass-panel::-webkit-scrollbar-track { background: transparent; } #aura-root .glass-panel::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 4px; } #aura-root .loader { border: 3px solid rgba(255,255,255,0.1); border-top: 3px solid #38bdf8; border-radius: 50%; width: 30px; height: 30px; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #viewer-container canvas { width: 100%; height: 100%; display: block; outline: none; } /* Color Picker Styling */ #aura-root .color-picker-wrapper { display: flex; align-items: center; justify-content: center; margin-top: 4px; } #aura-root .color-picker-wrapper input[type=”color”] { -webkit-appearance: none; width: 30px; height: 30px; padding: 0; border: 1px solid rgba(255,255,255,0.2); border-radius: 50%; background: transparent; overflow: hidden; cursor: pointer; } #aura-root .color-picker-wrapper input[type=”color”]::-webkit-color-swatch-wrapper { padding: 0; } #aura-root .color-picker-wrapper input[type=”color”]::-webkit-color-swatch { border: none; border-radius: 50%; } .label-text { font-size: 0.7rem; font-weight: 600; color: #9ca3af; margin-bottom: 0.4rem; display: block; letter-spacing: 0.02em; } .group-title { font-size: 0.7rem; font-weight: 800; color: #38bdf8; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 1rem; border-bottom: 1px solid rgba(255,255,255,0.05); padding-bottom: 4px; display: flex; align-items: center; gap: 0.5rem;} .ui-transition { transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s; } .ui-hidden { transform: translateX(-110%); opacity: 0; }
📷

Aura 360

Load an image to begin editing

{ “imports”: { “three”: “https://cdn.jsdelivr.net/npm/three@0.165.0/build/three.module.js”, “three/addons/”: “https://cdn.jsdelivr.net/npm/three@0.165.0/examples/jsm/” } } import * as THREE from ‘three’; import { OrbitControls } from ‘three/addons/controls/OrbitControls.js’; // — XMP Metadata Injector (Google Maps Fix) — function addXmp(base64Img, width, height) { const piece1 = “http://ns.adobe.com/xap/1.0/\x00″; const piece2 = ` True Aura 360 equirectangular 0.0 0.0 0.0 ${width} ${height} 0 0 ${width} ${height} `; const binaryStr = atob(base64Img.split(‘,’)[1]); let len = binaryStr.length; const data = new Uint8Array(len); for (let i = 0; i < len; i++) data[i] = binaryStr.charCodeAt(i); const xmpBytes = new Uint8Array(piece1.length + piece2.length); for (let i = 0; i < piece1.length; i++) xmpBytes[i] = piece1.charCodeAt(i); for (let i = 0; i > 8) & 0xFF; app1[3] = length & 0xFF; app1.set(xmpBytes, 4); const result = new Uint8Array(data.length + app1.length); result.set(data.subarray(0, 2), 0); result.set(app1, 2); result.set(data.subarray(2), 2 + app1.length); return new Blob([result], { type: ‘image/jpeg’ }); } const vertexShader = `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`; const fragmentShader = ` varying vec2 vUv; uniform sampler2D uTexture; uniform vec2 uTextureSize; uniform float uBrightness; uniform float uContrast; uniform float uSaturation; uniform float uVibrance; uniform float uShadows; uniform float uHighlights; uniform float uTemperature; uniform float uTint; uniform vec3 uHighlightColor; uniform vec3 uShadowColor; uniform float uSharpen; uniform float uDenoise; uniform float uStructure; uniform float uSkySmooth; uniform float uGroundDetail; uniform float uHorizonFade; const vec3 luma = vec3(0.299, 0.587, 0.114); vec3 blur(sampler2D tex, vec2 uv, vec2 texSize, float strength) { float radius = strength * 1.5; float dx = radius / texSize.x; float dy = radius / texSize.y; // FIXED WEIGHTS (Sum = 1.0) vec3 col = texture2D(tex, uv).rgb * 0.25; // Center col += texture2D(tex, uv + vec2(dx, 0.0)).rgb * 0.125; col += texture2D(tex, uv + vec2(-dx, 0.0)).rgb * 0.125; col += texture2D(tex, uv + vec2(0.0, dy)).rgb * 0.125; col += texture2D(tex, uv + vec2(0.0, -dy)).rgb * 0.125; col += texture2D(tex, uv + vec2(dx, dy)).rgb * 0.0625; col += texture2D(tex, uv + vec2(-dx, dy)).rgb * 0.0625; col += texture2D(tex, uv + vec2(dx, -dy)).rgb * 0.0625; col += texture2D(tex, uv + vec2(-dx, -dy)).rgb * 0.0625; return col; } void main() { vec3 original = texture2D(uTexture, vUv).rgb; vec3 color = original; // Masks float horizon = 0.5; float mask = smoothstep(horizon – uHorizonFade, horizon + uHorizonFade, vUv.y); // Detail float blurStrength = uDenoise + (uSkySmooth * mask * 3.0); if (blurStrength > 0.0) { color = mix(color, blur(uTexture, vUv, uTextureSize, blurStrength), clamp(blurStrength, 0.0, 1.0)); } vec3 structBlur = blur(uTexture, vUv, uTextureSize, 1.0); vec3 diff = original – structBlur; float detailAmp = uSharpen + uStructure + (uGroundDetail * (1.0 – mask)); color += diff * detailAmp; // Color Grading color = clamp(color, 0.0, 1.0); color.r += uTemperature; color.b -= uTemperature; color.g += uTint; color = clamp(color, 0.0, 1.0); color = pow(color, vec3(1.0 – uShadows * 0.5)); color = 1.0 – pow(vec3(1.0) – color, vec3(1.0 + uHighlights * 2.0)); color = clamp(color, 0.0, 1.0); color = (color – 0.5) * uContrast + 0.5; color += uBrightness; color = clamp(color, 0.0, 1.0); // VIBRANCE & SATURATION float l = dot(color, luma); float maxComp = max(color.r, max(color.g, color.b)); float minComp = min(color.r, min(color.g, color.b)); float sat = maxComp – minComp; float vibFactor = 1.0 + (uVibrance * (1.0 – sat)); color = mix(vec3(l), color, vibFactor); color = mix(vec3(l), color, uSaturation); // Split Toning color = mix(color, color * uShadowColor, (1.0 – l)); color += (uHighlightColor – vec3(1.0)) * l * 0.5; color += (uShadowColor – vec3(1.0)) * (1.0 – l) * 0.5; color = clamp(color, 0.0, 1.0); gl_FragColor = vec4(color, 1.0); } `; let scene, camera, renderer, controls, sphereMesh; let textureLoader = new THREE.TextureLoader(); let originalTexture = null, originalFile = null; let customMaterial, stabilityApiKey = null; let currentAiMode = null; const viewerContainer = document.getElementById(‘viewer-container’); const loadingMessage = document.getElementById(‘loading-message’); const sidebar = document.getElementById(‘glass-sidebar’); const toggleUiBtn = document.getElementById(‘toggle-ui-btn’); const iconEye = document.getElementById(‘icon-eye’); const iconEyeOff = document.getElementById(‘icon-eye-off’); const fileInput = document.getElementById(‘aura-file-upload’); const downloadBtn = document.getElementById(‘aura-download-btn’); const highlightPicker = document.getElementById(‘aura-highlight-color’); const shadowPicker = document.getElementById(‘aura-shadow-color’); function init() { scene = new THREE.Scene(); const width = viewerContainer.clientWidth; const height = viewerContainer.clientHeight; camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); camera.position.set(0, 0, 0.1); scene.add(camera); renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true, alpha: true }); renderer.setSize(width, height); renderer.setPixelRatio(window.devicePixelRatio); viewerContainer.appendChild(renderer.domElement); controls = new OrbitControls(camera, renderer.domElement); controls.enableZoom = false; controls.enablePan = false; controls.enableDamping = true; controls.dampingFactor = 0.1; controls.rotateSpeed = -0.25; viewerContainer.addEventListener(‘wheel’, onMouseWheel, { passive: false }); const resizeObserver = new ResizeObserver(() => { onWindowResize(); }); resizeObserver.observe(viewerContainer); animate(); loadApiKey(); } function onMouseWheel(event) { event.preventDefault(); camera.fov = Math.max(20, Math.min(100, camera.fov + event.deltaY * 0.05)); camera.updateProjectionMatrix(); } function create360Sphere(texture) { originalTexture = texture; const textureSize = new THREE.Vector2(texture.image.width, texture.image.height); if (sphereMesh) { customMaterial.uniforms.uTexture.value = texture; customMaterial.uniforms.uTextureSize.value = textureSize; customMaterial.needsUpdate = true; } else { const geometry = new THREE.SphereGeometry(500, 60, 40); geometry.scale(-1, 1, 1); customMaterial = new THREE.ShaderMaterial({ uniforms: { uTexture: { value: texture }, uTextureSize: { value: textureSize }, uBrightness: { value: 0.0 }, uContrast: { value: 1.0 }, uSaturation: { value: 1.0 }, uVibrance: { value: 0.0 }, uShadows: { value: 0.0 }, uHighlights: { value: 0.0 }, uTemperature: { value: 0.0 }, uTint: { value: 0.0 }, uSharpen: { value: 0.0 }, uDenoise: { value: 0.0 }, uStructure: { value: 0.0 }, uSkySmooth: { value: 0.0 }, uGroundDetail: { value: 0.0 }, uHorizonFade: { value: 0.1 }, uHighlightColor: { value: new THREE.Color(1, 1, 1) }, uShadowColor: { value: new THREE.Color(1, 1, 1) } }, vertexShader: vertexShader, fragmentShader: fragmentShader }); sphereMesh = new THREE.Mesh(geometry, customMaterial); scene.add(sphereMesh); } resetSliders(); if (loadingMessage) loadingMessage.style.display = ‘none’; } function resetSliders() { document.querySelectorAll(‘input[type=”range”]’).forEach(el => { if (el.id.includes(‘contrast’) || el.id.includes(‘saturation’)) el.value = 1; else if (el.id.includes(‘horizon’)) el.value = 0.1; else el.value = 0; }); highlightPicker.value = “#ffffff”; shadowPicker.value = “#ffffff”; } function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } function onWindowResize() { if (!viewerContainer || viewerContainer.clientWidth === 0) return; camera.aspect = viewerContainer.clientWidth / viewerContainer.clientHeight; camera.updateProjectionMatrix(); renderer.setSize(viewerContainer.clientWidth, viewerContainer.clientHeight); } function onFileChange(event) { const file = event.target.files[0]; if (file) { originalFile = file; const reader = new FileReader(); reader.onload = (e) => { textureLoader.load(e.target.result, (texture) => { // — FIX: DARKNESS ISSUE — texture.colorSpace = THREE.NoColorSpace; texture.minFilter = THREE.LinearFilter; create360Sphere(texture); }); }; reader.readAsDataURL(file); } } function updateUniform(name, val) { if (customMaterial) customMaterial.uniforms[name].value = parseFloat(val); } function updateColorUniform(name, hexVal) { if (customMaterial) { const col = new THREE.Color(hexVal); customMaterial.uniforms[name].value.set(col.r, col.g, col.b); } } function toggleUI() { const isHidden = sidebar.classList.contains(‘ui-hidden’); if (isHidden) { sidebar.classList.remove(‘ui-hidden’); iconEye.classList.remove(‘hidden’); iconEyeOff.classList.add(‘hidden’); } else { sidebar.classList.add(‘ui-hidden’); iconEye.classList.add(‘hidden’); iconEyeOff.classList.remove(‘hidden’); } } function downloadImage() { if (!originalTexture) return alert(‘Please load an image first.’); toggleModal(true, { loading: true }); setTimeout(() => { try { const offscreenCanvas = document.createElement(‘canvas’); offscreenCanvas.width = originalTexture.image.width; offscreenCanvas.height = originalTexture.image.height; const offscreenRenderer = new THREE.WebGLRenderer({ canvas: offscreenCanvas, antialias: true }); const planeScene = new THREE.Scene(); const planeCamera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0.1, 10); const planeGeo = new THREE.PlaneGeometry(1, 1); const downloadMaterial = customMaterial.clone(); downloadMaterial.uniforms.uTexture.value = originalTexture; planeScene.add(new THREE.Mesh(planeGeo, downloadMaterial)); planeCamera.position.z = 1; offscreenRenderer.render(planeScene, planeCamera); // Generate Data const dataUrl = offscreenRenderer.domElement.toDataURL(‘image/jpeg’, 0.92); // Inject Metadata const blob = addXmp(dataUrl, originalTexture.image.width, originalTexture.image.height); const blobUrl = URL.createObjectURL(blob); const link = document.createElement(‘a’); link.download = ‘aura-360-edit.jpg’; link.href = blobUrl; link.click(); URL.revokeObjectURL(blobUrl); offscreenRenderer.dispose(); planeGeo.dispose(); downloadMaterial.dispose(); toggleModal(false); } catch (error) { alert(error.message); toggleModal(false); } }, 100); } function toggleModal(show, options = {}) { const modal = document.getElementById(‘ai-modal’); if (show) { modal.classList.remove(‘hidden’); if (options.loading) { document.getElementById(‘ai-modal-loading’).classList.remove(‘hidden’); document.getElementById(‘ai-modal-content’).classList.add(‘hidden’); document.getElementById(‘ai-modal-apikey’).classList.add(‘hidden’); } } else { modal.classList.add(‘hidden’); } } const STABILITY_API_KEY_NAME = ‘aura360_stability_key’; function loadApiKey() { try { const key = localStorage.getItem(STABILITY_API_KEY_NAME); if (key) stabilityApiKey = key; } catch (e) {} } function saveApiKey() { const key = document.getElementById(‘api-key-input’).value.trim(); if (key && key.startsWith(‘sk-‘)) { localStorage.setItem(STABILITY_API_KEY_NAME, key); stabilityApiKey = key; toggleModal(false); if (currentAiMode === ‘enhance’) onAiEnhanceClick(); } } function onAiEnhanceClick() { currentAiMode = ‘enhance’; if (!originalFile) return alert(‘Load image first.’); if (!stabilityApiKey) { toggleModal(true); document.getElementById(‘ai-modal-loading’).classList.add(‘hidden’); document.getElementById(‘ai-modal-apikey’).classList.remove(‘hidden’); return; } callStabilityApi_Upscale(); } async function callStabilityApi_Upscale() { toggleModal(true, { loading: true }); const formData = new FormData(); formData.append(‘image’, originalFile); try { const res = await fetch(‘https://api.stability.ai/v1/generation/esrgan-v1-x2plus/image-to-image/upscale’, { method: ‘POST’, headers: { ‘Authorization’: `Bearer ${stabilityApiKey}`, ‘Accept’: ‘application/json’ }, body: formData, }); if (!res.ok) throw new Error((await res.json()).message); const result = await res.json(); const b64 = result.artifacts[0].base64; textureLoader.load(`data:image/png;base64,${b64}`, (tex) => { tex.colorSpace = THREE.NoColorSpace; // FIX create360Sphere(tex); originalTexture = tex; toggleModal(false); const byteChars = atob(b64); const byteNumbers = new Array(byteChars.length); for (let i = 0; i toggleModal(false)); document.getElementById(‘api-key-save-btn’).addEventListener(‘click’, saveApiKey); const mapUniform = (id, uni) => document.getElementById(id).addEventListener(‘input’, (e) => updateUniform(uni, e.target.value)); mapUniform(‘aura-brightness’, ‘uBrightness’); mapUniform(‘aura-contrast’, ‘uContrast’); mapUniform(‘aura-saturation’, ‘uSaturation’); mapUniform(‘aura-vibrance’, ‘uVibrance’); mapUniform(‘aura-shadows’, ‘uShadows’); mapUniform(‘aura-highlights’, ‘uHighlights’); mapUniform(‘aura-temperature’, ‘uTemperature’); mapUniform(‘aura-tint’, ‘uTint’); mapUniform(‘aura-sharpen’, ‘uSharpen’); mapUniform(‘aura-denoise’, ‘uDenoise’); mapUniform(‘aura-structure’, ‘uStructure’); mapUniform(‘aura-sky-smooth’, ‘uSkySmooth’); mapUniform(‘aura-ground-detail’, ‘uGroundDetail’); mapUniform(‘aura-horizon-fade’, ‘uHorizonFade’); highlightPicker.addEventListener(‘input’, (e) => updateColorUniform(‘uHighlightColor’, e.target.value)); shadowPicker.addEventListener(‘input’, (e) => updateColorUniform(‘uShadowColor’, e.target.value)); init();

Edited with Aura 360 Pro and Aura Studio Pro. Get it as part of your premium membership.

The Immersive Editor

Experience your virtual tours like never before. Aura 360 offers a distraction-free, full-screen editing environment for your equirectangular photos.

With our new Glass Interface, the tools float over your image, allowing you to see every detail of your color grading and sharpening adjustments in real-time.

✨ Features

  • Immersive View: The editor fills your entire screen. Click the Eye Icon (👁️) to hide the interface completely and inspect your work.

  • Smart Sky & Ground Masks: Separately blur noisy skies and sharpen detailed ground textures using our intelligent horizon detection.

  • Split Toning: Inject cinematic color moods by tinting highlights and shadows independently.

  • Structure Engine: Bring out the grit and texture in architectural surfaces with the new “Structure” slider.

  • AI Upscaling: Double the resolution of your shots using the integrated Stability.ai engine.


How to Use Aura 360

1. Load & Inspect

Click “Load 360° Image” in the top left. Your image will load in full 360° view. Click and drag to look around.

2. Color & Light

Use the Floating Sidebar to adjust your image.

  • Brightness/Contrast: Set your base exposure.

  • Split Toning: Click the color circles to tint your Highlights (e.g., warm orange) and Shadows (e.g., cool teal) for a professional look.

3. Enhance Details

  • Structure: Slide this up to make details “pop.”

  • Smart Masks: Use Sky Smoothness to remove grain from the sky and Ground Detail to sharpen the floor.

4. Check Your Work

Click the Eye Icon at the top left to toggle the UI off. This lets you view the tour without any obstructions. Click it again to bring the controls back.

5. Export

Click the “Download Image (JPG)” button at the bottom of the panel.