All checks were successful
Deploy to production / deploy (push) Successful in 2m55s
- Modified the production counting logic in FalukantService to count each completed production directly from the falukant_log.production table, simplifying the query structure. - Added a new localization entry for "scoreHowToRaise" in Cebuano, German, English, Spanish, and French to provide users with clear guidance on how to improve their score through various factors. - Updated the OverviewView component to display the new score improvement information, enhancing user experience and understanding of scoring dynamics.
1334 lines
48 KiB
Vue
1334 lines
48 KiB
Vue
<template>
|
|
<div class="falukant-overview">
|
|
<StatusBar />
|
|
<section class="falukant-hero surface-card">
|
|
<div>
|
|
<span class="falukant-kicker">{{ $t('sectionBar.sections.falukant') }}</span>
|
|
<h2>{{ $t('falukant.overview.title') }}</h2>
|
|
<p>{{ $t('falukant.overview.heroIntro') }}</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section
|
|
v-if="falukantUser?.debtorsPrison?.active"
|
|
class="falukant-debt-warning surface-card"
|
|
:class="{ 'is-prison': falukantUser?.debtorsPrison?.inDebtorsPrison }"
|
|
>
|
|
<strong>
|
|
{{ falukantUser?.debtorsPrison?.inDebtorsPrison
|
|
? $t('falukant.bank.debtorsPrison.titlePrison')
|
|
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
|
</strong>
|
|
<p>
|
|
{{ falukantUser?.debtorsPrison?.inDebtorsPrison
|
|
? $t('falukant.bank.debtorsPrison.descriptionPrison')
|
|
: $t('falukant.bank.debtorsPrison.descriptionWarning') }}
|
|
</p>
|
|
</section>
|
|
|
|
<div v-if="falukantUser?.character && !falukantUser?.debtorsPrison?.inDebtorsPrison" class="imagecontainer">
|
|
<div :style="getAvatarStyle" class="avatar"></div>
|
|
<div class="house-with-character">
|
|
<div :style="getHouseStyle" class="house"></div>
|
|
<div class="character-foreground">
|
|
<Character3D
|
|
:gender="falukantUser.character.gender"
|
|
:age="falukantUser.character.age"
|
|
:no-background="true"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else-if="falukantUser?.character && falukantUser?.debtorsPrison?.inDebtorsPrison" class="imagecontainer imagecontainer--prison">
|
|
<div class="debtors-prison-visual" aria-hidden="true">
|
|
<div class="debtors-prison-visual__moon"></div>
|
|
<div class="debtors-prison-visual__tower"></div>
|
|
<div class="debtors-prison-visual__bars debtors-prison-visual__bars--left"></div>
|
|
<div class="debtors-prison-visual__bars debtors-prison-visual__bars--right"></div>
|
|
<div class="debtors-prison-visual__ground"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<section v-if="falukantUser?.character" class="falukant-summary-grid">
|
|
<article class="summary-card surface-card">
|
|
<span class="summary-card__label">{{ $t('falukant.overview.metadata.certificate') }}</span>
|
|
<strong>{{ falukantUser?.certificate ?? '---' }}</strong>
|
|
<p>{{ $t('falukant.overview.summary.certificateHint') }}</p>
|
|
</article>
|
|
<article class="summary-card surface-card">
|
|
<span class="summary-card__label">{{ $t('falukant.overview.summary.branches') }}</span>
|
|
<strong>{{ branchCount }}</strong>
|
|
<p>{{ $t('falukant.overview.summary.branchesHint') }}</p>
|
|
</article>
|
|
<article class="summary-card surface-card">
|
|
<span class="summary-card__label">{{ $t('falukant.overview.summary.productions') }}</span>
|
|
<strong>{{ productionCount }}</strong>
|
|
<p>{{ $t('falukant.overview.summary.productionsHint') }}</p>
|
|
</article>
|
|
<article class="summary-card surface-card">
|
|
<span class="summary-card__label">{{ $t('falukant.overview.summary.stock') }}</span>
|
|
<strong>{{ stockEntryCount }}</strong>
|
|
<p>{{ $t('falukant.overview.summary.stockHint') }}</p>
|
|
</article>
|
|
<article v-if="falukantUser?.debtorsPrison?.active" class="summary-card surface-card">
|
|
<span class="summary-card__label">{{ $t('falukant.bank.debtorsPrison.creditworthiness') }}</span>
|
|
<strong>{{ falukantUser.debtorsPrison.creditworthiness }}</strong>
|
|
<p>
|
|
{{ falukantUser.debtorsPrison.nextForcedAction
|
|
? $t(`falukant.bank.debtorsPrison.actions.${falukantUser.debtorsPrison.nextForcedAction}`)
|
|
: $t('falukant.bank.debtorsPrison.titleWarning') }}
|
|
</p>
|
|
</article>
|
|
</section>
|
|
|
|
<section v-if="falukantUser?.character" class="falukant-routine-grid">
|
|
<article
|
|
v-for="action in routineActions"
|
|
:key="action.route"
|
|
class="routine-card surface-card"
|
|
>
|
|
<span class="routine-card__eyebrow">{{ action.kicker }}</span>
|
|
<h3>{{ action.title }}</h3>
|
|
<p>{{ action.description }}</p>
|
|
<button type="button" :class="action.secondary ? 'button-secondary' : ''" @click="openRoute(action.route)">
|
|
{{ action.cta }}
|
|
</button>
|
|
</article>
|
|
</section>
|
|
|
|
<section v-if="certificateProgress" class="certificate-panel surface-card">
|
|
<div class="certificate-panel__header">
|
|
<div>
|
|
<h3>{{ $t('falukant.overview.certificate.title') }}</h3>
|
|
<p>{{ $t('falukant.overview.certificate.description') }}</p>
|
|
</div>
|
|
<div class="certificate-panel__badges">
|
|
<span class="summary-card__label">{{ $t('falukant.overview.certificate.current') }}: {{ certificateProgress.currentCertificate }}</span>
|
|
<span class="summary-card__label">{{ $t('falukant.overview.certificate.next') }}: {{ certificateProgress.nextCertificate }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="certificate-panel__score">
|
|
<span>{{ $t('falukant.overview.certificate.score') }}</span>
|
|
<strong>{{ certificateProgress.score }}</strong>
|
|
<span class="certificate-panel__state" :class="certificateProgressStateClass">
|
|
{{ $t(`falukant.overview.certificate.state.${certificateProgressStateKey}`) }}
|
|
</span>
|
|
</div>
|
|
<p class="certificate-panel__hint">
|
|
{{ certificateProgressHint }}
|
|
</p>
|
|
<p class="certificate-panel__howto">
|
|
{{ $t('falukant.overview.certificate.scoreHowToRaise') }}
|
|
</p>
|
|
|
|
<div class="certificate-panel__grid">
|
|
<article class="certificate-panel__block">
|
|
<h4>{{ $t('falukant.overview.certificate.factors') }}</h4>
|
|
<div class="detail-list">
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.certificate.factor.avgKnowledge') }}</span>
|
|
<strong>{{ formatCertificateValue(certificateProgress.currentValues.avgKnowledge, 1) }}</strong>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.certificate.factor.completedProductions') }}</span>
|
|
<strong>{{ certificateProgress.currentValues.completedProductions }}</strong>
|
|
<p
|
|
v-if="certificateProgress.certificateProductionsCountSince"
|
|
class="certificate-panel__productions-since"
|
|
>
|
|
{{
|
|
$t('falukant.overview.certificate.productionsSince', {
|
|
date: formatDate(certificateProgress.certificateProductionsCountSince),
|
|
})
|
|
}}
|
|
</p>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.certificate.factor.reputation') }}</span>
|
|
<strong>{{ certificateProgress.currentValues.reputation }}</strong>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.certificate.factor.housePosition') }}</span>
|
|
<strong>{{ certificateProgress.currentValues.housePosition }}</strong>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.certificate.factor.highestPoliticalOfficeRank') }}</span>
|
|
<strong>{{ certificateProgress.currentValues.highestPoliticalOfficeRank }}</strong>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.certificate.factor.highestChurchOfficeRank') }}</span>
|
|
<strong>{{ certificateProgress.currentValues.highestChurchOfficeRank }}</strong>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.certificate.factor.nobilityLevel') }}</span>
|
|
<strong>{{ certificateProgress.currentValues.nobilityLevel }}</strong>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="certificate-panel__block">
|
|
<h4>{{ $t('falukant.overview.certificate.requirements') }}</h4>
|
|
<div class="certificate-requirements">
|
|
<div
|
|
class="certificate-requirement"
|
|
:class="{ 'is-met': certificateProgress.scoreRequirementMet }"
|
|
>
|
|
<span>{{ $t('falukant.overview.certificate.scoreGate') }}</span>
|
|
<strong>{{ formatCertificateRequirement(certificateProgress.score, certificateProgress.nextScoreThreshold) }}</strong>
|
|
</div>
|
|
<div
|
|
v-for="requirement in certificateProgress.nextRequirements"
|
|
:key="requirement.type"
|
|
class="certificate-requirement"
|
|
:class="{ 'is-met': requirement.met }"
|
|
>
|
|
<span>{{ certificateRequirementLabel(requirement.type) }}</span>
|
|
<strong>{{ formatCertificateRequirement(requirement.current, requirement.required) }}</strong>
|
|
</div>
|
|
<div
|
|
v-if="certificateProgress.statusRequirement"
|
|
class="certificate-requirement"
|
|
:class="{ 'is-met': certificateProgress.statusRequirement.fulfilled }"
|
|
>
|
|
<span>{{ certificateStatusRequirementLabel(certificateProgress.statusRequirement.mode) }}</span>
|
|
<strong>{{ certificateProgress.statusRequirement.metCount }}/{{ certificateProgress.statusRequirement.requiredCount }}</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<ul v-if="certificateProgress.statusRequirement?.options?.length" class="certificate-status-options">
|
|
<li
|
|
v-for="option in certificateProgress.statusRequirement.options"
|
|
:key="option.type"
|
|
:class="{ 'is-met': option.met }"
|
|
>
|
|
{{ certificateRequirementLabel(option.type) }}:
|
|
{{ formatCertificateRequirement(option.current, option.required) }}
|
|
</li>
|
|
</ul>
|
|
</article>
|
|
|
|
<article class="certificate-panel__block">
|
|
<h4>{{ $t('falukant.overview.certificate.levelMatrix') }}</h4>
|
|
<div class="certificate-level-list">
|
|
<div
|
|
v-for="entry in certificateLevelMatrix"
|
|
:key="entry.level"
|
|
class="certificate-level-list__item"
|
|
:class="{ 'is-current': entry.level === (falukantUser?.certificate ?? 1) }"
|
|
>
|
|
<strong>{{ $t('falukant.overview.certificate.levelLabel', { level: entry.level }) }}</strong>
|
|
<span>{{ entry.products.join(', ') }}</span>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Erben-Auswahl wenn kein Charakter vorhanden -->
|
|
<div v-if="!falukantUser?.character" class="heir-selection-container">
|
|
<h3>{{ $t('falukant.overview.heirSelection.title') }}</h3>
|
|
<p>{{ $t('falukant.overview.heirSelection.description') }}</p>
|
|
<div v-if="loadingHeirs" class="loading">{{ $t('falukant.overview.heirSelection.loading') }}</div>
|
|
<div v-else-if="potentialHeirs.length === 0" class="no-heirs">
|
|
{{ $t('falukant.overview.heirSelection.noHeirs') }}
|
|
</div>
|
|
<div v-else class="heirs-list">
|
|
<div v-for="heir in potentialHeirs" :key="heir.id" class="heir-card">
|
|
<div class="heir-info">
|
|
<div class="heir-name">
|
|
{{ $t(`falukant.titles.${heir.gender}.noncivil`) }}
|
|
{{ heir.definedFirstName?.name || '---' }} {{ heir.definedLastName?.name || '' }}
|
|
</div>
|
|
<div class="heir-age">{{ $t('falukant.overview.metadata.age') }}: {{ heir.age }}</div>
|
|
</div>
|
|
<button @click="selectHeir(heir.id)" class="select-heir-button">
|
|
{{ $t('falukant.overview.heirSelection.select') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Normale Übersicht wenn Charakter vorhanden -->
|
|
<div v-if="falukantUser?.character" class="overviewcontainer">
|
|
<section class="overview-panel surface-card">
|
|
<h3>{{ $t('falukant.overview.metadata.title') }}</h3>
|
|
<div class="detail-list">
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.metadata.name') }}</span>
|
|
<strong>{{ falukantUser?.character?.definedFirstName?.name }} {{ falukantUser?.character?.definedLastName?.name }}</strong>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.metadata.nobleTitle') }}</span>
|
|
<strong>{{ $t('falukant.titles.' + falukantUser?.character?.gender + '.' + falukantUser?.character?.nobleTitle?.labelTr) }}</strong>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.metadata.money') }}</span>
|
|
<strong>
|
|
{{ moneyValue != null
|
|
? moneyValue.toLocaleString(locale, { style: 'currency', currency: 'EUR' })
|
|
: '---' }}
|
|
</strong>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.metadata.age') }}</span>
|
|
<strong>{{ falukantUser?.character?.age }}</strong>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.metadata.mainbranch') }}</span>
|
|
<strong>{{ falukantUser?.mainBranchRegion?.name }}</strong>
|
|
</div>
|
|
<div class="detail-list__item">
|
|
<span>{{ $t('falukant.overview.metadata.certificate') }}</span>
|
|
<strong>{{ falukantUser?.certificate ?? '---' }}</strong>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<section class="overview-panel surface-card">
|
|
<h3>{{ $t('falukant.overview.productions.title') }}</h3>
|
|
<div v-if="productions.length > 0" class="overview-card-list">
|
|
<article v-for="(production, index) in productions" :key="index" class="overview-entry-card">
|
|
<strong>{{ $t(`falukant.product.${production.productName}`) }}</strong>
|
|
<div class="overview-entry-card__meta">
|
|
<span>{{ $t('falukant.branch.sale.region') }}: {{ production.cityName }}</span>
|
|
<span>{{ $t('falukant.branch.production.quantity') }}: {{ production.quantity }}</span>
|
|
<span>{{ $t('falukant.branch.production.ending') }}: {{ formatDate(production.endTimestamp) }}</span>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
<p v-else>{{ $t('falukant.branch.production.noProductions') }}</p>
|
|
</section>
|
|
<section class="overview-panel surface-card">
|
|
<h3>{{ $t('falukant.overview.stock.title') }}</h3>
|
|
<div v-if="allStock.length > 0" class="overview-card-list">
|
|
<article v-for="(item, index) in allStock" :key="index" class="overview-entry-card">
|
|
<strong>{{ $t(`falukant.product.${item.productLabelTr}`) }}</strong>
|
|
<div class="overview-entry-card__meta">
|
|
<span>{{ $t('falukant.branch.sale.region') }}: {{ item.regionName }}</span>
|
|
<span>{{ $t('falukant.branch.sale.quantity') }}: {{ item.quantity }}</span>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
<p v-else>{{ $t('falukant.branch.sale.noInventory') }}</p>
|
|
</section>
|
|
<section class="overview-panel surface-card">
|
|
<h3>{{ $t('falukant.overview.branches.title') }}</h3>
|
|
<div class="overview-card-list">
|
|
<article v-for="branch in falukantUser?.branches" :key="branch.id" class="overview-entry-card overview-entry-card--action">
|
|
<div>
|
|
<strong>{{ branch.region.name }}</strong>
|
|
<div class="overview-entry-card__meta">
|
|
<span>{{ $t(`falukant.overview.branches.level.${branch.branchType.labelTr}`) }}</span>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="button-secondary" @click="openBranch(branch.id)">{{ $t('falukant.overview.summary.open') }}</button>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import StatusBar from '@/components/falukant/StatusBar.vue';
|
|
import Character3D from '@/components/Character3D.vue';
|
|
import apiClient from '@/utils/axios.js';
|
|
import { showError, showSuccess } from '@/utils/feedback.js';
|
|
import { mapState } from 'vuex';
|
|
|
|
const AVATAR_POSITIONS = {
|
|
male: {
|
|
width: 195,
|
|
height: 300,
|
|
positions: {
|
|
"0-1": { x: 161, y: 28 },
|
|
"2-3": { x: 802, y: 28 },
|
|
"4-6": { x: 1014, y: 28 },
|
|
"7-10": { x: 800, y: 368 },
|
|
"11-13": { x: 373, y: 368 },
|
|
"14-16": { x: 1441, y: 28 },
|
|
"17-20": { x: 1441, y: 368 },
|
|
"21-30": { x: 1014, y: 368 },
|
|
"31-45": { x: 1227, y: 368 },
|
|
"45-55": { x: 803, y: 687 },
|
|
"55+": { x: 1441, y: 687 },
|
|
},
|
|
},
|
|
female: {
|
|
width: 223,
|
|
height: 298,
|
|
positions: {
|
|
"0-1": { x: 302, y: 66 },
|
|
"2-3": { x: 792, y: 66 },
|
|
"4-6": { x: 62, y: 66 },
|
|
"7-10": { x: 1034, y: 66 },
|
|
"11-13": { x: 1278, y: 66 },
|
|
"14-16": { x: 303, y: 392 },
|
|
"17-20": { x: 1525, y: 392 },
|
|
"21-30": { x: 1278, y: 392 },
|
|
"31-45": { x: 547, y: 718 },
|
|
"45-55": { x: 1034, y: 718 },
|
|
"55+": { x: 1525, y: 718 },
|
|
},
|
|
},
|
|
};
|
|
|
|
const CERTIFICATE_PRODUCT_LEVELS = [
|
|
{ level: 1, products: ['fish', 'meat', 'leather', 'wood', 'stone', 'milk', 'cheese', 'bread', 'wheat', 'grain', 'carrot'] },
|
|
{ level: 2, products: ['beer', 'iron', 'copper', 'spices', 'salt', 'sugar', 'vinegar', 'cotton', 'wine'] },
|
|
{ level: 3, products: ['gold', 'diamond', 'furniture', 'clothing'] },
|
|
{ level: 4, products: ['jewelry', 'painting', 'book', 'weapon', 'armor', 'shield'] },
|
|
{ level: 5, products: ['horse', 'ox'] },
|
|
];
|
|
|
|
export default {
|
|
name: 'FalukantOverviewView',
|
|
components: {
|
|
StatusBar,
|
|
Character3D,
|
|
},
|
|
data() {
|
|
return {
|
|
falukantUser: null,
|
|
allStock: [],
|
|
productions: [],
|
|
potentialHeirs: [],
|
|
loadingHeirs: false,
|
|
pendingOverviewRefresh: null,
|
|
};
|
|
},
|
|
computed: {
|
|
...mapState(['socket', 'daemonSocket', 'user']),
|
|
getAvatarStyle() {
|
|
if (!this.falukantUser || !this.falukantUser.character) return {};
|
|
const { gender, age } = this.falukantUser.character;
|
|
const imageUrl = `/images/falukant/avatar/${gender}.png`;
|
|
const ageGroup = this.getAgeGroup(age);
|
|
const genderData = AVATAR_POSITIONS[gender] || {};
|
|
const position = genderData.positions?.[ageGroup] || { x: 0, y: 0 };
|
|
const width = genderData.width || 100;
|
|
const height = genderData.height || 100;
|
|
return {
|
|
backgroundImage: `url(${imageUrl})`,
|
|
backgroundPosition: `-${position.x}px -${position.y}px`,
|
|
backgroundSize: '1792px 1024px',
|
|
width: `${width}px`,
|
|
height: `${height}px`,
|
|
};
|
|
},
|
|
getHouseStyle() {
|
|
if (!this.falukantUser || !this.falukantUser.userHouse?.houseType) return {};
|
|
const imageUrl = '/images/falukant/houses.png';
|
|
const pos = this.falukantUser.userHouse.houseType.position;
|
|
const index = pos - 1;
|
|
const columns = 3;
|
|
const spriteSize = 300;
|
|
const x = (index % columns) * spriteSize;
|
|
const y = Math.floor(index / columns) * spriteSize;
|
|
return {
|
|
backgroundImage: `url(${imageUrl})`,
|
|
backgroundPosition: `-${x}px -${y}px`,
|
|
backgroundSize: `${columns * spriteSize}px auto`,
|
|
width: `300px`,
|
|
height: `300px`,
|
|
border: '1px solid #ccc',
|
|
borderRadius: '4px',
|
|
imageRendering: 'crisp-edges',
|
|
};
|
|
},
|
|
moneyValue() {
|
|
const m = this.falukantUser?.money;
|
|
return typeof m === 'string' ? parseFloat(m) : m;
|
|
},
|
|
branchCount() {
|
|
return this.falukantUser?.branches?.length || 0;
|
|
},
|
|
productionCount() {
|
|
return this.productions.length;
|
|
},
|
|
stockEntryCount() {
|
|
return this.allStock.length;
|
|
},
|
|
certificateProgress() {
|
|
return this.falukantUser?.certificateProgress || null;
|
|
},
|
|
certificateLevelMatrix() {
|
|
return CERTIFICATE_PRODUCT_LEVELS.map((entry) => ({
|
|
level: entry.level,
|
|
products: entry.products.map((productKey) => this.$t(`falukant.product.${productKey}`)),
|
|
}));
|
|
},
|
|
certificateProgressStateKey() {
|
|
const progress = this.certificateProgress;
|
|
if (!progress) return 'notReady';
|
|
if (progress.readyForNextCertificate) return 'ready';
|
|
if (progress.minimumRequirementsMet && !progress.scoreRequirementMet) return 'minimumsMetScoreBlocked';
|
|
if (!progress.minimumRequirementsMet && progress.scoreRequirementMet) return 'scoreMetMinimumsMissing';
|
|
return 'notReady';
|
|
},
|
|
certificateProgressStateClass() {
|
|
switch (this.certificateProgressStateKey) {
|
|
case 'ready':
|
|
return 'is-ready';
|
|
case 'minimumsMetScoreBlocked':
|
|
return 'is-warning';
|
|
case 'scoreMetMinimumsMissing':
|
|
return 'is-info';
|
|
default:
|
|
return '';
|
|
}
|
|
},
|
|
certificateProgressHint() {
|
|
const progress = this.certificateProgress;
|
|
if (!progress) return '';
|
|
if (progress.readyForNextCertificate) {
|
|
return this.$t('falukant.overview.certificate.hint.ready', {
|
|
next: progress.nextCertificate,
|
|
threshold: this.formatCertificateValue(progress.nextScoreThreshold, 1),
|
|
});
|
|
}
|
|
if (progress.minimumRequirementsMet && !progress.scoreRequirementMet) {
|
|
return this.$t('falukant.overview.certificate.hint.minimumsMetScoreBlocked', {
|
|
next: progress.nextCertificate,
|
|
target: progress.targetCertificate,
|
|
threshold: this.formatCertificateValue(progress.nextScoreThreshold, 1),
|
|
});
|
|
}
|
|
if (!progress.minimumRequirementsMet && progress.scoreRequirementMet) {
|
|
return this.$t('falukant.overview.certificate.hint.scoreMetMinimumsMissing', {
|
|
next: progress.nextCertificate,
|
|
});
|
|
}
|
|
return this.$t('falukant.overview.certificate.hint.notReady', {
|
|
next: progress.nextCertificate,
|
|
threshold: this.formatCertificateValue(progress.nextScoreThreshold, 1),
|
|
});
|
|
},
|
|
routineActions() {
|
|
return [
|
|
{
|
|
kicker: this.$t('falukant.overview.routine.branch.kicker'),
|
|
title: this.$t('falukant.overview.routine.branch.title'),
|
|
description: this.$t('falukant.overview.routine.branch.description'),
|
|
cta: this.$t('falukant.overview.routine.branch.cta'),
|
|
route: 'BranchView',
|
|
},
|
|
{
|
|
kicker: this.$t('falukant.overview.routine.finance.kicker'),
|
|
title: this.$t('falukant.overview.routine.finance.title'),
|
|
description: this.$t('falukant.overview.routine.finance.description'),
|
|
cta: this.$t('falukant.overview.routine.finance.cta'),
|
|
route: 'MoneyHistoryView',
|
|
secondary: true,
|
|
},
|
|
{
|
|
kicker: this.$t('falukant.overview.routine.family.kicker'),
|
|
title: this.$t('falukant.overview.routine.family.title'),
|
|
description: this.$t('falukant.overview.routine.family.description'),
|
|
cta: this.$t('falukant.overview.routine.family.cta'),
|
|
route: 'FalukantFamily',
|
|
secondary: true,
|
|
},
|
|
{
|
|
kicker: this.$t('falukant.overview.routine.house.kicker'),
|
|
title: this.$t('falukant.overview.routine.house.title'),
|
|
description: this.$t('falukant.overview.routine.house.description'),
|
|
cta: this.$t('falukant.overview.routine.house.cta'),
|
|
route: 'HouseView',
|
|
secondary: true,
|
|
},
|
|
];
|
|
},
|
|
locale() {
|
|
return window.navigator.language || 'en-US';
|
|
},
|
|
},
|
|
watch: {
|
|
socket(newSocket) {
|
|
if (newSocket) {
|
|
this.setupSocketEvents();
|
|
}
|
|
},
|
|
daemonSocket(newSocket, oldSocket) {
|
|
if (oldSocket) {
|
|
oldSocket.removeEventListener('message', this.handleDaemonMessage);
|
|
}
|
|
if (newSocket) {
|
|
newSocket.addEventListener('message', this.handleDaemonMessage);
|
|
}
|
|
},
|
|
},
|
|
async mounted() {
|
|
await this.fetchFalukantUser();
|
|
if (!this.falukantUser?.character) {
|
|
await this.fetchPotentialHeirs();
|
|
} else {
|
|
await this.fetchAllStock();
|
|
await this.fetchProductions();
|
|
}
|
|
this.setupSocketEvents();
|
|
if (this.daemonSocket) {
|
|
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
|
}
|
|
},
|
|
beforeUnmount() {
|
|
if (this.pendingOverviewRefresh) {
|
|
clearTimeout(this.pendingOverviewRefresh);
|
|
this.pendingOverviewRefresh = null;
|
|
}
|
|
if (this.daemonSocket) {
|
|
this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
|
|
}
|
|
if (this.socket) {
|
|
this.socket.off("falukantUserUpdated", this.fetchFalukantUser);
|
|
this.socket.off("falukantUpdateStatus");
|
|
this.socket.off("falukantUpdateFamily");
|
|
this.socket.off("falukantUpdateChurch");
|
|
this.socket.off("falukantUpdateDebt");
|
|
this.socket.off("falukantUpdateProductionCertificate");
|
|
this.socket.off("children_update");
|
|
this.socket.off("falukantBranchUpdate");
|
|
this.socket.off("stock_change");
|
|
}
|
|
},
|
|
methods: {
|
|
setupSocketEvents() {
|
|
if (this.socket) {
|
|
this.socket.on("falukantUserUpdated", this.fetchFalukantUser);
|
|
this.socket.on("falukantUpdateStatus", (data) => {
|
|
this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
|
});
|
|
this.socket.on("falukantUpdateFamily", (data) => {
|
|
this.handleEvent({ event: 'falukantUpdateFamily', ...data });
|
|
});
|
|
this.socket.on("falukantUpdateChurch", (data) => {
|
|
this.handleEvent({ event: 'falukantUpdateChurch', ...data });
|
|
});
|
|
this.socket.on("falukantUpdateDebt", (data) => {
|
|
this.handleEvent({ event: 'falukantUpdateDebt', ...data });
|
|
});
|
|
this.socket.on("falukantUpdateProductionCertificate", (data) => {
|
|
this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data });
|
|
});
|
|
this.socket.on("children_update", (data) => {
|
|
this.handleEvent({ event: 'children_update', ...data });
|
|
});
|
|
this.socket.on("falukantBranchUpdate", (data) => {
|
|
this.handleEvent({ event: 'falukantBranchUpdate', ...data });
|
|
});
|
|
this.socket.on("stock_change", (data) => {
|
|
this.handleEvent({ event: 'stock_change', ...data });
|
|
});
|
|
} else {
|
|
// Versuche es nach kurzer Verzögerung erneut
|
|
setTimeout(() => {
|
|
this.setupSocketEvents();
|
|
}, 1000);
|
|
}
|
|
},
|
|
getAgeGroup(age) {
|
|
if (age <= 1) return '0-1';
|
|
if (age <= 3) return '2-3';
|
|
if (age <= 6) return '4-6';
|
|
if (age <= 10) return '7-10';
|
|
if (age <= 13) return '11-13';
|
|
if (age <= 16) return '14-16';
|
|
if (age <= 20) return '17-20';
|
|
if (age <= 30) return '21-30';
|
|
if (age <= 45) return '31-45';
|
|
if (age <= 55) return '45-55';
|
|
return '55+';
|
|
},
|
|
handleDaemonMessage(event) {
|
|
if (event.data === 'ping') return;
|
|
try {
|
|
const message = JSON.parse(event.data);
|
|
this.handleEvent(message);
|
|
} catch (err) {
|
|
console.error('Overview: Error processing daemon message:', err);
|
|
}
|
|
},
|
|
matchesCurrentUser(eventData) {
|
|
if (eventData?.user_id == null) {
|
|
return true;
|
|
}
|
|
const currentIds = [this.user?.id, this.user?.hashedId]
|
|
.filter(Boolean)
|
|
.map((value) => String(value));
|
|
return currentIds.includes(String(eventData.user_id));
|
|
},
|
|
queueOverviewRefresh() {
|
|
if (this.pendingOverviewRefresh) {
|
|
clearTimeout(this.pendingOverviewRefresh);
|
|
}
|
|
this.pendingOverviewRefresh = setTimeout(async () => {
|
|
this.pendingOverviewRefresh = null;
|
|
await this.fetchFalukantUser();
|
|
if (this.falukantUser?.character) {
|
|
await this.fetchProductions();
|
|
await this.fetchAllStock();
|
|
}
|
|
}, 120);
|
|
},
|
|
async handleEvent(eventData) {
|
|
if (!this.falukantUser?.character) return;
|
|
if (!this.matchesCurrentUser(eventData)) return;
|
|
switch (eventData.event) {
|
|
case 'falukantUpdateStatus':
|
|
case 'falukantUpdateFamily':
|
|
case 'falukantUpdateChurch':
|
|
case 'falukantUpdateDebt':
|
|
case 'falukantUpdateProductionCertificate':
|
|
case 'children_update':
|
|
case 'falukantBranchUpdate':
|
|
this.queueOverviewRefresh();
|
|
break;
|
|
case 'production_ready':
|
|
case 'production_started':
|
|
await this.fetchProductions();
|
|
await this.fetchAllStock();
|
|
break;
|
|
case 'stock_change':
|
|
case 'selled_items':
|
|
await this.fetchProductions();
|
|
await this.fetchAllStock();
|
|
break;
|
|
}
|
|
},
|
|
async fetchFalukantUser() {
|
|
const falukantUser = await apiClient.get('/api/falukant/user');
|
|
if (!falukantUser.data) {
|
|
this.$router.push({ name: 'FalukantCreate' });
|
|
return;
|
|
}
|
|
this.falukantUser = falukantUser.data;
|
|
},
|
|
async fetchAllStock() {
|
|
const response = await apiClient.get('/api/falukant/stockoverview');
|
|
const rawData = response.data;
|
|
const aggregated = {};
|
|
for (const item of rawData) {
|
|
const key = `${item.regionName}__${item.productLabelTr}`;
|
|
if (!aggregated[key]) {
|
|
aggregated[key] = {
|
|
regionName: item.regionName,
|
|
productLabelTr: item.productLabelTr,
|
|
quantity: 0,
|
|
};
|
|
}
|
|
aggregated[key].quantity += item.quantity;
|
|
}
|
|
this.allStock = Object.values(aggregated);
|
|
},
|
|
openBranch(branchId) {
|
|
this.$router.push({ name: 'BranchView', params: { branchId } });
|
|
},
|
|
openRoute(routeName) {
|
|
if (routeName === 'BranchView') {
|
|
const firstBranch = this.falukantUser?.branches?.[0];
|
|
if (firstBranch?.id) {
|
|
this.openBranch(firstBranch.id);
|
|
}
|
|
return;
|
|
}
|
|
this.$router.push({ name: routeName });
|
|
},
|
|
async fetchProductions() {
|
|
try {
|
|
const response = await apiClient.get('/api/falukant/productions');
|
|
this.productions = response.data;
|
|
} catch (error) {
|
|
console.error('Error fetching productions:', error);
|
|
}
|
|
},
|
|
formatDate(timestamp) {
|
|
return new Date(timestamp).toLocaleString();
|
|
},
|
|
formatCertificateValue(value, digits = 0) {
|
|
if (value == null) return '---';
|
|
return new Intl.NumberFormat(this.locale, {
|
|
minimumFractionDigits: digits,
|
|
maximumFractionDigits: digits,
|
|
}).format(value);
|
|
},
|
|
formatCertificateRequirement(current, required) {
|
|
const currentDigits = typeof current === 'number' && !Number.isInteger(current) ? 1 : 0;
|
|
const requiredDigits = typeof required === 'number' && !Number.isInteger(required) ? 1 : 0;
|
|
return `${this.formatCertificateValue(current, currentDigits)} / ${this.formatCertificateValue(required, requiredDigits)}`;
|
|
},
|
|
certificateRequirementLabel(type) {
|
|
return this.$t(`falukant.overview.certificate.factor.${type}`);
|
|
},
|
|
certificateStatusRequirementLabel(mode) {
|
|
return this.$t(`falukant.overview.certificate.statusMode.${mode}`);
|
|
},
|
|
async fetchPotentialHeirs() {
|
|
this.loadingHeirs = true;
|
|
try {
|
|
const response = await apiClient.get('/api/falukant/heirs/potential');
|
|
this.potentialHeirs = response.data || [];
|
|
if (this.potentialHeirs.length === 0) {
|
|
console.warn('No potential heirs returned from API');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching potential heirs:', error);
|
|
console.error('Error details:', error.response?.data || error.message);
|
|
this.potentialHeirs = [];
|
|
} finally {
|
|
this.loadingHeirs = false;
|
|
}
|
|
},
|
|
async selectHeir(heirId) {
|
|
try {
|
|
await apiClient.post('/api/falukant/heirs/select', { heirId });
|
|
await this.fetchFalukantUser();
|
|
if (this.falukantUser?.character) {
|
|
await this.fetchAllStock();
|
|
await this.fetchProductions();
|
|
}
|
|
showSuccess(this, this.$t('falukant.overview.heirSelection.success'));
|
|
} catch (error) {
|
|
console.error('Error selecting heir:', error);
|
|
showError(this, this.$t('falukant.overview.heirSelection.error'));
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.falukant-overview {
|
|
max-width: var(--content-max-width);
|
|
margin: 0 auto;
|
|
padding-bottom: 24px;
|
|
}
|
|
|
|
.falukant-hero {
|
|
padding: 24px 26px;
|
|
margin-bottom: 16px;
|
|
background:
|
|
radial-gradient(circle at top right, rgba(248, 162, 43, 0.16), transparent 28%),
|
|
linear-gradient(180deg, rgba(255, 250, 243, 0.98) 0%, rgba(247, 238, 224, 0.98) 100%);
|
|
}
|
|
|
|
.falukant-kicker {
|
|
display: inline-block;
|
|
margin-bottom: 10px;
|
|
padding: 4px 10px;
|
|
border-radius: 999px;
|
|
background: rgba(248, 162, 43, 0.14);
|
|
color: #8a5411;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
|
|
.falukant-hero p {
|
|
margin: 0;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.falukant-debt-warning {
|
|
margin-bottom: 16px;
|
|
padding: 16px 18px;
|
|
border: 1px solid rgba(180, 120, 40, 0.32);
|
|
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 250, 239, 0.98));
|
|
}
|
|
|
|
.falukant-debt-warning.is-prison {
|
|
border-color: rgba(146, 57, 40, 0.4);
|
|
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
|
|
}
|
|
|
|
.falukant-debt-warning p {
|
|
margin: 6px 0 0;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.falukant-summary-grid,
|
|
.falukant-routine-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 14px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.falukant-routine-grid {
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
}
|
|
|
|
.certificate-panel {
|
|
margin-bottom: 16px;
|
|
padding: 18px;
|
|
}
|
|
|
|
.certificate-panel__header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
gap: 16px;
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.certificate-panel__header p {
|
|
margin: 6px 0 0;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.certificate-panel__badges {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
|
|
.certificate-panel__score {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.certificate-panel__state {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 4px 10px;
|
|
border-radius: 999px;
|
|
background: rgba(185, 99, 24, 0.12);
|
|
color: #8d5412;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.certificate-panel__state.is-ready {
|
|
background: rgba(68, 138, 86, 0.14);
|
|
color: #2f6b3d;
|
|
}
|
|
|
|
.certificate-panel__state.is-warning {
|
|
background: rgba(185, 99, 24, 0.16);
|
|
color: #8d5412;
|
|
}
|
|
|
|
.certificate-panel__state.is-info {
|
|
background: rgba(34, 96, 164, 0.14);
|
|
color: #21598f;
|
|
}
|
|
|
|
.certificate-panel__hint {
|
|
margin: 0 0 16px;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.certificate-panel__howto {
|
|
margin: -8px 0 16px;
|
|
color: var(--color-text-secondary);
|
|
font-size: 0.92rem;
|
|
}
|
|
|
|
.certificate-panel__productions-since {
|
|
margin: 6px 0 0;
|
|
font-size: 0.85rem;
|
|
line-height: 1.35;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.certificate-panel__grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 14px;
|
|
}
|
|
|
|
.certificate-panel__block h4 {
|
|
margin: 0 0 10px;
|
|
}
|
|
|
|
.certificate-requirements {
|
|
display: grid;
|
|
gap: 10px;
|
|
}
|
|
|
|
.certificate-requirement {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 12px 14px;
|
|
border-radius: var(--radius-md);
|
|
background: rgba(185, 99, 24, 0.08);
|
|
}
|
|
|
|
.certificate-requirement.is-met {
|
|
background: rgba(68, 138, 86, 0.12);
|
|
}
|
|
|
|
.certificate-status-options {
|
|
margin: 12px 0 0;
|
|
padding-left: 18px;
|
|
}
|
|
|
|
.certificate-status-options li {
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.certificate-status-options li.is-met {
|
|
color: #2f6b3d;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.certificate-level-list {
|
|
display: grid;
|
|
gap: 10px;
|
|
}
|
|
|
|
.certificate-level-list__item {
|
|
display: grid;
|
|
gap: 6px;
|
|
padding: 12px 14px;
|
|
border-radius: var(--radius-md);
|
|
background: rgba(138, 84, 17, 0.06);
|
|
}
|
|
|
|
.certificate-level-list__item strong {
|
|
color: #7a4b12;
|
|
}
|
|
|
|
.certificate-level-list__item span {
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.certificate-level-list__item.is-current {
|
|
background: rgba(68, 138, 86, 0.12);
|
|
box-shadow: inset 0 0 0 1px rgba(68, 138, 86, 0.22);
|
|
}
|
|
|
|
.summary-card,
|
|
.routine-card {
|
|
padding: 18px;
|
|
}
|
|
|
|
.summary-card strong {
|
|
display: block;
|
|
margin: 6px 0 8px;
|
|
font-size: 1.8rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
.summary-card p,
|
|
.routine-card p {
|
|
margin: 0;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.summary-card__label,
|
|
.routine-card__eyebrow {
|
|
display: inline-flex;
|
|
margin-bottom: 4px;
|
|
color: var(--color-text-muted);
|
|
font-size: 0.76rem;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.routine-card h3 {
|
|
margin: 0 0 8px;
|
|
}
|
|
|
|
.routine-card button {
|
|
margin-top: 14px;
|
|
}
|
|
|
|
.overviewcontainer {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
gap: 12px;
|
|
}
|
|
|
|
.overview-panel {
|
|
padding: 18px;
|
|
}
|
|
|
|
.detail-list,
|
|
.overview-card-list {
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.detail-list__item,
|
|
.overview-entry-card {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 14px 16px;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
background: rgba(255, 255, 255, 0.66);
|
|
}
|
|
|
|
.detail-list__item {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.detail-list__item span,
|
|
.overview-entry-card__meta {
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.overview-entry-card {
|
|
align-items: flex-start;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.overview-entry-card__meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px 16px;
|
|
margin-top: 6px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.overview-entry-card--action {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
|
|
.imagecontainer {
|
|
margin-top: 20px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 20px;
|
|
position: relative;
|
|
z-index: 0;
|
|
}
|
|
|
|
.imagecontainer--prison {
|
|
min-height: 320px;
|
|
}
|
|
|
|
.debtors-prison-visual {
|
|
position: relative;
|
|
width: min(100%, 540px);
|
|
height: 320px;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
background:
|
|
radial-gradient(circle at 18% 22%, rgba(255, 233, 167, 0.95) 0, rgba(255, 233, 167, 0.95) 10%, rgba(255, 233, 167, 0) 11%),
|
|
linear-gradient(180deg, #273149 0%, #31476b 42%, #6d5953 42%, #6d5953 100%);
|
|
box-shadow: var(--shadow-soft);
|
|
}
|
|
|
|
.debtors-prison-visual__moon {
|
|
position: absolute;
|
|
top: 42px;
|
|
left: 72px;
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 50%;
|
|
background: rgba(255, 241, 194, 0.9);
|
|
box-shadow: 0 0 24px rgba(255, 241, 194, 0.5);
|
|
}
|
|
|
|
.debtors-prison-visual__tower {
|
|
position: absolute;
|
|
left: 50%;
|
|
bottom: 54px;
|
|
width: 160px;
|
|
height: 190px;
|
|
transform: translateX(-50%);
|
|
border-radius: 18px 18px 10px 10px;
|
|
background:
|
|
linear-gradient(180deg, #8c8a86 0%, #6f6a64 100%);
|
|
box-shadow: inset 0 0 0 2px rgba(53, 49, 45, 0.18);
|
|
}
|
|
|
|
.debtors-prison-visual__tower::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: -26px;
|
|
left: 18px;
|
|
width: 124px;
|
|
height: 34px;
|
|
border-radius: 10px 10px 0 0;
|
|
background:
|
|
repeating-linear-gradient(90deg, #7d786f 0, #7d786f 18px, #646057 18px, #646057 26px);
|
|
}
|
|
|
|
.debtors-prison-visual__tower::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 50%;
|
|
bottom: 0;
|
|
width: 42px;
|
|
height: 88px;
|
|
transform: translateX(-50%);
|
|
border-radius: 18px 18px 0 0;
|
|
background: #40362f;
|
|
box-shadow: inset 0 0 0 2px rgba(17, 13, 11, 0.22);
|
|
}
|
|
|
|
.debtors-prison-visual__bars {
|
|
position: absolute;
|
|
top: 116px;
|
|
width: 34px;
|
|
height: 54px;
|
|
border-radius: 8px;
|
|
background:
|
|
repeating-linear-gradient(90deg, rgba(33, 31, 29, 0.85) 0, rgba(33, 31, 29, 0.85) 5px, transparent 5px, transparent 11px);
|
|
}
|
|
|
|
.debtors-prison-visual__bars--left {
|
|
left: calc(50% - 54px);
|
|
}
|
|
|
|
.debtors-prison-visual__bars--right {
|
|
right: calc(50% - 54px);
|
|
}
|
|
|
|
.debtors-prison-visual__ground {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
height: 64px;
|
|
background:
|
|
linear-gradient(180deg, rgba(72, 57, 51, 0.2), rgba(72, 57, 51, 0.42)),
|
|
repeating-linear-gradient(90deg, #756357 0, #756357 18px, #6d5a4f 18px, #6d5a4f 30px);
|
|
}
|
|
|
|
.avatar {
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
background-color: rgba(255,255,255,0.72);
|
|
background-repeat: no-repeat;
|
|
image-rendering: crisp-edges;
|
|
box-shadow: var(--shadow-soft);
|
|
}
|
|
|
|
.house-with-character {
|
|
position: relative;
|
|
width: 300px;
|
|
height: 300px;
|
|
}
|
|
|
|
.house {
|
|
position: absolute;
|
|
inset: 0;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
background-repeat: no-repeat;
|
|
image-rendering: crisp-edges;
|
|
z-index: 1;
|
|
}
|
|
|
|
.character-foreground {
|
|
position: absolute;
|
|
bottom: -15px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 55%;
|
|
height: 55%;
|
|
z-index: 2;
|
|
}
|
|
|
|
.heir-selection-container {
|
|
border: 1px solid rgba(177, 59, 53, 0.18);
|
|
border-radius: var(--radius-lg);
|
|
padding: 20px;
|
|
margin: 20px 0;
|
|
background-color: rgba(255, 243, 205, 0.92);
|
|
box-shadow: var(--shadow-soft);
|
|
}
|
|
|
|
.heir-selection-container h3 {
|
|
margin-top: 0;
|
|
color: #856404;
|
|
}
|
|
|
|
.heirs-list {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
gap: 15px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.heir-card {
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
padding: 15px;
|
|
background-color: rgba(255,255,255,0.86);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.heir-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.heir-name {
|
|
font-weight: bold;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.heir-age {
|
|
color: #666;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
@media (max-width: 1100px) {
|
|
.falukant-routine-grid {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.falukant-summary-grid,
|
|
.falukant-routine-grid,
|
|
.certificate-panel__grid,
|
|
.overviewcontainer {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.certificate-panel__header,
|
|
.certificate-panel__score,
|
|
.certificate-requirement {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
}
|
|
|
|
.select-heir-button {
|
|
background-color: #28a745;
|
|
color: white;
|
|
border: none;
|
|
padding: 8px 16px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.select-heir-button:hover {
|
|
background-color: #218838;
|
|
}
|
|
|
|
.loading, .no-heirs {
|
|
text-align: center;
|
|
padding: 20px;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
@media (max-width: 960px) {
|
|
.overviewcontainer {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.imagecontainer {
|
|
flex-direction: column;
|
|
}
|
|
}
|
|
</style>
|