The website at levimc.org: Vite + React 19 + react-router 7 + TypeScript + Tailwind CSS v4,
styled with the shared LeviMC design system, levimc-kit, which lives here in kit/.
levimc-kit is the one source of the LeviMC chrome — the bar on top (Home · Projects ▾ · Mods · Blog · Community ·
About, language · theme · GitHub), the footer, the theme and the docs engine — for every LeviMC site: levimc.org
(and LeviMC Mods at levimc.org/mods/, see below) and the project docs at levimc.org// (LeviLamina with its Doxygen API, LegacyScriptEngine, lip,
LeviLauncher for Windows and Android). Every deploy publishes it at https://levimc.org/kit/v2/ (kit/scripts/publish-kit.mjs);
the other sites fetch it when they build (scripts/fetch-kit.mjs, run by their package install) and never edit it.
After a deploy that changed the kit, the sync job rebuilds every site (with the KIT_SYNC_TOKEN secret: a
fine-grained token with Actions: read and write on those repositories); each site also checks hourly and rebuilds
when its live pages run an older kit. node kit/scripts/chrome-parity.mjs checks that every live site shows the
same bar and footer.
Every route is prerendered to static HTML at build time and hydrated in the browser; GitHub Pages serves dist/.
Prerequisites: Node.js 22.12+ (CI uses Node 24)
# Install dependencies
npm ci
# Run development server
npm run dev
# Type-check the app and the kit components
npm run typecheck
# Unit tests: the blog's content build, and the kit's cross-site preference tests
npm test
# Build for production: client build, SSR build, prerender every route into dist/
npm run build
# Preview the build with GitHub Pages semantics (http://127.0.0.1:4173/)
npm run preview
# Check every internal link in dist/ (add --external to also check external URLs)
npm run linkcheck
# Browser smoke test against the preview server
# (needs Playwright: `npm i --no-save playwright`, or LEVIMC_PW_DIR=<dir containing node_modules/playwright>;
# an HTTPS_PROXY / HTTP_PROXY in the environment is used for the external live-data requests)
npm run smoke
# The kit's check of the unified LeviMC chrome (same bar, Auto/Light/Dark theme, cross-site cookies)
npm run smoke:kitsrc/App.tsx— routes (/,/community/,/about/,/support/,/blog/…, and the 404 page); every page but the home and the 404 is its own chunk (src/lazy-route.ts): the page being opened is loaded before hydrating, the others when the browser is idlesrc/pages/,src/components/— page content;src/i18n/locales/{en,zh,zh-Hant}.ts— all site text (English, Simplified and Traditional Chinese)src/routes.ts— route list, titles (" - LeviMC", " - Blog - LeviMC", "Page not found - LeviMC") and descriptions;src/head.ts— per-page<head>tags (Open Graph with the social cardpublic/og/levimc.jpg, made bynode scripts/build-og.mjs)src/components/SiteHeader.tsx,src/components/Footer.tsx— the kit GlobalBar and SiteFooter, the same on every LeviMC site (each project in the Projects menu leads to its home page: its LeviMC Mods page, docs or repository; the footer links Support us). The project list isPROJECTSinkit/components/projects.ts; the home page's project cards read it too.src/prepaint.ts— the pre-paint script: theme and language come from the cross-site LeviMC preferences (cookieslevimc_theme= auto | light | dark, Auto by default, andlevimc_lang, shared by levimc.org and every *.levimc.org site; seekit/README.md, "Cross-site preferences"). The bar and footer speak the page's language, or a stored language the site has no pages in when the kit has chrome strings for it (chromeLocaleForinsrc/i18n)src/hooks/useExternalData.ts,src/services/githubService.ts— the live numbers (GitHub stars, the latest LeviLamina release, Discord presence): requested once the page is idle after hydration, kept for the visit insessionStorage(30 minutes)public/donors.csv— the supporters listed on/support/(read at build time, also served at/donors.csv)content/blog/,blog/,src/blog/— the blog (see below)prerender.mjs— writesdist/<route>/index.html,404.html,sitemap.xml,robots.txt; modulepreloads each page's chunk (from Vite's manifest) and a blog post's markdown chunklegacy/— the redirect pages of retired docs sites (/LeviLaunchroid/…,/preloader-android/…,/LiteXLoader/…) and the forwarding map at the top of404.html(other forms of those URLs, and wrongly cased project paths like/levilamina/→/LeviLamina/).node legacy/build.mjs dist(the last step ofnpm run build) writes them; the table islegacy/redirects.mjs. They moved here from LiteLDev/liteldev.github.io with the docs.
The LeviMC blog lives at levimc.org/blog/ (it moved here from the Hexo site blog.levimc.org, formerly the LiteLDev/blog repository). Posts are Markdown files with Hexo-style front matter, rendered at build time with the kit's markdown pipeline (the same prose, code highlighting and admonitions as the docs) and prerendered to static HTML.
Create content/blog/posts/<name>.md — <name> becomes the last part of the URL:
---
title: 通过Patch修复BDS的空包刷屏问题
date: 2024-08-11 09:51:07
author: ShrBox
tags: [Bedrock Dedicated Server, LeviLamina]
category: Bedrock Dedicated Server
---
First paragraph (it is also the excerpt on the index and in the feeds) …dateis local time in Asia/Shanghai (as on the old blog); the post is published at/blog/<year>/<month>/<day>/<name>/. Setpublished: falseto keep a draft out of the build.tags(a list) andcategory(orcategories) get their own pages at/blog/tags/<slug>/and/blog/categories/<slug>/(Hexo's slugs: spaces and punctuation become-).author: add the author tocontent/blog/authors.yml(GitHub login and numeric user id) to show their avatar and link their profile.- Markdown: GitHub-flavoured (tables, strikethrough, task lists, footnotes), raw HTML such as
<details><summary>…</summary>…</details>, fenced code with a language,> [!NOTE]alerts and VitePress-style::: tipcontainers. As in Hexo, a single newline inside a paragraph is a line break. - Images: put them under
public/blog/images/and link them as/blog/images/…. (The old posts' images ons2.loli.netare served from the same path underpublic/blog/images/; the build fails if one is missing.) - Comments: every post and page shows the Waline comments of https://waline.levimc.org/, keyed by the post's old
path (
/<year>/<month>/<day>/<name>/), so the comments from the old blog stay with their post.
npm run build writes the index (10 posts per page), the posts, tag / category / archive pages, /blog/about/,
/blog/friends/ (from content/blog/pages/) and the feeds /blog/rss.xml, /blog/atom.xml, /blog/feed.json.
The catalogue of mods at levimc.org/mods/ (formerly pkg.levimc.org, the
LiteLDev/pkg.levimc.org repository, merged here with its history) is its own app in mods/: pnpm,
built from the repositories in mods/repos.txt (their version tags: each one's lip.toml, else its
tooth.json) with this checkout's kit (pnpm install copies ../kit) and Vite base: '/mods/'. The deploy builds it
after the site and copies mods/dist/ into dist/mods/; it also runs every hour, so the catalogue picks up new
versions. .github/workflows/mods-discover.yml searches GitHub for repositories with a top-level lip.toml or
tooth.json and opens a pull request for each new one it finds; merging one lists it (once its latest version depends
on LeviLamina). The mods job of .github/workflows/ci.yml lints, tests and builds it when a change touches mods/ or kit/.
cd mods
pnpm install
pnpm dev # http://localhost:5173/mods/The two apps share the origin: their bar and footer links into the other one are ordinary links (isAppPath).
robots.txt lists the mods sitemap.
/api/v4/mc/versions/ lists Minecraft's release versions (the software, not
LeviMC's own projects), one key per lip <side>/<platform> (platform: a .NET RID), each newest first, spelled as Mojang spells
them (1.16.1.02 keeps its zero):
{ "server/win-x64": ["1.26.63.1", "1.26.62.1", …], "server/linux-x64": ["1.26.63.1", …] }
Today these are Bedrock Dedicated Server's releases (no previews). The file is
versions/versions.json, published as it is by the deploy. Mojang's download API names only
the latest release, so the history is that file: .github/workflows/versions.yml runs every hour,
node versions/update.mjs adds the release Mojang names now, and a new one is committed and deployed. A new key is a
line in DOWNLOADS in versions/update.mjs and a list in the file. npm test checks the file's shape.
Each project's docs are published by its own repository at levimc.org/<Project>/ (LeviLamina, LegacyScriptEngine,
lip, LeviLauncher, BeDD, bedboo): GitHub Pages serves a project repository's site below the organisation site's
domain, and this repository is the organisation site. A path that a project with Pages owns is never this site's, so
no route here may start with a repository's name. robots.txt lists their sitemaps; the bar and footer load them
in full (isAppPath in src/components/SiteHeader.tsx). They were at docs.levimc.org until the move described in
MIGRATION.md; docs.levimc.org redirects every URL to the same path here (308, Cloudflare).
A small LeviChan head peeks over the top edge of the home hero's showcase card (src/levichan/LeviChanPeek.tsx).
Clicking it opens her 3D space at once; nothing of it is downloaded or drawn before that, and a reload does not
reopen it (the story keeps its own place). The world (three.js, import('./levichan/space')) and her runtime
(Babylon and babylon-mmd, import('./levichan/levi')) are two chunks fetched in parallel, and the model and motions
come from public/levichan/ as they are needed (the model ~9 MB, each motion a few KB, on first use). Nothing waits
on her: motes lift off the page while the blocks on screen are snapshotted, the world comes up, and the rest of the
blocks, her and the cubes gather out of swirling motes as they arrive (src/levichan/particles.ts, one draw for all
of them).
src/levichan/space.ts— the space, drawn with three.js on a canvas over the page (the page itself is left as it is underneath). It opens on the page exactly as it was on screen; its components tumble out of it and the camera swoops back over them. The camera then flies like Minecraft's: WASD (or the arrows) on the level, Space up, Shift down, Ctrl faster, the wheel toward or away from where it looks, a drag to look around, F to fly over to her (or the menu's "follow her", which keeps her in view). On a touch screen: one finger looks around, a pinch goes forward and back, two fingers move. Her WebGL canvas is drawn over the world's with a camera matched to it, both from one loop capped at 60 frames a second; the world is drawn only when something in it or the camera changed (its shadows only when something casting one moved), and a click that comes while she is busy (climbing, mid-play) is done as soon as she is free. Click a block and she makes her way to it and plays with it; double-click a small one and she carries it somewhere else; drag a cube to throw it, drag her to pick her up. Links and controls on the blocks only light up: nothing navigates from inside the space (the browser's back and forward take the new page apart too). The menu (her avatar, bottom left, top right on a phone, or a right click on her): whether she watches the cursor, fewer fidgets, follow her, fly to her, leave (the blocks fly back into the page and the camera returns to it).src/levichan/diorama.ts— the world: each component of the page (cards, buttons, the bar, pictures, headings, text) is snapshotted (modern-screenshot; those on screen first, the rest streamed in) and becomes a block with real depth (two draws each: its face, and its sides and back), its picture on the face, scattered over the ground in no particular order: upright, lying flat as platforms, turned sideways, stacked on others (always leaving a strip of the one below to climb from) or hanging a hop away from a neighbour, never closer than her height over another's top. The blocks answer her play: they press in, bounce, spin, flip, wobble, glow or dip as she lands; a link she presses lights up on its face. Loose Minecraft cubes fall in with simple physics (gravity, bounces, landing on blocks, pushing each other).src/levichan/mover.ts— where she is and how she gets around: the floors she can stand on (blocks' tops and the ground, a body's width in from every edge and every block in her way, with room above for her), a shortest path over them by walking (straight where nothing is in the way), hopping (up to 1 m, down, across gaps, on arcs that clear the blocks around), climbing onto ledges half a metre up, scaling taller faces and dropping off high ones; whenever nothing is under her feet she falls, and the blocks she falls past push her off. She carries a small block over her head across her floor and sets it down where it fits; she can be carried by the cursor. Blocks in front of her hide her (depth-only boxes in her scene).src/levichan/levi.ts— her runtime: the PMX on babylon-mmd (WASM, Bullet physics), cel shading as in the LeviChan repository's renders, clips crossfaded over a looping base (idle, or a walk / run cycle), gaze, blinks and expressions, a hit test.src/levichan/brain.ts— what she does: the visitor never picks a motion; each one answers something they do (click the ground and she goes there, three quick clicks and she mines it; click a block and she goes to it and plays with it in its own way: stomps a button down or whacks it, poses on the title, hums on a heading, reads a text aloud, cheers a picture into a spin, sits on a card or twirls it into a flip; pat her head, poke her, press and hold, double-click her skirt, circle her with the cursor, pick her up and drop her, throw a cube at her, linger on a link, switch language or theme, come back to the tab, the Konami code or typing "levi"...). Every 5-11 s she does something on her own: a fidget or a happy clip, a stroll, a trip to another block in view, tidying a small block away somewhere else; she makes her way after the view when the visitor flies off elsewhere (or reaches up and pouts if she cannot get there), waves when the cursor comes back to her and shows off her dress when it rests on it.src/levichan/story/— the story that runs in the space, a small galgame ("The Page Apart"): she speaks through a dialogue box and acts each line out; chapters send the visitor to do things in the world (fly the camera, take her to the marked keystone blocks, fix the blocks a bug got into, throw cubes, find a glowing cube at the edge of the world and drag it back to her...), and some steps are real-time waits (8, 15 and 5 minutes) that keep running while the page is closed. Everything the visitor does to her and to the world moves a 71-dimension hidden state (state.ts); the choices and that state pick one of ten endings (two happy, two bad, the rest her own). The story and the endings seen are kept in localStorage (levichan:story,levichan:endings), so a reload carries on mid-sentence; the menu can start it over. The script (script.ts) is in Chinese and English.public/levichan/— her assets, made bynode scripts/levichan-assets.mjs --pmx <LeviChan out/pmx> --motions <LeviChan web motions>from a LeviChan build: the model from the round 11 branches merged together, the clips fromtools/web_clips.pyonr11/web-motions(blender -b out/levi.blend -P tools/motion_vmd.py -- --extra-clips web_clips --clips <every web clip> --out out/web_vmd, andblender -b -P tools/web_clips.py -- out/web_vmd/clips.json).public/levichan/NOTICEholds the third-party model data's terms.
MIT