refactoring project website landing page

This commit is contained in:
Marc Froehlich
2026-09-13 10:38:53 +02:00
parent 821535a24f
commit 923d76beb0
12 changed files with 297 additions and 55 deletions
Binary file not shown.
Binary file not shown.
+3
View File
@@ -170,6 +170,9 @@ After implementation use targeted documentation-impact checks. Do not run a comp
The repository contains the KST4Contest website under `website/`, published separately from the desktop application build. The repository contains the KST4Contest website under `website/`, published separately from the desktop application build.
- Historical GHz-Tagung papers are published as German and English PDF assets below `website/src/assets/papers/` and explained on `/background/`. Their editable ODT sources remain outside the public Eleventy input tree and must never be copied into the generated site.
- The home-page news teaser is derived from the date-sorted Eleventy `news` tag collection. It deliberately renders nothing when that collection is empty; it must not be pinned to a release version.
Current website/deployment scripts and update-feed behaviour must be inspected before changes; do not rely on historical assumptions. Current website/deployment scripts and update-feed behaviour must be inspected before changes; do not rely on historical assumptions.
- `APPLICATION_CURRENT_VERSION` is the user-visible semantic version and must use the dotted `major.minor.patch` form. `APPLICATION_CURRENTVERSIONNUMBER` is retained only for older feeds and encodes patch releases by appending the patch digit, for example `1.43.1` as `1.431`. - `APPLICATION_CURRENT_VERSION` is the user-visible semantic version and must use the dotted `major.minor.patch` form. `APPLICATION_CURRENTVERSIONNUMBER` is retained only for older feeds and encodes patch releases by appending the patch digit, for example `1.43.1` as `1.431`.
+14
View File
@@ -73,6 +73,12 @@ function getManualPageOrder(lang, slug) {
return index >= 0 ? index : 999; return index >= 0 ? index : 999;
} }
function selectLatestNews(newsItems) {
return [...(newsItems || [])]
.sort((first, second) => second.date - first.date)
.slice(0, 1);
}
function githubCompatibleSlug(value) { function githubCompatibleSlug(value) {
return (value || "") return (value || "")
.trim() .trim()
@@ -152,6 +158,12 @@ module.exports = function (eleventyConfig) {
}); });
}); });
eleventyConfig.addCollection("latestNews", function (collectionApi) {
return selectLatestNews(
collectionApi.getFilteredByTag("news")
);
});
eleventyConfig.addFilter("whereTag", function(collection, tag) { eleventyConfig.addFilter("whereTag", function(collection, tag) {
return collection.filter(item => item.data.tags && item.data.tags.includes(tag)); return collection.filter(item => item.data.tags && item.data.tags.includes(tag));
}); });
@@ -231,3 +243,5 @@ module.exports = function (eleventyConfig) {
htmlTemplateEngine: "njk" htmlTemplateEngine: "njk"
}; };
}; };
module.exports.selectLatestNews = selectLatestNews;
+3 -1
View File
@@ -25,6 +25,8 @@ description: About KST4Contest and its contest-oriented ON4KST workflow.
<p> <p>
The project is open source and focused on practical contest station workflows. The project is open source and focused on practical contest station workflows.
Features are designed around real operating pressure, not theoretical UI concepts. Features are designed around real operating pressure, not theoretical UI concepts.
The <a href="/background/">background and conference papers</a> explain how
work at DM5M and later analysis of archived ON4KST chat data influenced the client.
</p> </p>
<h2>Community supported</h2> <h2>Community supported</h2>
@@ -42,4 +44,4 @@ description: About KST4Contest and its contest-oriented ON4KST workflow.
</article> </article>
</section> </section>
+98
View File
@@ -0,0 +1,98 @@
---
layout: base.njk
lang: en
title: Background and conference papers
description: The operating background of KST4Contest and the German and English papers presented at GHz-Tagung Dorsten in 2025 and 2026.
---
<section class="hero">
<p class="badge">Project background</p>
<h1>Background and conference papers</h1>
<p class="lead">
KST4Contest grew out of practical contest operation at DM5M. Individual
functions started with concrete problems encountered while using the ON4KST
chat; later development was also influenced by analysis of archived chat data.
</p>
</section>
<section class="section narrow">
<div class="section-heading">
<p class="eyebrow">Two points in the project history</p>
<h2>What the papers document</h2>
<p>
These papers preserve the implementation and knowledge available at the
respective GHz-Tagung. Their historical version references and plans have
deliberately not been rewritten to match the current application.
</p>
</div>
<div class="grid">
<article class="card" id="paper-2025">
<p class="eyebrow">GHz-Tagung Dorsten · 2025</p>
<h3>Softwaregestützte Optimierung der Betriebstechnik von Conteststationen am Beispiel von KST4Contest</h3>
<p><strong>English title:</strong> Software-assisted optimisation of contest station operating practice using KST4Contest</p>
<p>
The paper covers the development of KST4Contest, practical contest
operating technique, the limits of conventional chat clients, Worked
information, Genius frequency and message analysis, AirScout, logger,
rotator and DX Cluster integration, and the operating and UI concept.
</p>
<p>
Marc Fröhlich, “Softwaregestützte Optimierung der Betriebstechnik von
Conteststationen am Beispiel von KST4Contest”, paper for the presentation
at <a href="https://ghztagung.darc.de/">GHz-Tagung Dorsten 2025</a>.
</p>
<p>
The English edition is a translation of the German original, not a
separate publication.
</p>
<div class="actions">
<a class="button" href="/assets/papers/kst4contest-ghz-tagung-2025-de.pdf">Download German PDF</a>
<a class="button secondary" href="/assets/papers/kst4contest-ghz-tagung-2025-en.pdf">Download English PDF</a>
</div>
<p><small>German PDF · 3.4 MB · English PDF · 3.4 MB</small></p>
</article>
<article class="card" id="paper-2026">
<p class="eyebrow">GHz-Tagung Dorsten · 2026</p>
<h3>Chat-Mining: Jagdinstinkte wecken mit KST4Contest</h3>
<p><strong>English title:</strong> Chat mining: awakening hunting instincts with KST4Contest</p>
<p>
The paper examines archived ON4KST chats, presence in both categories,
activity periods, sked requests and reply behaviour, the relationship
between useful information and reply rate, and the functions derived
from those observations.
</p>
<p>
Marc Fröhlich, “Chat-Mining: Jagdinstinkte wecken mit KST4Contest”, paper
for the presentation at <a href="https://ghztagung.darc.de/">GHz-Tagung
Dorsten 2026</a>, including section “Erfolgsrezept”.
</p>
<p>
The English edition is a translation of the German original, not a
separate study.
</p>
<div class="actions">
<a class="button" href="/assets/papers/kst4contest-ghz-tagung-2026-de.pdf">Download German PDF</a>
<a class="button secondary" href="/assets/papers/kst4contest-ghz-tagung-2026-en.pdf">Download English PDF</a>
</div>
<p><small>German PDF · 1.8 MB · English PDF · 1.7 MB</small></p>
</article>
</div>
</section>
<section class="section narrow">
<div class="cta-panel">
<p class="eyebrow">Current software</p>
<h2>The papers explain the background, not today's complete feature set</h2>
<p>
For current behaviour and configuration, use the complete function
overview and the manual. The conference papers remain historical sources.
</p>
<div class="actions">
<a class="button" href="/features/">Open all functions</a>
<a class="button secondary" href="/manual/">Read the current manual</a>
<a class="button ghost" href="/">Return to the home page</a>
</div>
</div>
</section>
+117 -54
View File
@@ -15,15 +15,15 @@ visitorCount: true
<h1>KST4Contest</h1> <h1>KST4Contest</h1>
<p class="lead"> <p class="lead">
ON4KST shows you the traffic. KST4Contest helps turn it into an operating KST4Contest is an ON4KST client for VHF, UHF and SHF contest operation
decision by combining chat, candidate prioritisation, sked planning, on 144 MHz and above. It keeps active stations, frequencies, skeds and
AirScout data and logger integration in one desktop client. aircraft-scatter opportunities in view while connecting chat information
with logger and station interfaces.
</p> </p>
<p> <p>
The available information is filtered, related to active stations and The client helps the operator choose the next useful station and prepare
presented in a contest-oriented workflow. The final decision still the next operating step. It does not automate the radio contact itself.
belongs to the operator.
</p> </p>
<div class="actions"> <div class="actions">
@@ -36,57 +36,71 @@ visitorCount: true
<div class="hero-panel"> <div class="hero-panel">
<div class="terminal-bar"> <div class="terminal-bar">
<span></span><span></span><span></span> <span></span><span></span><span></span>
<strong>Functions sharing the same station context</strong> <strong>Contest information ready for the next decision</strong>
</div> </div>
<div class="mock-grid"> <div class="mock-grid">
{% for feature in collections.sortedFeatures %} <div class="mini-card">
{% if loop.index <= 6 %} <strong>Active stations and QRGs</strong>
<div class="mini-card"> <small>Chat messages, recognised frequencies and known bands stay connected to the station.</small>
<strong>{{ feature.data.icon }} {{ feature.data.title }}</strong> </div>
<small>{{ feature.data.summary }}</small> <div class="mini-card">
</div> <strong>Skeds and AP windows</strong>
{% endif %} <small>Scheduled contacts and AirScout timing remain visible while the contest continues.</small>
{% endfor %} </div>
<div class="mini-card">
<strong>Logger and station status</strong>
<small>Worked information, the current TRX QRG and rotor direction inform the same workflow.</small>
</div>
</div> </div>
</div> </div>
</section> </section>
{% if collections.latestNews.length %}
{% set latestPost = collections.latestNews[0] %}
<section class="section narrow" aria-labelledby="latest-news-heading">
<article class="card">
<p class="eyebrow">Latest news · {{ latestPost.date | readableDate }}</p>
<h2 id="latest-news-heading"><a href="{{ latestPost.url }}">{{ latestPost.data.title }}</a></h2>
<p>{{ latestPost.data.summary }}</p>
<a href="{{ latestPost.url }}">Open this update →</a>
</article>
</section>
{% endif %}
<section class="section"> <section class="section">
<div class="section-heading"> <div class="section-heading">
<p class="eyebrow">The operating problem</p> <p class="eyebrow">Practical tasks</p>
<h2>The useful station is rarely the only station in the chat</h2> <h2>From chat traffic to the next operating step</h2>
<p> <p>
During an active contest, messages, sked requests, frequency information Messages, station activity and station interfaces provide different
and band changes arrive continuously. The task is to identify which part pieces of the same operating situation. KST4Contest brings them together
of that traffic matters now, which candidate should be monitored and where they are needed.
which contact is better scheduled for later.
</p> </p>
</div> </div>
<div class="grid"> <div class="grid">
<article class="card"> <article class="card">
<h3>Observe</h3> <h3>Keep track of chat and active stations</h3>
<p> <p>
Messages, locators, detected frequencies and known band activity are Follow messages across two ON4KST categories, see active bands and keep
assigned to stations across as many as two ON4KST chat categories. recognised frequencies attached to the callsign that sent them.
</p> </p>
</article> </article>
<article class="card"> <article class="card">
<h3>Evaluate</h3> <h3>Select and plan contacts</h3>
<p> <p>
Direction, distance, worked status, NOT-QRV information, chat activity, Use filters and priorities to find candidates, schedule skeds and keep
sked context and aircraft scatter data feed filters and candidate scores. their timing visible in the timeline together with AirScout data.
</p> </p>
</article> </article>
<article class="card"> <article class="card">
<h3>Act</h3> <h3>Connect logger and station components</h3>
<p> <p>
Candidates can be contacted, scheduled or monitored. Skeds remain Synchronise Worked status with supported loggers and pass useful context
visible, while supported loggers and station interfaces receive the to the TRX, rotator and local DX Cluster interfaces.
information required for the next operating step.
</p> </p>
</article> </article>
</div> </div>
@@ -94,25 +108,77 @@ visitorCount: true
<section class="section"> <section class="section">
<div class="section-heading"> <div class="section-heading">
<p class="eyebrow">Functions</p> <p class="eyebrow">What the chat data showed</p>
<h2>One workflow, shared context</h2> <h2>Useful detail makes a reply easier</h2>
<p> <p>
The individual functions are not isolated tools. They use the same Archived ON4KST chat logs from several IARU contests in the preceding two
station, message, band and timing information, so that a change in one years were analysed. The concrete example comes from the July 2025 contest
part of the workflow can also affect filters, priorities and reminders. and combines the 144/432 MHz and Microwave categories. It compares sked
requests with the replies and sked windows they produced.
</p>
<p>
The text analysis considered action and sked words, frequency, timing,
beam direction, aircraft-scatter information, locator, mode and courtesy
or anchor words. Within this sample, requests containing AP and beam
information were associated with an almost 30% higher chat reply rate
than short, unspecific requests such as “Hi noname, try 2m?”.
</p>
<p>
This measures chat replies, not completed QSOs. It is a correlation within
the investigated sample, not general proof of causation and not a promise
that an individual sked will succeed. The practical point is modest:
information that can be used immediately saves the other operator from
having to ask for it first. KST4Contest supplies part of that context when
preparing messages and skeds.
</p>
<p>
Source: Marc Fröhlich, “Chat-Mining: Jagdinstinkte wecken mit KST4Contest”,
conference paper for GHz-Tagung Dorsten 2026, section “Erfolgsrezept”.
<a href="/background/#paper-2026">Read the background and download the paper</a>.
</p>
</div>
</section>
<section class="section">
<div class="section-heading">
<p class="eyebrow">Selected functions</p>
<h2>The parts most often used during a contest</h2>
<p>
These examples show how station, band, message and timing information is
used in practice. The complete function overview remains available for
configuration details and less frequently used tools.
</p> </p>
</div> </div>
<div class="grid"> <div class="grid">
{% for feature in collections.sortedFeatures %} <article class="card feature-card">
<article class="card feature-card"> <h3><a href="/features/global-message-views/">Chat overview and station filters</a></h3>
<div class="feature-icon">{{ feature.data.icon }}</div> <p>Separate global and private views keep the traffic readable, while station filters narrow the candidate list without hiding the underlying chat context.</p>
<p class="eyebrow">{{ feature.data.category }}</p> </article>
<h3><a href="{{ feature.url }}">{{ feature.data.title }}</a></h3> <article class="card feature-card">
<p>{{ feature.data.summary }}</p> <h3><a href="/features/priority-score/">Priority and band opportunities</a></h3>
<a href="{{ feature.url }}">Read how it works →</a> <p>Candidate scores combine available station information with Worked and band state so that a useful next call is easier to find.</p>
</article> </article>
{% endfor %} <article class="card feature-card">
<h3><a href="/features/timeline/">Skeds and AP timeline</a></h3>
<p>Manual skeds, reminders and aircraft-scatter windows remain visible instead of disappearing in the continuing chat stream.</p>
</article>
<article class="card feature-card">
<h3><a href="/features/airscout/">AirScout and path information</a></h3>
<p>AirScout data adds timing and path context to a selected station without turning that assessment into a propagation promise.</p>
</article>
<article class="card feature-card">
<h3><a href="/features/log-sync/">Logger and Worked synchronisation</a></h3>
<p>Supported logger data updates global and per-band Worked information across the active callsign variants.</p>
</article>
<article class="card feature-card">
<h3><a href="/features/trx-qrg-synchronisation/">TRX, rotator and DX Cluster integration</a></h3>
<p>The current QRG, rotor direction and local spots carry selected station information into the next manual operating step.</p>
</article>
</div>
<div class="actions">
<a class="button secondary" href="/features/">Open the complete function overview</a>
</div> </div>
</section> </section>
@@ -121,10 +187,9 @@ visitorCount: true
<p class="eyebrow">Limits</p> <p class="eyebrow">Limits</p>
<h2>A priority score is not a propagation forecast</h2> <h2>A priority score is not a propagation forecast</h2>
<p> <p>
KST4Contest can only evaluate the information it knows. Scores, AP Priorities are not a propagation forecast. Scores, filters and AP windows
windows, filters and path profiles support the operator's decision; they depend on the data available to the client. They help with sorting and
do not guarantee a contact. If the input data is incomplete or outdated, timing, but they do not guarantee a QSO.
the result can be incomplete or outdated as well.
</p> </p>
<div class="actions"> <div class="actions">
@@ -139,11 +204,9 @@ visitorCount: true
<p class="eyebrow">Project status</p> <p class="eyebrow">Project status</p>
<h2>Stable for operation, Nightly for testing</h2> <h2>Stable for operation, Nightly for testing</h2>
<p> <p>
KST4Contest is open-source software. The current stable release is the Stable is the normal choice for contest operation. Nightly contains the
normal choice for contest operation. Nightly builds follow ongoing latest development state and is intended for testing. Changing versions
development and are useful when a particular fix or feature needs immediately before a contest is still not recommended.
testing. A few minutes before a contest is not the ideal time to discover
what changed.
</p> </p>
<div class="actions"> <div class="actions">
+62
View File
@@ -0,0 +1,62 @@
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");
const eleventyConfig = require("../.eleventy.js");
test("selects the newest news article without relying on a version", () => {
const older = { date: new Date("2026-08-22"), data: { title: "Older" } };
const newest = { date: new Date("2026-09-03"), data: { title: "Newest" } };
assert.deepEqual(
eleventyConfig.selectLatestNews([older, newest]),
[newest]
);
});
test("an empty news collection omits the latest-news item safely", () => {
assert.deepEqual(eleventyConfig.selectLatestNews([]), []);
assert.deepEqual(eleventyConfig.selectLatestNews(), []);
});
test("homepage keeps the statistical result tied to its limits", () => {
const source = fs.readFileSync(
path.join(__dirname, "../src/index.njk"),
"utf8"
);
assert.match(source, /almost 30% higher chat reply rate/);
assert.match(source, /chat replies, not completed QSOs/);
assert.match(source, /correlation within[\s\S]*not general proof of causation/);
assert.match(source, /July 2025 contest/);
assert.match(source, /144\/432 MHz and Microwave categories/);
});
test("background page links all public papers and no ODT source", () => {
const source = fs.readFileSync(
path.join(__dirname, "../src/background/index.njk"),
"utf8"
);
for (const name of [
"kst4contest-ghz-tagung-2025-de.pdf",
"kst4contest-ghz-tagung-2025-en.pdf",
"kst4contest-ghz-tagung-2026-de.pdf",
"kst4contest-ghz-tagung-2026-en.pdf"
]) {
assert.match(source, new RegExp(name.replaceAll(".", "\\.")));
}
assert.doesNotMatch(source, /\.odt\b/i);
});
test("homepage contains one selected function grid and a complete-overview link", () => {
const source = fs.readFileSync(
path.join(__dirname, "../src/index.njk"),
"utf8"
);
assert.equal((source.match(/<h2>The parts most often used during a contest<\/h2>/g) || []).length, 1);
assert.match(source, /href="\/features\/"[^>]*>Open the complete function overview/);
assert.doesNotMatch(source, /Observe|Evaluate|>Act<|One workflow, shared context/);
});