[{"data":1,"prerenderedAt":355},["ShallowReactive",2],{"project-case-tamriel-codex":3},[4,13,19,82,98,115,151,272,319,343,349],{"component":5,"data":6},"ProjectCaseHeader",{"index":7,"subtitle":8,"name":9,"headline":10,"url":11,"tone":12},"07","OFFLINE HANDBOOK · LARP","Tamriel Codex","An offline handbook for Android and Windows in 10 days, ready by game day",null,"accent",{"component":14,"data":15},"ProjectCaseCover",{"image":16,"imageMobile":17,"imageAlt":18},"tamriel-codex\u002Fmedia\u002Fpreview.webp","tamriel-codex\u002Fmedia\u002Fpreview-mobile.webp","Home screen of the Tamriel Codex app",{"component":20,"data":21},"ProjectCaseMeta",{"tags":22,"stack":24},[23],"Mobile first",[25,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80],{"label":26,"own":27},"Vue 3",true,{"label":29,"own":27},"TypeScript",{"label":31,"own":27},"Vite",{"label":33,"own":27},"Pinia",{"label":35,"own":27},"Vue Router",{"label":37,"own":27},"VueUse",{"label":39,"own":27},"vee-validate",{"label":41,"own":27},"Tailwind CSS",{"label":43,"own":27},"daisyUI",{"label":45,"own":27},"Iconify",{"label":47,"own":27},"Capacitor",{"label":49,"own":27},"Electron",{"label":51,"own":27},"electron-builder",{"label":53,"own":27},"SQLite",{"label":55,"own":27},"jeep-sqlite",{"label":57,"own":27},"sql.js",{"label":59,"own":27},"qrcode",{"label":61,"own":27},"qr-scanner",{"label":63,"own":27},"marked",{"label":65,"own":27},"pdfjs-dist",{"label":67,"own":27},"mammoth",{"label":69,"own":27},"sharp",{"label":71,"own":27},"node-html-parser",{"label":73,"own":27},"vue-tsc",{"label":75,"own":27},"ESLint",{"label":77,"own":27},"oxlint",{"label":79,"own":27},"oxfmt",{"label":81,"own":27},"Playwright",{"component":83,"data":84},"ProjectCaseFacts",{"facts":85},[86,89,92,95],{"key":87,"value":88},"role","Sole developer",{"key":90,"value":91},"team","One person, built together with Claude",{"key":93,"value":94},"duration","10 days, June 25 – July 4, 2026",{"key":96,"value":97},"status","Used at the game, next version planned",{"component":99,"data":100},"ProjectCaseRole",{"id":87,"name":101,"items":102},"What I did",[103,106,109,112],{"title":104,"text":105},"The whole app","The interface, the local database and the Android and Windows builds from one Vue 3 and TypeScript codebase. I picked up the mobile stack, Capacitor and the Android build, along the way.",{"title":107,"text":108},"Exporting the game data","Wrote a parser with Codex and Playwright that exported texts, PDFs and images from the VK group. Then, together with Claude, turned them into the app's data.",{"title":110,"text":111},"Search across the handbook","Global search from the header across 11 sections, from the pantheon to the news. Clicking a result opens the card or scrolls the rules to the right section.",{"title":113,"text":114},"Sharing without internet","Came up with and built sharing of notes, characters and backstories by QR: one player shows the code on screen, the other scans it with the camera.",{"component":116,"data":117},"ProjectCaseChapter",{"id":118,"name":119,"blocks":120},"context","Context and starting point",[121,130,141],{"component":116,"data":122},{"id":123,"name":124,"blocks":125},"game","The game",[126],{"component":127,"data":128},"ProjectCaseText",{"text":129},"A live-action role-playing game set in The Elder Scrolls world, about 100 players. There was no client as such: I built the app for free at the players' request. At the game it was used by the Aldmeri Dominion faction, which is why the codex speaks in the voice of an Altmer chronicler.",{"component":116,"data":131},{"id":132,"name":133,"blocks":134},"situation","The situation",[135,138],{"component":127,"data":136},{"text":137},"The game materials were scattered across a VK group: rules in .docx and PDF, news, newspapers, decrees and rumors as posts, articles, attachments and images. Players had to bring a stack of paper to the game or read an awkward PDF on their phones.",{"component":127,"data":139},{"text":140},"Players needed to find things in the game's knowledge base fast and keep their own notes. There is no internet at the game site, people play from Android phones and PCs, and the deadline was fixed: the game on July 5, 2026.",{"component":116,"data":142},{"id":143,"name":144,"blocks":145},"goal","Goal",[146],{"component":147,"data":148},"ProjectCaseGoal",{"value":149,"text":150},"July 5","is game day, and the app had to work by then. The aim was to make the game better: get rid of the stack of paper and awkward PDFs, give players the rules and lore with fast search, their own notes and a way to share them without internet.",{"component":116,"data":152},{"id":153,"name":154,"blocks":155},"process","Process, architecture and decisions",[156,167,199,235,243,251,258],{"component":116,"data":157},{"id":158,"name":159,"blocks":160},"contents","What's inside",[161,164],{"component":127,"data":162},{"text":163},"The game rules are a tree of collapsible sections, next to a quick guide, 13 classes with levels and the original PDFs of seven rulebooks in a built-in viewer. Lore: the pantheon ranked by the game's decree, clans and houses, towers, a guide to Tamriel and the creation of the world.",{"component":127,"data":165},{"text":166},"\"News of Tamriel\" brings together a newspaper by province, history and rumors by year and documents of the online cycles with filters. The four in-game scripts have their own fonts, glyph tables and a live translator. Players keep their own data right there: notes, important characters, backstories and tags with filtering.",{"component":116,"data":168},{"id":169,"name":170,"blocks":171},"architecture","Architecture",[172,175],{"component":127,"data":173},{"text":174},"To make it by game day, I went with a familiar web stack instead of native Android, Flutter or React Native: one Vue codebase runs on both the phone and the PC. All content is built into the app, and the VK link stays only as attribution.",{"component":176,"data":177},"ProjectCaseArchitecture",{"items":178},[179,183,187,191,195],{"label":180,"title":181,"text":182},"CLIENT","One codebase","Vue 3 and strict TypeScript, Pinia, Vue Router, Tailwind CSS and daisyUI.",{"label":184,"title":185,"text":186},"SHELLS","Two shells","Capacitor builds the Android APK, Electron builds a portable Windows .exe that needs no installation.",{"label":188,"title":189,"text":190},"CONTENT","Built-in handbook","Static data in TypeScript files, with images and PDFs bundled inside the app.",{"label":192,"title":193,"text":194},"DATA","Player data","SQLite on the device: notes, characters, tags and backstories. The schema is updated through versioned migrations.",{"label":196,"title":197,"text":198},"BUILD","Content pipeline","At build time the rules turn from .docx into a section tree, images are compressed to 768 px WebP, and the original PDFs are placed alongside.",{"component":116,"data":200},{"id":201,"name":113,"blocks":202},"exchange",[203,219,222],{"component":204,"data":205},"ProjectCaseSteps",{"steps":206},[207,210,213,216],{"title":208,"text":209},"Note","A player opens a note, a character or a backstory",{"title":211,"text":212},"QR on screen","The app shows the entry as a QR code",{"title":214,"text":215},"Camera","Another player scans the code, and the app remembers the chosen camera",{"title":217,"text":218},"A copy of their own","The second player gets the same entry",{"component":127,"data":220},{"text":221},"Inside the code is compact JSON, compressed and encoded in base45. That string fits the alphanumeric QR mode, which is denser than the byte mode, and native scanners don't mangle it.",{"component":223,"data":224},"ProjectCasePipeline",{"stages":225},[226,229,232],{"name":227,"next":228},"JSON · UTF-8","compression",{"name":230,"next":231},"deflate-raw","encoding",{"name":233,"next":234},"base45","QR code",{"component":116,"data":236},{"id":237,"name":238,"blocks":239},"mobile","A phone at the game site",[240],{"component":127,"data":241},{"text":242},"On the phone, important characters collapse into accordion cards, the hardware Back button closes modals, and tap targets are 44–48 px. Light and dark themes are both available: depending on the device, each player preferred one or the other.",{"component":116,"data":244},{"id":245,"name":246,"blocks":247},"growth","Room for updates",[248],{"component":127,"data":249},{"text":250},"Every QR code carries the schema version: if a code was made in a different version, the older app asks to be updated. Database migrations carry the player's data over when a new build is installed. A new handbook section takes one navigation entry: the menu item, the home page card and the route appear on their own.",{"component":116,"data":252},{"id":253,"name":193,"blocks":254},"security",[255],{"component":127,"data":256},{"text":257},"All player data stays on the device, nothing is sent to a server. The QR format is closed, so only the app can read the codes. Incoming codes are validated: a damaged code or one from an unknown schema version is rejected.",{"component":116,"data":259},{"id":260,"name":261,"blocks":262},"quality","Quality and builds",[263,266,269],{"component":127,"data":264},{"text":265},"The app was written together with Claude following the rules in the repository's CLAUDE.md: code conventions, the search architecture, tone and rules for content, tap target sizes. That kept the pace high: 106 commits in 10 days.",{"component":127,"data":267},{"text":268},"Types are checked by vue-tsc on every build, and the code goes through ESLint and oxlint. I tested the app by hand on my own phone and PC, and before the game handed it to a few players and collected their feedback.",{"component":127,"data":270},{"text":271},"The APK and the portable .exe are built with local scripts, and the builds were handed out to players as files in the chat and the group.",{"component":116,"data":273},{"id":274,"name":275,"blocks":276},"challenges","Challenges",[277],{"component":278,"data":279},"ProjectCaseChallenges",{"items":280},[281,297,307],{"id":282,"name":283,"rows":284},"vk-data","Game data only in a VK group",[285,288,291,294],{"key":286,"text":287},"problem","News, newspapers, decrees, rumors and documents sat in a VK group as posts, articles, PDF attachments and images, while the app had to work without internet.",{"key":289,"text":290},"solution","I needed a quick solution, so I built the export scenario with Codex and Playwright. Articles became text, PDF attachments became text via pdftotext, and post images were downloaded locally. Then the agent wrote the scripts that converted everything into the app's format.",{"key":292,"text":293},"result","All built-in content came from this export: about 24 newsletter pieces, rumors covering 11 years, about 37 cycle documents and 41 images. It all works offline and can be found with the search in the header.",{"key":295,"text":296},"retro","For the next game: content updates without rebuilding the app, so organizers can hand new materials to players themselves.",{"id":298,"name":299,"rows":300},"rules","Rules from documents to search",[301,303,305],{"key":286,"text":302},"The rulebooks were in .docx and PDF, and a PDF is awkward to read on a phone and slow to search for the right clause.",{"key":289,"text":304},"A script builds the section tree from the .docx versions of the rulebooks. The original PDFs sit alongside and open in the built-in viewer.",{"key":292,"text":306},"Search finds a rule down to the section and scrolls to it. Players specifically mentioned the rules with fast search.",{"id":308,"name":309,"rows":310},"qr-size","A note in one QR code",[311,313,315,317],{"key":286,"text":312},"The limit is the largest QR code, version 40 with 4,296 characters, and Cyrillic text compresses poorly.",{"key":289,"text":314},"I chose field limits so that even incompressible Cyrillic text is guaranteed to fit: a note gets 100 characters for the title and 2,000 for the text. The final check is done against the actual code size.",{"key":292,"text":316},"At the game, players shared notes by QR without internet.",{"key":295,"text":318},"Next step is large notes: split the data across several QR codes in a row, or use a QR code as a bridge to connect two phones and transfer data directly.",{"component":116,"data":320},{"id":321,"name":322,"blocks":323},"results","Results",[324,340],{"component":325,"data":326},"ProjectCaseResults",{"stats":327},[328,331,334,337],{"value":329,"label":330},"10 days","from the first to the last commit, the day before the game",{"value":332,"label":333},"2","platforms from one codebase: Android and Windows",{"value":335,"label":336},"11","sections in global search",{"value":338,"label":339},"4","in-game alphabets with fonts and a translator",{"component":127,"data":341},{"text":342},"At the game on July 5, 2026, the app was used by the Aldmeri Dominion faction. Instead of a stack of paper and PDFs on a phone, anything took seconds to find. Players mentioned the convenience, the rules with fast search, the light and dark themes, the joke easter eggs and the help with translating the alphabets.",{"component":344,"data":345},"ProjectCaseCta",{"id":346,"title":347,"text":348,"url":11},"cta","Need an app that works offline on phones and PCs?","I'll build the Android and Windows versions from one codebase: a local database, fast search and data sharing without a network.",{"component":350,"data":351},"ProjectCaseNext",{"slug":352,"name":353,"index":354,"tone":12},"someday","Someday","08",1792947429827]