A lightweight base for single-file Preact apps. No install, no build step. Copy this file into a project directory, open it in a browser, start building.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>mew mew</title>
<style>

/*
 * mew mew design system  karsonkalt.dev
 *
 * A lightweight UI base for single-file Preact apps.
 * No build step. No bundler. Copy this file into a project and start building.
 *
 * STACK
 *   import { html, render, useState, useEffect } from 'https://esm.sh/htm/preact/standalone'
 *   All UI is written as tagged template literals  no JSX, no transpilation.
 *
 * TOKENS
 *   Three surface depths: --bg / --surface / --code-bg
 *   One border: --border
 *   Text: --text / --dim / --blue / --green / --red / --code
 *   Type: --f (13px base) / --ff (system-ui) / --ffm (monospace)
 *   Never hardcode colors or font sizes. Always var(--x).
 *
 * LAYOUT CLASSES
 *   .box .row     flex column / row (min-height:0 on both  safe for nested scroll)
 *   .f1           flex:1 + min-height:0
 *   .sc .cl       overflow auto / hidden
 *   .ac .jb       align-items:center / justify-content:space-between
 *   .bb .br       1px border-bottom / border-right
 *   .wrap         flex-wrap:wrap
 *   .rel          position:relative
 *   .fs0          flex-shrink:0
 *   gap: .g4 .g5 .g6 .g8 .g10 .g12 .g14 .g16 .g20 .g24
 *   pad: .p4 .p6 .p8 .p12 .p14 .p16 .p20 .p24
 *       .ph8 .ph12 .ph16 .ph20  (horizontal only)
 *       .pv8 .pv12              (vertical only)
 *
 * TYPE CLASSES
 *   .lg .sm .xs   15px·600 / 11px / 10px
 *   .bold .med    weight 600 / 500
 *   .dim .lnk     --dim color / --blue color
 *   .mono         monospace
 *   .tr           truncate with ellipsis (display:block)
 *   .caps         uppercase + letter-spacing
 *   .sub          11px dim line (display:block, mt:2px)
 *   .i            italic
 *   .ma           margin:auto
 *
 * BASE COMPONENTS (defined in <script> below)
 *   Box({ row,f1,sc,cl,ac,jb,bb,br,rel,fs0,wrap,gap,pad,flex,class,style,...rest })
 *     The only layout primitive. Never use a raw <div> for structure.
 *   Txt({ dim,lnk,mono,tr,sm,xs,bold,med,i,ma,class,style,...rest })
 *     Inline text with token modifiers.
 *   Btn({ primary,danger,disabled,onClick,style,children })
 *     primary  green fill. danger  red outline. default  surface.
 *   Ghost({ onClick,class,style,children })
 *     Inline text action button. Blue, no border.
 *   Nav({ on,onClick,children })
 *     Sidebar nav item. .on = active state (blue left border).
 *     Wrap children in .lbl-primary and .lbl-secondary spans.
 *   Pre({ code,style,children })
 *     Preformatted block. code=true applies monospace code styling.
 *   Field({ label,children })
 *     Label above, content below, 5px gap.
 *
 * RULES FOR THE NEXT AGENT
 *   This file is the base. Your job is to implement the application on top of it.
 *   The comment block above describes the system. The CSS and JS below are the artifact.
 *
 *   1. Use html`...` for all markup. No .jsx files, no createElement calls.
 *   2. Every layout container is <${Box}>. Never a raw <div> for structure.
 *   3. Token classes only. No inline colors, no hardcoded font sizes.
 *   4. Buttons are <${Btn}> or <${Ghost}>. No raw <button> with ad-hoc styles.
 *   5. Controlled inputs: always bind value + onInput.
 *      Every <option> must have an explicit value attribute.
 *   6. Keep state close to where it's used.
 *      Pass the setter down as a prop named "set".
 *   7. Fetch in useEffect. Swallow AbortError. Surface everything else.
 *   8. The .field / .lbl classes exist for form layout — use Field for all labeled inputs.
 *   9. Compose upward from these primitives. Do not redefine what's already here.
 *  10. Everything you add  classes, components, patterns  gets a * suffix in the
 *      doc comment of that file. This marks it as agent/user-authored, not base system.
 *      Keep the * inventory current. A future agent reads it to know what is portable
 *      (no *) vs what belongs to this project (* = do not carry forward blindly).
 *  11. Update the MODE block whenever the shape of the app changes. One line max.
 *
 * ── MODE (agent-maintained) ───────────────────────────────────────────────────
 *   Describe the layout pattern and interaction model of this implementation.
 *   Update this block whenever the shape of the app changes. Keep it to 3 lines max.
 *
 *   Patterns to pick from (or combine):
 *     list  inspector     sidebar list, detail pane opens on selection
 *     data-viz             chart-first layout, tables are secondary
 *     query viewer         input (sql/j1ql/filter) + result table below
 *     data dense           multi-column table, filters prominent, minimal chrome
 *     dashboard            metric cards + trend charts, read-only overview
 *     form / wizard        sequential input, validation, submit flow
 *     canvas               freeform spatial layout (graph, diagram, map)
 *
 *   current: not yet set  replace this line after first implementation pass
 */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0 }
button, input, select, textarea { font: inherit }
button { appearance: none; -webkit-appearance: none; cursor: pointer }

:root {
  --bg:      #0d1117;
  --surface: #161b22;
  --code-bg: #0a0d12;
  --border:  #30363d;
  --text:    #e6edf3;
  --dim:     #8b949e;
  --blue:    #58a6ff;
  --green:   #3fb950;
  --red:     #f85149;
  --code:    #79c0ff;
  --f:       13px;
  --ff:      system-ui, sans-serif;
  --ffm:     ui-monospace, monospace;
}

html, body { height: 100%; overflow: hidden }
body { background: var(--bg); color: var(--text); font: var(--f)/1.5 var(--ff); display: flex; flex-direction: column }

/* layout */
.box { display: flex; flex-direction: column; min-height: 0; min-width: 0 }
.row { flex-direction: row }
.f1  { flex: 1; min-height: 0 }
.sc  { overflow: auto }
.cl  { overflow: hidden }
.ac  { align-items: center }
.jb  { justify-content: space-between }
.bb  { border-bottom: 1px solid var(--border) }
.br  { border-right:  1px solid var(--border) }
.rel { position: relative }
.fs0 { flex-shrink: 0 }
.wrap{ flex-wrap: wrap }

/* gap */
.g2{gap:2px}.g3{gap:3px}.g4{gap:4px}.g5{gap:5px}.g6{gap:6px}.g8{gap:8px}
.g10{gap:10px}.g12{gap:12px}.g14{gap:14px}.g16{gap:16px}.g20{gap:20px}.g24{gap:24px}

/* padding */
.p4{padding:4px}.p6{padding:6px}.p8{padding:8px}.p12{padding:12px}
.p14{padding:14px}.p16{padding:16px}.p20{padding:20px}.p24{padding:24px}
.ph8{padding-left:8px;padding-right:8px}
.ph12{padding-left:12px;padding-right:12px}
.ph16{padding-left:16px;padding-right:16px}
.ph20{padding-left:20px;padding-right:20px}
.pv8{padding-top:8px;padding-bottom:8px}
.pv12{padding-top:12px;padding-bottom:12px}

/* text */
.dim  { color: var(--dim) }
.lnk  { color: var(--blue) }
.mono { font-family: var(--ffm) }
.tr   { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block }
.lg   { font-size: 15px; font-weight: 600; line-height: 1.2 }
.sm   { font-size: 11px }
.xs   { font-size: 10px }
.bold { font-weight: 600 }
.med  { font-weight: 500 }
.i    { font-style: italic }
.ma   { margin: auto }
.caps { text-transform: uppercase; letter-spacing: .04em }
.sub  { display: block; font-size: 11px; margin-top: 2px; line-height: 1.3; color: var(--dim) }

/* buttons */
.btn       { background: #21262d; border: 1px solid var(--border); border-radius: 6px; padding: 5px 12px; cursor: pointer; color: var(--text); font-weight: 500 }
.btn:hover { background: #30363d }
.btn:disabled { opacity: .5; cursor: default }
.btn-p       { background: #238636; border-color: #2ea043; color: #fff }
.btn-p:hover { background: #2ea043 }
.btn-d       { background: transparent; border-color: rgba(248,81,73,.4); color: var(--red) }
.ghost       { background: none; border: none; color: var(--blue); cursor: pointer; padding: 2px 0; font-size: 12px }
.ghost:hover { opacity: .8 }

/* nav item */
.nav        { display: flex; align-items: flex-start; width: 100%; text-align: left; background: none; border: none; border-left: 2px solid transparent; padding: 9px 10px 9px 14px; border-radius: 0 6px 6px 0; cursor: pointer; color: var(--dim) }
.nav:hover  { background: var(--surface) }
.nav.on     { background: var(--surface); border-left-color: var(--blue); color: var(--text) }
.nav.on .lbl-primary { color: var(--text); font-weight: 500 }
.nav > div  { flex: 1; min-width: 0; overflow: hidden }
.lbl-primary   { color: var(--text); font-size: 13px; line-height: 1.4; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
.lbl-secondary { color: var(--dim); font-size: 11px; font-family: var(--ffm); display: block; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }

/* field / form */
.field { display: flex; flex-direction: column; gap: 5px }
.lbl   { font-size: 11px; font-weight: 600; color: var(--dim) }
input[type=text], input[type=date], input[type=number], select, textarea {
  background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
  color: var(--text); padding: 4px 8px
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--blue); outline-offset: -1px }

/* pre */
.pre  { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 10px 12px; font-family: var(--ffm); font-size: 12px; color: var(--dim); white-space: pre-wrap; overflow-x: auto }
.pre.code { color: var(--code); background: var(--code-bg) }

/* table */
table { border-collapse: collapse; width: 100%; font-size: 12px }
th { text-align: left; padding: 5px 10px; color: var(--dim); font-weight: 500; border-bottom: 1px solid var(--border); white-space: nowrap }
td { padding: 4px 10px; border-bottom: 1px solid var(--border); font-family: var(--ffm); white-space: nowrap }
tr:last-child td { border-bottom: none }
</style>
</head>
<body>
<div id="app"></div>
<script type="module">
import { html, render, useState, useEffect } from 'https://esm.sh/htm/preact/standalone'

// ── layout lookup tables ───────────────────────────────────────────────────────
const G = {2:'g2',3:'g3',4:'g4',5:'g5',6:'g6',8:'g8',10:'g10',12:'g12',14:'g14',16:'g16',20:'g20',24:'g24'}
const P = {'4px':'p4','6px':'p6','8px':'p8','12px':'p12','14px':'p14','16px':'p16','20px':'p20','24px':'p24'}

// ── base components ───────────────────────────────────────────────────────────
function Box({ row,f1,sc,cl,ac,jb,bb,br,rel,fs0,wrap, gap,pad,flex, class:cx='', style:s={}, children, ...rest }) {
  const gc = G[gap], pc = P[pad]
  const style = { ...(gc?{}:{gap}), ...(pc?{}:{padding:pad}), flex, ...s }
  const c = ['box',row&&'row',f1&&'f1',sc&&'sc',cl&&'cl',ac&&'ac',jb&&'jb',bb&&'bb',br&&'br',rel&&'rel',fs0&&'fs0',wrap&&'wrap',gc,pc,cx].filter(Boolean).join(' ')
  return html`<div class=${c} style=${Object.keys(style).some(k=>style[k]!=null)?style:undefined} ...${rest}>${children}</div>`
}

function Txt({ dim,lnk,mono,tr,sm,xs,bold,med,i,ma, class:cx='', style:s={}, children, ...rest }) {
  const c = [dim&&'dim',lnk&&'lnk',mono&&'mono',tr&&'tr',sm&&'sm',xs&&'xs',bold&&'bold',med&&'med',i&&'i',ma&&'ma',cx].filter(Boolean).join(' ')
  return html`<span class=${c||undefined} style=${Object.keys(s).length?s:undefined} ...${rest}>${children}</span>`
}

const Btn = ({ primary,danger,disabled,onClick,style:s={},children }) =>
  html`<button class=${['btn',primary&&'btn-p',danger&&'btn-d'].filter(Boolean).join(' ')} disabled=${disabled} onClick=${onClick} style=${Object.keys(s).length?s:undefined}>${children}</button>`

const Ghost = ({ onClick,children,class:cx='',style:s={} }) =>
  html`<button class=${['ghost',cx].filter(Boolean).join(' ')} onClick=${onClick} style=${Object.keys(s).length?s:undefined}>${children}</button>`

const Nav = ({ on,onClick,children }) =>
  html`<button class=${on?'nav on':'nav'} onClick=${onClick}><div>${children}</div></button>`

const Pre = ({ code,style:s={},children }) =>
  html`<pre class=${code?'pre code':'pre'} style=${Object.keys(s).length?s:undefined}>${children}</pre>`

const Field = ({ label,children }) =>
  html`<div class="field"><span class="lbl">${label}</span>${children}</div>`

// ── app ───────────────────────────────────────────────────────────────────────
// Placeholders below show common structural zones. Delete what you don't need,
// rename what you do. The comments describe intent, not implementation.

// Topbar — app name, global actions, gear/settings toggle.
// Keep it ≤38px tall. One row only.
function Topbar() {
  return html`
    <${Box} row ac jb bb class="topbar" style=$>
      <${Txt} bold>app name</${Txt}>
      <${Box} row gap=${8}>
        <!-- global actions / toggles go here -->
      </${Box}>
    </${Box}>
  `
}

// Sidebar — nav list, section headers, search.
// Use <${Nav}> for items. .lbl-primary + .lbl-secondary for two-line labels.
function Sidebar({ selected, onSelect }) {
  const items = [] // replace with real data
  return html`
    <${Box} br sc class="nav-panel" style=$>
      <!-- section header example -->
      <${Box} row ac jb class="ph16 pv8 bb">
        <${Txt} sm bold dim>section</${Txt}>
        <${Ghost} onClick=${()=>{}}>+ new</${Ghost}>
      </${Box}>
      ${items.map(item => html`
        <${Nav} key=${item.id} on=${selected===item.id} onClick=${()=>onSelect(item.id)}>
          <span class="lbl-primary">${item.name}</span>
          <span class="lbl-secondary">${item.meta}</span>
        </${Nav}>
      `)}
    </${Box}>
  `
}

// Detail — main content area. Receives the selected item.
// Swap this out entirely depending on the mode (table, chart, form, etc).
function Detail({ item }) {
  if (!item) return html`<${Box} f1 ac style=$><${Txt} dim i>select something</${Txt}></${Box}>`
  return html`
    <${Box} f1 sc>
      <!-- detail header -->
      <${Box} row ac jb bb class="ph20 pv12" style=$>
        <${Box} gap=${2}>
          <${Txt} bold>${item.name}</${Txt}>
          <${Txt} dim sm>${item.meta}</${Txt}>
        </${Box}>
        <${Box} row gap=${6}>
          <${Btn} onClick=${()=>{}}>action</${Btn}>
          <${Btn} danger onClick=${()=>{}}>delete</${Btn}>
        </${Box}>
      </${Box}>
      <!-- body — replace with table / chart / form / etc -->
      <${Box} pad="20px" gap=${12}>
        <${Txt} dim i>content goes here</${Txt}>
      </${Box}>
    </${Box}>
  `
}

// Root app shell. Wires layout together.
// Replace the list→inspector pattern here with whatever MODE this becomes.
function App() {
  const [items]    = useState([]) // replace with fetch
  const [selected, setSelected] = useState(null)
  const item = items.find(i => i.id === selected) ?? null

  return html`
    <${Box} f1>
      <${Topbar}/>
      <${Box} row f1 cl>
        <${Sidebar} selected=${selected} onSelect=${setSelected}/>
        <${Detail} item=${item}/>
      </${Box}>
    </${Box}>
  `
}

render(html`<${App}/>`, document.getElementById('app'))
</script>
</body>
</html>