aiplang 2.11.5 → 2.11.7
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 +189 -10
- 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.7'
|
|
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.7'`)
|
|
690
690
|
lines.push(``)
|
|
691
691
|
return lines.join('\n')
|
|
692
692
|
}
|
|
@@ -1044,6 +1044,11 @@ function parseBlock(line) {
|
|
|
1044
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
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
1046
|
if(line.startsWith('feature{')) { const b=parseBlock(line.replace(/^feature/,'row3'));if(b){b.variant='feature'};return b }
|
|
1047
|
+
if(line.startsWith('marquee{')) { const m=line.match(/^marquee\{([^}]*)\}/);if(m){const vm=line.match(/variant:(\S+)/);return{kind:'marquee',items:m[1].split('|').map(s=>s.trim()).filter(Boolean),variant:vm?.[1]}} }
|
|
1048
|
+
if(line.startsWith('cta{')) { const m=line.match(/^cta\{([^}]*)\}/);if(m){const pts=m[1].split('|');let t='',s='',links=[];pts.forEach(p=>{const lm=p.match(/^([^>]+)>([^>]+)$/);if(lm)links.push({label:lm[1].trim(),path:lm[2].trim()});else if(!t)t=p.trim();else s=p.trim()});const vm=line.match(/variant:(\S+)/);return{kind:'cta',title:t,sub:s,links,variant:vm?.[1]}} }
|
|
1049
|
+
if(line.startsWith('steps{')) { const m=line.match(/^steps\{([^}]*)\}/);if(m){const vm=line.match(/variant:(\S+)/);return{kind:'steps',items:m[1].split('|').map(it=>{const p=it.trim().split('>');return{num:p[0]?.trim(),title:p[1]?.trim(),desc:p[2]?.trim()}}),variant:vm?.[1]}} }
|
|
1050
|
+
if(line.startsWith('compare{')) { const m=line.match(/^compare\{([^}]*)\}/);if(m){const vm=line.match(/variant:(\S+)/);return{kind:'compare',rows:m[1].split('|').map(r=>r.trim().split(':').map(c=>c.trim())),variant:vm?.[1]}} }
|
|
1051
|
+
if(line.startsWith('video{')) { const m=line.match(/^video\{([^}]*)\}/);if(m){const pts=m[1].split('|');return{kind:'video',url:pts[0]?.trim(),poster:pts[1]?.trim()}} }
|
|
1047
1052
|
if(line.startsWith('faq{')) {
|
|
1048
1053
|
const body=line.slice(4,line.lastIndexOf('}')).trim()
|
|
1049
1054
|
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)
|
|
@@ -1277,6 +1282,11 @@ function renderBlock(b, page) {
|
|
|
1277
1282
|
case 'install': return rInstall(b)
|
|
1278
1283
|
case 'feature': return rRow(b)
|
|
1279
1284
|
case 'testimonial': return rTestimonial(b)
|
|
1285
|
+
case 'marquee': return rMarquee(b)
|
|
1286
|
+
case 'cta': return rCta(b)
|
|
1287
|
+
case 'steps': return rSteps(b)
|
|
1288
|
+
case 'compare': return rCompare(b)
|
|
1289
|
+
case 'video': return rVideo(b)
|
|
1280
1290
|
case 'gallery': return rGallery(b)
|
|
1281
1291
|
case 'raw': return (b.html||'')+'\n'
|
|
1282
1292
|
case 'html': return `<div class="fx-html">${b.content||''}</div>\n`
|
|
@@ -1400,10 +1410,17 @@ function parseFeature(line) {
|
|
|
1400
1410
|
|
|
1401
1411
|
function rHero(b) {
|
|
1402
1412
|
let h1='',sub='',img='',ctas=''
|
|
1413
|
+
let heroBadge = ''
|
|
1403
1414
|
for(const item of (b.items||[])) for(const f of item){
|
|
1415
|
+
if(f.text?.startsWith('badge:')) { heroBadge=`<div class="fx-hero-badge"><span class="fx-hero-badge-dot"></span>${esc(f.text.slice(6).trim())}</div>`; continue }
|
|
1404
1416
|
if(f.isImg) img=`<img src="${esc(f.src)}" class="fx-hero-img" alt="hero" loading="eager">`
|
|
1405
1417
|
else if(f.isLink) ctas+=`<a href="${esc(f.path)}" class="fx-cta">${esc(f.label)}</a>`
|
|
1406
|
-
else if(!h1)
|
|
1418
|
+
else if(!h1) {
|
|
1419
|
+
// *texto* entre asteriscos = gradient text
|
|
1420
|
+
const gt = f.text.match(/^\*(.*?)\*$/)
|
|
1421
|
+
if(gt) h1=`<h1 class="fx-title"><span class="fx-gradient-text">${esc(gt[1])}</span></h1>`
|
|
1422
|
+
else h1=`<h1 class="fx-title">${esc(f.text)}</h1>`
|
|
1423
|
+
}
|
|
1407
1424
|
else sub+=`<p class="fx-sub">${esc(f.text)}</p>`
|
|
1408
1425
|
}
|
|
1409
1426
|
const v = b.variant || (img ? 'split' : 'centered')
|
|
@@ -1413,6 +1430,7 @@ function rHero(b) {
|
|
|
1413
1430
|
return `<section class="fx-hero fx-hero-landing"${bgStyle}><div class="fx-hero-inner">${h1}${sub}${ctas}</div></section>
|
|
1414
1431
|
`
|
|
1415
1432
|
}
|
|
1433
|
+
if (h1) h1 = heroBadge + h1
|
|
1416
1434
|
if (v === 'minimal') {
|
|
1417
1435
|
return `<section class="fx-hero fx-hero-minimal"${bgStyle}><div class="fx-hero-inner">${h1}${sub}${ctas}</div></section>\n`
|
|
1418
1436
|
}
|
|
@@ -1468,7 +1486,7 @@ function rRow(b) {
|
|
|
1468
1486
|
}).join('')
|
|
1469
1487
|
const v=b.variant||''
|
|
1470
1488
|
const wrapStyle=b.style?` style="${b.style.replace(/,/g,';')}"`:''
|
|
1471
|
-
return `<div class="fx-grid fx-grid-${b.cols||3}${v?' fx-grid-'+v:''}"${wrapStyle}>${cards}</div>\n`
|
|
1489
|
+
return `<div class="fx-grid fx-grid-${b.cols||3}${v?' fx-grid-'+v:''} fx-animate-stagger"${wrapStyle}>${cards}</div>\n`
|
|
1472
1490
|
}
|
|
1473
1491
|
|
|
1474
1492
|
function rSect(b) {
|
|
@@ -1482,7 +1500,7 @@ function rSect(b) {
|
|
|
1482
1500
|
const bgStyle=b.bg?` style="background:${b.bg}"`:(b.style?` style="${b.style.replace(/,/g,';')}"`:'' )
|
|
1483
1501
|
const v = b.variant||''
|
|
1484
1502
|
const cls = v ? ` fx-sect-${v}` : ''
|
|
1485
|
-
return `<section class="fx-sect${cls}"${bgStyle}>${inner}</section>\n`
|
|
1503
|
+
return `<section class="fx-sect${cls} fx-animate"${bgStyle}>${inner}</section>\n`
|
|
1486
1504
|
}
|
|
1487
1505
|
|
|
1488
1506
|
|
|
@@ -1525,7 +1543,7 @@ function rBenchmark(b) {
|
|
|
1525
1543
|
<div class="fx-bench-bar"><div class="fx-bench-fill" style="width:${isLow?pct+'%':pct+'%'}"></div></div>
|
|
1526
1544
|
</div>`
|
|
1527
1545
|
}).join('')
|
|
1528
|
-
return `<div class="fx-benchmark">${cards}</div>\n`
|
|
1546
|
+
return `<div class="fx-benchmark fx-animate-stagger">${cards}</div>\n`
|
|
1529
1547
|
}
|
|
1530
1548
|
|
|
1531
1549
|
// ── rInstall: multi-step code box com botões ──────────────────────
|
|
@@ -1552,8 +1570,12 @@ function rStatsUpgraded(b) {
|
|
|
1552
1570
|
const lbl = parts[1]?.trim()
|
|
1553
1571
|
const vs = parts[2]?.trim()
|
|
1554
1572
|
const bind = isDyn(val) ? ` data-fx-bind="${esc(val)}"` : ''
|
|
1573
|
+
// Números → animados com counter
|
|
1574
|
+
const isNum = !isDyn(val) && /^[\d.,]+[KkMmBb%]?$/.test(val?.replace(/ms|KB|GB|px/,''))
|
|
1575
|
+
const numAttr = isNum && !isDyn(val) ? ` data-to="${val.replace(/[^\d.]/g,'')}" data-dec="${val.includes('.')?val.split('.')[1]?.replace(/[^\d]/g,'').length||0:0}"` : ''
|
|
1576
|
+
const countCls = isNum && !isDyn(val) ? ' fx-count' : ''
|
|
1555
1577
|
return `<div class="fx-stat">
|
|
1556
|
-
<div class="fx-stat-val"${bind}>${esc(val)}</div>
|
|
1578
|
+
<div class="fx-stat-val${countCls}"${bind}${numAttr}>${esc(val)}</div>
|
|
1557
1579
|
<div class="fx-stat-lbl">${esc(lbl||'')}</div>
|
|
1558
1580
|
${vs ? `<div class="fx-stat-vs">${esc(vs)}</div>` : ''}
|
|
1559
1581
|
</div>`
|
|
@@ -1561,23 +1583,116 @@ function rStatsUpgraded(b) {
|
|
|
1561
1583
|
return `<div class="fx-stats">${cells}</div>\n`
|
|
1562
1584
|
}
|
|
1563
1585
|
|
|
1586
|
+
|
|
1587
|
+
|
|
1588
|
+
// ── rMarquee: faixa de logos/texto em loop infinito ───────────────
|
|
1589
|
+
function rMarquee(b) {
|
|
1590
|
+
const speed = b.variant === 'fast' ? '15s' : b.variant === 'slow' ? '40s' : '25s'
|
|
1591
|
+
const items = (b.items||[]).map(item =>
|
|
1592
|
+
`<span class="fx-marquee-item">${esc(item)}</span><span class="fx-marquee-sep">·</span>`
|
|
1593
|
+
).join('')
|
|
1594
|
+
// Duplicar para loop contínuo
|
|
1595
|
+
return `<div class="fx-marquee"><div class="fx-marquee-track" style="animation-duration:${speed}">${items}${items}</div></div>\n`
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// ── rCta: seção call-to-action com glow ───────────────────────────
|
|
1599
|
+
function rCta(b) {
|
|
1600
|
+
const btns = (b.links||[]).map((l,i) =>
|
|
1601
|
+
`<a href="${esc(l.path)}" class="fx-cta${i===0?'':' fx-cta-outline'}">${esc(l.label)}</a>`
|
|
1602
|
+
).join('')
|
|
1603
|
+
const v = b.variant || 'default'
|
|
1604
|
+
return `<section class="fx-cta-section fx-cta-${v}">
|
|
1605
|
+
<div class="fx-cta-glow"></div>
|
|
1606
|
+
<div class="fx-cta-inner">
|
|
1607
|
+
${b.title?`<h2 class="fx-cta-title">${esc(b.title)}</h2>`:''}
|
|
1608
|
+
${b.sub?`<p class="fx-cta-sub">${esc(b.sub)}</p>`:''}
|
|
1609
|
+
<div class="fx-cta-actions">${btns}</div>
|
|
1610
|
+
</div>
|
|
1611
|
+
</section>\n`
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// ── rSteps: passos 1-2-3 com linha conectora ──────────────────────
|
|
1615
|
+
function rSteps(b) {
|
|
1616
|
+
const items = (b.items||[]).map((step, i) =>
|
|
1617
|
+
`<div class="fx-step">
|
|
1618
|
+
<div class="fx-step-num">${esc(step.num||String(i+1))}</div>
|
|
1619
|
+
<div class="fx-step-body">
|
|
1620
|
+
<div class="fx-step-title">${esc(step.title||'')}</div>
|
|
1621
|
+
<div class="fx-step-desc">${esc(step.desc||'')}</div>
|
|
1622
|
+
</div>
|
|
1623
|
+
</div>`
|
|
1624
|
+
).join('')
|
|
1625
|
+
const v = b.variant === 'vertical' ? ' fx-steps-vertical' : ''
|
|
1626
|
+
return `<div class="fx-steps${v} fx-animate-stagger">${items}</div>\n`
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// ── rCompare: tabela X vs Y ───────────────────────────────────────
|
|
1630
|
+
function rCompare(b) {
|
|
1631
|
+
const rows = b.rows || []
|
|
1632
|
+
if (!rows.length) return ''
|
|
1633
|
+
// Primeira linha = cabeçalho se tiver textos
|
|
1634
|
+
const header = rows[0]
|
|
1635
|
+
const isHeader = header.length > 1 && !header[0].startsWith('✅') && !header[0].startsWith('❌')
|
|
1636
|
+
const headerHtml = isHeader
|
|
1637
|
+
? `<div class="fx-compare-header">${header.map((h,i) => `<div class="fx-compare-cell${i===0?' fx-compare-feature':i===1?' fx-compare-col-a':' fx-compare-col-b'}">${esc(h)}</div>`).join('')}</div>`
|
|
1638
|
+
: ''
|
|
1639
|
+
const dataRows = isHeader ? rows.slice(1) : rows
|
|
1640
|
+
const bodyHtml = dataRows.map(row => {
|
|
1641
|
+
const feature = row[0] || ''
|
|
1642
|
+
const a = row[1] || ''
|
|
1643
|
+
const b2 = row[2] || ''
|
|
1644
|
+
const checkA = a === '✅' || a === 'sim' || a === 'yes' ? '✅' : a === '❌' || a === 'nao' || a === 'no' ? '❌' : esc(a)
|
|
1645
|
+
const checkB = b2 === '✅' || b2 === 'sim' || b2 === 'yes' ? '✅' : b2 === '❌' || b2 === 'nao' || b2 === 'no' ? '❌' : esc(b2)
|
|
1646
|
+
return `<div class="fx-compare-row">
|
|
1647
|
+
<div class="fx-compare-cell fx-compare-feature">${esc(feature)}</div>
|
|
1648
|
+
<div class="fx-compare-cell fx-compare-col-a">${checkA}</div>
|
|
1649
|
+
<div class="fx-compare-cell fx-compare-col-b">${checkB}</div>
|
|
1650
|
+
</div>`
|
|
1651
|
+
}).join('')
|
|
1652
|
+
return `<div class="fx-compare">${headerHtml}${bodyHtml}</div>\n`
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
// ── rVideo: embed de vídeo ou youtube ─────────────────────────────
|
|
1656
|
+
function rVideo(b) {
|
|
1657
|
+
const url = b.url || ''
|
|
1658
|
+
const poster = b.poster || ''
|
|
1659
|
+
// Detectar YouTube
|
|
1660
|
+
const ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/)
|
|
1661
|
+
if (ytMatch) {
|
|
1662
|
+
const id = ytMatch[1]
|
|
1663
|
+
return `<div class="fx-video-wrap"><div class="fx-video-yt"><iframe src="https://www.youtube-nocookie.com/embed/${esc(id)}?rel=0" frameborder="0" allowfullscreen loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe></div></div>\n`
|
|
1664
|
+
}
|
|
1665
|
+
// Vídeo HTML5
|
|
1666
|
+
return `<div class="fx-video-wrap"><video class="fx-video" controls${poster?` poster="${esc(poster)}"`:''} preload="metadata"><source src="${esc(url)}"></video></div>\n`
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// ── autoYear: substitui © YYYY por © <span data-fx-year></span> ──
|
|
1670
|
+
function autoYear(text) {
|
|
1671
|
+
// Substitui padrões como "© 2024", "© 2025", "© 2026" ou só "©" pelo ano dinâmico
|
|
1672
|
+
const replaced = esc(text).replace(/©\s*(\d{4})?/g, (_, yr) =>
|
|
1673
|
+
`© <span class="fx-year">${yr||new Date().getFullYear()}</span>`
|
|
1674
|
+
)
|
|
1675
|
+
return replaced
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1564
1678
|
function rFoot(b) {
|
|
1679
|
+
const _yearScript = `<script>document.querySelectorAll('.fx-year').forEach(function(el){el.textContent=new Date().getFullYear()})</script>`
|
|
1565
1680
|
let brand='', links='', note=''
|
|
1566
1681
|
let itemIdx = 0
|
|
1567
1682
|
for(const item of (b.items||[])) for(const f of item){
|
|
1568
1683
|
if(f.isLink) links+=`<a href="${esc(f.path)}" class="fx-footer-link">${esc(f.label)}</a>`
|
|
1569
1684
|
else if(itemIdx===0) { brand=`<span class="fx-footer-brand">${esc(f.text)}</span>`; itemIdx++ }
|
|
1570
|
-
else note=`<span class="fx-footer-note">${
|
|
1685
|
+
else note=`<span class="fx-footer-note">${autoYear(f.text)}</span>`
|
|
1571
1686
|
}
|
|
1572
1687
|
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
|
|
1688
|
+
return `<footer class="fx-footer"><div class="fx-footer-inner">${brand}<div class="fx-footer-links">${links}</div>${note}</div></footer>${_yearScript}
|
|
1574
1689
|
`
|
|
1575
1690
|
}
|
|
1576
1691
|
// fallback centrado
|
|
1577
1692
|
let inner=''
|
|
1578
1693
|
for(const item of (b.items||[])) for(const f of item){
|
|
1579
1694
|
if(f.isLink) inner+=`<a href="${esc(f.path)}" class="fx-footer-link">${esc(f.label)}</a>`
|
|
1580
|
-
else inner+=`<p class="fx-footer-text">${
|
|
1695
|
+
else inner+=`<p class="fx-footer-text">${autoYear(f.text)}</p>`
|
|
1581
1696
|
}
|
|
1582
1697
|
return `<footer class="fx-footer">${inner}</footer>
|
|
1583
1698
|
`
|
|
@@ -1746,6 +1861,70 @@ function css(theme) {
|
|
|
1746
1861
|
.fx-footer-brand{font-size:1rem;font-weight:800;letter-spacing:-.02em}
|
|
1747
1862
|
.fx-footer-links{display:flex;gap:1.5rem;flex-wrap:wrap}
|
|
1748
1863
|
.fx-footer-note{font-size:.72rem;opacity:.3;font-family:monospace}
|
|
1864
|
+
/* ── marquee ── */
|
|
1865
|
+
.fx-marquee{overflow:hidden;padding:1.5rem 0;border-top:1px solid rgba(255,255,255,.06);border-bottom:1px solid rgba(255,255,255,.06);-webkit-mask:linear-gradient(90deg,transparent,black 10%,black 90%,transparent);mask:linear-gradient(90deg,transparent,black 10%,black 90%,transparent)}
|
|
1866
|
+
.fx-marquee-track{display:flex;width:max-content;animation:fx-marquee 25s linear infinite}
|
|
1867
|
+
.fx-marquee:hover .fx-marquee-track{animation-play-state:paused}
|
|
1868
|
+
@keyframes fx-marquee{0%{transform:translateX(0)}100%{transform:translateX(-50%)}}
|
|
1869
|
+
.fx-marquee-item{font-size:.9375rem;font-weight:600;opacity:.35;white-space:nowrap;padding:0 1.5rem;transition:opacity .2s}
|
|
1870
|
+
.fx-marquee-item:hover{opacity:.7}
|
|
1871
|
+
.fx-marquee-sep{opacity:.15;padding:0 .25rem}
|
|
1872
|
+
/* ── cta section ── */
|
|
1873
|
+
.fx-cta-section{position:relative;padding:6rem 2.5rem;text-align:center;overflow:hidden}
|
|
1874
|
+
.fx-cta-glow{position:absolute;width:600px;height:400px;border-radius:50%;background:radial-gradient(ellipse,rgba(var(--accent-rgb,255,87,34),.12),transparent 70%);left:50%;top:50%;transform:translate(-50%,-50%);pointer-events:none}
|
|
1875
|
+
.fx-cta-inner{position:relative;z-index:1;max-width:44rem;margin:0 auto;display:flex;flex-direction:column;align-items:center;gap:1.5rem}
|
|
1876
|
+
.fx-cta-title{font-size:clamp(2rem,5vw,4rem);font-weight:900;letter-spacing:-.04em;line-height:1.05}
|
|
1877
|
+
.fx-cta-sub{font-size:1.0625rem;line-height:1.75;opacity:.65;max-width:36rem}
|
|
1878
|
+
.fx-cta-actions{display:flex;gap:.875rem;flex-wrap:wrap;justify-content:center}
|
|
1879
|
+
.fx-cta-outline{background:transparent!important;border:1px solid rgba(255,255,255,.2)!important;color:inherit!important}
|
|
1880
|
+
.fx-cta-outline:hover{background:rgba(255,255,255,.05)!important}
|
|
1881
|
+
/* ── steps ── */
|
|
1882
|
+
.fx-steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:2rem;padding:2rem 2.5rem 4rem;position:relative}
|
|
1883
|
+
.fx-steps::before{content:"";position:absolute;top:3.5rem;left:calc(2.5rem + 1.5rem);right:calc(2.5rem + 1.5rem);height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.1),transparent);pointer-events:none}
|
|
1884
|
+
.fx-step{display:flex;flex-direction:column;align-items:flex-start;gap:1rem}
|
|
1885
|
+
.fx-step-num{width:48px;height:48px;border-radius:50%;background:var(--accent,#ff5722);color:#fff;font-weight:900;font-size:1.125rem;display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative;z-index:1}
|
|
1886
|
+
.fx-step-title{font-size:1rem;font-weight:700;margin-bottom:.375rem;letter-spacing:-.02em}
|
|
1887
|
+
.fx-step-desc{font-size:.875rem;line-height:1.65;opacity:.6}
|
|
1888
|
+
.fx-steps-vertical{grid-template-columns:1fr}
|
|
1889
|
+
.fx-steps-vertical::before{display:none}
|
|
1890
|
+
.fx-steps-vertical .fx-step{flex-direction:row}
|
|
1891
|
+
/* ── compare ── */
|
|
1892
|
+
.fx-compare{max-width:640px;margin:0 auto 4rem;padding:0 2.5rem}
|
|
1893
|
+
.fx-compare-header{display:grid;grid-template-columns:1fr 1fr 1fr;padding:.75rem 1rem;background:rgba(255,255,255,.04);border-radius:.75rem .75rem 0 0;font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;opacity:.6}
|
|
1894
|
+
.fx-compare-row{display:grid;grid-template-columns:1fr 1fr 1fr;padding:.75rem 1rem;border-bottom:1px solid rgba(255,255,255,.05);transition:background .15s}
|
|
1895
|
+
.fx-compare-row:hover{background:rgba(255,255,255,.02)}
|
|
1896
|
+
.fx-compare-row:last-child{border-bottom:none;border-radius:0 0 .75rem .75rem}
|
|
1897
|
+
.fx-compare-cell{font-size:.875rem}
|
|
1898
|
+
.fx-compare-feature{opacity:.7}
|
|
1899
|
+
.fx-compare-col-a{text-align:center;color:#4ade80}
|
|
1900
|
+
.fx-compare-col-b{text-align:center;opacity:.35}
|
|
1901
|
+
/* ── video ── */
|
|
1902
|
+
.fx-video-wrap{padding:0 2.5rem 3rem}
|
|
1903
|
+
.fx-video-yt{position:relative;padding-bottom:56.25%;height:0;overflow:hidden;border-radius:1rem;border:1px solid rgba(255,255,255,.06)}
|
|
1904
|
+
.fx-video-yt iframe{position:absolute;top:0;left:0;width:100%;height:100%}
|
|
1905
|
+
.fx-video{width:100%;border-radius:1rem;border:1px solid rgba(255,255,255,.06)}
|
|
1906
|
+
/* ── gradient text ── */
|
|
1907
|
+
.fx-gradient-text{background:linear-gradient(135deg,var(--accent,#ff5722),#ff8a50,#ffd4c4);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
|
1908
|
+
/* ── scroll animations (IntersectionObserver) ── */
|
|
1909
|
+
.fx-animate{opacity:0;transform:translateY(20px);transition:opacity .6s cubic-bezier(.4,0,.2,1),transform .6s cubic-bezier(.4,0,.2,1)}
|
|
1910
|
+
.fx-animate.fx-visible{opacity:1;transform:none}
|
|
1911
|
+
.fx-animate-delay-1{transition-delay:.1s}
|
|
1912
|
+
.fx-animate-delay-2{transition-delay:.2s}
|
|
1913
|
+
.fx-animate-delay-3{transition-delay:.3s}
|
|
1914
|
+
.fx-animate-stagger>*{opacity:0;transform:translateY(16px);transition:opacity .5s cubic-bezier(.4,0,.2,1),transform .5s cubic-bezier(.4,0,.2,1)}
|
|
1915
|
+
.fx-animate-stagger.fx-visible>*:nth-child(1){opacity:1;transform:none;transition-delay:.05s}
|
|
1916
|
+
.fx-animate-stagger.fx-visible>*:nth-child(2){opacity:1;transform:none;transition-delay:.15s}
|
|
1917
|
+
.fx-animate-stagger.fx-visible>*:nth-child(3){opacity:1;transform:none;transition-delay:.25s}
|
|
1918
|
+
.fx-animate-stagger.fx-visible>*:nth-child(4){opacity:1;transform:none;transition-delay:.35s}
|
|
1919
|
+
.fx-animate-stagger.fx-visible>*:nth-child(5){opacity:1;transform:none;transition-delay:.45s}
|
|
1920
|
+
.fx-animate-stagger.fx-visible>*:nth-child(6){opacity:1;transform:none;transition-delay:.55s}
|
|
1921
|
+
/* ── number counter ── */
|
|
1922
|
+
.fx-count{display:inline-block}
|
|
1923
|
+
/* ── hero badge ── */
|
|
1924
|
+
.fx-hero-badge{display:inline-flex;align-items:center;gap:.5rem;font-size:.7rem;letter-spacing:.12em;text-transform:uppercase;padding:.3rem 1rem;border-radius:999px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04);margin-bottom:1.25rem}
|
|
1925
|
+
.fx-hero-badge-dot{width:6px;height:6px;border-radius:50%;background:var(--accent,#ff5722);animation:fx-blink 2s ease infinite}
|
|
1926
|
+
@keyframes fx-blink{0%,100%{opacity:1}50%{opacity:.2}}
|
|
1927
|
+
.fx-year{font-variant-numeric:tabular-nums}
|
|
1749
1928
|
.fx-hero-minimal{min-height:50vh!important}
|
|
1750
1929
|
.fx-hero-minimal .fx-hero-inner{gap:1rem}
|
|
1751
1930
|
.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.7',
|
|
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,
|