[
  {
    "path": ".editorconfig",
    "content": "root = true\ncharset = utf-8\nend_of_line = lf\nindent_size = 2\ntab_width = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: 🐞 Bug Report\nabout: Create a bug report to help us improve Nue\ntype: bug\n---\n\n<!-- Please search to see if an issue or discussion already exists for the bug you encountered -->\n\n### Describe the Bug\n<!-- Describe current and expected behavior -->\n\n### Environment\n<!--\nExample:\n- OS: Windows / Mac / Linux\n- Nuekit version & JS runtime: `nue --version`\n-->\n\n### Minimal Reproduction\n<!-- Please provide a minimal reproduction (e.g. GitHub repo, code snippets, ...) or simple steps to reproduce the bug -->\n\n### Logs & Additional Context\n<!-- Add additional information like logs or other related information -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: 💬 Discussions\n    url: https://github.com/nuejs/nue/discussions\n    about: Use discussions if you have an idea for improvement or want to ask a question\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "content": "---\nname: 💡 Feature Request or Improvement\nabout: Suggest an idea or improvement\nlabels: new feature, improvement\ntype: feature\n---\n\n### Is your feature request or improvement related to a problem?\n<!-- Describe what the problem is -->\n\n### Solution you'd like\n<!-- A clear and concise description of what you want to happen -->\n\n### Alternatives you've considered\n<!-- Alternative solutions or features you have thought of -->\n\n### Additional context\n<!-- Add any other context or screenshots about the feature request or improvement here -->\n"
  },
  {
    "path": ".github/workflows/build-template-zips.yml",
    "content": "name: Build template zips\non:\n  push:\n    branches: [ master, 2.0 ]\n    paths:\n      - 'packages/templates/**'\npermissions:\n  contents: write\njobs:\n  build-templates:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - name: Create template zips\n        run: |\n          cd packages/templates\n          zip -r minimal.zip minimal/\n          zip -r blog.zip blog/\n          zip -r spa.zip spa/\n          zip -r full.zip full/\n      - name: Commit zips\n        run: |\n          git config user.name github-actions\n          git config user.email github-actions@github.com\n          git add packages/templates/*.zip\n          git diff --quiet && git diff --staged --quiet || git commit -m \"Update template zips\"\n          git push"
  },
  {
    "path": ".github/workflows/links.yaml.disabled",
    "content": "name: Check Links\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - '**/README.md'\n      - 'packages/nuejs.org/**'\n  pull_request:\n    paths:\n      - '**/README.md'\n      - 'packages/nuejs.org/**'\n  workflow_dispatch:\n\njobs:\n  readme:\n    if: ${{ github.repository_owner == 'nuejs' || github.event_name == 'workflow_dispatch' }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Check README links\n        uses: lycheeverse/lychee-action@v2\n        with:\n          args: >\n            --no-progress\n            --include-fragments\n            --exclude-path \"node_modules\"\n            --exclude \"localhost\"\n            --base \".\"\n            --\n            \"**/README.md\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n\n  site:\n    if: ${{ github.repository_owner == 'nuejs' || github.event_name == 'workflow_dispatch' }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Build docs\n        uses: ./.github/workflows/repo/build-page\n        with:\n          root: packages/nuejs.org\n\n      # keep server running in background\n      - name: Run server\n        run: nue -pr packages/nuejs.org -P 8080 &\n\n      # find generated HTML files, convert paths to urls\n      # (might be superfluous in the future with some fixes on lychee)\n      - name: Find files to check\n        id: files\n        run: |\n          links=$(find packages/nuejs.org/.dist/prod -name \"*.html\" -printf \"http://localhost:8080/%P \")\n          echo \"links=$links\" >> $GITHUB_OUTPUT\n\n      - name: Check site links\n        uses: lycheeverse/lychee-action@v2\n        with:\n          args: >\n            --no-progress\n            --include-fragments\n            --accept \"403, 429, 503, 999\"\n            --remap \"http://localhost:8080/@ https://nuejs.org/@\"\n            --remap \"http://localhost:8080/todomvc https://nuejs.org/todomvc\"\n            --remap \"http://localhost:8080/glow-demo https://nuejs.org/glow-demo\"\n            --remap \"http://localhost:8080/docs/nuejs/examples https://nuejs.org/docs/nuejs/examples\"\n            --remap \"http://localhost:8080/docs/tutorials https://nuejs.org/docs/tutorials\"\n            --remap \"http://localhost:8080/hyper/demo/ https://nuejs.org/hyper/demo/\"\n            --exclude \"http://localhost:8080/docs/how-it-works.html\"\n            --exclude \"http://localhost:8080/img/mpa-build.mp4\"\n            --exclude \"http://localhost:8080/404.html\"\n            --exclude \"https?://x.com/\"\n            --\n            ${{ steps.files.outputs.links }}\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/push-examples.yaml.disabled",
    "content": "name: Push examples to web server\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - packages/examples/**\n  workflow_dispatch:\n\njobs:\n  examples:\n    if: ${{ github.repository_owner == 'nuejs' || github.event_name == 'workflow_dispatch' }}\n\n    strategy:\n      fail-fast: false\n      matrix:\n        dir: # dirs in \"examples\" to run build & push process for\n          - simple-blog\n          - simple-mpa\n\n    env:\n      dir: 'packages/examples/${{ matrix.dir }}/'\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n        with: # get entries to check for change in dir\n          fetch-depth: 2\n\n      - name: Check dir changes\n        run: git diff --name-only HEAD^ HEAD | grep -q \"^${{ env.dir }}\"\n        if: ${{ github.event_name != 'workflow_dispatch' }}\n\n      # subsitutes non-alphanumeric characters with _\n      - name: Build secret variable names\n        run: |\n          mdir=\"${{ matrix.dir }}\"\n          name=\"${mdir//[^[:alnum:]]/_}\"\n          echo \"$name\"\n          echo \"storage_name=STORAGE_NAME_$name\" >>  $GITHUB_ENV\n          echo \"storage_pass=STORAGE_PASS_$name\" >>  $GITHUB_ENV\n          echo \"pull_id=PULL_ID_$name\" >>  $GITHUB_ENV\n\n      # creates archive of dir, removes img dirs, moves content to subdir \"source\"\n      - name: Create test archive\n        run: tar -C \"${{ env.dir }}\" --exclude \"*/img\" --transform \"s/^\\.\\//source\\//\" -cf \"${{ env.dir }}test.tar.gz\" .\n        if: ${{ matrix.dir == 'simple-blog' }}\n\n      # creates archive of dir, moves content to subdir \"source\"\n      - name: Create source archive\n        run: git archive --prefix \"source/\" -o \"${{ env.dir }}source.tar.gz\" \"HEAD:${{ env.dir }}\"\n\n      # Copy source archive, to allow old source path on web server. Step should get removed after new release and a bit of time\n      - name: Duplicate source archive\n        run: cp \"${{ env.dir }}source.tar.gz\" \"${{ env.dir }}${{ matrix.dir }}.tar.gz\"\n\n      - name: Nue build\n        uses: ./.github/workflows/repo/build-page\n        with:\n          root: ${{ env.dir }}\n\n      - name: BunnyCDN storage deployer\n        uses: ayeressian/bunnycdn-storage-deploy@v2.2.5\n        with:\n          source: '${{ env.dir }}.dist/prod'\n          storageZoneName: '${{ secrets[env.storage_name] }}'\n          storagePassword: '${{ secrets[env.storage_pass] }}'\n          pullZoneId: '${{ secrets[env.pull_id] }}'\n          accessKey: '${{ secrets.BUNNY_API_KEY }}'\n          upload: 'true'\n          remove: 'false'\n          purgePullZone: 'true'\n"
  },
  {
    "path": ".github/workflows/push-site.yaml.disabled",
    "content": "name: Push site to web server\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - packages/nuejs.org/**\n  workflow_dispatch:\n\njobs:\n  site:\n    if: ${{ github.repository_owner == 'nuejs' || github.event_name == 'workflow_dispatch' }}\n\n    env:\n      dir: 'packages/nuejs.org/'\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Nue build\n        uses: ./.github/workflows/repo/build-page\n        with:\n          root: ${{ env.dir }}\n\n      - name: BunnyCDN storage deployer\n        uses: ayeressian/bunnycdn-storage-deploy@v2.2.5\n        with:\n          source: '${{ env.dir }}.dist/prod'\n          storageZoneName: '${{ secrets.STORAGE_NAME_site }}'\n          storagePassword: '${{ secrets.STORAGE_PASS_site }}'\n          pullZoneId: '${{ secrets.PULL_ID_site }}'\n          accessKey: '${{ secrets.BUNNY_API_KEY }}'\n          upload: 'true'\n          remove: 'false'\n          purgePullZone: 'true'\n"
  },
  {
    "path": ".github/workflows/repo/build-page/action.yaml",
    "content": "name: Repository nue page build\ndescription: |\n  Internal composite action to install dependencies, register the `nue` command, and build a Nue project.\n\ninputs:\n  root:\n    description: |\n      The root directory of the Nue project to build.\n      This is passed to the `--root` option of the `nue build` command.\n    default: '.'\n    required: false\n\nruns:\n  using: composite\n  steps:\n    - uses: oven-sh/setup-bun@v2\n\n    - name: Install and link nue\n      shell: bash\n      run: |\n        bun install\n        cd packages/nuekit/\n        bun link\n\n    - name: Build Nue Project\n      shell: bash\n      run: nue build -pr \"${{ inputs.root }}\"\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: Test\n\non:\n  push:\n    branches:\n      - master\n    paths-ignore:\n      - .github/**\n      - packages/templates/**\n      - packages/www/**\n  pull_request:\n    paths-ignore:\n      - .github/**\n      - packages/templates/**\n      - packages/www/**\n  workflow_dispatch:\n\njobs:\n  test:\n    if: ${{ github.repository_owner == 'nuejs' || github.event_name == 'workflow_dispatch' }}\n    strategy:\n      matrix:\n        os: ['ubuntu-22.04', 'macos-latest', 'windows-latest']\n\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: oven-sh/setup-bun@v2\n\n      - name: Install and test with Bun\n        run: |\n          bun -v\n          bun install\n          bun test --coverage\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n\ncoverage\n.dist\n.nue\ndist\n_test\ntest_dir\nperformance.js\n\n.DS_Store\n.idea\n.vscode\n.env"
  },
  {
    "path": ".prettierrc.yaml",
    "content": "printWidth: 100\ntabWidth: 2\nuseTabs: false\nsemi: false\nsingleQuote: true\nquoteProps: 'as-needed'\ntrailingComma: 'es5'\nbracketSpacing: true\narrowParens: 'avoid'\nrequirePragma: false\ninsertPragma: false\nproseWrap: 'preserve'\nhtmlWhitespaceSensitivity: 'css'\nendOfLine: 'lf'\nembeddedLanguageFormatting: 'auto'\nsingleAttributePerLine: false"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "\n\n## Details here\n\nhttps://nuejs.org/docs/contributing"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2025-present, Tero Piirainen\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "bunfig.toml",
    "content": "[test]\ncoveragePathIgnorePatterns = [\"**/test/**\", \"**/test_dir/**\"]\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"nue-monorepo\",\n  \"private\": true,\n  \"workspaces\": [ \"packages/*\" ]\n}\n"
  },
  {
    "path": "packages/nuedom/Makefile",
    "content": "\nrun-tests:\n\tbun test\n\ndemo:\n\t@ bun bin/serve\n\nminify:\n\t@ bun bin/minify.js\n\ncoverage:\n\tbun test --coverage\n\n\n\n\n"
  },
  {
    "path": "packages/nuedom/README.md",
    "content": "\n# Nuedom: HTML first UI assembly\nNuedom (or just \"Nue\") is a markup language that extends HTML with just enough syntax to build websites, apps and SVG images. It's a different development model based on document structure rather than programmatic composition.\n\n## UI assembly\nNue differs from React \"composition\" in both syntax and architecture:\n\n**HTML over JavaScript** - In Nue, your UI is a document tree with data bindings and event listeners. In React, it's JavaScript functions returning objects. This difference changes the way you think about application structure.\n\n**Standards first** - `<dialog>`, `<details>`, `<popover>`, form validation, scroll-snap, container queries. Modern HTML is interactive. Nue adds the missing pieces for dynamic behavior.\n\n**DOM based** - Nue's AST maps directly to DOM operations. No virtual DOM, no reconciliation. A button is a `<button>`, not a \"`<Button>`\" component with 50KB of dependencies and hundreds of megabytes of runtime.\n\n### How it looks\n\n```html\n<!doctype html>\n\n<!-- form with native validation -->\n<form :onsubmit=\"submit\">\n  <input type=\"email\" name=\"email\" required>\n  <textarea name=\"message\" minlength=\"10\" required></textarea>\n  <button>Send</button>\n\n  <script>\n    async submit(e) {\n      await fetch('/api/contact', {\n        body: new FormData(e.target),\n        method: 'POST'\n      })\n      success.showPopover()\n    }\n  </script>\n</form>\n\n<!-- native dialog for success -->\n<dialog id=\"success\" popover>\n  <h2>Message sent!</h2>\n  <button popovertarget=\"success\">Close</button>\n</dialog>\n```\n\nCheck [Nue website](https://nuejs.org/docs/nuedom) for details.\n"
  },
  {
    "path": "packages/nuedom/bin/minify.js",
    "content": "\nimport { version } from '../package.json' with { type: 'json' }\nconst year = new Date().getFullYear()\n\nconst banner = `/**\n * Nue v${version} - Copyright (c) ${year} Tero Piirainen and contributors\n *\n * Licensed under the MIT License\n */`\n\nfor (const name of ['nue', 'nue-jit']) {\n  try {\n    const result = await Bun.build({\n      entrypoints: [`src/${name}.js`],\n      naming: `[dir]/${name}.js`,\n      target: 'browser',\n      outdir: 'dist',\n      minify: true,\n      banner\n    })\n\n    if (result.success) console.info(`Minified dist/${name}.js`)\n    else {\n      console.error('Build failed:')\n      result.logs.forEach(log => console.error(log))\n      process.exit(1)\n    }\n\n  } catch (err) {\n    console.error(`Bundling error: ${err.message}`)\n    console.info(err)\n    process.exit(1)\n  }\n}\n"
  },
  {
    "path": "packages/nuedom/bin/serve.js",
    "content": "\nconst server = Bun.serve({\n  port: 4400,\n\n  routes: {\n    '/favicon.ico': async () => {\n      return new Response('', { headers: { 'Content-Type': 'text/plain' } })\n    },\n  },\n\n\n  fetch(req) {\n    let path = new URL(req.url).pathname\n    if (path.endsWith('/')) path += 'index.html'\n    return new Response(Bun.file('.' + path))\n  }\n})\n\nconsole.log('\\nDemo ready:\\n')\nconsole.log(`✅ Browser: ${server.url}test/\\n`)\nconsole.log(`✅ Editor:  test/index.html\\n`)\n"
  },
  {
    "path": "packages/nuedom/package.json",
    "content": "{\n  \"name\": \"nuedom\",\n  \"version\": \"0.1.0\",\n  \"description\": \"HTML first UI assembly\",\n  \"homepage\": \"https://nuejs.org/docs/nuedom\",\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n\n  \"main\": \"src/index.js\",\n  \"browser\": \"src/nue-jit.js\",\n\n  \"repository\": {\n    \"url\": \"https://github.com/nuejs/nue\",\n    \"directory\": \"packages/nuedom\",\n    \"type\": \"git\"\n  },\n\n  \"engines\": {\n    \"bun\": \">= 1.2\",\n    \"node\": \">= 20.0.0\"\n  },\n\n  \"scripts\": {\n    \"minify\": \"bun bin/minify.js\"\n  },\n\n  \"files\": [ \"src\" ]\n}\n"
  },
  {
    "path": "packages/nuedom/src/compiler/ast.js",
    "content": "\nimport { HTML5_TAGS, SVG_TAGS } from './html5.js'\nimport { parseAttributes } from './attributes.js'\nimport { addContext } from './context.js'\n\n\n// former tag.js / parseTag\nexport function createAST(block, imports) {\n  const { tag, children, text, meta } = block\n  if (text) return parseText(text, imports)\n\n  const { tagName, attr } = parseOpeningTag(tag)\n  if (tagName == 'slot') return { slot: true }\n\n  const specs = attr ? parseAttributes(attr, imports) : {}\n  const comp = { tag: tagName.trim(), ...specs }\n\n  if (meta) comp.meta = meta\n\n  const i = SVG_TAGS.findIndex(el => el.toLowerCase() == tagName.toLowerCase())\n\n  if (i >= 0) {\n    comp.svg = true\n\n    const correct = SVG_TAGS[i]\n    if (correct != tagName) {\n      comp.tag = correct\n      console.warn(`Fixed SVG case: ${tagName} -> ${correct}`)\n    }\n\n  } else if (tagName.includes('-') || !HTML5_TAGS.includes(tagName)) {\n    comp.is_custom = true\n  }\n\n  const mount = comp.attr?.find(a => a.name == 'mount')\n  if (mount) {\n    comp.attr.splice(comp.attr.indexOf(mount), 1)\n    comp.is_custom = true\n    comp.mount = mount\n  }\n\n  if (children.length) {\n    const ret = parseChildren(children, imports)\n    if (ret.script) comp.script = convertGetters(convertFunctions(ret.script))\n    if (ret.children.length) comp.children = ret.children\n  }\n  return comp\n}\n\nfunction parseOpeningTag(tag) {\n  tag = tag.replace('/>', '>')\n  const i = tag.indexOf(' ')\n  if (i == -1) return { tagName: tag.slice(1, -1) }\n  return { tagName: tag.slice(1, i), attr: tag.slice(i + 1, -1).trim() }\n}\n\nfunction parseChildren(arr, imports) {\n  const scriptEl = arr.find(el => el.tag == '<script>')\n  const children = arr.filter(el => el != scriptEl).map(el => createAST(el, imports))\n  const script = scriptEl ? scriptEl.children[0]?.text.trim() : ''\n  return { children: mergeConditionals(children), script }\n}\n\n\nfunction mergeConditionals(arr) {\n  return arr.reduce((result, current) => {\n    const is_if = current.if || current['else-if'] || current.else\n    const last = result[result.length - 1]\n\n    if (is_if) {\n      if (current.if) result.push({ some: [current] })\n      else if (last?.some) last.some.push(current)\n      else result.push({ some: [current] })\n    } else {\n      result.push(current)\n    }\n\n    return result\n  }, [])\n}\n\n\nfunction parseText(text, imports) {\n  if (text[0] == '{' && text.endsWith('}')) {\n    const is_html = text[1] == '{'\n    const am = is_html ? 2 : 1\n    const expr = { fn: addContext(text.slice(am, -am), imports).trim() }\n    if (is_html) expr.html = is_html\n    return expr\n  }\n  return { text }\n}\n\n\n\n\n// foo() {} --> this.foo = function() { }\nexport function convertFunctions(script) {\n  return script.replace(\n    /^( *)(async\\s+)?(\\w+)\\s*\\(([^)]*)\\)\\s*{/gm, (_, indent, asy, name, args) => {\n      if (_.includes('function') || ['for', 'if'].includes(name)) return _\n      return `${indent}this.${name} = ${asy ? 'async ' : ''}function(${args.trim()}) {`\n    }\n  )\n}\n\nexport function convertGetters(script) {\n  return script\n    // Handle multiline getters first\n    .replace(\n      /^( *)get (\\w+)\\(\\)\\s*\\{([\\s\\S]*?)\\n\\1\\}/gm,\n      (_, indent, name, body) => {\n        return `${indent}Object.defineProperty(this, '${name}', { get() {${body}} })`\n      }\n    )\n    // Then handle single-line getters\n    .replace(\n      /^( *)get (\\w+)\\(\\)\\s*\\{([^}]*)\\}/gm,\n      (_, indent, name, body) => {\n        return `${indent}Object.defineProperty(this, '${name}', { get() {${body}} })`\n      }\n    )\n}"
  },
  {
    "path": "packages/nuedom/src/compiler/attributes.js",
    "content": "\nimport { BOOLEAN, EVENTS, STRICT_ATTRS } from './html5.js'\nimport { addContext } from './context.js'\n\n\nexport function tokenizeAttr(attrStr) {\n return attrStr.match(/[^\\s=]+(=\"[^\"]*\"|=[^\\s]*|)/g)?.map(token =>\n   token.replace(/=\"([^\"]*)\"/, '=$1')\n ) || []\n}\n\nexport function parseAttributes(attrs, imports) {\n  const tokens = tokenizeAttr(attrs)\n  const result = {}\n\n  function push(key, val) {\n    const arr = result[key] = result[key] || []\n    arr.push(val)\n  }\n\n  for (const token of tokens) {\n    let { name, value } = parseNameVal(token)\n    const is_colon = name[0] == ':'\n    const base = is_colon ? name.slice(1) : name\n\n    if (name == ':each') {\n      result.for = parseFor(value, imports)\n\n    } else if (name == ':is') {\n      result.is = value\n\n    } else if (name == 'style') {\n      // ignore\n\n    } else if ([':if', ':else-if', ':else'].includes(name)) {\n      result[base] = base == 'else' ? true : addContext(value, imports)\n\n    // event handler\n    } else if (name.slice(0, 3) == ':on' && EVENTS.includes(name.slice(3))) {\n      if (!value) value = name.slice(1)\n      if (!/\\W/.test(value)) value += '($e)'\n      push('handlers', { name: base, h_fn: addContext(value, imports) })\n\n    } else {\n      const attr = { name: base }\n\n      // :href --> :href=\"href\"\n      if (is_colon && !value) value = base\n      const fn = is_colon && !value.includes('{') ? addContext(value, imports) : parseExpression(value, name, imports)\n\n      if (fn) attr.fn = fn\n      else attr.val = value\n\n      if (name.startsWith('--')) {\n        attr.name = name.slice(2)\n        attr.is_var = true\n\n      } else if (BOOLEAN.includes(base)) {\n        attr.bool = true\n        if (!fn && !value) attr.val = true\n\n      } else if (is_colon) {\n        attr.is_data = true\n\n      } else {\n        const correct = STRICT_ATTRS.find(el => el.toLowerCase() == name.toLowerCase())\n\n        if (correct && name != correct) {\n          attr.name = correct\n          console.warn(`Fixed SVG case: ${name} -> ${correct}`)\n        }\n      }\n\n      push('attr', attr)\n    }\n  }\n  return result\n}\n\nfunction parseNameVal(attr) {\n  const i = attr.indexOf('=')\n  if (i == -1) return { name: attr, value: '' }\n\n  const name = attr.slice(0, i)\n  const value = attr.slice(i + 1)\n  return { name, value }\n}\n\nexport function parseFor(str, imports) {\n  const [args, _, expr] = str.split(/\\s+(in|of)\\s+/)\n  const for_args = parseForArgs(args.trim())\n  const fn = addContext(expr, imports)\n  return { fn, ...for_args }\n}\n\n\nexport function parseForArgs(args) {\n  // Clean parentheses first, which fixes the failing test\n  const cleaned = args.replace(/^\\(|\\)$/g, '');\n  const hasIndex = /[}\\]],/.exec(cleaned) || (!'{['.includes(cleaned[0]) && cleaned.includes(','));\n  const keys = cleaned.replace(/[(){}[\\]]/g, '').split(',').map(part => part.trim());\n  const is_entries = cleaned.includes('[');\n  return hasIndex ? { keys: keys.slice(0, -1), index: keys.pop(), is_entries } : { keys, is_entries };\n}\n\nexport function parseExpression(str, attr_name, imports) {\n const parts = str.split(/(\\{[^{}]*\\}|\\[[^\\[\\]]*\\])/)\n if (parts.length < 2) return\n\n return parts.map(part => {\n   if (part[0] == '[' && attr_name == 'class') {\n     return parseClassHelper(part.slice(1, -1).trim(), imports)\n   } else if (part[0] == '{') {\n     return `(${ addContext(part.slice(1, -1).trim(), imports) })`\n   }\n   return part ? `\"${part}\"` : ''\n\n }).filter(part => part !== '').join(' + ')\n}\n\nexport function parseClassHelper(val, imports) {\n  const pairs = val.split(',').map(pair => pair.trim())\n\n  const obj = pairs.map(pair => {\n    let [name, expr] = pair.split(':').map(s => s.trim())\n    if (!expr) expr = name\n    if (name.includes('-')) name = `\"${ name }\"`\n    return `${ name }: ${ addContext(expr, imports) }`\n  })\n\n  return `_.$concat({${obj.join(', ')}})`\n}\n\n\n"
  },
  {
    "path": "packages/nuedom/src/compiler/compiler.js",
    "content": "\n/* Compiles template string to JavaScript object (AST) */\nimport { inspect } from 'node:util'\nimport { parseNue } from './document.js'\n\n\nexport function compileNue(template) {\n  const doc = typeof template == 'string' ? parseNue(template) : template\n  return compileDoc(doc)\n}\n\nfunction compileDoc(doc) {\n  const { script, lib } = doc\n  const js = []\n\n  if (script) js.push(script)\n\n  const lib_str = compileJS(inspect(lib, { compact: true, depth: Infinity }))\n  js.push(`export const lib = ${ lib_str }`)\n\n  return js.join('\\n')\n}\n\n\n\nconst RE_FN = /(script|h_fn|fn):\\s*(['\"`])([^\\2]*?)\\2/g\n\nexport function compileJS(js) {\n  return js.replace(RE_FN, function(_, key, __, expr) {\n    return key == 'script' ? `${key}: function() { ${expr.trim().replaceAll('\\\\n', '\\n')} \\n\\t\\t}`\n      : `${key}: ${compileFn(expr, key[0] == 'h')}`\n  })\n}\n\n// compiler.test.js: _.foo + 1 --> _=>(_.foo + 1)\nexport function compileFn(str, is_event) {\n  str = str.trim()\n\n  const word = str.startsWith('_.') ? str.slice(2) : str\n\n  const is_simple = !/\\W/.test(word)\n\n  if (is_event) {\n    return '(_,$e)=>' + (is_simple ? `${str}($e)`\n      : str.includes(';') ? `{${str}}`\n      : str\n    )\n  }\n  return '_=>' + (is_simple ? str : `(${str})`)\n}\n\n\n"
  },
  {
    "path": "packages/nuedom/src/compiler/context.js",
    "content": "\nimport { JS } from './html5.js'\n\n\nconst CONTEXT_RE = /'[^']*'|\"[^\"]*\"|[a-zA-Z_$][a-zA-Z0-9_$]*(?:\\.[a-zA-Z_$][a-zA-Z0-9_$]*)*/g\n\nexport function addContext(expr, exceptions = ['state']) {\n  const reserved = new Set([...JS, ...exceptions ])\n\n  return expr.replace(CONTEXT_RE, (match, offset, str) => {\n\n    if (match.includes('$event')) return match.replaceAll('$event', '$e')\n    if (match == 'this') return '_'\n\n    // Skip string literals\n    if (match[0] == '\"' || match[0] == \"'\") return match\n\n    // Skip property keys\n    if (str[offset + match.length] == ':') return match\n\n    // Skip if preceded by . or /\n    if (offset > 0 && /[.\\/]/.test(str[offset - 1])) return match\n\n    const root = match.split('.')[0]\n\n    return reserved.has(root) ? match : `_.${match}`\n  })\n}\n\n"
  },
  {
    "path": "packages/nuedom/src/compiler/document.js",
    "content": "\nimport { createAST } from './ast.js'\nimport { tokenize } from './tokenizer.js'\n\n/*\n  {\n    doctype: 'html',\n    script: 'imort { foo, bar } from ...',\n    meta: {},\n    lib: []\n  }\n*/\nexport function parseNue(template) {\n  const tokens = tokenize(template)\n  const blocks = parseBlocks(tokens)\n  const script = []\n  const page = {}\n  let lib = []\n\n  blocks.forEach((el, i) => {\n\n    if (el.doctype) {\n      page.doctype = el.doctype\n\n    } else if (el.tag) {\n      if (isScript(el)) {\n        script.push(el.children[0].text.trim())\n\n      } else {\n        lib.push(el)\n      }\n\n    } else if (el.meta) {\n      const next = blocks[i + 1]\n      const target = next?.tag  && !isScript(next) ? next : page\n      target.meta = el.meta\n    }\n  })\n\n  page.script = script.join('\\n')\n\n  // reserved names\n  const names = parseNames(page.script)\n  lib = page.lib = lib.map(el => createAST(el, names))\n\n  // root\n  page.root = lib[0]\n\n  // all custom elements\n  const type = page.doctype || ''\n  const all_custom = lib.every(ast => ast.is_custom || ast.is)\n  page.is_lib = all_custom || type.endsWith('lib')\n\n  page.is_dhtml = type.includes('dhtml')\n    || lib.some(ast => ast.handlers?.length > 0)\n    || page.script?.includes('import ')\n\n  return page\n}\n\nfunction isScript(block) {\n  return block.tag?.startsWith('<script')\n}\n\nexport function parseBlocks(tokens) {\n  const blocks = []\n  let i = 0\n\n  while (i < tokens.length) {\n    if (tokens[i].meta) {\n      blocks.push(tokens[i])\n      i++\n    } else {\n      const result = parseBlock(tokens, i)\n      if (result.node) blocks.push(result.node)\n      i = result.next\n    }\n  }\n\n  return blocks\n}\n\n\nfunction parseBlock(tokens, i) {\n  const tag = tokens[i]\n\n  if (i >= tokens.length || !tag.startsWith('<') || tag.startsWith('</')) return { next: i + 1 }\n  i++\n\n  const low = tag.toLowerCase()\n\n  // !doctype\n  if (tag.startsWith('<!')) {\n    const node = { doctype: low.slice(2, -1).replace('doctype', '').trim() }\n    return { node, next: i++ }\n  }\n\n  if (low.startsWith('<?xml')) {\n    const node = { xml: true }\n    return { node, next: i++ }\n  }\n\n  // ignore <style> blocks\n  if (low.startsWith('<style')) return { next: i }\n\n  if (tag.endsWith('/>')) return { node: { tag, children: [] }, next: i }\n\n  const children = []\n\n  while (i < tokens.length && (tokens[i].meta || !tokens[i].startsWith('</'))) {\n    if (tokens[i].meta) {\n      i++\n    } else if (tokens[i].startsWith('<') && !tokens[i].startsWith('</')) {\n      const result = parseBlock(tokens, i)\n      if (result.node) children.push(result.node)\n      i = result.next\n    } else {\n      children.push({ text: tokens[i] })\n      i++\n    }\n  }\n\n  if (i < tokens.length) i++ // Skip closing tag\n\n  return { node: { tag, children }, next: i }\n}\n\n\nexport function parseNames(script) {\n  const lines = script.trim().split('\\n')\n  const arr = []\n  for (const line of lines) {\n    if (line.trim().startsWith('//')) continue\n    arr.push(\n      ...getFunctionNames(line),\n      ...getVariableNames(line)\n    )\n  }\n  return arr\n}\n\nfunction getVariableNames(line) {\n  // Match destructuring: import { ... } or const { ... } =\n  const destructMatch = line.match(/(import|const|var|let)\\s*{\\s*([^}]+)\\s*}/)\n  if (destructMatch) {\n    return destructMatch[2].split(',').map(el => {\n      return el.includes(' as ') ? el.split(' as ')[1].trim() : el.trim()\n    })\n  }\n\n  // Match regular declarations: const FOO =\n  const regularMatch = line.match(/(const|var|let)\\s+([A-Za-z_$][A-Za-z0-9_$]*)\\s*=/)\n  if (regularMatch) {\n    return [regularMatch[2]]\n  }\n\n  return []\n}\n\nfunction getFunctionNames(line) {\n  const match = line.match(/function\\s*([^\\(]+)\\s*\\(/)\n  return match ? [match[1]] : []\n}\n\n\n"
  },
  {
    "path": "packages/nuedom/src/compiler/html5.js",
    "content": "\n// Web platform constants (HTML5, SVG, DOM APIs)\n\nexport const JS = '_ $e document window location localStorage sessionStorage alert Array Boolean Date Error false in Infinity instanceof isFinite isNaN JSON Math NaN new null Number Object parseFloat parseInt String true typeof undefined history navigator screen scrollTo scrollBy innerWidth innerHeight outerWidth outerHeight console'.split(' ')\n\nexport const EVENTS = 'click submit change input focus blur keydown keyup keypress mouseover mouseout mousedown mouseup mousemove mouseenter mouseleave wheel scroll resize load unload beforeunload error abort touchstart touchend touchmove touchcancel drag dragstart dragend dragenter dragleave dragover drop animationstart animationend animationiteration transitionend contextmenu dblclick pointerdown pointermove cut copy paste'.split(' ')\n\nexport const BOOLEAN = 'disabled checked selected hidden readonly required autofocus autoplay async controls defer loop multiple muted nowrap open reversed scoped seamless sorted translate visibility pointer-events draggable contenteditable'.split(' ')\n\n\nexport const HTML5_TAGS = 'a abbr address area article aside audio b base bdi bdo blockquote body br button canvas caption cite code col colgroup data datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hr html i iframe img input ins kbd label legend li link main map mark meta meter nav noscript object ol optgroup option output p param picture pre progress q rp rt ruby s samp script section select small source span strong style sub summary sup table tbody td template textarea tfoot th thead time title tr track u ul var video wbr slot portal'.split(' ')\n\n\nexport const SVG_TAGS = 'animate animateMotion animateTransform circle clipPath defs desc ellipse\\\n feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting\\\n feDisplacementMap feDistantLight feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR\\\n feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting\\\n feSpotLight feTile feTurbulence filter foreignObject g hatch hatchPath image line linearGradient\\\n marker mask metadata mpath path pattern polygon polyline radialGradient rect set stop style svg\\\n switch symbol text textPath title tspan use view'.split(' ')\n\nexport const SELF_CLOSING = 'img br hr input meta link area base col embed keygen param source track wbr'.split(' ')\n\n// case sensitive attributes (bad casd breaks rendering)\nexport const STRICT_ATTRS = 'viewBox preserveAspectRatio'.split(' ')\n\n\n/*\nexport const ATTR = 'class id style lang dir title accesskey tabindex href src alt type value name for rel target action method placeholder pattern min max step width height poster media sizes srcset content role form download'.split(' ')\n\nexport const SVG_ATTR = 'x y cx cy r rx ry d points fill stroke stroke-width transform dx dy text-anchor viewBox preserveAspectRatio clip-path xmlns opacity font-size mask filter'.split(' ')\n\n ATTR.push(...SVG_ATTR)\n*/\n\n"
  },
  {
    "path": "packages/nuedom/src/compiler/tokenizer.js",
    "content": "\nimport { SELF_CLOSING } from './html5.js'\n\n\nexport function tokenize(template) {\n  template = template.trim()\n  const tokens = []\n  let pos = 0\n\n  while (pos < template.length) {\n    const char = template[pos]\n\n    if (char == '<' && template.slice(pos, pos + 4) == '<!--') {\n      pos = parseComment(template, pos, tokens)\n\n    } else if (char == '<') {\n      if (template.slice(pos, pos + 7) == '<script') {\n        pos = addScript(template, pos, tokens)\n      } else {\n        pos = addTag(template, pos, tokens)\n      }\n    } else if (template[pos] == '{') {\n      pos = addExpr(template, pos, tokens)\n\n    } else {\n      pos = addText(template, pos, tokens)\n    }\n  }\n\n  return tokens.filter(el => el.meta || el.trim())\n}\n\nfunction parseComment(template, pos, tokens) {\n  const end = template.indexOf('-->', pos + 4)\n  if (end == -1) throw new SyntaxError(`Unclosed comment at ${pos}`)\n\n  // annotations\n  const meta = parseAnnotations(template.slice(pos + 4, end))\n  if (meta) tokens.push({ meta })\n\n  return end + 3\n}\n\n\nfunction parseAnnotations(comment) {\n  const lines = comment.split('\\n')\n  const ret = {}\n  lines.forEach(line => {\n    const trimmed = line.trim()\n    if (trimmed[0] == '@') {\n      const match = trimmed.match(/^@(\\w+)(?:\\s+(.+))?$/)\n      if (match) {\n        const [, key, value] = match\n        ret[key] = value || true\n      }\n    }\n  })\n  return Object.keys(ret).length ? ret : null\n}\n\nfunction addTag(template, pos, tokens) {\n\n  let i = pos + 1\n  let quote = null\n  while (i < template.length) {\n    if (\"'\\\"\".includes(template[i]) && (i == 0 || template[i - 1] != '\\\\')) {\n      quote = quote == null ? template[i] : null\n    }\n    if (template[i] == '>' && quote == null) {\n      i++\n      tokens.push(toSelfClosing(template.slice(pos, i)))\n      return i\n    }\n    if (template[i] == '<' && i > pos && quote == null) {\n      throwSyntaxError(pos, template.slice(pos, i + 10) + \"...\")\n    }\n    i++\n  }\n  throwSyntaxError(pos, template.slice(pos, pos + 10) + \"...\")\n}\n\n\n// <img>, <img src=\"a\">\nfunction toSelfClosing(tag) {\n  let name = tag.slice(1, -1)\n  const i = name.indexOf(' ')\n  if (i > 0) name = name.slice(0, i)\n  return SELF_CLOSING.includes(name) ? tag.slice(0, -1) + '/>' : tag\n}\n\nfunction addScript(template, pos, tokens) {\n  const tagEnd = template.indexOf('>', pos)\n  if (tagEnd == -1) throw new SyntaxError(`Unclosed script tag at ${pos}`)\n  tokens.push(template.slice(pos, tagEnd + 1)) // Add <script> with attributes\n  pos = tagEnd + 1\n\n  const contentStart = pos\n  const scriptEnd = template.indexOf('</script>', pos)\n  if (scriptEnd == -1) throw new SyntaxError(`Missing </script>`)\n  tokens.push(template.slice(contentStart, scriptEnd)) // Add script content as one token\n  tokens.push('</script>')\n  return scriptEnd + 9 // Move past </script>\n}\n\n\nfunction addExpr(template, pos, tokens) {\n  const start = template.slice(pos, pos + 3)\n  let braces = 1\n  if (start.startsWith('{{{')) braces = 3\n  else if (start.startsWith('{{')) braces = 2\n\n  const closing = '}'.repeat(braces)\n  const i = template.indexOf(closing, pos + braces)\n  if (i == -1) throw new SyntaxError(`Unclosed expression at ${pos}`)\n\n  const end = i + braces\n  let token = template.slice(pos, end)\n\n  // normalize {{{ foo }}} to {{ foo }}\n  if (braces == 3) token = '{{' + token.slice(3, -3) + '}}'\n\n  tokens.push(token)\n  return end\n}\n\nfunction addText(template, pos, tokens) {\n  let i = pos\n  while (i < template.length) {\n    if (template[i] == '<' || (i + 1 < template.length && template[i] == '{')) break\n    i++\n  }\n  tokens.push(template.slice(pos, i))\n  return i\n}\n\nfunction throwSyntaxError(pos, snippet) {\n  throw new SyntaxError(`Unclosed tag at position ${pos}: \"${snippet}\"`)\n}\n\n"
  },
  {
    "path": "packages/nuedom/src/dom/diff.js",
    "content": "\n// Nue • (c) 2025 Tero Piirainen & contributors, MIT Licensed\n\n/*\n  Universal DOM diff optimized for simplicity and performance:\n\n  1. No VDOM overhead\n     Diffs DOM nodes directly, skipping virtual tree creation\n\n  2. Targeted updates\n     Only changes attributes and children that differ\n\n  3. Keyed diffs\n     Uses keys to reuse nodes like React, but without component lifecycle baggage\n\n  4. Positional fallback\n     Handles non-keyed cases with simple, linear iteration\n\n  5. Minimal Memory\n     Brutally simple: just two Array.from() calls, no deep cloning or state tracking\n\n  6. Raw JavaScript\n     Low-level control over output (no extra transpilile step)\n*/\n\nexport function domdiff(prev, next) {\n  const parent = prev.parentNode\n  if (prev == next) return prev\n  if (!prev && next) return parent?.appendChild(next)\n  if (!next && prev) return parent?.removeChild(prev)\n\n  if (prev.nodeType == 3 && next.nodeType == 3) {\n    prev.textContent = next.textContent\n    return prev\n  }\n  if (prev.nodeType != next.nodeType || prev.tagName != next.tagName) {\n    return parent.replaceChild(next, prev)\n  }\n\n  updateAttributes(prev, next)\n  const kids = Array.from(next.childNodes)\n  kids[0]?.getAttribute?.('key') ? diffChildrenByKey(prev, kids) : diffChildren(prev, kids)\n  return prev\n}\n\nfunction updateAttributes(prev, next) {\n  for (let attr of next.attributes) {\n    if (prev.getAttribute(attr.name) != attr.value)\n      prev.setAttribute(attr.name, attr.value)\n  }\n  for (let attr of prev.attributes) {\n    if (!next.hasAttribute(attr.name))\n      prev.removeAttribute(attr.name)\n  }\n}\n\nfunction diffChildren(prev, kids) {\n  const prevKids = Array.from(prev.childNodes)\n  const len = Math.max(prevKids.length, kids.length)\n  for (let i = 0; i < len; i++) {\n    const prevKid = prevKids[i]\n    const kid = kids[i]\n    if (!prevKid) prev.appendChild(kid)\n    else if (!kid) prev.removeChild(prevKids[i])\n    else domdiff(prevKid, kid, prev)\n  }\n}\n\nfunction diffChildrenByKey(prev, kids) {\n  const prevKids = Array.from(prev.childNodes)\n  const keyMap = {}\n  for (let kid of prevKids) keyMap[kid.getAttribute('key')] = kid\n  while (prev.firstChild) prev.removeChild(prev.firstChild)\n  for (let kid of kids) {\n    const key = kid.getAttribute('key')\n    const prevKid = keyMap[key]\n    prev.appendChild(prevKid ? domdiff(prevKid, kid, prev) : kid)\n  }\n}"
  },
  {
    "path": "packages/nuedom/src/dom/fakedom.js",
    "content": "\n// TODO: use html5.js\nconst caseSensitive = ['foreignObject', 'clipPath', 'linearGradient', 'radialGradient', 'textPath', 'animateMotion', 'animateTransform']\n\nconst voidTags = ['img', 'br', 'hr', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'source', 'track', 'wbr']\n\nfunction createNode(nodeType, tagName = null) {\n  return {\n    nodeType, // 1=element, 3=text, 11=fragment\n    tagName: tagName?.toUpperCase(),\n    innerHTML: '',\n    children: [],\n    childNodes: [],\n    attributes: new AttributeMap(),\n    classList: createClassList(),\n    parentNode: null\n  }\n}\n\nclass AttributeMap extends Map {\n  *[Symbol.iterator]() {\n    for (const [name, value] of super[Symbol.iterator]()) {\n      yield { name, value }\n    }\n  }\n}\n\nfunction createClassList() {\n  const classes = new Set()\n  return {\n    classes,\n    add(...names) {\n      names.forEach(name => { if (name) classes.add(name) })\n    },\n    get length() { return classes.size },\n    toString() { return Array.from(classes).join(' ') }\n  }\n}\n\nfunction serialize(node) {\n  if (node.nodeType == 3) return node._textContent || node.textContent\n\n  // fragments\n  if (node.nodeType == 11) return node.children.map(serialize).join('')\n\n  const lowerTag = node.tagName.toLowerCase()\n  const tag = caseSensitive.find(name => name.toLowerCase() == lowerTag) || lowerTag\n\n  const attrs = []\n\n  // add classList if it has classes and no class attribute already set\n  if (node.classList && node.classList.length > 0 && !node.attributes.has('class')) {\n    attrs.push(`class=\"${node.classList.toString()}\"`)\n  }\n\n  for (const attr of node.attributes) {\n    attrs.push(`${attr.name}=\"${attr.value}\"`)\n  }\n\n  const attrStr = attrs.length ? ' ' + attrs.join(' ') : ''\n  const children = node.children.map(serialize).join('')\n\n  // self-closing tags\n  if (voidTags.includes(tag)) return `<${tag}${attrStr}>`\n\n  return `<${tag}${attrStr}>${children}</${tag}>`\n}\n\nfunction createElement(tag, nodeType = 1) {\n  const base = createNode(nodeType, tag)\n  const fns = {}\n\n  const element = {\n    ...base,\n\n    // methods\n    appendChild(child) {\n      if (!child) return child\n      child.parentNode = this\n      this.children.push(child)\n      this.childNodes.push(child)\n      return child\n    },\n\n    removeChild(child) {\n      const index = this.children.indexOf(child)\n      if (index > -1) {\n        this.children.splice(index, 1)\n        this.childNodes.splice(index, 1)\n        child.parentNode = null\n      }\n      return child\n    },\n\n    replaceChild(newChild, oldChild) {\n      const index = this.children.indexOf(oldChild)\n      if (index > -1) {\n        this.children[index] = newChild\n        this.childNodes[index] = newChild\n        newChild.parentNode = this\n        oldChild.parentNode = null\n      }\n      return oldChild\n    },\n\n    setAttribute(name, value) {\n      this.attributes.set(name, String(value))\n\n      if (name == 'class') {\n        this.classList.classes.clear()\n        this.classList.add(value)\n      }\n    },\n\n    getAttribute(name) {\n      return this.attributes.get(name) || null\n    },\n\n    hasAttribute(name) {\n      return this.attributes.has(name)\n    },\n\n    removeAttribute(name) {\n      this.attributes.delete(name)\n      if (name == 'class') {\n        this.classList.classes.clear()\n      }\n    },\n\n    replaceWith(...nodes) {\n      if (!this.parentNode) return\n\n      const parent = this.parentNode\n      const index = parent.children.indexOf(this)\n\n      if (index > -1) {\n        parent.children.splice(index, 1, ...nodes)\n        parent.childNodes.splice(index, 1, ...nodes)\n        nodes.forEach(node => node.parentNode = parent)\n        this.parentNode = null\n      }\n    },\n\n    // only element selectors needed (on test suite)\n    querySelector(query) {\n      if (this.tagName == query.toUpperCase()) return this\n\n      for (let child of this.children) {\n        const el = child.querySelector?.(query)\n        if (el) return el\n      }\n    },\n\n    // event handling\n    addEventListener(name, fn) {\n      fns[name] = fn\n    },\n\n    dispatchEvent(e) {\n      const fn = fns[e.type]\n      fn?.({ target: this, ...e })\n    },\n\n    // getters\n    get firstChild() {\n      return this.childNodes[0]\n    },\n\n    get innerHTML() {\n      return this.children.map(serialize).join('')\n    },\n\n    get outerHTML() {\n      return serialize(this)\n    },\n\n    // Add this to your createElement function in the element object:\n    get textContent() {\n      let text = ''\n      for (let child of this.childNodes) {\n        text += child.textContent || ''\n      }\n      return text\n    },\n\n    set innerHTML(html) {\n      this.children.length = 0\n      this.childNodes.length = 0\n\n      // TODO: html parsing instead of textContent\n      if (html) {\n        const textNode = createNode(3)\n        textNode.textContent = html\n        this.appendChild(textNode)\n      }\n    }\n  }\n\n  return element\n}\n\nexport function createDocument() {\n  return {\n    createElement,\n\n    createElementNS(ns, tag) {\n      return createElement(tag) // simplified\n    },\n\n\n    createTextNode(text) {\n      const node = createNode(3)\n      node.textContent = text ?? ''\n      return node\n    },\n\n    createDocumentFragment() {\n      return createElement(null, 11)\n    },\n\n    body: createElement('body')\n  }\n}"
  },
  {
    "path": "packages/nuedom/src/dom/node.js",
    "content": "\n// Nue • (c) 2025 Tero Piirainen & contributors, MIT Licensed\n\nimport { domdiff } from './diff.js'\n\nconst is_browser = typeof window == 'object'\n\n\nexport function createNode(ast, data={}, opts={}, parent) {\n  const { script } = ast\n  let root\n\n  function update(values) {\n    if (values) Object.assign(self, values)\n\n    if (fire('onupdate') !== false) {\n      const next = render(ast, self).firstChild\n\n      // Domino: cannot be swapped (TODO: check with fakedom)\n      if (is_browser || parent) domdiff(root, next, root.parentNode)\n      else domdiff(root.firstChild, next, root)\n\n      fire('updated')\n    }\n  }\n\n  // Object.assign(self, getAttrData(ast, self))\n  const self = { ...data, ...opts.globals, ...getAttrData(ast, data), update, parent }\n\n\n  if (script) {\n    try {\n      if (typeof script == 'string') new Function(script).call(self)\n      else script.call(self)\n\n    } catch (e) {\n      console.error('<script> error:', script, e)\n    }\n  }\n\n  function fire(name, wrap) {\n    const fn = self[name]\n    if (wrap?.tagName) root = self.root = wrap\n    if (is_browser && fn && typeof fn == 'function') return fn.call(self, self, update)\n  }\n\n  function mount(wrap) {\n    fire('onmount', wrap)\n    const frag = render(ast, self)\n    const root = frag.firstChild\n    is_browser ? wrap.replaceWith(root) : wrap.appendChild(root)\n    if (is_browser) fire('mounted', root)\n    return root\n  }\n\n  self.mount = function(name, wrap, data) {\n    if (typeof wrap == 'string') wrap = root?.querySelector(wrap)\n    const ast = opts.deps?.find(c => name == (c.is || c.tag))\n\n    // convert to <div> (TODO: do this at compile time)\n    if (ast.is_custom) { ast.is = ast.tag; ast.tag = 'div'; delete ast.is_custom }\n\n    const block = createNode(ast, data, opts, self)\n    block.mount(wrap)\n  }\n\n\n  function render(_ast=ast, data=self) {\n    return _ast.text || _ast.fn ? renderText(_ast, data) :\n      _ast.some ? renderIf(_ast, data) :\n      _ast.for ? renderLoop(_ast, data) :\n      _ast.is_custom ? renderComponent(_ast, data) :\n      _ast.tag ? renderTag(_ast, data) :\n      createFragment()\n  }\n\n  function renderTag(ast, self) {\n    const tag = ast.svg ? document.createElementNS('http://www.w3.org/2000/svg', ast.tag) :\n      document.createElement(ast.tag)\n\n    setAttributes(tag, ast, self)\n    if (parent && ast.is_child) setAttributes(tag, parent.ast, parent.self)\n\n    const am = tag.classList.length\n    if (am > (opts.max_class_names || 3)) {\n      console.error(`More than ${am} class names in class=\"${tag.classList }\"`)\n    }\n\n    const cls = tag.classList?.toString()\n    if (/[:\\[\\]]/.test(cls)) {\n      console.error(`Invalid characters in class name: ${cls}`)\n    }\n\n\n    ast.handlers?.forEach(h => {\n      const name = h.name.slice(2) // onclick --> click\n\n      tag.addEventListener(name, function(e) {\n        if (name == 'submit') e.preventDefault()\n        exec(h.h_fn, self, e)\n        update()\n      })\n    })\n\n    ast.children?.forEach((child, i) => {\n      if (child.slot) {\n        // slot content from opts\n        if (opts.slot) return tag.appendChild(renderHTML(opts.slot))\n\n        parent?.ast.children?.forEach((node, i) => {\n          tag.appendChild(render(node, parent.self))\n          addSpace(tag, node, parent.ast.children[i + 1])\n        })\n      } else {\n        tag.appendChild(render(child, self))\n      }\n      addSpace(tag, child, ast.children[i+1])\n    })\n\n    const frag = createFragment()\n    frag.appendChild(tag)\n    return frag\n  }\n\n\n  function renderIf(ast, self) {\n\n    const child = ast.some.find(el => {\n      const fn = el.if || el['else-if']\n      if (fn) {\n        const ret = exec(fn, self)\n        return ret && ret != '[Error]'\n      } else if (el.else) {\n        return true\n      }\n    })\n\n    if (!child) return createFragment()\n\n    if (child.tag == 'template') {\n      const frag = createFragment()\n      child.children.forEach(node => {\n        frag.appendChild(render(node, self))\n      })\n      return frag\n    } else {\n      return render(child, self)\n    }\n  }\n\n  function renderLoop(impl, self) {\n    const ast = { ...impl }\n    const { keys, is_entries, index, fn } = ast.for\n    delete ast.for\n\n    const items = exec(fn, self) || []\n    const frag = createFragment()\n    const is_template = ast.tag == 'template'\n\n    items.forEach((item, i) => {\n      // set loop variables\n      const child = { ...self }\n      if (index) child[index] = i\n      if (keys[1]) keys.forEach((key, i ) => child[key] = item[is_entries ? i : key])\n      else child[keys[0]] = item\n\n      if (is_template) {\n        ast.children.forEach(node => {\n          frag.appendChild(render(node, child))\n        })\n      } else {\n        frag.appendChild(render(ast, child))\n      }\n    })\n    return frag\n  }\n\n  function renderComponent(ast, self) {\n\n    const attr_data = getAttrData(ast, self)\n\n    // render function?\n    const fn = opts.fns && opts.fns[ast.tag]\n    if (fn) return renderHTML(fn({ ...attr_data, ...self }, opts))\n\n    // find component\n    const comp = findComponent(ast, self)\n    if (!comp) return renderHTML(renderClientStub(ast.tag, attr_data))\n\n    const tag = ast.mount && ast.tag != 'template' ? ast.tag : comp.is ? comp.tag : 'div'\n\n    const block = createNode(\n      { ...comp, tag, is_custom: false, is_child: true },\n      attr_data,\n      opts,\n      createNode(ast, self, opts, parent)\n    )\n\n    const frag = block.render()\n    block.fire('onmount', frag.firstChild)\n    block.fire('mounted')\n    return frag\n  }\n\n  function findComponent(ast, self) {\n    let { mount, tag } = ast\n    if (mount) tag = mount.fn ? exec(mount.fn, self) : mount.val\n    return opts.deps?.find(c => c != ast && tag == (c.is || c.tag))\n  }\n\n  return {\n    mount, update, render, ast, self, fire, get root() { return root },\n  }\n}\n\nfunction renderText(ast, self) {\n  const val = ast.fn ? exec(ast.fn, self) : ast.text || ''\n  return ast.html ? renderHTML(val) : document.createTextNode(val)\n}\n\nfunction getAttrData(ast, self) {\n\n  const ret = {}\n  ast.attr?.forEach(a => {\n    const val = a.fn ? exec(a.fn, { ...self, $concat }) : a.val\n    if (a.name == 'bind') {\n      if (typeof val == 'object') Object.assign(ret, val)\n    } else {\n      ret[a.name] = val\n    }\n  })\n  return ret\n}\n\nfunction setAttributes(el, ast, self) {\n  const vars = []\n\n  ast.attr?.forEach(a => {\n    if (a.is_data) return\n    let { name, val, fn } = a\n    if (fn) val = exec(fn, { ...self, $concat })\n\n    if (a.is_var) {\n      vars.push({ name, val })\n    } else if (a.bool) {\n      if (val) el.setAttribute(name, '')\n    } else if (name == 'class') {\n      el.classList.add(...val.trim().split(/ +/))\n    } else {\n      el.setAttribute(name, val)\n    }\n  })\n\n  if (vars.length) {\n    el.setAttribute('style', vars.map(v => `--${v.name}:${v.val};`).join(''))\n  }\n}\n\nfunction exec(fn, self, e) {\n  try {\n    if (typeof fn == 'string') fn = new Function('_', '$e', 'return ' + fn)\n    let val = fn(self, e)\n    if (typeof val == 'string') val = val.replaceAll('undefined', '')\n\n    return val == null ? '' : Number.isNaN(val) ? 'N/A' : val\n  } catch (e) {\n    console.error('Nue error:', e.message)\n    return '[Error]'\n  }\n}\n\nfunction renderHTML(html) {\n  const frag = document.createDocumentFragment()\n  frag.innerHTML = html || ''\n  return frag\n}\n\nfunction $concat(obj) {\n  return Object.keys(obj).map(key => obj[key] ? key : '').filter(key => !!key).join(' ')\n}\n\nfunction createFragment() {\n  return document.createDocumentFragment()\n}\n\nfunction addSpace(to, child, next) {\n  if (child.tag && next?.tag || child.fn && next?.fn) {\n    to.appendChild(document.createTextNode(' '))\n  }\n}\n\nfunction renderClientStub(tag, self) {\n  const json = JSON.stringify(self)\n  const js = json != '{}' ? `<script type=\"application/json\">${json}</script>` : ''\n  return `<${tag} nue=\"${tag}\"></${tag}>${js}`\n}\n\n"
  },
  {
    "path": "packages/nuedom/src/dom/render.js",
    "content": "\nimport { createDocument } from './fakedom.js'\nimport { parseNue } from '../compiler/document.js'\nimport { createNode } from './node.js'\n\nfunction renderAST(ast, opts={}) {\n  const { root } = mountAST(ast, opts)\n  return root.innerHTML\n}\n\nexport function renderNue(template, opts={}) {\n  if (typeof template != 'string') return renderAST(template, opts)\n  const { lib } = parseNue(template)\n  const { deps=[] } = opts\n  opts.deps = [...lib.slice(1), ...deps]\n  return renderAST(lib[0], opts)\n}\n\n// exported for testing purposes only\nexport function mountAST(ast, opts) {\n  const dep = opts.deps?.find(c => ast.tag == (c.is || c.tag))\n\n  if (ast.is_custom && (!dep && !ast.mount || ast == dep)) {\n    ast = Object.assign({}, ast)\n    delete ast.is_custom;\n    ast.tag = 'div'\n  }\n\n  global.document = createDocument()\n  const node = createNode(ast, opts.data, opts)\n  node.mount(document.body)\n  return node\n}\n\n"
  },
  {
    "path": "packages/nuedom/src/index.js",
    "content": "\n// Server-side API\nexport { renderNue } from './dom/render.js'\nexport { parseNue } from './compiler/document.js'\nexport { compileNue } from './compiler/compiler.js'\nexport { createDocument } from './dom/fakedom.js'\n"
  },
  {
    "path": "packages/nuedom/src/nue-jit.js",
    "content": "\n// The browser API with compiler\nimport { parseNue } from './compiler/document.js'\nimport { createNode } from './dom/node.js'\n\nconst template = document.querySelector('template')?.innerHTML\n\nif (template) {\n  const { root, lib } = parseNue(template)\n  const app = createNode(root, {}, { deps: lib })\n  const wrap = document.createElement('div')\n  document.body.appendChild(wrap)\n  app.mount(wrap)\n}\n"
  },
  {
    "path": "packages/nuedom/src/nue.js",
    "content": "\n// The browser API\nimport { createNode } from './dom/node.js'\n\nexport { domdiff } from './dom/diff.js'\n\nexport function mount(ast, opts) {\n  if (ast.is_custom) ast = { ...ast, is_custom: false, tag: 'div' }\n  const node = createNode(ast, opts.data, opts)\n  node.mount(opts.root)\n  return node\n}"
  },
  {
    "path": "packages/nuedom/test/ast.test.js",
    "content": "\nimport { createAST, convertFunctions, convertGetters } from '../src/compiler/ast.js'\nimport { tokenize } from '../src/compiler/tokenizer.js'\nimport { parseBlocks } from '../src/compiler/document.js'\n\n\nfunction testTag(template, expected) {\n  const [ block ] = parseBlocks(tokenize(template))\n\n  const tag  = createAST(block)\n  if (expected === true) return tag\n\n  expect(tag).toEqual(expected)\n}\n\n\ntest('closed tag', () => {\n  testTag('<foo/>', { tag: 'foo', is_custom: true })\n})\n\ntest('SVG tag', () => {\n  testTag('<path/>', { tag: 'path', svg: true })\n})\n\ntest('is attrib', () => {\n  testTag('<p :is=\"hey\">Hello</p>', { tag: 'p', is: 'hey', children: [{ text: 'Hello' }] })\n})\n\n\ntest('nested element', () => {\n  testTag('<foo><p>Hi</p></foo>', {\n    tag: \"foo\",\n    is_custom: true,\n    children: [{ tag: \"p\", children: [{ text: \"Hi\" }]}]\n  })\n})\n\ntest('nested tag', () => {\n  testTag('<p><bar :count=2></p>', {\n    tag: 'p',\n    children: [{\n      tag: 'bar',\n      attr: [{ name: 'count', fn: '2', is_data: true } ],\n     is_custom: true,\n   }],\n  })\n})\n\ntest('element with expression', () => {\n  testTag('<p>{ val } {{ val }}</p>', {\n    children: [ {fn: '_.val', }, { fn: '_.val', html: true }],\n    tag: \"p\",\n  })\n})\n\ntest('nested script', () => {\n  const template = `\n    <counter>\n      <script>this.count = 1</script>\n    </counter>\n  `\n  testTag(template, {\n    tag: \"counter\",\n    script: \"this.count = 1\",\n    is_custom: true,\n  })\n})\n\ntest('client script', () => {\n  const ast = testTag(`\n    <body>\n      <script src=\"/analytics.js\"></script>\n      <script type=\"module\">alalytics(666)</script>\n      <script></script>\n    </body>\n  `, true)\n\n  expect(ast.children.length).toBe(2)\n})\n\n\ntest('conditional', () => {\n  const template = `\n    <cond-test>\n      <h3>Hello</h3>\n      <b :if=\"am < 100\">Lol</b>\n      <b :else-if=\"am < 50\">Mid</b>\n      <b :else>Meh</b>\n      <a :if=\"foo\">Test</a>\n    </cond-test>\n  `\n  const kids = testTag(template, true).children\n  expect(kids.length).toBe(3)\n  expect(kids[1].some.length).toBe(3)\n  expect(kids[2].some.length).toBe(1)\n})\n\ntest('slot', () => {\n  const template = `\n    <slot-test>\n      <h3>Hello</h3>\n      <slot/>\n    </slot-test>\n  `\n  const kids = testTag(template, true).children\n  expect(kids[1]).toEqual({ slot: true })\n})\n\ntest('newlines', () => {\n  const template = `\n    <textarea\n      amount=\"10\"\n      class=\"bar\">\n      foo\n      bar\n    </textarea>\n  `\n  const tag = testTag(template, true)\n  expect(tag.tag).toBe('textarea')\n  expect(tag.attr.length).toBe(2)\n  expect(tag.children[0].text).toInclude('\\n')\n})\n\n\ntest('skip style tags', () => {\n  testTag('<div><style></style></div>', { tag: 'div' })\n})\n\ntest('convert function', () => {\n  expect(convertFunctions(`format(str) {}`)).toEqual('this.format = function(str) {}')\n  expect(convertFunctions(`async format(str) {}`)).toEqual('this.format = async function(str) {}')\n  expect(convertFunctions(`if(true) {}`)).toEqual('if(true) {}')\n  expect(convertFunctions(`for(true) {}`)).toEqual('for(true) {}')\n  expect(convertFunctions(`for (true) {}`)).toEqual('for (true) {}')\n})\n\ntest('simple getter', () => {\n  expect(convertGetters('get foo() { }')).toEqual(\"Object.defineProperty(this, 'foo', { get() { } })\")\n})\n\ntest('multiline getter', () => {\n  const js = convertGetters(`\n    get status() {\n      const { start, length } = view\n      return start + length\n    }\n  `)\n\n  expect(js).toInclude('{ get() {')\n  expect(js).toInclude('return start + length} })')\n})"
  },
  {
    "path": "packages/nuedom/test/attributes.test.js",
    "content": "\nimport {\n  tokenizeAttr,\n  parseAttributes,\n  parseClassHelper,\n  parseExpression,\n  parseFor,\n  parseForArgs,\n} from '../src/compiler/attributes.js'\n\n\ntest('tokenize attr', () => {\n  const attr = 'class=test disabled :onclick=\"log($event)\"'\n  expect(tokenizeAttr(attr)).toEqual(['class=test', 'disabled', ':onclick=log($event)'])\n})\n\ntest('tokenize class helper', () => {\n  const attr = 'class=\"{ active: active(item), foo: true } bar\"'\n  expect(tokenizeAttr(attr)).toEqual([\"class={ active: active(item), foo: true } bar\"])\n})\n\ntest('tokenize complex', () => {\n  const attr = `:onclick=\"this.fire('bomb' == state) || console.log(a || $event)\" disabled goto=\"10\"`\n  const attrs = tokenizeAttr(attr)\n  expect(attrs[0]).toBe(\":onclick=this.fire('bomb' == state) || console.log(a || $event)\")\n  expect(attrs.length).toBe(3)\n})\n\n\n// parse expression\ntest('plain value', () => {\n  expect(parseExpression('plain value')).toBeUndefined()\n})\n\ntest('expression', () => {\n  expect(parseExpression('{ 1 + 1 }')).toBe('(1 + 1)')\n})\n\ntest('interpolation', () => {\n  expect(parseExpression(\"foo { cute('hey') } baz\")).toBe(`\"foo \" + (_.cute('hey')) + \" baz\"`)\n})\n\ntest('class helper', () => {\n  expect(parseClassHelper(\"tabular: true, is-active: false\")).toBe('_.$concat({tabular: true, \"is-active\": false})')\n})\n\ntest('class helper plain value', () => {\n  expect(parseClassHelper(\"hypa, move: 1\")).toBe('_.$concat({hypa: _.hypa, move: 1})')\n})\n\ntest('complex class helper', () => {\n  const expr = parseExpression('prefix [ hey: bar, boo: baz() ]', 'class')\n  expect(expr).toBe('\"prefix \" + _.$concat({hey: _.bar, boo: _.baz()})')\n})\n\ntest('complex event argument', () => {\n  expect(parseAttributes(':oninput=\"seek(index, $event)\"').handlers[0]).toEqual({\n    h_fn: '_.seek(_.index, $e)',\n    name: 'oninput',\n  })\n})\n\n// for arguments\ntest('for args', () => {\n  expect(parseForArgs('item, $i')).toMatchObject({ keys: ['item'], index: '$i' })\n})\n\ntest('for arg parenthesis', () => {\n  expect(parseForArgs('(item, i)')).toMatchObject({ keys: ['item'], index: 'i' })\n})\n\ntest('[k, v]', () => {\n  expect(parseForArgs('[k, v]')).toEqual({ keys: ['k', 'v'], is_entries: true })\n})\n\ntest('([k, v])', () => {\n  expect(parseForArgs('([k, v])')).toEqual({ keys: ['k', 'v'], is_entries: true })\n})\n\ntest('[lang, text], i', () => {\n  expect(parseForArgs('[lang, text], i')).toEqual({ keys: ['lang', 'text'], index: 'i', is_entries: true })\n})\n\ntest('({ lang, text }, i)', () => {\n  expect(parseForArgs('({ lang, text }, i)')).toMatchObject({ keys: ['lang', 'text'], index: 'i' })\n})\n\ntest('({k, v})', () => {\n  expect(parseForArgs('({k, v})')).toEqual({ keys: ['k', 'v'], is_entries: false })\n})\n\ntest('simple for clause', () => {\n  expect(parseFor('item, i in items')).toMatchObject({ fn: '_.items', keys: ['item'], index: 'i' })\n})\n\ntest('complex for clause', () => {\n  expect(parseFor('({ foo, bar }, $index) in Object.entries(items)')).toMatchObject({\n    fn: 'Object.entries(_.items)',\n    keys: ['foo', 'bar'],\n    index: '$index'\n  })\n})\n\ntest('basic attrs', () => {\n  const [a, b] = parseAttributes('class=test disabled').attr\n  expect(a).toEqual({ name: \"class\", val: \"test\" })\n  expect(b).toEqual({ name: \"disabled\", bool: true, val: true })\n})\n\ntest('equals character', () => {\n  const [ attr ] = parseAttributes('href=\"?id=100\"').attr\n  expect(attr.name).toBe('href')\n  expect(attr.val).toBe('?id=100')\n})\n\ntest('for attribute', () => {\n  expect(parseAttributes(':each=\"item, i of items\"').for).toMatchObject({\n    fn: '_.items',\n    keys: [ 'item' ],\n    index: 'i',\n  })\n})\n\ntest('if attribute', () => {\n  expect(parseAttributes(':if=\"item\"')).toEqual({ if: \"_.item\" })\n})\n\ntest('skip style attribute', () => {\n  const { attr } = parseAttributes('width=100 style=\"color: blue\"')\n  expect(attr.length).toBe(1)\n})\n\n\n"
  },
  {
    "path": "packages/nuedom/test/compiler.test.js",
    "content": "\nimport { compileFn } from '../src/compiler/compiler.js'\nimport { compileNue  } from '..'\n\n\ntest('empty template', () => {\n  const js = compileNue('')\n  expect(js).toContain('export const lib = []')\n})\n\ntest('compileNue', () => {\n  const template = '<p>Hello</p>'\n  const js = compileNue(template)\n  expect(js).toStartWith('export const lib = [')\n})\n\n\ntest('multiple components', () => {\n  const template = `\n    <comp1>\\${ fn(1) }</comp1>\n    <comp2>\\${ fn(2) }</comp2>\n  `\n  const js = compileNue(template)\n  expect(js).toContain(\"tag: 'comp1'\")\n  expect(js).toContain(\"tag: 'comp2'\")\n})\n\ntest('function', () => {\n  const js = compileNue('<b>${ fn(2) }</b>')\n  expect(js).toInclude('fn: _=>(_.fn(2))')\n})\n\ntest('quoted function', () => {\n  const js = compileNue(`<b>\\${ 'foo' + \"bar\" }</b>`)\n  expect(js).toInclude(`=>('foo' + \"bar\")`)\n})\n\ntest('script blocks', () => {\n  const template = `\n    <script>\n      import { format } from './utils.js'\n    </script>\n    <script>\n      function pretty() { }\n    </script>\n    <time is=\"pretty-date\">\\${ format(date) }</time>\n  `\n  const js = compileNue(template)\n  expect(js).toContain('import { format }')\n  expect(js).toContain('pretty() { }')\n  expect(js).toContain('export const lib =')\n})\n\ntest('<script> block', () => {\n  const js = compileNue(`\n    <a>\n      \\${ val }\n      <script>\n        this.val = 110\n\n        this.setup = function() {\n          return \"done\"\n        }\n      </script>\n    </a>\n  `)\n  expect(js).toInclude('script: function()')\n  expect(js).toInclude('_=>_.val')\n})\n\n\ntest('compileFn', () => {\n  expect(compileFn('_.foo')).toBe('_=>_.foo')\n  expect(compileFn('_.foo + 1')).toBe('_=>(_.foo + 1)')\n  expect(compileFn('_.foo', true)).toBe('(_,$e)=>_.foo($e)')\n  expect(compileFn('i++; log(1)', true)).toBe('(_,$e)=>{i++; log(1)}')\n  expect(compileFn('log(1)', true)).toBe('(_,$e)=>log(1)')\n})\n\n"
  },
  {
    "path": "packages/nuedom/test/component.test.js",
    "content": "\nimport { renderNue } from '../src/dom/render.js'\n\ntest('self-closing', () => {\n  expect(renderNue('<a/>')).toBe('<a></a>')\n})\n\ntest('render', () => {\n  expect(renderNue('<hey/><hey>Hello</hey>')).toBe('<div>Hello</div>')\n})\n\ntest('child', () => {\n  const html = renderNue('<a><hey/></a> <hey>Hello</hey>')\n  expect(html).toBe('<a><div>Hello</div></a>')\n})\n\ntest('<hey>', () => {\n  const html = renderNue('<hey/> <hey>Hello</hey>')\n  expect(html).toBe('<div>Hello</div>')\n})\n\ntest(':is=hey', () => {\n  const html = renderNue('<hey/> <a :is=\"hey\">Hello</a>')\n  expect(html).toBe('<a>Hello</a>')\n})\n\ntest('<app>', () => {\n  const html = renderNue('<app>Hey</app>')\n  expect(html).toBe('<div>Hey</div>')\n})\n\ntest('<body app>', () => {\n  const html = renderNue('<body :is=\"app\">Hey</body>')\n  expect(html).toBe('<body>Hey</body>')\n})\n\n\ntest('params', () => {\n  const html = renderNue(`<hey text=\"foo\" :text=\"'bar'\"/> <hey>{ text }</hey>`)\n  expect(html).toBe('<div text=\"foo\">bar</div>')\n})\n\ntest('child data', () => {\n  const template = `\n    <figure>\n      <bar-chart :hey=\"rand()\" :value/>\n      <script>\n        this.value = 100\n        rand() { return ['hey'] }\n      </script>\n    </figure>\n    <bar-chart>{ hey[0] } { value }</bar-chart>\n  `\n  expect(renderNue(template)).toInclude('<div>hey 100</div>')\n})\n\ntest('script', () => {\n  const template = `\n    <hey/>\n    <hey>\n      { text }\n      <script>\n        this.text = 'Hello'\n      </script>\n    </hey>\n  `\n  const html = renderNue(template)\n  expect(html).toBe('<div>Hello</div>')\n})\n\n\ntest('if-else', () => {\n  const template = `\n    <div>\n      <item :if=\"ok\"/>\n      <bar :else/>\n    </div>\n\n    <item>Fail</item>\n    <p :is=\"bar\">Else</p>\n  `\n  const html = renderNue(template)\n  expect(html).toBe('<div><p>Else</p></div>')\n})\n\ntest('parent & child params', () => {\n  const template = `\n    <item :amount=\"2\"/>\n    <item class=\"bar\" data-amount=\"{ amount }\"/>\n  `\n  const html = renderNue(template)\n  expect(html).toBe('<div class=\"bar\" data-amount=\"2\"></div>')\n})\n\ntest('class merging', () => {\n  const template = `\n    <item class=\"bar\"/>\n    <item class=\"foo\">\n      <img class=\"nested\">\n    </item>\n  `\n  const html = renderNue(template)\n  expect(html).toBe('<div class=\"foo bar\"><img class=\"nested\"></div>')\n})\n\ntest(':bind', () => {\n  const template = `\n    <item :bind=\"{ data }\"/>\n    <item><h1>{title}</h1><p>{ desc }</p></item>\n  `\n  const data = { title: 'Hello', desc: 'World' }\n  const html = renderNue(template, { data: { data } })\n  expect(html).toBe('<div><h1>Hello</h1> <p>World</p></div>')\n})\n\ntest(':bind this', () => {\n  const html = renderNue(`\n    <div>\n      <item :bind=\"{ this }\"/>\n      <script>\n        this.title = 'Hello'\n        this.desc = 'World'\n      </script>\n    </div>\n    <item>\n      <h1>{ title }</h1>\n      <p>{ desc }</p>\n    </item>\n  `)\n  expect(html).toInclude('<h1>Hello</h1> <p>World</p>')\n})\n\n\ntest('slot', () => {\n  const template = `\n    <item :hey=\"{ title }\">\n      <p>{ desc }</p>\n      <small>yo</small>\n      { desc }\n    </item>\n\n    <item>\n      <h1>{ hey }</h1>\n      <slot/>\n    </item>\n  `\n  const html = renderNue(template, { data: { title: 'Hello', desc: 'World' } })\n  expect(html).toBe('<div><h1>Hello</h1><p>World</p> <small>yo</small>World</div>')\n})\n\n\ntest('mount attribute', () => {\n  const template = `\n    <a :mount=\"type\" :key=\"id\"/>\n\n    <item href=\"id:{ key }\">\n      <b>Hello</b>\n    </item>\n  `\n  const html = renderNue(template, { data: { type: 'item', id: 1 } })\n  expect(html).toBe('<a href=\"id:1\"><b>Hello</b></a>')\n})\n\ntest('<template :mount>', () => {\n  const template = `\n    <template mount=\"item\"/>\n    <item>\n      <b>Hello</b>\n    </item>\n  `\n  const html = renderNue(template)\n  expect(html).toBe('<div><b>Hello</b></div>')\n})\n\n\n\n\n\n"
  },
  {
    "path": "packages/nuedom/test/context.test.js",
    "content": "\nimport { addContext } from '../src/compiler/context.js'\n\ntest('simple variable', () => {\n  expect(addContext('className')).toBe('_.className')\n})\n\ntest('handle reserved words', () => {\n  expect(addContext('Math.PI')).toBe('Math.PI')\n})\n\ntest('math operations', () => {\n  expect(addContext('x + y * 2')).toBe('_.x + _.y * 2')\n})\n\ntest('array access', () => {\n  expect(addContext('items[0].name')).toBe('_.items[0].name')\n})\n\ntest('ternary with multiple variables', () => {\n  expect(addContext('x > 0 ? y : z')).toBe('_.x > 0 ? _.y : _.z')\n})\n\ntest('plain string', () => {\n  expect(addContext(\"'hello'\")).toBe(\"'hello'\")\n})\n\ntest('dollar variable', () => {\n  expect(addContext(\"$foo\")).toBe(\"_.$foo\")\n})\n\ntest('this', () => {\n  expect(addContext(\"this\")).toBe(\"_\")\n})\n\ntest('$event variable', () => {\n  expect(addContext(\"$event\")).toBe(\"$e\")\n})\n\ntest('$event.target', () => {\n  expect(addContext('$event.target')).toBe('$e.target')\n})\n\ntest('string expression', () => {\n  expect(addContext('a + \"hello\"')).toBe('_.a + \"hello\"')\n})\n\ntest('nested function calls', () => {\n  expect(addContext('format(getName(user))'))\n    .toBe('_.format(_.getName(_.user))')\n})\n\ntest('complex #1', () => {\n  expect(addContext('cute ? prettyDate(date) : Date.now()', ['prettyDate']))\n    .toBe('_.cute ? prettyDate(_.date) : Date.now()')\n})\n\ntest('complex #2', () => {\n  const expr = addContext('router.set({ foo: el.id })')\n  expect(expr).toBe('_.router.set({ foo: _.el.id })')\n})\n\n// Function calls with multiple arguments\ntest('function calls with args', () => {\n  expect(addContext('calculate(a, b, Math.max(x, y))'))\n    .toBe('_.calculate(_.a, _.b, Math.max(_.x, _.y))')\n})\n\n// Method chaining\ntest('method chaining', () => {\n  expect(addContext('users.filter(u => u.active).map(getName)'))\n    .toBe('_.users.filter(_.u => _.u.active).map(_.getName)')\n})\n\n\n// Arrow functions\ntest('arrow functions', () => {\n  expect(addContext('items.map(item => item.id * factor)'))\n    .toBe('_.items.map(_.item => _.item.id * _.factor)')\n})\n\n// Logical operators\ntest('logical operators', () => {\n  expect(addContext('user && user.name || defaultName'))\n    .toBe('_.user && _.user.name || _.defaultName')\n})\n\n// Comparison chains\ntest('comparison chains', () => {\n  expect(addContext('min <= value && value <= max'))\n    .toBe('_.min <= _.value && _.value <= _.max')\n})\n\n// Nested objects\ntest('nested object literals', () => {\n  expect(addContext('{ user: { name: userName, settings: { theme: currentTheme } } }'))\n    .toBe('{ user: { name: _.userName, settings: { theme: _.currentTheme } } }')\n})\n\n// Array literals\ntest('array literals', () => {\n  expect(addContext('[first, second, items[index]]'))\n    .toBe('[_.first, _.second, _.items[_.index]]')\n})\n\n// Regular expressions\ntest('regex literals', () => {\n  expect(addContext('/test/.test(input)'))\n    .toBe('/test/.test(_.input)')\n})\n\n// Numbers and operators\ntest('numeric operations', () => {\n  expect(addContext('price * 1.2 + tax - discount'))\n    .toBe('_.price * 1.2 + _.tax - _.discount')\n})\n\n"
  },
  {
    "path": "packages/nuedom/test/document.test.js",
    "content": "\nimport { parseNue, parseNames } from '../src/compiler/document.js'\n\ntest('doctype & root', () => {\n  const doc = parseNue('<!dhtml> <app/> <section :is=\"mod\"/>')\n\n  expect(doc).toMatchObject({\n    root: { tag: \"app\", is_custom: true },\n    doctype: \"dhtml\",\n    is_dhtml: true,\n    is_lib: true,\n  })\n})\n\n\ntest('imports', () => {\n  const page = parseNue(`\n    <script>\n      import { hello } from 'hello.js'\n    </script>\n\n    <a :class=\"hello\" :onclick=\"hello\">{ hello() }</a>\n  `)\n\n\n  const { root } = page\n  expect(root.attr[0].fn).toBe('hello')\n  expect(root.handlers[0].h_fn).toBe('hello($e)')\n  expect(root.children[0].fn).toBe('hello()')\n  expect(page.is_dhtml).toBeTrue()\n})\n\ntest('meta', () => {\n  const page = parseNue(`\n    <!-- @license MIT -->\n    <script>\n      import { hello } from 'hello.js'\n    </script>\n  `)\n  expect(page.meta.license).toBe('MIT')\n})\n\ntest('svg document', () => {\n  const page = parseNue(`\n    <?xml version=\"1.0\">\n\n    <!--\n      @use [foo, bar]\n    -->\n    <svg></svg>\n  `)\n  expect(page.lib[0]).toMatchObject({\n    meta: { use: \"[foo, bar]\" },\n    svg: true,\n  })\n})\n\n\ntest('full', () => {\n  const page = parseNue(`\n    <!-- @spa -->\n    <!doctype dhtml>\n\n    <script>\n      import { getContacts } from '/@shared/model.js'\n    </script>\n\n    <!-- @reactive -->\n    <custom>\n      <script>this.foo = 10</script>\n    </custom>\n\n    <!-- @author tipiirai -->\n    <another>\n      <p>World</p>\n    </another>\n\n    <script>\n      import { foo as state } from './foo.js'\n    </script>\n  `)\n\n  expect(page.doctype).toEqual('dhtml')\n\n  // meta\n  const [a, b] = page.lib\n  expect(page.meta).toEqual({ spa: true })\n  expect(a.meta).toEqual({ reactive: true })\n  expect(b.meta).toEqual({ author: 'tipiirai' })\n\n  // script\n  expect(page.script).toInclude('model.js')\n  expect(page.script).toInclude('foo.js')\n\n})\n\n\ntest('parseNames', () => {\n  const template = `\n    import { getContacts } from 'model.js'\n\n    import {foo as state, another } from 'foo.js'\n\n    // import { nothing } from 'nothing.js'\n\n    const { MEME, PRANK } = await import('/kama')\n\n    const FOO = [ 100 ]\n\n    export function format() {\n\n    }\n\n    function trick() {\n\n    }\n  `\n\n  expect(parseNames(template)).toEqual([\n   'getContacts', 'state', 'another', 'MEME', 'PRANK', 'FOO', 'format', 'trick'\n  ])\n\n})\n\n\n\n\n\n\n\n"
  },
  {
    "path": "packages/nuedom/test/domdiff.test.js",
    "content": "\nimport { createDocument } from '../src/dom/fakedom.js'\nimport { domdiff } from '../src/dom/diff.js'\n\n// helper functions\nconst text = (content) => document.createTextNode(content)\n\nconst div = (content) => {\n  const el = document.createElement('div')\n  if (content) el.appendChild(text(content))\n  return el\n}\n\nconst span = (content) => {\n  const el = document.createElement('span')\n  if (content) el.appendChild(text(content))\n  return el\n}\n\nconst p = (content) => {\n  const el = document.createElement('p')\n  if (content) el.appendChild(text(content))\n  return el\n}\n\nconst ul = () => document.createElement('ul')\n\nconst li = (content, key) => {\n  const el = document.createElement('li')\n  if (content) el.appendChild(text(content))\n  if (key) el.setAttribute('key', key)\n  return el\n}\n\nbeforeAll(() => global.document = createDocument() )\n\ntest('text update', () => {\n  const oldDiv = div('1')\n  const newDiv = div('2')\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.textContent).toBe('2')\n})\n\ntest('attribute update', () => {\n  const oldDiv = div()\n  oldDiv.setAttribute('class', 'old')\n  const newDiv = div()\n  newDiv.setAttribute('class', 'new')\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.getAttribute('class')).toBe('new')\n})\n\ntest('attribute addition', () => {\n  const oldDiv = div()\n  const newDiv = div()\n  newDiv.setAttribute('id', 'test')\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.getAttribute('id')).toBe('test')\n})\n\ntest('attribute removal', () => {\n  const oldDiv = div()\n  oldDiv.setAttribute('id', 'test')\n  const newDiv = div()\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.hasAttribute('id')).toBe(false)\n})\n\ntest('element addition', () => {\n  const oldDiv = div()\n  const newDiv = div()\n  newDiv.appendChild(span('New'))\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.children.length).toBe(1)\n  expect(oldDiv.firstChild.tagName).toBe('SPAN')\n  expect(oldDiv.textContent).toBe('New')\n})\n\ntest('element removal', () => {\n  const oldDiv = div()\n  oldDiv.appendChild(span('Old'))\n  const newDiv = div()\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.children.length).toBe(0)\n})\n\ntest('multiple element addition', () => {\n  const oldDiv = div()\n  const newDiv = div()\n  newDiv.appendChild(span('A'))\n  newDiv.appendChild(p('B'))\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.children.length).toBe(2)\n  expect(oldDiv.children[0].tagName).toBe('SPAN')\n  expect(oldDiv.children[0].textContent).toBe('A')\n  expect(oldDiv.children[1].tagName).toBe('P')\n  expect(oldDiv.children[1].textContent).toBe('B')\n})\n\ntest('multiple element removal', () => {\n  const oldDiv = div()\n  oldDiv.appendChild(span('A'))\n  oldDiv.appendChild(p('B'))\n  const newDiv = div()\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.children.length).toBe(0)\n})\n\ntest('element reorder', () => {\n  const oldDiv = div()\n  oldDiv.appendChild(span('A'))\n  oldDiv.appendChild(p('B'))\n\n  const newDiv = div()\n  newDiv.appendChild(p('B'))\n  newDiv.appendChild(span('A'))\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.children[0].tagName).toBe('P')\n  expect(oldDiv.children[0].textContent).toBe('B')\n  expect(oldDiv.children[1].tagName).toBe('SPAN')\n  expect(oldDiv.children[1].textContent).toBe('A')\n})\n\ntest('keyed list reorder', () => {\n  const oldUl = ul()\n  oldUl.appendChild(li('A', '1'))\n  oldUl.appendChild(li('B', '2'))\n\n  const newUl = ul()\n  newUl.appendChild(li('B', '2'))\n  newUl.appendChild(li('A', '1'))\n\n  domdiff(oldUl, newUl)\n  expect(oldUl.children[0].getAttribute('key')).toBe('2')\n  expect(oldUl.children[0].textContent).toBe('B')\n  expect(oldUl.children[1].getAttribute('key')).toBe('1')\n  expect(oldUl.children[1].textContent).toBe('A')\n})\n\ntest('keyed list addition', () => {\n  const oldUl = ul()\n  oldUl.appendChild(li('A', '1'))\n\n  const newUl = ul()\n  newUl.appendChild(li('A', '1'))\n  newUl.appendChild(li('B', '2'))\n\n  domdiff(oldUl, newUl)\n  expect(oldUl.children.length).toBe(2)\n  expect(oldUl.children[0].getAttribute('key')).toBe('1')\n  expect(oldUl.children[0].textContent).toBe('A')\n  expect(oldUl.children[1].getAttribute('key')).toBe('2')\n  expect(oldUl.children[1].textContent).toBe('B')\n})\n\ntest('keyed list removal', () => {\n  const oldUl = ul()\n  oldUl.appendChild(li('A', '1'))\n  oldUl.appendChild(li('B', '2'))\n\n  const newUl = ul()\n  newUl.appendChild(li('A', '1'))\n\n  domdiff(oldUl, newUl)\n  expect(oldUl.children.length).toBe(1)\n  expect(oldUl.children[0].getAttribute('key')).toBe('1')\n  expect(oldUl.children[0].textContent).toBe('A')\n})\n\ntest('nested element update', () => {\n  const oldDiv = div()\n  oldDiv.appendChild(span('Hello'))\n\n  const newDiv = div()\n  newDiv.appendChild(span('World'))\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.children[0].textContent).toBe('World')\n})\n\ntest('nested element addition', () => {\n  const oldDiv = div()\n  oldDiv.appendChild(span('A'))\n\n  const newDiv = div()\n  newDiv.appendChild(span('A'))\n  const nested = div()\n  nested.appendChild(p('B'))\n  newDiv.appendChild(nested)\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.children.length).toBe(2)\n  expect(oldDiv.children[0].tagName).toBe('SPAN')\n  expect(oldDiv.children[0].textContent).toBe('A')\n  expect(oldDiv.children[1].tagName).toBe('DIV')\n  expect(oldDiv.children[1].children[0].tagName).toBe('P')\n  expect(oldDiv.children[1].children[0].textContent).toBe('B')\n})\n\ntest('text node addition', () => {\n  const oldDiv = div()\n  const newDiv = div('Hello')\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.textContent).toBe('Hello')\n})\n\ntest('text node removal', () => {\n  const oldDiv = div('Hello')\n  const newDiv = div()\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.textContent).toBe('')\n})\n\ntest('mixed content update', () => {\n  const oldDiv = div()\n  oldDiv.appendChild(text('Hello'))\n  oldDiv.appendChild(span('A'))\n\n  const newDiv = div()\n  newDiv.appendChild(text('World'))\n  newDiv.appendChild(span('B'))\n\n  domdiff(oldDiv, newDiv)\n  expect(oldDiv.childNodes[0].textContent).toBe('World')\n  expect(oldDiv.childNodes[1].tagName).toBe('SPAN')\n  expect(oldDiv.childNodes[1].textContent).toBe('B')\n})\n\ntest('complete node replacement', () => {\n  const parent = div()\n  const oldDiv = div()\n  oldDiv.appendChild(span('A'))\n  parent.appendChild(oldDiv)\n\n  const newDiv = div()\n  newDiv.appendChild(p('B'))\n\n  domdiff(oldDiv, newDiv)\n  expect(parent.children[0].tagName).toBe('DIV')\n  expect(parent.children[0].children[0].tagName).toBe('P')\n  expect(parent.children[0].children[0].textContent).toBe('B')\n})"
  },
  {
    "path": "packages/nuedom/test/event.test.js",
    "content": "\nimport { clickable } from './event.util.js'\n\n\ntest('event handler', () => {\n  const template = '<button :onclick=\"counter[0]++\">{ counter[0] }</button>'\n  const data = { counter: [1] }\n  const root = clickable(template, data)\n  expect(root.html).toBe('<button>1</button>')\n  root.click()\n  expect(data.counter[0]).toBe(2)\n  expect(root.html).toBe('<button>2</button>')\n})\n\ntest('event argument', () => {\n  const template = `\n    <button :onclick=\"setName($event)\">\n      { name }\n\n      <script>\n        setName(e) {\n          this.name = e.target.tagName\n        }\n      </script>\n    </button>\n  `\n\n  const button = clickable(template)\n  expect(button.html).toBe('<button></button>')\n  button.click()\n  expect(button.html).toBe('<button>BUTTON</button>')\n})\n\ntest('conditional', () => {\n  const template = `\n    <div>\n      <h3 :if=\"count\">Hello</h3>\n      <a :onclick=\"count++\"/>\n    </div>\n  `\n  const root = clickable(template, { count: 0 })\n  expect(root.html).toBe('<div><a></a></div>')\n\n  root.click('a')\n  expect(root.html).toBe('<div><h3>Hello</h3><a></a></div>')\n})\n\ntest('callback', () => {\n  const template = `\n    <div>\n      <child :callback/>\n    </div>\n\n    <child>\n      <button :onclick=run/>\n      <script>\n        run() {\n          this.callback()\n        }\n      </script>\n    </child>\n  `\n  let counter = 0\n  const root = clickable(template, { callback: () => counter++ })\n  root.click()\n  root.click()\n  expect(counter).toBe(2)\n})\n\ntest('method', () => {\n  const template = `\n    <a :onclick=\"increment()\">\n      Count: { count }\n      <script>\n        this.count = 0\n\n        this.increment = function() {\n          this.count++\n        }\n      </script>\n    </a>\n  `\n  const root = clickable(template)\n  expect(root.html).toInclude('Count: 0')\n  root.click('a')\n  expect(root.html).toInclude('Count: 1')\n})\n\ntest('method syntax', () => {\n  const template = `\n    <a :onclick=\"hey()\">\n      { val }  { avg }\n      <script>\n        hey() {\n          this.val = 'hey'\n        }\n\n        get avg() {\n          return 100\n        }\n      </script>\n    </a>\n  `\n  const root = clickable(template)\n  root.click('a') // Await click\n  const html = root.html\n  expect(html).toInclude('hey')\n  expect(html).toInclude('100')\n})\n\ntest('child/bind updates', () => {\n  const template = `\n    <div>\n      <h1>{ data.hello }</h1>\n      <child :bind=\"data\"/>\n      <button :onclick=\"change\"/>\n      <script>\n        this.data = {\n          hello: 'Hello',\n          world: 'World',\n        }\n        change() {\n          this.data = {\n            hello: 'Holy',\n            world: 'Smoke',\n          }\n        }\n      </script>\n    </div>\n\n    <child>\n      <p>{ world }</p>\n    </child>\n  `\n\n  const root = clickable(template)\n  expect(root.html).toInclude('<h1>Hello</h1> <div><p>World</p>')\n  root.click()\n  expect(root.html).toInclude('<h1>Holy</h1> <div><p>Smoke</p>')\n})\n\n\ntest('event bind shortcut', async () => {\n  const template = `\n    <a :onclick disabled=\"{ disabled }\">\n      <script>\n        onclick() {\n          this.disabled = 1\n        }\n      </script>\n    </a>\n  `\n  const root = clickable(template)\n  expect(root.html).toBe('<a></a>')\n  root.click('a')\n  expect(root.html).toBe('<a disabled=\"\"></a>')\n})\n\ntest('loop + if', () => {\n  const template = `\n    <div>\n      <a :each=\"val in [1,2]\" :if=\"doit\">{val}</a>\n      <button :onclick=\"doit = true\"/>\n    </div>\n  `\n  const root = clickable(template)\n  expect(root.html).toBe('<div><button></button></div>')\n\n  root.click()\n  expect(root.html).toInclude('<a>1</a><a>2</a>')\n})\n\ntest.skip('loop :onclick', () => {\n  const template = `\n    <div>\n      <button :each=\"el, i of [{ id: 2 }]\" :onclick=\"doit = true\">\n        { el.id } / { i }\n      </button>\n\n      <p :if=\"doit\">Hey</p>\n    </div>\n  `\n  const root = clickable(template)\n  expect(root.html).toEndWith('</button></div>')\n  root.click()\n  expect(root.html).toBe('<div><button>2 / 0</button><p>Hey</p></div>')\n})"
  },
  {
    "path": "packages/nuedom/test/event.util.js",
    "content": "\nimport { parseNue } from '../src/compiler/document.js'\nimport { mountAST } from '../src/dom/render.js'\n\n\nexport function clickable(template, data) {\n  const { lib } = parseNue(template)\n  const block = mountAST(lib[0], { data, deps: lib.slice(1) })\n  const { root } = block\n\n  function click(selector='button') {\n    const el = root.querySelector(selector)\n    el?.dispatchEvent({ type: 'click' })\n  }\n  return { block, click, get html() { return root.innerHTML || '' } }\n}\n\n\n\n"
  },
  {
    "path": "packages/nuedom/test/fakedom.test.js",
    "content": "\nimport { createDocument } from '../src/dom/fakedom.js'\n\nconst document = createDocument()\n\n// helper functions\nconst el = (tag) => document.createElement(tag)\nconst text = (content) => document.createTextNode(content)\n\ntest('innerHTML', () => {\n  const body = el('body')\n  const link = el('a')\n  link.setAttribute('href', '/test')\n  link.appendChild(text('Click me'))\n  body.appendChild(link)\n  expect(body.innerHTML).toBe('<a href=\"/test\">Click me</a>')\n})\n\ntest('innerHTML handles nested elements', () => {\n  const div = el('div')\n  const span = el('span')\n  span.appendChild(text('Hello'))\n  div.appendChild(span)\n  expect(div.innerHTML).toBe('<span>Hello</span>')\n})\n\n// firstChild tests\ntest('firstChild returns first child or text node', () => {\n  const div = el('div')\n  const span1 = el('span')\n  const span2 = el('span')\n  div.appendChild(span1)\n  div.appendChild(span2)\n  expect(div.firstChild).toBe(span1)\n\n  // test with text node as first child\n  const div2 = el('div')\n  const textNode = text('Hello')\n  div2.appendChild(textNode)\n  div2.appendChild(el('span'))\n  expect(div2.firstChild).toBe(textNode)\n})\n\ntest('firstChild returns undefined for empty element', () => {\n  const div = el('div')\n  expect(div.firstChild).toBeUndefined()\n})\n\n// replaceChild tests\ntest('replaceChild replaces existing child and maintains order', () => {\n  const div = el('div')\n  const span1 = el('span')\n  const span2 = el('span')\n  const span3 = el('span')\n  const newP = el('p')\n\n  div.appendChild(span1)\n  div.appendChild(span2)\n  div.appendChild(span3)\n\n  const returned = div.replaceChild(newP, span2)\n\n  // check replacement worked\n  expect(div.children).toContain(newP)\n  expect(div.children).not.toContain(span2)\n  expect(newP.parentNode).toBe(div)\n  expect(span2.parentNode).toBe(null)\n\n  // check order maintained\n  expect(div.children[0]).toBe(span1)\n  expect(div.children[1]).toBe(newP)\n  expect(div.children[2]).toBe(span3)\n\n  // check return value\n  expect(returned).toBe(span2)\n})\n\ntest('attributes work with standard iteration', () => {\n  const button = el('button')\n  button.setAttribute('disabled', '')\n  button.setAttribute('type', 'submit')\n\n  const attrs = []\n  for (let attr of button.attributes) {\n    attrs.push(`${attr.name}=\"${attr.value}\"`)\n  }\n\n  expect(attrs).toContain('disabled=\"\"')\n  expect(attrs).toContain('type=\"submit\"')\n  expect(button.outerHTML).toBe('<button disabled=\"\" type=\"submit\"></button>')\n})\n\ntest('classList and class attribute work together', () => {\n  const div = el('div')\n  div.classList.add('foo', 'bar')\n\n  expect(div.classList.length).toBe(2)\n  expect(div.classList.toString()).toBe('foo bar')\n  expect(div.outerHTML).toBe('<div class=\"foo bar\"></div>')\n})\n\ntest('void tags serialize as self-closing', () => {\n  const div = el('div')\n  const img = el('img')\n  img.setAttribute('src', 'test.jpg')\n  div.appendChild(img)\n\n  expect(div.innerHTML).toBe('<img src=\"test.jpg\">')\n})\n\ntest('SVG elements preserve case-sensitive tag names', () => {\n  const svg = el('svg')\n  const foreignObject = el('foreignObject')\n  svg.appendChild(foreignObject)\n\n  expect(svg.innerHTML).toBe('<foreignObject></foreignObject>')\n})\n\ntest('document fragment works', () => {\n  const fragment = document.createDocumentFragment()\n  fragment.appendChild(el('div'))\n  fragment.appendChild(text('hello'))\n\n  expect(fragment.innerHTML).toBe('<div></div>hello')\n})\n\ntest('textContent', () => {\n  const div = el('div')\n  div.appendChild(text('Hello'))\n  console.info(div.textContent)\n  // expect(div.textContent).toBe('Hello')  // This will probably fail\n})\n\ntest('textContent getter on elements collects from multiple text children', () => {\n  const div = el('div')\n  div.appendChild(text('Hello'))\n  div.appendChild(text(' World'))\n  expect(div.textContent).toBe('Hello World')\n})\n\ntest('textContent getter on elements collects from nested text', () => {\n  const div = el('div')\n  const span = el('span')\n  span.appendChild(text('Hello'))\n  div.appendChild(span)\n  expect(div.textContent).toBe('Hello')\n})\n\n"
  },
  {
    "path": "packages/nuedom/test/index.html",
    "content": "\n<!doctype html>\n\n<!--\n<script src=\"https://cdn.jsdelivr.net/gh/nuejs/nue@2.0/packages/nuedom/src/nue-jit.js\" type=\"module\"></script>\n-->\n\n<script src=\"../src/nue-jit.js\" type=\"module\"></script>\n\n<style>\n  body {\n   font-family: system-ui;\n   display: grid;\n   place-items: center;\n   min-height: 100vh;\n   margin: 0;\n  }\n  button {\n   font-family: inherit;\n   padding: .5em 1.5em;\n   background-color: black;\n   border-radius: .25em;\n   cursor: pointer;\n   color: white;\n   border: 0;\n  }\n</style>\n\n\n<template>\n\n  <button :onclick=\"count++\">\n    Count: <b>{ count }</b>\n\n    <script>\n      this.count = 0\n    </script>\n  </button>\n\n</template>\n"
  },
  {
    "path": "packages/nuedom/test/loop.test.js",
    "content": "\nimport { clickable } from './event.util.js'\nimport { renderNue } from '../src/dom/render.js'\n\ntest('loop index', () => {\n  const template = '<ul><li :each=\"(el, i) in items\">{i}: {el}</li></ul>'\n  const html = renderNue(template, { data: { items: ['a', 'b'] } })\n  expect(html).toBe('<ul><li>0: a</li><li>1: b</li></ul>')\n})\n\ntest('loop data access', () => {\n  const template = '<div><p :each=\"el in items\">{el} {foo}</p></div>'\n  const html = renderNue(template, { data: { foo: 1, items: ['F', 'F'] }})\n  expect(html).toInclude('<p>F 1</p><p>F 1</p>')\n})\n\ntest('template loop', () => {\n  const template = `\n    <dl>\n      <template :each=\"(el, i) in meta\">\n        <dt>{ el.title }</dt>\n        <dd>{ el.data }</dd>\n      </template>\n    </dl>\n  `\n  const meta = [\n    { title: 'Name', data: 'Alice' },\n    { title: 'Age', data: '30' }\n  ]\n\n  const html = renderNue(template, { data: { meta } })\n  expect(html).toBe('<dl><dt>Name</dt><dd>Alice</dd><dt>Age</dt><dd>30</dd></dl>')\n})\n\n\ntest('Object.entries()', () => {\n  const template = `\n    <dl>\n      <template :each=\"[key, val] in Object.entries(items)\">\n        <td>{ key }</td>\n        <dd>{ val }</dd>\n      </template>\n    </dl>\n  `\n  const items = { Email: 'm@example.com' }\n  const html = renderNue(template, { data: { items } })\n  expect(html).toBe('<dl><td>Email</td><dd>m@example.com</dd></dl>')\n})\n\ntest('loop deconstruct', () => {\n  const template = `\n    <dl>\n      <template :each=\"{ key, val } in items\">\n        <td>{ key }</td>\n        <dd>{ val }</dd>\n      </template>\n    </dl>\n  `\n  const items = [{ key: 'Email', val: 'm@example.com' }]\n  const html = renderNue(template, { data: { items } })\n  expect(html).toBe('<dl><td>Email</td><dd>m@example.com</dd></dl>')\n})\n\ntest('nested loop', () => {\n  const template = `\n    <ul>\n      <li :each=\"arr in items\">\n        <p :each=\"el in arr\">{ el }</p>\n      </li>\n    </ul>\n  `\n  const items = [['A', 'B'], ['C', 'D']]\n  const html = renderNue(template, { data: { items } })\n  expect(html).toBe('<ul><li><p>A</p><p>B</p></li><li><p>C</p><p>D</p></li></ul>')\n})\n\ntest('component loop', () => {\n  const template = `\n    <ul>\n      <item :each=\"text of arr\" :text=\"{ text }\"/>\n      <script>\n        this.arr = ['hello', 'world']\n      </script>\n    </ul>\n\n    <li :is=\"item\">{ text }</li>\n  `\n  const html = renderNue(template)\n  expect(html).toBe('<ul><li>hello</li><li>world</li></ul>')\n})\n\n\ntest('slot loop', () => {\n  const html = renderNue(`\n    <a>\n      <child :each=\"el, i in new Array(2).fill(1)\">{i}</child>\n    </a>\n    <b :is=\"child\">i: <slot/></b>\n  `)\n  expect(html).toBe('<a><b>i: 0</b><b>i: 1</b></a>')\n})\n\n"
  },
  {
    "path": "packages/nuedom/test/render.test.js",
    "content": "\nimport { renderNue } from '../src/dom/render.js'\n\njest.spyOn(console, 'error').mockImplementation(() => {})\njest.spyOn(console, 'warn').mockImplementation(() => {})\nafterEach(() => console.error.mockClear())\n\ntest('element', () => {\n  expect(renderNue('<div/>')).toBe('<div></div>')\n})\n\ntest('text', () => {\n  expect(renderNue('<div>Hello</div>')).toBe('<div>Hello</div>')\n})\n\ntest('expression', () => {\n  expect(renderNue('<div>{ 1 + 2 }</div>')).toBe('<div>3</div>')\n})\n\ntest('NaN values', () => {\n  expect(renderNue('<div>{ a * b }</div>')).toBe('<div>N/A</div>')\n})\n\ntest('expression whitespace', () => {\n  expect(renderNue('<a>{ \"a\" } { \"b\" }</a>')).toBe('<a>a b</a>')\n})\n\ntest('text whitespace', () => {\n  const template = '<div>{ a } / { b } ({ c }) !</div>'\n  const html = renderNue(template, { data: { a: 1, b: 2, c: 3 } })\n  expect(html).toBe('<div>1 / 2 (3) !</div>')\n})\n\ntest('tag whitespace', () => {\n  const html = renderNue('<a><b>Hey</b> <em>Yo</em></a>')\n  expect(html).toInclude('</b> <em>')\n})\n\ntest('component + text whitespace', () => {\n  const html = renderNue('<a><note/> Bro</a><note>Yo</note>')\n  expect(html).toBe('<a><div>Yo</div> Bro</a>')\n})\n\ntest('errors', () => {\n  const html = renderNue('<div>{ foo() }</div>')\n  expect(html).toBe('<div>[Error]</div>')\n  expect(console.error).toHaveBeenCalled()\n})\n\ntest('do not render event handlers', () => {\n  const html = renderNue('<a :onclick=\"click\"></a>', { click: () => true })\n  expect(html).toBe('<a></a>')\n})\n\ntest('attributes', () => {\n  const html = renderNue(`<a class=\"btn\" disabled/>`)\n  expect(html).toBe('<a class=\"btn\" disabled=\"\"></a>')\n})\n\ntest('attribute expressions', () => {\n  const html = renderNue(`<a class=\"{'f' + 1}\"/>`)\n  expect(html).toBe('<a class=\"f1\"></a>')\n})\n\ntest('render prop', () => {\n  const html = renderNue('<h1>{ msg }</h1>', { data: { msg: 'Hello'} })\n  expect(html).toBe('<h1>Hello</h1>')\n})\n\n\ntest('nesting', () => {\n  const template = '<div><span>{ text }</span></div>'\n  const html = renderNue(template, { data: { text: 'Nested' } })\n  expect(html).toBe('<div><span>Nested</span></div>')\n})\n\n\ntest('svg', () => {\n  const template = `\n    <svg xmlns=\"http://www.w3.org/2000/svg\" viewbox=\"0 0 24 24\">\n      <foreignobject/>\n      <path d=\"M6 8a6 6 0 0 1 12\"/>\n    </svg>\n  `\n  const svg = renderNue(template)\n  expect(svg).toStartWith('<svg xmlns=\"http://www.w3.org/2000/svg\"')\n\n  // close tags\n  expect(svg).toInclude('<path d=\"M6 8a6 6 0 0 1 12\"></path>')\n\n  // fix cases\n  expect(svg).toInclude('viewBox')\n  expect(svg).toInclude('foreignObject')\n})\n\ntest('interpolation', () => {\n  const template = '<div class=\"item { type }\"/>'\n  const html = renderNue(template, { data: { type: 'active' } })\n  expect(html).toBe('<div class=\"item active\"></div>')\n})\n\ntest('html', () => {\n  const html = renderNue('<div>{{ html }} here</div>', { data: { html: '<b>Bold</b> text' } })\n  expect(html).toBe('<div><b>Bold</b> text here</div>')\n})\n\ntest('html false', () => {\n  const html = renderNue('<a>{{ html }}</a>', { data: { html: false } })\n  expect(html).toBe('<a></a>')\n})\n\ntest('class mapping', () => {\n  const template = '<label class=\"[ is-active: foo ] bar\">Test</label>'\n  const html = renderNue(template, { data: { foo: true } })\n  expect(html).toBe('<label class=\"is-active bar\">Test</label>')\n})\n\ntest('class mapping with functions', () => {\n  const template = '<div class=\"[ active: isActive(), error: hasError() ]\">Test</div>'\n  const data = { isActive: () => true, hasError: () => true }\n  const html = renderNue(template, { data })\n  expect(html).toBe('<div class=\"active error\">Test</div>')\n})\n\ntest(':var-* attributes', () => {\n  const template = '<a --index=\"1\" --random=\"{ random }\"></a>'\n  const html = renderNue(template, { data: { random: 100 } })\n  expect(html).toBe('<a style=\"--index:1;--random:100;\"></a>')\n})\n\ntest('max class names', () => {\n  const tmpl = '<a class=\"{ class }\"/>'\n  renderNue(tmpl, { data: { class: 'foo bar baz boo' }})\n  renderNue(tmpl, { data: { class: 'foo bar' }, max_class_names: 1 })\n  expect(console.error).toHaveBeenCalledTimes(2)\n})\n\ntest('invalid character warnings', () => {\n  renderNue('<a class=\"hover:md\"/>')\n  renderNue('<a class=\"bar\"/>')\n  expect(console.error).toHaveBeenCalledTimes(1)\n})\n\n\ntest('template if', () => {\n  const template = `\n    <dl>\n      <template :if=\"flag\">\n        <dt>Foo</dt>\n      </template>\n      <template :else>\n        <dt>Bar</dt>\n      </template>\n    </dl>\n  `\n  expect(renderNue(template, { data: { flag: true } })).toBe('<dl><dt>Foo</dt></dl>')\n  expect(renderNue(template)).toBe('<dl><dt>Bar</dt></dl>')\n})\n\ntest('bad if clause', () => {\n  expect(renderNue('<div><b :if=\"bad.clause\"/></div>')).toBe('<div></div>')\n})\n\ntest('component conditional', () => {\n  const template = `\n    <div>\n      <note :if=\"obj.error\"/>\n    </div>\n\n    <note>Hello</note>\n  `\n  const html = renderNue(template, { data: { obj: { error: true } } })\n  expect(html).toBe('<div><div>Hello</div></div>')\n})\n\n\ntest('if-else', () => {\n  const template = `\n    <div>\n      <h3>Hello</h3>\n      <b :if=\"am < 10\">Small</b>\n      <b :else-if=\"am < 50\">Mid</b>\n      <strong :else>Big</strong>\n      <a :if=\"none\">Empty</a>\n    </div>`\n\n  const html = renderNue(template, { data: { am: 30 } })\n  expect(html).toBe('<div><h3>Hello</h3><b>Mid</b></div>')\n})\n\n\ntest('passtrough scripts', () => {\n  const html = renderNue(`\n    <body>\n      <script src=\"/analytics.js\"></script>\n      <script type=\"module\">track(666)</script>\n      <script>// ignored</script>\n    </body>\n  `)\n  expect(html).toInclude('analytics.js')\n  expect(html).toInclude('type=\"module')\n})\n\n\ntest('render functions', () => {\n  const custom = (data) => `<h1>${ data.hello }, ${ data.who }</h1>`\n  const template = `<div><custom hello=\"Hello\"/></div>`\n  const html = renderNue(template, { data: { who: 'World' }, fns: { custom } })\n  expect(html).toBe('<div><h1>Hello, World</h1></div>')\n})\n\n\ntest('JSON stubs', () => {\n  const template = `<div><foo hello=\"Hello\"/></div>`\n  const html = renderNue(template)\n  expect(html).toInclude('<foo nue=\"foo\">')\n  expect(html).toInclude('<script type=\"application/json\">{\"hello\":\"Hello\"}')\n})"
  },
  {
    "path": "packages/nuedom/test/tokenizer.test.js",
    "content": "\nimport { tokenize } from '../src/compiler/tokenizer.js'\n\n\ntest('tag', () => {\n  expect(tokenize('<p>Hello</p>')).toEqual(['<p>', 'Hello', '</p>'])\n})\n\ntest('self-closing tag', () => {\n  expect(tokenize('<div/>')).toEqual(['<div/>'])\n})\n\ntest('expressions', () => {\n  const tokens = tokenize('<span>{ text } foo {{ html }} bar {{{ html }}}</span>')\n  expect(tokens).toEqual(\n    [ \"<span>\", \"{ text }\", \" foo \", \"{{ html }}\", \" bar \", \"{{ html }}\", \"</span>\" ]\n  )\n})\n\ntest('nested tags', () => {\n  expect(tokenize('<div><p>Hi</p></div>')).toEqual(['<div>', '<p>', 'Hi', '</p>', '</div>'])\n})\n\ntest('open tags', () => {\n  expect(tokenize('<hr>')).toEqual(['<hr/>'])\n  expect(tokenize('<img src>')).toEqual(['<img src/>'])\n  expect(tokenize('<hr><a></a>')).toEqual(['<hr/>', '<a>', '</a>'])\n})\n\ntest('complex', () => {\n  const els = tokenize(`\n    <div>\n      <p foo=\"10\">Hi</p>\n      <span><b>there</b></span>\n    </div>\n  `)\n  expect(els.length).toBe(10)\n})\n\ntest('simple, unquoted attributes', () => {\n  const els = tokenize('<p :onclick=hey>Hi</p>')\n  expect(els[0]).toBe('<p :onclick=hey>')\n  expect(els.length).toBe(3)\n})\n\ntest('class helper', () => {\n  const els = tokenize('<p :class=\"{ foo: true }\">Hi</p>')\n  expect(els.length).toBe(3)\n})\n\ntest('single quoted attr', () => {\n  const els = tokenize(`<p :class='{ boo || \"baz\" }'>Hi</p>`)\n  expect(els.length).toBe(3)\n})\n\ntest('complex attribute', () => {\n  const els = tokenize('<p :class=\"{ foo: true } baz { bar }\">Hi</p>')\n  expect(els.length).toBe(3)\n})\n\ntest('elem with attributes', () => {\n  expect(tokenize('<div class=test disabled>')).toEqual(['<div class=test disabled>'])\n})\n\ntest('newlines', () => {\n  const els = tokenize(`\n    <div\n      amount=\"10\"\n      class=\"bar\"/>\n  `)\n  expect(els.length).toBe(1)\n})\n\ntest('if and angle brackets', () => {\n  expect(tokenize('<a :if=\"var > 10\"/>').length).toBe(1)\n  expect(tokenize('<a :if=\"{ var < 10 }\"/>').length).toBe(1)\n  expect(tokenize('<b :if=\"am < 50\"/>').length).toBe(1)\n})\n\ntest('unclosed tag throws', () => {\n  expect(() => tokenize('<a href=\"boo\"<b/>')).toThrow(SyntaxError)\n})\n\ntest('comments', () => {\n  const els = tokenize(`\n    <h1>Hello</h1>\n    <!--\n      <b>World</p>\n    -->\n  `)\n\n  expect(els.join('').trim()).toEqual('<h1>Hello</h1>')\n})\n\ntest('annotations', () => {\n  const els = tokenize(`\n    <!--\n      @license MIT\n      @author tipiirai\n    -->\n    <h1>Hello</h1>\n  `)\n\n  expect(els[0].meta).toEqual({license: \"MIT\", author: \"tipiirai\" })\n})\n\n\ntest('scripts with expressions', () => {\n  const tokens = tokenize('<a><script>`{expr}`</script></a>')\n  expect(tokens.length).toBe(5)\n  expect(tokens[2]).toBe('`{expr}`')\n})\n\ntest('scripts with inner HTML', () => {\n  const tokens = tokenize('<a><script>\"<b>B</b>\"</script></a>')\n  expect(tokens.length).toBe(5)\n  expect(tokens[2]).toBe('\"<b>B</b>\"')\n})\n\n\n\n\n"
  },
  {
    "path": "packages/nueglow/Makefile",
    "content": "\nrun-tests:\n\tbun test"
  },
  {
    "path": "packages/nueglow/README.md",
    "content": "\n# Nueglow: CSS first syntax highlighting\nNueglow is syntax highlighting that works with your design system. It generates semantic HTML that your CSS can style. One minuscule highlighter for all languages.\n\n\n<a href=\"https://nuejs.org/blog/nueglow\">\n  <img src=\"https://nuejs.org/img/glow-dark-big.png\"></a>\n\n\n## The highlighter problem\nPopular syntax highlighters can be troublesome:\n\n**Massive codebases** - tools like Shiki ship 14MB and 44 packages. Each language needs its own grammar file with thousands of cryptic regex rules.\n\n**Theme lock-in** - Themes come as giant JSON files with 300+ predefined colors. You can't easily adapt them to your brand or design system.\n\n**Grammar complexity** - HTML grammar alone has 2000+ lines of regex patterns. Adding a new language means wiring another massive grammar file.\n\nThese syntax highlighters were built for code editors, not websites. They assume you want their themes and their markup. Your design system becomes an afterthought.\n\n\n## Why Nueglow\n\n**Universal language support** - Works with JavaScript, TypeScript, Python, HTML, CSS, YAML, JSON, Markdown, Bash, SQL, and virtually any other syntax. No grammar files needed.\n\n**Language-agnostic parsing** - Instead of per-language rules, Nueglow recognizes common patterns: strings, comments, keywords, operators. This works across all programming languages.\n\n**Semantic HTML output** - Keywords become `<b>`, strings become `<em>`, comments become `<sup>`. Your CSS controls the appearance.\n\n**Design system friendly** - Only 10-15 HTML elements to style. Fits naturally into any design system without fighting existing patterns.\n\n**Lightweight** - Complete highlighting for all languages in under 3KB of CSS. No JavaScript runtime, no theme bundles.\n\n<img src=\"https://nuejs.org/img/glow-light-big.png\">\n\n\nSee the [Nue website](https://nuejs.org/docs/nueglow) for details and documentation.\n"
  },
  {
    "path": "packages/nueglow/css/build.js",
    "content": "// Running: bun css/build.js\nimport { promises as fs } from 'node:fs'\n\nasync function minify(names, toname) {\n  const min = await (await Bun.build({\n    entrypoints: names.map(name => `css/${name}.css`),\n    minify: true,\n    throw: true,\n    experimentalCss: true,\n  })).outputs.map(async file => await file.text())\n\n  const to = `minified/${toname}.css`\n  await fs.mkdir('minified', { recursive: true })\n  await fs.writeFile(to, min)\n  console.log('>', to, (await fs.stat(to)).size)\n}\n\n\nawait minify(['syntax', 'markers'], 'syntax')\nawait minify(['syntax'], 'syntax.nano')\n\n"
  },
  {
    "path": "packages/nueglow/css/light.css",
    "content": "\n/* example light mode */\n\npre {\n  --glow-bg-color: #f9f9f9;\n  --glow-base-color: #555;\n\n  /* brand coloring */\n  --glow-primary-color: #0068d6;\n  --glow-secondary-color: #bd2864;\n  --glow-accent-color: #456aff;\n\n  /* rare case. make it \"pop\" */\n  --glow-special-color: #7820bc;\n\n  /* shades of gray */\n  --glow-char-color: #8e989c;\n  --glow-comment-color: #9aa1a3;\n  --glow-counter-color: #bbb;\n\n  /* selection color */\n  --glow-marked-color: #51c6fe29;\n}"
  },
  {
    "path": "packages/nueglow/css/markers.css",
    "content": "\n/* styling for specially marked lines */\n\npre {\n  --glow-line-color: 50, 180, 250;\n  --glow-del-color: 250, 110, 130;\n  --glow-ins-color: 50, 210, 190;\n  --glow-line-opacity: 0.15;\n\n  --padd: var(--glow-padding, 1.5em);\n\n  ins, del, dfn {\n    min-width: calc(100% + calc(var(--padd) * 2));\n    margin-left: calc(var(--padd) * -1);\n    padding-left: var(--padd);\n    border-left: .2em solid white;\n    display: inline-block;\n    position: relative;\n    width: 100%;\n\n    :first-child { margin-left: -.2em; }\n\n    /* plus & minus characters  */\n    &:before {\n      position: absolute;\n      left: 95%;\n    }\n\n    /* with line numbers */\n    span & {\n      margin-left: calc(-3.5em - var(--padd));\n      padding-left: calc(3.5em + var(--padd));\n\n      /* &:before { left: calc(var(--padd) - 1.5em); } */\n    }\n  }\n\n  ins {\n    border-color: rgb(var(--glow-ins-color));\n    background-color: rgba(var(--glow-ins-color), var(--glow-line-opacity));\n    &:before {\n      content: '+';\n      color: rgb(var(--glow-ins-color));\n    }\n  }\n\n  del {\n    border-color: rgb(var(--glow-del-color));\n    background-color: rgba(var(--glow-del-color), var(--glow-line-opacity));\n    border-radius: 0;\n\n    &:before {\n      content: '-';\n      color: rgb(var(--glow-del-color));\n    }\n  }\n\n  dfn {\n    border-color: rgb(var(--glow-line-color));\n    background-color: rgba(var(--glow-line-color), var(--glow-line-opacity));\n  }\n}\n"
  },
  {
    "path": "packages/nueglow/css/syntax.css",
    "content": "\n\npre {\n  background-color: var(--glow-bg-color, #20293A);\n  color: var(--glow-base-color, #a2aab1);\n  padding: var(--glow-padding, 1.5em);\n\n  counter-reset: line-counter 0;\n  font-family: monospace;\n  line-height: 1.7;\n  overflow-x: auto;\n\n  /* reset */\n  * {\n    font-weight: 400;\n    font-style: inherit;\n    text-decoration: inherit\n  }\n\n  /* primary accent color */\n  b { color: var(--glow-primary-color, #7dd3fc) }\n\n  /* secondary accent color */\n  em { color: var(--glow-secondary-color, #f472b6) }\n\n  /* special emphasis */\n  strong { color: var(--glow-accent-color, #419fff) }\n\n  /* brackets, special characters */\n  i { color: var(--glow-char-color, #64748b) }\n\n\n  /* vawy error message */\n  u {\n    text-decoration: underline wavy var(--glow-error-color, red);\n    text-decoration-thickness: .15em;\n    text-underline-offset: .5em;\n  }\n\n  /* comments */\n  sup {\n    color: var(--glow-comment-color, #6f7a7d);\n    font-size: inherit;\n    vertical-align: inherit;\n    font-style: italic;\n  }\n\n  /* special */\n  label { font-weight: bold; color: var(--glow-special-color, #fff); }\n\n  /* marked code */\n  mark {\n    background-color: var(--glow-marked-color, #2dd4bf26);\n    color: unset;\n    border-radius: .2em;\n    padding: .3em .4em;\n    margin: -.3em -.4em;\n  }\n\n  /* line numbers */\n  span {\n    counter-increment: line-counter 1;\n\n    &:before {\n      color: var(--glow-counter-color, #475569);\n      content: counter(line-counter);\n      display: inline-block;\n      text-align: right;\n      padding-right: 1em;\n      margin-right: 1em;\n      width: 2.5em;\n    }\n  }\n\n  /* erroneous line number styling */\n  span:has(u):before {\n    background-color: var(--glow-error-color, red);\n    border-radius: .2em;\n    font-weight: bold;\n    color: white;\n  }\n}\n"
  },
  {
    "path": "packages/nueglow/index.js",
    "content": "\nconst MIXED_HTML = ['html', 'jsx', 'php', 'astro', 'dhtml', 'vue', 'svelte', 'hb']\nconst LINE_COMMENT = { clojure: ';;', lua: '--', python: '#' }\nconst PREFIXES = { '+': 'ins', '-': 'del', '>': 'dfn' }\nconst MARK = /(••?)([^•]+)\\1/g   // ALT + q\nconst NL = '\\n'\n\nconst COMMON_WORDS = 'null|true|false|undefined|import|from|async|await|package|begin\\\n|interface|class|new|int|func|function|get|set|export|default|const|var|let\\\n|return|yield|for|while|defer|if|then|else|elif|fi|int|string|number|def|public|static|void\\\n|continue|break|switch|case|final|finally|try|catch|while|super|long|float\\\n|throw|fun|val|use|fn|my|end|local|until|next|bool|ns|defn|puts|require|each'\n\n// Implement most~50% of words to cover 95% of cases\nconst SPECIAL_WORDS = {\n  cpp: 'cout|cin|using|namespace',\n  python: 'None|nonlocal|lambda',\n  go: 'chan|fallthrough'\n}\n\n// special rules (growing list)\nconst RULES = {\n  css: [\n    { tag: 'strong', re: /#[0-9a-f]{3,7}/gi },\n    { tag: 'label', re: /!important/gi },\n    { tag: 'em', re: /--[\\w\\d\\-]+/gi },\n  ],\n\n  json: [\n    { tag: 'b', re: /(\".+\"):/gi },\n  ],\n  yaml: [\n    { tag: 'b', re: /([\\w ]+):/gi },\n  ],\n}\n\n\nconst HTML_TAGS = [\n\n  // line comment\n  { tag: 'sup', re: /# .+/ },\n\n  { tag: 'label', re: /\\[([a-z\\-]+)/g, lang: ['md', 'toml'], shift: true },\n\n  // string value (keep second on the list)\n  { tag: 'em', re: /'[^']*'|\"[^\"]*\"/g, is_string: true },\n\n  // HTML tag name\n  { tag: 'strong', re: /<([\\w\\-]+ )/g, shift: true, lang: MIXED_HTML },\n  { tag: 'strong', re: /<\\/?([\\w\\-]+)>/g, shift: true, lang: MIXED_HTML },\n\n  // ALL CAPS (constants)\n  // { tag: 'b', re: /\\b[A-Z]{2,}\\b/g },\n\n  // @special\n  { tag: 'label', re: /\\B@[\\w\\-]+/gi },\n\n  // char\n  { tag: 'i', re: /[^\\w •]/g },\n\n  // variable name\n  { tag: 'b', re: /\\b([a-z][\\w\\-]+)\\s*[:=\\(!\\[]/gi },\n\n  // property name\n  { tag: 'b', re: /\"\\w+\":/g },\n\n  // function name\n  { tag: 'b', re: /([\\w]+)\\(/gi },\n\n  // numeric value\n  { tag: 'em', re: /\\b\\d+\\.?[%\\w\\b]*/g },\n\n  // variable name\n  { tag: 'b', re: /([\\w]+)\\./g, lang: ['js'] },\n]\n\n\nfunction getTags(lang) {\n  const tags = HTML_TAGS.filter(el => !el.lang || el.lang.includes(lang))\n\n  // custom keywords\n  if (!['yaml', 'html', 'json'].includes(lang)) {\n    const w = SPECIAL_WORDS[lang]\n    const words = (w ? w + '|' : '') + COMMON_WORDS\n    const re = new RegExp(`\\\\b(${words})\\\\b`, 'gi')\n    tags.splice(4, 0, { tag: 'strong', re })\n  }\n\n  // custom rules\n  const rules = RULES[lang]\n  if (rules) tags.unshift(...rules)\n\n  return tags\n}\n\nfunction encode(str) {\n  return str.replaceAll('<', '&lt;').replaceAll('>', '&gt;')\n}\n\n// wrap token\nfunction elem(name, str) {\n  if (str == '<') str = '&lt;'\n  else if (str == '>') str = '&gt;'\n  return `<${name}>${str}</${name}>`\n}\n\n/*\n  Markdown/MDX requires a special treatment, because it's so\n  different from others (not a programming language)\n*/\nfunction isMD(lang) {\n  return ['md', 'mdx', 'nuemark'].includes(lang)\n}\n\nfunction getMDTags(str) {\n  const s = str.trim()\n  const c = s[0]\n\n  // divider\n  if (s.startsWith('---')) return [{ tag: 'i', re: /-+/ }]\n\n  // line comment\n  if (s.startsWith('// ')) return [{ tag: 'sup', re: /.+/ }]\n\n  if (['![', '[!'].includes(s.slice(0, 2))) return [{ tag: 'em', re: /.+/ }]\n\n  if (['import', 'export'].includes(s.slice(0, 6))) return getTags('js')\n\n  // HTML\n  if (c == '<') return getTags('html')\n\n  // heading\n  if (c == '#') return [{ tag: 'label', re: /.+/ }]\n\n  // quote\n  if (c == '>') return [{ tag: 'i', re: />/ }, { tag: 'sup', re: / .+/ }]\n\n  // front matter / yaml\n  if (/^\\w+: /.exec(s)) return getTags('yaml')\n\n  // component\n  if (c == '[' && s.endsWith(']')) {\n    return s[1] == '.' ? [{ tag: 'label', re: /\\w+/g }] : getTags('md')\n  }\n\n  // lists, links, images, fenced code\n  return [\n    // inline code\n    { tag: 'strong', re: /\\`.+\\`/g },\n\n    // image\n    { tag: 'em', re: /^(!.+)/g, shift: true },\n\n    // list\n    { tag: 'b', re: /[\\*\\_\\[\\]\\(\\)<>]+/g },\n  ]\n}\n\n\nexport function parseRow(row, lang) {\n  const tags = isMD(lang) ? getMDTags(row) : getTags(lang)\n  const tokens = []\n\n  // line comment (language specific)\n  const re = new RegExp(`${LINE_COMMENT[lang] || '//'} .+`)\n  tags.unshift({ tag: 'sup', re })\n\n\n  for (const el of tags) {\n    const { re, shift } = el\n\n    row.replace(re, function(match, start, n) {\n      if (arguments.length == 4) {\n        const more = shift ? match.indexOf(start) : 0\n        match = start; start = n + more\n      }\n      const end = start + match.length\n      tokens.push({ start, end, ...el })\n    })\n  }\n  return tokens.sort((a, b) => a.start - b.start)\n}\n\nfunction renderString(str) {\n  return encode(str).replace(/\\$?\\{([^\\}]+)\\}/g, function(_, content) {\n    return elem('i', _.replace(content, elem('b', content)))\n  })\n}\n\n\n// exported for testing purposes\nexport function renderRow(row, lang, mark = true) {\n  if (!row) return ''\n\n  const els = parseRow(row, lang)\n  const ret = []\n  var index = 0\n\n  for (var i = 0, max = 0, len = els.length, el, next; (el = els[i]); i++) {\n    const { start, end } = el\n    next = els[i + 1] || []\n\n    // skip overlappings\n    if (start < max) continue\n    if (start == next[0] && next[1] > end) continue\n    if (end > max) max = end; else continue\n\n    // construct final result\n    ret.push(row.substring(index, start))\n    const code = row.substring(start, end)\n    ret.push(elem(el.tag, el.is_string ? renderString(code) : code))\n\n    index = end\n  }\n\n  ret.push(row.substring(index))\n  const res = ret.join('')\n\n  return !mark ? res : res.replace(MARK, (_, marker, content) => {\n    return elem(marker[1] ? 'u' : 'mark', content)\n  })\n}\n\n\n// comment start & end\nconst COMMENT = [/(\\/\\* |^ *{# |<!--|'''|=begin)/, /(\\*\\/|#}|-->|'''|=end)$/]\n\nexport function parseSyntax(lines, lang, prefix = true) {\n  const [comm_start, comm_end] = COMMENT\n  const html = []\n\n  // multi-line comment\n  let comment\n\n  function endComment() {\n    html.push({ comment })\n    comment = null\n  }\n\n  lines.forEach((line, i) => {\n    if (!comment) {\n      if (comm_start.test(line)) {\n        comment = [line]\n        if (comm_end.test(line) && line?.trim() != \"'''\") endComment()\n      } else {\n\n        // highlighted line\n        const is_md = isMD(lang)\n        const c = line[0]\n        let wrap = prefix && (is_md ? (c == '|' && 'dfn') : PREFIXES[c])\n        if (wrap && is_md && line == '---') wrap = null\n        if (wrap) line = (line[1] == ' ' ? ' ' : '') + line.slice(1)\n\n        // escape character\n        if (prefix && c == '\\\\') line = line.slice(1)\n\n        html.push({ line, wrap })\n      }\n\n    } else {\n      comment.push(line)\n      if (comm_end.test(line)) endComment()\n    }\n  })\n\n\n  return html\n}\n\n\n// code, { language: 'js', numbered: true }\nexport function glow(str, opts = { prefix: true, mark: true }) {\n  if (typeof opts == 'string') opts = { language: opts }\n  const lines = Array.isArray(str) ? str : str.trim().split(/\\r?\\n/)\n\n  if (!lines[0]) return ''\n\n  // language\n  let lang = opts.language\n  if (!lang && lines[0][0] == '<') lang = 'html'\n  const html = []\n\n  function push(line) {\n    html.push(opts.numbered ? elem('span', line) : line)\n  }\n\n  parseSyntax(lines, lang, opts.prefix).forEach(function(block) {\n    let { line, comment, wrap } = block\n\n    // EOL comment\n    if (comment) {\n      return comment.forEach(el => push(elem('sup', encode(el))))\n\n    } else {\n      line = renderRow(line, lang, opts.mark)\n    }\n\n    if (wrap) line = elem(wrap, line)\n    push(line)\n  })\n\n  return `<code language=\"${lang || '*'}\">${html.join(NL)}</code>`\n}\n"
  },
  {
    "path": "packages/nueglow/package.json",
    "content": "{\n  \"name\": \"nue-glow\",\n  \"version\": \"0.2.5\",\n  \"description\": \"Markdown syntax highlighter for CSS developers\",\n  \"homepage\": \"https://nuejs.org/blog/introducing-glow/\",\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n\n  \"repository\": {\n    \"url\": \"https://github.com/nuejs/nue\",\n    \"directory\": \"packages/nueglow\",\n    \"type\": \"git\"\n  },\n  \"engines\": {\n    \"bun\": \">= 1\",\n    \"node\": \">= 18\"\n  },\n  \"scripts\": {\n    \"css\": \"bun ./css/build.js\",\n    \"test\": \"node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --runInBand\"\n  },\n\n  \"files\": [\"index.js\"],\n\n  \"jest\": {\n    \"setupFilesAfterEnv\": [\n      \"<rootDir>/../../setup-jest.js\"\n    ],\n    \"collectCoverageFrom\": [\n      \"<rootDir>/src/**\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/nueglow/test/generate.js",
    "content": "import { promises as fs } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport { glow } from '../src/glow.js'\n\n\nconst root = process.argv[2] || dirname(fileURLToPath(import.meta.url))\n\n\n// Nue / html\nconst HTML = `\n<figure @name=\"img\" class=•\"baz { foo } \\${ bar }\"•>\n  <img loading=\"lazy\" :alt=\"alt\" :src=\"_ || src\">\n\n  <!-- HTML comment here -->\n  <p>I finally made it to the public</p>\n\n  <figcaption :if=\"caption\">{{ caption }}</figcaption>\n\n  <script>\n    •constructor(data)• {\n      this.caption = data.caption || ''\n    }\n  </script>\n</figure>\n`\n\nconst JSX = `\nimport { FormEvent } from 'react';\n\n/*\n  Multi-line comment goes here\n*/\nexport default function Page() {\n  async function onSubmit(event: FormEvent<Element>) {\n+   const response = await fetch('/api/submit', {\n+     method: 'POST',\n      body: formData,\n    });\n  }\n\n  return (\n    <form onSubmit=•{onSubmit}•>\n>     <input type=\"text\" name=\"name\" />\n      <button type=\"submit\">Submit</button>\n    </form>\n  );\n}`\n\nconst CSS = `\n@import \"../css/dark.css\";\n\n/* Let's check out CSS code */\n.syntax {\n  border: 1px solid #fff1 !important;\n  background-color: var(--base-600);\n  border-radius: var(--radius);\n  margin-bottom: 3em;\n\n  @media(width < 900) {\n    transform: scale(1.1);\n    filter: blur(4px);\n  }\n\n  @starting-style {\n    transition: transform 4s;\n  }\n}\n`\nconst JAVASCRIPT = `\n\"use strict\"\n\n// import some UI stuff\nimport { layout } from 'components/layout'\n\n// environment\nconst ENV = {\n  scripts: ['lol.js'],\n  styles: ['lma\\\\no.css'],\n  desc: undefined\n}\n\nexport default function({ val }) {\n  const fooo = val.split('\\\\n') // 30px\n  return \\`<div class='node'></div>\\`\n}\n`\n\n// Markdown\nconst MARKDOWN = `\n---\ntitle: Lightning CSS might yield our thinking\ntags: [ •css, design systems• ]\npubDate: 2024-02-12\n---\n\n# This is something about Lightning CSS\nI'm baby else umami wolf yield batch iceland\nadaptogen. Iceland **chambray** raclette stumptown\n\n![Hey](/world.png)\n\n> Air plant adaptogen artisan gastropub deep v dreamcatcher\n> Pinterest intelligentsia gluten-free truffaut.\n\n* first\n* second\n\n[grid]\n  nollie: \"Something\"\n  list: [ foo, bar ]\n  foo: 10\n  bar: 30\n`\n\nconst YAML = `\ntitle: Do this or else that yield happens\ntags: [ function, default, const ]\ndate: 2024-02-12\nmore: \"strings\"\ncount: 10\nxmas: true\n\n# Comment here\nDocumentation:\n  Hello World: /syntax-test\n  Nothing goes: /morphine/boss \"hello\"\nlist:\n  - Michelangelo \"boost\"\n`\n\n\nconst NUEMARK = `\n---\ntitle: Noel's cringe content\n|description: Not much to say\nunlisted: true\n---\n\n# Lets get magical\nI'm baby truffaut umami wolf small batch iceland\nadaptogen. Iceland **chambray** raclette stumptown\n\n// line comment here\n[table head=\"Foo | Bar | Baz\"]\n  - Content first               | + | + | +\n  - Content collections         | + | + | +\n  - Hot-reloading               | + | + | +\n  - AI content generation       | + | + | +\n\n> This is my blockquote right here\n\n[.listbox]\n  * Nothing here to see\n  * This one is a banger\n\n  ![Hello](/banger.png)\n\n[image loading=\"eager\"]\n| small: \"/img/explainer-tall.png\"\n  src: \"/img/explainer.png\"\n  hidden: true\n  width: 800\n`\n\n\nconst MDX = `\nimport {Chart} from './snowfall.js'\nexport const year = 2023\n\n# Last year’s snowfall\n\nIn {year}, the snowfall was above average.\nIt was followed by a warm spring which caused\nflood conditions in many of the nearby rivers.\n\n![Hey](/world.png)\n\n> Air plant adaptogen artisan gastropub deep v dreamcatcher\n> Pinterest intelligentsia gluten-free truffaut.\n\n<Chart year={year} color=\"#fcb32c\" />\n\n<Elemment { ...attr }>\n  <p class=\"epic\">Yo</p>\n</Element>\n`\n\n\n\nconst SHELL = `\n#!/bin/bash\n\nmyfile = 'cars.txt'\n\ntouch $myfile\nif [ -f $myfile ]; then\n   rm cars.txt\n   echo \"$myfile deleted\"\nfi\n\n# open demo on the browser\nopen \"http://localhost:8080\"\n`\n\nconst TOML = `\n# This is a TOML document\n\ntitle = \"TOML Example\"\n\n[owner]\nname = \"Tom Preston-Werner\"\ndob = 1979-05-27T07:32:00-08:00\n\n[database]\nenabled = true\nports = [ 8000, 8001, 8002 ]\ndata = [ [\"delta\", \"phi\"], [3.14] ]\ntemp_targets = { cpu = 79.5, case = 72.0 }\n`\n\nconst ZIG = `\nconst std = @import(\"std\");\nconst parseInt = std.fmt.parseInt;\n\ntest \"parse integers\" {\n    const input = \"123 67 89,99\";\n    const ally = std.testing.allocator;\n\n    // Ensure the list is freed at scope exit\n    defer list.deinit();\n\n    var it = std.mem.tokenizeAny(u8, input, \" ,\");\n    while (it.next()) |num| {\n        const n = try parseInt(u32, num, 10);\n        try list.append(n); // EOL comment\n    }\n}\n`\n\nconst CPP = `\n#include <iostream>\nusing namespace std;\n\nint main() {\n\n  int first_number, second_number, sum;\n\n  cout << \"Enter two integers: \";\n  cin >> first_number >> second_number;\n\n  // sum of two numbers in stored\n  sum = first_number + second_number;\n\n  // prints sum\n  cout << first_number << \" + \"\n    <<  second_number << \" = \" << sum;\n\n  return 0;\n}\n`\n\nconst GO = `\npackage main\n\nimport \"fmt\"\n\n// fibonacci is a function that returns a function\nfunc fibonacci() func() int {\n  f2, f1 := 0, 1\n  return func() int {\n    f := f2\n    f2, f1 = f1, f+f1\n    return f\n  }\n}\n\nfunc main() {\n  f := fibonacci()\n  for i := 0; i < 10; i++ {\n    fmt.Println(f())\n  }\n}\n`\n\nconst JSON = `\n{\n  \"author\": \"John Doe <john.doe@gmail.com>\",\n  \"keywords\": [\"json\", \"es5\"],\n  \"version\": 1.5,\n  \"keywords\": [\"json\", \"json5\"],\n  \"version\": 1.7,\n\n  \"scripts\": {\n    \"test\": \"mocha --ui exports --reporter spec\",\n    \"build\": \"./lib/cli.js -c package.json5\",\n  }\n}\n`\n\nconst JSON5 = `\n{\n  // this is a JSON5 snippet\n  author: 'John Doe <john.doe@gmail.com>',\n- keywords: ['json', 'es5'],\n- version: 1.5,\n+ keywords: ['json', 'json5'],\n+ version: 1.7,\n\n  scripts: {\n    test: 'mocha --ui exports --reporter spec',\n    build: './lib/cli.js -c package.json5',\n  }\n}\n`\n\nconst TS = `\n// user interface\ninterface User { name: string;  id: number; }\n\n// account interface\nclass UserAccount {\n  name: string;\n  id: number;\n\n  constructor(name: string, id: number) {\n    this.name = name;\n    this.id = id;\n  }\n}\n\nconst user: User = new UserAccount(\"Murphy\", 1);\n`\n\n\nconst STYLED = `\nimport styled from 'styled-components';\n\nconst Wrapper = styled.section\\`\n  background: papayawhip;\n  color: \\${aquasky},\n  padding: 4em;\n\\`;\n\n// Wrapper becomes a React component\nrender(\n  <Wrapper defaultOpened=\"yes\">\n    <Title>\n      Hello World!\n    </Title>\n  </Wrapper>\n);\n`\n\nconst ASTRO = `\n\\\\---\nimport MyComponent from \"./MyComponent.astro\";\nconst items = [\"Dog\", \"Cat\", \"Platypus\"];\n\\\\---\n\n<ul>\n> {items.map((item) => (\n    <li>{item}</li>\n  ))}\n</ul>\n\n<!-- renders as <div>Hello!</div> -->\n<Element>\n- <p>Hello</p>\n+ <p>Hello!</p>\n</Element>\n`\n\nconst HASKELL = `\nputTodo :: (Int, String) -> IO ()\nputTodo (n, todo) = putStrLn (show n ++ \": \" ++ todo)\n\nprompt :: [String] -> IO ()\nprompt todos = do\n  putStrLn \"\"\n  putStrLn \"Current TODO list:\"\n  mapM_ putTodo (zip [0..] todos)\n\ndelete :: Int -> [a] -> Maybe [a]\n`\n\nconst PYTHON = `\n# Function definition\ndef find_square(num):\n    result = num * num\n    return result\n\n'''\nThis is a multiline comment\n'''\nsquare = find_square(3) // 2\n\n# Weirdoes\nif (False) continue\nelif (True) nonlocal + zoo\nelse None\n\nprint('Square:', square)\n`\n\n\nconst JAVA = `\n// Importing generic Classes/Files\nimport java.io.*;\n\nclass GFG {\n\n  // Function to find the biggest of three numbers\n  static int biggestOfThree(int x, int y, int z) {\n    return z > (x > y ? x : y) ? z : ((x > y) ? x : y);\n  }\n\n  // Main driver function\n  public static void main(String[] args) {\n    int a, b, c;\n    a = 5; b = 10; c = 3;\n\n    // Calling the above function in main\n    largest = biggestOfThree(a, b, c);\n  }\n}\n`\n\nconst KOTLIN = `\n@OptIn(DelicateCoroutinesApi::class)\n\nfun main() = runBlocking {\n  val job = GlobalScope.launch {\n    // root coroutine with launch\n    println(\"Throwing exception from launch\")\n    throw IndexOutOfBoundsException()\n  }\n  try {\n    deferred.await()\n    println(\"Unreached\")\n  } catch (e: ArithmeticException) {\n    println(\"Caught ArithmeticException\")\n  }\n}\n`\n\nconst RUST = `\nuse std::fmt::{ Debug, Display };\n\n// all drinks are emptied\nfn compare_prints<T: Debug + Display>(t: &T) {\n  println!(\"Debug: \\`{:?}\\`\", t);\n}\n\nfn compare_types<T: Debug, U: Debug>(t: &T, u: &U) {\n  println!(\"t: \\`{:?}\\`\", t);\n}\n`\nconst PERL = `\n#!/usr/bin/perl\nuse warnings;\nuse Path::Tiny;\n\n# foo/bar\nmy $dir = path('foo','bar');\n\n# Iterate over the content of foo/bar\nmy $iter = $dir->iterator;\n\nwhile (my $file = $iter->()) {\n\n  # Print out the file name and path\n  print \"$file\";\n}\n`\n\nconst LUA = `\n\\\\-- This here is a comment\nfunction perm (a)\n  local n = table.getn(a)\n  return coroutine.wrap(function () permgen(a, n) end)\nend\n\n\n\\\\-- Another function\nfunction printResult (a)\n  for i,v in ipairs(a) do\n    io.write(v, \" \")\n  end\n  io.write(\"hello\")\nend\n`\n\nconst RUBY = `\n\n# line comment here\ndef get_numbers_stack(list)\n  stack  = [[0, []]]\n  output = []\n\n  =begin\n    Ruby multiline comments are pretty weirdoes\n    Or maybe not??\n  =end\n  until stack.empty?\n    index, taken = stack.pop\n    next output << taken if index == list.size\n    stack.unshift [index + 1, taken]\n    stack.unshift [index + 1, taken + [list[index]]]\n  end\n  output\nend\n`\n\nconst PHP = `\n<!DOCTYPE html>\n\n<!-- HTML comment -->\n<form method=\"get\" action=\"target_proccessor.php\">\n  <input type=\"search\" name=\"search\">\n  <input type=\"submit\" name=\"submit\" value=\"Search\">\n\n  <?php\n    // inline PHP comment\n    $camp = array(\"zero\" => \"free\", \"one\" => \"code\" );\n    print_r($camp);\n  ?>\n</form>\n`\n\nconst CSHARP = `\npublic void MyTaskAsync(string[] files) {\n\n  MyTaskWorker worker = new MyTaskWorker(MyTaskWorker);\n  AsyncCallback fooback = new AsyncCallback(MyTask);\n\n  lock (_sync) {\n    if (_myTaskIsRunning)\n      throw new OperationException(\n        \"The control is busy.\"\n      );\n\n    // one-line comment here\n    AsyncOperation async = Async.CreateOperation(null);\n    bool cancelled;\n\n    worker.BeginInvoke(files, context, out cancelled);\n\n    _myTaskIsRunning = true;\n    _myTaskContext = context;\n  }\n}\n`\n\nconst CLOJURE = `\n(ns clojure.examples.hello\n   (:genclass))\n\n;; This program displays Hello World\n(defn Example []\n  (println [+ 1 2 3]))\n(Example)\n\n:dev-http {8080 \"public\"}\n  :builds\n  {:app\n    {:target :browser\n      :output-dir \"public/app/js\"\n`\n\nconst NIM = `\nimport std/strformat\n\ntype\n  Person = object\n    name: string\n    age: Natural # Ensures the age is positive\n\nlet people = [\n  Person(name: \"John\", age: 45),\n  Person(name: \"Kate\", age: 30)\n]\n\nfor person in people:\n  # Type-safe string interpolation,\n  # evaluated at compile time.\n  echo(fmt\"{person.name} is {person.age} years old\")\n`\n\nconst CRYSTAL = `\n# A very basic HTTP server\nrequire \"http/server\"\n\nserver = HTTP::Server.new do |context|\n  context.response.content_type = \"text/plain\"\n  context.response.print \"Hello world, got #{context}\"\nend\n\nputs \"Listening http://127.0.0.1:8080\"\nserver.listen(8080)\n`\n\nconst JULIA = `\nfunction finalize_ref(r::AbstractRemoteRef)\n  # Check if the finalizer is already run\n\n  if islocked(client_refs) || 100\n      # delay finalizer for later\n      finalizer(finalize_ref, r)\n      return nothing # really nothing\n  end\n\n  t = @task begin; sleep(5); println('done'); end\n\n  # lock should always be followed by try\n  Threads.@threads for i = 1:10\n    a[i] = Threads.threadid()\n  end\nend\n`\n\nconst HB = `\n{#\n  Mixed Django style comment\n#}\n\n<h1>\n  {{#if quotaFull}}\n    Please come back tomorrow.\n  {{/if}}\n</h1>\n\n<!-- handlebars example -->\n<ul>\n  {{#each serialList}}\n    <li>{{this}}</li>\n  {{/each}}\n</ul>\n`\n\nconst SVELTE = `\n<script>\n  // line comment\n  import Info from './Info.svelte';\n\n  const pkg = {\n    name: 'svelte',\n    version: 3,\n    speed: 'blazing',\n    website: 'https://svelte.dev'\n  };\n</script>\n\n<!-- layout goes here -->\n<p>These styles...</p>\n<Nested />\n<Info {...pkg} />\n\n<style>\n  /* CSS comment */\n  p {\n    color: purple;\n    font-family: 'Comic Sans MS', cursive;\n    font-size: 2em;\n  }\n</style>\n`\n\nconst SQL = `\nSELECT\n  date_trunc('week', orderdate),\n  •count(1)•\n\nFROM orders\n\n•WHERE orderdate between• '2024-01-01' AND '2024-02-01'\n\nRANK() OVER (ORDER ••BY __ order_amount DESC••)\n\nINNER JOIN payment_status p ON o.status_id = p.id;\n`\n\nasync function renderPage(items) {\n  const html = [\n    '<meta charset=\"utf-8\">',\n    '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">',\n    '<link rel=\"stylesheet\" href=\"glow-test.css\">',\n    '<body class=\"is-dark\">'\n  ]\n\n  items.forEach(opts => {\n    const { title } = opts\n    const language = opts.lang || title.toLowerCase()\n    const code = glow(opts.code, { language, numbered: true })\n\n    html.push(`\n      <div class=\"syntax ${opts.class || ''}\">\n        <header><h2>${opts.title || language}</h2></header>\n        <pre glow>${code}</pre>\n      </div>\n    `)\n  })\n\n  html.push('</body>')\n\n  // save\n  const path = join(root, 'glow-test.html')\n  await fs.writeFile(path, html.join('\\n'), 'utf-8')\n  console.info('wrote', path)\n}\n\n\nawait renderPage([\n  { title: 'Astro', code: ASTRO, },\n  { title: 'C#', code: CSHARP },\n  { title: 'C++', code: CPP, lang: 'cpp', },\n  { title: 'Clojure Script', code: CLOJURE, lang: 'clojure' },\n  { title: 'Crystal', code: CRYSTAL, lang: 'crystal' },\n  { title: 'CSS', lang: 'css', code: CSS },\n  { title: 'GO', code: GO, lang: 'go', },\n  { title: 'Handlebars', code: HB, lang: 'hb' },\n  { title: 'Haskell', code: HASKELL, },\n  { title: 'HTML', lang: 'html', code: HTML, },\n  { title: 'Java', code: JAVA, lang: 'java' },\n  { title: 'JavaScript', code: JAVASCRIPT, lang: 'js', },\n  { title: 'JSON', code: JSON, lang: 'json', },\n  { title: 'JSON5', code: JSON5, lang: 'json5', },\n  { title: 'JSX', code: JSX, lang: 'jsx' },\n  { title: 'Julia', code: JULIA, lang: 'julia' },\n  { title: 'Kotlin', code: KOTLIN, lang: 'java' },\n  { title: 'Lua', code: LUA, lang: 'lua' },\n  { title: 'Markdown', code: MARKDOWN, lang: 'md', },\n  { title: 'MDX', code: MDX, lang: 'mdx', },\n  { title: 'Nim', code: NIM, lang: 'nim' },\n  { title: 'Nuemark', code: NUEMARK, lang: 'nuemark', },\n  { title: 'Perl', code: PERL, lang: 'perl' },\n  { title: 'PHP', code: PHP, lang: 'php' },\n  { title: 'Python', code: PYTHON, lang: 'python', },\n  { title: 'Ruby', code: RUBY, lang: 'ruby' },\n  { title: 'Rust', code: RUST, lang: 'rust' },\n  { title: 'Shell', code: SHELL, lang: 'sh', },\n  { title: 'SQL', code: SQL, class: '_editing-demo' },\n  { title: 'Styled component', code: STYLED, lang: 'jsx', },\n  { title: 'Svelte', code: SVELTE },\n  { title: 'TOML', code: TOML, lang: 'toml', },\n  { title: 'TypeScript', code: TS, lang: 'ts', },\n  { title: 'ZIG', code: ZIG, lang: 'zig', },\n  { title: 'YAML', code: YAML, lang: 'yaml', },\n\n]//.filter(el => ['md'].includes(el.lang))\n  // ]\n)\n"
  },
  {
    "path": "packages/nueglow/test/glow-test.css",
    "content": "\n@import \"../css/syntax.css\";\n@import \"../css/markers.css\";\n/*@import \"../css/light.css\";*/\n\n*, *::before, *::after { box-sizing: border-box; }\n\n:root { --gap: clamp(1em, 5vmin, 3rem); }\n\nbody {\n  font-family: sf pro display;\n  margin: var(--gap);\n  gap: var(--gap);\n  .syntax { break-inside: avoid; }\n  @media (min-width: 1000px) {\n    column-count: 2;\n  }\n}\n\n\n/* wrapper */\n.syntax {\n  border-radius: 6px;\n  margin-bottom: var(--gap);\n  header { padding: .7em 1.5em; }\n\n  h2 {\n    letter-spacing: 0.02em;\n    font-weight: 400;\n    font-size: 90%;\n    text-align: center;\n    margin: .0;\n  }\n}\n\n/* dark == default */\nbody {\n  background: #111729;\n  color: #fff;\n\n  .syntax {\n    background-color: #20293A;\n    border-color:  #fff1;\n    box-shadow: none;\n    header { border-bottom: 1px solid #fff1; }\n  }\n}\n\n.is-light {\n  background: #f3f8fa;\n  color: #444;\n\n  .syntax {\n    background-color: #fff;\n    border: 1px solid #eee;\n    box-shadow: 0 0 .5em #eee;\n    header { border-bottom: 1px solid #eee; }\n  }\n}\n\n\n\n"
  },
  {
    "path": "packages/nueglow/test/glow.test.js",
    "content": "import { parseRow, parseSyntax, renderRow, glow } from '..'\n\n\ntest('HTML', () => {\n  const row = '<div class=\"hello\">'\n\n  // parse\n  const [char, prop, ...rest] = parseRow(row)\n\n  expect(char.tag).toBe('i')\n  expect(prop.tag).toBe('strong')\n  expect(prop.start).toBe(5)\n\n  expect(rest.length).toBeGreaterThan(4)\n\n  // render\n  const html = renderRow(row)\n  expect(html).toStartWith('<i>&lt;</i>')\n  expect(html).toInclude('<strong>class</strong>')\n  expect(html).toInclude('<em>\"hello\"</em>')\n})\n\ntest('Emphasis', () => {\n  const html = renderRow('Hey •[img]• girl')\n  expect(html).toInclude('Hey <mark>')\n  expect(html).toInclude('</i></mark> girl')\n})\n\n\n/* multiline comments */\ntest('parse HTML comment', () => {\n  const blocks = parseSyntax(['<div>', '<!--', 'comment', '-->', '</div>'])\n  expect(blocks[1].comment[0]).toBe('<!--')\n})\n\n\ntest('parse JS comment', () => {\n  const blocks = parseSyntax(['/* First */', 'function() {', '/*', 'Second', '*/'])\n  expect(blocks[0].comment).toEqual(['/* First */'])\n  expect(blocks[2].line).toEqual('/*')\n})\n\n/* prefix and mark */\ntest('disable mark', () => {\n  const html = renderRow('Hey •[img]• girl', undefined, false)\n  expect(html).toInclude('Hey •')\n  expect(html).toInclude('• girl')\n})\n\ntest('escape prefixes', () => {\n  const blocks = parseSyntax([\n    '\\\\+ not really adding a line',\n    '\\\\- not really removing a line',\n    '\\\\| not really marking a line'\n  ], 'md')\n\n  expect(blocks[0].line).toEqual('+ not really adding a line')\n  expect(blocks[1].line).toEqual('- not really removing a line')\n  expect(blocks[2].line).toEqual('| not really marking a line')\n})\n\ntest('disable prefixes', () => {\n  const blocks = parseSyntax([\n    '+ not really adding a line',\n    '- not really removing a line',\n    '> not really marking a line'\n  ], undefined, false)\n\n  expect(blocks[0].wrap).toEqual(false)\n  expect(blocks[1].wrap).toEqual(false)\n  expect(blocks[2].wrap).toEqual(false)\n})\n"
  },
  {
    "path": "packages/nuekit/Makefile",
    "content": "\ntests:\n\tcd test && bun test"
  },
  {
    "path": "packages/nuekit/README.md",
    "content": "\n# The UNIX of the Web\nWeb development became complicated. Hundreds of packages, 400MB of dependencies, hours of configuration before writing a single line of code. We forgot that it doesn't have to be this way.\n\n\n## How web development should work\n\n**Instant start** - Create `index.html` or `index.md` and you're running. No setup, no configuration, no waiting.\n\n**Single-page apps** - Write semantic HTML with dynamic expressions. Import business logic from pure JavaScript modules. Let your design system handle presentation.\n\n**Content sites** - Front pages, documentation, blogs, marketing pages. Write Nuemark content, add layout modules for structure, trust your design system for consistency.\n\n**Universal hot reload** - Content, CSS, layouts, data, components, server routes, configurations. Save and watch the browser update instantly.\n\n**Complete system** - Content sites, SPAs, server routes, backend models.\n\nOne tool, complete control. The UNIX philosophy applied to web development.\n\n**Nue is the entire ecosystem in 1MB**\n\n\nVisit [Nue website](https://nuejs.org) for comprehensive documentation.\n\n\n## Migration from React/Next.js\n\n**Less scaffolding** - From 500MB+ of node_modules to 1MB global install. From complex project setups to just `index.html` to get started.\n\n**Pure separation** - Business logic in JS modules. Structure in HTML. Design in CSS. No more mixed concerns in components.\n\n**Faster everything** - Builds in milliseconds. Hot reload across frontend and backend. Pages 10x smaller.\n\nSee the [migration guide](https://nuejs.org/docs/migration) for the complete story.\n\n\n"
  },
  {
    "path": "packages/nuekit/client/error.js",
    "content": "\nfunction renderDialog(e) {\n  return `\n    <dialog id=\"nuerror\">\n      <header>\n        <h2>${ e.title }</h2>\n        <p>${ e.text } at ${ e.path } (${ e.line } / ${ e.column })</p>\n      </header>\n\n      <pre>\n        ${ e.lineText }\n      </pre>\n\n      <footer>\n        <button popovertarget=\"nuerror\">Close</button>\n      </footer>\n    </dialog>\n  `\n}\n\nexport async function showError(error) {\n  window.nuerror?.remove()\n  document.body.insertAdjacentHTML('beforeend', renderDialog(error))\n  window.nuerror.showPopover()\n}\n\n"
  },
  {
    "path": "packages/nuekit/client/hmr.js",
    "content": "\n\nfunction createConnection() {\n  const server = new WebSocket(`ws://${location.host}`)\n\n  server.onmessage = async function(e) {\n    const asset = JSON.parse(e.data)\n\n    return (asset.is_yaml || asset.is_js || asset.is_ts) ? location.reload()\n      : asset.error ? await handleError(asset)\n      : asset.is_md ? await reloadContent(asset)\n      : asset.is_html ? await reloadHTML(asset)\n      : asset.is_svg ? reloadVisual(asset)\n      : asset.is_css ? reloadCSS(asset)\n      : null\n  }\n\n  // reconnect after 1 second\n  server.onclose = function() {\n    console.log('HMR reconnecting...')\n    setTimeout(createConnection, 3000)\n  }\n\n  server.onerror = function() {\n    server.close()\n  }\n}\n\ncreateConnection()\n\n\nasync function handleError(asset) {\n  const { showError } = await import('./error.js')\n  const { error, path } = asset\n  showError({ ...error, path })\n}\n\nasync function reloadContent(asset) {\n  const { url } = asset\n\n  if (url != location.pathname) return location.href = url\n\n  // domdiff\n  const { mountAll } = await import('./mount.js')\n  const { domdiff } = await import('/@nue/nue.js')\n\n  const { title, body } = parsePage(asset.content)\n  if (title) document.title = title\n  const lib = asset.ast?.lib\n\n  // focused HMR\n  if (lib?.length == 1) {\n    const { tag } = lib[0]\n    domdiff($(tag), body.querySelector(tag))\n\n  // diff everything\n  } else {\n    domdiff($('body'), body)\n  }\n\n  await mountAll()\n}\n\n\nlet reload_count = 0\n\nfunction reloadVisual(asset) {\n\n  // HMR mode\n  if (location.pathname.endsWith('.svg')) return reloadSVG(asset.content)\n\n  // reload <svg> and <object> tags\n  const { url } = asset\n\n  function reload(el, attr) {\n    if (el) el[attr] = `${url}?${reload_count++}`\n  }\n\n  reload($(`object[data*='${url}']`), 'data')\n  reload($(`img[src*='${url}']`), 'src')\n}\n\nfunction reloadSVG(html) {\n  const svg = html.slice(html.indexOf('<svg '), html.indexOf('</svg>') + 6)\n  document.body.innerHTML = svg\n}\n\nfunction reloadCSS(asset) {\n  const { url, content } = asset\n  const orig = $(`[href=\"${url}\"]`)\n  const style = createStyle(url, content)\n\n\n  if (orig) orig.replaceWith(style)\n  else document.head.appendChild(style)\n}\n\n\nasync function reloadHTML(asset) {\n  const { ast } = asset\n\n  return ast.is_dhtml ? await reloadComponents(asset)\n    : ast.is_lib ? location.reload()\n    : await reloadContent(asset)\n}\n\nasync function reloadComponents(asset) {\n  const { mountAll } = await import('./mount.js')\n  const state = saveState()\n  await mountAll(asset.path)\n  restoreState(state)\n}\n\n\n/***** helper functions *****/\n\nfunction createStyle(url, content) {\n  const el = document.createElement('style')\n  el.setAttribute('href', url)\n  el.innerHTML = content\n  return el\n}\n\n// state before remounting\nfunction saveState() {\n  const formdata = [...document.forms].map(form => new FormData(form))\n  const el = $('[popover]')\n  const popover = el?.checkVisibility() && el.id\n  const dialog = $('dialog[open]')?.id\n  return { formdata, popover, dialog }\n}\n\nfunction restoreState({ formdata, popover, dialog }) {\n  formdata.forEach((data, i) => deserialize(document.forms[i], data))\n\n  // re-open popover\n  if (popover) window[popover]?.showPopover()\n\n  // re-open dialog\n  if (dialog) {\n    const el = window[dialog]\n    if (el) { el.close(); el.showModal() }\n  }\n}\n\nfunction deserialize(form, data) {\n  for (const [key, val] of data.entries()) {\n    const el = form.elements[key]\n    if (el.type == 'checkbox') el.checked = !!val\n    else el.value = val\n  }\n}\n\nfunction parsePage(html) {\n  const root = document.createElement('html')\n  root.innerHTML = html\n  return { title: $('title', root)?.textContent, body: $('body', root) }\n}\n\nfunction $(query, root=document) {\n  return root.querySelector(query)\n}\n"
  },
  {
    "path": "packages/nuekit/client/mount.js",
    "content": "\n// both args are optional\nexport async function mountAll(reload_path) {\n  const roots = document.querySelectorAll('[nue]')\n  const deps = roots.length ? await importComponents(reload_path) : []\n  if (!deps.length) return\n\n  const { mount } = await import('/@nue/nue.js')\n\n  for (const root of [...roots]) {\n    const name = root.getAttribute('nue') || root.tagName.toLowerCase()\n    const comp = deps.find(a => [a.is, a.tag].includes(name))\n\n    if (comp) {\n      const node = mount(comp, { root, deps, data: getData(root) })\n      node.root.setAttribute('nue', name)\n    }\n  }\n}\n\nfunction getData(root) {\n  if (root.firstChild?.tagName == 'SCRIPT') root.after(root.firstChild)\n  const script = root.nextElementSibling\n  if (script?.type == 'application/json') return JSON.parse(script.textContent)\n}\n\nexport function getImportPaths() {\n  const el = document.querySelector('[name=\"libs\"]')\n  return el ? el.getAttribute('content').split(' ') : []\n}\n\nlet reload_count = 0\n\nasync function importComponents(reload_path) {\n  const comps = []\n\n  for (let path of getImportPaths()) {\n    const count = path == reload_path ? `?${ reload_count++ }` : ''\n    const { lib } = await import(`/${path}.js${count}`)\n    if (lib) comps.push(...lib)\n  }\n\n  return comps\n}\n\n\n// initial page load\naddEventListener('route', () =>  mountAll())\n\naddEventListener('DOMContentLoaded', () => dispatchEvent(new Event('route')))\n"
  },
  {
    "path": "packages/nuekit/client/transitions.js",
    "content": "// Client-side routing and view transitions for multipage applications\n\nfunction $(query, root = document) {\n  return root.querySelector(query)\n}\n\nfunction $$(query, root = document) {\n  return [...root.querySelectorAll(query)]\n}\n\nconst scrollPos = {}\nconst cache = {}\n\nexport async function loadPage(path) {\n  dispatchEvent(new Event('before:route'))\n\n  // DOM of the new page\n  const dom = createDOM(await fetchHTML(path))\n\n  // update page metadata and scripts\n  await updatePageHead(dom)\n\n  // update stylesheets\n  const sheets = updatePageStyles(dom)\n  await loadStylesheets(sheets)\n\n  // inline style tag (single)\n  updateInlineStyle(dom)\n\n  // update page content - keep the working logic\n  const ignoreMain = updateContent($('main'), $('main', dom))\n  updateContent($('body'), $('body', dom), ignoreMain)\n\n\n  dispatchRouteEvents()\n  setActive(path)\n}\n\nasync function updatePageHead(dom) {\n  // title\n  const title = $('title', dom)?.textContent\n  if (title) document.title = title\n\n  // update component list\n  updateMeta('libs', dom)\n\n  // load scripts (modules are loaded only once by the browser)\n  for (const script of $$('script[src]', dom)) {\n    await import(script.getAttribute('src'))\n  }\n}\n\n\nfunction updateMeta(name, dom) {\n  $(`meta[name=\"${name}\"]`)?.remove()\n  const meta = $(`meta[name=\"${name}\"]`, dom)\n  if (meta) $('head').appendChild(meta)\n}\n\nfunction handlePageScroll() {\n  const { hash } = location\n  const el = hash && $(hash)\n  const scrollTop = el ?\n    el.offsetTop - (parseInt(getComputedStyle(el).scrollMarginTop) || 0) :\n    0\n  scrollTo(0, scrollTop)\n}\n\nfunction dispatchRouteEvents() {\n  dispatchEvent(new Event('route'))\n  const [_, app] = location.pathname.split('/')\n  dispatchEvent(new Event(`route:${app || 'home'}`))\n}\n\n// setup linking\nexport function onclick(root, fn) {\n  root.addEventListener('click', e => {\n    const el = e.target.closest('[href]')\n    if (!el) return\n\n    const path = el.getAttribute('href')\n    const target = el.getAttribute('target')\n    const filename = path?.split('/')?.pop()?.split(/[#?]/)?.shift()\n\n    if (shouldIgnoreClick(e, path, target, filename)) return\n\n    // all good\n    if (path != location.pathname) fn(el.pathname, el)\n    e.preventDefault()\n  })\n}\n\nfunction shouldIgnoreClick(e, path, target, filename) {\n  return e.defaultPrevented || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey ||\n    !path || path[0] == '#' || path.includes('//') || path.startsWith('mailto:') ||\n    (filename?.includes('.') && !filename.endsWith('.html')) || !!target\n}\n\nexport function toRelative(path) {\n  const curr = location.pathname\n  return curr.slice(0, curr.lastIndexOf('/') + 1) + path\n}\n\nexport function setActive(path, attrname = 'aria-current') {\n  if (path[0] != '/') path = toRelative(path)\n\n  // remove old selections\n  $$(`[${attrname}]`).forEach(el => el.removeAttribute(attrname))\n\n  // add new ones\n  $$('a').forEach(el => {\n    if (!el.hash && el.pathname == path) {\n      el.setAttribute(attrname, 'page')\n    }\n  })\n}\n\nexport function setupTransitions() {\n  // view transition fallback (Safari, Firefox) • caniuse.com/view-transitions\n  if (!document.startViewTransition) {\n    document.startViewTransition = (fn) => fn()\n  }\n\n  // Fix: window.onpopstate, event.state == null?\n  // https://stackoverflow.com/questions/11092736/window-onpopstate-event-state-null\n  history.pushState({ path: location.pathname }, 0)\n\n  // save scroll position whenever user scrolls\n  addEventListener('scroll', () => {\n    scrollPos[location.pathname] = window.scrollY\n  })\n\n  // autoroute / document clicks\n  onclick(document, async (path, el) => {\n    const img = $('img', el)\n    if (img) img.style.viewTransitionName = 'active-image'\n\n    document.startViewTransition(async () => {\n      await loadPage(path)\n      history.pushState({ path }, 0, path)\n      handlePageScroll()\n    })\n  })\n\n  // initial active\n  setActive(location.pathname)\n\n  // back button\n  addEventListener('popstate', e => {\n    const { path } = e.state || {}\n\n    if (path) {\n      const pos = scrollPos[path]\n\n      document.startViewTransition(async () => {\n        await loadPage(path)\n        scrollTo(0, pos || 0)\n      })\n    }\n  })\n}\n\n/* -------- utilities ---------- */\n\nfunction haveSameChildren(a, b) {\n  if (a.children.length != b.children.length) return false\n\n  for (let i = 0; i < a.children.length; i++) {\n    if (a.children[i].tagName != b.children[i].tagName) return false\n  }\n\n  return true\n}\n\n// smart DOM diffing and updating\nexport function updateContent(current, incoming, ignoreMain) {\n  if (!current || !incoming) return true\n\n  current.className = incoming.className\n\n  if (haveSameChildren(current, incoming)) {\n    Array.from(current.children).forEach((el, i) => {\n      if (!(ignoreMain && el.tagName == 'MAIN')) {\n        updateElement(el, incoming.children[i])\n      }\n    })\n    return true\n  } else {\n    current.innerHTML = incoming.innerHTML\n    return false\n  }\n}\n\nfunction updateElement(current, incoming) {\n  const currentHTML = current.outerHTML.replace(' aria-current=\"page\"', '')\n  if (currentHTML != incoming.outerHTML) {\n    current.replaceWith(incoming.cloneNode(true))\n  }\n}\n\nfunction updatePageStyles(dom) {\n  const sheets = findNewStyles($$('link'), $$('link', dom))\n  sheets.forEach(style => $('head').appendChild(style))\n  return sheets\n}\n\nexport function findNewStyles(current, incoming) {\n  // disable styles not in incoming\n  current.forEach(el => {\n    const href = el.getAttribute('href')\n    el.disabled = !incoming.find(style => style.getAttribute('href') == href)\n  })\n\n  // return styles not in current\n  return incoming.filter(el => {\n    const href = el.getAttribute('href')\n    return !current.find(style => style.getAttribute('href') == href)\n  })\n}\n\nfunction updateInlineStyle(dom) {\n  $$('style').forEach(el => el.remove())\n  $$('style', dom).forEach(el => $('head').appendChild(el))\n}\n\nasync function fetchHTML(path) {\n  let html = cache[path]\n  if (html) return html\n\n  const resp = await fetch(path)\n  html = await resp.text()\n\n  if (resp.status == 404 && html?.trim()[0] != '<') {\n    const title = document.title = 'Page not found'\n    $('article').innerHTML = `<section><h1>${title}</h1></section>`\n  } else {\n    cache[path] = html\n  }\n\n  return html\n}\n\nfunction createDOM(html) {\n  const parser = new DOMParser()\n  return parser.parseFromString(html, 'text/html')\n}\n\nasync function loadStylesheets(linkElements) {\n  const promises = linkElements.map(link => loadStylesheet(link.href))\n  await Promise.all(promises)\n}\n\nfunction loadStylesheet(href) {\n  return new Promise(resolve => {\n    const link = document.createElement('link')\n    link.rel = 'stylesheet'\n    link.href = href\n    link.onload = resolve\n    $('head').appendChild(link)\n  })\n}\n\n// browser environment\nif (typeof window == 'object') setupTransitions()\n\n"
  },
  {
    "path": "packages/nuekit/package.json",
    "content": "{\n  \"name\": \"nuekit\",\n  \"version\": \"2.0.0-beta.2\",\n  \"description\": \"The UNIX of the Web\",\n  \"homepage\": \"https://nuejs.org\",\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"repository\": {\n    \"url\": \"https://github.com/nuejs/nue\",\n    \"directory\": \"packages/nuekit\",\n    \"type\": \"git\"\n  },\n  \"bin\": {\n    \"nue\": \"./src/cli.js\"\n  },\n  \"engines\": {\n    \"bun\": \">= 1.2.2\"\n  },\n  \"scripts\": {\n    \"test\": \"cd test && bun test\"\n  },\n  \"dependencies\": {\n    \"nue-edgeserver\": \"^0.1.0\",\n    \"nuestate\": \"^0.1.0\",\n    \"nueyaml\": \"^0.1.0\",\n    \"nuedom\": \"^0.1.0\",\n    \"nuemark\": \"^0.7.0\",\n    \"nue-glow\": \"^0.2.0\"\n  },\n\n  \"files\": [ \"src\", \"client\", \"favicon.ico\" ]\n}\n"
  },
  {
    "path": "packages/nuekit/src/asset.js",
    "content": "\nimport { join } from 'node:path'\n\nimport { parseNuemark } from 'nuemark'\nimport { parseYAML } from 'nueyaml'\nimport { parseNue } from 'nuedom'\n\nimport { renderMD, renderHTML } from './render/page'\nimport { getCollections } from './collections'\nimport { mergeConf, mergeData } from './conf'\nimport { listDependencies } from './deps'\nimport { mergeSharedData } from './site'\nimport { renderSVG } from './render/svg'\nimport { minifyCSS } from './tools/css'\n\n\nexport function createAsset(file, site={}) {\n  const { files=[], conf={} } = site\n  let cachedObj = null\n\n  function flush() {\n    cachedObj = null\n    file.flush()\n  }\n\n  function toAssets(paths) {\n    const arr = paths.map(path => files.find(file => file.path == path))\n    return arr.map(file => createAsset(file, site))\n  }\n\n  function getDeps(opts={}) {\n    const { include, exclude } = opts\n    const paths = files.map(f => f.path)\n    const deps = listDependencies(file.path, { paths, include, exclude })\n    return toAssets(deps)\n  }\n\n  async function config() {\n    const asset = getDeps().find(f => f.base == 'app.yaml')\n    return asset ? mergeConf(conf, await asset.parse()) : conf\n  }\n\n  async function data() {\n    const assets = getDeps()\n    const app_files = assets.filter(f => f.is_yaml && f.name != 'site' && f.basedir != '@shared')\n    const app_data = await Promise.all(app_files.map(f => f.parse()))\n    const ret = mergeData([conf, ...app_data])\n\n    // content collections\n    const colls = conf.collections\n\n    if (colls) {\n      const md_paths = files.filter(f => f.is_md).map(f => f.path)\n      Object.assign(ret, await getCollections(toAssets(md_paths), colls))\n    }\n\n    // shared data, functions, and transformation\n    await mergeSharedData(assets, ret)\n\n    return ret\n  }\n\n  // list all dependencies (public method)\n  async function assets() {\n    return getDeps(await config())\n  }\n\n  async function parse() {\n    if (!cachedObj) {\n      const str = await file.text()\n\n      cachedObj = file.is_js || file.is_ts ? await import(join(process.cwd(), file.path) + '?' + Math.random())\n        : file.is_json ? JSON.parsek(str)\n        : file.is_md ? parseNuemark(str)\n        : file.is_yaml ? parseYAML(str)\n        : parseNue(str)\n    }\n    return cachedObj\n  }\n\n  async function toExt() {\n    if (file.is_html) {\n      const { is_dhtml } = await parse()\n      if (is_dhtml) return '.html.js'\n    }\n    return file.is_md ? '.html' : file.is_ts ? '.js' : file.ext\n  }\n\n  async function contentType() {\n    const types = {\n      '.html.js': 'application/javascript',\n      '.html': 'text/html; charset=utf-8',\n      '.js': 'application/javascript',\n      '.svg': 'image/svg+xml',\n      '.css': 'text/css',\n    }\n\n    return types[await toExt()]\n  }\n\n  async function components(force_html) {\n    const { is_dhtml=false } = await parse()\n    const ret = []\n\n    for (const asset of (await assets()).filter(el => el.is_html)) {\n      const ast = await asset.parse()\n      const { doctype='' } = ast\n\n      if (ast.is_lib) {\n        const same_type = is_dhtml == ast.is_dhtml\n        const isomorphic = doctype.startsWith('html+dhtml')\n        const forced = force_html && !ast.is_dhtml\n        if (isomorphic || same_type || forced) ret.push(...ast.lib)\n      }\n    }\n\n    return ret\n  }\n\n  async function render(params) {\n    const asset = createAsset(file, site)\n    const { is_prod } = conf\n\n    if (file.is_svg) {\n      const { svg } = await config()\n      if (svg?.process) return await renderSVG(asset, { fonts: svg.fonts, ...params })\n    }\n\n    return file.is_js && is_prod || file.is_ts ? compileJS(file.rootpath, is_prod)\n      : file.is_css && is_prod ? minifyCSS(await file.text())\n      : file.is_html ? await renderHTML(asset)\n      : file.is_md ? await renderMD(asset)\n      : null\n  }\n\n  return { ...file, flush, config, data, assets, parse, components, toExt, contentType, render }\n}\n\n\nexport async function compileJS(path, minify, bundle) {\n  const result = await Bun.build({\n    external: bundle ? undefined : ['*'],\n    entrypoints: [path],\n    target: 'browser',\n    minify\n  })\n\n  const [ js ] = result.outputs\n  return await js.text()\n}\n\n"
  },
  {
    "path": "packages/nuekit/src/cli.js",
    "content": "#!/usr/bin/env bun\n\nimport { styleText } from 'node:util'\nimport { version } from './system'\n\n// [-npe] --> [-n, -p, -e]\nexport function expandArgs(args) {\n  const arr = []\n  args.forEach(str => {\n    if (str[0] == '-' && str[1] != '-' && str[2]) {\n      str.slice(1).split('').forEach(el => arr.push('-' + el))\n    } else {\n      arr.push(str)\n    }\n  })\n  return arr\n}\n\n\n\nexport function getArgs(argv) {\n  const commands = ['serve', 'dev', 'build', 'preview', 'create', 'push']\n\n  // default values\n  const args = { paths: [] }\n  let opt\n\n  expandArgs(argv).forEach((arg) => {\n\n    // skip\n    if (arg == '--') {\n\n    // command\n    } else if (!args.cmd && commands.includes(arg)) {\n      args.is_prod = arg == 'build'\n      args.cmd = arg\n\n    // options\n    } else if (!opt && arg[0] == '-') {\n\n      // global options\n      if (['-h', '--help'].includes(arg)) args.help = true\n      else if (['-v', '--version'].includes(arg)) args.version = true\n      else if (['-s', '--silent'].includes(arg)) args.silent = true\n      else if (['--verbose'].includes(arg)) args.verbose = true\n\n      // dev & preview options\n      else if (['-p', '--port'].includes(arg)) opt = 'port'\n\n      // build options\n      else if (['-n', '--dry-run'].includes(arg)) args.dryrun = true\n      else if (['-i', '--init'].includes(arg)) args.init = true\n      else if (['--clean'].includes(arg)) args.clean = true\n\n      // values\n\n      // bad argument\n      else throw `Unknown option: \"${arg}\"`\n\n    // values\n    } else if (opt) {\n      args[opt] = 1 * arg || arg\n      opt = null\n\n    } else {\n      args.paths.push(arg)\n    }\n  })\n\n  if (opt) throw `${opt} not set`\n\n  return args\n}\n\nconst HELP = `\nUsage\n  nue [command] [options] [file_matches]\n  nue -v or --version\n  nue -h or --help\n\nCommands\n  serve     Start development server (default command)\n  build     Build a production site\n  preview   Preview the production site\n  create    Use a project starter template\n\nOptions\n  -p or --port          Serve/preview the site on this port\n  -n or --dry-run       Show what would be built. Does not build anything\n  -s or --silent        Suppress output messages\n  -i or --init          build Nue runtime files (rare need)\n  --verbose             Show detailed output\n  --clean               Clean output directory before building\n\nFile matches\n  Only build files that match the rest of the arguments. For example:\n  \"nue build .ts .css\" will build all TypeScript and CSS files\n\nExamples\n  # serve current directory\n  nue\n\n  # build all Markdown and CSS files\n  nue build .md .css\n\n  # build with verbose output\n  nue build --verbose\n\n  # preview on specific port\n  nue preview --port 8080\n\n  # more examples\n  https://nuejs.org/docs/cli\n\n ┏━┓┏┓┏┳━━┓\n ┃┏┓┫┃┃┃┃━┫  ${version}\n ┃┃┃┃┗┛┃┃━┫  nuejs.org\n ┗┛┗┻━━┻━━┛\n`\n\n\nfunction format(line) {\n  return line.match(/^\\w[\\w ]+$/) ? styleText(['bold', 'white'],line) // titles\n    : '┏┃┗'.includes(line.trim()[0]) ? styleText(['green'], line) // ascii art\n    : line.includes('#') ? styleText(['cyan'], line) // comments\n    : styleText(['gray'], line)\n}\n\nexport function printHelp() {\n  console.info(HELP.split('\\n').map(format).join('\\n'))\n}\n\n\nfunction printVersion() {\n  console.log(`Nue ${ version } • Bun ${ Bun.version }`)\n}\n\nasync function run(args) {\n\n  // help\n  if (args.help) return printHelp()\n\n  // version\n  printVersion()\n  if (args.version) return\n\n\n  // command\n  const { cmd, paths } = args\n\n  // create\n  if (cmd == 'create') {\n    const { create } = await import('./cmd/create')\n    const [ name, dir ] = paths\n    return await create(name, { dir })\n  }\n\n  // push (private repo ATM)\n  if (cmd == 'push') {\n    const { fswalk } = await import('./tools/fswalk')\n    const { push } = await import('nuepush')\n    const files = await fswalk('.dist')\n    return await push(files, args)\n  }\n\n  // config\n  const { readSiteConf } = await import('./conf')\n  const conf = await readSiteConf(args)\n  if (!conf) return console.error('Not a Nue directory')\n\n  // preview\n  if (cmd == 'preview') {\n    const { preview } = await import('./cmd/preview')\n    return await preview(conf, args)\n  }\n\n  // site\n  const { createSite } = await import('./site')\n  const site = await createSite(conf)\n\n  // build\n  if (cmd == 'build' || paths.length) {\n    const { build } = await import('./cmd/build')\n    await build(site, args)\n\n  // dev | serve\n  } else if (!cmd || cmd == 'dev' || cmd == 'serve') {\n    const { serve } = await import('./cmd/serve')\n    await serve(site, args)\n  }\n\n}\n\nconst { argv } = process\n\nif (argv[1].endsWith('cli.js')) {\n  const args = getArgs(argv.slice(2))\n  await run(args)\n}\n\n\n"
  },
  {
    "path": "packages/nuekit/src/cmd/build.js",
    "content": "\nimport { mkdir, rmdir, writeFile, unlink } from 'node:fs/promises'\nimport { join, sep } from 'node:path'\nimport { tmpdir } from 'os'\n\nimport { generateSitemap, generateFeed } from '../render/feed'\nimport { createSystemFiles } from '../system'\n\nexport async function build(site, args) {\n  const { paths=[], dryrun, silent } = args\n  const { conf, assets } = site\n  const { dist } = conf\n\n  // subset\n  const subset = paths.length ? assets.filter(el => matches(el.path, paths)) : assets\n\n  if (dryrun) {\n    subset.filter(el => !el.is_yaml).forEach(el => console.log(el.path))\n    return subset\n  }\n\n  // build subset\n  const start = performance.now()\n  await buildAll(subset, { ...args, dist })\n\n  // sitemap.xml\n  if (!paths.length && conf.sitemap?.enabled) {\n    const xml = await generateSitemap(assets, conf)\n    if (xml) await writeFile(join(dist, 'sitemap.xml'), xml)\n  }\n\n  // feed.xml\n  if (!paths.length && conf.rss?.enabled) {\n    const xml = await generateFeed(assets, conf)\n    if (xml) await writeFile(join(dist, 'feed.xml'), xml)\n  }\n\n  // stats\n  if (!silent) {\n    stats(subset).forEach(line => console.log(line))\n    const time = Math.round(performance.now() - start)\n    console.log(`-----\\nBuilt in: ${time}ms\\n`)\n  }\n}\n\n\nexport async function buildAll(subset, args) {\n  const { dist, init, verbose, clean } = args\n\n  // .dist directory\n  if (clean) await rmdir(dist, { recursive: true, force: true })\n  await mkdir(dist, { recursive: true })\n\n  // .dist/@nue directory\n  await createSystemFiles(dist, init)\n\n  // build files\n  subset = subset.filter(el => !el.is_yaml && !el.dir.startsWith(`@shared${sep}data`))\n\n  await Promise.all(subset.map(async asset => {\n    try {\n      if (verbose) console.log(asset.path)\n      await buildAsset(asset, dist)\n\n    } catch (error) {\n      console.error(`Failed to build ${asset.path}:`, error)\n      throw error // or return null to continue with others\n    }\n  }))\n}\n\nexport async function buildAsset(asset, dist) {\n  const result = await asset.render()\n\n  if (result?.js) {\n    const minified = await minifyJS(result.js)\n    await asset.write(dist, minified, '.html.js')\n  }\n\n  if (result?.html) await asset.write(dist, result.html)\n\n  if (typeof result == 'string') {\n    await asset.write(dist, result, await asset.toExt())\n\n  } else if (!asset.is_html) {\n    await asset.copy(dist)\n  }\n}\n\n\n// TODO: layout and colors\nexport function stats(assets) {\n  const lines = []\n\n  const types = {\n    md: 'Markdown',\n    js: 'JavaScript',\n    ts: 'TypeScript',\n    html: 'HTML',\n    svg: 'SVG',\n    css: 'CSS',\n    png: 'PNG',\n    webp: 'WebP',\n  }\n\n  for (const type in types) {\n    const count = assets.filter(el => el.type == type).length\n    if (count) lines.push(`${types[type]} files: ${count}`)\n  }\n\n  // misc files\n  const basics = ['yaml', ...Object.keys(types)]\n  const count = assets.filter(el => !basics.includes(el.type)).length\n  if (count) lines.push(`Misc files: ${count}`)\n\n  return lines\n}\n\nexport function matches(path, patterns) {\n  return patterns.some(match => {\n    return match.startsWith('./') ? path == match.slice(2) : path.includes(match)\n  })\n}\n\nexport async function minifyJS(code) {\n  const path = join(tmpdir(), `temp-${Date.now()}.js`)\n  await writeFile(path, code)\n\n  const result = await Bun.build({\n    entrypoints: [path],\n    external: ['*'],\n    minify: true\n  })\n\n  await unlink(path)\n  return await result.outputs[0].text()\n}\n"
  },
  {
    "path": "packages/nuekit/src/cmd/create.js",
    "content": "\nimport { mkdir } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nexport const NAMES = 'blog full minimal spa'.split(' ')\n\nexport async function create(name, { dir, baseurl }) {\n\n  if (!name) return console.log('❌ USAGE: nue create <template-name>')\n\n  if (!NAMES.includes(name)) {\n    return console.log('❌ Choose one: ' + NAMES.join(', '))\n  }\n\n  if (await Bun.file(name).exists()) {\n    return console.log(`✨ ${name} directory already exists`)\n  }\n\n  try {\n    // load zip from local or remote\n    const zip = dir ? await getLocalZip(name, dir) : await fetchZip(name, baseurl)\n    await unzip(name, zip)\n\n    // success message\n    console.log(`\\n🎉  \"${name}\" directory created. Your next steps:`)\n    console.log(`   cd ${name}`)\n    console.log(`   nue\\n`)\n\n    return true\n\n  } catch (error) {\n    console.error(`❌ ${error.message}`)\n  }\n}\n\n\nexport async function getLocalZip(name, dir) {\n  const path = join(dir, `${name}.zip`)\n  if (!await Bun.file(path).exists()) throw new Error(`${path} not found`)\n  console.log(`📦 Using local template: ${path}`)\n  return Bun.file(path)\n}\n\n// download from github\n//\nexport async function fetchZip(name, baseurl='https://github.com/nuejs/nue/raw/master/packages/templates') {\n  const url = `${baseurl}/${name}.zip`\n  const resp = await fetch(url)\n  if (resp.status != 200) throw new Error(`${url} not found`)\n  console.log(`📦 Downloading ${name} template...`)\n  return resp\n}\n\n\n// unzip.js\nexport async function unzip(dir, zip) {\n  const filename = `${dir}.zip`\n\n  try {\n    // write zip file\n    await Bun.write(filename, zip)\n\n    // extract (expects \"minimal\" directory inside zip)\n    const cmd = process.platform == 'win32' ? ['tar', '-xf', filename] : ['unzip', '-q', filename]\n    const proc = Bun.spawn(cmd)\n    const exitCode = await proc.exited\n\n    if (exitCode !== 0) {\n      const stderr = await new Response(proc.stderr).text()\n      throw new Error(`Unpacking archive failed with exit code ${exitCode}: ${stderr}`)\n    }\n\n  // clean up\n  } finally {\n    try {\n      await Bun.file(filename).delete()\n    } catch (e) {console.info(e)}\n  }\n}"
  },
  {
    "path": "packages/nuekit/src/cmd/preview.js",
    "content": "\nimport { extname, join } from 'node:path'\n\nimport { createServer } from '../tools/server'\nimport { getServer } from '../server'\n\n\nexport async function preview(conf, opts) {\n  const { dist=\".dist\" } = conf\n  const port = opts.port || 4040\n\n  // dist directory\n  const has_index = await Bun.file(join(dist, 'index.html')).exists()\n  if (!has_index) return console.error('run `nue build` first')\n\n  // user server\n  const handler = await getServer(conf?.server)\n\n  // dev server\n  const server = createServer({ port, handler }, url => getFile(dist, url))\n\n  const url = server.url.toString()\n  console.log('Serving on', url)\n\n  return { stop() { server.stop() }, url, port, }\n\n}\n\nexport async function getFile(dist, url) {\n\n  // favicon\n  if (url == '/favicon.ico') return Bun.file(join(import.meta.dir, '../../favicon.ico'))\n\n  // file\n  let path = join(dist, url)\n  if (url.endsWith('/')) path += 'index.html'\n\n  const ext = extname(path)\n  if (!ext) path += '.html'\n\n  const file = Bun.file(path)\n  if (await file.exists()) return file\n\n  // 404\n  if (!ext) {\n    const err_page = Bun.file(join(dist, '404.html'))\n    if (await err_page.exists()) return err_page\n  }\n}\n\n"
  },
  {
    "path": "packages/nuekit/src/cmd/serve.js",
    "content": "\nimport { extname, join } from 'node:path'\n\nimport { generateSitemap, generateFeed } from '../render/feed'\nimport { createServer, broadcast } from '../tools/server'\nimport { fswatch } from '../tools/fswatch'\n\nimport { getSystemFiles } from '../system'\nimport { getServer } from '../server'\n\n\nexport async function serve(site, { silent }) {\n  const { conf, assets } = site\n  const { root, ignore, port } = conf\n\n  // user server\n  const handler = await getServer(conf?.server)\n\n  // dev server\n  const server = createServer({ port, handler }, (url, params) =>\n    onServe(url, assets, { params, conf })\n  )\n\n  const watcher = fswatch(root, { ignore })\n\n  watcher.onupdate = async function(path) {\n    const asset = await site.update(path)\n\n    // site.yaml update\n    if (asset.base == 'site.yaml') {\n      const data = asset.content = await asset.parse()\n      Object.assign(conf, data)\n      return broadcast(asset)\n    }\n\n    if (asset) {\n      asset.content = await asset.render({ hmr: true }) || await asset.text()\n      if (asset.is_html) {\n        asset.ast = await asset.parse()\n        delete asset.ast.root\n      }\n      broadcast(asset)\n    }\n  }\n\n  watcher.onremove = async function(path) {\n    site.remove(path)\n    broadcast({ remove: path })\n  }\n\n  const url = server.url.toString()\n  if (!silent) console.log('Serving on', url)\n\n  return { stop() { watcher.close(); server.stop() }, url, port, }\n\n}\n\n\n// server requests\nexport async function onServe(url, assets, opts={}) {\n  const { params={}, conf={} } = opts\n  const asset = findAssetByURL(url, assets)\n  const ext = extname(url)\n\n  // return File || { content, type }\n  if (asset) {\n    const result = await asset.render(params)\n    if (!result) return Bun.file(asset.rootpath)\n    const content = (ext ? result.js : result.html) || result\n    const type = ext && params.hmr == null ? await asset.contentType() : 'text/html; charset=utf-8'\n    return { content, type }\n  }\n\n  // SPA entry page\n  if (!ext) {\n    const app = url.split('/')[1]\n    const spa = assets.find(asset => ['index.html', `${app}/index.html`].includes(asset.path))\n    if (spa) return (await spa.render()).html\n  }\n\n  // sitemap.xml\n  if (url == '/sitemap.xml' && conf.sitemap?.enabled) {\n    const content = await generateSitemap(assets, conf)\n    return content ? { content, type: 'application/xml; charset=utf-8' } : null\n  }\n\n  // feed.xml\n  if (url == '/feed.xml' && conf.rss?.enabled) {\n    const content = await generateFeed(assets, conf)\n    return content ? { content, type: 'application/xml; charset=utf-8' } : null\n  }\n\n  // custom error page\n  if (!ext) {\n    const err = assets.find(asset => asset.name == '404')\n    return err ? { content: await err.render(), status: 404 } : null\n  }\n\n  // favicon\n  if (url == '/favicon.ico') return Bun.file(join(import.meta.dir, '../../favicon.ico'))\n\n}\n\n\nconst sysfiles = getSystemFiles()\n\nexport function findAssetByURL(url, assets=[]) {\n  return [...sysfiles, ...assets].find(asset => {\n    return url.endsWith('.html.js') ? asset.path == url.slice(1, -3)\n      :  asset.url == url\n  })\n}"
  },
  {
    "path": "packages/nuekit/src/collections.js",
    "content": "\n// assumes is_md\nexport async function getCollections(files, opts) {\n  const arr = {}\n\n  for (const [name, conf] of Object.entries(opts)) {\n    arr[name] = await createCollection(files, conf)\n  }\n\n  return arr\n}\n\nexport async function createCollection(files, conf) {\n  let matchedPages = matchPages(files, conf.include)\n  let filteredPages = await filterPages(matchedPages, conf)\n  return sortPages(filteredPages, conf.sort)\n}\n\nfunction matchPages(files, patterns=[]) {\n  const ret = []\n\n  for (const pattern of patterns) {\n    for (const page of files) {\n      if (page.path.includes(pattern)) ret.push(page)\n    }\n  }\n\n  return ret\n}\n\nasync function filterPages(files, conf) {\n  const ret = []\n\n  for (const page of files) {\n    const { meta } = await page.parse()\n\n    // require?\n    if (conf.require && !conf.require.every(field => meta[field])) continue\n\n    // tags?\n    if (conf.tags && !conf.tags.some(tag => meta.tags?.includes(tag))) continue\n\n    // skip?\n    if (conf.skip?.some(field => meta[field])) continue\n\n    const { url, dir, slug } = page\n    ret.push({ ...meta, url, dir, slug })\n  }\n\n  return ret\n}\n\nfunction sortPages(files, sorting) {\n  if (!sorting) return files\n\n  const [field, direction = 'asc'] = sorting.split(' ')\n\n  return files.toSorted((a, b) => {\n    const aVal = a[field]\n    const bVal = b[field]\n    const result = aVal < bVal ? -1 : aVal > bVal ? 1 : 0\n    return direction == 'desc' ? -result : result\n  })\n}\n"
  },
  {
    "path": "packages/nuekit/src/conf.js",
    "content": "\nimport { readdir } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport { parseYAML } from 'nueyaml'\n\n// configuration properties (separate from data)\nconst SITE_CONF = 'site design server collections production port sitemap links'.split(' ')\n\nconst ALL_CONF = SITE_CONF.concat('include exclude meta content import_map svg'.split(' '))\n\n// default skip list\nconst SKIP = `node_modules .toml .rs .lock package.json .lockb lock.yaml README.md Makefile`.split(' ')\n\n\nexport async function readSiteConf(args={}) {\n  const { is_prod=false, port, root='.' } = args\n  const files = await readdir(root)\n\n  let conf = {}\n\n  if (files.includes('site.yaml')) {\n    const file = Bun.file(join(root, 'site.yaml'))\n    conf = await parseYAML(await file.text())\n\n  } else {\n    const index = files.find(name => ['index.md', 'index.html'].includes(name))\n    if (!index) return null\n  }\n\n  // build ignore list into config\n  const ignore = [...SKIP, ...(conf.site?.skip || [])]\n  ignore.push(conf.server?.dir || join('@shared', 'server'))\n  ignore.push(join('@shared', 'test'))\n\n  // production override\n  if (is_prod && conf.meta) {\n    Object.assign(conf.meta, conf.production)\n    delete conf.production\n  }\n\n  if (port) conf.port = port\n\n  // dist\n  conf.dist = join(root, '.dist')\n\n  return { ...conf, is_prod, root, ignore }\n}\n\n\nexport function mergeData(dataset) {\n  const ret = {}\n\n  dataset.forEach(data => {\n    data && Object.entries(data).forEach(([key, val]) => {\n      if (key == 'meta') return Object.assign(ret, val)\n      if (!ALL_CONF.includes(key)) ret[key] = val\n    })\n  })\n\n  return ret\n}\n\nexport function mergeConf(site_conf, app_conf) {\n  const conf = structuredClone(site_conf)\n  for (const key in app_conf) mergeValue(conf, key, app_conf[key])\n  return conf\n}\n\nexport function mergeValue(conf, key, val) {\n\n  if (!ALL_CONF.includes(key) || SITE_CONF.includes(key)) return\n\n  // merge meta / content\n  if (['meta', 'content'].includes(key) && typeof val == 'object') {\n    return conf[key] = { ...conf[key], ...val }\n  }\n\n  // override (include, exclude, import_map)\n  return conf[key] = val\n}\n\n"
  },
  {
    "path": "packages/nuekit/src/deps.js",
    "content": "\nimport { join, normalize, dirname, extname, basename, sep } from 'node:path'\n\n// app, lib, server are @shared, but not auto-included\nconst AUTO_INCLUDED = ['data', 'design', 'ui'].map(dir => join('@shared', dir))\n\nconst ASSET_TYPES = ['.html', '.js', '.ts', '.yaml', '.css']\n\n\nexport function listDependencies(basepath, { paths, exclude=[], include=[] }) {\n\n  // folder dependency\n  let deps = paths.filter(path => isDep(basepath, path, paths))\n\n  // extensions\n  deps = deps.filter(path => ASSET_TYPES.includes(extname(path)))\n\n  // exclusions\n  exclude.forEach(pattern => {\n    deps = deps.filter(path => !path.includes(pattern))\n  })\n\n  // Re-inclusions\n  include.forEach(pattern => {\n    paths.forEach(path => {\n      if (path.includes(pattern)) deps.push(path)\n    })\n  })\n\n  return [...new Set(deps)]\n}\n\n\nfunction isDep(page_path, asset_path, all_paths) {\n  // self\n  if (page_path == asset_path) return false\n\n  // root level assets (global)\n  const dir = dirname(asset_path)\n  if (dir == '.') return true\n\n  // shared dir -> auto-included\n  if (AUTO_INCLUDED.some(dir => asset_path.startsWith(dir + sep))) return true\n\n  // SPA: entire app tree\n  if (basename(page_path) == 'index.html') {\n    const dir = dirname(page_path)\n    return dir == '.' ? !all_paths.some(el => extname(el) == '.md') : asset_path.startsWith(dir + sep)\n  }\n\n  // index.md -> home dir\n  const pagedir = dirname(page_path)\n  if (pagedir == '.' && basename(page_path) == 'index.md') return dirname(asset_path) == 'home'\n\n  // hierarchical inclusion (handles root ui and app ui)\n  const page_dirs = parseDirs(dirname(page_path))\n  const asset_dir = dirname(asset_path)\n\n  // check if asset is in ui of any parent directory\n  return page_dirs.some(pageDir => {\n    const ui_dir = pageDir ? join(pageDir, 'ui') : 'ui'\n    return asset_dir == ui_dir || asset_dir == pageDir\n  })\n}\n\n\n// parseDirs('a/b/c') --> ['a', 'a/b', 'a/b/c']\nexport function parseDirs(dir) {\n  const els = normalize(dir).split(sep)\n  return els.map((el, i) => els.slice(0, i + 1).join('/'))\n}"
  },
  {
    "path": "packages/nuekit/src/file.js",
    "content": "\nimport { parse, sep, join } from 'node:path'\nimport { lstat } from 'node:fs/promises'\n\nexport async function createFile(root, path) {\n  try {\n    const rootpath = join(root, path)\n    const stat = await lstat(rootpath)\n    const info = getFileInfo(path)\n    const file = Bun.file(rootpath)\n    const mtime = stat.mtime\n    let cachedText = null\n\n    // is_md, is_js, ...\n    if (stat.isSymbolicLink()) info.is_symlink = true\n\n    async function text() {\n      if (!cachedText) cachedText = await file.text()\n      return cachedText\n    }\n\n    function flush() {\n      cachedText = null\n    }\n\n    async function copy(dist) {\n      const to = join(dist, path)\n      await Bun.write(to, file)\n      return to\n    }\n\n    async function write(dist, content, ext) {\n      const toname = ext ? info.base.replace(info.ext, ext) : info.base\n      const to = join(dist, info.dir, toname)\n      await Bun.write(to, content)\n      return to\n    }\n\n    return { ...info, rootpath, mtime, text, copy, write, flush }\n\n  } catch (error) {\n    console.warn(`Warning: Error reading ${path}: ${error.message}`)\n    return null\n  }\n}\n\nexport function getFileInfo(path) {\n  const info = parse(path)\n  delete info.root\n\n  const { ext, dir } = info\n  const type = info.ext.slice(1)\n  const url = getURL(info)\n  const slug = getSlug(info)\n\n  if (dir.includes(sep)) info.basedir = dir.split(sep)[0]\n\n  return { ...info, path, type, url, slug, [`is_${type}`]: true }\n}\n\nexport function getURL(file) {\n  let { name, base, ext, dir } = file\n\n  if (['.md', '.html'].includes(ext)) {\n    if (name == 'index') name = ''\n    ext = ''\n  }\n\n  if (ext == '.ts') ext = '.js'\n  const els = dir.split(sep)\n  els.push(name + ext)\n\n  return `/${ els.join('/') }`.replace('//', '/')\n}\n\nexport function getSlug(file) {\n  let { name, base, ext } = file\n  return name == 'index' ? '' : ext == '.md' ? name : base\n}\n"
  },
  {
    "path": "packages/nuekit/src/render/feed.js",
    "content": "\nimport { createCollection } from '../collections'\nimport { trim } from './page'\n\nconst XML = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\n\nexport async function generateSitemap(assets, conf) {\n  const origin = getOrigin(conf)\n  const { skip } = conf.sitemap\n\n  const pages = []\n\n  for (const asset of assets.filter(el => el.is_md)) {\n    const { meta } = await asset.parse()\n    const { mtime, url } = asset\n\n    // skip?\n    if (skip?.some(field => meta[field])) continue\n    pages.push({ mtime, origin, url })\n  }\n  return renderSitemap(pages)\n}\n\nexport async function generateFeed(assets, conf) {\n  const key = conf.rss.collection\n  if (!key) return console.warn('rss.collection missing from site.yaml')\n\n  const coll = conf.collections?.[key]\n  const origin = getOrigin(conf)\n\n  if (coll) {\n    const pages = await createCollection(assets.filter(el => el.is_md), coll)\n    return renderFeed({ ...conf.rss, origin }, pages)\n  }\n}\n\n\nexport function renderSitemap(pages) {\n  const xml = [ XML, '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">' ]\n\n  pages.forEach(page => {\n    if (page.url == '/404') return\n\n    xml.push(trim(`\n    <url>\n      <loc>${ page.origin }${ page.url }</loc>\n      <lastmod>${ page.mtime.toISOString().slice(0, 10) }</lastmod>\n    </url>`))\n  })\n\n  xml.push('</urlset>')\n  return xml.join('\\n')\n}\n\nexport function renderFeed(meta, pages) {\n  const xml = [ XML, '<rss version=\"2.0\">', '<channel>' ]\n  const { title='', description='', origin } = meta\n\n  xml.push(`\n    <link>${ origin }</link>\n    <title>${ title }</title>\n    <description>${ description }</description>\n  `)\n\n  pages.forEach(page => {\n    const date = page.pubDate || page.date || ''\n    const desc = page.description || page.desc || ''\n\n    xml.push(trim(`\n    <item>\n      <title>${ page.title }</title>\n      <description>${ desc }</description>\n      <pubDate>${ date.toISOString?.().slice(0, 10) }</pubDate>\n      <link>${ origin }${ page.url }</link>\n    </item>`))\n  })\n\n  xml.push('</channel>', '</rss>')\n  return xml.join('\\n')\n}\n\n\nfunction getOrigin(conf) {\n  const { origin='' } = conf.site || {}\n  if (!origin) console.warn('site.origin missing from site.yaml')\n  return origin\n}"
  },
  {
    "path": "packages/nuekit/src/render/head.js",
    "content": "\nimport { parse, sep } from 'node:path'\n\nimport { elem } from 'nuemark'\nimport { version } from '../system'\nimport { minifyCSS } from '../tools/css'\n\nexport async function renderHead({ conf, data, assets, libs=[] }) {\n  const { title, favicon } = data\n  const head = []\n\n  if (title) head.push(elem('title', renderTitle(title, data.title_template)))\n\n  if (favicon) head.push(elem('link', { rel: 'icon', href: favicon }))\n\n  // meta\n  head.push(...renderMeta(data, libs))\n\n  // @layers\n  const layers = conf.design?.layers\n  if (layers) head.push(elem('style', `@layer ${layers.join(', ')}`))\n\n  // styles\n  head.push(...await renderStyles(assets, conf))\n\n  // system scripts\n  const addJS = name => assets.push(parse(`@nue/${name}.js`))\n  if (conf.site?.view_transitions) addJS('transitions')\n  if (libs.length) addJS('mount')\n  if (!conf.is_prod) addJS('hmr')\n\n\n  // all scripts\n  const scripts = renderScripts(assets)\n\n  if (scripts.length || libs.length) {\n    head.push(importMap(conf.import_map))\n    head.push(...scripts)\n  }\n\n  // RSS feed\n  if (conf.rss?.enabled) {\n    const { title } = conf.rss\n    const link = elem('link', { rel: 'alternate', type: 'application/rss+xml', title, href: '/feed.xml' })\n    head.push(link)\n  }\n\n  return head\n}\n\nexport function renderMeta(data, libs) {\n  const desc = data.desc || data.description\n\n  const props = {\n    viewport: 'width=device-width,initial-scale=1',\n    'article:published_time': data.date || data.pubDate,\n    generator: `Nue v${version} (nuejs.org)`,\n    'date.updated': new Date().toISOString().slice(0, 16) + 'Z',\n\n    'og:title': renderTitle(data.title, data.title_template),\n    'og:description': desc,\n    'og:image': ogImage(data),\n    libs: libs?.join(' '),\n\n    description: desc,\n    'theme-color': '',\n    author: '',\n    robots: '',\n  }\n\n  const meta = [elem('meta', { charset: 'utf-8'})]\n\n  Object.entries(props).map(([key, val]) => {\n    const content = data[key] || val\n    if (content && data[key] !== false) meta.push(elem('meta', { name: key, content }))\n  })\n\n  return meta\n}\n\nfunction renderTitle(title, template) {\n  const str = template ? template.replace('%s', title) : title\n\n  // Strip Markdown formatting (bold only for now)\n  return str?.replaceAll('**', '')\n}\n\nexport function renderScripts(assets) {\n  const scripts = assets.filter(f => ['.js', '.ts'].includes(f.ext) && f.dir != `@shared${sep}data`)\n  return scripts.map(s => elem('script', { src: `/${s.dir}/${s.name}.js`, type: 'module' }))\n}\n\nexport async function renderStyles(assets, conf={}) {\n  const { inline_css } = conf?.design || {}\n  const css_files = assets.filter(file => file.is_css)\n\n  if (conf.is_prod && inline_css) {\n    const css = await inlineCSS(css_files)\n    return [ elem('style', css) ]\n  }\n\n  return css_files.map(file => elem('link', { rel: 'stylesheet', href: `/${file.path}` }))\n}\n\nexport async function inlineCSS(assets, minify=true) {\n  const css_files = assets.filter(el => el.is_css)\n  if (!css_files.length) return ''\n\n  const css = await Promise.all(css_files.map(file => file.text()))\n  const str = css.join('\\n').trim()\n  return minifyCSS(str)\n}\n\n\nfunction importMap(imports) {\n  return !Object.keys(imports || {})[0] ? ''\n    : elem('script', { type: 'importmap' }, JSON.stringify({ imports }))\n}\n\nfunction ogImage(data) {\n  const og = data.og_image || data.og\n  const { origin='' } = data\n\n  if (og) {\n    const img = og[0] == '/' ? og : `/${data.dir}/${og}`\n    return (data.is_prod ? origin : '') + img\n  }\n}\n\n"
  },
  {
    "path": "packages/nuekit/src/render/page.js",
    "content": "\nimport { renderScripts, renderHead } from './head'\nimport { renderNue, compileNue } from 'nuedom'\nimport { elem, renderInline as markdown } from 'nuemark'\n\nconst globals = { markdown }\n\nexport function renderSlots({ head=[], content='', comps=[], data={}, conf={} }) {\n  const attr = getAttr(data)\n  const { scope } = data\n  const { max_class_names } = conf.design || {}\n\n  function slot(name) {\n    if (data[name] === false) return ''\n    const comp = comps.find(el => el.is ? el.is == name : el.tag == name)\n    return comp ? renderNue(comp, { data, deps: comps, globals, max_class_names }) : ''\n  }\n\n  const article = scope == 'article' ? content : `\n    <article>\n      ${ slot('pagehead') }\n      ${ content }\n      ${ slot('pagefoot') }\n    </article>\n  `\n\n  const main = scope == 'main' ? content : `\n    <main>\n      ${ slot('aside') }\n      ${ article }\n      ${ slot('beside') }\n    </main>\n  `\n\n\n  const body = scope == 'body' ? content : `\n    <body${attr.class}>\n      ${ slot('banner') }\n      ${ slot('header') }\n      ${ slot('subheader') }\n      ${ main }\n      ${ slot('footer') }\n      ${ slot('bottom') }\n    </body>\n  `\n\n  return `\n    <!doctype html>\n\n    <html lang=\"${attr.language}\"${attr.dir}>\n      <head>\n        ${ head.join('\\n\\t') }\n        ${ slot('head') }\n      </head>\n      ${body}\n    </html>\n  `\n}\n\n\n// used by renderMD and renderHTML\nexport async function renderPage({ asset, content, comps, data={}, conf }) {\n  const { is_dhtml, doctype } = await asset.parse()\n  const assets = await asset.assets()\n  const libs = await getLibPaths(assets)\n\n  if (is_dhtml) libs.unshift(asset.path)\n\n  const head = await renderHead({ conf, data, assets, libs })\n  return renderSlots({ head, content, comps, data, conf })\n}\n\n\nexport async function renderMD(asset) {\n  const doc = await asset.parse()\n  const { meta, headings } = doc\n  const comps = await asset.components()\n\n  // data\n  const conf = await asset.config()\n  const data = await asset.data()\n\n  // content conf\n  const heading_ids = meta?.heading_ids || conf.content?.heading_ids\n  const sections = meta?.sections || conf.content?.sections\n\n  Object.assign(data, meta, { headings }, fileMeta(asset))\n\n  const content = doc.render({\n    data, sections, heading_ids,\n    tags: convertToTags(comps, data),\n    links: conf.links\n  })\n\n  return await renderPage({ content, comps, data, asset, conf })\n}\n\n// <!html>\nexport async function renderHTML(asset) {\n  const ast = await asset.parse()\n  const { is_dhtml } = ast\n\n  // raw HTML\n  const is_raw = ast.doctype == 'html' && ['html', 'head'].includes(ast.root.tag)\n  if (is_raw) return await asset.text()\n\n  // library --> skip\n  if (ast.is_lib) return is_dhtml ? compileNue(ast) : null\n\n  // !dhtml -> renderDHTML\n  if (is_dhtml) return await renderDHTML(asset)\n\n  // data\n  const data = await asset.data()\n  Object.assign(data, ast.meta, fileMeta(asset))\n\n  // root\n  const conf = await asset.config()\n  const root = createWrapper(ast.lib, conf.content?.sections)\n  data.scope = root.tag\n\n  // content\n  const comps = await asset.components()\n\n  const { max_class_names } = conf.design || {}\n  const content = renderNue(root, { data, deps: comps, globals, max_class_names })\n\n  // page\n  return await renderPage({ content, comps, data, asset, conf })\n}\n\nfunction createWrapper(lib, use_sections) {\n  if (lib.length == 1) return lib[0]\n  const wrap = { tag: use_sections ? 'section' : 'article', children: lib }\n\n  // hoist child scripts into parent\n  const script = []\n  lib.forEach(el => {\n    if (el.script) script.push(el.script)\n    delete el.script\n  })\n  wrap.script = script.join('\\n')\n\n  return wrap\n}\n\n\n// <!dhtml>\nexport async function renderDHTML(asset) {\n  const doc = await asset.parse()\n  const comps = await asset.components(true)\n  const data = await asset.data()\n  const conf = await asset.config()\n  const root = createWrapper(doc.lib, conf.content?.sections)\n\n  // force component name\n  if (!root.is_custom && !root.is) root.is = 'default-app'\n\n  // server-side html\n  const content = root.is_custom ? elem('div', { nue: root.tag }) : elem(root.tag, { nue: root.is })\n  data.scope = root.tag\n\n  // \"state\" to importmap (if <body>)\n  const map = conf.import_map ??= {}\n  map.state = '/@nue/state.js'\n  const html = await renderPage({ asset, content, comps, data, conf })\n\n  return { html, js: compileNue({ ...doc, lib: [ root ]} ) }\n}\n\n/***** Helper functions *****/\n\nfunction fileMeta(asset) {\n  const { url, slug, dir } = asset\n  return { url, slug, dir }\n}\n\n// custom components as Markdown extensions (tags)\nfunction convertToTags(deps, data) {\n  const tags = {}\n\n  deps.forEach(ast => {\n    if (!ast.is_custom && !ast.is) return\n    const name = ast.is || ast.tag\n\n    // if (ast.is_custom) { delete ast.is_custom; ast.tag = 'div' }\n\n    tags[name] = function(args) {\n      const { attr, blocks } = this\n      return renderNue(ast, {\n        data: { ...args, ...attr, attr, blocks },\n        slot: this.innerHTML,\n        globals,\n        deps\n      })\n    }\n  })\n\n  return tags\n}\n\nasync function getLibPaths(assets) {\n  const paths = []\n  for (const asset of assets.filter(el => el.is_html)) {\n    const { doctype } = await asset.parse()\n    if (doctype?.includes('dhtml lib')) paths.push(asset.path)\n  }\n  return paths\n}\n\nfunction getAttr(data) {\n  const { language = 'en-US', direction } = data\n  return {\n    class: data.class ? ` class=\"${data.class}\"` : '',\n    dir: direction ? ` dir=\"${direction}\"` : '',\n    language,\n  }\n}\n\nexport function trim(str) {\n  return str.replace(/^\\s*[\\r\\n]/gm, '').replace(/^ {4}/gm, '')\n}\n\n\n\n\n\n"
  },
  {
    "path": "packages/nuekit/src/render/svg.js",
    "content": "\nimport { parseYAMLArray } from 'nueyaml'\n\nimport { renderNue } from 'nuedom'\nimport { elem } from 'nuemark'\n\nimport { inlineCSS } from './head'\nimport { trim } from './page'\n\n\n// opts: { hmr, fonts }\nexport async function renderSVG(asset, opts={}) {\n  const { root } = await asset.parse()\n  const hmr = opts.hmr != null\n  const fonts = await renderFonts(opts.fonts, hmr)\n  const deps = await asset.components()\n  const data = await asset.data()\n\n  transform(root)\n\n  const styles = getStyles(await asset.assets(), parseYAMLArray('tmp: ' + root.meta?.css))\n\n  if (hmr) {\n    const { base } = asset\n    const body = renderNue(root, { deps, data })\n    return renderHMR({ body, base, fonts, styles })\n\n  } else {\n    const css = [...fonts, await inlineCSS(styles)]\n\n    if (css[0]) {\n      const kids = root.children ??= []\n      kids.unshift({ tag: 'style', children: [{ text: `<![CDATA[${css.join('\\n')}]]>` }] })\n    }\n\n    return renderNue(root, { deps, data })\n  }\n}\n\nexport function getStyles(assets, patterns) {\n  if (!patterns?.length) return []\n\n  return assets.filter(asset =>\n    asset.is_css && patterns.some(match => asset.path.includes(match))\n  )\n}\n\nexport function renderHMR({ body, base, fonts, styles }) {\n  const links = styles.map(file => {\n    return elem('link', { rel: 'stylesheet', href: `${file.url}` })\n  })\n\n  return trim(`\n    <!doctype html>\n    <html>\n      <head>\n        <title>${base} (dev mode)</title>\n        <style>\n        ${ fonts.join('\\n') }\n        body { margin: 0 }\n        </style>\n\n        ${ links.join('\\n')}\n        <script type=\"module\" src=\"/@nue/hmr.js\"></script>\n      </head>\n\n      <body>${body}</body>\n    </html>\n  `)\n}\n\n\nexport async function renderFonts(conf, external) {\n  if (!conf) return []\n  const fn = external ? renderFont : renderInlineFont\n  const promises = Object.entries(conf).map(([name, path]) => fn(name, path))\n\n  try {\n    return await Promise.all(promises)\n  } catch(e) {\n    console.error('Font file not found', e.path)\n    return []\n  }\n}\n\nfunction renderFont(name, path) {\n  if (!path.startsWith('data') && path[0] != '/') path = '/' + path\n  return `@font-face { font-family: '${name}'; src: url('${path}')}`\n}\n\nasync function renderInlineFont(name, path) {\n  const buffer = await Bun.file(path).arrayBuffer()\n  const data = 'data:font/woff2;base64,' + Buffer.from(buffer).toString('base64')\n  return renderFont(name, data)\n}\n\n\n/*** SVG transformations ***/\nfunction transform(svg) {\n  pushAttr(svg, 'xmlns', 'http://www.w3.org/2000/svg')\n  pushViewport(svg)\n  svg.children?.forEach(el => {\n    if (el.tag == 'html') convertHTMLTag(el)\n  })\n}\n\n\nfunction pushAttr(svg, name, val) {\n  const attr = svg.attr ??= []\n  if (!attr.find(el => el.name == name)) {\n    attr.push({ name, val })\n  }\n}\n\nfunction pushViewport(svg) {\n  const { attr=[] } = svg\n  const find = (name) => attr.find(el => el.name == name)\n  const box = find('viewBox')\n  const w = find('width')\n  const h = find('height')\n  if (!box && w && h) attr.push({ name: 'viewBox', val: `0 0 ${w.val} ${h.val}` })\n}\n\nexport function convertHTMLTag(tag) {\n  tag.tag = 'foreignObject'\n\n  Object.entries({ x: 0, y: 0, width: '100%', height: '100%'}).forEach(([name, val]) => {\n    pushAttr(tag, name, val)\n  })\n\n  tag.children?.forEach(el => pushAttr(el, 'xmlns', 'http://www.w3.org/1999/xhtml'))\n  return tag\n\n}\n\n\n\n"
  },
  {
    "path": "packages/nuekit/src/server/README.md",
    "content": "\n# User server\nDevelopment environment for the user's server-side code. Not to confuse with tools/server.js, which is the Nue development server.\n\n## Files\n\n**index.js** - Main entry point. Returns either a local worker runtime or proxy to remote server\n\n**worker.js** - Creates local worker environment that mimics edge runtime APIs\n\n**proxy.js** - Creates proxy to remote server for testing against live environments  \n\n**env.js** - Simplified mocks for real server models (done later)\n\n\n\n"
  },
  {
    "path": "packages/nuekit/src/server/index.js",
    "content": "\nimport { createWorker } from './worker.js'\nimport { createProxy } from './proxy.js'\n\nexport async function getServer(opts={}) {\n  return opts.url ? createProxy(opts) : await createWorker(opts)\n}\n"
  },
  {
    "path": "packages/nuekit/src/server/model.js",
    "content": "\nimport { readdir, mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nconst SESSIONS_PATH = join(process.cwd(), '.nue', 'sessions.json')\nconst NOW = Date.now()\nconst DAY = 86400000\n\n\nfunction createModel(items) {\n\n  items.forEach((el, i) => {\n    el.created = NOW - DAY * i\n    el.id = i + 1\n  })\n\n  async function create(obj) {\n    const id = items.length + 1\n    const created = Date.now()\n    const item = { id, created, ...obj }\n    items.unshift(item)\n    return item\n  }\n\n  // implemented with true event sourcing later\n  async function getAll() {\n    return items\n  }\n\n  async function size() {\n    return items.length\n  }\n\n  async function get(id) {\n    const item = items.find(el => el.id == id)\n    return {\n      ...item,\n\n      async update(data) {\n        Object.assign(item, data)\n        return item\n      },\n\n      async remove() {\n        const i = items.indexOf(item)\n        items.splice(i, 1)\n      }\n    }\n  }\n\n  return { getAll, size, create, get }\n}\n\n\nasync function saveSessions(sessions) {\n  await mkdir(join(process.cwd(), '.nue'), { recursive: true })\n  await writeFile(SESSIONS_PATH, JSON.stringify([...sessions], null, 2))\n}\n\nasync function readSessions() {\n  try {\n    const data = await readFile(SESSIONS_PATH, 'utf-8')\n    return new Set(JSON.parse(data))\n  } catch {\n    return new Set()\n  }\n}\n\n\n\n// specialized models\nasync function createUserModel(items) {\n  const users = createModel(items)\n  const sessions = await readSessions()\n\n  async function login(email, password) {\n    const user = (await users.getAll()).find(el => el.email == email)\n\n    // mock: plaintext passwords. production uses hashed\n    if (user?.password == password) {\n      const sessionId = crypto.randomUUID()\n      sessions.add(sessionId)\n      await saveSessions(sessions)\n      return { sessionId, user }\n    }\n  }\n\n  async function authenticate(sessionId) {\n    return sessions.has(sessionId)\n  }\n\n  async function logout(sessionId) {\n    sessions.delete(sessionId)\n    await saveSessions(sessions)\n  }\n\n  return { ...users, login, logout, authenticate }\n}\n\n\n\nexport async function createEnv(dir) {\n  const files = await readdir(dir)\n  const env = {}\n\n  for (const file of files) {\n    if (file.endsWith('.json')) {\n      const type = file.replace('.json', '')\n      const path = join(dir, file)\n      const items = JSON.parse(await readFile(path, 'utf8'))\n      const model = env[type] = type == 'users' ? await createUserModel(items) : createModel(items)\n      console.log(`Model \"${type}\" loaded (${ await model.size() } records)`)\n    }\n  }\n\n  return env\n}"
  },
  {
    "path": "packages/nuekit/src/server/proxy.js",
    "content": "\nexport function createProxy(opts) {\n\n  return async function(req) {\n    const url = new URL(req.url)\n    const match = opts.routes?.some(path => url.pathname.startsWith(path))\n    if (!match) return null\n\n    const { method, headers } = req\n    const target = new URL(opts.url)\n    const fullUrl = `${target.protocol}//${target.host}${url.pathname}${url.search}`\n    const body = ['GET', 'HEAD'].includes(method) ? null : await req.arrayBuffer()\n\n    return await fetch(fullUrl, { headers, method, body })\n  }\n\n}"
  },
  {
    "path": "packages/nuekit/src/server/worker.js",
    "content": "\nimport { join } from 'node:path'\nimport { existsSync } from 'fs'\n\nimport { routes, fetch, matches } from 'nue-edgeserver'\n\nimport { createEnv } from './model'\n\nexport async function importWorker({ dir, reload }) {\n  const path = join(process.cwd(), dir, 'index.js') + (reload ? '?t=' + Date.now() : '')\n\n  try {\n    routes.length = 0\n    await import(path)\n\n  } catch (err) {\n    if (err.code == 'ERR_MODULE_NOT_FOUND') return null\n    throw err\n  }\n}\n\nexport async function createWorker(opts = {}) {\n  const { dir='@shared/server', reload } = opts\n\n  await importWorker({ dir, reload })\n  if (!routes.length) return null\n\n  const data_dir = join(process.cwd(), dir, 'data')\n  const env = existsSync(data_dir) ? await createEnv(data_dir) : {}\n\n  console.log(`Backend server started with ${routes.length} routes`, reload ? '(reloadable)' : '')\n\n  return async function(req) {\n    if (reload)  await importWorker({ dir, reload })\n    const url = new URL(req.url)\n    const { method, body } = req\n\n    const match = matches(method, url.pathname)\n    if (!match) return null\n\n    const headers = { ...getCFHeaders(), ...Object.fromEntries(req.headers) }\n    const workerReq = new Request(req.url, { method, headers, body })\n\n    return await fetch(workerReq, env)\n  }\n}\n\nfunction getCFHeaders() {\n  return {\n    // Location headers\n    'cf-ipcountry': 'FI',\n    'cf-ipcity': 'Helsinki',\n    'cf-ipcontinent': 'EU',\n    'cf-iplongitude': '24.95034',\n    'cf-iplatitude': '60.18427',\n    'cf-region': 'Uusimaa',\n    'cf-region-code': 'FI-18',\n    'cf-metro-code': '0',\n    'cf-postal-code': '00500',\n    'cf-timezone': 'Europe/Helsinki',\n\n    // Other CF headers\n    'cf-ray': '8' + Math.random().toString().slice(2, 18),\n    'cf-visitor': '{\"scheme\":\"https\"}',\n    'cf-connecting-ip': '127.0.0.1',\n    'cf-request-id': Math.random().toString(16).slice(2, 18),\n    'cf-cache-status': 'DYNAMIC',\n    'cf-edge-request-id': Math.random().toString(16).slice(2, 18),\n    'cf-worker': 'localhost'\n  }\n}\n\n"
  },
  {
    "path": "packages/nuekit/src/site.js",
    "content": "\nimport { parse, sep } from 'node:path'\nimport { fswalk } from './tools/fswalk'\nimport { createAsset } from './asset'\nimport { createFile } from './file'\n\nexport async function createSite(conf) {\n  const { root, ignore } = conf\n\n  // assets\n  const paths = sortAssets(await fswalk(root, { ignore }))\n  const files = await Promise.all(paths.map(path => createFile(root, path)))\n\n  // createAsset options\n  const site_opts = { files, conf }\n\n  const assets = files.map(file => createAsset(file, site_opts))\n\n  function get(path) {\n    return assets.find(el => el.path == path)\n  }\n\n  function remove(path) {\n    function splice(arr) {\n      const i = arr.findIndex(el => el.path == path)\n      if (i >= 0) arr.splice(i, 1)\n    }\n    splice(files)\n    splice(assets)\n  }\n\n  async function update(path) {\n    let asset = get(path)\n\n    // update existing\n    if (asset) { asset.flush(); return asset }\n\n    // add new one\n    const file = await createFile(root, path)\n\n    if (file) {\n      files.push(file)\n      asset = createAsset(file, site_opts)\n      assets.push(asset)\n\n      sortAssets(files)\n      sortAssets(assets)\n      return asset\n    }\n  }\n\n  return { assets, conf, get, remove, update }\n\n}\n\nexport function sortAssets(items) {\n\n  function prio(path) {\n    const { dir, base } = parse(path)\n    return base == dir.startsWith('@shared') ? 0 : !dir ? 2 : 1\n  }\n\n  return items.sort((a, b) => {\n    if (a.path) { a = a.path; b = b.path }\n    const prioA = prio(a)\n    const prioB = prio(b)\n    return prioA == prioB ? a.localeCompare(b) : prioA - prioB\n  })\n\n}\n\n\nexport async function mergeSharedData(assets, data={}) {\n  const shared = assets.filter(a => a.dir?.startsWith(`@shared${sep}data`))\n  const statics = shared.filter(f => f.is_json || f.is_yaml)\n\n  const dataset = await Promise.all(statics.map(f => f.parse()))\n\n  dataset.forEach(more => Object.assign(data, more))\n\n  // modifier scripts\n  const mods = shared.filter(f => (f.is_js || f.is_ts) && !f.name?.endsWith('.test'))\n\n  for (const mod of mods) {\n    const fns = await mod.parse()\n    await fns.default?.(data)\n  }\n\n  return data\n}\n\n\n"
  },
  {
    "path": "packages/nuekit/src/system.js",
    "content": "\nimport { mkdir, rm } from 'node:fs/promises'\nimport { fileURLToPath } from 'node:url'\nimport { join } from 'node:path'\n\nimport { version as pkgVersion } from '../package.json' with { type: 'json' }\nimport { compileJS } from './asset'\n\nexport const version = pkgVersion\n\nexport function getClientFiles() {\n  return 'error hmr mount transitions'.split(' ').map(name => {\n    const base = `${name}.js`\n    return { base, rootpath: join(import.meta.dir, '../client', base) }\n  })\n}\n\nexport function getPackages() {\n  return [\n    { base: 'nue.js', rootpath: resolve('nuedom/src/nue.js'), bundle: true },\n    { base: 'state.js', rootpath: resolve('nuestate/src/state.js') },\n  ]\n}\n\nfunction resolve(pkg) {\n  return fileURLToPath(import.meta.resolve(pkg))\n}\n\nexport function getSystemFiles(is_prod) {\n  let files = [ ...getClientFiles(), ...getPackages() ]\n  if (is_prod) files = files.filter(el => !['error.js', 'hmr.js'].includes(el.base))\n\n  for (const file of files) {\n    file.url = `/@nue/${file.base}`\n\n    file.render = async function() {\n      if (is_prod || file.bundle) {\n        return await compileJS(file.rootpath, is_prod, file.bundle)\n      }\n      // if (!file.content) file.content =\n      // return file.content\n    }\n\n    file.contentType = async function() {\n      return 'application/javascript'\n    }\n  }\n  return files\n}\n\n\nexport async function createSystemFiles(dist, force) {\n  const dir = join(process.cwd(), dist, '@nue')\n\n  const ver = Bun.file(join(dir, version))\n  if (!force && await ver.exists()) return false\n\n  // recreate direcotry\n  await rm(dir, { recursive: true, force: true })\n  await mkdir(dir, { recursive: true })\n\n  // write version file\n  await ver.writer().end()\n\n  const files = getSystemFiles(true)\n\n  for (const file of files) {\n    Bun.write(join(dir, file.base), await file.render())\n  }\n\n  return files\n}\n\n"
  },
  {
    "path": "packages/nuekit/src/tools/README.md",
    "content": "\n# Tools\nGeneric utilities that can potentially be used outside Nue.\n\n## Files\n\n**css.js** - CSS parser & minifier. Turns into design system validator later.\n**fswalk.js** - File system traversal\n**fswatch.js** - File system watching\n**server.js** - Generic server and HMR framework\n\n\n\n\n"
  },
  {
    "path": "packages/nuekit/src/tools/css.js",
    "content": "export function minifyCSS(str) {\n  return parseCSS(str).map(serialize).join('')\n}\n\nexport function parseCSS(str) {\n  const tokens = tokenize(str)\n  let i = 0\n  const nodes = []\n\n  function parseRule() {\n    const node = { name: '', props: [], children: [] }\n\n    // Check for top-level comment\n    if (i < tokens.length && tokens[i].type == 'comment') {\n      node.comment = tokens[i].value\n      i++\n    }\n\n    // Selector or at-rule\n    if (i < tokens.length && tokens[i].type == 'text') {\n      node.name = tokens[i].value\n      i++\n\n      // Handle at-rules that end with semicolon (like @layer)\n      if (node.name.startsWith('@') && i < tokens.length && tokens[i].type == 'semicolon') {\n        i++ // skip semicolon\n        return node\n      }\n    }\n\n    // Opening brace\n    if (i < tokens.length && tokens[i].type == 'open-brace') {\n      i++\n\n      // Parse contents\n      while (i < tokens.length && tokens[i].type != 'close-brace') {\n        if (tokens[i].type == 'text') {\n          // Check if it's a property or nested selector\n          if (i + 1 < tokens.length && tokens[i + 1].type == 'colon') {\n            // Property declaration\n            const property = tokens[i].value\n            i += 2 // skip text and colon\n\n            const value = tokens[i].value\n            i++ // skip value\n\n            node.props.push({ name: property, value })\n\n            // Skip semicolon if present\n            if (i < tokens.length && tokens[i].type == 'semicolon') {\n              i++\n            }\n          } else {\n            // Nested selector - recursively parse\n            const child = parseRule()\n            node.children.push(child)\n          }\n        } else {\n          i++ // skip unexpected tokens\n        }\n      }\n\n      // Closing brace\n      if (i < tokens.length && tokens[i].type == 'close-brace') {\n        i++\n      }\n    }\n\n    return node\n  }\n\n  while (i < tokens.length) {\n    const node = parseRule()\n    if (node.name || node.comment) {\n      nodes.push(node)\n    }\n  }\n\n  return nodes\n}\n\n\nfunction serialize(node) {\n  let result = ''\n\n  if (node.name) {\n    // Handle at-rules that end with semicolon\n    if (node.name.startsWith('@') && node.props.length == 0 && node.children.length == 0) {\n      result += node.name + ';'\n    } else {\n      result += node.name + '{'\n\n      // Add properties\n      if (node.props.length > 0) {\n        result += node.props.map(prop => prop.name + ':' + prop.value).join(';')\n\n        // Add semicolon after props if there are children\n        if (node.children.length > 0) result += ';'\n      }\n\n      // Add children\n      result += node.children.map(serialize).join('')\n\n      result += '}'\n    }\n  }\n\n  return result\n}\n\n\nexport function tokenize(css) {\n  const tokens = []\n  let i = 0\n\n  while (i < css.length) {\n    const char = css[i]\n\n    if (/\\s/.test(char)) {\n      i++\n      continue\n    }\n\n    if (char == '/' && css[i + 1] == '*') {\n      const start = i\n      i += 2\n      while (i < css.length && !(css[i] == '*' && css[i + 1] == '/')) i++\n      i += 2\n      tokens.push({ type: 'comment', value: css.slice(start, i) })\n      continue\n    }\n\n    if (char == '{') {\n      tokens.push({ type: 'open-brace', value: char })\n      i++\n      continue\n    }\n\n    if (char == '}') {\n      tokens.push({ type: 'close-brace', value: char })\n      i++\n      continue\n    }\n\n    if (char == ';') {\n      tokens.push({ type: 'semicolon', value: char })\n      i++\n      continue\n    }\n\n    if (char == ':') {\n      // Don't tokenize colons that are part of selectors\n      // Look back to see if we just had text (property name) or if this starts a selector\n      const lastToken = tokens[tokens.length - 1]\n      if (lastToken && lastToken.type == 'text') {\n        tokens.push({ type: 'colon', value: char })\n        i++\n        continue\n      }\n    }\n\n    // Text token\n    const { value, end } = readTextToken(css, i)\n    if (value) {\n      tokens.push({ type: 'text', value })\n    }\n    i = end\n  }\n\n  return tokens\n}\n\nfunction readTextToken(css, start) {\n  let i = start\n\n  while (i < css.length) {\n    const char = css[i]\n\n    // Stop at structural characters\n    if (char == '{' || char == '}' || char == ';') break\n    if (char == '/' && css[i + 1] == '*') break\n\n    // Stop at colon only if it's preceded by text (making it a property colon)\n    if (char == ':') {\n      // If we're in the middle of text and hit a colon, check if it's a property declaration\n      const textSoFar = css.slice(start, i).trim()\n      if (textSoFar) {\n        // Look ahead for value\n        let j = i + 1\n        while (j < css.length && /\\s/.test(css[j])) j++\n        while (j < css.length && css[j] != ';' && css[j] != '}' && css[j] != '{') j++\n\n        if (j < css.length && (css[j] == ';' || css[j] == '}')) {\n          break\n        }\n      }\n    }\n\n    i++\n  }\n\n  return { value: css.slice(start, i).trim(), end: i }\n}"
  },
  {
    "path": "packages/nuekit/src/tools/fswalk.js",
    "content": "\nimport { readdir, stat } from 'node:fs/promises'\nimport { parse, join, relative } from 'node:path'\n\nexport function matches(path, patterns) {\n  return patterns.some(pattern => path.includes(pattern))\n}\n\nexport function isSkipped(path) {\n  const { base, dir } = parse(path)\n  if ('._'.includes(base[0]) || '._'.includes(dir[0])) return true\n}\n\nfunction warn(message, path) {\n  console.warn(`Warning: ${message} ${path}`)\n}\n\nasync function walkDirectory(dir, root, opts) {\n  const { ignore = [], followSymlinks } = opts\n  const results = []\n\n  try {\n    const entries = await readdir(dir, { withFileTypes: true })\n\n    for (const entry of entries) {\n      const fullPath = join(dir, entry.name)\n      const relativePath = relative(root, fullPath)\n\n      if (isSkipped(relativePath) || matches(relativePath, ignore)) continue\n\n      try {\n        if (entry.isDirectory()) {\n          const subResults = await walkDirectory(fullPath, root, opts)\n          results.push(...subResults)\n\n        } else if (entry.isFile()) {\n          results.push(relativePath)\n        }\n\n      } catch (error) {\n        if (error.code == 'ENOENT') {\n          warn('Broken symlink', relativePath)\n        } else if (error.code == 'EACCES') {\n          warn('Permission denied', relativePath)\n        } else {\n          warn('Error accessing', relativePath)\n        }\n      }\n    }\n  } catch (error) {\n    warn('Permission denied reading directory', dir)\n  }\n\n  return results\n}\n\nexport async function fswalk(root = '.', opts = {}) {\n  try {\n    await stat(root)\n  } catch (error) {\n    throw new Error(`Root directory does not exist: ${root}`)\n  }\n\n  return await walkDirectory(root, root, opts)\n}"
  },
  {
    "path": "packages/nuekit/src/tools/fswatch.js",
    "content": "\nimport { promises as fs, watch } from 'node:fs'\nimport { join, extname } from 'node:path'\nimport { fswalk, matches } from './fswalk'\n\n// Main fswatch function\nexport function fswatch(root, opts = {}) {\n  const { ignore = ['.*', '_*', 'node_modules'] } = opts\n  // const shouldProcess = createDeduplicator()\n\n  // Start watching\n  const watcher = watch(root, { recursive: true }, async function(event, path) {\n    const { onupdate, onremove } = watcher\n    if (!path) return\n\n    // Skip editor backup files\n    if (isEditorBackup(path)) return\n\n    // Skip if this is a duplicate event\n    // if (!shouldProcess()) return\n\n    // Check if path should be ignored\n    if (matches(path, ignore)) return\n\n    try {\n      const fullPath = join(root, path)\n      const stat = await fs.lstat(fullPath)\n\n      // Process all files in the directory\n      if (onupdate && stat.isDirectory()) {\n        const paths = await fswalk(fullPath, ignore)\n\n        for (const subPath of paths) {\n          await onupdate(join(path, subPath))\n        }\n      }\n\n      if (onupdate && extname(path)) {\n        await onupdate(path)\n      }\n\n    } catch (error) {\n      if (error.errno == -2 && onremove) {\n        await onremove(path)\n\n      } else if (error.errno != -2) {\n        console.error('fswatch error:', error)\n      }\n    }\n  })\n\n  return watcher\n}\n\n// Check if file is an editor backup/temp file\nexport function isEditorBackup(path) {\n  return path.endsWith('~') || path.endsWith('.bck')\n}\n\n// Deduplicate rapid fire events\nexport function createDeduplicator() {\n  let lastTime = 0\n  return function shouldProcess() {\n    const now = Date.now()\n    if (now - lastTime < 50) return false\n    lastTime = now\n    return true\n  }\n}"
  },
  {
    "path": "packages/nuekit/src/tools/server.js",
    "content": "\nexport function createServer({ port=4000, handler }, callback) {\n\n  async function fetch(req) {\n    const { pathname, searchParams } = new URL(req.url)\n\n    // custom handler (proxy or worker)\n    const result = handler && await handler(req)\n    if (result) return result\n\n    // WebSocket connection for HMR\n    if (req.headers.get('upgrade') == 'websocket') {\n      return server.upgrade(req) ? undefined : new Response('Upgrade failed', { status: 500 })\n    }\n\n    // regular file serving\n    try {\n      const res = await callback(pathname, Object.fromEntries(searchParams))\n\n      // res = Bun.file\n      if (res?.stream) return new Response(res, { status: 200 })\n\n      // res = { content, type, status } || HTML <string>\n      if (res) {\n        return new Response(res.content || res, {\n          headers: { 'Content-Type': res.type || 'text/html; charset=utf-8' },\n          status: res.status || 200\n        })\n\n      } else {\n        console.error('Not found', pathname)\n        return new Response('404 Not Found', { status: 404 })\n      }\n\n    } catch (e) {\n      console.error(e)\n      return new Response('500 Server Error', { status: 500 })\n    }\n  }\n\n  const server = Bun.serve({ idleTimeout: 0, port, fetch, websocket })\n  return server\n}\n\n\nconst sessions = []\n\nconst websocket = {\n  open(ws) {\n    sessions.push(ws)\n    // console.log(`HMR connected, total: ${sessions.length}`)\n  },\n  close(ws) {\n    const i = sessions.indexOf(ws)\n    if (i >= 0) sessions.splice(i, 1)\n  }\n}\n\nexport function broadcast(data) {\n  sessions.forEach(ws => {\n    try { ws.send(JSON.stringify(data)) } catch(e) {}\n  })\n}\n\n"
  },
  {
    "path": "packages/nuekit/test/cli.test.js",
    "content": "\nimport { expandArgs, getArgs } from '../src/cli'\n\ntest('expandArgs', () => {\n  expect(expandArgs(['--silent', '-pve'])).toEqual([ \"--silent\", \"-p\", \"-v\", \"-e\" ])\n})\n\ntest('getArgs', () => {\n  const args = getArgs([\n    'build', '--silent', '--port', '2000', '-ni', '--', 'index.md', '.css'\n  ])\n\n  expect(args).toMatchObject({\n    cmd: 'build',\n    paths: [ 'index.md', '.css' ],\n    silent: true,\n    dryrun: true,\n    init: true,\n    port: 2000,\n  })\n})\n\ntest('bad option', () => {\n  expect(() => getArgs(['-k'])).toThrow('Unknown option: \"-k\"')\n})"
  },
  {
    "path": "packages/nuekit/test/cmd/build.test.js",
    "content": "\nimport { join } from 'node:path'\nimport { build, matches, stats, buildAsset, buildAll, minifyJS } from '../../src/cmd/build'\nimport { testDir, writeAll, removeAll, fileset } from '../test-utils'\nimport { trim } from '../../src/render/page'\nimport { createSite } from '../../src/site'\n\n\n\ntest('matches', () => {\n  expect(matches('index.md', ['foo'])).toBeFalse()\n  expect(matches('index.md', ['.md'])).toBeTrue()\n  expect(matches('docs/index.md', ['docs'])).toBeTrue()\n  expect(matches('index.md', ['./index.md'])).toBeTrue()\n})\n\ntest('stats', () => {\n  const lines = stats([{ type: 'css' }, { type: 'js' }, { type: 'js' }, { type: 'yaml' } ])\n  expect(lines).toEqual([ \"JavaScript files: 2\", \"CSS files: 1\" ])\n})\n\n\nconst CONF = {\n  root: testDir,\n  is_prod: true,\n  dist: join(testDir, '.dist'),\n\n  ignore: [ 'node_modules', 'functions' ],\n\n  site: {\n    view_transitions: true,\n    origin: 'https://acme.org'\n  },\n\n  design: {\n    inline_css: true\n  },\n\n  sitemap: { enabled: true },\n\n  rss: {\n    enabled: true,\n    collection: 'blog'\n  },\n\n  collections: {\n    blog:{include: [ 'blog/' ] }\n  }\n}\n\ndescribe('build', async () => {\n\n  beforeEach(async () => {\n    await writeAll([\n      ['@shared/ui/keyboard.ts', 'export const foo = 100'],\n      ['@shared/data/author.json', '{ \"foo\": 10 }'],\n      ['@shared/design/base.css', '/* CSS */'],\n      ['@shared/design/global.css', 'body { font-size: 15px }'],\n      ['index.md', '# Hello'],\n      ['404.md', '# 404'],\n      'blog/index.md',\n      'docs/index.md',\n\n      // should be ignored\n      'node_modules/test',\n      'functions/test',\n    ])\n  })\n\n  afterEach(async () => await removeAll())\n\n\n  test('buildAsset: MD', async () => {\n    const site = await createSite(CONF)\n    const home = site.get('index.md')\n\n    await buildAsset(home, testDir)\n    const html = await Bun.file(join(testDir, 'index.html')).text()\n\n    expect(html).toInclude('<style>body{font-size:15px}</style>')\n    expect(html).toInclude('/@nue/transitions.js')\n    expect(html).toInclude('<h1>Hello</h1>')\n  })\n\n  test('buildAll', async () => {\n    const { dist } = CONF\n    const { assets } = await createSite(CONF)\n\n    await buildAll(assets, { dist })\n    const results = await fileset(dist)\n\n    expect(assets.length).toBe(8)\n    expect(results.length).toBe(12)\n\n    // typescript conversion / minify\n    const js = await results.read('@shared/ui/keyboard.js')\n\n    expect(js).toInclude('var o=100;export{o as foo};')\n\n    // front page render\n    const home = await results.read('index.html')\n    expect(home).toInclude('<h1>Hello</h1>')\n  })\n\n\n  test('build feeds', async () => {\n    const site = await createSite(CONF)\n\n    await build(site, {  silent: true })\n    const results = await fileset(join(testDir, '.dist'))\n\n    // sitemap\n    const sitemap = await results.read('sitemap.xml')\n    expect(sitemap.length).toBeGreaterThan(250)\n\n\n    const feed = await results.read('feed.xml')\n    expect(sitemap.length).toBeGreaterThan(300)\n  })\n\n\n  test('build filtering', async () => {\n    const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {})\n    const site = await createSite(CONF)\n    const subset = await build(site, { dryrun: true, paths: ['.md', '.css'] })\n    expect(subset.length).toBe(6)\n  })\n\n})\n\ndescribe('SPA build', async () => {\n\n  beforeEach(async () => {\n    await writeAll([\n      ['index.html', '<!doctype dhtml> <body>Hello</body>'],\n      ['base.css', ':root { --brand: #ccc }'],\n    ])\n  })\n\n  afterEach(async () => await removeAll())\n\n  test('build SPA', async () => {\n    const { assets } = await createSite(CONF)\n    const { dist } = CONF\n    await buildAll(assets, { dist })\n    const results = await fileset(dist)\n    expect(results.length).toBe(8)\n\n    // html page\n    const html = await results.read('index.html')\n\n    expect(html).toInclude('<body nue=\"default-app\"></body>')\n    expect(html).toInclude('/@nue/mount.js')\n    expect(html).not.toInclude('/base.css')\n    expect(html).toInclude('<style>:root{--brand:#ccc}</style>')\n\n    // SPA minify\n    const js = await results.read('index.html.js')\n    expect(js).toInclude('var t=[')\n\n    // minified CSS files should still exist\n    const css = await results.read('base.css')\n    expect(css).toBe(':root{--brand:#ccc}')\n\n  })\n\n})\n\n\ntest('Minify JS', async () => {\n  const code = `\n    async function test()  {\n      const nue = await import('/@nue/nue.js')\n      nue.hello()\n    }\n    await test()\n  `\n  expect(await minifyJS(code)).toStartWith('var')\n})\n\n\n"
  },
  {
    "path": "packages/nuekit/test/cmd/create.test.js",
    "content": "\nimport { create, unzip, getLocalZip, fetchZip } from '../../src/cmd/create'\nimport { rm, readdir } from 'node:fs/promises'\n\nconst testdir = import.meta.dirname\n\n// suppress console messages\njest.spyOn(console, 'log').mockImplementation(() => {})\n\nafterEach(async () => {\n  await rm('minimal', { recursive: true, force: true })\n})\n\ntest('getLocalZip', async () => {\n  const zip = await getLocalZip('minimal', testdir)\n  expect(await zip.exists()).toBeTrue()\n})\n\ntest.skip('fetchZip', async () => {\n  const zip = await fetchZip('minimal', 'https://nuejs.org')\n  expect(zip.status).toBe(200)\n})\n\ntest('unzip', async () => {\n  const dir = 'minimal'\n  const zip = await getLocalZip(dir, testdir)\n  await unzip(dir, zip)\n  const files = await readdir(dir)\n  expect(new Set(files)).toEqual(new Set([\"index.html\", \"index.css\"]))\n})\n\ntest('create', async () => {\n  expect(await create('minimal', { dir: testdir })).toBeTrue()\n})\n"
  },
  {
    "path": "packages/nuekit/test/cmd/serve.test.js",
    "content": "\nimport { findAssetByURL, onServe } from '../../src/cmd/serve.js'\n\ntest('find /', () => {\n  const asset = findAssetByURL('/', [{ url: '/' }])\n  expect(asset).not.toBeNull()\n})\n\ntest('find /@nue/nue.js', () => {\n  const asset = findAssetByURL('/@nue/nue.js')\n  expect(asset.render).not.toBeNull()\n})\n\ntest('find app.html.js', () => {\n  const asset = findAssetByURL('/app.html.js', [{ path: 'app.html' }])\n  expect(asset).not.toBeNull()\n})\n\ntest('onServe /test', async () => {\n  const page = { url: '/test', render() { return 'hey' } }\n  const result = await onServe('/test', [ page ], {})\n  expect(result.content).toBe('hey')\n  expect(result.type).toInclude('text/html')\n})\n\ntest('onServe /app/users', async () => {\n  const spa = { path: 'app/index.html', render() { return { html: 'hey' }} }\n  const result = await onServe('/app/users', [ spa ])\n  expect(result).toBe('hey')\n})\n\ntest('onServe 404.html', async () => {\n  const page = { name: '404', render() { return 'error' } }\n  const result = await onServe('/failure', [ page ])\n  expect(result).toEqual({ content: 'error', status: 404 })\n})\n\n\ndescribe.skip('old stuff', async () => {\n\n  beforeEach(async () => {\n    await writeAll([\n      ['lib/components.html', '<!doctype dhtml>'],\n      ['lib/keyboard.js', '// keyboard'],\n      ['lib/base.css', '/* CSS */'],\n      ['blog/entry.md', '# Entry'],\n      ['index.md', '# Home'],\n      ['404.md', '# 404'],\n    ])\n  })\n\n  afterEach(async () => await removeAll())\n\n\n  test('serve', async () => {\n\n    // server\n    const server = site.serve()\n    expect(server.url).toBe('http://localhost:6666/')\n    expect(server.port).toBe(6666)\n\n    // request home page\n    const res = await fetch(new Request(server.url))\n    expect(res.status).toBe(200)\n    expect(await res.text()).toInclude('<h1>Hello</h1>')\n\n    // 404 page\n    const fail = await fetch(new Request(server.url + 'missing'))\n    expect(fail.status).toBe(404)\n    expect(await fail.text()).toInclude('<h1>404</h1>')\n\n    // request CSS\n    await site.build({ filters: ['**/*.css'] })\n    const css = await fetch(new Request(server.url + '@shared/design/base.css'))\n    expect(await css.text()).toBe('/* hey */')\n\n    server.stop()\n\n    // await new Promise(resolve => {resolve() })\n  })\n\n})"
  },
  {
    "path": "packages/nuekit/test/collections.test.js",
    "content": "\nimport { getCollections } from '../src/collections.js'\nimport { createAsset } from '../src/asset'\n\n// Mock page objects\nconst pages = [\n  {\n    path: 'blog/post1.md',\n    parse: async () => ({ meta: { title: 'First Post', date: '2024-01-01', draft: false } })\n  },\n  {\n    path: 'blog/post2.md',\n    parse: async () => ({ meta: { title: 'Second Post', date: '2024-01-02', tags: ['design'] } })\n  },\n  {\n    path: 'blog/draft.md',\n    parse: async () => ({ meta: { title: 'Draft Post', draft: true } })\n  },\n  {\n    path: 'docs/guide.md',\n    parse: async () => ({ meta: { title: 'Guide', order: 1 } })\n  }\n]\n\ntest('basic collection matching', async () => {\n  const opts = { blog: { include: ['blog/'] } }\n\n  const collections = await getCollections(pages, opts)\n  expect(collections.blog).toHaveLength(3)\n})\n\ntest('require filtering', async () => {\n  const opts = {\n    blog: {\n      include: ['blog/'],\n      require: ['date']\n    }\n  }\n\n  const collections = await getCollections(pages, opts)\n  expect(collections.blog).toHaveLength(2)\n})\n\ntest('skip filtering', async () => {\n  const opts = {\n    blog: {\n      include: ['blog/'],\n      skip: ['draft']\n    }\n  }\n\n  const collections = await getCollections(pages, opts)\n  expect(collections.blog).toHaveLength(2)\n})\n\ntest('tags filtering', async () => {\n  const opts = {\n    blog: {\n      include: ['blog/'],\n      tags: ['design']\n    }\n  }\n\n  const collections = await getCollections(pages, opts)\n  expect(collections.blog).toHaveLength(1)\n  expect(collections.blog[0].tags).toEqual(['design'])\n})\n\ntest('sorting', async () => {\n  const opts = {\n    blog: {\n      include: ['blog/'],\n      skip: ['draft'],\n      sort: 'date desc'\n    }\n  }\n\n  const collections = await getCollections(pages, opts)\n  expect(collections.blog[0].title).toBe('Second Post')\n})\n\n\ntest('rendering', async () => {\n  const files = [\n    { is_md: true, path: 'blog/index.md', async text() { return '# Hello' } },\n\n    { is_html: true, path: '@shared/ui/pagefoot.html', async text() {\n      return '<pagefoot><p :each=\"page of pages\">{ page.title }</p></pagefoot>'\n    }},\n  ]\n\n  const conf = {\n    collections: { pages: { include: [ 'blog/' ]} }\n  }\n\n  const page = createAsset(files[0], { files, conf })\n\n  const html = await page.render()\n  expect(html).toInclude('<div><p>Hello</p></div>')\n\n})\n\n"
  },
  {
    "path": "packages/nuekit/test/conf.test.js",
    "content": "\nimport { readSiteConf, mergeData, mergeValue } from '../src/conf'\nimport { testDir, writeAll, removeAll } from './test-utils'\nimport { createAsset } from '../src/asset'\nimport { getFileInfo } from '../src/file'\nimport { trim } from '../src/render/page'\n\n\ntest('site overrides', async () => {\n  const CONF = trim(`\n    site:\n      skip: [functions]\n\n    server:\n      dir: epic-server\n\n    meta:\n      title: Bad\n\n    production:\n      title: Good\n  `)\n\n  await writeAll([['site.yaml', CONF]])\n  const conf = await readSiteConf({ root: testDir, is_prod: true, port: 666 })\n\n  expect(conf).toMatchObject({\n    server: { dir: \"epic-server\" },\n    meta: { title: \"Good\" },\n    port: 666,\n    is_prod: true,\n  })\n\n  expect(conf.ignore).toContain('functions')\n  expect(conf.ignore).toContain('epic-server')\n  expect(conf.ignore.length).toBeGreaterThan(10)\n\n  await removeAll()\n})\n\ntest('site overrides', () => {\n  const conf = { port: 4000, rss: { enabled: true } }\n  mergeValue(conf, 'port', 8080)\n  mergeValue(conf, 'site', { other: true })\n  expect(conf.port).toBe(4000)\n  expect(conf.rss.enabled).toBe(true)\n})\n\n\ntest('meta/content merge', () => {\n  const conf = { meta: { title: 'Site', author: 'John' } }\n  mergeValue(conf, 'meta', { title: 'App' })\n  expect(conf.meta.title).toBe('App')\n  expect(conf.meta.author).toBe('John')\n})\n\ntest('simple override', () => {\n  const conf = { exclude: ['old'] }\n  mergeValue(conf, 'exclude', ['new'])\n  expect(conf.exclude).toEqual(['new'])\n})\n\ntest('mergeData', () => {\n  const data = mergeData([\n    { sitename: 'Acme', port: 4000, meta: { title: 'Old', desc: 'Old' } },\n    { meta: { title: 'New' }, team: [], desc: 'New' }\n  ])\n\n  expect(data).toEqual({\n    sitename: \"Acme\",\n    title: \"New\",\n    desc: \"New\",\n    team: [],\n  })\n})\n\n\ntest('asset data/config', async () => {\n\n  const files = [\n    { path: 'site.yaml', text: 'dont: doit' },\n    { path: 'team.yaml', text: 'team: [ jane, john ]' },\n    { path: 'blog/app.yaml', text: 'port: 3000\\ninclude: [ syntax ]\\nmeta:\\n title: Acme' },\n    { path: 'blog/entry/data.yaml', text: 'size: 1000' },\n    { path: 'docs/app.yaml', text: 'should_not: be' },\n\n  ].map(file => {\n    const { text } = file\n    return { ...getFileInfo(file.path), text: async function() { return text } }\n  })\n\n  const conf = { is_prod: true, port: 5000 }\n\n  const asset = createAsset({ path: 'blog/entry/index.md' }, { files, conf })\n\n  expect(await asset.data()).toEqual({\n    is_prod: true,\n    team: [ \"jane\", \"john\" ],\n    title: \"Acme\",\n    size: 1000,\n  })\n\n  expect(await asset.config()).toEqual({\n    is_prod: true,\n    include: [ \"syntax\" ],\n    meta: { title: \"Acme\" },\n    port: 5000\n  })\n\n})\n\n"
  },
  {
    "path": "packages/nuekit/test/css.test.js",
    "content": "\nimport { tokenize, parseCSS, minifyCSS } from '../src/tools/css'\n\ndescribe('tokenizer', () => {\n\n  test('comments', () => {\n    const tokens = tokenize('/* header styles */')\n    expect(tokens).toEqual([\n      { type: 'comment', value: '/* header styles */' }\n    ])\n  })\n\n  test('simple rule', () => {\n    const tokens = tokenize('div { color: red; }')\n    expect(tokens).toEqual([\n      { type: 'text', value: 'div' },\n      { type: 'open-brace', value: '{' },\n      { type: 'text', value: 'color' },\n      { type: 'colon', value: ':' },\n      { type: 'text', value: 'red' },\n      { type: 'semicolon', value: ';' },\n      { type: 'close-brace', value: '}' }\n    ])\n  })\n\n  test('nested rule', () => {\n    const tokens = tokenize('.card { padding: 1rem; &:hover { color: blue; } }')\n    expect(tokens).toEqual([\n      { type: 'text', value: '.card' },\n      { type: 'open-brace', value: '{' },\n      { type: 'text', value: 'padding' },\n      { type: 'colon', value: ':' },\n      { type: 'text', value: '1rem' },\n      { type: 'semicolon', value: ';' },\n      { type: 'text', value: '&:hover' },\n      { type: 'open-brace', value: '{' },\n      { type: 'text', value: 'color' },\n      { type: 'colon', value: ':' },\n      { type: 'text', value: 'blue' },\n      { type: 'semicolon', value: ';' },\n      { type: 'close-brace', value: '}' },\n      { type: 'close-brace', value: '}' }\n    ])\n  })\n\n  test('complex selector', () => {\n    const tokens = tokenize('.item:nth-child(2n+1):not(.hidden) { display: block; }')\n\n    expect(tokens).toEqual([\n      { type: 'text', value: '.item:nth-child(2n+1):not(.hidden)' },\n      { type: 'open-brace', value: '{' },\n      { type: 'text', value: 'display' },\n      { type: 'colon', value: ':' },\n      { type: 'text', value: 'block' },\n      { type: 'semicolon', value: ';' },\n      { type: 'close-brace', value: '}' }\n    ])\n  })\n\n  test('@layer definition', () => {\n    const tokens = tokenize('@layer base, component;')\n    expect(tokens).toEqual([\n      { type: \"text\", value: \"@layer base, component\" },\n      { type: \"semicolon\", value: \";\" }\n    ])\n  })\n\n})\n\ndescribe('parser', () => {\n  test('simple rule', () => {\n    const result = parseCSS('div { color: red; }')\n    expect(result).toEqual([{\n      name: 'div',\n      props: [{ name: 'color', value: 'red' }],\n      children: []\n    }])\n  })\n\n  test('rule with comment', () => {\n    const result = parseCSS('/* header styles */ .header { padding: 1rem; }')\n    expect(result).toEqual([{\n      name: '.header',\n      comment: '/* header styles */',\n      props: [{ name: 'padding', value: '1rem' }],\n      children: []\n    }])\n  })\n\n  test('nested rule', () => {\n    const result = parseCSS('.card { padding: 1rem; &:hover { color: blue; } }')\n    expect(result).toEqual([{\n      name: '.card',\n      props: [{ name: 'padding', value: '1rem' }],\n      children: [{\n        name: '&:hover',\n        props: [{ name: 'color', value: 'blue' }],\n        children: []\n      }]\n    }])\n  })\n})\n\ndescribe('minify', () => {\n  test('simple rule', () => {\n    const result = minifyCSS('div { color: red; }')\n    expect(result).toBe('div{color:red}')\n  })\n\n  test('strips whitespace', () => {\n    const result = minifyCSS(`\n      .header {\n        padding: 1rem;\n        margin: 0;\n      }\n    `)\n    expect(result).toBe('.header{padding:1rem;margin:0}')\n  })\n\n  test('comment', () => {\n    expect(minifyCSS('/* CSS */')).toBe('')\n  })\n\n  test(':root + @layer', () => {\n    const css = minifyCSS('@layer base { :root { --brand: #ccc } }')\n    expect(css).toBe('@layer base{:root{--brand:#ccc}}')\n  })\n\n  test(':root props', () => {\n    const css = minifyCSS(':root { --brand: #ccc }')\n    expect(css).toBe(':root{--brand:#ccc}')\n  })\n\n  test('strips comments', () => {\n    const result = minifyCSS('/* header styles */ .header { padding: 1rem; }')\n    expect(result).toBe('.header{padding:1rem}')\n  })\n\n  test('nested rules', () => {\n    const result = minifyCSS('.card { padding: 1rem; &:hover { color: blue; } }')\n    expect(result).toBe('.card{padding:1rem;&:hover{color:blue}}')\n  })\n\n  test('multiple rules', () => {\n    const result = minifyCSS('div { color: red; } .box { width: 100px; }')\n    expect(result).toBe('div{color:red}.box{width:100px}')\n  })\n})\n"
  },
  {
    "path": "packages/nuekit/test/deps.test.js",
    "content": "\nimport { test, expect } from 'bun:test'\nimport { listDependencies, parseDirs } from '../src/deps'\n\nconst paths = [\n\n  // root (2)\n  'site.yaml',\n  'globals.js',\n  'index.md',\n\n  // shared (6)\n  '@shared/design/base.css',\n  '@shared/ui/global.css',\n  '@shared/data/authors.yaml',\n  '@shared/ui/page.html',\n  '@shared/ui/button.html',\n  '@shared/ui/keyboard.js',\n\n  // libs\n  '@shared/lib/calendar.js',\n\n  // app (4)\n  'app/index.html',\n  'app/main.js',\n  'app/ui/header.html',\n  'app/ui/login.html',\n\n  // blog (4)\n  'blog/index.md',\n  'blog/layout.html',\n  'blog/entry/index.md',\n  'blog/entry/content.html',\n\n  // marketing (2)\n  'marketing/table.html',\n  'marketing/chart.js',\n\n  // home directory\n  'home/layout.css',\n  'home/home.yaml',\n]\n\ntest('parseDirs', () => {\n  expect(parseDirs('blog')).toEqual(['blog'])\n  expect(parseDirs('blog/entry')).toEqual(['blog', 'blog/entry'])\n})\n\ntest('SPA app', () => {\n  const deps = listDependencies('app/index.html', { paths })\n  expect(deps.length).toBe(2 + 6 + 3) // root + shared + app (no self)\n  expect(deps).toContain('site.yaml')\n  expect(deps).toContain('globals.js')\n})\n\ntest('root SPA', () => {\n  const paths = [ 'ui/spa.css', 'ui/users.html', 'index.html' ]\n  const deps = listDependencies('index.html', { paths })\n  expect(deps.length).toBe(2)\n})\n\ntest('MPA deps', () => {\n  const deps = listDependencies('blog/entry/index.md', { paths })\n\n  expect(deps.length).toBe(2 + 6 + 2) // root + shared + blog hierarchy\n  expect(deps).toContain('site.yaml')\n  expect(deps).toContain('blog/layout.html')\n})\n\ntest('standalone html', () => {\n  const deps = listDependencies('marketing/table.html', { paths })\n  expect(deps.length).toBe(2 + 6 + 1) // root + shared + marketing\n  expect(deps).toContain('site.yaml')\n  expect(deps).toContain('marketing/chart.js')\n  expect(deps).not.toContain('app/main.js')\n})\n\n\ntest('local CSS', () => {\n  const deps = listDependencies('app/index.html', { paths: [...paths, 'app/custom.css'] })\n  expect(deps.length).toBe(2 + 6 + 4) // custom.css included\n  expect(deps).toContain('app/custom.css')\n})\n\ntest('exclusions', () => {\n  const deps = listDependencies('app/index.html', {\n    exclude: ['app/ui', '@shared', 'site.yaml'],\n    paths,\n  })\n\n  expect(deps).toEqual(['globals.js', 'app/main.js'])\n})\n\ntest('inclusions', () => {\n  const deps = listDependencies('app/index.html', {\n    exclude: ['@shared/', 'app/ui'],\n    include: ['keyboard', 'calendar'],\n    paths,\n  })\n\n  expect(deps.includes('@shared/ui/keyboard.js')).toBeTrue()\n  expect(deps.includes('@shared/lib/calendar.js')).toBeTrue()\n  expect(deps.length).toBe(5)\n})\n\n\ntest('home auto-include', () => {\n  const deps = listDependencies('index.md', { paths, exclude: [ '@' ] })\n  expect(deps.includes('home/layout.css')).toBeTrue()\n  expect(deps.length).toBe(4)\n})\n\n\n\n"
  },
  {
    "path": "packages/nuekit/test/file.test.js",
    "content": "\nimport { join, parse } from 'node:path'\n\nimport { testDir, write, removeAll } from './test-utils'\nimport { createFile, getFileInfo, getURL, getSlug } from '../src/file'\n\n\ntest('url property', () => {\n  function testURL(path, expected) {\n    expect(getURL(parse(path))).toBe(expected)\n  }\n  testURL('index.md', '/')\n  testURL('index.css', '/index.css')\n  testURL('blog/entry.md', '/blog/entry')\n  testURL('app/index.html', '/app/')\n  testURL('blog/table.html', '/blog/table')\n  testURL('docs/installation.md', '/docs/installation')\n  testURL('@shared/design/base.css', '/@shared/design/base.css')\n  testURL('site.yaml', '/site.yaml')\n})\n\ntest('slug property', () => {\n  expect(getSlug(parse('blog/entry.md'))).toBe('entry')\n  expect(getSlug(parse('blog/index.html'))).toBe('')\n})\n\n\ntest('getFileInfo', () => {\n  const info = getFileInfo('blog/table.html')\n    expect(info).toEqual({\n    dir: \"blog\",\n    base: \"table.html\",\n    ext: \".html\",\n    name: \"table\",\n    path: \"blog/table.html\",\n    slug: \"table.html\",\n    type: \"html\",\n    url: \"/blog/table\",\n    is_html: true,\n  })\n})\n\ntest('createFile', async () => {\n  const path = await write('@shared/model/index.ts', '// hello')\n  const file = await createFile(testDir, path)\n\n  expect(file).toMatchObject({\n    rootpath: 'test_dir/@shared/model/index.ts',\n    path: '@shared/model/index.ts',\n    dir: '@shared/model',\n    basedir: '@shared',\n    base: 'index.ts',\n    name: 'index',\n    is_ts: true,\n    ext: '.ts',\n  })\n\n  expect(file.mtime).toBeInstanceOf(Date)\n  expect(await file.text()).toBe('// hello')\n\n  // copy operation\n  await file.copy(join(testDir, '.dist'))\n  expect(await Bun.file(join(testDir, '.dist', file.path)).exists()).toBeTrue()\n\n  await removeAll()\n})\n\n\n\n\n\n\n"
  },
  {
    "path": "packages/nuekit/test/fswalk.test.js",
    "content": "\nimport { mkdir } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport { testDir, writeAll, removeAll } from './test-utils'\nimport { fswalk } from '../src/tools/fswalk'\n\n\nbeforeEach(async () => {\n  await writeAll([\n    ['file1.txt', 'content1'],\n    ['file2.js', 'console.log(\"hello\")'],\n    ['.hidden', 'hidden content'],\n    ['nested/nested-file.md', '# Nested'],\n    ['node_modules/package.json', '{}'],\n    ['real-dir/real-file.txt', 'real content']\n  ])\n})\n\nafterEach(async () => { await removeAll() })\n\n\ntest('returns array of file paths', async () => {\n  const paths = await fswalk(testDir)\n\n  expect(Array.isArray(paths)).toBe(true)\n  expect(paths.length).toBeGreaterThan(0)\n  expect(paths).toContain('file1.txt')\n  expect(paths).toContain('file2.js')\n  expect(paths).toContain('nested/nested-file.md')\n})\n\ntest('ignores dotfiles', async () => {\n  const paths = await fswalk(testDir)\n  expect(paths).not.toContain('.hidden')\n})\n\ntest('ignores patterns from ignore array', async () => {\n  const paths = await fswalk(testDir, { ignore: ['node_modules'] })\n  const hasNodeModules = paths.some(path => path.includes('node_modules'))\n  expect(hasNodeModules).toBe(false)\n})\n\ntest('throws error when root does not exist', async () => {\n  await expect(fswalk('./non-existent-directory')).rejects.toThrow('Root directory does not exist')\n})\n\ntest('returns empty array for empty directory', async () => {\n  const emptyDir = join(testDir, 'empty')\n  await mkdir(emptyDir)\n  const paths = await fswalk(emptyDir)\n  expect(paths).toEqual([])\n})\n\ntest('multiple ignore patterns', async () => {\n  const paths = await fswalk(testDir, { ignore: ['.js', 'node_modules'] })\n\n  expect(paths).not.toContain('file2.js')\n  const hasNodeModules = paths.some(path => path.includes('node_modules'))\n  expect(hasNodeModules).toBe(false)\n})\n\ntest('uses current directory as default root', async () => {\n  const paths = await fswalk()\n  expect(Array.isArray(paths)).toBe(true)\n})\n\ntest('works with empty options object', async () => {\n  const paths = await fswalk(testDir, {})\n  expect(Array.isArray(paths)).toBe(true)\n  expect(paths.length).toBeGreaterThan(0)\n})\n\ntest('respects glob patterns in ignore', async () => {\n  await writeAll([\n    ['src/components/Button.jsx', 'export default Button'],\n    ['src/index.js', 'main file'],\n  ])\n\n  const paths = await fswalk(testDir, { ignore: ['src/components'] })\n  expect(paths).toContain('src/index.js')\n  expect(paths).not.toContain('src/components/Button.jsx')\n})\n\n"
  },
  {
    "path": "packages/nuekit/test/fswatch.test.js",
    "content": "import { test, expect } from 'bun:test'\nimport { promises as fs } from 'node:fs'\nimport { join } from 'node:path'\n\nimport {\n  createDeduplicator,\n  isEditorBackup,\n  fswatch,\n} from '../src/tools/fswatch.js'\n\n// Helper function to wait for expected array length\nasync function waitForEvents(array, expectedCount, maxWait = 1000) {\n  const start = Date.now()\n  while (array.length < expectedCount && Date.now() - start < maxWait) {\n    await new Promise(resolve => setTimeout(resolve, 5))\n  }\n}\n\ntest('identify backup files', () => {\n  expect(isEditorBackup('file.txt~')).toBe(true)\n  expect(isEditorBackup('script.js~')).toBe(true)\n  expect(isEditorBackup('file.txt_123456.bck')).toBe(true)\n  expect(isEditorBackup('regular.css')).toBe(false)\n})\n\ntest.skip('deduplicator blocks rapid events', async () => {\n  const shouldProcess = createDeduplicator()\n\n  expect(shouldProcess()).toBe(true)\n  expect(shouldProcess()).toBe(false)\n\n  // Wait longer than debounce period\n  await new Promise(resolve => setTimeout(resolve, 60))\n  expect(shouldProcess()).toBe(true)\n})\n\ntest('watches single file changes', async () => {\n  const tmpDir = await fs.mkdtemp('/tmp/fswatch-test-')\n  const testFile = join(tmpDir, 'test.txt')\n\n  const changes = []\n  const watcher = fswatch(tmpDir)\n  watcher.onupdate = async path => changes.push(path)\n\n  // Create a file\n  await fs.writeFile(testFile, 'hello')\n\n  // Wait for event\n  await waitForEvents(changes, 1)\n\n  expect(changes).toContain('test.txt')\n\n  watcher.close()\n  await fs.rm(tmpDir, { recursive: true })\n})\n\ntest('watches directory creation and processes files', async () => {\n  const tmpDir = await fs.mkdtemp('/tmp/fswatch-test-')\n  const newDir = join(tmpDir, 'newdir')\n\n  const changes = []\n  const watcher = fswatch(tmpDir)\n  watcher.onupdate = async path => changes.push(path)\n\n  // Create directory with files\n  await fs.mkdir(newDir)\n  await fs.writeFile(join(newDir, 'file1.txt'), 'content1')\n  await fs.writeFile(join(newDir, 'file2.js'), 'content2')\n\n  // Wait for events\n  await waitForEvents(changes, 2)\n\n  expect(changes).toContain('newdir/file1.txt')\n  expect(changes).toContain('newdir/file2.js')\n\n  watcher.close()\n  await fs.rm(tmpDir, { recursive: true })\n})\n\ntest('ignores files matching patterns', async () => {\n  const tmpDir = await fs.mkdtemp('/tmp/fswatch-test-')\n\n  const changes = []\n  const watcher = fswatch(tmpDir, { ignore: ['*.log', '.hidden*'] })\n  watcher.onupdate = async path => changes.push(path)\n\n  // Create files\n  await fs.writeFile(join(tmpDir, 'good.txt'), 'content')\n  await fs.writeFile(join(tmpDir, 'debug.log'), 'logs')\n  await fs.writeFile(join(tmpDir, '.hidden'), 'secret')\n\n  // Wait for the one file we expect\n  await waitForEvents(changes, 1)\n\n  expect(changes).toContain(\"good.txt\")\n  expect(changes).not.toContain('.hidden')\n\n  watcher.close()\n  await fs.rm(tmpDir, { recursive: true })\n})\n\ntest('handles file removal', async () => {\n  const tmpDir = await fs.mkdtemp('/tmp/fswatch-test-')\n  const testFile = join(tmpDir, 'test.txt')\n\n  const removed = []\n  const watcher = fswatch(tmpDir)\n  watcher.onremove = async path => removed.push(path)\n\n  // Create then remove file\n  await fs.writeFile(testFile, 'content')\n  await new Promise(resolve => setTimeout(resolve, 50)) // Let creation settle\n  await fs.unlink(testFile)\n\n  // Wait for removal event\n  await waitForEvents(removed, 1)\n\n  expect(removed).toContain('test.txt')\n\n  watcher.close()\n  await fs.rm(tmpDir, { recursive: true })\n})\n\ntest('ignores files without extensions', async () => {\n  const tmpDir = await fs.mkdtemp('/tmp/fswatch-test-')\n\n  const changes = []\n  const watcher = fswatch(tmpDir)\n  watcher.onupdate = async path => changes.push(path)\n\n  // Create files with and without extensions\n  await fs.writeFile(join(tmpDir, 'withext.txt'), 'content')\n  await fs.writeFile(join(tmpDir, 'noext'), 'content')\n\n  // Wait for the one file we expect\n  await waitForEvents(changes, 1)\n\n  expect(changes).toContain('withext.txt')\n  expect(changes).not.toContain('noext')\n\n  watcher.close()\n  await fs.rm(tmpDir, { recursive: true })\n})"
  },
  {
    "path": "packages/nuekit/test/render/asset-render.test.js",
    "content": "\nimport { createAsset } from '../../src/asset'\n\n\ntest('index.html', async () => {\n  const page = createAsset({\n    async text() { return '<h1>Hey { slug }</h1> <p>{{ markdown(\"*boo*\") }}</p>' },\n    is_html: true,\n    slug: 'world',\n    url: '/'\n  })\n\n  const html = await page.render()\n  expect(html).toInclude('<article><h1>Hey world</h1>')\n  expect(html).toInclude('<p><em>boo</em></p>')\n})\n\n\ntest('html lib', async () => {\n  const files = [\n    { is_html: true, path: 'user-list.html', ext: '.html',\n      async text() { return '<main><user-list/></main>' }\n    },\n    { is_html: true, path: 'lib.html', async text() { return '<ul :is=\"user-list\"/>' }},\n  ]\n\n  const page = createAsset(files[0], { files })\n\n  // document\n  const doc = await page.parse()\n  expect(doc.lib.length).toBe(1)\n\n  // reactive components / dependencies\n  const comps = await page.components()\n  expect(comps.length).toBe(1)\n  expect(comps[0].tag).toBe('ul')\n\n  // render HTML\n  const html = await page.render()\n  expect(html).toInclude('<main><ul></ul></main>')\n  expect(await page.contentType()).toInclude('text/html')\n})\n\n\ntest('dhtml', async () => {\n  const file = createAsset({\n    async text() { return '<!dhtml lib> <comp>Again</comp>' },\n    path: 'lib.html',\n    is_html: true,\n  })\n\n  const page = createAsset({\n    async text() { return '<!dhtml> <h1>Hey</h1> <p>World</p> <comp/>' },\n    path: 'index.html',\n    is_html: true,\n  }, { files: [ file ] })\n\n  const { html, js } = await page.render()\n\n  expect(html).toInclude('<meta name=\"libs\" content=\"index.html lib.html\">')\n  expect(html).toInclude('<article nue=\"default-app\"></article>')\n  expect(html).toInclude('\"state\":\"/@nue/state.js\"')\n  expect(js).toInclude(`export const lib = [ { tag: 'article'`)\n  expect(js).toInclude(\"is: 'default-app'\")\n\n  expect(await file.render()).toInclude(\"export const lib = [ { tag: 'comp'\")\n})\n\n\n\n\n\n"
  },
  {
    "path": "packages/nuekit/test/render/feed.test.js",
    "content": "\nimport { generateSitemap, generateFeed, renderSitemap, renderFeed } from '../../src/render/feed'\n\n\ntest('generateSitemap', async () => {\n  const asset = {\n    is_md: true,\n    url: '/page1',\n    mtime: new Date('2024-01-01'),\n    parse: async () => ({ meta: { title: 'Page 1' } })\n  }\n\n  const draft = {\n    is_md: true,\n    parse: async () => ({ meta: { draft: true } })\n  }\n\n  const conf = {\n    site: { origin: 'https://example.com' },\n    sitemap: { skip: ['draft'] }\n  }\n\n  const xml = await generateSitemap([asset, draft ], conf)\n  expect(xml.length < 200).toBeTrue()\n  expect(xml).toInclude('<loc>https://example.com/page1</loc>')\n})\n\ntest('generateFeed', async () => {\n\n  const meta = {\n    date: new Date('2026-01-01'),\n    title: 'First',\n    desc: 'Desc',\n  }\n\n  const asset = {\n    is_md: true,\n    path: 'blog/first.md',\n    url: '/blog/first',\n    parse: async () => ({ meta })\n  }\n\n  const conf = {\n    site: { origin: 'https://acme.org' },\n    rss: {\n      title: 'Acme Blog',\n      collection: 'blog',\n      description: 'Acme desc'\n    },\n    collections: {\n      blog: {\n        include: ['blog/']\n      }\n    }\n  }\n\n  const xml = await generateFeed([asset], conf)\n\n  expect(xml.length).toBeGreaterThan(300)\n  expect(xml).toInclude('<title>Acme Blog</title>')\n  expect(xml).toInclude('<link>https://acme.org/blog/first</link>')\n  expect(xml).toInclude('<pubDate>2026-01-01</pubDate>')\n})\n\ntest('renderSitemap', () => {\n  const page = { mtime: new Date(), url: '/about', origin: 'https://acme.org' }\n  const xml = renderSitemap([page])\n  expect(xml).toInclude('<loc>https://acme.org/about</loc>')\n})\n\ntest('renderFeed', () => {\n  const meta = {\n    title: 'My Blog',\n    description: 'A test blog',\n    origin: 'https://example.com'\n  }\n\n  const page = {\n    title: 'First Post',\n    description: 'My first blog post',\n    pubDate: new Date('2025-01-15'),\n    url: '/first-post',\n  }\n\n  const xml = renderFeed(meta, [page])\n\n  expect(xml).toInclude('<rss version=\"2.0\">')\n  expect(xml).toInclude('<title>My Blog</title>')\n  expect(xml).toInclude('<title>First Post</title>')\n  expect(xml).toInclude('<pubDate>2025-01-15</pubDate>')\n  expect(xml).toInclude('https://example.com/first-post')\n})"
  },
  {
    "path": "packages/nuekit/test/render/head.test.js",
    "content": "\nimport { renderScripts, renderStyles, renderMeta, renderHead, } from '../../src/render/head'\n\ntest('renderScripts', () => {\n  const [foo, bar] = renderScripts([\n    { ext: '.ts', dir: '@shared', name: 'foo' },\n    { ext: '.js', dir: 'blog', name: 'bar' },\n  ])\n  expect(foo).toBe('<script src=\"/@shared/foo.js\" type=\"module\"></script>')\n  expect(bar).toInclude('/blog/bar.js')\n})\n\ntest('renderStyles', async () => {\n  const files = [\n    { is_css: true, path: 'base.css', base: 'base.css', async text() { return '' } },\n    { is_css: true, path: 'a/style.css', async text() { return 'body {}' } },\n    { is_css: true, path: 'b/style.css', async text() { return '' } },\n  ]\n\n  // sort base first\n  const css = await renderStyles(files)\n  expect(css[0]).toBe('<link rel=\"stylesheet\" href=\"/base.css\">')\n  expect(css[1]).toContain('a/style.css')\n  expect(css[2]).toContain('b/style.css')\n\n  // inline css\n  const style = await renderStyles(files, { is_prod: true, design: { inline_css: true }})\n  expect(style[0]).toBe('<style>body{}</style>')\n})\n\ntest('renderMeta', async () => {\n  const meta = await renderMeta({\n    author: 'tipiirai',\n    version: '1.0',\n    desc: 'hello',\n    og: 'foo.png',\n    dir: 'blog',\n\n  }, ['/blog/foo.js'])\n\n  expect(meta.length).toBeGreaterThan(7)\n  expect(meta.includes('<meta charset=\"utf-8\">')).toBeTrue()\n  expect(meta.includes('<meta name=\"author\" content=\"tipiirai\">')).toBeTrue()\n  expect(meta.includes('<meta name=\"libs\" content=\"/blog/foo.js\">')).toBeTrue()\n\n})\n\ntest('title_template', async () => {\n  const meta = await renderMeta({\n    title_template: '%s World',\n    'date.updated': false,\n    generator: false,\n    title: 'Hello'\n  })\n  expect(meta.includes('<meta name=\"og:title\" content=\"Hello World\">')).toBeTrue()\n  expect(meta.length).toBe(3)\n})\n\ntest('renderHead', async () => {\n  const conf = {\n    import_map: { d3: 'd3.js' }\n  }\n\n  const data = {\n    title: 'Hello',\n    version: '1.0',\n  }\n\n  const assets = [\n    { ext: '.css', path: 'foo.css' },\n    { ext: '.js', dir: 'blog', name: 'bar' },\n  ]\n\n  const head = await renderHead({ conf, data, assets })\n\n  expect(head.length).toBeGreaterThan(7)\n\n  const map = head.find(el => el.includes('importmap'))\n  expect(map).toStartWith('<script type=\"importmap\">')\n  expect(map).toInclude('{\"imports\":{\"d3\":\"d3.js\"}}')\n})\n\n\n"
  },
  {
    "path": "packages/nuekit/test/render/md.test.js",
    "content": "\nimport { createAsset } from '../../src/asset'\n\n\ntest('MD: page assets', async () => {\n\n  const files = [\n    { is_html: true, path: 'header.html', async text() { return '<!html lib><header class=\"foo\"/> <navi/>' }},\n    { is_html: true, path: 'footer.html', async text() { return '<!html lib><footer/>' }},\n    { is_html: true, path: 'reactive.html', async text() { return '<!dhtml lib> <foo/>' }},\n  ]\n\n  const page = createAsset({\n    async text() { return '# Hello' },\n    path: 'page.md',\n    is_md: true,\n  }, { files })\n\n  // document meta\n  const doc = await page.parse()\n  expect(doc.meta.title).toBe('Hello')\n\n  // layout components\n  const comps = await page.components()\n  expect(comps.length).toBe(3)\n  expect(comps[0].tag).toBe('header')\n\n  // render HTML\n  const html = await page.render()\n  expect(html).toInclude('<header class=\"foo\">')\n  expect(html).toInclude('script src=\"/@nue/mount.js\"')\n\n})\n\ntest('MD: custom tags with <slot/>', async () => {\n  const files = [\n    { is_html: true, path: 'c.html', async text() {\n      return '<!html lib><custom class=\"{ class } { nothing }\"><slot/></custom>'\n    }}\n  ]\n  const page = createAsset({\n    async text() { return ['# Hello', '[custom.blue]', '  World'].join('\\n') },\n    is_md: true,\n  }, { files })\n\n  const html = await page.render()\n  expect(html).toInclude('<div class=\"blue\">World</div>')\n})\n\n\ntest('Built-in functions & variables', async () => {\n  const file = { is_html: true, path: 'c.html', async text() {\n    return `\n      <!html lib>\n      <header>\n        <pretty-title/>\n        <p>{ headings.length }</p>\n        <p>{ url }</p>\n      </header>\n\n      <pretty-title>\n        {{ markdown(\"*hey*\") }}\n      </pretty-title>\n    `\n  }}\n\n  const page = createAsset({\n    async text() { return '# Hey' },\n    is_md: true,\n    url: '/'\n  }, { files: [file] })\n\n  const html = await page.render()\n  expect(html).toInclude('<header><div><em>hey</em></div> <p>1</p> <p>/</p></header>')\n})\n\n"
  },
  {
    "path": "packages/nuekit/test/render/page.test.js",
    "content": "\nimport {\n  renderMD,\n  renderPage,\n  renderSlots,\n  renderDHTML,\n  renderHTML,\n\n} from '../../src/render/page'\n\n\ntest('renderSlots', () => {\n  const comps = [{ tag: 'header' }]\n  const content = '<h1>Hey</h1>'\n  let html = renderSlots({ content, comps })\n  expect(html).toInclude('<header></header>')\n  expect(html).toInclude(content)\n  expect(html).toInclude('<main>')\n\n  // scoping & slot disable\n  html = renderSlots({ content, comps, data: { scope: 'main', header: false } })\n  expect(html).not.toInclude('<main>')\n  expect(html).not.toInclude('<header>')\n  expect(html).toInclude(content)\n\n})\n\n\ntest('renderPage', async () => {\n  const content = '<h1>Hey</h1>'\n\n  const asset = {\n    url: '/blog/',\n    async assets() { return [] },\n    async parse() { return {} },\n  }\n\n  const html = await renderPage({ conf: {}, asset, content })\n  expect(html).toInclude('/@nue/hmr.js')\n})\n\n\ntest('renderMD', async () => {\n\n  const asset = {\n    async config() { return {} },\n\n    async data() {\n      return { language: 'fi', scope: 'body' }\n    },\n    async parse() {\n      return {\n        render() { return '<h1>Hello</h1>' }\n      }\n    },\n    async assets() {\n      return [\n        { async parse() { return { doctype: 'dhtml lib' } }, path: 'comps.html', is_html: true }\n      ]\n    },\n    async components() {\n      return []\n    },\n  }\n\n  const html = await renderMD(asset)\n  expect(html).toInclude('<html lang=\"fi\">')\n  expect(html).toInclude('<meta name=\"libs\" content=\"comps.html\">')\n  expect(html).toInclude('script src=\"/@nue/mount.js\"')\n  expect(html).not.toInclude('<body>')\n  expect(html).toInclude('<h1>Hello</h1>')\n})\n\n\ntest('renderHTML', async () => {\n  const asset = {\n    async data() {return {} },\n    async config() { return {} },\n    async assets() { return [] },\n\n    async parse() {\n      const root = { tag: 'main', children: [{ text: 'Hello' }, { tag: 'world', is_custom: true }]}\n      return { root, lib: [root] }\n    },\n    async components() {\n      return [\n        { tag: 'world', children: [{ text: 'World' }]}\n      ]\n    },\n  }\n\n\n  const html = await renderHTML(asset)\n  expect(html).toInclude('<main>Hello<div>World</div></main>')\n})\n\ntest('standalone HTML', async () => {\n  const asset = {\n    async parse() {\n      return { doctype: 'html', root: { tag: 'html' } }\n    },\n\n    async text() {\n      return '<html/>'\n    }\n  }\n\n  const html = await renderHTML(asset)\n  expect(html).toBe('<html/>')\n})\n\n\ntest('renderDHTML', async () => {\n  const asset = {\n    async data() { return {} },\n    async assets() { return [] },\n    async config() { return {} },\n    async components() { return [] },\n\n    async parse() {\n      const root = { tag: 'main', is: 'app' }\n      return { root, lib: [root] }\n    },\n  }\n\n  const { html, js } = await renderDHTML(asset)\n  expect(html).toInclude('<main nue=\"app\"></main>')\n  expect(js).toInclude('export const lib = [')\n})\n\n"
  },
  {
    "path": "packages/nuekit/test/render/svg.test.js",
    "content": "\nimport { renderFonts, renderHMR, renderSVG, convertHTMLTag } from '../../src/render/svg'\n\nconst testfile = import.meta.filename\n\ntest('renderFont / inlined', async () => {\n  const [ css ] = await renderFonts({ Test: testfile })\n  expect(css).toInclude('data:font/woff2;base64')\n  expect(css.length).toBeGreaterThan(200)\n  expect(css).toInclude('@font-face')\n})\n\ntest('renderFonts / external', async () => {\n  const [ css ] = await renderFonts({ Test: 'test.woff2' }, true)\n  expect(css).toBe(`@font-face { font-family: 'Test'; src: url('/test.woff2')}`)\n})\n\ntest('renderFonts: bad path', async () => {\n  const ret = await renderFonts({ Test: 'foo' })\n  expect(ret).toEqual([])\n})\n\ntest('renderHMR', () => {\n  const html = renderHMR({\n    body: '<svg>',\n    base: 'foo.svg',\n    fonts: ['@font-face {}'],\n    styles: [{ url: '/foo.css' }]\n  })\n\n  expect(html).toInclude('@font-face')\n  expect(html).toInclude('href=\"/foo.css\"')\n  expect(html).toInclude('/@nue/hmr.js')\n  expect(html).toInclude('<body><svg></body>')\n})\n\ntest('renderSVG', async () => {\n\n  const asset = {\n    async data() { return {} },\n    async components() { return [] },\n    async config() { return {} },\n\n    base: 'test.svg',\n\n    async parse() {\n      const attr = [{ name: 'width', val: 100 }, { name: 'height', val: 100 }]\n      return {\n        root: { tag: 'svg', attr, meta: { css: '[ table ]' } },\n      }\n    },\n    async assets() {\n      async function text() { return `:root { --brand: #ccc }` }\n      return [ { is_css: true, path: 'table.css', url: '/table.css', text } ]\n    },\n  }\n\n\n  // SVG\n  const svg = await renderSVG(asset, { fonts: { Test: testfile } })\n  expect(svg).toInclude(\"url('data:font/woff2;base64\")\n  expect(svg).toInclude(':root{--brand:#ccc}')\n  expect(svg).toInclude('</style></svg>')\n\n  // HTML (HMR)\n  const html = await renderSVG(asset, { hmr: true, fonts: { Test: 'test.woff' } })\n  expect(html).toInclude('<!doctype html>')\n  expect(html).toInclude('@font-face')\n  expect(html).toInclude('<link rel=\"stylesheet\" href=\"/table.css\">')\n  expect(html).toInclude('xmlns=\"http://www.w3.org/2000/svg')\n  expect(html).toInclude('0 0 100 100')\n\n})\n\n\ntest('custom <html> tag', () => {\n  const { tag, children, attr } = convertHTMLTag({ tag: 'html', children: [{ tag: 'table' }] })\n  expect(tag).toBe('foreignObject')\n  expect(attr.length).toBe(4)\n  expect(children[0].attr[0].name).toEqual('xmlns')\n\n})\n"
  },
  {
    "path": "packages/nuekit/test/server/model.test.js",
    "content": "\nimport { testDir, writeAll, removeAll } from '../test-utils'\nimport { createEnv } from '../../src/server/model'\n\n\nafterAll(async () => await removeAll())\n\n\ntest('createEnv', async () => {\n  await writeAll([\n    ['users.json', '[{ \"name\": \"John\" }]'],\n    ['leads.json', '[{ \"name\": \"Jane\" }]'],\n  ])\n\n  const { users, leads } = await createEnv(testDir)\n\n  const [ john ] = await users.getAll()\n  expect(john).toMatchObject({ id: 1, name: 'John' })\n\n  expect((await leads.get(1))).toMatchObject({ id: 1, name: 'Jane' })\n\n})\n\n\ntest('auth flow', async () => {\n  await writeAll([\n    ['users.json', '[{ \"email\": \"hey@cc.com\", \"password\": \"test\" }]']\n  ])\n\n  const { users } = await createEnv(testDir)\n\n  const { sessionId } = await users.login('hey@cc.com', 'test')\n  expect(sessionId.length).toBe(36)\n\n  expect(await users.authenticate(sessionId)).toBeTrue()\n\n  await users.logout(sessionId)\n  expect(await users.authenticate(sessionId)).toBeFalse()\n\n})\n\n"
  },
  {
    "path": "packages/nuekit/test/server/proxy.test.js",
    "content": "\nimport { createProxy } from '../../src/server/proxy.js'\n\ntest('proxy forwards matching requests', async () => {\n  global.fetch = async (url) => {\n    expect(url).toBe('http://backend.com/api/users')\n    return new Response('ok')\n  }\n\n  const proxy = createProxy({\n    routes: ['/api'],\n    url: 'http://backend.com'\n  })\n\n  const request = new Request('http://localhost/api/users')\n  const response = await proxy(request)\n\n  expect(response.status).toBe(200)\n})\n\ntest('proxy ignores non-matching requests', async () => {\n  const proxy = createProxy({\n    routes: ['/api'],\n    url: 'http://backend.com'\n  })\n\n  const request = new Request('http://localhost/static/file.css')\n  const response = await proxy(request)\n\n  expect(response).toBe(null)\n})\n\ntest('proxy preserves method and query', async () => {\n  global.fetch = async (url, opts) => {\n    expect(url).toBe('http://backend.com/api/search?q=test')\n    expect(opts.method).toBe('POST')\n    return new Response('ok')\n  }\n\n  const proxy = createProxy({\n    routes: ['/api'],\n    url: 'http://backend.com'\n  })\n\n  const request = new Request('http://localhost/api/search?q=test', {\n    method: 'POST',\n    body: 'data'\n  })\n\n  await proxy(request)\n})"
  },
  {
    "path": "packages/nuekit/test/server/worker.test.js",
    "content": "\nimport { importWorker, createWorker } from '../../src/server/worker'\nimport { testDir, write, removeAll } from '../test-utils'\nimport { routes, fetch, matches } from 'nue-edgeserver'\n\n// create server\nbeforeAll(async () => {\n  await write('index.ts', `\n    get('/api/users', (c) => {\n      return c.json(['jane'])\n    })\n  `)\n})\n\nafterAll(async () => await removeAll())\n\n\ntest('importWorker', async () => {\n\n  await importWorker({ dir: testDir })\n\n  // routes\n  expect(routes.length).toBe(1)\n\n  // match function\n  expect(matches('GET', '/api/users')).toBeTrue()\n\n  // GET\n  let resp = await fetch(new Request('http://localhost/api/users'))\n  expect(await resp.json()).toEqual(['jane'])\n\n  // 404\n  resp = await fetch(new Request('http://localhost/static/file.css'))\n  expect(resp.status).toBe(404)\n})\n\n\ntest('createWorker', async () => {\n\n  await createWorker({ dir: testDir, reload: true })\n\n  // GET\n  let resp = await fetch(new Request('http://localhost/api/users'))\n  expect(await resp.json()).toEqual(['jane'])\n})\n"
  },
  {
    "path": "packages/nuekit/test/server.test.js",
    "content": "\nimport { testDir, writeAll, removeAll } from './test-utils'\nimport { broadcast, createServer } from '../src/tools/server'\n\nawait writeAll([\n  ['index.html', '<h1>Home</h1>'],\n  ['style.css',  'body { color: red; }'],\n])\n\nconst server = createServer({\n  port: 0,\n  dist: testDir\n\n}, async (pathname) => {\n  if (pathname == '/html') return { content: '<p>Hello</p>' }\n  if (pathname == '/css') return { content: 'body {}', type: 'text/css' }\n  if (pathname == '/custom-404') return { content: 'Not found', status: 404 }\n  if (pathname == '/') return Bun.file(`${testDir}/index.html`)\n  if (pathname == '/not-found') return null\n  return Bun.file(`${testDir}${pathname}`)\n})\n\nafterAll(async () => {\n  await removeAll()\n  server.stop()\n})\n\ntest('static file', async () => {\n  const res = await server.fetch(new Request('http://localhost/style.css'))\n  expect(res.status).toBe(200)\n  expect(await res.text()).toBe('body { color: red; }')\n})\n\n\ntest('/', async () => {\n  const res = await server.fetch(new Request('http://localhost/'))\n  expect(res.status).toBe(200)\n  expect(await res.text()).toBe('<h1>Home</h1>')\n})\n\ntest('html response', async () => {\n  const res = await server.fetch(new Request('http://localhost/html'))\n  expect(await res.text()).toBe('<p>Hello</p>')\n  expect(res.headers.get('content-type')).toInclude('text/html')\n})\n\ntest('custom mime type', async () => {\n  const res = await server.fetch(new Request('http://localhost/css'))\n  expect(await res.text()).toBe('body {}')\n  expect(res.headers.get('content-type')).toBe('text/css')\n})\n\ntest('custom 404 response', async () => {\n  const res = await server.fetch(new Request('http://localhost/custom-404'))\n  expect(res.status).toBe(404)\n  expect(await res.text()).toBe('Not found')\n})\n\ntest('not found', async () => {\n  const res = await server.fetch(new Request('http://localhost/not-found'))\n  expect(res.status).toBe(404)\n  expect(await res.text()).toBe('404 Not Found')\n})\n\ntest('custom handler', async () => {\n  const server = createServer({\n    port: 0,\n    dist: testDir,\n    handler: (req) => {\n      if (new URL(req.url).pathname == '/api/test') {\n        return new Response('handler response')\n      }\n    }\n  }, async ({ pathname }) => Bun.file(`${testDir}${pathname}`))\n\n  const res = await server.fetch(new Request('http://localhost/api/test'))\n  expect(res.status).toBe(200)\n  expect(await res.text()).toBe('handler response')\n\n  server.stop()\n})\n\ntest('HMR WebSocket connection', async () => {\n  const ws = new WebSocket(`ws://localhost:${server.port}`)\n\n  await new Promise((resolve) => { ws.onopen = resolve })\n\n  expect(ws.readyState).toBe(WebSocket.OPEN)\n  ws.close()\n})\n\n\ntest('broadcast', async () => {\n  const ws = new WebSocket(`ws://localhost:${server.port}`)\n\n  await new Promise((resolve) => {\n    ws.onopen = resolve\n  })\n\n  const messagePromise = new Promise((resolve) => {\n    ws.onmessage = (event) => {\n      resolve(JSON.parse(event.data))\n    }\n  })\n\n  broadcast({ type: 'reload' })\n\n  const message = await messagePromise\n  expect(message).toEqual({ type: 'reload' })\n\n  ws.close()\n})"
  },
  {
    "path": "packages/nuekit/test/site.test.js",
    "content": "\nimport { sortAssets, mergeSharedData } from '../src/site'\n\ntest('sortAssets', () => {\n  const sorted = sortAssets([\n    'blog/post.md',\n    'index.html',\n    '@shared/design/area.css',\n    'docs/getting-started.md',\n    'global.css',\n    '@shared/design/base.css',\n    'about.md'\n  ])\n\n  expect(sorted).toEqual([\n    \"@shared/design/area.css\",\n    \"@shared/design/base.css\",\n    \"blog/post.md\",\n    \"docs/getting-started.md\",\n    \"about.md\",\n    \"global.css\",\n    \"index.html\",\n  ])\n})\n\ntest('sort assets', () => {\n  const assets = [{ path: 'b' }, { path: 'a' }]\n  const sorted = sortAssets(assets)\n\n  // both should be sorted\n  expect(assets[0].path).toBe('a')\n  expect(sorted[0].path).toBe('a')\n})\n\n\ntest('mergeSharedData', async () => {\n  const dir = '@shared/data'\n\n  const assets = [\n    { dir, is_yaml: true, parse() { return { port: 100 } }},\n    { dir, is_js: true, parse() { return { default: function(data) { data.port = 200 }} }},\n  ]\n\n  const data = await mergeSharedData(assets)\n  expect(data).toEqual({ port: 200 })\n\n})\n"
  },
  {
    "path": "packages/nuekit/test/system.test.js",
    "content": "\nimport { version, getSystemFiles, createSystemFiles } from '../src/system'\nimport { testDir, removeAll } from './test-utils'\n\ntest('version', () => {\n  expect(version).not.toBeNull()\n})\n\ntest('get system files', async () => {\n\n  // dev files\n  expect(getSystemFiles().length).toBe(6)\n\n  // production files\n  const files = getSystemFiles(true)\n  expect(files.length).toBe(4)\n\n  // render\n  const js = await files[0].render()\n  expect(js.length).toBeGreaterThan(1000)\n})\n\ntest('get system files', async () => {\n  const files = await createSystemFiles(testDir, true)\n  expect(files.length).toBe(4)\n  expect(await createSystemFiles(testDir)).toBeFalse()\n  await removeAll()\n})"
  },
  {
    "path": "packages/nuekit/test/test-utils.js",
    "content": "\nimport { writeFile, mkdir, rmdir } from 'node:fs/promises'\nimport { join } from 'node:path'\n\nimport { fswalk } from '../src/tools/fswalk'\nimport { createFile } from '../src/file'\n\nexport const testDir = './test_dir'\n\nexport async function writeAll(items) {\n  const paths = []\n\n  for (const el of items) {\n    if (Array.isArray(el)) {\n      await write(el[0], el[1])\n      paths.push(el[0])\n\n    } else {\n      await write(el, '')\n      paths.push(el)\n    }\n  }\n  return paths\n}\n\nexport async function write(path, content) {\n  await mkdir(testDir, { recursive: true })\n  if (typeof content == 'object') content = toYAML(content)\n\n  // create subdir\n  const i = path.lastIndexOf('/')\n  if (i > 0) await await mkdir(join(testDir, path.slice(0, i)), { recursive: true })\n\n  // write file\n  await writeFile(join(testDir, path), content)\n  return path\n}\n\nfunction toYAML(data) {\n  const yaml = []\n  Object.entries(data).forEach(([key, val]) => {\n    yaml.push(`${key}: ${val}`)\n  })\n  return yaml.join('\\n')\n}\n\nexport async function removeAll() {\n  try {\n    await rmdir(testDir, { recursive: true, force: true })\n  } catch (error) {}\n}\n\n// for debugging / testing\nexport async function fileset(dir) {\n  const paths = await fswalk(dir)\n  const files = await Promise.all(paths.map(path => createFile(dir, path)))\n\n  files.read = async function(path) {\n    const file = files.find(el => el.path == path)\n    return await file?.text()\n  }\n  return files\n}\n\n"
  },
  {
    "path": "packages/nuemark/.npmignore",
    "content": "test/\n"
  },
  {
    "path": "packages/nuemark/Makefile",
    "content": "tests:\n\tcd test && bun test # block"
  },
  {
    "path": "packages/nuemark/README.md",
    "content": "\n# Nuemark: Content first web development\nNuemark is a Markdown-based authoring format for rich, interactive content. It places content at the heart of web development, delivering a true content-first approach where writing comes before coding, and structure drives presentation.\n\n<a href=\"https://nuejs.org/\">\n  <img src=\"https://nuejs.org/img/nuemark-content-big.png\" width=\"650\" height=\"1321\">\n</a>\n\n\n## The problem with content\nModern tooling poses challenges to content development:\n\n**JavaScript frameworks trap content in code**. A blog post becomes a React component. A landing page requires TypeScript knowledge. Marketing teams wait for developers to update copy. Content lives inside JavaScript bundles, invisible to search engines without complex hydration strategies.\n\n**Plain Markdown is too limited**. Originally designed for converting text to HTML emails, standard Markdown lacks the structures modern websites need. No layouts. No responsive images. No interactive elements. You quickly hit the ceiling and resort to raw HTML.\n\n**MDX mixes concerns**. It promises rich content but delivers mixed JavaScript. Import statements, JSX components, and business logic tangled with prose. Non-technical team members can't safely edit content without breaking the build.\n\n\n## Why Nuemark\nNuemark extends Markdown with purpose-built syntax for modern web development while keeping content pure and accessible to everyone.\n\n**Rich document structures** - Automatic sections, grids, stacks, and columns through clean, indentation-based syntax. No div/span soup, nor CSS classes mixed in your content.\n\n**Built-in components** - Responsive images, videos, tables, and expandable content work out of the box.\n\n**Full Markdown compatibility** - Everything from standard Markdown works, plus commonly needed extensions like tables, footnotes, and syntax highlighting.\n\n**Extensible architecture** - Developers create custom tags that content authors use naturally. Define once, use everywhere. Components receive arguments, data, and nested content without exposing implementation details.\n\n**Structured data access** - Parse and query document structure, headings, and front matter metadata programmatically.\n\n\nSee the [Nuemark syntax reference](https://nuejs.org/docs/nuemark-syntax) for complete documentation of all features and extensions.\n\n"
  },
  {
    "path": "packages/nuemark/index.js",
    "content": "\nimport { parseDocument } from './src/parse-document.js'\nimport { renderLines } from './src/render-blocks.js'\n\nconst EOL = /\\r\\n|\\r|\\n/\n\nexport function nuemark(content, opts) {\n  return renderLines(content.split(EOL), opts)\n}\n\nexport function parseNuemark(content) {\n  return parseDocument(content.split(EOL))\n}\n\n/* utilities */\nexport { elem } from './src/render-blocks.js'\nexport { renderInline } from './src/render-inline.js'\nexport { parseSize, renderIcon } from './src/render-tag.js'\n"
  },
  {
    "path": "packages/nuemark/package.json",
    "content": "{\n  \"name\": \"nuemark\",\n  \"version\": \"0.7.0\",\n  \"description\": \"Markdown flavour for rich, interactive websites\",\n  \"homepage\": \"https://nuejs.org\",\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"main\": \"index.js\",\n  \"repository\": {\n    \"url\": \"https://github.com/nuejs/nue\",\n    \"directory\": \"packages/nuemark\",\n    \"type\": \"git\"\n  },\n  \"scripts\": {\n    \"test\": \"node --experimental-vm-modules ../../node_modules/jest/bin/jest.js --runInBand\"\n  },\n  \"dependencies\": {\n    \"nueyaml\": \"^0.1.0\",\n    \"nue-glow\": \"^0.2.0\"\n  },\n  \"files\": [ \"src\" ],\n  \"jest\": {\n    \"setupFilesAfterEnv\": [\n      \"<rootDir>/../../setup-jest.js\"\n    ],\n    \"collectCoverageFrom\": [\n      \"<rootDir>/src/**\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/nuemark/src/parse-blocks.js",
    "content": "\nimport { parseYAML } from 'nueyaml'\n\nimport { parseInline, parseLinkTitle } from './parse-inline.js'\nimport { parseTag } from './parse-tag.js'\n\n\nexport function parseBlocks(lines, capture) {\n  const blocks = []\n  let spaces, block\n\n  // capture things while recursing blocks\n  capture = capture || {\n    footnotes: [],\n    reflinks: {},\n    noterefs: []\n  }\n\n  lines.forEach(line => {\n    const c = line[0]\n    const trimmed = line.trim()\n    const indent = trimmed && line.length - line.trimStart().length\n    if (!spaces) spaces = indent\n\n\n    // fenced code\n    if (c == '`' && line.startsWith('```')) {\n      // new code block\n      if (!block?.is_code) {\n        const specs = line.slice(line.lastIndexOf('`') + 1).trim()\n        block = { is_code: true, ...parseTag(specs), code: [] }\n        return blocks.push(block)\n\n        // end of code\n      } else {\n        return block = null\n      }\n    }\n\n    // fenced code lines first\n    if (block?.is_code) return block.code.push(line)\n\n\n    // skip HTML and line comments\n    if (c == '<' || trimmed.startsWith('//')) return\n\n    // empty line\n    if (!trimmed) {\n      if (!block) return\n      if (block.is_tag) return block.body.push(line)\n      if (block.is_list) return addListEntry(block, line)\n      if (block.is_content) return block = null\n    }\n\n\n    // heading (must be before the last two if clauses)\n    if (c == '#') {\n      blocks.push(parseHeading(line))\n      return block = null\n    }\n\n\n    // thematic break (before list)\n    const hr = getBreak(line)\n    if (hr) {\n      blocks.push(hr)\n      return block = null\n    }\n\n\n    // list item\n    const item = getListItem(line)\n\n    if (item) {\n      const { line, numbered } = item\n\n      // new list\n      if (!block?.is_list) {\n        block = { is_list: true, numbered, entries: [[line]] }\n        return blocks.push(block)\n\n        // new list item\n      } else {\n        return block.entries.push([line])\n      }\n    }\n\n    // blockquote\n    if (c == '>') {\n      line = line.slice(2)\n\n      // new quote\n      if (!block?.is_quote) {\n        block = { is_quote: true, content: [line] }\n        return blocks.push(block)\n      } else {\n        return block.content.push(line)\n      }\n    }\n\n    // reflink or footnote (can be nested on any level)\n    const ref = parseRef(trimmed)\n    if (ref) {\n      const { key, value } = ref\n      if (key[0] == '^') {\n        capture.footnotes.push(ref)\n        capture.noterefs.push(key)\n      } else {\n        capture.reflinks[key] = parseLinkTitle(value)\n      }\n      return\n    }\n\n\n    // tag\n    if (c == '[' && trimmed.endsWith(']') && !trimmed.includes('][')) {\n      const tag = parseTag(trimmed.slice(1, -1))\n      block = { is_tag: true, ...tag, body: [] }\n      return blocks.push(block)\n    }\n\n    // table\n    if (c == '|' && trimmed.endsWith('|')) {\n\n      // new table\n      if (!block?.is_table) {\n        block = { is_table: true, head: false, rows: [] }\n        blocks.push(block)\n      }\n      const row = parseTableRow(line)\n\n      return block.rows.length == 1 && row[0].includes('---') ?\n        block.head = true : block.rows.push(row)\n    }\n\n\n    // nested content or data\n    if (indent) {\n      line = line.slice(spaces)\n\n      if (block?.is_tag) block.body.push(line)\n      else if (block?.is_list) addListEntry(block, line)\n\n      // blockquotes\n    } else if (block?.is_quote && c) {\n      if (c) block.content.push(line)\n\n      // content (append)\n    } else if (block?.is_content) {\n      block.content.push(line)\n\n      // new content block\n    } else {\n      block = c ? { is_content: true, content: [line] } : { is_newline: true }\n      blocks.push(block)\n    }\n\n  })\n\n  /* tokenize lists and quotes. parse component data */\n  blocks.forEach(block => processNestedBlocks(block, capture))\n\n  return { blocks, ...capture }\n}\n\n// recursive processing of nested blocks\nfunction processNestedBlocks(block, capture) {\n  const { name } = block\n\n\n  if (block.is_list) {\n    block.items = block.entries.map(lines => {\n      const { blocks } = parseBlocks(lines, capture)\n      return blocks\n    })\n\n  } else if (block.is_quote) {\n    const { blocks } = parseBlocks(block.content, capture)\n    block.blocks = blocks\n\n\n  } else if (block.is_tag) {\n    const body = block.body.join('\\n')\n\n    try {\n      if (body && name && isYAML(body.trim())) {\n        Object.assign(block.data, parseYAML(body))\n        block.has_data = true\n      }\n    } catch (e) {\n      console.error('YAML parse error', body, e)\n    }\n\n    if (name != 'table') {\n      const { blocks } = parseBlocks(block.body, capture)\n      if (!block.has_data) block.blocks = blocks\n      delete block.body\n\n      if (name == 'define') {\n        capture.noterefs.push(...getFootnoteIds(blocks))\n      }\n    }\n  }\n}\n\n\n/******* UTILS ********/\n\n\nexport function parseHeading(str) {\n  const level = str.search(/[^#]/)\n  const tokens = parseInline(str.slice(level).trim())\n  const text = tokens.map(el => el.text || el.body || '').join('').trim()\n  const specs = tokens.find(el => el.is_attr)\n  const attr = specs?.attr || {}\n  return { is_heading: true, level, tokens, text, attr }\n}\n\nfunction getFootnoteIds(blocks) {\n  return blocks.filter(el => el.is_heading && el.attr.id)\n    .map(el => `^${el.attr.id}`)\n}\n\nfunction parseRef(str) {\n  if (str[0] == '[') {\n    const i = str.indexOf(']:')\n    if (i > 1) {\n      const key = str.slice(1, i)\n      return { key, value: str.slice(i + 2).trim() }\n    }\n  }\n}\n\nfunction getListItem(line) {\n  if (line[1] == ' ' && '-*'.includes(line[0])) return { line: line.slice(1).trim() }\n  const num = /^\\d+\\. /.exec(line)\n  if (num) return { line: line.slice(num[0].length).trim(), numbered: true }\n}\n\nexport function getBreak(str) {\n  const HR = ['---', '***', '___', '- - -', '* * *']\n\n  for (const hr of HR) {\n    if (str.startsWith(hr) && !/[^\\*\\-\\_ ]/.test(str)) {\n      return { is_break: true, is_separator: hr == '---' }\n    }\n  }\n}\n\nfunction isYAML(str) {\n  if (str.trimLeft()[0] == '-') return true\n  const i = str.indexOf(':')\n  return i > 0 && /^\\w+$/.test(str.slice(0, i))\n}\n\nfunction parseTableRow(line) {\n  return line.slice(1, -2).split('|').map(el => el.trim())\n}\n\nfunction addListEntry({ entries }, line) {\n  const last = entries[entries.length - 1]\n  last.push(line)\n}\n\n\n"
  },
  {
    "path": "packages/nuemark/src/parse-document.js",
    "content": "\nimport { parseYAML } from 'nueyaml'\n\nimport { parseBlocks } from './parse-blocks.js'\nimport { parseLinkTitle } from './parse-inline.js'\nimport { createHeadingId, elem, renderBlocks } from './render-blocks.js'\nimport { renderInline } from './render-inline.js'\n\n\nexport function parseDocument(lines) {\n  const meta = stripMeta(lines)\n  const things = parseBlocks(lines)\n  const { blocks } = things\n\n  // title\n  if (!meta.title) {\n    const tag = blocks.find(el => el.is_tag)\n    meta.title = getTitle(blocks) || tag && getTitle(tag.blocks) || ''\n  }\n\n  // description\n  if (!meta.description) {\n    const block = blocks.find(el => el.is_content)\n    meta.description = block?.content[0]\n  }\n\n  const sections = sectionize(blocks) || [blocks]\n\n  function renderSections(opts) {\n    const classList = Array.isArray(opts.sections) ? opts.sections : []\n    const wrap = opts.content_wrapper\n    const html = []\n\n    sections.forEach((section, i) => {\n      const content = renderBlocks(section, opts)\n      html.push(elem('section', { class: classList[i] }, wrap ? elem('div', { class: wrap }, content) : content))\n    })\n    return html.join('\\n\\n')\n  }\n\n  return {\n    blocks,\n\n    // OPTS: { data, sections, content_wrapper, heading_ids, links, tags }\n    render(opts = {}) {\n      Object.assign(things.reflinks, parseReflinks(opts.links))\n      opts = { ...opts, ...things }\n      const html = opts.sections ? renderSections(opts) : renderBlocks(blocks, opts)\n      return html + renderFootnotes(things.footnotes)\n    },\n\n    get headings() {\n      return blocks.filter(b => !!b.level).map(h => {\n        const id = h.attr.id || createHeadingId(h.text)\n        return { id, ...h }\n      })\n    },\n\n    codeblocks: blocks.filter(el => el.is_code),\n    meta,\n  }\n}\n\nexport function sectionize(blocks = []) {\n  const arr = []\n  let section\n\n  // first (sub)heading\n  const hr = blocks.find(el => el.is_separator)\n  const level = blocks.find(el => el.level <= 3)?.level\n\n  // no heading nor separator -> no sections\n  if (!level && !hr) return\n\n  blocks.forEach((el, i) => {\n    const cut = hr ? el.is_separator : level == 3 ? el.level == 3 : el.level <= 2\n\n    // add new section\n    if (!section || cut) arr.push(section = [])\n\n    // add content to section\n    if (!el.is_separator) section?.push(el)\n  })\n\n  return arr[0] && arr\n}\n\n\nfunction renderFootnotes(arr) {\n  if (!arr.length) return ''\n  const html = arr.map(el => elem('li', elem('a', { name: el.key }) + renderInline(el.value)))\n  return elem('ol', { role: 'doc-endnotes' }, html.join('\\n'))\n}\n\n\nfunction getTitle(blocks) {\n  const h1 = blocks?.find(el => el.level == 1)\n  return h1?.text || ''\n}\n\n// extracts meta from the head, lines array gets spliced/mutated\nexport function stripMeta(lines) {\n  let start = 0, end = -1\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i]\n    const is_front = line == '---'\n    if (!start) {\n      if (is_front) start = i + 1\n      else if (line.trim()) return {}\n    }\n    else if (is_front) { end = i; break }\n  }\n\n  if (!start) return {}\n\n  const front = lines.slice(start, end).join('\\n')\n  lines.splice(0, end + 1)\n  return parseYAML(front)\n}\n\nfunction parseReflinks(links = {}) {\n  for (const key in links) {\n    const href = links[key]\n    if (typeof href == 'string') links[key] = parseLinkTitle(href)\n  }\n  return links\n}\n\n\n\n"
  },
  {
    "path": "packages/nuemark/src/parse-inline.js",
    "content": "\n/* Inline tokenizer */\nimport { parseAttr, parseTag } from './parse-tag.js'\n\n\nexport const FORMATTING = {\n  '***': 'EM',\n  '___': 'EM',\n  '**':  'strong',\n  '__':  'strong',\n\n  // after strongs\n  '*':   'em',\n  '_':   'em',\n  '\"':   'q',\n  '`':   'code',\n  '~':   's',\n  '|':   'mark',\n  '•':   'b',\n}\n\n// chars to escape\nexport const ESCAPED = { '<': '&lt;', '>': '&gt;' }\n\n// tested: regexp is faster than custom lookup function\nconst SIGNIFICANT = /[\\*_\\[\"`~\\\\|•{\\\\<>]|!\\[/\n\n\n// c == first character (for quick tests)\nconst PARSERS = [\n\n  // \\{ escaped }\n  (str, c) => {\n    if (c == '\\\\') return { text: str.slice(1, 2), end: 2 }\n  },\n\n  // &lt; and &gt;\n  (str, c) => {\n    const text = ESCAPED[c]\n    return text ? { text, end: 1 } : null\n  },\n\n\n  // bold, italics, etc..\n  (str, c) => {\n    for (const fmt in FORMATTING) {\n      if (str.startsWith(fmt)) {\n        const len = fmt.length\n        const i = str.indexOf(fmt, len)\n        const next = str[i + len]\n\n        // if next char is a word -> no formatting\n        if (i == -1 || next && /\\w/.test(str[i + len])) return { text: fmt }\n\n        // no spaces before/after the body\n        const body = str.slice(len, i)\n        if (!body || body.length != body.trim().length) return { text: fmt }\n\n        return { is_format: true, tag: FORMATTING[fmt], body, end: i + len }\n      }\n    }\n  },\n\n  // [link](/), [tag], or [^footnote]\n  (str, c) => {\n    if (c == '[') {\n      const i = str.indexOf(']', 1)\n\n      if (i == -1) return { text: c }\n\n      // links\n      if ('([]'.includes(str[i + 1])) {\n        const link = parseLink(str) || parseLink(str, true)\n        if (link) return link\n      }\n\n      // parse tag\n      const tag = parseTag(str.slice(1, i).trim())\n      const { name } = tag\n      const is_footnote = name[0] == '^'\n      const end = i + 1\n\n      // footnote?\n      if (is_footnote) {\n        const rel = name.slice(1)\n        return rel >= 0 || isValidName(rel) ? { is_footnote, href: '#' + name, end } : { text: c }\n      }\n\n      // normal tag\n      if (name == '!' || isValidName(name)) return { is_tag: true, ...tag, end }\n\n      return { text: c }\n    }\n  },\n\n  // ![image](/src.png)\n  (str, c) => {\n    if (c == '!' && str[1] == '[') {\n      const img = parseLink(str.slice(1))\n\n      if (img) {\n        img.end++\n        return img && { is_image: true, ...img }\n      } else {\n        return { text: c }\n      }\n    }\n  },\n\n  // { variables } / { #id.classNames }\n  (str, c) => {\n    if (c == '{') {\n      const i = str.indexOf('}', 2)\n      if (i == -1) return { text: c }\n      const name = str.slice(1, i).trim()\n\n      return '.#'.includes(name[0])\n        ? { is_attr: true, attr: parseAttr(name), end: i + 1 }\n        : { is_var: true, name, end: i + 1 }\n    }\n  },\n\n  // plain text\n  (text) => {\n    const i = text.search(SIGNIFICANT)\n    return i >= 0 ? { text: text.slice(0, i) } : { text }\n  }\n]\n\n\nfunction isValidName(name) {\n\n  // cannot be a number\n  if (name >= 0) return false\n\n  // cannot contain special characters\n  const i = name.search(/\\W/)\n  const char = name[i]\n  return i == -1 || i > 0 && char == '-'\n}\n\n\n\nexport function parseInline(str) {\n  const tokens = []\n\n  while (str) {\n    for (const fn of PARSERS) {\n      const item = fn(str, str[0])\n      if (item) {\n        tokens.push(item)\n        str = str.slice(item.end || item.text.length)\n        delete item.end; break\n      }\n    }\n  }\n\n  // merge text siblings into one\n  return tokens.filter(({ text }, i) => {\n    const next = tokens[i + 1]\n    if (text && next?.text) next.text = text + next.text\n    else return true\n  })\n}\n\n\n/*** link / image parsing ****/\n\n\nexport function parseLink(str, is_reflink) {\n  const [open, close] = is_reflink ? '[]' : '()'\n  let i = str.indexOf(']' + open, 1)\n\n  // not a link\n  // const next = str[i + 1]\n  if (i == -1) return\n\n  let j = i > 0 ? str.indexOf(close, i + 2) : 0\n\n  // not a link\n  if (j == -1) return\n\n  // label\n  let label = str.slice(1, i)\n\n  // image / reflink inside label\n  if (label.includes('![') || label.includes('][')) {\n    i = str.indexOf(']' + open, j)\n    label = str.slice(1, i)\n    j = str.indexOf(close, i + 2)\n    if (i == -1 || j == -1) return\n\n  } else {\n    // links with closing bracket (ie. Wikipedia)\n    if (str[j + 1] == ')') j++\n  }\n\n  // href & title\n  let { href, title } = parseLinkTitle(str.slice(i + 2, j))\n\n  // footnote reference\n  const is_footnote = href[0] == '^'\n  if (is_footnote) {\n    is_reflink = null\n    href = '#' + href\n  }\n\n  return {\n    href, title, label,\n    is_footnote,\n    is_reflink,\n    end: j + 1\n  }\n}\n\nexport function parseLinkTitle(href) {\n  const i = href.indexOf('\"')\n  if (i > 0) {\n    const j = href.indexOf('\"', i + 1)\n    if (j > 0) return {\n      href: href.slice(0, i).trim(),\n      title: href.slice(i + 1, -1)\n    }\n  }\n  return { href }\n}\n\n\n\n"
  },
  {
    "path": "packages/nuemark/src/parse-tag.js",
    "content": "\nconst ATTR = 'id is class style hidden disabled popovertarget popover'.split(' ')\n\n/*\n  --> { name, attr, data }\n*/\nexport function parseTag(input) {\n  const { str, getValue } = valueGetter(input)\n  const strings = str.split(/\\s+/)\n  const specs = strings.filter((s, i) => !i || s.match(/^[#|.]/)).join('')\n  const attribs = strings.filter(s => !specs.includes(s))\n  const self = { ...parseSpecs(specs), data: {} }\n  const classes = new Set((self.attr?.class || '').split(' '))\n\n  function set(key, val) {\n    if (key == 'class') return val.split(' ').forEach(v => classes.add(v))\n\n    const ctx = ATTR.includes(key) || key.startsWith('data-') ? 'attr' : 'data'\n    self[ctx][key] = val\n  }\n\n  for (const el of attribs) {\n    const [key, val] = el.split('=')\n\n    // data: key=\"value\"\n    if (val) set(key, parseValue((getValue(val) || val)))\n\n    // key only\n    else if (!/\\W/.test(key)) set(key, true)\n\n    // default key\n    else set('_', getValue(key) || key)\n  }\n\n  if (classes.size) self.attr.class = [...classes].join(' ')\n\n  return self\n}\n\n\n\nfunction parseValue(val) {\n  return val == \"false\" ? false : val == \"true\" ? true : val == \"0\" ? 0 : (1 * val || val)\n}\n\n/*\n  foo=\"bar\" baz=\"hey dude\" -->\n    { str: foo=:1: bar=:2: }\n    getValue(':1:') => 'bar'\n*/\nconst RE = { single_quote: /'([^']+)'/g, double_quote: /\"([^\"]+)\"/g }\n\nexport function valueGetter(input) {\n  const strings = []\n  const push = (_, el) => `:${strings.push(el)}:`\n  const str = input.replace(RE.single_quote, push).replace(RE.double_quote, push)\n\n  function getValue(key) {\n    if (key[0] == ':' && key.slice(-1) == ':') {\n      return strings[1 * key.slice(1, -1) - 1]\n    }\n  }\n\n  return { str, getValue }\n}\n\n// tabs.foo#bar.baz -> { name: 'tabs', class: ['foo', 'bar'], id: 'bar' }\nexport function parseSpecs(str) {\n  const self = { name: str, attr: {} }\n  const i = str.search(/[\\#\\.]/)\n\n  if (i >= 0) {\n    self.name = str.slice(0, i) || null\n    self.attr = parseAttr(str.slice(i))\n  }\n  return self\n}\n\nexport function parseAttr(str) {\n  const attr = {}\n\n  // classes\n  const classes = new Set()\n  str.replace(/\\.([\\w\\-]+)/g, (_, el) => classes.add(el))\n  if (classes.size) attr.class = [...classes].join(' ')\n\n  // id\n  str.replace(/#([\\w\\-]+)/, (_, el) => attr.id = el)\n\n  return attr\n}\n\n\n\n"
  },
  {
    "path": "packages/nuemark/src/render-blocks.js",
    "content": "\nimport { glow } from 'nue-glow'\n\nimport { parseBlocks } from './parse-blocks.js'\nimport { renderInline, renderTokens } from './render-inline.js'\nimport { renderTable, renderTag, wrap } from './render-tag.js'\n\n\n// for testing only\nexport function renderLines(lines, opts) {\n  const things = parseBlocks(lines)\n  return renderBlocks(things.blocks, { ...opts, ...things })\n}\n\nexport function renderBlocks(blocks, opts = {}) {\n  return blocks.map(b => renderBlock(b, opts)).join('\\n')\n}\n\nfunction renderBlock(block, opts) {\n  const fn = opts?.beforerender\n  if (fn) fn(block)\n\n  return block.is_content ? renderContent(block.content, opts)\n    : block.is_heading ? renderHeading(block, opts)\n    : block.is_quote ? elem('blockquote', renderBlocks(block.blocks, opts))\n    : block.is_tag ? renderTag(block, opts)\n    : block.is_table ? renderTable(block, opts)\n    : block.is_list ? renderList(block, opts)\n    : block.is_code ? renderCode(block, opts)\n    : block.is_newline ? ''\n    : block.is_break ? '<hr>'\n    : block.is_html ? block.html\n    : console.error('Unknown block', block)\n}\n\n// recursive\nfunction renderList({ items, numbered }, opts) {\n  const html = items.map(blocks => elem('li', renderBlocks(blocks, opts)))\n  return elem(numbered ? 'ol' : 'ul', html.join('\\n'))\n}\n\nexport function renderHeading(h, opts = {}) {\n  const attr = { ...h.attr }\n  const show_id = opts.heading_ids\n  if (show_id && !attr.id) attr.id = createHeadingId(h.text)\n\n  // anchor\n  const a = show_id ? elem('a', { href: `#${attr.id}`, title: h.text }) : ''\n\n  return elem('h' + h.level, attr, a + renderTokens(h.tokens, opts))\n}\n\n\nexport function createHeadingId(text) {\n  let hash = text.slice(0, 32).replace(/'/g, '').replace(/[\\W_]/g, '-').replace(/-+/g, '-').toLowerCase()\n  if (hash[0] == '-') hash = hash.slice(1)\n  if (hash.endsWith('-')) hash = hash.slice(0, -1)\n  return hash\n}\n\nexport function renderContent(lines, opts) {\n  const html = lines.map(line => renderInline(line, opts)).join(' ')\n  return elem('p', html)\n}\n\nfunction renderCode({ name, code, attr, data }, opts) {\n  const { numbered } = data\n  const klass = attr.class\n  delete attr.class\n\n  let html = renderTag({\n      name: 'codeblock', attr, data: { ...data, language: name, code },\n      blocks: [{ is_html: true, html: glow(code, { language: name, numbered }) }],\n    }, opts)\n\n  const caption = data.caption || data._\n\n  if (caption) {\n    const figcaption = elem('figcaption', renderInline(caption))\n    return elem('figure', { class: klass }, figcaption + html)\n  }\n\n  return wrap(klass, html)\n}\n\n/**** utilities ****/\nconst SELF_CLOSING = ['img', 'source', 'meta', 'link']\n\nexport function elem(name, attr, body) {\n  if (typeof attr == 'string') { body = attr; attr = null }\n\n  const html = [`<${name}${renderAttrs(attr)}>`]\n\n  if (body) html.push(body)\n  if (!SELF_CLOSING.includes(name)) html.push(`</${name}>`)\n  return html.join('')\n}\n\n\nfunction renderAttrs(attr) {\n  const arr = []\n  for (const key in attr) {\n    const val = attr[key]\n    if (val) arr.push(val === true ? key : `${key}=\"${val}\"`)\n  }\n  return arr[0] ? ' ' + arr.join(' ') : ''\n}\n\n\n\n\n\n"
  },
  {
    "path": "packages/nuemark/src/render-inline.js",
    "content": "\nimport { ESCAPED, parseInline } from './parse-inline.js'\nimport { elem } from './render-blocks.js'\nimport { renderTag } from './render-tag.js'\n\n\nexport function renderToken(token, opts = {}) {\n  const { data = {} } = opts\n  const { text } = token\n\n  return text ? text :\n    token.is_format ? formatText(token, opts) :\n      token.is_var ? renderVariable(token.name, data) :\n        token.is_image ? renderImage(token) :\n          token.is_tag ? renderTag(token, opts) :\n            token.href ? renderLink(token, opts) :\n              ''\n}\n\nfunction formatText({ tag, body }, opts) {\n  const html = tag == 'code' ? renderCode(body) : renderInline(body, opts)\n  return tag == 'EM' ? elem('em', elem('strong', html)) : elem(tag, html)\n}\n\nfunction renderCode(code) {\n  return code.replace(/[<>]/g, char => ESCAPED[char])\n}\n\nfunction renderImage(img) {\n  const { title } = img\n  return elem('img', { src: img.href, alt: img.label, title, loading: 'lazy' })\n}\n\nfunction renderLink(link, opts) {\n  const { is_footnote, href, title } = link\n  const { reflinks = {}, noterefs = [] } = opts\n  const url = reflinks[href] || { href }\n\n  let label = renderInline(link.label, opts)\n\n  // noterefs\n  if (is_footnote) {\n    const index = noterefs.findIndex(el => el == href.slice(1))\n    if (index >= 0) label += elem('sup', { role: 'doc-noteref' }, index + 1)\n  }\n\n  return elem('a', { title, ...url, rel: is_footnote ? 'footnote' : null }, label)\n}\n\nexport function renderTokens(tokens, opts) {\n  return tokens.map(token => renderToken(token, opts)).join('').trim()\n}\n\nexport function renderInline(str, opts) {\n  return str ? renderTokens(parseInline(str), opts) : ''\n}\n\nfunction renderVariable(expr, data) {\n  const fn = new Function('data', `return data.${expr}`)\n  try {\n    return fn(data) || ''\n  } catch (e) { return '' }\n}\n\n\n\n"
  },
  {
    "path": "packages/nuemark/src/render-tag.js",
    "content": "\nimport { join, extname } from 'node:path'\nimport { readFileSync } from 'node:fs'\n\nimport { sectionize } from './parse-document.js'\nimport { elem, renderBlocks } from './render-blocks.js'\nimport { renderInline } from './render-inline.js'\n\n\n// built-in tags\nconst TAGS = {\n\n  codeblock() {\n    const { attr, blocks, render } = this\n    return elem('pre', attr, render(blocks))\n  },\n\n  accordion({ name, open }) {\n    const html = this.sections?.map((blocks, i) => {\n      const head = elem('summary', blocks[0].text)\n      const body = elem('div', this.render(blocks.slice(1)))\n      const opened = open === true && i == 0 || i === open\n      return elem('details', { name, open: opened }, head + body)\n    })\n\n    return html && elem('div', this.attr, html.join('\\n'))\n  },\n\n  block() {\n    const { render, attr, blocks } = this\n    const divs = sectionize(blocks)\n\n    const html = !divs || !divs[1] ? render(blocks) :\n      divs.map(blocks => elem('div', render(blocks))).join('\\n')\n\n    return elem(attr.popover ? 'dialog' : 'div', attr, html)\n  },\n\n  // @depreciated\n  button(data) {\n    const { href } = data\n    const label = this.renderInline(data.label || data._) || this.innerHTML || ''\n\n    return href ? elem('a', { ...this.attr, href, role: 'button' }, label) :\n      elem('button', this.attr, label)\n  },\n\n  define() {\n    const html = this.sections?.map((blocks, i) => {\n      const { attr, text } = blocks[0]\n      const title = attr.id ? elem('a', { name: `^${attr.id}` }, text) : text\n      const dt = elem('dt', { class: attr.class }, title)\n      const dd = elem('dd', this.render(blocks.slice(1)))\n      return dt + dd\n    })\n\n    return html && elem('dl', this.attr, html.join('\\n'))\n  },\n\n\n  image() {\n    const { attr, data } = this\n    const { caption, href, loading = 'lazy' } = data\n    const src = data.src || data._ || data.large\n    const alt = data.alt || caption\n\n    // img tag\n    const img_attr = { alt, loading, src, ...parseSize(data) }\n    let img = data.small ? createPicture(img_attr, data) : elem('img', img_attr)\n\n    // wrap image inside a link\n    if (href) img = elem('a', { href }, img)\n\n    // figcaption\n    const figcaption = caption ? this.renderInline(caption) : this.innerHTML\n    if (figcaption) img += elem('figcaption', figcaption)\n\n    // always wrapped inside a figure\n    return elem('figure', attr, img)\n  },\n\n\n  list() {\n    const items = this.sections || getListItems(this.blocks)\n    const item_attr = { class: this.data.items }\n    const html = items.map(blocks => elem('li', item_attr, this.render(blocks)))\n    const ul = elem('ul', this.attr, html.join('\\n'))\n    return wrap(this.data.wrapper, ul)\n  },\n\n  svg(data) {\n    const src = data.src || data._\n    return src ? readIcon(src) : ''\n  },\n\n  icon(data) {\n    return renderIcon(data.src || data._, data.symbol, data.icon_dir)\n  },\n\n  table() {\n    const { attr, data, body, opts } = this\n    let table = { rows: data.rows || data.items }\n    if (!table.rows && body) table = parseTable(body)\n\n    const html = renderTable({ attr, ...data, ...table }, opts)\n    return wrap(data.wrapper, html)\n  },\n\n  video() {\n    const { data } = this\n    const src = data.src || data._\n    const type = getMimeType(src)\n    const attr = { ...this.attr, src, type, ...getVideoAttrs(data) }\n    return elem('video', attr, this.innerHTML)\n  },\n\n  object() {\n    const { attr, data } = this\n    const { href, loading = 'lazy' } = data\n    const src = data.data || data._\n\n    const obj_attr = { ...parseSize(data), type: getMimeType(src), data: src, loading }\n\n    let tag = elem('object', obj_attr, this.innerHTML)\n\n    // wrap object inside a link\n    if (href) tag = elem('a', { href }, tag)\n\n    // always wrapped inside a figure\n    return elem('figure', attr, tag)\n  },\n\n\n\n  // shortcut\n  '!': function() {\n    const tag = getMimeType(this.data._).startsWith('video') ? TAGS.video : TAGS.image\n    return tag.call(this)\n  }\n}\n\n\nexport function readIcon(path, icon_dir) {\n  if (!path.endsWith('.svg')) {\n    path += '.svg'\n    if (icon_dir && path[0] != '/') path = join(icon_dir, path)\n  }\n\n  path = join('.', path)\n\n  try {\n    const svg = readFileSync(path, 'utf-8')\n    return svg.replace('<svg', '<svg class=\"icon\"')\n  } catch (e) {\n    console.error('svg not found', path)\n    return ''\n  }\n}\n\nexport function renderIcon(name, symbol, icon_dir) {\n  return name ? readIcon(name, icon_dir) : symbol ? elem('svg', { class: 'icon icon-' + symbol }, `<use href=\"#${symbol}\"/>`) : ''\n}\n\n\nexport function renderTag(tag, opts = {}) {\n  const tags = { ...TAGS, ...opts.tags }\n  const fn = tags[tag.name || 'block']\n\n  // render client stub\n  if (!fn) return renderIsland(tag, opts.data)\n\n  const data = { ...opts.data, ...extractData(tag.data, opts.data) }\n\n  const api = {\n    ...tag,\n    get innerHTML() { return getInnerHTML(this.blocks, opts) },\n    render(blocks) { return renderBlocks(blocks, opts) },\n    renderInline(str) { return renderInline(str, opts) },\n    sections: sectionize(tag.blocks),\n    data,\n    opts,\n    tags,\n  }\n\n  return fn.call(api, data)\n}\n\n\nexport function renderIsland(tag, all_data) {\n  const { name, attr } = tag\n  const data = extractData(tag.data, all_data)\n\n  const json = !Object.keys(data)[0] ? '' :\n    elem('script', { type: 'application/json' }, JSON.stringify(data))\n  return elem(name, { 'nue': name, ...attr }, json)\n}\n\n\n\n\n/******* utilities *******/\n\nconst MIME = {\n  jpg: 'image/jpeg',\n  svg: 'image/svg+xml',\n  mov: 'video/mov',\n  webm: 'video/webm',\n  mp4: 'video/mp4',\n  html: 'text/html'\n}\n\nfunction getMimeType(path = '') {\n  const type = extname(path).slice(1)\n  return type ? MIME[type] || `image/${type}` : 'text/html'\n}\n\nexport function createPicture(img_attr, data) {\n  const { small, offset = 750 } = data\n\n  const sources = [small, img_attr.src].map(src => {\n    const prefix = src == small ? 'max' : 'min'\n    const media = `(${prefix}-width: ${parseInt(offset)}px)`\n    return elem('source', { srcset: src, media, type: getMimeType(src) })\n  })\n\n  sources.push(elem('img', img_attr))\n  return elem('picture', sources.join('\\n'))\n}\n\nfunction getListItems(arr) {\n  if (arr && arr[0]) return arr[0].items || arr.map(el => [el])\n}\n\nexport function parseSize(data) {\n  const { size = '' } = data\n  const [w, h] = ('' + size).trim().split(/\\s*\\D\\s*/)\n  return { width: w || data.width, height: h || data.height || w }\n}\n\n\n// :rows=\"pricing\" --> rows -> all_data.pricing\nfunction extractData(data, all_data) {\n  for (const key in data) {\n    if (key.startsWith(':')) {\n      data[key.slice(1)] = all_data[data[key]]\n      delete data[key]\n    }\n  }\n  return data\n}\n\nfunction getVideoAttrs(data) {\n  const keys = 'autoplay controls loop muted poster preload src width'.split(' ')\n  const attr = {}\n  for (const key of keys) {\n    const val = data[key]\n    if (val) attr[key] = val\n  }\n  return attr\n}\n\nexport function wrap(name, html) {\n  return name ? elem('div', { class: name }, html) : html\n}\n\n\n\nfunction getInnerHTML(blocks = [], opts) {\n  const [first, second] = blocks\n  if (!first) return ''\n  const { content } = first\n  return content && !second ? renderInline(content.join(' '), opts) : renderBlocks(blocks, opts)\n}\n\n\n// table helpers\nexport function renderTable(table, opts) {\n  const { rows, attr, head = true } = table\n  if (!rows) return ''\n\n  // column count\n  let colcount\n\n  const html = rows.map((row, i) => {\n    if (typeof row == 'string') row = row.split(/\\s*\\|\\s*/)\n    if (!i) colcount = row.length\n\n    const is_head = head && i == 0 && rows.length > 1\n    const is_foot = table.foot && i > 1 && i == rows.length - 1\n    const colspan = colcount - row.length + 1\n\n    const cells = row.map(td => {\n      const attr = colspan > 1 ? { colspan } : null\n      return elem(is_head || is_foot ? 'th' : 'td', attr, renderInline(td, opts))\n    })\n\n    const tr = elem('tr', cells.join(''))\n    return is_head ? elem('thead', tr) : is_foot ? elem('tfoot', tr) : tr\n  })\n\n  const caption = table.caption ? elem('caption', renderInline(table.caption, opts)) : ''\n\n  return elem('table', attr, caption + html.join('\\n'))\n}\n\nexport function parseTable(lines) {\n  const rows = []\n  const specs = {}\n\n  lines.forEach((line, i) => {\n    if (!line.trim()) return\n\n\n    if (line.startsWith('---')) {\n      if (rows.length == 1) specs.head = true\n      else if (i == lines.length - 2) specs.foot = true\n      return\n    }\n\n    // split to cells\n    let els = line.split(/\\s*[;|]\\s/)\n    if (i == 0) specs.cols = els.length\n\n    // append to previous row\n    if (els.length < specs.cols) {\n      const prev = rows[rows.length - 1]\n      if (prev.length < specs.cols) return prev.push(...els)\n    }\n\n    rows.push(els)\n  })\n\n  return { rows, ...specs }\n}"
  },
  {
    "path": "packages/nuemark/test/block.test.js",
    "content": "\nimport { nuemark } from '../index.js'\nimport { getBreak, parseBlocks, parseHeading } from '../src/parse-blocks.js'\nimport { renderBlocks, renderHeading, renderLines } from '../src/render-blocks.js'\n\n\ntest('paragraphs', () => {\n  const { blocks } = parseBlocks(['a', 'b', '', '', 'c'])\n  expect(blocks.length).toBe(2)\n\n  const html = renderBlocks(blocks)\n  expect(html).toStartWith('<p>a b</p>')\n  expect(html).toEndWith('<p>c</p>')\n})\n\ntest('list items', () => {\n  const { blocks } = parseBlocks(['- a', '', '  a1', '- b', '', '', '- c'])\n  expect(blocks.length).toBe(1)\n  expect(blocks[0].entries).toEqual([[\"a\", \"\", \"a1\"], [\"b\", \"\", \"\"], [\"c\"]])\n})\n\n\ntest('nested lists', () => {\n  const { blocks } = parseBlocks(['- item', '', '  - nested 1', '', '', '  - nested 2'])\n  expect(blocks.length).toBe(1)\n  expect(blocks[0].entries[0]).toEqual([\"item\", \"\", \"- nested 1\", \"\", \"\", \"- nested 2\"])\n\n  const html = renderBlocks(blocks)\n  expect(html).toEndWith('<li><p>nested 2</p></li></ul></li></ul>')\n})\n\n\ntest('nested tag data', () => {\n  const { blocks } = parseBlocks(['[hello] ', '', '', '  foo: bar', '', '  bro: 10'])\n  expect(blocks[0].name).toBe('hello')\n  expect(blocks[0].data).toEqual({ foo: \"bar\", bro: 10 })\n})\n\ntest('nested tag content', () => {\n  const { blocks } = parseBlocks(['[.stack]', '', '', '  line1', '', '  line2'])\n  expect(blocks.length).toBe(1)\n  expect(blocks[0].blocks.length).toBe(2)\n\n  const html = renderBlocks(blocks)\n  expect(html).toStartWith('<div class=\"stack\"><p>line1</p>')\n})\n\ntest('subsequent blockquotes', () => {\n  const { blocks } = parseBlocks(['> hey', '> boy', '', '> another'])\n  expect(blocks.length).toBe(3)\n  const html = renderBlocks(blocks)\n  expect(html).toStartWith('<blockquote><p>hey boy</p></blockquote>')\n})\n\n\ntest('numbered items', () => {\n  const { blocks } = parseBlocks(['1. Yo', '10. Bruh', '* Bro'])\n  expect(blocks[0].numbered).toBeTrue()\n  expect(blocks[0].entries).toEqual([[\"Yo\"], [\"Bruh\"], [\"Bro\"]])\n})\n\n\ntest('multiple thematic breaks', () => {\n  const { blocks } = parseBlocks(['A', '---', 'B', '---', 'C'])\n  expect(blocks.length).toBe(5)\n})\n\n\ntest('parse thematic break', () => {\n  const hrs = ['***', '___', '- - -', '*** --- ***']\n  for (const str of hrs) {\n    expect(getBreak(str)).toBeDefined()\n  }\n  expect(getBreak('*** yo')).toBeUndefined()\n})\n\ntest('render thematic break', () => {\n  const html = renderLines(['***', '***x-bold***', '', '***   hey   ***'])\n  expect(html).toStartWith('<hr>')\n  expect(html).toInclude('<p><em><strong>x-bold')\n  expect(html).toEndWith('<p>***   hey   ***</p>')\n})\n\ntest('render thematic break', () => {\n  expect(renderLines(['hello', '***'])).toBe('<p>hello</p>\\n<hr>')\n})\n\ntest('parse heading', () => {\n  const h = parseHeading('# Hello')\n  expect(h).toMatchObject({ attr: {}, text: 'Hello', level: 1 })\n})\n\ntest('render heading', () => {\n  expect(nuemark('# Hello')).toBe('<h1>Hello</h1>')\n  expect(nuemark('##Hello')).toBe('<h2>Hello</h2>')\n  expect(nuemark('### Hello, *world*')).toBe('<h3>Hello, <em>world</em></h3>')\n})\n\ntest('heading class name', () => {\n  const html = nuemark('# Hello { .boss }')\n  expect(html).toBe('<h1 class=\"boss\">Hello</h1>')\n})\n\ntest('heading attr', () => {\n  const h = parseHeading('# Hey { #foo.bar }')\n  expect(h.text).toBe('Hey')\n  expect(h.attr).toEqual({ class: \"bar\", id: \"foo\" })\n\n  expect(renderHeading(h)).toBe('<h1 class=\"bar\" id=\"foo\">Hey</h1>')\n\n  const html = renderHeading(h, { heading_ids: true })\n  expect(html).toInclude('<a href=\"#foo\" title=\"Hey\"></a>')\n})\n\ntest('generated heading id', () => {\n  const html = nuemark('# Hello', { heading_ids: true })\n  expect(html).toBe('<h1 id=\"hello\"><a href=\"#hello\" title=\"Hello\"></a>Hello</h1>')\n})\n\ntest('heading block count', () => {\n  const { blocks } = parseBlocks(['# Yo', 'rap', '## Bruh', 'bat', '## Bro'])\n  expect(blocks.length).toBe(5)\n})\n\n\ntest('render blockquote', () => {\n  const html = renderLines(['> ## Hey', '> 1. dude'])\n  expect(html).toStartWith('<blockquote><h2>Hey</h2>\\n<ol><li><p>dude')\n})\n\ntest('render fenced code', () => {\n  const html0 = renderLines(['```css .pink numbered', 'em {}', '```'])\n  expect(html0).toStartWith('<div class=\"pink\"><pre><code language=\"css\"><span>')\n  const html1 = renderLines(['```css.pink numbered', 'em {}', '```'])\n  expect(html1).toStartWith('<div class=\"pink\"><pre><code language=\"css\"><span>')\n  const html2 = renderLines(['```.pink numbered', 'em {}', '```'])\n  expect(html2).toStartWith('<div class=\"pink\"><pre><code language=\"*\"><span>')\n  const html3 = renderLines(['```.pink', 'em {}', '```'])\n  expect(html3).toStartWith('<div class=\"pink\"><pre><code language=\"*\">')\n})\n\ntest('fenced code with caption', () => {\n  const html = renderLines(['``` css.bad \"Hey *there*\"', 'em {}', '```'])\n  expect(html).toStartWith('<figure class=\"bad\"><figcaption>Hey <em>there</em>')\n  expect(html).toEndWith('</i></code></pre></figure>')\n})\n\ntest('multi-line list entries', () => {\n  const list = parseBlocks(['* foo', '  boy', '* bar']).blocks[0]\n  expect(list.entries).toEqual([[\"foo\", \"boy\"], [\"bar\"]])\n})\n\ntest('nested list', () => {\n  const { items } = parseBlocks(['* > foo', '  1. boy', '  2. bar']).blocks[0]\n  const [[quote, nested]] = items\n\n  expect(quote.is_quote).toBeTrue()\n  expect(nested.is_list).toBeTrue()\n})\n\ntest('blockquote', () => {\n  const [quote] = parseBlocks(['> foo', '> boy']).blocks\n  expect(quote.is_quote).toBeTrue()\n  expect(quote.content).toEqual([\"foo\", \"boy\"])\n})\n\ntest('fenced code blocks', () => {\n  const [code] = parseBlocks(['``` css.foo numbered', 'func()', '```']).blocks\n\n  expect(code.name).toBe('css')\n  expect(code.attr).toEqual({ class: 'foo' })\n  expect(code.code).toEqual([\"func()\"])\n  expect(code.data.numbered).toBeTrue()\n})\n\ntest('parse table', () => {\n  const lines = [\n    '| Month    | Amount  |',\n    '| -------- | ------- |',\n    '| January  | $250    |',\n    '| February | $80     |',\n  ]\n\n  // parse\n  const [table] = parseBlocks(lines).blocks\n  expect(table.rows[1]).toEqual([\"January\", \"$250\"])\n  expect(table.rows.length).toBe(3)\n  expect(table.head).toBeTrue()\n\n  // render\n  const html = renderLines(lines)\n  expect(html).toInclude('<th>Month</th><th>Amount</th>')\n  expect(html).toInclude('<tr><td>February</td><td>$80</td>')\n})\n\ntest('parse reflinks', () => {\n  const { reflinks } = parseBlocks([\n    '[.hero]',\n    '  # Hello, World',\n    '  [foo]: //website.com',\n    '[1]: //another.net \"something\"'\n  ])\n\n  expect(reflinks).toEqual({\n    \"1\": {\n      href: \"//another.net\",\n      title: \"something\",\n    },\n    foo: {\n      href: \"//website.com\",\n    },\n  })\n})\n\ntest('footnotes with [define]', () => {\n  const html = renderLines([\n    '[hey][^ki] [yo][^ko]',\n    '[define]',\n    '  ## King { #ki }',\n    '  ## Kong { #ko }',\n  ])\n\n  expect(html).toInclude('<a href=\"#^ki\" rel=\"footnote\">hey<sup role=\"doc-noteref\">1</sup></a>')\n  expect(html).toInclude('1</sup></a> <a')\n  expect(html).toInclude('2</sup></a></p>')\n  expect(html).toInclude('<dl><dt><a name=\"^ki\">King')\n})\n\n\ntest('complex tag data', () => {\n  const comp = parseBlocks(['[hello#foo.bar world size=\"10\"]', '  foo: bar']).blocks[0]\n  expect(comp.attr).toEqual({ class: \"bar\", id: \"foo\", })\n  expect(comp.data).toEqual({ world: true, size: 10, foo: \"bar\", })\n})\n\ntest('duplicate tag classes', () => {\n  const { blocks } = parseBlocks(['[hello.c.c.bar class=\"foo bar\" world]'])\n  expect(blocks[0].attr.class).toBe('c bar foo')\n  expect(blocks[0].data).toEqual({ world: true })\n})\n\ntest('escaping', () => {\n  const html = renderLines([\n    '\\\\[code]', '',\n    '\\\\> blockquote', '',\n    '\\\\## title',\n  ])\n\n  expect(html).toInclude('<p>[code]</p>')\n  expect(html).toInclude('<p>> blockquote</p>')\n  expect(html).toInclude('<p>## title</p>')\n})\n\ntest('before render callback', () => {\n  function beforerender(block) {\n    if (block.is_heading) block.level = 2\n    if (block.is_content) block.content = ['World']\n  }\n\n  const html = renderLines(['# Hello', 'Bar'], { beforerender })\n  expect(html).toBe('<h2>Hello</h2>\\n<p>World</p>')\n})\n\n\n\n\n\n"
  },
  {
    "path": "packages/nuemark/test/document.test.js",
    "content": "\nimport { parseBlocks } from '../src/parse-blocks.js'\nimport { parseDocument, sectionize, stripMeta } from '../src/parse-document.js'\n\n\ntest('front matter', () => {\n  const lines = ['---', 'foo: 10', 'bar: 20', '---', '# Hello']\n  const meta = stripMeta(lines)\n  expect(meta).toEqual({ foo: 10, bar: 20 })\n  expect(lines).toEqual([ \"# Hello\" ])\n})\n\n\ntest('empty meta', () => {\n  const lines = ['# Hello']\n  const meta = stripMeta(lines)\n  expect(meta).toEqual({})\n  expect(lines).toEqual([ \"# Hello\" ])\n})\n\n\ntest('document title', () => {\n  const { meta } = parseDocument(['# Hello'])\n  expect(meta.title).toBe('Hello')\n})\n\ntest('title inside hero', () => {\n  const { meta } = parseDocument(['[.hero]', '  # Hello'])\n  expect(meta.title).toBe('Hello')\n})\n\ntest('description', () => {\n  const { meta } = parseDocument(['# Hello', 'This is bruh', '', 'Yo'])\n  expect(meta.description).toBe('This is bruh')\n})\n\n\ntest('render method', () => {\n  const doc = parseDocument(['# Hello'])\n  expect(doc.render()).toBe('<h1>Hello</h1>')\n})\n\n\ntest('sectionize', () => {\n  const tests = [\n    ['### h3', 'para', '### h3', 'para', '#### h4', 'para'],\n    ['# h1', 'para', '## h2', 'para', '### h3', 'para'],\n    ['para', '## h3', '---', 'para', '## h2'],\n    ['## lol', '---', '## bol'],\n    ['lol', '---', 'bol'],\n  ]\n\n  for (const test of tests) {\n    const { blocks } = parseBlocks(test)\n    expect(sectionize(blocks).length).toBe(2)\n  }\n})\n\ntest('non section', () => {\n  const { blocks } = parseBlocks(['hello', 'world'])\n  expect(sectionize(blocks)).toBeUndefined()\n})\n\n\ntest('multiple sections', () => {\n  const lines = [\n    '# Hello', 'World',\n    '## Foo', 'Bar',\n    '---', 'Bruh', '***',\n  ]\n\n  const doc = parseDocument(lines)\n  // expect(doc.sections.length).toBe(2)\n\n  const html = doc.render({ sections: ['hero'] })\n  expect(html).toStartWith('<section class=\"hero\"><h1>Hello</h1>')\n  expect(html).toEndWith('<hr></section>')\n})\n\n\ntest('render reflinks', () => {\n  const links = { external: 'https://bar.com/zappa \"External link\"' }\n\n  const doc = parseDocument([\n    '[Hey *dude*][local]',\n    'Inlined [Second][external]',\n    '[local]: /blog/yo.html \"Local link\"'\n  ])\n\n  const html = doc.render({ links })\n  expect(html).toInclude('<a title=\"Local link\" href=\"/blog/yo.html\">Hey <em>dude</em></a>')\n  expect(html).toInclude('Inlined <a title=\"External link\" href=\"https://bar.com/zappa\">Second</a>')\n})\n\n\ntest('footnotes', () => {\n  const doc = parseDocument([\n    'This,[^1] [here][^a] goes.[^b]',\n    '[^1]: foo',\n    '[^a]: bar',\n    '[^b]: baz',\n  ])\n\n  const html = doc.render()\n  expect(html).toInclude('<a href=\"#^1\" rel=\"footnote\">')\n  expect(html).toInclude('<ol role=\"doc-endnotes\"><li><a name=\"^1\"></a>foo</li>')\n})\n\n\n"
  },
  {
    "path": "packages/nuemark/test/inline.test.js",
    "content": "\nimport { parseInline, parseLink } from '../src/parse-inline.js'\nimport { renderInline, renderToken, renderTokens } from '../src/render-inline.js'\n\n\ntest('plain text', () => {\n  const tests = [\n    'Hello, World!',\n    'Unclosed \"quote',\n    'Unclosed ****format',\n    'Unopened italics__ too',\n    'Mega # weir%$ ¶{}€ C! char *s',\n    'A very long string \\n with  odd \\t spacing',\n  ]\n\n  for (const test of tests) {\n    const [{ text }] = parseInline(test)\n    expect(text).toBe(test)\n  }\n})\n\n\ntest('formatting', () => {\n  const tests = [\n    ['_', '*yo*', 'em'],\n    ['*', 'yo 90', 'em'],\n    ['__', 'Ö#(/&', 'strong'],\n    ['**', 'go _ open', 'strong'],\n    ['~', 'striked', 's'],\n    ['•', 'bold', 'b'],\n    // ['|', 'mark', '|'],\n  ]\n\n  for (const test of tests) {\n    const [ chars, body, tag ] = test\n    const html = parseInline(`A ${chars + body + chars} here`)\n    expect(html[1].tag).toBe(tag)\n    expect(html[1].body).toBe(body)\n    expect(html.length).toBe(3)\n  }\n})\n\ntest('formatting inside string', () => {\n  expect(renderInline('a_b_c')).toBe('a_b_c')\n  expect(renderInline('a/c/d')).toBe('a/c/d')\n  expect(renderInline('a _b_')).toBe('a <em>b</em>')\n  expect(renderInline('a _b_ c')).toBe('a <em>b</em> c')\n})\n\ntest('formatting and special chars at the end', () => {\n  expect(renderInline('**hello**')).toBe('<strong>hello</strong>')\n  expect(renderInline('**hello**:')).toEndWith('</strong>:')\n  expect(renderInline('**hello**,')).toEndWith('</strong>,')\n  expect(renderInline('**hello*')).toBe('**hello*')\n  expect(renderInline('** hello **')).toBe('** hello **')\n  expect(renderInline('** hello**')).toBe('** hello**')\n})\n\ntest('bold + em', () => {\n  expect(renderInline('***hello***')).toBe('<em><strong>hello</strong></em>')\n  expect(renderInline('___hello___')).toBe('<em><strong>hello</strong></em>')\n  expect(renderInline('___ hello ___')).toBe('___ hello ___')\n})\n\ntest('unclosed formatting', () => {\n  expect(renderInline('_foo *bar*')).toBe('_foo <em>bar</em>')\n  expect(renderInline('yo /foo /bar _baz_')).toBe('yo /foo /bar <em>baz</em>')\n  expect(renderInline('foo/bar *baz*')).toBe('foo/bar <em>baz</em>')\n})\n\ntest('inline render basics', () => {\n  const tests = [\n    { text: 'hey', html: 'hey' },\n    { is_format: true, tag: 'b', body: 'hey', html: '<b>hey</b>' },\n    { is_format: true, tag: 'b', body: '*hey*', html: '<b><em>hey</em></b>' },\n    { href: '/', label: 'hey', html: '<a href=\"/\">hey</a>' },\n    { href: '/', label: '*hey*', html: '<a href=\"/\"><em>hey</em></a>' },\n    { href: '/', label: 'hey', title: 'yo', html: '<a title=\"yo\" href=\"/\">hey</a>' },\n  ]\n\n  for (const test of tests) {\n    const html = renderToken(test)\n    expect(html).toBe(test.html)\n  }\n})\n\n\n// image\ntest('render image', () => {\n  const html = renderInline('![foo](/bar.png) post')\n  expect(html).toBe('<img src=\"/bar.png\" alt=\"foo\" loading=\"lazy\"> post')\n})\n\ntest('unclosed image', () => {\n  expect(renderInline('![foo]')).toStartWith('!<foo nue')\n})\n\ntest('inline HTML', () => {\n  expect(renderInline('<! kama >')).toBe('&lt;! kama &gt;')\n})\n\ntest('inline code', () => {\n  const html = renderInline('Hey `[zoo] *boo*`')\n  expect(html).toBe('Hey <code>[zoo] *boo*</code>')\n\n  expect(renderInline('Hey `<script>`')).toBe('Hey <code>&lt;script&gt;</code>')\n})\n\ntest('HTML escaping', () => {\n  const html = renderInline('Hey <script>')\n  expect(html).toBe('Hey &lt;script&gt;')\n})\n\ntest('escaping', () => {\n  expect(renderInline('Hey \\\\*bold* dude')).toBe('Hey *bold* dude')\n  expect(renderInline('Hey \\\\{ var }')).toBe('Hey { var }')\n  expect(renderInline('Hey \\\\[tag]')).toBe('Hey [tag]')\n})\n\n\ntest('simple link', () => {\n  const [text, link] = parseInline('Goto [label](/url/)')\n  expect(link.label).toBe('label')\n  expect(link.href).toBe('/url/')\n})\n\ntest('link subject', () => {\n  const link = parseLink('[Hello](/world \"today\")')\n  expect(link).toMatchObject({ href: '/world', title: 'today', label: 'Hello' })\n})\n\ntest('parse inline link', () => {\n  const [text, link] = parseInline('Goto [label](/url/ \"the subject\")')\n  expect(link.title).toBe('the subject')\n  expect(link.label).toBe('label')\n  expect(link.href).toBe('/url/')\n})\n\ntest('parse reflink', () => {\n  const link = parseLink('[Hello][world \"now\"]', true)\n  expect(link).toMatchObject({ href: 'world', title: 'now', label: 'Hello' })\n})\n\ntest('parse reflink + link', () => {\n  const tokens = parseInline('hello [ref][r] world [link](/)')\n  expect(tokens.length).toBe(4)\n  expect(tokens).toEqual([\n    { text: 'hello ' },\n    {\n      href: \"r\",\n      title: undefined,\n      label: \"ref\",\n      is_footnote: false,\n      is_reflink: true,\n    },\n    { text: ' world ' },\n    {\n      href: \"/\",\n      title: undefined,\n      label: \"link\",\n      is_footnote: false,\n      is_reflink: undefined,\n    },\n  ])\n})\n\ntest('bad component names', () => {\n  const tests = ['[(10)] [3 % 8]', '[-hey]', '[he+y] there']\n  for (const test of tests) {\n    const html = renderInline(test)\n    expect(html).toBe(test)\n  }\n})\n\ntest('inline image tag', () => {\n  const html = renderInline('[! foo.svg]')\n  expect(html).toStartWith('<figure>')\n})\n\ntest('image link', () => {\n  const html = renderInline('[![](/img/foo.png)](/)')\n  expect(html).toStartWith('<a href=\"/\"><img src=\"/img/foo.png\"')\n})\n\ntest('complex label with an image', () => {\n  const complex_label = 'Hey ![Cat](/cat.png)!'\n  const link = `[${complex_label}](/link/ \"lol\")`\n\n  const el = parseLink(link)\n  expect(el.label).toBe(complex_label)\n  expect(el.href).toBe('/link/')\n\n  const html = renderInline(link)\n  expect(html).toStartWith('<a title=\"lol\" href=\"/link/\">Hey')\n  expect(html).toEndWith('alt=\"Cat\" loading=\"lazy\">!</a>')\n})\n\n\ntest('parse complex Wikipedia-style link', () => {\n  const [text, link, rest] = parseInline('Goto [label](/url/(master)) plan')\n  expect(link.href).toBe('/url/(master)')\n})\n\ntest('parse footnote link', () => {\n  const link = parseLink('[Hello][^1]', true)\n  expect(link.href).toBe('#^1')\n})\n\ntest('parse footnote ref', () => {\n  const [text, tag] = parseInline('Hello [^1]', true)\n  expect(tag.href).toBe('#^1')\n})\n\ntest('render footnote link', () => {\n  const noterefs = ['^1', '^go']\n  const rel = { is_footnote: true, href: '#^go' }\n  const html = renderToken(rel, { noterefs })\n  expect(html).toStartWith('<a href=\"#^go\" rel=\"footnote\">')\n  expect(html).toInclude('<sup role=\"doc-noteref\">2</sup>')\n})\n\ntest('footnote mix', () => {\n  const html = renderInline('[a][^1] b [^1]', { noterefs: ['^1'] })\n  expect(html).toInclude('a<sup')\n  expect(html).toEndWith('1</sup></a>')\n})\n\n\ntest('render reflinks', () => {\n  const ref = { href: '/', title: 'Bruh' }\n  const html = renderToken({ href: 'ref', label: 'Foobar' }, { reflinks: { ref } })\n  expect(html).toBe('<a title=\"Bruh\" href=\"/\">Foobar</a>')\n})\n\ntest('parse simple image', () => {\n  const [text, img] = parseInline('Image ![](yo.svg)')\n  expect(img.is_image).toBeTrue()\n  expect(img.href).toBe('yo.svg')\n})\n\n\n// parse tags and args\ntest('inline tag', () => {\n  const [el] = parseInline('[version]')\n  expect(el.name).toBe('version')\n})\n\ntest('inline tag with reflink', () => {\n  const els = parseInline('[tip] and [link][foo]')\n  const [ tag, and, link] = els\n  expect(tag.is_tag).toBeTrue()\n  expect(link.is_reflink).toBeTrue()\n})\n\ntest('link with a tag', () => {\n  const html = renderInline('[[my-tag]](/)')\n  expect(html).toBe('<a href=\"/\"><my-tag nue=\"my-tag\"></my-tag></a>')\n})\n\ntest('link with image tag', () => {\n  const html = renderInline('lol [[! yo.svg]](/)')\n  expect(html).toStartWith('lol <a href=\"/\">')\n  expect(html).toEndWith('src=\"yo.svg\"></figure></a>')\n})\n\n\ntest('tag args', () => {\n  const [ text, comp, rest] = parseInline('Hey [print foo] thing')\n  expect(comp.name).toBe('print')\n  expect(comp.data.foo).toBeTrue()\n})\n\ntest('{ variables }', () => {\n  const tokens = parseInline('v{ version } ({ date })')\n  expect(tokens.length).toBe(5)\n  expect(tokens[1]).toEqual({ is_var: true, name: \"version\" })\n\n  const data = { version: '1.0.1', date: '2025-01-01' }\n  const text = renderTokens(tokens, { data })\n  expect(text).toBe('v1.0.1 (2025-01-01)')\n})\n\ntest('complex variable getters', () => {\n  const opts = { data: { package: { name: 'glow', items: [1, 2] } }}\n  expect(renderInline('{ package.name }', opts)).toBe('glow')\n  expect(renderInline('{ package.items[0] }', opts)).toBe('1')\n  expect(renderInline('{ package.name.toUpperCase() }', opts)).toBe('GLOW')\n  expect(renderInline('{ zap.erro.boo }', opts)).toBe('')\n})\n\n\ntest('{ #foo.bar }', () => {\n  const tokens = parseInline('Hey { #foo.bar }')\n  expect(tokens[1].attr).toEqual({ class: \"bar\", id: \"foo\" })\n\n  const text = renderTokens(tokens)\n  expect(text.trim()).toEqual('Hey')\n})\n\n"
  },
  {
    "path": "packages/nuemark/test/tag.test.js",
    "content": "\nimport { dirname, join, relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport { parseAttr, parseSpecs, parseTag, valueGetter } from '../src/parse-tag.js'\nimport { renderLines } from '../src/render-blocks.js'\nimport { parseTable, renderTable } from '../src/render-tag.js'\n\n\nconst relpath = relative(process.cwd(), dirname(fileURLToPath(import.meta.url)))\n\n\n// parsing\ntest('valueGetter', () => {\n  const { str, getValue } = valueGetter(`foo=\"yo\" bar=\"hey dude\"`)\n  expect(str).toBe('foo=:1: bar=:2:')\n  expect(getValue(':1:')).toBe('yo')\n  expect(getValue(':2:')).toBe('hey dude')\n})\n\ntest('parseAttr', () => {\n  expect(parseAttr('.bar#foo')).toEqual({ id: 'foo', class: 'bar' })\n  expect(parseAttr('.bar#foo.baz')).toEqual({ id: 'foo', class: 'bar baz' })\n})\n\ntest('parseSpecs', () => {\n  expect(parseSpecs('tabs')).toEqual({ name: 'tabs', attr: {} })\n  expect(parseSpecs('tabs.#foo.bar')).toEqual({ name: 'tabs', attr: { id: 'foo', class: 'bar' } })\n})\n\ntest('parse plain args', () => {\n  const { name, data }= parseTag('video src=\"/a.mp4\" loop muted')\n  expect(name).toBe('video')\n  expect(data.loop).toBe(true)\n  expect(data.muted).toBe(true)\n})\n\ntest('parse attrs', () => {\n  expect(parseTag('#foo.bar').attr).toEqual({ id: \"foo\", class: \"bar\" })\n  expect(parseTag('list.tweets').attr).toEqual({ class: \"tweets\" })\n})\n\ntest('parse all', () => {\n  const { name, attr, data } = parseTag('tip#foo.bar \"Hey there\" size=\"40\" grayed hidden')\n  expect(data).toEqual({ _: \"Hey there\", size: 40, grayed: true })\n  expect(attr).toEqual({ class: \"bar\", id: \"foo\", hidden: true })\n  expect(name).toBe('tip')\n})\n\n\n// custom tags\nconst tags = {\n  print(data) {\n    return `<b>${ data?.value }</b>`\n  },\n}\n\ntest('inline tag', () => {\n  const html = renderLines(['Value: [print value=\"110\"]'], { tags })\n  expect(html).toBe('<p>Value: <b>110</b></p>')\n\n})\n\ntest('block tag', () => {\n  const html = renderLines(['[print]', '  value: 110'], { tags })\n  expect(html).toBe('<b>110</b>')\n})\n\n// accordion\ntest('[accordion] tag', () => {\n  const content = [\n    '[accordion.tabbed name=\"tabs\" open]',\n    '  ## A',\n    '  Desc A',\n    '  ## B',\n    '  Desc B',\n  ]\n\n  const html = renderLines(content)\n\n  expect(html).toStartWith('<div class=\"tabbed\"><details name=\"tabs\" open><summary>A')\n  expect(html).toInclude('<summary>B</summary><div><p>Desc B</p>')\n})\n\n// list sections\ntest('[list] sections', () => {\n  const content = [\n    '[list.features items=\"card\"]',\n    '  ## Something',\n    '  Described here',\n\n    '  ## Another',\n    '  Described here',\n  ]\n\n  const html = renderLines(content)\n  expect(html).toStartWith('<ul class=\"features\"><li class=\"card\"><h2>Something</h2>\\n<p>Described')\n})\n\n// list items\ntest('[list] items', () => {\n  const content = ['[list]', '  * foo', '  * bar' ]\n  const html = renderLines(content)\n  expect(html).toStartWith('<ul><li><p>foo</p></li>')\n  expect(html).toEndWith('<li><p>bar</p></li></ul>')\n})\n\n\n// list code blocks\ntest('[list] blocks', () => {\n  const content = ['[list]', '  ``` .foo', '  ```', '  ``` .bar', '  ```']\n  const html = renderLines(content)\n  expect(html).toStartWith('<ul><li><div class=\"foo\">')\n  expect(html).toInclude('<li><div class=\"bar\">')\n})\n\n// list code blocks\ntest('[list] wrapper', () => {\n  const html = renderLines(['[list wrapper=\"pink\"]', '  para'])\n  expect(html).toStartWith('<div class=\"pink\"><ul><li><p>para</p>')\n})\n\n\n// anonymous tag\ntest('.list', () => {\n  const html = renderLines(['[.list]', '  - elem 1', '  - elem 2'])\n  expect(html).toBe('<div class=\"list\"><ul><li><p>elem 1</p></li>\\n<li><p>elem 2</p></li></ul></div>')\n})\n\ntest('.note', () => {\n  const html = renderLines(['[.note]', '  ## Note', '  Hello'])\n  expect(html).toBe('<div class=\"note\"><h2>Note</h2>\\n<p>Hello</p></div>')\n})\n\n// anonymous .stack\ntest('.stack', () => {\n  const html = renderLines(['[.stack]', '  Hey', '  ---', '  Girl'])\n  expect(html).toStartWith('<div class=\"stack\"><div><p>Hey</p></div>')\n})\n\ntest('client-side island', () => {\n  const html = renderLines(['[contact-me]', '  cta: Submit'])\n  expect(html).toStartWith('<contact-me nue=\"contact-me\"><script')\n})\n\ntest('table options', () => {\n  const rows = [['a', 'b'], ['c', 'd'], ['foot']]\n  const html = renderTable({ rows, head: true, foot: true, caption: '*Hello*'  })\n\n  expect(html).toStartWith('<table><caption><em>Hello')\n  expect(html).toInclude('<thead><tr><th>a</th>')\n  expect(html).toInclude('<tr><td>c</td><td>d</td></tr>')\n  expect(html).toInclude('<tfoot><tr><th colspan=\"2\">')\n})\n\ntest('parse table', () => {\n  const lines = [\n    '| Bar | Baz',\n    '---',\n    'a | b',\n    'c',\n    '  ',\n    '---',\n    'foot'\n  ]\n  const table = parseTable(lines)\n  const { rows } = table\n  expect(table.cols).toBe(3)\n  expect(table.head).toBe(true)\n  expect(table.foot).toBe(true)\n  expect(rows.length).toBe(3)\n  expect(rows[1]).toEqual([ \"a\", \"b\", \"c\" ])\n})\n\n\ntest('[table] with external data', () => {\n  const foo = [ ['Foo', 'Buzz'], ['hey', 'girl']]\n  const opts = { data: { foo } }\n\n  const html = renderLines(['[table :rows=\"foo\"]'], opts)\n  expect(html).toStartWith('<table><thead><tr><th>Foo</th><th>Buzz</th></tr>')\n\n  const html2 = renderLines(['[table :rows=\"foo\" head=false]'], opts)\n  expect(html2).toStartWith('<table><tr><td>Foo</td><td>Buzz</td></tr>')\n\n  // table wrapper\n  const html3 = renderLines(['[table wrapper=\"pink\" :rows=\"foo\"]'], opts)\n  expect(html3).toStartWith('<div class=\"pink\"><table>')\n\n})\n\ntest.skip('[table] nested YAML', () => {\n  const html = renderLines(['[table]', '  items:', '    - [foo, bar]', '    - [foo, bar]'])\n  expect(html).toStartWith('<table><thead><tr><th>foo</th><th>')\n  expect(html).toEndWith('<td>bar</td></tr></table>')\n})\n\ntest('[table] nested string', () => {\n  const html = renderLines(['[table head=false]', '  a | b', '  ---', '  c | d'])\n  expect(html).toStartWith('<table><thead><')\n  expect(html).toEndWith('<td>d</td></tr></table>')\n})\n\ntest('[table] empty cells', () => {\n  const html = renderLines(['[table head=false]', '  | b | c', '  d |     | f'])\n  expect(html).toInclude('<tr><td></td><td>b')\n  expect(html).toInclude('<td>d</td><td></td><td>f')\n})\n\n\ntest('[button] inline label', () => {\n  const html = renderLines(['[button href=\"/\" \"Hey, *world*\"]'])\n  expect(html).toBe('<a href=\"/\" role=\"button\">Hey, <em>world</em></a>')\n})\n\ntest('[button] nested label', () => {\n  const html = renderLines(['[button href=/]', '  ![](/joku.png)'])\n  expect(html).toStartWith('<a href=\"/\" role=\"button\"><img src=\"/joku.png\"')\n})\n\n\ntest('[image] tag', () => {\n  const html = renderLines(['[image /meow.png]'])\n  expect(html).toBe('<figure><img loading=\"lazy\" src=\"/meow.png\"></figure>')\n})\n\ntest('[image] nested arg', () => {\n  const html = renderLines(['[image]', '  src: img.png'])\n  expect(html).toBe('<figure><img loading=\"lazy\" src=\"img.png\"></figure>')\n})\n\ntest('picture', () => {\n  const html = renderLines([\n    '[image caption=\"Hello\"]',\n    '  href: /',\n    '  small: small.png',\n    '  large: large.png',\n  ])\n\n  expect(html).toStartWith('<figure><a href=\"/\"><picture><source srcset')\n  expect(html).toEndWith('</a><figcaption>Hello</figcaption></figure>')\n})\n\ntest('[object] tag', () => {\n  const html = renderLines(['[object table.html]', '  [! foo.png]'])\n  expect(html).toInclude('<object type=\"text/html\" data=\"table.html\" loading=\"lazy\">')\n  expect(html).toInclude('<img loading=\"lazy\" src=\"foo.png\">')\n})\n\ntest('[object] tag data', () => {\n  const html = renderLines(['[object.foo /visuals/table]', '  loading: eager'])\n  expect(html).toStartWith(\n    '<figure class=\"foo\"><object type=\"text/html\" data=\"/visuals/table\" loading=\"eager\">'\n  )\n})\n\ntest('[video] tag', () => {\n  const html = renderLines(['[video /meow.mp4 autoplay]', '  ### Hey'])\n  expect(html).toStartWith('<video src=\"/meow.mp4\" type=\"video/mp4\" autoplay>')\n  expect(html).toEndWith('<h3>Hey</h3></video>')\n})\n\ntest('! shortcut', () => {\n  const html = renderLines(['[! /meow.mp4 autoplay]'])\n  expect(html).toStartWith('<video src=\"/meow.mp4\" type=\"video/mp4\" autoplay>')\n})\n\nconst svgpath = join(relpath, 'test.svg')\n\ntest('[svg]', () => {\n  const html = renderLines([`[svg ${svgpath}]`])\n  expect(html).toBe('<svg class=\"icon\"/>')\n})\n\ntest('[svg] nested in [button]', () => {\n  const html = renderLines(['[button href=\"/\"]', `  [svg ${svgpath}] *Yo*`])\n  expect(html).toBe('<a href=\"/\" role=\"button\"><svg class=\"icon\"/> <em>Yo</em></a>')\n})\n\n\ntest('[define]', () => {\n  const content = [\n    '[define]',\n    '  ## Design System { #ds.foo }',\n    '  Design system is...',\n\n    '  ## Content First { #cf }',\n    '  Content first is...'\n  ]\n\n  const html = renderLines(content)\n  expect(html).toStartWith('<dl><dt class=\"foo\"><a name=\"^ds\">')\n  expect(html).toInclude('Design System</a></dt><dd>')\n  expect(html).toEndWith('<dd><p>Content first is...</p></dd></dl>')\n\n})\n"
  },
  {
    "path": "packages/nueserver/Makefile",
    "content": "\nrun-tests:\n\tbun test"
  },
  {
    "path": "packages/nueserver/README.md",
    "content": "\n# Nueserver: Edge first development\nNueserver is an HTTP server built for edge deployment. Write code locally, deploy globally when ready.\n\n> **Disclaimer** Nueserver currently works for local development only. It's the foundation for Nue's upcoming backend vision. See the [roadmap](https://nuejs.org/docs/roadmap) for details\n\n## What is edge first\nMost web frameworks treat edge deployment as an afterthought. You develop with Node locally, then discover your code doesn't work at the edge. You build with traditional databases, then learn edge can't maintain connections.\n\nEvery Next.js developer knows this journey:\n\n**NPM everywhere** - Your `npm run dev` spins up a Node.js server. Full runtime, any npm package, unlimited memory. You install packages freely: bcrypt, sharp, mongoose.\n\n**Database connections** - You connect to Postgres or MySQL. Connection pooling handles load. Prisma makes queries elegant. Your `.env.local` has `DATABASE_URL` pointing to localhost.\n\n**Production reality** - You deploy and things break. Replace bcrypt with Web Crypto? Swap sharp for browser-compatible alternatives? Abandon your ORM for raw SQL? Set up Postgres edge proxies?\n\nThe problem: you develop one way and deploy another. Your simple app turns complex. Nueserver flips this. Your development environment uses edge-compatible patterns from day one.\n\n\n## How it works\nNueserver provides a simple HTTP server with global route handlers. No classes, no imports, no server setup:\n\n```javascript\nget('/api/users', async (c) => {\n  const users = await c.env.users.getAll()\n  return c.json(users)\n})\n\npost('/api/users', async (c) => {\n  const data = await c.req.json()\n  const user = await c.env.users.create(data)\n  return c.json(user, 201)\n})\n\nuse('/admin/*', async (c, next) => {\n  const auth = c.req.header('authorization')\n  if (!auth) return c.json({ error: 'Unauthorized' }, 401)\n  await next()\n})\n```\n\nCloudFlare headers are mocked locally for edge-compatible development:\n\n```javascript\npost('/api/contact', async (c) => {\n  const country = c.req.header('cf-ipcountry')\n  const ip = c.req.header('cf-connecting-ip')\n\n  const data = await c.req.json()\n  return c.json({ ...data, country, ip })\n})\n```\n\nWhen deployment arrives, these headers provide real geolocation and network data at the edge.\n\n\n## Design principles\nNueserver draws inspiration from Hono's clean API while with the follwing differences:\n\n**Global methods** - No Hono imports or server exports. Use `get()`, `post()`, or `use()` directly on the code.\n\n**No HTML responses** - Use `c.json()` and `c.text()` only. HTML generation belongs in the frontend.\n\n**No file serving** - Static assets are handled by the build system, not the server layer. Each concern stays in its domain.\n\n**No complex routing** - Simple patterns that map to CloudFlare's routing capabilities. No regex routes, no complex parameter validation.\n\n**No middleware chaining complexity** - Linear middleware execution with explicit `next()` calls. Predictable flow, easy debugging.\n\nThis focused API makes the server layer predictable and portable. Your HTTP logic stays clean while other layers handle their specific concerns.\n\n## Installation\n\nFor real projects, use Nuekit for the full development experience:\n\n```bash\nbun install --global nuekit\n```\n\nOr install Nueserver directly as a library:\n\n```bash\nbun install nue-edgeserver\n```\n\nSee the [Server API reference](https://nuejs.org/docs/server-api) for complete routing and context documentation.\n\n"
  },
  {
    "path": "packages/nueserver/nueserver.js",
    "content": "\n// Nueserver: Edge-first HTTP server\nexport const routes = []\n\n// Context for each request\nfunction createContext(req, env = {}) {\n  const url = new URL(req.url)\n\n  const contextReq = {\n    ...req,\n    param: (key) => contextReq._params?.[key],\n    query: (key) => {\n      if (key) return url.searchParams.get(key)\n      const params = {}\n      for (const [k, v] of url.searchParams.entries()) params[k] = v\n      return params\n    },\n    json: () => req.json(),\n    text: () => req.text(),\n    header: (key) => req.headers.get(key)\n  }\n\n  return {\n    req: contextReq,\n    env,\n    json: (data, status = 200) => Response.json(data, { status }),\n    text: (text, status = 200) => new Response(text, { status }),\n    status: status => ({ json: data => Response.json(data, { status }) })\n  }\n}\n\n// Check if a route matches this method and path\nexport function matches(method, path) {\n  for (const route of routes) {\n\n    // middleware == no method\n    if (!route.method || route.method == method.toUpperCase()) {\n      const { match } = matchPath(route.path, path)\n      if (match) return true\n    }\n  }\n\n  return false\n}\n\nexport function matchPath(pattern, path) {\n  if (pattern == '*') return { match: true, params: {} }\n\n  const parts = pattern.split('/')\n  const pathParts = path.split('/')\n\n  // Handle trailing wildcard\n  const hasWildcard = parts[parts.length - 1] == '*'\n  const len = hasWildcard ? parts.length - 1 : parts.length\n\n  // wildcard -> path must be LONGER than pattern (minus *)\n  // exact match -> lengths must be equal\n  if (hasWildcard ? pathParts.length <= len : pathParts.length != len) {\n    return { match: false }\n  }\n\n  const params = {}\n\n  for (let i = 0; i < len; i++) {\n    const patternPart = parts[i]\n    const pathPart = pathParts[i]\n\n    if (patternPart.startsWith(':')) {\n      params[patternPart.slice(1)] = pathPart\n    } else if (patternPart != pathPart) {\n      return { match: false }\n    }\n  }\n\n  return { match: true, params }\n}\n\nglobalThis.get = (path, handler) => {\n  routes.push({ method: 'GET', path, handler })\n}\n\nglobalThis.post = (path, handler) => {\n  routes.push({ method: 'POST', path, handler })\n}\n\nglobalThis.del = (path, handler) => {\n  routes.push({ method: 'DELETE', path, handler })\n}\n\nglobalThis.use = (path, handler) => {\n  if (typeof path == 'function') {\n    handler = path\n    path = '*'\n  }\n  routes.push({ path, handler })\n}\n\n// Main request handler\nexport async function fetch(request, env = {}) {\n  const url = new URL(request.url)\n  const method = request.method\n  const path = url.pathname\n\n  const context = createContext(request, env)\n\n  try {\n\n    // process all routes in order\n    for (const route of routes) {\n\n      if (route.method && route.method != method) continue\n\n      const { match, params } = matchPath(route.path, path)\n      if (!match) continue\n\n      context.req._params = params\n\n      if (!route.method) {\n        // Middleware: call with next() function\n        const result = await route.handler(context, () => Promise.resolve())\n        if (result instanceof Response) return result\n\n      } else {\n        // Regular route: call directly\n        const result = await route.handler(context)\n        if (result instanceof Response) return result\n      }\n    }\n\n    return new Response('Not Found', { status: 404 })\n\n  } catch (error) {\n    console.error('Server error:', error)\n    return new Response('Internal Server Error', { status: 500 })\n  }\n}\n\n\n// Auto-export for CloudFlare Workers\nexport default { fetch }\n"
  },
  {
    "path": "packages/nueserver/package.json",
    "content": "{\n  \"name\": \"nue-edgeserver\",\n  \"version\": \"0.1.0\",\n  \"description\": \"Edge first server development\",\n  \"homepage\": \"https://nuejs.org/docs/nueserver\",\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n\n  \"main\": \"nueserver\",\n  \"files\": [ \"nueserver.js\" ],\n\n  \"repository\": {\n    \"url\": \"https://github.com/nuejs/nue\",\n    \"directory\": \"packages/nueserver\",\n    \"type\": \"git\"\n  }\n}\n"
  },
  {
    "path": "packages/nueserver/test/route.test.js",
    "content": "\nimport { matchPath } from '..'\n\nconst cases = [\n  // Basic matching\n  ['/', '/', true, {}],\n  ['/users', '/users', true, {}],\n  ['/users', '/posts', false],\n\n  // Parameters\n  ['/users/:id', '/users/123', true, { id: '123' }],\n  ['/users/:id/posts/:postId', '/users/123/posts/456', true, { id: '123', postId: '456' }],\n  ['/api/users/:id/profile', '/api/users/123/profile', true, { id: '123' }],\n\n  // Length mismatches\n  ['/users/:id', '/users/123/extra', false],\n\n  // Global wildcard\n  ['*', '/any/path/here', true, {}],\n\n  // Path wildcards\n  ['/admin/*', '/admin/users', true, {}],\n  ['/admin/*', '/admin/users/123/profile', true, {}],\n  ['/admin/*', '/public/users', false],\n  ['/admin/*', '/admin', false],\n  ['/users/:id/*', '/users/123/posts/456', true, { id: '123' }]\n]\n\ncases.forEach(([pattern, path, shouldMatch, expectedParams = {}]) => {\n  const name = shouldMatch\n    ? `${pattern} matches ${path}${Object.keys(expectedParams).length ? ` → ${JSON.stringify(expectedParams)}` : ''}`\n    : `${pattern} rejects ${path}`\n\n  test(name, () => {\n    const result = matchPath(pattern, path)\n\n    if (shouldMatch) {\n      expect(result).toEqual({ match: true, params: expectedParams })\n    } else {\n      expect(result).toEqual({ match: false })\n    }\n  })\n})"
  },
  {
    "path": "packages/nueserver/test/server.test.js",
    "content": "import { fetch, routes } from '..'\n\n// clear routes\nafterAll(() => routes.length = 0)\n\ntest('GET', async () => {\n  get('/users', (c) => {\n    return c.json({ users: ['alice', 'bob'] })\n  })\n\n  const res = await fetch(new Request('http://localhost/users'))\n  const data = await res.json()\n\n  expect(res.status).toBe(200)\n  expect(data.users).toEqual(['alice', 'bob'])\n})\n\ntest('POST', async () => {\n  post('/api/users', async (c) => {\n    const body = await c.req.json()\n    return c.json({ created: body.name })\n  })\n\n  const res = await fetch(new Request('http://localhost/api/users', {\n    method: 'POST',\n    body: JSON.stringify({ name: 'charlie' }),\n    headers: { 'content-type': 'application/json' }\n  }))\n  const data = await res.json()\n\n  expect(data.created).toBe('charlie')\n})\n\ntest('route params', async () => {\n  get('/users/:id', (c) => {\n    const id = c.req.param('id')\n    return c.json({ userId: id })\n  })\n\n  const res = await fetch(new Request('http://localhost/users/123'))\n  const data = await res.json()\n\n  expect(data.userId).toBe('123')\n})\n\ntest('middleware', async () => {\n  use('/admin/*', (c, next) => {\n    if (!c.req.header('authorization')) {\n      return c.json({ error: 'Unauthorized' }, 401)\n    }\n    return next()\n  })\n\n  get('/admin/users', (c) => {\n    return c.json({ admin: true })\n  })\n\n  // Without auth header\n  let res = await fetch(new Request('http://localhost/admin/users'))\n  expect(res.status).toBe(401)\n\n  // With auth header\n  res = await fetch(new Request('http://localhost/admin/users', {\n    headers: { authorization: 'Bearer token' }\n  }))\n  expect(res.status).toBe(200)\n})\n\ntest('404', async () => {\n  const res = await fetch(new Request('http://localhost/nonexistent'))\n  expect(res.status).toBe(404)\n})"
  },
  {
    "path": "packages/nuestate/Makefile",
    "content": "\nrun-tests:\n\tcd test && bun test"
  },
  {
    "path": "packages/nuestate/README.md",
    "content": "\n# Nuestate: URL-first state management\nNuestate puts your application state in the URL by default. This makes bookmarking, sharing, and browser navigation work naturally without extra code. State changes automatically update the URL and trigger component re-renders.\n\n## Why URL-first?\nMost state management solutions treat the URL as an afterthought. You have to manually sync state with the URL, handle browser navigation, and write extra code for bookmarking and sharing.\n\nWith Nuestate your state lives in the URL by default, so these features work automatically:\n\n- **Bookmarking** - Users can bookmark any application state and return to it later\n- **Sharing** - Send someone a URL and they see exactly what you see\n- **Browser navigation** - Back/forward buttons navigate through state changes\n- **Standard routing** - `<a href>` tags become SPA navigation with `autolink`\n- **No sync code** - No need to manually keep URL and state in sync\n\n\n## How it works\n\nImport and use the state object anywhere in your application:\n\n```javascript\nimport { state } from 'state'\n\n// Read and write state\nstate.view = 'users'     // URL updates to include view=users\nstate.search = 'john'    // URL becomes ?view=users&search=john\n```\n\nConfigure where different pieces of state should live:\n\n```javascript\nstate.setup({\n  route: '/app/:section/:id',\n  query: ['search', 'filter', 'page'],\n  session: ['user', 'preferences'],\n  local: ['theme', 'language']\n})\n\n// Route parameters update the URL path\nstate.section = 'products'\nstate.id = '123'\n// URL becomes: /app/products/123\n\n// Query parameters update the URL search\nstate.search = 'shoes'\n// URL becomes: /app/products/123?search=shoes\n```\n\nListen to state changes:\n\n```javascript\nstate.on('search filter', async (changes) => {\n  const results = await fetchResults(changes.search, changes.filter)\n  state.results = results\n})\n```\n\nUse state directly in components with standard DOM events:\n\n```html\n<input value=\"{ state.search }\" :oninput=\"state.search = $event.target.value\">\n```\n\nSee the [State API documentation](https://nuejs.org/docs/state-api) for complete details on all methods and configuration options.\n\n"
  },
  {
    "path": "packages/nuestate/package.json",
    "content": "{\n  \"name\": \"nuestate\",\n  \"version\": \"0.1.1\",\n  \"description\": \"URL-first state management\",\n  \"homepage\": \"https://nuejs.org/docs/nuestate\",\n  \"main\": \"src/state.js\",\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"files\": [ \"src\" ],\n\n  \"repository\": {\n    \"url\": \"https://github.com/nuejs/nue\",\n    \"directory\": \"packages/nuestate\",\n    \"type\": \"git\"\n  }\n}\n"
  },
  {
    "path": "packages/nuestate/src/state.js",
    "content": "\nif (typeof global == 'object') global.window = null\n\nconst CONTEXTS = ['path_params', 'query', 'session', 'local', 'emit_only', 'memory']\n\nlet memory = {}\nlet fns = []\nlet opts\n\nexport const api = {\n\n  setup(args={}) {\n    const { route='', query=[], memory=[] } = args\n    opts = {...args, path_params: getRouteParams(route), route, query, memory }\n    if (window) {\n      if (opts.autolink) autolink()\n      addEventListener('popstate', onpopstate)\n    }\n  },\n\n  get data() {\n    return translate(!window ? memory : {\n      ...getStoreData(sessionStorage),\n      ...getStoreData(localStorage),\n      ...getURLData(location),\n      ...memory\n    })\n  },\n\n  on(names, fn) {\n    const i = getFnIndex(names, fn)\n    fns[i >= 0 ? i : fns.length] = { names, fn }\n  },\n\n  off(names, fn) {\n    const i = getFnIndex(names, fn)\n   if (i >= 0) fns.splice(i, 1)\n  },\n\n  emit(name, val) {\n    if (opts.emit_only?.includes(name)) {\n      fire({ [name]: val })\n    }\n  },\n\n  set(data, is_popstate) {\n    const { changes, at } = getChanges(this.data, data)\n\n    if (at.size) {\n      save(changes)\n      fire(changes)\n      if (window && !is_popstate) pushURLState(at, { ...api.data, ...changes })\n    }\n  },\n\n  init() {\n    fire(getURLData(location))\n  },\n\n  clear() {\n    memory = {}\n    fns = []\n  },\n}\n\n\n// proxy\nexport const state = new Proxy(api, {\n  get(api, prop) {\n    return api[prop] || api.data[prop]\n  },\n\n  set(api, prop, val) {\n    if (api[prop]) console.error(`(fail) cannot override state.${prop}`)\n    api.set({ [prop]: val })\n    return true\n  }\n})\n\n\nfunction translate(obj) {\n  for (const key in obj) {\n    const val = obj[key]\n    if (val == null) delete obj[key]\n    else if (val == 'true') obj[key] = true\n    else if (val == 'false') obj[key] = false\n    else if (typeof val == 'string' && val != '' && !isNaN(+val)) obj[key] = +val\n  }\n  return obj\n}\n\nfunction pushURLState(at, data) {\n  const search = opts.query[0] ? renderQuery(opts.query, data) : ''\n\n  if (at.has('path_params')) {\n    history.pushState(true, 0, renderPath(opts.route, data) + search)\n\n  } else if (at.has('query')) {\n    history.replaceState(true, 0, search || './')\n  }\n}\n\nfunction onpopstate({ state }) {\n  state ? api.set(getURLData(location), true) : api.init()\n}\n\n\n// start automatic linking\nfunction autolink(root=document) {\n\n  // clicks\n  root.addEventListener('click', e => {\n    const link = e.target.closest('a[href]')\n    if (!link || e.defaultPrevented || e.metaKey || e.ctrlKey || !getPathData(opts.route, link.pathname)) return\n    api.set(getURLData(link))\n    e.preventDefault()\n  })\n}\n\nfunction getChanges(orig, data) {\n  const at = new Set()\n  const changes = {}\n\n  for (const key in data) {\n    for (const ctx of CONTEXTS) {\n      if (opts[ctx]?.includes(key) && orig[key] !== data[key]) {\n        changes[key] = data[key]\n        at.add(ctx)\n      }\n    }\n  }\n  return { changes, at }\n}\n\nfunction fire(changes) {\n  for (const el of fns) {\n    if (el.names.split(' ').some(name => name in changes)) {\n      el.fn(changes)\n    }\n  }\n}\n\n\nfunction save(changes) {\n  for (const key in changes) {\n    const val = changes[key]\n\n    if (opts.session?.includes(key)) {\n      setStoreValue(sessionStorage, key, val)\n\n    } else if (opts.local?.includes(key)) {\n      setStoreValue(localStorage, key, val)\n\n    } else if ([...opts.memory, ...opts.path_params, ...opts.query].includes(key)) {\n      memory[key] = val\n    }\n  }\n}\n\n\nfunction getFnIndex(names, fn) {\n  return fns.findIndex(el => el.names == names && el.fn.toString() == fn.toString())\n}\n\nfunction getRouteParams(route) {\n  return route.split('/').filter(el => el[0] == ':').map(el => el.slice(1))\n}\n\nexport function getPathData(route, pathname) {\n  const tokens = route.split('/')\n  const els = pathname.split('/')\n  const data = {}\n\n  for (let i = 1; i < tokens.length; i++) {\n    const token = tokens[i]\n    const el = els[i]\n\n    if (token[0] == ':') { data[token.slice(1)] = el || null }\n    else if (token != el) return\n  }\n  return data\n}\n\nexport function getQueryData(params, search) {\n  const args = new URLSearchParams(search)\n  const data = {}\n\n  params.forEach(key => {\n    data[key] = args.get(key) || null\n  })\n  return data\n}\n\nfunction getURLData({ pathname, search }) {\n  return {\n    ...getPathData(opts.route, pathname),\n    ...getQueryData(opts.query, search),\n  }\n}\n\n\nexport function renderPath(route, data={}) {\n  const els = route.split('/').map(token => token[0] == ':' ? data[token.slice(1)] : token)\n  const i = els.indexOf(undefined)\n  return (i > 0 ? els.slice(0, i + 1) : els).join('/').replace('//', '/')\n}\n\nexport function renderQuery(params, data={}) {\n  const query_data = {}\n\n  params.forEach(function(key) {\n    const val = data[key]\n    if (val) query_data[key] = val\n  })\n\n  const query = new URLSearchParams(query_data)\n  return query.size ? '?' + query : ''\n}\n\n\n// storage\nconst KEY = '$state'\n\nfunction getStoreData(store) {\n  return JSON.parse(store[KEY] || '{}')\n}\n\nfunction setStoreValue(store, key, val) {\n  const data = getStoreData(store)\n  if (data[key] != val) {\n    data[key] = val\n    store[KEY] = JSON.stringify(data)\n    return true\n  }\n}\n"
  },
  {
    "path": "packages/nuestate/test/browser.test.js",
    "content": "import { test, expect, mock } from 'bun:test'\nimport { state } from '../src/state.js'\n\nglobal.sessionStorage = {}\nglobal.localStorage = {}\nglobal.window = {}\n\ntest.skip('click flow', () => {\n\n  // capture handler\n  let clickHandler\n  const addEventListener = mock((event, handler) => {\n    if (event == 'click') clickHandler = handler\n  })\n\n  // click\n  function click(pathname) {\n    clickHandler({\n      target: { closest: () => ({ pathname }) },\n      preventDefault: mock()\n    })\n  }\n\n  // mockups\n  global.document = { addEventListener }\n  global.location = { pathname: '/app', search: '' }\n  global.history = { pushState: (state, _, path) => location.pathname = path }\n\n  state.setup({ route: '/app/:view/:id', autolink: true })\n\n  let count = 0\n\n  state.on('view id', () => count++)\n\n  // click\n  click('/app/users/123')\n  expect(state.view).toBe('users')\n  expect(state.data).toMatchObject({ view: 'users', id: 123 })\n  expect(count).toBe(1)\n\n\n  // same click -> no change\n  click('/app/users/123')\n  expect(count).toBe(1)\n\n  // non-matching click -> ignore\n  click('/other/path')\n  expect(count).toBe(1)\n  expect(state.view).toBe('users')\n\n  // id change\n  state.id = 100\n  expect(location.pathname).toBe('/app/users/100')\n  expect(count).toBe(1)\n\n  // view change\n  state.view = 'leads'\n  expect(location.pathname).toBe('/app/leads/100')\n  expect(state.data).toEqual({ view: 'leads', id: 100 })\n  expect(count).toBe(3)\n\n  // truncate id\n  click('/app/leads/')\n  expect(count).toBe(4)\n  expect(state.data).toEqual({ view: 'leads' })\n\n\n})\n\n\ntest.skip('back button', () => {\n  let popstateHandler\n\n  const addEventListener = mock((event, handler) => {\n    if (event == 'popstate') popstateHandler = handler\n  })\n\n  global.addEventListener = addEventListener\n  global.location = { pathname: '/app/users/123', search: '?expand=true' }\n  global.history  = { pushState: mock() }\n\n  state.setup({ route: '/app/:view/:id', query: ['expand'] })\n\n  expect(state.data).toEqual({ view: 'users', id: 123, expand: 'true' })\n\n  // simulate popstate with valid history state\n  location.pathname = '/app/users/126'\n  location.search = ''\n  popstateHandler({ state: { pathname: location.pathname } })\n\n  expect(state.data).toEqual({ view: 'users', id: 126 })\n})\n\n"
  },
  {
    "path": "packages/nuestate/test/index.html",
    "content": "\n<p>\n  <a href=\"/test/\">Home</a>\n</p>\n\n<p>\n  <a href=\"/test/customers/\">/customers/</a>\n  <a href=\"/test/users/\">/users/</a>\n  <a href=\"/test/users/100\">/users/100</a>\n  <a href=\"3000\">3000</a>\n</p>\n\n<p>\n  <a href=\"?start=0\">?start=0</a>\n  <a href=\"?start=10\">?start=10</a>\n  <a href=\"?uid=2000\">?uid=2000</a>\n</p>\n\n<p>\n  <a href=\"/something/else\">/something/else</a>\n</p>\n\n<script type=\"module\">\n  import { state } from '/src/state.js'\n\n  state.setup({\n    route: '/test/:type/:id',\n    query: ['uid', 'start'],\n    autolink: true\n  })\n\n  state.on('type start', changes => {\n    console.info('type start', changes)\n  })\n\n  state.on('id uid', changes => {\n    console.info('id uid', changes)\n  })\n\n  state.init()\n\n</script>"
  },
  {
    "path": "packages/nuestate/test/state.test.js",
    "content": "\nimport { getPathData, getQueryData, renderPath, renderQuery, api, state } from '../src/state.js'\n\nbeforeAll(() => global.window = null)\n\ndescribe('internal methods', () => {\n\n  test('get path data', () => {\n    const route = '/app/:type/:id'\n    expect(getPathData(route, '/app/leads/389/bang')).toEqual({ type: 'leads', id: '389' })\n    expect(getPathData(route, '/app/users/89')).toEqual({ type: 'users', id: '89' })\n    expect(getPathData(route, '/admin/users')).toBeUndefined()\n  })\n\n\n  test('get query data', () => {\n    const params = ['start', 'length']\n    expect(getQueryData(params, '?start=10&length=20&foo=bar'))\n      .toEqual({ start: '10', length: '20' })\n  })\n\n  test('renderPath', () => {\n    const route = '/app/:view/:id'\n    expect(renderPath(route, {})).toBe('/app/')\n    expect(renderPath(route, { id: 100 })).toBe('/app/')\n    expect(renderPath(route, { view: 'people', id: null })).toBe('/app/people/')\n    expect(renderPath(route, { view: 'people', id: 30 })).toBe('/app/people/30')\n  })\n\n\n  test('renderQuery', () => {\n    const data = { q: 'john', start: 10, foo: 1 }\n    expect(renderQuery(['q', 'start'], data)).toBe('?q=john&start=10')\n    expect(renderQuery([], data)).toBe('')\n  })\n\n})\n\ndescribe('event emitting', () => {\n\n  beforeEach(() => api.clear())\n\n  test('on', () => {\n    api.setup({ memory: ['foo', 'bar'] })\n\n    let count = 0\n    api.on('foo', () => count++)\n    api.on('foo bar', () => count++)\n    api.on('bar', () => count++)\n\n    api.set({ foo: true })\n    api.set({ bar: true })\n\n    expect(count).toBe(4)\n    expect(api.data).toEqual({ foo: true, bar: true })\n  })\n\n  test('duplicates', () => {\n    api.setup({ memory: ['foo'] })\n\n    let count = 0\n    api.on('foo', () => count++)\n    api.on('foo', () => count++)\n    api.on('foo bar', () => count++)\n\n    api.set({ foo: true })\n    api.set({ foo: true })\n\n    expect(count).toBe(2)\n  })\n\n  test('emit', () => {\n    api.setup({ emit_only: ['foo'] })\n\n    let count = 0\n    api.on('foo', () => count++)\n    api.set({ foo: true })\n\n    expect(count).toBe(1)\n  })\n\n  test('batch', () => {\n    api.setup({ memory: ['foo', 'bar'] })\n\n    let count = 0\n    api.on('foo bar', () => count++)\n    api.set({ foo: true, bar: true })\n\n    expect(count).toBe(1)\n  })\n\n  test('state', () => {\n    state.setup({ emit_only: ['foo'], memory: ['bar'] })\n\n    let count = 0\n    state.on('foo', () => count++)\n    state.foo = true\n    state.bar = true\n    state.on = false\n\n    expect(count).toBe(1)\n    expect(state.data).toEqual({ bar: true })\n  })\n\n\n})\n"
  },
  {
    "path": "packages/nueyaml/Makefile",
    "content": "\ntest:\n\tbun test"
  },
  {
    "path": "packages/nueyaml/README.md",
    "content": "# Nueyaml: YAML without the problems\nNueyaml is YAML stripped down to its essence so you can write complex configurations without the usual YAML pitfalls.\n\n\n## The problem with standard YAML\nThe original YAML specification buried a beautiful idea under 80 pages of features that cause more problems than they solve. It guesses what you mean, often guessing wrong:\n\n```yaml\n# YAML surprises\ncountry: NO      # may become false (Norway problem)\ntime: 12:30      # may become 750 (minutes)\nversion: 1.10    # may become 1.1 (float)\nport: 08080      # may become 4176 (octal)\n```\n\nThese \"conveniences\" turn configuration files into minefields. You quote some values defensively, but not others. You remember some gotchas, but not all. Your configuration works until it doesn't.\n\n\n## How Nueyaml fixes it\nNueyaml has one rule: be predictable. If it looks like a string to a human, it's a string:\n\n```yaml\n# Nueyaml: No surprises\ncountry: NO       # string \"NO\"\ntime: 12:30       # string \"12:30\"\nversion: 1.10     # number 1.10\nport: 08080       # string \"08080\"\n```\n\nOnly obvious numbers (`123`, `45.67`) become numbers. Only `true` and `false` become booleans. Everything else stays a string.\n\nLearn the details from [Nue website](https://nuejs.org/docs/nueyaml)"
  },
  {
    "path": "packages/nueyaml/nueyaml.js",
    "content": "\nexport function stripComments(line) {\n  // Full line comment\n  if (line.trim().startsWith('#')) return ''\n\n  // Inline comment (# preceded by whitespace)\n  const match = line.match(/\\s#/)\n  if (!match) return line\n  return line.substring(0, match.index)\n}\n\nexport function measureIndent(line) {\n  let indent = 0\n  for (let char of line) {\n    if (char == ' ') indent++\n    else break\n  }\n  return indent\n}\n\nexport function detectIndentSize(lines) {\n  for (let line of lines) {\n    const stripped = stripComments(line)\n    if (stripped.trim() == '') continue\n\n    const indent = measureIndent(stripped)\n    if (indent > 0) return indent\n  }\n  return 2 // default to 2 spaces\n}\n\nexport function validateIndentation(lines) {\n  // Check for tabs in indentation (beginning of line only)\n  for (let i = 0; i < lines.length; i++) {\n    const line = stripComments(lines[i])\n    const leadingWhitespace = line.match(/^[\\s]*/)[0]\n    if (leadingWhitespace.includes('\\t')) {\n      throw new Error(`Tabs not allowed for indentation. Use spaces only. Line ${i + 1}`)\n    }\n  }\n\n  const indentSize = detectIndentSize(lines)\n  const indentLevels = new Set()\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = stripComments(lines[i])\n    if (line.trim() == '') continue\n\n    const indent = measureIndent(line)\n    if (indent > 0) {\n      indentLevels.add(indent)\n    }\n  }\n\n  // Check that all indentation levels are multiples of the base indent size\n  for (let level of indentLevels) {\n    if (level % indentSize != 0) {\n      throw new Error(`Inconsistent indentation. Expected multiples of ${indentSize} spaces.`)\n    }\n  }\n\n  return indentSize\n}\n\nexport function isNumber(str) {\n  if (str == '' || str == '-' || str == '+' || str[0] == '0') return false\n  return /^-?\\d+(\\.\\d+)?$/.test(str)\n}\n\nexport function parseValue(raw) {\n  const val = raw.trim()\n\n  if (val == '') return null\n  if (val == 'true') return true\n  if (val == 'false') return false\n  if (isNumber(val)) return parseFloat(val)\n\n  // Unwrap quoted strings\n  if ((val.startsWith('\"') && val.endsWith('\"')) ||\n      (val.startsWith(\"'\") && val.endsWith(\"'\"))) {\n    return val.slice(1, -1)\n  }\n\n  // ISO date format\n  if (/^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}Z)?$/.test(val)) {\n    return new Date(val)\n  }\n\n  return val\n}\n\n// cannot be made public. requires prop name \"foo: [1, a, b]\"\nexport function parseYAMLArray(line) {\n  const match = line?.match(/^\\s*\\w+\\s*:\\s*\\[(.*)\\]$/)\n  if (!match) return null\n  if (match[1].trim() == '') return []\n  const items = match[1].split(',').map(item => parseValue(item))\n  return items\n}\n\nexport function detectStructure(lines) {\n  const blocks = []\n\n  for (let i = 0; i < lines.length; i++) {\n    const line = stripComments(lines[i])\n    const trimmed = line.trim()\n\n    if (trimmed == '') continue\n\n    const indent = measureIndent(line)\n\n    // Check for array item first\n    if (trimmed.startsWith('- ')) {\n      const content = trimmed.slice(2).trim()\n      const colonIndex = content.indexOf(': ')\n\n      if (colonIndex > 0) {\n        // Array item with key-value: - key: value\n        blocks.push({\n          type: 'arrayitem',\n          key: content.slice(0, colonIndex),\n          value: content.slice(colonIndex + 2),\n          indent,\n          lineIndex: i\n        })\n      } else {\n        // Simple array item: - value\n        blocks.push({\n          type: 'arrayitem',\n          value: parseValue(content),\n          indent,\n          lineIndex: i\n        })\n      }\n      continue\n    }\n\n    // Check for key-value pair\n    const colonSpaceIndex = trimmed.indexOf(': ')\n    const colonIndex = trimmed.indexOf(':')\n\n    if (colonIndex > 0) {\n      const useSpace = colonSpaceIndex > 0\n      const splitIndex = useSpace ? colonSpaceIndex : colonIndex\n      const offset = useSpace ? 2 : 1\n\n      blocks.push({\n        type: 'keyvalue',\n        key: trimmed.slice(0, splitIndex),\n        value: trimmed.slice(splitIndex + offset),\n        indent,\n        lineIndex: i\n      })\n      continue\n    }\n\n    // Multi-line string continuation\n    blocks.push({\n      type: 'multiline',\n      value: trimmed,\n      indent,\n      lineIndex: i\n    })\n  }\n\n  return blocks\n}\n\nexport function buildObject(blocks) {\n  const result = {}\n  let i = 0\n\n  while (i < blocks.length) {\n    const block = blocks[i]\n\n    if (block.type == 'keyvalue') {\n      let value\n\n      // Check for inline array\n      if (block.value != '') {\n        const arrayItems = parseYAMLArray(block.key + ': ' + block.value)\n        if (arrayItems) {\n          value = arrayItems\n        } else {\n          value = parseValue(block.value)\n        }\n      } else {\n        // Look ahead for children\n        const children = []\n        let j = i + 1\n\n        while (j < blocks.length && blocks[j].indent > block.indent) {\n          children.push(blocks[j])\n          j++\n        }\n\n        if (children.length > 0) {\n          // Check if first child is array item\n          if (children[0].type == 'arrayitem') {\n            value = []\n            let k = 0\n\n            while (k < children.length) {\n              const child = children[k]\n\n              if (child.type == 'arrayitem' && child.indent == children[0].indent) {\n                // Check if array item has a key (object) or just a value\n                if ('key' in child) {\n                  // Array item with object content: - name: value\n                  const itemObj = {}\n                  itemObj[child.key] = parseValue(child.value)\n\n                  // Look for additional properties of this array item\n                  let m = k + 1\n                  while (m < children.length && children[m].indent > child.indent) {\n                    const nestedChild = children[m]\n\n                    if (nestedChild.type == 'keyvalue') {\n                      // Handle nested key-value pairs\n                      if (nestedChild.value != '') {\n                        // Check for inline array in nested property\n                        const nestedArrayItems = parseYAMLArray(nestedChild.key + ': ' + nestedChild.value)\n                        if (nestedArrayItems) {\n                          itemObj[nestedChild.key] = nestedArrayItems\n                        } else {\n                          itemObj[nestedChild.key] = parseValue(nestedChild.value)\n                        }\n                      } else {\n                        // Look for children of this nested key\n                        const nestedChildren = []\n                        let n = m + 1\n\n                        while (n < children.length && children[n].indent > nestedChild.indent) {\n                          nestedChildren.push(children[n])\n                          n++\n                        }\n\n                        if (nestedChildren.length > 0 && nestedChildren[0].type == 'arrayitem') {\n                          // Nested array\n                          const nestedArray = []\n                          for (let nestedArrayChild of nestedChildren) {\n                            if (nestedArrayChild.type == 'arrayitem' && nestedArrayChild.indent == nestedChildren[0].indent) {\n                              if ('key' in nestedArrayChild) {\n                                const nestedItemObj = {}\n                                nestedItemObj[nestedArrayChild.key] = parseValue(nestedArrayChild.value)\n                                nestedArray.push(nestedItemObj)\n                              } else {\n                                nestedArray.push(nestedArrayChild.value)\n                              }\n                            }\n                          }\n                          itemObj[nestedChild.key] = nestedArray\n                          m = n - 1 // Skip processed nested children\n                        } else {\n                          itemObj[nestedChild.key] = null\n                        }\n                      }\n                    }\n                    m++\n                  }\n\n                  value.push(itemObj)\n                  k = m\n                } else {\n                  // Simple array item: - value\n                  value.push(child.value)\n                  k++\n                }\n              } else {\n                k++\n              }\n            }\n          }\n          // Check if first child is multiline\n          else if (children[0].type == 'multiline') {\n            value = children.map(c => c.value).join('\\n')\n          }\n          // Otherwise it's a nested object\n          else {\n            value = buildObject(children)\n          }\n\n          i = j - 1 // Skip processed children\n        } else {\n          value = null\n        }\n      }\n\n      result[block.key] = value\n    }\n\n    i++\n  }\n\n  return result\n}\n\nexport function parseYAML(text) {\n  const lines = text.split('\\n')\n  validateIndentation(lines)\n  const blocks = detectStructure(lines)\n  return buildObject(blocks)\n}"
  },
  {
    "path": "packages/nueyaml/nueyaml.test.js",
    "content": "import {\n  stripComments,\n  measureIndent,\n  isNumber,\n  parseValue,\n  parseYAMLArray,\n  detectStructure,\n  buildObject,\n  validateIndentation,\n  parseYAML\n} from '.'\n\n// stripComments tests\ntest('no comments', () => {\n  expect(stripComments('key: value')).toBe('key: value')\n})\n\ntest('line comment', () => {\n  expect(stripComments('key: value # comment')).toBe('key: value')\n})\n\ntest('full comment', () => {\n  expect(stripComments('# full comment')).toBe('')\n})\n\ntest('hash in string', () => {\n  expect(stripComments('key: value#notcomment')).toBe('key: value#notcomment')\n})\n\n// measureIndent tests\ntest('measureIndent', () => {\n  expect(measureIndent('key: value')).toBe(0)\n  expect(measureIndent('  key: value')).toBe(2)\n  expect(measureIndent('    key: value')).toBe(4)\n})\n\n// validateIndentation tests\ntest('valid indentation', () => {\n  expect(() => validateIndentation(['  key: value'])).not.toThrow()\n  expect(() => validateIndentation(['  key:', '    nested: value'])).not.toThrow()\n})\n\ntest('invalid indentation', () => {\n  expect(() => validateIndentation(['\\tkey: value'])).toThrow('Tabs not allowed for indentation')\n  expect(() => validateIndentation(['key: \"value\\twith\\ttab\"'])).not.toThrow()\n  expect(() => validateIndentation(['  key:', '   bad: value'])).toThrow('Inconsistent indentation')\n})\n\n// isNumber tests\ntest('valid numbers', () => {\n  expect(isNumber('42')).toBe(true)\n  expect(isNumber('3.14')).toBe(true)\n  expect(isNumber('-42')).toBe(true)\n})\n\ntest('invalid numbers', () => {\n  expect(isNumber('hello')).toBe(false)\n  expect(isNumber('00800')).toBe(false)\n  expect(isNumber('')).toBe(false)\n  expect(isNumber('-')).toBe(false)\n})\n\n// parseValue tests\ntest('types', () => {\n  expect(parseValue('hello')).toBe('hello')\n  expect(parseValue('42')).toBe(42)\n  expect(parseValue('true')).toBe(true)\n  expect(parseValue('false')).toBe(false)\n  expect(parseValue('')).toBe(null)\n})\n\ntest('quoted strings', () => {\n  expect(parseValue('\"hello\"')).toBe('hello')\n  expect(parseValue(\"'hello'\")).toBe('hello')\n  expect(parseValue('a \"quoted\" word')).toBe('a \"quoted\" word')\n})\n\ntest('dates', () => {\n  expect(parseValue('2024-01-15')).toEqual(new Date('2024-01-15'))\n  expect(parseValue('2024-01-15T10:30:00Z')).toEqual(new Date('2024-01-15T10:30:00Z'))\n})\n\n\n// parseYAMLArray tests\ntest('arrays', () => {\n  expect(parseYAMLArray('tags: [one, two, three]')).toEqual(['one', 'two', 'three'])\n  expect(parseYAMLArray('nums: [1, 2, 3]')).toEqual([1, 2, 3])\n  expect(parseYAMLArray('empty: []')).toEqual([])\n  expect(parseYAMLArray('key: value')).toBeNull()\n})\n\ntest('array looking value', () => {\n  expect(parseYAMLArray('hey [foo]')).toBeNull()\n  expect(parseYAMLArray('hey: \"[foo]\"')).toBeNull()\n  expect(parseYAMLArray('hey: a [foo]')).toBeNull()\n  expect(parseYAMLArray('hey: [foo](/bar)')).toBeNull()\n})\n\n\n\n// detectStructure tests\ntest('basic structures', () => {\n  expect(detectStructure(['name: John'])).toEqual([{\n    type: 'keyvalue', key: 'name', value: 'John', indent: 0, lineIndex: 0\n  }])\n\n  expect(detectStructure(['- apple'])).toEqual([{\n    type: 'arrayitem', value: 'apple', indent: 0, lineIndex: 0\n  }])\n\n  expect(detectStructure(['- name: John'])).toEqual([{\n    type: 'arrayitem', key: 'name', value: 'John', indent: 0, lineIndex: 0\n  }])\n})\n\n\ntest('complex keys', () => {\n  expect(detectStructure(['/app/:view:id: baz'])).toEqual([{\n    type: 'keyvalue', key: '/app/:view:id', value: 'baz', indent: 0, lineIndex: 0\n  }])\n})\n\ntest('multiline and comments', () => {\n  expect(detectStructure(['description:', '  line one', '  line two'])).toEqual([\n    { type: 'keyvalue', key: 'description', value: '', indent: 0, lineIndex: 0 },\n    { type: 'multiline', value: 'line one', indent: 2, lineIndex: 1 },\n    { type: 'multiline', value: 'line two', indent: 2, lineIndex: 2 }\n  ])\n\n  expect(detectStructure(['# comment', 'name: John # inline'])).toEqual([{\n    type: 'keyvalue', key: 'name', value: 'John', indent: 0, lineIndex: 1\n  }])\n})\n\n\n// buildObject tests\ntest('buildObject', () => {\n  expect(buildObject([\n    { type: 'keyvalue', key: 'name', value: 'John', indent: 0 }\n\n  ])).toEqual({ name: 'John' })\n\n  expect(buildObject([\n    { type: 'keyvalue', key: 'items', value: '', indent: 0 },\n    { type: 'arrayitem', value: 'first', indent: 2 },\n    { type: 'arrayitem', value: 'second', indent: 2 }\n\n  ])).toEqual({ items: ['first', 'second'] })\n})\n\ntest('complex structures', () => {\n\n  expect(buildObject([\n    { type: 'keyvalue', key: 'users', value: '', indent: 0 },\n    { type: 'arrayitem', key: 'name', value: 'John', indent: 2 },\n    { type: 'keyvalue', key: 'age', value: '30', indent: 4 },\n    { type: 'arrayitem', key: 'name', value: 'Jane', indent: 2 }\n  ])).toEqual({\n    users: [\n      { name: 'John', age: 30 },\n      { name: 'Jane' }\n    ]\n  })\n})\n\n// Integration tests\ndescribe('parseYAML', () => {\n  test('basic parsing', () => {\n    expect(parseYAML('name: John\\nage: 30')).toEqual({ name: 'John', age: 30 })\n\n    expect(parseYAML('tags: [one, two, three]')).toEqual({ tags: ['one', 'two', 'three'] })\n\n    expect(parseYAML('items:\\n  - first\\n  - second')).toEqual({ items: ['first', 'second'] })\n  })\n\n  test('advanced features', () => {\n    expect(parseYAML('message: \"Hello world\"\\nvalue: \\'single quotes\\'')).toEqual({\n      message: 'Hello world',\n      value: 'single quotes'\n    })\n\n    expect(parseYAML('/api/users/:id: handler\\npassword: secret#123#abc')).toEqual({\n      '/api/users/:id': 'handler',\n      password: 'secret#123#abc'\n    })\n\n    expect(parseYAML('description:\\n  Multi-line\\n  string content')).toEqual({\n      description: 'Multi-line\\nstring content'\n    })\n  })\n\n  test('nested lists', () => {\n    const input = `items:\n  - name: design\n\n    tags: [mies, rams] # eol\n\n    # comment\n    cron:\n      - 10 * * *\n      - 20 * * *`\n\n    const [ item ] = parseYAML(input).items\n\n    expect(item).toEqual({\n      name: \"design\",\n      tags: [ \"mies\", \"rams\" ],\n      cron: [ \"10 * * *\", \"20 * * *\" ],\n    })\n  })\n\n  test('complex structure', () => {\n    const input = `app:\n  name: My App\n  features: [auth, cache]\n\nservers:\n  - name: web-01\n    ip: 192.168.1.1\n  - name: web-02\n    ip: 192.168.1.2`\n\n    expect(parseYAML(input)).toEqual({\n      app: { name: 'My App', features: ['auth', 'cache'] },\n      servers: [\n        { name: 'web-01', ip: '192.168.1.1' },\n        { name: 'web-02', ip: '192.168.1.2' }\n      ]\n    })\n  })\n\n  test('validation errors', () => {\n    expect(() => parseYAML('\\tkey: value')).toThrow('Tabs not allowed for indentation')\n    expect(() => parseYAML('  key:\\n   bad: value')).toThrow('Inconsistent indentation')\n  })\n})"
  },
  {
    "path": "packages/nueyaml/package.json",
    "content": "{\n  \"name\": \"nueyaml\",\n  \"version\": \"0.1.0\",\n  \"description\": \"YAML without the problems\",\n  \"homepage\": \"https://nuejs.org/docs/nueyaml\",\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"main\": \"nueyaml\",\n  \"files\": [ \"nueyaml.js\" ],\n\n  \"repository\": {\n    \"url\": \"https://github.com/nuejs/nue\",\n    \"directory\": \"packages/nueyaml\",\n    \"type\": \"git\"\n  }\n}\n"
  },
  {
    "path": "packages/templates/blog/index.css",
    "content": "\n/* settings  */\n@layer base, components;\n\n@layer base {\n\n  *, *:before, *:after {\n    box-sizing: border-box;\n  }\n\n  /* colors */\n  :root {\n    --base-100: #f5f7f9;\n    --base-200: #E2E8F0;\n    --base-400: #94A3B8;\n    --base-600: #475569;\n    --base-800: #1E293B;\n  }\n\n  /* global layout & styling */\n  body {\n    line-height: 1.5;\n    font-family: system-ui;\n    color: var(--base-600);\n    padding: 1rem 1.5rem;\n    margin: 0;\n\n    /* header */\n    > header {\n      border-bottom: 1px solid var(--base-200);\n      padding-bottom: 1rem;\n      a {\n        text-decoration: none;\n        color: var(--base-800);\n        font-weight: 600;\n      }\n    }\n\n    > main {\n      max-width: 650px;\n      margin: 6rem auto;\n      min-height: calc(100vh - 32rem);\n    }\n\n    /* footer */\n    > footer {\n      text-align: center;\n      border-top: 1px solid var(--base-200);\n      color: var(--base-800);\n      padding-block: 6rem;\n    }\n  }\n\n\n  /* typography */\n  h1, h2, h3, strong {\n    color: var(--base-800);\n  }\n\n  h1 {\n    font-size: 2.5em;\n    font-weight: 800;\n    line-height: 1.125;\n    letter-spacing: -.02em;\n    margin-block: .25em;\n\n    + p {\n      font-size: 1.25em;\n      margin: 0;\n    }\n  }\n\n  h2 {\n    margin-block: 3em -.25em;\n  }\n\n  p {\n    text-wrap: pretty;\n  }\n\n  /* placeholder images */\n  figure {\n    background-color: var(--base-200);\n    margin: 1.5em 0;\n\n    > div {\n      height: var(--height);\n    }\n\n    @media (width > 900px) {\n      &.hero {\n        margin: 3em -15vw;\n      }\n    }\n\n    /* bauhaus coloring */\n    &.yellow  { background-color: #F6C950 }\n    &.blue    { background-color: #2A63B3 }\n    &.red     { background-color: #CB3B32 }\n  }\n\n\n  /* syntax blocks */\n  pre {\n    background-color: var(--base-100);\n    color: #005bff;\n    margin-block: 1em 2em;\n    padding: 2em;\n\n    /* primary accent color */\n    b { color: #ff3aa1; font-weight: normal }\n\n    /* secondary accent color */\n    em { color: #278e4f; font-style: normal; }\n\n    /* special emphasis */\n    strong { color: #00c9ff; font-weight: normal }\n\n    /* brackets, commas, semicolons... */\n    i { color: var(--base-400); font-style: normal; }\n\n    /* comments */\n    sup {\n      opacity: .5;\n      vertical-align: inherit;\n      color: var(--base-600);\n      font-size: inherit;\n    }\n\n    /* highlight */\n    label {\n      font-weight: bold;\n      color: #0069d4;\n    }\n  }\n}\n\n@layer components {\n\n  /* major listings */\n  ul:has(h2, h3) {\n    list-style-type: none;\n    margin-block: 3rem;\n    padding: 0;\n\n    a { text-decoration: none }\n\n    h3, p { margin-block: .25em }\n\n    p { color: var(--base-600) }\n\n    time { color: var(--base-400) }\n\n    li {\n      margin-bottom: 2em;\n      &:hover h3 { text-decoration: underline }\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "packages/templates/blog/index.html",
    "content": "\n<!doctype html>\n\n<article>\n  <h1>Design engineering blog</h1>\n\n  <ul>\n    <li :each=\"page in blog\">\n      <a href=\"{ page.url }\">\n        <p><time>{ formatDate(page.date) }</time></p>\n        <h3>{ page.title }</h3>\n        <p>{{ markdown(page.description) }}</p>\n      </a>\n    </li>\n  </ul>\n\n  <script>\n    formatDate(date) {\n      return new Date(date).toLocaleDateString('en-US', {\n        year: 'numeric',\n        month: 'short',\n        day: 'numeric'\n      })\n    }\n  </script>\n</article>"
  },
  {
    "path": "packages/templates/blog/layout.html",
    "content": "\n<!doctype html lib>\n\n<header>\n  <a href=\"/\">{ author }</a>\n</header>\n\n<footer>\n  © Copyright { new Date().getFullYear() } / { author }\n</footer>"
  },
  {
    "path": "packages/templates/blog/posts/components.html",
    "content": "\n\n<header :is=\"pagehead\">\n  { formatDate(date) }\n\n  <script>\n    formatDate(date) {\n      return new Date(date).toLocaleDateString('en-US', {\n        year: 'numeric',\n        month: 'short',\n        day: 'numeric'\n      })\n    }\n  </script>\n</header>\n\n<figure :is=\"placeholder\" class=\"{ class }\">\n  <div --height=\"{ height }px\"/>\n</figure>"
  },
  {
    "path": "packages/templates/blog/posts/css-is-awesome.md",
    "content": "---\ndate: 2025-08-05\n---\n\n# Modern CSS is awesome\nCSS has transformed dramatically over the past decade, but the JavaScript ecosystem hasn't noticed. While React developers debate CSS-in-JS solutions, the language itself evolved into something unrecognizable from its 2013 limitations.\n\n[placeholder.blue.hero height=\"400\"]\n\n\n## Stuck in the past\nReact emerged when CSS was genuinely limited. No variables, no nesting, no real layout system. Global scope created conflicts. Cascade felt unpredictable. The community built tools to work around these limitations: preprocessors, naming conventions, CSS-in-JS.\n\nThese solutions became orthodoxy. We normalized the idea that CSS needs fixing, that styling should live inside components, that global scope is dangerous. The JavaScript ecosystem created an entire industry around CSS's perceived brokenness.\n\nBut CSS kept evolving. While developers debated utility frameworks and CSS-in-JS libraries, the language gained powerful native features. Variables arrived. Nesting landed in browsers. Container queries, layers, and scope solved real problems. Grid and flexbox revolutionized layout.\n\nThe gap between perception and reality widened. Modern CSS can do things that seemed impossible in 2013, but the ecosystem remains stuck in defensive patterns.\n\n\n## CSS in 2025\nToday's CSS bears little resemblance to the language React was designed to fix. Modern CSS has everything needed for sophisticated design systems.\n\n**CSS variables** create design tokens that cascade and inherit naturally:\n\n```css\n:root {\n  --primary-hue: 210;\n  --primary-color: hsl(var(--primary-hue) 60% 50%);\n  --primary-light: hsl(var(--primary-hue) 60% 90%);\n}\n\nbutton {\n  background: var(--primary-color);\n  border: 1px solid var(--primary-color);\n}\n\nbutton:hover {\n  background: var(--primary-light);\n}\n```\n\n**Native nesting** eliminates preprocessor complexity:\n\n```css\n.card {\n  padding: 1rem;\n  border-radius: 8px;\n\n  h3 {\n    margin-top: 0;\n  }\n\n  &:hover {\n    box-shadow: 0 4px 8px rgba(0,0,0,0.1);\n  }\n}\n```\n\n**CSS layers** solve specificity wars forever:\n\n```css\n@layer base, layout, components, overrides;\n\n@layer base {\n  button {  }\n}\n\n@layer components {\n  .primary {  }\n}\n\n@layer overrides {\n  .hidden {\n    display: none !important;\n  }\n}\n```\n\n**Container queries** create truly responsive components:\n\n```css\n.sidebar {\n  container-type: inline-size;\n}\n\n.card {\n  padding: 1rem;\n}\n\n@container (min-width: 300px) {\n  .card {\n    display: grid;\n    grid-template-columns: auto 1fr;\n  }\n}\n```\n\n**CSS scope** provides encapsulation without JavaScript:\n\n```css\n@scope (.component) {\n  button {\n    /* Only affects buttons inside .component */\n  }\n}\n```\n\nThis is a completely different language from what React was built to replace.\n\n## Design system best practices\n\nModern CSS enables new patterns for building maintainable design systems.\n\n### Trust HTML semantics\n\nHTML already provides most of what a design system needs. Lists have `<ul>`, `<ol>`, `<dl>`. Tables have semantic structure. Forms have fieldsets and labels. Navigation has `<nav>`. Interactive elements have `<button>`, `<details>`, `<dialog>`.\n\nStyle these native elements directly:\n\n```css\n/* Not this: component classes */\n.list-component { }\n.nav-component { }\n.button-component { }\n\n/* This: semantic elements */\nul { }\nnav { }\nbutton { }\n```\n\nUse attribute selectors (`[disabled]`, `[aria-expanded]`), pseudo-classes (`:invalid`, `:checked`), and `:has()` for state-based styling. The browser already knows what's interactive and what's not.\n\n### Class names for layout\n\nHTML can't express spatial relationships. These aren't semantic, so class names handle layout:\n\n```css\n.stack {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-m);\n}\n\n.grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n  gap: var(--space-l);\n}\n\n.columns {\n  column-count: var(--count, 2);\n  column-gap: var(--space-m);\n}\n```\n\nAdd minimal modifiers for variations:\n\n```css\n.thin { max-width: 40ch; }\n.wide { max-width: 80ch; }\n.compact { --space-m: 0.5rem; }\n```\n\nModern nested CSS eliminates the need for inner class names. A constrained system enables creative combinations without chaos.\n\n### Layer everything\n\nCSS layers solve specificity wars forever:\n\n```css\n@layer base, layout, components, overrides;\n\n@layer base {\n  /* Variables, semantic elements */\n  :root { --primary: #0066cc; }\n  button { padding: 0.5rem 1rem; }\n}\n\n@layer layout {\n  /* Spatial relationships */\n  .stack { display: flex; flex-direction: column; }\n  .grid { display: grid; }\n}\n\n@layer components {\n  /* Component variations */\n  .primary { background: var(--primary); color: white; }\n}\n\n@layer overrides {\n  /* Overrides */\n  .hidden { display: none !important; }\n}\n```\n\nEach layer has clear boundaries and purpose. No more specificity hacks, no more source order gymnastics. The cascade becomes predictable.\n\n### Keep it minimal\n\nEven complex design systems need surprisingly few classes. Maybe 10-30 total. Not 50. Definitely not 500. A design system fails when developers escape to local styling.\n\nThe best way to ensure adoption is constraint. Learning 10 classes is manageable. Learning 100 is not. Minimal systems force creative solutions within boundaries—exactly what good design requires.\n\n## The benefits\n\nModern CSS creates possibilities that CSS-in-JS can't match.\n\n**Performance** - Native CSS parsing and rendering beats JavaScript-generated styles. No runtime overhead, no flash of unstyled content, no hydration delays.\n\n**Debugging** - Browser dev tools understand CSS natively. Inspect elements, edit properties, see computed values. No source maps, no JavaScript debugging for visual issues.\n\n**Maintainability** - Design changes happen in design files. A single variable update cascades everywhere. No hunting through component props or JavaScript configurations.\n\n**Standards compliance** - CSS follows web standards that evolve carefully. Your investment compounds over decades. Framework APIs change every few years.\n\n**Design workflow** - Designers work directly with CSS. No translation layer between design tools and implementation. The feedback loop becomes immediate.\n\n**Future-proof** - Modern CSS features work in all browsers. No polyfills, no build steps, no runtime dependencies. The platform provides everything.\n\nCSS in 2025 is a mature, powerful language for building design systems. The JavaScript ecosystem just hasn't caught up yet.\n\n\n"
  },
  {
    "path": "packages/templates/blog/posts/design-engineering.md",
    "content": "---\ndate: 2025-08-15\n---\n\n# What is design engineering?\nWeb development split into two camps: those who design and those who code. This division is artificial. The web is a design medium that happens to be programmable.\n\n[placeholder.yellow.hero height=\"400\"]\n\n## The artificial divide\nModern frameworks optimize for engineering mindset. Everything becomes a programming problem. CSS becomes CSS-in-JS. Design becomes component props. Layout becomes flexbox utilities. We've turned visual decisions into code decisions.\n\nDesigners must learn React to do their job. They write JavaScript to change a color. They debug webpack to update spacing. The tool shapes the thinking, and the thinking becomes programmatic.\n\nMeanwhile, engineers build without design sense. They memorize utility classes instead of understanding visual hierarchy. They copy-paste components without grasping the underlying system. They optimize for code patterns while users struggle with confusing interfaces.\n\nThe result is neither good design nor good engineering. It's compromise in both directions.\n\n\n## Two mindsets, one medium\nDesign mindset sees patterns, rhythm, and hierarchy. It asks: How does this feel? What draws the eye? Where does attention flow?\n\nEngineering mindset sees data structures, state management, and abstractions. It asks: How does this scale? What are the edge cases? How do we test this?\n\nBoth are essential. Neither is sufficient alone. The web demands both mindsets because interfaces are both visual and functional. The mistake is believing one matters more than the other.\n\n\n[placeholder.blue height=\"300\"]\n\n\n## Reuniting the disciplines\n\nTrue design engineering happens when both mindsets have equal tools and equal power. CSS for design decisions. HTML for structure. JavaScript for business logic. Each layer owns its domain completely.\n\nThis isn't about roles or job titles. One person can embody both mindsets. Teams can specialize. The key is that the technology supports both ways of thinking equally. Design decisions happen in design tools. Programming decisions happen in programming tools. Neither compromises for the other.\n\nWhen a designer changes a color, it changes everywhere instantly. When an engineer refactors business logic, no visual elements break. When both work on the same feature, they're enhancing different layers of the same system.\n\n## What design engineering looks like\n\n**Direct manipulation** - Designers control visual properties through CSS variables, not component props. Change `--primary-color` and see it across the entire product. No pull requests, no developer bottlenecks.\n\n**Semantic structure** - Engineers build with meaningful HTML elements that designers can style predictably. A `<button>` behaves like a button. A `<nav>` means navigation. The structure describes intent, not appearance.\n\n**Clear boundaries** - Design system lives in CSS files. Business logic lives in JavaScript modules. Content lives in Markdown files. Each discipline owns its layer completely.\n\n**Shared vocabulary** - Both mindsets understand the same concepts: semantic elements, design tokens, component APIs. Communication becomes precise because the tools create common ground.\n\n\n[placeholder.red height=\"150\"]\n\n\n## The benefits\n\n**Parallel development** - Design and engineering work streams never block each other. Visual iterations happen independently of feature development. Code refactoring doesn't break designs.\n\n**Compound expertise** - Skills in each discipline deepen without interference. Designers become better at systematic thinking. Engineers develop stronger aesthetic judgment. Neither has to compromise their core competency.\n\n**Faster iteration** - Design changes deploy instantly through CSS. Feature changes don't require design system updates. The feedback loop becomes immediate for both disciplines.\n\n**Maintainable systems** - Clear separation of concerns makes debugging predictable. Visual bugs live in CSS. Logic bugs live in JavaScript. Structure bugs live in HTML. No hunting through component hierarchies.\n\n**Future-proof architecture** - Web standards evolve slowly and deliberately. CSS knowledge stays relevant for decades. HTML semantics remain stable. JavaScript fundamentals persist across framework changes.\n\nDesign engineering isn't about making everyone learn everything. It's about giving each discipline the right tools for their domain. When design and engineering have equal power in their respective layers, both can work at full potential."
  },
  {
    "path": "packages/templates/blog/posts/design-systems.md",
    "content": "---\ndate: 2025-08-01\n---\n\n# What is a CSS design system?\nModern CSS has everything needed for real design systems. Variables, nesting, layers, and scope create visual languages that scale across product lines. When design lives in one place, both designers and developers work at full speed.\n\n\n[placeholder.red.hero height=\"500\"]\n\n## The problem we created\n\nFor 15 years we've been writing CSS to survive in chaos. We use BEM because we gave up on the cascade. We use CSS-in-JS because we're scared of namespace collisions. We write utility classes because we stopped trusting our ability to name things. We compile, process, and transform CSS because we think native CSS isn't enough.\n\nThese practices assume CSS is broken and needs fixing. They're defensive strategies. They're about working around the language rather than harnessing its potential.\n\n## CSS design system defined\n\nA design system isn't a component library or a utility framework. It's a visual language expressed through CSS. Modern CSS gives us everything we need: variables, nesting, container queries, layers, and scope. Here's what makes a real design system:\n\n### Central\n\nA design system lives in one place. Not scattered across components, not mixed with JavaScript, not generated at build time. When you can see the whole system at once, you understand it. When it's fragmented across a thousand files, you have no system—just a collection of accidents waiting to happen.\n\n### Semantic\n\nHTML already contains a rich design vocabulary. Lists, tables, forms, buttons, navigation—they all have meaning. A design system extends this vocabulary rather than replacing it. A `<button>` is already interactive. A `<nav>` already means navigation. The system styles what exists instead of recreating it with divs and classes.\n\n### Minimal\n\nConstraints create consistency. A design system with 20 well-chosen classes beats one with 2000 utilities. HTML can't express spatial relationships, so classes handle layout: `.stack`, `.grid`, `.columns`. A few modifiers capture variations: `.primary`, `.compact`, `.inverted`. Everything else? Already in HTML.\n\n## Small projects\n\nStart with global styles plus area-specific CSS. This follows the classic web development pattern that pre-dated the component revolution - global stylesheets with area-specific additions. It's perfect for personal projects, prototypes, or small teams where you need the flexibility to add styles ad-hoc without teaching a formal system to others:\n\n```\n├── global.css       # Site-wide design system\n├── index.md\n└── blog/\n    ├── blog.css     # Blog-specific styles\n    ├── css-is-awesome.md\n    ├── design-systems.md\n    └── ...\n```\n\nFiles are loaded automatically based on location. The `global.css` applies everywhere. The `blog.css` only applies to pages in the blog directory. No imports, no bundling - just files where you need them.\n\n## Larger projects\n\nScale up with a centralized design system for larger teams, client work, or any project where consistency and maintainability matter more than development speed. This approach enforces constraints that prevent the CSS sprawl that kills long-term projects:\n\n```bash\nnue create full\n```\n\nThis creates a complete design system in `@shared/design/`:\n\n```\n@shared/design/\n├── base.css         # Typography, colors, spacing\n├── button.css       # All button variants\n├── content.css      # Blog posts, documentation\n├── dialog.css       # Modals, popovers\n├── document.css     # Page structure\n├── form.css         # All form elements\n├── layout.css       # Grid, stack, columns\n├── syntax.css       # Code highlighting\n├── table.css        # Data tables\n└── apps.css         # SPA-specific components\n```\n\nAll files load automatically across your entire site. Marketing pages, documentation, blogs, login screens, and single-page apps all use the same visual language. Change a variable in `base.css` and see it everywhere.\n\n## The compound effect\n\nA real design system compounds its value over time. Each project strengthens the system. Each use case refines the patterns. Each team member contributes to a shared language.\n\nWhen design is scattered in components, every project starts from scratch. You rebuild the same button in every framework migration. You rewrite the same styles for every new component library. Nothing compounds. Everything churns.\n\nWhen design is central and semantic, it goes beyond frameworks. Your CSS design system works with any HTML. It worked with jQuery. It works with React. It will work with whatever comes next. The investment compounds across technologies and time.\n\nThis is why CSS matters. Not because it's powerful—though it is. Not because it's fast—though it is. But because CSS is the only layer that survives every paradigm shift. Your React components from 2016 are obsolete. Your CSS from 2016 still works."
  },
  {
    "path": "packages/templates/blog/posts/extreme-performance.md",
    "content": "\n---\ndate: 2025-09-15\n---\n\n# Extreme performance\nToday the web performance industry optimizes the wrong things. Complex bundlers, code splitting, and tree shaking treat symptoms while ignoring the root cause: JavaScript dependency for basic functionality.\n\n\n[placeholder.blue.hero height=\"450\"]\n\n\n## The bundler trap\nModern web development assumes you need complex build tools to achieve performance. Webpack configurations span hundreds of lines. Vite promises speed through clever caching. Next.js provides automatic optimization through framework magic.\n\nThese tools work around a fundamental problem: applications that require JavaScript to render basic content. When your page needs 200KB of JavaScript to display a blog post, optimization becomes an arms race. Bundle splitting, lazy loading, and preloading try to make the inevitable download faster.\n\nBut faster delivery of unnecessary code is still unnecessary code. The real win is not needing the code at all.\n\n\n## Progressive enhancement\nThe web was designed as a layered medium. HTML provides structure and content. CSS adds presentation. JavaScript enables interaction. Each layer enhances the previous one.\n\nThis architecture enables extreme performance through careful layering:\n\n**HTML first** - Content and structure load immediately. No JavaScript required for reading, navigation, or basic functionality. Screen readers work. Search engines index everything. Users get instant content.\n\n**CSS enhancement** - Visual design loads as a separate layer. Typography, colors, spacing, and layout enhance the structured content. The page becomes beautiful as styles load.\n\n**JavaScript optional** - Interactive features enhance the styled content. Form validation, dynamic updates, and complex interactions layer on top. But the core experience works without them.\n\nMost web applications need surprisingly little JavaScript. Content sites need none. Marketing pages need minimal interaction. Even complex applications can render their initial state as HTML.\n\n## Going extreme\n\nWith proper layering, you can achieve performance that no bundler can match: everything needed to render the page in the first HTML response.\n\n### CSS inlining\n\nModern CSS best practices keep stylesheets small enough to inline completely:\n\n```yaml\n# In site.yaml\ndesign:\n  inline_css: true\n```\n\nWhen your entire design system weighs less than Tailwind's reset CSS, inlining becomes viable. The initial HTML download contains everything needed for perfect rendering. No secondary requests, no flash of unstyled content, no layout shifts.\n\nThis eliminates the CSS network request entirely. While bundlers debate optimal chunking strategies, inlined CSS delivers instantaneous rendering. Nothing can beat zero network requests.\n\n\n[placeholder.red height=\"250\"]\n\n\n### HTML-driven interactivity\n\nMany \"interactive\" features work with pure HTML:\n\n```html\n<!-- Expandable content -->\n<details>\n  <summary>Advanced options</summary>\n  <div>Content hidden by default</div>\n</details>\n\n<!-- Modal dialogs -->\n<button popovertarget=\"settings\">Settings</button>\n<dialog id=\"settings\" popover>\n  <h2>Settings</h2>\n  <button popovertarget=\"settings\">Close</button>\n</dialog>\n\n<!-- Form validation -->\n<form>\n  <input type=\"email\" required>\n  <input type=\"url\" required>\n  <button>Submit</button>\n</form>\n```\n\nNo JavaScript required. The browser provides rich interactivity through semantic HTML. Your \"complex\" interface becomes a collection of standard elements.\n\n### Minimal JavaScript\n\nWhen JavaScript is needed, modern approaches keep it tiny:\n\n```html\n<!-- Component with minimal enhancement -->\n<form onsubmit=\"handleSubmit(event)\">\n  <input name=\"email\" type=\"email\" required>\n  <button>Subscribe</button>\n\n  <script>\n    async function handleSubmit(e) {\n      e.preventDefault()\n      const data = new FormData(e.target)\n      await fetch('/subscribe', { method: 'POST', body: data })\n      location.href = '/thanks'\n    }\n  </script>\n\n</form>\n```\n\nThe form works without JavaScript - server-side processing handles submission. JavaScript enhances with smoother UX. Total enhancement cost: 200 bytes.\n\n## The performance hierarchy\n\nDifferent optimization strategies have dramatically different impact:\n\n**Bundler optimization** - 10-30% improvement. Code splitting, tree shaking, and compression optimize delivery of necessary code.\n\n**Framework switching** - 50-70% improvement. Moving from React to lighter alternatives reduces baseline JavaScript requirements.\n\n**Progressive enhancement** - 80-95% improvement. Making JavaScript optional eliminates most performance bottlenecks.\n\n**CSS inlining** - 99% improvement for initial render. Zero network requests for visual presentation beats any bundler optimization.\n\nThe biggest gains come from architectural decisions, not build tools.\n\n## Measuring what matters\n\nTraditional metrics miss the point:\n\n**Time to Interactive** becomes irrelevant when the page is interactive immediately through HTML.\n\n**First Contentful Paint** happens instantly when CSS is inlined and content is in HTML.\n\n**Cumulative Layout Shift** becomes zero when all styling loads with initial HTML.\n\n**Lighthouse scores** improve dramatically when you need fewer resources to begin with.\n\nThe ultimate performance optimization is not needing to optimize. When your architecture eliminates bottlenecks, complex tooling becomes unnecessary.\n\n## The benefits\n\nExtreme performance changes user experience fundamentally:\n\n**Instant loading** - Pages render immediately. No loading states, no progressive enhancement delays, no \"app is loading\" screens.\n\n**Reliable experience** - Works on slow connections, old devices, with JavaScript disabled. Progressive enhancement ensures universal access.\n\n**Simple deployment** - No build complexity, no CDN configuration, no cache invalidation strategies. Static HTML files work everywhere.\n\n**Maintainable codebase** - Less JavaScript means fewer bugs. Smaller surface area means easier debugging. Standard HTML/CSS means longer-lasting code.\n\n**Lower costs** - Less bandwidth, less server processing, less CDN usage. Extreme performance is also extreme efficiency.\n\nThe web's layered architecture enables performance that modern frameworks can't achieve. HTML, CSS, and minimal JavaScript create experiences faster than any bundler optimization. When you optimize architecture instead of build tools, users notice the difference.\n"
  },
  {
    "path": "packages/templates/blog/site.yaml",
    "content": "\nmeta:\n  title: Emma Bennet / Design Engineering Blog\n  title_template: %s / Emma Bennet\n  author: Emma Bennet\n\nsite:\n  origin: https://acme.org\n  view_transitions: true\n\nrss:\n  enabled: true\n  title: Acme blog\n  description: Latest news on web development\n  collection: blog\n\ndesign:\n  inline_css: true\n\ncollections:\n  blog:\n    include: [ posts/ ]\n    require: [ title ]\n    sort: date desc\n\n"
  },
  {
    "path": "packages/templates/full/.gitignore",
    "content": ".dist\n.nue\n"
  },
  {
    "path": "packages/templates/full/404.md",
    "content": "\n# 404 Not found\nPage not implemented"
  },
  {
    "path": "packages/templates/full/@shared/design/README.md",
    "content": "\n# Basic rules\n\n**Element selectors for 90% of things** class names only for the missing elements in the HTML spec.\n\n**Page structure (rems)**. Page margins, section gaps, grid spacing, major layout breaks. These relate to the overall document scale and should respond to user font size preferences consistently.\n\n**Elements/components (ems)**. Button padding, input margins, card spacing, typography margins. These relate to the element's own content and should scale with that specific text size."
  },
  {
    "path": "packages/templates/full/@shared/design/base.css",
    "content": "\n@layer base, component, modifier;\n\n@layer base {\n\n  :root {\n\n    /* base colors */\n    --base-200: #E2E8F0;\n    --base-400: #94A3B8;\n    --base-600: #475569;\n    --base-800: #1E293B;\n\n    --yellow: #F6C950;\n    --blue: #2A63B3;\n    --red: #CB3B32;\n\n    /* link colors */\n    --link: #00e;\n    --visited: #551a8b;\n\n    /* typographic grid */\n    --m: 1.5rem;\n    --l: 3rem;\n    --xl: 4.5rem;\n    --max-line-width: 45rem;\n  }\n}\n\n\n"
  },
  {
    "path": "packages/templates/full/@shared/design/button.css",
    "content": "\n@layer component {\n\n  button, .button {\n    background-color: var(--base-800);\n    text-align: center;\n    font-family: inherit;\n    font-size: inherit;\n    padding: .5em 1em;\n    cursor: pointer;\n    color: var(--base-200);\n    border-radius: .25em;\n    gap: .5em;\n    border: unset;\n\n\n    &[disabled] {\n      pointer-events: none;\n      opacity: .33;\n    }\n\n    &.plain {\n      background-color: unset;\n      color: var(--base-600);\n      border: 0;\n      padding: 0;\n    }\n  }\n\n  .button {\n    padding-block: .25em;\n  }\n}"
  },
  {
    "path": "packages/templates/full/@shared/design/components.css",
    "content": "\n\n@layer component {\n\n  .logo {\n    font-weight: 550;\n    b { color: var(--red) }\n    &:hover { color: black }\n  }\n\n\n  .toast {\n    background-color: var(--base-800);\n    padding: .5em 1em;\n    position: fixed;\n    bottom: 1em;\n    right: 1em;\n    p { color: var(--base-200); margin: 0 }\n  }\n}\n\n\n\n"
  },
  {
    "path": "packages/templates/full/@shared/design/content.css",
    "content": "\n/* Content elements (headings, paragraphs, lists, images, blockquotes, ...) */\n\n@layer base {\n  *, *:before, *:after {\n    box-sizing: border-box;\n  }\n\n  body {\n    line-height: 1.5;\n    color: var(--base-800);\n    font-family: system-ui;\n  }\n\n  img {\n    max-width: 100%;\n    height: auto;\n  }\n\n  h1, h2, h3, h4 {\n    margin-block: 0.5em;\n    text-wrap: balance;\n    line-height: 1.25;\n    + p { margin-top: 0 }\n  }\n\n  ol, ul, table {\n    margin-block: var(--m)\n  }\n\n  a {\n    color: var(--link);\n    &:visited { color: var(--visited); }\n    &[name] { color: inherit }\n    &:target { background-color: #fff3cd }\n  }\n\n  strong { color: var(--base-800) }\n\n\n  p, li, td {\n    color: var(--base-600);\n    text-wrap: pretty;\n\n    code {\n      background-color: var(--base-200);\n      font-family: courier, monospace;\n      padding: .125em .25em;\n      font-size: .875em;\n    }\n  }\n\n  ol, ul { padding-left: 1em }\n\n  li { padding-left: .5em }\n\n  hr {\n    border: 1px solid var(--base-200);\n    margin-block: var(--l);\n    border-width: 1px 0 0;\n  }\n\n  blockquote {\n    font-weight: bold;\n    margin: var(--l) 0;\n  }\n\n  small, figcaption {\n    font-weight: 400;\n    font-size: .875em;\n    color: var(--base-600);\n  }\n\n  dl {\n    display: grid;\n    grid-template-columns:25% 1fr;\n    > * { margin: .25em 0 }\n    dt { font-weight: 500 }\n    p:first-child { margin-top: 0 }\n  }\n\n  time {\n    white-space: nowrap\n  }\n}\n"
  },
  {
    "path": "packages/templates/full/@shared/design/dialog.css",
    "content": "\ndialog {\n  margin-top: 10em;\n  padding: 1.5em;\n  max-width: 100%;\n  width: 25em;\n\n  > :first-child { margin: 0 }\n\n  &::backdrop {\n    background-color: rgba(0,0,0, 50%);\n    backdrop-filter: blur(5px);\n  }\n}"
  },
  {
    "path": "packages/templates/full/@shared/design/document.css",
    "content": "\n/* For content heavy documents only (excluded from single-page-apps) */\n\n@layer base {\n\n  article {\n    flex-direction: column;\n    display: flex;\n    gap: var(--xl); /* section gaps */\n  }\n\n  h1, h2, h3, p, li {\n    max-width: var(--max-line-width)\n  }\n\n  h1 {\n    font-size: 2.25em;\n    & + p, & + time + p {\n      font-size: 1.125em;\n      text-wrap: balance;\n    }\n  }\n\n  section > :is(h3, h4) {\n    margin-top: var(--l)\n  }\n}\n\n@layer component {\n\n  /* place things in a row  */\n  .blocks {\n    align-items: center;\n    flex-direction: row;\n    display: flex;\n    gap: var(--m);\n  }\n\n  /* columns  */\n  @media (width > 900px) {\n    .columns {\n      column-count: var(--column-count, 2);\n      margin-block: var(--l);\n      column-gap: var(--xl);\n      div { break-inside: avoid }\n\n      > :first-child {\n        &, h2, h3 {\n          margin-top: 0\n        }\n      }\n    }\n  }\n\n  /* note  */\n  .note {\n    background-color: var(--base-200);\n    max-width: var(--max-line-width);\n    margin-block: var(--m);\n    padding: 1em;\n    p { margin: 0 }\n  }\n\n}"
  },
  {
    "path": "packages/templates/full/@shared/design/figure.css",
    "content": "\n@layer component {\n\n  /* placeholder images */\n  figure {\n    background-color: var(--base-200);\n    margin: var(--m) 0;\n\n    > div {\n      height: var(--height);\n    }\n\n    @media (width > 900px) {\n      &.hero {\n        margin: 3em -15vw;\n      }\n    }\n\n    /* bauhaus coloring */\n    &.yellow  { background-color: var(--yellow) }\n    &.blue    { background-color: var(--blue) }\n    &.red     { background-color: var(--red) }\n  }\n\n}"
  },
  {
    "path": "packages/templates/full/@shared/design/form.css",
    "content": "\n@layer base {\n\n  form {\n    flex-direction: column;\n    margin-block: 1em;\n    display: flex;\n    gap: 1em;\n\n  }\n\n  label h3 {\n    font-size: 1em;\n    margin-block: .5em;\n  }\n\n  input, textarea {\n    border: 1px solid var(--base-400);\n    background-color: transparent;\n    color: var(--base-800);\n    font-family: inherit;\n    font-size: inherit;\n    padding: 0.5em;\n    margin: 0;\n    h3 + & { width: 100% }\n  }\n\n  /* container for checkboxes and radio buttons */\n  fieldset {\n    display: flex;\n    gap: 1em;\n\n    label {\n      display: inline-flex;\n      align-items: baseline;\n      cursor: pointer;\n      gap: .25em;\n    }\n\n    &:not(:has(legend)) {\n      border: unset;\n      padding: 0;\n    }\n  }\n}\n\n\n\n"
  },
  {
    "path": "packages/templates/full/@shared/design/layout.css",
    "content": "\n@layer base {\n  body {\n    max-width: 1000px;\n    flex-direction: column;\n    min-height: 100vh;\n    margin: 0 auto;\n    display: flex;\n    gap: var(--l);\n\n    > * { padding: 1em }\n    > main { flex: 1 }\n  }\n\n  header, footer {\n    align-items: center;\n    display: flex;\n    gap: 1em;\n\n    nav:last-of-type {\n      margin-left: auto;\n      justify-content: right;\n    }\n  }\n\n  nav {\n    align-items: center;\n    display: flex;\n    gap: 2em;\n\n    a {\n      &, &:visited { color: var(--base-600) }\n      text-decoration: none;\n      white-space: nowrap;\n\n      &[aria-current=\"page\"] {\n        font-weight: 800;\n        color: var(--base-800);\n      }\n    }\n\n    /* toolbars */\n    &:has(button) { gap: .75em }\n  }\n}\n"
  },
  {
    "path": "packages/templates/full/@shared/design/modifier.css",
    "content": "\n/* universal modifiers */\n@layer modifier {\n\n  /* size  */\n  .thin    { max-width: 25em }\n  .wide    { max-width: inherit; width: 100% }\n\n\n  /* space (loose, right) */\n  .loose   { margin-block: var(--l) }\n\n  /* typography\n  .small   { font-size: .875em }\n  .large   { font-size: 1.25em }\n  */\n\n  /* browser fixes */\n  [hidden] { display: none }\n  a:has(img:only-child) { font-size: 0 }\n\n}"
  },
  {
    "path": "packages/templates/full/@shared/design/syntax.css",
    "content": "\n@layer base {\n  pre {\n    border: 1px solid var(--base-200);\n    counter-reset: line-number 0;\n    color: var(--base-600);\n    padding: 2em;\n\n    /* reset */\n    * {\n      font-weight: 400;\n      font-style: inherit;\n      text-decoration: inherit\n    }\n\n    /* primary accent color */\n    b { color: #ff3aa1 }\n\n    /* secondary accent color */\n    em { color: #4065ff }\n\n    /* special emphasis */\n    strong { color: #419fff }\n\n    /* brackets, commas, semicolons... */\n    i { color: var(--base-400) }\n\n    /* comments */\n    sup {\n      opacity: .5;\n      vertical-align: inherit;\n      font-size: inherit;\n    }\n\n    /* marked segments */\n    mark {\n      background-color: #2bb8ff26;\n      color: inherit;\n      border-radius: .2em;\n      padding-block: .25em;\n    }\n\n    /* spotlight */\n    label {\n      font-weight: bold;\n      color: var(--base-800);\n    }\n\n    /* line numbers */\n    span {\n      counter-increment: line-number 1;\n\n      &:before {\n        opacity: .3;\n        content: counter(line-number);\n        display: inline-block;\n        text-align: right;\n        padding-right: 1em;\n        margin-right: 1em;\n        width: 2.5em;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/templates/full/@shared/design/table.css",
    "content": "\n@layer base {\n\n  table {\n    border-collapse: collapse;\n    width: 100%;\n\n    tr {\n      vertical-align: baseline;\n    }\n\n    th, td {\n      border-bottom: 1px solid var(--base-200);\n      padding: .5em 0;\n      text-align: left;\n\n      /* gap */\n      &:not(:first-child) { padding-left: 2em }\n    }\n\n    td:first-child {\n      white-space: nowrap;\n      width: 25%;\n    }\n\n    tfoot td {\n      color: var(--base-800);\n      font-weight: 500;\n      border: 0;\n    }\n  }\n}"
  },
  {
    "path": "packages/templates/full/@shared/lib/crud.js",
    "content": "\nexport async function post(route, data) {\n  const resp = await fetch(route, {\n    headers: { 'Content-Type': 'application/json', ...getAuthHeader() },\n    body: JSON.stringify(data),\n    method: 'POST',\n  })\n\n  catchError(resp)\n  return await resp.json()\n}\n\nexport async function get(route, params) {\n  const qs =  new URLSearchParams(params).toString()\n  if (qs) route += `?${ qs }`\n\n  const resp = await fetch(route, { headers: getAuthHeader() })\n  catchError(resp)\n  return await resp.json()\n}\n\nexport async function del(route) {\n  const resp = await fetch(route, { method: 'delete', headers: getAuthHeader() })\n  catchError(resp)\n  return await resp.json()\n}\n\n\nfunction getAuthHeader() {\n  const sid = localStorage.$sid\n  return sid ? { Authorization: `Bearer ${sid}` } : {}\n}\n\nfunction catchError(resp) {\n  if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${resp.statusText}`)\n}\n\n"
  },
  {
    "path": "packages/templates/full/@shared/server/data/leads.json",
    "content": "[\n  {\n    \"email\": \"sarah.johnson@gmail.com\",\n    \"country\": \"US\",\n    \"subscribed\": true,\n    \"source\": \"www.google.com\",\n    \"comment\": \"Love the speed focus!\"\n  },\n  {\n    \"email\": \"alex.mueller@web.de\",\n    \"country\": \"DE\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"james.smith@outlook.com\",\n    \"country\": \"GB\",\n    \"subscribed\": true,\n    \"source\": \"news.ycombinator.com\",\n    \"comment\": \"Finally, minimal design that works\"\n  },\n  {\n    \"email\": \"marie.dubois@orange.fr\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"medium.com\",\n    \"comment\": \"Web standards FTW\"\n  },\n  {\n    \"email\": \"yuki.tanaka@yahoo.co.jp\",\n    \"country\": \"JP\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"carlos.silva@gmail.com\",\n    \"country\": \"BR\",\n    \"subscribed\": true,\n    \"source\": \"www.reddit.com\"\n  },\n  {\n    \"email\": \"emily.davis@hotmail.com\",\n    \"country\": \"US\",\n    \"subscribed\": true,\n    \"source\": \"github.com\",\n    \"comment\": \"CSS wizardry impressed me\"\n  },\n  {\n    \"email\": \"thomas.wagner@gmx.de\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"source\": \"www.google.com\"\n  },\n  {\n    \"email\": \"olivia.brown@yahoo.co.uk\",\n    \"country\": \"GB\",\n    \"subscribed\": true,\n    \"comment\": \"Clean code = clean design\"\n  },\n  {\n    \"email\": \"pierre.martin@free.fr\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"substack.com\"\n  },\n  {\n    \"email\": \"hiroshi.sato@gmail.com\",\n    \"country\": \"JP\",\n    \"subscribed\": true,\n    \"source\": \"www.linkedin.com\",\n    \"comment\": \"Performance metrics blew my mind\"\n  },\n  {\n    \"email\": \"ana.santos@uol.com.br\",\n    \"country\": \"BR\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"michael.wilson@gmail.com\",\n    \"country\": \"US\",\n    \"subscribed\": true,\n    \"source\": \"t.co\",\n    \"comment\": \"Separation of concerns done right\"\n  },\n  {\n    \"email\": \"lisa.schneider@t-online.de\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"source\": \"duckduckgo.com\"\n  },\n  {\n    \"email\": \"david.taylor@btinternet.com\",\n    \"country\": \"GB\",\n    \"subscribed\": true,\n    \"comment\": \"Minimalism but maximum impact\"\n  },\n  {\n    \"email\": \"sophie.bernard@gmail.com\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"api.daily.dev\"\n  },\n  {\n    \"email\": \"kenji.nakamura@docomo.ne.jp\",\n    \"country\": \"JP\",\n    \"subscribed\": true,\n    \"source\": \"www.bing.com\",\n    \"comment\": \"Content-first approach is genius\"\n  },\n  {\n    \"email\": \"ricardo.oliveira@hotmail.com\",\n    \"country\": \"BR\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"jennifer.martinez@yahoo.com\",\n    \"country\": \"US\",\n    \"subscribed\": true,\n    \"source\": \"search.brave.com\",\n    \"comment\": \"No bloat, just results\"\n  },\n  {\n    \"email\": \"hans.fischer@web.de\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"source\": \"news.ycombinator.com\"\n  },\n  {\n    \"email\": \"charlotte.jones@gmail.com\",\n    \"country\": \"GB\",\n    \"subscribed\": true,\n    \"comment\": \"Semantic HTML appreciation\"\n  },\n  {\n    \"email\": \"luc.moreau@wanadoo.fr\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"www.ecosia.org\"\n  },\n  {\n    \"email\": \"sakura.yamamoto@softbank.ne.jp\",\n    \"country\": \"JP\",\n    \"subscribed\": true,\n    \"source\": \"www.reddit.com\",\n    \"comment\": \"Design systems done properly\"\n  },\n  {\n    \"email\": \"pedro.costa@gmail.com\",\n    \"country\": \"BR\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"robert.clark@hotmail.com\",\n    \"country\": \"US\",\n    \"subscribed\": true,\n    \"source\": \"github.com\",\n    \"comment\": \"Performance budget respect\"\n  },\n  {\n    \"email\": \"inge.hoffmann@gmx.de\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"source\": \"medium.com\"\n  },\n  {\n    \"email\": \"george.white@sky.com\",\n    \"country\": \"GB\",\n    \"subscribed\": true,\n    \"comment\": \"Progressive enhancement rocks\"\n  },\n  {\n    \"email\": \"claire.rousseau@laposte.net\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"www.google.com\"\n  },\n  {\n    \"email\": \"taro.suzuki@nifty.com\",\n    \"country\": \"JP\",\n    \"subscribed\": true,\n    \"source\": \"substack.com\",\n    \"comment\": \"Accessibility built-in, not bolted-on\"\n  },\n  {\n    \"email\": \"lucia.ferreira@terra.com.br\",\n    \"country\": \"BR\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"kevin.thompson@gmail.com\",\n    \"country\": \"US\",\n    \"subscribed\": true,\n    \"source\": \"www.linkedin.com\",\n    \"comment\": \"Core Web Vitals champion\"\n  },\n  {\n    \"email\": \"petra.klein@t-online.de\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"source\": \"t.co\"\n  },\n  {\n    \"email\": \"harry.evans@virgin.net\",\n    \"country\": \"GB\",\n    \"subscribed\": true,\n    \"comment\": \"Standards compliance beauty\"\n  },\n  {\n    \"email\": \"antoine.durand@sfr.fr\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"api.daily.dev\"\n  },\n  {\n    \"email\": \"mai.kobayashi@yahoo.co.jp\",\n    \"country\": \"JP\",\n    \"subscribed\": true,\n    \"source\": \"duckduckgo.com\",\n    \"comment\": \"100/100 Lighthouse scores possible!\"\n  },\n  {\n    \"email\": \"bruno.pereira@bol.com.br\",\n    \"country\": \"BR\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"ashley.harris@outlook.com\",\n    \"country\": \"US\",\n    \"subscribed\": true,\n    \"source\": \"www.bing.com\",\n    \"comment\": \"CSS architecture masterclass\"\n  },\n  {\n    \"email\": \"stefan.weber@web.de\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"source\": \"search.brave.com\"\n  },\n  {\n    \"email\": \"lucy.walker@tiscali.co.uk\",\n    \"country\": \"GB\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"jean.leclerc@club-internet.fr\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"www.ecosia.org\",\n    \"comment\": \"Form follows function perfection\"\n  },\n  {\n    \"email\": \"akira.watanabe@au.com\",\n    \"country\": \"JP\",\n    \"subscribed\": true,\n    \"source\": \"news.ycombinator.com\"\n  },\n  {\n    \"email\": \"mariana.alves@ig.com.br\",\n    \"country\": \"BR\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"daniel.lee@verizon.net\",\n    \"country\": \"US\",\n    \"subscribed\": true,\n    \"source\": \"www.reddit.com\"\n  },\n  {\n    \"email\": \"birgit.schulz@gmx.de\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"source\": \"github.com\"\n  },\n  {\n    \"email\": \"paul.robinson@o2.co.uk\",\n    \"country\": \"GB\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"camille.robert@alice.fr\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"medium.com\"\n  },\n  {\n    \"email\": \"yuki.inoue@biglobe.ne.jp\",\n    \"country\": \"JP\",\n    \"subscribed\": true,\n    \"source\": \"substack.com\"\n  },\n  {\n    \"email\": \"rafael.souza@globo.com\",\n    \"country\": \"BR\",\n    \"subscribed\": true\n  },\n  {\n    \"email\": \"michelle.garcia@aol.com\",\n    \"country\": \"US\",\n    \"subscribed\": true,\n    \"source\": \"www.google.com\"\n  },\n  {\n    \"email\": \"wolfgang.bauer@yahoo.de\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"source\": \"www.linkedin.com\"\n  },\n  {\n    \"email\": \"john.peterson@techcorp.com\",\n    \"name\": \"John Peterson\",\n    \"country\": \"US\",\n    \"subscribed\": false,\n    \"source\": \"www.linkedin.com\",\n    \"website\": \"https://techcorp.com\",\n    \"comment\": \"Interested in enterprise design system implementation - what packages do you offer?\"\n  },\n  {\n    \"email\": \"anna.schulte@innovate.de\",\n    \"name\": \"Anna Schulte\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"website\": \"https://innovate.de\",\n    \"comment\": \"Need to rebuild our webapp with better performance. Can we schedule a technical discussion?\"\n  },\n  {\n    \"email\": \"richard.thompson@dataflow.co.uk\",\n    \"name\": \"Richard Thompson\",\n    \"country\": \"GB\",\n    \"subscribed\": false,\n    \"source\": \"github.com\",\n    \"website\": \"https://dataflow.co.uk\",\n    \"comment\": \"Evaluating template solutions for 50+ landing pages. What customization options are available?\"\n  },\n  {\n    \"email\": \"isabelle.moreau@digitech.fr\",\n    \"name\": \"Isabelle Moreau\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"www.google.com\",\n    \"website\": \"https://digitech.fr\"\n  },\n  {\n    \"email\": \"takeshi.yamada@cloudtech.jp\",\n    \"name\": \"Takeshi Yamada\",\n    \"country\": \"JP\",\n    \"subscribed\": false,\n    \"website\": \"https://cloudtech.jp\",\n    \"comment\": \"Our current site scores 40 on Lighthouse. How quickly can you help us reach 90+?\"\n  },\n  {\n    \"email\": \"fernando.santos@innovabr.com.br\",\n    \"name\": \"Fernando Santos\",\n    \"country\": \"BR\",\n    \"subscribed\": true,\n    \"source\": \"news.ycombinator.com\",\n    \"website\": \"https://innovabr.com.br\",\n    \"comment\": \"Looking for content-first CMS integration. Do your templates support headless architecture?\"\n  },\n  {\n    \"email\": \"sarah.collins@nexustech.com\",\n    \"name\": \"Sarah Collins\",\n    \"country\": \"US\",\n    \"subscribed\": false,\n    \"source\": \"medium.com\",\n    \"website\": \"https://nexustech.com\",\n    \"comment\": \"Team wants to adopt your CSS methodology. Do you offer training or documentation?\"\n  },\n  {\n    \"email\": \"marcus.hoffmann@futureware.de\",\n    \"name\": \"Marcus Hoffmann\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"website\": \"https://futureware.de\"\n  },\n  {\n    \"email\": \"helen.ward@smartsys.co.uk\",\n    \"name\": \"Helen Ward\",\n    \"country\": \"GB\",\n    \"subscribed\": false,\n    \"source\": \"www.reddit.com\",\n    \"website\": \"https://smartsys.co.uk\",\n    \"comment\": \"Impressed by separation of concerns approach. Can we see case studies from similar B2B companies?\"\n  },\n  {\n    \"email\": \"philippe.martin@webpro.fr\",\n    \"name\": \"Philippe Martin\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"substack.com\",\n    \"website\": \"https://webpro.fr\",\n    \"comment\": \"Need white-label solution for client projects. What licensing terms do you offer?\"\n  },\n  {\n    \"email\": \"hiroki.tanaka@digitalsol.jp\",\n    \"name\": \"Hiroki Tanaka\",\n    \"country\": \"JP\",\n    \"subscribed\": false,\n    \"website\": \"https://digitalsol.jp\"\n  },\n  {\n    \"email\": \"carlos.rodrigues@techmind.com.br\",\n    \"name\": \"Carlos Rodrigues\",\n    \"country\": \"BR\",\n    \"subscribed\": true,\n    \"source\": \"t.co\",\n    \"website\": \"https://techmind.com.br\",\n    \"comment\": \"Our dev team struggles with performance optimization. Do you provide consulting services?\"\n  },\n  {\n    \"email\": \"david.miller@alphatech.com\",\n    \"name\": \"David Miller\",\n    \"country\": \"US\",\n    \"subscribed\": false,\n    \"source\": \"api.daily.dev\",\n    \"website\": \"https://alphatech.com\",\n    \"comment\": \"Considering migration from React bloat to web standards. What migration path do you recommend?\"\n  },\n  {\n    \"email\": \"sabine.fischer@quantum.de\",\n    \"name\": \"Sabine Fischer\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"website\": \"https://quantum.de\"\n  },\n  {\n    \"email\": \"james.clark@velocity.co.uk\",\n    \"name\": \"James Clark\",\n    \"country\": \"GB\",\n    \"subscribed\": false,\n    \"source\": \"duckduckgo.com\",\n    \"website\": \"https://velocity.co.uk\",\n    \"comment\": \"Budget approved for Q3 redesign. Can we discuss enterprise pricing and timeline?\"\n  },\n  {\n    \"email\": \"marie.bernard@nexwave.fr\",\n    \"name\": \"Marie Bernard\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"www.bing.com\",\n    \"website\": \"https://nexwave.fr\",\n    \"comment\": \"Multi-language site requirements. How do your templates handle internationalization?\"\n  },\n  {\n    \"email\": \"kenji.sato@streamtech.jp\",\n    \"name\": \"Kenji Sato\",\n    \"country\": \"JP\",\n    \"subscribed\": false,\n    \"website\": \"https://streamtech.jp\"\n  },\n  {\n    \"email\": \"patricia.lima@digibras.com.br\",\n    \"name\": \"Patricia Lima\",\n    \"country\": \"BR\",\n    \"subscribed\": true,\n    \"source\": \"search.brave.com\",\n    \"website\": \"https://digibras.com.br\",\n    \"comment\": \"Compliance team loves your accessibility approach. What WCAG level do you guarantee?\"\n  },\n  {\n    \"email\": \"robert.anderson@coretech.com\",\n    \"name\": \"Robert Anderson\",\n    \"country\": \"US\",\n    \"subscribed\": false,\n    \"source\": \"www.ecosia.org\",\n    \"website\": \"https://coretech.com\",\n    \"comment\": \"Need templates that work with our existing CI/CD pipeline. What build tools do you support?\"\n  },\n  {\n    \"email\": \"greta.muller@innovatech.de\",\n    \"name\": \"Greta Muller\",\n    \"country\": \"DE\",\n    \"subscribed\": true,\n    \"website\": \"https://innovatech.de\"\n  },\n  {\n    \"email\": \"andrew.wilson@cybernet.co.uk\",\n    \"name\": \"Andrew Wilson\",\n    \"country\": \"GB\",\n    \"subscribed\": false,\n    \"source\": \"github.com\",\n    \"website\": \"https://cybernet.co.uk\",\n    \"comment\": \"Stakeholders want proof of performance claims. Can you share before/after metrics from similar projects?\"\n  },\n  {\n    \"email\": \"sylvie.dubois@techvision.fr\",\n    \"name\": \"Sylvie Dubois\",\n    \"country\": \"FR\",\n    \"subscribed\": true,\n    \"source\": \"www.linkedin.com\",\n    \"website\": \"https://techvision.fr\"\n  },\n  {\n    \"email\": \"masaki.honda@hypertech.jp\",\n    \"name\": \"Masaki Honda\",\n    \"country\": \"JP\",\n    \"subscribed\": false,\n    \"website\": \"https://hypertech.jp\"\n  },\n  {\n    \"email\": \"ricardo.machado@cloudware.com.br\",\n    \"name\": \"Ricardo Machado\",\n    \"country\": \"BR\",\n    \"subscribed\": true,\n    \"source\": \"news.ycombinator.com\",\n    \"website\": \"https://cloudware.com.br\"\n  },\n  {\n    \"email\": \"karen.taylor@digitech.com\",\n    \"name\": \"Karen Taylor\",\n    \"country\": \"US\",\n    \"subscribed\": false,\n    \"source\": \"medium.com\",\n    \"website\": \"https://digitech.com\"\n  }\n]"
  },
  {
    "path": "packages/templates/full/@shared/server/data/users.json",
    "content": "[\n  { \"email\": \"admin@example.com\", \"password\": \"demo123\" }\n]"
  },
  {
    "path": "packages/templates/full/@shared/server/index.js",
    "content": "\n\n// login\npost('/api/login', async (c) => {\n  const { users } = c.env\n  const { email, password } = await c.req.json()\n\n  const ret = await users.login(email, password)\n  return ret ? c.json(ret) : c.json({ error: 'Invalid credentials' }, 401)\n})\n\npost('/api/logout', async (c) => {\n  const { users } = c.env\n  const sessionId = c.req.header('Authorization')?.replace('Bearer ', '')\n  await users.logout(sessionId)\n  return c.json({ success: true })\n})\n\npost('/api/leads', async (c) => {\n  const { users } = c.env\n  const country = c.req.header('cf-ipcountry')\n  const data = await c.req.json()\n  const user = await users.create({ ...data, country })\n  console.log('created', user)\n  return c.json(user)\n})\n\n// authenticated requests\nuse('/api/admin/*', async (c, next) => {\n  const { users } = c.env\n  const sessionId = c.req.header('Authorization')?.replace('Bearer ', '')\n  if (await users.authenticate(sessionId)) await next()\n  else return c.json({ error: 'Invalid session' }, 401)\n})\n\nget('/api/admin/all', async (c) => {\n  const { leads } = c.env\n  return c.json({ leads: await leads.getAll() })\n})\n\nget('/api/admin/leads/:id', async (c) => {\n  const { leads } = c.env\n  const lead = await leads.get(c.req.param('id'))\n  return lead ? c.json(lead) : c.json({ error: 'Lead not found' }, 404)\n})\n\ndel('/api/admin/leads/:id', async (c) => {\n  const { leads } = c.env\n  const lead = await leads.get(c.req.param('id'))\n  if (!lead) return c.json({ error: 'Not found' }, 404)\n  await lead.remove()\n  return c.json({ success: true })\n})\n\n\n\n"
  },
  {
    "path": "packages/templates/full/@shared/ui/components.html",
    "content": "\n<figure :is=\"placeholder\" class=\"{ class }\">\n  <div --height=\"{ height }px\"/>\n</figure>\n\n<logo class=\"logo\">\n  <b>■</b> Form & Function\n</logo>"
  },
  {
    "path": "packages/templates/full/@shared/ui/footer.html",
    "content": "<!doctype html lib>\n\n<footer>\n  © 2025 <logo/>\n\n  <nav>\n    <a href=\"/legal/\">Legal</a>\n    <a href=\"/terms/\">Terms</a>\n  </nav>\n</footer>\n"
  },
  {
    "path": "packages/templates/full/@shared/ui/header.html",
    "content": "<!doctype html lib>\n\n<header>\n  <nav>\n    <a href=\"/\"><logo/></a>\n    <a href=\"/docs/\">Docs</a>\n    <a href=\"/blog/\">Blog</a>\n  </nav>\n\n  <nav>\n    <a href=\"/contact/\">Contact</a>\n    <a class=\"button\" href=\"/login/\">Login</a>\n  </nav>\n</header>\n\n"
  },
  {
    "path": "packages/templates/full/@shared/ui/isomorphic.html",
    "content": "<!doctype html+dhtml lib>\n\n<!--\n  Used on server layouts and in client app\n-->\n\n<time :is=\"pretty-date\">\n  { pretty }\n\n  <script>\n    const DATE_FORMAT = new Intl.DateTimeFormat('en-US', {\n      month: 'short', day: 'numeric', year: 'numeric'\n    })\n    let date = this.date || new Date()\n    if (typeof date == 'string') date = new Date(date)\n    this.pretty = DATE_FORMAT.format(date)\n  </script>\n</time>"
  },
  {
    "path": "packages/templates/full/Makefile",
    "content": "\ninit:\n\tcd @shared/server && bun db/init/load-data autoload\n\ntest:\n\tcd @shared/server  && bun test\n\n\n\n"
  },
  {
    "path": "packages/templates/full/admin/app.yaml",
    "content": "\nexclude: [ document.css ]"
  },
  {
    "path": "packages/templates/full/admin/index.html",
    "content": "<!doctype dhtml>\n\n<script>\n  import { get, post } from 'crud'\n  import { state } from 'state'\n\n  state.setup({\n    query: ['type', 'query', 'start'],\n    emit_only: ['deleted'],\n    memory: ['ondelete'],\n    route: '/admin/:id',\n    autolink: true,\n  })\n\n  if (!localStorage.$sid) location.href = '/login/'\n</script>\n\n<body>\n\n  <header>\n    <nav>\n      <a href=\"/admin/\" class=\"logo\"><b>■</b> Admin</a>\n    </nav>\n    <nav>\n      <button class=\"plain\" :onclick=\"logout\">Logout</button>\n    </nav>\n  </header>\n\n  <main>\n    <article/>\n  </main>\n\n  <confirm-delete/>\n\n  <script>\n    async onmount() {\n      const all = await get('/api/admin/all')\n\n      state.on('id', ({ id }) => {\n        this.mount(id ? 'contact-details' : 'contact-list', 'article', all)\n      })\n\n      state.init()\n    }\n\n    async logout() {\n      await post('/api/logout')\n      delete localStorage.$sid\n      location.href = '/'\n    }\n  </script>\n\n</body>\n\n"
  },
  {
    "path": "packages/templates/full/admin/ui/lead.html",
    "content": "<!doctype dhtml lib>\n\n<script>\n  import { state } from 'state'\n  import { get } from 'crud'\n</script>\n\n<article :is=\"contact-details\">\n\n  <h1>Contact</h1>\n\n  <nav>\n    <button onclick=\"history.go(-1)\">Back</button>\n    <button popovertarget=\"confirm-delete\">Delete</button>\n  </nav>\n\n  <dl>\n    <template :if=\"name\">\n      <dt>Name</dt><dd>{ name }</dd>\n    </template>\n\n    <dt>Email</dt><dd>{ email }</dd>\n    <dt>Registered</dt><dd><pretty-date :date=\"created\"/></dd>\n    <dt>Country</dt><dd><country-emoji :code=\"country\"/> <span>{ country_name }</span></dd>\n    <dt>Email</dt><dd>{ email }</dd>\n\n    <template :if=\"company_name\">\n      <dt>Company</dt><dd>{ company_name }</dd>\n    </template>\n\n    <template :if=\"website\">\n      <dt>Website</dt><dd>{ website }</dd>\n    </template>\n\n    <dt>Mailing list</dt><dd>{ subscribed ? '✅ Member' : '❌ Not member' }</dd>\n\n    <template :if=\"comment\">\n      <dt>Comment</dt>\n      <dd>{ comment }</dd>\n    </template>\n  </dl>\n\n  <script>\n\n    async onmount() {\n      const lead = await get(`/api/admin/leads/${state.id}`)\n      this.update(lead)\n    }\n  </script>\n\n</article>\n\n"
  },
  {
    "path": "packages/templates/full/admin/ui/leads.html",
    "content": "<!doctype dhtml lib>\n\n<script>\n  import { state } from 'state'\n</script>\n\n<article :is=\"contact-list\">\n\n  <h1>Leads</h1>\n\n  <header>\n    <!-- search -->\n    <input type=\"search\" name=\"query\" placeholder=\"Search...\"\n      :oninput=\"state.query = $event.target.value\" value=\"{ state.query }\">\n\n    <!-- pagination -->\n    <nav>\n      <button class=\"plain\" :onclick=\"seek(-1)\" :disabled=\"!hasPrev()\">←</button>\n      <small>{ status }</small>\n      <button class=\"plain\" :onclick=\"seek(1)\" :disabled=\"!hasNext()\">→</button>\n    </nav>\n  </header>\n\n  <table>\n    <tr :each=\"el in items\" key=\"{ el.id }\">\n      <td>\n        <country-emoji :code=\"el.country\"/>\n        <a href=\"{ el.id }\">{ el.email }</a>\n      </td>\n      <td>{ el.comment }</td>\n      <td><pretty-date :date=\"el.created\"/></td>\n      <td>\n        <button class=\"plain\" popovertarget=\"confirm-delete\" title=\"Delete\"\n          :onclick=\"state.ondelete = el.id\">×</button>\n      </td>\n    </tr>\n  </table>\n\n  <toast :message :if=\"message\"/>\n\n  <script>\n    const { leads } = this\n    const page_size = 15\n\n    get status() {\n      const { start=0 } = state\n      return `${ start + 1 } – ${ start + page_size } of ${ leads.length }`\n    }\n\n    hasNext() {\n      return page_size + (state.start || 0) < leads.length\n    }\n\n    hasPrev() {\n      return state.start - page_size >= 0\n    }\n\n    seek(direction) {\n      state.start = (state.start || 0) + page_size * direction\n    }\n\n    state.on('deleted', ({ deleted }) => {\n      this.items = this.items.filter(el => el.id != deleted)\n      this.update({ message: 'Lead deleted succesfully' })\n      setTimeout(() => this.update({ message: null }), 3000)\n    })\n\n    state.on('id type query start', args => {\n      const { start=0, type, query } = args\n      let all = query ? leads.filter(el => el.email.includes(query)) : leads\n      this.update({ items: all.slice(start, page_size + start) })\n    })\n  </script>\n\n</article>\n\n\n"
  },
  {
    "path": "packages/templates/full/admin/ui/shared.html",
    "content": "<!doctype dhtml lib>\n\n<script>\n  import { state } from 'state'\n  import { del } from 'crud'\n</script>\n\n<!-- pretty date -->\n<script>\n  const DATE_FORMAT = new Intl.DateTimeFormat('en-US', {\n    month: 'short', day: 'numeric', year: 'numeric'\n  })\n</script>\n\n<time :is=\"pretty-date\">\n  { pretty }\n\n  <script>\n    this.pretty = this.date ? DATE_FORMAT.format(new Date(this.date)) : ''\n  </script>\n</time>\n\n<!--\n  @param { string } code - Two-letter country code (e.g., 'US', 'FR')\n-->\n<b :is=\"country-emoji\">\n  { emoji }\n\n  <script>\n    this.emoji = this.code?.toUpperCase().replace(/./g, c =>\n      String.fromCodePoint(c.charCodeAt(0) + 127397)\n    )\n  </script>\n</b>\n\n<toast class=\"toast\" :role=\"role || 'status'\">\n  <p>{ message }</p>\n</toast>\n\n\n<!-- delete dialog -->\n<dialog :is=\"confirm-delete\" id=\"confirm-delete\" popover>\n  <h2>Delete this lead?</h2>\n  <p>This action cannot be undone.</p>\n\n  <footer>\n    <button :onclick  popovertarget=\"{ id }\">Delete</button>\n    <button class=\"plain\" popovertarget=\"{ id }\">Cancel</button>\n  </footer>\n\n  <script>\n    async onclick() {\n      const id = state.id || state.ondelete\n\n      if (id) {\n        await del(`/api/admin/leads/${id}`)\n        if (state.id) state.id = null\n        state.set({ ondelete: null, deleted: id })\n      }\n    }\n  </script>\n</dialog>"
  },
  {
    "path": "packages/templates/full/blog/components.html",
    "content": "\n<pagehead>\n  <pretty-date :date=\"date\"/> • <span>{ author }</span>\n</pagehead>\n\n\n<table :is=\"blog-entries\">\n  <tr :each=\"page in blog\">\n    <td><pretty-date :date=\"page.date\"/></td>\n    <td>\n      <a href=\"{ page.url }\">{ page.title }</a>\n      <p>{{ markdown(page.description) }}</p>\n    </td>\n  </tr>\n</table>"
  },
  {
    "path": "packages/templates/full/blog/css-beats-js.md",
    "content": "\n---\ndate: 2025-08-05\n---\n\n# Why CSS beats CSS-in-JS\nCSS-in-JS promised to solve CSS problems by moving styles into JavaScript. Instead, it created new problems while ignoring the solutions that already exist. Real CSS has evolved past the limitations that drove developers to JavaScript in the first place.\n\n[placeholder.yellow height=\"400\"]\n\n## The promise that didn't deliver\n\nCSS-in-JS emerged from real frustrations. Global namespaces caused conflicts. Unused styles bloated bundles. Dynamic styling felt clunky. The solution seemed obvious: move everything to JavaScript where we have modules, variables, and logic.\n\nBut CSS-in-JS didn't solve these problems. It relocated them. Global conflicts became runtime overhead. Unused styles became larger JavaScript bundles. Dynamic styling became complex prop drilling and theme providers.\n\nMeanwhile, CSS kept evolving. Custom properties gave us real variables. Cascade layers eliminated specificity wars. Container queries made components truly responsive. The language that CSS-in-JS tried to replace became the solution.\n\n## Real problems, wrong solutions\n\nEvery CSS-in-JS library tries to solve legitimate CSS challenges. But they solve yesterday's problems with today's complexity instead of using today's CSS features.\n\n### Scoping and conflicts\n\n**The CSS-in-JS approach:** Generate unique class names at runtime. Add vendor prefixes. Include only used styles.\n\n```jsx\nconst Button = styled.button`\n  background: ${props => props.primary ? '#007bff' : '#6c757d'};\n  color: white;\n  border: none;\n  padding: 0.5rem 1rem;\n`\n```\n\n**Modern CSS approach:** Use cascade layers and logical organization. Scope with custom properties.\n\n```css\n@layer components {\n  button {\n    background: var(--button-bg, var(--neutral-600));\n    color: var(--button-text, white);\n    border: none;\n    padding: var(--space-2) var(--space-4);\n  }\n\n  button[data-variant=\"primary\"] {\n    background: var(--primary-600);\n  }\n}\n```\n\nLayers solve specificity problems permanently. Custom properties provide clean theming. No runtime overhead, no build complexity, no vendor lock-in.\n\n### Dynamic styling\n\n**The CSS-in-JS approach:** Props, theme providers, and runtime style injection.\n\n```jsx\nconst Card = styled.div`\n  padding: ${props => props.compact ? '0.5rem' : '1rem'};\n  background: ${props => props.theme.surface};\n  border-radius: ${props => props.theme.radius};\n`\n\n<ThemeProvider theme={darkTheme}>\n  <Card compact primary>Content</Card>\n</ThemeProvider>\n```\n\n**Modern CSS approach:** Custom properties and data attributes.\n\n```css\n.card {\n  padding: var(--card-padding, var(--space-4));\n  background: var(--surface-color);\n  border-radius: var(--radius);\n}\n\n.card[data-compact] {\n  --card-padding: var(--space-2);\n}\n```\n\n```html\n<div class=\"card\" data-compact style=\"--surface-color: var(--dark-surface)\">\n  Content\n</div>\n```\n\nNo theme providers needed. No prop drilling. No runtime style computation. Just CSS doing what it was designed to do.\n\n## Performance that matters\n\nCSS-in-JS adds overhead at every level. Parse JavaScript. Execute functions. Generate styles. Inject into DOM. Compare that to CSS which browsers optimize at the engine level.\n\n[.columns]\n  **Runtime overhead** happens with every render. CSS-in-JS libraries must parse template literals, execute functions, and inject styles during the component lifecycle. This work repeats for every component instance.\n\n  [placeholder height=\"200\"]\n\n  **Bundle bloat** means larger downloads. Styled-components adds 42KB. Emotion adds 35KB. Both require React as a peer dependency. Meanwhile, your entire CSS design system weighs 4KB and works with any HTML.\n\n  [placeholder.blue height=\"200\"]\n\nThe performance difference becomes dramatic at scale. CSS loads once and browsers cache it forever. CSS-in-JS processes styles on every page load, every route change, every component update.\n\n## The maintenance trap\n\nCSS-in-JS creates vendor lock-in disguised as developer experience. Your styles become JavaScript code that only works with specific libraries and build tools.\n\n```jsx\n// This only works with styled-components\nconst Button = styled.button.attrs(props => ({\n  type: props.type || 'button'\n}))`\n  background: ${props => props.theme.colors.primary};\n  transition: ${props => props.theme.transitions.fast};\n\n  &:hover {\n    background: ${props => darken(0.1, props.theme.colors.primary)};\n  }\n`\n```\n\nWhen you need to migrate frameworks or update dependencies, you rewrite everything. The CSS knowledge doesn't transfer. The components don't work elsewhere.\n\nCompare this to semantic CSS:\n\n```css\nbutton {\n  background: var(--primary-600);\n  transition: var(--transition-fast);\n}\n\nbutton:hover {\n  background: var(--primary-700);\n}\n```\n\nThis CSS works with React, Vue, Svelte, or vanilla HTML. It works today and will work in 10 years. No migration needed when you change frameworks. No rewriting when libraries update.\n\n## Design system anti-patterns\n\nCSS-in-JS encourages patterns that fragment design systems. Every component becomes a styling decision point. Designers lose control over the visual language.\n\n```jsx\n// Styling scattered across components\nconst HeaderButton = styled.button`\n  background: #3b82f6;\n  border-radius: 6px;\n`\n\nconst SidebarButton = styled.button`\n  background: #2563eb;\n  border-radius: 4px;\n`\n\nconst FooterButton = styled.button`\n  background: #1d4ed8;\n  border-radius: 8px;\n`\n```\n\nEach button has slightly different colors and border radius. The design system fragments because styling decisions happen in isolation. Consistency requires vigilant code review and shared constants that developers often ignore.\n\nCSS design systems prevent this fragmentation:\n\n```css\nbutton {\n  background: var(--primary-600);\n  border-radius: var(--radius);\n}\n```\n\nOne source of truth. Consistent everywhere. Designers control the system through CSS variables. Developers use semantic HTML without making visual decisions.\n\n## Real CSS solutions\n\nModern CSS provides clean solutions for every problem CSS-in-JS tries to solve:\n\n**Custom properties** replace JavaScript variables with better performance and browser optimization.\n\n**Cascade layers** eliminate specificity conflicts more elegantly than generated class names.\n\n**Container queries** make components responsive without JavaScript media query libraries.\n\n**Logical properties** handle internationalization better than CSS-in-JS direction utilities.\n\n**Native nesting** provides the syntax benefits without build complexity.\n\nThese aren't workarounds or polyfills. They're native browser features optimized at the engine level.\n\n## Choose your complexity\n\nCSS-in-JS asks you to accept JavaScript complexity to solve CSS problems. Modern CSS asks you to learn CSS features to solve CSS problems directly.\n\nThe complexity you choose shapes your entire development experience. CSS-in-JS complexity grows with your application. CSS complexity stays constant because the browser handles optimization.\n\nWhen you bet on web standards instead of JavaScript abstractions, your investment compounds over time. Your CSS skills apply everywhere. Your design systems outlast framework changes. Your performance improves as browsers optimize further.\n\nCSS didn't get worse when CSS-in-JS emerged. CSS got better while we were looking elsewhere. It's time to look back."
  },
  {
    "path": "packages/templates/full/blog/css-is-awesome.md",
    "content": "---\ndate: 2025-08-05\n---\n\n# Modern CSS is awesome\nCSS has transformed dramatically over the past decade, but the JavaScript ecosystem hasn't noticed. While React developers debate CSS-in-JS solutions, the language itself evolved into something unrecognizable from its 2013 limitations.\n\n[placeholder.blue height=\"400\"]\n\n\n## Stuck in the past\nReact emerged when CSS was genuinely limited. No variables, no nesting, no real layout system. Global scope created conflicts. Cascade felt unpredictable. The community built tools to work around these limitations: preprocessors, naming conventions, CSS-in-JS.\n\nThese solutions became orthodoxy. We normalized the idea that CSS needs fixing, that styling should live inside components, that global scope is dangerous. The JavaScript ecosystem created an entire industry around CSS's perceived brokenness.\n\nBut CSS kept evolving. While developers debated utility frameworks and CSS-in-JS libraries, the language gained powerful native features. Variables arrived. Nesting landed in browsers. Container queries, layers, and scope solved real problems. Grid and flexbox revolutionized layout.\n\nThe gap between perception and reality widened. Modern CSS can do things that seemed impossible in 2013, but the ecosystem remains stuck in defensive patterns.\n\n\n## CSS in 2025\nToday's CSS bears little resemblance to the language React was designed to fix. Modern CSS has everything needed for sophisticated design systems.\n\n**CSS variables** create design tokens that cascade and inherit naturally:\n\n```css\n:root {\n  --primary-hue: 210;\n  --primary-color: hsl(var(--primary-hue) 60% 50%);\n  --primary-light: hsl(var(--primary-hue) 60% 90%);\n}\n\nbutton {\n  background: var(--primary-color);\n  border: 1px solid var(--primary-color);\n}\n\nbutton:hover {\n  background: var(--primary-light);\n}\n```\n\n**Native nesting** eliminates preprocessor complexity:\n\n```css\n.card {\n  padding: 1rem;\n  border-radius: 8px;\n\n  h3 {\n    margin-top: 0;\n  }\n\n  &:hover {\n    box-shadow: 0 4px 8px rgba(0,0,0,0.1);\n  }\n}\n```\n\n**CSS layers** solve specificity wars forever:\n\n```css\n@layer base, layout, components, overrides;\n\n@layer base {\n  button {  }\n}\n\n@layer components {\n  .primary {  }\n}\n\n@layer overrides {\n  .hidden {\n    display: none !important;\n  }\n}\n```\n\n**Container queries** create truly responsive components:\n\n```css\n.sidebar {\n  container-type: inline-size;\n}\n\n.card {\n  padding: 1rem;\n}\n\n@container (min-width: 300px) {\n  .card {\n    display: grid;\n    grid-template-columns: auto 1fr;\n  }\n}\n```\n\n**CSS scope** provides encapsulation without JavaScript:\n\n```css\n@scope (.component) {\n  button {\n    /* Only affects buttons inside .component */\n  }\n}\n```\n\nThis is a completely different language from what React was built to replace.\n\n## Design system best practices\n\nModern CSS enables new patterns for building maintainable design systems.\n\n### Trust HTML semantics\n\nHTML already provides most of what a design system needs. Lists have `<ul>`, `<ol>`, `<dl>`. Tables have semantic structure. Forms have fieldsets and labels. Navigation has `<nav>`. Interactive elements have `<button>`, `<details>`, `<dialog>`.\n\nStyle these native elements directly:\n\n```css\n/* Not this: component classes */\n.list-component { }\n.nav-component { }\n.button-component { }\n\n/* This: semantic elements */\nul { }\nnav { }\nbutton { }\n```\n\nUse attribute selectors (`[disabled]`, `[aria-expanded]`), pseudo-classes (`:invalid`, `:checked`), and `:has()` for state-based styling. The browser already knows what's interactive and what's not.\n\n### Class names for layout\n\nHTML can't express spatial relationships. These aren't semantic, so class names handle layout:\n\n```css\n.stack {\n  display: flex;\n  flex-direction: column;\n  gap: var(--space-m);\n}\n\n.grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n  gap: var(--space-l);\n}\n\n.columns {\n  column-count: var(--count, 2);\n  column-gap: var(--space-m);\n}\n```\n\nAdd minimal modifiers for variations:\n\n```css\n.thin { max-width: 40ch; }\n.wide { max-width: 80ch; }\n.compact { --space-m: 0.5rem; }\n```\n\nModern nested CSS eliminates the need for inner class names. A constrained system enables creative combinations without chaos.\n\n### Layer everything\n\nCSS layers solve specificity wars forever:\n\n```css\n@layer base, layout, components, overrides;\n\n@layer base {\n  /* Variables, semantic elements */\n  :root { --primary: #0066cc; }\n  button { padding: 0.5rem 1rem; }\n}\n\n@layer layout {\n  /* Spatial relationships */\n  .stack { display: flex; flex-direction: column; }\n  .grid { display: grid; }\n}\n\n@layer components {\n  /* Component variations */\n  .primary { background: var(--primary); color: white; }\n}\n\n@layer overrides {\n  /* Overrides */\n  .hidden { display: none !important; }\n}\n```\n\nEach layer has clear boundaries and purpose. No more specificity hacks, no more source order gymnastics. The cascade becomes predictable.\n\n### Keep it minimal\n\nEven complex design systems need surprisingly few classes. Maybe 10-30 total. Not 50. Definitely not 500. A design system fails when developers escape to local styling.\n\nThe best way to ensure adoption is constraint. Learning 10 classes is manageable. Learning 100 is not. Minimal systems force creative solutions within boundaries—exactly what good design requires.\n\n## The benefits\n\nModern CSS creates possibilities that CSS-in-JS can't match.\n\n**Performance** - Native CSS parsing and rendering beats JavaScript-generated styles. No runtime overhead, no flash of unstyled content, no hydration delays.\n\n**Debugging** - Browser dev tools understand CSS natively. Inspect elements, edit properties, see computed values. No source maps, no JavaScript debugging for visual issues.\n\n**Maintainability** - Design changes happen in design files. A single variable update cascades everywhere. No hunting through component props or JavaScript configurations.\n\n**Standards compliance** - CSS follows web standards that evolve carefully. Your investment compounds over decades. Framework APIs change every few years.\n\n**Design workflow** - Designers work directly with CSS. No translation layer between design tools and implementation. The feedback loop becomes immediate.\n\n**Future-proof** - Modern CSS features work in all browsers. No polyfills, no build steps, no runtime dependencies. The platform provides everything.\n\nCSS in 2025 is a mature, powerful language for building design systems. The JavaScript ecosystem just hasn't caught up yet.\n\n\n"
  },
  {
    "path": "packages/templates/full/blog/design-engineering.md",
    "content": "---\ndate: 2025-08-15\n---\n\n# What is design engineering?\nWeb development split into two camps: those who design and those who code. This division is artificial. The web is a design medium that happens to be programmable.\n\n[placeholder.yellow height=\"400\"]\n\n## The artificial divide\nModern frameworks optimize for engineering mindset. Everything becomes a programming problem. CSS becomes CSS-in-JS. Design becomes component props. Layout becomes flexbox utilities. We've turned visual decisions into code decisions.\n\nDesigners must learn React to do their job. They write JavaScript to change a color. They debug webpack to update spacing. The tool shapes the thinking, and the thinking becomes programmatic.\n\nMeanwhile, engineers build without design sense. They memorize utility classes instead of understanding visual hierarchy. They copy-paste components without grasping the underlying system. They optimize for code patterns while users struggle with confusing interfaces.\n\nThe result is neither good design nor good engineering. It's compromise in both directions.\n\n\n## Two mindsets, one medium\nDesign mindset sees patterns, rhythm, and hierarchy. It asks: How does this feel? What draws the eye? Where does attention flow?\n\nEngineering mindset sees data structures, state management, and abstractions. It asks: How does this scale? What are the edge cases? How do we test this?\n\nBoth are essential. Neither is sufficient alone. The web demands both mindsets because interfaces are both visual and functional. The mistake is believing one matters more than the other.\n\n\n[placeholder.blue height=\"300\"]\n\n\n## Reuniting the disciplines\n\nTrue design engineering happens when both mindsets have equal tools and equal power. CSS for design decisions. HTML for structure. JavaScript for business logic. Each layer owns its domain completely.\n\nThis isn't about roles or job titles. One person can embody both mindsets. Teams can specialize. The key is that the technology supports both ways of thinking equally. Design decisions happen in design tools. Programming decisions happen in programming tools. Neither compromises for the other.\n\nWhen a designer changes a color, it changes everywhere instantly. When an engineer refactors business logic, no visual elements break. When both work on the same feature, they're enhancing different layers of the same system.\n\n## What design engineering looks like\n\n**Direct manipulation** - Designers control visual properties through CSS variables, not component props. Change `--primary-color` and see it across the entire product. No pull requests, no developer bottlenecks.\n\n**Semantic structure** - Engineers build with meaningful HTML elements that designers can style predictably. A `<button>` behaves like a button. A `<nav>` means navigation. The structure describes intent, not appearance.\n\n**Clear boundaries** - Design system lives in CSS files. Business logic lives in JavaScript modules. Content lives in Markdown files. Each discipline owns its layer completely.\n\n**Shared vocabulary** - Both mindsets understand the same concepts: semantic elements, design tokens, component APIs. Communication becomes precise because the tools create common ground.\n\n\n[placeholder.red height=\"150\"]\n\n\n## The benefits\n\n**Parallel development** - Design and engineering work streams never block each other. Visual iterations happen independently of feature development. Code refactoring doesn't break designs.\n\n**Compound expertise** - Skills in each discipline deepen without interference. Designers become better at systematic thinking. Engineers develop stronger aesthetic judgment. Neither has to compromise their core competency.\n\n**Faster iteration** - Design changes deploy instantly through CSS. Feature changes don't require design system updates. The feedback loop becomes immediate for both disciplines.\n\n**Maintainable systems** - Clear separation of concerns makes debugging predictable. Visual bugs live in CSS. Logic bugs live in JavaScript. Structure bugs live in HTML. No hunting through component hierarchies.\n\n**Future-proof architecture** - Web standards evolve slowly and deliberately. CSS knowledge stays relevant for decades. HTML semantics remain stable. JavaScript fundamentals persist across framework changes.\n\nDesign engineering isn't about making everyone learn everything. It's about giving each discipline the right tools for their domain. When design and engineering have equal power in their respective layers, both can work at full potential."
  },
  {
    "path": "packages/templates/full/blog/design-systems.md",
    "content": "---\ndate: 2025-08-01\n---\n\n# What is a CSS design system?\nModern CSS has everything needed for real design systems. Variables, nesting, layers, and scope create visual languages that scale across product lines. When design lives in one place, both designers and developers work at full speed.\n\n\n[placeholder.red height=\"500\"]\n\n## The problem we created\n\nFor 15 years we've been writing CSS to survive in chaos. We use BEM because we gave up on the cascade. We use CSS-in-JS because we're scared of namespace collisions. We write utility classes because we stopped trusting our ability to name things. We compile, process, and transform CSS because we think native CSS isn't enough.\n\nThese practices assume CSS is broken and needs fixing. They're defensive strategies. They're about working around the language rather than harnessing its potential.\n\n## CSS design system defined\n\nA design system isn't a component library or a utility framework. It's a visual language expressed through CSS. Modern CSS gives us everything we need: variables, nesting, container queries, layers, and scope. Here's what makes a real design system:\n\n### Central\n\nA design system lives in one place. Not scattered across components, not mixed with JavaScript, not generated at build time. When you can see the whole system at once, you understand it. When it's fragmented across a thousand files, you have no system—just a collection of accidents waiting to happen.\n\n### Semantic\n\nHTML already contains a rich design vocabulary. Lists, tables, forms, buttons, navigation—they all have meaning. A design system extends this vocabulary rather than replacing it. A `<button>` is already interactive. A `<nav>` already means navigation. The system styles what exists instead of recreating it with divs and classes.\n\n### Minimal\n\nConstraints create consistency. A design system with 20 well-chosen classes beats one with 2000 utilities. HTML can't express spatial relationships, so classes handle layout: `.stack`, `.grid`, `.columns`. A few modifiers capture variations: `.primary`, `.compact`, `.inverted`. Everything else? Already in HTML.\n\n## Small projects\n\nStart with global styles plus area-specific CSS. This follows the classic web development pattern that pre-dated the component revolution - global stylesheets with area-specific additions. It's perfect for personal projects, prototypes, or small teams where you need the flexibility to add styles ad-hoc without teaching a formal system to others:\n\n```\n├── global.css       # Site-wide design system\n├── index.md\n└── blog/\n    ├── blog.css     # Blog-specific styles\n    ├── css-is-awesome.md\n    ├── design-systems.md\n    └── ...\n```\n\nFiles are loaded automatically based on location. The `global.css` applies everywhere. The `blog.css` only applies to pages in the blog directory. No imports, no bundling - just files where you need them.\n\n## Larger projects\n\nScale up with a centralized design system for larger teams, client work, or any project where consistency and maintainability matter more than development speed. This approach enforces constraints that prevent the CSS sprawl that kills long-term projects:\n\n```bash\nnue create full\n```\n\nThis creates a complete design system in `@shared/design/`:\n\n```\n@shared/design/\n├── base.css         # Typography, colors, spacing\n├── button.css       # All button variants\n├── content.css      # Blog posts, documentation\n├── dialog.css       # Modals, popovers\n├── document.css     # Page structure\n├── form.css         # All form elements\n├── layout.css       # Grid, stack, columns\n├── syntax.css       # Code highlighting\n├── table.css        # Data tables\n└── apps.css         # SPA-specific components\n```\n\nAll files load automatically across your entire site. Marketing pages, documentation, blogs, login screens, and single-page apps all use the same visual language. Change a variable in `base.css` and see it everywhere.\n\n## The compound effect\n\nA real design system compounds its value over time. Each project strengthens the system. Each use case refines the patterns. Each team member contributes to a shared language.\n\nWhen design is scattered in components, every project starts from scratch. You rebuild the same button in every framework migration. You rewrite the same styles for every new component library. Nothing compounds. Everything churns.\n\nWhen design is central and semantic, it goes beyond frameworks. Your CSS design system works with any HTML. It worked with jQuery. It works with React. It will work with whatever comes next. The investment compounds across technologies and time.\n\nThis is why CSS matters. Not because it's powerful—though it is. Not because it's fast—though it is. But because CSS is the only layer that survives every paradigm shift. Your React components from 2016 are obsolete. Your CSS from 2016 still works."
  },
  {
    "path": "packages/templates/full/blog/extreme-performance.md",
    "content": "\n---\ndate: 2025-09-15\n---\n\n# Extreme performance\nToday the web performance industry optimizes the wrong things. Complex bundlers, code splitting, and tree shaking treat symptoms while ignoring the root cause: JavaScript dependency for basic functionality.\n\n\n[placeholder.blue height=\"450\"]\n\n\n## The bundler trap\nModern web development assumes you need complex build tools to achieve performance. Webpack configurations span hundreds of lines. Vite promises speed through clever caching. Next.js provides automatic optimization through framework magic.\n\nThese tools work around a fundamental problem: applications that require JavaScript to render basic content. When your page needs 200KB of JavaScript to display a blog post, optimization becomes an arms race. Bundle splitting, lazy loading, and preloading try to make the inevitable download faster.\n\nBut faster delivery of unnecessary code is still unnecessary code. The real win is not needing the code at all.\n\n\n## Progressive enhancement\nThe web was designed as a layered medium. HTML provides structure and content. CSS adds presentation. JavaScript enables interaction. Each layer enhances the previous one.\n\nThis architecture enables extreme performance through careful layering:\n\n**HTML first** - Content and structure load immediately. No JavaScript required for reading, navigation, or basic functionality. Screen readers work. Search engines index everything. Users get instant content.\n\n**CSS enhancement** - Visual design loads as a separate layer. Typography, colors, spacing, and layout enhance the structured content. The page becomes beautiful as styles load.\n\n**JavaScript optional** - Interactive features enhance the styled content. Form validation, dynamic updates, and complex interactions layer on top. But the core experience works without them.\n\nMost web applications need surprisingly little JavaScript. Content sites need none. Marketing pages need minimal interaction. Even complex applications can render their initial state as HTML.\n\n## Going extreme\n\nWith proper layering, you can achieve performance that no bundler can match: everything needed to render the page in the first HTML response.\n\n### CSS inlining\n\nModern CSS best practices keep stylesheets small enough to inline completely:\n\n```yaml\n# In site.yaml\ndesign:\n  inline_css: true\n```\n\nWhen your entire design system weighs less than Tailwind's reset CSS, inlining becomes viable. The initial HTML download contains everything needed for perfect rendering. No secondary requests, no flash of unstyled content, no layout shifts.\n\nThis eliminates the CSS network request entirely. While bundlers debate optimal chunking strategies, inlined CSS delivers instantaneous rendering. Nothing can beat zero network requests.\n\n\n[placeholder.red height=\"250\"]\n\n\n### HTML-driven interactivity\n\nMany \"interactive\" features work with pure HTML:\n\n```html\n<!-- Expandable content -->\n<details>\n  <summary>Advanced options</summary>\n  <div>Content hidden by default</div>\n</details>\n\n<!-- Modal dialogs -->\n<button popovertarget=\"settings\">Settings</button>\n<dialog id=\"settings\" popover>\n  <h2>Settings</h2>\n  <button popovertarget=\"settings\">Close</button>\n</dialog>\n\n<!-- Form validation -->\n<form>\n  <input type=\"email\" required>\n  <input type=\"url\" required>\n  <button>Submit</button>\n</form>\n```\n\nNo JavaScript required. The browser provides rich interactivity through semantic HTML. Your \"complex\" interface becomes a collection of standard elements.\n\n### Minimal JavaScript\n\nWhen JavaScript is needed, modern approaches keep it tiny:\n\n```html\n<!-- Component with minimal enhancement -->\n<form onsubmit=\"handleSubmit(event)\">\n  <input name=\"email\" type=\"email\" required>\n  <button>Subscribe</button>\n\n  <script>\n    async function handleSubmit(e) {\n      e.preventDefault()\n      const data = new FormData(e.target)\n      await fetch('/subscribe', { method: 'POST', body: data })\n      location.href = '/thanks'\n    }\n  </script>\n\n</form>\n```\n\nThe form works without JavaScript - server-side processing handles submission. JavaScript enhances with smoother UX. Total enhancement cost: 200 bytes.\n\n## The performance hierarchy\n\nDifferent optimization strategies have dramatically different impact:\n\n**Bundler optimization** - 10-30% improvement. Code splitting, tree shaking, and compression optimize delivery of necessary code.\n\n**Framework switching** - 50-70% improvement. Moving from React to lighter alternatives reduces baseline JavaScript requirements.\n\n**Progressive enhancement** - 80-95% improvement. Making JavaScript optional eliminates most performance bottlenecks.\n\n**CSS inlining** - 99% improvement for initial render. Zero network requests for visual presentation beats any bundler optimization.\n\nThe biggest gains come from architectural decisions, not build tools.\n\n## Measuring what matters\n\nTraditional metrics miss the point:\n\n**Time to Interactive** becomes irrelevant when the page is interactive immediately through HTML.\n\n**First Contentful Paint** happens instantly when CSS is inlined and content is in HTML.\n\n**Cumulative Layout Shift** becomes zero when all styling loads with initial HTML.\n\n**Lighthouse scores** improve dramatically when you need fewer resources to begin with.\n\nThe ultimate performance optimization is not needing to optimize. When your architecture eliminates bottlenecks, complex tooling becomes unnecessary.\n\n## The benefits\n\nExtreme performance changes user experience fundamentally:\n\n**Instant loading** - Pages render immediately. No loading states, no progressive enhancement delays, no \"app is loading\" screens.\n\n**Reliable experience** - Works on slow connections, old devices, with JavaScript disabled. Progressive enhancement ensures universal access.\n\n**Simple deployment** - No build complexity, no CDN configuration, no cache invalidation strategies. Static HTML files work everywhere.\n\n**Maintainable codebase** - Less JavaScript means fewer bugs. Smaller surface area means easier debugging. Standard HTML/CSS means longer-lasting code.\n\n**Lower costs** - Less bandwidth, less server processing, less CDN usage. Extreme performance is also extreme efficiency.\n\nThe web's layered architecture enables performance that modern frameworks can't achieve. HTML, CSS, and minimal JavaScript create experiences faster than any bundler optimization. When you optimize architecture instead of build tools, users notice the difference.\n"
  },
  {
    "path": "packages/templates/full/blog/index.md",
    "content": "\n---\npagehead: false\nskip: true\n---\n\n# Blog\n\n[blog-entries]\n\n"
  },
  {
    "path": "packages/templates/full/blog/web-standards.md",
    "content": "\n---\ndate: 2025-09-05\nauthor: Maude Bonnet\n---\n\n# Web standards never get old\nHTML from 2006 still works. CSS only grows more powerful. JavaScript remains the language of the web. Framework APIs change every few years, but web standards compound over decades.\n\n[placeholder.blue height=\"350\"]\n\n## The framework cycle\n\nRemember when jQuery was essential? Then Backbone became the standard. Angular took over, then React dominated. Each promised to be different, to last longer, to solve problems for good.\n\nThe pattern repeats every 3-5 years. New framework emerges. Ecosystem forms around it. Tools multiply. Then something newer appears. The cycle starts again. Developers rewrite applications, learn new APIs, migrate component libraries.\n\nMeanwhile, HTML keeps working. CSS adds new features. JavaScript evolves through standards committees with careful consideration for backwards compatibility. The foundation stays solid while the abstractions churn.\n\n## What survives\n\nYour HTML semantics from 2010 render perfectly in 2025 browsers. Forms work the same way. Navigation works the same way. The `<button>` element still handles clicks, focus, and keyboard navigation automatically.\n\nCSS you wrote before flexbox still works. Grid layout added new possibilities without breaking old code. Custom properties enhanced styling without replacing selectors. Each addition strengthened the language instead of fragmenting it.\n\nJavaScript functions from ES5 run unchanged in modern browsers. ES6 classes didn't break ES5 functions. Async/await didn't deprecate promises. The language grows by addition, not replacement.\n\n## Skills that compound\n\nFramework knowledge expires. How many React developers remember class components? How many know Higher-Order Components? How many still use Redux? Each pattern had its moment, then became legacy code requiring migration.\n\nWeb standards knowledge accumulates. Learning CSS grid doesn't obsolete flexbox. Understanding custom properties enhances all CSS work. Mastering semantic HTML improves every project. Each skill reinforces the others.\n\n[.columns]\n  **Framework expertise** becomes outdated as new versions change APIs. You relearn patterns, migrate codebases, and update dependencies. The knowledge has a shelf life measured in years.\n\n  [placeholder height=\"180\"]\n\n  **Standards expertise** grows more valuable with time. HTML accessibility principles apply everywhere. CSS layout techniques work in any framework. JavaScript fundamentals transfer across environments.\n\n  [placeholder.red height=\"180\"]\n\n## Products that last\n\nApplications built on web standards age gracefully. HTML semantics work with future assistive technologies. CSS layouts adapt to new screen sizes. JavaScript modules integrate with whatever comes next.\n\nFramework applications require constant maintenance. Dependencies update. APIs change. Security patches demand code changes. The technical debt accumulates until migration becomes necessary.\n\nWhen your foundation is standards-based, maintenance focuses on features instead of infrastructure. You add capabilities rather than fixing compatibility. The application improves instead of just surviving.\n\n## The long view\n\nWeb standards move slowly by design. Committees debate changes for years. Browser vendors implement carefully. Backwards compatibility gets absolute priority. This deliberate pace creates stability that frameworks can't match.\n\nThe web platform in 2025 runs code from 2005 perfectly. Try running a jQuery plugin from 2015 in a modern React app. Try using Angular 1 components in Angular 17. The web survives. The frameworks fragment.\n\nStandards-first development isn't about avoiding modern tools. It's about building on foundations that last. Use frameworks when they add value. But remember what survives when they don't.\n\nHTML, CSS, and JavaScript aren't going anywhere. Your investment in web standards compounds forever.\n"
  },
  {
    "path": "packages/templates/full/contact/contact.html",
    "content": "<!doctype dhtml lib>\n\n<script>\n  import { post } from 'crud'\n</script>\n\n<form :is=\"contact-form\" :onsubmit=\"submit\" class=\"thin\">\n  <label>\n    <h3>Full name</h3>\n    <input type=\"text\" name=\"name\" required autocomplete=\"name\">\n  </label>\n\n  <label>\n    <h3>Company email</h3>\n    <input type=\"email\" name=\"email\" required autocomplete=\"email\">\n  </label>\n\n  <label>\n    <h3>Company website</h3>\n    <input type=\"url\" name=\"website\" autocomplete=\"url\">\n  </label>\n\n  <label>\n    <h3>How can we help?</h3>\n    <textarea name=\"comment\" rows=\"4\"\n      placeholder=\"Tell us about your project, needs, and timeline\"></textarea>\n  </label>\n\n  <label>\n    <input type=\"checkbox\" name=\"subscribed\" value=\"1\">\n    Get emails from Acme about product updates. You can unsubscribe at any time.\n  </label>\n\n  <p>\n    <button type=\"submit\">Submit info</button>\n  </p>\n\n  <script>\n    async submit(e) {\n      const data = Object.fromEntries(new FormData(e.target))\n      await post('/api/leads', data)\n      location.href = 'thanks'\n    }\n  </script>\n\n</form>\n\n"
  },
  {
    "path": "packages/templates/full/contact/index.md",
    "content": "\n\n# Contact us\n\n[.columns]\n  [contact-form]\n  ---\n  [placeholder.yellow height=\"500\"]"
  },
  {
    "path": "packages/templates/full/contact/thanks.md",
    "content": "\n# Thank you\nWe got your message and will be in touch soon. Someone from our team will get back to you within 24 hours.\n\n"
  },
  {
    "path": "packages/templates/full/docs/accessible-design.md",
    "content": "\n---\ntags: [ design ]\norder: 5\n---\n\n# Accessible design principles\nDesign inclusive interfaces that work for everyone from the start.\n\n[placeholder height=\"500\"]\n\n## Introduction\nThe rest of this document is placeholder content to demonstrate the documentation layout and structure. In a real implementation, this would contain detailed guidance, best practices, and working examples specific to the topic.\n\n```html\n<!doctype html>\n<article>\n  <header>\n    <h1>Example Documentation Page</h1>\n    <p>This shows how content would be structured</p>\n  </header>\n\n  <section>\n    <h2>Key Concepts</h2>\n    <p>Real documentation would explain fundamental principles here</p>\n  </section>\n\n  <aside>\n    <h3>Quick Reference</h3>\n    <ul>\n      <li>Important point one</li>\n      <li>Important point two</li>\n    </ul>\n  </aside>\n</article>\n```\n\n## Diving into details\n\nThis section would provide comprehensive explanations, step-by-step instructions, and practical examples. The placeholder content demonstrates typical documentation patterns while maintaining the Form & Function brand voice focused on standards-first development.\n\n```css\n/* Layered styling with cascade control */\n@layer base, components, utilities;\n\n\n/* Advanced design system with computed color variations */\n@layer base {\n  :root {\n    --primary-h: 220;\n    --primary-s: 85%;\n    --primary-l: 55%;\n\n    /* Computed color scales using relative color syntax */\n    --primary-50: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 40%));\n    --primary-100: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 30%));\n    --primary-200: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 20%));\n\n    /* Adaptive spacing scale */\n    --space-unit: clamp(0.25rem, 0.5vw, 0.5rem);\n    --space-1: calc(var(--space-unit) * 1);\n    --space-2: calc(var(--space-unit) * 2);\n  }\n}\n```\n\n## Implementation guide\nReal documentation would include practical implementation steps, common pitfalls to avoid, and troubleshooting guidance. This placeholder structure shows how technical content would be organized for maximum clarity and usability.\n\n\n## Conclusion\n\nActual documentation would summarize key takeaways and provide next steps for readers. This template demonstrates the clean, focused approach that makes technical documentation both usable and maintainable.\n\n**Key principles for this topic would include:**\n\n- Start with semantic HTML foundations\n- Use progressive enhancement techniques\n- Prioritize performance and accessibility\n- Test across browsers and devices\n- Maintain consistency with design systems\n- Document decisions for future reference"
  },
  {
    "path": "packages/templates/full/docs/app.yaml",
    "content": "\nheading_ids: true\n"
  },
  {
    "path": "packages/templates/full/docs/color-theory.md",
    "content": "---\ntags: [ design ]\norder: 3\n---\n\n# Color theory for interfaces\nCreate accessible color palettes that communicate meaning and support usability.\n\n[placeholder height=\"500\"]\n\n## Introduction\nThe rest of this document is placeholder content to demonstrate the documentation layout and structure. In a real implementation, this would contain detailed guidance, best practices, and working examples specific to the topic.\n\n```html\n<!doctype html>\n<article>\n  <header>\n    <h1>Example Documentation Page</h1>\n    <p>This shows how content would be structured</p>\n  </header>\n\n  <section>\n    <h2>Key Concepts</h2>\n    <p>Real documentation would explain fundamental principles here</p>\n  </section>\n\n  <aside>\n    <h3>Quick Reference</h3>\n    <ul>\n      <li>Important point one</li>\n      <li>Important point two</li>\n    </ul>\n  </aside>\n</article>\n```\n\n## Diving into details\n\nThis section would provide comprehensive explanations, step-by-step instructions, and practical examples. The placeholder content demonstrates typical documentation patterns while maintaining the Form & Function brand voice focused on standards-first development.\n\n```css\n/* Layered styling with cascade control */\n@layer base, components, utilities;\n\n\n/* Advanced design system with computed color variations */\n@layer base {\n  :root {\n    --primary-h: 220;\n    --primary-s: 85%;\n    --primary-l: 55%;\n\n    /* Computed color scales using relative color syntax */\n    --primary-50: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 40%));\n    --primary-100: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 30%));\n    --primary-200: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 20%));\n\n    /* Adaptive spacing scale */\n    --space-unit: clamp(0.25rem, 0.5vw, 0.5rem);\n    --space-1: calc(var(--space-unit) * 1);\n    --space-2: calc(var(--space-unit) * 2);\n  }\n}\n```\n\n## Implementation guide\nReal documentation would include practical implementation steps, common pitfalls to avoid, and troubleshooting guidance. This placeholder structure shows how technical content would be organized for maximum clarity and usability.\n\n\n## Conclusion\n\nActual documentation would summarize key takeaways and provide next steps for readers. This template demonstrates the clean, focused approach that makes technical documentation both usable and maintainable.\n\n**Key principles for this topic would include:**\n\n- Start with semantic HTML foundations\n- Use progressive enhancement techniques\n- Prioritize performance and accessibility\n- Test across browsers and devices\n- Maintain consistency with design systems\n- Document decisions for future reference"
  },
  {
    "path": "packages/templates/full/docs/css-architecture-patterns.md",
    "content": "---\ntags: [ dev ]\norder: 2\n---\n\n\n# CSS architecture patterns\nOrganize stylesheets for maintainability, performance, and team collaboration.\n\n[placeholder height=\"500\"]\n\n## Introduction\nThe rest of this document is placeholder content to demonstrate the documentation layout and structure. In a real implementation, this would contain detailed guidance, best practices, and working examples specific to the topic.\n\n```html\n<!doctype html>\n<article>\n  <header>\n    <h1>Example Documentation Page</h1>\n    <p>This shows how content would be structured</p>\n  </header>\n\n  <section>\n    <h2>Key Concepts</h2>\n    <p>Real documentation would explain fundamental principles here</p>\n  </section>\n\n  <aside>\n    <h3>Quick Reference</h3>\n    <ul>\n      <li>Important point one</li>\n      <li>Important point two</li>\n    </ul>\n  </aside>\n</article>\n```\n\n## Diving into details\n\nThis section would provide comprehensive explanations, step-by-step instructions, and practical examples. The placeholder content demonstrates typical documentation patterns while maintaining the Form & Function brand voice focused on standards-first development.\n\n```css\n/* Layered styling with cascade control */\n@layer base, components, utilities;\n\n\n/* Advanced design system with computed color variations */\n@layer base {\n  :root {\n    --primary-h: 220;\n    --primary-s: 85%;\n    --primary-l: 55%;\n\n    /* Computed color scales using relative color syntax */\n    --primary-50: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 40%));\n    --primary-100: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 30%));\n    --primary-200: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 20%));\n\n    /* Adaptive spacing scale */\n    --space-unit: clamp(0.25rem, 0.5vw, 0.5rem);\n    --space-1: calc(var(--space-unit) * 1);\n    --space-2: calc(var(--space-unit) * 2);\n  }\n}\n```\n\n## Implementation guide\nReal documentation would include practical implementation steps, common pitfalls to avoid, and troubleshooting guidance. This placeholder structure shows how technical content would be organized for maximum clarity and usability.\n\n\n## Conclusion\n\nActual documentation would summarize key takeaways and provide next steps for readers. This template demonstrates the clean, focused approach that makes technical documentation both usable and maintainable.\n\n**Key principles for this topic would include:**\n\n- Start with semantic HTML foundations\n- Use progressive enhancement techniques\n- Prioritize performance and accessibility\n- Test across browsers and devices\n- Maintain consistency with design systems\n- Document decisions for future reference"
  },
  {
    "path": "packages/templates/full/docs/design-systems-that-scale.md",
    "content": "\n---\ntags: [ design ]\norder: 1\n---\n\n# Design systems that scale\nBuild consistent visual languages with CSS that work across your entire product ecosystem.\n\n[placeholder height=\"500\"]\n\n## Introduction\nThe rest of this document is placeholder content to demonstrate the documentation layout and structure. In a real implementation, this would contain detailed guidance, best practices, and working examples specific to the topic.\n\n```html\n<!doctype html>\n<article>\n  <header>\n    <h1>Example Documentation Page</h1>\n    <p>This shows how content would be structured</p>\n  </header>\n\n  <section>\n    <h2>Key Concepts</h2>\n    <p>Real documentation would explain fundamental principles here</p>\n  </section>\n\n  <aside>\n    <h3>Quick Reference</h3>\n    <ul>\n      <li>Important point one</li>\n      <li>Important point two</li>\n    </ul>\n  </aside>\n</article>\n```\n\n## Diving into details\n\nThis section would provide comprehensive explanations, step-by-step instructions, and practical examples. The placeholder content demonstrates typical documentation patterns while maintaining the Form & Function brand voice focused on standards-first development.\n\n```css\n/* Layered styling with cascade control */\n@layer base, components, utilities;\n\n\n/* Advanced design system with computed color variations */\n@layer base {\n  :root {\n    --primary-h: 220;\n    --primary-s: 85%;\n    --primary-l: 55%;\n\n    /* Computed color scales using relative color syntax */\n    --primary-50: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 40%));\n    --primary-100: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 30%));\n    --primary-200: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 20%));\n\n    /* Adaptive spacing scale */\n    --space-unit: clamp(0.25rem, 0.5vw, 0.5rem);\n    --space-1: calc(var(--space-unit) * 1);\n    --space-2: calc(var(--space-unit) * 2);\n  }\n}\n```\n\n## Implementation guide\nReal documentation would include practical implementation steps, common pitfalls to avoid, and troubleshooting guidance. This placeholder structure shows how technical content would be organized for maximum clarity and usability.\n\n\n## Conclusion\n\nActual documentation would summarize key takeaways and provide next steps for readers. This template demonstrates the clean, focused approach that makes technical documentation both usable and maintainable.\n\n**Key principles for this topic would include:**\n\n- Start with semantic HTML foundations\n- Use progressive enhancement techniques\n- Prioritize performance and accessibility\n- Test across browsers and devices\n- Maintain consistency with design systems\n- Document decisions for future reference"
  },
  {
    "path": "packages/templates/full/docs/index.md",
    "content": "---\ntitle: Documentation\n---\n\n## Design\n[contents :items=\"design\"]\n\n## Development\n[contents :items=\"dev\"]\n"
  },
  {
    "path": "packages/templates/full/docs/layout-with-css.md",
    "content": "\n---\ntags: [ design ]\norder: 4\n---\n\n# Layout with modern CSS\nUse grid, flexbox, and container queries to build responsive layouts without frameworks.\n\n[placeholder height=\"500\"]\n\n## Introduction\nThe rest of this document is placeholder content to demonstrate the documentation layout and structure. In a real implementation, this would contain detailed guidance, best practices, and working examples specific to the topic.\n\n```html\n<!doctype html>\n<article>\n  <header>\n    <h1>Example Documentation Page</h1>\n    <p>This shows how content would be structured</p>\n  </header>\n\n  <section>\n    <h2>Key Concepts</h2>\n    <p>Real documentation would explain fundamental principles here</p>\n  </section>\n\n  <aside>\n    <h3>Quick Reference</h3>\n    <ul>\n      <li>Important point one</li>\n      <li>Important point two</li>\n    </ul>\n  </aside>\n</article>\n```\n\n## Diving into details\n\nThis section would provide comprehensive explanations, step-by-step instructions, and practical examples. The placeholder content demonstrates typical documentation patterns while maintaining the Form & Function brand voice focused on standards-first development.\n\n```css\n/* Layered styling with cascade control */\n@layer base, components, utilities;\n\n\n/* Advanced design system with computed color variations */\n@layer base {\n  :root {\n    --primary-h: 220;\n    --primary-s: 85%;\n    --primary-l: 55%;\n\n    /* Computed color scales using relative color syntax */\n    --primary-50: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 40%));\n    --primary-100: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 30%));\n    --primary-200: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 20%));\n\n    /* Adaptive spacing scale */\n    --space-unit: clamp(0.25rem, 0.5vw, 0.5rem);\n    --space-1: calc(var(--space-unit) * 1);\n    --space-2: calc(var(--space-unit) * 2);\n  }\n}\n```\n\n## Implementation guide\nReal documentation would include practical implementation steps, common pitfalls to avoid, and troubleshooting guidance. This placeholder structure shows how technical content would be organized for maximum clarity and usability.\n\n\n## Conclusion\n\nActual documentation would summarize key takeaways and provide next steps for readers. This template demonstrates the clean, focused approach that makes technical documentation both usable and maintainable.\n\n**Key principles for this topic would include:**\n\n- Start with semantic HTML foundations\n- Use progressive enhancement techniques\n- Prioritize performance and accessibility\n- Test across browsers and devices\n- Maintain consistency with design systems\n- Document decisions for future reference"
  },
  {
    "path": "packages/templates/full/docs/layout.html",
    "content": "<!html lib>\n\n<table :is=\"contents\">\n  <tr :each=\"page in items\">\n    <td><a href=\"{ page.url }\">{ page.title }</a></td>\n    <td>{{ markdown(page.description) }}</td>\n  </tr>\n</table>\n\n\n<anchors>\n  <p :each=\"el in links\">\n    <a href=\"#{ el.id }\">{ el.text }</a>\n  </p>\n\n  <script>\n    this.links = this.headings.filter(el => el.level == 2)\n  </script>\n</anchors>\n"
  },
  {
    "path": "packages/templates/full/docs/performance-optimization.md",
    "content": "\n---\ntags: [ dev ]\norder: 4\n---\n\n# Performance optimization\nDeliver fast-loading sites through efficient code and smart resource management.\n\n[placeholder height=\"500\"]\n\n## Introduction\nThe rest of this document is placeholder content to demonstrate the documentation layout and structure. In a real implementation, this would contain detailed guidance, best practices, and working examples specific to the topic.\n\n```html\n<!doctype html>\n<article>\n  <header>\n    <h1>Example Documentation Page</h1>\n    <p>This shows how content would be structured</p>\n  </header>\n\n  <section>\n    <h2>Key Concepts</h2>\n    <p>Real documentation would explain fundamental principles here</p>\n  </section>\n\n  <aside>\n    <h3>Quick Reference</h3>\n    <ul>\n      <li>Important point one</li>\n      <li>Important point two</li>\n    </ul>\n  </aside>\n</article>\n```\n\n## Diving into details\n\nThis section would provide comprehensive explanations, step-by-step instructions, and practical examples. The placeholder content demonstrates typical documentation patterns while maintaining the Form & Function brand voice focused on standards-first development.\n\n```css\n/* Layered styling with cascade control */\n@layer base, components, utilities;\n\n\n/* Advanced design system with computed color variations */\n@layer base {\n  :root {\n    --primary-h: 220;\n    --primary-s: 85%;\n    --primary-l: 55%;\n\n    /* Computed color scales using relative color syntax */\n    --primary-50: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 40%));\n    --primary-100: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 30%));\n    --primary-200: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 20%));\n\n    /* Adaptive spacing scale */\n    --space-unit: clamp(0.25rem, 0.5vw, 0.5rem);\n    --space-1: calc(var(--space-unit) * 1);\n    --space-2: calc(var(--space-unit) * 2);\n  }\n}\n```\n\n## Implementation guide\nReal documentation would include practical implementation steps, common pitfalls to avoid, and troubleshooting guidance. This placeholder structure shows how technical content would be organized for maximum clarity and usability.\n\n\n## Conclusion\n\nActual documentation would summarize key takeaways and provide next steps for readers. This template demonstrates the clean, focused approach that makes technical documentation both usable and maintainable.\n\n**Key principles for this topic would include:**\n\n- Start with semantic HTML foundations\n- Use progressive enhancement techniques\n- Prioritize performance and accessibility\n- Test across browsers and devices\n- Maintain consistency with design systems\n- Document decisions for future reference"
  },
  {
    "path": "packages/templates/full/docs/progressive-enhancement.md",
    "content": "---\ntags: [ dev ]\norder: 3\n---\n\n# Progressive enhancement\nBuild resilient web experiences that work everywhere and improve with capability.\n\n[placeholder height=\"500\"]\n\n## Introduction\nThe rest of this document is placeholder content to demonstrate the documentation layout and structure. In a real implementation, this would contain detailed guidance, best practices, and working examples specific to the topic.\n\n```html\n<!doctype html>\n<article>\n  <header>\n    <h1>Example Documentation Page</h1>\n    <p>This shows how content would be structured</p>\n  </header>\n\n  <section>\n    <h2>Key Concepts</h2>\n    <p>Real documentation would explain fundamental principles here</p>\n  </section>\n\n  <aside>\n    <h3>Quick Reference</h3>\n    <ul>\n      <li>Important point one</li>\n      <li>Important point two</li>\n    </ul>\n  </aside>\n</article>\n```\n\n## Diving into details\n\nThis section would provide comprehensive explanations, step-by-step instructions, and practical examples. The placeholder content demonstrates typical documentation patterns while maintaining the Form & Function brand voice focused on standards-first development.\n\n```css\n/* Layered styling with cascade control */\n@layer base, components, utilities;\n\n\n/* Advanced design system with computed color variations */\n@layer base {\n  :root {\n    --primary-h: 220;\n    --primary-s: 85%;\n    --primary-l: 55%;\n\n    /* Computed color scales using relative color syntax */\n    --primary-50: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 40%));\n    --primary-100: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 30%));\n    --primary-200: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 20%));\n\n    /* Adaptive spacing scale */\n    --space-unit: clamp(0.25rem, 0.5vw, 0.5rem);\n    --space-1: calc(var(--space-unit) * 1);\n    --space-2: calc(var(--space-unit) * 2);\n  }\n}\n```\n\n## Implementation guide\nReal documentation would include practical implementation steps, common pitfalls to avoid, and troubleshooting guidance. This placeholder structure shows how technical content would be organized for maximum clarity and usability.\n\n\n## Conclusion\n\nActual documentation would summarize key takeaways and provide next steps for readers. This template demonstrates the clean, focused approach that makes technical documentation both usable and maintainable.\n\n**Key principles for this topic would include:**\n\n- Start with semantic HTML foundations\n- Use progressive enhancement techniques\n- Prioritize performance and accessibility\n- Test across browsers and devices\n- Maintain consistency with design systems\n- Document decisions for future reference"
  },
  {
    "path": "packages/templates/full/docs/semantic-html.md",
    "content": "\n---\ntags: [ dev ]\norder: 1\n---\n\n# Semantic HTML structure\nWrite meaningful markup that browsers and assistive technologies understand natively.\n\n[placeholder height=\"500\"]\n\n## Introduction\nThe rest of this document is placeholder content to demonstrate the documentation layout and structure. In a real implementation, this would contain detailed guidance, best practices, and working examples specific to the topic.\n\n```html\n<!doctype html>\n<article>\n  <header>\n    <h1>Example Documentation Page</h1>\n    <p>This shows how content would be structured</p>\n  </header>\n\n  <section>\n    <h2>Key Concepts</h2>\n    <p>Real documentation would explain fundamental principles here</p>\n  </section>\n\n  <aside>\n    <h3>Quick Reference</h3>\n    <ul>\n      <li>Important point one</li>\n      <li>Important point two</li>\n    </ul>\n  </aside>\n</article>\n```\n\n## Diving into details\n\nThis section would provide comprehensive explanations, step-by-step instructions, and practical examples. The placeholder content demonstrates typical documentation patterns while maintaining the Form & Function brand voice focused on standards-first development.\n\n```css\n/* Layered styling with cascade control */\n@layer base, components, utilities;\n\n\n/* Advanced design system with computed color variations */\n@layer base {\n  :root {\n    --primary-h: 220;\n    --primary-s: 85%;\n    --primary-l: 55%;\n\n    /* Computed color scales using relative color syntax */\n    --primary-50: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 40%));\n    --primary-100: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 30%));\n    --primary-200: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 20%));\n\n    /* Adaptive spacing scale */\n    --space-unit: clamp(0.25rem, 0.5vw, 0.5rem);\n    --space-1: calc(var(--space-unit) * 1);\n    --space-2: calc(var(--space-unit) * 2);\n  }\n}\n```\n\n## Implementation guide\nReal documentation would include practical implementation steps, common pitfalls to avoid, and troubleshooting guidance. This placeholder structure shows how technical content would be organized for maximum clarity and usability.\n\n\n## Conclusion\n\nActual documentation would summarize key takeaways and provide next steps for readers. This template demonstrates the clean, focused approach that makes technical documentation both usable and maintainable.\n\n**Key principles for this topic would include:**\n\n- Start with semantic HTML foundations\n- Use progressive enhancement techniques\n- Prioritize performance and accessibility\n- Test across browsers and devices\n- Maintain consistency with design systems\n- Document decisions for future reference"
  },
  {
    "path": "packages/templates/full/docs/testing-web-standards.md",
    "content": "\n---\ntags: [ dev ]\norder: 5\n---\n\n# Testing web standards\nValidate accessibility, performance, and cross-browser compatibility effectively.\n\n[placeholder height=\"500\"]\n\n## Introduction\nThe rest of this document is placeholder content to demonstrate the documentation layout and structure. In a real implementation, this would contain detailed guidance, best practices, and working examples specific to the topic.\n\n```html\n<!doctype html>\n<article>\n  <header>\n    <h1>Example Documentation Page</h1>\n    <p>This shows how content would be structured</p>\n  </header>\n\n  <section>\n    <h2>Key Concepts</h2>\n    <p>Real documentation would explain fundamental principles here</p>\n  </section>\n\n  <aside>\n    <h3>Quick Reference</h3>\n    <ul>\n      <li>Important point one</li>\n      <li>Important point two</li>\n    </ul>\n  </aside>\n</article>\n```\n\n## Diving into details\n\nThis section would provide comprehensive explanations, step-by-step instructions, and practical examples. The placeholder content demonstrates typical documentation patterns while maintaining the Form & Function brand voice focused on standards-first development.\n\n```css\n/* Layered styling with cascade control */\n@layer base, components, utilities;\n\n\n/* Advanced design system with computed color variations */\n@layer base {\n  :root {\n    --primary-h: 220;\n    --primary-s: 85%;\n    --primary-l: 55%;\n\n    /* Computed color scales using relative color syntax */\n    --primary-50: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 40%));\n    --primary-100: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 30%));\n    --primary-200: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 20%));\n\n    /* Adaptive spacing scale */\n    --space-unit: clamp(0.25rem, 0.5vw, 0.5rem);\n    --space-1: calc(var(--space-unit) * 1);\n    --space-2: calc(var(--space-unit) * 2);\n  }\n}\n```\n\n## Implementation guide\nReal documentation would include practical implementation steps, common pitfalls to avoid, and troubleshooting guidance. This placeholder structure shows how technical content would be organized for maximum clarity and usability.\n\n\n## Conclusion\n\nActual documentation would summarize key takeaways and provide next steps for readers. This template demonstrates the clean, focused approach that makes technical documentation both usable and maintainable.\n\n**Key principles for this topic would include:**\n\n- Start with semantic HTML foundations\n- Use progressive enhancement techniques\n- Prioritize performance and accessibility\n- Test across browsers and devices\n- Maintain consistency with design systems\n- Document decisions for future reference"
  },
  {
    "path": "packages/templates/full/docs/web-typo-fundamentals.md",
    "content": "\n---\ntags: [ design ]\norder: 2\n---\n\n# Web typography fundamentals\nMaster typographic hierarchy, spacing, and readability for digital interfaces.\n\n[placeholder height=\"500\"]\n\n## Introduction\nThe rest of this document is placeholder content to demonstrate the documentation layout and structure. In a real implementation, this would contain detailed guidance, best practices, and working examples specific to the topic.\n\n```html\n<!doctype html>\n<article>\n  <header>\n    <h1>Example Documentation Page</h1>\n    <p>This shows how content would be structured</p>\n  </header>\n\n  <section>\n    <h2>Key Concepts</h2>\n    <p>Real documentation would explain fundamental principles here</p>\n  </section>\n\n  <aside>\n    <h3>Quick Reference</h3>\n    <ul>\n      <li>Important point one</li>\n      <li>Important point two</li>\n    </ul>\n  </aside>\n</article>\n```\n\n## Diving into details\n\nThis section would provide comprehensive explanations, step-by-step instructions, and practical examples. The placeholder content demonstrates typical documentation patterns while maintaining the Form & Function brand voice focused on standards-first development.\n\n```css\n/* Layered styling with cascade control */\n@layer base, components, utilities;\n\n\n/* Advanced design system with computed color variations */\n@layer base {\n  :root {\n    --primary-h: 220;\n    --primary-s: 85%;\n    --primary-l: 55%;\n\n    /* Computed color scales using relative color syntax */\n    --primary-50: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 40%));\n    --primary-100: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 30%));\n    --primary-200: hsl(from hsl(var(--primary-h) var(--primary-s) var(--primary-l)) h s calc(l + 20%));\n\n    /* Adaptive spacing scale */\n    --space-unit: clamp(0.25rem, 0.5vw, 0.5rem);\n    --space-1: calc(var(--space-unit) * 1);\n    --space-2: calc(var(--space-unit) * 2);\n  }\n}\n```\n\n## Implementation guide\nReal documentation would include practical implementation steps, common pitfalls to avoid, and troubleshooting guidance. This placeholder structure shows how technical content would be organized for maximum clarity and usability.\n\n\n## Conclusion\n\nActual documentation would summarize key takeaways and provide next steps for readers. This template demonstrates the clean, focused approach that makes technical documentation both usable and maintainable.\n\n**Key principles for this topic would include:**\n\n- Start with semantic HTML foundations\n- Use progressive enhancement techniques\n- Prioritize performance and accessibility\n- Test across browsers and devices\n- Maintain consistency with design systems\n- Document decisions for future reference"
  },
  {
    "path": "packages/templates/full/index.md",
    "content": "\n# The new standard for web design and development\nForm & Function is an example brand created to demonstrate this multi-page application template with placeholder content. The images are simple HTML divs with Bauhaus-inspired colors and the CSS uses an intentionally minimal brutalist approach. Just 4-5kb covers both the content site and a single-page app.\n\n[placeholder.blue height=\"450\"]\n\n---\n\n# Design systems that scale with your business\nBuild consistent interfaces, streamlined workflows, and maintainable codebases with our systematic approach to web development.\n\n[.columns]\n  ## Standards-first development approach\n  We build with semantic HTML, modern CSS, and clean JavaScript that browsers understand natively.\n\n  [placeholder height=\"300\"]\n\n  ## Performance without compromise\n  Lightning-fast websites that work perfectly on every device. No bloated frameworks or unnecessary complexity.\n\n  [placeholder.red height=\"300\"]\n\n\n[.blocks]\n  ### Clean code architecture\n  Separation of concerns means your content, design, and functionality stay organized and maintainable as you grow.\n\n  ### Future-proof foundations\n  Web standards evolve slowly. Your investment in HTML, CSS, and JS compounds over decades.\n\n  ### Designer-developer harmony\n  Clear boundaries between design and code enable teams to work in parallel without stepping on each other.\n\n---\n\n[.columns]\n  # Design systems built to last\n  Central visual language expressed through CSS that works across your entire product ecosystem and brand identity.\n\n  [placeholder.yellow height=\"250\"]\n\n  - **Semantic HTML structure** Build with elements that browsers and assistive technologies understand by default.\n\n  - **Systematic CSS design** Consistent colors, typography, and spacing controlled from a single source of truth.\n\n  - **Minimal class systems** Constrained design vocabulary that prevents inconsistency and reduces maintenance overhead.\n\n  ---\n  [placeholder height=\"730\"]\n\n\n---\n\n# Built for the present. Ready for the future.\nModern web standards provide everything you need to build fast, accessible, and maintainable digital products.\n\n[placeholder.red height=\"330\"]\n\n[.columns]\n  [placeholder height=\"230\"]\n\n  **Web standards never expire**, unlike framework APIs that change every few years. HTML from 2006 still works. CSS only grows more powerful. JavaScript remains the language of the web. These skills compound over decades and your products stay fresh without constant rewrites or technical debt accumulation.\n\n\n  **Performance through simplicity** means faster load times, better user experience, and lower hosting costs. When every piece serves a purpose, your sites become lean and efficient. No unnecessary abstractions, no bloated dependencies, no complex build processes that slow down development cycles.\n\n  [placeholder height=\"230\"]\n"
  },
  {
    "path": "packages/templates/full/login/index.html",
    "content": "<!doctype dhtml>\n\n<script>\n  import { post } from 'crud'\n</script>\n\n<h1>Admin login</h1>\n\n<form :onsubmit=\"submit\" class=\"thin\">\n  <div class=\"error note\" :if=\"failure\">\n    Failure\n  </div>\n\n  <label>\n    <h3>Email</h3>\n    <input name=\"email\" type=\"email\" value=\"admin@example.com\"\n      autofocus autocomplete=\"email\" class=\"fullsize\">\n  </label>\n\n  <label>\n    <h3>Password</h3>\n    <input name=\"password\" type=\"password\" value=\"demo123\"\n      autocomplete=\"current-password\" class=\"fullsize\">\n  </label>\n\n  <p>\n    <button class=\"fullsize\" :disabled>Log in</button>\n  </p>\n\n  <script>\n    submit(e) {\n      this.disabled = true\n      const data = Object.fromEntries(new FormData(e.target))\n\n      post('/api/login', data).then(ret => {\n        localStorage.$sid = ret.sessionId\n        location.href = '/admin/'\n\n      }).catch(err => {\n        this.update({ failure: true, disabled: false })\n      })\n    }\n  </script>\n\n</form>\n"
  },
  {
    "path": "packages/templates/full/package.json",
    "content": "{\n  \"description\": \"Full nue template with raw/brutalist design system\",\n  \"type\": \"module\"\n}\n"
  },
  {
    "path": "packages/templates/full/site.yaml",
    "content": "\nmeta:\n  title_template: %s / Form & Function\n  description: Designer and UX developer\n\n  # default author for blog entries\n  author: Emma Bennet\n\nsite:\n  origin: https://acme.org\n  view_transitions: true\n\nsitemap:\n  enabled: true\n\nrss:\n  title: UX developer blog\n  collection: blog\n  enabled: true\n\ncontent:\n  sections: true\n\ncollections:\n  blog:\n    include: [ blog/ ]\n    skip: [ skip ]\n    sort: date desc\n\n  design:\n    include: [ docs/ ]\n    tags: [ design ]\n    sort: order\n\n  dev:\n    include: [ docs/ ]\n    tags: [ dev ]\n    sort: order\n\n\n# custom imports\nimport_map:\n  crud: /@shared/lib/crud.js\n  admin: /admin/admin.js\n\nserver:\n  reload: true\n\n"
  },
  {
    "path": "packages/templates/minimal/index.css",
    "content": "\n*, *:before, *:after {\n  box-sizing: border-box;\n}\n\nbody {\n  font-family: system-ui;\n  line-height: 1.5;\n  margin: 2em;\n}\n\nh1, h2, h3, p {\n  margin-block: .25em;\n}\n"
  },
  {
    "path": "packages/templates/minimal/index.html",
    "content": "\n<h1>Hello, World!</h1>\n<p>Welcome to extreme simplicity and performance</p>"
  },
  {
    "path": "packages/templates/spa/css/base.css",
    "content": "\n@layer base, components;\n\n@layer base {\n\n  *, *:before, *:after {\n    box-sizing: border-box;\n  }\n\n  body {\n    line-height: 1.5;\n    font-family: system-ui;\n    font-size: 14px;\n    max-width: 1000px;\n    margin: 1em auto;\n    padding: 1em;\n  }\n\n  h1 {\n    font-size: 1.5em;\n  }\n\n  strong {\n    font-weight: 550;\n  }\n\n  table {\n    width: 100%;\n    td {\n      padding: .5em 0;\n      border-bottom: 1px solid #eee;\n    }\n  }\n\n  dl {\n    display: grid;\n    grid-template-columns:25% 1fr;\n    > * { margin: .25em 0 }\n    dt { font-weight: 500 }\n    p:first-child { margin-top: 0 }\n  }\n\n}"
  },
  {
    "path": "packages/templates/spa/css/components.css",
    "content": "\n\n@layer components {\n  .status {\n    text-transform: capitalize;\n    font-weight: 500;\n\n    &:before {\n      content: '';\n      display: inline-block;\n      border-radius: 50%;\n      margin-right: 6px;\n      height: 8px;\n      width: 8px;\n    }\n\n    &.active:before { background: #00db1c; }\n\n    &.pending:before {background: #ffdc00; }\n\n    &.inactive {\n      filter: grayscale(1);\n      font-weight: 400;\n      opacity: .5;\n    }\n  }\n}"
  },
  {
    "path": "packages/templates/spa/index.html",
    "content": "<!doctype dhtml>\n\n<script>\n  import { state } from 'state'\n  state.setup({ route: '/:id', autolink: true })\n</script>\n\n<body>\n  <main>\n    <article/>\n  </main>\n\n  <script>\n    state.on('id', ({ id }) => {\n      this.mount(id ? 'user' : 'users', 'article')\n    })\n\n    mounted() {\n      state.init()\n    }\n  </script>\n</body>"
  },
  {
    "path": "packages/templates/spa/server/data/users.json",
    "content": "[\n  {\"name\":\"Sarah Chen\",\"email\":\"sarah.chen@example.com\",\"country\":\"Singapore\",\"role\":\"Product Manager\",\"status\":\"active\"},\n  {\"name\":\"Marcus Johnson\",\"email\":\"marcus.j@example.com\",\"country\":\"USA\",\"role\":\"Frontend Developer\",\"status\":\"active\"},\n  {\"name\":\"Priya Sharma\",\"email\":\"priya.sharma@example.com\",\"country\":\"India\",\"role\":\"UX Designer\",\"status\":\"active\"},\n  {\"name\":\"Lars Andersson\",\"email\":\"lars.a@example.com\",\"country\":\"Sweden\",\"role\":\"Backend Developer\",\"status\":\"inactive\"},\n  {\"name\":\"Maria Rodriguez\",\"email\":\"maria.r@example.com\",\"country\":\"Spain\",\"role\":\"Content Writer\",\"status\":\"active\"},\n  {\"name\":\"James Wilson\",\"email\":\"james.wilson@example.com\",\"country\":\"UK\",\"role\":\"Marketing Lead\",\"status\":\"active\"},\n  {\"name\":\"Yuki Tanaka\",\"email\":\"yuki.t@example.com\",\"country\":\"Japan\",\"role\":\"UI Designer\",\"status\":\"active\"},\n  {\"name\":\"Ahmed Hassan\",\"email\":\"ahmed.h@example.com\",\"country\":\"Egypt\",\"role\":\"Product Owner\",\"status\":\"active\"},\n  {\"name\":\"Emma Thompson\",\"email\":\"emma.t@example.com\",\"country\":\"Canada\",\"role\":\"Content Strategist\",\"status\":\"pending\"},\n  {\"name\":\"Carlos Silva\",\"email\":\"carlos.silva@example.com\",\"country\":\"Brazil\",\"role\":\"Full Stack Developer\",\"status\":\"active\"},\n  {\"name\":\"Fatima Al-Zahra\",\"email\":\"fatima.z@example.com\",\"country\":\"Morocco\",\"role\":\"UX Researcher\",\"status\":\"active\"},\n  {\"name\":\"Oliver Schmidt\",\"email\":\"oliver.s@example.com\",\"country\":\"Germany\",\"role\":\"DevOps Engineer\",\"status\":\"active\"},\n  {\"name\":\"Anastasia Petrov\",\"email\":\"anastasia.p@example.com\",\"country\":\"Russia\",\"role\":\"Brand Designer\",\"status\":\"inactive\"},\n  {\"name\":\"Michael O'Connor\",\"email\":\"michael.oc@example.com\",\"country\":\"Ireland\",\"role\":\"Marketing Manager\",\"status\":\"active\"},\n  {\"name\":\"Li Wei\",\"email\":\"li.wei@example.com\",\"country\":\"China\",\"role\":\"Mobile Developer\",\"status\":\"active\"},\n  {\"name\":\"Isabella Rossi\",\"email\":\"isabella.r@example.com\",\"country\":\"Italy\",\"role\":\"Content Manager\",\"status\":\"pending\"}\n]"
  },
  {
    "path": "packages/templates/spa/server/index.js",
    "content": "\n/*\n  users model auto-generated from users.json mockup\n  later users is available on cloudflare as real, functional model\n*/\n\nget('/users', async (c) => {\n  const { users } = c.env\n  return c.json(await users.getAll())\n})\n\nget('/users/:id', async (c) => {\n  const { users } = c.env\n  const id = c.req.param('id')\n  const user = await users.get(id)\n  if (!user) return c.json({ error: 'User not found' }, 404)\n  return c.json(user)\n})\n"
  },
  {
    "path": "packages/templates/spa/site.yaml",
    "content": "\nmeta:\n  title: Minimal SPA\n\nserver:\n  dir: server\n  reload: true"
  },
  {
    "path": "packages/templates/spa/ui/entry.html",
    "content": "\n<!doctype dhtml lib>\n\n<script>\n  import { state } from 'state'\n</script>\n\n<!-- user -->\n<article :is=\"user\">\n  <h1>{ name || email }</h1>\n\n  <nav>\n    <button onclick=\"history.go(-1)\">Back</button>\n  </nav>\n\n  <dl>\n    <dt>Registered</dt><dd><pretty-date :date=\"created\"/></dd>\n    <dt>Country</dt><dd>{ country }</dd>\n    <dt>Email</dt><dd>{ email }</dd>\n    <dt>Role</dt><dd>{ role }</dd>\n    <dt>Status</dt><dd><span class=\"status {status}\">{ status }</span></dd>\n  </dl>\n\n  <script>\n    state.on('id', async ({ id }) => {\n      if (!id) return\n      const user = await fetch(`/users/${id}`).then(r => r.json())\n      this.update(user)\n    })\n  </script>\n\n</article>"
  },
  {
    "path": "packages/templates/spa/ui/table.html",
    "content": "\n<!doctype dhtml lib>\n\n<!-- user table -->\n<article :is=\"users\">\n  <h1>Users</h1>\n\n  <table>\n    <tr :each=\"user in users\">\n      <td><a href=\"/{ user.id }\">{ user.name }</a></td>\n      <td><strong>{ user.email }</strong></td>\n      <td>{ user.country }</td>\n      <td>{ user.role }</td>\n      <td><span class=\"status { user.status }\">{ user.status }</span></td>\n      <td><pretty-date :date=\"user.created\"/></td>\n    </tr>\n  </table>\n\n  <script>\n    async mounted() {\n      const users = await fetch('/users').then(r => r.json())\n      this.update({ users })\n    }\n  </script>\n</article>\n\n\n<!-- pretty date -->\n<time :is=\"pretty-date\">\n  { formatDate(date) }\n\n  <script>\n    const opts = { year: 'numeric', month: 'short', day: 'numeric' }\n    formatDate(date) {\n      return new Date(date).toLocaleDateString('en-US', opts)\n    }\n  </script>\n</time>\n\n"
  },
  {
    "path": "packages/www/404.md",
    "content": "\n# Not found"
  },
  {
    "path": "packages/www/@shared/.gitignore",
    "content": "ui/ping.js\ndesign\n"
  },
  {
    "path": "packages/www/@shared/data/authors.yaml",
    "content": "\nauthors:\n  default: tero\n\n  tero:\n    name: Tero Piirainen\n    img: /img/tero.webp\n    username: tipiirai\n\n  john:\n    name: John Doe\n    img: /img/john.webp\n    username: johndoe"
  },
  {
    "path": "packages/www/@shared/data/features.yaml",
    "content": "\ntooling:\n  - title: Nuekit\n    desc: Standards first web framework\n    info: Full-stack web framework based on modern HTML, CSS, and JavaScript\n\n  - title: Nuedom\n    desc: HTML first UI assembly\n    info: Build reactive components with semantic HTML instead of virtual DOM abstractions\n\n  - title: Nuestate\n    desc: URL first state management\n    info: Incredibly simple state management based on native pushState and modern web storage\n\n  - title: Nuemark\n    desc: Content first web development\n    info: Build rich, interactive sites focusing purely on content\n\n  - title: Nueserver\n    desc: Edge-first server development\n    info: Write server code that works identically locally and on CloudFlare Workers\n\n  - title: Nueglow\n    desc: CSS first syntax highlighting\n    info: Semantic, design system compatible syntax blocks. Style with CSS, not class names\n\n\nfeatures:\n  Dev server:\n    - Direct serving\n    - Universal HMR\n    - SPA development\n    - SVG development\n    - Minimal config\n    - 10ms startup\n\n  Static site generator:\n    - Extended Markdown\n    - Layout module system\n    - Dynamic islands\n    - View transitions\n    - Content collections\n    - RSS feeds & sitemaps\n\n  Application builder:\n    - TypeScript transpiling\n    - JS/CSS optimization\n    - CSS inlining\n    - API routing\n    - Full-stack previews\n    - CloudFlare compatibiliity\n\n\n\nassembly:\n  - index.md\n  - app/index.html\n  - blog/index.md\n  - login/index.html\n  - about/index.md\n  - join/index.html\n  - contact/index.md\n"
  },
  {
    "path": "packages/www/@shared/data/topics.js",
    "content": "\nexport default function(data) {\n  const { topics } = data\n\n  data.getTopicCategory = function(slug) {\n    return getCategory(topics, slug)\n  }\n\n  // translate topic entries\n  for (const [cat, items] of Object.entries(topics)) {\n    if (!topics[cat][0]?.title) topics[cat] = items.map(parseEntry)\n  }\n}\n\nexport function getCategory(topics, slug) {\n  for (const category in topics) {\n    for (const item of topics[category]) {\n      if (slug == item.slug) return category\n    }\n  }\n}\n\nexport function parseEntry(el) {\n  const [content, explicitSlug] = el.split(' | ')\n\n  // Split content by / to separate title and desc\n  const [title, desc = ''] = content.split(' / ')\n\n  // Use explicit slug or generate from title\n  const slug = explicitSlug || title.toLowerCase().replaceAll(' ', '-')\n\n  return { title: title.trim(), desc: desc.trim(), slug: slug.trim() }\n}\n\n\n"
  },
  {
    "path": "packages/www/@shared/data/topics.test.js",
    "content": "\nimport { parseEntry, getCategory, default as mutate } from './topics'\n\ntest('mutate', () => {\n  const data = { topics: { first: ['Hello'] }}\n  mutate(data)\n\n  expect(data.getTopicCategory).toBeFunction()\n  expect(data.topics.first[0].slug).toEqual('hello')\n})\n\ntest('parseEntry', () => {\n  expect(parseEntry('Nue')).toEqual({\n    title: \"Nue\",\n    desc: \"\",\n    slug: \"nue\",\n  })\n\n  expect(parseEntry('Foo bar / Some | splat')).toEqual({\n    title: \"Foo bar\",\n    desc: \"Some\",\n    slug: \"splat\",\n  })\n})\n\ntest('getCategory', () => {\n  const topics = {\n    first: [{ slug: 'foo' }],\n    second: [{ slug: 'bar' }],\n  }\n\n  expect(getCategory(topics, 'foo')).toBe('first')\n})\n\n"
  },
  {
    "path": "packages/www/@shared/data/topics.yaml",
    "content": "\ntopics:\n  essentials:\n    - Why Nue\n    - Getting started\n    - Migration\n    - Roadmap\n    - Contributing\n\n  tools:\n    - Nuekit / Standards first web framework\n    - Nuedom / HTML first UI assembly\n    - Nuestate / URL first state management\n    - Nuemark / Content first web development\n    - Nueserver / Edge-first server development\n    - Nueglow / CSS first syntax highlighting\n    - Nueyaml / YAML without the problems\n\n  concepts:\n    - Minimalism\n    - Separation of concerns\n    - Design systems\n    - Design engineering\n    - Universal data model\n\n  developing:\n    - Project structure\n    - Website development\n    - CSS development\n    - Interactive components\n    - JS enhancements\n    - SVG development\n    - SPA development\n\n  reference:\n    - Configuration\n    - Command line (CLI) | cli\n    - Build system\n    - HTML file types\n    - HTML syntax\n    - Layout system\n    - Page dependencies\n    - Template data\n    - State API\n    - Server API\n    - Nuemark syntax\n    - YAML syntax\n    - Syntax highlighting\n\n"
  },
  {
    "path": "packages/www/@shared/lib/assembly/assembly.css",
    "content": "\n@layer component {\n\n  .assembly {\n    margin-top: 2em;\n\n    aside {\n      font-family: var(--monospace);\n      border: 1px solid var(--brand);\n      font-size: 1em;\n      max-width: 200px;\n      padding: 2em;\n\n      p { margin: 0; color: var(--gray-strong); }\n      b { color: var(--brand); font-weight: 600 }\n    }\n\n    ul {\n      position: relative;\n      overflow: hidden;\n    }\n\n    /* pulsating arrow */\n    svg {\n      max-width: 2em;\n      color: var(--text-strong);\n      align-self: center;\n      margin: -1em 0 0 -.5em;\n      animation: flow .75s ease-in-out infinite alternate;\n      display: block;\n    }\n\n    li {\n      position: absolute;\n      width: 150px;\n      animation: move calc(var(--items) * 2s) infinite linear;\n      animation-delay: calc(var(--index) * -2s);\n      h4 {\n        font-size: .9em;\n        font-weight: 500;\n        white-space: nowrap;\n        color: var(--text-strong);\n      }\n    }\n\n    /* code lines */\n    hr {\n      margin: .75em 0;\n      width: calc(20% + calc(70% * var(--random)));\n    }\n\n  }\n\n  @keyframes flow {\n    0% { opacity: 0.5; transform: translatex(-.2em) }\n    100% { opacity: 1; transform: scale(1.05) translatex(.1em); }\n  }\n\n  @keyframes move {\n    0% { transform: translateX(-150px) }\n    100% { transform: translateX(calc(900px + 150px)) }\n  }\n}"
  },
  {
    "path": "packages/www/@shared/lib/assembly/assembly.html",
    "content": "\n<nue-assembly class=\"assembly ui flex\">\n  <aside class=\"card\">\n    <h3>@shared</h3>\n    <p>├── <b>ui</b></p>\n    <p>├── <b>app</b></p>\n    <p>├── <b>data</b></p>\n    <p>├── <b>design</b></p>\n    <p>└── <b>server</b></p>\n  </aside>\n\n  <arrow-right/>\n\n  <ul class=\"list\" --items=\"{ assembly.length }\">\n    <li :each=\"path, i in assembly\" class=\"card\" --index=\"{ i }\">\n      <h4>{ path }</h4>\n      <hr :each=\"el in new Array(10).fill(0)\"\n        --random=\"{ Math.round(Math.random() * 100) / 100 }\">\n    </li>\n  </ul>\n</nue-assembly>\n\n\n\n"
  },
  {
    "path": "packages/www/@shared/lib/console/console.css",
    "content": "\n@layer component {\n\n  .console {\n    background-color: var(--gray-subtle);\n    margin-block: 2em -.5em;\n    color: var(--text);\n    height: 33em;\n\n    pre {\n      font-family: var(--monospace);\n      color: var(--text-subtle);\n      font-size: .875em;\n      max-width: 38em;\n      margin: 0;\n    }\n\n    div {\n      white-space: nowrap;\n      &:empty { height: 1.5em }\n    }\n\n    b {\n      color: var(--text-strong);\n      font-weight: 450;\n    }\n\n    .response {\n       &::first-letter { color: var(--brand) }\n    }\n\n    time {\n      color: var(--brand);\n      float: right;\n    }\n\n    header {\n      float: right;\n      padding: 1.25em 1.5em;\n    }\n\n    &.is-playing header { display: none }\n\n    .command {\n      color: var(--brand);\n      &:before {\n        content: \"$ \";\n        color: var(--brand-subtle);\n      }\n\n      /* cursor */\n      &.is-typing:after {\n        content: '';\n        display: inline-block;\n        width: 0.5em;\n        height: 1em;\n        background-color: var(--accent);\n        margin-left: .15em;\n        position: relative;\n        opacity: .75;\n        top: 0.15em;\n      }\n\n      &.is-comment {\n        color: var(--accent);\n        &:after { animation: blink 1s infinite .5s; }\n      }\n    }\n  }\n\n  @keyframes blink {\n    0%, 50% { opacity: 1 }\n    51%, 100% { opacity: 0 }\n  }\n}\n\n"
  },
  {
    "path": "packages/www/@shared/lib/console/console.html",
    "content": "<!doctype dhtml lib>\n\n<script>\n  import { createConsole } from './console.js'\n</script>\n\n<console class=\"console\">\n\n  <header class=\"row\">\n    <a :onclick=\"model.prev()\">←</a>\n    <a :onclick=\"model.next()\">→</a>\n  </header>\n\n  <pre/>\n\n  <script>\n    async mounted() {\n      const res = await fetch('/home/commands.txt')\n      const $pre = this.root.querySelector('pre')\n\n      let playing = false\n      let $line\n\n      const model = this.model = createConsole(await res.text(), e => {\n        const { line } = e\n\n        if (e.scene_started) {\n          $pre.innerHTML = ''\n          return playing = true\n        }\n\n        if (e.line_started) {\n          // previous line\n          if ($line) $line.classList.remove('is-typing')\n\n          // new line\n          $line = document.createElement('div')\n          $line.className = e.is_command ? 'command is-typing' : 'response'\n        }\n\n\n        if (line != null) {\n          if (!$line.parentNode) $pre.appendChild($line)\n          $line.innerHTML = line || ''\n          if (line == '#') $line.classList.add('is-comment')\n        }\n\n        if (e.scene_ended) playing = false\n      })\n\n      model.start()\n\n      addEventListener('keydown', function(e) {\n        if (e.key == 'Enter' && !playing) model.next()\n      })\n\n    }\n  </script>\n</console>"
  },
  {
    "path": "packages/www/@shared/lib/console/console.js",
    "content": "\nexport function createConsole(text, callback) {\n  const scenes = text.trim().split('---').map(s => s.trim().split('\\n'))\n  let scene = 0\n  let currentRun = 0\n\n  function runScene() {\n    if (scene >= scenes.length) return callback({ completed: true, scene })\n\n    const runId = ++currentRun\n    callback({ scene_started: true, scene })\n\n    runLines(scenes[scene], e => {\n      if (currentRun == runId) {\n        callback({ ...e, scene })\n      }\n    }, () => currentRun == runId)\n  }\n\n  return {\n    get totalScenes() { return scenes.length },\n    get currentScene() { return scene },\n\n    next() {\n      if (scene < scenes.length - 1) scene++\n      else scene = 0\n      runScene()\n    },\n    prev() {\n      if (scene > 0) scene--\n      else scene = scenes.length - 1\n      runScene()\n    },\n    start: runScene,\n  }\n}\n\nfunction runLines(lines, callback, isActive) {\n  let i = 0\n\n  function nextLine() {\n    if (!isActive()) return\n    if (i >= lines.length) return callback({ scene_ended: true })\n\n    const line = lines[i]\n    const is_command = line.startsWith('$')\n    const fn = is_command ? runCommand : processResponse\n    callback({ line_started: true, is_command })\n\n    fn(line, (line, line_done) => {\n      if (!isActive()) return\n      callback({ line, line_done, is_command })\n      if (line_done) { i++; setTimeout(nextLine, 20) }\n    }, isActive)\n  }\n\n  nextLine()\n}\n\nfunction runCommand(text, callback, isActive) {\n  text = text.slice(2)\n  let output = ''\n  let i = 0\n\n  function type() {\n    if (!isActive()) return\n    if (i >= text.length) {\n      callback(output, true)\n      return\n    }\n\n    const char = text[i]\n    if (char == '^') output = output.slice(0, -1)\n    else output += char\n    callback(output)\n    i++\n\n    const delay = Math.random() * (text[0] == '#' ? 75 : 200)\n    setTimeout(type, delay)\n  }\n\n  type()\n}\n\nfunction processResponse(str, callback, isActive) {\n  const { text, time, delay } = parseDelay(str)\n  setTimeout(() => {\n    if (!isActive()) return\n    if (time) {\n      callback(text, false)\n      setTimeout(() => {\n        if (!isActive()) return\n        callback(text + `<time>[${time}ms]</time>`, true)\n      }, time)\n    } else {\n      callback(text, true)\n    }\n  }, delay)\n}\n\nfunction parseDelay(text) {\n\n  // prefix: 1s: ___\n  let match = text.trim().match(/(\\d+(?:\\.\\d+)?)s:(\\s*.+)$/)\n  const delay = match ? parseFloat(match[1]) * 1000 : 0\n  if (match) text = match[2]\n\n  // suffix: ___ [10ms]\n  match = text.match(/^(.+?)\\s*\\[(\\d+)ms\\]$/)\n  const time = match ? parseInt(match[2]) : 0\n  if (match) text = match[1]\n  return { text, time, delay }\n}\n\n\n"
  },
  {
    "path": "packages/www/@shared/lib/hero/cta-buttons.html",
    "content": "\n<cta-buttons class=\"cta-buttons row\">\n  <a href=\"/docs\" class=\"brand button\">Learn Nue</a>\n  <a href=\"/docs/getting-started\" class=\"button\">Get started -></a>\n</cta-buttons>"
  },
  {
    "path": "packages/www/@shared/lib/hero/hero.css",
    "content": "@layer design {\n\n  h1 {\n    font-size: 4em;\n    max-width: unset;\n    letter-spacing: -.025em;\n    line-height: 1;\n    background-image: linear-gradient(to bottom right, #fff 30%, #ffffff80);\n    background-clip: text;\n    -webkit-text-fill-color: transparent;\n    margin-top: .5em;\n\n    + p {\n      font-size: 1.25em;\n      max-width: inherit;\n    }\n\n    @media (width < 750px) { font-size: 3.25em }\n  }\n\n  h2 {\n    font-size: 2em;\n    font-weight: 600;\n    line-height: 1;\n    margin-block: 1em .25em;\n\n    + p {\n      font-size: 1.125em;\n      margin-bottom: 1.5em;\n    }\n  }\n}"
  },
  {
    "path": "packages/www/@shared/lib/stack/stack.css",
    "content": "\n/* nue stack */\n@layer component {\n  .ui.stack {\n    aside {\n      --cell-width: 175px;\n      font-size: .875em;\n      margin-top: 1em;\n      > div { padding-left: 1.5em; }\n      p { color: var(--text-subtle) }\n    }\n\n    @media (width < 900px) {\n      section { --cell-width: 250px }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/www/@shared/lib/stack/stack.html",
    "content": "\n<nue-stack class=\"card ui stack\">\n\n  <section class=\"grid\">\n    <div :each=\"tool, i in tooling\" class=\"card\" title=\"{ tool.info }\">\n      <h3 class=\"row\"><box-icon class=\"branded\"/> { tool.title }</h3>\n      <p>{ tool.desc }</p>\n    </div>\n  </section>\n\n  <aside class=\"grid\">\n    <div :each=\"[ title, feats ] in Object.entries(features)\">\n      <h3>{ title }</h3>\n      <p :each=\"feat in feats\">{ feat }</p>\n    </div>\n  </aside>\n\n</nue-stack>"
  },
  {
    "path": "packages/www/@shared/lib/syntax.css",
    "content": "\n@layer design {\n\n  pre {\n    background-color: var(--gray-subtle);\n    border-radius: var(--radius-m);\n    counter-reset: line-number 0;\n    color: var(--accent);\n    overflow: hidden;\n    padding: 1.5em;\n\n    /* reset */\n    * {\n      font-weight: 400;\n      font-style: inherit;\n      text-decoration: inherit\n    }\n\n    b { color: var(--brand); }\n\n    em { color: var(--accent) }\n\n    /* special emphasis */\n    strong {\n      color: var(--pop);\n    }\n\n    /* brackets, commas, semicolons... */\n    i { color: var(--text-subtle) }\n\n    /* comments */\n    sup {\n      color: var(--text-subtle);\n      vertical-align: inherit;\n      font-size: inherit;\n    }\n\n    /* marked segments */\n    mark {\n      background-color: color-mix(in srgb, var(--brand) 30%, transparent);\n      color: inherit;\n      border-radius: .2em;\n      padding-block: .25em;\n    }\n\n    /* spotlight */\n    label {\n      color: var(--text-strong);\n      font-weight: bold;\n    }\n\n    /* line numbers */\n    span {\n      counter-increment: line-number 1;\n\n      &:before {\n        opacity: .3;\n        content: counter(line-number);\n        display: inline-block;\n        text-align: right;\n        padding-right: 1em;\n        margin-right: 1em;\n        width: 2.5em;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "packages/www/@shared/lib/video/controller.js",
    "content": "/*\n  videoId: 39b76cca-e55b-4e9b-8583-b053f9dbd55d\n  poster: thumbnail_70d8de32.jpg\n  width: 704\n  height: 407\n*/\nexport function createVideo({ videoId, poster, width, height }) {\n  const base = `https://video.nuejs.org/${videoId}`\n  const video = document.createElement('video')\n\n  // video attributes\n  video.poster = poster?.includes('/') ? poster : `${base}/${poster || 'thumbnail.jpg'}`\n  video.width = width\n  video.height = height\n  video.muted = true\n\n\n  // video source\n  const use_hls = video.canPlayType('application/vnd.apple.mpegURL')\n  video.appendChild(createSource(base, use_hls))\n  return video\n}\n\nfunction createSource(base, use_hls) {\n  const el = document.createElement('source')\n  const filename = use_hls ? 'playlist.m3u8' : `play_${getQuality()}p.mp4`\n\n  el.type = use_hls ? 'application/x-mpegURL' : 'video/mp4'\n  el.src = `${base}/${filename}`\n  return el\n}\n\nfunction getQuality() {\n  return [720, 480, 360].find(w => w < window.innerWidth)\n}\n\n// use a global listener (to support upcoming keyboard shortcuts)\ndocument.addEventListener('click', e => {\n  const el = e.target\n  const parent = el.closest('.player')\n  const video = el.matches('video') ? el : parent?.querySelector('video')\n  if (!video) return\n\n  if (el.matches('.maximize')) {\n    video.requestFullscreen()\n  } else {\n    video.paused ? video.play() : video.pause()\n    parent?.classList.toggle('paused', video.paused)\n  }\n})"
  },
  {
    "path": "packages/www/@shared/lib/video/player.html",
    "content": "<!doctype dhtml lib>\n\n<script>\n  import { createVideo } from './controller.js'\n</script>\n\n<video-player class=\"player paused\">\n\n  <play-icon/>\n\n  <maximize-icon/>\n\n  <script>\n    mounted() {\n      const video = createVideo(this)\n      this.root.appendChild(video)\n    }\n  </script>\n\n</video-player>\n\n\n<svg :is=\"play-icon\" width=\"70\" height=\"70\" viewBox=\"0 0 24 24\" class=\"icon play\">\n  <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n  <path d=\"M9 9.003a1 1 0 0 1 1.517-.859l4.997 2.997a1 1 0 0 1 0 1.718l-4.997 2.997A1 1 0 0 1 9 14.996z\"/>\n</svg>\n\n<svg :is=\"maximize-icon\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" class=\"icon maximize\">\n  <path d=\"M15 3h6v6\"/><path d=\"m21 3-7 7\"/><path d=\"m3 21 7-7\"/><path d=\"M9 21H3v-6\"/>\n</svg>\n"
  },
  {
    "path": "packages/www/@shared/lib/video/video.css",
    "content": "\n@layer component {\n  .player {\n    position: relative;\n    display: inline-flex;\n    justify-content: center;\n    margin: 1em 0;\n    \n    video {\n      border-radius: var(--radius-m);\n      max-width: 100%;\n    }\n\n    .icon {\n      color: black;\n      position: absolute;\n      cursor: pointer;\n    }\n\n    .play {\n      align-self: center;\n      stroke-width: 1.25;\n      fill: white;\n      width: 4em;\n    }\n\n    &:not(.paused) .play {\n      display: none;\n    }\n\n    .maximize {\n      top: 1em;\n      right: 1em;\n      z-index: 1;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/www/@shared/ui/global-layout.css",
    "content": "\n/* global layout */\n\n@layer layout {\n  body {\n    max-width: var(--page-width);\n    flex-direction: column;\n    min-height: 100vh;\n    margin: 0 auto;\n    display: flex;\n\n    > * { padding: 1em }\n    > main { flex: 1 }\n\n\n    > header {\n      align-items: center;\n      margin-bottom: 3em;\n      display: flex;\n      gap: 1em;\n\n      nav {\n        align-items: center;\n        display: flex;\n        gap: 2em;\n\n        &:last-of-type {\n          margin-left: auto;\n          justify-content: right;\n        }\n      }\n\n      /* mobile layout */\n      @media (width < 750px) {\n        .teaser, .social { display: none }\n      }\n    }\n\n\n    > footer {\n      color: var(--text-subtle);\n      padding-bottom: 4.5em;\n    }\n\n    /* section gaps */\n    main > article {\n      flex-direction: column;\n      display: flex;\n      gap: 3em;\n\n      section {\n        > h3, > h4 { margin-top: 2.5em }\n      }\n\n      .thin { max-width: 75% }\n\n      .wide {\n        max-width: inherit;\n        width: 100%\n      }\n    }\n\n    /* bottom cta */\n    main > aside {\n      margin-block: 5em;\n    }\n  }\n}\n\n\n\n"
  },
  {
    "path": "packages/www/@shared/ui/global-layout.html",
    "content": "<!doctype html lib>\n\n<header class=\"ui\">\n  <nav>\n    <a href=\"/\"><b class=\"wordmark\">NUE</b></a>\n    <a href=\"/docs/\">Docs</a>\n    <a href=\"/blog/\">Blog</a>\n\n    <a :if=\"teaser\" class=\"teaser row\" href=\"{ teaser.url }\">\n      <span class=\"inverse tag\">{ teaser.tag}</span>\n      { teaser.text }\n    </a>\n  </nav>\n\n  <nav>\n    <a  href=\"{ slack.url }\" class=\"social row\">\n      <slack-icon width=\"18\" height=\"18\"/>\n      <strong>Slack</strong>\n    </a>\n\n    <a  href=\"{ gh.url }\" class=\"row pill\">\n      <github-icon width=\"18\" height=\"18\"/> { gh.count }\n    </a>\n  </nav>\n</header>\n\n<!-- join mailing list -->\n<aside :is=\"beside\">\n  <h2>{ join.title }</h2>\n  <p>{{ markdown(join.desc) }}</p>\n  <join-list :bind=\"join.form\"/>\n</aside>\n\n<footer class=\"ui\">\n  <hr>\n  <b class=\"wordmark\">NUE</b>\n  <span>— { tagline }</span>\n</footer>\n\n\n"
  },
  {
    "path": "packages/www/@shared/ui/grid.css",
    "content": "\n@layer layout {\n\n  .grid {\n    display: grid;\n    grid-template-columns: repeat(auto-fit, minmax(var(--cell-width, 250px), 1fr));\n    gap: 1em;\n  }\n\n  .row {\n    align-items: center;\n    display: flex;\n    gap: .5em;\n  }\n\n  .flex {\n    display: flex;\n    gap: 1em;\n    > * { flex: 1 }\n\n    &.wrap {\n      flex-wrap: wrap;\n      > * { flex: 1 1 var(--cell-width, 200px) }\n    }\n  }\n\n  .stack {\n    flex-direction: column;\n    align-items: unset;\n    display: flex;\n    gap: 1em;\n  }\n\n  @media (width > 900px) {\n    .columns {\n      column-count: var(--count, 2);\n      margin-block: 2em;\n      column-gap: 1.5em;\n      div { break-inside: avoid }\n    }\n  }\n\n}\n"
  },
  {
    "path": "packages/www/@shared/ui/icons.html",
    "content": "\n<svg :is=\"arrow-right\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" class=\"icon\">\n  <path d=\"M5 12h14\" />\n  <path d=\"m12 5 7 7-7 7\" />\n</svg>\n\n<svg :is=\"box-icon\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" class=\"icon\">\n  <path d=\"M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z\"/><path d=\"m3.3 7 8.7 5 8.7-5\"/><path d=\"M12 22V12\"/>\n</svg>\n\n\n<svg :is=\"slack-icon\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" class=\"icon\">\n  <rect width=\"3\" height=\"8\" x=\"13\" y=\"2\" rx=\"1.5\"/><path d=\"M19 8.5V10h1.5A1.5 1.5 0 1 0 19 8.5\"/><rect width=\"3\" height=\"8\" x=\"8\" y=\"14\" rx=\"1.5\"/><path d=\"M5 15.5V14H3.5A1.5 1.5 0 1 0 5 15.5\"/><rect width=\"8\" height=\"3\" x=\"14\" y=\"13\" rx=\"1.5\"/><path d=\"M15.5 19H14v1.5a1.5 1.5 0 1 0 1.5-1.5\"/><rect width=\"8\" height=\"3\" x=\"2\" y=\"8\" rx=\"1.5\"/><path d=\"M8.5 5H10V3.5A1.5 1.5 0 1 0 8.5 5\"/>\n</svg>\n\n\n<svg :is=\"github-icon\" width=\"24\" height=\"24\" fill=\"currentColor\" viewBox=\"0 0 24 24\">\n  <path d=\"m12 .5c-6.63 0-12 5.28-12 11.792 0 5.211 3.438 9.63 8.205 11.188.6.111.82-.254.82-.567 0-.28-.01-1.022-.015-2.005-3.338.711-4.042-1.582-4.042-1.582-.546-1.361-1.335-1.725-1.335-1.725-1.087-.731.084-.716.084-.716 1.205.082 1.838 1.215 1.838 1.215 1.07 1.803 2.809 1.282 3.495.981.108-.763.417-1.282.76-1.577-2.665-.295-5.466-1.309-5.466-5.827 0-1.287.465-2.339 1.235-3.164-.135-.298-.54-1.497.105-3.121 0 0 1.005-.316 3.3 1.209.96-.262 1.98-.392 3-.398 1.02.006 2.04.136 3 .398 2.28-1.525 3.285-1.209 3.285-1.209.645 1.624.24 2.823.12 3.121.765.825 1.23 1.877 1.23 3.164 0 4.53-2.805 5.527-5.475 5.817.42.354.81 1.077.81 2.182 0 1.578-.015 2.846-.015 3.229 0 .309.21.678.825.56 4.801-1.548 8.236-5.97 8.236-11.173 0-6.512-5.373-11.792-12-11.792z\"/>\n</svg>"
  },
  {
    "path": "packages/www/@shared/ui/join-list.html",
    "content": "<!doctype dhtml lib>\n\n<join-list>\n\n  <p :if=\"sessionStorage.joined\">{ done }</p>\n\n  <form :else class=\"ui flex\" --width=\"40em\" :onsubmit=\"submit\">\n    <div class=\"stack\">\n      <input type=\"email\" placeholder=\"Your email\" class=\"flex-3\" name=\"email\">\n      <textarea placeholder=\"{ comment }\" name=\"comment\"></textarea>\n    </div>\n    <div>\n      <button class=\"brand\">{ cta }</button>\n    </div>\n  </form>\n\n  <script>\n    submit(e) {\n      const data = Object.fromEntries(new FormData(e.target).entries())\n\n      fetch('/public/members', {\n        'Content-Type': 'application/json',\n        body: JSON.stringify(data),\n        method: 'POST',\n      })\n\n      sessionStorage.joined = true\n    }\n  </script>\n</join-list>\n"
  },
  {
    "path": "packages/www/Makefile",
    "content": "\nrun-tests:\n\tcd @shared/data && bun test"
  },
  {
    "path": "packages/www/blog/2.0/app.yaml",
    "content": "\ninclude: [ console, stack, syntax ]"
  },
  {
    "path": "packages/www/blog/2.0/index.md",
    "content": "\n---\ntitle: **Nue 2.0:** The UNIX of the web\n# date: 2023-09-18\n---\n\nHere's Nue 2.0 — a complete website development environment in 1MB.\n\n[console]\n\n\n## The entire ecosystem in 1MB\nNue is made of small, focused tools that do one thing well.\n\n[nue-stack]\nWhat used to be 500MB is now 1MB\n\n\n## What's new\n\n### Complete rewrite\nNuekit was rebuilt from scratch with a clear focus: do one thing well. The new architecture serves files directly from your source directory during development. No temporary `.dist/dev` folder, no build step before you start working. Just create files and see them in the browser.\n\nWe also switched from supporting both Bun and Node to Bun only. This decision enables the entire framework to stay lean while delivering better performance.\n\n\n### HTML templating\nAll HTML files now use the same `.html` extension. The document type declaration determines how Nue processes each file:\n\n```html\n<!-- Server-rendered page -->\n<!doctype html>\n\n<!-- Dynamic, client-rendered page -->\n<!doctype dhtml>\n\n<!-- Server-side component library -->\n<!html lib>\n\n<!-- Client-side library -->\n<!dhtml lib>\n\n<!-- Isomorphic library -->\n<!html+dhtml>\n```\n\nWhen you omit the doctype, Nue detects the type automatically based on your markup. If your file has event handlers or imports, it becomes dynamic. If it's pure content structure, it renders server-side.\n\nThis replaces the old `.dhtml` extension system with a more flexible approach that follows HTML standards.\n\n\n### Zero dependencies\nNue 2.0 has no external dependencies. Everything needed to build websites lives in that 1MB executable:\n\n- Custom CSS parser\n- Custom YAML parser (Nueyaml)\n- Custom DOM implementation for server-side rendering and testing\n\nBuilding without dependencies gives us full control over the entire stack. Each tool does one thing well and communicates seamlessly with the others. This is how you build frameworks the UNIX way: small, focused tools that work together perfectly.\n\nThis gives us a lean developer experience without the typical framework bloat.\n\n\n### New features\n\n**SVG development** - Process SVG files as templates with full HMR support. Embed your design system styles and fonts directly into standalone graphics. Mix HTML and SVG naturally using `<html>` tags that become `<foreignObject>` automatically.\n\n**Sitemap generation** - Enable in your config and Nue generates `sitemap.xml` automatically from your pages. Skip pages with draft or private flags.\n\n**RSS feeds** - Point at any content collection and get a complete RSS feed with auto-discovery link tags. Perfect for blogs and documentation sites.\n\n\n### SPA development\nSingle-page applications get a new development model built on Nuestate. Define your route patterns in one place and let the URL drive your application. Regular `<a href>` links become SPA navigation automatically with the `autolink` option.\n\nThe upcoming [universal data model](universal-data-model) will unify how data flows through SPAs, but the foundation is here now: clean separation between routing, data model, state, and the UI layer.\n\n\n### New tools\n\n**Nuestate** - URL-first state management that puts application state in the URL by default. Bookmarking, sharing, and browser navigation work automatically without extra code. No stores, no reducers, no actions. Just read and write to a state object.\n\n**Nueserver** - \"Edge-first\" backend development. Write server code once and run it identically on your machine and across global edge locations.\n\n**Server proxy** - Not ready for edge-first? Configure Nue to proxy API calls to your existing backend.\n\n\n### New templates\nFour options for different needs:\n\n**minimal** - Just `index.html` and `index.css` to start from scratch\n\n**blog** - Simple Markdown based blog with minimal coding/configuration\n\n**spa** - Single-page application with a simple server and UI\n\n**full** - Full stack web with blog, docs, marketing pages, authentication, and SPA\n\n\n## Why Bun\nNue and Bun share the same vision for web development.\n\n**Standards based** - Bun uses browser APIs you already know: `fetch()`, `Request`, `Response`, `URL`, `Headers`, and `FormData`. No framework-specific APIs to learn. Code that works in the browser works in Bun.\n\n**Rich in features** - Core features like bundling, serving, and file handling are written in native code (Zig). No need for Vite, ESBuild, or separate build tools.\n\n**Performance** - Bun is faster than Node\n\nWhile Node support would be convenient, Bun-only is what enables Nue's extreme performance and simplicity. It's what makes the developer experience special.\n\n\n## Upgrading from 1.0\nNue 2.0 is not backwards compatible with version 1.0. The safest upgrade path is to install locally in your project:\n\n```bash\nbun install nuekit@latest\n```\n\nRun commands with `bunx`:\n\n```bash\nbunx nue serve\nbunx nue build\n```\n\nThis lets you test the new version without breaking anything. Once you've migrated all projects, switch to global installation:\n\n```bash\nbun install --global nuekit@latest\n```\n\n## Beta notice\n\nThis is a beta release. Tested on macOS only. Linux and Windows compatibility unknown. Report issues on [GitHub](https://github.com/nuejs/nue/issues).\n\n\n## Get started\n\n```bash\n# Install Bun 1.2+ (if you don't have it yet)\ncurl -fsSL https://bun.sh/install | bash\n\n# Install Nuekit globally\nbun install --global nuekit\n\n# Create your first project\nnue create blog    # or minimal, spa, full\n```\n\nStart developing:\n\n```bash\nnue    # Starts serving at http://localhost:4000\n```"
  },
  {
    "path": "packages/www/blog/app.yaml",
    "content": "\ninclude: [ video, syntax ]"
  },
  {
    "path": "packages/www/blog/backstory/index.md",
    "content": "---\ntitle: Because web development used to be **FUN!**\ndesc: Why I'm building a complete frontend ecosystem from scratch\ndate: 2023-09-18\n---\n\nI still remember the exact moment when I discovered **jQuery**. It was a revelation: Can I suddenly add logic to my pages in the same way I add styling? I surely can — this must be huge!\n\nAnd it was: In the following years we got introduced to groundbreaking concepts like **separation of concerns**, **progressive enhancement**, and **responsive web design**. Things were great.\n\n\n## Then it changed { #then }\n\nDevelopers discovered **React** and single-page applications. **Facebook** took over. Slowly but steadily **JavaScript** took control of everything. Layout, styling, and business logic were bundled together. CSS was ditched in the name of \"global namespace pollution\" and content-heavy websites were developed like they were single-page apps. Eventually, JS engineers won, [UX developers][divide] lost, and the old foundation was gone.\n\n\n## Nue brings the fun back { #magic }\n\nNue brings perfromance-, design-, and UX engineering back to the forefront. What used to take an absurd amount of TypeScript/TSX can now be done with a fraction of the code, your codebases look clean, and the results are leaner. You begin to wonder why you ever built website any other way.\n\n### Peace ❤️\n\n[! signature.png width=\"200\"]\n"
  },
  {
    "path": "packages/www/blog/index.md",
    "content": "\n---\nsubheader: false\npagehead: false\nnoindex: true\n---\n\n# Blog\nSubscribe to [RSS feed](/feed.xml)\n\n[blog-entries]"
  },
  {
    "path": "packages/www/blog/large-scale-apps/index.md",
    "content": "---\ntitle: Apps lighter than a React button\ndescription: Nue is HTML, CSS, and JavaScript taken to their absolute peak.\ndate: 2025-04-01\n---\n\nOn this release, we’re showing what happens when you push modern web standards — HTML, CSS, and JS — to their peak:\n\n[video-player]\n  videoId: 39b76cca-e55b-4e9b-8583-b053f9dbd55d\n  poster: thumbnail_70d8de32.jpg\n  width: 704\n  height: 407\n\n\nThis entire [app](//mpa.nuejs.org/app/) is _lighter_ than a React/ShadCN button:\n\n[image]\n  small: react-button.png\n  large: react-button-big.png\n  size:  704 × 394\n\n\n## Going large-scale\nHere’s the same app, now with a **Rust** computation engine and **Event Sourcing** for instant search and other operations over **150,000** records — far past where [JS-version](//github.com/nuejs/nue/blob/master/packages/examples/simple-mpa/app/model/engines/javascript.js) of the engine crashed with a maximum call stack exception.\n\n[video-player]\n  videoId: eb65fcdd-5be4-4923-a783-f41efafe58a7\n  poster: ./rust-splash.png\n  width: 704\n  height: 440\n\nIn the [above demo](//mpa.nuejs.org/app/?rust) you can see instant operations across 150,000 records with Rust/WASM\n\n\n## Here's what this means\n\n\n### For Rust, Go, and JS engineers\n\nThis is a game-changer for Rust, Go, and JS engineers stuck wrestling with React idioms instead of leaning on timeless software patterns. Nue emphasizes a model-first approach, delivering modular design with simple, testable functions, true static typing, and minimal dependencies. Nue is a liberating experience for system devs whose skills can finally shine in a separated model layer.\n\n\n### For design engineers\nThis is an important shift for design engineers bogged down by React patterns and [40,000+ line][new_york] design systems. Build radically simpler systems with modern CSS (@layers, variables, calc()) and take control of your typography and whitespace.\n\n\n### For UX Engineers\n\nThis is a wake-up call for UX engineers tangled in React hooks and utility class walls instead of owning the user experience. Build apps as light as a React button to push the web — and your skills — forward.\n\n\n## FAQ: WTH is Nue?\nNue is a web framework focused on web standards, currently in active development. I'm aiming to reveal the hidden complexity that’s become normalized in modern web development. When a single button outweighs an entire application, something’s fundamentally broken.\n\nNue drives the inevitable shift. We’re rebuilding tools and frameworks from the ground up with a cleaner, more robust architecture. Our goal is to bring back the joy of web development for everyone — whether you’re focused on performance, design, or UX.\n\n\n[new_york]: //github.com/shadcn-ui/ui/tree/main/apps/v4/registry/new-york-v4"
  },
  {
    "path": "packages/www/blog/perfect-web-framework/index.md",
    "content": "---\ndate: 2024-01-23\ntitle: The perfect web framework for UX developers\ndesc: Nue's ultimate goal and the development steps to get there\nog: /img/perfect-banner-big.jpg\n---\n\n\nIn June 2023 I had my final frontend rage-quit moment. The anger was so intense that I decided to change the direction of my career from a startup founder to a full-time OSS developer. I would devote all my time to fixing everything that is wrong with the current front-end ecosystem.\n\nI created a [project on GitHub](//github.com/nuejs/nue) and called it **\"Nue\"**. I wanted it to be the best web framework for [UX developers](//css-tricks.com/the-great-divide/) and design-led organizations. It'll consist of the following:\n\n\n[image]\n  small: perfect-banner.jpg\n  large: perfect-banner-big.jpg\n  size: 550 × 625\n\n\n[.note]\n  ## Terms\n  **UX** = improved user experience, **DX** = improved developer experience\n\n\n## Content focused `DX` { #content }\n\nThe purpose of a website is to present content. To spread your thoughts or generate sales. For this obvious reason, your website should be optimized for content producers.\n\nIdeally, content people should be able to create new content and update the information architecture without seeking assistance from designers or developers. Several authors could proceed in isolation and break nothing but their local language or grammar.\n\nThe content-first approach is the most important thing in a framework. It defines the overall system architecture and forms a solid ground for a [semantic design system](#cascade) and [universal template](#template).\n\nNote that single-page applications are no different: The content just comes from a database and not from a file. The design system can be shared.\n\n\n[.problem]\n  Popular frameworks like **Next.js** or **Astro** are optimized for JavaScript developers with a deep understanding of **React**, **TypeScript**, **CSS-in-JS**, **Tailwind**, and whatnot. For example, you need hundreds of lines of code spanning 10–20 files to create a rich/interactive web page.\n\n\n## World-class design `UX` { #design }\n\nWorld-class means the highest caliber design in the world. The top 1% of websites. The future **Stripes**, **Amies**, and **Linears**.\n\nEverything is pixel-perfect down to the tiniest detail. From design tokens to motion design. The underlying design system makes sure that all areas of your site have the same, consistent look and feel. Be it minimalistic, heroic, or playful.\n\nWhen a content-first design system is in place, content teams can ship great-looking content, without disturbing designers or developers.\n\n[.problem]\n  Current frameworks like **Bootstrap** and Tailwind lack a global, content-first design system that spans the entire website. UX/CSS professionals have a hard time contributing because CSS is tightly coupled inside the JavaScript code.\n\n\n## Motherf**king fast `UX` { #speed }\n\n[motherfuckingwebsite.com](//motherfuckingwebsite.com/) is a developer meme from **Barry Smith**. This plaintext website carries an important message: Get rid of the clutter and focus on content and performance.\n\nThe lesser-known fact is that you can build a design system that offers the performance levels of MF, and the design standards of Stripe and Linear.\n\nA perfect web framework gives you exactly that: The fastest possible website with a pixel-perfect design. A Moterf**king Stripe if you will.\n\n[.problem]\n  Current frameworks resort to [chickenshit minimalism][chicken] — the illusion of minimalism backed by megabytes of cruft. For example, the landing page of *create-next-app* uses 42,440 lines (363kb) of JavaScript to print \"Hello, World\"\n\n  [chicken]: //idlewords.com/talks/website_obesity.htm#minimalism\n\n\n## Consistent MPA+SPA experience `UX` { #ux }\n\nAll areas of your website should offer a consistently great user experience. This includes your content-heavy areas like the documentation and blog, the customer-facing app, and your internal admin dashboard.\n\nThe same \"motherf**king\" performance levels and the same pixel-perfect look and feel. And with a seamless \"turbo\" linking between your multipage and single-page apps.\n\n[.problem]\n  Current frameworks lack a hybrid multipage/single-page application development model. You end up mixing services and domain names. Say Astro for marketing pages, **Nuxt** for documentation, **Medium** for blog, and Vite for the SaaS app. It's impossible to offer a uniform design, performance, and user experience across the board.\n\n\n## Instant development loop `DX` { #devloop }\n\nA perfect framework should show a live preview of your change after you save a file. Be it content, styling, layout, server component, or client-side reactive component — you should see the change immediately. And if you make a mistake, the framework will tell you exactly what went wrong.\n\nThis kind of *universal hot-reloading* offers a true WYSIWYG experience for content producers, designers, and developers. Instant development loop gives a significant boost to your daily productivity.\n\n\n[.problem]\n  **Vite** and Next.js projects can have hundreds, even thousands of NPM modules. As the number of modules increases, your Hot Module Replacement (HMR) operations become heavier and slower. A styling change on your JS file can take seconds before it reaches the browser. And because HMR is limited to JavaScript and TypeScript files, both content teams and UX/HTML/CSS developers are missing the benefits of hot-reloading.\n\n\n## Easy to scale `DX` { #scale }\n\nAn ideal framework lets people with different backgrounds take part in scaling the system. Content teams scale the content, UX engineers focus on design and user experience, and JS developers work on the business model, networking, and infrastructure.\n\nThey use the same system for content-focused apps and single-page apps and enjoy the productivity boost from universal hot-reloading. They share components and design elements, so they can move faster and keep things consistent.\n\n[.problem]\n  Vite and **React** place all the burden on the JavaScript developer. They develop React components where content, styling, and logic are all mixed. Scaling becomes hard when content teams and UX developers cannot participate. And if you use different systems for blogging, documentation, and single-page apps — extra developer time is spent on keeping the external services together.\n\n\n## Sub-second deploy times `DX` { #ship }\n\nWhen you push out a new blog entry or a product release, something always goes wrong: Typos, wording issues, missing links, broken styling on mobile, ... You name it. In this situation, it's critical to have a fast shipping engine in place. You want to fix your errors before the next bunch of visitors arrives.\n\nThis sort of hot deployment engine is an extension to a full-blown git-based deployment system with support for versioned pushes, rollbacks, and staging environments.\n\n[.problem]\n  Shipping with **Vercel** or **Netlify** is slow. Deployment always triggers a full rebuild: All the pages, styles, scripts, and images are re-pushed. Production pushes take several minutes, sometimes more than an hour. Even, if it was just a small typo.\n\n\n## Universal template `DX` { #universal }\n\nThe universal template is a central starting point for hybrid multipage and single-page applications. It lets you quickly create a blog, startup, e-commerce site, or anything in between. First, you choose the desired apps, and then you link them to a chosen look and feel.\n\nFor example, you might choose a rich front page, documentation area, blogging area, onboarding flow, admin dashboard, and login page. Then you choose the design: Minimalistic, modern, playful, or heroic. Or perhaps abstract, brutal, pixelated, or retro.\n\nAnd of course, your site is automatically [content-first](#), [pixel-perfect](#), and [motherf**king fast](#).\n\n[.problem]\n  Tailwind, Bootstrap, or *WordPress* templates have no concept of apps and the tightly coupled design has limited customization possibilities. You only get what's there and implement the missing pieces from scratch.\n\n\n## Built-in cloud services `DX` { #complete }\n\nA perfect SAAS template comes with integrated customer relationship management, billing, and charging — along with unified analytics for traffic, people, and revenue.\n\nThis pack of services is connected to a desired cloud storage and the generated single-page application becomes part of your project directory. It automatically inherits your look and feel and extreme performance characteristics.\n\nThe best part: You can use this generic single-page app as a template for your customer-facing app.\n\nWe're looking at the fastest way to start a new, fully functional business.\n\n[.problem]\n  Vercel, *Heroku*, or *Render* don't have integrated services. You need to build them yourself or fall back to external services like HubSpot or Google Analytics. It's impossible to get a consistent UX/DX across the board.\n"
  },
  {
    "path": "packages/www/blog/perfect-web-framework/perfect.css",
    "content": "\nh2 code {\n  background-color: #e3f0ff;\n  font-size: 1rem;\n  color: var(--brand);\n  line-height: 1;\n  font-family: courier;\n  padding: .25em .5em;\n  border-radius: var(--radius-m);\n  float: right;\n  margin: .25em 0 0;\n}\n\n.problem {\n  border-left: 5px solid red;\n  background-color: #fff5f3;\n  padding: .1em 2.5em .1em 1.5em;\n  font-size: 95%;\n  margin: 2em 0;\n}\n\n\n#design, #speed, #ux {\n  code {\n    background-color: #cdffdf;\n    color: #28ac6c;\n  }\n}\n"
  },
  {
    "path": "packages/www/blog/rethinking-reactivity/index.md",
    "content": "---\ntitle: Rethinking reactivity\ndesc: How Nue JS works? How is it different from Svelte? Here's how\nog: /img/meme-big.jpg\ndate: 2023-10-02\n---\n\n**Nue JS** is a tiny, 2.1kb min-brotlied library for building user interfaces. It's an alternative to frameworks like *Vue*, *React*, and *Svelte* — and it's the central piece of the [Nue ecosystem](/), which is a ridiculously easier alternative to *Next.js* and *Astro*\n\n[image]\n  large: meme-big.jpg\n  small: meme.jpg\n  size: 600 × 428 px\n\nNue was linked to **Hacker News** on September 2023, and it was really a dream start for the project. I received a super warm welcome from developers, over 120k people came to see the website, and the project rapidly got thousands of stars on GitHub. My time lately has gone on fixing bugs, merging pull requests, and giving support.\n\nCommon questions are: \"How does reactivity work\"? and \"How is this different from React or Svelte\"? Here I do my best to answer those exact questions.\n\n\n## HTML-based\n\nIf React is \"just JavaScript\", then Nue is \"just HTML\". Here's how the difference between React and Nue using an identical counter component:\n\n\n### React\n\n```jsx\n/**\n * React counter: \"It's Just JavaScript\"\n */\nimport React, { useState } from \"react\";\n\nfunction App() {\n  const [count, setCount] = useState(0);\n\n  return (\n    <div>\n      <h2>You clicked {count} times!</h2>\n      <button onClick={() => setCount(count - 1)}>\n        Decrement\n      </button>\n      <button onClick={() => setCount(count + 1)}>\n        Increment\n      </button>\n    </div>\n  );\n}\n```\n\n[Source](//codesandbox.io/s/react-hooks-counter-demo-kqm9s?file=/src/index.js:0-427)\n\n\n### Nue\n\n[.note]\n  ### Note\n  This page shows an older Nue version with different syntax, but the concept remains the same.\n\n```html\n<!--\n  Nue counter: \"It's just HTML\"\n-->\n<div>\n  <h2>You clicked {count} times!</h2>\n  <button @click=\"count--\">Decrement</button>\n  <button @click=\"count++\">Increment</button>\n  <script>count = 0</script>\n</div>\n```\n\n[counter]\n\n[Source](//github.com/nuejs/nue/blob/master/packages/nuejs.org/blog/rethinking-reactivity/examples.dhtml)\n\nTo understand this choice we must go back in time. The world used to be slightly different before React and the kids entered the picture. There were two kinds of frontend skills: **UX development** and **JS development**:\n\n[.flex.thin]\n  ### UX development\n\n  - HTML + CSS\n  - Lightweight JavaScript\n  - Look and feel\n  - Responsive design\n  - Accessibility\n  - UI libraries\n\n  ---\n\n  ### JS development\n\n  - Advanced TypeScript/JS\n  - Business logic\n  - Backend integration\n  - End-to-end testing\n  - Keeping up the JS infra\n  - Performance optimization\n\n\nPeople who cared about UX could focus on the *front of the frontend*, and JS developers could focus on the *back of the frontend*. The talent was naturally spread and people did what they loved. It was great. *Brad Frost* wrote a [great article][brad] on the topic.\n\n\nToday frontend development is dictated by the JS developer and everything is assembled with TypeScript or JavaScript: Logic, layout, styling, images, and content.\n\nNue wants to change this and bring UX developers back to the forefront. For them, it's more natural to start with HTML (content first!), then add styling, and finally JavaScript if needed. JavaScript is *optional* in content-heavy websites. Yes, we're talking about \"old school\" things like progressive enhancement, separation of concerns, and semantic web design.\n\nTherefore: Nue is HTML first.\n\n\n## Instances\n\nIn 1998 *O'Reilly* published [Dynamic HTML: The Definitive Reference][dhtml] by *Danny Goodman*. This book changed the way I build websites. Before reading the book I had only played with HTML and CSS, but suddenly web was programmable? With Java? No -- JavaScript. WTH!\n\n[! dhtml.jpg width=\"200\"]\n\n[dhtml]: //www.amazon.com/Dynamic-HTML-Definitive-Danny-Goodman/dp/1565924940\n\nSuddenly I could do things like this:\n\n```html\n<FORM ACTION=\"/cgi-bin/form.cgi\"\n  •ONSUBMIT=\"return isValidEmail(this.email.value)\"•>\n  <INPUT TYPE=\"text\" NAME=\"email\">\n  <INPUT TYPE=\"submit\" VALUE=\"Submit\">\n</FORM>\n\n<!-- don't forget the type attribute! -->\n<SCRIPT TYPE=\"text/javascript\">\n  function isValidEmail(email) {\n    return /^\\S+@\\S+$/.test(email)\n  }\n</SCRIPT>\n```\n\nYes, HTML was in all caps back then. And there were no `type=\"email\"` fields, no `<button>` tag, nor the ability to post data with JavaScript. AJAX was invented seven years later. But I could make HTML dynamic and could move some of the dynamics from backend to the frontend.\n\nToday HTML, CSS, and JavaScript have incredibly more power. Especially JavaScript. One notable thing is [classes][classes], introduced in ECMAScript 2015 (aka \"ES6\") and now supported by all major browsers.\n\nNue uses classes to bring the DHTML vibes back to modern component-based web development. Let's rewrite the above example with Nue:\n\n```html\n<form @submit.prevent=\"submit\">\n  <input type=\"email\" name=\"email\" placeholder=\"your@address.com\" required>\n  <button>Submit</button>\n\n  •<script>•\n    // input validation is natively taken care of by the browser\n    async submit({ target }) {\n      await fetch(`/api/leads?email=${target.email.value}`)\n      location.href = '/thank-you'\n    }\n  •</script>•\n\n</form>\n```\n\nThe most notable thing is the `<script>` tag, which is now nested *inside* the component. This is the place for your ES6 class variables and methods.\n\nES6 classes make your code look amazingly compact and clean. You can add variables, methods, [getters][getters], [setters][setters], and `async` methods with the cute and short syntax. Here is a snippet from a [Todo MVC](/todomvc/) app written with Nue:\n\n```html\n<script>\n  clearCompleted() {\n    this.items.forEach(item => delete item.done)\n    this.save()\n  }\n\n  get numActive() {\n    return this.items.filter(item => !item.done).length\n  }\n\n  get hasCompleted() {\n    return this.items.find(item => item.done)\n  }\n\n  set filter(name) {\n    history.replaceState('', '', '#' + name)\n    this.filter = name\n  }\n\n  // ... clipped ...\n\n</script>\n```\n\n[classes]: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes\n[getters]: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get\n[setters]: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set\n\n\n## Reactivity model\n\nReactivity means that when the component state changes, the component automatically re-renders itself to the new state. Nue is no different from the other frameworks here:\n\n```html\n<button •@click=\"count++\"•>\n  Clicked { count } { count == 1 ? 'time' : 'times' }\n  <script>count = 0</script>\n</button>\n```\n\n[simple-counter]\n\nNue automatically updates the view when an event handler is clicked. Nue also re-renders automatically when working with arrays. For example:\n\n```html\n<div>\n  <p>\n    <button @click=\"addFruit\">Add</button>\n    <button @click=\"images.pop()\" :disabled=\"!images[4]\">Remove</button>\n  </p>\n\n  <img :for=\"img in images\" :src=\"/demo/img/{img}.jpg\">\n\n  <script>\n    images = ['popcorn', 'peas', 'lemons', 'tomatoes']\n\n    addFruit() {\n      const img = this.images[Math.floor(Math.random() * 4)]\n      this.images•.push(img)•\n    }\n  </script>\n</div>\n```\n\n[reactive-loop]\n\nBoth `push()` and `pop()` methods update the view automatically. Same with all the other Array methods like `sort()`, `unshift()`, `sort()`, `reverse()`, and `splice()`.\n\nSometimes only you know when an update must happen in which case you must call an instance method `this.update()` manually. For example, after some data has been fetched from the server:\n\n```html\n<div class=\"user\">\n  <img :src=\"user.avatar\">\n  <h3>{ user.name }</h3>\n  <p>{ user.email }</p>\n\n  <button @click=\"loadJane\" :disabled=\"is_loaded\">Load Jane</button>\n\n  <script>\n    user = {\n      avatar: 'avatars/john.jpg',\n      email: 'john@acme.org',\n      name: 'John Doe',\n    }\n\n    async loadJane() {\n      const req = await fetch('jane.json')\n      this.user = await req.json()\n      this.is_loaded = true\n      •this.update()•\n    }\n  </script>\n</div>\n```\n\n[user-update]\n\nAs a user of Nue JS, the `update()` method is really the only special thing you need to know about reactivity. Overall you need less thinking and framework-specific abstractions when working with Nue. For example, here's how you initialize a single reactive variable in various frameworks:\n\n### React\n\n```js\nimport { useState } from \"react\"\nconst [count, setCount] = useState(0)\n```\n\n### Vue\n\n```js\nimport { ref } from 'vue'\nconst count = ref(0)\n```\n\n### Svelte / \"Runes\"\n\n```js\nlet count = $state(0);\n```\n\n### Nue\n\n```js\ncount = 0\n```\n\n## Reactivity under the hood\n\nHere's how Nue JS works.\n\nFirst, a Nue component is compiled or \"transpiled\" to plain JavaScript so that browsers can run it. Let's look at our counter component again:\n\n```html\n<button :is=\"counter\" @click=\"count++\">\n  Clicked { count } { count == 1 ? 'time' : 'times' }\n  <script>count = 0</script>\n</button>\n```\n\nHere's what the counter looks like after the compilation:\n\n```js\n{\n  name: 'counter',\n  tmpl: '<button @click=\"0\">:1:</button>',\n  Impl: class { count = 0 },\n  fns: [\n    (_,e) => { _.count++ },\n    _ => ['Clicked ',_.count,' ',_.count == 1 ? 'time' : 'times']\n  ]\n}\n```\n\nThe compiled component has four properties:\n\n1. `name` — the component name\n2. `tmpl` — component's HTML/template code\n3. `Impl` — the ES6 class to create the component instance from\n4. `fns` — the template expressions turned to [CSP][csp]-compliant JavaScript\n\nAll the expressions on the template are replaced with an index number to match the function on the `fns` array. For example the click handler `@click=\"0\"` is the first function on the array, that is `fns[0]`. The underscore is the component instance where all the variables and methods can be found.\n\nWhen the component is mounted on the page, Nue creates a DOM tree from the template and makes the expression/function mapping. Each Nue component holds an array of expressions that are executed every time the component state changes. Nue also keeps track of all the child components, and they also get re-rendered when the parent state changes.\n\n`:if`, and `:for` expressions are also put on the execution array, but their logic is more complex, so they are executed with a dedicated [handler][if] [function][for].\n\nRe-rendering *mutates* the attributes, elements, and text nodes in place. No DOM diffing is needed.\n\nThat's Nue reactivity in short.\n\n[csp]: //developer.mozilla.org/en-US/docs/Web/HTTP/CSP\n[for]: //github.com/nuejs/nue/blob/master/packages/nuejs/src/browser/for.js\n[if]:  //github.com/nuejs/nue/blob/master/packages/nuejs/src/browser/if.js\n\n\n## Keeping things small\n\nThe compiled Nue code is very small: Only like ~1.2x larger than the HTML-based source code. This makes Nue applications the smallest on the market.\n\n*Evan You* (creator of Vue) [compared][evan] the generated code size of Vue and Svelte components. He used TodoMVC as the measure for an individual component. By adding [Nue TodoMVC](/todomvc/) to the mix we get the following data:\n\n[evan]: //github.com/yyx990803/vue-svelte-size-analysis\n\n[table head]\n  - Framework              | Vue     | Svelte  | Nue\n  - Framework size         | 16.89kb | 1.85kb  | 2.13 kb\n  - Todo MVC size          | 1.10kb  | 1.88kb  | 0.96 kb\n  - Framework + 1 Todo     | 17.99kb | 3.73kb  | 3.09 kb\n  - Framework + 10 Todos   | 27.89kb | 20.65kb | 11.73 kb\n\nNue has the smallest footprint:\n\n[image]\n  large: file-sizes-big.png\n  small: file-sizes.png\n  width: 550\n\n\n## Predicting the future\n\nI see that frontend development is trending into the following directions:\n\n**Multipage applications (MPA)** are on the rise. With the emergence of server components and tools like Astro and Nue, people will eventually realize that the SPA (single page application) model is not ideal for \"normal\", content-heavy websites.\n\n**UX development** becomes a thing again. Not everything should be controlled by JavaScript and by JS engineers. User experience optimization requires a different set of goals, skills, and interests. And the MPA trend increases this need because JS developers are less needed in developing content-heavy websites.\n\n**More standards-based coding**. As developers move to multipage applications JavaScript is rendered on the server side, and client-side JavaScript becomes optional. This forces the pre-SPA best practices to come back: Separation of concerns, progressive enhancement, and semantic web design.\n\nNue is designed from the ground up to be on par with the above trends.\n"
  },
  {
    "path": "packages/www/blog/standards-first-react-alternative/complex-table.md",
    "content": "\n---\nback_to: standards-first-react-alternative/\npagehead: false\nnoindex: true\n---\n\n# More complex table\nA sortable/filterable table component, wrapped inside a card component. Implemented with modern React and Hyper.\n\n## With modern React\nExcessive boilerplate through Tanstack Table, ShadCN, and TypeScript interfaces\n\n``` jsx\nimport * as React from \"react\"\n\nimport {\n  ColumnDef,\n  SortingState,\n  flexRender,\n  getCoreRowModel,\n  getSortedRowModel,\n  getFilteredRowModel,\n  useReactTable,\n  ColumnFiltersState,\n} from \"@tanstack/react-table\"\n\nimport { ArrowUpDown, Search } from \"lucide-react\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\n\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableCaption,\n  TableRow,\n} from \"@/components/ui/table\"\n\nimport {\n  Card,\n  CardTitle,\n  CardHeader,\n  CardDescription,\n  CardContent,\n} from \"@/components/ui/card\"\n\nimport { Person, DataTableProps } from \"./DataTable.types.ts\";\n\n\n// Create a reusable sortable header component\nconst SortableHeader = ({ column, title, align = \"left\" }) => (\n  <div className={align === \"right\" ? \"text-right\" : \"\"}>\n    <Button\n      variant=\"ghost\"\n      onClick={() => column.toggleSorting(column.getIsSorted() === \"asc\")}\n    >\n      {title}\n      <ArrowUpDown className=\"ml-2 h-4 w-4\" />\n    </Button>\n  </div>\n);\n\n// Define the columns more concisely\nconst columns: ColumnDef<Person>[] = [\n  {\n    accessorKey: \"name\",\n    header: ({ column }) => <SortableHeader column={column} title=\"Name\" />,\n  },\n  {\n    accessorKey: \"email\",\n    header: ({ column }) => <SortableHeader column={column} title=\"Email\" />,\n  },\n  {\n    accessorKey: \"age\",\n    header: ({ column }) => <SortableHeader column={column} title=\"Age\" />,\n  },\n  {\n    accessorKey: \"total\",\n    header: ({ column }) => <SortableHeader column={column} title=\"Total\" align=\"right\" />,\n    cell: ({ row }) => {\n      const amount = parseFloat(row.getValue(\"total\"))\n      return <div className=\"text-right font-medium\">{new Intl.NumberFormat('en-US').format(amount)}</div>\n    },\n  },\n]\n\nexport default function DataTable({ data }: DataTableProps) {\n  const [sorting, setSorting] = React.useState<SortingState>([])\n  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([])\n\n  const table = useReactTable({\n    data,\n    columns,\n    onSortingChange: setSorting,\n    onColumnFiltersChange: setColumnFilters,\n    getCoreRowModel: getCoreRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n    state: {\n      sorting,\n      columnFilters,\n    },\n  })\n\n  return (\n    <div className=\"p-8\">\n      <Card>\n        <CardHeader>\n          <CardTitle>Table example</CardTitle>\n          <CardDescription>A table example with filtering and sortable columns</CardDescription>\n        </CardHeader>\n        <CardContent>\n          <div className=\"flex items-center py-4\">\n            <div className=\"relative max-w-sm\">\n              <Search className=\"absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground\" />\n              <Input\n                placeholder=\"Filter by name...\"\n                value={(table.getColumn(\"name\")?.getFilterValue() as string) || \"\"}\n                onChange={(e) => table.getColumn(\"name\")?.setFilterValue(e.target.value)}\n                className=\"pl-8\"\n              />\n            </div>\n          </div>\n        </CardContent>\n        <Table>\n          <TableHeader>\n            {table.getHeaderGroups().map((headerGroup) => (\n              <TableRow key={headerGroup.id}>\n                {headerGroup.headers.map((header) => (\n                  <TableHead key={header.id} className=\"px-4 py-3\">\n                    {header.isPlaceholder\n                      ? null\n                      : flexRender(\n                          header.column.columnDef.header,\n                          header.getContext()\n                        )}\n                  </TableHead>\n                ))}\n              </TableRow>\n            ))}\n          </TableHeader>\n          <TableBody>\n            {\n              table.getRowModel().rows.map((row) => (\n                <TableRow key={row.id}>\n                  {row.getVisibleCells().map((cell) => (\n                    <TableCell key={cell.id} className=\"px-7 py-3\">\n                      {flexRender(\n                        cell.column.columnDef.cell,\n                        cell.getContext()\n                      )}\n                    </TableCell>\n                  ))}\n                </TableRow>\n              ))\n            ) : (\n              <TableRow>\n                <TableCell\n                  colSpan={columns.length}\n                  className=\"h-24 text-center\"\n                >\n                  No results.\n                </TableCell>\n              </TableRow>\n            )}\n          </TableBody>\n          <TableCaption className=\"mb-5\">{ data.length } people in total</TableCaption>\n        </Table>\n      </Card>\n    </div>\n  )\n}\n```\n\nHere is the extra TypeScript needed (not included in the comparison images):\n\n```\ntype Person = {\n  id: string;\n  name: string;\n  email: string;\n  age: number;\n  total: number;\n};\n\ninterface DataTableProps {\n  data: Person[]\n}\n```\n\n\n## With vanilla TSX { #oldschool }\nThis is an oldschool example using external CSS, which is no longer the \"idiomatic\" way to build React compomnents:\n\n```\nimport React, { useState, useMemo } from \"react\";\n\nimport { Person, DataTableProps } from \"./DataTable.types.ts\";\n\nexport default function DataTable({ data }: DataTableProps) {\n  const [sortField, setSortField] = useState<keyof Person | null>(null);\n  const [sortDirection, setSortDirection] = useState<1 | -1>(1);\n  const [filterValue, setFilterValue] = useState(\"\");\n\n  const handleSort = (field: keyof Person) => {\n    if (sortField === field) {\n      setSortDirection(sortDirection === 1 ? -1 : 1);\n    } else {\n      setSortField(field);\n      setSortDirection(1);\n    }\n  };\n\n  const filteredData = useMemo(() => {\n    return filterValue\n      ? data.filter(person =>\n          person.name.toLowerCase().includes(filterValue.toLowerCase())\n        )\n      : data;\n  }, [data, filterValue]);\n\n  const sortedData = useMemo(() => {\n    if (!sortField) return filteredData;\n\n    return [...filteredData].sort((a, b) => {\n      if (a[sortField] > b[sortField]) return sortDirection;\n      if (a[sortField] < b[sortField]) return -sortDirection;\n      return 0;\n    });\n  }, [filteredData, sortField, sortDirection]);\n\n  return (\n    <div className=\"card-container\">\n      <div className=\"card\">\n        <div className=\"card-header\">\n          <h1 className=\"card-title\">Table example</h1>\n          <p className=\"card-description\">A table example with filtering and sortable columns</p>\n          <div className=\"search-container\">\n            <input\n              type=\"text\"\n              placeholder=\"Filter by name...\"\n              value={filterValue}\n              onChange={(e) => setFilterValue(e.target.value)}\n              className=\"search-input\"\n            />\n          </div>\n        </div>\n\n        <table className=\"data-table\">\n          <thead>\n            <tr>\n              <th>\n                <button className=\"sort-button\" onClick={() => handleSort(\"name\")}>\n                  Name\n                  <span className=\"sort-icon\">↕</span>\n                </button>\n              </th>\n              <th>\n                <button className=\"sort-button\" onClick={() => handleSort(\"email\")}>\n                  Email\n                  <span className=\"sort-icon\">↕</span>\n                </button>\n              </th>\n              <th>\n                <button className=\"sort-button\" onClick={() => handleSort(\"age\")}>\n                  Age\n                  <span className=\"sort-icon\">↕</span>\n                </button>\n              </th>\n              <th>\n                <button className=\"sort-button\" onClick={() => handleSort(\"total\")}>\n                  Total\n                  <span className=\"sort-icon\">↕</span>\n                </button>\n              </th>\n            </tr>\n          </thead>\n          <tbody>\n            {sortedData.length > 0 ? (\n              sortedData.map((person) => (\n                <tr key={person.id}>\n                  <td>{person.name}</td>\n                  <td>{person.email}</td>\n                  <td>{person.age}</td>\n                  <td className=\"text-right\">\n                    {new Intl.NumberFormat('en-US').format(person.total)}\n                  </td>\n                </tr>\n              ))\n            ) : (\n              <tr>\n                <td colSpan={4} className=\"empty-message\">\n                  No results.\n                </td>\n              </tr>\n            )}\n          </tbody>\n          <caption className=\"table-caption\">{data.length} people in total</caption>\n        </table>\n      </div>\n    </div>\n  );\n}\n```\n\n\n## With Hyper\nUses only about 40 lines of code, roughly 75% reduction in code to impolement the same features.\n\n\n``` html\n<div class=\"card\">\n  <header>\n    <h1>Table example</h1>\n    <p>A table example with filtering and sortable columns</p>\n    <input type=\"search\" :input=\"filter\" placeholder=\"Filter by name...\">\n  </header>\n\n  <table>\n    <tr>\n      <th><a :onclick=\"sort('name')\">Name</a></th>\n      <th><a :onclick=\"sort('email')\">Email</a></th>\n      <th><a :onclick=\"sort('age')\">Age</a></th>\n      <th><a :onclick=\"sort('total')\">Total</a></th>\n    </tr>\n\n    <tr :for=\"user of subset || users\" key=\"${ user.id }\">\n      <td>${ user.name }</td>\n      <td>${ user.email }</td>\n      <td>${ user.age }</td>\n      <td>${ new Intl.NumberFormat('en-US').format(user.total) }</td>\n    </tr>\n    <tr :if=\"subset && !subset[0]\"><td colspan=\"4\">No results</td></tr>\n\n    <caption>${ users.length } people in total</caption>\n  </table>\n\n  <script>\n    sort(by) {\n      this.by = this.by == by ? this.by : by\n      this.dir = this.by == by ? -this.dir || -1 : 1\n      this.users.sort((a, b) => (a[by] > b[by] ? 1 : -1) * this.dir)\n    }\n\n    filter(e) {\n      const val = e.target.value.trim().toLowerCase()\n      this.subset = val ? this.users.filter(el => el.name.toLowerCase().includes(val)) : null\n    }\n  </script>\n</div>\n```\n"
  },
  {
    "path": "packages/www/blog/standards-first-react-alternative/index.md",
    "content": "\n---\ntitle: Hyper: Standards first React alternative\nhero_title: **Hyper:** Standards first React alternative\ndate: 2025-05-08\n---\n\n[.note]\n  ### Note\n  Hyper was an early preview of what became [Nuedom](/docs/nuedom). The syntax here is a bit different, but the core ideas remain the same.\n\nHyper is a standards first markup language for building user interfaces. It enables developers (and AI models) to generate complex UIs with amazingly clean syntax.\n\n[image.bordered]\n  large: img/hyper-banner-dark-big.png\n  small: img/hyper-banner-dark.png\n  size: 1305 × 517\n\n\n## Project goals\n1. **Standards first**: User interfaces should be assembled with HTML, styled with CSS, and enhanced with JavaScript.\n\n2. **Simplicity**: UI composition should be easy and require as few idioms and abstractions as possible, both on client and server (SSR).\n\n3. **Design Systems**: Design should be a separate subsystem, easily accessible for developers who care about and understand design.\n\n4. **Scalability**: Complex UIs should retain simplicity as the application grows.\n\nTo understand how these goals are met, we use React as a counter-example because it's the opposite of Hyper's architecture and design goals. React embraces a monolithic architecture where logic, structure, and styling are mixed together, while Hyper is what React 1.0 (2013) originally envisioned: just a headless view layer.\n\nLet's study the difference in more detail:\n\n\n\n## Simple components\nBelow is a basic `<table>` component defined in three ways:\n\n[.flex]\n  [! img/simple-table-1.png]\n    caption: Modern React\n    href: simple-table\n\n  [! img/simple-table-2.png]\n    caption: Old school React\n    href: simple-table#oldschool\n\n  [! img/simple-table-3.png]\n    caption: Hyper\n    href: simple-table#hyper\n\n\n1. **Modern React** Modern React represents a common approach to building user interfaces today using component libraries such as ShadCN/UI, Material UI, Chakra, or Tailwind Catalyst. In this example, we chose ShadCN as it's gained significant popularity in recent years and has strong AI tool support. For example, Claude and ChatGPT offer built-in support for ShadCN in their code previews. [Source](simple-table) • [Demo](/hyper/demo/react/simple-table)\n\n2. **Old school React** is how React components were built back in the days when styling was decoupled from the component code. [Source](simple-table#oldschool) • [Demo](/hyper/demo/react/simple-table-oldschool)\n\n3. **Hyper** demonstrates the standards-first approach. [Source](simple-table#hyper) • [Demo](/hyper/demo/table/simple-table)\n\nWhile these differences might seem minor, they become apparent when we move to more complex components:\n\n\n## Complex components\nNext we examine how these approaches handle increasing complexity. Here's the same table component, but now with sorting and filtering:\n\n[.flex]\n  [! img/complex-table-1.png caption=\"Modern React\"]\n    href: complex-table\n\n  [! img/complex-table-2.png caption=\"Vanilla TSX\"]\n    href: complex-table#oldschool\n\n  [! img/complex-table-3.png caption=\"Hyper\"]\n    href: complex-table#hyper\n\n\n1. **Modern React** is assembled according to ShadCN's [data table](//ui.shadcn.com/docs/components/data-table) documentation. The bundled JavaScript is **91.3KB**. [Source](complex-table) • [Demo](/hyper/demo/react/complex-table)\n\n2. **Vanilla TSX** uses `useState` and `useMemo` to implement the added functionality, and the HTML is tagged the old school way with numerous class names.\n\n3. **Hyper** uses semantic HTML with minimal class names and two instance methods for sorting and filtering. The resulting JS is only **3.9KB** minzipped (1.2KB + 2.7KB for hyper.js). [Source](complex-table#hyper) • [Demo](/hyper/demo/table/complex-table)\n\n\n\n## Design systems\nHere's an example dashboard assembled with Hyper:\n\n[image.large]\n  large: img/minimalist-big.png\n  small: img/minimalist.png\n  href: /hyper/demo/dashboard/minimal\n\n[Source](//github.com/nuejs/nue/blob/master/packages/hyper/demo/dashboard/components/0-dashboard) • [Demo](/hyper/demo/dashboard/minimal)\n\nHere is the same dashboard, but with \"Ramsian\" look and feel:\n\n[image.large]\n  large: img/ramsian-big.png\n  small: img/ramsian.png\n  href: /hyper/demo/dashboard/ramsian\n\nThis transformation required zero changes to component code. Just a [32-line CSS file](//github.com/nuejs/nue/blob/master/packages/hyper/demo/dashboard/ramsian.css) extending the base design system. [Demo](/hyper/demo/dashboard/ramsian)\n\n\n### Modern React: tightly coupled design\nThis kind of design swap becomes a large programming effort when design choices are coupled into components via CSS-in-JS or Tailwind. For example, in ShadCN, to change the typography of your headings, you need to edit [alert-dialog.tsx](//github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/alert-dialog.tsx), [alert.tsx](//github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/alert.tsx), [card.tsx](//github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/card.tsx), [dialog.tsx](//github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/dialog.tsx), [drawer.tsx](//github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/drawer.tsx), and [sheet.tsx](//github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/sheet.tsx). This requires understanding of idioms like `data-slot`, `{...props}`, `cn()`, `clsx()`, and `twMerge()`. Here's the code for the title and description elements:\n\n[image]\n  large: img/shadcn-typo-big.png\n  small: img/shadcn-typo.png\n\nTight coupling can also create a maintenance issue. For example, ShadCN's \"New York\" theme duplicates their default theme, resulting in 40,000+ lines of [additional TSX code][new_york] to maintain.\n\nWhile you definitely *can* decouple your styling from your React components, this pattern is rarely seen in real-world applications. Instead, the use of vanilla CSS is often discouraged due to concerns about \"global namespace pollution\" or other reasons.\n\n\n### Hyper: decoupled design system\nBy contrast, Hyper colocates your typography concerns into a single CSS file, acting as the single source of truth for your `h2` an `p` element styling:\n\n```.thin\n// typography.css\n@layer globals {\n  h2 {\n    font-size: 1.125em;\n    font-weight: 500;\n  }\n  p {\n    color: var(--base-500);\n    text-wrap: balance;\n    line-height: 1.5;\n  }\n\n  // all typography rules \"colocated\" here\n}\n```\n\nThis solves three key issues in modern React:\n\n1. **Truly reusable components** across projects and styling contexts\n1. **Central design system** easily maintainable from the same place\n1. **Zero boilerplate** due to strict separation of concerns\n\nNue actually _enforces_ you to external design system. Tight coupling in any form: CSS-in-JS, class name abuse, component-specific `<style>` elements, inline `style` attributes, or cryptic class name values like `size-[max(100%,2.75rem)]` are systematically eliminated.\n\n\n## Scalability\nHere's how simplicity scales: a full-scale [app](//mpa.nuejs.org/app/) lighter than a React button:\n\n[image]\n  small: /blog/large-scale-apps/react-button.png\n  large: /blog/large-scale-apps/react-button-big.png\n  size:  704 × 394\n\nYour application starts simple, and remains simple as the project grows. [Source](//github.com/nuejs/nue/tree/master/packages/examples/simple-mpa) • [Demo](//mpa.nuejs.org/app/)\n\n\n\n## FAQ\n\n### How is this different from Svelte and Vue?\nWhile Svelte and Vue both offer a more lightweight development environment than React, they still diverge from Hyper's standards-first vision. Though they provide better separation of concerns with their component structure, many popular patterns in these ecosystems still encourage coupling design with components through scoped CSS, CSS-in-JS libraries, or Tailwind integration.\n\n\n### What is Nue?\nNue is a website/webapp generator based on [Nue JS](//github.com/nuejs/nue/tree/master/packages/nuejs) templating. Hyper is the next evolution of Nue JS, which it will replace. All Nue projects reside under the same [monorepo](//github.com/nuejs/nue/tree/master/packages/).\n\n\n### Isn't this just another framework?\nHyper takes a different approach than what this question suggests. Rather than adding to the ecosystem of tools and abstractions, Hyper aims to reduce tooling and complexity by returning to web standards. It eliminates the need for many specialized frameworks, libraries, and practices that have emerged to solve challenges within the React ecosystem. Our goal is to offer a simpler path reducing the need for constantly learning new frameworks and tools.\n\n\n### Why are standards so important?\nStandards offer significant long-term benefits:\n\n1. **Timeless skills**: Knowledge of HTML, CSS, and JavaScript fundamentals remains valuable across decades, while framework-specific knowledge can become outdated.\n\n2. **Sustainable products**: Applications built primarily with web standards tend to require less frequent and less disruptive rewrites.\n\nConsider how React's ecosystem has evolved over time: Class components gave way to hooks, state management shifted from Redux to Context to various alternatives like Zustand and Jotai, and styling approaches continue to evolve from Styled Components to Emotion to CSS Modules and beyond. Each shift requires developers to learn new patterns and often refactor existing code.\n\n\n\n"
  },
  {
    "path": "packages/www/blog/standards-first-react-alternative/simple-table.md",
    "content": "\n---\nback_to: standards-first-react-alternative/\npagehead: false\nnoindex: true\n---\n\n# Simple table component\n\n## With modern React\nModern React uses TypeScript and component libraries like ShadCN or Chakra UI. Here's ShadCN <Table> in action:\n\n\n``` jsx\nimport React from \"react\";\n\nimport { User, UserTableProps } from \"./UserTable.types.ts\";\n\nimport {\n  Table,\n  TableBody,\n  TableCaption,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\n\nexport const UserTable: React.FC<UserTableProps> = ({ users }) => {\n  return (\n    <Table>\n      <TableHeader>\n        <TableRow>\n          <TableHead>Name</TableHead>\n          <TableHead>Email</TableHead>\n          <TableHead>Age</TableHead>\n        </TableRow>\n      </TableHeader>\n      <TableBody>\n        {users.map((user, index) => (\n          <TableRow key={index}>\n            <TableCell>{user.name}</TableCell>\n            <TableCell>{user.email}</TableCell>\n            <TableCell>{user.age}</TableCell>\n          </TableRow>\n        ))}\n      </TableBody>\n    </Table>\n  );\n};\n\nexport default UserTable;\n```\n\nHere is the extra TypeScript needed (not included in the comparison images):\n\n```\n// UserTable.types.ts\nexport interface User {\n  name: string;\n  email: string;\n  age: number;\n}\n\nexport interface UserTableProps {\n  users: User[];\n}\n```\n\n\n## Old school React { #oldschool }\nIn the early days of React (circa 2013-2016), styling followed standard web development patterns with completely separate concerns. CSS files were loaded directly in HTML, not into the React file. A simple table component would be implemented like this:\n\n``` jsx\nimport React from 'react';\n\nclass TableComponent extends React.Component {\n  render() {\n    return (\n      <table>\n        <thead>\n          <tr>\n            <th>Name</th>\n            <th>Email</th>\n            <th>Age</th>\n          </tr>\n        </thead>\n        <tbody>\n          {this.props.users.map(user => (\n            <tr>\n              <td>{user.name}</td>\n              <td>{user.email}</td>\n              <td>{user.age}</td>\n            </tr>\n          ))}\n        </tbody>\n      </table>\n    );\n  }\n}\n\nexport default TableComponent;\n```\n\n\n## Hyper\nClean, semantic HTML. Any attempt to hardcode styling is prohibited.\n\n```\n<table>\n  <tr>\n    <th>Name</th>\n    <th>Email</th>\n    <th>Age</th>\n  </tr>\n  <tr :for=\"user of users\">\n    <td>${ user.name }</td>\n    <td>${ user.email }</td>\n    <td>${ user.age }</td>\n  </tr>\n</table>\n```\n"
  },
  {
    "path": "packages/www/blog/standards-first-web-framework/index.md",
    "content": "---\ntitle: A standards first web framework\ndescription: Nue is HTML, CSS, and JavaScript taken to their absolute peak.\ndate: 2025-01-16\n---\n\nToday Nue takes a new, more natural direction: it becomes a **standards first web framework**. The focus has always been to strip away artificial layers and help developers take modern HTML, CSS, and JavaScript to their absolute peak. This shift is important, because now we can more directly work on solving the two key issues in frontend development:\n\n\n## 1: The frontend engineering problem\n\nI have lived through the eras of **DHTML**, **jQuery**, and now the era of **React**. I experienced positive growth in the first two eras, but this last one has been painful to watch: instead of building on web standards, we've buried them under layers of abstractions. What began as HTML, CSS, and JavaScript has devolved into a complex build orchestration demanding hundreds of dependencies, even for a simple page.\n\nWe've normalized this extraordinary complexity. Landing pages are turned into JavaScript monoliths with hundreds of lines of **TypeScript**. Blog posts that should be static content require multiple React components and a chain of plugins. Documentation and blogging areas need a new framework with different patterns and a separate maintenance burden.\n\nThe cost is more than just complexity, build times, and development speed. It's the whole culture of web development. Teams spend more time wrestling with framework documentation than solving real problems. Build times that should take milliseconds are taking minutes. Each day we drift further from the web's core strengths.\n\nThe biggest trouble is this: the more you invest in learning today's frameworks, the more technical debt you accumulate in your mind. React patterns from just a few years ago are now considered antipatterns. State management solutions keep cycling through **Redux**, **MobX**, **Recoil**, and countless others. Meanwhile, the web platform itself has quietly evolved to provide native solutions for most of what these frameworks try to solve.\n\n\n## 2: The design engineering problem\n\nI've been building frontends for almost three decades now. I remember when CSS was released to **Internet Explorer 3.0**. Ever since, I've enjoyed working in the intersection between design and engineering. My heroes aren't framework creators but the masters of Bauhaus: **Mies van der Rohe** and **Dieter Rams**. They showed how mathematics and systematic thinking create more impact than anything else.\n\nToday should be the golden age for design engineering. Modern CSS finally gives us the power to express sophisticated design systems through mathematics. We can create linear, typographic scales, color relationships through **OKLCH** calculations, and layouts with container queries. We have endless possibilities for systematic design.\n\nBut the JavaScript monolith has blocked the progress:\n\n1. First, JavaScript engineers have hijacked the conversation. They obsess over \"global namespace pollution\", \"dead code elimination\", or \"type safety\" while completely missing the point. When was the last time you saw engineers debating the merits of the **Perfect Fifth** typographic scale or the principles behind Dieter Rams's systematic approach?\n\n2. The core issue, however, is the inability to participate in the actual craft. Design decisions are buried in React components with cryptic expressions like `flex items-center shadow-lg p-6 hover:bg-gray-50 dark:bg-gray-800 py-[calc(theme(spacing[2.5])-1px)]`. This might make sense for JavaScript engineers, but it's an insult to systematic design.\n\n3. The gap between design and engineering has never been wider. There's even a name for it: \"a designer-developer handoff\". The path from **Figma** to React is cryptic. Meanwhile, the amazing dynamics between CSS variables and `calc()` remains unexplored.\n\nBy coupling design decisions to JavaScript components, we've created a design monoculture. You cannot take a headless structure and apply different designs to it. You can't switch between Rams's human-centric functionalism and Mies's architectural minimalism.\n\nThe JavaScript monolith isn't just a technical problem, it is suffocating the creative results of systematic design. The grand, architectural idea of \"form follows function\" is dead.\n\n\n## The standards first web framework\n\nTo relieve my disappointment with the frontend ecosystem, I'm showing a better way to build websites. It is based on the following foundations:\n\n1. **Standards first**: Browsers have evolved significantly in the past decade. By working with the standards rather than against them, we create better products with less code.\n\n2. **HTML first**: semantic HTML is the foundation for everything: layouts, server components, and interactive islands. The same syntax and mental model is better for developers, search engines, and screen readers.\n\n3. **Content first**: Rich Markdown syntax handles everything from marketing to documentation. Content lives in clean, accessible files — not in JavaScript.\n\n4. **Design systems**: modern, systematic CSS is more powerful than you think. Learn to build design systems, and you'll get better interfaces with less code.\n\nHere's what this enables:\n\n1. **Faster tooling**: Content updates in 10-50ms, style changes in 5-20ms, and component modifications in 20-100ms. It's a whole new hot-reloading experience.\n\n2. **Cleaner code**: No TypeScript imports, no utility classes, no state management complexity. Just clean, minimalistic code that naturally separates content, structure, and styling.\n\n3. **Faster pages**: The fastest page load is one that requires just a single request. No framework initialization, no cumulative layout shifts, no waiting for JavaScript. When content and styling arrive together, pages simply appear.\n\n[Learn how Nue works →](/docs/)\n\n[image standards-first.png]\n  alt: Standards first\n  width: 600\n\n[.heroquote]\n  > “Nue is exactly what it promises: faster tooling, cleaner code, and a liberating experience. You must try it.”\n\n  — **Mauricio Wolff**, Staff Product Designer at Miro\n\n- - -\nNow a little bit about the future.\n\n## The design system of my dreams\n\nI'm slowly building something I've dreamed about since CSS was invented: A global design system that takes mathematical relationships to their ultimate conclusion. When finished, it will be as simple as:\n\n```sh\nnue create startup --design rams\n```\n\nLet's dive into this. First off, this command structure is the very reason I started building Nue in the first place. It demonstrates the benefits of separation of concerns as clearly as possible. A template defines the **function** — whether it's a blog, an idea showcase, a startup platform, or an enterprise site. The design system defines the **form**, which brings the template to life through a user-selected look and feel.\n\nBehind the \"rams\" command argument is something called a **global design system** that acts as a basis for four wildly different mathematical expressions:\n\n1. **Mies** — This design system is obviously special for me. It's the hardcore minimalism that many designers look up to. The result looks remarkably similar to **Linear**, but achieves its commanding presence through mathematical precision: stark contrasts, intense whitespace, and bold architectural functionalism. Check **Seagram Building** in **New York** and you'll get the idea.\n\n2. **Rams** — Dieter Rams is the man behind **Apple**'s design philosophy. Like Mies, he used mathematics to achieve harmony. This design system is perhaps closest to what current utility-first frameworks aim for. Think **Stripe**, but without the decorative stripes.\n\n3. **Muriel** — **Muriel Cooper** revolutionized digital design at MIT by showing how mathematical systems could create both precision and joy. This system builds on her work, proving how systematic thinking enables creative freedom. Like Apple, it achieves playfulness through calculation — but goes further by making every relationship mathematically pure.\n\nImagine what this means for web development: you can create an Apple-like startup site for one client, a Stripe-like platform for another, and a Linear-inspired interface for a third. All through a single command that takes seconds rather than the months of custom development these looks typically require.\n\nBut even this is just the beginning…\n\n\n## Single-page applications\n\nOnce the design systems are out, I'll release the next major piece: single-page applications. These sit right in the middle of design and engineering and want Nue to take these artforms to their absolute peak. Three key methods:\n\n1. Architecture is based on **MVC** (model, view, controller) which creates the much-needed separation of concerns. JS engineers work on pure business logic. UI developers craft the experience. Everyone stays in their zone of expertise.\n\n2. The design system is clearly separated. Change the look and feel of the website from Mies to Muriel, and the SPA will follow. Form follows function.\n\n3. The SPA becomes a seamless part of your marketing site: the design system, performance, and view transitions. The movement between static and dynamic pages is seamless. It all feels like one product.\n\nThink what this means for agencies and freelancers:\n\n```sh\nnue create spa --function crm --todir app --design inherit\n```\n\nThis creates a complete startup platform, all expressing itself through the same sophisticated design system. What previously required multiple teams and months of development now emerges from one command.\n\nYou can see how this changes things. Instead of building from scratch, you get Linear-level design, Stripe-level functionality, and a consistent UX in an instant.\n\n\n"
  },
  {
    "path": "packages/www/blog/tailwind-misinformation-engine/index.md",
    "content": "---\ndate: 2024-02-18\nog: img/tailwind-messaging-pillars.jpg\ntitle: Tailwind marketing and misinformation engine\ndesc: The origins of Tailwind and how it is framed to solve the proposed issues of CSS\n---\n\n\n**Adam Wathan** explained in his [keynote speech](//youtu.be/CLkxRnRQtDE?t=109) at Tailwind Connect 2023 how Tailwind CSS was born out of this sentence:\n\n[image \"img/adam-keynote.jpg\"]\n  caption: The most reusable components are those with class names that are independent of the content.\n\n\n\nThe [sentence](//youtu.be/CLkxRnRQtDE?t=109) is from **Nicolas Gallagher**'s article about [HTML semantics and front-end architecture](//nicolasgallagher.com/about-html-semantics-front-end-architecture/). It was a turning point for **Adam Wathan**, the creator and frontman of Tailwind. After reading the article he was [\"fully convinced that optimizing for reusable CSS was going to be the right choice\"](//adamwathan.me/css-utility-classes-and-separation-of-concerns/)\n\n\n## Phase 1: The Origins of Tailwind { #origins }\n\nNicholas points out in the article that scalable HTML/CSS must [\"rely on classes within the HTML to allow for the creation of reusable components\"](//nicolasgallagher.com/about-html-semantics-front-end-architecture/). So instead of using a content-dependent class name like \"news\", one should use a **content-independent** name like \"uilist\" or \"uilist-item\":\n\n```html\n<nav class=\"•uilist•\">\n  <span class=\"•uilist-item•\">\n    ...\n  </span>\n</nav>\n```\n\nThe more generic the name, the more reusable it is. He used the famous [media object](//www.stubbornella.org/2010/06/25/the-media-object-saves-hundreds-of-lines-of-code/) as a prime example of reusable CSS.\n\n\n**But that's not how Adam understood the sentence.** Instead of moving towards more reusable class names, he introduced a custom grammar to inline styling rules directly to HTML:\n\n```html\n<!-- \"uilist\" -->\n<div class=\"\n> sticky top-0 z-40 w-full backdrop-blur flex-none\n> transition-colors duration-500 lg:z-50 lg:border-b\n> bg-white/95 supports-backdrop-blur:bg-white/60\n> dark:bg-transparent\">\n\n  <!-- \"uilist-item\" -->\n  <span class=\"\n>   py-4 border-b border-slate-900/10 lg:px-8\n>   lg:border-0 dark:border-slate-300/10 px-4\">\n    ...\n  </span>\n</div>\n```\n\n\nThis was quite a big step away from what Nicolas was saying, who resisted the idea of coupling visual information to elements, like everyone else in the industry back then.\n\nHowever, in his keynote speech, Adam [makes us believe](//youtu.be/CLkxRnRQtDE?si=mOLOiY8cKLJVb6XZ&t=172) that the language he created was a prime example of Nicholas' thinking. And because Nicolas was [working at Twitter](//youtu.be/CLkxRnRQtDE?si=af_srSIFIqgmp0mc&t=186), Adam's take on CSS should work for sites small and large.\n\nOn August 7, 2017, Adam wrote an article about [CSS utility classes and \"Separation of Concerns\"][adam]. It demonstrates with cleverly chosen examples, how his new creation leads to more maintainable CSS architecture.\n\n[adam]: //adamwathan.me/css-utility-classes-and-separation-of-concerns/\n\nBut there was a challenge: To make such a statement, he needed to reshape the established CSS best practices. So he introduced [new terms](//adamwathan.me/css-utility-classes-and-separation-of-concerns/) to back his contradictory idea:\n\n[image \"img/tailwind-practises.png\" width=\"570\"]\n  caption: The new terms and phrases Tailwind developers are familiar with\n\n\nThe new approach can be summarized as follows:\n\n> [\"Semantic class names” are the reason CSS is hard to maintain](//tailwindcss.com/)\n\nThis was a hefty statement as it contradicts with all the prior work and studies about CSS.\n\n\nIn his keynote speech, Adam uses [harsh words](//youtu.be/CLkxRnRQtDE?si=s5bmoLnGsmbYDzMA) to describe the traditional way of structuring CSS, as opposed to how Tailwind is described:\n\n[image \"img/villain-and-hero.png\" width=\"570\"]\n  caption: Words used on the keynote speech and Tailwind website\n\nOld best practices like \"semantic\", \"separation of concerns\", or \"clean\" are usually quoted, which is a common way to question the validity of the word.\n\nUnfair or not, this marketing scheme worked. Developers took the new terms and practices for granted and started tweeting and blogging about them. It was a gold mine for Tailwind's commercial business model.\n\n\n## Phase 2: Utility-first workflow { #phase2 }\n\nOnce they started cashing, Tailwind wanted to make sure the users were properly onboarded and locked in to the system. They introduced [\"utility-first workflow\"](//tailwindcss.com/docs/reusing-styles).\n\n> Tailwind encourages a utility-first workflow, where designs are implemented using only low-level utility classes. This is a powerful way to avoid premature abstraction and the pain points that come with it.\n\nHere's how the flow works:\n\n\n### Step 1: Onboarding\n\nIn the utility-first approach, the idea is to \"build everything out of utilities, and later extract repeating patterns as they emerge\". You are encouraged to try the system. Adam says:\n\n> [If you give it a chance, I really think you’ll wonder how you ever worked with CSS any other way.](//tailwindcss.com/)\n\nSounds good, so let's try it.\n\nOnce installed, you quickly start to see why people enjoy Tailwind. You can write your styling in the same place as your markup and never think about semantic class names. You feel productive with all the handy shortcuts together with hot-module replacement.\n\n\n### Step 2: \"Premature abstraction\"\n\nAt some point, hundreds of utilities later, the code you've written doesn't look pretty. You start wondering what comes next after the utility-first step. How to clean things up?\n\nTurns out there is no next step. Or it kind of exists, but it's called \"premature abstraction\". You can start extracting classes with @apply, but the documentation for [reusing styles](//tailwindcss.com/docs/reusing-styles) describes it as a bad practice.\n\n> [Whatever you do, don’t use @apply just to make things look “cleaner”](//tailwindcss.com/docs/reusing-styles#avoiding-premature-abstraction)\n\nBut what should I use @apply for if not for cleaning up? The documentation does not say. It only tells me why it should **not** be used.\n\n\n### Step 3: Vendor lock-in\n\nSo I keep coming back to the first step resulting in more and more utility classes. I'm locked inside a loop:\n\n[image \"img/utility-first-loop.png\"]\n  caption: Utility-first workflow\n\nI find this a rather clever way to lock people using Tailwind, resulting in more retention, loyalty, and money.\n\nThe only escape from the mess is a JavaScript component, but we're talking about a CSS framework here. I could hide any kind of inline styling mechanism behind React.\n\nWhich is our next topic.\n\n\n## Phase 3: Catalyst UI kit { #catalyst }\n\nIn December 2023, Tailwind introduced *Catalyst* with a richer set of language expressions and a React-based UI library.\n\n\n### Domain-specific language (DSL)\n\nTo keep up with the ever-evolving CSS standard Tailwind introduced another set of language literals. Over the years, Tailwind has grown from a simple set of atoms to a vendor-specific language with expressions, operators, and method calls.\n\nLet's look at the source code of the first button on [Catalyst demo page](//catalyst.tailwindui.com/):\n\n[image \"img/tailwind-button.png\" width=\"500\"]\n\n[.small]\n  The black button source code. The expressions are sorted alphabetically:\n\n```html.small\n<button class=\"\n  [&amp;>[data-slot=icon]]:-mx-0.5\n  [&amp;>[data-slot=icon]]:my-0.5\n  [&amp;>[data-slot=icon]]:shrink-0\n  [&amp;>[data-slot=icon]]:size-5\n  [&amp;>[data-slot=icon]]:sm:my-1\n  [&amp;>[data-slot=icon]]:sm:size-4\n  [&amp;>[data-slot=icon]]:text-[--btn-icon]\n  [--btn-bg:theme(colors.zinc.900)]\n  [--btn-border:theme(colors.zinc.950/90%)]\n  [--btn-hover-overlay:theme(colors.white/10%)]\n  [--btn-icon:theme(colors.zinc.400)]\n  after:-z-10\n  after:absolute\n  after:data-[active]:bg-[--btn-hover-overlay]\n  after:data-[disabled]:shadow-none\n  after:data-[hover]:bg-[--btn-hover-overlay]\n  after:inset-0\n  after:rounded-[calc(theme(borderRadius.lg)-1px)]\n  after:shadow-[shadow:inset_0_1px_theme(colors.white/15%)]\n  before:-z-10\n  before:absolute\n  before:bg-[--btn-bg]\n  before:data-[disabled]:shadow-none\n  before:inset-0\n  before:rounded-[calc(theme(borderRadius.lg)-1px)]\n  before:shadow\n  bg-[--btn-border]\n  border\n  border-transparent\n  dark:[--btn-bg:theme(colors.zinc.600)]\n  dark:[--btn-hover-overlay:theme(colors.white/5%)]\n  dark:after:-inset-px\n  dark:after:rounded-lg\n  dark:before:hidden\n  dark:bg-[--btn-bg]\n  dark:border-white/5\n  dark:text-white\n  data-[active]:[--btn-icon:theme(colors.zinc.300)]\n  data-[disabled]:opacity-50\n  data-[focus]:outline\n  data-[focus]:outline-2\n  data-[focus]:outline-blue-500\n  data-[focus]:outline-offset-2\n  data-[hover]:[--btn-icon:theme(colors.zinc.300)]\n  focus:outline-none\n  font-semibold\n  forced-colors:[--btn-icon:ButtonText]\n  forced-colors:data-[hover]:[--btn-icon:ButtonText]\n  gap-x-2\n  inline-flex\n  isolate\n  items-center\n  justify-center\n  px-[calc(theme(spacing[3.5])-1px)]\n  py-[calc(theme(spacing[2.5])-1px)]\n  relative\n  rounded-lg\n  sm:px-[calc(theme(spacing.3)-1px)]\n  sm:py-[calc(theme(spacing[1.5])-1px)]\n  sm:text-sm/6\n  text-base/6\n  text-white\"> Button </button>\n```\n\nI have many questions about this:\n\nMost importantly: How is this wall of text more maintainable than a class name like \"primary\"?\n\nDo I need another wall for the white button?\n\nAlso: Are there any limits to the utility-first workflow? When can I use @apply to clean things up? After 50 expressions? 100 expressions? 1000?\n\n\n### \"Modeled after HTML\"\n\nAnother major feature in Catalyst was a new markup language that separates all the language literals behind React components. Here's a dialog example using [Catalyst components](//catalyst.tailwindui.com/docs):\n\n```html numbered\n<Dialog>\n  <DialogTitle>Join mailing list</DialogTitle>\n  <DialogDescription>\n    Expect <Strong>no spamming</Strong>\n  </DialogDescription>\n\n  <DialogBody>\n    <Field>\n      <Label>Email</Label>\n      <Input name=\"email\" />\n    </Field>\n  </DialogBody>\n\n  <DialogActions>\n    <Button plain>Cancel</Button>\n    <Button>Join</Button>\n  </DialogActions>\n</Dialog>\n```\n\nThe markup feels surprisingly similar to semantic HTML:\n\n[image \"img/catalyst-markup.png\" width=\"570\"]\n  caption: Web standards vs vendor-specific markup\n\nThis raises more questions:\n\nMost importantly: How is `<button class=\"plain\">` different from `<Button plain>`? Isn't this \"semantic\" — the root of all bad in CSS?\n\nAnd standard HTML `<dialog>` is bad, but `<Dialog>` with uppercase is legit?\n\n\nWhy introduce so many different versions of the `<p>` tag?\n\n```html\n<!-- Catalyst <p> tags -->\n<Text>\n<Description>\n<DialogDescription>\n<AlertDescription>\n...\n```\n\nWhy is content-aware naming okay in element names but bad in class names?\n\nIs separation of concerns suddenly okay with Catalyst, but bad with vanilla HTML and CSS?\n\nI'm confused, to say the least.\n\n- - -\n\n\n## I love ❤️ CSS\n\nI started web development at the age of a `<blink>` tag and CSS has always been my favorite part of the web development stack. I'm particularly fascinated about the crossing between design and [front-of-the frontend](//bradfrost.com/blog/post/front-of-the-front-end-and-back-of-the-front-end-web-development/).\n\nWhen Microsoft released [Internet Explorer 4.0](//en.wikipedia.org/wiki/Internet_Explorer_4) with solid support for both external stylesheets and DHTML, It nailed me to the separation of concerns pattern. I see it as the most important component for software scalability, and it's particularly important with HTML and CSS. The way of organizing design has been around for centuries: There are element types and contexts. The nuanced relationship between [form and function](//en.wikipedia.org/wiki/Form_follows_function). CSS is the missing tool to bring foundational design-thinking to frontend development.\n\nFast-forward to this date, and the solid foundation has almost disappeared. Styling is inlined and CSS is written with JavaScript. There are no element types, nor contexts. Styling is flat and not cascading. Global is feared instead of used.\n\nWe're using maybe 30% of the full potential.\n\nI'm not a fan of any of that.\n\nI recommend everyone to take a closer look to what has happened to CSS there in the past 10 years. Regardless of your current stance. It's a powerful language that far surpasses the capabilities of Tailwind. Learn to build scalable architectures, and see how atomic class names and inline styling fit into the bigger picture.\n\n\n### First things first: Learn CSS\n\nThe first step is to learn CSS. It's the ultimate design language for the web. A safe bet for years to come.\n\n1. Start from the [Nicholas' post][nicolas] and learn the benefits of semantic naming. Understand how Adam cherry-picked one sentence and misused it to validate the contrasting practices of Tailwind.\n\n2. Study MDN documentation on web standards. There's a lot, so start with the most important aspects of CSS: [the cascade][cascade] and [specificity][specificity].\n\n3. Take inspiration. Learn how the best developers in the game like [Ryan Mulligan](//ryanmulligan.dev/blog/), [Ahmad Shadeed](//ishadeed.com/), and [Josh Comeau](//www.joshwcomeau.com/) use CSS in more stylish, and creative ways.\n\n[nicolas]: //nicolasgallagher.com/about-html-semantics-front-end-architecture/\n[cascade]: //developer.mozilla.org/en-US/docs/Web/CSS/Cascade\n[specificity]: //developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Nesting_and_specificity\n\n\n### Content first\n\nHere's a better workflow. It has many names: \"standards first\", \"content first\", or \"progressive enhancement\".\n\n[image \"img/standards-first.png\" width=\"650\"]\n  caption: Standards first model\n\n\nYou start with a pure, semantic layout and figure out all the reusable pieces of CSS. At times, especially when building new components, you might want to prototype quickly with inline styling. But that's okay and part of the system. You can clean things up later.\n\n> Clean code is easier to maintain\n\nThere are no \"pain points\" in clean code, only benefits. This is the system I want to teach to my kids. I want them to understand how web standards work, and where all the trends come from.\n\nBecause trends are temporary, but standards are forever.\n\n\n### Stay relevant\n\nMy guess: It's only a matter of time before Tailwind collapses. The vendor-specific language and the misleading communication cannot hold water very long. The utility soup produced today will eventually turn into a technical debt. The next generation looks back and asks: \"You actually wrote **that**?\"\n\nLearn to write clean HTML and CSS and stay relevant for years to come.\n\n[image \"img/tw-switch.png\" width=\"600\"]\n\n\n[.footnote]\n  Thanks to **Alan Hemmings, Janne Lehtinen, Anssi Piirainen, Anni Myllykangas, Courtney Couch, Lari Hotari, Joona Piirainen, Jukka Kujansivu, Lauri Heiskanen**, and **David Henzel** for reading drafts of this. And very special thanks to the friends (with no name) who use Tailwind daily. Your feedback was especially important.\n"
  },
  {
    "path": "packages/www/blog/tailwind-vs-semantic-css/index.md",
    "content": "---\ntitle: Tailwind vs Semantic CSS\ndesc: Comparing two identically designed websites, their weight, amount of HTML and CSS, rendering speed, and best practices.\nog: img/markup-big.png\ndate: 2023-10-23\n---\n\nThis study compares two websites with similar design: The commercial Spotlight template from developers of Tailwind vs the same site with semantic CSS:\n\n[.flex]\n  [image]\n    href: //spotlight.tailwindui.com/\n    small: img/tw-home.png\n    large: img/tw-home-big.png\n  ---\n  [image]\n    href: /@spotlight/\n    large: img/nue-home-big.png\n    small: img/nue-home.png\n\n\n## Front page HTML\n\nThe main difference: Tailwind uses \"utility\" classes and the semantic version uses external stylesheets. That is: Tailwind styles elements inline, directly on the markup and the semantic version respects the [separation of concerns](//en.wikipedia.org/wiki/Separation_of_concerns) principle.\n\nYou can see the difference by drilling down to the first A-element:\n\n[image]\n  small: img/markup.png\n  large: img/markup-big.png\n  caption: Drilling down to the first element on the main navigation\n\n\nTailwind needs significantly more coding because you are completely lacking the power of CSS: The way it cascades and the richness of the selectors. You are forced to wrap divs inside divs inside divs and fill the elements with Tailwind-specific class syntax.\n\nHere is the full HTML source code of the front page.\n\n[image]\n  small: img/html.jpg\n  large: img/html-big.jpg\n  caption: Full HTML coding on the front page\n\n\nTailwind (and Next.js) generate 75K of unminified HTML, while the semantic version is only 8K. While some parts come from Next, it's pretty clear that Tailwind requires significantly more HTML to render the same design, than the semantic version.\n\nWith Tailwind the [Text to HTML Ratio][tw-ratio] is only 2.3%, which is \"Very low\" according to SiteGuru. [Nue ratio][nue-ratio], however, is 20.3% which is \"Good\".\n\n[tw-ratio]: //www.siteguru.co/free-seo-tools/text-to-html-ratio?url=spotlight.tailwindui.com\n\n[nue-ratio]: //www.siteguru.co/free-seo-tools/text-to-html-ratio?url=nuejs.org/@spotlight/\n\n\n## Front page CSS\n\nLet's study the difference in CSS coding:\n\n[image]\n  small: img/css.jpg\n  large: img/css-big.jpg\n  caption: Full CSS coding on the front page\n\n\nBlue is semantic CSS, gray is utility classes, and black-bordered is primary CSS (which makes your pages render faster).\n\nSome key takes:\n\n1. Tailwind CSS is seven times larger: 33K vs 4.6K. Overall you need eight times more HTML/CSS code with Tailwind to render the page (108K vs 12.6K). While the design is not identical, but it's easy to see the ballpark figure in there. Tailwind-generated sites are multiple times larger.\n\n2. Most of the semantic CSS is reusable on other pages and only a fraction of the CSS is specific to the front page. It's easy to create new pages when the groundwork is already done.\n\n3. \"Spotlight\" is just a *theme* extending a base design. There is an extremely minimalistic [base-version](/@base/) of the website that can be used to create new themes, like our Spotlight theme.\n\n[image]\n  small: img/extending.png\n  large: img/extending-big.png\n  caption: Creating a new design by extending a semantic base design\n\n\nTheming is a powerful concept in CSS. You can alter your design by swapping parts of your CSS with another one or overriding a [base version](/@base/). Theming is impossible with Tailwind because the design is tightly coupled to the markup. If you want a new design, you must edit your markup and override your earlier work.\n\n\n## Rendering speed\n\nThe two metrics that measure page rendering speed are [first contentful paint](//web.dev/articles/fcp) (FCP) and [largest contentful paint](//web.dev/articles/lcp) (LCP). The semantic version is faster than in both metrics and in both mobile and PC. Here's LCP on mobile for example:\n\n[image]\n  small: img/lcp-mobile.png\n  large: img/lcp-mobile-big.png\n  caption: Largest Contentful Paint (LCP) rendering speed on mobile\n\n\nPlease compare [Tailwind metrics](//pagespeed.web.dev/analysis/https-spotlight-tailwindui-com/cqtnf4xxoy?form_factor=mobile) with [Semantic CSS metrics](//pagespeed.web.dev/analysis/https-nuejs-org-spotlight/6nnhwwnz8b?form_factor=mobile).\n\nTwo reasons why the semantic version is faster:\n\n1. The primary CSS is [inlined](//imkev.dev/loading-css) on the HTML page so that all the assets for the first viewport are fetched in the initial request. This is probably the most important performance optimization for the perceived page-loading experience.\n\n2. The first request is [less than 14K][14k], which is the maximum size of the first TCP packet.\n\n[14k]: //endtimes.dev/why-your-website-should-be-under-14kb-in-size/\n\n\n## Separation of concerns\n\nTailwind embraces **tight coupling**. That is: The structure and styling are tied together. The semantic approach is the opposite: The structure and styling are **loosely coupled**. Here's what that means:\n\n[image]\n  small: img/coupling.png\n  large: img/coupling-big.png\n  caption: Tight coupling vs Loose coupling\n\n\nThe semantic version, allows you to change the design of the gallery freely. You name the component and style it externally. With Tailwind the style cannot be separated from the structure.\n\nHere's a better example. Let's look at the \"Uses\" or \"Setup\" page on both implementations:\n\n[.flex]\n  [image]\n    href: //spotlight.tailwindui.com/uses\n    small: img/tw-uses.png\n    large: img/tw-uses-big.png\n    caption: Tailwind UI version →\n  ---\n  [image]\n    href: /@spotlight/setup/\n    large: img/nue-uses-big.png\n    small: img/nue-uses.png\n    caption: Semantic version →\n\nWith Tailwind, you must create a JavaScript component to construct a suitable HTML structure for the design. With the semantic version, we can use Markdown in place of the custom JSX component because the generated HTML is semantic and can be styled externally with CSS selectors:\n\n[image]\n  small: img/content-first.png\n  large: img/content-first-big.png\n  caption: Tight vs loose coupling from a different angle\n  width: 825 x 641\n\nLoose coupling makes you think **content first**. There is no need to write a component for every situation because you can use external CSS to do the heavy lifting.\n\n&nbsp;\n\n\n## But …\n\n\n### But naming things is unnecessary\n\nNaming things is a skill. You name things that repeat. Think of function names in JavaScript or component names in Figma. The same goes for CSS class names. Be good at naming, and you can move from repeating things to reusing things. That is: You can move from this:\n\n```html\n<!-- utility-first css -->\n<button className=\"group mb-8 flex h-10 w-10\n  items-center justify-center rounded-full\n  bg-white shadow-md shadow-zinc-800/5 ring-1\n  ring-zinc-900/5 transition dark:border\n  dark:border-zinc-700/50 dark:bg-zinc-800\n  dark:ring-0 dark:ring-white/10\n  dark:hover:border-zinc-700\n  dark:hover:ring-white/20\n  lg:absolute lg:-left-5\n  lg:mb-0 lg:-mt-2\n  xl:-top-1.5\n  xl:left-0\n  xl:mt-0\">\n```\n\nTo this\n\n```html\n<!-- semantic css -->\n<button class=\"secondary\">\n```\n\nWithout turning into components.\n\n\n### But co-location is important?\n\nCo-location is a catchy name for tight coupling. A term to promote the idea that styling should be tied to the presentation. Repeating things vs. reusing things. See above.\n\n\n### But Tailwind is a great design system\n\nTailwind has great defaults for colors, spacing, and responsive design. That part is roughly 3% of your Tailwind CSS file. It's easy to copy these defaults to your semantic design system if needed.\n\n\n### But I move faster with Tailwind\n\nYes. You can move faster with Tailwind. But only when:\n\n1. You are comparing Tailwind with your earlier, bad experiences with CSS or you are new to CSS development.\n\n2. You don't care about building reusable CSS for later use. That is: You are not naming things that repeat.\n\nIf you really want to move faster, you'll create a set of CSS components that you can reuse. Like `<button class=\"secondary\">`.\n\n\n### But why is Tailwind so popular then?\n\nBecause mastering CSS requires practice. It takes several failed attempts before you get it. Most developers haven't gone through that, so they only remember the bad things.\n\nThe fact is that Tailwind's popularity will eventually fade. CSS-in-JS is trending now, but standards are forever. At some point, we'll all experience a \"WTF moment\" when looking at the tightly coupled Tailwind code.\n"
  },
  {
    "path": "packages/www/blog/ui/blog.css",
    "content": "\n@layer layout {\n  .list {\n    margin-top: 2em;\n\n    strong { flex: 3 }\n\n    a {\n      text-decoration: none;\n      padding-block: .125em;\n    }\n  }\n\n  header > time {\n    font-size: .875em;\n    margin-bottom: 1em;\n    display: inline-block;\n  }\n\n  .author {\n    font-size: .875em;\n    margin-top: 1em;\n    gap: 1em;\n    align-items; center;\n\n    img {\n      border-radius: var(--radius-m);\n      margin-bottom: -.25em;\n    }\n\n    h3 { margin: 0 }\n  }\n}"
  },
  {
    "path": "packages/www/blog/ui/blog.html",
    "content": "\n<ul :is=\"blog-entries\" class=\"list\">\n  <li :each=\"el in blog\">\n    <a href=\"{ el.url }\" class=\"flex\">\n      <pretty-date :date=\"el.date\"/>\n      <strong>{{ markdown(el.title) }}</strong>\n    </a>\n  </li>\n</ul>\n\n<!-- the hero area for each blog entry -->\n<header :is=\"pagehead\">\n  <pretty-date :date/>\n\n  <h1>{{ markdown(hero_title || title) }}</h1>\n\n  <author :bind=\"authors[author || authors.default]\"/>\n\n</header>\n\n\n<author class=\"author row\">\n  <span><img src=\"{ img }\" width=\"36\" height=\"36\"></span>\n\n  <aside>\n    <h3>{ name }</h3>\n    <a href=\"//x.com/{ username }\">@{ username }</a>\n  </aside>\n</author>\n\n<time :is=\"pretty-date\">\n  { pretty }\n\n  <script>\n    const DATE_FORMAT = new Intl.DateTimeFormat('en-US', {\n      month: 'short', day: 'numeric', year: 'numeric'\n    })\n    let date = this.date || new Date()\n    if (typeof date == 'string') date = new Date(date)\n    this.pretty = DATE_FORMAT.format(date)\n  </script>\n</time>"
  },
  {
    "path": "packages/www/docs/app.yaml",
    "content": "\nbeside: false\ninclude: [ syntax ]\n\ncontent:\n  heading_ids: true"
  },
  {
    "path": "packages/www/docs/build-system.md",
    "content": "\n# Build system\nNue's build system is designed around file-based separation of concerns. Each file type has a specific role - content, design, structure, or logic - which allows for granular processing and optimization.\n\n\n## How it works\n\n**Direct serving** - Nue serves files directly from your source folder. Request your SPA `/app/` and Nue processes `app/index.html` in real-time. No build step, no intermediate artifacts, no .dist directory to keep in sync.\n\n**Universal HMR** - All asset types update instantly. Content, layouts, CSS, JavaScript, data, configuration — even server routes. Every change is a small diff operation reaching your browser in 10-20ms.\n\n**Production builds** - Optimize what matters: the initial HTTP request. Make it as fast as possible by loading everything needed to render at once. No amount of JavaScript optimization can beat this approach.\n\n\n## Development server\nNue processes files on request during development. The server can start instantly because there is no build step. When you request `/blog/my-post/` in the browser:\n\n1. Nue finds `blog/my-post.md`\n2. Processes the Markdown content\n3. Combines it with layout modules\n4. Applies CSS and JavaScript assets\n5. Serves the complete HTML\n\nThis happens fast enough to feel instant because each processing step is lightweight.\n\n### Hot module replacement\nHMR works through WebSocket connections. The server watches file changes and sends targeted updates based on file type:\n\n\n**Markdown content** - DOM diffing updates only the changed content. Your scroll position, form inputs, and component state all persist.\n\n**HTML components and layouts** - Smart reload that preserves application state while updating the affected components.\n\n**SVG graphics** - Visual updates without page refresh for dynamic graphics and icons.\n\n**CSS stylesheets** - Style injection and cleanup. New styles apply instantly, removed styles are cleaned up automatically.\n\n**YAML configurations, JavaScript, and TypeScript changes** - resort to full page reload because these affect application state and logic.\n\nEach file type gets precisely the update strategy it needs. No unnecessary full page reloads, no lost application state.\n\n\n\n\n## Production builds\n\n```bash\nnue build\n```\n\n**HTML generation** - Markdown content processed through templates with all layout modules applied.\n\n**CSS processing** - Stylesheets minified and optionally inlined into pages.\n\n**JavaScript handling** - TypeScript converted to JavaScript. Reactive components transpiled to minimal client-side code.\n\n**Asset optimization** - Images, fonts, and other assets copied with optimizations applied.\n\n\n### Build performance\n\nSmall to medium sites consistently build under 100ms because:\n\n**Simple file processing** - Each file type has straightforward processing rules. No complex dependency graphs.\n\n**Tiny toolchain** - The 1MB Nue executable handles everything without spawning multiple processes.\n\n\n### Optimization strategies\n\n**CSS inlining** - Design systems built with constraints stay small enough to inline completely:\n\n```yaml\ndesign:\n  inline_css: true\n```\n\nThis eliminates the CSS network request entirely. The initial HTML download contains everything needed to render correctly.\n\n**View transitions** - Built-in client-side navigation that feels like a single-page app:\n\n```yaml\nsite:\n  view_transitions: true\n```\n\nPages load instantly after the first visit because CSS and JavaScript are cached.\n\n**Precise loading** - JavaScript modules load exactly when needed. The Nue.js client runtime is 2.5KB gzipped.\n\n**HTTP/2 friendly** - Multiple small, focused files work better with HTTP/2 than single large bundles.\n"
  },
  {
    "path": "packages/www/docs/cli.md",
    "content": "\n# Command line interface\nNuekit provides a simple command line interface for developing and building websites. The CLI follows UNIX conventions with short flags, long options, and predictable behavior.\n\n## Installation\n\n```bash\nbun install --global nuekit\n```\n\nThe `nue` command becomes available globally after installation.\n\n## Basic usage\n\n```bash\nnue [command] [options] [file_matches]\n```\n\nIf no command is specified, `dev` is used by default:\n\n```bash\nnue                    # same as \"nue dev\"\nnue --port 8080        # serve on port 8080 (default 4000)\nnue build              # build production site\nnue preview            # preview built site\n```\n\n## Commands\n\n### dev (default)\nStart the development server with hot reloading:\n\n```bash\nnue dev\nnue serve              # alias to dev\nnue                    # short alias\nnue --port 8080        # serve on specific port\n```\n\nThe development server starts at http://localhost:4000 (by default)\n\n\n### build\nBuild the production site:\n\n```bash\nnue build              # build entire site\nnue build --clean      # clean output directory first\nnue build --dry-run    # show what would be built\nnue build --init       # build Nue runtime files (rare need)\n```\n\nBuilt files go to the `.dist` directory by default.\n\n### preview\n\nPreview the production build locally:\n\n```bash\nnue preview              # preview on default port (4000)\nnue preview --port 8080  # preview on specific port\n```\n\nThis serves the built files from `.dist` directory.\n\n### create\n\nCreate a new project from a starter template:\n\n```bash\n# minimal, blog, spa, or full\nnue create blog\n```\n\nThis extracts a downloaded template to a new directory. Issue `nue` from the directory and the newly created site is served at http://localhost:4000\n\n\n## Global options\n\n### -p, --port\n\nSet the port for serve or preview:\n\n```bash\nnue --port 3000        # serve on port 3000\nnue preview --port 8080 # preview on port 8080\n```\n\n### -h, --help\n\nShow help information:\n\n```bash\nnue --help\nnue -h\n```\n\n### -v, --version\n\nShow version information:\n\n```bash\nnue --version\nnue -v\n```\n\nDisplays both Nue and Bun versions.\n\n### -s, --silent\n\nSuppress output messages:\n\n```bash\nnue build --silent     # build without progress messages\n```\n\n### --verbose\n\nShow detailed output:\n\n```bash\nnue build --verbose    # detailed build information\n```\n\n## Build options\n\n### -n, --dry-run\n\nShow what would be built without actually building:\n\n```bash\nnue build --dry-run    # preview build process\n```\n\nUseful for testing build configuration.\n\n### -i, --init\n\nBuild Nue runtime files to `.dist/@nue` directory:\n\n```bash\nnue build --init\n```\n\nThis creates the minified JavaScript files needed for client-side functionality. This is done automatically on the first build, or if the Nue version changes. This is mostly for the developers of Nue so you rarely need to do this.\n\n### --clean\n\nClean the output directory before building:\n\n```bash\nnue build --clean      # remove .dist then build\n```\n\n## File matching\n\nTarget specific file types by adding patterns:\n\n```bash\nnue build .md          # build only Markdown files\nnue build .css .js     # build CSS and JavaScript files\nnue .html              # serve only HTML files\n```\n\nPatterns match file extensions. Multiple patterns are combined with OR logic.\n\n### Examples\n\n```bash\n# Build only content files\nnue build .md .yaml\n\n# Serve only stylesheets\nnue .css\n\n# Build everything except specific types\nnue build --verbose\n\n# Preview with custom port\nnue preview --port 5000\n```\n\n## Configuration\n\nThe CLI reads configuration from `site.yaml`. See [Configuration reference](/docs/configuration) for all available options.\n\n## Troubleshooting\n\n### Command not found\n\nIf `nue` command is not found after installation:\n\n```bash\n# Check if Bun is in PATH\nwhich bun\n\n# Install globally\nbun install --global nuekit\n```\n\n### Permission denied\nOn some systems you may need to make the binary executable:\n\n```bash\nchmod +x node_modules/.bin/nue\n```\n\n### Port already in use\nIf the default port is taken, specify a different one:\n\n```bash\nnue --port 3001\n```\n\n"
  },
  {
    "path": "packages/www/docs/configuration.md",
    "content": "\n# Configuration\nNue uses a three-tier configuration system. Site-wide settings in `site.yaml` provide defaults, app-level `app.yaml` files customize sections, and page front matter gives final control. The settings cascade: deeper level settings override parent settings.\n\n\n## Site-wide settings\nGlobal configuration in `site.yaml` that affects the entire site:\n\n```yaml\n# Development server port (default: 4000)\n# Override in command line: nue --port 9090\nport: 8080\n\n# Global site behavior (site.yaml only)\nsite:\n\n  # Origin URL for sitemap.xml and RSS feed\n  origin: https://example.com\n\n  # Enable view transitions between pages (default = false)\n  view_transitions: true\n\n  # Skip files/directories from processing\n  # Appends to:\n  #  node_modules package.json lock.yaml README.md Makefile .toml .rs .lock .lockb\n  skip: [test/, @plans/]\n\n\n# Design system settings (site.yaml only)\ndesign:\n\n  # Configure CSS @layer cascade order (added to head with style tag)\n  layers: [ settings, elements, components ]\n\n  # Limit class names per element to prevent utility abuse. Default 3.\n  max_class_names: 3\n\n  # Inline all CSS in production builds\n  inline_css: true\n\n\n# Server infrastructure (site.yaml only)\nserver:\n  # Server code directory (default: @shared/server)\n  dir: @shared/server\n\n  # Auto-reload server on changes during development\n  reload: true\n\n\n# Alternatively: use a reverse proxy\nserver:\n\n  # Point to your existing backend server\n  url: http://localhost:5000\n\n  # Which routes get forwarded to that server\n  routes: [/api/, /private/]\n\n\n# Site-wide collections (app.yaml can extend)\ncollections:\n\n  # Collection name becomes variable for .html templates\n  blog:\n    # .md files to include (substring match)\n    include: [posts/]\n\n    # Required front matter fields\n    require: [date]\n\n    # Required tags property to include \"design\"\n    tags: [ design ]\n\n    # Exclude if these fields exist\n    skip: [draft]\n\n    # Sort by front matter field and direction\n    sort: date desc\n\n  team:\n    include: [team/]\n    require: [ role, email ]\n    sort: name asc\n\n\n# Sitemap generation (site.yaml only)\nsitemap:\n  # Generate sitemap.xml for search engines (default: false)\n  enabled: true\n\n  # Skip pages with these front matter fields\n  skip: [draft, private]\n\n\n# RSS feed generation (site.yaml only)\nrss:\n  # Generate /feed.xml with auto-discovery link tag (default: false)\n  enabled: true\n\n  # Use this collection for feed content\n  collection: blog\n\n  # Feed metadata displayed in RSS readers\n  title: Acme developer blog\n  description: Latest news on web technologies\n\n\n# Client-side import-map. (app.yaml can override)\nimport_map:\n  app: /@shared/app/index.js\n  d3: /lib/d3.js\n\n\n# Content processing defaults. (app.yaml can override)\ncontent:\n  # Add IDs to headings for linking (default: false)\n  heading_ids: true\n\n  # Auto-wrap content in sections (default: false)\n  sections: true\n\n  # Assign class names to auto-generated sections\n  sections: [hero, features, testimonials]\n\n  # Wrap section content with inner div for layout control (default: null)\n  section_wrapper: wrap\n\n\n# Default metadata for all pages. (app.yaml and front matter can override)\nmeta:\n  # Default page title\n  title: The UNIX of the web\n\n  # Title template for non-home pages (%s replaced with title)\n  title_template: \"%s / Acme Inc (DEV)\"\n\n  # Default meta description\n  description: Standards-first web framework\n\n  # Favicon path\n  favicon: /img/logo.svg\n\n  # Open Graph image for social media previews\n  og_image: /img/social.png\n\n  # Viewport meta tag\n  viewport: width=device-width,initial-scale=1\n\n  # HTML lang attribute\n  language: en-US\n\n  # HTML dir attribute\n  direction: <empty>\n\n  # <body> class name attribute (for app/page specific styling)\n  class: <empty>\n\n  # Default publish date. Usually set in front matter only\n  pubDate: null\n\n  # Theme color for mobile browsers\n  theme_color: \"#0066cc\"\n\n  # Default author\n  author: Jane Doe\n\n  # Search engine directives\n  robots: index, follow\n\n# Global link definitions for Nuemark pages\nlinks:\n  # Example: See [dev branch][dev] on Github\n  dev: //github.com/nuejs/nue/tree/dev/packages\n  css_vars: //developer.mozilla.org/en-US/docs/Web/CSS/var\n\n\n# Production overrides for metadata\nproduction:\n\n  # metadata override\n  title_template: \"%s / Acme Inc\"\n\n  # any value here overrides development data\n  analytics_id: GA-PROD-789012\n```\n\nSee [template data](template-data) for details.\n\n\n### Aliases\nNue recognizes common metadata aliases:\n\n- `desc` → `description`\n- `og` → `og_image`\n- `date` → `pubDate`\n\n\n### App-level overrides (`app.yaml`)\nDirectory-specific settings using nested namespaces. Can override site defaults or extend collections:\n\n```yaml\n# blog/app.yaml (for example)\n\n# Metadata overrides\nmeta:\n  # Override site title for the blog\n  title: Blog Title\n\n  # Override site author\n  author: Blog Author\n\n# Content processing overrides\ncontent:\n  sections: false\n\n\n# Additional collections to site collections\ncollections:\n\n  # Adds to existing site collections\n  featured:\n    include: [featured/]\n    skip: [ todo ]\n    sort: date desc\n```\n\n\n### Page-level overrides (.md file front matter)\nIndividual page settings using flat properties. Highest priority, overrides both site and app settings:\n\n```yaml\n---\n# Metadata using flat syntax (overrides site.meta and app.meta)\n\n# Page-specific title\ntitle: Page Title\n\n# Page-specific description\ndescription: Page description\n\n# Page-specific social image\nog_image: /img/page-specific.png\n\n# Content settings using flat syntax (overrides site.content and app.content)\nsections: [hero, features]\n---\n```\n\n\n\n### SVG processing (`app.yaml`)\nEnable [SVG development](/docs/svg-development) in specific directories. This is an application-only setting in `app.yaml`:\n\n```\n# visuals/app.yaml (for example)\nsvg:\n  # Process .svg files as Nue templates\n  process: true\n\n  # Embed fonts directly in SVG output\n  fonts:\n    Inter: @shared/design/inter.woff2\n```\n\n"
  },
  {
    "path": "packages/www/docs/contributing.md",
    "content": "# Contributing\nI'm **Tero Piirainen**, Nue's creator. Here's how I work and the best ways to contribute.\n\n## Work happens in bursts\nI work in bursts. This means gaps in community responses, delayed pull request reviews, and periods of radio silence. Progress might look slow from the outside, but I'm working constantly.\n\n### Major steps in the roadmap\nUp next are multi-site development, deployments, multi-site templates and design systems. The wild pivots of early development are behind us.\n\n### Vision matters\nI have strong opinions about Nue's direction, especially around separation of concerns. I'm protective of the principles that make Nue different from other frameworks.\n\n\n## Best ways to help\n\n### Bug reports\nTest Nue in your projects and [report issues](//github.com/nuejs/nue/issues). Clear reproduction steps help the most.\n\n### Coding\nFor code contributions, reach out on [Slack](//join.slack.com/t/nuejs/shared_invite/zt-2wf8ozu5i-N2Y9PA_D17weIWuN2QPOqQ) first. **Let's talk about your idea before writing code**. This saves time and ensures your contribution aligns with the project's direction.\n\nSmall, focused pull requests have the best chance of getting merged. Large architectural changes require discussion first.\n\n\n## Development setup\nRun Nue directly from source for testing changes\n\n```\ngit clone https://github.com/nuejs/nue\ncd nue\nbun install\n\n# Test with a template using local nuekit\ncd packages/templates/full\n../../nuekit/src/cli.js\n\n→ Nue 2.0-beta • Bun 1.2.22\n→ Serving on http://localhost:4000/\n```"
  },
  {
    "path": "packages/www/docs/css-development.md",
    "content": "\n# CSS development\nNue represents a shift from component-scoped styling to [design systems](/docs/design-systems). CSS becomes a visual language that works across your entire site.\n\n\n## Small projects\nStart with global styles plus area-specific CSS. This follows the classic web development pattern that pre-dated the component revolution: global stylesheets with area-specific additions. It's perfect for personal projects, prototypes, or small teams where you need the flexibility to add styles ad-hoc without teaching a formal system to others:\n\n\n```\n├── global.css       # Site-wide design system\n├── index.md\n└── blog/\n    ├── blog.css     # Blog-specific styles\n    ├── css-is-awesome.md\n    ├── design-systems.md\n    └── ...\n```\n\nFiles are loaded automatically based on location. The `global.css` applies everywhere. The `blog.css` only applies to pages in the blog directory. No imports, no bundling. Just files where you need them.\n\n\n## Larger projects\nScale up with a focused design system for larger teams, client work, or any project where consistency and maintainability matter more than development speed. This approach enforces constraints that prevent the CSS sprawl that kills long-term projects:\n\n\n```bash\nnue create full\n```\n\nThis creates a complete design system in `@shared/design/`:\n\n```\n@shared/design/\n├── base.css         # Typography, colors, spacing\n├── button.css       # All button variants\n├── content.css      # Blog posts, documentation\n├── dialog.css       # Modals, popovers\n├── document.css     # Page structure\n├── form.css         # All form elements\n├── layout.css       # Grid, stack, columns\n├── syntax.css       # Code highlighting\n├── table.css        # Data tables\n└── apps.css         # SPA-specific components\n```\n\nAll files load automatically across your entire site. Marketing pages, documentation, blogs, login screens, and single-page apps all use the same visual language. Change a variable in `base.css` and see it everywhere.\n\n\n### Configuration\nControl the design system through `site.yaml`:\n\n```yaml\ndesign:\n  base: base.css       # Load first for layer ordering\n\n  # Limit class names per element to prevent utility abuse\n  max_class_names: 3\n\n  # inline all css to pages in production build (performance optimization)\n  inline_css: true\n```\n\n### App-specific styling\nOverride globally through app configuration. In `app/app.yaml`:\n\n```yaml\nexclude: [content.css]   # Skip content-specific styles\ninclude: [apps.css]      # Add SPA-specific styles\n```\n\nExclusions use fuzzy matching. \"apps/\" would exclude both \"apps/canvas.css\" and \"apps/ui.css\". This lets you fine-tune which parts of the design system apply to different areas.\n\n\n## CSS best practices\nThe full template follows these principles for maintainable design systems:\n\n\n### Trust HTML semantics\nHTML already provides most of what a design system needs. Lists have `<ul>`, `<ol>`, `<dl>`. Tables have semantic structure. Forms have fieldsets and labels. Navigation has `<nav>`. Interactive elements have `<button>`, `<details>`, `<dialog>`.\n\nStyle these native elements directly:\n\n```css\n/* Not this: component classes */\n.list-component { }\n.nav-component { }\n.button-component { }\n\n/* This: semantic elements */\nul { }\nnav { }\nbutton { }\n```\n\nUse attribute selectors (`[disabled]`, `[aria-expanded]`), pseudo-classes (`:invalid`, `:checked`), and `:has()` for state-based styling. The browser already knows what's interactive and what's not.\n\n\n### Class names for layout\nHTML can't express spatial relationships. These aren't semantic, so class names handle layout:\n\n```css\n.stack { }      /* Vertical spacing */\n.grid { }       /* Responsive grid */\n.columns { }    /* Text columns */\n```\n\nAdd minimal modifiers for variations:\n\n```css\n.thin { }       /* Narrower block */\n.wide { }       /* Wider block */\n.compact { }    /* Tighter spacing */\n```\n\nModern nested CSS eliminates the need for inner class names. A constrained system enables creative combinations without chaos.\n\n### Layer everything\n\nCSS layers solve specificity wars forever:\n\n```css\n@layer base, layout, components, utilities;\n\n@layer base {\n  /* variables, semantic elements */\n}\n\n@layer component {\n  /* component class names (.stack) */\n}\n\n@layer modifier {\n  /* modifier classes (.thin) */\n}\n```\n\nEach layer has clear boundaries and purpose. No more specificity hacks, no more source order gymnastics. The cascade becomes predictable.\n\n\n### Keep it minimal\nEven complex design systems need surprisingly few classes. Maybe 10-30 total. Not 50. Definitely not 500. A design system fails when developers escape to local styling.\n\nThe best way to ensure adoption is constraint. Learning 10 classes is manageable. Learning 100 is not. Minimal systems force creative solutions within boundaries - exactly what good design requires.\n\n\n## Brutalist foundation\nThe full template uses raw, \"brutalist\" design principles. It's the thinnest possible layer on top of browser defaults for meaningful graphics. This foundation works for two reasons:\n\n**It's a starting point, not an endpoint** - Build your brand on top of solid fundamentals rather than fighting against opinionated defaults.\n\n**It demonstrates the principles** - Shows how semantic HTML plus minimal CSS creates functional, accessible interfaces without complexity.\n\nThis foundation will expand into more expressive templates. See the [roadmap](/docs/roadmap) for the upcoming design systems for more sophisiticated Miesian or Ramsian feel.\n\n"
  },
  {
    "path": "packages/www/docs/design-engineering.md",
    "content": "\n# Design engineering\nWeb development split into two camps: those who design and those who code. This division is artificial. The web is a design medium that happens to be programmable.\n\n## Two mindsets, one medium\nDesign mindset sees patterns, rhythm, and hierarchy. It asks: How does this feel? What draws the eye? Where does attention flow?\n\nEngineering mindset sees data structures, state management, and abstractions. It asks: How does this scale? What are the edge cases? How do we test this?\n\nBoth are essential. Neither is sufficient alone. The web demands both mindsets because interfaces are both visual and functional. The mistake is believing one matters more than the other.\n\n## The artificial divide\nModern frameworks optimize for engineering mindset. Everything becomes a programming problem. CSS becomes CSS-in-JS. Design becomes component props. Layout becomes flexbox utilities. We've turned visual decisions into code decisions.\n\nDesigners must learn React to do their job. They write JavaScript to change a color. They debug webpack to update spacing. The tool shapes the thinking, and the thinking becomes programmatic.\n\nThis isn't about roles or job titles. One person can embody both mindsets. Teams can specialize. The key is that the technology supports both ways of thinking equally. Design decisions happen in design tools. Programming decisions happen in programming tools. Neither compromises for the other.\n\n## When tools enable both mindsets\nTrue design engineering happens when both mindsets have equal tools and equal power. CSS for design decisions. HTML for structure. JavaScript for business logic. Each layer owns its domain completely.\n\n**For designers**, this means direct control. Change a variable, see it everywhere. No asking developers to update components. No waiting for builds. No translation layer between design intent and implementation. The feedback loop becomes immediate.\n\n**For developers**, this means assembly without thinking about presentation. Grab a `<nav>`, it's already styled. Add a `<button>`, it already works. Wrap content in `.stack`, spacing is handled. No memorizing utilities, no fighting specificity, no debugging why styles don't apply. The design system becomes invisible infrastructure that just works.\n\nWhen design is truly decoupled from structure, both disciplines work at full speed. Designers iterate on visual language and developers build features without breaking design. Neither blocks the other.\n\n"
  },
  {
    "path": "packages/www/docs/design-systems.md",
    "content": "\n# Design systems\nWeb design used to be its own discipline. Designers controlled visual language through CSS, creating cohesive experiences across entire sites. Today, design has been absorbed into the JavaScript ecosystem - scattered across components, utility classes, and framework abstractions. Nue enables you to build and maintain a real system with modern CSS.\n\n\n## The problem we created\nFor 15 years we've been writing CSS to survive in chaos. We use BEM because we gave up on the cascade. We use CSS-in-JS because we're scared of namespace collisions. We write utility classes because we stopped trusting our ability to name things. We compile, process, and transform CSS because we think native CSS isn't enough.\n\nThese practices assume CSS is broken and needs fixing. They're defensive strategies. They're about working around the language rather than harnessing its potential.\n\n## A modern CSS design system\nA design system isn't a component library or a utility framework. It's a visual language expressed through CSS. Modern CSS gives us everything we need: variables, nesting, container queries, layers, and scope. Here's what makes a real design system:\n\n### Cascading\nA design system is built in layers that inherit and can be swapped. Start with the raw basis - typography, colors, spacing. Add a design philosophy layer - Miesian minimalism focusing on content first, or Ramsian principles putting human needs first. Then add expression - brand-specific styling and personality.\n\nVisual CSS, layout CSS, and components should be separate concerns. This separation enables inheritance and swapping. Change the philosophy layer and watch it cascade through every component. Swap the expression layer for a complete rebrand without touching structure.\n\n### Semantic\nHTML already contains a rich design vocabulary. Lists, tables, forms, buttons, navigation - they all have meaning. A design system extends this vocabulary rather than replacing it. A `<button>` is already interactive. A `<nav>` already means navigation. The system styles what exists instead of recreating it with divs and classes.\n\n### Minimal\nConstraints create consistency. A design system with 20 well-chosen classes beats one with 2000 utilities. HTML can't express spatial relationships, so classes handle layout: `.stack`, `.grid`, `.columns`. A few modifiers capture variations: `.primary`, `.compact`, `.inverted`. Everything else? Already in HTML.\n\n\n## Why these principles matter\nThese three principles create a system that's invisible to those using it - the ultimate user-friendliness.\n\n### For designers\nA cascading system means direct control. Change a variable, see it everywhere. No asking developers to update components. No waiting for builds. No translation layer between design intent and implementation. The feedback loop becomes immediate. CSS speaks their language - visual properties, not programming abstractions.\n\n\n### For developers\nSemantic HTML and minimal classes mean assembly without thinking about presentation. Grab a `<nav>`, it's already styled. Add a `<button>`, it already works. Wrap content in `.stack`, spacing is handled. No memorizing utilities, no fighting specificity, no debugging why styles don't apply. The design system becomes invisible infrastructure that just works.\n\nWhen design is truly decoupled from structure, both disciplines work at full speed. Designers iterate on visual language without breaking functionality. Developers build features without breaking design. Neither blocks the other. Neither compromises for the other.\n\n\n## Long-term value\nA design system grows more valuable over time. Each project adds to the system. Each use case improves the patterns. Each team member builds on shared foundations.\n\nThis approach scales with [multi-site development](/docs/roadmap). Managing design systems across multiple brands, products, and domains becomes manageable. Shared inheritance scales from one site to entire ecosystems.\n\nCSS persists through paradigm shifts. Your React components from 2016 need updates. Your CSS from 2016 still works.\n"
  },
  {
    "path": "packages/www/docs/examples/nue-counter.html",
    "content": "<!doctype html>\n\n<head>\n  <title>Nue In-browser compilation</title>\n  <script type=\"module\" src=\"//esm.sh/nuedom\"></script>\n</head>\n\n<template>\n  <button :onclick=\"count++\">\n    Count: <b>{ count }</b>\n\n    <script>\n      this.count = 0\n    </script>\n  </button>\n</template>\n\n"
  },
  {
    "path": "packages/www/docs/getting-started.md",
    "content": "\n# Getting started\nInstall, create, develop:\n\n```bash\n# Install Bun 1.2+ (if you don't have it yet)\ncurl -fsSL https://bun.sh/install | bash\n\n# Install Nuekit globally\nbun install --global nuekit\n\n# Create your first project\nnue create blog    # or minimal, spa, full\n```\n\nStart developing:\n\n```bash\nnue dev    # Starts serving at http://localhost:4000\n```\n\n## Template types\n\n**minimal** - Just `index.html` and `index.css` to start from scratch\n\n**blog** - Simple Markdown based blog with minimal coding/configuration\n\n**spa** - Single-page application with a simple server and UI\n\n**full** - Full stack web with blog, docs, marketing pages, authentication, and SPA\n\n\n## Upgrading\nUpdate the global nue command to the newest version:\n\n```\nbun install --global nuekit@latest\n```\n\n\n## Upgrading from 1.0 to 2.0\nNue 2.0 is not backwards compatible with the previous version. The safest upgrade path is to install locally in your project:\n\n```bash\nbun install --global nuekit@latest\n```\n\nRun commands with `bunx`:\n\n```bash\ntouch index.html\nbunx nue serve\nbunx nue build\n```\n\nThis approach lets you test the new version without breaking anything. Once you've migrated all projects, switch to global installation:\n\n```bash\nbun remove --global nuekit\nbun install --global nuekit@latest\n```\n\n\n\n## Why Bun?\nNue uses Bun exclusively because they share the same vision:\n\n### Standards based\nBun uses browser APIs you already know: `fetch()`, `Request`, `Response`, `URL`, `Headers`, and `FormData`. No framework-specific APIs to learn. Code that works in the browser works in Bun.\n\n### Rich in features\nCore features like bundling, serving, and file handling are written in native code (Zig). No need for Vite, ESBuild, or separate build tools.\n\n### Performance\nBun is faster than Node in almost every operation.\n\nWhile Node support would be convenient, Bun-only is what enables Nue's extreme performance and simplicity. It's what makes the developer experience so special.\n\n\n## Why --global?\nNue works like UNIX tools: `grep`, `sort`, or `git`. Just create an empty directory, add `index.html`, and run `nue`.\n\n\n"
  },
  {
    "path": "packages/www/docs/html-file-types.md",
    "content": "\n# HTML file types\nNue uses document type declarations to determine how `.html` files should be processed. Each type serves a different purpose for pages, components, and layouts.\n\n\n## Server-side HTML\nServer-rendered static pages that generate complete HTML documents:\n\n```\n<!doctype html>\n<h1>About Us</h1>\n<p>We build standards-first web experiences.</p>\n```\nGenerates a complete HTML document with head, body, and meta tags during build time. Best for static content, landing pages, and server-rendered templates.\n\n\n## Dynamic HTML pages\nClient-rendered dynamic pages with interactive behavior:\n\n```\n<!doctype dhtml>\n<article>\n  <button :onclick=\"count++\">{ count }</button>\n\n  <script>\n    this.count = 0\n  </script>\n</article>\n```\n\nBecomes a client-side component that mounts and runs in the browser. Used for interactive pages, forms, dashboards, and single-page applications.\n\n## HTML libraries\nCreate reusable components and [layout modules](/docs/layout-system) for server-rendered pages:\n\n```\n<!html lib>\n\n<header>\n  <a href=\"/\">Home</a>\n</header>\n\n<footer>\n  © Copyright { new Date().getFullYear() } Acme Inc\n</footer>\n\n<author class=\"card\">\n  <img src=\"{ avatar }\" alt=\"{ name }\">\n  <h3>{ name }</h3>\n  <p>{ role }</p>\n\n  <script>\n    this.avatar = this.avatar || '/img/default-avatar.png'\n  </script>\n</author>\n\n```\n\nComponents defined in HTML libraries can be used in other HTML pages and in Nuemark content as custom tags.\n\n\n### DHTML libraries\nCreate interactive components for client-side use:\n\n```\n<!dhtml lib>\n\n<script>\n  import { postMember } from 'app'\n</script>\n\n<form :is=\"member-form\" :onsubmit=\"submit\">\n  <label>\n    <h3>Email</h3>\n    <input type=\"email\" name=\"email\" required autocomplete=\"email\">\n  </label>\n\n  <label>\n    <h3>Feedback</h3>\n    <textarea name=\"comment\" rows=\"4\"\n      placeholder=\"Optional, but highly valued!\"></textarea>\n  </label>\n\n  <p>\n    <button>Join mailing list</button>\n  </p>\n\n  <script>\n    async submit(e) {\n      const data = Object.fromEntries(new FormData(e.target))\n      await postMember(data)\n      location.href = '/contact/thanks'\n    }\n  </script>\n\n</form>\n```\n\nDHTML library components become interactive client-side elements that can be embedded in any page.\n\n\n### Isomorphic libraries\nComponents that work on both server and client:\n\n```\n<!html+dhtml>\n\n<time :is=\"pretty-date\">\n  { pretty }\n\n  <script>\n    const DATE_FORMAT = new Intl.DateTimeFormat('en-US', {\n      month: 'short', day: 'numeric', year: 'numeric'\n    })\n    const date = this.date || new Date()\n    this.pretty = DATE_FORMAT.format(date)\n  </script>\n</time>\n```\n\nIsomorphic libraries render on the server during build and can also be used as interactive components on the client. Essential for design systems and reusable UI components.\n\n\n### Raw HTML page\nIf the page starts with `<!doctype html>` and contains `<html>` or `<head>` tag at the root level, the page is rendered as is without Nue processing\n\n```html\n<!doctype html>\n\n<head>\n  ...\n</head>\n```\n\n\n\n## Doctype syntax\nYou can omit the `doctype` keyword:\n\n```\n<!html>        <!-- same as <!doctype html> -->\n<!dhtml>       <!-- same as <!doctype dhtml> -->\n<!html lib>    <!-- library declaration -->\n<!dhtml lib>   <!-- library declaration -->\n<!html+dhtml>  <!-- isomorphic library -->\n```\n\nThe `<!doctype ...>` prefix is recommended for pages, and should be omitted from libraries.\n\n### Auto-detection\nWhen `<!...>` declaration is missing, Nue automatically detects the type:\n\n### DHTML when it finds:\n- Event handlers (`:onclick`, `:onsubmit`, etc.)\n- JavaScript imports (`import { ... } from '...'`)\n- Client-side scripting patterns\n\n```\n<button :onclick=\"handleClick\">Click me</button>\n```\n\n### HTML when:\nPure content structure and no dynamic features detected\n\n```\n<main>\n  <h1>Static content</h1>\n</main>\n```\n\n### Library when:\nNue auto-detects libraries when all elements are custom\n\n```\n<my-component>\n  <h3>Custom component</h3>\n</my-component>\n\n<figure :is=\"media\">\n  <h3>Custom component via :is attribute</h3>\n</figure>\n```\n\nAt minimum you should declare at least <!dhtml> for robustness and clarity.\n\n\n"
  },
  {
    "path": "packages/www/docs/html-syntax.md",
    "content": "\n# HTML syntax\nNue extends standard HTML with expressions, control flow, and components. Everything works on both server and client unless marked **client-only**.\n\n## Standard HTML\nEvery HTML document is a valid Nue document:\n\n```html\n<!doctype html>\n\n<html>\n  <head>...</head>\n\n  <body>\n    <article>\n      <button onclick=\"history.go(-1)\">Back</button>\n      <button popovertarget=\"confirm-delete\">Delete</button>\n    </article>\n\n    <dialog id=\"confirm-delete\">\n      <h2>Delete user?</h2>\n    </dialog>\n  </body>\n</html>\n```\n\n\n## Expressions\nInsert dynamic values with curly brackets:\n\n```html\n<!-- text content -->\n<span>{ username }</span>\n\n<!-- JavaScript expressions -->\n<p>{ username.toUpperCase() }</p>\n\n<!-- unescaped HTML -->\n<div>{{ markdown(description) }}</div>\n<div>{{ renderContent(article) }}</div>\n\n<!-- triple brackets also supported -->\n<div>{{{ userSubmittedContent }}}</div>\n\n```\n\n\n\n\n## Attributes\nDynamic attributes use the same expression syntax:\n\n```html\n<!-- attribute values -->\n<time datetime=\"{ date.toISOString() }\">\n\n<!-- boolean attributes (falsy values remove the attribute) -->\n<button disabled=\"{ is_disabled }\">\n\n<!-- class name interpolation -->\n<div class=\"gallery { type }\">\n\n<!-- conditional classes -->\n<div class=\"[ is-active: isActive, has-error: hasError ]\">\n\n<!-- combine static and dynamic -->\n<div class=\"gallery { type } [ is-active: isActive ]\">\n```\n\n\n## Loops\nRender lists with `:each`:\n\n```html\n<!-- basic loop -->\n<li :each=\"item in items\">{ item.name }</li>\n\n<!-- with index -->\n<li :each=\"item, i in items\">\n  { i }: { item.name }\n</li>\n\n<!-- destructuring -->\n<li :each=\"{ name, price } in products\">\n  { name } costs { price }\n</li>\n\n<!-- loop objects -->\n<li :each=\"[key, val] in Object.entries(data)\">\n  { key } = { val }\n</li>\n\n<!-- template loops (no wrapper element) -->\n<dl>\n  <template :each=\"term in glossary\">\n    <dt>{ term.word }</dt>\n    <dd>{ term.definition }</dd>\n  </template>\n</dl>\n```\n\n\n## Conditionals\nControl rendering with `:if`:\n\n```html\n<p :if=\"count > 100\">Too many!</p>\n<p :else-if=\"count > 10\">Getting there</p>\n<p :else>{ count } items</p>\n\n<!-- combine with loops (condition evaluated first) -->\n<ul :if=\"items.length\">\n  <li :each=\"item in items\">{ item }</li>\n</ul>\n<p :else>No items</p>\n```\n\n\n## Components\nReusable pieces of UI:\n\n```html\n<!-- define a component -->\n<product-card>\n  <h3>{ name }</h3>\n  <p>{ price }</p>\n\n  <script>\n    // default values\n    this.name = 'Untitled'\n    this.price = 0\n  </script>\n</product-card>\n\n<!-- use the component -->\n<product-card/>\n\n<!-- pass properties -->\n<product-card :name=\"Coffee\" :price=\"12\"/>\n\n<!-- pass data variables -->\n<product-card :name=\"productName\" :price=\"productPrice\"/>\n\n<!-- shorthand (passes the name and price variables) -->\n<product-card :name :price/>\n\n<!-- regular attributes (no colon prefix) are rendered -->\n<product-card id=\"featured\" class=\"highlight\"/>\n\n<!-- loop components -->\n<product-card :each=\"item in products\" :bind=\"item\"/>\n```\n\n### Component root element\nComponents default to `<div>` wrapper. Change with `:is`:\n\n```html\n<!-- this component renders as <figure> -->\n<image-card •:is=\"figure\"•>\n  <img src=\"{ url }\">\n  <figcaption>{ caption }</figcaption>\n</image-card>\n```\n\n\n## Event handlers\n**Client-only** - Handle user interactions:\n\n```html\n<counter>\n  <button :onclick=\"count++\">{ count }</button>\n\n  <script>\n    this.count = 0\n  </script>\n</counter>\n\n<!-- method handlers -->\n<counter>\n  <button :onclick=\"increment\">+</button>\n  <button :onclick=\"decrement\">-</button>\n  <p>Count: { count }</p>\n  <p>Double: { double }</p>\n\n  <script>\n    this.count = 0\n\n    increment() {\n      this.count++\n    }\n\n    decrement() {\n      if (this.count > 0) this.count--\n    }\n\n    // getter methods are supported\n    get double() {\n      return this.count * 2\n    }\n  </script>\n</counter>\n\n<!-- event object -->\n<form :onsubmit=\"handleSubmit\">\n  <script>\n    handleSubmit(e) {\n      // forms automatically call e.preventDefault()\n      console.log('Submitted:', e.target)\n    }\n  </script>\n</form>\n```\n\n\n## Lifecycle methods\n**Client-only** - Run code at specific moments:\n\n```html\n<user-profile>\n  <h2>{ user.name }</h2>\n\n  <script>\n    // before mounting to DOM\n    onmount() {\n      console.log('About to mount')\n    }\n\n    // after mounting to DOM\n    mounted() {\n      console.log('Mounted!')\n    }\n\n    // before updating\n    onupdate() {\n      console.log('About to update')\n    }\n\n    // after updating\n    updated() {\n      console.log('Updated!')\n    }\n  </script>\n</user-profile>\n```\n\n\n### Manual updates\n**Client-only** - Trigger component re-render with new data:\n\n```javascript\nthis.update(data)\n```\n\nEvent handlers update automatically, but async operations or external events (like web socket messages) need manual updates:\n\n\n```html\n<script>\n  async mounted() {\n    const data = await fetch('/api/user')\n    const user = await data.json()\n\n    // Manual update required after async operations\n    this.update({ user })\n  }\n</script>\n```\n\n\n## Dynamic mounting\n**Client-only** - Mount components programmatically inside a single-page app:\n\n```javascript\nthis.mount(name, target, data)\n```\n\n**name** - Component name\n\n**target** - DOM element or CSS selector\n\n**data** - Optional data to pass to component\n\n```html\n<my-app>\n\n  <article/>\n\n  <script>\n    state.on('id', ({ id }) => {\n      this.mount(id ? 'user-details' : user-list', 'article')\n    })\n  </script>\n\n</my-app>\n```\n\nSee [single-page apps](/docs/single-page-apps) for routing patterns.\n\n\n## Shared scripts\nDefine functions and data for multiple components:\n\n```html\n<!-- top-level script -->\n<script>\n  // available to all components\n  const TAX_RATE = 0.08\n\n  function formatPrice(num) {\n    return '$' + num.toFixed(2)\n  }\n</script>\n\n<!-- use in components -->\n<product-card>\n  <p>{ formatPrice(price) }</p>\n  <p>Tax: { formatPrice(price * TAX_RATE) }</p>\n\n  <script>\n    this.price = 10\n  </script>\n</product-card>\n\n<!-- another component definition -->\n<shopping-cart>\n  <p>{ formatPrice(price) }</p>\n\n  <script>\n    // ...\n  </script<\n</shopping-cart>\n```\n\n\n## JavaScript imports\n**Client-only** - Import external modules:\n\n```html\n<script>\n  import { formatDistance } from './utils.js'\n  import { store } from './store.js'\n</script>\n\n<!-- imported functions available in templates -->\n<article>\n  <time>{ formatDistance(date) }</time>\n  <p>Cart items: { store.cart.length }</p>\n</article>\n```\n\n\n## Passthrough scripts\n**Server-only** - Scripts with `type` or `src` pass through unchanged:\n\n```html\n<!-- these render as-is to the client -->\n<script src=\"/analytics.js\"></script>\n\n<script type=\"module\">\n  console.log('This runs on the client')\n</script>\n```\n\n\n## Slots\nComponent composition pattern:\n\n```html\n<!-- component with slot -->\n<card>\n  <div class=\"card\">\n    <slot/>\n  </div>\n</card>\n\n<!-- using the slot -->\n<card>\n  <h2>This goes inside the card</h2>\n  <p>So does this</p>\n</card>\n\n<!-- multiple instances -->\n<card :each=\"post in posts\">\n  <h2>{ post.title }</h2>\n  <p>{ post.excerpt }</p>\n</card>\n```\n\n\n## CSS variables\nPass design tokens without inline styles:\n\n```html\n<!-- renders as style=\"--spacing: 2rem\" -->\n<section --spacing=\"2rem\">\n  <style>\n    section {\n      padding: var(--spacing);\n    }\n  </style>\n</section>\n\n<!-- dynamic values -->\n<div --columns=\"{ columnCount }\">\n```\n\nNo inline styles or class overloading. Your design system stays the single source of truth.\n"
  },
  {
    "path": "packages/www/docs/index.md",
    "content": "\n---\nheading_ids: false\ntitle_template: false\nnoindex: true\npagefoot: false\nfooter: true\nbeside: true\n---\n\n# Learn Nue\nThe UNIX of the Web\n\n[.topics]\n\n  ### Essentials\n  [topics category=\"essentials\"]\n\n  ### Developing\n  [topics category=\"developing\"]\n\n  ---\n\n  ### Tools\n  [topics category=\"tools\"]\n\n\n  ### Concepts\n  [topics category=\"concepts\"]\n\n  ---\n\n  ### Reference\n  [topics category=\"reference\"]"
  },
  {
    "path": "packages/www/docs/interactive-components.md",
    "content": "\n# Interactive components\nInteractive components add dynamic behavior to your pages. Small, focused components like forms, dialogs, or image galleries that work alongside your content and layouts.\n\n## Creating components\n\nComponents live in `.html` files using Nue's template syntax. Mark them with the `:is` attribute:\n\n```html\n<form :is=\"newsletter-form\" :onsubmit=\"submit\" autocomplete=\"on\">\n  <label>\n    <span>Your name</span>\n    <input type=\"text\" name=\"name\" placeholder=\"John Doe\" required>\n  </label>\n\n  <label>\n    <span>Your email</span>\n    <input type=\"email\" name=\"email\" placeholder=\"your@email.com\" required>\n  </label>\n\n  <button>Submit</button>\n\n  <script>\n    async submit(e) {\n      const data = Object.fromEntries(new FormData(e.target))\n      await fetch('/api/subscribe', {\n        method: 'POST',\n        body: JSON.stringify(data)\n      })\n      location.href = '/thanks'\n    }\n  </script>\n</form>\n```\n\nThe component starts as server-rendered HTML. When the browser loads it, a 2.5KB runtime makes it interactive. The `:onsubmit` handler prevents the default form submission and runs your custom logic instead.\n\n ## Using components\n\nDrop components into content or layouts wherever you need interaction.\n\n### In content\n\nAdd components to Markdown files:\n\n```md\n### Join our newsletter\n\nGet updates on new releases.\n\n[newsletter-form]\n```\n\nNue replaces the tag with rendered HTML. Content writers use simple tags without touching code.\n\n### In layouts\n\nEmbed components in layout modules:\n\n```html\n<footer>\n  <h3>Stay updated</h3>\n  <p>Get our latest news delivered to your inbox</p>\n  <newsletter-form/>\n</footer>\n```\n\nThe component slots into static structure, adding interaction where needed.\n\n## Component libraries\n\nComponents are discovered automatically based on file location:\n\n```\n@system/ui/          # Global components (all pages)\nblog/ui/             # Blog-specific components\napp/components.html  # App area components\n```\n\nPut multiple components in one file or separate them - Nue finds them either way:\n\n```html\n<!-- In @system/ui/forms.html -->\n<form :is=\"newsletter-form\">\n  ...\n</form>\n\n<form :is=\"contact-form\">\n  ...\n</form>\n```\n\nSee [Project structure](/docs/project-structure) for details on organizing larger applications.\n\n## Template data\n\nComponents receive data from multiple sources that cascade together:\n\n```html\n<form :is=\"newsletter-form\">\n  <h3>{ newsletter_title }</h3>\n  <p>{ site_name } newsletter</p>\n  <button>{ cta || 'Submit' }</button>\n</form>\n```\n\nData flows from YAML files, page front matter, and collections. The component above might use:\n\n- `newsletter_title` from `site.yaml`\n- `site_name` from global configuration\n- `cta` from page front matter\n\nPass page-specific data through attributes:\n\n```md\n---\ncta: \"Subscribe now\"\n---\n\n[newsletter-form]\n```\n\nComponents also access collections and parsed content structure like headings. See [Template data](/docs/template-data) for complete details on data sources and precedence.\n\n"
  },
  {
    "path": "packages/www/docs/js-enhancements.md",
    "content": "\n# JavaScript enhancements\nContent-driven websites uses (optional) JavaScript for global behavior\n\n## UI controllers\nSome JavaScript needs to work across your entire application: keyboard shortcuts that work on every page, analytics modules that track user behavior globally, or tooltip systems that enhance any element. These cross-cutting concerns don't belong in individual components. Instead, they need their own space as global controllers that run once and manage application-wide behavior.\n\n```javascript\n// @shared/ui/keyboard.js\ndocument.addEventListener('keydown', (evt) => {\n  const { target, key } = evt\n\n  if (key == 'Escape') {\n    // Close modals, clear forms\n  }\n\n  if (key == 'Tab') {\n    // Focus management\n  }\n})\n```\n\nPlace controllers in `@shared/ui/` where they automatically load on every page. They run globally across your entire application.\n\n### Controller responsibilities:\n\n**Global behavior** - Handle interactions that span multiple pages and components.\n\n**Logic decoupling** - Components stay focused on structure while controllers handle cross-cutting concerns.\n\n**Centralized management** - All global JavaScript lives in one place, making it easy to maintain and debug.\n\n\n## External libraries\nThe modern web platform provides surprisingly complete functionality out of the box. Before reaching for a library, check if native APIs can handle your needs. When you do need external code, simply place it to your system's library folder and avoid the dependency chaos that plagues many projects.\n\n### Recommended structure\nDownload minimal versions to `@shared/lib/`:\n\n```yaml\nimport_map:\n  d3: /@shared/lib/d3.js\n  utils: /@shared/lib/utils.js\n```\n\nUse on your JS modules:\n\n```javascript\nimport * as d3 from 'd3'\nimport { formatCurrency } from 'utils'\n```\n\n### External scripts\nNon-module scripts like Google Analytics load through [layout modules](/docs/layout-system):\n\n```html\n<!-- In layout.html -->\n<head>\n  <script async src=\"https://www.googletagmanager.com/gtag/js\"></script>\n</head>\n```\n\nOr in the \"bottom\" slot for scripts that should load after page content.\n\n\n\n\n"
  },
  {
    "path": "packages/www/docs/layout-system.md",
    "content": "\n# Layout system\nMost websites share common elements like headers, footers, navigation, and sidebars across pages. Nue's layout system lets you define these once and reuse them everywhere - whether your pages are static or dynamic.\n\n## How it works\n\nEvery page starts with a basic HTML structure. Your content gets wrapped automatically:\n\n```html\n<html>\n  <head>\n    <!-- auto-generated head -->\n  </head>\n  <body>\n    <main>\n      <article>\n        <!-- your content here -->\n      </article>\n    </main>\n  </body>\n</html>\n```\n\nHere's what Chrome DevTools shows for a simple `.md` file with just `# Hello, World!`:\n\n[image.bordered]\n  src: img/default-layout.png\n  size: 400 x 262\n\nThis gives you semantic HTML out of the box. But you'll want to add your own header, footer, and other common elements.\n\n\n## Layout modules\nTraditional templating systems use a single master template that wraps your content. You define the entire page structure and insert content into one spot.\n\nNue uses **slots** and **layout modules**. Instead of one wrapper template, you create individual modules (header, footer, sidebar) and Nue assembles them around your content using predefined slots.\n\n**Slots** are predefined positions in your page structure. **Layout modules** are the HTML components that fill those slots. This modular system lets you mix and match different headers, footers, and sidebars for different sections of your site without duplicating the surrounding template structure.\n\nThink of slots as empty containers and modules as the HTML that fills them:\n\n[image.bordered]\n  small: img/layout-slots.png\n  large: img/layout-slots-big.png\n  size: 500 x 543\n\nAvailable slots:\n\n| Slot name    | Purpose                                            |\n|--------------|----------------------------------------------------|\n| \"banner\"     | Temporary announcements above the header           |\n| \"header\"     | Global site header                                 |\n| \"subheader\"  | Breadcrumbs or secondary navigation                |\n| \"main\"       | Global main                                        |\n| \"aside\"      | Sidebars for documentation or catalogs             |\n| \"pagehead\"   | Hero areas for blog posts or marketing pages       |\n| \"pagefoot\"   | Call-to-action sections                            |\n| \"beside\"     | Table of contents or complementary navigation      |\n| \"footer\"     | Global footer                                      |\n| \"bottom\"     | Overlays or menus below the main footer            |\n\n\n## Creating a layout module\nLayout modules are just HTML templates. Create them in any `.html` file.\n\n\n### HTML5 landmarks\nFor semantic landmark elements like `<header>` and `<footer>`, use the tag name directly:\n\n```html\n<header>\n  <a href=\"/\" class=\"logo\">{ site_name }</a>\n  <nav>\n    <a href=\"/docs\">Documentation</a>\n    <a href=\"/blog\">Blog</a>\n    <a href=\"/about\">About</a>\n  </nav>\n  <button popovertarget=\"menu\">Menu</button>\n</header>\n\n<footer>\n  <p>&copy; 2025 { site_name }. All rights reserved.</p>\n  <nav>\n    <a href=\"/privacy\">Privacy</a>\n    <a href=\"/terms\">Terms</a>\n  </nav>\n</footer>\n```\n\n### Other slots\nFor non-semantic slots, use the `:is` attribute:\n\n```html\n<div :is=\"banner\">\n  <strong>Major update available!</strong>\n  <a href=\"/blog/release-2.0/\">Check out v2.0</a>\n</div>\n\n<section :is=\"pagehead\">\n  <h1>{ title }</h1>\n  <p>{ description }</p>\n</section>\n```\n\n### Head content\nAdd custom head elements with a `<head>` module:\n\n```html\n<head>\n  <meta http-equiv=\"Content-Security-Policy\"\n    content=\"default-src 'self'; img-src https://*; child-src 'none';\">\n  <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n</head>\n```\nThis content gets added after the auto-generated head elements.\n\n\n## File organization\nYou can organize layout modules however you want:\n\n```\nsite.html          // global modules\nblog/\n  layout.html      // blog-specific modules\n  post.html        // individual post layout\ndocs/\n  layout.html      // documentation modules\n```\n\nA single file can contain multiple modules. Put your header, footer, and navigation all in one file if you prefer.\n\nSee [project structure](project-structure) for details on how to organize your layout for small vs large apps.\n\n\n## Override behavior\n\nMore specific modules override global ones:\n\n- `blog/layout.html` modules override `site.html` modules for blog pages\n- `blog/post.html` modules override `blog/layout.html` for individual posts\n- Page-level front matter overrides everything\n\n## Disabling modules\n\nTurn off modules through YAML configuration:\n\n```yaml\n# In app.yaml or page front matter\nbanner: false\naside: false\npagehead: false\n```\n\n## Content scope\n\nBy default, your content renders inside `<article>`. Change this with the `scope` setting:\n\n```yaml\nscope: main\n```\n\nAvailable scopes:\n- `article` (default) - Content goes in `<article>`\n- `main` - Content goes in `<main>`\n- `body` - Content goes in `<body>`\n- `html` - Content replaces everything, including `<head>`\n\n### HTML pages\n\nFor `.html` files, your root element defines the scope automatically:\n\n```html\n<!doctype html>\n<main>\n  <h1>Full control of main element</h1>\n</main>\n```\n\n### Dynamic HTML pages\nDynamic HTML pages use the same layout system:\n\n```html\n<!doctype dhtml>\n<main>\n  <h1>Full control of main element</h1>\n</main>\n```\n\nThe `dhtml` doctype marks the page as client-rendered. Like server-rendered HTML pages, your root element defines the scope.\n\nThis gives you complete control over the page structure while still benefiting from Nue's block system.\n\n\n### Single page apps\nSingle page apps are simply a dhtml + body combo:\n\n\n```html\n<!doctype dhtml>\n\n<body>\n  <h1>Full control of the whole page</h1>\n</body>\n```\n\nSee [SPA development](spa-development) for details.\n\n\n\n"
  },
  {
    "path": "packages/www/docs/migration.md",
    "content": "\n# Migration from Next.js\nA step-by-step migration guide for moving a serious full-stack application from Next.js ecosystem to Nue and web standards. This covers hybrid MPA+SPA architectures with content sites, dynamic applications, and backend integration.\n\n[TOC]\n\n## What to expect\n\nAfter migration you'll see:\n\n**90% less project scaffolding** - From hundreds of megabytes of NPM modules and dozens of configuration files (TypeScript, ESLint, Tailwind, PostCSS, Webpack) to minimal configuration and zero redundancy.\n\n**Cleaner and smaller codebase** - Similar to simplified project setup, your monolithic components become leaner with architectural clarity. App concerns live in isolated layers that can be managed and scaled independently.\n\n**Lightweight pages and apps** - Order of magnitude smaller footprint across all pages. Content-heavy marketing sites, documentation, blogs, and full single-page applications all become dramatically lighter. We're talking single-page apps that take less bandwidth than a single React button component.\n\n**Faster builds** - Build times drop from seconds to milliseconds. HMR spans all pages, apps, assets, and server routes. Every update takes around 20ms.\n\n\n\n## Project setup\nWhen moving to Nue, start with a fresh project structure. The monolithic Next.js architecture where everything lives in components doesn't translate directly to Nue's separated concerns. The focus is on cleaning up the entire architecture rather than porting it file-by-file.\n\nThe first step is cleaning up unnecessary NPM modules, project scaffolding, and configuration. This is quite significant in Next.js. An empty Next.js project created with `npx create-next-app@latest` (v15.5) contains 336 packages, 18,666 files, and 427MB. A size of eight Windows 95 installations.\n\nAdd a component library like ShadCN and you're at 470MB (9x Windows 95). A typical Next.js project has at least the following configuration files to start with:\n\n```\n.\n├── app/\n│   └── page.tsx\n├── components/\n│   └── ui/\n│       └── button.tsx\n├── components.json\n├── eslint.config.mjs\n├── lib/\n│   └── utils.ts\n├── next-env.d.ts\n├── next.config.ts\n├── package-lock.json\n├── package.json\n├── postcss.config.mjs\n└── tsconfig.json\n```\n\nWith Nue:\n\n```\n.\n├── index.css\n└── index.html\n```\n\n**No configuration required.** Nue installs globally with `bun install -g nuekit` and works like a UNIX command (`nue`, `nue build`, `nue --help`). Your project stays clean.\n\nThe shift is from configuration theater with third-party syntaxes to minimalism and web standards. You lose no functionality - only complexity. The `index.html` can be dynamic or server-rendered, contain expressions and event handlers, or serve as your SPA entry point. It's a clearer foundation for both developers and AI models to understand and build upon.\n\n\n\n\n## Content\nMigrating from monolithic components and MDX to a content-first architecture.\n\n\n### Next.js: 500MB of redundancy\nSince Next.js provides no content authoring tools natively, you need these additional packages for a content-focused site with blogs, documentation, and marketing pages:\n\n```bash\n# MDX, MD extensions, content processing and view transitions\nnpm install @next/mdx @mdx-js/loader @mdx-js/react\nnpm install gray-matter contentlayer next-contentlayer\nnpm install remark-gfm rehype-autolink-headings rehype-slug\nnpm install shiki date-fns next-seo feed next-sitemap\nnpm install framer-motion\n```\n\nThis causes your repository size to balloon from 470MB to a whopping 550MB, over 500 times of Nue without even reaching the complete feature set yet.\n\n\n### Next.js content architecture\nAfter implementing a handful of blog entries, documentation, and marketing pages, your Next.js project structure looks like this:\n\n```\n.\n├── app/\n│   ├── blog/\n│   │   ├── [slug]/\n│   │   │   └── page.tsx\n│   │   └── page.tsx\n│   ├── docs/\n│   │   ├── [...slug]/\n│   │   │   └── page.tsx\n│   │   └── page.tsx\n│   ├── about/\n│   │   └── page.tsx\n│   ├── pricing/\n│   │   └── page.tsx\n│   └── layout.tsx\n├── content/\n│   ├── blog/\n│   │   ├── first-post.mdx\n│   │   ├── design-systems.mdx\n│   │   └── web-standards.mdx\n│   └── docs/\n│       ├── getting-started.mdx\n│       ├── api-reference.mdx\n│       └── deployment.mdx\n├── components/\n│   ├── ui/\n│   │   ├── button.tsx\n│   │   ├── card.tsx\n│   │   └── badge.tsx\n│   ├── blog-post.tsx\n│   ├── doc-layout.tsx\n│   └── hero-section.tsx\n├── lib/\n│   ├── content.ts\n│   └── utils.ts\n├── components/\n│   ├── page-transition.tsx\n│   └── variants.ts\n├── hooks/\n│   └── use-router-events.ts\n├── contentlayer.config.js\n├── next.config.js\n├── mdx-components.tsx\n├── package.json\n├── tsconfig.json\n└── [8 other config files...]\n```\n\n### Nue migration\nNue gives you the same functionality with this structure:\n\n```\n.\n├── layout.html\n├── components.html\n├── docs/\n│   ├── layout.html\n│   ├── getting-started.md\n│   ├── api-reference.md\n│   └── deployment.md\n├── blog/\n│   ├── layout.html\n│   ├── first-post.md\n│   ├── design-systems.md\n│   └── web-standards.md\n├── about.md\n├── pricing.md\n└── index.md\n```\n\n### Nue configuration\nEnable all the features (collections, RSS feeds, sitemaps, view transitions) with this `site.yaml` config:\n\n```yaml\nsite:\n  view_transitions: true\n  sitemap: true\n\ncontent:\n  heading_ids: true\n  sections: true\n\ncollections:\n  blog:\n    match: [blog/*.md]\n    sort: date desc\n    rss: true\n\n  docs:\n    match: [docs/*.md]\n    sort: order asc\n```\n\nThis literally drops thousands of lines of code, configuration and scaffoling to get a versatile content engine running  under Next.js.\n\n### Nue benefits\n\n**Massive simplification** - After content migration your file system goes from 30+ files across 8 directories with complex interdependencies to 11 clean files organized by purpose. No routing logic, no component glue code, no build configuration.\n\n**Scalable content** - All pages, from rich front pages to simple blog entries, are editable with pure content. Page development becomes a content project, not a software engineering and TSX debugging project. Writers work independently without breaking builds.\n\n**All features in 1MB** - Syntax highlighting, heading links, collections, RSS feeds, sitemaps, view transitions, responsive images, layout inheritance, and content processing.\n\n**Rich layouts** - With [slots and layout modules](/docs/layout-system), create sophisticated page structures without component hierarchies. Section-specific layouts inherit and override automatically.\n\n\n## Backend\nMigrating your backend infrastructure (server and databases) from third-party APIs to [edge first](/docs/edge-first) approach and web standards.\n\n### Next.js: More packages\n\nThe backend landscape is fragmented with options ranging from the complex T3 stack to newer Server Actions. This guide assumes tight integration with the Vercel ecosystem using these dependencies, after which the project size reaches 1.4G:\n\n```bash\nnpm install @vercel/kv @vercel/postgres\nnpm install drizzle-orm drizzle-kit\nnpm install next-auth@beta @auth/drizzle-adapter\nnpm install @types/node\n```\n\nThis requires at least the following TypeScript configuration files:\n\n```\n.\n├── drizzle.config.ts     # Database schema config\n├── auth.config.ts        # Auth.js configuration\n├── middleware.ts         # Route protection\n├── .env.local           # Database URLs and secrets\n├── lib/\n│   ├── auth.ts          # Auth setup\n│   ├── db.ts            # Database connection\n│   └── schema.ts        # Drizzle schema\n├── app/api/\n│   ├── auth/[...nextauth]/\n│   └── users/\n│       └── route.ts     # API endpoints\n└── __tests__/\n    ├── auth.test.ts\n    └── api.test.ts\n```\n\n### Next.js: Non-standard APIs\n\nYour server code becomes tightly coupled to framework abstractions:\n\n```typescript\n// middleware.ts\nimport { withAuth } from \"next-auth/middleware\"\nimport { NextResponse } from \"next/server\"\nimport type { NextRequest } from \"next/server\"\nimport { db } from \"@/lib/db\"\nimport { eq } from \"drizzle-orm\"\nimport { users } from \"@/lib/schema\"\n```\n\nThese are proprietary APIs, not web standards. Your code only works within the Next.js ecosystem.\n\n### Nue migration\nNue skips NPM installs and configuration files. Jump straight to development using the global 1MB nue executable:\n\n```\nserver/\n├── index.js             # Server routes\n├── db/                  # Database files\n│   ├── app.db           # SQLite database\n│   ├── kv.json          # KV store data\n│   └── init/            # Schema and sample data\n├── model/               # Business logic\n│   ├── auth.js          # Authentication\n│   ├── index.js         # Data operations\n│   └── utils.js         # Utilities\n└── test/                # Test suites\n    ├── mock.js          # Mock environment\n    ├── model.test.js    # Business logic tests\n    └── server.test.js   # Integration tests\n```\n\n### Nue server setup\nConfigure the entire system centrally in `site.yaml\n\n```yaml\nserver:\n  dir: server          # Server directory\n  db: db/app.db        # SQL database location\n  kv: db/kv.json       # KV database location\n  reload: true         # Server route HMR\n```\n\n### Edge first\n\nWrite code that works identically locally and globally:\n\n```js\n// Authentication middleware\nuse('/admin/*', async (c, next) => {\n  // Same API locally and on CloudFlare Edge\n  const { KV, DB } = c.env\n\n  // CloudFlare headers work locally too\n  const country = c.req.header('cf-ipcountry')\n\n  const user = await KV.get(`session:${sessionId}`, { type: 'json' })\n  if (!user) return c.json({ error: 'Unauthorized' }, 401)\n\n  await next()\n})\n\n// API routes with standard Request/Response\nget('/api/users', async (c) => {\n  const { DB } = c.env\n  const users = await DB.prepare('SELECT * FROM users').all()\n  return c.json(users)\n})\n```\n\n### Nue benefits\n\n**90% less boilerplate** - No TypeScript configs, no authentication setup files, no database connection boilerplate, no middleware configuration.\n\n**Edge first architecture** - Server routes, KV storage, and SQL databases work seamlessly locally and globally with identical APIs.\n\n**Integrated development** - Frontend and backend on same port with `nue dev`. Instant startup, built-in HMR for all server routes and database changes.\n\n**Web standards** - Work with standard `Request`/`Response` objects, not framework abstractions. Code that runs anywhere.\n\n\n\n## Business logic\nOn this step, our goal is to build an isolated, testable business logic layer that works independently of any UI framework. We use plain JavaScript or TypeScript to create a portable model that is free from frontend concerns.\n\n\n### Next.js: More packages\nNext.js has no notion of a decoupled business logic layer. Instead there are multiple options to integrate business logic into your components. Tools like Redux Toolkit, RTK Query and Formik. Or SWR, Valtio, and React Final Form. However the most popular stack currently is likely TanStack Query, Zustand and React Hook Form. This has become the de facto standard for modern React applications in 2024-2025. So let's add some more packages:\n\n```bash\nnpm add @tanstack/react-query zustand react-hook-form\nnpm add -D @tanstack/react-query-devtools\nnpm add @hookform/resolvers zod\n```\n\n### Next.js: Non-standard APIs\nWith Next.js you are using non-standard APIs and mixing business logic, state management, framework patterns, and rendering together:\n\n```jsx\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { z } from 'zod'\n\nconst userSchema = z.object({\n  name: z.string().min(1, 'Name required'),\n  // validation rules...\n})\n\nexport default function UserProfile({ userId }) {\n  const { data: user, isLoading } = useQuery({\n    queryKey: ['user', userId],\n    queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json())\n  })\n\n  const updateMutation = useMutation({\n    mutationFn: (data) => fetch(`/api/users/${userId}`, {\n      method: 'PUT',\n      // API logic...\n    }),\n    onSuccess: () => {\n      queryClient.invalidateQueries(['user', userId])\n      // cache invalidation logic...\n    }\n  })\n\n  return (\n    <div className=\"max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md\">\n      <form onSubmit={handleSubmit((data) => updateMutation.mutate(data))}>\n\n      // rendering logic ...\n      </form>\n    </div>\n  )\n}\n```\n\nThis approach tangles business logic with UI concerns. Data fetching, validation, caching, and rendering all live in the same component. Testing becomes complex (or impossible) because you can't test business logic without mounting React components.\n\n\n### Nue migration\nNue separates business logic into pure, testable modules. Your application model lives independently of any UI framework:\n\n```\n@shared/\n├── app/\n│   ├── index.js          # Main app exports\n│   ├── users.js          # User operations\n│   ├── payments.js       # Payment processing\n│   └── analytics.js      # Analytics tracking\n└── test/\n    ├── users.test.js     # Unit tests\n    ├── payments.test.js\n    └── analytics.test.js\n```\n\nConfigure the import map in `site.yaml`:\n\n```yaml\nimport_map:\n  app: /@shared/app/index.js\n```\n\n### Pure business logic\nThe application code is pure JavaScript with no frontend concerns:\n\n```js\n// snippet from `nue create full`\nexport async function login(email, password) {\n  const ret = await post('/api/login', { email, password })\n  localStorage.$sid = ret.sessionId\n}\n\nexport async function postContact(data) {\n  return await post('/api/contacts', data)\n}\n\nexport async function getContacts(params) {\n  return await get('/admin/contacts', params)\n}\n```\n\n### Nue benefits\n\n**Architectural clarity** - Business logic, data operations, and validation live separately from UI components. Each layer can be developed independently.\n\n**Testability** - Unit test your application logic without mixing frontend concerns. Pure functions are trivial to test.\n\n**Portability** - Your business model works with any UI layer. Migrate from React to Vue to vanilla JavaScript (or TypeScript) without rewriting core application logic.\n\n**Future-proof architecture** - Pure JavaScript stays relevant forever. No trendy frontend tools risk making your model outdated.\n\n**Advanced possibilities** - Decoupling enables ambitious logic engines built in Rust or Go, like those from Figma or Notion.\n\n\n\n\n## UI development\nOn this step, we migrate all React components into clean, semantic HTML and detach all business logic and styling, leaving the code focused on structure only. This makes UI development similar to content development: rapid assembly of interfaces.\n\n### Next.js: Mixed concerns\nReact components mix business logic, styling, data fetching, validation, and rendering together in a single file. For example:\n\n\n```jsx\nimport { useState, useEffect } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useMutation } from '@tanstack/react-query'\nimport { useForm } from 'react-hook-form'\nimport { zodResolver } from '@hookform/resolvers/zod'\nimport { z } from 'zod'\nimport { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Button } from '@/components/ui/button'\nimport { Input } from '@/components/ui/input'\nimport { Label } from '@/components/ui/label'\nimport { Alert, AlertDescription } from '@/components/ui/alert'\nimport { Loader2 } from 'lucide-react'\nimport { cn } from '@/lib/utils'\n\n// Validation schema mixed with UI component\nconst loginSchema = z.object({\n  email: z.string().email('Invalid email address'),\n  password: z.string().min(6, 'Password must be at least 6 characters')\n})\n\ntype LoginFormData = z.infer<typeof loginSchema>\n\nexport default function LoginForm() {\n\n  // State management hooks scattered throughout component\n  const [isLoading, setIsLoading] = useState(false)\n  const [error, setError] = useState<string | null>(null)\n  const router = useRouter()\n\n  // Form validation library integration\n  const { register, handleSubmit, formState: { errors } } = useForm<LoginFormData>({\n    resolver: zodResolver(loginSchema)\n  })\n\n  // Business logic embedded in component\n  const loginMutation = useMutation({\n    mutationFn: async (data: LoginFormData) => {\n      // API call logic mixed with component\n      const response = await fetch('/api/auth/login', {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify(data)\n      })\n      return response.json()\n    }\n  })\n\n  // Event handlers with side effects\n  const onSubmit = async (data: LoginFormData) => {\n    // Authentication logic inside UI component\n    await loginMutation.mutateAsync(data)\n    router.push('/app/')\n  }\n\n  // Styling through utility classes and pre-built components\n  return (\n    <div className=\"min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8\">\n      <div className=\"max-w-md w-full space-y-8\">\n        <Card className=\"w-full\">\n          <CardHeader>\n            // Semantic HTML buried under framework abstractions\n            <CardTitle className=\"text-2xl font-bold text-center\">\n              Sign in to your account\n            </CardTitle>\n          </CardHeader>\n\n          // etc...\n\n        </Card>\n      </div>\n    </div>\n  )\n}\n```\n\nThis monolithic component mixes concerns together:\n\n- **Business logic** - API calls, authentication, token storage\n- **Styling** - Tailwind classes, conditional styling, layout positioning\n- **Validation** - Zod schemas, form validation, error handling\n- **State management** - Multiple useState hooks, useEffect lifecycle\n- **UI structure** - Form elements, labels, buttons buried in framework abstractions\n\nThere are almost as many ways to create a React form as there are developers, because the tools and patterns evolve quickly. So this example might not represent \"idiomatic\" React, but it demonstrates the fundamental issue: concerns are inevitably mixed together.\n\n\n### Nue: Semantic HTML\nNue separates UI structure from all other concerns. Components focus purely on semantic HTML and user interactions:\n\n```html\n<script>\n  import { login } from 'app'\n</script>\n\n<form :onsubmit=\"submit\">\n\n  <!-- UI code goes here-->\n  <label>\n    <h3>Email</h3>\n    <input name=\"email\" type=\"email\" value=\"admin@example.com\"\n      autofocus autocomplete=\"email\" class=\"fullsize\">\n  </label>\n\n  <label>\n    <h3>Password</h3>\n    <input name=\"password\" type=\"password\" value=\"demo123\"\n      autocomplete=\"current-password\" class=\"fullsize\">\n  </label>\n\n\n  <!-- event handlers here  -->\n  <script>\n    async submit(e) {\n      const { email, password } = e.target\n\n      try {\n        // handlers call methods in your business model\n        await login(email, password)\n        location.href = '/app/'\n\n      } catch (error) {\n        this.update({ error: 'Invalid credentials' })\n      }\n    }\n  </script>\n\n</form>\n```\n\n### Nue benefits\n\n\n**Standards first** - Components use semantic HTML elements (`<form>`, `<table>`, `<button>`) instead of framework abstractions.\n\n**Immediate productivity** - New team members can contribute immediately. HTML knowledge transfers directly.\n\n**Application assembly** - With concerns separated, building interfaces becomes assembly work. Import business functions, write semantic HTML, let the design system handle presentation.\n\n**Future-proof** - HTML semantics outlast frameworks. Your `<form>` elements will work in browsers 20 years from now. React components from 2020 already feel outdated.\n\n\n\n### Styling\nStyling is the most important migration point for building maintainable and scalable products. This final migration step moves from hardcoded styling monoliths to a modern standards-based [design system](/docs/design-systems).\n\n\n### Next.js: hardcoded styling\nThe React ecosystem promotes mixing styling directly into components. While multiple approaches exist, the current trend seems to combine Tailwind, ShadCN/UI, clsx, and tailwind-merge. Something like this:\n\n```jsx\nimport { clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs) {\n  return twMerge(clsx(inputs))\n}\n\nexport function ProductCard({ product, isFeature, variant, className }) {\n  return (\n    <div className={cn(\n      \"bg-white border rounded-lg p-4 shadow-sm\",\n      isFeature && \"border-blue-500 shadow-blue-100\",\n      variant === \"compact\" && \"p-2\",\n      variant === \"featured\" && \"border-2 shadow-lg\",\n      className\n    )}>\n      <h3 className={cn(\n        \"font-semibold text-gray-900\",\n        isFeature && \"text-blue-700\",\n        variant === \"compact\" && \"text-sm\"\n      )}>\n        {product.name}\n      </h3>\n      <button className={cn(\n        \"bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700\",\n        isFeature && \"bg-gradient-to-r from-blue-600 to-purple-600\",\n        variant === \"compact\" && \"px-2 py-1 text-sm\"\n      )}>\n        Add to Cart\n      </button>\n    </div>\n  )\n}\n```\n\nThis approach stems from fears of global namespace pollution, desire for co-location, and challenges with naming conventions. Every component becomes a styling puzzle requiring utility memorization and merge logic.\n\n\n### Nue: design system\nNue embraces minimal and semantic design systems that are centrailly maintained. You can return to clean, isolated CSS code structured as a proper system and avoid all problems that drove developers to CSS-in-JS:\n\n```\n@shared/design/\n├── base.css         # Typography, colors, spacing\n├── button.css       # All button variants\n├── content.css      # Blog posts, documentation\n├── dialog.css       # Modals, popovers\n├── document.css     # Page structure\n├── form.css         # All form elements\n├── layout.css       # Grid, stack, columns\n├── syntax.css       # Code highlighting\n├── table.css        # Data tables\n└── apps.css         # SPA-specific components\n```\n\nThe same product card becomes pure structure:\n\n```html\n<article class=\"card featured\">\n  <h3>{ product.name }</h3>\n  <button>Add to Cart</button>\n</article>\n```\n\nCSS handles all presentation decisions in one place. Variants, states, and responsive behavior live in the design system, not scattered across components.\n\n### Benefits\n\n**Rapid assembly** - Developers focus on structure while the design system ensures consistency. No styling decisions needed during development.\n\n**Central maintenance** - Design changes happen once and cascade everywhere. Rebrand your entire application by updating CSS variables.\n\n**Minimal footprint** - Complete design system runs under 4.3KB, smaller than Tailwind's preflight CSS before adding any utilities.\n\n**Swappable design** - Replace parts of the system or swap entire design languages without touching HTML structure. True separation enables design flexibility.\n\n**Team specialization** - Designers control visual language through CSS. Developers control structure through HTML. Neither blocks the other.\n\n\n\n## Migration complete\nAfter following this migration guide, you've transformed a complex Next.js application into a clean, standards-based architecture. The transformation is quite dramatic:\n\n**From 575MB to 1MB** - Your project dependencies dropped from over 450+ NPM packages to a single global installation with zero external dependencies. Configuration files reduced from 15+ to one central `site.yaml`.\n\n**From mixed concerns to architectural clarity** - Business logic lives in pure JavaScript modules. Content lives in Markdown files. Design lives in CSS. Structure lives in semantic HTML. Each layer works independently and can scale without affecting others.\n\n**From framework lock-in to web standards** - Your forms use `<form>` elements. Your buttons use `<button>` elements. Your navigation uses `<nav>` elements. The browser understands your application natively. No hydration, no virtual DOM, no framework abstractions between you and the platform.\n\n**From slow to instant feedback loop** - Development builds take milliseconds instead of seconds. Hot reload works across all assets - frontend, backend, and database changes. The feedback loop becomes immediate.\n\n### What you gained\n\n**Maintainability** - Clear separation of concerns makes the codebase easier to understand and modify. New team members can contribute immediately using skills they already have.\n\n**Performance** - Order of magnitude improvements in bundle size, build speed, and runtime performance. Your entire application weighs less than a single React component.\n\n**Future-proofing** - Web standards evolve slowly and deliberately. HTML, CSS, and JavaScript knowledge stays relevant for decades. Your investment compounds over time.\n\n**Team velocity** - Designers control visual language through CSS without touching components. Content creators work independently through Markdown. Developers focus on business logic and structure. Nobody blocks anyone else.\n\n\n\n"
  },
  {
    "path": "packages/www/docs/minimalism.md",
    "content": "# Minimalism\nThe best solutions are often the simplest ones. While the web development ecosystem has grown increasingly complex, Nue returns to first principles: do one thing well, work together seamlessly, stay small and focused.\n\n## The dependency hell\nModern web development has lost touch with simplicity. A fresh Next.js project created with `create-next-app` weighs 427MB just to display \"Hello, World.\" That's eight times the size of Windows 95. Add a component library like ShadCN and you're at 470MB. Include content processing with MDX and you reach 550MB - over 500 times larger than what Nue needs for the same functionality.\n\nThis didn't happen overnight. Each package solved a real problem, but we stopped asking whether the cure was worse than the disease. We normalized the idea that thousands of dependencies are simply the cost of modern development. We accepted that `node_modules` would become a black hole of complexity that no one fully understands.\n\nThe absurdity becomes clear when you step back. We need hundreds of megabytes and thousands of files to serve HTML, CSS, and JavaScript - the same technologies that built the web with kilobytes for decades. Something fundamental went wrong.\n\n## The 1MB ecosystem\nNue provides a complete full-stack development environment in 1MB. This includes everything you need: dev server with hot reloading, static site generator, component system, backend layer with CloudFlare compatibility, SQL and KV databases, API routing, testing framework, TypeScript transpiling, CSS processing, and production optimization.\n\nThis follows **Dieter Rams**' principle: **less, but better**. When every piece is designed to work together from the ground up, you eliminate the redundancy that comes from assembling disparate packages. No duplicate parsers, no competing abstractions, no compatibility layers.\n\nThe UNIX philosophy applies to web development: write programs that do one thing well and work together. Nue follows this principle - each tool focused, all tools integrated, everything working on web standards.\n\nZero external dependencies means zero supply chain vulnerabilities, zero version conflicts, zero time resolving package compatibility issues. The entire system is comprehensible and under your control.\n\n\n## Less is more\n**Less is More.** This simple idea changed architecture forever. Rather than adding more ornament, more decoration, more complexity, **Mies van der Rohe** leaned on universal building blocks that could deliver simple, beautiful, timeless spaces.\nWeb development needs the same revolution.\n\nInstead of adding more layers, more libraries, more API methods, we need better ways to reach elegant, lasting solutions. Just like Mies found the essential elements—steel, glass, open space—that could create any building, Nue takes the core web standards—HTML, CSS, JavaScript—to create uncompromising results.\n\n\n> So einfach wie möglich, koste es, was es wolle.\nAs simple as possible, whatever the cost\nLudwig Mies van der Rohe\nGerman Architect\n1886 – 1969\n"
  },
  {
    "path": "packages/www/docs/nuedom.md",
    "content": "\n# **Nuedom:** HTML first UI assembly\nNuedom (or just \"Nue\") is a markup language that extends HTML with just enough syntax to build websites, apps and SVG images. It's a different development model based on document structure rather than programmatic composition.\n\n## UI assembly\nNue differs from React \"composition\" in both syntax and architecture:\n\n**HTML over JavaScript** - In Nue, your UI is a document tree with data bindings and event listeners. In React, it's JavaScript functions returning objects. This difference changes the way you think about application structure.\n\n**Standards first** - `<dialog>`, `<details>`, `<popover>`, form validation, scroll-snap, container queries. Modern HTML is interactive. Nue adds the missing pieces for dynamic behavior.\n\n**DOM based** - Nue's AST maps directly to DOM operations. No virtual DOM, no reconciliation. A button is a `<button>`, not a \"<Button>\" component with 50KB of dependencies and hundreds of megabytes of runtime.\n\n### How it looks\n\n```html\n<!doctype html>\n\n<!-- form with native validation -->\n<form :onsubmit=\"submit\">\n  <input type=\"email\" name=\"email\" required>\n  <textarea name=\"message\" minlength=\"10\" required></textarea>\n  <button>Send</button>\n\n  <script>\n    async submit(e) {\n      await fetch('/api/contact', {\n        body: new FormData(e.target),\n        method: 'POST'\n      })\n      success.showPopover()\n    }\n  </script>\n</form>\n\n<!-- native dialog for success -->\n<dialog id=\"success\" popover>\n  <h2>Message sent!</h2>\n  <button popovertarget=\"success\">Close</button>\n</dialog>\n```\n\nNo validation library. No form state management. No modal component. No orchestration code. The browser provides everything through HTML - validation, form data serialization, popover control. The document structure IS the application architecture.\n\n## Server and client\nNue runs seamlessly on server and client:\n\n**Server-side rendering** - Generate static HTML with Nue's template engine. For content sites, blogs, documentation.\n\n**Single-page applications** - Build fully dynamic SPAs with client-side routing and state management. The same syntax, the same mental model.\n\n**Hybrid applications** - Start with SSR for fast initial loads and SEO. Add client-side interactivity where needed.\n\nThe same `.html` file works as a server template and a client component. No special markers, no \"use client\" directives, no hydration. Write once, run anywhere.\n\n## Architectural constraints\nNue enforces [separation of concerns](/docs/separation-of-concerns) through built-in constraints:\n\n**No style blocks** - CSS belongs in `.css` files, not embedded in HTML. Style blocks are stripped during processing.\n\n**No inline styles** - The `style` attribute is ignored completely. All presentation decisions happen in your [design system](/docs/design-systems).\n\n**Class name limits** - Maximum 3 class names per element (configurable). This prevents utility class bloat and forces systematic design thinking.\n\n**Clean class syntax** - Class names must be valid CSS identifiers. No colons, no special characters, no framework artifacts.\n\nThese constraints aren't limitations - they're guardrails toward maintainable architecture. When you can't mix concerns, you're forced to separate them properly. The result is cleaner code that scales better and breaks less often.\n\n## In-browser compilation\nNue compiles directly in the browser with a 2KB compiler - no build tools, no Babel, no webpack:\n\n```html\n<!doctype html>\n\n<head>\n  <title>Nue In-browser compilation</title>\n  <script src=\"//esm.sh/nuedom\" type=\"module\"></script>\n</head>\n\n<template>\n  <button :onclick=\"count++\">\n    Count: <b>{ count }</b>\n\n    <script>\n      this.count = 0\n    </script>\n  </button>\n</template>\n```\n\nSave as HTML, open on localhost (http protocol needed for modules). The component auto-mounts and runs.\n\n[View live demo](/docs/examples/nue-counter)\n\nNue syntax is simple enough to compile in real-time without massive toolchains. Compare this to React's Babel transform that requires hundreds of megabytes of dependencies just to turn JSX into JavaScript.\n\n## Installation\nFor real projects, use Nuekit for the full development experience:\n\n```bash\nbun install --global nuekit\n```\n\nOr install Nue directly as a library:\n\n```bash\nbun install nuedom\n\n## try the demo with in-browser compilation\nbun bin/serve\n```\n\nFor experiments and prototypes.\n\nSee the [HTML syntax reference](/docs/html-syntax) for the complete API.\n\n"
  },
  {
    "path": "packages/www/docs/nueglow.md",
    "content": "\n# **Nueglow:** CSS first syntax highlighting\nNueglow is syntax highlighting that works with your design system. It generates semantic HTML that your CSS can style. One minuscule highlighter for all languages.\n\n[image]\n  caption: 30+ languages highlighted. Click for standalone demo.\n  large: img/glow-dark-big.png\n  small: img/glow-dark.png\n  href: /glow-demo/\n\n\n## The highlighter problem\nPopular syntax highlighters can be troublesome:\n\n**Massive codebases** - tools like Shiki ship 14MB and 44 packages. Each language needs its own grammar file with thousands of cryptic regex rules.\n\n**Theme lock-in** - Themes come as giant JSON files with 300+ predefined colors. You can't easily adapt them to your brand or design system.\n\n**Grammar complexity** - HTML grammar alone has 2000+ lines of regex patterns. Adding a new language means wiring another massive grammar file.\n\nThese syntax highlighters were built for code editors, not websites. They assume you want their themes and their markup. Your design system becomes an afterthought.\n\n\n## Why Nueglow\n\n**Universal language support** - Works with JavaScript, TypeScript, Python, HTML, CSS, YAML, JSON, Markdown, Bash, SQL, and virtually any other syntax. No grammar files needed.\n\n**Language-agnostic parsing** - Instead of per-language rules, Nueglow recognizes common patterns: strings, comments, keywords, operators. This works across all programming languages.\n\n**Semantic HTML output** - Keywords become `<b>`, strings become `<em>`, comments become `<sup>`. Your CSS controls the appearance.\n\n**Design system friendly** - Only 10-15 HTML elements to style. Fits naturally into any design system without fighting existing patterns.\n\n**Lightweight** - Complete highlighting for all languages in under 3KB of CSS. No JavaScript runtime, no theme bundles.\n\n[image]\n  small: img/glow-light.png\n  large: img/glow-light-big.png\n  href: /glow-demo/light.html\n  caption: Light mode styling across 30+ languages\n\n## How it works\n\nNueglow generates clean, semantic HTML:\n\n```html\n<pre>\n  <code language=\"javascript\">\n    <b>const</b> total <i>=</i> items<i>.</i><strong>reduce</strong><i>((</i>sum<i>,</i> item<i>)</i> <i>=></i> <i>{</i>\n      <b>return</b> sum <i>+</i> item<i>.</i>price\n    <i>},</i> <em>0</em><i>)</i>\n  </code>\n</pre>\n```\n\nYour CSS defines the appearance:\n\n```css\npre {\n  /* Keywords */\n  b { color: var(--keyword) }\n\n  /* Values */\n  em { color: var(--value) }\n\n  /* Punctuation */\n  i { color: var(--punct) }\n\n  /* Comments */\n  sup { color: var(--comment) }\n}\n```\n\nChange your color variables and every code block updates instantly. No theme switching, no configuration files.\n\n## Features\n\n**Line highlighting** - Mark specific lines as added, removed, or emphasized\n\n**Region selection** - Highlight code sections with bullet markers\n\n**Line numbers** - CSS counter-based numbering that matches your typography\n\n**Error marking** - Visual indicators for syntax errors or problems\n\n**Language detection** - Automatic recognition from fenced code block languages\n\n**Custom classes** - Add specific styling for different contexts\n\n## Installation\n\nGet started with Nueglow through Nuekit for the full development experience:\n\n```bash\nbun install --global nuekit\n```\n\nOr use Nueglow outside the Nue ecosystem with your own site generator:\n\n```bash\nbun install nueglow\n```\n\nSee the [Syntax highlighting reference](syntax-highlighting) for complete documentation of all features and styling options."
  },
  {
    "path": "packages/www/docs/nuekit.md",
    "content": "\n# **Nuekit:** Standards first web framework\nNuekit offers a complete client/server development environment for content sites and single-page applications in a single, 1MB executable. It takes HTML, CSS, and JavaScript to their absolute peak.\n\n\n## Rapid application assembly\nNuekit separates applications into structure and system layers. Applications contain only HTML and Markdown - the skeletal framework of your interface. The system layer handles design, behavior, data, and business logic. This separation enables assembly-line development where you combine semantic markup with prepared components.\n\n\n### User interfaces\n```html\n<form :onsubmit=\"submit\">\n  <label>\n    <h3>Email</h3>\n    <input name=\"email\" type=\"email\" autofocus class=\"fullsize\">\n  </label>\n  \n  <button>Sign In</button>\n  \n  <script>\n    async submit(e) {\n      await login(e.target.email.value)\n      location.href = '/dashboard'\n    }\n  </script>\n</form>\n```\n\nSemantic HTML with minimal reactive syntax. Business logic imported from the system layer. Design handled by CSS classes from your design system.\n\n### Single-page apps  \n```html\n<!doctype dhtml>\n\n<body>\n  <nav>\n    <a href=\"/users\">Users</a>\n    <a href=\"/settings\">Settings</a>\n  </nav>\n  \n  <main>\n    <article/>\n  </main>\n  \n  <script>\n    state.on('route', ({ route }) => {\n      this.mount(route || 'dashboard', 'article')\n    })\n  </script>\n</body>\n```\n\nURL-based routing with automatic navigation. Components mount based on state changes. Standard HTML links become SPA navigation automatically.\n\n### Content and pages\n```md\n---\ntitle: Getting Started\nsections: [hero, features, testimonials]\n---\n\n[.hero]\n  # Transform your workflow\n  Build consistent interfaces without the complexity\n\n  [button \"Get Started\" href=\"/docs\"]\n\n[.features]\n  ## Design System First\n  Central CSS controls all visual decisions\n  \n  ## Component Assembly  \n  Mix and match prepared components\n```\n\nRich content creation with layout blocks. Design system classes provide consistent styling. Content creators work independently without breaking builds.\n\n\n### SVG development\n\n```xml\n<!--\n  @include [base.css, table.css]\n-->\n<svg width=\"400\" height=\"300\">\n  <rect :each=\"item in data\" \n    width=\"{ item.width }\" \n    height=\"{ item.height }\"\n    class=\"bar { item.status }\"/>\n    \n  <html>\n    <table>\n      <tr :each=\"row in tableData\">\n        <td>{ row.label }</td>\n      </tr>\n    </table>\n  </html>\n</svg>\n```\n\nDevelop SVG visuals with design system integration and embedded HTML. Live-reload from `my-graphic.svg?hmr` for instant updates to data, CSS, and markup without browser reload.\n\n\n## HMR everything\nTraditional development requires constant context switching. Edit Markdown or CSS, then reload page. Change layout, lose form state. Update server routes, restart everything. Nuekit maintains your development flow across every type of change.\n\n**Immediate updates**: Content changes update through DOM diffing while preserving scroll position and form state. CSS modifications inject directly without page reloads. Component library updates cascade to all pages using them.\n\n**Full stack**: Server route changes reload instantly without restarting the development server.\n\n**Creative flow**: When the feedback loop disappears, development becomes exploration. Try design variations instantly. Test content changes without losing context. The creative process stays unbroken.\n\n\n## Installation\nCheck [Getting started](getting-started) for details.\n"
  },
  {
    "path": "packages/www/docs/nuemark-syntax.md",
    "content": "\n# Nuemark syntax\nComplete reference for Nuemark's content-first markup syntax. See the [Nuemark introduction](/docs/nuemark) for an overview.\n\n## File structure\nNuemark files use `.md` extension and can include optional YAML front matter:\n\n```md\n---\ntitle: My Page\ndate: 2024-01-15\ntags: [web, design]\n---\n\n# Page content starts here\n```\n\nFront matter provides metadata accessible to layouts, components, and the build system. All standard YAML types are supported.\n\n## Standard Markdown\nNuemark fully supports standard Markdown syntax:\n\n```md\n# Heading 1\n## Heading 2\n### Heading 3\n\nThis is a paragraph with **bold** and *italic* text, plus `inline code`.\n\n- Unordered list item\n- Another item\n  - Nested item\n\n1. Ordered list\n2. Second item\n\n> Blockquote with multiple lines\n> continues here\n\n[Link text](https://example.com)\n\n![Alt text](image.jpg)\n```\n\n### Code blocks\n\nFenced code blocks with syntax highlighting:\n\n```md\n​```js\nfunction hello() {\n  return \"Hello world\"\n}\n​```\n```\n\nSupported languages include JavaScript, TypeScript, Python, HTML, CSS, and many more through built-in [Nueglow](/docs/nueglow) syntax highlighting.\n\n## Enhanced formatting\nNuemark extends standard Markdown with additional formatting options:\n\n```md\n**bold** or __bold__     → <strong>bold</strong>\n*italic* or _italic_     → <em>italic</em>  \n`code`                   → <code>code</code>\n~strikethrough~          → <s>strikethrough</s>\n\"quoted text\"            → <q>quoted text</q>\n\\|highlighted|            → <mark>highlighted</mark>\n```\n\nThe bullet character `•` provides non-semantic bold:\n\n```md\n•bold text•              → <b>bold text</b>\n```\n\n\n## Headings with attributes\nAdd IDs and classes to headings for styling and linking:\n\n```md\n# Nuemark: Content-first web development { .heroic }\n\n## Nuemark Introduction { #intro }\n\n## How to use Nuemark { #howto.heroic }\n```\n\nGenerates:\n\n```html\n<h1 class=\"heroic\">Nuemark: Content-first web development</h1>\n<h2 id=\"intro\">Nuemark Introduction</h2>\n<h2 id=\"howto\" class=\"heroic\">How to use Nuemark</h2>\n```\n\n## Variables\n\nEmbed dynamic values using curly braces:\n\n```md\nCurrent version: { version }\nPage title: { title }\nAuthor: { author }\n```\n\nVariables can access any data supplied to the context or via front matter.\n\n\n## Sections\nEnable automatic sectioning to wrap content in semantic HTML sections:\n\n```md\n---\nsections: true\n---\n\n# Introduction\nFirst section content...\n\n## Features\nSecond section content...\n\n## Technical Details\nThird section content...\n```\n\nGenerates:\n\n```html\n<article>\n  <section>\n    <h1>Introduction</h1>\n    <p>First section content...</p>\n  </section>\n  <section>\n    <h2>Features</h2>\n    <p>Second section content...</p>\n  </section>\n  <section>\n    <h2>Technical Details</h2>\n    <p>Third section content...</p>\n  </section>\n</article>\n```\n\n### Section classes\n\nAssign classes to sections:\n\n```md\n---\nsections: [hero, features, details]\n---\n```\n\n### Manual sections\n\nUse triple dashes for explicit section breaks:\n\n```md\nFirst section content...\n\n---\n\nSecond section content...\n\n---\n\nThird section content...\n```\n\n\n## Section wrapper\nEnable automatic sectioning to wrap content in semantic HTML sections:\n\n```md\n ---\n section_wrapper: wrap\n ---\n```\n\nGenerates:\n\n```html\n<section>\n  <div class=\"wrap\">\n    <!-- content here -->\n  </div>\n</section>\n```\n\nWrapper allows more fine tuned design control on backgrounds and inner content blocks with max-width setting.\n\n\n## Blocks\n\nCreate structured layouts with block syntax. Any class name from your design system can be used - blocks simply wrap content in a `<div>` with your specified class:\n\n```md\n[.note]\n  ### Important Note\n  This content is wrapped in a div with class \"note\"\n```\n\nGenerates:\n\n```html\n<div class=\"note\">\n  <h3>Important Note</h3>\n  <p>This content is wrapped in a div with class \"note\"</p>\n</div>\n```\n\nThe class name is entirely up to you - use whatever makes sense for your design system:\n\n```md\n[.warning]          → <div class=\"warning\">...</div>\n[.testimonial]      → <div class=\"testimonial\">...</div>\n[.pricing-tier]     → <div class=\"pricing-tier\">...</div>\n[.photo-gallery]    → <div class=\"photo-gallery\">...</div>\n```\n\n### Nested divs\n\nBlocks automatically create nested divs based on your content structure. The first heading level encountered determines how content is grouped:\n\n```md\n[.features]\n  ### Feature One\n  First feature description\n\n  ### Feature Two\n  Second feature description\n```\n\nSince the first heading is `h3`, each `h3` creates a new nested div:\n\n```html\n<div class=\"features\">\n  <div>\n    <h3>Feature One</h3>\n    <p>First feature description</p>\n  </div>\n  <div>\n    <h3>Feature Two</h3>\n    <p>Second feature description</p>\n  </div>\n</div>\n```\n\nYou can also use triple dashes (`---`) to explicitly create nested divs:\n\n```md\n[.testimonials]\n  \"Great product!\"\n  - Sarah Chen\n\n  ---\n\n  \"Changed our workflow\"\n  - Michael Park\n```\n\nGenerates:\n\n```html\n<div class=\"testimonials\">\n  <div>\n    <p>\"Great product!\"</p>\n    <p>- Sarah Chen</p>\n  </div>\n  <div>\n    <p>\"Changed our workflow\"</p>\n    <p>- Michael Park</p>\n  </div>\n</div>\n```\n\n### Common patterns\n\nWhile you can use any class name, here are some commonly used patterns:\n\n**Grid layouts** - For responsive multi-column layouts:\n\n```md\n[.grid]\n  ### Feature One\n  First feature description\n\n  ### Feature Two\n  Second feature description\n\n  ### Feature Three\n  Third feature description\n```\n\n**Stack layouts** - For vertical arrangements with consistent spacing:\n\n```md\n[.stack]\n  ### Design\n  Focus on systematic design\n\n  ### Engineering\n  Built for performance\n\n  ### Content\n  Pure content structure\n```\n\nThese work because your CSS defines how `.grid` and `.stack` behave. Nuemark just provides the structure - your design system controls the presentation.\n\n### Nested blocks\n\nBlocks can be nested:\n\n```md\n[.feature]\n  ## Main Feature\n  Feature description\n\n  [.grid]\n    ### Sub-feature A\n    Description A\n\n    ### Sub-feature B\n    Description B\n```\n\n## Tag syntax\n\nTags extend Nuemark with rich components using square bracket syntax:\n\n```md\n[tagname options]\n```\n\n### Option formats\n\n**Named attributes:**\n```md\n[image src=\"photo.jpg\" alt=\"Description\" loading=\"eager\"]\n```\n\n**Plain values:**\n```md\n[image photo.jpg]\n```\n\n**Nested YAML:**\n```md\n[image]\n  src: photo.jpg\n  alt: Description\n  caption: Photo caption\n```\n\n**ID and classes:**\n```md\n[image#hero.responsive photo.jpg]\n```\n\n### Nested content\n\nTags can include nested content:\n\n```md\n[note]\n  This is nested content that becomes\n  part of the component. Markdown **works** here.\n```\n\n## Built-in tags\n\n### Images\n\nBasic image:\n```md\n[image photo.jpg]\n```\n\nImage with caption:\n```md\n[image photo.jpg]\n  This is the image caption with **markdown** support\n```\n\nResponsive images:\n```md\n[image]\n  small: mobile.jpg\n  large: desktop.jpg\n  alt: Responsive image\n```\n\nImage link:\n```md\n[image photo.jpg]\n  href: /gallery/\n  caption: Click to view gallery\n```\n\n### Videos\n\nBasic video:\n```md\n[video intro.mp4]\n```\n\nVideo with options:\n```md\n[video]\n  src: intro.mp4\n  poster: thumbnail.jpg\n  autoplay: true\n  muted: true\n  loop: true\n```\n\n### Tables\n\nEnhanced table syntax:\n```md\n[table]\n  Name     | Email              | Role\n  Alice    | alice@example.com  | Developer\n  Bob      | bob@example.com    | Designer\n```\n\nTable with caption:\n```md\n[table caption=\"Team Members\"]\n  Name     | Email              | Role\n  ------\n  Alice    | alice@example.com  | Developer\n  Bob      | bob@example.com    | Designer\n  ------\n  Total: 2 team members\n```\n\n### Inline SVG\n\nEmbed SVG icons:\n```md\nContinue reading [svg /icons/arrow-right.svg]\n```\n\n## Accordions\n\nCreate collapsible content sections with the accordion tag, perfect for FAQs or any content that benefits from progressive disclosure:\n\n```md\n[accordion]\n  ## First Question\n  Answer to the first question\n\n  ## Second Question\n  Answer to the second question\n\n  ## Third Question\n  Answer to the third question\n```\n\nGenerates semantic HTML using native `<details>` and `<summary>` elements:\n\n```html\n<div>\n  <details>\n    <summary>First Question</summary>\n    <p>Answer to the first question</p>\n  </details>\n  <details>\n    <summary>Second Question</summary>\n    <p>Answer to the second question</p>\n  </details>\n  <details>\n    <summary>Third Question</summary>\n    <p>Answer to the third question</p>\n  </details>\n</div>\n```\n\nLike blocks, accordions create sections based on the first heading level encountered or section separator (`---`).\n\n\n### Accordion options\n\n**name** - Groups accordions so only one can be open at a time:\n```md\n[accordion name=\"faq\"]\n```\n\nWhen accordions share the same name, opening one automatically closes others in the group.\n\n**open** - Sets initial state:\n```md\n[accordion open]        # First item open by default\n[accordion open=\"2\"]    # Second item open by default\n```\n\n## Footnotes\n\nStandard footnote syntax:\n```md\nThis needs clarification[^1].\n\n[^1]: This is the footnote content.\n```\n\nNamed footnotes:\n```md\n[Separation of Concerns][^soc] is fundamental.\n\n[^soc]: Keeping HTML, CSS, and JavaScript separate.\n```\n\nDefine footnotes with description lists:\n```md\n[define]\n  ## Term One { #term1 }\n  Definition of term one\n  \n  ## Term Two { #term2 }\n  Definition of term two\n```\n\n## Custom components\nDevelopers can create custom tags that content authors use naturally. Components are defined in HTML files:\n\n```html\n<!doctype html lib>\n\n<!-- Button component -->\n<a :is=\"button\" class=\"button { class }\" href=\"{ href }\">\n  { label || _ }\n</a>\n\n<!-- Card component -->\n<div :is=\"card\" class=\"card { type }\">\n  <h3>{ title }</h3>\n  <slot/>\n  <footer :if=\"footer\">{ footer }</footer>\n</div>\n```\n\nUse in Markdown:\n```md\n[button \"Get Started\" href=\"/docs/\"]\n\n[card type=\"feature\"]\n  title: Key Feature\n  footer: Learn more\n  \n  This is the card content with full\n  **Markdown** support.\n```\n\n### Component properties\n\nComponents receive:\n- **Named attributes** from the tag\n- **Unnamed attribute** via `_`\n- **Nested markdown as HTML** via `<slot/>` tag\n- **Page metadata** from front matter\n- **Site data** via .yaml files\n\n\n\n"
  },
  {
    "path": "packages/www/docs/nuemark.md",
    "content": "\n# **Nuemark:** Content first web development\nNuemark is a Markdown-based authoring format for rich, interactive content. It places content at the heart of web development, delivering a true content-first approach where writing comes before coding, and structure drives presentation.\n\n[image]\n  large: img/nuemark-content-big.jpg\n  small: img/nuemark-content.png\n  size: 650 x 1321\n\n\n## The problem with content\nModern tooling poses challenges to content development:\n\n**JavaScript frameworks trap content in code**. A blog post becomes a React component. A landing page requires TypeScript knowledge. Marketing teams wait for developers to update copy. Content lives inside JavaScript bundles, invisible to search engines without complex hydration strategies.\n\n**Plain Markdown is too limited**. Originally designed for converting text to HTML emails, standard Markdown lacks the structures modern websites need. No layouts. No responsive images. No interactive elements. You quickly hit the ceiling and resort to raw HTML.\n\n**MDX mixes concerns**. It promises rich content but delivers mixed JavaScript. Import statements, JSX components, and business logic tangled with prose. Non-technical team members can't safely edit content without breaking the build.\n\n\n## Why Nuemark\nNuemark extends Markdown with purpose-built syntax for modern web development while keeping content pure and accessible to everyone.\n\n**Rich document structures** - Automatic sections, grids, stacks, and columns through clean, indentation-based syntax. No div/span soup, nor CSS classes mixed in your content.\n\n**Built-in components** - Responsive images, videos, tables, and expandable content work out of the box.\n\n**Full Markdown compatibility** - Everything from standard Markdown works, plus commonly needed extensions like tables, footnotes, and syntax highlighting.\n\n**Extensible architecture** - Developers create custom tags that content authors use naturally. Define once, use everywhere. Components receive arguments, data, and nested content without exposing implementation details.\n\n**Structured data access** - Parse and query document structure, headings, and front matter metadata programmatically. [Nuekit](nuekit) uses this internally for table of contents generation and content collections.\n\n\n## How it looks\nContent authors write naturally, focusing on structure and meaning:\n\n```md\n[.hero]\n  # Content is king\n  Web design is 95% typography\n\n  [button \"Learn more\" href=\"/docs/\"]\n  [button.primary \"Get started\" href=\"/get-started/\"]\n\n  ---\n  [image typography.png]\n```\n\nThis generates semantic HTML your design system can style:\n\n```html\n<section class=\"hero\">\n  <div>\n    <h1>Content is king</h1>\n    <p>Web design is 95% typography</p>\n    <div>\n      <a class=\"button\" href=\"/docs/\">Learn more</a>\n      <a class=\"button primary\" href=\"/get-started/\">Get started</a>\n    </div>\n  </div>\n  <div>\n    <figure>\n      <img src=\"typography.png\" loading=\"lazy\">\n    </figure>\n  </div>\n</section>\n```\n\nClean input, clean output. No framework markup, no hydration markers, no client-side JavaScript.\n\n\n## Content first\nNuemark transforms how content-focused applications are built. With Nuemark and a solid design system, content teams work independently on marketing pages, documentation, and blogs without touching code.\n\nWriters focus on content, not implementation. They use familiar Markdown with prepared components like `[.columns]`, `[.stack]` or `[testimonial]`. The design system ensures everything looks right automatically — consistent typography, spacing, colors, and responsive behavior.\n\nThis separation lets everyone work in their expertise. Content teams write. Design systems handle presentation. Developers build capabilities. Nobody blocks anyone else.\n\nNuemark generates predictable, semantic HTML that design systems can rely on. A heading becomes `<h1>`. An image becomes `<figure>`. A quote becomes `<blockquote>`. Your design system styles semantic elements, not arbitrary class names.\n\nThis is why Nuemark exists: to separate content from presentation completely. When done right, both can evolve independently. Update your design system, and thousands of pages update instantly. Scale from ten pages to ten thousand without architectural changes.\n\nSee how this works with [design systems](/docs/design-systems).\n\n\n## Custom extensions\nDevelopers extend Nuemark with custom tags that feel native to content authors:\n\n```html\n<!doctype html lib>\n\n<!-- custom button tag -->\n<a :is=\"button\" class=\"button { class }\" href=\"{ href }\">{ label || _ }</a>\n\n<!-- testimonial tag -->\n<blockquote :is=\"testimonial\">\n  <p>{quote}</p>\n  <footer>\n    <cite>{author}</cite>\n    <span>{role} at {company}</span>\n  </footer>\n</blockquote>\n```\n\nContent authors use it naturally:\n\n```md\n[testimonial]\n  quote: This changed how our entire team works\n  author: Sarah Chen\n  role: VP of Marketing\n  company: Example Corp\n```\n\n\n## Installation\nGet started with Nuemark through Nuekit for the full development experience:\n\n```bash\nbun install --global nuekit\n```\n\nOr use Nuemark outside the Nue ecosystem on your own site generators\n\n```bash\nbun install nuemark\n```\n\nSee the [Nuemark syntax reference](/docs/nuemark-syntax) for complete documentation of all features and extensions.\n\n"
  },
  {
    "path": "packages/www/docs/nueserver.md",
    "content": "\n# **Nueserver:** Edge first development\nNueserver is an HTTP server built for edge deployment. Write code locally, deploy globally when ready.\n\n> **Disclaimer** Nueserver currently works for local development only. It's the foundation for Nue's upcoming backend vision. See the [roadmap](roadmap) for details\n\n\n## What is edge first\nMost web frameworks treat edge deployment as an afterthought. You develop with Node locally, then discover your code doesn't work at the edge. You build with traditional databases, then learn edge can't maintain connections.\n\nEvery Next.js developer knows this journey:\n\n**NPM everywhere** - Your `npm run dev` spins up a Node.js server. Full runtime, any npm package, unlimited memory. You install packages freely: bcrypt, sharp, mongoose.\n\n**Database connections** - You connect to Postgres or MySQL. Connection pooling handles load. Prisma makes queries elegant. Your `.env.local` has `DATABASE_URL` pointing to localhost.\n\n**Production reality** - You deploy and things break. Replace bcrypt with Web Crypto? Swap sharp for browser-compatible alternatives? Abandon your ORM for raw SQL? Set up Postgres edge proxies?\n\nThe problem: you develop one way and deploy another. Your simple app turns complex. Nueserver flips this. Your development environment uses edge-compatible patterns from day one.\n\n\n## How it works\nNueserver provides a simple HTTP server with global route handlers. No classes, no imports, no server setup:\n\n```javascript\nget('/api/users', async (c) => {\n  const users = await c.env.users.getAll()\n  return c.json(users)\n})\n\npost('/api/users', async (c) => {\n  const data = await c.req.json()\n  const user = await c.env.users.create(data)\n  return c.json(user, 201)\n})\n\nuse('/admin/*', async (c, next) => {\n  const auth = c.req.header('authorization')\n  if (!auth) return c.json({ error: 'Unauthorized' }, 401)\n  await next()\n})\n```\n\nWhen working with Nuekit CloudFlare headers are mocked locally for edge-compatible development:\n\n```javascript\npost('/api/contact', async (c) => {\n  const country = c.req.header('cf-ipcountry')\n  const ip = c.req.header('cf-connecting-ip')\n\n  const data = await c.req.json()\n  return c.json({ ...data, country, ip })\n})\n```\n\nWhen deployment arrives, these headers provide real geolocation and network data at the edge.\n\n\n## Design principles\nNueserver draws inspiration from Hono's clean API while with the follwing differences:\n\n**Global methods** - No Hono imports or server exports. Use `get()`, `post()`, or `use()` directly on the code.\n\n**No HTML responses** - Use `c.json()` and `c.text()` only. HTML generation belongs in the frontend.\n\n**No file serving** - Static assets are handled by the build system, not the server layer. Each concern stays in its domain.\n\n**No complex routing** - Simple patterns that map to CloudFlare's routing capabilities. No regex routes, no complex parameter validation.\n\n**No middleware chaining complexity** - Linear middleware execution with explicit `next()` calls. Predictable flow, easy debugging.\n\nThis focused API makes the server layer predictable and portable. Your HTTP logic stays clean while other layers handle their specific concerns.\n\n\n\n## Installation\n\nFor real projects, use Nuekit for the full development experience:\n\n```bash\nbun install --global nuekit\n```\n\nOr install Nueserver directly as a library:\n\n```bash\nbun install nueserver\n```\n\nSee the [Server API reference](server-api) for complete routing and context documentation.\n"
  },
  {
    "path": "packages/www/docs/nuestate.md",
    "content": "\n# **Nuestate:** URL-first state management\nNuestate puts your application state in the URL by default. This makes bookmarking, sharing, and browser navigation work naturally without extra code. State changes automatically update the URL and trigger component re-renders.\n\nThe library provides a simple `state` proxy object for reading and writing application state directly. Changes are automatically persisted to the URL, browser storage, or kept in memory based on your configuration.\n\n\n## Why URL-first?\nMost state management solutions treat the URL as an afterthought. You have to manually sync state with the URL, handle browser navigation, and write extra code for bookmarking and sharing.\n\nNuestate flips this around. Your state lives in the URL by default, so these features work automatically:\n\n**Bookmarking works** - Users can bookmark any application state and return to it later\n\n**Sharing works** - Send someone a URL and they see exactly what you see\n\n**Browser navigation works** - Back/forward buttons navigate through state changes\n\n**Standard routing works** - Regular `<a href>` tags become SPA navigation with `autolink`\n\n**No sync code** - No need to manually keep URL and state in sync\n\n## How it works\n\nImport and use the state object anywhere in your application:\n\n```javascript\nimport { state } from 'state'\n\n// Read and write state\nstate.view = 'users'     // URL updates to include view=users\nstate.search = 'john'    // URL becomes ?view=users&search=john\n```\n\nConfigure where different pieces of state should live:\n\n```javascript\nstate.setup({\n  route: '/app/:section/:id',\n  query: ['search', 'filter', 'page'],\n  session: ['user', 'preferences'],\n  local: ['theme', 'language']\n})\n\n// Route parameters update the URL path\nstate.section = 'products'\nstate.id = '123'\n// URL becomes: /app/products/123\n\n// Query parameters update the URL search\nstate.search = 'shoes'\n// URL becomes: /app/products/123?search=shoes\n```\n\nListen to state changes:\n\n```javascript\nstate.on('search filter', async (changes) => {\n  const results = await fetchResults(changes.search, changes.filter)\n  state.results = results\n})\n```\n\nUse state directly in components with standard DOM events:\n\n```html\n<input value=\"{ state.search }\" :oninput=\"state.search = $event.target.value\">\n```\n\nSee the [State API documentation](/docs/state-api) for complete details on all methods and configuration options.\n\n## Standard routing\n\nNuestate turns regular HTML links into SPA navigation with the `autolink` option:\n\n```javascript\nstate.setup({\n  route: '/app/:section/:id',\n  autolink: true\n})\n```\n\nNow standard `<a href>` tags automatically update state instead of reloading the page:\n\n```html\n<!-- These work as SPA navigation -->\n<a href=\"/app/users\">Users</a>\n<a href=\"/app/users/123\">User Details</a>\n<a href=\"/app/products\">Products</a>\n\n<!-- External links still work normally -->\n<a href=\"https://example.com\">External Site</a>\n```\n\nWhen someone clicks `/app/users/123`, Nuestate automatically sets:\n- `state.section = 'users'`\n- `state.id = '123'`\n\nNo routing libraries, no special components. Just HTML links that work exactly as you'd expect, but faster.\n\n## Storage types\n\nDifferent storage types serve different purposes:\n\n**URL parameters** - For shareable, bookmarkable state that defines what the user sees\n\n**Session storage** - For user-specific data that should persist during the browser session\n\n**Local storage** - For user preferences that should persist across sessions\n\n**Memory** - For temporary data and UI state that doesn't need persistence\n\nChoose the right storage type based on how long the data should live and whether it should be shareable.\n\n## Less is More\n\nNuestate keeps both your API surface and your applications small:\n\n**Small API** - Just read and write to the state object. No stores, reducers, actions, or complex patterns to learn.\n\n**Less boilerplate** - No manual URL synchronization code. No setup for browser navigation. No extra logic for bookmarking and sharing.\n\n**Smaller apps** - Under 2KB with zero dependencies. Your total bundle stays small when state management doesn't bloat it.\n\n**Less to learn** - State works like any JavaScript object. If you understand `obj.prop = value`, you understand Nuestate.\n\nThe library does one thing well: manage application state with URL synchronization built in. Like a UNIX command.\n\n## Installation\n\n```bash\nbun install nuestate\n```\n\nOr use it directly in the browser:\n\n```html\n<script type=\"module\">\n  import { state } from '//esm.sh/nuestate'\n</script>\n```"
  },
  {
    "path": "packages/www/docs/nueyaml.md",
    "content": "\n# **Nueyaml:** YAML without the problems\nNueyaml is YAML stripped down to its essence so you can write complex configurations without the usual YAML pitfalls.\n\n\n## The problem with standard YAML\nThe original YAML specification buried a beautiful idea under 80 pages of features that cause more problems than they solve. It guesses what you mean, often guessing wrong:\n\n```yaml\n# Standard YAML surprises\ncountry: NO      # becomes false (Norway problem)\ntime: 12:30      # becomes 750 (minutes)\nversion: 1.10    # becomes 1.1 (float)\nport: 08080      # becomes 4176 (octal)\n```\n\nThese \"conveniences\" turn configuration files into minefields. You quote some values defensively, but not others. You remember some gotchas, but not all. Your configuration works until it doesn't.\n\n\n## How Nueyaml fixes it\nNueyaml has one rule: be predictable. If it looks like a string to a human, it's a string:\n\n```yaml\n# Nueyaml - no surprises\ncountry: NO       # string \"NO\"\ntime: 12:30       # string \"12:30\"\nversion: 1.10     # number 1.10\nport: 08080       # string \"08080\"\n```\n\nOnly obvious numbers (`123`, `45.67`) become numbers. Only `true` and `false` become booleans. Everything else stays a string.\n\n\n## Simple type system\nNueyaml supports just the types you actually need:\n\n**Strings** - The default. No quotes needed unless you want them.\n\n**Numbers** - Integers and decimals that look like numbers.\n\n**Booleans** - Only `true` and `false`. Not `yes`, `YES`, `on`, or `True`.\n\n**Dates** - Single ISO format: `2024-01-15` or `2024-01-15T10:30:00Z`\n\n**Null** - Empty values become null.\n\n**Arrays and Objects** - Standard YAML collections.\n\nThat's it. No binary data. No sets. No ordered maps. No custom types. no Norway problem. Just the data types every configuration file needs.\n\n\n## How it looks\nWrite complex configurations without issues:\n\n```yaml\n# API routes with special characters\n/api/users/:id: getUserHandler\n/api/posts: getPostsHandler\n\n# Responsive breakpoints\nmobile: @media (max-width: 768px)\ntablet: @media (max-width: 1024px)\n\n# Multi-line content\ndescription:\n  This is a multi-line string\n  that preserves line breaks\n  exactly as written.\n\n# Mixed data types\nserver:\n  host: localhost\n  port: 8080\n  debug: true\n  started: 2024-01-15T10:30:00Z\n  description:\n```\n\nProperty names can contain any characters. Values are predictable. Multi-line strings just work. No anchors, no references, no merge conflicts.\n\n\n## FAQ\n\n\n### Why YAML?\nConfiguration files should be written for humans, not parsers. Compare these:\n\n```json\n{\n  \"database\": {\n    \"host\": \"localhost\",\n    \"port\": 5432,\n    \"credentials\": {\n      \"username\": \"admin\",\n      \"password\": \"secret123\"\n    }\n  }\n}\n```\n\n```yaml\ndatabase:\n  host: localhost\n  port: 5432\n  credentials:\n    username: admin\n    password: secret123\n```\n\nYAML removes the syntactic noise. No quotes around keys. No brackets. No commas. Just structure through indentation, like Python code or Markdown documents.\n\n\n### Why not TOML?\n\nTOML gets verbose with nested data:\n\n```toml\n[database]\nhost = \"localhost\"\n\n[database.credentials]\nusername = \"admin\"\n\n[[servers]]\nname = \"web-01\"\n\n[[servers]]\nname = \"web-02\"\n```\n\n```yaml\ndatabase:\n  host: localhost\n  credentials:\n    username: admin\n\nservers:\n  - name: web-01\n  - name: web-02\n```\n\nTOML's section headers and dotted keys obscure the structure. Nueyaml's indentation makes hierarchy visible.\n\n### Why not JSON5?\n\nJSON5 still requires quotes and commas everywhere:\n\n```json5\n{\n  \"features\": [\"auth\", \"analytics\"],\n  \"api_key\": \"sk-1234\"\n}\n```\n\n```yaml\nfeatures: [auth, analytics]\napi_key: sk-1234\n```\n\nJSON5 improves JSON but keeps its ceremonial syntax. Nueyaml eliminates the noise.\n\n\n### Is this compatible with YAML?\nYes. Nueyaml is valid YAML, but not all YAML files are valid Nueyaml. We support the useful subset and reject the dangerous parts. Your existing YAML tools can read Nueyaml files, but Nueyaml parsers reject complex YAML features.\n\n\n### What about existing YAML files?\nMost real-world YAML files already follow Nueyaml's restrictions. They don't use anchors, tags, or multiple date formats. They're already Nueyaml-compatible. The spec just makes it official.\n\n## Installation\n\n```bash\nbun install nueyaml\n```\n\nParse configuration files:\n\n```javascript\nimport { parse } from 'nueyaml'\n\nconst config = parseYAML(yamlString)\n```\n\nSee the [YAML syntax reference](/docs/yaml-syntax) for complete documentation of supported features.\n\n"
  },
  {
    "path": "packages/www/docs/page-dependencies.md",
    "content": "\n# Page dependencies\nHow Nue automatically discovers and includes files for each page.\n\n## Convention-based loading\nNue uses directory-based conventions to determine which CSS, JavaScript, HTML components, and data files each page needs. Unlike bundlers that require explicit imports, files are included automatically based on their location in your project structure.\n\nThis system eliminates import statements while ensuring pages get exactly what they need. Nue projects feel like organized file systems rather than complex dependency graphs.\n\n\n## Directory hierarchy\nAssets are loaded in this order of precedence:\n\n### Root level\nGlobal assets that apply to every page:\n```\nglobal.css          # Site-wide styles\napp.js              # Global JavaScript\nsite.yaml           # Site data\n```\n\n### App level\nArea-specific assets that apply to pages within that directory:\n\n```\nblog/\n├── blog.css        # Only applies to blog pages\n├── layout.html     # Blog-specific layouts\n└── data.yaml       # Blog data\n\nadmin/\n├── admin.js        # Only applies to admin pages\n└── uilib.html      # Admin UI components\n```\n\n### Page level\nAssets in the same directory as the page:\n\n```\nblog/css-is-awesome/\n├── effects.css     # Only applies to this specific page\n├── awesome.html    # Page-specific components\n└── products.yaml   # Page-specific data\n```\n\nPage-specific directories are rare but allow construction of complex content that needs dedicated assets.\n\n\n## Shared assets\nLarger projects can use @shared directory for globally available assets:\n\n```\n@shared/\n├── ui/          # Auto-included UI components/controllers.\n├── lib/         # Selective UI components/controllers\n├── design/      # Design system CSS files (.css)\n└── data/        # Site-wide data files (.yaml)\n```\n\nThe UI components can operate either on client, on server, or both. The ui and lib directories can contain .html, .js, .ts, and component specific .css files.\n\nSystem-level assets load before root-level assets, establishing the foundation that everything else builds upon.\n\n## Include and exclude\nControl which assets load across your entire site in `site.yaml`:\n\n```yaml\n# Exclude files by name or pattern (applies to all asset types)\nexclude: [apps.css, syntax.css]\n\n# Rarely needed globally since assets are auto-included already\ninclude: []\n```\n\n## App-level overrides\nOverride global settings in app directories with `app.yaml`:\n\n```yaml\n# In admin/app.yaml - only applies to admin pages\nexclude: [ marketing-effects ]\ninclude: [ apps.css ]\n```\n\nPatterns are fuzzy so \"marketing-effects\" would match both marketing-effects.html and marketing-effects.css.\n\nApp-level inclusions and exclusions override global ones (not extend the arrays).\n\n## Single-page applications\nSPA root files (`app/index.html`) automatically include all assets from their directory tree no matter what subdirectories are there:\n\n```\napp/\n├── index.html      # SPA root\n├── app.js          # Included\n└── ui/             # All included\n    ├── user.html\n    ├── users.html\n    └── utils.html\n```\n\nThis ensures SPAs have access to all their components and utilities without explicit configuration."
  },
  {
    "path": "packages/www/docs/project-structure.md",
    "content": "\n# Project structure\nNue projects use a file-based routing system where your directory structure maps directly to your website's URLs. This guide covers how Nue organizes files and scales from simple pages to complex applications.\n\n\n\n## How it works\n\nFiles map directly to URLs:\n\n```\nindex.html          → /\nabout.html          → /about\nblog/index.html     → /blog/\nblog/first-post.md  → /blog/first-post/\n```\n\nAny HTML or Markdown file becomes a page. CSS files become stylesheets. Everything else passes through unchanged.\n\n## Minimal project\n\n```bash\nnue create minimal\n```\n\nCreates:\n\n```\n├── index.css\n└── index.html\n```\n\nA single HTML file and stylesheet - no configuration, no scaffolding, no setup ceremony. Just open `index.html` in a browser and you have a working site. This demonstrates Nue's zero-friction approach: your project structure is your site structure.\n\n\n## Blog\n\n```bash\nnue create blog\n```\nCreates:\n\n```\n├── site.yaml\n├── layout.html       # global header and footer\n├── index.css\n├── index.md\n└── posts/\n    ├── header.html   # page header aka. \"hero\" layout\n    ├── first.md\n    └── second.md\n```\n\nA content-focused site with shared layouts and automatic post collections. The `layout.html` provides common structure, while `posts/` contains your Markdown content. The `site.yaml` configures collections and metadata.\n\n\n\n## Single-page application\n\n```bash\nnue create spa\n```\n\nCreates:\n\n```\n.\n├── css\n│  ├── base.css\n│  └── components.css\n├── index.html\n├── server\n│  ├── data\n│  │  └── users.json\n│  └── index.js\n├── site.yaml\n└── ui\n    └── lib.html\n```\n\nThe `index.html` controls routing and state, while `ui/` contains individual page components. The `server/users.json` becomes a CloudFlare compatible datastore, and `server/index.js` define your routes. See [SPA development](single-page-apps) for details.\n\n\n## Larger projects\n\n```bash\nnue create full\n```\n\nFor serious applications, the `@shared/` directory separates your application's foundation from individual apps. This division enables application \"assembly\" - apps focus purely on structure (HTML/Markdown) while the system handles all other concerns:\n\n```\n├── @shared/           # centralized system\n├── app/               # application pages\n├── blog/              # content areas\n├── contact/\n├── docs/\n├── img/\n├── login/\n├── index.md\n├── 404.md\n└── site.yaml\n```\n\n### System directories\n\nThese directories have fixed names and special behavior:\n\n```\n@shared/\n├── design/           # CSS design system (auto-loaded client-side)\n├── ui/               # UI components. On server & Client. (auto-loaded client-side)\n├── data/             # YAML data for HTML templates (server-side processing)\n└── server/           # Backend code (not frontend assets)\n```\n\n### Recommended directories\nThese client-side directories follow naming conventions but aren't hardcoded:\n\n```\n@shared/\n├── lib/              # Third-party libraries to import\n└── app/              # Business logic / data models (imported)\n```\n\nIt's recommended to add these to import map in site.yaml. For example:\n\n```yaml\n# In site.yaml\nimport_map:\n  app: /@shared/app/index.js\n  lib: /@shared/lib/\n```\n\nThis enables clean imports on your frontend code:\n\n```javascript\nimport { login } from 'app'           // @shared/app/index.js\nimport * as d3 from 'lib/d3'          // @shared/lib/d3.js\n```\n\nWith the system layer handling design, behavior, and logic, application development can focus solely on content and structure. Your system remains simple as your website/business grows.\n\n\n### Application UI folder\nIf your application has several CSS/JS assets, place them in a `ui` folder within the app (like `blog/ui`) instead of cluttering the application root.\n\n### Home folder\nYour home page assets can go in a `home` folder to separate them from root assets that are shared across all applications.\n\n\n## File types\n\n**`.html`** - Pages, components, and layouts\n\n**`.md`** - Content using Nuemark syntax\n\n**`.css`** - Stylesheets (loaded automatically)\n\n**`.js`** - Client-side JavaScript\n\n**`.ts`** - TypeScript (transpiled to JavaScript)\n\n**`.svg`** - Graphics (processed via app.yaml config)\n\n**`.yaml`** - Configuration and data\n\n**`404.md`** or **`404.html`** - Custom error pages\n\n\n## Routing\nFile names determine routing:\n\n```\nindex.html             → /\nabout.md               → /about/\ncontact/index.md       → /contact/\ncontact/thanks.md      → /contact/thanks\napp/index.html         → /app/ (handles all /app/* routes)\nadmin/index.html       → /admin/ (handles all /admin/* routes)\n404.md                 → custom error page\n```\n\nSee [page dependencies](page-dependencies) for details\n\n\n"
  },
  {
    "path": "packages/www/docs/roadmap.md",
    "content": "\n# Roadmap\nOur next major steps:\n\n## Multi-site development\nBuild multiple websites from one shared system. Think of it like having a design system that works across different brands, but for everything - layouts, business logic, and data.\n\n```\n.\n@shared/          # Global design system, components, logic\nacme.org/         # Marketing site\napp.acme.com/     # Web application\nblog.acme.com/    # Company blog\npartners.com/     # Partner portal\nglobal.yaml       # Shared configuration\n```\n\nDevelop all sites with global hot reloading:\n\n```sh\nnue dev --all\n\nacme.org      → http://localhost:4000\napp.acme.com  → http://localhost:4001\nblog.acme.com → http://localhost:4002\npartners.com  → http://localhost:4003\n```\n\nEdit a shared component and watch it update across all running sites. Change the global color scheme and see it cascade everywhere. Add a new API endpoint and it's available to all sites.\n\n\n## Deployments\nDeploy your multi-site setup directly to cloud:\n\n```sh\n# Push all sites (only changed files)\nnue push --all\n\n→  app.acme.com   5 files    [220ms]\n→  blog.acme.com  12 files   [200ms]\n→  partners.com   8 files    [110ms]\n```\n\nOnly the actual changes get **streamed** through one connection. Like everything else in Nue, deployments across multiple websites become almost instant. All `yoursite.nuejs.com` subdomains are free, with paid tiers starting at $2/month for custom domains later on.\n\n\n## Templates\nCreate a professional website instantly from a template:\n\n```sh\n# Early-stage startup with Miesian design language\nnue create mvp --design mies\n\n# Full startup template with Ramsian principles\nnue create startup --design rams\n\n# Blog template with Muriel (Cooper) inspired design\nnue create blog --design muriel\n```\n\nTemplates work seamlessly with multi-site deployment:\n\n```sh\n# Push your new system\nnue push --all\n```\n\n## Analytics application\nWe're building a complete analytics platform. Not a demo, not a prototype - a production application that handles millions of visitors.\n\n**Multi-site tracking** - One dashboard for all your sites. Track acme.org, blog.acme.com, and app.acme.com separately while comparing metrics across your network. Privacy-friendly by design so no consent banners needed.\n\n**Full-stack foundation** - Building analytics forces us to solve the hard problems every business application faces. Event sourcing for high-volume data. Aggregation for historical queries. Client-side processing of cacheable chunks. UI components for charts, customer dashboards, admin panels, and any data-heavy application.\n\n**The ultimate stress test** - Analytics demands scale from day one. Millions of page views generate continuous event streams. Users expect instant visualizations across arbitrary time ranges. Aggregations need to handle months of historical data without sampling.\n\nIf the foundation works here, it works anywhere.\n\n\n## More to come\nWe're building both frontend and backend ecosystems that grow together.\n\n**Universal data model** - Authentication, user accounts, email notifications, mailing lists, payment processing, refunds. The fundamental operations every online business needs, built on the same foundation we develop for analytics.\n\n**Local dev environment** - Generate realistic business scenarios from high-level parameters. A thousand customers with purchase histories. Email campaigns with open rates and conversions. Support tickets with resolution times. Develop against data that looks like production.\n\n**SPA templates** - Pre-built admin interfaces for common business needs. Analytics dashboards, customer relationship management, mailing list administration, payment processing. Each template demonstrates the patterns while giving you a working starting point. Customize the design system, extend the data model, deploy immediately.\n\n\n"
  },
  {
    "path": "packages/www/docs/separation-of-concerns.md",
    "content": "\n# Separation of concerns\nUNIX tools do one thing well. `grep` finds patterns. `sort` orders data. Modern frameworks abandoned this wisdom, creating kitchen sink solutions that mix everything together. Nue returns to architectural clarity.\n\n## The problem we created\nFrontend development is the only ecosystem that abandoned separation of concerns. Server-side developers would never put SQL queries inside CSS files, yet we routinely embed styling in JavaScript components. Backend architects carefully separate data access, business logic, and API layers - but frontend treats this as old-fashioned thinking.\n\nThis cultural shift happened gradually. As JavaScript became more powerful, we started moving everything into it. CSS became CSS-in-JS. HTML became JSX. Business logic became hooks. Content became components. Each concern lost its dedicated domain.\n\nThe result is monolithic components that mix presentation, behavior, data fetching, and business rules. Testing becomes complex because you can't isolate logic from rendering. Collaboration breaks down because designers need to understand JavaScript to change colors. Maintenance becomes expensive because changing one concern affects all the others.\n\n## Full separation\nWeb applications have distinct layers, each requiring different skills and mindsets. Mixing them creates unnecessary complexity.\n\n### Backend\nUniversal CRUD operations for business records. Team, leads, customers, charges, items - the core models every application needs. This layer should be solved once as infrastructure, not rebuilt for each project.\n\n### Business logic\nYour application core. Pure, testable functions that work with data. Think of how Figma and Notion handle this - their engines are built with Rust, completely separate from the UI. Keep HTML and CSS out of your business model.\n\n### Design\nYour visual language belongs in CSS. Colors, typography, spacing - the systematic approach that creates consistency. When design lives in one place, you can rebrand without touching JavaScript.\n\n### Content\nWriters shouldn't need to understand JavaScript to publish content. When content lives in code, engineers become the bottleneck. Content must be separate or scaling becomes impossible.\n\n### Structure\nSemantic HTML that assembles the other layers. Clean markup that browsers understand natively, styled by design systems, populated by business logic, filled with content.\n\n\n\n## Application assembly\nWhen concerns are neatly separated, web development becomes assembly. You can build complex applications with semantic markup alone.\n\n### Content assembly\nContent creators write in Markdown. Designers control presentation through CSS. Layout modules provide structure. The three layers combine automatically - content flows into layouts, layouts inherit design systems, everything renders as clean HTML.\n\nWriters focus on messaging. Designers focus on visual language. Developers focus on functionality. Nobody blocks anyone else because each concern has its own domain and tools.\n\n### UI assembly\nBusiness logic lives in pure JavaScript modules. Design lives in CSS design systems. Structure lives in semantic HTML. Components become thin assembly layers that combine these concerns without owning them.\n\nChange business logic without touching styles. Update the design system without breaking functionality. Restructure components without losing data operations. Each layer evolves independently because dependencies flow in one direction.\n\nNue enforces this pattern. Try to write CSS-in-JS and the system rejects it. Try to load utility classes everywhere and the design system stops you. These constraints aren't limitations - they're guardrails toward maintainable architecture that enable rapid application assembly.\n\n\n"
  },
  {
    "path": "packages/www/docs/server-api.md",
    "content": "# Nueserver API\n[Nueserver](nueserver) is a minimal HTTP server built for edge deployment. Write code using CloudFlare Workers patterns during local development.\n\n\n## Quick start\n\n```javascript\nget('/api/users', async (c) => {\n  return c.json([{ id: 1, name: 'Alice' }])\n})\n\npost('/api/users', async (c) => {\n  const user = await c.req.json()\n  return c.json(user, 201)\n})\n```\n\n## Route handlers\n\n### get(path, handler)\nHandle GET requests:\n\n```javascript\nget('/users', async (c) => {\n  return c.json(users)\n})\n\nget('/users/:id', async (c) => {\n  const id = c.req.param('id')\n  const user = users.find(u => u.id == id)\n  return c.json(user)\n})\n```\n\n### post(path, handler)\nHandle POST requests:\n\n```javascript\npost('/users', async (c) => {\n  const data = await c.req.json()\n  const user = createUser(data)\n  return c.json(user, 201)\n})\n```\n\n### del(path, handler)\nHandle DELETE requests:\n\n```javascript\ndel('/users/:id', async (c) => {\n  const id = c.req.param('id')\n  deleteUser(id)\n  return c.json({ deleted: id })\n})\n```\n\n### use(path, middleware)\nAdd middleware that runs before route handlers:\n\n```javascript\nuse('/admin/*', async (c, next) => {\n  const auth = c.req.header('authorization')\n  if (!auth) return c.json({ error: 'Unauthorized' }, 401)\n  await next()\n})\n\n// Global middleware\nuse(async (c, next) => {\n  console.log(c.req.method, c.req.url)\n  await next()\n})\n```\n\n## Route patterns\n\n### Static routes\n```javascript\nget('/users', handler)\nget('/api/status', handler)\n```\n\n### Parameters\n```javascript\nget('/users/:id', handler)          // /users/123\nget('/posts/:slug/comments', handler) // /posts/hello/comments\n```\n\n### Wildcards\n```javascript\nuse('/admin/*', middleware)         // Matches /admin/users, /admin/settings\nget('/files/*', handler)           // Matches any path under /files\n```\n\n## Context object\n\nEvery handler receives a context object with request and response helpers.\n\n### Request (c.req)\n\n```javascript\nget('/example', async (c) => {\n  // Get route parameters\n  const id = c.req.param('id')\n\n  // Get query parameters\n  const page = c.req.query('page')     // single param\n  const params = c.req.query()         // all params as object\n\n  // Get headers\n  const auth = c.req.header('authorization')\n\n  // Parse request body\n  const data = await c.req.json()      // JSON\n  const text = await c.req.text()      // plain text\n})\n```\n\n### Response helpers (c)\n\n```javascript\nget('/example', async (c) => {\n  // JSON response\n  return c.json({ message: 'Hello' })\n\n  // JSON with status\n  return c.json({ error: 'Not found' }, 404)\n\n  // Text response\n  return c.text('Hello world')\n\n  // Status then JSON\n  return c.status(201).json({ created: true })\n})\n```\n\n### Environment (c.env)\nAccess environment-specific resources. Currently supports CloudFlare headers during development:\n\n```javascript\npost('/contact', async (c) => {\n  // CloudFlare headers (mocked locally)\n  const country = c.req.header('cf-ipcountry')\n  const ip = c.req.header('cf-connecting-ip')\n\n  const data = await c.req.json()\n  return c.json({ ...data, country, ip })\n})\n```\n\nFuture versions will provide business model abstractions through `c.env`:\n\n```javascript\n// Coming: business model primitives\nconst { customers, leads, charges } = c.env\n\nget('/api/customers', async (c) => {\n  const all = await customers.all()\n  return c.json(all)\n})\n```\n\n## Middleware patterns\n\n### Authentication\n```javascript\nuse('/api/*', async (c, next) => {\n  const token = c.req.header('authorization')\n  if (!isValid(token)) {\n    return c.json({ error: 'Invalid token' }, 401)\n  }\n  await next()\n})\n```\n\n### CORS\n```javascript\nuse(async (c, next) => {\n  const response = await next()\n  response.headers.set('Access-Control-Allow-Origin', '*')\n  return response\n})\n```\n\n### Logging\n```javascript\nuse(async (c, next) => {\n  const start = Date.now()\n  const response = await next()\n  console.log(`${c.req.method} ${c.req.url} - ${Date.now() - start}ms`)\n  return response\n})\n```\n\n## Error handling\n\nErrors return 500 automatically:\n\n```javascript\nget('/might-fail', async (c) => {\n  // This error becomes a 500 response\n  throw new Error('Something went wrong')\n})\n```\n\nReturn custom errors:\n\n```javascript\nget('/users/:id', async (c) => {\n  const user = findUser(c.req.param('id'))\n  if (!user) {\n    return c.json({ error: 'User not found' }, 404)\n  }\n  return c.json(user)\n})\n```\n\n## Development workflow\n\nRoutes are global functions. No imports needed:\n\n```javascript\n// Define routes anywhere\nget('/health', async (c) => {\n  return c.json({ status: 'ok' })\n})\n\n// Use middleware\nuse('/admin/*', requireAuth)\n\n// Handle different methods\npost('/webhook', handleWebhook)\ndel('/cache/:key', clearCache)\n```\n\nThe server handles everything automatically. Same code runs in development with `nue serve` and will run in production on CloudFlare Workers when deployment arrives."
  },
  {
    "path": "packages/www/docs/single-page-apps.md",
    "content": "\n# Single-page apps\nSingle-page applications (SPAs) in Nue are dynamic web apps that run entirely in the browser. Unlike content-focused apps that generate static pages, SPAs use client-side routing and state management to create fluid, app-like experiences without page reloads.\n\n\n## Getting started\nCreate a SPA template to see how client-side applications work:\n\n```bash\nnue create spa\n```\n\nThis generates a complete SPA structure:\n\n```\n├── index.html          # SPA entry point\n├── ui/\n|   └── lib.html        # UI components\n├── server/             # Backend (CloudFlare compatible)\n|   ├── index.js        # Hono-based router\n|   └── users.json      # KV datastore\n└── css/                # Design\n```\n\nThe structure separates concerns. Routing and state live in `index.html`. UI components live in `ui/`. Server logic lives in `server/`. Design lives in CSS.\n\n\n## SPA entry point\nThe `index.html` file controls your entire application. When you use `<!doctype dhtml>` with `<body>` scope, Nue automatically makes this file handle all routes within its directory - `/users`, `/settings`, `/dashboard`, or any other path.\n\n```html\n<!doctype dhtml>\n\n<script>\n  import { state } from 'state'\n\n  // Configure routing with URL parameters\n  state.setup({\n    route: '/:id',\n    autolink: true\n  })\n</script>\n\n<body>\n  <main>\n    <article/>\n  </main>\n\n  <script>\n    // update view based on state (URL)\n    state.on('id', ({ id }) => {\n      this.mount(id ? 'user' : 'users', 'article')\n    })\n\n    // initialize from current URL\n    mounted() {\n      state.init()\n    }\n  </script>\n</body>\n```\n\n### How routing works\n\n**SPA detection** - The combination of `<!doctype dhtml>` and `<body>` scope tells Nue this file should handle all routes in its directory. Any URL like `/123` or `/settings` gets routed to this file.\n\n**URL parameters** - The `route: '/:id'` pattern captures URLs like `/123` or `/alice`. When someone visits `/123`, `state.id` becomes `\"123\"`.\n\n**Automatic routing** - With `autolink: true`, regular `<a href=\"/123\">` links update state instead of reloading the page. No special router components needed.\n\n**Dynamic mounting** - The `state.on('id')` listener decides which component to display. If there's an ID, show the `user` component. If not, show the `users` list. See [dynamic mounting](#dynamic-mounting) later on this document.\n\n**Browser navigation** - Back/forward buttons work automatically. Bookmarking works. Sharing URLs works. The browser's navigation just works.\n\n\n## UI libraries\nLike the SPA entry point, the dynamic UI components also live in `.html` files. These files can be found anywhere within the application directory, which in this case is the root.\n\n### Users list component\nThe users component displays a table of all users with links to individual profiles:\n\n```html\n<!doctype dhtml>\n\n<script>\n  import { state } from 'state'\n</script>\n\n<article :is=\"users\">\n  <h1>Users</h1>\n\n  <table>\n    <tr :each=\"user in users\">\n      <td><a href=\"/{ user.id }\">{ user.name }</a></td>\n      <td><strong>{ user.email }</strong></td>\n      <td>{ user.country }</td>\n      <td>{ user.role }</td>\n      <td><span class=\"status {user.status}\">{ user.status }</span></td>\n      <td><pretty-date :date=\"user.created\"/></td>\n    </tr>\n  </table>\n\n  <script>\n    async mounted() {\n      const users = await fetch('/users').then(r => r.json())\n      this.update({ users })\n    }\n  </script>\n</article>\n```\n\n**Standard HTML** - Notice now this is mostly standard HTML. A `<table>` with `<tr>` elements, semantic `<article>` structure, and regular `<a>` links. The dynamic parts are minimal additions - `:each` for loops, `{ }` for data binding, and one `mounted()` method. You're writing HTML that browsers understand natively, just enhanced with the minimum syntax needed for interactivity.\n\n\n### User detail component\nThe user component shows detailed information for a single user:\n\n```html\n<article :is=\"user\">\n  <h1>{ name || email }</h1>\n\n  <nav>\n    <button onclick=\"history.go(-1)\">Back</button>\n  </nav>\n\n  <dl>\n    <dt>Registered</dt><dd><pretty-date :date=\"created\"/></dd>\n    <dt>Country</dt><dd>{ country }</dd>\n    <dt>Email</dt><dd>{ email }</dd>\n    <dt>Role</dt><dd>{ role }</dd>\n    <dt>Status</dt><dd><span class=\"status {status}\">{ status }</span></dt>\n  </dl>\n\n  <script>\n    state.on('id', async ({ id }) => {\n      const user = id && await fetch(`/users/${id}`).then(r => r.json())\n      this.update(user)\n    })\n  </script>\n</article>\n```\n\n**Semantic HTML** - Notice the `<dl>` (description list) element for displaying user properties. This is the semantically correct HTML for name-value pairs. Combined with `<nav>` for navigation and `<article>` for the main content, the structure tells browsers and screen readers exactly what each piece of content represents. Your design system handles the presentation - the HTML focuses purely on meaning and structure.\n\n\n### Reusable components\nCreate small, focused components that work across your entire application:\n\n```html\n<time :is=\"pretty-date\">\n  { formatDate(date) }\n\n  <script>\n    const opts = { year: 'numeric', month: 'short', day: 'numeric' }\n\n    formatDate(date) {\n      return new Date(date).toLocaleDateString('en-US', opts)\n    }\n  </script>\n</time>\n```\n\n**Single responsibility** - This component does one thing: format dates. It uses the browser's native `Intl.DateTimeFormat` instead of a date library. The `:date` attribute passes data cleanly without props drilling or context providers. Write small, focused components that solve specific problems using web standards - the UNIX philosophy (do one thing well) applied to UI development.\n\n\n\nLooking at single-page-apps.md, I'd add a new section called **Dynamic component mounting** right after the \"UI libraries\" section (around line 90) and before the \"Development workflow\" section (around line 160).\n\nHere's what to add:\n\n\n## Dynamic mounting\nThe `this.mount()` method lets you change which component displays based on application state. This is the core mechanism that makes SPAs work - instead of navigating to different pages, you mount different components in the same container.\n\n```html\n<!doctype dhtml>\n\n<script>\n  import { state } from 'state'\n\n  state.setup({\n    route: '/:section/:id'\n  })\n</script>\n\n<body>\n  <main>\n    <article/>\n  </main>\n\n  <script>\n    // Listen to route changes and mount appropriate components\n    state.on('section id', ({ section, id }) => {\n      const root = this.querySelector('article')\n\n      if (section == 'users') {\n        this.mount(id ? 'user-detail' : 'user-list', root)\n      } else if (section == 'products') {\n        this.mount('product-catalog', root)\n      } else {\n        this.mount('home-page', root)\n      }\n    })\n\n    // Initialize from current URL\n    mounted() {\n      state.init()\n    }\n  </script>\n</body>\n```\n\n### Passing data to components\n\nComponents can receive data when mounted:\n\n```javascript\n// Mount with state data\nthis.mount('user-profile', root, {\n  userId: state.id,\n  editMode: state.edit\n})\n\n// Mount with API data\nconst userData = await fetch(`/api/users/${state.id}`).then(r => r.json())\nthis.mount('user-profile', root, userData)\n```\n\n### Component cleanup\n\nWhen you mount a new component, the previous component is automatically unmounted and cleaned up. No manual cleanup needed:\n\n```javascript\n// This automatically unmounts the previous component\nstate.on('view', ({ view }) => {\n  const container = this.querySelector('main')\n\n  // Previous component is cleaned up automatically\n  this.mount(view == 'settings' ? 'settings-page' : 'dashboard', container)\n})\n```\n\nThis mounting system is what makes Nue SPAs feel like traditional multi-page sites while maintaining the performance benefits of client-side routing.\n\n\n\n## Development workflow\nSPAs work best when you start with your data model and build the interface around it.\n\n\n### Start with data structure\nDefine your data model first. In the SPA template, users have this structure:\n\n```json\n{\n  \"id\": \"1\",\n  \"name\": \"Alice Johnson\",\n  \"email\": \"alice@example.com\",\n  \"country\": \"United States\",\n  \"role\": \"admin\",\n  \"status\": \"active\",\n  \"created\": \"2024-01-15T10:30:00Z\"\n}\n```\n\nWhile our app has a super simple data model it still shapes how you build components. Each field becomes a display element, each relationship becomes navigation.\n\n\n### Build static components\nStart with hard-coded data to establish your UI structure:\n\n```html\n<article :is=\"users\">\n  <h1>Users</h1>\n  <table>\n    <tr>\n      <td><a href=\"/1\">Alice Johnson</a></td>\n      <td>alice@example.com</td>\n      <td>admin</td>\n    </tr>\n  </table>\n</article>\n```\n\nFocus on structure, not data loading. Get the HTML semantics right first.\n\n### Add data loading\nReplace static content with API calls:\n\n```html\n<script>\n  async mounted() {\n    const users = await fetch('/users').then(r => r.json())\n    this.update({ users })\n  }\n</script>\n```\n\nNow your static structure becomes dynamic without changing the template.\n\n\n### Connect routing\nLink components through the SPA entry point and state listeners:\n\n```javascript\n// In index.html\nstate.on('id', ({ id }) => {\n  const root = document.querySelector('article')\n  this.mount(id ? 'user' : 'users', root)\n})\n```\n\nThis gives you client-side routing and URL-based state in a fraction of the code you'd need with traditional SPA frameworks. See the [State API reference](/docs/state-api) for complete state management details.  and [Server development](/docs/server-development) for backend integration.\n\n\n## Scaling up\nThe patterns you've learned here scale to enterprise-grade applications. To see more advanced implementations:\n\n```bash\nnue create full\n```\n\nThe full template demonstrates how these basic concepts extend to complex real-world scenarios:\n\n**SPA + MPA integration** - Combining single-page apps with content areas like blogs and documentation\n\n**Separated business logic** - Moving data operations to dedicated modules (`app.js`) for better testing and organization\n\n**Advanced state patterns** - Search, filtering, and pagination using the same state listeners you learned here\n\n**Authentication flows** - Login pages and session management with server integration\n\nThe same concepts apply - state listeners, component mounting, and URL-based routing. Just more of them, organized for maintainability and team collaboration.\n"
  },
  {
    "path": "packages/www/docs/spa-development.md",
    "content": "\n# Single-page application (SPA) development\nSingle-page applications in Nue are dynamic web apps that run entirely in the browser. Unlike content-focused apps that generate static pages, SPAs use client-side routing and state management to create fluid, app-like experiences without page reloads.\n\n> **Local development only**: SPA's currently work only as a local mockup to get a glimpse of what's coming. Production deployments with CloudFlare integration and [universal data model](universal-data-model) come later.\n\n\n## Getting started\nHere's the setup for the simplest of SPAs:\n\n```bash\nnue create spa\n```\n\nThis gives you the following project structure:\n\n```\n├── index.html           # SPA entry point\n├── ui/                  # UI components\n|   ├── entry.html\n|   └── table.html\n├── server/\n|   ├── index.js         # Route handlers\n|   ├── data/            # Local data mocks\n|   |   └── users.json   # Mock for user data\n└── css/                 # Styling\n```\n\nStart development:\n\n```bash\nnue dev\n```\n\nThis runs both frontend and backend together with hot reload for all assets.\n\n\n## SPA architecture\nNue SPAs separate concerns into distinct layers that work together seamlessly.\n\n\n### Entry point and routing\nThe `index.html` file controls your entire application. When you use `<!doctype dhtml>` with `<body>` scope, this file handles all routes within its directory.\n\n```html\n<!doctype dhtml>\n\n<script>\n  import { state } from 'state'\n  state.setup({ route: '/:id', autolink: true })\n</script>\n\n<body>\n  <main>\n    <article/>\n  </main>\n\n  <script>\n    state.on('id', ({ id }) => {\n      this.mount(id ? 'user' : 'users', 'article')\n    })\n\n    mounted() {\n      state.init()\n    }\n  </script>\n</body>\n```\n\n### How it works\n\n- URL parameters like `/:id` capture routes (`/123` sets `state.id = \"123\"`)\n- The `autolink` option turns regular `<a href>` links into SPA navigation\n- State listeners mount components based on URL changes\n- Browser back/forward buttons work automatically\n- The `this.mount()` method renders different components based on the URL\n\n\nSee [state API](state-api) and [HTML syntax](html-syntax) for details on dynamic state management and dynamic mounting.\n\n\n### UI components\nComponents live in `.html` files and focus purely on structure and presentation:\n\n```html\n<article :is=\"users\">\n  <h1>Users</h1>\n\n  <table>\n    <tr :each=\"user in users\">\n      <td><a href=\"/{ user.id }\">{ user.name }</a></td>\n      <td>{ user.email }</td>\n      <td>{ user.role }</td>\n    </tr>\n  </table>\n\n  <script>\n    async mounted() {\n      const users = await fetch('/api/users').then(r => r.json())\n      this.update({ users })\n    }\n  </script>\n</article>\n```\n\nThis is standard HTML with minimal additions. A `<table>` is a `<table>`, an `<a>` is an `<a>`. The dynamic parts (`:each`, `{ }` expressions) are just enough syntax to make HTML reactive.\n\n\n### Data fetching\nWhen component is mounted:\n\n```javascript\nasync mounted() {\n  const users = await fetch('/users').then(r => r.json())\n  this.update({ users })\n}\n```\n\n\n### Server side\nRoute handlers connect your UI to business models:\n\n```javascript\nget('/users', async (c) => {\n  const { users } = c.env\n  return c.json(await users.getAll())\n})\n```\n\n### Data modeling\nThe future version of Nue will provide a [universal data model](universal-data-model) that works identically in local development and on the cloud. This model offers ready-made objects for common web development needs: authentication, lead generation, customer management, payments, and more.\n\nDuring development, these models are automatically generated from JSON files in the `server/data/` folder. The `spa` template includes this `server/data/users.json` file:\n\n```json\n[\n  {\n    \"name\": \"Sarah Chen\",\n    \"email\": \"sarah.chen@example.com\",\n    \"country\": \"Singapore\",\n    \"role\": \"Product Manager\",\n    \"status\": \"active\"\n  },\n  {\n    \"name\": \"Marcus Johnson\",\n    \"email\": \"marcus.j@example.com\",\n    \"country\": \"USA\",\n    \"role\": \"Frontend Developer\",\n    \"status\": \"active\"\n  }\n]\n```\n\nThis model becomes available automatically with simple CRUD methods:\n\n```javascript\n// In your route handlers\nconst { users } = c.env\n\n// Get all records\nawait users.getAll()\n\n// Get single record\nawait users.get(id)\n\n// Create new record\nawait users.create({ name: 'Charlie', email: 'charlie@example.com' })\n\n// Update record\nawait users.update(id, { role: 'admin' })\n\n// Delete record\nawait users.remove(id)\n```\n\nThese models will be released according to the [roadmap](roadmap).\n\n\n## Custom server\nIf you prefer full control over your backend or want to use your own technology stack, configure Nue as a proxy to your existing server:\n\n```yaml\n# site.yaml\nserver:\n  url: http://localhost:5000\n  routes: [/api/, /admin/]\n```\n\n### How it works\n- Requests to `/api/users` get proxied to `http://localhost:5000/api/users`\n- Requests to `/admin/dashboard` get proxied to `http://localhost:5000/admin/dashboard`\n- All other requests (`/`, `/about/`) are served by Nue normally\n\n\n### When to use this\n- You already have a working backend (Express, FastAPI, Rails, Django)\n- Your team prefers a specific backend language (Python, Go, Rust, PHP)\n- You're integrating with existing infrastructure or databases\n- You want immediate production deployment without waiting for CloudFlare integration\n\nThis approach gives you Nue's frontend benefits (minimal bundles, instant HMR, standards-based components) while keeping your backend exactly as it is. The proxy works identically in development and production.\n\n\n## Local development with hot-reloading\nEverything runs on one port with `nue dev`:\n\n```\nFrontend: http://localhost:4000\nBackend:  http://localhost:4000/api/*\n```\n\nChanges to any part of your application reload instantly:\n\n**Frontend changes** - Components, state, and UI update in milliseconds\n\n**Backend changes** - Server routes reload without restarting\n\n**Model changes** - Business logic propagates to both layers\n\n**CSS changes** - Styles inject directly without page reloads\n\n\n## Full template\nThe patterns you've learned scale to larger applications. To see how:\n\n```bash\nnue create full\n```\n\nThe full template adds:\n\n**Authentication flows** - Login, sessions, and route protection\n\n**Advanced state patterns** - Search, filtering, pagination across collections\n\n**Hybrid architecture** - Marketing site, documentation, and SPA in one project\n\n**Shared design system** - Single CSS foundation for all applications\n\n"
  },
  {
    "path": "packages/www/docs/state-api.md",
    "content": "\n# State API\nComplete reference for Nuestate's state management API. For an overview and introduction, see the [Nuestate documentation](/docs/nuestate).\n\n## state object\n\nThe main `state` object is a proxy that handles all reading and writing of application state. Import it from anywhere in your application:\n\n```javascript\nimport { state } from 'state'\n```\n\nNote: Nuekit automatically maps `'state'` to `/@nue/state.js` via import maps, so you don't need to specify the full path.\n\nOr use it directly from a CDN:\n\n```html\n<script type=\"module\">\n  import { state } from '//esm.sh/nuestate'\n</script>\n```\n\n### Reading state\n\nAccess any property directly:\n\n```javascript\nconsole.log(state.view)     // current value or undefined\nconsole.log(state.search)   // current value or undefined\nconsole.log(state.user)     // current value or undefined\n```\n\nState is automatically populated from the current URL and browser storage when the application starts.\n\n### Writing state\n\nSet any property directly:\n\n```javascript\nstate.view = 'users'\nstate.search = 'john'\nstate.user = { name: 'Alice', id: 123 }\n```\n\nState changes automatically trigger URL updates and storage persistence based on your configuration.\n\n## state.setup()\n\nConfigure where different pieces of state should be stored and how routing should work.\n\n```javascript\nstate.setup(config)\n```\n\n### Configuration options\n\n**route** - Route pattern with parameters\n\n```javascript\nstate.setup({\n  route: '/app/:section/:id'\n})\n\n// Setting route parameters updates the URL path\nstate.section = 'products'  // URL: /app/products\nstate.id = '123'           // URL: /app/products/123\n```\n\n**query** - Array of properties that go in URL search parameters\n\n```javascript\nstate.setup({\n  query: ['search', 'filter', 'page']\n})\n\n// Setting query properties updates the URL search\nstate.search = 'shoes'     // URL: ?search=shoes\nstate.filter = 'active'    // URL: ?search=shoes&filter=active\nstate.page = 2            // URL: ?search=shoes&filter=active&page=2\n```\n\n**session** - Array of properties stored in sessionStorage\n\n```javascript\nstate.setup({\n  session: ['user', 'preferences', 'cart']\n})\n\n// These persist until the browser session ends\nstate.user = { name: 'Alice' }\nstate.cart = [{ id: 1, name: 'Shoes' }]\n```\n\n**local** - Array of properties stored in localStorage\n\n```javascript\nstate.setup({\n  local: ['theme', 'language', 'settings']\n})\n\n// These persist permanently on the device\nstate.theme = 'dark'\nstate.language = 'en'\n```\n\n**memory** - Array of properties kept only in memory\n\n```javascript\nstate.setup({\n  memory: ['temp_data', 'ui_state', 'removeId']\n})\n\n// These exist only while the page is loaded\nstate.temp_data = { processing: true }\nstate.ui_state = { modal_open: false }\nstate.removeId = 123  // ID for confirmation dialog\n```\n\n**emit_only** - Array of properties that only trigger events without storage\n\n```javascript\nstate.setup({\n  emit_only: ['deleted', 'saved', 'error']\n})\n\n// These fire events but don't persist anywhere\nstate.emit('deleted', userId)  // Triggers listeners without storing\n```\n\n**autolink** - Enable automatic link handling for SPA navigation\n\n```javascript\nstate.setup({\n  route: '/app/:section/:id',\n  autolink: true\n})\n\n// Clicks on matching links automatically update state instead of page reload\n```\n\n### Complete configuration example\n\n```javascript\nstate.setup({\n  route: '/shop/:category/:product',\n  query: ['search', 'color', 'size', 'page'],\n  session: ['user', 'cart'],\n  local: ['theme', 'currency'],\n  memory: ['loading', 'errors', 'removeId'],\n  emit_only: ['deleted', 'saved'],\n  autolink: true\n})\n```\n\n## state.on()\n\nListen to state changes with event handlers.\n\n```javascript\nstate.on(properties, callback)\n```\n\n### Single property\n\n```javascript\nstate.on('search', (changes) => {\n  console.log('Search changed to:', changes.search)\n})\n```\n\n### Multiple properties\n\n```javascript\nstate.on('search filter page', (changes) => {\n  console.log('Changed properties:', changes)\n  // changes object contains only the properties that changed\n})\n```\n\n**Note:** `state.on()` replaces any previous listener with the same property names, so you don't need to call `state.off()` to avoid duplicate listeners.\n\n### Callback parameter\n\nThe callback receives a `changes` object containing only the properties that changed:\n\n```javascript\nstate.on('user cart', (changes) => {\n  if (changes.user) {\n    console.log('User changed:', changes.user)\n  }\n  if (changes.cart) {\n    console.log('Cart changed:', changes.cart)\n    updateCartDisplay(changes.cart)\n  }\n})\n```\n\n### Async handlers\n\nEvent handlers can be async:\n\n```javascript\nstate.on('search category', async (changes) => {\n  const results = await fetchProducts(changes.search, changes.category)\n  state.products = results\n})\n```\n\n## state.emit()\n\nTrigger events for emit-only properties without storing the value:\n\n```javascript\nstate.setup({\n  emit_only: ['deleted', 'saved', 'error']\n})\n\n// Fire event without persistence\nstate.emit('deleted', userId)\n\n// Listen to emit-only events\nstate.on('deleted', ({ deleted }) => {\n  console.log('User deleted:', deleted)\n  refreshUserList()\n})\n```\n\n## state.init()\n\nInitialize state from the current URL. Call this after your UI is mounted:\n\n```javascript\nmounted() {\n  state.init()  // Populates state from current URL and fires initial events\n}\n```\n\nThis is essential for SPAs to handle direct navigation to URLs with state parameters.\n\n## Storage behavior\n\n### URL parameters (route and query)\n\nURL parameters are always strings:\n\n```javascript\nstate.page = 2\nconsole.log(state.page)        // \"2\" (string)\nconsole.log(location.search)   // \"?page=2\"\n\nstate.active = true\nconsole.log(state.active)      // \"true\" (string)\nconsole.log(location.search)   // \"?active=true\"\n\n// Objects become \"[object Object]\" - not useful\nstate.filter = { color: 'red' }\nconsole.log(state.filter)      // \"[object Object]\" (string)\n```\n\n### Browser storage (session and local)\n\nSession and local storage use JSON serialization:\n\n```javascript\nstate.setup({ session: ['user'], local: ['settings'] })\n\n// Objects are JSON.stringify'd on save, JSON.parse'd on read\nstate.user = { name: 'Alice', id: 123 }\nconsole.log(typeof state.user.id)  // 'number' (restored from JSON)\n\n// Arrays work the same way\nstate.settings = ['dark-mode', 'notifications']\nconsole.log(Array.isArray(state.settings))  // true (restored from JSON)\n```\n\n### Memory storage\n\nMemory storage keeps exact references to any JavaScript value:\n\n```javascript\nstate.setup({ memory: ['cache', 'handler', 'domRef'] })\n\n// Any object type works\nconst data = new Map()\nstate.cache = data\nconsole.log(state.cache === data)  // true (same reference)\n\n// Functions, DOM elements, etc.\nstate.handler = () => console.log('click')\nstate.domRef = document.querySelector('#myButton')\n```\n\n### Memory use cases\n\nMemory storage is perfect for temporary UI state:\n\n```javascript\nstate.setup({\n  memory: ['removeId', 'selectedItems', 'dragState']\n})\n\n// Confirmation dialog\nfunction showDeleteConfirm(userId) {\n  state.removeId = userId  // Store ID for confirmation\n}\n\n// In delete confirmation component\nstate.on('removeId', ({ removeId }) => {\n  if (removeId) {\n    this.querySelector('dialog').showModal()\n  }\n})\n```\n\n## Route patterns\n\n### Simple parameters\n\n```javascript\nstate.setup({\n  route: '/users/:id'\n})\n\nstate.id = '123'  // URL: /users/123\n```\n\n### Multiple parameters\n\n```javascript\nstate.setup({\n  route: '/shop/:category/:product/:variant'\n})\n\nstate.category = 'shoes'\nstate.product = 'sneakers'\nstate.variant = 'red-large'\n// URL: /shop/shoes/sneakers/red-large\n```\n\n### Optional parameters\n\nUse query parameters for optional values:\n\n```javascript\nstate.setup({\n  route: '/products/:category',\n  query: ['color', 'size', 'page']\n})\n\nstate.category = 'shoes'    // URL: /products/shoes\nstate.color = 'red'         // URL: /products/shoes?color=red\nstate.size = 'large'        // URL: /products/shoes?color=red&size=large\n```\n\n## Integration patterns\n\n### SPA root component\n\n```html\n<!doctype dhtml>\n<script>\n  import { hasSession, logout } from 'app'\n  import { state } from 'state'\n\n  state.setup({\n    query: ['type', 'query', 'start'],\n    emit_only: ['deleted'],\n    memory: ['removeId'],\n    route: '/app/:id',\n    autolink: true,\n  })\n\n  if (!hasSession()) location.href = '/login/'\n</script>\n\n<body>\n  <header>\n    <nav>\n      <a href=\"/\"><img src=\"/img/logo.png\" width=\"60\" height=\"22\"></a>\n      <a href=\"/app/\">Contacts</a>\n    </nav>\n    <nav>\n      <button class=\"plain\" :onclick=\"logout(); location.href = '/'\">Logout</button>\n    </nav>\n  </header>\n\n  <main>\n    <article/>\n  </main>\n\n  <confirm-delete/>\n\n  <script>\n    state.on('id', ({ id }) => {\n      const wrap = this.root.querySelector('article')\n      this.mount(id ? 'contact-details' : 'contact-list', wrap)\n    })\n\n    mounted() {\n      console.log('app mounted')\n      state.init()  // Initialize from current URL\n    }\n  </script>\n</body>\n```\n\n### Component with state\n\n```html\n<product-filter>\n  <input type=\"search\"\n    value=\"{ state.search }\"\n    :oninput=\"handleSearch\">\n\n  <select :onchange=\"handleCategory\">\n    <option value=\"\">All categories</option>\n    <option :each=\"cat in categories\" value=\"{ cat }\">{ cat }</option>\n  </select>\n\n  <div :each=\"product in products\" key=\"{ product.id }\">\n    <h3>{ product.name }</h3>\n    <p>${ product.price }</p>\n  </div>\n\n  <script>\n    import { state } from 'state'\n\n    state.setup({\n      query: ['search', 'category'],\n      memory: ['products', 'categories']\n    })\n\n    handleSearch(e) {\n      state.search = e.target.value\n    }\n\n    handleCategory(e) {\n      state.category = e.target.value\n    }\n\n    // React to state changes\n    state.on('search category', async () => {\n      const products = await fetchProducts(state.search, state.category)\n      state.products = products\n      this.update() // Trigger component re-render\n    })\n\n    async mounted() {\n      const categories = await fetchCategories()\n      state.categories = categories\n\n      // Initial load if there's existing state\n      if (state.search || state.category) {\n        const products = await fetchProducts(state.search, state.category)\n        state.products = products\n      }\n\n      this.update()\n    }\n\n    get products() {\n      return state.products || []\n    }\n\n    get categories() {\n      return state.categories || []\n    }\n  </script>\n</product-filter>\n```\n\n### Manual updates\n\nComponents need manual updates after async state changes:\n\n```javascript\nstate.on('user', async (changes) => {\n  const profile = await fetchUserProfile(changes.user.id)\n  state.profile = profile\n  this.update() // Required for component re-render\n})\n```\n\n### Initialization\n\nHandle initial state from URLs:\n\n```javascript\n// URL: /products/shoes?color=red&page=2\n\nconsole.log(state.category) // 'shoes'\nconsole.log(state.color)    // 'red'\nconsole.log(state.page)     // 2\n\n// State is automatically populated from the current URL\n```\n\n## Error handling\n\n### Invalid route parameters\n\nRoute parameters that don't match the pattern are ignored:\n\n```javascript\nstate.setup({\n  route: '/users/:id'\n})\n\n// Current URL: /about\nstate.id = '123'  // No effect, URL stays /about\n```\n\n### Storage limitations\n\nBrowser storage has size limits. Large objects may not persist:\n\n```javascript\nstate.setup({ local: ['large_data'] })\n\ntry {\n  state.large_data = hugeMegabyteObject\n} catch (error) {\n  console.log('Storage quota exceeded')\n}\n```\n\n### Type conversion errors\n\nInvalid JSON in URL parameters falls back to string:\n\n```javascript\n// URL: ?data=invalid-json\nconsole.log(state.data)        // 'invalid-json' (string)\nconsole.log(typeof state.data) // 'string'\n```\n\n"
  },
  {
    "path": "packages/www/docs/svg-development.md",
    "content": "\n# SVG development\nNue offers a focused development environment for developing SVG graphics. A programmatic alternative to design tools, for creating static and interactive visuals that integrate with your design system.\n\n## Setting up\nBy default, SVG files are served directly without processing. Enable SVG processing at the directory level with `app.yaml`:\n\n```yaml\n# In visuals/app.yaml\nsvg:\n  process: true\n```\n\nThis tells Nue to process SVG files in the `visuals/` directory as templates rather than serving them as static assets.\n\n## Dynamic SVG markup\nMix SVG elements with HTML and Nue's template syntax:\n\n```xml\n<!-- table.svg -->\n<svg width=\"500\" height=\"400\">\n  <!-- SVG elements -->\n  <circle r=\"100\" class=\"primary\"/>\n\n  <!-- Mixed HTML content -->\n  <html>\n    <table>\n      <tr :each=\"user in users\">\n        <td>{ user.name }</td>\n        <td>{ user.email }</td>\n      </tr>\n    </table>\n  </html>\n</svg>\n```\n\n**Dynamic syntax** - Use the full [HTML syntax](/docs/html-syntax) reference: loops, conditionals, expressions, and data binding.\n\n**Data cascade** - Variables come from the same sources as HTML templates: parent YAML files, `@shared/data/`, and front matter.\n\n**HTML embedding** - The `<html>` tag becomes a `<foreignObject>` automatically, letting you embed rich HTML content inside SVG graphics.\n\n### Generated output\nThe above template generates clean, standalone SVG:\n\n```xml\n<svg xmlns=\"http://www.w3.org/2000/svg\"\n     viewBox=\"0 0 500 400\"\n     width=\"500\" height=\"400\">\n\n  <circle r=\"100\" class=\"primary\"/>\n\n  <foreignObject x=\"0\" y=\"0\" width=\"100%\" height=\"100%\">\n    <table xmlns=\"http://www.w3.org/1999/xhtml\">\n      <tr><td>Alice Johnson</td><td>alice@example.com</td></tr>\n      <tr><td>Bob Smith</td><td>bob@example.com</td></tr>\n    </table>\n  </foreignObject>\n</svg>\n```\n\n**Automatic namespaces** - SVG and HTML namespaces are added automatically if missing.\n\n**Auto viewBox** - When not specified, viewBox matches the width/height attributes.\n\n**foreignObject wrapper** - HTML content gets wrapped with proper positioning and sizing.\n\n## Styling and fonts\nEmbed your design system directly into SVG output for consistent, self-contained graphics.\n\n### Design system integration\nInclude your CSS design system in SVG output:\n\n```xml\n<!--\n  @include [base, graphics]\n-->\n<svg>\n  <circle class=\"accent\"/>\n  <rect class=\"primary-bg\"/>\n\n  <html>\n    <div class=\"card\">\n      <h3>Design system styles work here</h3>\n    </div>\n  </html>\n</svg>\n```\n\nYour CSS design system styles both SVG elements and HTML content:\n\n```css\n/* In @shared/design/graphics.css */\n.accent {\n  fill: var(--accent-color);\n  stroke: var(--border-color);\n}\n\n.primary-bg {\n  fill: var(--primary-color);\n}\n\n.card {\n  background: var(--surface-color);\n  border-radius: var(--radius);\n}\n```\n\n**Explicit inclusion** - Unlike HTML pages, SVG processing starts with no styles. Use `@include` to add what you need.\n\n**Style embedding** - CSS gets embedded as `<style>` blocks with proper CDATA sections for SVG compatibility.\n\n### Font embedding\nConfigure fonts at the directory level:\n\n```yaml\n# In visuals/app.yaml\nsvg:\n  fonts:\n    Inter: @shared/design/inter.woff2\n    Mono: @shared/design/mono.woff2\n```\n\nOr customize per file with HTML comments:\n\n```xml\n<!--\n  @fonts [Inter, Mono]\n-->\n<svg>\n  <text font-family=\"Inter\">Embedded font text</text>\n</svg>\n```\n\n**Default behavior** - All configured fonts are embedded by default.\n\n**Per-file control** - Use `@fonts false` to disable embedding, or `@fonts [Inter]` to include specific fonts only.\n\n\n\n## Hot reloading\nView SVG files with live updates during development:\n\n```\nhttp://localhost:4000/visuals/table.svg?hmr\n```\n\nThe `?hmr` parameter generates an HTML wrapper that enables hot reloading:\n\n- **CSS changes** - Styling updates instantly\n- **SVG changes** - Graphics update without refresh\n- **Data changes** - Template re-renders with new data\n\n\n### Production output\nWithout the `?hmr` parameter, you get clean, standalone SVG that works anywhere:\n\n```xml\n<!-- Production SVG output -->\n<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 500 400\">\n  <style><![CDATA[\n    /* embedded design system styles */\n    circle { fill: var(--brand-color); }\n    .card { background: var(--surface-color); }\n  ]]></style>\n  <!-- embedded font definitions -->\n  <!-- your dynamic content -->\n</svg>\n```\n\n## Interactive visuals\nFor graphics that need user interaction, create SVG components instead of processed files.\n\n### Component definition\nDefine interactive SVG in library files:\n\n```html\n<!-- In ui/graphics.html -->\n<svg :is=\"interactive-chart\" width=\"500\" height=\"300\">\n  <rect :each=\"bar in data\"\n    :onclick=\"selectBar(bar)\"\n    class=\"bar { bar.selected ? 'selected' : '' }\"\n    width=\"{ bar.width }\"\n    height=\"{ bar.height }\"/>\n\n  <script>\n    selectBar(bar) {\n      this.data.forEach(b => b.selected = false)\n      bar.selected = true\n      this.update()\n    }\n  </script>\n</svg>\n```\n\nStyle with your design system:\n\n```css\n/* In @shared/design/graphics.css */\n.bar {\n  fill: var(--muted-color);\n  transition: fill 0.2s;\n\n  &.selected {\n    fill: var(--primary-color);\n  }\n\n  &:hover {\n    fill: var(--accent-color);\n  }\n}\n\n```\n\n### Usage in content\nUse interactive components in Nuemark content:\n\n```md\n# Sales Dashboard\n\nHere's our quarterly performance:\n\n[interactive-chart]\n  data:\n    - width: 100\n      height: 200\n      label: Q1\n    - width: 120\n      height: 180\n      label: Q2\n```\n\nOr in HTML templates:\n\n```html\n<main>\n  <h1>Dashboard</h1>\n  <interactive-chart :data=\"salesData\"/>\n</main>\n```\n\n## Use cases\n\n### Static graphics\nPerfect for generating charts, diagrams, and infographics that scale perfectly and integrate with your design system:\n\n```xml\n<!-- org-chart.svg -->\n<svg width=\"800\" height=\"600\">\n  <html>\n    <div class=\"org-chart\">\n      <div :each=\"person in team\" class=\"person-card\">\n        <h3>{ person.name }</h3>\n        <p>{ person.role }</p>\n      </div>\n    </div>\n  </html>\n</svg>\n```\n\n### Dynamic visualizations\nCreate data-driven graphics that update based on your content:\n\n```xml\n<!-- progress-chart.svg -->\n<svg width=\"400\" height=\"200\">\n  <rect :each=\"project in projects\"\n    class=\"progress-bar { project.status }\"\n    width=\"{ project.completion * 300 }\"\n    height=\"30\"\n    y=\"{ $index * 40 }\"/>\n</svg>\n```\n\nStyle with CSS:\n\n```css\n.progress-bar {\n  fill: var(--muted-color);\n\n  &.completed {\n    fill: var(--success-color);\n  }\n\n  &.in-progress {\n    fill: var(--warning-color);\n  }\n}\n```\n\n### Design system assets\nGenerate consistent icons and graphics that match your brand:\n\n```xml\n<!-- icon-set.svg -->\n<svg width=\"100\" height=\"100\">\n  <circle class=\"icon-bg\"/>\n  <path class=\"icon-fg\" d=\"...\"/>\n</svg>\n```\n\nThe same design tokens that style your HTML also style your graphics, ensuring complete visual consistency across your entire application.\n\n"
  },
  {
    "path": "packages/www/docs/syntax-highlighting.md",
    "content": "\n# Syntax highlighting\nReference for Nueglow's syntax highlighting. See the [Nueglow introduction](/docs/nueglow) for an overview.\n\n## How it works\nNueglow generates semantic HTML without class names. Your design system controls the appearance through CSS element selectors.\n\n### Basic example\nThis Markdown code block:\n\n````md\n ```javascript\n // Calculate total\n const total = items.reduce((sum, item) => {\n   return sum + item.price\n }, 0)\n ```\n````\n\nGenerates this HTML:\n\n```html\n<pre>\n  <code language=\"javascript\">\n    <sup>// Calculate total</sup>\n    <b>const</b> total <i>=</i> items<i>.</i>reduce<i>((</i>sum<i>,</i> item<i>)</i> <i>=></i> <i>{</i>\n      <b>return</b> sum <i>+</i> item<i>.</i>price\n    <i>},</i> <em>0</em><i>)</i>\n  </code>\n</pre>\n```\n\nNo classes, just semantic HTML elements. Your CSS defines how keywords, comments, and punctuation look.\n\n## HTML elements\nNueglow maps code tokens to semantic HTML elements:\n\n| Element | Purpose | Common use |\n|---------|---------|------------|\n| `<b>`   | Keywords | `const`, `function`, `class`, `if`, `return` |\n| `<em>` | Values | Strings, numbers, booleans |\n| `<strong>` | Special emphasis | Important identifiers, class names |\n| `<i>` | Punctuation | Brackets, commas, operators |\n| `<sup>` | Comments | Single and multi-line comments |\n| `<del>` | Deleted lines | Diff removals |\n| `<ins>` | Inserted lines | Diff additions |\n| `<dfn>` | Highlighted lines | Focus lines |\n| `<u>` | Errors | Syntax errors, problems |\n| `<mark>` | Marked regions | Selected code |\n| `<label>` | Special tokens | Standout words, annotations |\n\n\n## Language support\nSpecify the language after the opening fence:\n\n```md\n  ```python\n  def hello():\n      return \"Hello world\"\n  ```\n\n  ```html\n  <!doctype html>\n  <body>\n    <h1>Title</h1>\n  </body>\n  ```\n\n  ```css\n  .container {\n    display: grid;\n    gap: 2rem;\n  }\n  ```\n```\n\nNue supports virtually all programming, markup, and markdown syntaxes including `javascript`, `typescript`, `python`, `html`, `css`, `yaml`, `json`, `markdown`, `bash`, `sql`. The trick is to study features (strings, comments, keywords) regardless of the language. This way there is no need for specialized grammar file for each language.\n\n\n## Selected regions\nUse bullet markers to highlight specific parts:\n\n````md\n ```javascript\n const config = {\n   •name: \"My App\"•,\n   version: \"1.0.0\"\n }\n ```\n````\n\nGenerates `<mark>` tags around the selected content:\n\n```html\n<mark>name: \"My App\"</mark>\n```\n\nDouble bullets mark errors:\n\n````md\n ```javascript\n const data = ••undefned•• // typo\n ```\n````\n\nGenerates `<u>` tags for error highlighting:\n\n```html\nconst data = <u>undefned</u>\n```\n\n## Line highlighting\nUse line prefixes to highlight, add, or remove entire lines:\n\n````md\n ```javascript\n   function process(data) {\n >   // This line is highlighted\n -   const old = data.value\n +   const updated = data.newValue\n     return updated\n   }\n ```\n````\n\nGenerates:\n\n```html\n<pre>\n  <code language=\"javascript\">\n    <b>function</b> process<i>(</i>data<i>)</i> <i>{</i>\n>   <dfn><sup>// This line is highlighted</sup></dfn>\n>   <del><b>const</b> old <i>=</i> data<i>.</i>value</del>\n>   <ins><b>const</b> updated <i>=</i> data<i>.</i>newValue</ins>\n    <b>return</b> updated\n    <i>}</i>\n  </code>\n</pre>\n```\n\nLine prefixes:\n- `>` highlights the line with `<dfn>`\n- `+` marks as inserted with `<ins>`\n- `-` marks as deleted with `<del>`\n\n\n## Line numbers\nAdd the `numbered` attribute to enable line numbering:\n\n````md\n ```css numbered\n body {\n   margin: 0;\n   padding: 0;\n }\n ```\n````\n\nEach line gets wrapped in a `<span>` for CSS counter styling:\n\n```html\n<pre>\n  <code language=\"css\" numbered>\n    <span>body {</span>\n    <span>  margin: 0;</span>\n    <span>  padding: 0;</span>\n    <span>}</span>\n  </code>\n</pre>\n```\n\n## Custom classes\nAdd a class for specific styling needs:\n\n````md\n ```javascript.twilight\n const theme = \"twilight\"\n ```\n````\n\nGenerates:\n\n```html\n<pre class=\"twilight\">\n  <code language=\"javascript\">\n    ...\n  </code>\n</pre>\n```\n\n## Styling with CSS\n### Essential selectors\nTarget syntax elements with nesting:\n\n```css\npre {\n  /* Keywords: const, function, return */\n  b { }\n\n  /* Values: strings, numbers */\n  em { }\n\n  /* Comments */\n  sup { }\n\n  /* Punctuation: brackets, operators */\n  i { }\n}\n```\n\n### Language-specific styling\nUse attribute selectors for different languages:\n\n```css\npre[language=\"html\"] { }\npre[language=\"python\"] b { }\n```\n\n### Line numbers\nStyle numbered code blocks with CSS counters:\n\n```css\npre[numbered] {\n  counter-reset: line 0;\n\n  span {\n    counter-increment: line;\n\n    &:before {\n      content: counter(line);\n    }\n  }\n}\n```\n\n### Highlighting patterns\nCommon selector patterns:\n\n```css\n/* Diff lines */\npre ins { }  /* added */\npre del { }  /* deleted */\n\n/* Highlighted lines */\npre dfn { }\n\n/* Error lines using :has() */\npre span:has(u) { }\n\n/* Selected regions */\npre mark { }\n```\n\n## Sample CSS\nFind CSS theming files in the [Nueglow repository](https://github.com/nuejs/nue/tree/master/packages/glow/css):\n\n- **syntax.css** basic features. uses CSS variables for theming. defaults to dark\n- **light.css** converts syntax.css to light theme\n- **markers.css** adds region and line highlighting\n\n"
  },
  {
    "path": "packages/www/docs/template-data.md",
    "content": "\n# Template data\nNue templates receive data from multiple sources that cascade and combine into a single context. This guide covers how data flows from YAML files, JSON files, and manipulation scripts to templates.\n\n\n## Data files\nAny `.yaml` file in your project becomes template data. Data files follow the same cascading hierarchy as [configuration](configuration):\n\n**Site level** - Root directory:\n\n```\nteam.yaml              # Custom site-wide data\nsite.yaml              # Metadata and custom properties only\n```\n\nExample metadata and custom properties in site.yaml:\n\n```yaml\n# site.yaml metadata\nmeta:\n  title: The UNIX of the web\n  description: Standards first web framework\n  og: /img/social.png\n\n# custom properties\nsite_name: Acme Inc\ncompany_email: hello@acme.com\n\nsocial_links:\n  twitter: https://twitter.com/acme\n  github: https://github.com/acme\n```\n\nAll configuration properties (`site`, `content`, `collections`, ...) are skipped from templates data.\n\n**App level** - Subdirectories:\n\n```\nblog/\n├── app.yaml           # Metadata and custom properties only\n└── authors.yaml       # App specific data\n\ndocs/\n├── app.yaml\n└── navigation.yaml\n```\n\n**Global data**\n\n```\n@shared/data/\n├── products.yaml\n├── plans.yaml\n└── social.yaml\n```\n\n### JSON files\nJSON files work identically to YAML files but are typically machine-generated from tools like TypeDoc, API documentation generators, or build processes:\n\n```json\n{\n  \"api_version\": \"2.0\",\n  \"endpoints\": [\n    {\n      \"path\": \"/users\",\n      \"method\": \"GET\",\n      \"description\": \"List all users\"\n    }\n  ]\n}\n```\n\nJSON data merges with YAML data at the same hierarchy level. Properties from both file types combine into a single context.\n\n\n## Data manipulation scripts\nTransform and enrich your data using JavaScript or TypeScript scripts. Place `.js` or `.ts` files in `@shared/data/` directory:\n\n```\n@shared/data/\n├── products.yaml\n├── team.yaml\n└── process.js         # Data manipulation script\n```\n\n### Script signature\nExport a default function that receives and returns the accumulated data:\n\n```javascript\n// @shared/data/process.js\nexport default function(data) {\n  // data contains all merged YAML and JSON from this directory level\n  \n  // add computed properties\n  data.featured_products = data.products.filter(p => p.featured)\n  \n  // enrich existing data\n  data.team = data.team.map(member => ({\n    ...member,\n    avatar_url: `/img/team/${member.avatar}`\n  }))\n  \n  // return modified data\n  return data\n}\n```\n\n### Processing order\nScripts run after all YAML and JSON files at the same level are merged:\n\n1. Load all `.yaml` and `.json` files in the directory\n2. Merge them into a single data object\n3. Run any `.js` or `.ts` scripts in alphabetical order\n4. Each script receives the current merged data and returns modified data\n\n\n\n\n## Data compilation\nData precedence from lowest to highest priority:\n\n1. **Start with global data** - Load all `@shared/data/*.yaml` and `*.json` files, then run any `.js`/`.ts` scripts\n2. **Add root-level data** - From `site.yaml`  and other root level .yaml files\n3. **Add app-level data** - From `app.yaml` and app-specific .yaml files\n4. **Add page front matter** - Page-specific overrides\n\nThe front matter metadata is flattened so that `title` property would override `meta.title` in site.yaml or app.yaml. Properties with the same name are always overridden, ie: \"team\" property on app level would override the team array on root level.\n\n\n### Content collections\nCollections defined in [configuration](configuration) become processed arrays. Each collection item includes all front matter properties plus generated metadata:\n\n```javascript\n// Collection item structure\n{\n  title: \"Post Title\",           // from h1 or front matter\n  description: \"Post summary\",   // from subtitle (h1 + p) or front matter\n  date: \"2024-01-15\",            // from front matter\n  url: \"/blog/post-slug/\",       // generated from file path\n  dir: \"/blog/\",                 // directory path\n  slug: \"post-slug\",             // filename without extension\n  author: \"Jane Doe\",            // from front matter\n  tags: [\"web\", \"design\"],       // from front matter\n\n  // ... plus any other front matter properties\n}\n```\n\n#### Generated properties\n- `is_prod` - `true` when site is built for production\n- `url` - Complete URL path generated from file path and name\n- `dir` - Directory path where the file is located\n- `slug` - Filename without extension, used for URL-friendly identifiers\n- Sorting applied as defined in collection configuration\n- Only files matching the `match` patterns are included\n\n\n## Content parsing\nNue automatically extracts content structure and metadata from pages:\n\n**Headings array** - All headings are parsed and made available as structured data:\n\n```javascript\nheadings: [\n  { id: \"hello\", text: \"Hello, World\", level: 1 },\n  { id: \"introduction\", text: \"Introduction\", level: 2 },\n  { id: \"getting-started\", text: \"Getting started\", level: 2 },\n  { id: \"basic-usage\", text: \"Basic usage\", level: 3 },\n  { id: \"advanced-features\", text: \"Advanced features\", level: 2 }\n]\n```\n\n**Title and description** - Automatically extracted from content structure:\n- `title` - From the first `<h1>` element in the content\n- `description` - From the first paragraph following an `<h1>`, or from a standalone paragraph\n\nFront matter values override these automatically parsed values.\n\n## Template context\nHere's what a typical template context looks like as JSON:\n\n```javascript\n{\n  // production flag\n  is_prod: false,\n\n  // flattened metadata (from meta namespace and front matter)\n  \"title\": \"My Site\",\n  \"description\": \"Standards-first web framework\",\n  \"author\": \"Jane Doe\",\n\n  // root-level data from YAML files\n  \"site_name\": \"Acme Inc\",\n  \"company_email\": \"hello@acme.com\",\n\n  // current page properties\n  \"url\": \"/blog/my-post/\",\n  \"dir\": \"/blog/\",\n  \"slug\": \"my-post\",\n\n  // parsed content structure\n  \"headings\": [\n    { id: \"hello\", text: \"Hello, World\", level: 1 },\n    { \"id\": \"overview\", \"text\": \"Overview\", \"level\": 2 },\n    { \"id\": \"features\", \"text\": \"Features\", \"level\": 2 }\n  ],\n\n  // built-in functions to process markdown to HTML\n  \"markdown\": function,\n\n  // team data from @shared/data/team.yaml (processed by scripts)\n  \"team\": [\n    {\n      \"name\": \"Alice Johnson\",\n      \"role\": \"Lead Designer\",\n      \"avatar\": \"alice.jpg\",\n      \"avatar_url\": \"/img/team/alice.jpg\"\n    },\n    {\n      \"name\": \"Bob Smith\",\n      \"role\": \"Frontend Developer\",\n      \"avatar\": \"bob.jpg\",\n      \"avatar_url\": \"/img/team/bob.jpg\"\n    }\n  ],\n\n  // content collection\n  \"blog\": [\n    {\n      \"title\": \"Design Systems at Scale\",\n      \"date\": \"2024-01-15\",\n      \"url\": \"/blog/design-systems-scale/\",\n      \"dir\": \"/blog/\",\n      \"slug\": \"design-systems-scale\",\n      \"description\": \"Building maintainable design systems\",\n      \"author\": \"Alice Johnson\",\n      \"tags\": [\"design\", \"systems\"]\n    },\n    {\n      \"title\": \"Web Standards First\",\n      \"date\": \"2024-01-10\",\n      \"url\": \"/blog/web-standards-first/\",\n      \"dir\": \"/blog/\",\n      \"slug\": \"web-standards-first\",\n      \"description\": \"Why standards matter\",\n      \"tags\": [\"standards\", \"web\"]\n    }\n  ]\n}\n```\n\n## Built-in functions\n\n### markdown function\nProcess markdown content and return HTML:\n\n```html\n<div class=\"content\">\n  {{ markdown(post.description) }}\n</div>\n```\n\nThis function is automatically available in all templates and handles the same Nuemark syntax used in content files.\n\n\n## Template examples\n\n### Basic template\nAccess any data using Nue's template syntax:\n\n```html\n<!doctype html>\n\n<article>\n  <h1>{ site_name } blog</h1>\n\n  <ul>\n    <li :each=\"post in blog\">\n      <a href=\"{ post.url }\">\n        <h2>{ post.title }</h2>\n        <time>{ post.date }</time>\n        <div>{{ markdown(post.description) }}</div>\n      </a>\n    </li>\n  </ul>\n</article>\n```\n\n### Table of contents component\nCreate a `[toc]` tag using the parsed headings data:\n\n```html\n<nav :is=\"toc\">\n  <h3>Table of contents</h3>\n  <ul>\n    <li :each=\"heading in tocHeadings\">\n      <a href=\"#{ heading.id }\">{ heading.text }</a>\n    </li>\n  </ul>\n\n  <script>\n    // Filter to show only h2 headings\n    this.tocHeadings = this.headings.filter(h => h.level == 2)\n  </script>\n</nav>\n```\n\nUse in Nuemark content:\n\n```md\n# Hello, world\n\n[toc]\n\n## First section\nLorem ipsum dolor sit amet...\n\n## Second section\nMore content here...\n```\n\n### Breadcrumb navigation\nUse the `dir` and `slug` properties for navigation:\n\n```html\n<nav :is=\"breadcrumbs\">\n  <a href=\"/\">Home</a>\n  <span :if=\"dir != '/'\">\n    <span>/</span>\n    <a href=\"{ dir }\">{ dir.replace('/', '') }</a>\n  </span>\n  <span :if=\"slug\">\n    <span>/</span>\n    <span>{ slug }</span>\n  </span>\n</nav>\n```\n\nThis automatically generates breadcrumbs like: Home / blog / my-post"
  },
  {
    "path": "packages/www/docs/ui/docs.css",
    "content": "\n@layer layout {\n\n  .topics {\n    font-size: var(--ui-sizing);\n    margin-top: 2em;\n    display: flex;\n\n    nav { margin: 0 10vw 3em 0 }\n\n    small {\n      font-size: inherit;\n      color: var(--text-subtle);\n      @media (500px < width < 900px) {\n        display: none;\n      }\n    }\n\n    @media (width < 700px) {\n      flex-wrap: wrap;\n    }\n  }\n\n  .learn-more {\n\n    .stack {\n      font-size: var(--ui-sizing);\n      margin-block: 1.5em 2em;\n      a { max-width: 13em }\n      gap: .75em;\n    }\n\n    [aria-current] {\n      background-color: var(--inverse);\n      color: var(--inverse-text);\n      padding: .25em .5em;\n      pointer-events: none;\n      margin-left: -.5em;\n      width: auto;\n\n      strong { color: white }\n      &:has(.row) { max-width: 25em }\n    }\n  }\n}"
  },
  {
    "path": "packages/www/docs/ui/docs.html",
    "content": "\n<!--\n  topics and getTopicCategory defined in @shared/data/data.js\n-->\n\n<nav :is=\"topics\" class=\"stack\">\n  <a :each=\"{ slug, desc, title } in topics[category]\" href=\"/docs/{ slug }\">\n    <template :if=\"!desc\">{ title }</template>\n    <div :if=\"desc\" class=\"row\">\n      <strong>{ title }</strong>\n      <small>{ desc }</small>\n    </div>\n  </a>\n</nav>\n\n<pagefoot class=\"learn-more\">\n  <h2>Learn more</h2>\n\n  <topics :topics :category=\"getTopicCategory(slug)\"/>\n\n  <p>\n    <a href=\"/docs/\"><strong>View all topics</strong></a>\n  </p>\n</pagefoot>\n"
  },
  {
    "path": "packages/www/docs/universal-data-model.md",
    "content": "\n# Universal data model\nNue provides a single data architecture that works the same way for all online businesses.\n\n> **Local development only**: This currently works as a local mockup for prototyping. Production deployments with CloudFlare integration come later in the [roadmap](roadmap).\n\n## The problem\nEvery online business needs the same things: users, authentication, payments, subscriptions, analytics, email campaigns, support tickets. But each one requires a different service with its own API.\n\nStripe for payments. Auth0 for users. Mailchimp for email. Google Analytics for tracking. Intercom for support. Each service has its own data format, its own query language, its own way of doing things.\n\nWant to see which customers from last month's email campaign made a purchase? You need to query Mailchimp, match email addresses in Stripe, cross-reference with your user database. Three APIs, three authentication methods, three different ways to filter and sort data.\n\nEvery feature becomes an integration project. Every question needs custom code to connect disparate systems.\n\n\n## One model for everything\nFuture versions of Nue provide ready-made objects for everything an online business needs:\n\n**Users and authentication** - Sign up, login, sessions, password resets. Works the same locally and on CloudFlare Workers.\n\n**Payments and subscriptions** - Charges, refunds, recurring billing. Track revenue without external payment processors for development.\n\n**Analytics and events** - Page views, conversions, custom events. Query your entire history at JavaScript speed.\n\n**Email campaigns** - Subscribers, campaigns, open rates, conversions. Build mailing list features without third-party services.\n\n**Customer relationships** - Contacts, notes, support tickets. CRM functionality built in.\n\nThe same API works everywhere. No environment variables. No switching between development mocks and production services. No integration complexity.\n\n\n## How it works\nDuring development, the model generates from JSON files in your project. For production, it connects to CloudFlare's edge infrastructure. The API stays identical:\n\n```javascript\n// Works in development and production, client and server\nconst { users, payments, events } = c.env\n\n// Server-side: simple CRUD operations\nconst user = await users.get(id)\nawait users.update(id, { role: 'admin' })\n\n// Client-side: query your data however you want\nconst revenue = events\n  .filter(e => e.type == 'charge')\n  .filter(e => e.ts >= lastMonth)\n  .reduce((sum, e) => sum + e.amount, 0)\n```\n\nNo schemas, nor query builders. Just JavaScript working with your data.\n\n\n## Why this matters\nBuilding the [analytics application](roadmap#analytics-application) requires solving the same problems every business application faces: authentication, data processing, aggregations, historical queries. By building analytics as a real product, we create the foundation that works for any business.\n\nThe SPA templates in the [roadmap](roadmap#more-to-come) (admin dashboards, CRM, mailing lists, payment processing) all build on this same model.\n\nThis is what makes Nue a complete foundation for building online businesses\n\n\n"
  },
  {
    "path": "packages/www/docs/website-development.md",
    "content": "\n# Website development\nWebsites are content-driven applications where information comes first. Blogs, documentation, marketing pages, company sites, and portfolios all follow this pattern: the content defines the structure, and the design system presents it.\n\n## Getting started\nLet's start with the the simplest of websites:\n\n```bash\nnue create blog\n```\n\nThis generates a content-focused file structure:\n\n```\n├── site.yaml          # global configuration\n├── layout.html        # shared header and footer\n├── index.md           # front page\n├── index.css          # design\n└── posts/\n    ├── header.html    # blog post hero section\n    ├── first.md       # example post\n    └── second.md      # example post\n```\n\nThe structure separates the concerns. Content lives in Markdown files. Design lives in CSS. Templates live in HTML. Configuration lives in YAML.\n\n\n## Layout modules\nContent-focused apps share common elements across pages. Headers, footers, navigation, and hero sections appear on multiple pages. Instead of repeating this HTML, you create layout modules that Nue assembles automatically.\n\n### Global layout\nPut site-wide elements in `layout.html` at your project root:\n\n```html\n<!-- this becomes your global site header -->\n<header>\n  <a href=\"/\" class=\"logo\">{ site_name }</a>\n  <nav>\n    <a href=\"/blog\">Blog</a>\n    <a href=\"/about\">About</a>\n    <a href=\"/contact\">Contact</a>\n  </nav>\n</header>\n\n\n<!-- this becomes your global site footer -->\n<footer>\n  <p>&copy; 2025 { site_name }. All rights reserved.</p>\n  <nav>\n    <a href=\"/privacy\">Privacy</a>\n    <a href=\"/terms\">Terms</a>\n  </nav>\n</footer>\n```\n\nThis header and footer appear on every page automatically. The `{ site_name }` variable comes from your `site.yaml` configuration.\n\n\n### Section-specific layout\nCreate area-specific modules by placing layout files in subdirectories:\n\n```\nblog/\n├── layout.html        # blog-specific modules\n├── my-first-entry.md\n├── form-follows-function.md\n└── content-first.md\n```\n\nModules in `blog/layout.html` only apply to pages in the `blog/` directory. They override global modules when both exist.\n\n\n### Blog post headers\nBlog posts often need special hero sections with titles, dates, and author information. Create these with the `pagehead` slot:\n\n```html\n<!-- In posts/header.html or blog/layout.html -->\n<pagehead>\n  <h1>{ title }</h1>\n  <time>{ date }</time>\n  <p>{ description }</p>\n</pagehead>\n```\n\nThis appears above your post content, creating a consistent header design across all blog posts.\n\n\n## Writing with Nuemark\nNuemark is Nue's content format. It extends standard Markdown with layout capabilities, making it perfect for content-focused apps.\n\n### Basic content\nWrite naturally, using standard Markdown:\n\n```md\n---\ntitle: Getting started with design systems\ndate: 2024-01-15\ndescription: How to build consistent interfaces\n---\n\n# Getting started with design systems\n\nDesign systems create consistency across your entire product. They're not just style guides - they're the foundation for scalable interface development.\n\n## Key principles\n\n**Start with constraints** - Fewer choices lead to more consistent outcomes.\n\n**Design tokens first** - Define colors, spacing, and typography before building components.\n\n**Document everything** - If it's not documented, it doesn't exist.\n```\n\nThe front matter provides metadata for your layout modules and collections. The content becomes the main article body.\n\n### Rich layouts\n\nUse Nuemark's block syntax to create structured layouts:\n\n```md\n[.hero]\n  # Transform your interface development\n  Build consistent, maintainable designs that scale with your team.\n\n  [button \"Learn more\" href=\"/docs/\"]\n  [button.primary \"Get started\" href=\"/get-started/\"]\n\n[.stack]\n  ## Design tokens\n  Central source of truth for colors, spacing, and typography.\n\n  ## Component library\n  Reusable interface elements that follow your design system.\n\n  ## Documentation\n  Living style guide that stays in sync with your code.\n```\n\nThis creates semantic HTML structure that your design system can style consistently. No CSS classes mixed into your content.\n\n### Custom components\n\nExtend Nuemark with custom tags for rich content elements:\n\n```html\n<!-- In layout.html or components library -->\n<blockquote :is=\"testimonial\">\n  <p>{ quote }</p>\n  <footer>\n    <cite>{ author }</cite>\n    <span>{ role } at { company }</span>\n  </footer>\n</blockquote>\n\n<div :is=\"stats\">\n  <div :each=\"stat in stats\">\n    <strong>{ stat.number }</strong>\n    <span>{ stat.label }</span>\n  </div>\n</div>\n```\n\nUse these components in your Markdown content:\n\n```md\n[testimonial]\n  quote: This framework changed how our team builds interfaces\n  author: Sarah Chen\n  role: Lead Designer\n  company: Example Corp\n\n[stats]\n  stats:\n    - number: 50%\n      label: Faster development\n    - number: 90%\n      label: Fewer bugs\n    - number: 100%\n      label: Designer satisfaction\n```\n\n## Collections\n\nContent-focused apps need to organize and display groups of related content. Collections make this automatic.\n\n\n### Configuring collections\nDefine collections in your `site.yaml`:\n\n```yaml\ncollections:\n  blog:\n    match: [posts/*.md]\n    sort: date desc\n\n  docs:\n    match: [docs/**/*.md]\n    sort: order asc\n\n  team:\n    match: [team/*.md]\n    sort: name asc\n```\n\nCollections automatically gather matching files and make them available as variables in your templates.\n\n### Displaying collections\n\nUse collections in your Markdown content to create dynamic lists:\n\n```md\n---\ntitle: Latest blog posts\n---\n\n# Our blog\n\nStay updated with the latest insights on design and development.\n\n[blog-entries]\n\n---\n\n[button \"View all posts\" href=\"/blog/\"]\n```\n\nCreate the `blog-entries` component to render your collection:\n\n```html\n<!-- In layout.html -->\n<div :is=\"blog-entries\">\n  <article :each=\"post in blog\" class=\"post-preview\">\n    <h2><a href=\"{ post.url }\">{ post.title }</a></h2>\n    <time>{ post.date }</time>\n    <p>{ post.description }</p>\n  </article>\n</div>\n```\n\nThis automatically displays all posts from your `blog` collection, sorted by date in descending order.\n\n\n### Collection metadata\nCollections provide rich metadata for each item:\n\n```javascript\n// Available for each collection item\n{\n  title: \"Post title\",           // from front matter\n  date: \"2024-01-15\",           // from front matter\n  description: \"Post summary\",   // from front matter\n  url: \"/posts/my-post/\",       // generated from filename\n  // ... any other front matter properties\n}\n```\n\nUse this metadata to create rich previews, category filters, or search functionality.\n\n\n## HTML pages for structure\nWhile Nuemark handles most content needs, sometimes you need full structural control. Compare the same homepage implemented with HTML:\n\n\n```html\n<!doctype html>\n\n<main>\n  <section class=\"hero\">\n    <h1>Our blog</h1>\n    <p>Stay updated with the latest insights on design and development.</p>\n  </section>\n\n  <section class=\"featured-posts\">\n    <div class=\"post-grid\">\n      <article :each=\"post in blog.slice(0, 3)\">\n        <h3><a href=\"{ post.url }\">{ post.title }</a></h3>\n        <time>{ post.date }</time>\n        <p>{ post.description }</p>\n      </article>\n    </div>\n  </section>\n</main>\n```\n\n### When to use HTML vs Nuemark\n\n**Use Nuemark for content pages** when writers need to safely edit content without breaking layouts. Blog posts, documentation, and marketing pages work well with Nuemark's structured approach and predefined components.\n\n**Use HTML for complex layouts** when you need precise structural control that Nuemark's block syntax can't express. Admin interfaces, dashboards, and forms with complex validation often require the full flexibility of HTML.\n\nBoth approaches work together in the same project. Your homepage might use HTML for a custom hero section while your blog uses Nuemark for consistent, editable content.\n\n\n## Add interactivity\nContent-focused apps often need interactive elements like newsletter signups, contact forms, or feedback widgets. Create these as dynamic, reusable HTML:\n\n```\n<!-- In components.html (a new document) -->\n\n<!dhtml>\n\n<form :is=\"member-form\" :onsubmit=\"submit\">\n  <label>\n    <h3>Email</h3>\n    <input type=\"email\" name=\"email\" required autocomplete=\"email\">\n  </label>\n\n  <label>\n    <h3>Feedback</h3>\n    <textarea name=\"comment\" rows=\"4\"\n      placeholder=\"Optional, but highly valued!\"></textarea>\n  </label>\n\n  <p>\n    <button>Join mailing list</button>\n  </p>\n\n  <script>\n    async submit(e) {\n      const data = Object.fromEntries(new FormData(e.target))\n      await postMember(data)\n      location.href = '/contact/thanks'\n    }\n  </script>\n</form>\n```\n\nUse this component in your content:\n\n\n```md\n# About our company\n\nWe believe technology should serve people, not the other way around.\n\n## Stay connected\n\n[member-form]\n```\n\nThe same component works on your layout modules too - in content pages, footers, or sidebars. Add it to your site footer in `layout.html`:\n\n```\n<footer>\n  <section>\n    <h3>Stay updated</h3>\n    <p>Get our latest insights delivered to your inbox.</p>\n>   <member-form/>\n  </section>\n\n  <p>&copy; 2025 { site_name }. All rights reserved.</p>\n</footer>\n```\n\n[.note]\n  ### Note\n  Nuemark uses square bracket syntax `[member-form]` for content authors, and layout files uses HTML syntax `<member-form/>`.\n\n\n\n## Content-first workflow\nContent-focused apps work best when you start with content and add structure around it.\n\n### Start with content\nWrite your content first using standard Markdown. Don't worry about layout or styling initially:\n\n```md\n# About our company\nWe believe technology should serve people, not the other way around.\n\n## Our mission\nTo build software that makes complex things simple.\n\n## Our team\nWe're a small group of ambitious designers and developers.\n\n[team]\n\n```\n\nYour structural data lives in YAML (or in database, explained [later](/docs/server-development)):\n\n```yaml\n// in team.yaml\nteam:\n  - name: Sarah Chen\n    role: Lead Designer\n    avatar: sarah.jpg\n\n  - name: Marcus Rodriguez\n    role: Frontend Developer\n    avatar: marcus.jpg\n\n  - name: Emma Thompson\n    role: Content Strategist\n    avatar: emma.jpg\n\n  - name: David Kim\n    role: Backend Engineer\n    avatar: david.jpg\n```\n\n### Add structure\nCreate reusable components for structured content:\n\n```html\n<div :is=\"team\" class=\"{ class }\">\n  <div :each=\"member in team\" class=\"card\">\n    <img src=\"/img/team/{ member.avatar }\" alt=\"{ member.name }\">\n    <h3>{ member.name }</h3>\n    <p>{ member.role }</p>\n  </div>\n</div>\n```\n\nEnrich your content with Nuemark capabilities:\n\n\n```md\n# About our company\nWe believe technology should serve people, not the other way around.\n\n## Our mission\nTo build software that makes complex things simple.\n\n[.columns]\n  ## Our team\n\n  [team.stack]\n\n  ---\n  [image]\n    small: /img/team/team.png\n    large: /img/team/team@2x.png\n...\n```\n\n\n### Start drafting your design\nYour CSS should be the single source of truth for all visual decisions:\n\n```css\n.stack {\n  display: flex;\n  flex-wrap: wrap;\n  /* ... */\n}\n\n.card {\n  background-color: var(--base-50);\n  box-shadow: var(--card-shadow);\n  /* ... */\n}\n\n.columns {\n  column-count: var(--count, 2);\n  column-gap: var(--m);\n  /* ... */\n}\n\n.grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n  /* ... */\n}\n```\n\nThis separation lets content creators focus on writing while designers control presentation through the design system. Neither blocks the other.\n\n\n"
  },
  {
    "path": "packages/www/docs/why-nue-old.md",
    "content": "\n# The UNIX of the Web\n\"Do one thing and do it well\". Nue is a shift from monolithic components to architectural sanity. It changes the way you think about web development.\n\n\n## The kitchen sink problem\nReact emerged in 2013 to make HTML \"reactive\". It started small and focused: the first version was just the view layer, deliberately leaving state management and routing to other tools. But then it went on a trajectory nobody could foresee. The ecosystem grew into a mountain of packages beyond anything imaginable. Today, a Next.js project created with `create-next-app` weighs 427MB just to display \"Hello, World.\"\n\nThe UNIX tradition has a name for this: kitchen sink software. Codebases that try to do everything inevitably do nothing well. They become unmaintainable, unpredictable, and harder to reason about.\n\nMeanwhile, the web evolved dramatically. The web in 2025 has capabilities that didn't exist in 2013. HTML can describe complex applications. CSS creates real design systems. JavaScript became ES6: practically a new language. Browser APIs handle what once required libraries.\n\nWhile React stayed on its own path, adding more and more packages. Nue stays close to metal and takes HTML, CSS, and JavaScript to their absolute peak.\n\n\n## The UNIX way\nNue is a new take on web frameworks. It uses zero external dependencies, every piece handcrafted to integrate perfectly, all focused on delivering the absolute best developer experience.\n\nThe result is remarkable: a complete **full-stack development environment in 1MB**. That includes content-focused apps, single-page applications, server-side rendering, hot reloading, syntax highlighting, CloudFlare compatible local dev environment with SQL database, KV database, and more. You'd need both Next.js and Astro to get even close to the feature set.\n\nWhile an empty Next.js project weighs 427MB, an empty Nue project is just an `index.html` file.\n\n\n### New performance tier\nNue operates at a completely different performance tier: **Build times** drop from 10+ seconds to 100 milliseconds. **Hot reload** happens in 10ms instead of taking seconds. **Bundle sizes** shrink so dramatically that entire single-page applications weigh less than individual React components.\n\nYou get instant feedback loops, dramatically less complexity, fewer bugs, easier debugging with thinner stack traces, and easier to maintain products.\n\n\n### Rapid application development\nReact's component model encourages everything-in-one thinking. Your application code becomes another kitchen sink: business logic mixed with styling mixed with markup mixed with data fetching.\n\nNue enforces architectural clarity. System code (logic, data, design) stays separate from application code (structure and content). Your SPAs focus solely on HTML structure. Your marketing pages become pure content.\n\nThe result: codebases that are easier to understand, maintain, and scale. You build applications by assembling pieces, not debugging JavaScript monoliths.\n\n\n## Standards are forever\nThe biggest tragedy in React is technical debt. Remember Redux? Higher-Order Components? Class components? Enzyme? Each solving problems that exist in the React ecosystem only, now forgotten and replaced with the next trend. Skills you learn today risk becoming obsolete in 2-3 years.\n\nWeb standards persist. HTML from 2006 still works. CSS only grows more powerful. JavaScript remains the language of the web. These skills compound over decades to come.\n\nProducts built on standards remain fresh forever. Your investment in web fundamentals never expires.\n\n"
  },
  {
    "path": "packages/www/docs/why-nue.md",
    "content": "\n# The UNIX of the Web\nWeb development became complicated. Hundreds of packages, 400MB of dependencies, hours of configuration before writing a single line of code. We forgot that it doesn't have to be this way.\n\n\n## How web development should work\n\n**Instant start** - Create `index.html` or `index.md` and you're running. No setup, no configuration, no waiting.\n\n**Single-page apps** - Write semantic HTML with dynamic expressions. Import business logic from pure JavaScript modules. Let your design system handle presentation.\n\n**Content sites** - Front pages, documentation, blogs, marketing pages. Write Nuemark content, add layout modules for structure, trust your design system for consistency.\n\n**Universal hot reload** - Content, CSS, layouts, data, components, server routes, configurations. Save and watch the browser update instantly.\n\n**Complete system** - Content sites, SPAs, server routes, backend models.\n\nOne tool, complete control. The UNIX philosophy applied to web development.\n\n**Nue is the entire ecosystem in 1MB**\n\n\n## The kitchen sink problem\nReact emerged in 2013 to make HTML \"reactive\". It started small and focused: the first version was just the view layer, deliberately leaving state management and routing to other tools. But then it went on a trajectory nobody could foresee. The ecosystem grew into a mountain of packages beyond anything imaginable. Today, a Next.js project created with `create-next-app` weighs 427MB just to display \"Hello, World.\"\n\nThe UNIX tradition has a name for this: kitchen sink software. Codebases that try to do everything inevitably do nothing well. They become unmaintainable, unpredictable, and harder to reason about.\n\nMeanwhile, the web evolved dramatically. The web in 2025 has capabilities that didn't exist in 2013. HTML can describe complex applications. CSS creates real design systems. JavaScript became ES6: practically a new language. Browser APIs handle what once required libraries.\n\nWhile React stayed on its own path, adding more and more packages, Nue stays close to metal and takes HTML, CSS, and JavaScript to their absolute peak.\n\n\n## Why this matters\n\n**Instant feedback:** Build times in milliseconds, not seconds. Hot reload across all assets in 10ms.\n\n**Tiny bundles:** Complete SPAs smaller than individual React components. Marketing sites under 30KB.\n\n**Clear architecture:** Business logic separated from design separated from content. Each layer scales independently.\n\n**Standards-based:** HTML, CSS, and JavaScript that work everywhere. No framework APIs to learn, no migration paths to navigate.\n\n\n### Standards are forever\nThe biggest tragedy in React is technical debt. Remember Redux? Higher-Order Components? Class components? Enzyme? Each solving problems that exist in the React ecosystem only, now forgotten and replaced with the next trend. Skills you learn today risk becoming obsolete in 2-3 years.\n\nWeb standards persist. HTML from 2006 still works. CSS only grows more powerful. JavaScript remains the language of the web. These skills compound over decades.\n\nProducts built on standards remain fresh forever. Your investment in web fundamentals never expires.\n\n\n## Who this is for\n\n**Solo developers** building client projects who want to 10x their output.\n\n**Technical founders** who need to ship products fast without framework overhead.\n\n**Full-stack developers** who prefer backends but need admin interfaces.\n\n**Agencies** delivering custom dashboards to small businesses.\n\n**Anyone** who remembers when web development was straightforward and wants that back.\n\n\n## Try it\n\n```bash\nbun install -g nuekit\nnue create blog\ncd blog\nnue\n```"
  },
  {
    "path": "packages/www/docs/yaml-syntax.md",
    "content": "# YAML syntax\n\nComplete reference for Nueyaml's simplified YAML syntax. For an overview and introduction, see the [Nueyaml documentation](/docs/nueyaml).\n\n## File structure\n\nAll Nueyaml files must start with an object at the root level. The parser always returns an object, never an array or primitive value.\n\n```yaml\n# Valid - root object\nname: My App\nversion: 1.0.0\n\n# Invalid - root array\n\\- item1\n\\- item2\n```\n\nFiles are encoded in UTF-8.\n\n## Data types\n\n### Strings\n\nEverything is a string by default. No quotes required unless forcing a value that looks like another type.\n\n```yaml\nname: John Doe\ntitle: Senior Developer\nmessage: Hello world\ncountry: NO             # string \"NO\", not false\ntime: 12:30             # string \"12:30\", not 750 minutes\n```\n\nQuoted strings are automatically unwrapped for YAML compatibility:\n\n```yaml\nmessage: \"Hello world\"  # becomes: Hello world\nnote: a \"quoted\" word   # becomes: a \"quoted\" word\nforce: \"123\"            # stays string \"123\", not number\n```\n\n### Numbers\n\nOnly integers and decimals are parsed as numbers. Must look like a number to a human.\n\n```yaml\nage: 30                 # integer\nprice: 19.99            # decimal\ncount: 0                # zero\nnegative: -42           # negative integer\n```\n\nNot supported:\n```yaml\nscientific: 1.2e3       # string \"1.2e3\"\noctal: 0o755            # string \"0o755\"\nhex: 0xFF               # string \"0xFF\"\nunderscore: 1_000       # string \"1_000\"\n```\n\n### Booleans\n\nOnly `true` and `false` are parsed as booleans. Everything else is a string.\n\n```yaml\nenabled: true\nvisible: false\nactive: yes             # string \"yes\"\non: ON                  # string \"ON\"\n```\n\n### Dates\n\nSingle ISO format only: `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM:SSZ`\n\n```yaml\ncreated: 2024-01-15\nupdated: 2024-01-15T10:30:00Z\n```\n\nOther formats become strings:\n```yaml\nus_date: 01/15/2024      # string\neuro_date: 15.01.2024    # string\ntext_date: Jan 15, 2024  # string\n```\n\n### Null\n\nEmpty values become null.\n\n```yaml\ndescription:      # null\navatar:           # null\n```\n\n### Arrays\n\nSupport both inline and block syntax.\n\n```yaml\n# Inline arrays\ntags: [frontend, javascript, react]\nnumbers: [1, 2, 3]\nmixed: [hello, 42, true]\n\n# Block arrays\ntags:\n  - frontend\n  - javascript\n  - react\n\n# Nested arrays\nmatrix:\n  - [1, 2, 3]\n  - [4, 5, 6]\n  - [7, 8, 9]\n```\n\n### Objects\n\nBlock syntax only. No inline object support.\n\n```yaml\n# Valid block object\nuser:\n  name: John Doe\n  age: 30\n  active: true\n\n# Invalid inline object\nuser: {name: John, age: 30}  # becomes string\n```\n\n## Multi-line strings\n\nWhen a value continues on the next line with increased indentation, it becomes a multi-line string.\n\n```yaml\ndescription:\n  This is a multi-line\n  string that preserves\n  line breaks exactly\n  as written.\n\n# Result: \"This is a multi-line\\nstring that preserves\\nline breaks exactly\\nas written.\"\n```\n\nMulti-line strings preserve:\n- Line breaks\n- Indentation relative to the first line\n- Trailing whitespace on each line\n- Blank lines within the content\n\n```yaml\ncode_example:\n  function hello() {\n    console.log('Hello')\n  }\n\n  hello()\n\n# Preserves the blank line and indentation\n```\n\n## Property names\n\nProperty names can contain any characters, including colons and special characters. The parser looks for `: ` (colon followed by space) as the key-value separator.\n\n```yaml\n# Special characters in keys\n/api/users/:id: getUserHandler\n/api/posts: getPostsHandler\n@media (max-width: 768px): mobile-styles\nusers[0].name: John\napi:key: secret-value\nwith spaces: allowed\n```\n\nThe only requirement is the `: ` separator:\n```yaml\nkey:value              # invalid - no space after colon\nkey: value             # valid\nkey : value            # valid - extra spaces ok\ncomplex:key: value     # valid - colon in key name\n```\n\nNueyaml does not support quoted property names:\n```yaml\n\"quoted key\": value    # not supported\n'single quotes': value # not supported\n```\n\n## Indentation and whitespace\n\nIndentation must use spaces only. Tabs are not allowed for indentation.\n\n```yaml\n# Valid - consistent 2-space indentation\napp:\n  name: Test\n  config:\n    debug: true\n\n# Valid - consistent 4-space indentation\napp:\n    name: Test\n    config:\n        debug: true\n\n# Invalid - mixed indentation\napp:\n  name: Test\n   config: mixed       # Error: not a multiple of 2\n```\n\nThe parser detects indentation size from the first indented line and enforces consistent multiples throughout.\n\nWhitespace rules:\n- Trailing spaces are ignored\n- Blank lines are allowed anywhere\n- Tabs are permitted within string values\n- Tabs cannot be used for structural indentation\n\n\n## Comments\nUse `#` for comments. Comments must be preceded by whitespace to avoid conflicts with values containing `#`.\n\n```yaml\n# Full-line comment\nname: John Doe         # Inline comment\npassword: secret#123   # The # in password is not a comment\napi_key: sk-1234#abcd  # Safe for cryptic values\n\n# Multi-line comments need\n# a # on each line\n```\n\nComment detection:\n```yaml\nvalid # comment        # Space before # makes it a comment\nno#comment            # No space, so # is part of the value\nalso#not#comment      # Multiple # without spaces are kept\n```\n\n## Error handling\n\nThe parser provides clear error messages with line numbers:\n\n```yaml\n# Example errors:\nuser:\n  name: John\n   age: 30            # Error: Line 3: Inconsistent indentation\n\ndata:\n  - item1\n  item2              # Error: Line 3: Expected array item indicator (-)\n```\n\n## Limitations\n\n### Not supported from YAML\n\n- **Anchors and references** (`&anchor`, `*reference`)\n- **Merge keys** (`<<: *defaults`)\n- **Tags** (`!!str`, `!!timestamp`)\n- **Multiple documents** (`---`, `...`)\n- **Flow collections** spanning multiple lines\n- **Block scalar indicators** (`|`, `>`, `|-`, `>+`)\n- **Escaped sequences** (`\\n`, `\\t`, `\\u0041`)\n- **Alternative booleans** (`yes`, `no`, `on`, `off`, `YES`, `True`)\n- **Multiple date formats**\n- **Binary data**\n- **Sets and ordered maps**\n- **Directives** (`%YAML`, `%TAG`)\n\n### Intentional restrictions\n\n- Root must be an object (not array or scalar)\n- No inline objects with `{}`\n- No quoted property names\n- No scientific notation\n- No octal or hexadecimal numbers\n- Tabs only for indentation (not content)\n- Single date format only\n\n## Complete example\n\n```yaml\n# Application configuration\napp:\n  name: My Application\n  version: 1.2.0\n  debug: false\n  launched: 2024-01-15\n\n# Database settings\ndatabase:\n  host: localhost\n  port: 5432\n  credentials:\n    username: admin\n    password:           # null for environment variable\n\n  connection_string:\n    postgresql://admin@localhost:5432/myapp\n    ?ssl=true&timeout=10\n\n# API routes - special characters in keys\n/api/users/:id: getUserHandler\n/api/posts: getPostsHandler\n/api/auth/login: loginHandler\n\n# Security\napi_keys:\n  github: ghp_xxxxxxxxxxxxxxxxxxxx\n  stripe: sk-test_#################\n  aws: AKIA#############\n\n# Features array\nfeatures: [auth, analytics, caching]\nexperimental:\n  - new-dashboard\n  - dark-mode\n  - websockets\n\n# Responsive design breakpoints\nbreakpoints:\n  @media (max-width: 768px): mobile\n  @media (max-width: 1024px): tablet\n  @media (min-width: 1025px): desktop\n\n# Multi-line content\nwelcome_message:\n  Welcome to our application!\n\n  This message spans multiple lines\n  and preserves blank lines and\n  indentation exactly as written.\n\n# Server configuration\nservers:\n  - name: web-01\n    ip: 192.168.1.10\n    active: true\n    roles: [web, api]\n\n  - name: web-02\n    ip: 192.168.1.11\n    active: false\n    roles: [web]\n\n  - name: db-01\n    ip: 192.168.1.20\n    active: true\n    roles: [database, cache]\n\n# Metadata\n_internal:              # Leading underscore is fine\n  build_number: 4567\n  commit: abc123def\n  timestamp: 2024-01-15T10:30:00Z\n```"
  },
  {
    "path": "packages/www/home/app.yaml",
    "content": "\nclass: dark heroic\ninclude: [ assembly, console, hero, stack, syntax ]\ntitle_template: false\n\ndesign:\n  inline_css: true"
  },
  {
    "path": "packages/www/home/commands.txt",
    "content": "\n$ # 1/3: creating a website\n\n$ bun install --global nuekit\n✓ Downloading packages... [850ms]\n✓ 5 packages installed (0.875MB) [51ms]\n\n$ nue crate^^^eate full\n✓ Downloading template... [352ms]\n✓ Unpacked \"full\" (1.5MB) [10ms]\n✓ Site loaded & created in <u>362ms</u>\n\n$ # press enter to see `nue dev`\n\n---\n$ # 2/3: developing with Nue\n\n$ cd full && nue dev\n✓ Read 8 pages and 43 assets [10ms]\n✓ Users dev model loaded (3 records) [3ms]\n✓ Leads dev model loaded (100 records) [2ms]\n✓ 5 worker routes loaded at /api/* [4ms]\n✓ Started serving: <u>http://localhost:4000</u> [41ms]\n\n⚡ <b>HMR: Watching for file updates:</b>\n1s: ✓ pushed index.md [15ms]\n2s: ✓ pushed base.css [8ms]\n2s: ✓ pushed components.html [12ms]\n1s: ✓ site.yaml update [25ms]\n1s: ✓ server routes updated [5ms]\n\n^C\n5 files updated\n\n$ # press enter for `nue build`\n---\n\n$ # 3/3: building for production\n\n$ nue build\n📦 <b>Building shared libs:</b>\n✓ Business model: 5 TS files (8KB) [8ms]\n✓ Design system: 14 CSS files (8KB) [9ms]\n✓ UI components: 5 files [7ms]\n\n📦 <b>Building pages:</b>\n✓ products/ (6 pages) [11ms]\n✓ blog/ (28 pages) [22ms]\n✓ docs/ (33 pages) [25ms]\n✓ Front page [3ms]\n\n📦 <b>Building apps:</b>\n✓ login (1 file) [4ms]\n✓ admin (8 files) [9ms]\n✓ Build complete in <u>98ms</u>\n\n$ # nue is fast\n"
  },
  {
    "path": "packages/www/home/home.css",
    "content": "\n@layer component {\n  .cta-buttons { gap: .75em }\n\n  /* captions */\n  [class] + p {\n    color: var(--text-subtle);\n    font-size: .875em\n  }\n}\n"
  },
  {
    "path": "packages/www/index.md",
    "content": "---\ntitle: The UNIX of the Web\n---\n\n# Fastest way to build modern websites\nNue is a development environment designed the UNIX way\n\n[cta-buttons]\n\n[console]\nNue is small and fast\n\n\n## The entire ecosystem in 1MB\nWhat used to be 500MB is now 1MB\n\n[nue-stack]\nFull-stack development without the bloat and complexity\n\n\n## Rapid application assembly\nMove fast by focusing on content and structure\n\n[nue-assembly]\nConstruct apps and pages with a scalable architecture\n\n\n\n"
  },
  {
    "path": "packages/www/package.json",
    "content": "{\n  \"name\": \"www\",\n  \"description\": \"Nue website\",\n  \"homepage\": \"https://nuejs.org\",\n  \"private\": true,\n  \"engines\": {\n    \"bun\": \">= 1.2\"\n  }\n}\n"
  },
  {
    "path": "packages/www/site.yaml",
    "content": "\n# Nue settings\nsite:\n  origin: https://nuejs.org\n  view_transitions: true\n\nmeta:\n  title: The UNIX of the Web\n  tagline: The UNIX of the Web\n  title_template: %s / Nue\n  description: Nue is the fastest way to build modern websites\n  favicon: /img/favicon.svg\n  origin: https://nuejs.org\n  og: /img/og.png\n\ncontent:\n  sections: true\n\ncollections:\n  blog:\n    include: [ blog ]\n    skip: [ noindex, draft ]\n    sort: date desc\n\ndesign:\n  layers: [ design, layout, component ]\n  inline_css: true\n\nsitemap:\n  enabled: true\n\nrss:\n  title: Nue developer blog\n  description: The UNIX of the Web\n  collection: blog\n  enabled: true\n\n\nteaser:\n  text: Nue 2.0 is out ->\n  url: /blog/2.0/\n  tag: New\n\ngh:\n  url: https://github.com/nuejs/nue\n  count: 8.8k\n\nslack:\n  url: //join.slack.com/t/nuejs/shared_invite/zt-2wf8ozu5i-N2Y9PA_D17weIWuN2QPOqQ\n\njoin:\n  title: 3.0 is coming\n  desc: Get email when [multi-site development](/docs/roadmap) is ready\n  form:\n    comment: Comment (Optional, but valued!)\n    done: We'll email you when the next major release is out\n    cta: Ping me\n\n"
  },
  {
    "path": "packages/www/test.md",
    "content": "---\nbeside: false\nclass: dark\n---\n\n[nue-assembly]\n"
  }
]