Implement debtors prison features across the application: Enhance FalukantController to include debtors prison logic in various service methods. Update FalukantService to manage debtors prison state and integrate it into user data retrieval. Modify frontend components, including DashboardWidget, StatusBar, and BankView, to display debtors prison status and warnings. Add localization for debtors prison messages in English, German, and Spanish, ensuring clarity in user notifications and actions.

This commit is contained in:
Torsten Schulz (local)
2026-03-23 11:59:59 +01:00
parent f2343098d2
commit 9b88a98a20
19 changed files with 1643 additions and 102 deletions

View File

@@ -8,6 +8,18 @@
<!-- OVERVIEW -->
<div v-if="activeTab === 'account'">
<div v-if="debtorsPrison.active" class="debt-status" :class="{ 'is-prison': debtorsPrison.inDebtorsPrison }">
<h3>{{ debtStatusTitle }}</h3>
<p>{{ debtStatusDescription }}</p>
<div class="debt-status__meta">
<span>{{ $t('falukant.bank.debtorsPrison.daysOverdue') }}: <strong>{{ debtorsPrison.daysOverdue }}</strong></span>
<span>{{ $t('falukant.bank.debtorsPrison.creditworthiness') }}: <strong>{{ debtorsPrison.creditworthiness }}</strong></span>
<span v-if="debtorsPrison.nextForcedAction">
{{ $t('falukant.bank.debtorsPrison.nextForcedAction') }}:
<strong>{{ $t(`falukant.bank.debtorsPrison.actions.${debtorsPrison.nextForcedAction}`) }}</strong>
</span>
</div>
</div>
<div class="account-section">
<table>
<tr>
@@ -26,6 +38,10 @@
<td>{{ $t('falukant.bank.account.availableCredit') }}</td>
<td>{{ formatCost(bankOverview.availableCredit) }}</td>
</tr>
<tr>
<td>{{ $t('falukant.bank.debtorsPrison.creditworthiness') }}</td>
<td>{{ bankOverview.creditworthiness }}</td>
</tr>
</table>
</div>
</div>
@@ -77,9 +93,12 @@
<p>
<strong>{{ $t('falukant.bank.credits.payoff.total') }}: {{ formatCost(creditCost()) }}</strong>
</p>
<button @click="confirmPayoff" class="button" :disabled="!selectedCredit">
<button @click="confirmPayoff" class="button" :disabled="!selectedCredit || isCreditBlocked">
{{ $t('falukant.bank.credits.payoff.confirm') }}
</button>
<p v-if="isCreditBlocked" class="payoff-hint payoff-hint--error">
{{ $t('falukant.bank.debtorsPrison.creditBlocked') }}
</p>
</div>
</div>
</div>
@@ -93,6 +112,7 @@ import StatusBar from '@/components/falukant/StatusBar.vue';
import SimpleTabs from '@/components/SimpleTabs.vue';
import apiClient from '@/utils/axios.js';
import { mapState } from 'vuex';
import { showError } from '@/utils/feedback.js';
export default {
name: 'BankView',
@@ -118,34 +138,114 @@ export default {
};
},
computed: {
...mapState(['socket'])
...mapState(['socket', 'daemonSocket', 'user']),
debtorsPrison() {
return this.bankOverview.debtorsPrison || {
active: false,
inDebtorsPrison: false,
daysOverdue: 0,
creditworthiness: 100,
nextForcedAction: null
};
},
isCreditBlocked() {
return this.debtorsPrison.inDebtorsPrison;
},
debtStatusTitle() {
return this.debtorsPrison.inDebtorsPrison
? this.$t('falukant.bank.debtorsPrison.titlePrison')
: this.$t('falukant.bank.debtorsPrison.titleWarning');
},
debtStatusDescription() {
return this.debtorsPrison.inDebtorsPrison
? this.$t('falukant.bank.debtorsPrison.descriptionPrison')
: this.$t('falukant.bank.debtorsPrison.descriptionWarning');
}
},
watch: {
socket(newVal, oldVal) {
if (oldVal) this.teardownSocketEvents();
if (newVal) this.setupSocketEvents();
},
daemonSocket(newVal, oldVal) {
if (oldVal) this.teardownSocketEvents();
if (newVal) this.setupSocketEvents();
}
},
async mounted() {
await this.loadBankOverview();
this.setupSocketEvents();
},
beforeUnmount() {
if (this.socket) {
this.socket.off('falukantUpdateStatus', this.loadBankOverview);
if (this._pendingRefresh) {
clearTimeout(this._pendingRefresh);
this._pendingRefresh = null;
}
this.teardownSocketEvents();
},
methods: {
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));
},
setupSocketEvents() {
this.teardownSocketEvents();
if (this.socket) {
this.socket.on('falukantUpdateStatus', (data) => {
this.handleEvent({ event: 'falukantUpdateStatus', ...data });
});
} else {
setTimeout(() => this.setupSocketEvents(), 1000);
this._statusSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data });
this._familySocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateFamily', ...data });
this._debtSocketHandler = (data) => this.handleEvent({ event: 'falukantUpdateDebt', ...data });
this.socket.on('falukantUpdateStatus', this._statusSocketHandler);
this.socket.on('falukantUpdateFamily', this._familySocketHandler);
this.socket.on('falukantUpdateDebt', this._debtSocketHandler);
}
if (this.daemonSocket) {
this._daemonHandler = (event) => this.handleDaemonMessage(event);
this.daemonSocket.addEventListener('message', this._daemonHandler);
}
},
teardownSocketEvents() {
if (this.socket) {
if (this._statusSocketHandler) this.socket.off('falukantUpdateStatus', this._statusSocketHandler);
if (this._familySocketHandler) this.socket.off('falukantUpdateFamily', this._familySocketHandler);
if (this._debtSocketHandler) this.socket.off('falukantUpdateDebt', this._debtSocketHandler);
}
if (this.daemonSocket && this._daemonHandler) {
this.daemonSocket.removeEventListener('message', this._daemonHandler);
this._daemonHandler = null;
}
},
handleEvent(eventData) {
if (!this.matchesCurrentUser(eventData)) {
return;
}
switch (eventData.event) {
case 'falukantUpdateStatus':
this.loadBankOverview();
this.queueBankRefresh();
break;
case 'falukantUpdateDebt':
this.queueBankRefresh();
break;
case 'falukantUpdateFamily':
if (['monthly', 'lover_installment'].includes(eventData.reason)) {
this.queueBankRefresh();
}
break;
}
},
queueBankRefresh() {
if (this._pendingRefresh) {
clearTimeout(this._pendingRefresh);
}
this._pendingRefresh = setTimeout(() => {
this._pendingRefresh = null;
this.loadBankOverview();
}, 120);
},
async loadBankOverview() {
try {
const { data } = await apiClient.get('/api/falukant/bank/overview');
@@ -155,6 +255,7 @@ export default {
}
},
async confirmPayoff() {
if (this.isCreditBlocked) return;
try {
await apiClient.post('/api/falukant/bank/credits', {
height: this.selectedCredit
@@ -163,16 +264,17 @@ export default {
this.selectedCredit = null;
this.activeTab = 'credits';
} catch (err) {
console.error(err);
showError(err.response?.data?.error || this.$t('falukant.bank.debtorsPrison.creditError'));
}
},
handleDaemonMessage(msg) {
try {
if (['falukantUpdateStatus', 'moneyChange', 'creditChange'].includes(msg.event)) {
this.loadBankOverview();
const data = JSON.parse(msg.data);
if (['falukantUpdateStatus', 'falukantUpdateDebt'].includes(data.event)) {
this.handleEvent(data);
}
} catch (err) {
console.error(evt, err);
console.error(err);
}
},
feeRate() {
@@ -190,4 +292,38 @@ export default {
<style scoped lang="scss">
h2 { padding-top: 20px; }
.debt-status {
margin-bottom: 1rem;
padding: 1rem 1.1rem;
border-radius: var(--radius-md);
border: 1px solid rgba(180, 120, 40, 0.35);
background: linear-gradient(180deg, rgba(255, 244, 223, 0.95), rgba(255, 249, 238, 0.98));
}
.debt-status.is-prison {
border-color: rgba(146, 57, 40, 0.45);
background: linear-gradient(180deg, rgba(255, 232, 225, 0.96), rgba(255, 245, 241, 0.98));
}
.debt-status h3 {
margin-bottom: 0.35rem;
}
.debt-status__meta {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-top: 0.75rem;
font-size: 0.95rem;
}
.payoff-hint {
margin-top: 0.75rem;
}
.payoff-hint--error {
color: #8b2f23;
font-weight: 600;
}
</style>

View File

@@ -15,9 +15,28 @@
</div>
</section>
<section
v-if="debtorsPrison.active"
class="branch-debt-warning surface-card"
:class="{ 'is-prison': debtorsPrison.inDebtorsPrison }"
>
<strong>
{{ debtorsPrison.inDebtorsPrison
? $t('falukant.bank.debtorsPrison.titlePrison')
: $t('falukant.bank.debtorsPrison.titleWarning') }}
</strong>
<p>
{{ debtorsPrison.inDebtorsPrison
? $t('falukant.branch.debtorsPrison.branchLocked')
: $t('falukant.branch.debtorsPrison.branchRisk') }}
</p>
</section>
<BranchSelection
:branches="branches"
:selectedBranch="selectedBranch"
:blocked="debtorsPrison.inDebtorsPrison"
:blocked-reason="debtorsPrison.inDebtorsPrison ? $t('falukant.branch.debtorsPrison.selectionBlocked') : ''"
@branchSelected="onBranchSelected"
@createBranch="createBranch"
@upgradeBranch="upgradeBranch"
@@ -404,6 +423,10 @@ export default {
branchTaxesLoading: false,
branchTaxesError: null,
currentCertificate: null,
debtorsPrison: {
active: false,
inDebtorsPrison: false
},
pendingBranchRefresh: null,
};
},
@@ -462,6 +485,7 @@ export default {
// Live-Socket-Events (Backend Socket.io)
if (this.socket) {
this.socket.on('falukantUpdateStatus', (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data }));
this.socket.on('falukantUpdateDebt', (data) => this.handleEvent({ event: 'falukantUpdateDebt', ...data }));
this.socket.on('falukantUpdateProductionCertificate', (data) => this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data }));
this.socket.on('falukantBranchUpdate', (data) => this.handleEvent({ event: 'falukantBranchUpdate', ...data }));
this.socket.on('transport_arrived', (data) => this.handleEvent({ event: 'transport_arrived', ...data }));
@@ -482,6 +506,7 @@ export default {
}
if (this.socket) {
this.socket.off('falukantUpdateStatus');
this.socket.off('falukantUpdateDebt');
this.socket.off('falukantUpdateProductionCertificate');
this.socket.off('falukantBranchUpdate');
this.socket.off('transport_arrived');
@@ -558,6 +583,10 @@ export default {
try {
const result = await apiClient.get('/api/falukant/user');
this.currentCertificate = result.data?.certificate ?? null;
this.debtorsPrison = result.data?.debtorsPrison || {
active: false,
inDebtorsPrison: false
};
} catch (error) {
console.error('Error loading certificate:', error);
}
@@ -678,6 +707,10 @@ export default {
},
async createBranch() {
if (this.debtorsPrison.inDebtorsPrison) {
showError(this, this.$t('falukant.branch.debtorsPrison.selectionBlocked'));
return;
}
await this.loadBranches();
// Nach dem Anlegen eines neuen Branches automatisch den
// zuletzt/neu erstellten Branch auswählen.
@@ -694,6 +727,10 @@ export default {
async upgradeBranch() {
if (!this.selectedBranch) return;
if (this.debtorsPrison.inDebtorsPrison) {
showError(this, this.$t('falukant.branch.debtorsPrison.selectionBlocked'));
return;
}
try {
await apiClient.post('/api/falukant/branches/upgrade', {
branchId: this.selectedBranch.id,
@@ -851,6 +888,7 @@ export default {
this.$refs.productionSection?.loadStorage();
break;
case 'falukantUpdateStatus':
case 'falukantUpdateDebt':
case 'falukantUpdateProductionCertificate':
case 'falukantBranchUpdate':
this.queueBranchRefresh();
@@ -1184,6 +1222,23 @@ export default {
color: var(--color-text-secondary);
}
.branch-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));
}
.branch-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));
}
.branch-debt-warning p {
margin: 6px 0 0;
color: var(--color-text-secondary);
}
.branch-hero__meta {
margin-top: 12px;
}

View File

@@ -11,6 +11,23 @@
</div>
</section>
<section
v-if="debtorsPrison.active"
class="family-debt-warning surface-card"
:class="{ 'is-prison': debtorsPrison.inDebtorsPrison }"
>
<strong>
{{ debtorsPrison.inDebtorsPrison
? $t('falukant.bank.debtorsPrison.titlePrison')
: $t('falukant.bank.debtorsPrison.titleWarning') }}
</strong>
<p>
{{ debtorsPrison.inDebtorsPrison
? $t('falukant.family.debtorsPrison.familyImpact')
: $t('falukant.family.debtorsPrison.familyWarning') }}
</p>
</section>
<div class="spouse-section">
<h3>{{ $t('falukant.family.spouse.title') }}</h3>
<div v-if="relationships.length > 0" class="relationship-container">
@@ -386,6 +403,10 @@ export default {
householdTension: null,
householdTensionScore: null,
householdTensionReasons: [],
debtorsPrison: {
active: false,
inDebtorsPrison: false
},
selectedChild: null,
pendingFamilyRefresh: null
}
@@ -426,11 +447,13 @@ export default {
if (this.socket) {
this._falukantUpdateStatusHandler = (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data });
this._falukantUpdateFamilyHandler = (data) => this.handleEvent({ event: 'falukantUpdateFamily', ...data });
this._falukantUpdateDebtHandler = (data) => this.handleEvent({ event: 'falukantUpdateDebt', ...data });
this._childrenUpdateHandler = (data) => this.handleEvent({ event: 'children_update', ...data });
this._familyChangedHandler = (data) => this.handleEvent({ event: 'familychanged', ...data });
this.socket.on('falukantUpdateStatus', this._falukantUpdateStatusHandler);
this.socket.on('falukantUpdateFamily', this._falukantUpdateFamilyHandler);
this.socket.on('falukantUpdateDebt', this._falukantUpdateDebtHandler);
this.socket.on('children_update', this._childrenUpdateHandler);
this.socket.on('familychanged', this._familyChangedHandler);
} else {
@@ -441,6 +464,7 @@ export default {
if (!this.socket) return;
if (this._falukantUpdateStatusHandler) this.socket.off('falukantUpdateStatus', this._falukantUpdateStatusHandler);
if (this._falukantUpdateFamilyHandler) this.socket.off('falukantUpdateFamily', this._falukantUpdateFamilyHandler);
if (this._falukantUpdateDebtHandler) this.socket.off('falukantUpdateDebt', this._falukantUpdateDebtHandler);
if (this._childrenUpdateHandler) this.socket.off('children_update', this._childrenUpdateHandler);
if (this._familyChangedHandler) this.socket.off('familychanged', this._familyChangedHandler);
},
@@ -454,6 +478,7 @@ export default {
if ([
'falukantUpdateStatus',
'falukantUpdateFamily',
'falukantUpdateDebt',
'children_update',
'falukantUpdateChurch',
'familychanged',
@@ -502,6 +527,7 @@ export default {
switch (eventData.event) {
case 'falukantUpdateStatus':
case 'falukantUpdateDebt':
case 'familychanged':
this.queueFamilyRefresh({ reloadCharacter: true });
break;
@@ -533,6 +559,10 @@ export default {
this.householdTension = response.data.householdTension;
this.householdTensionScore = response.data.householdTensionScore;
this.householdTensionReasons = response.data.householdTensionReasons || [];
this.debtorsPrison = response.data.debtorsPrison || {
active: false,
inDebtorsPrison: false
};
} catch (error) {
console.error('Error loading family data:', error);
}
@@ -874,6 +904,23 @@ export default {
color: var(--color-text-secondary);
}
.family-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));
}
.family-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));
}
.family-debt-warning p {
margin: 6px 0 0;
color: var(--color-text-secondary);
}
.marriage-overview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));

View File

@@ -2,6 +2,22 @@
<div class="house-view">
<StatusBar />
<h2>{{ $t('falukant.house.title') }}</h2>
<section
v-if="debtorsPrison.active"
class="house-debt-warning surface-card"
:class="{ 'is-prison': debtorsPrison.inDebtorsPrison }"
>
<strong>
{{ debtorsPrison.inDebtorsPrison
? $t('falukant.bank.debtorsPrison.titlePrison')
: $t('falukant.bank.debtorsPrison.titleWarning') }}
</strong>
<p>
{{ debtorsPrison.inDebtorsPrison
? $t('falukant.house.debtorsPrison.houseRisk')
: $t('falukant.house.debtorsPrison.houseWarning') }}
</p>
</section>
<div class="existing-house">
<div :style="houseType ? houseStyle(houseType.position, 341) : {}" class="house"></div>
<div class="status-panel surface-card">
@@ -127,7 +143,7 @@
<div class="buyable-house-price">
{{ $t('falukant.house.price') }}: {{ buyCost(house) }}
</div>
<button @click="buyHouse(house.id)">
<button @click="buyHouse(house.id)" :disabled="debtorsPrison.inDebtorsPrison">
{{ $t('falukant.house.buy') }}
</button>
</div>
@@ -161,11 +177,15 @@ export default {
servantPayLevel: 'normal',
servantPayOptions: ['low', 'normal', 'high'],
buyableHouses: [],
currency: '€'
currency: '€',
debtorsPrison: {
active: false,
inDebtorsPrison: false
}
};
},
computed: {
...mapState(['socket']),
...mapState(['socket', 'daemonSocket', 'user']),
allRenovated() {
return Object.values(this.status).every(v => v >= 100);
}
@@ -176,6 +196,10 @@ export default {
const userRes = await apiClient.get('/api/falukant/houses');
this.userHouse = userRes.data;
this.houseType = this.userHouse.houseType;
this.debtorsPrison = this.userHouse.debtorsPrison || {
active: false,
inDebtorsPrison: false
};
const { roofCondition, wallCondition, floorCondition, windowCondition } = this.userHouse;
this.status = { roofCondition, wallCondition, floorCondition, windowCondition };
this.servantSummary = this.userHouse.servantSummary || this.servantSummary;
@@ -327,14 +351,27 @@ export default {
handleDaemonMessage(evt) {
try {
const msg = JSON.parse(evt.data);
if (msg.event === 'houseupdated') this.loadData();
if (!this.matchesCurrentUser(msg)) return;
if (['houseupdated', 'falukantUpdateStatus', 'falukantUpdateDebt', 'falukantHouseUpdate'].includes(msg.event)) this.loadData();
} catch { }
},
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));
},
setupSocketEvents() {
if (this.socket) {
this.socket.on('falukantHouseUpdate', (data) => {
this.handleEvent({ event: 'falukantHouseUpdate', ...data });
});
this.socket.on('falukantUpdateDebt', (data) => {
this.handleEvent({ event: 'falukantUpdateDebt', ...data });
});
this.socket.on('falukantUpdateStatus', (data) => {
this.handleEvent({ event: 'falukantUpdateStatus', ...data });
});
@@ -345,6 +382,7 @@ export default {
handleEvent(eventData) {
switch (eventData.event) {
case 'falukantUpdateStatus':
case 'falukantUpdateDebt':
case 'falukantHouseUpdate':
this.loadData();
break;
@@ -354,10 +392,17 @@ export default {
async mounted() {
await this.loadData();
this.setupSocketEvents();
if (this.daemonSocket) {
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
}
},
beforeUnmount() {
if (this.daemonSocket) {
this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
}
if (this.socket) {
this.socket.off('falukantHouseUpdate', this.loadData);
this.socket.off('falukantUpdateDebt', this.loadData);
this.socket.off('falukantUpdateStatus', this.loadData);
}
}
@@ -387,6 +432,22 @@ h2 {
margin: 0 0 10px;
}
.house-debt-warning {
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));
}
.house-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));
}
.house-debt-warning p {
margin: 6px 0 0;
color: var(--color-text-secondary);
}
.existing-house {
display: flex;
gap: 20px;

View File

@@ -9,7 +9,24 @@
</div>
</section>
<div v-if="falukantUser?.character" class="imagecontainer">
<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>
@@ -23,6 +40,16 @@
</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>
@@ -44,6 +71,15 @@
<strong>{{ stockEntryCount }}</strong>
<p>Verdichteter Blick auf Warenbestand über alle Regionen.</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">
@@ -357,6 +393,7 @@ export default {
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");
@@ -376,6 +413,9 @@ export default {
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 });
});
@@ -446,6 +486,7 @@ export default {
case 'falukantUpdateStatus':
case 'falukantUpdateFamily':
case 'falukantUpdateChurch':
case 'falukantUpdateDebt':
case 'falukantUpdateProductionCertificate':
case 'children_update':
case 'falukantBranchUpdate':
@@ -579,6 +620,23 @@ export default {
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;
@@ -694,6 +752,101 @@ export default {
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);