diff --git a/public/app.js b/public/app.js index 332bfc1..cd63464 100644 --- a/public/app.js +++ b/public/app.js @@ -31,6 +31,7 @@ const VIEWS = { obd2: () => import('./views/obd2.js'), links: () => import('./views/links.js'), mirror: () => import('./views/mirror.js'), + forge: () => import('./views/forge.js'), settings: () => import('./views/settings.js'), jobs: () => import('./views/jobs.js'), speedtest: () => import('./views/speedtest.js') diff --git a/public/components/sidebar.js b/public/components/sidebar.js index 8785522..59e0769 100644 --- a/public/components/sidebar.js +++ b/public/components/sidebar.js @@ -135,7 +135,8 @@ export function renderSidebar(root) { navItem('AI Usage', '/ai-usage'), navItem('OBD2', '/obd2'), navItem('Links', '/links'), - navItem('MagicMirror', '/mirror') + navItem('MagicMirror', '/mirror'), + navItem('Forge', '/forge') ) ); diff --git a/public/router.js b/public/router.js index d1736a5..11c6342 100644 --- a/public/router.js +++ b/public/router.js @@ -31,6 +31,7 @@ const ROUTES = [ { name: 'obd2', re: /^\/obd2$/, keys: [] }, { name: 'links', re: /^\/links$/, keys: [] }, { name: 'mirror', re: /^\/mirror$/, keys: [] }, + { name: 'forge', re: /^\/forge$/, keys: [] }, { name: 'settings', re: /^\/settings$/, keys: [] }, { name: 'jobs', re: /^\/jobs$/, keys: [] }, { name: 'speedtest', re: /^\/speedtest$/, keys: [] }, diff --git a/public/views/forge.js b/public/views/forge.js new file mode 100644 index 0000000..36d305c --- /dev/null +++ b/public/views/forge.js @@ -0,0 +1,49 @@ +// Forge — 3D printing, modelling & engineering hub. Links out to the self-hosted +// tools (Manyfold today; OctoPrint / Spoolman as the workshop grows). +import { el, mount, safeHref } from '../dom.js'; + +function card({ name, status, blurb, href, accent }) { + const live = status === 'Live'; + return el('a', { + href: href ? safeHref(href) : '#', + target: href ? '_blank' : null, + rel: href ? 'noopener noreferrer' : null, + style: { + display: 'block', textDecoration: 'none', color: 'inherit', + border: '1px solid var(--border)', borderRadius: '8px', padding: '1rem 1.1rem', + background: 'var(--panel, var(--bg2, #1a1a22))', opacity: live ? '1' : '0.7', + cursor: href ? 'pointer' : 'default' + }, + onclick: href ? null : (e) => e.preventDefault() + }, + el('div', { style: { display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: '0.5rem' } }, + el('strong', { style: { fontSize: '1.02rem' } }, name), + el('span', { style: { + fontSize: '0.65rem', letterSpacing: '0.08em', textTransform: 'uppercase', + color: live ? (accent || 'var(--accent, #ff7a45)') : 'var(--muted, #8a8a99)' + } }, status)), + el('p', { style: { margin: '0.4rem 0 0', fontSize: '0.85rem', color: 'var(--muted, #9a9aa8)', lineHeight: '1.4' } }, blurb), + href ? el('span', { style: { fontSize: '0.78rem', color: 'var(--accent, #ff7a45)' } }, '→ open') : null + ); +} + +export async function render(main) { + mount(main, + el('h1', { class: 'view-h1' }, 'Forge'), + el('p', { class: 'view-sub' }, '3D printing, modelling & engineering projects.'), + el('div', { style: { + display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', + gap: '0.9rem', marginTop: '1rem' + } }, + card({ name: 'Manyfold', status: 'Live', + blurb: 'Your 3D model & file library — store, tag and organise STL/3MF/Chitubox projects; import straight from Printables, Thingiverse & MyMiniFactory.', + href: 'https://forge.hynesy.com' }), + card({ name: 'Spoolman', status: 'Recommended', + blurb: 'Self-hosted resin/filament inventory — track bottles, usage and cost. The natural next add for the resin workflow.' }), + card({ name: 'OctoPrint', status: 'Planned', + blurb: 'FDM printer control & monitoring. For a future filament printer — the resin Mars 3 Pro prints standalone from USB via Chitubox, so this is parked until there is an FDM machine.' }) + ), + el('p', { style: { marginTop: '1.2rem', fontSize: '0.8rem', color: 'var(--muted, #8a8a99)' } }, + 'Forge grows with the workshop — more 3D-printing, modelling and engineering tools land here as they are stood up.') + ); +}