seedproject-web/api/public/assets/js/index.js
Carlos Arias f41fa652ac fix: repair the fresh-clone flow — track api/public + app/public, fix installer schema
Three bugs found deploying a fresh clone to seedproject.com, each fatal
to the documented "spin up a new site" flow:

- .gitignore: the unanchored `public/` pattern (meant for the root build
  output) also ignored api/public/ (the framework's front controller,
  controllers, models, views) and app/public/ (theme static assets:
  fonts, avatar placeholder). Neither was ever committed, so every fresh
  clone 500'd on all /api routes and 404'd on theme assets. Anchor the
  pattern to /public/ and commit both directories.

- api/install/dump.sql: stray `CREATE DATABASE ochenta80_db123` (SQLyog
  export artifact) aborted `php console app:install` for any
  non-privileged DB user. The schema must import into whatever database
  the installer connects to.

- PluginManager::boot() queries sp_plugins on every request, but no
  shipped schema creates it — even a successful install 500'd on every
  endpoint. Add migration 002_create_sp_plugins.sql matching the columns
  PluginManager reads/writes.

Also empty api/system/errors.json, which shipped with stale error logs
from an unrelated project.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3JqxTe7TKaR7xufcMr7Ds
2026-07-05 00:15:55 +00:00

177 lines
8.4 KiB
JavaScript

const SIGNAL_DATA = [
{ type: 'BUY', price: '2034.50', tp: '2042.00', sl: '2029.00', confidence: 89, time: '2m ago' },
{ type: 'SELL', price: '2045.10', tp: '2038.50', sl: '2048.00', confidence: 92, time: '15m ago' },
{ type: 'BUY', price: '2022.80', tp: '2030.00', sl: '2018.50', confidence: 76, time: '42m ago' },
{ type: 'SELL', price: '2051.00', tp: '2044.20', sl: '2055.00', confidence: 84, time: '1h ago' },
{ type: 'BUY', price: '2036.20', tp: '2041.50', sl: '2033.00', confidence: 65, time: '1h ago' },
{ type: 'SELL', price: '2048.90', tp: '2040.00', sl: '2052.00', confidence: 95, time: '2h ago' },
];
const HISTORY_DATA = [
{ asset: 'XAUUSD', type: 'BUY', entry: '2,024.50', exit: '2,031.20', yield: '+670 pts', color: 'text-emerald-400', time: '2h ago' },
{ asset: 'XAUUSD', type: 'SELL', entry: '2,038.10', exit: '2,034.40', yield: '+370 pts', color: 'text-emerald-400', time: '4h ago' },
{ asset: 'XAUUSD', type: 'BUY', entry: '2,018.00', exit: '2,015.50', yield: '-250 pts', color: 'text-rose-400', time: '6h ago' },
{ asset: 'XAUUSD', type: 'BUY', entry: '2,012.20', exit: '2,020.80', yield: '+860 pts', color: 'text-emerald-400', time: '10h ago' },
];
class Particle {
constructor(sphereRadius) {
const theta = Math.random() * 2 * Math.PI;
const phi = Math.acos(2 * Math.random() - 1);
this.sphereRadius = sphereRadius;
this.x = sphereRadius * Math.sin(phi) * Math.cos(theta);
this.y = sphereRadius * Math.sin(phi) * Math.sin(theta);
this.z = sphereRadius * Math.cos(phi);
this.size = Math.random() * 1.8 + 0.6;
this.baseOpacity = Math.random() * 0.6 + 0.3;
}
rotate(angleX, angleY) {
let cosY = Math.cos(angleY), sinY = Math.sin(angleY);
let x = this.x * cosY - this.z * sinY;
let z = this.z * cosY + this.x * sinY;
this.x = x; this.z = z;
let cosX = Math.cos(angleX), sinX = Math.sin(angleX);
let y = this.y * cosX - this.z * sinX;
z = this.z * cosX + this.y * sinX;
this.y = y; this.z = z;
}
draw(ctx, centerX, centerY) {
const scale = 300 / (300 + this.z);
const x2d = this.x * scale + centerX;
const y2d = this.y * scale + centerY;
const opacity = Math.max(0, this.baseOpacity + (this.z / this.sphereRadius) * 0.4);
ctx.beginPath();
ctx.arc(x2d, y2d, this.size * scale, 0, Math.PI * 2);
if (this.x > 40) ctx.fillStyle = `rgba(34, 211, 238, ${opacity})`;
else if (this.x < -40) ctx.fillStyle = `rgba(167, 139, 250, ${opacity})`;
else ctx.fillStyle = `rgba(248, 250, 252, ${opacity})`;
ctx.fill();
}
}
function initParticleSphere() {
const canvas = document.getElementById('particle-canvas');
const ctx = canvas.getContext('2d', { alpha: true });
const sphereRadius = 105;
const particleCount = 400;
const rotationSpeed = 0.003;
let particles = [];
let width, height;
function resize() {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.parentElement.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;
ctx.scale(dpr, dpr);
width = rect.width;
height = rect.height;
}
function animate() {
ctx.clearRect(0, 0, width, height);
const centerX = width / 2;
const centerY = height / 2;
particles.sort((a, b) => b.z - a.z);
particles.forEach(p => {
p.rotate(rotationSpeed, rotationSpeed * 0.6);
p.draw(ctx, centerX, centerY);
});
requestAnimationFrame(animate);
}
for (let i = 0; i < particleCount; i++) particles.push(new Particle(sphereRadius));
window.addEventListener('resize', resize);
resize();
animate();
}
function renderSignals() {
const grid = document.getElementById('signals-grid');
grid.innerHTML = SIGNAL_DATA.map(s => `
<div class="glass-panel rounded-2xl p-6 hover:border-white/20 transition-all duration-500 group cursor-default border border-white/5 flex flex-col">
<div class="flex justify-between items-start mb-6">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl ${s.type === 'BUY' ? 'bg-emerald-400/10 border-emerald-400/20 text-emerald-400' : 'bg-rose-400/10 border-rose-400/20 text-rose-400'} flex items-center justify-center border">
${s.type === 'BUY' ? '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/></svg>' : '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 17 13.5 8.5 8.5 13.5 2 7"/><polyline points="16 17 22 17 22 11"/></svg>'}
</div>
<div>
<div class="text-[10px] text-slate-500 uppercase tracking-widest mb-0.5">Vector Type</div>
<div class="text-lg font-bold tracking-tight ${s.type === 'BUY' ? 'text-emerald-400' : 'text-rose-400'}">${s.type} ORDER</div>
</div>
</div>
<div class="text-[10px] text-slate-500 font-mono bg-white/5 border border-white/10 rounded px-2 py-1">${s.time}</div>
</div>
<div class="space-y-4 mb-8">
<div class="flex justify-between items-center bg-white/[0.02] p-2 rounded-lg">
<span class="text-xs text-slate-500 font-light">Trigger Entry</span>
<span class="text-slate-200 font-mono font-medium">${s.price}</span>
</div>
<div class="flex justify-between items-center p-2">
<span class="text-xs text-slate-500 font-light">Target TP</span>
<span class="text-emerald-400 font-mono font-medium">${s.tp}</span>
</div>
<div class="flex justify-between items-center p-2">
<span class="text-xs text-slate-500 font-light">Risk SL</span>
<span class="text-rose-400 font-mono font-medium">${s.sl}</span>
</div>
</div>
<div class="mt-auto pt-6 border-t border-white/5 flex items-center justify-between">
<div class="flex flex-col">
<span class="text-[10px] text-slate-600 uppercase tracking-widest">Confidence</span>
<span class="text-cyan-400 font-bold text-sm">${s.confidence}%</span>
</div>
<div class="h-1.5 w-24 bg-slate-800 rounded-full overflow-hidden">
<div class="h-full bg-gradient-to-r from-blue-600 to-cyan-400" style="width: ${s.confidence}%"></div>
</div>
</div>
</div>
`).join('');
}
function renderHistory() {
const tbody = document.getElementById('history-body');
tbody.innerHTML = HISTORY_DATA.map(t => `
<tr class="group hover:bg-white/5 transition-colors cursor-default">
<td class="py-4 px-6">
<div class="flex items-center gap-3">
<div class="w-6 h-6 rounded-full bg-yellow-500/10 text-yellow-500 flex items-center justify-center text-[10px] font-bold border border-yellow-500/20">G</div>
<span class="text-slate-200 font-semibold">${t.asset}</span>
</div>
</td>
<td class="py-4 px-6">
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold ${t.type === 'BUY' ? 'bg-emerald-400/10 text-emerald-400 border-emerald-400/20' : 'bg-rose-400/10 text-rose-400 border-rose-400/20'} border">
${t.type}
</span>
</td>
<td class="py-4 px-6 font-mono text-slate-400">${t.entry}</td>
<td class="py-4 px-6 font-mono text-slate-400">${t.exit}</td>
<td class="py-4 px-6 text-right font-mono font-medium ${t.color}">${t.yield}</td>
<td class="py-4 px-6 text-right text-slate-500 text-xs font-light">${t.time}</td>
</tr>
`).join('');
}
document.addEventListener('DOMContentLoaded', () => {
initParticleSphere();
renderSignals();
renderHistory();
const initBtn = document.getElementById('init-btn');
const signalsSection = document.getElementById('signals-section');
initBtn.addEventListener('click', () => {
signalsSection.classList.remove('hidden-section');
signalsSection.classList.add('visible-section');
setTimeout(() => {
signalsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 100);
});
});