1import {useState, type ReactNode} from 'react';2import clsx from 'clsx';3import Link from '@docusaurus/Link';4import Layout from '@theme/Layout';5import firstSteps from '@site/../docs/media/first-steps.gif';6import styles from './index.module.css';78// A cell of the landing page's sheet: a feature, what the control panel9// shows when the pointer is on it, and where it leads.10type Cell = {11 addr: string;12 entry: string;13 hint: string;14 title: string;15 body: string;16 to: string;17};1819// The control panel at rest: the pointer on A1, holding a label, written20// with the apostrophe 012 shows before text.21const home: Cell = {22 addr: 'A1',23 entry: "'Start here",24 hint: 'Hover a cell, or Tab through them. F1 opens the guides.',25 title: '',26 body: '',27 to: '/docs/',28};2930const cells: Cell[] = [31 {32 addr: 'A1',33 entry: '=SUM(B2:B9)*(1+Tax)',34 hint: 'Formulas: Enter commits, arrows point at cells, suggestions as you type',35 title: 'Formulas as in Sheets',36 body: "Sheets' functions with suggestions and argument hints, named ranges, arrays that spill, LAMBDA, decimal arithmetic for money, and undo for everything.",37 to: '/docs/formulas/',38 },39 {40 addr: 'B1',41 entry: '=SORT(FILTER(A2:C40, C2:C40>10), 3, FALSE)',42 hint: 'Working with sheets: sort, filter, rules, pivots, charts, macros',43 title: 'Data tools',44 body: 'Freeze, sort, filter, find and replace, conditional formatting, dropdowns and checkboxes, live pivot tables, and charts that float over the grid.',45 to: '/docs/sheets/',46 },47 {48 addr: 'A2',49 entry: "'.012 CSV TSV JSON NUON XLSX SQLite Parquet WK1",50 hint: 'Files: import, download, Excel, the diff-friendly .012 format',51 title: 'Files in, files out',52 body: 'A diff-friendly JSON format of its own; imports from CSV to Lotus 1-2-3, exports to CSV, JSON, NUON, XLSX and SQLite.',53 to: '/docs/files/',54 },55 {56 addr: 'B2',57 entry: "'theme = light:Catppuccin Latte,dark:Catppuccin Mocha",58 hint: 'The terminal: its features, themes, 012 serve over SSH',59 title: 'Native to the terminal',60 body: 'Your terminal\'s own colors or any of hundreds of schemes, the mouse, hyperlinks, charts as images, and your sheets over SSH with 012 serve.',61 to: '/docs/terminal/',62 },63 {64 addr: 'A3',65 entry: "'ls | sheet | where size > 1kb",66 hint: 'Nushell: pipelines, notebooks, types, a cookbook',67 title: 'Nushell, both ways',68 body: "A stage in a nushell pipeline with file sizes, durations and dates kept, or a notebook of nushell cells whose outputs, sent to a sheet, are live tables that formulas and charts read.",69 to: '/docs/nushell/',70 },71 {72 addr: 'B3',73 entry: "'$app | where status >= 500",74 hint: 'Following files: a linked region follows a log as it grows',75 title: 'Files that keep growing',76 body: 'Link a CSV, JSON lines or NUON log and its rows come in as they are written, like tail -f; formulas, charts and pipelines over it keep up.',77 to: '/docs/files/following/',78 },79];8081// The menu bar lists the navbar's sections, in its order: on this page82// it stands in for them (custom.css hides the navbar's at widths where83// both would show).84const menu: {label: string; to: string}[] = [85 {label: 'Getting started', to: '/docs/getting-started/'},86 {label: 'Sheets', to: '/docs/sheets/'},87 {label: 'Formulas', to: '/docs/formulas/'},88 {label: 'Files', to: '/docs/files/'},89 {label: 'Nushell', to: '/docs/nushell/'},90 {label: 'Agents', to: '/docs/agents/'},91 {label: 'Terminal', to: '/docs/terminal/'},92 {label: 'Reference', to: '/docs/reference/'},93];9495function Chip({children}: {children: ReactNode}) {96 return <kbd className={styles.chip}>{children}</kbd>;97}9899// The control panel: the entry of the cell under the pointer, the menu100// bar and the context line, with the mode indicator at the right.101function ControlPanel({cell, pointing}: {cell: Cell; pointing: boolean}) {102 return (103 <section className={styles.panel} aria-label="Control panel" data-control-panel>104 <div className={clsx(styles.line, styles.line1)}>105 <span className={styles.addr}>{cell.addr}:</span>106 <span className={styles.entry}>{cell.entry}</span>107 <span className={styles.mode}>{pointing ? 'POINT' : 'READY'}</span>108 </div>109 <nav className={clsx(styles.line, styles.line2)} aria-label="Sections">110 {menu.map((m) => (111 <Link key={m.label} to={m.to} className={styles.menuItem}>112 <u>{m.label[0]}</u>113 {m.label.slice(1)}114 </Link>115 ))}116 </nav>117 <div className={clsx(styles.line, styles.line3)} aria-live="polite">118 {cell.hint}119 <span className={styles.cursor} aria-hidden="true" />120 </div>121 </section>122 );123}124125function Hero() {126 return (127 <section className={styles.hero}>128 <figure className={styles.frame}>129 <figcaption className={styles.frameTitle}>first-steps.tape</figcaption>130 <img131 src={firstSteps}132 alt="Typing a small budget, pointing at cells in a formula, and watching totals recalculate"133 width={1000}134 height={600}135 />136 <span className={styles.frameCount}>Catppuccin Mocha</span>137 </figure>138 <div className={styles.heroText}>139 <h1 className={styles.srOnly}>012</h1>140 <p className={styles.sub}>141 The control panel, the mode indicator and the character grid of Lotus 1-2-3. Inside the grid, Google142 Sheets: typing replaces a cell, <code>=</code> starts a formula, Enter and Tab move you on, and Sheets'143 shortcuts do what you expect. One pure-Go binary.144 </p>145 <pre className={styles.install} aria-label="Install">146 <span className={styles.prompt}>$ </span>curl -fsSL https://012.dev.site/install.sh | sh147 </pre>148 <div className={styles.actions}>149 <Link className={clsx(styles.action, styles.actionPrimary)} to="/docs/getting-started/">150 <Chip>F1</Chip> Get started151 </Link>152 <Link className={styles.action} to="/docs/reference/keys/">153 <Chip>F10</Chip> Every key154 </Link>155 <span className={styles.searchHint}>156 <Chip>Ctrl+K</Chip> searches the docs157 </span>158 </div>159 </div>160 </section>161 );162}163164// The features as a sheet: column and row headers around six cells,165// the one under the pointer drawn as 012 draws the active cell.166function Sheet({active, onPoint}: {active: string | null; onPoint: (c: Cell | null) => void}) {167 const cols = ['A', 'B'];168 const rows = [1, 2, 3];169 return (170 <section className={styles.sheet} aria-label="Features" onMouseLeave={() => onPoint(null)}>171 <div className={styles.corner} />172 {cols.map((c) => (173 <div key={c} className={clsx(styles.colHeader, active?.startsWith(c) && styles.headerActive)}>174 {c}175 </div>176 ))}177 {rows.map((r) => (178 <Row key={r} r={r} active={active} onPoint={onPoint} />179 ))}180 </section>181 );182}183184function Row({r, active, onPoint}: {r: number; active: string | null; onPoint: (c: Cell | null) => void}) {185 const row = cells.filter((c) => c.addr.endsWith(String(r)));186 return (187 <>188 <div className={clsx(styles.rowHeader, active?.endsWith(String(r)) && styles.headerActive)}>{r}</div>189 {row.map((c) => (190 <Link191 key={c.addr}192 to={c.to}193 className={clsx(styles.cell, active === c.addr && styles.cellActive)}194 onMouseEnter={() => onPoint(c)}195 onFocus={() => onPoint(c)}196 onBlur={() => onPoint(null)}>197 <span className={styles.cellEntry}>{c.entry}</span>198 <h2 className={styles.cellTitle}>{c.title}</h2>199 <p className={styles.cellBody}>{c.body}</p>200 <span className={styles.cellGo}>Open {c.to.replaceAll('/', ' ').trim().split(' ').pop()} →</span>201 </Link>202 ))}203 </>204 );205}206207function StatusLine() {208 const refs = [209 {label: 'Keys and mouse', to: '/docs/reference/keys/'},210 {label: 'Functions', to: '/docs/reference/functions/'},211 {label: 'Configuration', to: '/docs/reference/config/'},212 {label: 'Macro API', to: '/docs/reference/macro-api/'},213 {label: 'Roadmap', to: 'https://github.com/FelineStateMachine/012/blob/main/ROADMAP.md'},214 ];215 return (216 <nav className={styles.status} aria-label="Reference">217 <span className={styles.statusLabel}>Reference</span>218 {refs.map((r) => (219 <Link key={r.label} to={r.to} className={styles.statusItem}>220 {r.label}221 </Link>222 ))}223 </nav>224 );225}226227export default function Home(): ReactNode {228 const [cell, setCell] = useState<Cell | null>(null);229 return (230 <Layout231 description="012 is a terminal spreadsheet with the look of Lotus 1-2-3 and the behavior of Google Sheets.">232 <main className={styles.page}>233 <ControlPanel cell={cell ?? home} pointing={cell !== null} />234 <Hero />235 <Sheet active={cell?.addr ?? null} onPoint={setCell} />236 <StatusLine />237 </main>238 </Layout>239 );240}