- character-builder/: Vue 3 + NestJS + Valkey app for planning house, class, character XP, 5 spec tracks, faction standing, and skill trees. Shareable via short link (POST /api/builds → 8-char nanoid). - character-builder/data/: parsed JSON tables (character XP through L200, 5 specs to L100, 2 faction standing tables, 5 class skill trees). - character-builder/scripts/extract.py: parser that regenerates data/*.json from the gitignored sample-data/*.html snapshots. - Dockerfile + docker-compose.yml: two-container deploy (app + Valkey). - specialization-calculator/: pre-existing single-file XP/quest calculator, carried into the repo. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
94 lines
2.5 KiB
Vue
94 lines
2.5 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import type { FactionTable, House } from '../types';
|
|
|
|
const props = defineProps<{
|
|
house: House;
|
|
table: FactionTable | null;
|
|
tier: number;
|
|
standingInto: number;
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
'update:tier': [n: number];
|
|
'update:standingInto': [n: number];
|
|
}>();
|
|
|
|
const tierCount = computed(() => props.table?.tiers.length ?? 0);
|
|
const maxTier = computed(() => Math.max(0, tierCount.value - 1));
|
|
|
|
const currentTier = computed(() => {
|
|
if (!props.table) return null;
|
|
return props.table.tiers[props.tier] || null;
|
|
});
|
|
const nextTier = computed(() => {
|
|
if (!props.table) return null;
|
|
return props.table.tiers[props.tier + 1] || null;
|
|
});
|
|
|
|
const pct = computed(() => {
|
|
if (!nextTier.value || !nextTier.value.standingRequired) return 0;
|
|
const p = (props.standingInto / nextTier.value.standingRequired) * 100;
|
|
return Math.min(100, Math.max(0, p));
|
|
});
|
|
|
|
function setTier(e: Event) {
|
|
const v = Number((e.target as HTMLInputElement).value) || 0;
|
|
emit('update:tier', Math.max(0, Math.min(maxTier.value, Math.floor(v))));
|
|
}
|
|
function setInto(e: Event) {
|
|
const v = Number((e.target as HTMLInputElement).value) || 0;
|
|
emit('update:standingInto', Math.max(0, Math.floor(v)));
|
|
}
|
|
|
|
function fmt(n: number): string {
|
|
return n.toLocaleString('en-US');
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div class="row">
|
|
<div class="field">
|
|
<label>Current Tier</label>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
:max="maxTier"
|
|
:value="tier"
|
|
@input="setTier"
|
|
/>
|
|
</div>
|
|
<div class="field">
|
|
<label>Rep into next tier</label>
|
|
<input type="number" min="0" :value="standingInto" @input="setInto" />
|
|
</div>
|
|
</div>
|
|
<div class="progress" :style="{ '--pct': pct + '%' }"></div>
|
|
<div class="progress-meta">
|
|
<span>
|
|
{{ currentTier?.name || '—' }} → {{ nextTier?.name || 'Max' }}
|
|
</span>
|
|
<span>
|
|
{{ fmt(standingInto) }} / {{ fmt(nextTier?.standingRequired || 0) }} rep
|
|
</span>
|
|
</div>
|
|
|
|
<div class="tier-list" v-if="table">
|
|
<div
|
|
v-for="t in table.tiers"
|
|
:key="t.tier"
|
|
:class="[
|
|
'tier-row',
|
|
t.tier < tier ? 'reached' : '',
|
|
t.tier === tier ? 'current' : '',
|
|
]"
|
|
>
|
|
<span class="num">{{ t.tier }}</span>
|
|
<span>{{ t.name || '—' }}</span>
|
|
<span>{{ fmt(t.standingRequired) }}</span>
|
|
<span style="color: var(--ink-muted)">{{ fmt(t.totalStanding) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|