aiplang 2.11.4 → 2.11.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/aiplang.js +192 -3
- package/package.json +1 -1
- package/server/server.js +1 -1
package/bin/aiplang.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('fs')
|
|
|
5
5
|
const path = require('path')
|
|
6
6
|
const http = require('http')
|
|
7
7
|
|
|
8
|
-
const VERSION = '2.11.
|
|
8
|
+
const VERSION = '2.11.5'
|
|
9
9
|
const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
|
|
10
10
|
const cmd = process.argv[2]
|
|
11
11
|
const args = process.argv.slice(3)
|
|
@@ -686,7 +686,7 @@ function generateTypes(app, srcFile) {
|
|
|
686
686
|
}
|
|
687
687
|
|
|
688
688
|
lines.push(`// ── aiplang version ──────────────────────────────────────────`)
|
|
689
|
-
lines.push(`export const AIPLANG_VERSION = '2.11.
|
|
689
|
+
lines.push(`export const AIPLANG_VERSION = '2.11.5'`)
|
|
690
690
|
lines.push(``)
|
|
691
691
|
return lines.join('\n')
|
|
692
692
|
}
|
|
@@ -1040,6 +1040,10 @@ function parseBlock(line) {
|
|
|
1040
1040
|
return{kind:'pricing',plans,extraClass,animate,variant,style,bg}
|
|
1041
1041
|
}
|
|
1042
1042
|
|
|
1043
|
+
if(line.startsWith('code{')) { const m=line.match(/^code\{([^}]*)\}/);if(m){const pts=m[1].split('|');const vm=line.match(/variant:(\S+)/);const am=line.match(/animate:(\S+)/);return{kind:'code',lang:pts[0]?.trim()||'aip',lines:pts.slice(1).map(l=>l.trim()),variant:vm?.[1],animate:am?.[1]}} }
|
|
1044
|
+
if(line.startsWith('benchmark{')) { const m=line.match(/^benchmark\{([^}]*)\}/);if(m){const vm=line.match(/variant:(\S+)/);const am=line.match(/animate:(\S+)/);return{kind:'benchmark',items:m[1].split('|').map(it=>{const p=it.trim().split(':');return{num:p[0]?.trim(),label:p[1]?.trim(),vs:p[2]?.trim(),pct:parseInt(p[3])||0}}),variant:vm?.[1],animate:am?.[1]}} }
|
|
1045
|
+
if(line.startsWith('install{')) { const m=line.match(/^install\{([^}]*)\}/);if(m){const vm=line.match(/variant:(\S+)/);return{kind:'install',cmds:m[1].split('|').map(c=>c.trim()).filter(Boolean),variant:vm?.[1]}} }
|
|
1046
|
+
if(line.startsWith('feature{')) { const b=parseBlock(line.replace(/^feature/,'row3'));if(b){b.variant='feature'};return b }
|
|
1043
1047
|
if(line.startsWith('faq{')) {
|
|
1044
1048
|
const body=line.slice(4,line.lastIndexOf('}')).trim()
|
|
1045
1049
|
const items=body.split('|').map(i=>{const idx=i.indexOf('>');return{q:i.slice(0,idx).trim(),a:i.slice(idx+1).trim()}}).filter(i=>i.q&&i.a)
|
|
@@ -1268,6 +1272,10 @@ function renderBlock(b, page) {
|
|
|
1268
1272
|
case 'select': return rSelectBlock(b)
|
|
1269
1273
|
case 'pricing': return rPricing(b)
|
|
1270
1274
|
case 'faq': return rFaq(b)
|
|
1275
|
+
case 'code': return rCode(b)
|
|
1276
|
+
case 'benchmark': return rBenchmark(b)
|
|
1277
|
+
case 'install': return rInstall(b)
|
|
1278
|
+
case 'feature': return rRow(b)
|
|
1271
1279
|
case 'testimonial': return rTestimonial(b)
|
|
1272
1280
|
case 'gallery': return rGallery(b)
|
|
1273
1281
|
case 'raw': return (b.html||'')+'\n'
|
|
@@ -1347,6 +1355,49 @@ function rNav(b) {
|
|
|
1347
1355
|
return `<nav class="fx-nav">${brand}<button class="fx-hamburger" onclick="this.classList.toggle('open');document.querySelector('.fx-nav-links').classList.toggle('open')" aria-label="Menu"><span></span><span></span><span></span></button><div class="fx-nav-links">${links}</div></nav>\n`
|
|
1348
1356
|
}
|
|
1349
1357
|
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
// ── Parser: code{lang|linha1|linha2} ─────────────────────────────
|
|
1361
|
+
function parseCode(line) {
|
|
1362
|
+
const m = line.match(/^code\{([^}]*)\}/)
|
|
1363
|
+
if (!m) return null
|
|
1364
|
+
const parts = m[1].split('|')
|
|
1365
|
+
const lang = parts[0]?.trim() || 'aip'
|
|
1366
|
+
const lines = parts.slice(1).map(l => l.trim())
|
|
1367
|
+
const b = parseBlockMeta(line)
|
|
1368
|
+
return { ...b, kind:'code', lang, lines }
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// ── Parser: benchmark{Num:Label:vs texto|...} ─────────────────────
|
|
1372
|
+
function parseBenchmark(line) {
|
|
1373
|
+
const m = line.match(/^benchmark\{([^}]*)\}/)
|
|
1374
|
+
if (!m) return null
|
|
1375
|
+
const items = m[1].split('|').map(item => {
|
|
1376
|
+
const parts = item.trim().split(':')
|
|
1377
|
+
return { num: parts[0]?.trim(), label: parts[1]?.trim(), vs: parts[2]?.trim(), pct: parts[3]?.trim() }
|
|
1378
|
+
})
|
|
1379
|
+
const b = parseBlockMeta(line)
|
|
1380
|
+
return { ...b, kind:'benchmark', items }
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// ── Parser: install{cmd1|cmd2|...} ───────────────────────────────
|
|
1384
|
+
function parseInstall(line) {
|
|
1385
|
+
const m = line.match(/^install\{([^}]*)\}/)
|
|
1386
|
+
if (!m) return null
|
|
1387
|
+
const cmds = m[1].split('|').map(c => c.trim()).filter(Boolean)
|
|
1388
|
+
const b = parseBlockMeta(line)
|
|
1389
|
+
return { ...b, kind:'install', cmds }
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// ── Parser: feature{emoji>Título>Desc | ...} (alias row3 otimizado)
|
|
1393
|
+
function parseFeature(line) {
|
|
1394
|
+
// Converte feature{} em row{} com cols=3 e variant=feature
|
|
1395
|
+
const inner = line.replace(/^feature/, 'row3')
|
|
1396
|
+
const b = parseRow(inner)
|
|
1397
|
+
if (b) b.variant = 'feature'
|
|
1398
|
+
return b
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1350
1401
|
function rHero(b) {
|
|
1351
1402
|
let h1='',sub='',img='',ctas=''
|
|
1352
1403
|
for(const item of (b.items||[])) for(const f of item){
|
|
@@ -1358,6 +1409,10 @@ function rHero(b) {
|
|
|
1358
1409
|
const v = b.variant || (img ? 'split' : 'centered')
|
|
1359
1410
|
const bgStyle = b.bg ? ` style="background:${b.bg}"` : b.style ? ` style="${b.style.replace(/,/g,';')}"` : ''
|
|
1360
1411
|
const inlineStyle = b.style && !b.bg ? ` style="${b.style.replace(/,/g,';')}"` : ''
|
|
1412
|
+
if (v === 'landing') {
|
|
1413
|
+
return `<section class="fx-hero fx-hero-landing"${bgStyle}><div class="fx-hero-inner">${h1}${sub}${ctas}</div></section>
|
|
1414
|
+
`
|
|
1415
|
+
}
|
|
1361
1416
|
if (v === 'minimal') {
|
|
1362
1417
|
return `<section class="fx-hero fx-hero-minimal"${bgStyle}><div class="fx-hero-inner">${h1}${sub}${ctas}</div></section>\n`
|
|
1363
1418
|
}
|
|
@@ -1430,13 +1485,102 @@ function rSect(b) {
|
|
|
1430
1485
|
return `<section class="fx-sect${cls}"${bgStyle}>${inner}</section>\n`
|
|
1431
1486
|
}
|
|
1432
1487
|
|
|
1488
|
+
|
|
1489
|
+
// ── rCode: code window com syntax highlight ───────────────────────
|
|
1490
|
+
function rCode(b) {
|
|
1491
|
+
const lang = b.lang || 'aip'
|
|
1492
|
+
const id = 'code_' + Math.random().toString(36).slice(2,7)
|
|
1493
|
+
const highlighted = (b.lines||[]).map(line => {
|
|
1494
|
+
let l = esc(line)
|
|
1495
|
+
if(lang==='aip'||lang==='aiplang') {
|
|
1496
|
+
l = l.replace(/^(~\w+)/g,'<span class="fx-kw">$1</span>')
|
|
1497
|
+
l = l.replace(/\$([\w.]+)/g,'<span class="fx-nb">$$$1</span>')
|
|
1498
|
+
l = l.replace(/(\{[^}]*\})/g,'<span class="fx-op">$1</span>')
|
|
1499
|
+
l = l.replace(/(#[\w-]+(?:\.\w+)*)/g,'<span class="fx-comment">$1</span>')
|
|
1500
|
+
} else if(lang==='bash'||lang==='sh') {
|
|
1501
|
+
l = l.replace(/^(\$ )/,'<span class="fx-kw">$1</span>')
|
|
1502
|
+
l = l.replace(/(#.*$)/,'<span class="fx-comment">$1</span>')
|
|
1503
|
+
l = l.replace(/(npx aiplang \w+)/g,'<span class="fx-fn">$1</span>')
|
|
1504
|
+
} else if(lang==='js'||lang==='ts') {
|
|
1505
|
+
l = l.replace(/\b(const|let|var|function|async|await|return|import|from|export|if|else|for)\b/g,'<span class="fx-kw">$1</span>')
|
|
1506
|
+
l = l.replace(/(\/\/.*$)/,'<span class="fx-comment">$1</span>')
|
|
1507
|
+
l = l.replace(/(['"`][^'"`]*['"`])/g,'<span class="fx-st">$1</span>')
|
|
1508
|
+
}
|
|
1509
|
+
return `<div class="fx-code-line">${l}</div>`
|
|
1510
|
+
}).join('')
|
|
1511
|
+
const copyBtn = `<button class="fx-code-copy" onclick="(function(b){navigator.clipboard&&navigator.clipboard.writeText(b.querySelectorAll('.fx-code-line').length?Array.from(b.querySelectorAll('.fx-code-line')).map(l=>l.innerText).join('\\n'):'');var t=b.querySelector('.fx-code-copy');t&&(t.textContent='copiado!',setTimeout(()=>t.textContent='copiar',1500))})(this.closest('.fx-code-window'))">copiar</button>`
|
|
1512
|
+
return `<div class="fx-code-window"><div class="fx-code-bar"><div class="fx-dots"><span></span><span></span><span></span></div><span class="fx-code-lang">${esc(lang)}</span>${copyBtn}</div><div class="fx-code-body" id="${id}">${highlighted}</div></div>\n`
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// ── rBenchmark: cards de benchmark com número + barra ─────────────
|
|
1516
|
+
function rBenchmark(b) {
|
|
1517
|
+
const cards = (b.items||[]).map(item => {
|
|
1518
|
+
const pct = item.pct || (item.num && item.num.includes('%') ? parseInt(item.num) : 85)
|
|
1519
|
+
const n = item.num || ''
|
|
1520
|
+
const isLow = pct < 20
|
|
1521
|
+
return `<div class="fx-bench-card">
|
|
1522
|
+
<div class="fx-bench-label">${esc(item.label||'')}</div>
|
|
1523
|
+
<div class="fx-bench-num">${esc(n)}</div>
|
|
1524
|
+
${item.vs ? `<div class="fx-bench-vs">${esc(item.vs)}</div>` : ''}
|
|
1525
|
+
<div class="fx-bench-bar"><div class="fx-bench-fill" style="width:${isLow?pct+'%':pct+'%'}"></div></div>
|
|
1526
|
+
</div>`
|
|
1527
|
+
}).join('')
|
|
1528
|
+
return `<div class="fx-benchmark">${cards}</div>\n`
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// ── rInstall: multi-step code box com botões ──────────────────────
|
|
1532
|
+
function rInstall(b) {
|
|
1533
|
+
const steps = (b.cmds||[]).map((cmd,i) => {
|
|
1534
|
+
const isComment = cmd.startsWith('#')
|
|
1535
|
+
if(isComment) return `<div class="fx-install-comment">${esc(cmd.slice(1).trim())}</div>`
|
|
1536
|
+
return `<div class="fx-install-line"><span class="fx-install-prompt">$</span><span class="fx-install-cmd">${esc(cmd)}</span></div>`
|
|
1537
|
+
}).join('')
|
|
1538
|
+
const firstCmd = (b.cmds||[]).find(c => !c.startsWith('#')) || ''
|
|
1539
|
+
const copy = `navigator.clipboard&&navigator.clipboard.writeText(${JSON.stringify(firstCmd)})`
|
|
1540
|
+
return `<div class="fx-install-wrap">
|
|
1541
|
+
<div class="fx-code-bar"><div class="fx-dots"><span></span><span></span><span></span></div><span class="fx-code-lang">terminal</span><button class="fx-code-copy" onclick="${copy};var t=this;t.textContent='copiado!';setTimeout(()=>t.textContent='copiar',1500)">copiar</button></div>
|
|
1542
|
+
<div class="fx-install-body">${steps}</div>
|
|
1543
|
+
</div>\n`
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// ── rStats upgrade: suporte a subtítulo via "val:label:vs" ────────
|
|
1547
|
+
function rStatsUpgraded(b) {
|
|
1548
|
+
const cells = (b.items||[]).map(item => {
|
|
1549
|
+
const raw = item[0]?.text || ''
|
|
1550
|
+
const parts = raw.split(':')
|
|
1551
|
+
const val = parts[0]?.trim()
|
|
1552
|
+
const lbl = parts[1]?.trim()
|
|
1553
|
+
const vs = parts[2]?.trim()
|
|
1554
|
+
const bind = isDyn(val) ? ` data-fx-bind="${esc(val)}"` : ''
|
|
1555
|
+
return `<div class="fx-stat">
|
|
1556
|
+
<div class="fx-stat-val"${bind}>${esc(val)}</div>
|
|
1557
|
+
<div class="fx-stat-lbl">${esc(lbl||'')}</div>
|
|
1558
|
+
${vs ? `<div class="fx-stat-vs">${esc(vs)}</div>` : ''}
|
|
1559
|
+
</div>`
|
|
1560
|
+
}).join('')
|
|
1561
|
+
return `<div class="fx-stats">${cells}</div>\n`
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1433
1564
|
function rFoot(b) {
|
|
1565
|
+
let brand='', links='', note=''
|
|
1566
|
+
let itemIdx = 0
|
|
1567
|
+
for(const item of (b.items||[])) for(const f of item){
|
|
1568
|
+
if(f.isLink) links+=`<a href="${esc(f.path)}" class="fx-footer-link">${esc(f.label)}</a>`
|
|
1569
|
+
else if(itemIdx===0) { brand=`<span class="fx-footer-brand">${esc(f.text)}</span>`; itemIdx++ }
|
|
1570
|
+
else note=`<span class="fx-footer-note">${esc(f.text)}</span>`
|
|
1571
|
+
}
|
|
1572
|
+
if(brand||links){
|
|
1573
|
+
return `<footer class="fx-footer"><div class="fx-footer-inner">${brand}<div class="fx-footer-links">${links}</div>${note}</div></footer>
|
|
1574
|
+
`
|
|
1575
|
+
}
|
|
1576
|
+
// fallback centrado
|
|
1434
1577
|
let inner=''
|
|
1435
1578
|
for(const item of (b.items||[])) for(const f of item){
|
|
1436
1579
|
if(f.isLink) inner+=`<a href="${esc(f.path)}" class="fx-footer-link">${esc(f.label)}</a>`
|
|
1437
1580
|
else inner+=`<p class="fx-footer-text">${esc(f.text)}</p>`
|
|
1438
1581
|
}
|
|
1439
|
-
return `<footer class="fx-footer">${inner}</footer
|
|
1582
|
+
return `<footer class="fx-footer">${inner}</footer>
|
|
1583
|
+
`
|
|
1440
1584
|
}
|
|
1441
1585
|
|
|
1442
1586
|
function rTable(b) {
|
|
@@ -1557,6 +1701,51 @@ function genThemeVarCSS(t) {
|
|
|
1557
1701
|
|
|
1558
1702
|
function css(theme) {
|
|
1559
1703
|
const base=`*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}html{scroll-behavior:smooth}body{font-family:-apple-system,'Segoe UI',system-ui,sans-serif;-webkit-font-smoothing:antialiased;min-height:100vh}a{text-decoration:none;color:inherit}input,button,select{font-family:inherit}img{max-width:100%;height:auto}.fx-nav{display:flex;align-items:center;justify-content:space-between;padding:1rem 2.5rem;position:sticky;top:0;z-index:50;backdrop-filter:blur(12px);flex-wrap:wrap;gap:.5rem}.fx-brand{font-size:1.25rem;font-weight:800;letter-spacing:-.03em}.fx-nav-links{display:flex;align-items:center;gap:1.75rem}.fx-nav-link{font-size:.875rem;font-weight:500;opacity:.65;transition:opacity .15s}.fx-nav-link:hover{opacity:1}.fx-hamburger{display:none;flex-direction:column;gap:5px;background:none;border:none;cursor:pointer;padding:.25rem}.fx-hamburger span{display:block;width:22px;height:2px;background:currentColor;transition:all .2s;border-radius:1px}.fx-hamburger.open span:nth-child(1){transform:rotate(45deg) translate(5px,5px)}.fx-hamburger.open span:nth-child(2){opacity:0}.fx-hamburger.open span:nth-child(3){transform:rotate(-45deg) translate(5px,-5px)}@media(max-width:640px){.fx-hamburger{display:flex}.fx-nav-links{display:none;width:100%;flex-direction:column;align-items:flex-start;gap:.75rem;padding:.75rem 0}.fx-nav-links.open{display:flex}}.fx-hero{display:flex;align-items:center;justify-content:center;min-height:92vh;padding:4rem 1.5rem}.fx-hero-split{display:grid;grid-template-columns:1fr 1fr;gap:3rem;align-items:center;padding:4rem 2.5rem;min-height:70vh}@media(max-width:768px){.fx-hero-split{grid-template-columns:1fr}}.fx-hero-img{width:100%;border-radius:1.25rem;object-fit:cover;max-height:500px}.fx-hero-inner{max-width:56rem;text-align:center;display:flex;flex-direction:column;align-items:center;gap:1.5rem}.fx-hero-split .fx-hero-inner{text-align:left;align-items:flex-start;max-width:none}.fx-title{font-size:clamp(2.5rem,8vw,5.5rem);font-weight:900;letter-spacing:-.04em;line-height:1}.fx-sub{font-size:clamp(1rem,2vw,1.25rem);line-height:1.75;max-width:40rem}.fx-cta{display:inline-flex;align-items:center;padding:.875rem 2.5rem;border-radius:.75rem;font-weight:700;font-size:1rem;letter-spacing:-.01em;transition:transform .15s;margin:.25rem}.fx-cta:hover{transform:translateY(-1px)}.fx-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:3rem;padding:5rem 2.5rem;text-align:center}.fx-stat-val{font-size:clamp(2.5rem,5vw,4rem);font-weight:900;letter-spacing:-.04em;line-height:1}.fx-stat-lbl{font-size:.75rem;font-weight:600;text-transform:uppercase;letter-spacing:.1em;margin-top:.5rem}.fx-grid{display:grid;gap:1.25rem;padding:1rem 2.5rem 5rem}.fx-grid-2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}.fx-grid-3{grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}.fx-grid-4{grid-template-columns:repeat(auto-fit,minmax(200px,1fr))}.fx-card{border-radius:1rem;padding:1.75rem;transition:transform .2s,box-shadow .2s}.fx-card:hover{transform:translateY(-2px)}.fx-card-img{width:100%;border-radius:.75rem;object-fit:cover;height:180px;margin-bottom:1rem}.fx-icon{font-size:2rem;margin-bottom:1rem}.fx-card-title{font-size:1.0625rem;font-weight:700;letter-spacing:-.02em;margin-bottom:.5rem}.fx-card-body{font-size:.875rem;line-height:1.65}.fx-card-link{font-size:.8125rem;font-weight:600;display:inline-block;margin-top:1rem;opacity:.6;transition:opacity .15s}.fx-card-link:hover{opacity:1}.fx-sect{padding:5rem 2.5rem}.fx-sect-title{font-size:clamp(1.75rem,4vw,3rem);font-weight:800;letter-spacing:-.04em;margin-bottom:1.5rem;text-align:center}.fx-sect-body{font-size:1rem;line-height:1.75;text-align:center;max-width:48rem;margin:0 auto}.fx-form-wrap{padding:3rem 2.5rem;display:flex;justify-content:center}.fx-form{width:100%;max-width:28rem;border-radius:1.25rem;padding:2.5rem}.fx-field{margin-bottom:1.25rem}.fx-label{display:block;font-size:.8125rem;font-weight:600;margin-bottom:.5rem}.fx-input{width:100%;padding:.75rem 1rem;border-radius:.625rem;font-size:.9375rem;outline:none;transition:box-shadow .15s}.fx-input:focus{box-shadow:0 0 0 3px rgba(37,99,235,.35)}.fx-btn{width:100%;padding:.875rem 1.5rem;border:none;border-radius:.625rem;font-size:.9375rem;font-weight:700;cursor:pointer;margin-top:.5rem;transition:transform .15s,opacity .15s;letter-spacing:-.01em}.fx-btn:hover{transform:translateY(-1px)}.fx-btn:disabled{opacity:.5;cursor:not-allowed;transform:none}.fx-btn-wrap{padding:0 2.5rem 1.5rem}.fx-standalone-btn{width:auto;padding:.75rem 2rem;margin-top:0}.fx-form-msg{font-size:.8125rem;padding:.5rem 0;min-height:1.5rem;text-align:center}.fx-form-err{color:#f87171}.fx-form-ok{color:#4ade80}.fx-table-wrap{overflow-x:auto;padding:0 2.5rem 4rem}.fx-table{width:100%;border-collapse:collapse;font-size:.875rem}.fx-th{text-align:left;padding:.875rem 1.25rem;font-size:.75rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em}.fx-th-actions{opacity:.6}.fx-tr{transition:background .1s}.fx-td{padding:.875rem 1.25rem}.fx-td-empty{padding:2rem 1.25rem;text-align:center;opacity:.4}.fx-td-actions{white-space:nowrap;padding:.5rem 1rem!important}.fx-action-btn{border:none;cursor:pointer;font-size:.75rem;font-weight:600;padding:.3rem .75rem;border-radius:.375rem;margin-right:.375rem;font-family:inherit;transition:opacity .15s}.fx-action-btn:hover{opacity:.85}.fx-edit-btn{background:#1e40af;color:#93c5fd}.fx-delete-btn{background:#7f1d1d;color:#fca5a5}.fx-select-wrap{padding:.5rem 2.5rem}.fx-select-block{width:auto;min-width:200px;margin-top:0}.fx-pricing{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1.5rem;padding:2rem 2.5rem 5rem;align-items:start}.fx-pricing-card{border-radius:1.25rem;padding:2rem;position:relative;transition:transform .2s}.fx-pricing-featured{transform:scale(1.03)}.fx-pricing-badge{position:absolute;top:-12px;left:50%;transform:translateX(-50%);background:#2563eb;color:#fff;font-size:.7rem;font-weight:700;padding:.25rem .875rem;border-radius:999px;white-space:nowrap;letter-spacing:.05em}.fx-pricing-name{font-size:.875rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;margin-bottom:.5rem;opacity:.7}.fx-pricing-price{font-size:3rem;font-weight:900;letter-spacing:-.05em;line-height:1;margin-bottom:.75rem}.fx-pricing-desc{font-size:.875rem;line-height:1.65;margin-bottom:1.5rem;opacity:.7}.fx-pricing-cta{display:block;text-align:center;padding:.75rem;border-radius:.625rem;font-weight:700;font-size:.9rem;transition:opacity .15s}.fx-pricing-cta:hover{opacity:.85}.fx-faq{max-width:48rem;margin:0 auto}.fx-faq-item{border-radius:.75rem;margin-bottom:.625rem;cursor:pointer;overflow:hidden;transition:background .15s}.fx-faq-q{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;font-size:.9375rem;font-weight:600}.fx-faq-arrow{transition:transform .2s;font-size:.75rem;opacity:.5}.fx-faq-item.open .fx-faq-arrow{transform:rotate(90deg)}.fx-faq-a{max-height:0;overflow:hidden;padding:0 1.25rem;font-size:.875rem;line-height:1.7;transition:max-height .3s,padding .3s}.fx-faq-item.open .fx-faq-a{max-height:300px;padding:.75rem 1.25rem 1.25rem}.fx-testi-wrap{padding:5rem 2.5rem;display:flex;justify-content:center}.fx-testi{max-width:42rem;text-align:center;display:flex;flex-direction:column;align-items:center;gap:1.25rem}.fx-testi-img{width:64px;height:64px;border-radius:50%;object-fit:cover}.fx-testi-avatar{width:64px;height:64px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.5rem;font-weight:700;background:#1e293b}.fx-testi-quote{font-size:1.25rem;line-height:1.7;font-style:italic;opacity:.9}.fx-testi-author{font-size:.875rem;font-weight:600;opacity:.5}.fx-gallery{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:.75rem;padding:1rem 2.5rem 4rem}.fx-gallery-item{border-radius:.75rem;overflow:hidden;aspect-ratio:4/3}.fx-gallery-item img{width:100%;height:100%;object-fit:cover;transition:transform .3s}.fx-gallery-item:hover img{transform:scale(1.04)}.fx-if-wrap{display:contents}.fx-footer{padding:3rem 2.5rem;text-align:center}.fx-footer-text{font-size:.8125rem}.fx-footer-link{font-size:.8125rem;margin:0 .75rem;opacity:.5;transition:opacity .15s}.fx-footer-link:hover{opacity:1}
|
|
1704
|
+
/* ── code window ── */
|
|
1705
|
+
.fx-code-window{border-radius:.875rem;overflow:hidden;border:1px solid rgba(255,255,255,.08);margin:0 2.5rem 2rem}
|
|
1706
|
+
.fx-code-bar{display:flex;align-items:center;gap:.75rem;padding:.625rem 1rem;background:rgba(255,255,255,.04);border-bottom:1px solid rgba(255,255,255,.06)}
|
|
1707
|
+
.fx-dots{display:flex;gap:.375rem}
|
|
1708
|
+
.fx-dots span{width:10px;height:10px;border-radius:50%;background:rgba(255,255,255,.15)}
|
|
1709
|
+
.fx-dots span:nth-child(1){background:#ff5f57}.fx-dots span:nth-child(2){background:#febc2e}.fx-dots span:nth-child(3){background:#28c840}
|
|
1710
|
+
.fx-code-lang{font-size:.62rem;letter-spacing:.1em;text-transform:uppercase;opacity:.35;font-family:monospace;margin-left:.25rem}
|
|
1711
|
+
.fx-code-copy{margin-left:auto;font-family:monospace;font-size:.62rem;letter-spacing:.1em;text-transform:uppercase;background:none;border:1px solid rgba(255,255,255,.15);color:rgba(255,255,255,.4);padding:.2rem .625rem;border-radius:.3rem;cursor:pointer;transition:all .15s}
|
|
1712
|
+
.fx-code-copy:hover{border-color:rgba(255,255,255,.3);color:rgba(255,255,255,.7)}
|
|
1713
|
+
.fx-code-body{padding:1.375rem 1.5rem;overflow-x:auto}
|
|
1714
|
+
.fx-code-line{font-family:"JetBrains Mono","Fira Code","Courier New",monospace;font-size:.8rem;line-height:1.75;color:#8899aa;white-space:pre}
|
|
1715
|
+
.fx-kw{color:#c792ea}.fx-st{color:#c3e88d}.fx-fn{color:#82aaff}.fx-nb{color:#f78c6c}.fx-op{color:var(--accent,#ff5722)}.fx-comment{color:#3d5166;font-style:italic}
|
|
1716
|
+
/* ── benchmark ── */
|
|
1717
|
+
.fx-benchmark{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:1rem;padding:1rem 2.5rem 4rem}
|
|
1718
|
+
.fx-bench-card{border-radius:1rem;padding:1.5rem;border:1px solid rgba(255,255,255,.06);background:rgba(255,255,255,.02);position:relative;overflow:hidden}
|
|
1719
|
+
.fx-bench-card::before{content:"";position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,var(--accent,#ff5722),transparent)}
|
|
1720
|
+
.fx-bench-label{font-size:.6rem;letter-spacing:.12em;text-transform:uppercase;opacity:.5;margin-bottom:.5rem;font-family:monospace}
|
|
1721
|
+
.fx-bench-num{font-size:clamp(2rem,5vw,3.5rem);font-weight:900;letter-spacing:-.04em;line-height:1;color:var(--accent,#ff5722)}
|
|
1722
|
+
.fx-bench-vs{font-size:.7rem;opacity:.4;margin-top:.25rem;font-family:monospace}
|
|
1723
|
+
.fx-bench-bar{margin-top:1rem;height:5px;background:rgba(255,255,255,.06);border-radius:3px;overflow:hidden}
|
|
1724
|
+
.fx-bench-fill{height:100%;border-radius:3px;background:var(--accent,#ff5722);transition:width 1.5s cubic-bezier(.4,0,.2,1)}
|
|
1725
|
+
/* ── install ── */
|
|
1726
|
+
.fx-install-wrap{border-radius:.875rem;overflow:hidden;border:1px solid rgba(255,255,255,.08);margin:0 2.5rem 2rem;max-width:540px}
|
|
1727
|
+
.fx-install-body{padding:1.25rem 1.5rem}
|
|
1728
|
+
.fx-install-line{display:flex;gap:.75rem;padding:.25rem 0;font-family:"JetBrains Mono","Courier New",monospace;font-size:.8rem;line-height:1.7}
|
|
1729
|
+
.fx-install-prompt{color:var(--accent,#ff5722);flex-shrink:0}
|
|
1730
|
+
.fx-install-cmd{color:rgba(255,255,255,.85)}
|
|
1731
|
+
.fx-install-comment{font-family:"JetBrains Mono","Courier New",monospace;font-size:.72rem;color:rgba(255,255,255,.25);padding:.25rem 0;font-style:italic}
|
|
1732
|
+
/* ── stats upgrade ── */
|
|
1733
|
+
.fx-stat-vs{font-size:.65rem;opacity:.35;margin-top:.2rem;letter-spacing:.02em}
|
|
1734
|
+
/* ── hero landing (dark variant) grid + glow ── */
|
|
1735
|
+
.fx-hero-landing{position:relative;overflow:hidden}
|
|
1736
|
+
.fx-hero-landing::before{content:"";position:absolute;inset:0;background:linear-gradient(rgba(255,255,255,.05) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.05) 1px,transparent 1px);background-size:60px 60px;mask-image:radial-gradient(ellipse 70% 60% at 50% 50%,black,transparent);pointer-events:none}
|
|
1737
|
+
.fx-hero-landing::after{content:"";position:absolute;width:700px;height:500px;border-radius:50%;background:radial-gradient(ellipse,rgba(255,87,34,.13) 0%,transparent 70%);left:50%;top:50%;transform:translate(-50%,-50%);pointer-events:none;animation:fx-breathe 6s ease-in-out infinite}
|
|
1738
|
+
@keyframes fx-breathe{0%,100%{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-50%,-50%) scale(1.1)}}
|
|
1739
|
+
/* ── feature grid ── */
|
|
1740
|
+
.fx-grid-feature{gap:1rem}
|
|
1741
|
+
.fx-grid-feature .fx-card{transition:border-color .2s,transform .15s}
|
|
1742
|
+
.fx-grid-feature .fx-card:hover{border-color:rgba(255,87,34,.25)}
|
|
1743
|
+
.fx-grid-feature .fx-icon{width:44px;height:44px;border-radius:.75rem;display:flex;align-items:center;justify-content:center;background:rgba(255,87,34,.08);border:1px solid rgba(255,87,34,.15)}
|
|
1744
|
+
/* ── footer upgrade ── */
|
|
1745
|
+
.fx-footer-inner{max-width:1100px;margin:0 auto;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:1rem}
|
|
1746
|
+
.fx-footer-brand{font-size:1rem;font-weight:800;letter-spacing:-.02em}
|
|
1747
|
+
.fx-footer-links{display:flex;gap:1.5rem;flex-wrap:wrap}
|
|
1748
|
+
.fx-footer-note{font-size:.72rem;opacity:.3;font-family:monospace}
|
|
1560
1749
|
.fx-hero-minimal{min-height:50vh!important}
|
|
1561
1750
|
.fx-hero-minimal .fx-hero-inner{gap:1rem}
|
|
1562
1751
|
.fx-hero-tall{min-height:98vh!important}
|
package/package.json
CHANGED
package/server/server.js
CHANGED
|
@@ -2432,7 +2432,7 @@ async function startServer(aipFile, port = 3000) {
|
|
|
2432
2432
|
})
|
|
2433
2433
|
|
|
2434
2434
|
srv.addRoute('GET', '/health', (req, res) => res.json(200, {
|
|
2435
|
-
status:'ok', version:'2.11.
|
|
2435
|
+
status:'ok', version:'2.11.5',
|
|
2436
2436
|
models: app.models.map(m=>m.name),
|
|
2437
2437
|
routes: app.apis.length, pages: app.pages.length,
|
|
2438
2438
|
admin: app.admin?.prefix || null,
|