Forge of Exiles is a Character and Thrall planner for Conan Exiles with Weapon and Armor look ups that can display recipes needed.
  • TypeScript 50%
  • Vue 37.2%
  • JavaScript 6.7%
  • CSS 5.4%
  • Dockerfile 0.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Vantz Stockwell 6e052a7270 Scale the interface to 1.4x — 140% zoom, made permanent
The design's register is a 14px root with 11-13px component text. That
reads on a 1080p panel and is too small on a 27-34" 1440p one, where it
needed browser zoom at 140% to be comfortable.

zoom is the right tool rather than simply enlarging fonts. The interface
carries 226 hardcoded pixel values in components and 95 more in the CSS
— control heights, badge heights, minimum column widths. Growing type
alone would overflow every fixed-height box it sits in. zoom scales
type, spacing, borders and controls together, which is exactly what the
browser does at 140% and why that looked right.

Body text goes 13px -> 18.2px, labels 12 -> 16.8, h1 24 -> 33.6.

One consequence had to be handled: zoom multiplies viewport units. 100vh
resolves against the unzoomed viewport and is then scaled, so a plain
h-screen sidebar rendered 2016px against a 1440px screen. Both viewport
units in app.vue are now divided by --ui-scale, which lands the sidebar
back at exactly one viewport.

Verified in a browser at 2560x1440 and 3440x1440 across the character
planner, thrall planner and the weapons table: sidebar fits, no
horizontal overflow, zero console errors. Retune from the one number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 18:35:48 -04:00
app Scale the interface to 1.4x — 140% zoom, made permanent 2026-08-05 18:35:48 -04:00
docs Record the nine plan defects found during execution 2026-08-04 01:34:40 -04:00
public Add Forge of Exiles branding 2026-08-02 16:33:58 -04:00
scripts Treat Wood as farmed, not converted from Branch 2026-08-03 11:57:46 -04:00
server Add a container, and correct two messages that misdirect the operator 2026-08-04 06:31:57 -04:00
test Give the session cookie a SameSite, and make the auth decisions testable 2026-08-04 01:53:58 -04:00
.dockerignore Add a container, and correct two messages that misdirect the operator 2026-08-04 06:31:57 -04:00
.env.example Add a container, and correct two messages that misdirect the operator 2026-08-04 06:31:57 -04:00
.gitignore Ignore .nuxtrc, the test-utils config marker 2026-08-03 17:44:17 -04:00
CLAUDE.md Add a container, and correct two messages that misdirect the operator 2026-08-04 06:31:57 -04:00
docker-compose.prod.yml Add the swervx-1 production overlay 2026-08-04 06:50:11 -04:00
docker-compose.yml Add a container, and correct two messages that misdirect the operator 2026-08-04 06:31:57 -04:00
Dockerfile Add a container, and correct two messages that misdirect the operator 2026-08-04 06:31:57 -04:00
nuxt.config.ts Fix review findings: live siteUrl fallback, tested session config, moved fetchProfile into steam.ts 2026-08-04 00:20:44 -04:00
package-lock.json Sync the lockfile with the Node engines pin 2026-08-04 02:14:55 -04:00
package.json Fix five things that only bite in production 2026-08-04 01:56:07 -04:00
README.md Add a container, and correct two messages that misdirect the operator 2026-08-04 06:31:57 -04:00
tsconfig.json Scaffold Forge of Exiles: Nuxt 4 + Tailwind 4 + Pinia, data pipeline from CUE4Parse extraction 2026-08-02 13:59:02 -04:00
vitest.config.ts Add the Steam OpenID module and a server test project 2026-08-03 23:47:27 -04:00

Forge of Exiles

A character planner, thrall planner and reference database for Conan Exiles (Enhanced) — built on stats read directly out of the game's own DataTables rather than community wiki transcription.

Not affiliated with Funcom.

Surface Route What it does
Character planner /planner/character 60-point attribute budget, gear, armor→damage-reduction curve, A/B build compare
Thrall planner /planner/thrall Two candidates side by side at a shared level; worst/expected/best attribute projections
Weapons /weapons 1,270 weapons, filterable by the effects they actually apply (bleed, sunder, cripple…)
Armor /armor 1,763 pieces
Thralls /thralls 4,602 capturable
Recipes /recipes 6,311 crafting chains
Builds /builds Saved character and thrall builds — sign in with Steam to save, rename, and publish

Quick start

npm install
npm run dev          # http://localhost:3000

That's it — the game data is committed to the repo, so a fresh clone runs without the extraction toolchain. You do not need Conan Exiles installed to work on this.

npm run build        # production build
npm run preview      # serve the build locally
npm test             # run the test suite

Where the data comes from

app/data/*.json is generated, not hand-written. It is produced by scripts/build-data.mjs, which reads raw CUE4Parse DataTable dumps of the shipped game files and emits trimmed, typed JSON.

node scripts/build-data.mjs [extractDir]      # default: ../conan-extract/out

The extract directory is not part of this repo and must be produced separately. If you don't have it, the script exits with a clear message — that is expected, and it is not a broken checkout.

Game data is identical for every user and never changes at runtime, so it ships in the bundle rather than a database. app/data/meta.json records the build timestamp, source, row counts and facet histograms.

Do not hand-edit anything in app/data/ — it will be silently overwritten the next time the pipeline runs. Corrections belong in scripts/build-data.mjs.

app/data-curated/ is the opposite: hand-authored TypeScript (player and thrall perks) that has no upstream source in the game files. Edit those directly.

Architecture

app/
  pages/           routes (Nuxt file-based routing)
  components/
    ds/            design-system primitives — Button, Input, Select, Tabs…
    planner/       character + thrall planner UI
    reference/     the dense data tables
    ui/            app-level shell pieces
  composables/     stateful logic — useBuild, useGear, useSettings, useThrallSide
  utils/           ALL arithmetic — characterMath, thrallMath, gearMath,
                   recipeChain, effects, palette
  data/            GENERATED game data (see above)
  data-curated/    hand-authored perk tables
  assets/css/      main.css — the design token layer
server/
  api/             Steam auth + saved-builds routes — thin, call into server/utils/
  routes/b/[id]    short-link redirect for a published build
  utils/           steam.ts, db.ts, ids.ts, session.ts, builds.ts, shortlink.ts —
                   plain functions, no Nitro auto-imports, unit-testable without
                   a running server. h3 helpers are imported explicitly and
                   runtime config arrives as an argument: the handler reads
                   useRuntimeConfig() and passes the secret and database path in
scripts/
  build-data.mjs   the data pipeline
test/
  utils/           specs for app/utils/
  composables/     specs for app/composables/ (Nuxt environment)
  server/          specs for server/utils/ (node environment, no server needed)
docs/
  design-handoff.md   design intent + the Corrosion skin rationale

All maths lives in app/utils/. Components and composables call into it; they don't re-derive. If you find yourself computing a stat inside a .vue file, it belongs in a util instead — derivePlayer, damageReduction, projectAttr, craftedArmor, craftedWeight, craftedDurability, seedChances, weightClassOf already exist.

Design system

The UI is the Corrosion Control Panel design system with a Conan Exiles (Hyborian bronze) game skin. The token layer in app/assets/css/main.css has three ordered layers: a neutral ramp, semantic surface/text/border tokens that flip with the theme, and the game skin's accent.

Components reference semantic token names only, never raw palette values — so a re-skin stays one file. The theme is driven by two attributes on <html>:

<html data-theme="dark" data-game="conan">

See docs/design-handoff.md for the full design rationale.

Stack

Nuxt 4 · Vue 3 (Composition API) · TypeScript (strict: true) · Tailwind v4 · Pinia · TanStack Table · Lucide icons

Configuration

Node 24 or newer is required (engines in package.json enforces it). The storage layer is node:sqlite, which is behind a flag on Node 22 and only stable from Node 24. On an older runtime that import throws ERR_UNKNOWN_BUILTIN_MODULE — and because the sign-in control lives in the global header, /api/me is on the critical path of every SSR render, so this is not a degrade-to-signed-out: it is the whole site down.

Copy .env.example to .env. Every value has a working default for local development, so the app runs with no .env at all. NUXT_SESSION_SECRET is required in production — it seals the session cookie, minimum 32 characters. NUXT_DATABASE_URL points at the SQLite file, defaulting to ./data/forge.db. NUXT_STEAM_API_KEY is optional: without it a signed-in user shows a bare SteamID, and with it Steam's Web API resolves a display name and avatar. NUXT_PUBLIC_SITE_URL is normally left unset — see .env.example for when it isn't.

The NUXT_ prefix is load-bearing in production. Nuxt overrides runtime config from NUXT_-prefixed variables at server start, and that is the only mechanism a prebuilt server has. The unprefixed names are read by nuxt.config.ts, which is evaluated at build time — so SESSION_SECRET works in development, where the config is re-evaluated on every start, and is ignored by a production server running an already-built .output.

Ignored quietly, which is the part that costs a day. A missing session secret is a deliberate graceful degrade — every page renders signed-out rather than erroring — so an operator who exports the unprefixed name gets a site that looks healthy, a sign-in button that goes to Steam and comes back, and a database quietly at the default path rather than the configured one. The failure does eventually announce itself, but only at the last step: the Steam return leg answers 500 saying the secret is missing. Everything before that point looks fine. Use the prefixed names; they work in development too.

Running in a container

export NUXT_SESSION_SECRET=$(openssl rand -base64 32)
docker compose up --build

The Dockerfile is an ordinary build — it compiles the source in its own build stage, so what you build is what you get. It deliberately does not follow the pattern used by the Swervx services on the same host, which download prebuilt CI artifacts and can therefore ship stale code from a plain docker build.

The runtime stage carries only Nuxt's .output, which bundles its own dependencies — no node_modules, no npm, about 20 MB. The database lives on a named volume at /data, not in the container's writable layer, so it survives a redeploy. The healthcheck hits /api/me, which exercises the handler stack and the session read without opening the database.

Two things to get right on a real deployment, both of which fail without saying so: the reverse proxy must set force_https or Steam rejects the assertion, and the environment variables need the NUXT_ prefix described above. docker-compose.yml refuses to start without NUXT_SESSION_SECRET rather than degrading to a site where nobody can sign in.