diff --git a/static/opax.html b/static/opax.html index 1008c61..d30edfc 100644 --- a/static/opax.html +++ b/static/opax.html @@ -218,6 +218,40 @@ .if-btn{font-size:11px;padding:4px 10px;border-radius:6px;background:var(--bg-3);border:1px solid var(--border);color:var(--text-dim);transition:background .14s;white-space:nowrap} .if-btn:hover{background:var(--bg);color:var(--text)} .if-frame{flex:1;border:none;width:100%;height:100%} + + /* MD MODULE */ + .md-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px} + .md-card{background:var(--bg-2);border:1px solid var(--border);border-radius:var(--radius);padding:20px;display:flex;flex-direction:column;gap:10px;cursor:pointer;transition:border-color .14s,background .14s} + .md-card:hover{border-color:var(--accent);background:rgba(124,92,255,.04)} + .md-hd{display:flex;align-items:center;gap:10px} + .md-icon{font-size:22px;flex-shrink:0} + .md-name{font-size:14px;font-weight:700} + .md-path{font-size:11px;font-family:var(--mono);color:var(--text-faint);margin-top:2px} + .md-desc{font-size:12px;color:var(--text-dim);line-height:1.5} + .md-ft{display:flex;align-items:center;justify-content:space-between;margin-top:4px} + .md-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:99px;background:rgba(124,92,255,.15);color:var(--accent)} + .md-badge.auth{background:rgba(62,207,142,.15);color:var(--green)} + .md-badge.append{background:rgba(0,212,168,.15);color:var(--accent-2)} + .md-open-btn{font-size:11px;font-weight:600;padding:5px 12px;border-radius:7px;background:var(--bg-3);border:1px solid var(--border);color:var(--text-dim);transition:background .14s,color .14s} + .md-open-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent)} + + /* inline MD viewer */ + .md-viewer{flex:1;overflow-y:auto;padding:32px 40px;font-size:14px;line-height:1.75;color:var(--text)} + .md-viewer h1{font-size:22px;font-weight:800;margin-bottom:16px;letter-spacing:-0.02em} + .md-viewer h2{font-size:16px;font-weight:700;margin:24px 0 10px;color:var(--text)} + .md-viewer h3{font-size:14px;font-weight:600;margin:18px 0 8px;color:var(--text-dim)} + .md-viewer p{margin-bottom:12px;color:var(--text-dim)} + .md-viewer code{font-family:var(--mono);font-size:12px;background:rgba(255,255,255,.06);padding:2px 6px;border-radius:4px} + .md-viewer pre{background:var(--bg-2);border:1px solid var(--border);padding:14px 16px;border-radius:10px;overflow-x:auto;font-family:var(--mono);font-size:12px;margin:12px 0;line-height:1.6} + .md-viewer table{width:100%;border-collapse:collapse;margin:14px 0;font-size:13px} + .md-viewer th{text-align:left;padding:8px 12px;border-bottom:1px solid var(--border);font-size:11px;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-faint)} + .md-viewer td{padding:9px 12px;border-bottom:1px solid var(--border);color:var(--text-dim)} + .md-viewer tr:last-child td{border-bottom:none} + .md-viewer ul,.md-viewer ol{margin:0 0 12px 20px;color:var(--text-dim)} + .md-viewer li{margin-bottom:4px} + .md-viewer blockquote{border-left:3px solid var(--accent);padding:8px 16px;margin:12px 0;background:rgba(124,92,255,.06);border-radius:0 8px 8px 0;color:var(--text-dim)} + .md-viewer hr{border:none;border-top:1px solid var(--border);margin:20px 0} + .md-viewer-loading{display:flex;align-items:center;justify-content:center;height:200px;color:var(--text-faint);font-family:var(--mono);font-size:13px;gap:10px}
@@ -259,6 +293,11 @@ Terminal +$1')
+ s=s.replace(/`([^`]+)`/g,'$1')
+ // headings
+ s=s.replace(/^### (.+)$/gm,'$1') + // tables (simple) + s=s.replace(/((?:^\|.+\|\n)+)/gm, tbl=>{ + const rows=tbl.trim().split('\n').filter(r=>!/^\|[-| :]+\|$/.test(r)) + if(!rows.length) return tbl + let out='
| ${c.trim()} | `:`${c.trim()} | ` }) + out+=i===0?'
|---|
${line}
`:'') + return s +} + // TERMINAL let termInited=false function initTerminal(){ @@ -614,10 +776,12 @@ async function termKey(e){ try{ const d=await fetch('/health').then(r=>r.json()); tprint(JSON.stringify(d,null,2),'ok') }catch(e){ tprint('Feil: '+e.message,'err') } break case 'billing': - try{ const d=await fetch('/billing/summary').then(r=>r.json()); tprint(JSON.stringify(d,null,2),'ok') }catch(e){ tprint('Feil: '+e.message,'err') } + try{ const d=await fetch('/billing/summary').then(r=>r.json()); tprint(JSON.stringify(d,null,2),'ok') } + catch(e){ tprint('Feil: '+e.message,'err') } break case 'build': - try{ const d=await fetch('/opax/build-status').then(r=>r.json()); tprint(JSON.stringify(d,null,2),'ok') }catch(e){ tprint('Feil: '+e.message,'err') } + try{ const d=await fetch('/opax/build-status').then(r=>r.json()); tprint(JSON.stringify(d,null,2),'ok') } + catch(e){ tprint('Feil: '+e.message,'err') } break default: tprint('Ukjent kommando: '+cmd+' (skriv «help»)','err') } @@ -625,7 +789,7 @@ async function termKey(e){ function confirmVmStop(){ if(!confirm('⚠️ Stop osvauco-dev-vm? Du mister SSH-tilgang til den startes igjen.')) return tprint('→ Stopper VM via: bash scripts/stop-osvauco-dev-vm.sh','info') - switchPanel('terminal');go('terminal') + go('terminal') } // JASON CHAT