LutraVault

{{ totalBoxes }} {{ totalBoxes === 1 ? 'Box' : 'Boxes' }} • {{ totalModels }} {{ totalModels === 1 ? 'Model' : 'Models' }} ({{ paintedModels }} {{ isCog ? 'Sanctified' : currentTheme === 'bimbo' ? 'Slayed' : 'Painted' }})

{{ slowLoad ? 'Still connecting… check your internet' : 'Loading your collection…' }}
✨ {{ guestImportNotice }}

{{ onboardAllDone ? (currentTheme === 'bimbo' ? 'You did it, babe 💅' : isCog ? 'Induction complete' : 'You are all set 🎉') : (currentTheme === 'bimbo' ? 'Hey babe, welcome ✨' : isCog ? 'Cogitator online' : 'Welcome to LutraVault') }}

{{ onboardAllDone ? 'Your stats and painting diary fill in by themselves from now on.' : 'Your pile of shame, paints and painting progress in one place. Pick a look first: you can change it any time at the top.' }}

{{ (i < 2 ? onboardSteps[i] : onboardAllDone) ? '✓' : i + 1 }} {{ st[0] }}{{ st[1] }}

{{ currentTheme === 'bimbo' ? 'Slay-O-Meter 💅' : 'Collection overview' }}

{{ currentTheme === 'bimbo' ? 'how hot is your pile, babe' : 'Progress across your whole collection' }}

{{ tile.label }}

{{ tile.value }}

{{ tile.sub }}

In {{ goalNow.prevMonth }}: {{ goalNow.recap.count }} of {{ goalNow.recap.goal }} {{ goalNow.recap.count >= goalNow.recap.goal ? (currentTheme === 'bimbo' ? '— you ate that 💅' : '🎉') : '— still good progress' }}
{{ goalNow.month }} goal: {{ goalNow.goal }} · starts {{ goalNow.daysLeft === 0 ? 'tomorrow' : 'in ' + plural(goalNow.daysLeft + 1, 'day', 'days') }}
No goal for {{ goalNow.month }}

{{ i ? ' · ' : '' }}{{ h.label }} {{ h.count }}/{{ h.goal }}{{ h.ok ? ' ✓' : '' }}

{{ currentTheme === 'bimbo' ? 'The glow-up pipeline' : 'Pipeline' }}

{{ modelsByStatus[st.key] }}

{{ statusLabel(st.key) }}

Reports

{{ newReportCount }} new

Couldn't reach the server, so this is the last saved copy. New reports may be missing: try ↻ Refresh in a moment.

Couldn't load reports. Check your connection and try ↻ Refresh.

No reports yet.

{{ reportKindLabel(r.kind) }} {{ reportWhen(r) }}

{{ [r.box, r.faction, r.subfaction && r.subfaction !== 'Generic' ? r.subfaction : ''].filter(Boolean).join(' · ') || '—' }} · from {{ r.email || r.name || 'user' }}

{{ r.text }}

{{ currentTheme === 'bimbo' ? 'Glow-up diary 📔' : 'Painting history' }}

{{ paintedMonth }}

{{ currentTheme === 'bimbo' ? 'slayed this month' : 'painted this month' }} · {{ changesMonth }} {{ changesMonth === 1 ? 'update' : 'updates' }}

{{ mo.count || '' }}
{{ mo.label }}

{{ currentTheme === 'bimbo' ? 'Your diary starts now, babe: every status change from today gets a date ✨' : 'History starts now: every status change from today is recorded with its date.' }}

Recent activity

{{ currentTheme === 'bimbo' ? 'Still waiting for their glow-up' : 'Waiting the longest' }}

{{ currentTheme === 'bimbo' ? 'Squad goals' : 'By faction' }}

{{ currentTheme === 'bimbo' ? 'Save your slay 💾' : 'Your data' }}

A backup file with your boxes, painting history, recipes, paints (your own too), shopping list, notes and books. Importing adds to your collection and never deletes anything. Excel is a copy to read or print; it can't be imported back.

Signed in as {{ currentUser.email || currentUser.displayName }}

{{ currentTheme === 'bimbo' ? 'My Dolls ✨' : 'Pile of Shame' }}

aka the Pile of Shame 💅

{{ shownBoxCount }} {{ shownBoxCount === 1 ? 'box' : 'boxes' }}
⠿

{{ currentTheme === 'bimbo' ? 'Arrange your dolls, babe 💅' : 'My order' }} — drag a box by ⠿ to put it where you want it. New boxes appear on top. Your order is kept when you switch to another sort and back.

{{ boxes.length === 0 ? (currentTheme === 'bimbo' ? 'No dolls yet, babe — add your first box ✨' : 'Your collection is empty. Add your first box.') : 'Nothing matches these filters.' }}

{{ grp.label }}

{{ grp.boxes.length }} {{ grp.boxes.length === 1 ? 'box' : 'boxes' }} · {{ grp.painted }}/{{ grp.total }} {{ currentTheme === 'bimbo' ? 'slayed' : 'painted' }}
{{ box.faction }} {{ box.subfaction }} # {{ t }} +{{ box.tags.length - 3 }}

{{ cogName(box.boxName) }}

{{ currentTheme === 'bimbo' ? '👑 All slayed' : '✓ Done' }}

blessing progress {{ cogBar(boxStats(box).painted, boxStats(box).total) }}

{{ boxStats(box).counts[st.key] }} {{ statusLabel(st.key) }}

{{ currentTheme === 'bimbo' ? 'Glam Recipes 🧪' : 'Recipes' }}

{{ currentTheme === 'bimbo' ? 'your signature looks' : 'Saved paint schemes you can apply to any model' }}

{{ currentTheme === 'bimbo' ? 'No signature looks yet — create your first one 💄' : 'No recipes yet. Create one to reuse a paint scheme.' }}
{{ recipe.category }}

{{ recipe.title }}

{{ isRecipeReady(recipe) ? (currentTheme === 'bimbo' ? '✨ Ready' : '✓ Ready') : (currentTheme === 'bimbo' ? 'Need to shop' : 'Missing paints') }}

{{ recipe.notes }}

{{ idx + 1 }} {{ paintName(pid) }} {{ paintBrand(pid) }} {{ isPaintInStock(pid) ? 'have' : 'need' }}

{{ currentTheme === 'bimbo' ? 'Vanity Rack 💅' : 'Paint shelf' }}

{{ ownedCount }} {{ ownedCount === 1 ? 'paint' : 'paints' }} {{ currentTheme === 'bimbo' ? 'on your rack' : 'in your collection' }}

Brand

Type · pick several

{{ shelfBrand }} lines

Show

Metallics

My brands

Pick the brands you use; the others stay out of the way everywhere on the shelf.

{{ shelfMatchingPaints.length }} {{ shelfMatchingPaints.length === 1 ? 'paint' : 'paints' }}
{{ shelfOwn === 'mine' ? 'No paints marked yet — tap a paint to add it to your shelf.' : 'Nothing matches these filters.' }}

{{ g.label }}

{{ g.mine }} of {{ g.total }} {{ currentTheme === 'bimbo' ? 'on your rack' : 'owned' }}
{{ paint.name }} {{ paint.ru || paint.brand }} {{ paint.line }} {{ ownedPaints[paint.id] ? '✓' : '+' }}

{{ currentTheme === 'bimbo' ? 'Love Notes 💌' : isCog ? 'Data-slates' : 'Notes' }}

{{ currentTheme === 'bimbo' ? 'Dear diary… recipes in progress, wishlists, base ideas 💅' : isCog ? 'Cogitator memory banks. Bind records to your units.' : 'Ideas, lists, recipes in progress. Attach boxes to them.' }}

{{ notes.length }} / {{ NOTE_LIMITS.notes }} notes

{{ currentTheme === 'bimbo' ? 'Book Club 📚' : 'Bookshelf' }}

{{ currentTheme === 'bimbo' ? 'grimdark reads for a glam girl' : 'Black Library novels: Warhammer 40,000 and The Horus Heresy' }}

{{ visibleBookCount }} books
{{ bookMineOnly && !boxes.length ? 'Add boxes to your collection to see books about your factions.' : 'No books match.' }}

{{ group.series }}

{{ seriesProgress(group) }} {{ currentTheme === 'bimbo' ? 'devoured' : 'read' }}

{{ bookEntry(book.id).notes }}

{{ currentTheme === 'bimbo' ? 'Catalog boutique 🛠💅' : 'Catalog' }}

Edit, hide or add boxes in Add Box for everyone. Saved to Firebase right away.

{{ catEditCount }} {{ catEditCount === 1 ? 'change' : 'changes' }} by you
Box · {{ catRows.length }}ModelsState

Nothing here.

{{ currentTheme === 'bimbo' ? 'Wishlist 🛍️' : 'Shopping list' }}

Boxes you want and paints to buy

{{ currentTheme === 'bimbo' ? 'Dolls I want 💖' : 'Boxes I want' }}

{{ plural(wishCards.length, 'box', 'boxes') }} · {{ plural(wishModelCount, 'model', 'models') }}

Boxes you'd like to buy. When you get one, tap ✓ Got it and it moves into your collection with all its models.

{{ cogName(w.boxName) }}

{{ w.factions.join(' + ') }} · {{ plural(w.models, 'model', 'models') }}

{{ currentTheme === 'bimbo' ? 'Paints I need 💅' : 'Paints to buy' }} · {{ missingPaintsList.length }}

{{ currentTheme === 'bimbo' ? 'Your rack has everything, queen 👑' : 'All needed paints are in stock.' }}

Tap 🛒 on any paint in the {{ currentTheme === 'bimbo' ? 'Vanity Rack' : 'Paint shelf' }} to add it here.

{{ group.brand }}

{{ group.items.length }}

{{ item.name }}

for {{ item.reasons.join(', ') }}

01001100 01010110 · praise the Omnissiah

{{ authMode === 'login' ? 'Sign In to LutraVault' : 'Create Cloud Account' }}

or email

{{ authError }}

{{ pickedList.length ? plural(pickedList.length, 'box', 'boxes') + ' · ' + plural(pickedModelCount, 'model', 'models') : (currentTheme === 'bimbo' ? 'Tap the boxes, babe 💅' : 'Tap boxes to pick them') }}
Set all their models to, as of
{{ cogName(selectedBox.boxName) }} · {{ selectedCount ? selectedCount + ' selected' : 'Tick models above' }}
Set to, as of

Move {{ moveDraft.ids.length === 1 ? 'this model' : plural(moveDraft.ids.length, 'model', 'models') }}

Statuses and painting history go with them.

No other boxes{{ moveDraft.query ? ' match' : ' yet' }}.

🦦🏡

{{ moveState === 'done' ? 'All moved!' : (currentTheme === 'bimbo' ? 'We moved, babe! 💅' : 'LutraVault has a new home') }}

{{ currentTheme === 'bimbo' ? 'Show off your pile 💅' : 'Share your collection' }}

Your collection card

The card uses the theme you have on. Nothing is published: it's just a picture.

{{ whenEdit.target === 'model' ? statusLabel(whenEdit.model.status) + ' since' : 'Date for the new statuses' }}

{{ whenEdit.target === 'model' ? whenEdit.model.name : 'Statuses you set now get this date. Back to today after Done.' }}

When?

{{ whenEdit.target === 'model' ? (whenEdit.w.year ? 'Year is enough; month and day are optional.' : "Don't remember? Leave it empty.") : (whenEdit.w.year ? 'Year is enough; month and day are optional.' : 'Pick a year to back-date, or keep today.') }}

{{ myPaintDraft.id ? (currentTheme === 'bimbo' ? 'Edit your shade 💅' : isCog ? 'Amend pigment record' : 'Edit your paint') : (currentTheme === 'bimbo' ? 'Your own shade 💅' : isCog ? 'Register pigment' : 'Add your own paint') }}

{{ currentTheme === 'bimbo' ? 'Any brand, a mix, a pot that is long gone. It goes straight onto your rack.' : isCog ? 'Any manufactorum, mix or lost formula. Logged as held in stores.' : 'Any brand, a mix or an old pot. It is added as owned and works in recipes.' }}

{{ myPaintDraft.error }}

{{ NOTE_LIMITS.items }} items is the most a list can hold.

{{ noteDraft.text.length }} / {{ NOTE_LIMITS.text }}

▣ {{ boxById.get(String(id)) ? cogName(boxById.get(String(id)).boxName) : 'Deleted box' }}

{{ boxes.length ? 'No box with that name.' : 'Your collection is empty — add boxes first.' }}

Add a recipe

to {{ recipePickFor.label }}

Edit box

{{ tagEditor.faction }}

{{ tagEditor.error }}

Used before:

{{ catDraft.isNew ? 'New box' : 'Edit box' }}

{{ catDraft.faction }} · {{ catDraft.isNew ? 'new' : catDraft.state }}

= {{ modelCountTotal(catDraftModels) }} models

⚠ Check before saving

{{ w }}

{{ catDraft.error }}

{{ isCog ? 'Query the noosphere' : 'What to paint tonight?' }} {{ isCog ? '' : '🎡' }}

from {{ wheelPool.length }} waiting {{ wheelPool.length === 1 ? 'model' : 'models' }}

{{ isCog ? '⚙️' : '🦦' }}

Nothing to pick with these filters.

{{ isCog ? '++ target acquired ++' : currentTheme === 'bimbo' ? "Tonight's doll is…" : 'Tonight you paint…' }}

{{ cogName(wheelResult.name) }}

{{ wheelResult.boxName }} · {{ wheelResult.faction }} · {{ statusLabel(wheelResult.status) }}

🎨 {{ statusLabel('WIP') }} ✓

Report a problem

{{ [reportDraft.box, reportDraft.faction].filter(Boolean).join(' · ') || 'A box that is not in the catalog' }}

{{ reportDraft.text.length }}/{{ REPORT_MAX }}

{{ reportDraft.error }}

{{ currentTheme === 'bimbo' ? 'Help, babe 💕' : 'Help and FAQ' }}

{{ currentTheme === 'bimbo' ? 'New dolls ✨' : 'Add boxes' }}

Search every game and faction at once. Tap a box to add it with its own faction.

Any other game or brand: Blood Bowl, Infinity, a bust, a 3D print… Type the box name below.

{{ newGame === 'ALL' ? ((newBox.name || '').trim().length < 2 ? 'Type at least 2 letters to search every game.' : 'Nothing found. To add your own box, pick its game first.') : newGame === 'Other' ? 'Type the name of your box above, then tap it to add.' : 'No boxes for this faction yet.' }}

To add · {{ basketBoxCount }} {{ basketBoxCount === 1 ? 'box' : 'boxes' }} · {{ basketModelCount }} models

{{ item.kind === 'dual' ? '⚔ ' : '' }}{{ item.name }} · {{ item.faction }} · {{ itemModels(item) }} models {{ item.qty }}
↳ {{ h.faction }} · {{ modelCountTotal(h.models) }}

Tap boxes to put them in the list. You can switch faction and keep adding.

Add Paint to {{ targetPart?.name }}

{{ pickerFilteredPaints.length ? 'Your paints. Type to search all ~3000 paints.' : 'Type to search all ~3000 paints.' }}

{{ p.name }}{{ p.ru ? p.ru + ' · ' : '' }}{{ p.brand }} · {{ p.line }}
{{ ownedPaints[p.id] ? 'In Stock' : 'Missing' }}

New recipe

{{ paintName(p) }} ({{ paintBrand(p) }})
{{ p.name }} {{ p.brand }} · {{ p.line }}

Apply "{{ selectedRecipeForApply?.title }}"

Add it to a whole box or to one model. You can also add recipes from a model card (+ Recipe) or to several ticked models (☑ Select).

Add to Global Catalog

Box contents: {{ adminParsedModels.map(m => m.count + '× ' + m.name).join(', ') }}

Same name as a built-in box replaces it (to fix its contents). Empty = one model named like the box.