@olonjs/cli 3.0.83 → 3.0.85
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.
|
@@ -596,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
596
596
|
"@tiptap/extension-link": "^2.11.5",
|
|
597
597
|
"@tiptap/react": "^2.11.5",
|
|
598
598
|
"@tiptap/starter-kit": "^2.11.5",
|
|
599
|
-
"@olonjs/core": "^1.0.
|
|
599
|
+
"@olonjs/core": "^1.0.73",
|
|
600
600
|
"clsx": "^2.1.1",
|
|
601
601
|
"lucide-react": "^0.474.0",
|
|
602
602
|
"react": "^19.0.0",
|
|
@@ -1780,102 +1780,6 @@ function App() {
|
|
|
1780
1780
|
export default App;
|
|
1781
1781
|
|
|
1782
1782
|
|
|
1783
|
-
END_OF_FILE_CONTENT
|
|
1784
|
-
# SKIP: src/App.tsx:Zone.Identifier is binary and cannot be embedded as text.
|
|
1785
|
-
echo "Creating src/App_.tsx..."
|
|
1786
|
-
cat << 'END_OF_FILE_CONTENT' > "src/App_.tsx"
|
|
1787
|
-
import { useState, useEffect } from 'react';
|
|
1788
|
-
import { JsonPagesEngine } from '@jsonpages/core';
|
|
1789
|
-
import type { LibraryImageEntry } from '@jsonpages/core';
|
|
1790
|
-
import { ComponentRegistry } from '@/lib/ComponentRegistry';
|
|
1791
|
-
import { SECTION_SCHEMAS } from '@/lib/schemas';
|
|
1792
|
-
import { addSectionConfig } from '@/lib/addSectionConfig';
|
|
1793
|
-
import { getHydratedData } from '@/lib/draftStorage';
|
|
1794
|
-
import type { JsonPagesConfig, ProjectState } from '@jsonpages/core';
|
|
1795
|
-
import type { SiteConfig, ThemeConfig, MenuConfig } from '@/types';
|
|
1796
|
-
|
|
1797
|
-
import siteData from '@/data/config/site.json';
|
|
1798
|
-
import themeData from '@/data/config/theme.json';
|
|
1799
|
-
import menuData from '@/data/config/menu.json';
|
|
1800
|
-
import { getFilePages } from '@/lib/getFilePages';
|
|
1801
|
-
|
|
1802
|
-
import fontsCss from './fonts.css?inline';
|
|
1803
|
-
import tenantCss from './index.css?inline';
|
|
1804
|
-
|
|
1805
|
-
const themeConfig = themeData as unknown as ThemeConfig;
|
|
1806
|
-
const menuConfig = menuData as unknown as MenuConfig;
|
|
1807
|
-
const TENANT_ID = 'alpha';
|
|
1808
|
-
|
|
1809
|
-
const filePages = getFilePages();
|
|
1810
|
-
const fileSiteConfig = siteData as unknown as SiteConfig;
|
|
1811
|
-
const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024;
|
|
1812
|
-
|
|
1813
|
-
function getInitialData() {
|
|
1814
|
-
return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
function App() {
|
|
1818
|
-
const [{ pages, siteConfig }] = useState(getInitialData);
|
|
1819
|
-
const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
|
|
1820
|
-
|
|
1821
|
-
useEffect(() => {
|
|
1822
|
-
fetch('/api/list-assets')
|
|
1823
|
-
.then((r) => (r.ok ? r.json() : []))
|
|
1824
|
-
.then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
|
|
1825
|
-
.catch(() => setAssetsManifest([]));
|
|
1826
|
-
}, []);
|
|
1827
|
-
|
|
1828
|
-
const config: JsonPagesConfig = {
|
|
1829
|
-
tenantId: TENANT_ID,
|
|
1830
|
-
registry: ComponentRegistry as JsonPagesConfig['registry'],
|
|
1831
|
-
schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
|
|
1832
|
-
pages,
|
|
1833
|
-
siteConfig,
|
|
1834
|
-
themeConfig,
|
|
1835
|
-
menuConfig,
|
|
1836
|
-
themeCss: { tenant: fontsCss + '\n' + tenantCss },
|
|
1837
|
-
addSection: addSectionConfig,
|
|
1838
|
-
persistence: {
|
|
1839
|
-
async saveToFile(state: ProjectState, slug: string): Promise<void> {
|
|
1840
|
-
const res = await fetch('/api/save-to-file', {
|
|
1841
|
-
method: 'POST',
|
|
1842
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1843
|
-
body: JSON.stringify({ projectState: state, slug }),
|
|
1844
|
-
});
|
|
1845
|
-
const body = (await res.json().catch(() => ({}))) as { error?: string };
|
|
1846
|
-
if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
|
|
1847
|
-
},
|
|
1848
|
-
},
|
|
1849
|
-
assets: {
|
|
1850
|
-
assetsBaseUrl: '/assets',
|
|
1851
|
-
assetsManifest,
|
|
1852
|
-
async onAssetUpload(file: File): Promise<string> {
|
|
1853
|
-
if (!file.type.startsWith('image/')) throw new Error('Invalid file type.');
|
|
1854
|
-
if (file.size > MAX_UPLOAD_SIZE_BYTES) throw new Error(`File too large. Max ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
|
|
1855
|
-
const base64 = await new Promise<string>((resolve, reject) => {
|
|
1856
|
-
const reader = new FileReader();
|
|
1857
|
-
reader.onload = () => resolve((reader.result as string).split(',')[1] ?? '');
|
|
1858
|
-
reader.onerror = () => reject(reader.error);
|
|
1859
|
-
reader.readAsDataURL(file);
|
|
1860
|
-
});
|
|
1861
|
-
const res = await fetch('/api/upload-asset', {
|
|
1862
|
-
method: 'POST',
|
|
1863
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1864
|
-
body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
|
|
1865
|
-
});
|
|
1866
|
-
const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
|
|
1867
|
-
if (!res.ok) throw new Error(body.error || `Upload failed: ${res.status}`);
|
|
1868
|
-
if (typeof body.url !== 'string') throw new Error('Invalid server response: missing url');
|
|
1869
|
-
return body.url;
|
|
1870
|
-
},
|
|
1871
|
-
},
|
|
1872
|
-
};
|
|
1873
|
-
|
|
1874
|
-
return <JsonPagesEngine config={config} />;
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
export default App;
|
|
1878
|
-
|
|
1879
1783
|
END_OF_FILE_CONTENT
|
|
1880
1784
|
mkdir -p "src/components"
|
|
1881
1785
|
echo "Creating src/components/NotFound.tsx..."
|
|
@@ -3689,6 +3593,9 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3689
3593
|
'--local-cyan': 'var(--color-secondary, #22d3ee)',
|
|
3690
3594
|
'--local-border': 'var(--border)',
|
|
3691
3595
|
'--local-surface': 'var(--card)',
|
|
3596
|
+
'--local-radius-sm': 'var(--theme-radius-sm)',
|
|
3597
|
+
'--local-radius-md': 'var(--theme-radius-md)',
|
|
3598
|
+
'--local-radius-lg': 'var(--theme-radius-lg)',
|
|
3692
3599
|
} as React.CSSProperties}
|
|
3693
3600
|
className="jp-hero relative min-h-screen flex items-center overflow-hidden pt-24 pb-0 bg-[var(--local-bg)]"
|
|
3694
3601
|
>
|
|
@@ -3747,7 +3654,7 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3747
3654
|
data-jp-item-id={cta.id ?? `legacy-${idx}`}
|
|
3748
3655
|
data-jp-item-field="ctas"
|
|
3749
3656
|
className={cn(
|
|
3750
|
-
'inline-flex items-center gap-2 px-7 py-3 rounded-[
|
|
3657
|
+
'inline-flex items-center gap-2 px-7 py-3 rounded-[var(--local-radius-md)] font-semibold text-[0.95rem] transition-all duration-200 no-underline',
|
|
3751
3658
|
cta.variant === 'primary'
|
|
3752
3659
|
? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 shadow-[0_0_24px_rgba(59,130,246,0.25)]'
|
|
3753
3660
|
: 'bg-transparent text-[var(--local-text)] border border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.3)] hover:bg-[rgba(255,255,255,0.04)]'
|
|
@@ -3780,13 +3687,13 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3780
3687
|
</div>
|
|
3781
3688
|
|
|
3782
3689
|
{/* RIGHT — ICE mini-mockup */}
|
|
3783
|
-
<div className="jp-animate-in jp-d2 rounded-[
|
|
3690
|
+
<div className="jp-animate-in jp-d2 rounded-[var(--local-radius-lg)] overflow-hidden border border-[rgba(255,255,255,0.10)] shadow-[0_0_0_1px_rgba(255,255,255,0.04),0_40px_80px_rgba(0,0,0,0.6),0_0_60px_rgba(59,130,246,0.08)]">
|
|
3784
3691
|
{/* Browser bar */}
|
|
3785
3692
|
<div className="bg-[#0f1923] px-3 py-2.5 flex items-center gap-1.5 border-b border-[rgba(255,255,255,0.05)]">
|
|
3786
3693
|
<span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
|
|
3787
3694
|
<span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
|
|
3788
3695
|
<span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
|
|
3789
|
-
<span className="mx-auto font-mono text-[0.60rem] text-[rgba(255,255,255,0.20)] bg-[rgba(255,255,255,0.04)] px-3 py-0.5 rounded">localhost:5173 · Studio</span>
|
|
3696
|
+
<span className="mx-auto font-mono text-[0.60rem] text-[rgba(255,255,255,0.20)] bg-[rgba(255,255,255,0.04)] px-3 py-0.5 rounded-[var(--local-radius-sm)]">localhost:5173 · Studio</span>
|
|
3790
3697
|
</div>
|
|
3791
3698
|
{/* Split: canvas + inspector */}
|
|
3792
3699
|
<div className="grid grid-cols-[1fr_260px] h-[360px] bg-[#060d1b]">
|
|
@@ -3810,8 +3717,8 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3810
3717
|
{data.description?.slice(0, 100)}…
|
|
3811
3718
|
</p>
|
|
3812
3719
|
<div className="flex gap-1.5">
|
|
3813
|
-
<span className="text-[0.58rem] font-semibold bg-[#3b82f6] text-white px-2.5 py-1 rounded">Read the Docs</span>
|
|
3814
|
-
<span className="text-[0.58rem] border border-[rgba(255,255,255,0.15)] text-[#94a3b8] px-2.5 py-1 rounded">View on NPM</span>
|
|
3720
|
+
<span className="text-[0.58rem] font-semibold bg-[#3b82f6] text-white px-2.5 py-1 rounded-[var(--local-radius-sm)]">Read the Docs</span>
|
|
3721
|
+
<span className="text-[0.58rem] border border-[rgba(255,255,255,0.15)] text-[#94a3b8] px-2.5 py-1 rounded-[var(--local-radius-sm)]">View on NPM</span>
|
|
3815
3722
|
</div>
|
|
3816
3723
|
<div className="flex gap-4 mt-3 pt-3 border-t border-[rgba(255,255,255,0.05)]">
|
|
3817
3724
|
{(data.metrics ?? []).map((m, i) => (
|
|
@@ -3858,15 +3765,15 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3858
3765
|
<div className="flex-1 px-3.5 py-3 flex flex-col gap-2.5 overflow-hidden">
|
|
3859
3766
|
<div>
|
|
3860
3767
|
<div className="font-mono text-[0.50rem] uppercase tracking-widest text-[#334155] mb-1">Title</div>
|
|
3861
|
-
<div className="bg-[rgba(59,130,246,0.05)] border border-[rgba(59,130,246,0.45)] rounded px-2 py-1.5 font-mono text-[0.58rem] text-[#e2e8f0] truncate">{data.title}</div>
|
|
3768
|
+
<div className="bg-[rgba(59,130,246,0.05)] border border-[rgba(59,130,246,0.45)] rounded-[var(--local-radius-sm)] px-2 py-1.5 font-mono text-[0.58rem] text-[#e2e8f0] truncate">{data.title}</div>
|
|
3862
3769
|
</div>
|
|
3863
3770
|
<div>
|
|
3864
3771
|
<div className="font-mono text-[0.50rem] uppercase tracking-widest text-[#334155] mb-1">Subtitle</div>
|
|
3865
|
-
<div className="bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] rounded px-2 py-1.5 font-mono text-[0.58rem] text-[#94a3b8] truncate">{data.titleHighlight}</div>
|
|
3772
|
+
<div className="bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] rounded-[var(--local-radius-sm)] px-2 py-1.5 font-mono text-[0.58rem] text-[#94a3b8] truncate">{data.titleHighlight}</div>
|
|
3866
3773
|
</div>
|
|
3867
3774
|
<div>
|
|
3868
3775
|
<div className="font-mono text-[0.50rem] uppercase tracking-widest text-[#334155] mb-1">Badge</div>
|
|
3869
|
-
<div className="bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] rounded px-2 py-1.5 font-mono text-[0.58rem] text-[#94a3b8] truncate">{data.badge}</div>
|
|
3776
|
+
<div className="bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] rounded-[var(--local-radius-sm)] px-2 py-1.5 font-mono text-[0.58rem] text-[#94a3b8] truncate">{data.badge}</div>
|
|
3870
3777
|
</div>
|
|
3871
3778
|
</div>
|
|
3872
3779
|
{/* Bottom bar */}
|
|
@@ -3874,8 +3781,8 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3874
3781
|
<span className="w-1.5 h-1.5 rounded-full bg-[#22c55e]" />
|
|
3875
3782
|
<span className="font-mono text-[0.50rem] text-[#475569]">All Changes Saved</span>
|
|
3876
3783
|
<div className="ml-auto flex gap-1.5">
|
|
3877
|
-
<span className="font-mono text-[0.48rem] px-1.5 py-0.5 rounded border border-[rgba(59,130,246,0.3)] bg-[rgba(59,130,246,0.12)] text-[#60a5fa]">⬡ HTML</span>
|
|
3878
|
-
<span className="font-mono text-[0.48rem] px-1.5 py-0.5 rounded border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.03)] text-[#94a3b8] opacity-50">{ } JSON</span>
|
|
3784
|
+
<span className="font-mono text-[0.48rem] px-1.5 py-0.5 rounded-[var(--local-radius-sm)] border border-[rgba(59,130,246,0.3)] bg-[rgba(59,130,246,0.12)] text-[#60a5fa]">⬡ HTML</span>
|
|
3785
|
+
<span className="font-mono text-[0.48rem] px-1.5 py-0.5 rounded-[var(--local-radius-sm)] border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.03)] text-[#94a3b8] opacity-50">{ } JSON</span>
|
|
3879
3786
|
</div>
|
|
3880
3787
|
</div>
|
|
3881
3788
|
</div>
|
|
@@ -7404,13 +7311,14 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/theme.json"
|
|
|
7404
7311
|
"fontFamily": {
|
|
7405
7312
|
"primary": "'Instrument Sans', system-ui, sans-serif",
|
|
7406
7313
|
"mono": "'JetBrains Mono', monospace",
|
|
7407
|
-
"display": "'Bricolage Grotesque', system-ui, sans-serif"
|
|
7314
|
+
"display": "'Bricolage Grotesque', system-ui, sans-serif",
|
|
7315
|
+
"display-2":"'Instrument Sans'"
|
|
7408
7316
|
}
|
|
7409
7317
|
},
|
|
7410
7318
|
"borderRadius": {
|
|
7411
|
-
"sm": "
|
|
7412
|
-
"md": "
|
|
7413
|
-
"lg": "
|
|
7319
|
+
"sm": "4px",
|
|
7320
|
+
"md": "8px",
|
|
7321
|
+
"lg": "36px"
|
|
7414
7322
|
}
|
|
7415
7323
|
}
|
|
7416
7324
|
}
|
|
@@ -8028,72 +7936,6 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/post.json"
|
|
|
8028
7936
|
]
|
|
8029
7937
|
}
|
|
8030
7938
|
END_OF_FILE_CONTENT
|
|
8031
|
-
mkdir -p "src/data/pages/servizi"
|
|
8032
|
-
echo "Creating src/data/pages/servizi/trattamento.json..."
|
|
8033
|
-
cat << 'END_OF_FILE_CONTENT' > "src/data/pages/servizi/trattamento.json"
|
|
8034
|
-
{
|
|
8035
|
-
"id": "servizi-trattamento-page",
|
|
8036
|
-
"slug": "servizi/trattamento",
|
|
8037
|
-
"meta": {
|
|
8038
|
-
"title": "Servizi - Trattamento",
|
|
8039
|
-
"description": "Pagina nested di smoke test per verificare routing visitor/admin/preview."
|
|
8040
|
-
},
|
|
8041
|
-
"sections": [
|
|
8042
|
-
{
|
|
8043
|
-
"id": "hero-servizi-trattamento",
|
|
8044
|
-
"type": "hero",
|
|
8045
|
-
"data": {
|
|
8046
|
-
"badge": "Smoke Test",
|
|
8047
|
-
"title": "Trattamento",
|
|
8048
|
-
"titleHighlight": "Pagina Nested",
|
|
8049
|
-
"description": "Questa pagina verifica il supporto ai nested slug su filesystem e router.",
|
|
8050
|
-
"ctas": [
|
|
8051
|
-
{
|
|
8052
|
-
"id": "cta-home",
|
|
8053
|
-
"label": "Torna Home",
|
|
8054
|
-
"href": "/",
|
|
8055
|
-
"variant": "primary"
|
|
8056
|
-
}
|
|
8057
|
-
]
|
|
8058
|
-
},
|
|
8059
|
-
"settings": {}
|
|
8060
|
-
}
|
|
8061
|
-
]
|
|
8062
|
-
}
|
|
8063
|
-
|
|
8064
|
-
END_OF_FILE_CONTENT
|
|
8065
|
-
echo "Creating src/data/pages/servizi_trattamento.json..."
|
|
8066
|
-
cat << 'END_OF_FILE_CONTENT' > "src/data/pages/servizi_trattamento.json"
|
|
8067
|
-
{
|
|
8068
|
-
"id": "servizi-trattamento-page",
|
|
8069
|
-
"slug": "servizi/trattamento",
|
|
8070
|
-
"meta": {
|
|
8071
|
-
"title": "Servizi - Trattamento",
|
|
8072
|
-
"description": "Pagina nested di smoke test per verificare routing visitor/admin/preview."
|
|
8073
|
-
},
|
|
8074
|
-
"sections": [
|
|
8075
|
-
{
|
|
8076
|
-
"id": "hero-servizi-trattamento",
|
|
8077
|
-
"type": "hero",
|
|
8078
|
-
"data": {
|
|
8079
|
-
"badge": "Smoke Test",
|
|
8080
|
-
"title": "Trattamentos",
|
|
8081
|
-
"titleHighlight": "Pagina Nested",
|
|
8082
|
-
"description": "Questa pagina verifica il supporto ai nested slug su filesystem e router.",
|
|
8083
|
-
"ctas": [
|
|
8084
|
-
{
|
|
8085
|
-
"id": "cta-home",
|
|
8086
|
-
"label": "Torna Home",
|
|
8087
|
-
"href": "/",
|
|
8088
|
-
"variant": "primary"
|
|
8089
|
-
}
|
|
8090
|
-
]
|
|
8091
|
-
},
|
|
8092
|
-
"settings": {}
|
|
8093
|
-
}
|
|
8094
|
-
]
|
|
8095
|
-
}
|
|
8096
|
-
END_OF_FILE_CONTENT
|
|
8097
7939
|
mkdir -p "src/emails"
|
|
8098
7940
|
echo "Creating src/emails/LeadNotificationEmail.tsx..."
|
|
8099
7941
|
cat << 'END_OF_FILE_CONTENT' > "src/emails/LeadNotificationEmail.tsx"
|
|
@@ -8407,7 +8249,7 @@ export default LeadSenderConfirmationEmail;
|
|
|
8407
8249
|
END_OF_FILE_CONTENT
|
|
8408
8250
|
echo "Creating src/fonts.css..."
|
|
8409
8251
|
cat << 'END_OF_FILE_CONTENT' > "src/fonts.css"
|
|
8410
|
-
@import url('https://fonts.googleapis.com/css2?family=
|
|
8252
|
+
@import url('https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Playfair+Display:wght@700;800;900&display=swap');
|
|
8411
8253
|
|
|
8412
8254
|
END_OF_FILE_CONTENT
|
|
8413
8255
|
mkdir -p "src/hooks"
|
|
@@ -8472,14 +8314,13 @@ cat << 'END_OF_FILE_CONTENT' > "src/index.css"
|
|
|
8472
8314
|
--font-primary: var(--theme-font-primary);
|
|
8473
8315
|
--font-mono: var(--theme-font-mono);
|
|
8474
8316
|
|
|
8475
|
-
/*
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
automatically and the fallback becomes dead code.
|
|
8317
|
+
/*
|
|
8318
|
+
DISPLAY FONT bridge
|
|
8319
|
+
The core now emits --theme-font-display from theme.json, so this keeps
|
|
8320
|
+
the tenant on the stable semantic alias rather than depending on the
|
|
8321
|
+
flattened internal variable path.
|
|
8481
8322
|
*/
|
|
8482
|
-
--font-display: var(--theme-font-display
|
|
8323
|
+
--font-display: var(--theme-font-display);
|
|
8483
8324
|
}
|
|
8484
8325
|
|
|
8485
8326
|
/*
|
|
@@ -8497,7 +8338,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/index.css"
|
|
|
8497
8338
|
--muted: var(--theme-surface-alt);
|
|
8498
8339
|
--muted-foreground: var(--theme-text-muted);
|
|
8499
8340
|
--border: var(--theme-border);
|
|
8500
|
-
--radius:
|
|
8341
|
+
--radius: 3.45rem;
|
|
8501
8342
|
|
|
8502
8343
|
/*
|
|
8503
8344
|
🔧 ACCENT CHAIN — Forward-compatible workaround
|
|
@@ -9394,24 +9235,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
9394
9235
|
|
|
9395
9236
|
|
|
9396
9237
|
|
|
9397
|
-
END_OF_FILE_CONTENT
|
|
9398
|
-
echo "Creating src/main_.tsx..."
|
|
9399
|
-
cat << 'END_OF_FILE_CONTENT' > "src/main_.tsx"
|
|
9400
|
-
import '@/types'; // TBP: load type augmentation from capsule-driven types
|
|
9401
|
-
import React from 'react';
|
|
9402
|
-
import ReactDOM from 'react-dom/client';
|
|
9403
|
-
import App from './App';
|
|
9404
|
-
// ... resto del file
|
|
9405
|
-
|
|
9406
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
9407
|
-
<React.StrictMode>
|
|
9408
|
-
<App />
|
|
9409
|
-
</React.StrictMode>
|
|
9410
|
-
);
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
9238
|
END_OF_FILE_CONTENT
|
|
9416
9239
|
# SKIP: src/registry-types.ts is binary and cannot be embedded as text.
|
|
9417
9240
|
mkdir -p "src/server"
|
|
@@ -596,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
596
596
|
"@tiptap/extension-link": "^2.11.5",
|
|
597
597
|
"@tiptap/react": "^2.11.5",
|
|
598
598
|
"@tiptap/starter-kit": "^2.11.5",
|
|
599
|
-
"@olonjs/core": "^1.0.
|
|
599
|
+
"@olonjs/core": "^1.0.73",
|
|
600
600
|
"clsx": "^2.1.1",
|
|
601
601
|
"lucide-react": "^0.474.0",
|
|
602
602
|
"react": "^19.0.0",
|
|
@@ -1144,264 +1144,6 @@ function App() {
|
|
|
1144
1144
|
|
|
1145
1145
|
export default App;
|
|
1146
1146
|
|
|
1147
|
-
END_OF_FILE_CONTENT
|
|
1148
|
-
echo "Creating src/App_.tsx..."
|
|
1149
|
-
cat << 'END_OF_FILE_CONTENT' > "src/App_.tsx"
|
|
1150
|
-
import { useState, useEffect } from 'react';
|
|
1151
|
-
import { JsonPagesEngine } from '@jsonpages/core';
|
|
1152
|
-
import type { LibraryImageEntry } from '@jsonpages/core';
|
|
1153
|
-
import { ComponentRegistry } from '@/lib/ComponentRegistry';
|
|
1154
|
-
import { SECTION_SCHEMAS } from '@/lib/schemas';
|
|
1155
|
-
import { addSectionConfig } from '@/lib/addSectionConfig';
|
|
1156
|
-
import { getHydratedData } from '@/lib/draftStorage';
|
|
1157
|
-
import type { JsonPagesConfig, ProjectState } from '@jsonpages/core';
|
|
1158
|
-
import type { SiteConfig, ThemeConfig, MenuConfig } from '@/types';
|
|
1159
|
-
|
|
1160
|
-
import siteData from '@/data/config/site.json';
|
|
1161
|
-
import themeData from '@/data/config/theme.json';
|
|
1162
|
-
import menuData from '@/data/config/menu.json';
|
|
1163
|
-
import { getFilePages } from '@/lib/getFilePages';
|
|
1164
|
-
|
|
1165
|
-
import fontsCss from './fonts.css?inline';
|
|
1166
|
-
import tenantCss from './index.css?inline';
|
|
1167
|
-
|
|
1168
|
-
const themeConfig = themeData as unknown as ThemeConfig;
|
|
1169
|
-
const menuConfig = menuData as unknown as MenuConfig;
|
|
1170
|
-
const TENANT_ID = 'alpha';
|
|
1171
|
-
|
|
1172
|
-
const filePages = getFilePages();
|
|
1173
|
-
const fileSiteConfig = siteData as unknown as SiteConfig;
|
|
1174
|
-
const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024;
|
|
1175
|
-
|
|
1176
|
-
function getInitialData() {
|
|
1177
|
-
return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
function App() {
|
|
1181
|
-
const [{ pages, siteConfig }] = useState(getInitialData);
|
|
1182
|
-
const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
|
|
1183
|
-
|
|
1184
|
-
useEffect(() => {
|
|
1185
|
-
fetch('/api/list-assets')
|
|
1186
|
-
.then((r) => (r.ok ? r.json() : []))
|
|
1187
|
-
.then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
|
|
1188
|
-
.catch(() => setAssetsManifest([]));
|
|
1189
|
-
}, []);
|
|
1190
|
-
|
|
1191
|
-
const config: JsonPagesConfig = {
|
|
1192
|
-
tenantId: TENANT_ID,
|
|
1193
|
-
registry: ComponentRegistry as JsonPagesConfig['registry'],
|
|
1194
|
-
schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
|
|
1195
|
-
pages,
|
|
1196
|
-
siteConfig,
|
|
1197
|
-
themeConfig,
|
|
1198
|
-
menuConfig,
|
|
1199
|
-
themeCss: { tenant: fontsCss + '\n' + tenantCss },
|
|
1200
|
-
addSection: addSectionConfig,
|
|
1201
|
-
persistence: {
|
|
1202
|
-
async saveToFile(state: ProjectState, slug: string): Promise<void> {
|
|
1203
|
-
const res = await fetch('/api/save-to-file', {
|
|
1204
|
-
method: 'POST',
|
|
1205
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1206
|
-
body: JSON.stringify({ projectState: state, slug }),
|
|
1207
|
-
});
|
|
1208
|
-
const body = (await res.json().catch(() => ({}))) as { error?: string };
|
|
1209
|
-
if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
|
|
1210
|
-
},
|
|
1211
|
-
},
|
|
1212
|
-
assets: {
|
|
1213
|
-
assetsBaseUrl: '/assets',
|
|
1214
|
-
assetsManifest,
|
|
1215
|
-
async onAssetUpload(file: File): Promise<string> {
|
|
1216
|
-
if (!file.type.startsWith('image/')) throw new Error('Invalid file type.');
|
|
1217
|
-
if (file.size > MAX_UPLOAD_SIZE_BYTES) throw new Error(`File too large. Max ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
|
|
1218
|
-
const base64 = await new Promise<string>((resolve, reject) => {
|
|
1219
|
-
const reader = new FileReader();
|
|
1220
|
-
reader.onload = () => resolve((reader.result as string).split(',')[1] ?? '');
|
|
1221
|
-
reader.onerror = () => reject(reader.error);
|
|
1222
|
-
reader.readAsDataURL(file);
|
|
1223
|
-
});
|
|
1224
|
-
const res = await fetch('/api/upload-asset', {
|
|
1225
|
-
method: 'POST',
|
|
1226
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1227
|
-
body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
|
|
1228
|
-
});
|
|
1229
|
-
const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
|
|
1230
|
-
if (!res.ok) throw new Error(body.error || `Upload failed: ${res.status}`);
|
|
1231
|
-
if (typeof body.url !== 'string') throw new Error('Invalid server response: missing url');
|
|
1232
|
-
return body.url;
|
|
1233
|
-
},
|
|
1234
|
-
},
|
|
1235
|
-
};
|
|
1236
|
-
|
|
1237
|
-
return <JsonPagesEngine config={config} />;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
export default App;
|
|
1241
|
-
|
|
1242
|
-
END_OF_FILE_CONTENT
|
|
1243
|
-
echo "Creating src/_App.tsx..."
|
|
1244
|
-
cat << 'END_OF_FILE_CONTENT' > "src/_App.tsx"
|
|
1245
|
-
/**
|
|
1246
|
-
* Thin Entry Point (Tenant).
|
|
1247
|
-
* Data from getHydratedData (file-backed or draft); assets from public/assets/images.
|
|
1248
|
-
* Supports Hybrid Persistence: Local Filesystem (Dev) or Cloud Bridge (Prod).
|
|
1249
|
-
*/
|
|
1250
|
-
import { useState, useEffect } from 'react';
|
|
1251
|
-
import { JsonPagesEngine } from '@jsonpages/core';
|
|
1252
|
-
import type { LibraryImageEntry } from '@jsonpages/core';
|
|
1253
|
-
import { ComponentRegistry } from '@/lib/ComponentRegistry';
|
|
1254
|
-
import { SECTION_SCHEMAS } from '@/lib/schemas';
|
|
1255
|
-
import { addSectionConfig } from '@/lib/addSectionConfig';
|
|
1256
|
-
import { getHydratedData } from '@/lib/draftStorage';
|
|
1257
|
-
import type { JsonPagesConfig, ProjectState } from '@jsonpages/core';
|
|
1258
|
-
import type { SiteConfig, ThemeConfig, MenuConfig } from '@/types';
|
|
1259
|
-
|
|
1260
|
-
import siteData from '@/data/config/site.json';
|
|
1261
|
-
import themeData from '@/data/config/theme.json';
|
|
1262
|
-
import menuData from '@/data/config/menu.json';
|
|
1263
|
-
import { getFilePages } from '@/lib/getFilePages';
|
|
1264
|
-
|
|
1265
|
-
import fontsCss from './fonts.css?inline';
|
|
1266
|
-
import tenantCss from './index.css?inline';
|
|
1267
|
-
|
|
1268
|
-
// Cloud Configuration (Injected by Vercel/Netlify Env Vars)
|
|
1269
|
-
const CLOUD_API_URL = import.meta.env.VITE_JSONPAGES_CLOUD_URL;
|
|
1270
|
-
const CLOUD_API_KEY = import.meta.env.VITE_JSONPAGES_API_KEY;
|
|
1271
|
-
|
|
1272
|
-
const themeConfig = themeData as unknown as ThemeConfig;
|
|
1273
|
-
const menuConfig = menuData as unknown as MenuConfig;
|
|
1274
|
-
const TENANT_ID = 'alpha';
|
|
1275
|
-
|
|
1276
|
-
const filePages = getFilePages();
|
|
1277
|
-
const fileSiteConfig = siteData as unknown as SiteConfig;
|
|
1278
|
-
const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024;
|
|
1279
|
-
|
|
1280
|
-
function getInitialData() {
|
|
1281
|
-
return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
function App() {
|
|
1285
|
-
const [{ pages, siteConfig }] = useState(getInitialData);
|
|
1286
|
-
const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
|
|
1287
|
-
|
|
1288
|
-
useEffect(() => {
|
|
1289
|
-
// In Cloud mode, listing assets might be different or disabled for MVP
|
|
1290
|
-
// For now, we keep the local fetch which will fail gracefully on Vercel (404)
|
|
1291
|
-
fetch('/api/list-assets')
|
|
1292
|
-
.then((r) => (r.ok ? r.json() : []))
|
|
1293
|
-
.then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
|
|
1294
|
-
.catch(() => setAssetsManifest([]));
|
|
1295
|
-
}, []);
|
|
1296
|
-
|
|
1297
|
-
console.log("🔍 DEBUG ENV:", {
|
|
1298
|
-
URL: import.meta.env.VITE_JSONPAGES_CLOUD_URL,
|
|
1299
|
-
KEY: import.meta.env.VITE_JSONPAGES_API_KEY ? "PRESENT" : "MISSING"
|
|
1300
|
-
});
|
|
1301
|
-
const config: JsonPagesConfig = {
|
|
1302
|
-
tenantId: TENANT_ID,
|
|
1303
|
-
registry: ComponentRegistry as JsonPagesConfig['registry'],
|
|
1304
|
-
schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
|
|
1305
|
-
pages,
|
|
1306
|
-
siteConfig,
|
|
1307
|
-
themeConfig,
|
|
1308
|
-
menuConfig,
|
|
1309
|
-
themeCss: { tenant: fontsCss + '\n' + tenantCss },
|
|
1310
|
-
addSection: addSectionConfig,
|
|
1311
|
-
persistence: {
|
|
1312
|
-
async saveToFile(state: ProjectState, slug: string): Promise<void> {
|
|
1313
|
-
|
|
1314
|
-
// ☁️ SCENARIO A: CLOUD BRIDGE (Production)
|
|
1315
|
-
if (CLOUD_API_URL && CLOUD_API_KEY) {
|
|
1316
|
-
console.log(`☁️ Saving ${slug} via Cloud Bridge...`);
|
|
1317
|
-
|
|
1318
|
-
const res = await fetch(`${CLOUD_API_URL}/save`, {
|
|
1319
|
-
method: 'POST',
|
|
1320
|
-
headers: {
|
|
1321
|
-
'Content-Type': 'application/json',
|
|
1322
|
-
'Authorization': `Bearer ${CLOUD_API_KEY}`
|
|
1323
|
-
},
|
|
1324
|
-
body: JSON.stringify({
|
|
1325
|
-
// Mapping logical slug to physical path in repo
|
|
1326
|
-
path: `src/data/pages/${slug}.json`,
|
|
1327
|
-
// We save the page config specifically
|
|
1328
|
-
content: state.page,
|
|
1329
|
-
message: `Content update for ${slug} via Visual Editor`
|
|
1330
|
-
}),
|
|
1331
|
-
});
|
|
1332
|
-
|
|
1333
|
-
if (!res.ok) {
|
|
1334
|
-
const err = await res.json().catch(() => ({}));
|
|
1335
|
-
throw new Error(err.error || `Cloud save failed: ${res.status}`);
|
|
1336
|
-
}
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
// 💻 SCENARIO B: LOCAL FILESYSTEM (Development)
|
|
1341
|
-
console.log(`💻 Saving ${slug} to Local Filesystem...`);
|
|
1342
|
-
const res = await fetch('/api/save-to-file', {
|
|
1343
|
-
method: 'POST',
|
|
1344
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1345
|
-
body: JSON.stringify({ projectState: state, slug }),
|
|
1346
|
-
});
|
|
1347
|
-
|
|
1348
|
-
const body = (await res.json().catch(() => ({}))) as { error?: string };
|
|
1349
|
-
if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
|
|
1350
|
-
},
|
|
1351
|
-
},
|
|
1352
|
-
assets: {
|
|
1353
|
-
assetsBaseUrl: '/assets',
|
|
1354
|
-
assetsManifest,
|
|
1355
|
-
async onAssetUpload(file: File): Promise<string> {
|
|
1356
|
-
// Note: Asset upload in Cloud Mode requires the R2 Bridge (Next Step in Roadmap)
|
|
1357
|
-
// For now, this works in Local Mode.
|
|
1358
|
-
if (!file.type.startsWith('image/')) throw new Error('Invalid file type.');
|
|
1359
|
-
if (file.size > MAX_UPLOAD_SIZE_BYTES) throw new Error(`File too large. Max ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
|
|
1360
|
-
|
|
1361
|
-
const base64 = await new Promise<string>((resolve, reject) => {
|
|
1362
|
-
const reader = new FileReader();
|
|
1363
|
-
reader.onload = () => resolve((reader.result as string).split(',')[1] ?? '');
|
|
1364
|
-
reader.onerror = () => reject(reader.error);
|
|
1365
|
-
reader.readAsDataURL(file);
|
|
1366
|
-
});
|
|
1367
|
-
|
|
1368
|
-
const res = await fetch('/api/upload-asset', {
|
|
1369
|
-
method: 'POST',
|
|
1370
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1371
|
-
body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
|
|
1372
|
-
});
|
|
1373
|
-
|
|
1374
|
-
const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
|
|
1375
|
-
if (!res.ok) throw new Error(body.error || `Upload failed: ${res.status}`);
|
|
1376
|
-
if (typeof body.url !== 'string') throw new Error('Invalid server response: missing url');
|
|
1377
|
-
return body.url;
|
|
1378
|
-
},
|
|
1379
|
-
},
|
|
1380
|
-
};
|
|
1381
|
-
|
|
1382
|
-
return <JsonPagesEngine config={config} />;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
export default App;
|
|
1386
|
-
|
|
1387
|
-
END_OF_FILE_CONTENT
|
|
1388
|
-
echo "Creating src/_main.tsx..."
|
|
1389
|
-
cat << 'END_OF_FILE_CONTENT' > "src/_main.tsx"
|
|
1390
|
-
import '@/types'; // TBP: load type augmentation from capsule-driven types
|
|
1391
|
-
import React from 'react';
|
|
1392
|
-
import ReactDOM from 'react-dom/client';
|
|
1393
|
-
import App from './App';
|
|
1394
|
-
// ... resto del file
|
|
1395
|
-
|
|
1396
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
1397
|
-
<React.StrictMode>
|
|
1398
|
-
<App />
|
|
1399
|
-
</React.StrictMode>
|
|
1400
|
-
);
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
1147
|
END_OF_FILE_CONTENT
|
|
1406
1148
|
mkdir -p "src/components"
|
|
1407
1149
|
echo "Creating src/components/NotFound.tsx..."
|
|
@@ -10933,24 +10675,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
10933
10675
|
|
|
10934
10676
|
|
|
10935
10677
|
|
|
10936
|
-
END_OF_FILE_CONTENT
|
|
10937
|
-
echo "Creating src/main_.tsx..."
|
|
10938
|
-
cat << 'END_OF_FILE_CONTENT' > "src/main_.tsx"
|
|
10939
|
-
import '@/types'; // TBP: load type augmentation from capsule-driven types
|
|
10940
|
-
import React from 'react';
|
|
10941
|
-
import ReactDOM from 'react-dom/client';
|
|
10942
|
-
import App from './App';
|
|
10943
|
-
// ... resto del file
|
|
10944
|
-
|
|
10945
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
10946
|
-
<React.StrictMode>
|
|
10947
|
-
<App />
|
|
10948
|
-
</React.StrictMode>
|
|
10949
|
-
);
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
10678
|
END_OF_FILE_CONTENT
|
|
10955
10679
|
# SKIP: src/registry-types.ts is binary and cannot be embedded as text.
|
|
10956
10680
|
mkdir -p "src/server"
|
|
@@ -596,7 +596,7 @@ cat << 'END_OF_FILE_CONTENT' > "package.json"
|
|
|
596
596
|
"@tiptap/extension-link": "^2.11.5",
|
|
597
597
|
"@tiptap/react": "^2.11.5",
|
|
598
598
|
"@tiptap/starter-kit": "^2.11.5",
|
|
599
|
-
"@olonjs/core": "^1.0.
|
|
599
|
+
"@olonjs/core": "^1.0.73",
|
|
600
600
|
"clsx": "^2.1.1",
|
|
601
601
|
"lucide-react": "^0.474.0",
|
|
602
602
|
"react": "^19.0.0",
|
|
@@ -1780,102 +1780,6 @@ function App() {
|
|
|
1780
1780
|
export default App;
|
|
1781
1781
|
|
|
1782
1782
|
|
|
1783
|
-
END_OF_FILE_CONTENT
|
|
1784
|
-
# SKIP: src/App.tsx:Zone.Identifier is binary and cannot be embedded as text.
|
|
1785
|
-
echo "Creating src/App_.tsx..."
|
|
1786
|
-
cat << 'END_OF_FILE_CONTENT' > "src/App_.tsx"
|
|
1787
|
-
import { useState, useEffect } from 'react';
|
|
1788
|
-
import { JsonPagesEngine } from '@jsonpages/core';
|
|
1789
|
-
import type { LibraryImageEntry } from '@jsonpages/core';
|
|
1790
|
-
import { ComponentRegistry } from '@/lib/ComponentRegistry';
|
|
1791
|
-
import { SECTION_SCHEMAS } from '@/lib/schemas';
|
|
1792
|
-
import { addSectionConfig } from '@/lib/addSectionConfig';
|
|
1793
|
-
import { getHydratedData } from '@/lib/draftStorage';
|
|
1794
|
-
import type { JsonPagesConfig, ProjectState } from '@jsonpages/core';
|
|
1795
|
-
import type { SiteConfig, ThemeConfig, MenuConfig } from '@/types';
|
|
1796
|
-
|
|
1797
|
-
import siteData from '@/data/config/site.json';
|
|
1798
|
-
import themeData from '@/data/config/theme.json';
|
|
1799
|
-
import menuData from '@/data/config/menu.json';
|
|
1800
|
-
import { getFilePages } from '@/lib/getFilePages';
|
|
1801
|
-
|
|
1802
|
-
import fontsCss from './fonts.css?inline';
|
|
1803
|
-
import tenantCss from './index.css?inline';
|
|
1804
|
-
|
|
1805
|
-
const themeConfig = themeData as unknown as ThemeConfig;
|
|
1806
|
-
const menuConfig = menuData as unknown as MenuConfig;
|
|
1807
|
-
const TENANT_ID = 'alpha';
|
|
1808
|
-
|
|
1809
|
-
const filePages = getFilePages();
|
|
1810
|
-
const fileSiteConfig = siteData as unknown as SiteConfig;
|
|
1811
|
-
const MAX_UPLOAD_SIZE_BYTES = 5 * 1024 * 1024;
|
|
1812
|
-
|
|
1813
|
-
function getInitialData() {
|
|
1814
|
-
return getHydratedData(TENANT_ID, filePages, fileSiteConfig);
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
function App() {
|
|
1818
|
-
const [{ pages, siteConfig }] = useState(getInitialData);
|
|
1819
|
-
const [assetsManifest, setAssetsManifest] = useState<LibraryImageEntry[]>([]);
|
|
1820
|
-
|
|
1821
|
-
useEffect(() => {
|
|
1822
|
-
fetch('/api/list-assets')
|
|
1823
|
-
.then((r) => (r.ok ? r.json() : []))
|
|
1824
|
-
.then((list: LibraryImageEntry[]) => setAssetsManifest(Array.isArray(list) ? list : []))
|
|
1825
|
-
.catch(() => setAssetsManifest([]));
|
|
1826
|
-
}, []);
|
|
1827
|
-
|
|
1828
|
-
const config: JsonPagesConfig = {
|
|
1829
|
-
tenantId: TENANT_ID,
|
|
1830
|
-
registry: ComponentRegistry as JsonPagesConfig['registry'],
|
|
1831
|
-
schemas: SECTION_SCHEMAS as unknown as JsonPagesConfig['schemas'],
|
|
1832
|
-
pages,
|
|
1833
|
-
siteConfig,
|
|
1834
|
-
themeConfig,
|
|
1835
|
-
menuConfig,
|
|
1836
|
-
themeCss: { tenant: fontsCss + '\n' + tenantCss },
|
|
1837
|
-
addSection: addSectionConfig,
|
|
1838
|
-
persistence: {
|
|
1839
|
-
async saveToFile(state: ProjectState, slug: string): Promise<void> {
|
|
1840
|
-
const res = await fetch('/api/save-to-file', {
|
|
1841
|
-
method: 'POST',
|
|
1842
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1843
|
-
body: JSON.stringify({ projectState: state, slug }),
|
|
1844
|
-
});
|
|
1845
|
-
const body = (await res.json().catch(() => ({}))) as { error?: string };
|
|
1846
|
-
if (!res.ok) throw new Error(body.error ?? `Save to file failed: ${res.status}`);
|
|
1847
|
-
},
|
|
1848
|
-
},
|
|
1849
|
-
assets: {
|
|
1850
|
-
assetsBaseUrl: '/assets',
|
|
1851
|
-
assetsManifest,
|
|
1852
|
-
async onAssetUpload(file: File): Promise<string> {
|
|
1853
|
-
if (!file.type.startsWith('image/')) throw new Error('Invalid file type.');
|
|
1854
|
-
if (file.size > MAX_UPLOAD_SIZE_BYTES) throw new Error(`File too large. Max ${MAX_UPLOAD_SIZE_BYTES / 1024 / 1024}MB.`);
|
|
1855
|
-
const base64 = await new Promise<string>((resolve, reject) => {
|
|
1856
|
-
const reader = new FileReader();
|
|
1857
|
-
reader.onload = () => resolve((reader.result as string).split(',')[1] ?? '');
|
|
1858
|
-
reader.onerror = () => reject(reader.error);
|
|
1859
|
-
reader.readAsDataURL(file);
|
|
1860
|
-
});
|
|
1861
|
-
const res = await fetch('/api/upload-asset', {
|
|
1862
|
-
method: 'POST',
|
|
1863
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1864
|
-
body: JSON.stringify({ filename: file.name, mimeType: file.type || undefined, data: base64 }),
|
|
1865
|
-
});
|
|
1866
|
-
const body = (await res.json().catch(() => ({}))) as { url?: string; error?: string };
|
|
1867
|
-
if (!res.ok) throw new Error(body.error || `Upload failed: ${res.status}`);
|
|
1868
|
-
if (typeof body.url !== 'string') throw new Error('Invalid server response: missing url');
|
|
1869
|
-
return body.url;
|
|
1870
|
-
},
|
|
1871
|
-
},
|
|
1872
|
-
};
|
|
1873
|
-
|
|
1874
|
-
return <JsonPagesEngine config={config} />;
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
export default App;
|
|
1878
|
-
|
|
1879
1783
|
END_OF_FILE_CONTENT
|
|
1880
1784
|
mkdir -p "src/components"
|
|
1881
1785
|
echo "Creating src/components/NotFound.tsx..."
|
|
@@ -3689,6 +3593,9 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3689
3593
|
'--local-cyan': 'var(--color-secondary, #22d3ee)',
|
|
3690
3594
|
'--local-border': 'var(--border)',
|
|
3691
3595
|
'--local-surface': 'var(--card)',
|
|
3596
|
+
'--local-radius-sm': 'var(--theme-radius-sm)',
|
|
3597
|
+
'--local-radius-md': 'var(--theme-radius-md)',
|
|
3598
|
+
'--local-radius-lg': 'var(--theme-radius-lg)',
|
|
3692
3599
|
} as React.CSSProperties}
|
|
3693
3600
|
className="jp-hero relative min-h-screen flex items-center overflow-hidden pt-24 pb-0 bg-[var(--local-bg)]"
|
|
3694
3601
|
>
|
|
@@ -3747,7 +3654,7 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3747
3654
|
data-jp-item-id={cta.id ?? `legacy-${idx}`}
|
|
3748
3655
|
data-jp-item-field="ctas"
|
|
3749
3656
|
className={cn(
|
|
3750
|
-
'inline-flex items-center gap-2 px-7 py-3 rounded-[
|
|
3657
|
+
'inline-flex items-center gap-2 px-7 py-3 rounded-[var(--local-radius-md)] font-semibold text-[0.95rem] transition-all duration-200 no-underline',
|
|
3751
3658
|
cta.variant === 'primary'
|
|
3752
3659
|
? 'bg-[var(--local-primary)] text-white hover:brightness-110 hover:-translate-y-0.5 shadow-[0_0_24px_rgba(59,130,246,0.25)]'
|
|
3753
3660
|
: 'bg-transparent text-[var(--local-text)] border border-[rgba(255,255,255,0.12)] hover:border-[rgba(255,255,255,0.3)] hover:bg-[rgba(255,255,255,0.04)]'
|
|
@@ -3780,13 +3687,13 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3780
3687
|
</div>
|
|
3781
3688
|
|
|
3782
3689
|
{/* RIGHT — ICE mini-mockup */}
|
|
3783
|
-
<div className="jp-animate-in jp-d2 rounded-[
|
|
3690
|
+
<div className="jp-animate-in jp-d2 rounded-[var(--local-radius-lg)] overflow-hidden border border-[rgba(255,255,255,0.10)] shadow-[0_0_0_1px_rgba(255,255,255,0.04),0_40px_80px_rgba(0,0,0,0.6),0_0_60px_rgba(59,130,246,0.08)]">
|
|
3784
3691
|
{/* Browser bar */}
|
|
3785
3692
|
<div className="bg-[#0f1923] px-3 py-2.5 flex items-center gap-1.5 border-b border-[rgba(255,255,255,0.05)]">
|
|
3786
3693
|
<span className="w-2.5 h-2.5 rounded-full bg-[#ef4444]" />
|
|
3787
3694
|
<span className="w-2.5 h-2.5 rounded-full bg-[#f59e0b]" />
|
|
3788
3695
|
<span className="w-2.5 h-2.5 rounded-full bg-[#22c55e]" />
|
|
3789
|
-
<span className="mx-auto font-mono text-[0.60rem] text-[rgba(255,255,255,0.20)] bg-[rgba(255,255,255,0.04)] px-3 py-0.5 rounded">localhost:5173 · Studio</span>
|
|
3696
|
+
<span className="mx-auto font-mono text-[0.60rem] text-[rgba(255,255,255,0.20)] bg-[rgba(255,255,255,0.04)] px-3 py-0.5 rounded-[var(--local-radius-sm)]">localhost:5173 · Studio</span>
|
|
3790
3697
|
</div>
|
|
3791
3698
|
{/* Split: canvas + inspector */}
|
|
3792
3699
|
<div className="grid grid-cols-[1fr_260px] h-[360px] bg-[#060d1b]">
|
|
@@ -3810,8 +3717,8 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3810
3717
|
{data.description?.slice(0, 100)}…
|
|
3811
3718
|
</p>
|
|
3812
3719
|
<div className="flex gap-1.5">
|
|
3813
|
-
<span className="text-[0.58rem] font-semibold bg-[#3b82f6] text-white px-2.5 py-1 rounded">Read the Docs</span>
|
|
3814
|
-
<span className="text-[0.58rem] border border-[rgba(255,255,255,0.15)] text-[#94a3b8] px-2.5 py-1 rounded">View on NPM</span>
|
|
3720
|
+
<span className="text-[0.58rem] font-semibold bg-[#3b82f6] text-white px-2.5 py-1 rounded-[var(--local-radius-sm)]">Read the Docs</span>
|
|
3721
|
+
<span className="text-[0.58rem] border border-[rgba(255,255,255,0.15)] text-[#94a3b8] px-2.5 py-1 rounded-[var(--local-radius-sm)]">View on NPM</span>
|
|
3815
3722
|
</div>
|
|
3816
3723
|
<div className="flex gap-4 mt-3 pt-3 border-t border-[rgba(255,255,255,0.05)]">
|
|
3817
3724
|
{(data.metrics ?? []).map((m, i) => (
|
|
@@ -3858,15 +3765,15 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3858
3765
|
<div className="flex-1 px-3.5 py-3 flex flex-col gap-2.5 overflow-hidden">
|
|
3859
3766
|
<div>
|
|
3860
3767
|
<div className="font-mono text-[0.50rem] uppercase tracking-widest text-[#334155] mb-1">Title</div>
|
|
3861
|
-
<div className="bg-[rgba(59,130,246,0.05)] border border-[rgba(59,130,246,0.45)] rounded px-2 py-1.5 font-mono text-[0.58rem] text-[#e2e8f0] truncate">{data.title}</div>
|
|
3768
|
+
<div className="bg-[rgba(59,130,246,0.05)] border border-[rgba(59,130,246,0.45)] rounded-[var(--local-radius-sm)] px-2 py-1.5 font-mono text-[0.58rem] text-[#e2e8f0] truncate">{data.title}</div>
|
|
3862
3769
|
</div>
|
|
3863
3770
|
<div>
|
|
3864
3771
|
<div className="font-mono text-[0.50rem] uppercase tracking-widest text-[#334155] mb-1">Subtitle</div>
|
|
3865
|
-
<div className="bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] rounded px-2 py-1.5 font-mono text-[0.58rem] text-[#94a3b8] truncate">{data.titleHighlight}</div>
|
|
3772
|
+
<div className="bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] rounded-[var(--local-radius-sm)] px-2 py-1.5 font-mono text-[0.58rem] text-[#94a3b8] truncate">{data.titleHighlight}</div>
|
|
3866
3773
|
</div>
|
|
3867
3774
|
<div>
|
|
3868
3775
|
<div className="font-mono text-[0.50rem] uppercase tracking-widest text-[#334155] mb-1">Badge</div>
|
|
3869
|
-
<div className="bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] rounded px-2 py-1.5 font-mono text-[0.58rem] text-[#94a3b8] truncate">{data.badge}</div>
|
|
3776
|
+
<div className="bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.07)] rounded-[var(--local-radius-sm)] px-2 py-1.5 font-mono text-[0.58rem] text-[#94a3b8] truncate">{data.badge}</div>
|
|
3870
3777
|
</div>
|
|
3871
3778
|
</div>
|
|
3872
3779
|
{/* Bottom bar */}
|
|
@@ -3874,8 +3781,8 @@ export const Hero: React.FC<{ data: HeroData; settings?: HeroSettings }> = ({ da
|
|
|
3874
3781
|
<span className="w-1.5 h-1.5 rounded-full bg-[#22c55e]" />
|
|
3875
3782
|
<span className="font-mono text-[0.50rem] text-[#475569]">All Changes Saved</span>
|
|
3876
3783
|
<div className="ml-auto flex gap-1.5">
|
|
3877
|
-
<span className="font-mono text-[0.48rem] px-1.5 py-0.5 rounded border border-[rgba(59,130,246,0.3)] bg-[rgba(59,130,246,0.12)] text-[#60a5fa]">⬡ HTML</span>
|
|
3878
|
-
<span className="font-mono text-[0.48rem] px-1.5 py-0.5 rounded border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.03)] text-[#94a3b8] opacity-50">{ } JSON</span>
|
|
3784
|
+
<span className="font-mono text-[0.48rem] px-1.5 py-0.5 rounded-[var(--local-radius-sm)] border border-[rgba(59,130,246,0.3)] bg-[rgba(59,130,246,0.12)] text-[#60a5fa]">⬡ HTML</span>
|
|
3785
|
+
<span className="font-mono text-[0.48rem] px-1.5 py-0.5 rounded-[var(--local-radius-sm)] border border-[rgba(255,255,255,0.08)] bg-[rgba(255,255,255,0.03)] text-[#94a3b8] opacity-50">{ } JSON</span>
|
|
3879
3786
|
</div>
|
|
3880
3787
|
</div>
|
|
3881
3788
|
</div>
|
|
@@ -7404,13 +7311,14 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/config/theme.json"
|
|
|
7404
7311
|
"fontFamily": {
|
|
7405
7312
|
"primary": "'Instrument Sans', system-ui, sans-serif",
|
|
7406
7313
|
"mono": "'JetBrains Mono', monospace",
|
|
7407
|
-
"display": "'Bricolage Grotesque', system-ui, sans-serif"
|
|
7314
|
+
"display": "'Bricolage Grotesque', system-ui, sans-serif",
|
|
7315
|
+
"display-2":"'Instrument Sans'"
|
|
7408
7316
|
}
|
|
7409
7317
|
},
|
|
7410
7318
|
"borderRadius": {
|
|
7411
|
-
"sm": "
|
|
7412
|
-
"md": "
|
|
7413
|
-
"lg": "
|
|
7319
|
+
"sm": "4px",
|
|
7320
|
+
"md": "8px",
|
|
7321
|
+
"lg": "36px"
|
|
7414
7322
|
}
|
|
7415
7323
|
}
|
|
7416
7324
|
}
|
|
@@ -8028,72 +7936,6 @@ cat << 'END_OF_FILE_CONTENT' > "src/data/pages/post.json"
|
|
|
8028
7936
|
]
|
|
8029
7937
|
}
|
|
8030
7938
|
END_OF_FILE_CONTENT
|
|
8031
|
-
mkdir -p "src/data/pages/servizi"
|
|
8032
|
-
echo "Creating src/data/pages/servizi/trattamento.json..."
|
|
8033
|
-
cat << 'END_OF_FILE_CONTENT' > "src/data/pages/servizi/trattamento.json"
|
|
8034
|
-
{
|
|
8035
|
-
"id": "servizi-trattamento-page",
|
|
8036
|
-
"slug": "servizi/trattamento",
|
|
8037
|
-
"meta": {
|
|
8038
|
-
"title": "Servizi - Trattamento",
|
|
8039
|
-
"description": "Pagina nested di smoke test per verificare routing visitor/admin/preview."
|
|
8040
|
-
},
|
|
8041
|
-
"sections": [
|
|
8042
|
-
{
|
|
8043
|
-
"id": "hero-servizi-trattamento",
|
|
8044
|
-
"type": "hero",
|
|
8045
|
-
"data": {
|
|
8046
|
-
"badge": "Smoke Test",
|
|
8047
|
-
"title": "Trattamento",
|
|
8048
|
-
"titleHighlight": "Pagina Nested",
|
|
8049
|
-
"description": "Questa pagina verifica il supporto ai nested slug su filesystem e router.",
|
|
8050
|
-
"ctas": [
|
|
8051
|
-
{
|
|
8052
|
-
"id": "cta-home",
|
|
8053
|
-
"label": "Torna Home",
|
|
8054
|
-
"href": "/",
|
|
8055
|
-
"variant": "primary"
|
|
8056
|
-
}
|
|
8057
|
-
]
|
|
8058
|
-
},
|
|
8059
|
-
"settings": {}
|
|
8060
|
-
}
|
|
8061
|
-
]
|
|
8062
|
-
}
|
|
8063
|
-
|
|
8064
|
-
END_OF_FILE_CONTENT
|
|
8065
|
-
echo "Creating src/data/pages/servizi_trattamento.json..."
|
|
8066
|
-
cat << 'END_OF_FILE_CONTENT' > "src/data/pages/servizi_trattamento.json"
|
|
8067
|
-
{
|
|
8068
|
-
"id": "servizi-trattamento-page",
|
|
8069
|
-
"slug": "servizi/trattamento",
|
|
8070
|
-
"meta": {
|
|
8071
|
-
"title": "Servizi - Trattamento",
|
|
8072
|
-
"description": "Pagina nested di smoke test per verificare routing visitor/admin/preview."
|
|
8073
|
-
},
|
|
8074
|
-
"sections": [
|
|
8075
|
-
{
|
|
8076
|
-
"id": "hero-servizi-trattamento",
|
|
8077
|
-
"type": "hero",
|
|
8078
|
-
"data": {
|
|
8079
|
-
"badge": "Smoke Test",
|
|
8080
|
-
"title": "Trattamentos",
|
|
8081
|
-
"titleHighlight": "Pagina Nested",
|
|
8082
|
-
"description": "Questa pagina verifica il supporto ai nested slug su filesystem e router.",
|
|
8083
|
-
"ctas": [
|
|
8084
|
-
{
|
|
8085
|
-
"id": "cta-home",
|
|
8086
|
-
"label": "Torna Home",
|
|
8087
|
-
"href": "/",
|
|
8088
|
-
"variant": "primary"
|
|
8089
|
-
}
|
|
8090
|
-
]
|
|
8091
|
-
},
|
|
8092
|
-
"settings": {}
|
|
8093
|
-
}
|
|
8094
|
-
]
|
|
8095
|
-
}
|
|
8096
|
-
END_OF_FILE_CONTENT
|
|
8097
7939
|
mkdir -p "src/emails"
|
|
8098
7940
|
echo "Creating src/emails/LeadNotificationEmail.tsx..."
|
|
8099
7941
|
cat << 'END_OF_FILE_CONTENT' > "src/emails/LeadNotificationEmail.tsx"
|
|
@@ -8407,7 +8249,7 @@ export default LeadSenderConfirmationEmail;
|
|
|
8407
8249
|
END_OF_FILE_CONTENT
|
|
8408
8250
|
echo "Creating src/fonts.css..."
|
|
8409
8251
|
cat << 'END_OF_FILE_CONTENT' > "src/fonts.css"
|
|
8410
|
-
@import url('https://fonts.googleapis.com/css2?family=
|
|
8252
|
+
@import url('https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Playfair+Display:wght@700;800;900&display=swap');
|
|
8411
8253
|
|
|
8412
8254
|
END_OF_FILE_CONTENT
|
|
8413
8255
|
mkdir -p "src/hooks"
|
|
@@ -8472,14 +8314,13 @@ cat << 'END_OF_FILE_CONTENT' > "src/index.css"
|
|
|
8472
8314
|
--font-primary: var(--theme-font-primary);
|
|
8473
8315
|
--font-mono: var(--theme-font-mono);
|
|
8474
8316
|
|
|
8475
|
-
/*
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
automatically and the fallback becomes dead code.
|
|
8317
|
+
/*
|
|
8318
|
+
DISPLAY FONT bridge
|
|
8319
|
+
The core now emits --theme-font-display from theme.json, so this keeps
|
|
8320
|
+
the tenant on the stable semantic alias rather than depending on the
|
|
8321
|
+
flattened internal variable path.
|
|
8481
8322
|
*/
|
|
8482
|
-
--font-display: var(--theme-font-display
|
|
8323
|
+
--font-display: var(--theme-font-display);
|
|
8483
8324
|
}
|
|
8484
8325
|
|
|
8485
8326
|
/*
|
|
@@ -8497,7 +8338,7 @@ cat << 'END_OF_FILE_CONTENT' > "src/index.css"
|
|
|
8497
8338
|
--muted: var(--theme-surface-alt);
|
|
8498
8339
|
--muted-foreground: var(--theme-text-muted);
|
|
8499
8340
|
--border: var(--theme-border);
|
|
8500
|
-
--radius:
|
|
8341
|
+
--radius: 3.45rem;
|
|
8501
8342
|
|
|
8502
8343
|
/*
|
|
8503
8344
|
🔧 ACCENT CHAIN — Forward-compatible workaround
|
|
@@ -9394,24 +9235,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
9394
9235
|
|
|
9395
9236
|
|
|
9396
9237
|
|
|
9397
|
-
END_OF_FILE_CONTENT
|
|
9398
|
-
echo "Creating src/main_.tsx..."
|
|
9399
|
-
cat << 'END_OF_FILE_CONTENT' > "src/main_.tsx"
|
|
9400
|
-
import '@/types'; // TBP: load type augmentation from capsule-driven types
|
|
9401
|
-
import React from 'react';
|
|
9402
|
-
import ReactDOM from 'react-dom/client';
|
|
9403
|
-
import App from './App';
|
|
9404
|
-
// ... resto del file
|
|
9405
|
-
|
|
9406
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
9407
|
-
<React.StrictMode>
|
|
9408
|
-
<App />
|
|
9409
|
-
</React.StrictMode>
|
|
9410
|
-
);
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
9238
|
END_OF_FILE_CONTENT
|
|
9416
9239
|
# SKIP: src/registry-types.ts is binary and cannot be embedded as text.
|
|
9417
9240
|
mkdir -p "src/server"
|