40 Commits
Author SHA1 Message Date
Rsclub2_2 360e55633b chore: set AUR package url to kst4contest.hamradioonline.de [skip ci] 2026-07-14 16:43:36 +02:00
Rsclub2_2 367967518e chore: fix kst4contest-git pkgver to 1.42.0 base [skip ci] 2026-07-14 16:41:33 +02:00
github-actions[bot] d8859247c6 chore: update AUR packages to v1.41.1 [skip ci] 2026-07-14 11:35:24 +00:00
github-actions[bot] bf1b72a6b9 chore: rebuild website roadmap [skip ci] 2026-07-13 18:35:32 +00:00
github-actions[bot] 9545e708fd chore: rebuild website roadmap [skip ci] 2026-07-13 18:35:16 +00:00
github-actions[bot] 37121949e4 chore: rebuild website roadmap [skip ci] 2026-07-13 18:22:24 +00:00
github-actions[bot] d70d8d7a24 chore: rebuild website roadmap [skip ci] 2026-07-13 18:20:50 +00:00
github-actions[bot] 6a715567dd chore: rebuild website roadmap [skip ci] 2026-07-13 18:20:37 +00:00
github-actions[bot] a5f96324e6 chore: rebuild website [skip ci] 2026-07-12 22:31:40 +00:00
Marc Froehlich a3e9581c4b modify interactive aircraft scatter hero effect 2026-07-13 00:31:18 +02:00
github-actions[bot] 3285960ee3 chore: rebuild website [skip ci] 2026-07-12 22:27:20 +00:00
Marc Froehlich d532f935d6 modify interactive aircraft scatter hero effect 2026-07-13 00:26:51 +02:00
github-actions[bot] 3988a6d456 chore: rebuild website [skip ci] 2026-07-12 21:52:34 +00:00
Marc Froehlich 598be64306 modify interactive aircraft scatter hero effect 2026-07-12 23:52:11 +02:00
github-actions[bot] 7fcea718ec chore: rebuild website [skip ci] 2026-07-12 21:23:10 +00:00
praktimarc fd7d0a6807 Bugfix/51 selectionmodel fix (#60)
* bugfix for #51. ManagebusManagementThread now never accesses or changes the JAVAFX view-backing ObservableList but using a concurrenthasmap which drives the Tableview backing list with snapshots

* Extending work at #51 for not adding users on UM3 message

* Add interactive aircraft scatter hero effect
2026-07-12 23:22:53 +02:00
github-actions[bot] e424b44cb2 chore: rebuild website [skip ci] 2026-07-11 21:21:26 +00:00
Marc Froehlich dc703fca9e Changes at the website 2026-07-11 23:21:02 +02:00
Marc Froehlich 77c9d34d64 Introduced clustering in the map 2026-07-10 00:30:15 +02:00
github-actions[bot] efd6ed6299 chore: rebuild website roadmap [skip ci] 2026-07-09 21:24:39 +00:00
github-actions[bot] de8e8ba471 chore: rebuild website roadmap [skip ci] 2026-07-09 21:24:26 +00:00
github-actions[bot] eaf611b203 chore: rebuild website roadmap [skip ci] 2026-07-09 16:48:10 +00:00
Rsclub2_2 e339b6fccf implement #49 2026-07-09 18:47:29 +02:00
github-actions[bot] bd9b42d4a5 chore: rebuild website roadmap [skip ci] 2026-07-09 16:46:38 +00:00
github-actions[bot] 275ce48769 chore: rebuild website [skip ci] 2026-07-09 14:12:59 +00:00
Rsclub2_2 d201997587 added automatic Changelog XML to new Website.
also now the XML gets pulled from new Website
2026-07-09 16:12:29 +02:00
github-actions[bot] 250290a1eb chore: rebuild website [skip ci] 2026-07-08 23:14:29 +00:00
Rsclub2_2 052cfea7ad News about Hotfix 2026-07-09 01:14:13 +02:00
github-actions[bot] d930b57196 chore: rebuild website roadmap [skip ci] 2026-07-08 23:03:34 +00:00
github-actions[bot] eb29d56f4e chore: rebuild website roadmap [skip ci] 2026-07-08 23:03:13 +00:00
github-actions[bot] eb2a7f88a4 chore: rebuild website roadmap [skip ci] 2026-07-08 23:02:52 +00:00
github-actions[bot] 8c3e4c15af chore: rebuild website roadmap [skip ci] 2026-07-08 21:55:02 +00:00
github-actions[bot] c8b1f97872 chore: rebuild website roadmap [skip ci] 2026-07-08 21:07:12 +00:00
github-actions[bot] b47fd21dd0 chore: rebuild website roadmap [skip ci] 2026-07-08 21:06:59 +00:00
github-actions[bot] f009ed4b3d chore: rebuild website roadmap [skip ci] 2026-07-08 20:48:02 +00:00
Rsclub2_2 47126f4ae1 Push website rebuild commits via deploy key instead of GITHUB_TOKEN 2026-07-08 22:44:39 +02:00
7d3177ba8a Add selected-band activity indicator for stations in map and details (#53)
* Initial plan

* Add map indicator for selected-band activity hints

* Add selected-band marker to main station table

* Move band-offer star from callsign to worked status cell

* Move band-offer star from wkdany column to per-band worked cells

Fixes a bad merge that had nested a method definition inside another
method (broke compilation), and relocates the new-band-opportunity
star so it appears on the specific band cell it applies to instead of
a single generic wkdany column, since a station can offer several
bands at once.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Changed Indicator to B+ instead of star for better understandability

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Philipp Wagner <philipp@wagnersnetz.de>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 22:32:54 +02:00
Rsclub2_2 ff71623b90 Website: Version Dynamic and Roadmap Padding 2026-07-08 22:03:38 +02:00
Rsclub2_2 ead985a741 Website Build with Roadmap. 2026-07-08 21:35:15 +02:00
Rsclub2_2 ef1eeeed36 Bump version to 1.42 now that v1.41.1 hotfix is tagged and released 2026-07-08 21:27:30 +02:00
87 changed files with 7266 additions and 2007 deletions
+19 -2
View File
@@ -22,7 +22,7 @@ env:
concurrency:
group: website-build-${{ github.ref }}
cancel-in-progress: true
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
@@ -35,6 +35,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
with:
ssh-key: ${{ secrets.WEBSITE_DEPLOY_KEY }}
- name: Set up Node.js
uses: actions/setup-node@v4
@@ -50,10 +52,25 @@ jobs:
- name: Build website
working-directory: website
run: npm run build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload website artifact
uses: actions/upload-artifact@v4.3.4
with:
name: kst4contest-website
path: website/_site/
retention-days: 7
retention-days: 7
- name: Commit rebuilt site
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add website/_site
if git diff --cached --quiet; then
echo "No changes to commit"
else
git commit -m "chore: rebuild website [skip ci]"
git push
fi
@@ -0,0 +1,59 @@
name: Rebuild Roadmap
on:
issues:
types: [opened, edited, closed, reopened, labeled, unlabeled, milestoned, demilestoned]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
concurrency:
group: website-build-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
jobs:
rebuild-roadmap:
name: Rebuild website roadmap
runs-on: ubuntu-latest
# Only worth a rebuild if the "enhancement" label is involved: either the
# issue currently carries it, or this event just added/removed it.
if: contains(github.event.issue.labels.*.name, 'enhancement') || github.event.label.name == 'enhancement'
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
with:
ref: main
ssh-key: ${{ secrets.WEBSITE_DEPLOY_KEY }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
cache: npm
cache-dependency-path: website/package-lock.json
- name: Install dependencies
working-directory: website
run: npm ci
- name: Build website
working-directory: website
run: npm run build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Commit rebuilt site
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add website/_site
if git diff --cached --quiet; then
echo "No changes to commit"
else
git commit -m "chore: rebuild website roadmap [skip ci]"
git push
fi
+16
View File
@@ -0,0 +1,16 @@
pkgbase = kst4contest-bin
pkgdesc = ON4KST Chat Client for VHF/UHF contest operation (pre-built)
pkgver = 1.41.1
pkgrel = 1
url = https://kst4contest.hamradioonline.de
arch = x86_64
license = GPL-3.0-only
depends = gst-plugins-base
depends = gst-plugins-good
provides = kst4contest
conflicts = kst4contest
conflicts = kst4contest-git
source = KST4Contest-v1.41.1-archlinux-x86_64.pkg.tar.zst::https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/KST4Contest-v1.41.1-archlinux-x86_64.pkg.tar.zst
sha256sums = 8e9a53ff832920c9ef2733635b90c5a4ffcd57a2958aaa251e92bd031142c614
pkgname = kst4contest-bin
+17
View File
@@ -0,0 +1,17 @@
# Maintainer: Philipp Wagner <philipp@wagnersnetz.de>
pkgname=kst4contest-bin
pkgver=1.41.1
pkgrel=1
pkgdesc="ON4KST Chat Client for VHF/UHF contest operation (pre-built)"
arch=('x86_64')
url="https://kst4contest.hamradioonline.de"
license=('GPL-3.0-only')
depends=('gst-plugins-base' 'gst-plugins-good')
provides=('kst4contest')
conflicts=('kst4contest' 'kst4contest-git')
source=("KST4Contest-v${pkgver}-archlinux-${CARCH}.pkg.tar.zst::https://github.com/praktimarc/kst4contest/releases/download/v${pkgver}/KST4Contest-v${pkgver}-archlinux-${CARCH}.pkg.tar.zst")
sha256sums=('8e9a53ff832920c9ef2733635b90c5a4ffcd57a2958aaa251e92bd031142c614')
package() {
cp -a "${srcdir}/usr" "${pkgdir}/"
}
+19
View File
@@ -0,0 +1,19 @@
pkgbase = kst4contest-git
pkgdesc = ON4KST Chat Client for VHF/UHF contest operation (git)
pkgver = 1.42.0.r145.gd885924
pkgrel = 1
url = https://kst4contest.hamradioonline.de
arch = x86_64
license = GPL-3.0-only
makedepends = java-environment=21
makedepends = maven
makedepends = git
depends = gst-plugins-base
depends = gst-plugins-good
provides = kst4contest
conflicts = kst4contest
conflicts = kst4contest-bin
source = kst4contest::git+https://github.com/praktimarc/kst4contest.git
sha256sums = SKIP
pkgname = kst4contest-git
+76
View File
@@ -0,0 +1,76 @@
# Maintainer: Philipp Wagner <philipp@wagnersnetz.de>
pkgname=kst4contest-git
pkgver=1.42.0.r145.gd885924
pkgrel=1
pkgdesc="ON4KST Chat Client for VHF/UHF contest operation (git)"
arch=('x86_64')
url="https://kst4contest.hamradioonline.de"
license=('GPL-3.0-only')
depends=('gst-plugins-base' 'gst-plugins-good')
makedepends=('java-environment=21' 'maven' 'git')
provides=('kst4contest')
conflicts=('kst4contest' 'kst4contest-bin')
source=("kst4contest::git+https://github.com/praktimarc/kst4contest.git")
sha256sums=('SKIP')
pkgver() {
cd "${srcdir}/kst4contest"
git describe --long --tags 2>/dev/null \
| sed 's/^v//;s/\([^-]*-g[0-9a-f]*\)/r\1/;s/-/./g' \
|| printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
}
build() {
cd "${srcdir}/kst4contest"
export JAVA_HOME=$(find /usr/lib/jvm -maxdepth 1 -name 'java-21-*' -type d | head -n 1)
export PATH="${JAVA_HOME}/bin:${PATH}"
mvn -B -DskipTests package dependency:copy-dependencies \
-DincludeScope=runtime \
-DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
mkdir -p dist
jpackage \
--type app-image \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
--dest dist
}
package() {
cd "${srcdir}/kst4contest"
install -dm755 "${pkgdir}/usr/lib/KST4Contest"
cp -a dist/KST4Contest/. "${pkgdir}/usr/lib/KST4Contest/"
install -dm755 "${pkgdir}/usr/bin"
printf '#!/bin/sh\nexec /usr/lib/KST4Contest/bin/KST4Contest "$@"\n' \
> "${pkgdir}/usr/bin/KST4Contest"
chmod 755 "${pkgdir}/usr/bin/KST4Contest"
install -dm755 "${pkgdir}/usr/share/applications"
cat > "${pkgdir}/usr/share/applications/KST4Contest.desktop" << 'EOF'
[Desktop Entry]
Type=Application
Name=KST4Contest
Comment=ON4KST Chat Client for VHF/UHF contest operation
Exec=KST4Contest
Icon=KST4Contest
Categories=Network;HamRadio;
Terminal=false
EOF
if [[ -f "${pkgdir}/usr/lib/KST4Contest/lib/KST4Contest.png" ]]; then
install -Dm644 "${pkgdir}/usr/lib/KST4Contest/lib/KST4Contest.png" \
"${pkgdir}/usr/share/icons/hicolor/256x256/apps/KST4Contest.png"
fi
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}
+18
View File
@@ -0,0 +1,18 @@
pkgbase = kst4contest
pkgdesc = ON4KST Chat Client for VHF/UHF contest operation
pkgver = 1.41.1
pkgrel = 1
url = https://kst4contest.hamradioonline.de
arch = x86_64
license = GPL-3.0-only
makedepends = java-environment=21
makedepends = maven
depends = gst-plugins-base
depends = gst-plugins-good
provides = kst4contest
conflicts = kst4contest-bin
conflicts = kst4contest-git
source = kst4contest-1.41.1.tar.gz::https://github.com/praktimarc/kst4contest/archive/refs/tags/v1.41.1.tar.gz
sha256sums = e96207a2d3fee19d35e34717f5312beb28bb087c040164e352337e749ce53b8d
pkgname = kst4contest
+69
View File
@@ -0,0 +1,69 @@
# Maintainer: Philipp Wagner <philipp@wagnersnetz.de>
pkgname=kst4contest
pkgver=1.41.1
pkgrel=1
pkgdesc="ON4KST Chat Client for VHF/UHF contest operation"
arch=('x86_64')
url="https://kst4contest.hamradioonline.de"
license=('GPL-3.0-only')
depends=('gst-plugins-base' 'gst-plugins-good')
makedepends=('java-environment=21' 'maven')
provides=('kst4contest')
conflicts=('kst4contest-bin' 'kst4contest-git')
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/praktimarc/kst4contest/archive/refs/tags/v${pkgver}.tar.gz")
sha256sums=('e96207a2d3fee19d35e34717f5312beb28bb087c040164e352337e749ce53b8d')
build() {
cd "${srcdir}/kst4contest-${pkgver}"
export JAVA_HOME=$(find /usr/lib/jvm -maxdepth 1 -name 'java-21-*' -type d | head -n 1)
export PATH="${JAVA_HOME}/bin:${PATH}"
mvn -B -DskipTests package dependency:copy-dependencies \
-DincludeScope=runtime \
-DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
mkdir -p dist
jpackage \
--type app-image \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql,java.net.http,jdk.crypto.ec \
--dest dist
}
package() {
cd "${srcdir}/kst4contest-${pkgver}"
install -dm755 "${pkgdir}/usr/lib/KST4Contest"
cp -a dist/KST4Contest/. "${pkgdir}/usr/lib/KST4Contest/"
install -dm755 "${pkgdir}/usr/bin"
printf '#!/bin/sh\nexec /usr/lib/KST4Contest/bin/KST4Contest "$@"\n' \
> "${pkgdir}/usr/bin/KST4Contest"
chmod 755 "${pkgdir}/usr/bin/KST4Contest"
install -dm755 "${pkgdir}/usr/share/applications"
cat > "${pkgdir}/usr/share/applications/KST4Contest.desktop" << 'EOF'
[Desktop Entry]
Type=Application
Name=KST4Contest
Comment=ON4KST Chat Client for VHF/UHF contest operation
Exec=KST4Contest
Icon=KST4Contest
Categories=Network;HamRadio;
Terminal=false
EOF
if [[ -f "${pkgdir}/usr/lib/KST4Contest/lib/KST4Contest.png" ]]; then
install -Dm644 "${pkgdir}/usr/lib/KST4Contest/lib/KST4Contest.png" \
"${pkgdir}/usr/share/icons/hicolor/256x256/apps/KST4Contest.png"
fi
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>de.x08</groupId>
<artifactId>praktiKST</artifactId>
<version>1.41.1-nightly</version>
<version>1.42.0-nightly</version>
<name>praktiKST</name>
@@ -20,9 +20,9 @@ public class ApplicationConstants {
/**
* Name of file to store preferences in.
*/
public static final double APPLICATION_CURRENTVERSIONNUMBER = 1.411;
public static final double APPLICATION_CURRENTVERSIONNUMBER = 1.42;
public static final String VERSIONINFOURLFORUPDATES_KST4CONTEST = "https://do5amf.funkerportal.de/kst4ContestVersionInfo.xml";
public static final String VERSIONINFOURLFORUPDATES_KST4CONTEST = "https://kst4contest.hamradioonline.de/kst4ContestVersionInfo.xml";
public static final String VERSIONINFDOWNLOADEDLOCALFILE = "kst4ContestVersionInfo.xml";
public static final String STYLECSSFILE_DEFAULT_DAYLIGHT = "KST4ContestDefaultDay.css";
@@ -24,6 +24,7 @@ import kst4contest.logic.PriorityCalculator;
import kst4contest.model.*;
import kst4contest.test.MockKstServer;
import kst4contest.utils.PlayAudioUtils;
import kst4contest.locatorUtils.Location;
import kst4contest.view.Kst4ContestApplication;
import java.io.*;
@@ -34,6 +35,7 @@ import java.util.function.Predicate;
/**
*
* Central Chat kst4contest.controller. Instantiate only one time per category of kst Chat.
@@ -318,33 +320,29 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
EnumSet<Band> offered = EnumSet.noneOf(Band.class);
synchronized (getLst_chatMemberList()) {
for (ChatMember cm : getLst_chatMemberList()) {
if (cm == null || cm.getCallSignRaw() == null) continue;
if (!normalizeCallRaw(cm.getCallSignRaw()).equals(callRaw)) continue;
for (ChatMember cm : findActiveChatMembersByRawCall(callRaw)) {
if (cm == null || cm.getCallSignRaw() == null) continue;
Map<Band, ChatMember.ActiveFrequencyInfo> map = cm.getKnownActiveBands();
if (map == null || map.isEmpty()) continue;
Map<Band, ChatMember.ActiveFrequencyInfo> map = cm.getKnownActiveBands();
if (map == null || map.isEmpty()) continue;
for (Map.Entry<Band, ChatMember.ActiveFrequencyInfo> e : map.entrySet()) {
if (e.getKey() == null || e.getValue() == null) continue;
long age = nowMs - e.getValue().timestampEpoch;
if (age >= 0 && age <= maxAgeMs) {
offered.add(e.getKey());
}
if (DEBUG_BAND_UPGRADE_HINT) {
System.out.println("[BandUpgradeHint] history call=" + callRaw
+ " band=" + e.getKey()
+ " freq=" + e.getValue().frequency
+ " ageMs=" + age);
}
for (Map.Entry<Band, ChatMember.ActiveFrequencyInfo> e : map.entrySet()) {
if (e.getKey() == null || e.getValue() == null) continue;
long age = nowMs - e.getValue().timestampEpoch;
if (age >= 0 && age <= maxAgeMs) {
offered.add(e.getKey());
}
if (DEBUG_BAND_UPGRADE_HINT) {
System.out.println("[BandUpgradeHint] history call=" + callRaw
+ " band=" + e.getKey()
+ " freq=" + e.getValue().frequency
+ " ageMs=" + age);
}
}
}
return offered;
}
@@ -356,21 +354,19 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
EnumSet<Band> worked = EnumSet.noneOf(Band.class);
synchronized (getLst_chatMemberList()) {
for (ChatMember cm : getLst_chatMemberList()) {
if (cm == null || cm.getCallSignRaw() == null) continue;
if (!normalizeCallRaw(cm.getCallSignRaw()).equals(callRaw)) continue;
for (ChatMember cm : findActiveChatMembersByRawCall(callRaw)) {
if (cm == null || cm.getCallSignRaw() == null) continue;
if (cm.isWorked144()) worked.add(Band.B_144);
if (cm.isWorked432()) worked.add(Band.B_432);
if (cm.isWorked1240()) worked.add(Band.B_1296);
if (cm.isWorked2300()) worked.add(Band.B_2320);
if (cm.isWorked3400()) worked.add(Band.B_3400);
if (cm.isWorked5600()) worked.add(Band.B_5760);
if (cm.isWorked10G()) worked.add(Band.B_10G);
if (cm.isWorked24G()) worked.add(Band.B_24G); // optional, only if your Band enum supports it
}
if (cm.isWorked144()) worked.add(Band.B_144);
if (cm.isWorked432()) worked.add(Band.B_432);
if (cm.isWorked1240()) worked.add(Band.B_1296);
if (cm.isWorked2300()) worked.add(Band.B_2320);
if (cm.isWorked3400()) worked.add(Band.B_3400);
if (cm.isWorked5600()) worked.add(Band.B_5760);
if (cm.isWorked10G()) worked.add(Band.B_10G);
if (cm.isWorked24G()) worked.add(Band.B_24G);
}
return worked;
}
@@ -645,7 +641,8 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
// rotatorClient.
this.lst_chatMemberList.clear();;
// this.lst_chatMemberList.clear();;
this.clearActiveChatMembers();
this.lst_clusterMemberList.clear();
this.setDisconnected(true);
@@ -713,7 +710,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
}
} else if (action.equals(ApplicationConstants.DISCSTRING_DISCONNECTONLY)){
this.lst_chatMemberList.clear();;
this.clearActiveChatMembers();
this.lst_clusterMemberList.clear();
@@ -959,16 +956,9 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) {
return null;
}
String normalizedTargetCall = normalizeCallRaw(targetCallsignRaw);
synchronized (getLst_chatMemberList()) {
for (ChatMember member : getLst_chatMemberList()) {
if (member == null || member.getCallSignRaw() == null) continue;
if (normalizeCallRaw(member.getCallSignRaw()).equals(normalizedTargetCall)) {
return member;
}
}
}
return null;
List<ChatMember> matchingMembers = findActiveChatMembersByRawCall(targetCallsignRaw);
return matchingMembers.isEmpty() ? null : matchingMembers.get(0);
}
private String resolveSkedTargetLocator(String targetCallsignRaw) {
@@ -976,22 +966,43 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
return null;
}
String normalizedTargetCall = normalizeCallRaw(targetCallsignRaw);
synchronized (getLst_chatMemberList()) {
for (ChatMember member : getLst_chatMemberList()) {
if (member == null || member.getCallSignRaw() == null) continue;
if (!normalizeCallRaw(member.getCallSignRaw()).equals(normalizedTargetCall)) continue;
String locator = member.getQra();
if (locator != null && !locator.isBlank()) {
return locator.trim().toUpperCase(Locale.ROOT);
}
for (ChatMember member : findActiveChatMembersByRawCall(targetCallsignRaw)) {
String locator = member.getQra();
if (locator != null && !locator.isBlank()) {
return locator.trim().toUpperCase(Locale.ROOT);
}
}
return null;
}
// private ChatMember resolveSkedTargetMember(String targetCallsignRaw) {
// if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) {
// return null;
// }
//
// List<ChatMember> matchingMembers = findActiveChatMembersByRawCall(targetCallsignRaw);
// return matchingMembers.isEmpty() ? null : matchingMembers.get(0);
//
// }
//
// private String resolveSkedTargetLocator(String targetCallsignRaw) {
// if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) {
// return null;
// }
//
// String normalizedTargetCall = normalizeCallRaw(targetCallsignRaw);
//
// for (ChatMember member : findActiveChatMembersByRawCall(targetCallsignRaw)) {
// String locator = member.getQra();
// if (locator != null && !locator.isBlank()) {
// return locator.trim().toUpperCase(Locale.ROOT);
// }
// }
//
// return null;
// }
public StationMetricsService getStationMetricsService() {
return stationMetricsService;
}
@@ -1014,10 +1025,14 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
return new HashMap<>(lastInboundCategoryByCallSignRaw);
}
// public List<ChatMember> snapshotChatMembers() {
// synchronized (getLst_chatMemberList()) {
// return new ArrayList<>(getLst_chatMemberList());
// }
// }
public List<ChatMember> snapshotChatMembers() {
synchronized (getLst_chatMemberList()) {
return new ArrayList<>(getLst_chatMemberList());
}
return new ArrayList<>(activeChatMembersByCallAndCategory.values());
}
public List<ContestSked> snapshotActiveSkeds() {
@@ -1126,13 +1141,27 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
private ObservableList<ChatMember> chatMemberList = FXCollections.observableArrayList(); // List of active stations
private ObservableList<ChatMember> lst_chatMemberList = FXCollections.synchronizedObservableList(chatMemberList); // List
// of active stn in chat
private FilteredList<ChatMember> lst_chatMemberListFiltered = new FilteredList<ChatMember>(chatMemberList);
private SortedList<ChatMember> lst_chatMemberSortedFilteredList = new SortedList<ChatMember>(lst_chatMemberListFiltered);
private ObservableList<ChatMember> lst_chatMemberList = FXCollections.synchronizedObservableList(chatMemberList); // List
private ObservableList<Predicate<ChatMember>> lst_chatMemberListFilterPredicates = FXCollections.observableArrayList();
private ObservableList<ClusterMessage> lst_clusterMemberList = FXCollections.observableArrayList();
/*
* Thread-safe active-member model.
*
* MessageBusManagementThread must not use the JavaFX TableView backing list as
* its primary data model. Otherwise every add/remove fires FilteredList,
* SortedList and TableView selection listeners on the MessageBus thread and JavaFX
* can throw "Not on FX application thread" or internal listener NPEs.
*
* This map is the worker-thread safe source of truth. The ObservableList below
* remains a UI mirror and is mutated only on the JavaFX application thread.
*/
private final java.util.concurrent.ConcurrentMap<String, ChatMember> activeChatMembersByCallAndCategory =
new java.util.concurrent.ConcurrentHashMap<>();
/*
* Message table update buffers.
@@ -1161,6 +1190,254 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
// ******************************************************************************************************************************************
/**
* Executes UI-bound work on the JavaFX application thread.
*
* Worker threads may call this safely. If the caller is already on the FX
* thread, the action is executed immediately to preserve ordering.
*/
private void runOnFxThread(Runnable action) {
if (action == null) {
return;
}
if (Platform.isFxApplicationThread()) {
action.run();
} else {
Platform.runLater(action);
}
}
/**
* Builds the active-member key. The raw/base callsign alone is not enough
* because the same station can be logged into multiple ON4KST categories at
* the same time. Therefore the category number is part of the key.
*/
private String buildActiveChatMemberKey(String callSign, ChatCategory category) {
String normalizedCallsign = ChatMember.normalizeCallSignToBaseCallSign(callSign);
if (normalizedCallsign == null || normalizedCallsign.isBlank()) {
return null;
}
int categoryNumber = category == null ? -1 : category.getCategoryNumber();
return normalizedCallsign.trim().toUpperCase(Locale.ROOT) + "|" + categoryNumber;
}
private String buildActiveChatMemberKey(ChatMember member) {
if (member == null) {
return null;
}
String callSign = member.getCallSignRaw() != null ? member.getCallSignRaw() : member.getCallSign();
return buildActiveChatMemberKey(callSign, member.getChatCategory());
}
/**
* Adds or replaces an active chat member in the worker-thread model and mirrors
* that change to the JavaFX list. This is the only supported path for ON4KST
* user-enter events.
*/
public void addOrUpdateActiveChatMember(ChatMember member) {
String key = buildActiveChatMemberKey(member);
if (key == null) {
return;
}
activeChatMembersByCallAndCategory.put(key, member);
runOnFxThread(() -> {
int existingIndex = findChatMemberIndexInUiListByKey(key);
if (existingIndex >= 0) {
lst_chatMemberList.set(existingIndex, member);
} else {
lst_chatMemberList.add(member);
}
});
}
/**
* Removes an active chat member from the worker-thread model and from the UI
* mirror. Removal from the ObservableList is always performed on the FX thread
* because the list is bound to FilteredList/SortedList/TableView.
*/
public boolean removeActiveChatMember(ChatMember member) {
String key = buildActiveChatMemberKey(member);
if (key == null) {
return false;
}
ChatMember removedMember = activeChatMembersByCallAndCategory.remove(key);
runOnFxThread(() -> lst_chatMemberList.removeIf(currentMember -> key.equals(buildActiveChatMemberKey(currentMember))));
return removedMember != null;
}
/**
* Clears the active-member model and the JavaFX UI mirror. Use this instead of
* getLst_chatMemberList().clear() from timers or worker threads.
*/
public void clearActiveChatMembers() {
activeChatMembersByCallAndCategory.clear();
runOnFxThread(() -> lst_chatMemberList.clear());
}
/**
* Resolves a member from the thread-safe active model. This avoids reading the
* TableView backing list from MessageBusManagementThread.
*/
public ChatMember findActiveChatMember(ChatMember lookForThis) {
String key = buildActiveChatMemberKey(lookForThis);
return key == null ? null : activeChatMembersByCallAndCategory.get(key);
}
public ChatMember findActiveChatMember(String callSign, ChatCategory category) {
String key = buildActiveChatMemberKey(callSign, category);
return key == null ? null : activeChatMembersByCallAndCategory.get(key);
}
public int getActiveChatMemberCount() {
return activeChatMembersByCallAndCategory.size();
}
/**
* Returns all active category variants of a callsign. This is used when a QRG or
* band hint should be propagated from one category instance to the same station
* in another category.
*/
public List<ChatMember> findActiveChatMembersByRawCall(String callSignRaw) {
String normalizedCallsign = ChatMember.normalizeCallSignToBaseCallSign(callSignRaw);
if (normalizedCallsign == null || normalizedCallsign.isBlank()) {
return Collections.emptyList();
}
String normalizedKeyPart = normalizedCallsign.trim().toUpperCase(Locale.ROOT);
List<ChatMember> matchingMembers = new ArrayList<>();
for (ChatMember member : activeChatMembersByCallAndCategory.values()) {
if (member == null) {
continue;
}
String memberCall = member.getCallSignRaw() != null ? member.getCallSignRaw() : member.getCallSign();
String normalizedMemberCall = ChatMember.normalizeCallSignToBaseCallSign(memberCall);
if (normalizedMemberCall != null && normalizedMemberCall.equalsIgnoreCase(normalizedKeyPart)) {
matchingMembers.add(member);
}
}
return matchingMembers;
}
/**
* Updates locator and derived direction values in the active model. The UI list
* contains the same member object, but the user-list refresh is still triggered
* on the JavaFX thread so TableView/map derived displays can repaint safely.
*/
public boolean updateActiveChatMemberLocator(ChatMember lookupMember, String newLocator) {
ChatMember activeMember = findActiveChatMember(lookupMember);
if (activeMember == null) {
return false;
}
activeMember.setQra(newLocator);
activeMember.setQrb(new Location().getDistanceKmByTwoLocatorStrings(chatPreferences.getStn_loginLocatorMainCat(), newLocator));
activeMember.setQTFdirection(new Location(chatPreferences.getStn_loginLocatorMainCat()).getBearing(new Location(newLocator)));
fireUserListUpdate("Locator changed");
return true;
}
public boolean updateActiveChatMemberState(ChatMember lookupMember, int newState) {
ChatMember activeMember = findActiveChatMember(lookupMember);
if (activeMember == null) {
return false;
}
activeMember.setState(newState);
fireUserListUpdate("User state changed");
return true;
}
/**
* Applies a UM3-style user-info update only to an already active chat member.
*
* ON4KST may send UM3 messages for stations that are not logged into the
* current channel, for example when a user changes locator/profile data on the
* website. Such stations must not be added to the visible chat member list,
* because they cannot be addressed in the channel. Their full information will
* be delivered again with UA0/UA5/UA2 when they actually join.
*
* @return true if an active member was found and updated, false if the UM3
* information was intentionally ignored.
*/
public boolean updateActiveChatMemberInfoIfPresent(ChatMember updatedMember) {
String key = buildActiveChatMemberKey(updatedMember);
if (key == null) {
return false;
}
ChatMember activeMember = activeChatMembersByCallAndCategory.get(key);
if (activeMember == null) {
return false;
}
activeMember.setName(updatedMember.getName());
activeMember.setQra(updatedMember.getQra());
activeMember.setState(updatedMember.getState());
activeMember.setLastActivity(updatedMember.getLastActivity());
activeMember.setActivityTimeLastInEpoch(updatedMember.getActivityTimeLastInEpoch());
activeMember.setQrb(updatedMember.getQrb());
activeMember.setQTFdirection(updatedMember.getQTFdirection());
fireUserListUpdate("User info updated");
return true;
}
/**
* Backward-compatible wrapper for older call sites. Despite the historic name,
* this method no longer adds unknown UM3 users to the active member model.
*/
public boolean updateOrAddActiveChatMemberInfo(ChatMember updatedMember) {
return updateActiveChatMemberInfoIfPresent(updatedMember);
}
/**
* Stores a newly detected QRG/band on all active category variants of a station.
* The old StringProperty is still set for compatibility with existing TableView
* columns and DXCluster code.
*/
public void applyDetectedFrequencyToActiveMembers(ChatMember sender, Band detectedBand, double detectedFrequencyMhz) {
if (sender == null || detectedBand == null || detectedFrequencyMhz <= 0) {
return;
}
String displayFrequency = String.valueOf(detectedFrequencyMhz);
String rawCall = sender.getCallSignRaw() != null ? sender.getCallSignRaw() : sender.getCallSign();
for (ChatMember member : findActiveChatMembersByRawCall(rawCall)) {
if (member == null) {
continue;
}
member.addKnownFrequency(detectedBand, detectedFrequencyMhz);
member.setFrequency(new SimpleStringProperty(displayFrequency));
}
// Also cover dummy senders that are not in the active model, e.g. [n/a] senders.
sender.addKnownFrequency(detectedBand, detectedFrequencyMhz);
sender.setFrequency(new SimpleStringProperty(displayFrequency));
fireUserListUpdate("Frequency detected");
}
private int findChatMemberIndexInUiListByKey(String key) {
if (key == null) {
return -1;
}
for (int i = 0; i < lst_chatMemberList.size(); i++) {
if (key.equals(buildActiveChatMemberKey(lst_chatMemberList.get(i)))) {
return i;
}
}
return -1;
}
@@ -2011,7 +2288,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
socket.close();
chatController.setConnectedAndLoggedIn(false);
chatController.getLst_chatMemberList().clear();
chatController.clearActiveChatMembers();
System.out.println("[Chatcontroller, Warning: ] Socket closed or disconnected");
File diff suppressed because it is too large Load Diff
@@ -64,6 +64,8 @@ import javafx.scene.shape.Polygon;
import javafx.stage.Screen;
import kst4contest.utils.ApplicationFileUtils;
import kst4contest.view.map.MapCallsignRawSnapshot;
import kst4contest.view.map.MapCallsignRawSnapshotBuilder;
import kst4contest.view.map.StationMapBridge;
import kst4contest.view.map.StationMapView;
import kst4contest.view.map.OfflineDemImportService;
@@ -116,6 +118,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
private final Tooltip tipSkedWarnIndicator = new Tooltip();
private Timeline skedWarnBlinkTimeline;
private final MapCallsignRawSnapshotBuilder mainViewBandMarkerSnapshotBuilder = new MapCallsignRawSnapshotBuilder();
// Timeline: show at most N priority markers per minute bucket (minute 0/1 often has many planes)
private static final int TIMELINE_PRIORITY_MARKERS_PER_MINUTE = 2;
@@ -414,6 +418,55 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
return sb.toString();
}
/**
* Checks whether a station has known activity on the given band and that band is
* one of my own currently enabled bands, i.e. a new-band opportunity worth flagging
* with a star in that band's table cell.
*
* @param chatMember station row
* @param band band to check
* @return true if the band cell should show a star
*/
private boolean isBandOfferForMainView(ChatMember chatMember, Band band) {
if (chatMember == null
|| band == null
|| chatcontroller == null
|| chatcontroller.getReachabilityService() == null
|| chatMember.getCallSignRaw() == null
|| chatMember.getCallSignRaw().isBlank()) {
return false;
}
EnumSet<Band> enabledBands = chatcontroller.getReachabilityService().getEnabledStationBands();
if (enabledBands == null || !enabledBands.contains(band)) {
return false;
}
List<ChatMember> variants = new ArrayList<>();
synchronized (chatcontroller.getLst_chatMemberList()) {
for (ChatMember variant : chatcontroller.getLst_chatMemberList()) {
if (variant == null || variant.getCallSignRaw() == null) {
continue;
}
if (variant.getCallSignRaw().equalsIgnoreCase(chatMember.getCallSignRaw())) {
variants.add(variant);
}
}
}
if (variants.isEmpty()) {
variants = List.of(chatMember);
}
List<MapCallsignRawSnapshot> snapshots = mainViewBandMarkerSnapshotBuilder.buildSnapshots(
variants,
null,
EnumSet.of(band)
);
return snapshots.stream().anyMatch(MapCallsignRawSnapshot::offersSelectedBand);
}
private String bandToHumanLabel(kst4contest.model.Band b) {
// Human-friendly labels for VHF/UHF/microwave contesting
return switch (b) {
@@ -1204,14 +1257,18 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
public ObservableValue<String> call(CellDataFeatures<ChatMember, String> cellDataFeatures) {
SimpleStringProperty callsgn = new SimpleStringProperty();
if (cellDataFeatures.getValue().getState() == 1) {
callsgn.setValue("(" + cellDataFeatures.getValue().getCallSign() + ")"); //away user
} else {
ChatMember member = cellDataFeatures.getValue();
String baseCallsign;
callsgn.setValue(cellDataFeatures.getValue().getCallSign());
if (member.getState() == 1) {
baseCallsign = "(" + member.getCallSign() + ")"; //away user
} else {
baseCallsign = member.getCallSign();
}
// System.out.println(cellDataFeatures.getValue().getCallSign() + " / " + cellDataFeatures.getValue().getState()+ " <<<<<<<<<<<<<<<<<< state ");
callsgn.setValue(baseCallsign);
// System.out.println(member.getCallSign() + " / " + member.getState()+ " <<<<<<<<<<<<<<<<<< state ");
return callsgn;
}
@@ -1656,30 +1713,6 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
}
});
/**
* Shows the compact worked/grid status and explains it by tooltip.
*/
wkdAny_subcol.setCellFactory(column -> new TableCell<ChatMember, String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setTooltip(null);
setStyle("");
return;
}
ChatMember member = getTableRow() == null ? null : getTableRow().getItem();
setText(item);
setTooltip(new Tooltip(buildWorkedAnyGridStatusTooltip(member)));
setAlignment(Pos.CENTER);
setStyle("-fx-font-weight: bold;");
}
});
TableColumn<ChatMember, String> vhfCol_subcol = new TableColumn<ChatMember, String>("144");
vhfCol_subcol
@@ -1691,6 +1724,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
if (cellDataFeatures.getValue().isWorked144()) {
wkd.setValue("X");
} else if (isBandOfferForMainView(cellDataFeatures.getValue(), Band.B_144)) {
wkd.setValue("B+");
} else {
wkd.setValue("");
}
@@ -1710,6 +1745,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
if (cellDataFeatures.getValue().isWorked432()) {
wkd.setValue("X");
} else if (isBandOfferForMainView(cellDataFeatures.getValue(), Band.B_432)) {
wkd.setValue("B+");
} else {
wkd.setValue("");
}
@@ -1729,6 +1766,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
if (cellDataFeatures.getValue().isWorked1240()) {
wkd.setValue("X");
} else if (isBandOfferForMainView(cellDataFeatures.getValue(), Band.B_1296)) {
wkd.setValue("B+");
} else {
wkd.setValue("");
}
@@ -1747,6 +1786,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
if (cellDataFeatures.getValue().isWorked2300()) {
wkd.setValue("X");
} else if (isBandOfferForMainView(cellDataFeatures.getValue(), Band.B_2320)) {
wkd.setValue("B+");
} else {
wkd.setValue("");
}
@@ -1765,6 +1806,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
if (cellDataFeatures.getValue().isWorked3400()) {
wkd.setValue("X");
} else if (isBandOfferForMainView(cellDataFeatures.getValue(), Band.B_3400)) {
wkd.setValue("B+");
} else {
wkd.setValue("");
}
@@ -1783,6 +1826,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
if (cellDataFeatures.getValue().isWorked5600()) {
wkd.setValue("X");
} else if (isBandOfferForMainView(cellDataFeatures.getValue(), Band.B_5760)) {
wkd.setValue("B+");
} else {
wkd.setValue("");
}
@@ -1801,6 +1846,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
if (cellDataFeatures.getValue().isWorked10G()) {
wkd.setValue("X");
} else if (isBandOfferForMainView(cellDataFeatures.getValue(), Band.B_10G)) {
wkd.setValue("B+");
} else {
wkd.setValue("");
}
@@ -4969,7 +5016,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
help2.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
getHostServices().showDocument("https://www.paypal.com/paypalme/do5amf");
getHostServices().showDocument("https://ko-fi.com/praktimarc");
}
});
@@ -5923,9 +5970,19 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
txt_myQTF.getStyleClass().add("text-input");
txt_myQTF.getStyleClass().add("text-input-MYQRG1");
txt_myQTF.textProperty().bind(Bindings.createStringBinding(
() -> Double.toString(chatcontroller.getChatPreferences().getActualQTF().get()),
chatcontroller.getChatPreferences().getActualQTF()));
// Rotator sync on/off determines whether this field is bound (read-only, driven
// by PSTRotator) or free for manual entry, mirroring the MYQRG sync behaviour above.
if (chatcontroller.getChatPreferences().isStn_pstRotatorEnabled()) {
txt_myQTF.textProperty().bind(Bindings.createStringBinding(
() -> Double.toString(chatcontroller.getChatPreferences().getActualQTF().get()),
chatcontroller.getChatPreferences().getActualQTF()));
txt_myQTF.setTooltip(new Tooltip("This is your current QTF, read out at PSTRotator"));
txt_myQTF.setFocusTraversable(false);
} else {
txt_myQTF.setText(Double.toString(chatcontroller.getChatPreferences().getActualQTF().get()));
txt_myQTF.setTooltip(new Tooltip("Enter your antenna heading (QTF) by hand - no rotator sync active"));
txt_myQTF.setFocusTraversable(true);
}
txt_myQTF.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
@@ -5935,13 +5992,16 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
// System.out.println("Textfield on focus");
// Do nothing until field loses focus, user will enter his frequency
} else {
if (txt_myQTF.textProperty().isBound()) {
return; // rotator sync active, field is driven by PSTRotator
}
try {
System.out.println(
"[Main.java, Info]: Set the MYQTF property by hand to: " + txt_myQTF.getText());
chatcontroller.getChatPreferences().getActualQTF().set(Integer.parseInt(txt_myQTF.getText()));}
chatcontroller.getChatPreferences().getActualQTF().set(Double.parseDouble(txt_myQTF.getText()));}
catch (Exception exception) {
System.out.println("bullshit entered in myqtf");
txt_myQTF.setText("0");
txt_myQTF.setText("0.0");
}
}
}
@@ -5950,8 +6010,6 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
txt_myQTF.setPrefSize(40, 0);
// txt_ownqrg.setMinSize(40, 0);
txt_myQTF.setAlignment(Pos.BASELINE_RIGHT);
txt_myQTF.setTooltip(new Tooltip("This is your current QTF, read out at PSTRotator"));
txt_myQTF.setFocusTraversable(false);
SplitPane mainWindowLeftSplitPane = new SplitPane();
mainWindowLeftSplitPane.setOrientation(Orientation.HORIZONTAL);
@@ -8590,9 +8648,21 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
chkBx_station_pstRotatorEnabled.setTooltip(new Tooltip(
"If disabled: no PSTRotator connection is started and antenna direction is ignored in priority scoring."
));
chkBx_station_pstRotatorEnabled.selectedProperty().addListener((obs, oldV, newV) ->
chatcontroller.getChatPreferences().setStn_pstRotatorEnabled(newV)
);
chkBx_station_pstRotatorEnabled.selectedProperty().addListener((obs, oldV, newV) -> {
chatcontroller.getChatPreferences().setStn_pstRotatorEnabled(newV);
if (newV) {
txt_myQTF.textProperty().bind(Bindings.createStringBinding(
() -> Double.toString(chatcontroller.getChatPreferences().getActualQTF().get()),
chatcontroller.getChatPreferences().getActualQTF()));
txt_myQTF.setTooltip(new Tooltip("This is your current QTF, read out at PSTRotator"));
txt_myQTF.setFocusTraversable(false);
} else {
txt_myQTF.textProperty().unbind();
txt_myQTF.setText(Double.toString(chatcontroller.getChatPreferences().getActualQTF().get()));
txt_myQTF.setTooltip(new Tooltip("Enter your antenna heading (QTF) by hand - no rotator sync active"));
txt_myQTF.setFocusTraversable(true);
}
});
grdPnlStation.add(lblCallSign, 0, 0);
@@ -20,6 +20,7 @@ public record MapCallsignRawSnapshot(
double longitudeDeg,
String bandSummary,
Map<String, String> lastKnownFrequenciesByBand,
boolean offersSelectedBand,
boolean warningToMyDirection,
boolean worked,
boolean selected,
@@ -59,9 +60,10 @@ public record MapCallsignRawSnapshot(
}
public String markerLabel() {
return bandSummary.isBlank()
String baseLabel = bandSummary.isBlank()
? displayCallSign
: displayCallSign + " (" + bandSummary + ")";
return offersSelectedBand ? baseLabel + " B+" : baseLabel;
}
public String detailFrequencyText() {
@@ -8,11 +8,13 @@ import kst4contest.model.ChatMember;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.EnumMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Builds immutable map snapshots from the currently visible chat members.
@@ -22,8 +24,11 @@ import java.util.Map;
*/
public final class MapCallsignRawSnapshotBuilder {
private static final Pattern TOKEN_SPLIT_PATTERN = Pattern.compile("[^A-Z0-9]+");
public List<MapCallsignRawSnapshot> buildSnapshots(Collection<ChatMember> visibleChatMembers,
ChatMember selectedChatMember) {
ChatMember selectedChatMember,
EnumSet<Band> selectedBands) {
if (visibleChatMembers == null || visibleChatMembers.isEmpty()) {
return List.of();
@@ -69,7 +74,9 @@ public final class MapCallsignRawSnapshotBuilder {
Location location = new Location(locator6);
LinkedHashMap<String, String> frequenciesByBand = collectLastKnownFrequenciesByBand(variants);
String bandSummary = String.join(", ", frequenciesByBand.keySet());
EnumSet<Band> sureBands = collectSureBands(variants, frequenciesByBand);
String bandSummary = buildBandSummary(sureBands);
boolean offersSelectedBand = hasAnySelectedBand(sureBands, selectedBands);
boolean warningToMyDirection = variants.stream().anyMatch(ChatMember::isInAngleAndRange);
boolean worked = variants.stream().anyMatch(this::isWorkedAtAnyBand);
@@ -92,6 +99,7 @@ public final class MapCallsignRawSnapshotBuilder {
location.getLongitude().toDegrees(),
bandSummary,
frequenciesByBand,
offersSelectedBand,
warningToMyDirection,
worked,
selected,
@@ -182,6 +190,101 @@ public final class MapCallsignRawSnapshotBuilder {
return ordered;
}
private EnumSet<Band> collectSureBands(List<ChatMember> variants,
LinkedHashMap<String, String> frequenciesByBand) {
EnumSet<Band> sureBands = EnumSet.noneOf(Band.class);
if (frequenciesByBand != null) {
for (String label : frequenciesByBand.keySet()) {
Band mappedBand = bandFromDisplayLabel(label);
if (mappedBand != null) {
sureBands.add(mappedBand);
}
}
}
for (ChatMember variant : variants) {
if (variant == null || variant.getName() == null || variant.getName().isBlank()) {
continue;
}
sureBands.addAll(detectBandsFromStationName(variant.getName()));
}
return sureBands;
}
private EnumSet<Band> detectBandsFromStationName(String stationName) {
EnumSet<Band> detectedBands = EnumSet.noneOf(Band.class);
if (stationName == null || stationName.isBlank()) {
return detectedBands;
}
String normalized = stationName.toUpperCase(Locale.ROOT);
String[] tokens = TOKEN_SPLIT_PATTERN.split(normalized);
for (String token : tokens) {
if (token == null || token.isBlank()) {
continue;
}
switch (token) {
case "2", "2M", "144", "144MHZ" -> detectedBands.add(Band.B_144);
case "70", "70CM", "432", "432MHZ" -> detectedBands.add(Band.B_432);
case "23", "23CM", "1296", "1296MHZ" -> detectedBands.add(Band.B_1296);
case "13", "13CM", "2300", "2320", "2320MHZ" -> detectedBands.add(Band.B_2320);
case "9", "9CM", "3400", "3400MHZ" -> detectedBands.add(Band.B_3400);
case "6", "6CM", "5600", "5760", "5760MHZ" -> detectedBands.add(Band.B_5760);
case "3", "3CM", "10G", "10GHZ", "10368", "10368MHZ" -> detectedBands.add(Band.B_10G);
case "24G", "24GHZ", "24048", "24048MHZ" -> detectedBands.add(Band.B_24G);
default -> {
}
}
}
return detectedBands;
}
private String buildBandSummary(EnumSet<Band> sureBands) {
if (sureBands == null || sureBands.isEmpty()) {
return "";
}
List<String> labels = sureBands.stream()
.sorted()
.map(this::toBandDisplayLabel)
.toList();
return String.join(", ", labels);
}
private boolean hasAnySelectedBand(EnumSet<Band> sureBands, EnumSet<Band> selectedBands) {
if (sureBands == null || sureBands.isEmpty() || selectedBands == null || selectedBands.isEmpty()) {
return false;
}
for (Band band : selectedBands) {
if (sureBands.contains(band)) {
return true;
}
}
return false;
}
private Band bandFromDisplayLabel(String label) {
if (label == null || label.isBlank()) {
return null;
}
return switch (label) {
case "144" -> Band.B_144;
case "432" -> Band.B_432;
case "1296" -> Band.B_1296;
case "2320" -> Band.B_2320;
case "3400" -> Band.B_3400;
case "5760" -> Band.B_5760;
case "10368" -> Band.B_10G;
case "24048" -> Band.B_24G;
default -> null;
};
}
/**
* Fallback for stations where the current displayed QRG exists but the
* knownActiveBands history has not yet been filled.
@@ -13,8 +13,12 @@ import java.nio.charset.StandardCharsets;
* - inspectPoint(x,y) returns what is under the cursor
* - zoomIn()/zoomOut() are callable from Java
* - grid / beam / connection use non-interactive panes
* - JavaScript logs are forwarded to Java through javaMapBridge
* - JavaScript errors are forwarded to Java through javaMapBridge
* - setTheme(light|dark) aligns the map with the JavaFX application theme
*
* Important:
* This version intentionally uses integer Leaflet zoom levels again.
* Fractional zoom in JavaFX WebView caused unreliable marker positioning.
*/
public final class MapHtmlResources {
@@ -184,6 +188,111 @@ public final class MapHtmlResources {
font-weight: 800;
}
.station-cluster-wrapper {
background: transparent;
border: none;
box-shadow: none;
}
.station-cluster-root {
position: relative;
width: 1px;
height: 1px;
pointer-events: auto;
cursor: pointer;
user-select: none;
}
/*
* Cluster design aligned with normal KST4Contest station markers:
* - dark center like station-dot
* - blue border for normal clustered stations
* - yellow border when the cluster contains worked stations
* - orange hover border similar to selected station state
*/
.station-cluster-bubble {
position: absolute;
left: -16px;
top: -16px;
min-width: 32px;
height: 32px;
padding: 0 7px;
border-radius: 18px;
background: #1d1d1d;
color: #f4f7fa;
border: 2px solid #4da6ff;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.35),
0 2px 7px rgba(0, 0, 0, 0.42);
box-sizing: border-box;
font-size: 13px;
font-weight: 800;
line-height: 28px;
text-align: center;
white-space: nowrap;
}
.station-cluster-bubble.medium {
left: -18px;
top: -18px;
min-width: 36px;
height: 36px;
border-radius: 20px;
font-size: 14px;
line-height: 32px;
}
.station-cluster-bubble.large {
left: -21px;
top: -21px;
min-width: 42px;
height: 42px;
border-radius: 23px;
font-size: 15px;
line-height: 38px;
}
.station-cluster-bubble.worked {
border-color: #ffd24d;
}
.station-cluster-bubble.warning {
border-color: #00ff66;
color: #00ff66;
}
.station-cluster-root:hover .station-cluster-bubble {
border-color: #ff9900;
box-shadow:
0 0 0 2px rgba(255, 153, 0, 0.28),
0 2px 9px rgba(0, 0, 0, 0.48);
}
body.kst-theme-dark .station-cluster-bubble {
background: #202428;
color: #f1f3f5;
border-color: #4da6ff;
box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.12),
0 2px 8px rgba(0, 0, 0, 0.58);
}
body.kst-theme-dark .station-cluster-bubble.worked {
border-color: #ffd24d;
}
body.kst-theme-dark .station-cluster-bubble.warning {
border-color: #00ff66;
color: #00ff66;
}
body.kst-theme-dark .station-cluster-root:hover .station-cluster-bubble {
border-color: #ff9900;
box-shadow:
0 0 0 2px rgba(255, 153, 0, 0.32),
0 2px 10px rgba(0, 0, 0, 0.65);
}
.maidenhead-grid-label-wrapper {
background: transparent;
border: none;
@@ -225,9 +334,6 @@ public final class MapHtmlResources {
<script>window._kstTileProxyPort=__TILE_PROXY_PORT__;</script>
<script>
window.kstMapApi = (function () {
let map;
let stationLayer;
@@ -235,11 +341,77 @@ public final class MapHtmlResources {
let beamLayer;
let connectionLayer;
let profileHoverMarker;
/*
* Currently visible individual station markers.
*
* A callsign is present here only when it is rendered individually.
* If the station is inside a cluster, stationsByCallsignRaw is used
* for focus/zoom operations.
*/
let markersByCallsignRaw = {};
/*
* All station data as received from Java.
*/
let stationData = [];
let stationsByCallsignRaw = {};
let clustersById = {};
let clusterSequence = 0;
let activeTheme = 'light';
let invalidateNotifyTimer = 0;
/*
* Integer zoom only.
*
* Fractional zoom is intentionally disabled because JavaFX WebView
* and Leaflet marker positioning were unreliable at intermediate
* zoom levels.
*/
const KST_ZOOM_STEP = 1;
const KST_MIN_ZOOM = 3;
const KST_MAX_ZOOM = 18;
/*
* Enables verbose JavaScript map logging.
*
* Keep this false for normal operation. jsError() still reports
* real errors.
*/
const KST_MAP_DEBUG = false;
/*
* Cluster settings.
*
* With integer zoom, clustering is disabled at zoom >= 8.
* At zoom 7 only very close stations are grouped.
*/
const KST_CLUSTER_DISABLE_ZOOM = 8;
/*
* Do not cluster pairs immediately. Two nearby stations are still
* readable and should remain individually clickable.
*/
const KST_CLUSTER_MIN_STATIONS = 3;
/*
* Cluster cell sizes in screen pixels.
*
* Smaller cells make clustering less aggressive. Stations must be
* closer together on screen before they are grouped.
*/
const KST_CLUSTER_CELL_SIZE_HIGH_ZOOM = 55;
const KST_CLUSTER_CELL_SIZE_MEDIUM_ZOOM = 70;
const KST_CLUSTER_CELL_SIZE_LOW_ZOOM = 95;
const KST_CLUSTER_CELL_SIZE_VERY_LOW_ZOOM = 125;
function jsLog(message) {
if (!KST_MAP_DEBUG) {
return;
}
try {
if (window.javaMapBridge) {
window.javaMapBridge.onJsLog(String(message));
@@ -358,6 +530,331 @@ public final class MapHtmlResources {
+ '</div>';
}
function getStationCallsignKey(station) {
if (!station || station.callSignRaw === null || station.callSignRaw === undefined) {
return '';
}
return String(station.callSignRaw);
}
function isStationPositionValid(station) {
if (!station) {
return false;
}
return isFinite(station.latitudeDeg) && isFinite(station.longitudeDeg);
}
/**
* Returns true for stations that must not be hidden inside a cluster.
*
* These stations remain individually visible even at low zoom levels
* because they are operationally important during contest operation.
*/
function shouldRenderStationIndividually(station) {
if (!station) {
return false;
}
if (station.selected) {
return true;
}
if (station.warningToMyDirection) {
return true;
}
return false;
}
/**
* Returns the cluster grid size in screen pixels for the current zoom level.
*
* The cluster calculation is screen-based, not locator-based. This makes
* the decision match the real visual problem: too many labels in the same
* screen area.
*/
function getClusterCellSizePx() {
if (!map) {
return KST_CLUSTER_CELL_SIZE_MEDIUM_ZOOM;
}
const zoom = Number(map.getZoom());
if (zoom >= 7) {
return KST_CLUSTER_CELL_SIZE_HIGH_ZOOM;
}
if (zoom >= 6) {
return KST_CLUSTER_CELL_SIZE_MEDIUM_ZOOM;
}
if (zoom >= 5) {
return KST_CLUSTER_CELL_SIZE_LOW_ZOOM;
}
return KST_CLUSTER_CELL_SIZE_VERY_LOW_ZOOM;
}
function buildClusterMarkerHtml(clusterId, clusterStations) {
const count = clusterStations ? clusterStations.length : 0;
let bubbleClasses = 'station-cluster-bubble';
if (count >= 20) {
bubbleClasses += ' large';
} else if (count >= 8) {
bubbleClasses += ' medium';
}
const containsWorkedStation = clusterStations
&& clusterStations.some(station => station && station.worked);
const containsWarningStation = clusterStations
&& clusterStations.some(station => station && station.warningToMyDirection);
if (containsWarningStation) {
bubbleClasses += ' warning';
} else if (containsWorkedStation) {
bubbleClasses += ' worked';
}
return '<div class="station-cluster-root" data-cluster-id="' + escapeHtml(clusterId) + '">'
+ '<div class="' + bubbleClasses + '">' + count + '</div>'
+ '</div>';
}
function buildClusterTooltipHtml(clusterStations) {
if (!clusterStations || clusterStations.length === 0) {
return '';
}
const callsigns = clusterStations
.map(station => station.markerLabel || station.callSignRaw || station.callSign || '')
.filter(value => value !== null && value !== undefined && String(value).trim() !== '')
.map(value => String(value).trim())
.sort();
const maxPreviewCount = 20;
const preview = callsigns
.slice(0, maxPreviewCount)
.map(value => escapeHtml(value))
.join('<br>');
const remainingCount = Math.max(0, callsigns.length - maxPreviewCount);
let html = '<b>' + clusterStations.length + ' stations</b>';
if (preview) {
html += '<br>' + preview;
}
if (remainingCount > 0) {
html += '<br>+' + remainingCount + ' more';
}
html += '<br><i>Click to zoom in</i>';
return html;
}
function addStationMarker(station) {
if (!stationLayer || !isStationPositionValid(station)) {
return;
}
const marker = L.marker(
[station.latitudeDeg, station.longitudeDeg],
{
interactive: true,
keyboard: false,
icon: L.divIcon({
className: 'station-marker-wrapper',
html: buildStationMarkerHtml(station),
iconSize: [1, 1],
iconAnchor: [0, 0]
})
}
);
marker.addTo(stationLayer);
const callSignKey = getStationCallsignKey(station);
if (callSignKey) {
markersByCallsignRaw[callSignKey] = marker;
}
}
function addClusterMarker(clusterStations) {
if (!stationLayer || !clusterStations || clusterStations.length === 0) {
return;
}
let latSum = 0.0;
let lonSum = 0.0;
let validCount = 0;
clusterStations.forEach(station => {
if (isStationPositionValid(station)) {
latSum += Number(station.latitudeDeg);
lonSum += Number(station.longitudeDeg);
validCount++;
}
});
if (validCount === 0) {
return;
}
const centerLat = latSum / validCount;
const centerLon = lonSum / validCount;
const clusterId = 'cluster-' + (++clusterSequence);
clustersById[clusterId] = clusterStations;
const clusterMarker = L.marker(
[centerLat, centerLon],
{
interactive: true,
keyboard: false,
icon: L.divIcon({
className: 'station-cluster-wrapper',
html: buildClusterMarkerHtml(clusterId, clusterStations),
iconSize: [1, 1],
iconAnchor: [0, 0]
})
}
);
clusterMarker.on('click', function () {
zoomToCluster(clusterStations);
});
clusterMarker.bindTooltip(buildClusterTooltipHtml(clusterStations), {
direction: 'top',
sticky: true,
opacity: 0.95
});
clusterMarker.addTo(stationLayer);
}
function renderAllStationsIndividually() {
stationData.forEach(station => {
addStationMarker(station);
});
}
/**
* Renders stations with simple screen-grid clustering.
*
* Important stations such as the currently selected station and warning
* stations are rendered individually before clustering. They remain
* visible even at low zoom levels.
*/
function renderClusteredStations() {
const clusterCellSizePx = getClusterCellSizePx();
const buckets = {};
stationData.forEach(station => {
if (!isStationPositionValid(station)) {
return;
}
if (shouldRenderStationIndividually(station)) {
addStationMarker(station);
return;
}
const point = map.latLngToContainerPoint([station.latitudeDeg, station.longitudeDeg]);
const cellX = Math.floor(point.x / clusterCellSizePx);
const cellY = Math.floor(point.y / clusterCellSizePx);
const key = cellX + ':' + cellY;
if (!buckets[key]) {
buckets[key] = [];
}
buckets[key].push(station);
});
Object.keys(buckets).forEach(key => {
const bucketStations = buckets[key];
if (bucketStations.length >= KST_CLUSTER_MIN_STATIONS) {
addClusterMarker(bucketStations);
} else {
bucketStations.forEach(station => addStationMarker(station));
}
});
}
/**
* Re-renders station markers for the current zoom and viewport.
*
* This is called when stations arrive from Java and after zoom/move
* events, because screen-grid clusters depend on the current viewport.
*/
function renderStationMarkers() {
if (!map || !stationLayer) {
return;
}
stationLayer.clearLayers();
markersByCallsignRaw = {};
clustersById = {};
clusterSequence = 0;
if (!stationData || stationData.length === 0) {
return;
}
if (Number(map.getZoom()) >= KST_CLUSTER_DISABLE_ZOOM) {
renderAllStationsIndividually();
} else {
renderClusteredStations();
}
}
/**
* Zooms into a cluster.
*
* A cluster click does not select one station. It moves the map towards
* the cluster center and increases zoom until individual stations become
* visible.
*/
function zoomToCluster(clusterStations) {
if (!map || !clusterStations || clusterStations.length === 0) {
return;
}
const latLngs = [];
clusterStations.forEach(station => {
if (isStationPositionValid(station)) {
latLngs.push(L.latLng(station.latitudeDeg, station.longitudeDeg));
}
});
if (latLngs.length === 0) {
return;
}
const bounds = L.latLngBounds(latLngs);
const currentZoom = Number(map.getZoom());
const targetZoom = clampZoomToLeafletLimits(
Math.min(KST_CLUSTER_DISABLE_ZOOM, currentZoom + 1)
);
map.setView(bounds.getCenter(), targetZoom, {
animate: false
});
notifyViewport();
}
function init() {
if (map) {
return true;
@@ -372,6 +869,17 @@ public final class MapHtmlResources {
map = L.map('map', {
zoomControl: true,
minZoom: KST_MIN_ZOOM,
maxZoom: KST_MAX_ZOOM,
/*
* Integer zoom only.
* This keeps station positions stable in JavaFX WebView.
*/
zoomSnap: 1,
zoomDelta: 1,
wheelPxPerZoomLevel: 120,
zoomAnimation: false,
fadeAnimation: false,
markerZoomAnimation: false,
@@ -383,7 +891,8 @@ public final class MapHtmlResources {
const tileLayer = L.tileLayer(
'http://127.0.0.1:' + window._kstTileProxyPort + '/tiles/{s}/{z}/{x}/{y}.png',
{
maxZoom: 18,
minZoom: KST_MIN_ZOOM,
maxZoom: KST_MAX_ZOOM,
updateWhenZooming: false,
keepBuffer: 4,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
@@ -419,12 +928,12 @@ public final class MapHtmlResources {
connectionLayer = L.layerGroup().addTo(map);
map.on('zoomend', function () {
jsLog('zoomend -> ' + map.getZoom());
renderStationMarkers();
notifyViewport();
});
map.on('moveend', function () {
jsLog('moveend');
renderStationMarkers();
notifyViewport();
});
@@ -476,20 +985,39 @@ public final class MapHtmlResources {
invalidateSize();
}
function zoomIn() {
/**
* Keeps the zoom value inside the explicit KST zoom limits.
*/
function clampZoomToLeafletLimits(zoom) {
return Math.max(KST_MIN_ZOOM, Math.min(KST_MAX_ZOOM, zoom));
}
/**
* Changes the zoom in stable integer steps.
*/
function changeZoomByKstStep(delta) {
if (!map) {
return;
}
map.setZoom(map.getZoom() + 1, { animate: false });
const currentZoom = Number(map.getZoom());
const nextZoom = clampZoomToLeafletLimits(currentZoom + delta);
if (nextZoom === currentZoom) {
return;
}
map.setZoom(nextZoom, {
animate: false
});
}
function zoomIn() {
changeZoomByKstStep(KST_ZOOM_STEP);
}
function zoomOut() {
if (!map) {
return;
}
map.setZoom(map.getZoom() - 1, { animate: false });
changeZoomByKstStep(-KST_ZOOM_STEP);
}
function getViewportState() {
@@ -522,6 +1050,12 @@ public final class MapHtmlResources {
return 'station|' + callSignRaw + '|' + el.tagName + '|' + (el.className || '') + '|' + (el.textContent || '').trim();
}
const clusterRoot = el.closest('.station-cluster-root');
if (clusterRoot) {
const clusterId = clusterRoot.getAttribute('data-cluster-id') || '';
return 'cluster|' + clusterId + '|' + el.tagName + '|' + (el.className || '') + '|' + (el.textContent || '').trim();
}
const zoomInButton = el.closest('.leaflet-control-zoom-in');
if (zoomInButton) {
return 'zoomIn||' + el.tagName + '|' + (el.className || '') + '|' + (el.textContent || '').trim();
@@ -542,8 +1076,8 @@ public final class MapHtmlResources {
if (!init()) {
return;
}
jsLog('setHome lat=' + lat + ' lon=' + lon + ' zoom=' + zoom);
map.setView([lat, lon], zoom);
map.setView([lat, lon], clampZoomToLeafletLimits(Math.round(Number(zoom))));
}
function setStations(stationsJson) {
@@ -551,30 +1085,17 @@ public final class MapHtmlResources {
return;
}
stationLayer.clearLayers();
markersByCallsignRaw = {};
stationData = JSON.parse(stationsJson);
stationsByCallsignRaw = {};
const stations = JSON.parse(stationsJson);
jsLog('setStations count=' + stations.length);
stations.forEach(station => {
const marker = L.marker(
[station.latitudeDeg, station.longitudeDeg],
{
interactive: true,
keyboard: false,
icon: L.divIcon({
className: 'station-marker-wrapper',
html: buildStationMarkerHtml(station),
iconSize: [1, 1],
iconAnchor: [0, 0]
})
}
);
marker.addTo(stationLayer);
markersByCallsignRaw[station.callSignRaw] = marker;
stationData.forEach(station => {
const callSignKey = getStationCallsignKey(station);
if (callSignKey) {
stationsByCallsignRaw[callSignKey] = station;
}
});
renderStationMarkers();
}
function setBeam(beamJson) {
@@ -592,8 +1113,6 @@ public final class MapHtmlResources {
return;
}
jsLog('setBeam points=' + points.length);
const latLngs = points.map(point => [point.lat, point.lon]);
L.polygon(latLngs, {
@@ -621,8 +1140,6 @@ public final class MapHtmlResources {
return;
}
jsLog('setConnection');
L.polyline(points.map(point => [point.lat, point.lon]), {
pane: 'connectionPane',
color: connectionColor(),
@@ -671,7 +1188,6 @@ public final class MapHtmlResources {
gridLayer.clearLayers();
const cells = JSON.parse(gridJson);
jsLog('setGrid cells=' + cells.length);
cells.forEach(cell => {
const rectangle = L.rectangle(
@@ -712,48 +1228,60 @@ public final class MapHtmlResources {
}
function focusCallsignRaw(callSignRaw) {
if (!map || !callSignRaw) {
return;
}
const marker = markersByCallsignRaw[callSignRaw];
if (!marker) {
jsLog('focusCallsignRaw skipped for ' + callSignRaw);
return;
}
jsLog('focusCallsignRaw ' + callSignRaw);
map.panTo(marker.getLatLng(), {
animate: false
});
notifyViewport();
}
if (!map || !callSignRaw) {
return;
}
const marker = markersByCallsignRaw[callSignRaw];
if (marker) {
map.panTo(marker.getLatLng(), {
animate: false
});
notifyViewport();
return;
}
/*
* If the station is currently hidden inside a cluster, there is no
* individual marker. Use the raw station data and zoom in far enough
* so clustering is disabled and the station becomes visible.
*/
const station = stationsByCallsignRaw[callSignRaw];
if (station && isStationPositionValid(station)) {
const focusZoom = clampZoomToLeafletLimits(
Math.max(Number(map.getZoom()), KST_CLUSTER_DISABLE_ZOOM)
);
map.setView([station.latitudeDeg, station.longitudeDeg], focusZoom, {
animate: false
});
notifyViewport();
}
}
return {
init: init,
invalidateSize: invalidateSize,
resize: resize,
zoomIn: zoomIn,
zoomOut: zoomOut,
inspectPoint: inspectPoint,
getViewportState: getViewportState,
setHome: setHome,
setStations: setStations,
setBeam: setBeam,
setConnection: setConnection,
setProfileHoverPoint: setProfileHoverPoint,
setGrid: setGrid,
focusCallsignRaw: focusCallsignRaw,
setTheme: setTheme
};
init: init,
invalidateSize: invalidateSize,
resize: resize,
zoomIn: zoomIn,
zoomOut: zoomOut,
inspectPoint: inspectPoint,
getViewportState: getViewportState,
setHome: setHome,
setStations: setStations,
setBeam: setBeam,
setConnection: setConnection,
setProfileHoverPoint: setProfileHoverPoint,
setGrid: setGrid,
focusCallsignRaw: focusCallsignRaw,
setTheme: setTheme
};
})();
</script>
</body>
</html>
""".replace("__TILE_PROXY_PORT__", String.valueOf(tileProxyPort));
}
}
}
@@ -11,6 +11,7 @@ import kst4contest.model.ChatPreferences;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -142,8 +143,13 @@ public final class StationMapBridge {
private void refreshNow() {
List<ChatMember> visibleChatMembers = new ArrayList<>(chatController.getLst_chatMemberSortedFilteredList());
ChatMember selectedChatMember = chatController.getScoreService().getSelectedChatMember();
EnumSet<Band> selectedBands = chatController.getReachabilityService().getEnabledStationBands();
List<MapCallsignRawSnapshot> snapshots = snapshotBuilder.buildSnapshots(visibleChatMembers, selectedChatMember);
List<MapCallsignRawSnapshot> snapshots = snapshotBuilder.buildSnapshots(
visibleChatMembers,
selectedChatMember,
selectedBands
);
MapCallsignRawSnapshot selectedSnapshot = null;
if (selectedChatMember != null && selectedChatMember.getCallSignRaw() != null) {
@@ -41,6 +41,14 @@ import javafx.scene.layout.ColumnConstraints;
*/
public final class StationMapView {
/**
* Enables verbose map debug output.
*
* Keep this false for normal operation because scroll and viewport logs are very
* noisy during map interaction.
*/
private static final boolean MAP_DEBUG_LOGGING = false;
private final PathProfileChart detailPathProfileChart = new PathProfileChart();
private final Label detailPathModeValue = new Label("-");
@@ -287,12 +295,17 @@ public final class StationMapView {
webView.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> handleWebViewClick(event));
webView.addEventHandler(ScrollEvent.SCROLL, event -> {
System.out.println("[StationMap FX] SCROLL x=" + (int) event.getX()
+ " y=" + (int) event.getY()
+ " deltaY=" + event.getDeltaY());
if (MAP_DEBUG_LOGGING) {
System.out.println("[StationMap FX] SCROLL x=" + (int) event.getX()
+ " y=" + (int) event.getY()
+ " deltaY=" + event.getDeltaY());
}
InteractiveTarget target = inspectInteractiveTarget(event.getX(), event.getY());
System.out.println("[StationMap FX] inspect scroll -> " + target);
if (MAP_DEBUG_LOGGING) {
System.out.println("[StationMap FX] inspect scroll -> " + target);
}
if (event.getDeltaY() > 0) {
executeMapScriptSafely("window.kstMapApi.zoomIn();");
@@ -855,7 +868,11 @@ public final class StationMapView {
detailLocatorValue.setText(selectedSnapshot.locator6());
detailQrbValue.setText(String.format(Locale.US, "%.0f km", selectedSnapshot.qrbKm()));
detailQtfValue.setText(String.format(Locale.US, "%.0f°", selectedSnapshot.qtfDeg()));
detailBandsValue.setText(selectedSnapshot.bandSummary().isBlank() ? "-" : selectedSnapshot.bandSummary());
String bandText = selectedSnapshot.bandSummary().isBlank() ? "-" : selectedSnapshot.bandSummary();
if (selectedSnapshot.offersSelectedBand()) {
bandText += " B+";
}
detailBandsValue.setText(bandText);
detailFrequenciesArea.setText(selectedSnapshot.detailFrequencyText());
detailAirplanesValue.setText(String.valueOf(selectedSnapshot.reachableAirplanes()));
triggerClusterSpotButton.setDisable(false);
@@ -650,6 +650,8 @@ svg.leaflet-image-layer.leaflet-interactive path {
border-right-color: #fff;
}
/* Printing */
@media print {
@@ -0,0 +1,56 @@
package kst4contest.test;
import kst4contest.model.Band;
import kst4contest.model.ChatMember;
import kst4contest.view.map.MapCallsignRawSnapshot;
import kst4contest.view.map.MapCallsignRawSnapshotBuilder;
import org.junit.jupiter.api.Test;
import java.util.EnumSet;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class MapCallsignRawSnapshotBuilderTest {
@Test
void marksSnapshotWhenNameAdvertisesSelectedBand() {
ChatMember station = buildStation("DL1ABC", "QRV 2-70-23", "JN58TD", 1_000L);
MapCallsignRawSnapshotBuilder builder = new MapCallsignRawSnapshotBuilder();
List<MapCallsignRawSnapshot> snapshots = builder.buildSnapshots(
List.of(station),
null,
EnumSet.of(Band.B_1296)
);
assertEquals(1, snapshots.size());
assertTrue(snapshots.get(0).offersSelectedBand());
}
@Test
void keepsSnapshotUnmarkedWhenNoSelectedBandMatches() {
ChatMember station = buildStation("DL1ABC", "QRV 2m only", "JN58TD", 1_000L);
MapCallsignRawSnapshotBuilder builder = new MapCallsignRawSnapshotBuilder();
List<MapCallsignRawSnapshot> snapshots = builder.buildSnapshots(
List.of(station),
null,
EnumSet.of(Band.B_2320)
);
assertEquals(1, snapshots.size());
assertFalse(snapshots.get(0).offersSelectedBand());
}
private ChatMember buildStation(String callSign, String name, String locator, long activityEpoch) {
ChatMember chatMember = new ChatMember();
chatMember.setCallSign(callSign);
chatMember.setName(name);
chatMember.setQra(locator);
chatMember.setActivityTimeLastInEpoch(activityEpoch);
return chatMember;
}
}
+35 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>About KST4Contest</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="About KST4Contest and its contest-oriented ON4KST workflow.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/about/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="About KST4Contest and its contest-oriented ON4KST workflow.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/about/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="About KST4Contest">
<meta name="twitter:description" content="About KST4Contest and its contest-oriented ON4KST workflow.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">About</p>
<h1>Built for real contest operation.</h1>
@@ -85,9 +86,23 @@
The project is open source and focused on practical contest station workflows.
Features are designed around real operating pressure, not theoretical UI concepts.
</p>
<h2>Community supported</h2>
<p>
KST4Contest is developed independently.
Community support helps finance hosting,
online services,
testing across multiple operating systems
and future contest-oriented features.
</p>
</article>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -95,7 +110,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -103,14 +117,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -118,5 +131,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+261 -1
View File
@@ -415,6 +415,23 @@ a:hover {
color: var(--muted);
}
.roadmap-version + .roadmap-version {
margin-top: 24px;
}
.roadmap-version ul {
padding-left: 20px;
}
.roadmap-version li {
margin-bottom: 8px;
}
.roadmap-done a {
color: var(--muted);
text-decoration: line-through;
}
.feature-icon {
font-size: 2rem;
margin-bottom: 14px;
@@ -544,4 +561,247 @@ a:hover {
.footer-grid {
grid-template-columns: 1fr;
}
}
}
/* ================================================================
KST4Contest brand recovery: green operator-console identity
and full-screen interactive hero
================================================================ */
:root {
--bg: #050806;
--bg2: #0b140f;
--panel: rgba(15, 28, 20, 0.82);
--panel2: rgba(24, 43, 31, 0.84);
--border: rgba(125, 178, 134, 0.22);
--text: #f4f7f3;
--muted: #a9b8ad;
--soft: #6e8173;
--accent: #54d63d;
--accent2: #8aef64;
--accent3: #31b54a;
--fx-plane: #79ff73;
--fx-plane-muted: rgba(121, 255, 115, 0.42);
--fx-beam: #52ff65;
--fx-beam-glow: rgba(82, 255, 101, 0.28);
--fx-reflection: #d2ffc8;
--fx-trail: rgba(121, 255, 115, 0.17);
--fx-grid: rgba(121, 255, 115, 0.055);
}
body {
background:
radial-gradient(circle at 14% 12%, rgba(52, 165, 57, 0.20), transparent 30%),
radial-gradient(circle at 82% 16%, rgba(95, 218, 82, 0.14), transparent 32%),
radial-gradient(circle at 50% 85%, rgba(32, 126, 55, 0.12), transparent 36%),
linear-gradient(180deg, #070b09 0%, #0b1410 100%);
}
body::before {
background:
radial-gradient(circle at 20% 20%, rgba(60, 173, 61, 0.18), transparent 28%),
radial-gradient(circle at 78% 18%, rgba(97, 222, 80, 0.13), transparent 30%),
radial-gradient(circle at 45% 80%, rgba(34, 122, 53, 0.11), transparent 26%);
}
body::after {
background-image:
linear-gradient(rgba(160,255,160,0.032) 1px, transparent 1px),
linear-gradient(90deg, rgba(160,255,160,0.032) 1px, transparent 1px);
}
.site-bg {
background:
linear-gradient(180deg, rgba(5, 8, 6, 0.10), rgba(5, 8, 6, 0.84)),
radial-gradient(circle at 8% 24%, rgba(52, 165, 57, 0.14), transparent 26%),
radial-gradient(circle at 82% 20%, rgba(95, 218, 82, 0.12), transparent 30%);
}
.site-header {
background: rgba(5, 8, 7, 0.82);
}
.brand-mark,
.nav-cta,
.button {
background: linear-gradient(135deg, var(--accent), var(--accent2));
}
.brand-mark {
box-shadow:
0 0 26px rgba(84, 214, 61, 0.42),
0 0 50px rgba(49, 181, 74, 0.16);
}
.nav-cta {
color: #061008 !important;
box-shadow: 0 0 22px rgba(84, 214, 61, 0.28);
}
main {
width: 100%;
}
.hero.hero-split {
position: relative;
width: 100%;
max-width: none;
min-height: calc(100vh - 73px);
display: grid;
grid-template-columns: minmax(420px, 0.9fr) minmax(520px, 1.1fr);
gap: clamp(40px, 5vw, 110px);
align-items: center;
align-content: center;
padding: clamp(56px, 7vh, 100px) clamp(28px, 6vw, 110px);
overflow: hidden;
}
.hero.hero-split > *:not(.hero-radio-fx):not(.hero-fx-vignette) {
position: relative;
z-index: 3;
}
.hero-copy {
max-width: 760px;
}
.hero.hero-split h1 {
font-size: clamp(3.4rem, 8vw, 7rem);
background: linear-gradient(90deg, #ffffff 0%, #c9ffd0 28%, #77ec70 58%, #45bd4d 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.badge,
.eyebrow {
color: #caffc0;
background: rgba(52, 165, 57, 0.12);
border-color: rgba(105, 232, 88, 0.32);
box-shadow: 0 0 22px rgba(82, 255, 101, 0.08);
}
.button {
color: #041007;
box-shadow:
0 16px 44px rgba(84, 214, 61, 0.22),
inset 0 1px 0 rgba(255,255,255,0.35);
}
.button.secondary {
color: white;
background: rgba(28, 43, 32, 0.90);
}
.hero-panel {
width: min(100%, 760px);
justify-self: center;
transform: perspective(1200px) rotateY(-4deg) rotateX(1.5deg);
}
.hero-panel,
.card,
.cta-panel {
background:
linear-gradient(180deg, rgba(255,255,255,0.045), transparent 42%),
rgba(15, 28, 20, 0.82);
}
.mini-card {
background: rgba(12, 25, 17, 0.88);
border-color: rgba(148, 184, 155, 0.16);
}
.card:hover {
border-color: rgba(84, 214, 61, 0.42);
background:
linear-gradient(180deg, rgba(255,255,255,0.055), transparent 42%),
rgba(12, 25, 17, 0.94);
box-shadow:
0 26px 90px rgba(0,0,0,0.50),
0 0 30px rgba(84,214,61,0.07);
}
.hero-radio-fx {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.78;
}
.hero-fx-vignette {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
background:
linear-gradient(90deg,
rgba(7,11,9,0.92) 0%,
rgba(7,11,9,0.70) 32%,
rgba(7,11,9,0.17) 62%,
rgba(7,11,9,0.38) 100%),
linear-gradient(180deg,
rgba(7,11,9,0.08),
rgba(7,11,9,0.58));
}
html[data-theme="light"] {
--bg: #f4f4f2;
--bg2: #ffffff;
--panel: rgba(255,255,255,0.90);
--panel2: rgba(242,242,239,0.94);
--border: rgba(91,91,86,0.20);
--text: #252522;
--muted: #656560;
--soft: #888881;
--accent: #d33732;
--accent2: #f25a51;
--accent3: #ae2727;
--fx-plane: #df3732;
--fx-plane-muted: rgba(211,55,50,0.38);
--fx-beam: #ef4039;
--fx-beam-glow: rgba(239,64,57,0.22);
--fx-reflection: #ff8d84;
--fx-trail: rgba(211,55,50,0.15);
--fx-grid: rgba(185,47,43,0.045);
}
@media (min-width: 1600px) {
.hero.hero-split {
grid-template-columns: minmax(520px, 0.85fr) minmax(620px, 1.15fr);
}
.hero-panel {
width: min(100%, 820px);
}
}
@media (max-width: 1000px) {
.hero.hero-split {
min-height: auto;
grid-template-columns: 1fr;
padding-top: 64px;
padding-bottom: 64px;
}
.hero-panel {
width: min(100%, 760px);
transform: none;
}
.hero-radio-fx {
opacity: 0.50;
}
.hero-fx-vignette {
background: linear-gradient(180deg, rgba(7,11,9,0.84), rgba(7,11,9,0.34));
}
}
@media (prefers-reduced-motion: reduce) {
.hero-radio-fx {
opacity: 0.28;
}
}
+509
View File
@@ -0,0 +1,509 @@
(() => {
"use strict";
const canvas = document.querySelector("[data-hero-radio-fx]");
if (!(canvas instanceof HTMLCanvasElement)) return;
const hero = canvas.closest(".hero");
const ctx = canvas.getContext("2d", { alpha: true, desynchronized: true });
if (!(hero instanceof HTMLElement) || !ctx) return;
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
const coarsePointer = matchMedia("(pointer: coarse)");
const state = {
width: 0,
height: 0,
dpr: 1,
running: false,
visible: true,
lastFrame: 0,
spawnTime: 0,
beamTime: 0,
frameId: 0,
pointer: { x: 0, y: 0, active: false },
planes: [],
beams: [],
reflections: []
};
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
const random = (min, max) => min + Math.random() * (max - min);
function css(name, fallback) {
return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
}
function palette() {
return {
plane: css("--fx-plane", "#79ff73"),
planeMuted: css("--fx-plane-muted", "rgba(121,255,115,.42)"),
beam: css("--fx-beam", "#52ff65"),
beamGlow: css("--fx-beam-glow", "rgba(82,255,101,.28)"),
reflection: css("--fx-reflection", "#d2ffc8"),
trail: css("--fx-trail", "rgba(121,255,115,.17)"),
grid: css("--fx-grid", "rgba(121,255,115,.055)")
};
}
function roundedRect(x, y, width, height, radius) {
const r = Math.min(radius, width / 2, height / 2);
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + width, y, x + width, y + height, r);
ctx.arcTo(x + width, y + height, x, y + height, r);
ctx.arcTo(x, y + height, x, y, r);
ctx.arcTo(x, y, x + width, y, r);
ctx.closePath();
}
function resize() {
const rect = hero.getBoundingClientRect();
state.width = Math.max(1, Math.round(rect.width));
state.height = Math.max(1, Math.round(rect.height));
state.dpr = Math.min(devicePixelRatio || 1, 1.5);
canvas.width = Math.round(state.width * state.dpr);
canvas.height = Math.round(state.height * state.dpr);
canvas.style.width = `${state.width}px`;
canvas.style.height = `${state.height}px`;
ctx.setTransform(state.dpr, 0, 0, state.dpr, 0, 0);
if (!state.pointer.active) {
state.pointer.x = state.width * 0.55;
state.pointer.y = state.height * 0.42;
}
}
function stations() {
const targetX = state.width * 0.54;
const targetY = state.height * 0.43;
const left = { x: state.width * 0.075, y: state.height * 0.82, label: "JO51IJ" };
const right = { x: state.width * 0.925, y: state.height * 0.80, label: "JN49FL" };
left.angle = Math.atan2(targetY - left.y, targetX - left.x);
right.angle = Math.atan2(targetY - right.y, targetX - right.x);
return [left, right];
}
function planeLimit() {
if (coarsePointer.matches) return 5;
return state.width < 900 ? 7 : 12;
}
function createPlane() {
const margin = 55;
const edge = Math.floor(Math.random() * 4);
let x;
let y;
if (edge === 0) {
x = random(0, state.width);
y = -margin;
} else if (edge === 1) {
x = state.width + margin;
y = random(0, state.height);
} else if (edge === 2) {
x = random(0, state.width);
y = state.height + margin;
} else {
x = -margin;
y = random(0, state.height);
}
const tx = state.pointer.active ? state.pointer.x : state.width * random(0.38, 0.72);
const ty = state.pointer.active ? state.pointer.y : state.height * random(0.20, 0.72);
const angle = Math.atan2(ty - y, tx - x);
const speed = random(18, 34);
return {
x, y, angle,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
size: random(5.5, 9),
age: 0,
maxAge: random(12, 20),
steering: random(0.65, 1.2),
wobble: random(0, Math.PI * 2),
wobbleSpeed: random(0.7, 1.4),
hit: 0
};
}
function updatePlane(plane, dt) {
plane.age += dt;
plane.wobble += dt * plane.wobbleSpeed;
const tx = state.pointer.active ? state.pointer.x : state.width * 0.56;
const ty = state.pointer.active ? state.pointer.y : state.height * 0.44;
const desired = Math.atan2(ty - plane.y, tx - plane.x);
let diff = desired - plane.angle;
while (diff > Math.PI) diff -= Math.PI * 2;
while (diff < -Math.PI) diff += Math.PI * 2;
plane.angle += diff * plane.steering * dt;
const speed = Math.hypot(plane.vx, plane.vy);
const wobble = Math.sin(plane.wobble) * 0.14;
plane.vx = Math.cos(plane.angle + wobble) * speed;
plane.vy = Math.sin(plane.angle + wobble) * speed;
plane.x += plane.vx * dt;
plane.y += plane.vy * dt;
plane.hit = Math.max(0, plane.hit - dt * 1.8);
}
function expired(plane) {
const margin = 180;
return plane.age > plane.maxAge || plane.x < -margin || plane.x > state.width + margin || plane.y < -margin || plane.y > state.height + margin;
}
function beamTarget() {
if (!state.planes.length) return null;
const tx = state.pointer.active ? state.pointer.x : state.width * 0.56;
const ty = state.pointer.active ? state.pointer.y : state.height * 0.44;
const nearby = state.planes
.map(plane => ({ plane, distance: Math.hypot(plane.x - tx, plane.y - ty) }))
.filter(item => item.distance < 280)
.sort((a, b) => a.distance - b.distance);
return nearby[0]?.plane || state.planes[Math.floor(Math.random() * state.planes.length)];
}
function createBeam(target) {
const source = stations()
.map(station => ({ station, distance: Math.hypot(target.x - station.x, target.y - station.y) }))
.sort((a, b) => a.distance - b.distance)[0].station;
const angle = Math.atan2(target.y - source.y, target.x - source.x);
const tip = 45;
state.beams.push({
startX: source.x + Math.cos(angle) * tip,
startY: source.y + Math.sin(angle) * tip,
endX: target.x,
endY: target.y,
age: 0,
maxAge: 0.72
});
target.hit = 1;
const values = [50, 75, 100];
state.reflections.push({
x: target.x,
y: target.y,
age: 0,
maxAge: 1.25,
radius: random(8, 13),
probability: values[Math.floor(Math.random() * values.length)]
});
}
function updateEffects(dt) {
state.beams.forEach(beam => beam.age += dt);
state.beams = state.beams.filter(beam => beam.age < beam.maxAge);
state.reflections.forEach(r => {
r.age += dt;
r.radius += dt * 30;
});
state.reflections = state.reflections.filter(r => r.age < r.maxAge);
}
function drawGrid(p) {
ctx.save();
ctx.strokeStyle = p.grid;
ctx.lineWidth = 1;
const spacing = 54;
for (let x = spacing; x < state.width; x += spacing) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, state.height);
ctx.stroke();
}
for (let y = spacing; y < state.height; y += spacing) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(state.width, y);
ctx.stroke();
}
ctx.restore();
}
function drawRadar(p) {
const x = state.pointer.active ? state.pointer.x : state.width * 0.56;
const y = state.pointer.active ? state.pointer.y : state.height * 0.44;
ctx.save();
ctx.strokeStyle = p.planeMuted;
ctx.lineWidth = 1;
for (const radius of [24, 48, 82]) {
ctx.globalAlpha = 0.24 - radius / 500;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.stroke();
}
ctx.globalAlpha = 0.2;
ctx.beginPath();
ctx.moveTo(x - 100, y);
ctx.lineTo(x + 100, y);
ctx.moveTo(x, y - 100);
ctx.lineTo(x, y + 100);
ctx.stroke();
ctx.restore();
}
function drawYagi(station, p) {
ctx.save();
ctx.translate(station.x, station.y);
ctx.rotate(station.angle);
ctx.strokeStyle = p.plane;
ctx.fillStyle = p.plane;
ctx.lineWidth = 1.5;
ctx.lineCap = "round";
ctx.shadowColor = p.beamGlow;
ctx.shadowBlur = 9;
ctx.beginPath();
ctx.moveTo(-30, 26);
ctx.lineTo(-30, -5);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(-32, 0);
ctx.lineTo(43, 0);
ctx.stroke();
for (const element of [
{ x: -27, length: 34 },
{ x: -12, length: 28 },
{ x: 2, length: 22 },
{ x: 14, length: 19 },
{ x: 25, length: 16 },
{ x: 35, length: 13 }
]) {
ctx.beginPath();
ctx.moveTo(element.x, -element.length / 2);
ctx.lineTo(element.x, element.length / 2);
ctx.stroke();
}
ctx.beginPath();
ctx.arc(-12, 0, 3.2, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
ctx.save();
ctx.font = "700 11px Inter, system-ui, sans-serif";
const width = ctx.measureText(station.label).width + 14;
const x = station.x - width / 2;
const y = station.y + 32;
ctx.fillStyle = "rgba(6,18,10,.86)";
ctx.strokeStyle = p.planeMuted;
roundedRect(x, y, width, 23, 6);
ctx.fill();
ctx.stroke();
ctx.fillStyle = p.plane;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(station.label, station.x, y + 11.5);
ctx.restore();
}
function drawPlane(plane, p) {
ctx.save();
ctx.translate(plane.x, plane.y);
ctx.rotate(plane.angle);
ctx.globalAlpha = clamp(Math.min(plane.age * 1.8, (plane.maxAge - plane.age) * 1.8), 0, 1);
ctx.strokeStyle = p.trail;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(-plane.size * 4.5, 0);
ctx.lineTo(-plane.size * 1.4, 0);
ctx.stroke();
ctx.fillStyle = plane.hit > 0 ? p.reflection : p.plane;
ctx.shadowColor = plane.hit > 0 ? p.reflection : p.beamGlow;
ctx.shadowBlur = plane.hit > 0 ? 16 : 5;
const s = plane.size;
ctx.beginPath();
ctx.moveTo(s * 1.65, 0);
ctx.lineTo(-s * 0.7, s * 0.34);
ctx.lineTo(-s * 0.15, s * 1.05);
ctx.lineTo(-s * 0.65, s * 1.08);
ctx.lineTo(-s * 1.35, s * 0.3);
ctx.lineTo(-s * 1.7, s * 0.28);
ctx.lineTo(-s * 1.12, 0);
ctx.lineTo(-s * 1.7, -s * 0.28);
ctx.lineTo(-s * 1.35, -s * 0.3);
ctx.lineTo(-s * 0.65, -s * 1.08);
ctx.lineTo(-s * 0.15, -s * 1.05);
ctx.lineTo(-s * 0.7, -s * 0.34);
ctx.closePath();
ctx.fill();
ctx.restore();
}
function drawBeam(beam, p) {
const progress = beam.age / beam.maxAge;
const alpha = Math.sin(progress * Math.PI);
ctx.save();
ctx.globalAlpha = alpha;
ctx.strokeStyle = p.beamGlow;
ctx.lineWidth = 8;
ctx.shadowColor = p.beam;
ctx.shadowBlur = 15;
ctx.setLineDash([10, 6]);
ctx.lineDashOffset = -progress * 36;
ctx.beginPath();
ctx.moveTo(beam.startX, beam.startY);
ctx.lineTo(beam.endX, beam.endY);
ctx.stroke();
ctx.setLineDash([]);
ctx.strokeStyle = p.beam;
ctx.lineWidth = 1.8;
ctx.shadowBlur = 5;
ctx.beginPath();
ctx.moveTo(beam.startX, beam.startY);
ctx.lineTo(beam.endX, beam.endY);
ctx.stroke();
ctx.restore();
}
function drawReflection(r, p) {
const progress = r.age / r.maxAge;
const alpha = 1 - progress;
ctx.save();
ctx.translate(r.x, r.y);
ctx.globalAlpha = alpha;
ctx.strokeStyle = p.reflection;
ctx.lineWidth = 1.6;
ctx.shadowColor = p.beam;
ctx.shadowBlur = 18;
for (const scale of [1, 1.65]) {
ctx.globalAlpha = alpha * (scale === 1 ? 1 : 0.55);
ctx.beginPath();
ctx.arc(0, 0, r.radius * scale, 0, Math.PI * 2);
ctx.stroke();
}
ctx.globalAlpha = alpha * 0.8;
const cross = r.radius * 1.25;
ctx.beginPath();
ctx.moveTo(-cross, 0);
ctx.lineTo(cross, 0);
ctx.moveTo(0, -cross);
ctx.lineTo(0, cross);
ctx.stroke();
ctx.globalAlpha = alpha;
ctx.fillStyle = p.reflection;
ctx.beginPath();
ctx.arc(0, 0, 3.8, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
ctx.save();
const label = `${r.probability}%`;
ctx.font = "800 12px Inter, system-ui, sans-serif";
const width = ctx.measureText(label).width + 16;
const height = 25;
const x = r.x + r.radius + 12;
const y = r.y - r.radius - 17;
ctx.globalAlpha = clamp(alpha * 1.25, 0, 1);
ctx.fillStyle = "rgba(6,18,10,.91)";
ctx.strokeStyle = p.plane;
ctx.shadowColor = p.beamGlow;
ctx.shadowBlur = 10;
roundedRect(x, y, width, height, 6);
ctx.fill();
ctx.stroke();
ctx.shadowBlur = 0;
ctx.fillStyle = p.reflection;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(label, x + width / 2, y + height / 2);
ctx.restore();
}
function draw(staticOnly = false) {
const p = palette();
ctx.clearRect(0, 0, state.width, state.height);
drawGrid(p);
drawRadar(p);
stations().forEach(station => drawYagi(station, p));
if (staticOnly) return;
state.planes.forEach(plane => drawPlane(plane, p));
state.beams.forEach(beam => drawBeam(beam, p));
state.reflections.forEach(r => drawReflection(r, p));
}
function animate(timestamp) {
if (!state.running) return;
const dt = clamp((timestamp - state.lastFrame) / 1000 || 0, 0, 0.05);
state.lastFrame = timestamp;
if (!state.visible || document.hidden) {
state.frameId = requestAnimationFrame(animate);
return;
}
state.spawnTime += dt;
state.beamTime += dt;
const spawnInterval = coarsePointer.matches ? 2.2 : 1.15;
const beamInterval = coarsePointer.matches ? 3.3 : 1.8;
if (state.spawnTime >= spawnInterval && state.planes.length < planeLimit()) {
state.spawnTime = 0;
state.planes.push(createPlane());
}
if (state.beamTime >= beamInterval && state.planes.length) {
state.beamTime = 0;
const target = beamTarget();
if (target) createBeam(target);
}
state.planes.forEach(plane => updatePlane(plane, dt));
state.planes = state.planes.filter(plane => !expired(plane));
updateEffects(dt);
draw(false);
state.frameId = requestAnimationFrame(animate);
}
function start() {
cancelAnimationFrame(state.frameId);
resize();
if (reducedMotion.matches) {
state.running = false;
draw(true);
return;
}
state.running = true;
state.lastFrame = performance.now();
if (!state.planes.length) {
const count = coarsePointer.matches ? 2 : 5;
for (let i = 0; i < count; i++) {
const plane = createPlane();
plane.age = Math.random() * 2;
state.planes.push(plane);
}
}
state.frameId = requestAnimationFrame(animate);
}
hero.addEventListener("pointermove", event => {
const rect = hero.getBoundingClientRect();
state.pointer.x = clamp(event.clientX - rect.left, 0, rect.width);
state.pointer.y = clamp(event.clientY - rect.top, 0, rect.height);
state.pointer.active = true;
}, { passive: true });
hero.addEventListener("pointerleave", () => state.pointer.active = false);
addEventListener("resize", resize, { passive: true });
document.addEventListener("visibilitychange", () => state.lastFrame = performance.now());
reducedMotion.addEventListener("change", start);
new ResizeObserver(() => {
resize();
if (reducedMotion.matches) draw(true);
}).observe(hero);
new IntersectionObserver(entries => {
state.visible = entries[0]?.isIntersecting ?? true;
state.lastFrame = performance.now();
}, { threshold: 0.05 }).observe(hero);
start();
})();
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Contact | KST4Contest</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Contact options for KST4Contest.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/contact/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Contact options for KST4Contest.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/contact/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Contact | KST4Contest">
<meta name="twitter:description" content="Contact options for KST4Contest.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Contact</p>
<h1>Contact</h1>
@@ -89,8 +90,7 @@
<a href="https://groups.google.com/g/kst4contest">Join the mailing list →</a>
</article>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -98,7 +98,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -106,14 +105,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -121,5 +119,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+57 -29
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Download KST4Contest</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Download KST4Contest releases for Windows, Linux and macOS.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/download/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Download KST4Contest releases for Windows, Linux and macOS.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/download/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Download KST4Contest">
<meta name="twitter:description" content="Download KST4Contest releases for Windows, Linux and macOS.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Download</p>
<h1>Get KST4Contest</h1>
@@ -95,7 +96,7 @@
<p><strong>ZIP x64</strong></p>
<p>Best choice for most Windows users.</p>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.0/praktiKST-v1.41.0-windows-x64.zip">Download →</a>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/praktiKST-v1.41.1-windows-x64.zip">Download →</a>
</article>
<article class="card download-card">
@@ -109,7 +110,7 @@
<p><strong>Flatpak (.flatpakref)</strong></p>
<p>Recommended for most Linux users. Open the file with GNOME Software / Discover, or run: flatpak install de.x08.KST4Contest.flatpakref</p>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.0/de.x08.KST4Contest.flatpakref">Download →</a>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/de.x08.KST4Contest.flatpakref">Download →</a>
</article>
<article class="card download-card">
@@ -121,7 +122,7 @@
<p><strong>AppImage x86_64</strong></p>
<p>Portable Linux build without package installation.</p>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.0/KST4Contest-v1.41.0-linux-x86_64.AppImage">Download →</a>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/KST4Contest-v1.41.1-linux-x86_64.AppImage">Download →</a>
</article>
<article class="card download-card">
@@ -133,7 +134,7 @@
<p><strong>DEB amd64</strong></p>
<p>Native package for Debian and Ubuntu based systems.</p>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.0/KST4Contest-v1.41.0-debian-amd64.deb">Download →</a>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/KST4Contest-v1.41.1-debian-amd64.deb">Download →</a>
</article>
<article class="card download-card">
@@ -145,7 +146,7 @@
<p><strong>RPM x86_64</strong></p>
<p>Native package for Fedora/RPM based systems.</p>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.0/KST4Contest-v1.41.0-fedora-x86_64.rpm">Download →</a>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/KST4Contest-v1.41.1-fedora-x86_64.rpm">Download →</a>
</article>
<article class="card download-card">
@@ -157,7 +158,7 @@
<p><strong>pkg.tar.zst</strong></p>
<p>Package build for Arch Linux users.</p>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.0/KST4Contest-v1.41.0-archlinux-x86_64.pkg.tar.zst">Download →</a>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/KST4Contest-v1.41.1-archlinux-x86_64.pkg.tar.zst">Download →</a>
</article>
<article class="card download-card">
@@ -169,7 +170,7 @@
<p><strong>DMG arm64</strong></p>
<p>For Apple Silicon Macs.</p>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.0/KST4Contest-v1.41.0-macos-arm64.dmg">Download →</a>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/KST4Contest-v1.41.1-macos-arm64.dmg">Download →</a>
</article>
<article class="card download-card">
@@ -181,7 +182,7 @@
<p><strong>DMG x86_64</strong></p>
<p>For Intel-based Macs.</p>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.0/KST4Contest-v1.41.0-macos-x86_64.dmg">Download →</a>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/KST4Contest-v1.41.1-macos-x86_64.dmg">Download →</a>
</article>
<article class="card download-card">
@@ -193,7 +194,7 @@
<p><strong>PDF</strong></p>
<p>English PDF manual.</p>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.0/KST4Contest-v1.41.0-manual-en.pdf">Download →</a>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/KST4Contest-v1.41.1-manual-en.pdf">Download →</a>
</article>
<article class="card download-card">
@@ -205,7 +206,7 @@
<p><strong>PDF</strong></p>
<p>German PDF manual.</p>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.0/KST4Contest-v1.41.0-manual-de.pdf">Download →</a>
<a class="button secondary" href="https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/KST4Contest-v1.41.1-manual-de.pdf">Download →</a>
</article>
</div>
@@ -226,7 +227,34 @@
</div>
</div>
</section>
</main>
<section class="section">
<div class="card content-card">
<h2>Enjoying KST4Contest?</h2>
<p>
If KST4Contest makes contesting easier for you,
please consider supporting future development.
</p>
<div class="actions">
<a class="button"
href="/support/">
❤️ Support Development
</a>
</div>
</div>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -234,7 +262,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -242,14 +269,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -257,5 +283,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>KST4Contest FAQ</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Frequently asked questions about KST4Contest, ON4KST contest operation, AirScout integration and VHF/UHF/SHF contest workflow.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/faq/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Frequently asked questions about KST4Contest, ON4KST contest operation, AirScout integration and VHF/UHF/SHF contest workflow.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/faq/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="KST4Contest FAQ">
<meta name="twitter:description" content="Frequently asked questions about KST4Contest, ON4KST contest operation, AirScout integration and VHF/UHF/SHF contest workflow.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<h1>Frequently asked questions</h1>
<p>
@@ -132,8 +133,7 @@
}
]
}
</script>
</main>
</script></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -141,7 +141,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -149,14 +148,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -164,5 +162,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>AirScout Integration</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="AirScout information helps operators evaluate AP windows, aircraft scatter timing and candidate stations during VHF/UHF/SHF contests.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/features/airscout/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="AirScout information helps operators evaluate AP windows, aircraft scatter timing and candidate stations during VHF/UHF/SHF contests.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/features/airscout/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="AirScout Integration">
<meta name="twitter:description" content="AirScout information helps operators evaluate AP windows, aircraft scatter timing and candidate stations during VHF/UHF/SHF contests.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Aircraft Scatter · since 1.26</p>
<h1>AirScout Integration</h1>
@@ -187,8 +188,7 @@
"name": "KST4Contest"
}
}
</script>
</main>
</script></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -196,7 +196,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -204,14 +203,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -219,5 +217,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Dual Chat Categories</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Dual category support gives contest operators better overview when working across multiple ON4KST chat rooms.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/features/dual-chat/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Dual category support gives contest operators better overview when working across multiple ON4KST chat rooms.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/features/dual-chat/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Dual Chat Categories">
<meta name="twitter:description" content="Dual category support gives contest operators better overview when working across multiple ON4KST chat rooms.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">ON4KST Chat · since 1.26</p>
<h1>Dual Chat Categories</h1>
@@ -163,8 +164,7 @@
"name": "KST4Contest"
}
}
</script>
</main>
</script></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -172,7 +172,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -180,14 +179,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -195,5 +193,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>DXCluster Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest includes DXCluster functionality to support situational awareness during contest operation.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/features/dx-cluster/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest includes DXCluster functionality to support situational awareness during contest operation.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/features/dx-cluster/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="DXCluster Server">
<meta name="twitter:description" content="KST4Contest includes DXCluster functionality to support situational awareness during contest operation.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Radio Workflow · since 1.23</p>
<h1>DXCluster Server</h1>
@@ -162,8 +163,7 @@
"name": "KST4Contest"
}
}
</script>
</main>
</script></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -171,7 +171,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -179,14 +178,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -194,5 +192,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>KST4Contest Features</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Contest-optimized ON4KST features for VHF, UHF and SHF operation.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/features/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Contest-optimized ON4KST features for VHF, UHF and SHF operation.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/features/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="KST4Contest Features">
<meta name="twitter:description" content="Contest-optimized ON4KST features for VHF, UHF and SHF operation.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Features</p>
<h1>Contest tools, not just chat windows.</h1>
@@ -148,8 +149,7 @@
</article>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -157,7 +157,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -165,14 +164,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -180,5 +178,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Log Synchronization</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Log synchronization helps KST4Contest understand worked stations, active bands and contest context.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/features/log-sync/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Log synchronization helps KST4Contest understand worked stations, active bands and contest context.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/features/log-sync/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Log Synchronization">
<meta name="twitter:description" content="Log synchronization helps KST4Contest understand worked stations, active bands and contest context.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Logger Integration · since 1.31</p>
<h1>Log Synchronization</h1>
@@ -163,8 +164,7 @@
"name": "KST4Contest"
}
}
</script>
</main>
</script></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -172,7 +172,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -180,14 +179,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -195,5 +193,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Macros and Variables</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Macros reduce repetitive typing and help operators respond quickly during active ON4KST contest sessions.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/features/macros/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Macros reduce repetitive typing and help operators respond quickly during active ON4KST contest sessions.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/features/macros/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Macros and Variables">
<meta name="twitter:description" content="Macros reduce repetitive typing and help operators respond quickly during active ON4KST contest sessions.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Operator Speed · since 1.0</p>
<h1>Macros and Variables</h1>
@@ -162,8 +163,7 @@
"name": "KST4Contest"
}
}
</script>
</main>
</script></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -171,7 +171,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -179,14 +178,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -194,5 +192,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Priority Score System</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest ranks stations by activity, direction, sked context, band information, QRG hints and operator workflow signals.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/features/priority-score/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest ranks stations by activity, direction, sked context, band information, QRG hints and operator workflow signals.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/features/priority-score/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Priority Score System">
<meta name="twitter:description" content="KST4Contest ranks stations by activity, direction, sked context, band information, QRG hints and operator workflow signals.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Contest Workflow · since 1.40</p>
<h1>Priority Score System</h1>
@@ -191,8 +192,7 @@
"name": "KST4Contest"
}
}
</script>
</main>
</script></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -200,7 +200,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -208,14 +207,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -223,5 +221,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Sked Reminder</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest keeps skeds in focus while the operator handles chat traffic, logging, bands and aircraft scatter timing.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/features/sked-reminder/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest keeps skeds in focus while the operator handles chat traffic, logging, bands and aircraft scatter timing.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/features/sked-reminder/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Sked Reminder">
<meta name="twitter:description" content="KST4Contest keeps skeds in focus while the operator handles chat traffic, logging, bands and aircraft scatter timing.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Sked Management · since 1.40</p>
<h1>Sked Reminder</h1>
@@ -185,8 +186,7 @@
"name": "KST4Contest"
}
}
</script>
</main>
</script></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -194,7 +194,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -202,14 +201,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -217,5 +215,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Timeline View</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="The timeline view helps operators understand short propagation windows and candidate timing during active contest operation.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/features/timeline/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="The timeline view helps operators understand short propagation windows and candidate timing during active contest operation.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/features/timeline/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Timeline View">
<meta name="twitter:description" content="The timeline view helps operators understand short propagation windows and candidate timing during active contest operation.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Contest Awareness · since 1.40</p>
<h1>Timeline View</h1>
@@ -163,8 +164,7 @@
"name": "KST4Contest"
}
}
</script>
</main>
</script></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -172,7 +172,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -180,14 +179,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -195,5 +193,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+8
View File
@@ -7,6 +7,14 @@
<language>en</language>
<item>
<title>Hotfix Version 1.41.1</title>
<link>https://kst4contest.hamradioonline.de/news/2026-07-08-hotfix-version-1-41-1/</link>
<guid>https://kst4contest.hamradioonline.de/news/2026-07-08-hotfix-version-1-41-1/</guid>
<pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
<description>Hotfix of a bug in Version 1.41</description>
</item>
<item>
<title>KST4Contest Website Launch</title>
<link>https://kst4contest.hamradioonline.de/news/2026-07-website-launch/</link>
+27 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>KST4Contest Contest-Optimized ON4KST Chat Client</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest is a modern ON4KST chat client built for VHF, UHF and SHF contest operation.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest is a modern ON4KST chat client built for VHF, UHF and SHF contest operation.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="KST4Contest Contest-Optimized ON4KST Chat Client">
<meta name="twitter:description" content="KST4Contest is a modern ON4KST chat client built for VHF, UHF and SHF contest operation.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,24 +47,28 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero hero-split">
<div>
<canvas class="hero-radio-fx" data-hero-radio-fx aria-hidden="true"></canvas>
<div class="hero-fx-vignette" aria-hidden="true"></div>
<div class="hero-copy">
<p class="badge">The contest-optimized ON4KST client</p>
<h1>KST4Contest</h1>
<p class="lead">
@@ -237,6 +241,7 @@
<div class="actions">
<a class="button" href="/download/">Download KST4Contest</a>
<a class="button secondary" href="/features/">Explore features</a>
<a class="button ghost" href="/support/">❤️ Support development</a>
</div>
</div>
</section>
@@ -253,7 +258,7 @@
"description":"Contest-optimized ON4KST client for VHF, UHF and SHF amateur radio contests."
}
</script>
</main>
</main>
<footer class="site-footer">
<div class="footer-grid">
@@ -261,7 +266,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -269,14 +273,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -284,5 +287,9 @@
</div>
</div>
</footer>
<script src="/assets/js/hero-radio-fx.js?v=1783967730122" defer></script>
</body>
</html>
</html>
+537
View File
@@ -0,0 +1,537 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<praktiKST>
<latestVersion>
<versionNumber>1.411</versionNumber>
<adminMessage></adminMessage>
<majorChanges>- Score of Top-Priority Candidates in the user section
- Broadcast Messages (To ALL Station in Contest) in seperate Window</majorChanges>
<latestVersionPathOnWebserver>https://github.com/praktimarc/kst4contest/releases/download/v1.41.1/praktiKST-v1.41.1-windows-x64.zip</latestVersionPathOnWebserver>
</latestVersion>
<needUpdateSinceLastVersion>
<filename>nothing</filename>
</needUpdateSinceLastVersion>
<roadmap>
<featurerequest>
<featureDescription>Cyclic update of own name in chat to actual QRG</featureDescription>
<requestDate>2024-02</requestDate>
<state>open</state>
</featurerequest>
<featurerequest>
<featureDescription>Acoustic warnings</featureDescription>
<requestDate>2023-12</requestDate>
<state>added</state>
</featurerequest>
<featurerequest>
<featureDescription>Airscout support</featureDescription>
<requestDate>2023-03</requestDate>
<state>added</state>
</featurerequest>
<featurerequest>
<featureDescription>cyclic beacon messages in public chat</featureDescription>
<requestDate>2023-03</requestDate>
<state>added</state>
</featurerequest>
</roadmap>
<bugsReported>
<bug>
<bugdescription>
stations like ol7c-7 and ol7c-2 will not be marked as worked if only "ol7c"
is in the log. Need to interprete that. May ignore suffix starting with "-"</bugdescription>
<state>solved</state>
</bug>
<bug>
<bugdescription>
worked callsigns table is updated only once after connected to chat
Should be available directly after program started.
But Reset works all time</bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>feature request: language-specific strings, maybe based to an xml file
</bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>feature request: Macros
</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
Automatic elimination of calls already connected from the log
</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
Limitation of users beyond a certain QRB
</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
User division for QTF perhaps settable based on the antenna lobe
</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
Notification of new incoming users</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
Evidence of a call which was written but not responded to.
</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
direction based reachable-warnings:
if the chatclient detects a realised qso between two stations which both must have
directed their beams to my station, there should be an instant warning to me for
working at least one of the stations at the detected qrg</bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>feature request:
implement ASShowpath message to airacout;
by doubleclicking a callsign in the chatmember list, the path and plane state should be
provided by airscout </bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>feature request:
make chatlines copyable to the clipboard...</bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>feature request:
adding an activity detection for sensing if another chatmembers alive-state</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>On sql error close the connection to make a clean exit</bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>SETNAME /MYQRG in options menu doesnt work</bugdescription>
<state>solved</state>
</bug>
<bug>
<bugdescription>Disconnect button does not work (DL5ZK)</bugdescription>
<state>solved</state>
</bug>
<bug>
<bugdescription>Password-wrong error not displayed (DO5SA)</bugdescription>
<state>solved</state>
</bug>
<bug>
<bugdescription>/CQ isnt written on own PMs</bugdescription>
<state>solved</state>
</bug>
<bug>
<bugdescription>rightclick-textsnippets arent available in cq-table</bugdescription>
<state>solved</state>
</bug>
</bugsReported>
<changeLog>
<changedVersionNumber>1.411</changedVersionNumber>
<date>2026-07-08</date>
<description>Release v1.41.1</description>
<added></added>
<changed>Hotfix of Bug #56</changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.41</changedVersionNumber>
<date>2026-07-01</date>
<description>Release v1.41.0</description>
<added>- Score of Top-Priority Candidates in the user section
- Broadcast Messages (To ALL Station in Contest) in seperate Window</added>
<changed>Migration to Github
- Band and Frequency Detection per Chatmember — KST4Contest identifies which bands or frequencies a station appears active or reachable on based on chat data. This information is integrated into the display and prioritization.
- Band Alert — If a logged station could be of interest on other locally active bands, KST14Contest can alert the user.
- Sked Reminder ALERT — New reminder function for planned Skeds to ensure that agreed contacts are not forgotten during contest operation.
- Skedfail Button — Failed Skeds can be marked; this allows the station to be handled differently in terms of score and workflow.
- Win-Test SKED Push via UDP — Skeds can be passed to Win-Test via a LOCKSKED/ADDSKED/UNLOCKSKED sequence. This feature is configurable in the preferences and disabled by default.
- Win-Test Broadcast Defaults and SKed Workflow — Improved broadcast addresses, frequency formats, mode selection, and notes including locator/azimuth.
- PSTRotator Interface — KST4Contest received an interface to PSTRoter to better integrate antenna direction and rotator workflow.
- QSO /-Callsign Sniffer — Callsigns or QSO-relevant information can be detected and utilized for further workflow.
- Chat History at Startup — Historical chat data can be loaded upon startup, allowing the client to quickly gain relevant context for scoring, activity, and station identification.
- Station Map / Map View — New map view with station markers, your own location, locator/Maidenhead reference, connection line, and antenna/beam representation.
- Local Leaflet /-TileProxy Backend — The map function utilizes embedded Leaflet resources and a local TileProxy approach, making the map more portable and independent.
- Maidenhead /-Grid Overlay — The map can display locator/grid information; recently added Grid-Square coloring.
- Path /-Terrain /-Horizon Profile — Map documentation describes a path profile with terrain, Fresnel, and horizon information for the selected link.
- Tropo /-AS Reachability Filters — New filters and sorting were added for Tropospheric reachability, AirScoop availability, large locator fields, and priority.
- Reachability Band Selector — Added a band selection for range/Troposphere calculations.
- 4-Char Locator Filter — Locator evaluations can be reduced to 4-character fields; this improves overview and performance.
- KST Testserver Support — A KST server test server was added into the Source.
@praktimarc and @Rsclub22</changed>
<fixed>- [BUG]
- [BUG] Programmstart kann ohne selektieren keine Nachrichten senden
- KST4Contest crashes after to many messages or to much RAM Usage
- Scaling Problem at 1280x800 screen resolution</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.40</changedVersionNumber>
<date>2026-02-16</date>
<description>Much changes and fixes</description>
<added>
- win-test support (not deactivable yet, beta)
- QSO sniffer which filters changable callsigns lists messages to your pm window
- Chatmember Score system. Each chatmember gets a score depending to antenna direction, activity time,
activity msgcount, positive signals, active bands, active frequencies, his sked direction (degrees),
self handed rate, sked-made-rate and some others
- Chatmember Band and frequency recognition: now we list all active bands of each other chatmember
- Band alert for logged stations
- Sked reminder ALERT with automatic messages in 2+1 / 5+2+1 / 10+5+2+1 minutes
- PSTRotator interface
- AirPlane Timeline
- Skedfail button in furtherinfo panel
- Wintest logsynch interface can now be activated and deactivated in the preferences
- load chat history on startup
</added>
<changed>
- added save option: ServerDNS / Port
- added save option: activate/deactivate pstRotator interface
- added save option: activate/deactivate wintest interface
- added save option: callsign sniffer
- added save option: boolean GUI_darkModeActiveByDefault = false
- added AP notes at the internal DX cluster spots
- Generic Autoanswer fires maximum once in 45 seconds per rx callsign
</changed>
<fixed>
- Userlist sort automatically on new member signon
- Posonpill-messages now kills only exactly one Client instance (bugreport 9A2HM)
- Crash of wtkst by disconnection of kst4Contest
- wtKST crashes when kst4contest is running (many reporters)
- wintest: logged per band excludes pre worked band (bugreport SM6VTZ)
- PSTRotator SOCKET Close on DISCONNECT BUTTON works now
- AirScout Map had not been updated (bugreport SM0RJV)
- QTFDefault had been not saved correctly (bugreport SM0RJV)
- Show path in AS button worked only with fixed server Strings (bugreport 9A2HM)
- Version number had been displayed wrong
- suffixes like /p, -2, etc. problem now fixed everywhere
- Dark mode: passing for qrg fields fix
</fixed>
<removed>
- No more date display. Not needed. Saving space, only time is important
</removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.31</changedVersionNumber>
<date>2025-12-13</date>
<description>Much changes and a hotfix</description>
<added>
- win-test support (not deactivable yet, beta)
- pstRotator support (not deactivable yet, beta)
- airScout fix (calculations depends to cpu load now)
- QSO sniffer
</added>
<changed>
- DNS from www.on4kst.info to www.on4kst.org (hotfix, now configurable in preferences)
</changed>
<fixed>
- Endless loop in error case lets freeze the client
- PSTRotator SOCKET Close on DISCONNECT BUTTON
- Dark mode: passing for qrg fields fix
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.266</changedVersionNumber>
<date>2025-10-03</date>
<description>Airscout interface did not work with multi-signons</description>
<added>nothing</added>
<changed>nothing</changed>
<fixed>
Fixed by using raw callsign (without -2 suffix etc.) when communicating with AirScout.
Thank you very much for testing this, Kreso 9A2HM!
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.265</changedVersionNumber>
<date>2025-09-28</date>
<description>Direction buttons stay now colored, if activated</description>
<added>nothing</added>
<changed>nothing</changed>
<fixed>The buttons stay now colored, if activated</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.264</changedVersionNumber>
<date>2025-08-02</date>
<description>Changed the callsign recognition pattern for Simplelogfile</description>
<added>nothing</added>
<changed>nothing</changed>
<fixed>
Callsigns like S53CC, S51A etc. are now correctly marked as worked in SimpleLogFile.txt.
(bugreport Boris, S53CC)
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.263</changedVersionNumber>
<date>2025-06-08</date>
<description>Airscout communication and Loginname</description>
<added>nothing</added>
<changed>
Only entries with QRB lower than max-QRB are sent to AirScout in 60s intervals (was 12s for all).
</changed>
<fixed>
- AS communication message count hugely reduced
- Name in chat is now saveable
- Fixed AS client name (was hard-wired to "KST")
73 / DO5AMF
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.262</changedVersionNumber>
<date>2025-05-21</date>
<description>Freezes caused by getting messages before user login should be fixed now</description>
<added>nothing</added>
<changed>nothing</changed>
<fixed>
ON4KST delivers messages of stations not yet logged in. That caused a freeze at the message
processing engine which is now fixed.
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.26</changedVersionNumber>
<date>2025-05</date>
<description>Login to multiple Channels via single signon / spend some colors</description>
<added>
1. UI: Dark mode. Switch in "Window -> use dark mode"
2. Usage of two Chatcategories at the same time
3. opposite station multi-callsign login-tagging
73 / DO5AMF
</added>
<changed>
- Coloring mechanic of the software. Modify colors via css by yourself...
</changed>
<fixed>
- Station tagging fixed completely
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.251</changedVersionNumber>
<date>2025-02</date>
<description>BUGFIX of 1.25, tnx Steve Clements!</description>
<added>
- Steve spotted a problem in udp broadcast spot info reading, its now fixed!
73 / DO5AMF
</added>
<changed></changed>
<fixed>- Station tagging</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.25</changedVersionNumber>
<date>2025-02</date>
<description>Wishlist-time</description>
<added>
- New configuration Tab: Messagehandling (auto-answering, CQ qrg auto-answer)
- Coloured lines: new pm rows appear in red, fade rainbow-like to white over 30s (tnx Gianluca)
73 / DO5AMF
</added>
<changed></changed>
<fixed>
- Users with suffixes like "-2 and -70" are now correctly marked as worked
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.24</changedVersionNumber>
<date>2024-11</date>
<description>Wishlist-time</description>
<added>
- Button to show qrz.com profile of a selected station
- Button to show qrzcq.com profile of a selected station
</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.23</changedVersionNumber>
<date>2024-10</date>
<description>DXCluster Server is now implemented</description>
<added>
- DXCluster Server (tnx OMAAO): KST4Contest now provides a DXCluster server to feed
your log client with station-reachable warnings.
</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.22</changedVersionNumber>
<date>2024-05</date>
<description>Increase usability, fixed AS button</description>
<added>- Variables (tnx OMAAO): MYLOCATORSHORT, MYQRGSHORT, QRZNAME</added>
<changed>- Sendtext-field focus on callsign click</changed>
<fixed>
- Worked-station-filter (tnx Gianluca): filter is now live
- Chatters list sorting by QRB (tnx Alessandro): fixed to numeric sort
- Airscout showpath button now maximizes AS and shows the path
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.21</changedVersionNumber>
<date>2024-04</date>
<description>Increase usability</description>
<added></added>
<changed>
- Window sizes and divider positions are now stored and restored on next startup
- Filters section is now a flowpane for lower resolutions
</changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.2</changedVersionNumber>
<date>2024-04</date>
<description>Increase usability</description>
<added>
- Selectable bands
- Unworkable tags for each callsign per band
- QTF-Arrow on the "show path in AS" button
</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.1</changedVersionNumber>
<date>2024-03</date>
<description>Increased workflow</description>
<added>
- Reachable warning function
- Variables FIRSTAP and SECONDAP for predefined texts
- Marking new connected stations (bold) and away-state (brackets)
- Activity-timer since last message of a chatter
</added>
<changed>- QRB/QTF info in pm-window</changed>
<fixed>
- Behaviour of messagefilter-radiobutton corrected (tnx Gian)
- Error sending macros on selected stations in pm window (tnx Gian)
</fixed>
<removed>- boot sound</removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.0</changedVersionNumber>
<date>2024-02</date>
<description>First stable release</description>
<added>
- audio notification support
- update-available warning
- changelog displaying
- known-bugs displaying
- message filters for selected station
- stationlist-filters (show by qtf / textstring, hide by qrb, qtf, workedstate)
- 10 macros (textsnippets available by hitting strg+numberkey)
</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.9</changedVersionNumber>
<date>2024-02</date>
<description>Now the chat is disconnectable without closing it</description>
<added></added>
<changed></changed>
<fixed>disconnect-button, Textsnippets in CQ Window, "/CQ" at click on own sendtexts,
setname "qrg" works now</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.8</changedVersionNumber>
<date>2023-10</date>
<description>savesettings function and disconnect function added. MYCALL-Highlighting fixed</description>
<added>savesettings function, disconnect function</added>
<changed></changed>
<fixed>MYCALL-Highlighting</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.7</changedVersionNumber>
<date>2023-07</date>
<description>Internal database for worked stations, shortcut and textsnippet editor</description>
<added>database, shortcut-editor, textsnipped-editor</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.6</changedVersionNumber>
<date>2023-07</date>
<description>Added periodically sent beacon function</description>
<added>beacon to public channel</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.5</changedVersionNumber>
<date>2023-02</date>
<description>Airscout interface, UDP interfaces for worked-station data and TRX info, shortcuts</description>
<added>Airscout-interface, packetreceiver for ucxlog, n1mm, qartest</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.4</changedVersionNumber>
<date>2022-12</date>
<description>Worked info by logfile via universal pattern matching logfile interpreter</description>
<added>simple-logfile</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.3</changedVersionNumber>
<date>2022-10</date>
<description>Added a GUI, chat was text based only</description>
<added>Graphical user interface</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.2</changedVersionNumber>
<date>2022-10</date>
<description>Pattern matching system for frequency information, frequency collection per chatmember</description>
<added>frequency-extraction for every callsign</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.15</changedVersionNumber>
<date>2022-09</date>
<description>Researched the Chatprotocol and developed the base communication with the chatserver</description>
<added></added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.1</changedVersionNumber>
<date>2022-09</date>
<description>Primary architecture of the chat, message bus system for handling the message queues</description>
<added></added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
</praktiKST>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Legal Notice | KST4Contest</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Legal notice for the KST4Contest website.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/legal-notice/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Legal notice for the KST4Contest website.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/legal-notice/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Legal Notice | KST4Contest">
<meta name="twitter:description" content="Legal notice for the KST4Contest website.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Legal Notice</p>
<h1>Legal Notice</h1>
@@ -99,8 +100,7 @@
<a href="https://github.com/praktimarc/kst4contest">GitHub</a>.
</p>
</article>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -108,7 +108,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -116,14 +115,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -131,5 +129,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/airscout-integration/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/airscout-integration/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/de/">← Back to manual overview</a></p>
@@ -173,8 +174,7 @@
<p>In der Benutzerliste gibt es einen Button mit einem Pfeil, der die Richtung (QTF) zur ausgewählten Station anzeigt. Ein Klick maximiert AirScout und zeigt den Pfad mit reflektierbaren Flugzeugen zum ausgewählten Gesprächspartner.</p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -182,7 +182,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -190,14 +189,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -205,5 +203,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/benutzeroberflaeche/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/benutzeroberflaeche/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/de/">← Back to manual overview</a></p>
@@ -175,8 +176,7 @@
</ul>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -184,7 +184,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -192,14 +191,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -207,5 +205,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/changelog/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/changelog/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/de/">← Back to manual overview</a></p>
@@ -310,8 +311,7 @@
</ul>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -319,7 +319,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -327,14 +326,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -342,5 +340,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/dx-cluster-server/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/dx-cluster-server/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/de/">← Back to manual overview</a></p>
@@ -137,8 +138,7 @@
<p>Weitere Testergebnisse sind willkommen bitte per E-Mail an DO5AMF melden.</p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -146,7 +146,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -154,14 +153,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -169,5 +167,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/funktionen/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/funktionen/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/de/">← Back to manual overview</a></p>
@@ -259,8 +260,7 @@
<p>Damit werden unbrauchbare, abgeschnittene Fenster beim Wechsel zwischen Geräten oder Monitoren verhindert.</p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -268,7 +268,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -276,14 +275,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -291,5 +289,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/home/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/home/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/de/">← Back to manual overview</a></p>
@@ -166,8 +167,7 @@
<p>Besonderer Dank gilt: Gianluca Costantino (IU3OAR), Alessandro Murador (IZ3VTH), Reczetár István (HA1FV), OM0AAO (Viliam Petrik, DX-Cluster-Idee), DC9DJ (Konrad Neitzel, Projektstruktur), DO5ALF (Andreas, Webmaster <a href="http://funkerportal.de">funkerportal.de</a>), PE0WGA (Franz van Velzen, Tester) sowie allen weiteren Testern und Ideengebern.</p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -175,7 +175,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -183,14 +182,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -198,5 +196,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>KST4Contest Deutsches Handbuch</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Deutsches Benutzerhandbuch für KST4Contest.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Deutsches Benutzerhandbuch für KST4Contest.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="KST4Contest Deutsches Handbuch">
<meta name="twitter:description" content="Deutsches Benutzerhandbuch für KST4Contest.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<h1>Deutsches Handbuch</h1>
<p>Das Handbuch wird zentral in GitHub gepflegt und als Wiki, PDF und Online-Dokumentation veröffentlicht.</p>
@@ -162,8 +163,7 @@
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -171,7 +171,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -179,14 +178,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -194,5 +192,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/installation/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/installation/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/de/">← Back to manual overview</a></p>
@@ -281,8 +282,7 @@ flatpak install kst4contest de.x08.KST4Contest//nightly
<p><em>(Gemeldet von PE0WGA, Franz van Velzen danke!)</em></p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -290,7 +290,6 @@ flatpak install kst4contest de.x08.KST4Contest//nightly
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -298,14 +297,13 @@ flatpak install kst4contest de.x08.KST4Contest//nightly
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -313,5 +311,7 @@ flatpak install kst4contest de.x08.KST4Contest//nightly
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/konfiguration/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/konfiguration/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/de/">← Back to manual overview</a></p>
@@ -217,8 +218,7 @@ Zudem wird die <strong>primäre Chat-Kategorie</strong> (z. B. IARU Region 1 VHF
</ul>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -226,7 +226,6 @@ Zudem wird die <strong>primäre Chat-Kategorie</strong> (z. B. IARU Region 1 VHF
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -234,14 +233,13 @@ Zudem wird die <strong>primäre Chat-Kategorie</strong> (z. B. IARU Region 1 VHF
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -249,5 +247,7 @@ Zudem wird die <strong>primäre Chat-Kategorie</strong> (z. B. IARU Region 1 VHF
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/log-synchronisation/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/log-synchronisation/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/de/">← Back to manual overview</a></p>
@@ -195,8 +196,7 @@
<p>Vor jedem neuen Contest: Datenbank zurücksetzen! → <a href="Konfiguration#worked-station-database-settings">Konfiguration Worked Station Database Settings</a></p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -204,7 +204,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -212,14 +211,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -227,5 +225,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/de/makros-und-variablen/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/de/makros-und-variablen/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/de/">← Back to manual overview</a></p>
@@ -206,8 +207,7 @@
<p>Ohne manuelle Tipparbeit, ohne Fehler, ohne Unterbrechung des CQ-Rufens.</p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -215,7 +215,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -223,14 +222,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -238,5 +236,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/airscout-integration/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/airscout-integration/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/en/">← Back to manual overview</a></p>
@@ -173,8 +174,7 @@
<p>In the user list there is a button with an arrow showing the direction (QTF) to the selected station. Clicking it maximises AirScout and shows the path with reflectable aircraft to the selected contact.</p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -182,7 +182,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -190,14 +189,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -205,5 +203,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/changelog/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/changelog/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/en/">← Back to manual overview</a></p>
@@ -310,8 +311,7 @@
</ul>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -319,7 +319,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -327,14 +326,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -342,5 +340,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/configuration/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/configuration/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/en/">← Back to manual overview</a></p>
@@ -217,8 +218,7 @@ Also, select the <strong>primary chat category</strong> (e.g., IARU Region 1 VHF
</ul>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -226,7 +226,6 @@ Also, select the <strong>primary chat category</strong> (e.g., IARU Region 1 VHF
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -234,14 +233,13 @@ Also, select the <strong>primary chat category</strong> (e.g., IARU Region 1 VHF
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -249,5 +247,7 @@ Also, select the <strong>primary chat category</strong> (e.g., IARU Region 1 VHF
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/dx-cluster-server/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/dx-cluster-server/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/en/">← Back to manual overview</a></p>
@@ -137,8 +138,7 @@
<p>Further test reports are welcome please send by email to DO5AMF.</p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -146,7 +146,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -154,14 +153,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -169,5 +167,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/features/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/features/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/en/">← Back to manual overview</a></p>
@@ -258,8 +259,7 @@
<p>This prevents unusable oversized windows when switching between machines or monitors.</p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -267,7 +267,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -275,14 +274,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -290,5 +288,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/home/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/home/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/en/">← Back to manual overview</a></p>
@@ -166,8 +167,7 @@
<p>Special thanks to: Gianluca Costantino (IU3OAR), Alessandro Murador (IZ3VTH), Reczetár István (HA1FV), OM0AAO (Viliam Petrik, DX cluster idea), DC9DJ (Konrad Neitzel, project structure), DO5ALF (Andreas, webmaster <a href="http://funkerportal.de">funkerportal.de</a>), PE0WGA (Franz van Velzen, tester) and all other testers and contributors.</p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -175,7 +175,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -183,14 +182,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -198,5 +196,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>KST4Contest English Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="English user manual for KST4Contest.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="English user manual for KST4Contest.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="KST4Contest English Manual">
<meta name="twitter:description" content="English user manual for KST4Contest.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<h1>English Manual</h1>
<p>The manual is maintained centrally in GitHub and published as Wiki, PDF and online documentation.</p>
@@ -162,8 +163,7 @@
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -171,7 +171,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -179,14 +178,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -194,5 +192,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/installation/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/installation/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/en/">← Back to manual overview</a></p>
@@ -281,8 +282,7 @@ flatpak install kst4contest de.x08.KST4Contest//nightly
<p><em>(Reported by PE0WGA, Franz van Velzen thank you!)</em></p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -290,7 +290,6 @@ flatpak install kst4contest de.x08.KST4Contest//nightly
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -298,14 +297,13 @@ flatpak install kst4contest de.x08.KST4Contest//nightly
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -313,5 +311,7 @@ flatpak install kst4contest de.x08.KST4Contest//nightly
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/log-sync/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/log-sync/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/en/">← Back to manual overview</a></p>
@@ -195,8 +196,7 @@
<p>Before each new contest: reset the database! → <a href="Configuration#worked-station-database-settings">Configuration Worked Station Database Settings</a></p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -204,7 +204,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -212,14 +211,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -227,5 +225,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/macros-and-variables/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/macros-and-variables/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/en/">← Back to manual overview</a></p>
@@ -206,8 +207,7 @@
<p>No manual typing, no errors, no interruption to CQ calling.</p>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -215,7 +215,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -223,14 +222,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -238,5 +236,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ manual.title }} | KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/en/user-interface/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="KST4Contest manual page: {{ manual.title }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/en/user-interface/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="section">
<div class="card manual-content">
<p><a href="/manual/en/">← Back to manual overview</a></p>
@@ -175,8 +176,7 @@
</ul>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -184,7 +184,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -192,14 +191,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -207,5 +205,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>KST4Contest Manual</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Online manual for KST4Contest, the contest-optimized ON4KST chat client.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/manual/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Online manual for KST4Contest, the contest-optimized ON4KST chat client.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/manual/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="KST4Contest Manual">
<meta name="twitter:description" content="Online manual for KST4Contest, the contest-optimized ON4KST chat client.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<h1>KST4Contest Manual</h1>
<p>
@@ -74,8 +75,7 @@
<a class="button" href="/manual/en/">English Manual</a>
<a class="button secondary" href="/manual/de/">Deutsches Handbuch</a>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -83,7 +83,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -91,14 +90,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -106,5 +104,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
@@ -0,0 +1,111 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hotfix Version 1.41.1</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest is a contest-optimized ON4KST chat client for VHF, UHF and SHF ham radio contests.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/news/2026-07-08-hotfix-version-1-41-1/">
<meta property="og:type" content="website">
<meta property="og:title" content="Hotfix Version 1.41.1">
<meta property="og:description" content="Contest-optimized ON4KST chat client for VHF, UHF and SHF ham radio contests.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/news/2026-07-08-hotfix-version-1-41-1/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Hotfix Version 1.41.1">
<meta name="twitter:description" content="Contest-optimized ON4KST chat client for VHF, UHF and SHF ham radio contests.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
</head>
<body>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
<a href="/features/">Features</a>
<a href="/screenshots/">Screenshots</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">News · July 8, 2026</p>
<h1>Hotfix Version 1.41.1</h1>
<p class="lead">Hotfix of a bug in Version 1.41</p>
</section>
<section class="section narrow">
<article class="card content-card">
<h2>Version 1.41.1</h2>
<p>We had a bug, where KST4Contest would clear out the text field, where you would send your messages after a period. It also happend to set your Cursor back to the Sending Field, when you were filtering for a station. We fixed this bug today. The new release is <a href="https://github.com/praktimarc/kst4contest/releases/tag/v1.41.1">here</a>. You can also download the artifacts as always via the <a href="/download/">Downloads-Page</a>.</p>
</article>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
<div>
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
<p><a href="/download/">Download</a></p>
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
<p><a href="/privacy/">Privacy Policy</a></p>
</div>
</div>
</footer>
</body>
</html>
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>KST4Contest Website Launch</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="KST4Contest is a contest-optimized ON4KST chat client for VHF, UHF and SHF ham radio contests.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/news/2026-07-website-launch/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Contest-optimized ON4KST chat client for VHF, UHF and SHF ham radio contests.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/news/2026-07-website-launch/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="KST4Contest Website Launch">
<meta name="twitter:description" content="Contest-optimized ON4KST chat client for VHF, UHF and SHF ham radio contests.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">News · July 7, 2026</p>
<h1>KST4Contest Website Launch</h1>
@@ -79,8 +80,7 @@
<p>Documentation, release information, feature pages and future updates can now grow from a shared content model.</p>
</article>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -88,7 +88,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -96,14 +95,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -111,5 +109,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+27 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>KST4Contest News</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="News, release updates and development notes for KST4Contest.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/news/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="News, release updates and development notes for KST4Contest.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/news/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="KST4Contest News">
<meta name="twitter:description" content="News, release updates and development notes for KST4Contest.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">News</p>
<h1>Development updates and release notes.</h1>
@@ -74,6 +75,13 @@
<section class="section">
<div class="grid">
<article class="card">
<p class="eyebrow">July 8, 2026</p>
<h3><a href="/news/2026-07-08-hotfix-version-1-41-1/">Hotfix Version 1.41.1</a></h3>
<p>Hotfix of a bug in Version 1.41</p>
<a href="/news/2026-07-08-hotfix-version-1-41-1/">Read more →</a>
</article>
<article class="card">
<p class="eyebrow">July 7, 2026</p>
<h3><a href="/news/2026-07-website-launch/">KST4Contest Website Launch</a></h3>
@@ -82,8 +90,7 @@
</article>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -91,7 +98,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -99,14 +105,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -114,5 +119,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+36 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>Privacy Policy | KST4Contest</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Privacy policy for the KST4Contest website.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/privacy/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Privacy policy for the KST4Contest website.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/privacy/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Privacy Policy | KST4Contest">
<meta name="twitter:description" content="Privacy policy for the KST4Contest website.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Privacy</p>
<h1>Privacy Policy</h1>
@@ -103,6 +104,22 @@
GitHub is responsible for its own data processing.
</p>
<h2>
Support via Ko-fi
</h2>
<p>
This website links to Ko-fi for voluntary financial support.
Payments are processed entirely by Ko-fi and its payment providers.
KST4Contest itself never receives or processes payment information.
</p>
<h2>Your rights</h2>
<p>
You may have the right to access, rectification, erasure, restriction of processing,
@@ -114,8 +131,7 @@
You have the right to lodge a complaint with a competent data protection supervisory authority.
</p>
</article>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -123,7 +139,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -131,14 +146,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -146,5 +160,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+258
View File
@@ -0,0 +1,258 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>KST4Contest Roadmap</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Planned enhancements for upcoming KST4Contest versions, generated from GitHub milestones and issues.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/roadmap/">
<meta property="og:type" content="website">
<meta property="og:title" content="KST4Contest Roadmap">
<meta property="og:description" content="Planned enhancements for upcoming KST4Contest versions, generated from GitHub milestones and issues.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/roadmap/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="KST4Contest Roadmap">
<meta name="twitter:description" content="Planned enhancements for upcoming KST4Contest versions, generated from GitHub milestones and issues.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
</head>
<body>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
<a href="/features/">Features</a>
<a href="/screenshots/">Screenshots</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Roadmap</p>
<h1>What's planned next.</h1>
<p class="lead">
Generated automatically from the "enhancement" issues in our
<a href="https://github.com/praktimarc/kst4contest">GitHub repository</a>,
grouped by their target version milestone.
</p>
</section>
<section class="section narrow">
<article class="card content-card roadmap-version">
<h2><a href="https://github.com/praktimarc/kst4contest/milestone/2">v1.42</a></h2>
<p>next Version</p>
<ul>
<li>
<a href="https://github.com/praktimarc/kst4contest/issues/52">[FEATURE] center horizontal slider</a>
</li>
<li class="roadmap-done">
<a href="https://github.com/praktimarc/kst4contest/issues/49">[FEATURE] QTF degree, manual change</a>
</li>
<li class="roadmap-done">
<a href="https://github.com/praktimarc/kst4contest/issues/22">Displaying where an Station is active.</a>
</li>
</ul>
</article>
<article class="card content-card roadmap-version">
<h2><a href="https://github.com/praktimarc/kst4contest/milestone/3">v1.50</a></h2>
<p>Greater Overhaul</p>
<ul>
<li>
<a href="https://github.com/praktimarc/kst4contest/issues/57">[FEATURE] User Config</a>
</li>
<li>
<a href="https://github.com/praktimarc/kst4contest/issues/21">QRB greater than Distance Button in the Userfield</a>
</li>
<li>
<a href="https://github.com/praktimarc/kst4contest/issues/20">i18n - Possible Translations?</a>
</li>
</ul>
</article>
<article class="card content-card roadmap-version">
<h2><a href="https://github.com/praktimarc/kst4contest/milestone/1">v1.41</a></h2>
<p>Latest Version</p>
<p class="eyebrow">Released July 5, 2026</p>
<ul>
<li class="roadmap-done">
<a href="https://github.com/praktimarc/kst4contest/issues/23">Score of Top-Priority Candidates in the user section</a>
</li>
</ul>
</article>
<article class="card content-card roadmap-version">
<h2>Planned, no version yet</h2>
<ul>
<li>
<a href="https://github.com/praktimarc/kst4contest/issues/62">Organizational enhacement: Test coverage for all features</a>
</li>
<li>
<a href="https://github.com/praktimarc/kst4contest/issues/61">Organizational enhancement: Updating</a>
</li>
<li>
<a href="https://github.com/praktimarc/kst4contest/issues/59">[FEATURE] window control</a>
</li>
<li>
<a href="https://github.com/praktimarc/kst4contest/issues/55">[FEATURE] Reset-Button for the map</a>
</li>
<li>
<a href="https://github.com/praktimarc/kst4contest/issues/47">[FEATURE] Sked push for UCXLOG</a>
</li>
<li>
<a href="https://github.com/praktimarc/kst4contest/issues/46">[FEATURE] SO2R Operation</a>
</li>
<li class="roadmap-done">
<a href="https://github.com/praktimarc/kst4contest/issues/19">Broadcast Messages (To ALL Station in Contest) in seperate Window</a> (not planned)
</li>
</ul>
</article>
</section>
<section class="section">
<div class="cta-panel">
<p class="eyebrow">
Community
</p>
<h2>
Development is driven by the community.
</h2>
<p>
Many roadmap items require
time,
online services
and infrastructure.
Community support helps accelerate development.
</p>
<div class="actions">
<a class="button"
href="/support/">
❤️ Support Development
</a>
</div>
</div>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
<div>
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
<p><a href="/download/">Download</a></p>
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
<p><a href="/privacy/">Privacy Policy</a></p>
</div>
</div>
</footer>
</body>
</html>
+20 -20
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>KST4Contest Screenshots</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Screenshots of KST4Contest, the contest-optimized ON4KST chat client for VHF, UHF and SHF operators.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/screenshots/">
@@ -12,34 +13,33 @@
<meta property="og:description" content="Screenshots of KST4Contest, the contest-optimized ON4KST chat client for VHF, UHF and SHF operators.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/screenshots/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="KST4Contest Screenshots">
<meta name="twitter:description" content="Screenshots of KST4Contest, the contest-optimized ON4KST chat client for VHF, UHF and SHF operators.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783425387839">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
@@ -47,22 +47,23 @@
<a href="/screenshots/">Screenshots</a>
<a href="/download/">Download</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Screenshots</p>
<h1>See the contest workflow.</h1>
@@ -100,8 +101,7 @@
</article>
</div>
</section>
</main>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
@@ -109,7 +109,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -117,14 +116,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -132,5 +130,7 @@
</div>
</div>
</footer>
</body>
</html>
</html>
+87 -75
View File
@@ -1,91 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://kst4contest.hamradioonline.de/faq/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/de/airscout-integration/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/de/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/en/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/airscout/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/dual-chat/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/dx-cluster/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/log-sync/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/macros/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/priority-score/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/sked-reminder/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/timeline/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/screenshots/</loc>
<lastmod>2026-07-06</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/news/2026-07-website-launch/</loc>
<lastmod>2026-07-07</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/news/2026-07-08-hotfix-version-1-41-1/</loc>
<lastmod>2026-07-08</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/about/</loc>
<lastmod>2026-07-07</lastmod>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/contact/</loc>
<lastmod>2026-07-07</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/legal-notice/</loc>
<lastmod>2026-07-07</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/privacy/</loc>
<lastmod>2026-07-07</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/</loc>
<lastmod>2026-07-07</lastmod>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/download/</loc>
<lastmod>2026-07-07</lastmod>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/faq/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/airscout/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/dual-chat/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/dx-cluster/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/log-sync/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/macros/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/priority-score/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/sked-reminder/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/timeline/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/legal-notice/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/de/airscout-integration/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/de/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/en/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/privacy/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/roadmap/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/screenshots/</loc>
<lastmod>2026-07-13</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/support/</loc>
<lastmod>2026-07-13</lastmod>
</url>
</urlset>
+268
View File
@@ -0,0 +1,268 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Support KST4Contest</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="Help support the ongoing development of KST4Contest.">
<link rel="canonical" href="https://kst4contest.hamradioonline.de/support/">
<meta property="og:type" content="website">
<meta property="og:title" content="Support KST4Contest">
<meta property="og:description" content="Help support the ongoing development of KST4Contest.">
<meta property="og:url" content="https://kst4contest.hamradioonline.de/support/">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Support KST4Contest">
<meta name="twitter:description" content="Help support the ongoing development of KST4Contest.">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v=1783967730122">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
</head>
<body>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav" aria-label="Main navigation">
<a href="/">Home</a>
<a href="/features/">Features</a>
<a href="/screenshots/">Screenshots</a>
<a href="/manual/">Manual</a>
<a href="/news/">News</a>
<a href="/roadmap/">Roadmap</a>
<a href="/about/">About</a>
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
<main>
<section class="hero">
<p class="badge">Support Development</p>
<h1>Help shape the future of KST4Contest.</h1>
<p class="lead">
KST4Contest is an independent open-source project developed in my free time.
Your support helps cover infrastructure costs and enables new contest-oriented
features that would otherwise be difficult to implement.
</p>
<div class="actions">
<a class="button"
href="https://ko-fi.com/praktimarc"
target="_blank"
rel="noopener">
❤️ Support on Ko-fi
</a>
<a class="button secondary"
href="https://github.com/praktimarc/kst4contest">
View Source Code
</a>
</div>
</section>
<section class="section">
<div class="section-heading">
<p class="eyebrow">What previous support has enabled</p>
<h2>Real improvements funded by the community.</h2>
</div>
<div class="grid">
<article class="card">
<h3>🛰 PSTRotator Integration</h3>
<p>
Support helped justify the investment into rotor hardware and the required
PSTRotator license. Today KST4Contest can automatically control antennas
during contest operation.
</p>
</article>
<article class="card">
<h3>🗺 Interactive Map</h3>
<p>
The interactive station map required commercial map API usage.
Community support helped cover those API costs and made the feature possible.
</p>
</article>
</div>
</section>
<section class="section">
<div class="section-heading">
<p class="eyebrow">Future goals</p>
<h2>What future support will fund</h2>
</div>
<div class="grid">
<article class="card">
<h3>☁ Map API</h3>
<p>
Keeping the online map service available requires ongoing API usage.
</p>
</article>
<article class="card">
<h3>📡 Tropo Server</h3>
<p>
Future propagation prediction services and server-side calculations
will require dedicated hosting infrastructure.
</p>
</article>
<article class="card">
<h3>🔌 New Integrations</h3>
<p>
Development of additional interfaces, logging software integrations,
contest tools and automation features.
</p>
</article>
<article class="card">
<h3>🚀 Faster Development</h3>
<p>
More development time means more contest-oriented features,
better testing and better documentation.
</p>
</article>
</div>
</section>
<section class="section">
<div class="cta-panel">
<p class="eyebrow">Every contribution helps</p>
<h2>Whether it's one coffee or ongoing support.</h2>
<p>
Your support directly improves KST4Contest.
Unlike commercial software, every new feature is driven by real contest
experience and community feedback.
</p>
<div class="actions">
<a class="button"
href="https://ko-fi.com/praktimarc"
target="_blank"
rel="noopener">
❤️ Support KST4Contest
</a>
</div>
</div>
</section></main>
<footer class="site-footer">
<div class="footer-grid">
<div>
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
<p><a href="/download/">Download</a></p>
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
<p><a href="/privacy/">Privacy Policy</a></p>
</div>
</div>
</footer>
</body>
</html>
+110 -83
View File
@@ -1,85 +1,112 @@
const latestTag = "v1.41.0";
const base = `https://github.com/praktimarc/kst4contest/releases/download/${latestTag}`;
const REPO = "praktimarc/kst4contest";
const API = `https://api.github.com/repos/${REPO}`;
const FALLBACK_TAG = "v1.41.1";
module.exports = [
{
os: "Windows",
format: "ZIP x64",
icon: "🪟",
recommended: true,
note: "Best choice for most Windows users.",
url: `${base}/praktiKST-${latestTag}-windows-x64.zip`
},
{
os: "Linux",
format: "Flatpak (.flatpakref)",
icon: "🐧",
recommended: true,
note: "Recommended for most Linux users. Open the file with GNOME Software / Discover, or run: flatpak install de.x08.KST4Contest.flatpakref",
url: `${base}/de.x08.KST4Contest.flatpakref`
},
{
os: "Linux",
format: "AppImage x86_64",
icon: "🐧",
recommended: false,
note: "Portable Linux build without package installation.",
url: `${base}/KST4Contest-${latestTag}-linux-x86_64.AppImage`
},
{
os: "Debian / Ubuntu",
format: "DEB amd64",
icon: "📦",
recommended: false,
note: "Native package for Debian and Ubuntu based systems.",
url: `${base}/KST4Contest-${latestTag}-debian-amd64.deb`
},
{
os: "Fedora",
format: "RPM x86_64",
icon: "📦",
recommended: false,
note: "Native package for Fedora/RPM based systems.",
url: `${base}/KST4Contest-${latestTag}-fedora-x86_64.rpm`
},
{
os: "Arch Linux",
format: "pkg.tar.zst",
icon: "📦",
recommended: false,
note: "Package build for Arch Linux users.",
url: `${base}/KST4Contest-${latestTag}-archlinux-x86_64.pkg.tar.zst`
},
{
os: "macOS Apple Silicon",
format: "DMG arm64",
icon: "🍎",
recommended: false,
note: "For Apple Silicon Macs.",
url: `${base}/KST4Contest-${latestTag}-macos-arm64.dmg`
},
{
os: "macOS Intel",
format: "DMG x86_64",
icon: "🍎",
recommended: false,
note: "For Intel-based Macs.",
url: `${base}/KST4Contest-${latestTag}-macos-x86_64.dmg`
},
{
os: "Manual English",
format: "PDF",
icon: "📘",
recommended: false,
note: "English PDF manual.",
url: `${base}/KST4Contest-${latestTag}-manual-en.pdf`
},
{
os: "Manual German",
format: "PDF",
icon: "📘",
recommended: false,
note: "German PDF manual.",
url: `${base}/KST4Contest-${latestTag}-manual-de.pdf`
async function fetchLatestReleaseTag() {
const headers = { Accept: "application/vnd.github+json" };
if (process.env.GITHUB_TOKEN) {
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
}
];
try {
const res = await fetch(`${API}/releases/latest`, { headers });
if (!res.ok) {
throw new Error(`GitHub API /releases/latest failed: ${res.status}`);
}
const release = await res.json();
return release.tag_name || FALLBACK_TAG;
} catch (err) {
console.warn(`[downloads] Could not load latest release tag, falling back to ${FALLBACK_TAG}: ${err.message}`);
return FALLBACK_TAG;
}
}
// Builds the download list from the latest GitHub release tag so it never
// drifts out of sync with what tagged-release.yml actually published.
module.exports = async function () {
const latestTag = await fetchLatestReleaseTag();
const base = `https://github.com/${REPO}/releases/download/${latestTag}`;
return [
{
os: "Windows",
format: "ZIP x64",
icon: "🪟",
recommended: true,
note: "Best choice for most Windows users.",
url: `${base}/praktiKST-${latestTag}-windows-x64.zip`
},
{
os: "Linux",
format: "Flatpak (.flatpakref)",
icon: "🐧",
recommended: true,
note: "Recommended for most Linux users. Open the file with GNOME Software / Discover, or run: flatpak install de.x08.KST4Contest.flatpakref",
url: `${base}/de.x08.KST4Contest.flatpakref`
},
{
os: "Linux",
format: "AppImage x86_64",
icon: "🐧",
recommended: false,
note: "Portable Linux build without package installation.",
url: `${base}/KST4Contest-${latestTag}-linux-x86_64.AppImage`
},
{
os: "Debian / Ubuntu",
format: "DEB amd64",
icon: "📦",
recommended: false,
note: "Native package for Debian and Ubuntu based systems.",
url: `${base}/KST4Contest-${latestTag}-debian-amd64.deb`
},
{
os: "Fedora",
format: "RPM x86_64",
icon: "📦",
recommended: false,
note: "Native package for Fedora/RPM based systems.",
url: `${base}/KST4Contest-${latestTag}-fedora-x86_64.rpm`
},
{
os: "Arch Linux",
format: "pkg.tar.zst",
icon: "📦",
recommended: false,
note: "Package build for Arch Linux users.",
url: `${base}/KST4Contest-${latestTag}-archlinux-x86_64.pkg.tar.zst`
},
{
os: "macOS Apple Silicon",
format: "DMG arm64",
icon: "🍎",
recommended: false,
note: "For Apple Silicon Macs.",
url: `${base}/KST4Contest-${latestTag}-macos-arm64.dmg`
},
{
os: "macOS Intel",
format: "DMG x86_64",
icon: "🍎",
recommended: false,
note: "For Intel-based Macs.",
url: `${base}/KST4Contest-${latestTag}-macos-x86_64.dmg`
},
{
os: "Manual English",
format: "PDF",
icon: "📘",
recommended: false,
note: "English PDF manual.",
url: `${base}/KST4Contest-${latestTag}-manual-en.pdf`
},
{
os: "Manual German",
format: "PDF",
icon: "📘",
recommended: false,
note: "German PDF manual.",
url: `${base}/KST4Contest-${latestTag}-manual-de.pdf`
}
];
};
+4 -1
View File
@@ -4,6 +4,9 @@ module.exports = [
{ title: "Screenshots", url: "/screenshots/" },
{ title: "Manual", url: "/manual/" },
{ title: "News", url: "/news/" },
{ title: "Roadmap", url: "/roadmap/" },
{ title: "About", url: "/about/" },
{ title: "FAQ", url: "/faq/" }
{ title: "FAQ", url: "/faq/" },
{ title: "Support", url: "/support/"
}
];
+84
View File
@@ -0,0 +1,84 @@
const REPO = "praktimarc/kst4contest";
const API = `https://api.github.com/repos/${REPO}`;
async function githubGet(path) {
const headers = { Accept: "application/vnd.github+json" };
if (process.env.GITHUB_TOKEN) {
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
}
const res = await fetch(`${API}${path}`, { headers });
if (!res.ok) {
throw new Error(`GitHub API ${path} failed: ${res.status}`);
}
return res.json();
}
// Builds the roadmap from all GitHub issues tagged "enhancement", grouped by
// their version milestone (open or already released). Issues without a
// milestone are shown as planned but with no assigned version yet. Issues
// closed as "not planned" are kept, struck through and labeled accordingly.
// Falls back to an empty list if the API is unreachable (e.g. offline build)
// so the site build never fails.
module.exports = async function () {
try {
const issues = await githubGet(
"/issues?labels=enhancement&state=all&per_page=100"
);
const groups = new Map();
for (const issue of issues) {
if (issue.pull_request) continue;
const milestone = issue.milestone;
const key = milestone ? milestone.number : "unscheduled";
if (!groups.has(key)) {
groups.set(key, {
version: milestone ? milestone.title : null,
description: milestone ? milestone.description : null,
url: milestone ? milestone.html_url : null,
released: milestone ? milestone.state === "closed" : false,
dueOn: milestone && milestone.due_on ? new Date(milestone.due_on) : null,
releasedOn: milestone && milestone.closed_at ? new Date(milestone.closed_at) : null,
enhancements: []
});
}
const notPlanned = issue.state === "closed" && issue.state_reason === "not_planned";
groups.get(key).enhancements.push({
title: issue.title,
url: issue.html_url,
done: issue.state === "closed" && !notPlanned,
notPlanned
});
}
const versions = [...groups.values()].filter((v) => v.enhancements.length > 0);
// Order: upcoming dated versions first (soonest due date), then
// upcoming undated versions, then already released versions (newest
// first), unscheduled enhancements at the very end.
versions.sort((a, b) => {
if (!a.version) return 1;
if (!b.version) return -1;
if (a.released !== b.released) return a.released ? 1 : -1;
if (!a.released) {
if (a.dueOn && b.dueOn) return a.dueOn - b.dueOn;
if (a.dueOn) return -1;
if (b.dueOn) return 1;
return a.version.localeCompare(b.version);
}
if (a.releasedOn && b.releasedOn) return b.releasedOn - a.releasedOn;
return b.version.localeCompare(a.version);
});
return versions;
} catch (err) {
console.warn(`[roadmap] Could not load GitHub issues: ${err.message}`);
return [];
}
};
+382
View File
@@ -0,0 +1,382 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Historical changelog entries for releases not published as GitHub Releases.
The CI pipeline prepends new entries from GitHub Releases on top of these. -->
<history>
<changeLog>
<changedVersionNumber>1.41</changedVersionNumber>
<date>2026-07-01</date>
<description>Map view and path calculations</description>
<added>
Band and Frequency Detection per Chatmember, Band alert, Win-Test SKED Push via UDP,
Win-Test Broadcast Defaults and SKed Workflow, Chat History at Startup, Station Map / Map View
Local Leaflet /-TileProxy Backend, Maidenhead /-Grid Overlay, Path calculation, path profile,
Reachability filters, NAC filters (grid sqaure marking)
</added>
<changed>
button housing, it works now on smaller screens / plus: detecting maximum screen size
</changed>
<fixed>
9A2HM reported that 23cm qsos with N1MM are not marked correctly, ist fixed
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.40</changedVersionNumber>
<date>2026-02-16</date>
<description>Much changes and fixes</description>
<added>
- win-test support (not deactivable yet, beta)
- QSO sniffer which filters changable callsigns lists messages to your pm window
- Chatmember Score system. Each chatmember gets a score depending to antenna direction, activity time,
activity msgcount, positive signals, active bands, active frequencies, his sked direction (degrees),
self handed rate, sked-made-rate and some others
- Chatmember Band and frequency recognition: now we list all active bands of each other chatmember
- Band alert for logged stations
- Sked reminder ALERT with automatic messages in 2+1 / 5+2+1 / 10+5+2+1 minutes
- PSTRotator interface
- AirPlane Timeline
- Skedfail button in furtherinfo panel
- Wintest logsynch interface can now be activated and deactivated in the preferences
- load chat history on startup
</added>
<changed>
- added save option: ServerDNS / Port
- added save option: activate/deactivate pstRotator interface
- added save option: activate/deactivate wintest interface
- added save option: callsign sniffer
- added save option: boolean GUI_darkModeActiveByDefault = false
- added AP notes at the internal DX cluster spots
- Generic Autoanswer fires maximum once in 45 seconds per rx callsign
</changed>
<fixed>
- Userlist sort automatically on new member signon
- Posonpill-messages now kills only exactly one Client instance (bugreport 9A2HM)
- Crash of wtkst by disconnection of kst4Contest
- wtKST crashes when kst4contest is running (many reporters)
- wintest: logged per band excludes pre worked band (bugreport SM6VTZ)
- PSTRotator SOCKET Close on DISCONNECT BUTTON works now
- AirScout Map had not been updated (bugreport SM0RJV)
- QTFDefault had been not saved correctly (bugreport SM0RJV)
- Show path in AS button worked only with fixed server Strings (bugreport 9A2HM)
- Version number had been displayed wrong
- suffixes like /p, -2, etc. problem now fixed everywhere
- Dark mode: passing for qrg fields fix
</fixed>
<removed>
- No more date display. Not needed. Saving space, only time is important
</removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.31</changedVersionNumber>
<date>2025-12-13</date>
<description>Much changes and a hotfix</description>
<added>
- win-test support (not deactivable yet, beta)
- pstRotator support (not deactivable yet, beta)
- airScout fix (calculations depends to cpu load now)
- QSO sniffer
</added>
<changed>
- DNS from www.on4kst.info to www.on4kst.org (hotfix, now configurable in preferences)
</changed>
<fixed>
- Endless loop in error case lets freeze the client
- PSTRotator SOCKET Close on DISCONNECT BUTTON
- Dark mode: passing for qrg fields fix
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.266</changedVersionNumber>
<date>2025-10-03</date>
<description>Airscout interface did not work with multi-signons</description>
<added>nothing</added>
<changed>nothing</changed>
<fixed>
Fixed by using raw callsign (without -2 suffix etc.) when communicating with AirScout.
Thank you very much for testing this, Kreso 9A2HM!
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.265</changedVersionNumber>
<date>2025-09-28</date>
<description>Direction buttons stay now colored, if activated</description>
<added>nothing</added>
<changed>nothing</changed>
<fixed>The buttons stay now colored, if activated</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.264</changedVersionNumber>
<date>2025-08-02</date>
<description>Changed the callsign recognition pattern for Simplelogfile</description>
<added>nothing</added>
<changed>nothing</changed>
<fixed>
Callsigns like S53CC, S51A etc. are now correctly marked as worked in SimpleLogFile.txt.
(bugreport Boris, S53CC)
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.263</changedVersionNumber>
<date>2025-06-08</date>
<description>Airscout communication and Loginname</description>
<added>nothing</added>
<changed>
Only entries with QRB lower than max-QRB are sent to AirScout in 60s intervals (was 12s for all).
</changed>
<fixed>
- AS communication message count hugely reduced
- Name in chat is now saveable
- Fixed AS client name (was hard-wired to "KST")
73 / DO5AMF
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.262</changedVersionNumber>
<date>2025-05-21</date>
<description>Freezes caused by getting messages before user login should be fixed now</description>
<added>nothing</added>
<changed>nothing</changed>
<fixed>
ON4KST delivers messages of stations not yet logged in. That caused a freeze at the message
processing engine which is now fixed.
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.26</changedVersionNumber>
<date>2025-05</date>
<description>Login to multiple Channels via single signon / spend some colors</description>
<added>
1. UI: Dark mode. Switch in "Window -> use dark mode"
2. Usage of two Chatcategories at the same time
3. opposite station multi-callsign login-tagging
73 / DO5AMF
</added>
<changed>
- Coloring mechanic of the software. Modify colors via css by yourself...
</changed>
<fixed>
- Station tagging fixed completely
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.251</changedVersionNumber>
<date>2025-02</date>
<description>BUGFIX of 1.25, tnx Steve Clements!</description>
<added>
- Steve spotted a problem in udp broadcast spot info reading, its now fixed!
73 / DO5AMF
</added>
<changed></changed>
<fixed>- Station tagging</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.25</changedVersionNumber>
<date>2025-02</date>
<description>Wishlist-time</description>
<added>
- New configuration Tab: Messagehandling (auto-answering, CQ qrg auto-answer)
- Coloured lines: new pm rows appear in red, fade rainbow-like to white over 30s (tnx Gianluca)
73 / DO5AMF
</added>
<changed></changed>
<fixed>
- Users with suffixes like "-2 and -70" are now correctly marked as worked
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.24</changedVersionNumber>
<date>2024-11</date>
<description>Wishlist-time</description>
<added>
- Button to show qrz.com profile of a selected station
- Button to show qrzcq.com profile of a selected station
</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.23</changedVersionNumber>
<date>2024-10</date>
<description>DXCluster Server is now implemented</description>
<added>
- DXCluster Server (tnx OMAAO): KST4Contest now provides a DXCluster server to feed
your log client with station-reachable warnings.
</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.22</changedVersionNumber>
<date>2024-05</date>
<description>Increase usability, fixed AS button</description>
<added>- Variables (tnx OMAAO): MYLOCATORSHORT, MYQRGSHORT, QRZNAME</added>
<changed>- Sendtext-field focus on callsign click</changed>
<fixed>
- Worked-station-filter (tnx Gianluca): filter is now live
- Chatters list sorting by QRB (tnx Alessandro): fixed to numeric sort
- Airscout showpath button now maximizes AS and shows the path
</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.21</changedVersionNumber>
<date>2024-04</date>
<description>Increase usability</description>
<added></added>
<changed>
- Window sizes and divider positions are now stored and restored on next startup
- Filters section is now a flowpane for lower resolutions
</changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.2</changedVersionNumber>
<date>2024-04</date>
<description>Increase usability</description>
<added>
- Selectable bands
- Unworkable tags for each callsign per band
- QTF-Arrow on the "show path in AS" button
</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.1</changedVersionNumber>
<date>2024-03</date>
<description>Increased workflow</description>
<added>
- Reachable warning function
- Variables FIRSTAP and SECONDAP for predefined texts
- Marking new connected stations (bold) and away-state (brackets)
- Activity-timer since last message of a chatter
</added>
<changed>- QRB/QTF info in pm-window</changed>
<fixed>
- Behaviour of messagefilter-radiobutton corrected (tnx Gian)
- Error sending macros on selected stations in pm window (tnx Gian)
</fixed>
<removed>- boot sound</removed>
</changeLog>
<changeLog>
<changedVersionNumber>1.0</changedVersionNumber>
<date>2024-02</date>
<description>First stable release</description>
<added>
- audio notification support
- update-available warning
- changelog displaying
- known-bugs displaying
- message filters for selected station
- stationlist-filters (show by qtf / textstring, hide by qrb, qtf, workedstate)
- 10 macros (textsnippets available by hitting strg+numberkey)
</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.9</changedVersionNumber>
<date>2024-02</date>
<description>Now the chat is disconnectable without closing it</description>
<added></added>
<changed></changed>
<fixed>disconnect-button, Textsnippets in CQ Window, "/CQ" at click on own sendtexts,
setname "qrg" works now</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.8</changedVersionNumber>
<date>2023-10</date>
<description>savesettings function and disconnect function added. MYCALL-Highlighting fixed</description>
<added>savesettings function, disconnect function</added>
<changed></changed>
<fixed>MYCALL-Highlighting</fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.7</changedVersionNumber>
<date>2023-07</date>
<description>Internal database for worked stations, shortcut and textsnippet editor</description>
<added>database, shortcut-editor, textsnipped-editor</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.6</changedVersionNumber>
<date>2023-07</date>
<description>Added periodically sent beacon function</description>
<added>beacon to public channel</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.5</changedVersionNumber>
<date>2023-02</date>
<description>Airscout interface, UDP interfaces for worked-station data and TRX info, shortcuts</description>
<added>Airscout-interface, packetreceiver for ucxlog, n1mm, qartest</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.4</changedVersionNumber>
<date>2022-12</date>
<description>Worked info by logfile via universal pattern matching logfile interpreter</description>
<added>simple-logfile</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.3</changedVersionNumber>
<date>2022-10</date>
<description>Added a GUI, chat was text based only</description>
<added>Graphical user interface</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.2</changedVersionNumber>
<date>2022-10</date>
<description>Pattern matching system for frequency information, frequency collection per chatmember</description>
<added>frequency-extraction for every callsign</added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.15</changedVersionNumber>
<date>2022-09</date>
<description>Researched the Chatprotocol and developed the base communication with the chatserver</description>
<added></added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
<changeLog>
<changedVersionNumber>0.1</changedVersionNumber>
<date>2022-09</date>
<description>Primary architecture of the chat, message bus system for handling the message queues</description>
<added></added>
<changed></changed>
<fixed></fixed>
<removed></removed>
</changeLog>
</history>
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Static roadmap/bugsReported sections carried over from the previously
hand-maintained kst4ContestVersionInfo.xml on do5amf.funkerportal.de.
UpdateChecker.java does not parse these today; kept for parity/history. -->
<legacy>
<roadmap>
<featurerequest>
<featureDescription>Cyclic update of own name in chat to actual QRG</featureDescription>
<requestDate>2024-02</requestDate>
<state>open</state>
</featurerequest>
<featurerequest>
<featureDescription>Acoustic warnings</featureDescription>
<requestDate>2023-12</requestDate>
<state>added</state>
</featurerequest>
<featurerequest>
<featureDescription>Airscout support</featureDescription>
<requestDate>2023-03</requestDate>
<state>added</state>
</featurerequest>
<featurerequest>
<featureDescription>cyclic beacon messages in public chat</featureDescription>
<requestDate>2023-03</requestDate>
<state>added</state>
</featurerequest>
</roadmap>
<bugsReported>
<bug>
<bugdescription>
stations like ol7c-7 and ol7c-2 will not be marked as worked if only "ol7c"
is in the log. Need to interprete that. May ignore suffix starting with "-"</bugdescription>
<state>solved</state>
</bug>
<bug>
<bugdescription>
worked callsigns table is updated only once after connected to chat
Should be available directly after program started.
But Reset works all time</bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>feature request: language-specific strings, maybe based to an xml file
</bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>feature request: Macros
</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
Automatic elimination of calls already connected from the log
</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
Limitation of users beyond a certain QRB
</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
User division for QTF perhaps settable based on the antenna lobe
</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
Notification of new incoming users</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
Evidence of a call which was written but not responded to.
</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>feature request:
direction based reachable-warnings:
if the chatclient detects a realised qso between two stations which both must have
directed their beams to my station, there should be an instant warning to me for
working at least one of the stations at the detected qrg</bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>feature request:
implement ASShowpath message to airacout;
by doubleclicking a callsign in the chatmember list, the path and plane state should be
provided by airscout </bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>feature request:
make chatlines copyable to the clipboard...</bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>feature request:
adding an activity detection for sensing if another chatmembers alive-state</bugdescription>
<state>added</state>
</bug>
<bug>
<bugdescription>On sql error close the connection to make a clean exit</bugdescription>
<state>open</state>
</bug>
<bug>
<bugdescription>SETNAME /MYQRG in options menu doesnt work</bugdescription>
<state>solved</state>
</bug>
<bug>
<bugdescription>Disconnect button does not work (DL5ZK)</bugdescription>
<state>solved</state>
</bug>
<bug>
<bugdescription>Password-wrong error not displayed (DO5SA)</bugdescription>
<state>solved</state>
</bug>
<bug>
<bugdescription>/CQ isnt written on own PMs</bugdescription>
<state>solved</state>
</bug>
<bug>
<bugdescription>rightclick-textsnippets arent available in cq-table</bugdescription>
<state>solved</state>
</bug>
</bugsReported>
</legacy>
+183
View File
@@ -0,0 +1,183 @@
const fs = require("fs");
const path = require("path");
const REPO = "praktimarc/kst4contest";
const API = `https://api.github.com/repos/${REPO}`;
const LABEL_MAP = { enhancement: "added", bug: "fixed" };
async function githubGet(urlPath) {
const headers = { Accept: "application/vnd.github+json" };
if (process.env.GITHUB_TOKEN) {
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
}
const res = await fetch(`${API}${urlPath}`, { headers });
if (!res.ok) {
throw new Error(`GitHub API ${urlPath} failed: ${res.status}`);
}
return res.json();
}
// UpdateChecker.java parses <versionNumber> with Double.parseDouble() and
// compares it against ApplicationConstants.APPLICATION_CURRENTVERSIONNUMBER,
// which encodes patch releases by appending the patch digit(s) instead of a
// third dot (e.g. tag v1.41.1 -> app version 1.411, v1.41.0 -> 1.41). See
// commit 38ef50c ("...set to 1.41.1 for upcoming hotfix" -> 1.411).
function toAppVersionNumber(tag) {
const cleaned = tag.replace(/^v/, "");
const [major, minor, ...rest] = cleaned.split(".");
if (rest.length === 0) return cleaned;
const patch = rest.join("");
return /^0*$/.test(patch) ? `${major}.${minor}` : `${major}.${minor}${patch}`;
}
function escapeXml(value) {
return String(value ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
// Strips GitHub's auto-generated release-notes markdown noise (headings,
// "Full Changelog" links, PR author attributions) down to plain text.
function cleanReleaseBody(body) {
const lines = [];
for (let line of (body || "").split("\n")) {
if (/^#{1,3}\s/.test(line)) continue;
if (line.trim().startsWith("**Full Changelog**")) continue;
line = line.replace(/\s+by @\S+ in https:\/\/\S+/, "");
line = line.replace(/^\* /, "- ");
if (line.trim()) lines.push(line);
}
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
}
// Groups closed issues (excluding PRs) in the (since, until] window by
// label into "added" (enhancement) / "fixed" (bug) bullet lists.
async function getIssuesForRelease(since, until) {
const buckets = { added: [], fixed: [] };
let page = 1;
for (;;) {
const items = await githubGet(`/issues?state=closed&per_page=100&page=${page}`);
if (!items.length) break;
for (const issue of items) {
if (issue.pull_request) continue;
const closed = issue.closed_at || "";
if (!(closed > since && closed <= until)) continue;
const labels = issue.labels.map((l) => l.name);
const bucket = Object.keys(LABEL_MAP).find((l) => labels.includes(l));
if (bucket) buckets[LABEL_MAP[bucket]].push(`- ${issue.title}`);
}
if (items.length < 100) break;
page++;
}
return { added: buckets.added.join("\n"), fixed: buckets.fixed.join("\n") };
}
// version-history.xml holds hand-written <changeLog> entries for releases
// published before GitHub Releases existed; re-emitted verbatim here.
function loadHistoryEntries() {
const historyPath = path.join(__dirname, "version-history.xml");
const xml = fs.readFileSync(historyPath, "utf-8");
return [...xml.matchAll(/<changeLog>[\s\S]*?<\/changeLog>/g)].map((m) => m[0]);
}
function historyEntryVersion(entryXml) {
const m = entryXml.match(/<changedVersionNumber>([\s\S]*?)<\/changedVersionNumber>/);
return m ? m[1].trim() : null;
}
// Static <roadmap>/<bugsReported> sections carried over from the previous
// hand-maintained XML on do5amf.funkerportal.de. UpdateChecker.java doesn't
// parse these, kept only so the migration doesn't drop existing content.
function loadLegacySections() {
const legacyPath = path.join(__dirname, "version-legacy-sections.xml");
const xml = fs.readFileSync(legacyPath, "utf-8");
const roadmap = xml.match(/<roadmap>[\s\S]*?<\/roadmap>/);
const bugsReported = xml.match(/<bugsReported>[\s\S]*?<\/bugsReported>/);
return [roadmap ? roadmap[0] : null, bugsReported ? bugsReported[0] : null].filter(Boolean);
}
// Builds kst4ContestVersionInfo.xml (the update-checker feed read by
// UpdateChecker.java) from GitHub releases + closed issues, falling back to
// version-history.xml for releases that predate GitHub Releases.
module.exports = async function () {
const fallback = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n<praktiKST></praktiKST>';
try {
const historyEntries = loadHistoryEntries();
const rawReleases = await githubGet("/releases?per_page=100");
const releases = rawReleases
.map((r) => ({
tagName: r.tag_name,
publishedAt: r.published_at || "",
name: r.name || r.tag_name,
body: r.body || "",
isPrerelease: r.prerelease
}))
.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : -1));
const stableReleases = releases.filter((r) => !r.isPrerelease);
const stable = stableReleases[0];
const ghVersions = new Set(stableReleases.map((r) => toAppVersionNumber(r.tagName)));
const parts = [];
parts.push('<?xml version="1.0" encoding="UTF-8" standalone="no"?>');
parts.push("<praktiKST>");
const latestTag = stable ? stable.tagName : null;
const winFilename = latestTag ? `praktiKST-${latestTag}-windows-x64.zip` : "";
parts.push(" <latestVersion>");
if (stable) {
const latestIssues = await getIssuesForRelease("1970-01-01T00:00:00Z", stable.publishedAt);
parts.push(` <versionNumber>${escapeXml(toAppVersionNumber(latestTag))}</versionNumber>`);
parts.push(" <adminMessage></adminMessage>");
parts.push(` <majorChanges>${escapeXml(latestIssues.added.slice(0, 300))}</majorChanges>`);
parts.push(
` <latestVersionPathOnWebserver>https://github.com/${REPO}/releases/download/${latestTag}/${winFilename}</latestVersionPathOnWebserver>`
);
}
parts.push(" </latestVersion>");
// UpdateChecker.parseUpdateXMLFile() never actually reads this field
// (setNeedUpdateResourcesSinceLastVersion is never called); the
// previous server always hardcoded "nothing" here, so we match that.
parts.push(" <needUpdateSinceLastVersion>");
parts.push(" <filename>nothing</filename>");
parts.push(" </needUpdateSinceLastVersion>");
for (const section of loadLegacySections()) {
parts.push(section);
}
for (let i = 0; i < stableReleases.length; i++) {
const rel = stableReleases[i];
const until = rel.publishedAt;
const since = stableReleases[i + 1] ? stableReleases[i + 1].publishedAt : "1970-01-01T00:00:00Z";
const issues = await getIssuesForRelease(since, until);
parts.push(" <changeLog>");
parts.push(` <changedVersionNumber>${escapeXml(toAppVersionNumber(rel.tagName))}</changedVersionNumber>`);
parts.push(` <date>${escapeXml(until.slice(0, 10))}</date>`);
parts.push(` <description>${escapeXml(rel.name)}</description>`);
parts.push(` <added>${escapeXml(issues.added)}</added>`);
parts.push(` <changed>${escapeXml(cleanReleaseBody(rel.body))}</changed>`);
parts.push(` <fixed>${escapeXml(issues.fixed)}</fixed>`);
parts.push(" <removed></removed>");
parts.push(" </changeLog>");
}
for (const entry of historyEntries) {
const version = historyEntryVersion(entry);
if (version && ghVersions.has(version)) continue;
parts.push(entry);
}
parts.push("</praktiKST>");
return parts.join("\n");
} catch (err) {
console.warn(`[versionInfo] Could not generate version info XML, using empty fallback: ${err.message}`);
return fallback;
}
};
+17 -17
View File
@@ -4,6 +4,7 @@
<meta charset="utf-8">
<title>{{ title or "KST4Contest" }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#08110b">
<meta name="description" content="{{ description or 'KST4Contest is a contest-optimized ON4KST chat client for VHF, UHF and SHF ham radio contests.' }}">
<link rel="canonical" href="https://kst4contest.hamradioonline.de{{ page.url }}">
@@ -12,34 +13,33 @@
<meta property="og:description" content="{{ description or 'Contest-optimized ON4KST chat client for VHF, UHF and SHF ham radio contests.' }}">
<meta property="og:url" content="https://kst4contest.hamradioonline.de{{ page.url }}">
<meta property="og:site_name" content="KST4Contest">
<meta property="og:locale" content="en_GB">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ title or 'KST4Contest' }}">
<meta name="twitter:description" content="{{ description or 'Contest-optimized ON4KST chat client for VHF, UHF and SHF ham radio contests.' }}">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<link rel="stylesheet" href="/assets/css/main.css?v={{ build.version }}">
<link rel="alternate" type="application/rss+xml" title="KST4Contest News" href="/feed.xml">
<meta property="og:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:image" content="https://kst4contest.hamradioonline.de/assets/social/kst4contest-og.png">
</head>
<body>
<div class="site-bg"></div>
<div class="site-bg" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark">◎</span>
<a class="brand" href="/" aria-label="KST4Contest home">
<span class="brand-mark" aria-hidden="true">◎</span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<nav class="site-nav" aria-label="Main navigation">
{% for item in navigation %}
<a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}
@@ -47,9 +47,7 @@
</nav>
</header>
<main>
{{ content | safe }}
</main>
<main>{{ content | safe }}</main>
<footer class="site-footer">
<div class="footer-grid">
@@ -57,7 +55,6 @@
<strong>KST4Contest</strong>
<p>Contest-optimized ON4KST workflow for VHF/UHF/SHF operators.</p>
</div>
<div>
<strong>Project</strong>
<p><a href="/features/">Features</a></p>
@@ -65,14 +62,13 @@
<p><a href="/manual/">Manual</a></p>
<p><a href="/roadmap/">Roadmap</a></p>
</div>
<div>
<strong>Community</strong>
<p><a href="https://github.com/praktimarc/kst4contest">GitHub Repository</a></p>
<p><a href="https://github.com/praktimarc/kst4contest/issues">Issues</a></p>
<p><a href="/contact/">Contact</a></p>
<p><a href="/support/">❤️ Support KST4Contest</a></p>
</div>
<div>
<strong>Legal</strong>
<p><a href="/legal-notice/">Legal Notice</a></p>
@@ -80,5 +76,9 @@
</div>
</div>
</footer>
{% if heroFx %}
<script src="/assets/js/hero-radio-fx.js?v={{ build.version }}" defer></script>
{% endif %}
</body>
</html>
</html>
+15
View File
@@ -26,5 +26,20 @@ description: About KST4Contest and its contest-oriented ON4KST workflow.
The project is open source and focused on practical contest station workflows.
Features are designed around real operating pressure, not theoretical UI concepts.
</p>
<h2>Community supported</h2>
<p>
KST4Contest is developed independently.
Community support helps finance hosting,
online services,
testing across multiple operating systems
and future contest-oriented features.
</p>
</article>
</section>
+261 -1
View File
@@ -415,6 +415,23 @@ a:hover {
color: var(--muted);
}
.roadmap-version + .roadmap-version {
margin-top: 24px;
}
.roadmap-version ul {
padding-left: 20px;
}
.roadmap-version li {
margin-bottom: 8px;
}
.roadmap-done a {
color: var(--muted);
text-decoration: line-through;
}
.feature-icon {
font-size: 2rem;
margin-bottom: 14px;
@@ -544,4 +561,247 @@ a:hover {
.footer-grid {
grid-template-columns: 1fr;
}
}
}
/* ================================================================
KST4Contest brand recovery: green operator-console identity
and full-screen interactive hero
================================================================ */
:root {
--bg: #050806;
--bg2: #0b140f;
--panel: rgba(15, 28, 20, 0.82);
--panel2: rgba(24, 43, 31, 0.84);
--border: rgba(125, 178, 134, 0.22);
--text: #f4f7f3;
--muted: #a9b8ad;
--soft: #6e8173;
--accent: #54d63d;
--accent2: #8aef64;
--accent3: #31b54a;
--fx-plane: #79ff73;
--fx-plane-muted: rgba(121, 255, 115, 0.42);
--fx-beam: #52ff65;
--fx-beam-glow: rgba(82, 255, 101, 0.28);
--fx-reflection: #d2ffc8;
--fx-trail: rgba(121, 255, 115, 0.17);
--fx-grid: rgba(121, 255, 115, 0.055);
}
body {
background:
radial-gradient(circle at 14% 12%, rgba(52, 165, 57, 0.20), transparent 30%),
radial-gradient(circle at 82% 16%, rgba(95, 218, 82, 0.14), transparent 32%),
radial-gradient(circle at 50% 85%, rgba(32, 126, 55, 0.12), transparent 36%),
linear-gradient(180deg, #070b09 0%, #0b1410 100%);
}
body::before {
background:
radial-gradient(circle at 20% 20%, rgba(60, 173, 61, 0.18), transparent 28%),
radial-gradient(circle at 78% 18%, rgba(97, 222, 80, 0.13), transparent 30%),
radial-gradient(circle at 45% 80%, rgba(34, 122, 53, 0.11), transparent 26%);
}
body::after {
background-image:
linear-gradient(rgba(160,255,160,0.032) 1px, transparent 1px),
linear-gradient(90deg, rgba(160,255,160,0.032) 1px, transparent 1px);
}
.site-bg {
background:
linear-gradient(180deg, rgba(5, 8, 6, 0.10), rgba(5, 8, 6, 0.84)),
radial-gradient(circle at 8% 24%, rgba(52, 165, 57, 0.14), transparent 26%),
radial-gradient(circle at 82% 20%, rgba(95, 218, 82, 0.12), transparent 30%);
}
.site-header {
background: rgba(5, 8, 7, 0.82);
}
.brand-mark,
.nav-cta,
.button {
background: linear-gradient(135deg, var(--accent), var(--accent2));
}
.brand-mark {
box-shadow:
0 0 26px rgba(84, 214, 61, 0.42),
0 0 50px rgba(49, 181, 74, 0.16);
}
.nav-cta {
color: #061008 !important;
box-shadow: 0 0 22px rgba(84, 214, 61, 0.28);
}
main {
width: 100%;
}
.hero.hero-split {
position: relative;
width: 100%;
max-width: none;
min-height: calc(100vh - 73px);
display: grid;
grid-template-columns: minmax(420px, 0.9fr) minmax(520px, 1.1fr);
gap: clamp(40px, 5vw, 110px);
align-items: center;
align-content: center;
padding: clamp(56px, 7vh, 100px) clamp(28px, 6vw, 110px);
overflow: hidden;
}
.hero.hero-split > *:not(.hero-radio-fx):not(.hero-fx-vignette) {
position: relative;
z-index: 3;
}
.hero-copy {
max-width: 760px;
}
.hero.hero-split h1 {
font-size: clamp(3.4rem, 8vw, 7rem);
background: linear-gradient(90deg, #ffffff 0%, #c9ffd0 28%, #77ec70 58%, #45bd4d 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.badge,
.eyebrow {
color: #caffc0;
background: rgba(52, 165, 57, 0.12);
border-color: rgba(105, 232, 88, 0.32);
box-shadow: 0 0 22px rgba(82, 255, 101, 0.08);
}
.button {
color: #041007;
box-shadow:
0 16px 44px rgba(84, 214, 61, 0.22),
inset 0 1px 0 rgba(255,255,255,0.35);
}
.button.secondary {
color: white;
background: rgba(28, 43, 32, 0.90);
}
.hero-panel {
width: min(100%, 760px);
justify-self: center;
transform: perspective(1200px) rotateY(-4deg) rotateX(1.5deg);
}
.hero-panel,
.card,
.cta-panel {
background:
linear-gradient(180deg, rgba(255,255,255,0.045), transparent 42%),
rgba(15, 28, 20, 0.82);
}
.mini-card {
background: rgba(12, 25, 17, 0.88);
border-color: rgba(148, 184, 155, 0.16);
}
.card:hover {
border-color: rgba(84, 214, 61, 0.42);
background:
linear-gradient(180deg, rgba(255,255,255,0.055), transparent 42%),
rgba(12, 25, 17, 0.94);
box-shadow:
0 26px 90px rgba(0,0,0,0.50),
0 0 30px rgba(84,214,61,0.07);
}
.hero-radio-fx {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.78;
}
.hero-fx-vignette {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
background:
linear-gradient(90deg,
rgba(7,11,9,0.92) 0%,
rgba(7,11,9,0.70) 32%,
rgba(7,11,9,0.17) 62%,
rgba(7,11,9,0.38) 100%),
linear-gradient(180deg,
rgba(7,11,9,0.08),
rgba(7,11,9,0.58));
}
html[data-theme="light"] {
--bg: #f4f4f2;
--bg2: #ffffff;
--panel: rgba(255,255,255,0.90);
--panel2: rgba(242,242,239,0.94);
--border: rgba(91,91,86,0.20);
--text: #252522;
--muted: #656560;
--soft: #888881;
--accent: #d33732;
--accent2: #f25a51;
--accent3: #ae2727;
--fx-plane: #df3732;
--fx-plane-muted: rgba(211,55,50,0.38);
--fx-beam: #ef4039;
--fx-beam-glow: rgba(239,64,57,0.22);
--fx-reflection: #ff8d84;
--fx-trail: rgba(211,55,50,0.15);
--fx-grid: rgba(185,47,43,0.045);
}
@media (min-width: 1600px) {
.hero.hero-split {
grid-template-columns: minmax(520px, 0.85fr) minmax(620px, 1.15fr);
}
.hero-panel {
width: min(100%, 820px);
}
}
@media (max-width: 1000px) {
.hero.hero-split {
min-height: auto;
grid-template-columns: 1fr;
padding-top: 64px;
padding-bottom: 64px;
}
.hero-panel {
width: min(100%, 760px);
transform: none;
}
.hero-radio-fx {
opacity: 0.50;
}
.hero-fx-vignette {
background: linear-gradient(180deg, rgba(7,11,9,0.84), rgba(7,11,9,0.34));
}
}
@media (prefers-reduced-motion: reduce) {
.hero-radio-fx {
opacity: 0.28;
}
}
+509
View File
@@ -0,0 +1,509 @@
(() => {
"use strict";
const canvas = document.querySelector("[data-hero-radio-fx]");
if (!(canvas instanceof HTMLCanvasElement)) return;
const hero = canvas.closest(".hero");
const ctx = canvas.getContext("2d", { alpha: true, desynchronized: true });
if (!(hero instanceof HTMLElement) || !ctx) return;
const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
const coarsePointer = matchMedia("(pointer: coarse)");
const state = {
width: 0,
height: 0,
dpr: 1,
running: false,
visible: true,
lastFrame: 0,
spawnTime: 0,
beamTime: 0,
frameId: 0,
pointer: { x: 0, y: 0, active: false },
planes: [],
beams: [],
reflections: []
};
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
const random = (min, max) => min + Math.random() * (max - min);
function css(name, fallback) {
return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
}
function palette() {
return {
plane: css("--fx-plane", "#79ff73"),
planeMuted: css("--fx-plane-muted", "rgba(121,255,115,.42)"),
beam: css("--fx-beam", "#52ff65"),
beamGlow: css("--fx-beam-glow", "rgba(82,255,101,.28)"),
reflection: css("--fx-reflection", "#d2ffc8"),
trail: css("--fx-trail", "rgba(121,255,115,.17)"),
grid: css("--fx-grid", "rgba(121,255,115,.055)")
};
}
function roundedRect(x, y, width, height, radius) {
const r = Math.min(radius, width / 2, height / 2);
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + width, y, x + width, y + height, r);
ctx.arcTo(x + width, y + height, x, y + height, r);
ctx.arcTo(x, y + height, x, y, r);
ctx.arcTo(x, y, x + width, y, r);
ctx.closePath();
}
function resize() {
const rect = hero.getBoundingClientRect();
state.width = Math.max(1, Math.round(rect.width));
state.height = Math.max(1, Math.round(rect.height));
state.dpr = Math.min(devicePixelRatio || 1, 1.5);
canvas.width = Math.round(state.width * state.dpr);
canvas.height = Math.round(state.height * state.dpr);
canvas.style.width = `${state.width}px`;
canvas.style.height = `${state.height}px`;
ctx.setTransform(state.dpr, 0, 0, state.dpr, 0, 0);
if (!state.pointer.active) {
state.pointer.x = state.width * 0.55;
state.pointer.y = state.height * 0.42;
}
}
function stations() {
const targetX = state.width * 0.54;
const targetY = state.height * 0.43;
const left = { x: state.width * 0.075, y: state.height * 0.82, label: "JO51IJ" };
const right = { x: state.width * 0.925, y: state.height * 0.80, label: "JN49FL" };
left.angle = Math.atan2(targetY - left.y, targetX - left.x);
right.angle = Math.atan2(targetY - right.y, targetX - right.x);
return [left, right];
}
function planeLimit() {
if (coarsePointer.matches) return 5;
return state.width < 900 ? 7 : 12;
}
function createPlane() {
const margin = 55;
const edge = Math.floor(Math.random() * 4);
let x;
let y;
if (edge === 0) {
x = random(0, state.width);
y = -margin;
} else if (edge === 1) {
x = state.width + margin;
y = random(0, state.height);
} else if (edge === 2) {
x = random(0, state.width);
y = state.height + margin;
} else {
x = -margin;
y = random(0, state.height);
}
const tx = state.pointer.active ? state.pointer.x : state.width * random(0.38, 0.72);
const ty = state.pointer.active ? state.pointer.y : state.height * random(0.20, 0.72);
const angle = Math.atan2(ty - y, tx - x);
const speed = random(18, 34);
return {
x, y, angle,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
size: random(5.5, 9),
age: 0,
maxAge: random(12, 20),
steering: random(0.65, 1.2),
wobble: random(0, Math.PI * 2),
wobbleSpeed: random(0.7, 1.4),
hit: 0
};
}
function updatePlane(plane, dt) {
plane.age += dt;
plane.wobble += dt * plane.wobbleSpeed;
const tx = state.pointer.active ? state.pointer.x : state.width * 0.56;
const ty = state.pointer.active ? state.pointer.y : state.height * 0.44;
const desired = Math.atan2(ty - plane.y, tx - plane.x);
let diff = desired - plane.angle;
while (diff > Math.PI) diff -= Math.PI * 2;
while (diff < -Math.PI) diff += Math.PI * 2;
plane.angle += diff * plane.steering * dt;
const speed = Math.hypot(plane.vx, plane.vy);
const wobble = Math.sin(plane.wobble) * 0.14;
plane.vx = Math.cos(plane.angle + wobble) * speed;
plane.vy = Math.sin(plane.angle + wobble) * speed;
plane.x += plane.vx * dt;
plane.y += plane.vy * dt;
plane.hit = Math.max(0, plane.hit - dt * 1.8);
}
function expired(plane) {
const margin = 180;
return plane.age > plane.maxAge || plane.x < -margin || plane.x > state.width + margin || plane.y < -margin || plane.y > state.height + margin;
}
function beamTarget() {
if (!state.planes.length) return null;
const tx = state.pointer.active ? state.pointer.x : state.width * 0.56;
const ty = state.pointer.active ? state.pointer.y : state.height * 0.44;
const nearby = state.planes
.map(plane => ({ plane, distance: Math.hypot(plane.x - tx, plane.y - ty) }))
.filter(item => item.distance < 280)
.sort((a, b) => a.distance - b.distance);
return nearby[0]?.plane || state.planes[Math.floor(Math.random() * state.planes.length)];
}
function createBeam(target) {
const source = stations()
.map(station => ({ station, distance: Math.hypot(target.x - station.x, target.y - station.y) }))
.sort((a, b) => a.distance - b.distance)[0].station;
const angle = Math.atan2(target.y - source.y, target.x - source.x);
const tip = 45;
state.beams.push({
startX: source.x + Math.cos(angle) * tip,
startY: source.y + Math.sin(angle) * tip,
endX: target.x,
endY: target.y,
age: 0,
maxAge: 0.72
});
target.hit = 1;
const values = [50, 75, 100];
state.reflections.push({
x: target.x,
y: target.y,
age: 0,
maxAge: 1.25,
radius: random(8, 13),
probability: values[Math.floor(Math.random() * values.length)]
});
}
function updateEffects(dt) {
state.beams.forEach(beam => beam.age += dt);
state.beams = state.beams.filter(beam => beam.age < beam.maxAge);
state.reflections.forEach(r => {
r.age += dt;
r.radius += dt * 30;
});
state.reflections = state.reflections.filter(r => r.age < r.maxAge);
}
function drawGrid(p) {
ctx.save();
ctx.strokeStyle = p.grid;
ctx.lineWidth = 1;
const spacing = 54;
for (let x = spacing; x < state.width; x += spacing) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, state.height);
ctx.stroke();
}
for (let y = spacing; y < state.height; y += spacing) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(state.width, y);
ctx.stroke();
}
ctx.restore();
}
function drawRadar(p) {
const x = state.pointer.active ? state.pointer.x : state.width * 0.56;
const y = state.pointer.active ? state.pointer.y : state.height * 0.44;
ctx.save();
ctx.strokeStyle = p.planeMuted;
ctx.lineWidth = 1;
for (const radius of [24, 48, 82]) {
ctx.globalAlpha = 0.24 - radius / 500;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.stroke();
}
ctx.globalAlpha = 0.2;
ctx.beginPath();
ctx.moveTo(x - 100, y);
ctx.lineTo(x + 100, y);
ctx.moveTo(x, y - 100);
ctx.lineTo(x, y + 100);
ctx.stroke();
ctx.restore();
}
function drawYagi(station, p) {
ctx.save();
ctx.translate(station.x, station.y);
ctx.rotate(station.angle);
ctx.strokeStyle = p.plane;
ctx.fillStyle = p.plane;
ctx.lineWidth = 1.5;
ctx.lineCap = "round";
ctx.shadowColor = p.beamGlow;
ctx.shadowBlur = 9;
ctx.beginPath();
ctx.moveTo(-30, 26);
ctx.lineTo(-30, -5);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(-32, 0);
ctx.lineTo(43, 0);
ctx.stroke();
for (const element of [
{ x: -27, length: 34 },
{ x: -12, length: 28 },
{ x: 2, length: 22 },
{ x: 14, length: 19 },
{ x: 25, length: 16 },
{ x: 35, length: 13 }
]) {
ctx.beginPath();
ctx.moveTo(element.x, -element.length / 2);
ctx.lineTo(element.x, element.length / 2);
ctx.stroke();
}
ctx.beginPath();
ctx.arc(-12, 0, 3.2, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
ctx.save();
ctx.font = "700 11px Inter, system-ui, sans-serif";
const width = ctx.measureText(station.label).width + 14;
const x = station.x - width / 2;
const y = station.y + 32;
ctx.fillStyle = "rgba(6,18,10,.86)";
ctx.strokeStyle = p.planeMuted;
roundedRect(x, y, width, 23, 6);
ctx.fill();
ctx.stroke();
ctx.fillStyle = p.plane;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(station.label, station.x, y + 11.5);
ctx.restore();
}
function drawPlane(plane, p) {
ctx.save();
ctx.translate(plane.x, plane.y);
ctx.rotate(plane.angle);
ctx.globalAlpha = clamp(Math.min(plane.age * 1.8, (plane.maxAge - plane.age) * 1.8), 0, 1);
ctx.strokeStyle = p.trail;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(-plane.size * 4.5, 0);
ctx.lineTo(-plane.size * 1.4, 0);
ctx.stroke();
ctx.fillStyle = plane.hit > 0 ? p.reflection : p.plane;
ctx.shadowColor = plane.hit > 0 ? p.reflection : p.beamGlow;
ctx.shadowBlur = plane.hit > 0 ? 16 : 5;
const s = plane.size;
ctx.beginPath();
ctx.moveTo(s * 1.65, 0);
ctx.lineTo(-s * 0.7, s * 0.34);
ctx.lineTo(-s * 0.15, s * 1.05);
ctx.lineTo(-s * 0.65, s * 1.08);
ctx.lineTo(-s * 1.35, s * 0.3);
ctx.lineTo(-s * 1.7, s * 0.28);
ctx.lineTo(-s * 1.12, 0);
ctx.lineTo(-s * 1.7, -s * 0.28);
ctx.lineTo(-s * 1.35, -s * 0.3);
ctx.lineTo(-s * 0.65, -s * 1.08);
ctx.lineTo(-s * 0.15, -s * 1.05);
ctx.lineTo(-s * 0.7, -s * 0.34);
ctx.closePath();
ctx.fill();
ctx.restore();
}
function drawBeam(beam, p) {
const progress = beam.age / beam.maxAge;
const alpha = Math.sin(progress * Math.PI);
ctx.save();
ctx.globalAlpha = alpha;
ctx.strokeStyle = p.beamGlow;
ctx.lineWidth = 8;
ctx.shadowColor = p.beam;
ctx.shadowBlur = 15;
ctx.setLineDash([10, 6]);
ctx.lineDashOffset = -progress * 36;
ctx.beginPath();
ctx.moveTo(beam.startX, beam.startY);
ctx.lineTo(beam.endX, beam.endY);
ctx.stroke();
ctx.setLineDash([]);
ctx.strokeStyle = p.beam;
ctx.lineWidth = 1.8;
ctx.shadowBlur = 5;
ctx.beginPath();
ctx.moveTo(beam.startX, beam.startY);
ctx.lineTo(beam.endX, beam.endY);
ctx.stroke();
ctx.restore();
}
function drawReflection(r, p) {
const progress = r.age / r.maxAge;
const alpha = 1 - progress;
ctx.save();
ctx.translate(r.x, r.y);
ctx.globalAlpha = alpha;
ctx.strokeStyle = p.reflection;
ctx.lineWidth = 1.6;
ctx.shadowColor = p.beam;
ctx.shadowBlur = 18;
for (const scale of [1, 1.65]) {
ctx.globalAlpha = alpha * (scale === 1 ? 1 : 0.55);
ctx.beginPath();
ctx.arc(0, 0, r.radius * scale, 0, Math.PI * 2);
ctx.stroke();
}
ctx.globalAlpha = alpha * 0.8;
const cross = r.radius * 1.25;
ctx.beginPath();
ctx.moveTo(-cross, 0);
ctx.lineTo(cross, 0);
ctx.moveTo(0, -cross);
ctx.lineTo(0, cross);
ctx.stroke();
ctx.globalAlpha = alpha;
ctx.fillStyle = p.reflection;
ctx.beginPath();
ctx.arc(0, 0, 3.8, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
ctx.save();
const label = `${r.probability}%`;
ctx.font = "800 12px Inter, system-ui, sans-serif";
const width = ctx.measureText(label).width + 16;
const height = 25;
const x = r.x + r.radius + 12;
const y = r.y - r.radius - 17;
ctx.globalAlpha = clamp(alpha * 1.25, 0, 1);
ctx.fillStyle = "rgba(6,18,10,.91)";
ctx.strokeStyle = p.plane;
ctx.shadowColor = p.beamGlow;
ctx.shadowBlur = 10;
roundedRect(x, y, width, height, 6);
ctx.fill();
ctx.stroke();
ctx.shadowBlur = 0;
ctx.fillStyle = p.reflection;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(label, x + width / 2, y + height / 2);
ctx.restore();
}
function draw(staticOnly = false) {
const p = palette();
ctx.clearRect(0, 0, state.width, state.height);
drawGrid(p);
drawRadar(p);
stations().forEach(station => drawYagi(station, p));
if (staticOnly) return;
state.planes.forEach(plane => drawPlane(plane, p));
state.beams.forEach(beam => drawBeam(beam, p));
state.reflections.forEach(r => drawReflection(r, p));
}
function animate(timestamp) {
if (!state.running) return;
const dt = clamp((timestamp - state.lastFrame) / 1000 || 0, 0, 0.05);
state.lastFrame = timestamp;
if (!state.visible || document.hidden) {
state.frameId = requestAnimationFrame(animate);
return;
}
state.spawnTime += dt;
state.beamTime += dt;
const spawnInterval = coarsePointer.matches ? 2.2 : 1.15;
const beamInterval = coarsePointer.matches ? 3.3 : 1.8;
if (state.spawnTime >= spawnInterval && state.planes.length < planeLimit()) {
state.spawnTime = 0;
state.planes.push(createPlane());
}
if (state.beamTime >= beamInterval && state.planes.length) {
state.beamTime = 0;
const target = beamTarget();
if (target) createBeam(target);
}
state.planes.forEach(plane => updatePlane(plane, dt));
state.planes = state.planes.filter(plane => !expired(plane));
updateEffects(dt);
draw(false);
state.frameId = requestAnimationFrame(animate);
}
function start() {
cancelAnimationFrame(state.frameId);
resize();
if (reducedMotion.matches) {
state.running = false;
draw(true);
return;
}
state.running = true;
state.lastFrame = performance.now();
if (!state.planes.length) {
const count = coarsePointer.matches ? 2 : 5;
for (let i = 0; i < count; i++) {
const plane = createPlane();
plane.age = Math.random() * 2;
state.planes.push(plane);
}
}
state.frameId = requestAnimationFrame(animate);
}
hero.addEventListener("pointermove", event => {
const rect = hero.getBoundingClientRect();
state.pointer.x = clamp(event.clientX - rect.left, 0, rect.width);
state.pointer.y = clamp(event.clientY - rect.top, 0, rect.height);
state.pointer.active = true;
}, { passive: true });
hero.addEventListener("pointerleave", () => state.pointer.active = false);
addEventListener("resize", resize, { passive: true });
document.addEventListener("visibilitychange", () => state.lastFrame = performance.now());
reducedMotion.addEventListener("change", start);
new ResizeObserver(() => {
resize();
if (reducedMotion.matches) draw(true);
}).observe(hero);
new IntersectionObserver(entries => {
state.visible = entries[0]?.isIntersecting ?? true;
state.lastFrame = performance.now();
}, { threshold: 0.05 }).observe(hero);
start();
})();
+28
View File
@@ -56,4 +56,32 @@ description: Download KST4Contest releases for Windows, Linux and macOS.
<a class="button secondary" href="/faq/">FAQ</a>
</div>
</div>
</section>
<section class="section">
<div class="card content-card">
<h2>Enjoying KST4Contest?</h2>
<p>
If KST4Contest makes contesting easier for you,
please consider supporting future development.
</p>
<div class="actions">
<a class="button"
href="/support/">
❤️ Support Development
</a>
</div>
</div>
</section>
+7 -2
View File
@@ -2,10 +2,14 @@
layout: base.njk
title: KST4Contest Contest-Optimized ON4KST Chat Client
description: KST4Contest is a modern ON4KST chat client built for VHF, UHF and SHF contest operation.
heroFx: true
---
<section class="hero hero-split">
<div>
<canvas class="hero-radio-fx" data-hero-radio-fx aria-hidden="true"></canvas>
<div class="hero-fx-vignette" aria-hidden="true"></div>
<div class="hero-copy">
<p class="badge">The contest-optimized ON4KST client</p>
<h1>KST4Contest</h1>
<p class="lead">
@@ -73,6 +77,7 @@ description: KST4Contest is a modern ON4KST chat client built for VHF, UHF and S
<div class="actions">
<a class="button" href="/download/">Download KST4Contest</a>
<a class="button secondary" href="/features/">Explore features</a>
<a class="button ghost" href="/support/">❤️ Support development</a>
</div>
</div>
</section>
@@ -88,4 +93,4 @@ description: KST4Contest is a modern ON4KST chat client built for VHF, UHF and S
"downloadUrl":"https://github.com/praktimarc/kst4contest/releases",
"description":"Contest-optimized ON4KST client for VHF, UHF and SHF amateur radio contests."
}
</script>
</script>
@@ -0,0 +1,9 @@
---
title: Hotfix Version 1.41.1
summary: Hotfix of a bug in Version 1.41
date: 2026-07-08
---
## Version 1.41.1
We had a bug, where KST4Contest would clear out the text field, where you would send your messages after a period. It also happend to set your Cursor back to the Sending Field, when you were filtering for a station. We fixed this bug today. The new release is [here](https://github.com/praktimarc/kst4contest/releases/tag/v1.41.1). You can also download the artifacts as always via the [Downloads-Page](/download/).
+16
View File
@@ -44,6 +44,22 @@ description: Privacy policy for the KST4Contest website.
GitHub is responsible for its own data processing.
</p>
<h2>
Support via Ko-fi
</h2>
<p>
This website links to Ko-fi for voluntary financial support.
Payments are processed entirely by Ko-fi and its payment providers.
KST4Contest itself never receives or processes payment information.
</p>
<h2>Your rights</h2>
<p>
You may have the right to access, rectification, erasure, restriction of processing,
+93
View File
@@ -0,0 +1,93 @@
---
layout: base.njk
title: KST4Contest Roadmap
description: Planned enhancements for upcoming KST4Contest versions, generated from GitHub milestones and issues.
---
<section class="hero">
<p class="badge">Roadmap</p>
<h1>What's planned next.</h1>
<p class="lead">
Generated automatically from the "enhancement" issues in our
<a href="https://github.com/praktimarc/kst4contest">GitHub repository</a>,
grouped by their target version milestone.
</p>
</section>
<section class="section narrow">
{% if roadmap.length == 0 %}
<article class="card content-card roadmap-version">
<p>No planned enhancements are currently assigned to a version milestone.</p>
<p>
See all open feature requests on
<a href="https://github.com/praktimarc/kst4contest/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement">GitHub</a>.
</p>
</article>
{% endif %}
{% for version in roadmap %}
<article class="card content-card roadmap-version">
{% if version.version %}
<h2><a href="{{ version.url }}">{{ version.version }}</a></h2>
{% else %}
<h2>Planned, no version yet</h2>
{% endif %}
{% if version.description %}<p>{{ version.description }}</p>{% endif %}
{% if version.released %}
<p class="eyebrow">Released{% if version.releasedOn %} {{ version.releasedOn | readableDate }}{% endif %}</p>
{% elif version.dueOn %}
<p class="eyebrow">Due {{ version.dueOn | readableDate }}</p>
{% endif %}
<ul>
{% for item in version.enhancements %}
<li{% if item.done or item.notPlanned %} class="roadmap-done"{% endif %}>
<a href="{{ item.url }}">{{ item.title }}</a>{% if item.done %} ✓{% endif %}{% if item.notPlanned %} (not planned){% endif %}
</li>
{% endfor %}
</ul>
</article>
{% endfor %}
</section>
<section class="section">
<div class="cta-panel">
<p class="eyebrow">
Community
</p>
<h2>
Development is driven by the community.
</h2>
<p>
Many roadmap items require
time,
online services
and infrastructure.
Community support helps accelerate development.
</p>
<div class="actions">
<a class="button"
href="/support/">
❤️ Support Development
</a>
</div>
</div>
</section>
+176
View File
@@ -0,0 +1,176 @@
---
layout: base.njk
title: Support KST4Contest
description: Help support the ongoing development of KST4Contest.
---
<section class="hero">
<p class="badge">Support Development</p>
<h1>Help shape the future of KST4Contest.</h1>
<p class="lead">
KST4Contest is an independent open-source project developed in my free time.
Your support helps cover infrastructure costs and enables new contest-oriented
features that would otherwise be difficult to implement.
</p>
<div class="actions">
<a class="button"
href="https://ko-fi.com/praktimarc"
target="_blank"
rel="noopener">
❤️ Support on Ko-fi
</a>
<a class="button secondary"
href="https://github.com/praktimarc/kst4contest">
View Source Code
</a>
</div>
</section>
<section class="section">
<div class="section-heading">
<p class="eyebrow">What previous support has enabled</p>
<h2>Real improvements funded by the community.</h2>
</div>
<div class="grid">
<article class="card">
<h3>🛰 PSTRotator Integration</h3>
<p>
Support helped justify the investment into rotor hardware and the required
PSTRotator license. Today KST4Contest can automatically control antennas
during contest operation.
</p>
</article>
<article class="card">
<h3>🗺 Interactive Map</h3>
<p>
The interactive station map required commercial map API usage.
Community support helped cover those API costs and made the feature possible.
</p>
</article>
</div>
</section>
<section class="section">
<div class="section-heading">
<p class="eyebrow">Future goals</p>
<h2>What future support will fund</h2>
</div>
<div class="grid">
<article class="card">
<h3>☁ Map API</h3>
<p>
Keeping the online map service available requires ongoing API usage.
</p>
</article>
<article class="card">
<h3>📡 Tropo Server</h3>
<p>
Future propagation prediction services and server-side calculations
will require dedicated hosting infrastructure.
</p>
</article>
<article class="card">
<h3>🔌 New Integrations</h3>
<p>
Development of additional interfaces, logging software integrations,
contest tools and automation features.
</p>
</article>
<article class="card">
<h3>🚀 Faster Development</h3>
<p>
More development time means more contest-oriented features,
better testing and better documentation.
</p>
</article>
</div>
</section>
<section class="section">
<div class="cta-panel">
<p class="eyebrow">Every contribution helps</p>
<h2>Whether it's one coffee or ongoing support.</h2>
<p>
Your support directly improves KST4Contest.
Unlike commercial software, every new feature is driven by real contest
experience and community feedback.
</p>
<div class="actions">
<a class="button"
href="https://ko-fi.com/praktimarc"
target="_blank"
rel="noopener">
❤️ Support KST4Contest
</a>
</div>
</div>
</section>
+5
View File
@@ -0,0 +1,5 @@
---
permalink: /kst4ContestVersionInfo.xml
eleventyExcludeFromCollections: true
---
{{ versionInfo | safe }}