2026-04-04 08:17:56 +02:00
|
|
|
---
|
|
|
|
|
title: TFW
|
2026-04-04 09:21:34 +02:00
|
|
|
tags:
|
|
|
|
|
- game
|
2026-04-04 08:17:56 +02:00
|
|
|
---
|
|
|
|
|
{% assign bodyClass = 'body_tfw' -%}
|
2026-04-23 21:45:14 +02:00
|
|
|
{% layout 'hippie-view/game.liquid' %}
|
2026-04-04 08:17:56 +02:00
|
|
|
|
|
|
|
|
{% block body %}
|
|
|
|
|
<header class="io">
|
|
|
|
|
<button data-action="escape">☰</button>
|
2026-04-04 12:12:21 +02:00
|
|
|
<button data-direction="previous"><</button>
|
|
|
|
|
<button data-view="quest">Quests</button>
|
|
|
|
|
<button data-view="region">Regions</button>
|
|
|
|
|
<button data-view="vendor">Vendors</button>
|
|
|
|
|
<button data-view="manufacture">Manufacture</button>
|
|
|
|
|
<button data-view="character">Characters</button>
|
|
|
|
|
<button data-view="stash">Stash</button>
|
|
|
|
|
<button data-view="secret">Secret Storage</button>
|
|
|
|
|
<button data-view="squad">Squads</button>
|
|
|
|
|
<button data-view="ready">Ready Room</button>
|
|
|
|
|
<button data-direction="next">></button>
|
2026-04-04 08:17:56 +02:00
|
|
|
</header>
|
2026-04-04 13:03:59 +02:00
|
|
|
<div id="viewQuest" class="view">
|
|
|
|
|
<main>
|
2026-04-04 08:17:56 +02:00
|
|
|
<nav>
|
|
|
|
|
<div class="important">Filter</div>
|
2026-04-05 12:18:40 +02:00
|
|
|
<input placeholder="Search" aria-label="search" type="text">
|
|
|
|
|
<select name="type" aria-label="type">
|
2026-04-04 10:22:04 +02:00
|
|
|
<option value="" selected>Type</option>
|
|
|
|
|
<option value="all">All</option>
|
|
|
|
|
<option value="assasin">Assasination</option>
|
|
|
|
|
<option value="loot">Looting</option>
|
|
|
|
|
<option value="extract">Extract</option>
|
|
|
|
|
<option value="fetch">Fetch</option>
|
|
|
|
|
<option value="kill">Kill</option>
|
|
|
|
|
</select>
|
2026-04-04 08:17:56 +02:00
|
|
|
</nav>
|
|
|
|
|
<div>
|
|
|
|
|
<div>
|
2026-04-19 12:21:38 +02:00
|
|
|
<table id="factionSelection" data-type="faction">
|
|
|
|
|
<colgroup>
|
|
|
|
|
<col class="g">
|
|
|
|
|
<col class="c">
|
|
|
|
|
<col class="f">
|
|
|
|
|
<col class="s">
|
|
|
|
|
</colgroup>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>Giver</th>
|
|
|
|
|
<th>Category</th>
|
|
|
|
|
<th>Faction</th>
|
|
|
|
|
<th>Status</th>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td class="background">
|
|
|
|
|
<span>Scavengers</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="subtle">All</td>
|
|
|
|
|
<td class="subtle">Scav</td>
|
|
|
|
|
<td class="subtle">Open</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td class="background">
|
|
|
|
|
<span>Eastern Consulate</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="subtle">All</td>
|
|
|
|
|
<td class="subtle">Eurasia</td>
|
|
|
|
|
<td class="subtle">Closed</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
|
|
|
|
<td class="background">
|
|
|
|
|
<span>СПЕЦНАЗ Commission</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td class="subtle">All</td>
|
|
|
|
|
<td class="subtle">Euruska</td>
|
|
|
|
|
<td class="subtle">Open</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
<table id="questSelection" data-type="quest">
|
2026-04-04 08:17:56 +02:00
|
|
|
<colgroup>
|
|
|
|
|
<col class="l">
|
|
|
|
|
<col class="q">
|
|
|
|
|
<col class="t">
|
|
|
|
|
</colgroup>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>Location</th>
|
|
|
|
|
<th>Quest</th>
|
|
|
|
|
<th>Type</th>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
2026-04-04 10:22:04 +02:00
|
|
|
<td class="background">
|
2026-04-04 08:17:56 +02:00
|
|
|
<span>Scorched Earth</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td>...</td>
|
|
|
|
|
<td class="subtle">Available</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr>
|
2026-04-04 10:22:04 +02:00
|
|
|
<td class="background">
|
2026-04-04 08:17:56 +02:00
|
|
|
<span>Location name</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td>...</td>
|
|
|
|
|
<td class="subtle">Available</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
2026-04-19 12:21:38 +02:00
|
|
|
<table id="questActive" data-type="quest">
|
2026-04-04 08:17:56 +02:00
|
|
|
<colgroup>
|
|
|
|
|
<col class="l">
|
|
|
|
|
<col class="q">
|
|
|
|
|
<col class="s">
|
|
|
|
|
</colgroup>
|
2026-04-13 23:46:45 +02:00
|
|
|
<thead>
|
2026-04-04 08:17:56 +02:00
|
|
|
<tr>
|
|
|
|
|
<th colspan="3">Active quests (Max.: 4)</th>
|
|
|
|
|
</tr>
|
2026-04-13 23:46:45 +02:00
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
2026-04-04 08:17:56 +02:00
|
|
|
<tr>
|
2026-04-04 10:22:04 +02:00
|
|
|
<td class="background"></td>
|
2026-04-04 08:17:56 +02:00
|
|
|
<td>King Of Kings</td>
|
|
|
|
|
<td class="subtle">Active</td>
|
|
|
|
|
</tr>
|
2026-04-13 23:46:45 +02:00
|
|
|
<tr class="complete">
|
2026-04-04 10:22:04 +02:00
|
|
|
<td class="background"></td>
|
2026-04-04 08:17:56 +02:00
|
|
|
<td>Garage Days Pt. 1</td>
|
2026-04-13 23:46:45 +02:00
|
|
|
<td class="subtle">Complete</td>
|
2026-04-04 08:17:56 +02:00
|
|
|
</tr>
|
2026-04-13 23:46:45 +02:00
|
|
|
</tbody>
|
2026-04-04 08:17:56 +02:00
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-04-19 12:21:38 +02:00
|
|
|
<aside>
|
|
|
|
|
<div class="faction">
|
|
|
|
|
<div class="background">
|
|
|
|
|
<hgroup>
|
|
|
|
|
<h2>Western Embassy</h2>
|
|
|
|
|
<p>Europa</p>
|
|
|
|
|
</hgroup>
|
|
|
|
|
</div>
|
|
|
|
|
<p>A hijacked medium mech dubbed the "Rat King" ...</p>
|
|
|
|
|
<hr class="dotted">
|
|
|
|
|
<p>Collect Rat King residue.</p>
|
|
|
|
|
<hr>
|
|
|
|
|
<p>Multiple rig container upgrades, 5000 CR, 5000 XP, 2 days of water, + Scav faction
|
|
|
|
|
rating</p>
|
|
|
|
|
</div>
|
2026-04-05 12:18:40 +02:00
|
|
|
<div class="quest">
|
|
|
|
|
<div class="background">
|
|
|
|
|
<h2>King Of Kings</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<p>A hijacked medium mech dubbed the "Rat King" ...</p>
|
|
|
|
|
<hr class="dotted">
|
|
|
|
|
<p>Collect Rat King residue.</p>
|
|
|
|
|
<hr>
|
|
|
|
|
<p>Multiple rig container upgrades, 5000 CR, 5000 XP, 2 days of water, + Scav faction
|
|
|
|
|
rating</p>
|
2026-04-04 10:22:04 +02:00
|
|
|
</div>
|
2026-04-19 12:21:38 +02:00
|
|
|
</aside>
|
2026-04-04 08:17:56 +02:00
|
|
|
</main>
|
|
|
|
|
<footer class="io">
|
|
|
|
|
<button data-action="back">Back</button>
|
2026-04-13 23:46:45 +02:00
|
|
|
<button data-action="accept">Accept quest</button>
|
|
|
|
|
<button data-action="abandon">Abandon quest</button>
|
|
|
|
|
<button data-action="claim">Claim reward</button>
|
2026-04-04 08:17:56 +02:00
|
|
|
</footer>
|
|
|
|
|
</div>
|
2026-04-04 13:03:59 +02:00
|
|
|
<div id="viewRegion" class="view"></div>
|
|
|
|
|
<div id="viewVendor" class="view"></div>
|
2026-04-04 08:17:56 +02:00
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
{%- block script %}
|
|
|
|
|
{{ block.super -}}
|
|
|
|
|
<script>
|
|
|
|
|
const menu = document.querySelector('body > header');
|
2026-04-04 10:22:04 +02:00
|
|
|
const placeholder = document.querySelectorAll('.background');
|
2026-04-05 12:18:40 +02:00
|
|
|
const viewQuest = document.getElementById('viewQuest');
|
2026-04-04 08:17:56 +02:00
|
|
|
|
2026-04-04 11:17:54 +02:00
|
|
|
class Menu {
|
2026-04-04 13:03:59 +02:00
|
|
|
constructor(element, options = {}) {
|
2026-04-04 12:12:21 +02:00
|
|
|
this._element = element;
|
2026-04-04 13:03:59 +02:00
|
|
|
this._siblings = element.querySelectorAll('button[data-view]');
|
|
|
|
|
this.default = options.default || 'quest';
|
|
|
|
|
|
2026-04-04 11:17:54 +02:00
|
|
|
element.addEventListener('click', this.onClick.bind(this)); // Bind to get the clicked element and not the DOM element of the class
|
2026-04-04 13:03:59 +02:00
|
|
|
|
|
|
|
|
this.#init();
|
2026-04-04 11:17:54 +02:00
|
|
|
}
|
2026-04-04 08:17:56 +02:00
|
|
|
|
2026-04-04 11:17:54 +02:00
|
|
|
escape() {
|
|
|
|
|
console.log('escape');
|
|
|
|
|
}
|
2026-04-04 08:17:56 +02:00
|
|
|
|
2026-04-04 13:03:59 +02:00
|
|
|
#init() {
|
|
|
|
|
const currentBtn = Array.from(this._siblings).find(
|
|
|
|
|
el => el.dataset.view === this.default
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
currentBtn.classList.add('active');
|
|
|
|
|
this.changeView(this.default);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-05 12:18:40 +02:00
|
|
|
// TODO: Sollte auch die Menüauswahl anpassen
|
2026-04-04 13:03:59 +02:00
|
|
|
changeView(type) {
|
|
|
|
|
console.debug(type);
|
|
|
|
|
const id = 'view' + capitalizeFirstLetter(type);
|
|
|
|
|
const views = document.querySelectorAll('.view');
|
|
|
|
|
|
|
|
|
|
for (const view of views) {
|
|
|
|
|
view.style.display = 'none';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
document.getElementById(id).style.display = 'flex';
|
2026-04-04 08:17:56 +02:00
|
|
|
}
|
2026-04-04 11:17:54 +02:00
|
|
|
|
|
|
|
|
onClick(event) {
|
2026-04-04 13:03:59 +02:00
|
|
|
const siblings = this._siblings;
|
2026-04-04 11:17:54 +02:00
|
|
|
const action = event.target.dataset.action;
|
2026-04-04 12:12:21 +02:00
|
|
|
const view = event.target.dataset.view;
|
|
|
|
|
const direction = event.target.dataset.direction;
|
|
|
|
|
|
|
|
|
|
if (event.button !== 0) return;
|
|
|
|
|
|
|
|
|
|
if (direction) {
|
2026-04-04 13:03:59 +02:00
|
|
|
const currentBtn = this._element.querySelector('.active');
|
|
|
|
|
let newButton, newView = undefined;
|
2026-04-04 11:17:54 +02:00
|
|
|
|
2026-04-04 13:03:59 +02:00
|
|
|
if (currentBtn === null) return;
|
2026-04-04 12:12:21 +02:00
|
|
|
|
2026-04-04 13:03:59 +02:00
|
|
|
if (direction === 'next') {
|
|
|
|
|
newButton = currentBtn.nextElementSibling;
|
|
|
|
|
newView = currentBtn.nextElementSibling.dataset.view;
|
2026-04-04 12:12:21 +02:00
|
|
|
} else {
|
2026-04-04 13:03:59 +02:00
|
|
|
newButton = currentBtn.previousElementSibling;
|
|
|
|
|
newView = currentBtn.previousElementSibling.dataset.view;
|
2026-04-04 12:12:21 +02:00
|
|
|
}
|
2026-04-04 13:03:59 +02:00
|
|
|
|
|
|
|
|
if (!newButton.dataset.view) {
|
|
|
|
|
newButton = direction === 'next' ? siblings[0] : siblings[siblings.length - 1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentBtn.classList.remove('active');
|
|
|
|
|
newButton.classList.add('active');
|
|
|
|
|
this.changeView(newView);
|
2026-04-04 12:12:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (view) {
|
2026-04-04 11:17:54 +02:00
|
|
|
for (const sibling of siblings) {
|
|
|
|
|
sibling.classList.remove('active');
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 13:03:59 +02:00
|
|
|
this.changeView(view);
|
2026-04-04 11:17:54 +02:00
|
|
|
event.target.classList.add('active');
|
|
|
|
|
}
|
2026-04-04 12:12:21 +02:00
|
|
|
|
|
|
|
|
if (action) this[action]();
|
2026-04-04 11:17:54 +02:00
|
|
|
};
|
|
|
|
|
}
|
2026-04-04 08:17:56 +02:00
|
|
|
|
2026-04-05 12:18:40 +02:00
|
|
|
// TODO: Allgemeinere Umsetzung anstreben
|
|
|
|
|
viewQuest.addEventListener('click', (event) => {
|
2026-04-19 12:21:38 +02:00
|
|
|
const tableTarget = event.target.closest('table');
|
2026-04-05 12:18:40 +02:00
|
|
|
const rows = viewQuest.querySelectorAll('tr');
|
|
|
|
|
const rowTarget = event.target.closest('tr');
|
2026-04-19 12:21:38 +02:00
|
|
|
const rowFaction = event.target.closest('#factionSelection tr');
|
2026-04-13 23:46:45 +02:00
|
|
|
const rowSelection = event.target.closest('#questSelection tr');
|
|
|
|
|
const rowActive = event.target.closest('#questActive tr:not(.complete)');
|
|
|
|
|
const rowComplete = event.target.closest('#questActive tr.complete');
|
|
|
|
|
const buttonAccept = viewQuest.querySelector('footer button[data-action=accept]');
|
|
|
|
|
const buttonAbandon = viewQuest.querySelector('footer button[data-action=abandon]');
|
|
|
|
|
const buttonClaim = viewQuest.querySelector('footer button[data-action=claim]');
|
2026-04-05 12:18:40 +02:00
|
|
|
|
|
|
|
|
if (event.button !== 0) return;
|
|
|
|
|
|
|
|
|
|
if (rowTarget) {
|
2026-04-19 12:21:38 +02:00
|
|
|
const rowsRemain = Array.from(rows).filter(
|
|
|
|
|
element => element !== rowTarget
|
|
|
|
|
);
|
|
|
|
|
const type = tableTarget.dataset.type;
|
|
|
|
|
const tableSibling = tableTarget.nextElementSibling;
|
|
|
|
|
|
|
|
|
|
rowsRemain.forEach((element) => {
|
|
|
|
|
element.classList.remove('active');
|
|
|
|
|
});
|
2026-04-05 12:18:40 +02:00
|
|
|
|
|
|
|
|
rowTarget.classList.add('active');
|
2026-04-19 12:21:38 +02:00
|
|
|
|
|
|
|
|
viewQuest.querySelector('aside > :not(.' + type + ')').style.display = 'none';
|
|
|
|
|
viewQuest.querySelector('aside > .' + type).style.display = 'block';
|
|
|
|
|
|
|
|
|
|
if (rowFaction) {
|
|
|
|
|
tableTarget.style.display = 'none';
|
|
|
|
|
tableSibling.style.display = 'table';
|
|
|
|
|
}
|
2026-04-13 23:46:45 +02:00
|
|
|
|
|
|
|
|
if (rowSelection) {
|
|
|
|
|
buttonClaim.style.display = 'none';
|
|
|
|
|
buttonAbandon.style.display = 'none';
|
|
|
|
|
buttonAccept.style.display = 'inline-block';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rowActive) {
|
|
|
|
|
buttonAccept.style.display = 'none';
|
|
|
|
|
buttonClaim.style.display = 'none';
|
|
|
|
|
buttonAbandon.style.display = 'inline-block';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rowComplete) {
|
|
|
|
|
buttonAccept.style.display = 'none';
|
|
|
|
|
buttonAbandon.style.display = 'none';
|
|
|
|
|
buttonClaim.style.display = 'inline-block';
|
|
|
|
|
}
|
2026-04-19 12:21:38 +02:00
|
|
|
} else {
|
|
|
|
|
deselector('quest');
|
2026-04-05 12:18:40 +02:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-04 10:22:04 +02:00
|
|
|
placeholder.forEach(element => {
|
|
|
|
|
const hue = randomIntFrom(0, 360);
|
2026-04-04 08:17:56 +02:00
|
|
|
const grayscale = randomFloatFrom(0, 1);
|
|
|
|
|
|
2026-04-05 12:18:40 +02:00
|
|
|
new RandomPixelPlaceholder(element, {
|
|
|
|
|
width: Math.floor(element.clientWidth),
|
|
|
|
|
height: Math.floor(element.clientHeight),
|
2026-04-04 08:17:56 +02:00
|
|
|
colors: ['#fad803', '#d30a51', '#273f8b', '#b7e0f0', '#52bed1', '#0c85ff'],
|
2026-04-05 12:18:40 +02:00
|
|
|
filter: 'grayscale(' + grayscale + ') hue-rotate(' + hue + 'deg)',
|
|
|
|
|
type: 'img'
|
2026-04-04 08:17:56 +02:00
|
|
|
});
|
|
|
|
|
});
|
2026-04-04 11:17:54 +02:00
|
|
|
|
|
|
|
|
new Menu(menu);
|
2026-04-19 12:21:38 +02:00
|
|
|
|
|
|
|
|
function deselector(type) {
|
|
|
|
|
const id = 'view' + capitalizeFirstLetter(type);
|
|
|
|
|
const view = document.getElementById(id);
|
|
|
|
|
const rows = view.querySelectorAll('tr');
|
|
|
|
|
const buttonAccept = view.querySelector('footer button[data-action=accept]');
|
|
|
|
|
const buttonAbandon = view.querySelector('footer button[data-action=abandon]');
|
|
|
|
|
const buttonClaim = view.querySelector('footer button[data-action=claim]');
|
|
|
|
|
|
|
|
|
|
for (const row of rows) {
|
|
|
|
|
row.classList.remove('active');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
view.querySelector('.' + type).style.opacity = 0;
|
|
|
|
|
buttonAbandon.style.display = 'none';
|
|
|
|
|
buttonAccept.style.display = 'none';
|
|
|
|
|
buttonClaim.style.display = 'none';
|
|
|
|
|
}
|
2026-04-04 08:17:56 +02:00
|
|
|
</script>
|
|
|
|
|
{% endblock %}
|