import { Hype } from '@because/hype' import { define, stylesToCSS } from '@because/forge' import { baseStyles, ToolScript, theme } from '@because/toes/tools' import { readdir, stat } from 'fs/promises' import { readFileSync } from 'fs' import { join, extname, basename } from 'path' import type { Child } from 'hono/jsx' const APPS_DIR = process.env.APPS_DIR! const app = new Hype({ prettyHTML: false }) const Container = define('Container', { fontFamily: theme('fonts-sans'), padding: '20px', paddingTop: 0, maxWidth: '1200px', margin: '0 auto', color: theme('colors-text'), }) const FileList = define('FileList', { listStyle: 'none', padding: 0, margin: '20px 0', border: `1px solid ${theme('colors-border')}`, borderRadius: theme('radius-md'), overflow: 'hidden', }) const FileItem = define('FileItem', { padding: '10px 15px', borderBottom: `1px solid ${theme('colors-border')}`, states: { ':last-child': { borderBottom: 'none', }, ':hover': { backgroundColor: theme('colors-bgHover'), }, } }) const FileLink = define('FileLink', { base: 'a', textDecoration: 'none', color: theme('colors-link'), display: 'flex', alignItems: 'center', gap: '8px', states: { ':hover': { textDecoration: 'underline', }, } }) const FileIcon = define('FileIcon', { base: 'svg', width: '18px', height: '18px', flexShrink: 0, fill: theme('colors-textMuted'), }) const CodeBlock = define('CodeBlock', { margin: '20px 0', border: `1px solid ${theme('colors-border')}`, borderRadius: theme('radius-md'), overflowX: 'auto', selectors: { '& pre': { margin: 0, padding: '15px', whiteSpace: 'pre', backgroundColor: theme('colors-bgSubtle'), }, '& pre code': { whiteSpace: 'pre', fontFamily: theme('fonts-mono'), }, }, }) const CodeHeader = define('CodeHeader', { padding: '10px 15px', backgroundColor: theme('colors-bgElement'), borderBottom: `1px solid ${theme('colors-border')}`, fontWeight: 'bold', fontSize: '14px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', }) const ErrorBox = define('ErrorBox', { color: theme('colors-error'), padding: '20px', backgroundColor: theme('colors-bgElement'), borderRadius: theme('radius-md'), margin: '20px 0', }) const Breadcrumb = define('Breadcrumb', { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '14px', marginBottom: '15px', flexWrap: 'wrap', }) const BreadcrumbLink = define('BreadcrumbLink', { base: 'a', textDecoration: 'none', color: theme('colors-link'), states: { ':hover': { textDecoration: 'underline', }, }, }) const BreadcrumbSeparator = define('BreadcrumbSeparator', { color: theme('colors-textMuted'), }) const BreadcrumbCurrent = define('BreadcrumbCurrent', { color: theme('colors-text'), fontWeight: 500, }) const MediaContainer = define('MediaContainer', { margin: '20px 0', border: `1px solid ${theme('colors-border')}`, borderRadius: theme('radius-md'), overflow: 'hidden', backgroundColor: theme('colors-bgSubtle'), }) const MediaHeader = define('MediaHeader', { padding: '10px 15px', backgroundColor: theme('colors-bgElement'), borderBottom: `1px solid ${theme('colors-border')}`, fontWeight: 'bold', fontSize: '14px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', }) const MediaContent = define('MediaContent', { padding: '20px', display: 'flex', justifyContent: 'center', alignItems: 'center', }) const ImagePreview = define('ImagePreview', { base: 'img', maxWidth: '100%', maxHeight: '600px', objectFit: 'contain', }) const AudioPlayer = define('AudioPlayer', { base: 'audio', width: '100%', maxWidth: '500px', }) const VideoPlayer = define('VideoPlayer', { base: 'video', maxWidth: '100%', maxHeight: '600px', }) const DownloadButton = define('DownloadButton', { base: 'a', display: 'inline-flex', alignItems: 'center', gap: '6px', padding: '8px 16px', backgroundColor: theme('colors-primary'), color: 'white', textDecoration: 'none', borderRadius: theme('radius-md'), fontSize: '14px', states: { ':hover': { opacity: 0.9, }, }, }) const EditButton = define('EditButton', { base: 'button', display: 'inline-flex', alignItems: 'center', gap: '6px', padding: '6px 12px', backgroundColor: theme('colors-bgElement'), color: theme('colors-text'), border: `1px solid ${theme('colors-border')}`, borderRadius: theme('radius-md'), fontSize: '13px', cursor: 'pointer', states: { ':hover': { backgroundColor: theme('colors-bgHover'), }, }, }) const EditLink = define('EditLink', { base: 'a', display: 'inline-flex', alignItems: 'center', gap: '6px', padding: '6px 12px', backgroundColor: theme('colors-bgElement'), color: theme('colors-text'), border: `1px solid ${theme('colors-border')}`, borderRadius: theme('radius-md'), fontSize: '13px', cursor: 'pointer', textDecoration: 'none', states: { ':hover': { backgroundColor: theme('colors-bgHover'), }, }, }) const FolderIcon = () => ( ) const FileIconSvg = () => ( ) interface LayoutProps { title: string children: Child highlight?: boolean editable?: boolean } const fileMemoryScript = ` (function() { var params = new URLSearchParams(window.location.search); var app = params.get('app'); var file = params.get('file'); var version = params.get('version') || 'current'; if (!app) return; var key = 'code-app:' + app + ':' + version + ':file'; if (params.has('file')) { // Explicit file param (even empty) - save it if (file) localStorage.setItem(key, file); else localStorage.removeItem(key); } else { // No file param - restore saved location var saved = localStorage.getItem(key); if (saved) { var url = '/?app=' + encodeURIComponent(app); if (version !== 'current') url += '&version=' + encodeURIComponent(version); url += '&file=' + encodeURIComponent(saved); window.location.replace(url); } } })(); ` function Layout({ title, children, highlight, editable }: LayoutProps) { return ( {title} {highlight && !editable && ( <> )} {editable && ( <> )}