APP-Backend / src /utils /deep-research-loader.js
Luca448's picture
Update independent app to newest state with LFS and binary removals
ff36e71
Raw History Blame Contribute Delete
9.7 kB
/**
* Deep Research Loader
* Fullscreen 3-phase loading animation for Deep Research.
* Phase 1: Rotating Earth with data arcs
* Phase 2: Article with scanning magnifying glass
* Phase 3: Skeleton report with charts
*/
import { Globe3D } from './globe3d.js';
export class DeepResearchLoader {
constructor() {
this.overlay = null;
this.currentPhase = 0;
this.statusTextEl = null;
this.phaseLabelEl = null;
this.dots = [];
}
/** Build and show the fullscreen overlay */
show() {
if (this.overlay) this.hide();
this.overlay = document.createElement('div');
this.overlay.className = 'dr-loader-overlay';
this.overlay.innerHTML = this._buildHTML();
document.body.appendChild(this.overlay);
// Cache elements
this.statusTextEl = this.overlay.querySelector('.dr-status-text');
this.phaseLabelEl = this.overlay.querySelector('.dr-phase-label');
this.dots = Array.from(this.overlay.querySelectorAll('.dr-phase-dot'));
const earthSvg = this.overlay.querySelector('.dr-earth-svg');
if (earthSvg) {
this.globe3D = new Globe3D(earthSvg);
}
// Force reflow then fade in
requestAnimationFrame(() => {
requestAnimationFrame(() => {
this.overlay.classList.add('visible');
this.setPhase(1);
});
});
}
/** Switch to a phase (1, 2, or 3) with crossfade */
setPhase(phase) {
if (!this.overlay || phase === this.currentPhase) return;
this.currentPhase = phase;
// Update phase containers
const phases = this.overlay.querySelectorAll('.dr-phase');
phases.forEach(el => el.classList.remove('active'));
const target = this.overlay.querySelector(`.dr-phase-${phase}`);
if (target) target.classList.add('active');
// Update dots
this.dots.forEach((dot, i) => {
dot.classList.remove('active', 'done');
if (i + 1 < phase) dot.classList.add('done');
else if (i + 1 === phase) dot.classList.add('active');
});
// Update phase label
const labels = { 1: 'RECHERCHE', 2: 'QUELLEN LESEN', 3: 'BERICHT ERSTELLEN' };
if (this.phaseLabelEl) this.phaseLabelEl.textContent = labels[phase] || '';
}
/** Update the status text */
setStatus(text) {
if (this.statusTextEl) this.statusTextEl.textContent = text;
}
/** Hide and destroy the overlay */
hide() {
if (!this.overlay) return;
this.overlay.classList.remove('visible');
if (this.globe3D) {
this.globe3D.destroy();
this.globe3D = null;
}
const el = this.overlay;
setTimeout(() => el.remove(), 700);
this.overlay = null;
this.currentPhase = 0;
}
/** Build the full HTML for the overlay */
_buildHTML() {
return `
<div class="dr-ambient-glow"></div>
<!-- Top Status Text -->
<div class="dr-status-text-container">
<div class="dr-status-text">Starte Deep Research...</div>
</div>
<!-- PHASE 1: Earth -->
<div class="dr-phase dr-phase-1">
${this._buildEarthSVG()}
</div>
<!-- PHASE 2: Article + Magnifier -->
<div class="dr-phase dr-phase-2">
${this._buildArticleSVG()}
</div>
<!-- PHASE 3: Skeleton Report -->
<div class="dr-phase dr-phase-3">
${this._buildReportSVG()}
</div>
<!-- Bottom Status Bar (Dots & Label) -->
<div class="dr-status-bar">
<div class="dr-phase-dots">
<div class="dr-phase-dot"></div>
<div class="dr-phase-dot"></div>
<div class="dr-phase-dot"></div>
</div>
<div class="dr-phase-label">RECHERCHE</div>
</div>
`;
}
/** Phase 1: 3D rotating globe via Globe3D JS class */
_buildEarthSVG() {
return `
<div class="dr-earth-container">
<svg class="dr-earth-svg" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<clipPath id="earth-clip">
<circle cx="200" cy="200" r="175" />
</clipPath>
<!-- 3D sphere shading: darken edges, highlight top-left -->
<radialGradient id="earth-3d-shade" cx="35%" cy="32%" r="65%">
<stop offset="0%" stop-color="rgba(140,200,255,0.12)" />
<stop offset="60%" stop-color="rgba(0,0,0,0)" />
<stop offset="100%" stop-color="rgba(0,0,0,0.55)" />
</radialGradient>
<!-- Atmosphere rim glow -->
<radialGradient id="earth-atmosphere" cx="50%" cy="50%" r="50%">
<stop offset="85%" stop-color="rgba(0,0,0,0)" />
<stop offset="95%" stop-color="rgba(66,133,244,0.15)" />
<stop offset="100%" stop-color="rgba(66,133,244,0.3)" />
</radialGradient>
</defs>
<!-- Ocean background (stays fixed) -->
<circle class="dr-earth-ocean" cx="200" cy="200" r="175" />
<!-- Globe3D class will inject the <g> with continents and arcs here -->
<!-- 3D shading overlay (fixed, gives sphere depth) -->
<circle cx="200" cy="200" r="175" fill="url(#earth-3d-shade)" />
<!-- Atmosphere rim -->
<circle cx="200" cy="200" r="175" fill="url(#earth-atmosphere)" />
<!-- Edge ring -->
<circle cx="200" cy="200" r="175" fill="none" stroke="rgba(122,186,255,0.25)" stroke-width="1.5" />
</svg>
</div>
`;
}
/** Phase 2: Scrolling article/website with scanning magnifying glass */
_buildArticleSVG() {
// Build a tall content strip that scrolls vertically, duplicated for seamless loop
const buildContentBlock = () => `
<div class="dr-article-line title"></div>
<div class="dr-article-line subtitle"></div>
<div class="dr-article-line long"></div>
<div class="dr-article-line long"></div>
<div class="dr-article-line medium"></div>
<div class="dr-article-line short"></div>
<div class="dr-article-img-placeholder"></div>
<div class="dr-article-line long"></div>
<div class="dr-article-line medium"></div>
<div class="dr-article-line long"></div>
<div class="dr-article-line short"></div>
<div class="dr-article-line title"></div>
<div class="dr-article-line long"></div>
<div class="dr-article-line long"></div>
<div class="dr-article-line medium"></div>
<div class="dr-article-img-placeholder"></div>
<div class="dr-article-line long"></div>
<div class="dr-article-line medium"></div>
<div class="dr-article-line short"></div>
<div class="dr-article-line long"></div>
<div class="dr-article-line long"></div>
<div class="dr-article-line medium"></div>
`;
return `
<div class="dr-article-container">
<!-- Browser chrome -->
<div class="dr-article-chrome">
<div class="dr-chrome-dot"></div>
<div class="dr-chrome-dot"></div>
<div class="dr-chrome-dot"></div>
<div class="dr-chrome-urlbar"></div>
</div>
<!-- Scrolling content (duplicated for seamless loop) -->
<div class="dr-article-scroll-content">
${buildContentBlock()}
${buildContentBlock()}
</div>
<!-- Magnifying glass overlay -->
<div class="dr-magnifier-overlay">
<svg viewBox="0 0 420 480" xmlns="http://www.w3.org/2000/svg">
<g class="dr-magnifier-group">
<circle class="dr-magnifier-lens" cx="0" cy="0" r="36" />
<path class="dr-magnifier-highlight" d="M-20,-26 A36,36 0 0,1 20,-26" />
<line class="dr-magnifier-handle" x1="25" y1="25" x2="50" y2="50" />
</g>
</svg>
</div>
</div>
`;
}
/** Phase 3: Skeleton report with image placeholders, pie & bar charts (continuously loops) */
_buildReportSVG() {
// Build a tall content strip that scrolls upward, duplicated for seamless loop
const buildReportBlock = () => `
<div class="dr-report-skel-line title"></div>
<div class="dr-report-skel-line subtitle"></div>
<div class="dr-report-skel-line long"></div>
<div class="dr-report-skel-line long"></div>
<div class="dr-report-skel-line medium"></div>
<div class="dr-report-skel-line short"></div>
<div class="dr-report-skel-image"></div>
<div class="dr-report-skel-line long"></div>
<div class="dr-report-skel-line medium"></div>
<div class="dr-report-skel-line long"></div>
<div class="dr-report-skel-line short"></div>
<div class="dr-report-skel-pie"></div>
<div class="dr-report-skel-line medium"></div>
<div class="dr-report-skel-line short"></div>
<div class="dr-report-skel-line long"></div>
<div class="dr-report-skel-bars">
<div class="dr-report-skel-bar"></div>
<div class="dr-report-skel-bar"></div>
<div class="dr-report-skel-bar"></div>
<div class="dr-report-skel-bar"></div>
<div class="dr-report-skel-bar"></div>
</div>
<div class="dr-report-skel-line long"></div>
<div class="dr-report-skel-line medium"></div>
<div class="dr-report-skel-line long"></div>
<div class="dr-report-skel-image"></div>
<div class="dr-report-skel-line long"></div>
<div class="dr-report-skel-line short"></div>
<div class="dr-report-skel-line medium"></div>
`;
return `
<div class="dr-report-container">
<div class="dr-report-scroll">
<div class="dr-report-block">${buildReportBlock()}</div>
<div class="dr-report-block">${buildReportBlock()}</div>
</div>
</div>
`;
}
}
export const deepResearchLoader = new DeepResearchLoader();