3 Commits

52 changed files with 2054 additions and 1427 deletions
@@ -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
+1 -19
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="About KST4Contest">
<meta name="twitter:description" content="About KST4Contest and its contest-oriented ON4KST workflow.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -87,22 +85,7 @@
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>
@@ -126,7 +109,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="Contact | KST4Contest">
<meta name="twitter:description" content="Contact options for KST4Contest.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -114,7 +112,6 @@
<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>
+1 -32
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="Download KST4Contest">
<meta name="twitter:description" content="Download KST4Contest releases for Windows, Linux and macOS.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -227,34 +225,6 @@
<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>
</main>
@@ -278,7 +248,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -157,7 +155,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -212,7 +210,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -188,7 +186,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="DXCluster Server">
<meta name="twitter:description" content="KST4Contest includes DXCluster functionality to support situational awareness during contest operation.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -187,7 +185,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="KST4Contest Features">
<meta name="twitter:description" content="Contest-optimized ON4KST features for VHF, UHF and SHF operation.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -173,7 +171,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="Log Synchronization">
<meta name="twitter:description" content="Log synchronization helps KST4Contest understand worked stations, active bands and contest context.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -188,7 +186,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -187,7 +185,6 @@
<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>
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -216,7 +214,6 @@
<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>
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -210,7 +208,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -188,7 +186,6 @@
<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>
+1 -5
View File
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -239,7 +237,6 @@
<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>
@@ -278,7 +275,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="Legal Notice | KST4Contest">
<meta name="twitter:description" content="Legal notice for the KST4Contest website.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -124,7 +122,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -198,7 +196,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -200,7 +198,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -335,7 +333,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -162,7 +160,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -284,7 +282,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -191,7 +189,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="KST4Contest Deutsches Handbuch">
<meta name="twitter:description" content="Deutsches Benutzerhandbuch für KST4Contest.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -187,7 +185,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -306,7 +304,6 @@ flatpak install kst4contest de.x08.KST4Contest//nightly
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -242,7 +240,6 @@ Zudem wird die <strong>primäre Chat-Kategorie</strong> (z. B. IARU Region 1 VHF
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -220,7 +218,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -231,7 +229,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -198,7 +196,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -335,7 +333,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -242,7 +240,6 @@ Also, select the <strong>primary chat category</strong> (e.g., IARU Region 1 VHF
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -162,7 +160,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -283,7 +281,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -191,7 +189,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="KST4Contest English Manual">
<meta name="twitter:description" content="English user manual for KST4Contest.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -187,7 +185,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -306,7 +304,6 @@ flatpak install kst4contest de.x08.KST4Contest//nightly
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -220,7 +218,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -231,7 +229,6 @@
<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>
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="{{ manual.title }} | KST4Contest Manual">
<meta name="twitter:description" content="KST4Contest manual page: {{ manual.title }}">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -200,7 +198,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="KST4Contest Manual">
<meta name="twitter:description" content="Online manual for KST4Contest, the contest-optimized ON4KST chat client.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -99,7 +97,6 @@
<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>
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -100,7 +98,6 @@
<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>
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -104,7 +102,6 @@
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="KST4Contest News">
<meta name="twitter:description" content="News, release updates and development notes for KST4Contest.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -114,7 +112,6 @@
<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>
+1 -20
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="Privacy Policy | KST4Contest">
<meta name="twitter:description" content="Privacy policy for the KST4Contest website.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -105,22 +103,6 @@
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,
@@ -155,7 +137,6 @@
<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>
+1 -46
View File
@@ -17,7 +17,7 @@
<meta name="twitter:title" content="KST4Contest Roadmap">
<meta name="twitter:description" content="Planned enhancements for upcoming KST4Contest versions, generated from GitHub milestones and issues.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -175,48 +173,6 @@
</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">
@@ -239,7 +195,6 @@ href="/support/">
<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>
+1 -4
View File
@@ -17,7 +17,7 @@
<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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<link rel="stylesheet" href="/assets/css/main.css?v=1783632276803">
<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">
@@ -57,8 +57,6 @@
<a href="/faq/">FAQ</a>
<a href="/support/">Support</a>
<a class="nav-cta" href="/download/">Download</a>
</nav>
</header>
@@ -125,7 +123,6 @@
<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>
+22 -26
View File
@@ -10,94 +10,90 @@
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/about/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/contact/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/download/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/faq/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/airscout/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/dual-chat/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/dx-cluster/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/log-sync/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/macros/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/priority-score/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/sked-reminder/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/features/timeline/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/legal-notice/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/de/airscout-integration/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/de/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/en/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/manual/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/privacy/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/roadmap/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/screenshots/</loc>
<lastmod>2026-07-11</lastmod>
</url>
<url>
<loc>https://kst4contest.hamradioonline.de/support/</loc>
<lastmod>2026-07-11</lastmod>
<lastmod>2026-07-09</lastmod>
</url>
</urlset>
-271
View File
@@ -1,271 +0,0 @@
<!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="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 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.">
<link rel="stylesheet" href="/assets/css/main.css?v=1783804882235">
<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>
<header class="site-header">
<a class="brand" href="/">
<span class="brand-mark"></span>
<span>
<strong>KST4Contest</strong>
<small>ON4KST Contest Client</small>
</span>
</a>
<nav class="site-nav">
<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>
+5
View File
@@ -81,5 +81,10 @@
</div>
</div>
</footer>
{% if heroFx %}
<script src="/assets/js/hero-radio-fx.js?v={{ build.version }}" defer></script>
{% endif %}
</body>
</html>
+198 -8
View File
@@ -7,10 +7,22 @@
--text: #f8fafc;
--muted: #aab6ca;
--soft: #64748b;
--accent: #38bdf8;
--accent2: #a855f7;
--accent3: #22c55e;
/*--accent: #38bdf8;*/
/*--accent2: #a855f7;*/
/*--accent3: #22c55e;*/
--shadow: 0 24px 80px rgba(0, 0, 0, 0.45);
--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);
}
* {
@@ -25,13 +37,39 @@ html {
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(circle at 14% 12%, rgba(168, 85, 247, 0.30), transparent 28%),
radial-gradient(circle at 82% 16%, rgba(56, 189, 248, 0.24), transparent 30%),
radial-gradient(circle at 50% 85%, rgba(34, 197, 94, 0.11), transparent 35%),
linear-gradient(180deg, #050816 0%, #07111f 100%);
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%
);
color: var(--text);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-family:
Inter,
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
line-height: 1.65;
overflow-x: hidden;
}
@@ -561,4 +599,156 @@ a:hover {
.footer-grid {
grid-template-columns: 1fr;
}
}
/*
* Interactive radio / aircraft-scatter hero layer
*/
.hero-radio-fx {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.76;
}
.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.68) 34%,
rgba(7, 11, 9, 0.16) 64%,
rgba(7, 11, 9, 0.42) 100%
),
linear-gradient(
180deg,
rgba(7, 11, 9, 0.10),
rgba(7, 11, 9, 0.58)
);
}
.hero-split > *:not(.hero-radio-fx):not(.hero-fx-vignette) {
position: relative;
z-index: 2;
}
/*
* Operator-console badge styling
*/
.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);
}
/*
* Light-mode identity:
* white/grey UI with red contest accents
*/
html[data-theme="light"] {
--bg: #f4f4f2;
--bg2: #ffffff;
--panel: rgba(255, 255, 255, 0.88);
--panel2: rgba(242, 242, 239, 0.92);
--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);
}
html[data-theme="light"] body {
background:
radial-gradient(
circle at 14% 12%,
rgba(211, 55, 50, 0.12),
transparent 30%
),
radial-gradient(
circle at 82% 16%,
rgba(242, 90, 81, 0.09),
transparent 32%
),
linear-gradient(
180deg,
#ffffff 0%,
#f0f0ed 100%
);
}
html[data-theme="light"] .hero-fx-vignette {
background:
linear-gradient(
90deg,
rgba(255, 255, 255, 0.95) 0%,
rgba(255, 255, 255, 0.76) 34%,
rgba(255, 255, 255, 0.18) 66%,
rgba(255, 255, 255, 0.48) 100%
),
linear-gradient(
180deg,
rgba(255, 255, 255, 0.06),
rgba(240, 240, 237, 0.62)
);
}
html[data-theme="light"] .badge,
html[data-theme="light"] .eyebrow {
color: #962a27;
background: rgba(211, 55, 50, 0.08);
border-color: rgba(211, 55, 50, 0.24);
}
@media (max-width: 900px) {
.hero-radio-fx {
opacity: 0.48;
}
.hero-fx-vignette {
background:
linear-gradient(
180deg,
rgba(7, 11, 9, 0.82),
rgba(7, 11, 9, 0.34)
);
}
html[data-theme="light"] .hero-fx-vignette {
background:
linear-gradient(
180deg,
rgba(255, 255, 255, 0.90),
rgba(255, 255, 255, 0.42)
);
}
}
@media (prefers-reduced-motion: reduce) {
.hero-radio-fx {
opacity: 0.28;
}
}
+710
View File
@@ -0,0 +1,710 @@
(() => {
"use strict";
const canvas = document.querySelector("[data-hero-radio-fx]");
if (!(canvas instanceof HTMLCanvasElement)) {
return;
}
const context = canvas.getContext("2d", {
alpha: true,
desynchronized: true
});
if (!context) {
return;
}
const hero = canvas.closest(".hero");
if (!(hero instanceof HTMLElement)) {
return;
}
const reducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
);
const coarsePointer = window.matchMedia(
"(pointer: coarse)"
);
const state = {
width: 0,
height: 0,
dpr: 1,
running: false,
visible: true,
lastFrame: 0,
spawnAccumulator: 0,
beamAccumulator: 0,
animationFrameId: 0,
pointer: {
x: 0,
y: 0,
active: false
},
planes: [],
beams: [],
reflections: []
};
const clamp = (value, minimum, maximum) =>
Math.max(minimum, Math.min(maximum, value));
const randomBetween = (minimum, maximum) =>
minimum + Math.random() * (maximum - minimum);
function getCssColor(name, fallback) {
const value = getComputedStyle(document.documentElement)
.getPropertyValue(name)
.trim();
return value || fallback;
}
function getPalette() {
return {
plane: getCssColor("--fx-plane", "#79ff73"),
planeMuted: getCssColor("--fx-plane-muted", "rgba(121,255,115,.42)"),
beam: getCssColor("--fx-beam", "#52ff65"),
beamGlow: getCssColor("--fx-beam-glow", "rgba(82,255,101,.28)"),
reflection: getCssColor("--fx-reflection", "#c8ffbf"),
trail: getCssColor("--fx-trail", "rgba(121,255,115,.16)"),
grid: getCssColor("--fx-grid", "rgba(121,255,115,.055)")
};
}
function resizeCanvas() {
const bounds = hero.getBoundingClientRect();
state.width = Math.max(1, Math.round(bounds.width));
state.height = Math.max(1, Math.round(bounds.height));
state.dpr = Math.min(window.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`;
context.setTransform(
state.dpr,
0,
0,
state.dpr,
0,
0
);
if (!state.pointer.active) {
state.pointer.x = state.width * 0.54;
state.pointer.y = state.height * 0.42;
}
}
function getPlaneLimit() {
if (coarsePointer.matches) {
return 5;
}
if (state.width < 900) {
return 7;
}
return 12;
}
function createPlane() {
const margin = 55;
const edge = Math.floor(Math.random() * 4);
let x;
let y;
switch (edge) {
case 0:
x = randomBetween(0, state.width);
y = -margin;
break;
case 1:
x = state.width + margin;
y = randomBetween(0, state.height);
break;
case 2:
x = randomBetween(0, state.width);
y = state.height + margin;
break;
default:
x = -margin;
y = randomBetween(0, state.height);
break;
}
const targetX = state.pointer.active
? state.pointer.x
: state.width * randomBetween(0.38, 0.72);
const targetY = state.pointer.active
? state.pointer.y
: state.height * randomBetween(0.2, 0.72);
const angle = Math.atan2(targetY - y, targetX - x);
const speed = randomBetween(18, 34);
return {
x,
y,
previousX: x,
previousY: y,
velocityX: Math.cos(angle) * speed,
velocityY: Math.sin(angle) * speed,
angle,
size: randomBetween(5.5, 9),
age: 0,
maximumAge: randomBetween(12, 20),
steering: randomBetween(0.65, 1.2),
wobble: randomBetween(0, Math.PI * 2),
wobbleSpeed: randomBetween(0.7, 1.4),
hit: 0
};
}
function updatePlane(plane, deltaSeconds) {
plane.age += deltaSeconds;
plane.wobble += deltaSeconds * plane.wobbleSpeed;
plane.previousX = plane.x;
plane.previousY = plane.y;
const targetX = state.pointer.active
? state.pointer.x
: state.width * 0.56;
const targetY = state.pointer.active
? state.pointer.y
: state.height * 0.44;
const desiredAngle = Math.atan2(
targetY - plane.y,
targetX - plane.x
);
let angleDifference = desiredAngle - plane.angle;
while (angleDifference > Math.PI) {
angleDifference -= Math.PI * 2;
}
while (angleDifference < -Math.PI) {
angleDifference += Math.PI * 2;
}
plane.angle += angleDifference * plane.steering * deltaSeconds;
const speed = Math.hypot(
plane.velocityX,
plane.velocityY
);
const wobble = Math.sin(plane.wobble) * 0.14;
plane.velocityX =
Math.cos(plane.angle + wobble) * speed;
plane.velocityY =
Math.sin(plane.angle + wobble) * speed;
plane.x += plane.velocityX * deltaSeconds;
plane.y += plane.velocityY * deltaSeconds;
plane.hit = Math.max(0, plane.hit - deltaSeconds * 1.8);
}
function isPlaneExpired(plane) {
const margin = 180;
return (
plane.age > plane.maximumAge ||
plane.x < -margin ||
plane.x > state.width + margin ||
plane.y < -margin ||
plane.y > state.height + margin
);
}
function chooseBeamTarget() {
if (state.planes.length === 0) {
return null;
}
const targetX = state.pointer.active
? state.pointer.x
: state.width * 0.56;
const targetY = state.pointer.active
? state.pointer.y
: state.height * 0.44;
const candidates = state.planes
.map((plane) => ({
plane,
distance: Math.hypot(
plane.x - targetX,
plane.y - targetY
)
}))
.filter((entry) => entry.distance < 260)
.sort((a, b) => a.distance - b.distance);
if (candidates.length > 0) {
return candidates[0].plane;
}
return state.planes[
Math.floor(Math.random() * state.planes.length)
];
}
function createBeam(target) {
const leftStation = {
x: state.width * 0.12,
y: state.height * 0.82
};
const rightStation = {
x: state.width * 0.88,
y: state.height * 0.78
};
const source =
Math.abs(target.x - leftStation.x) <
Math.abs(target.x - rightStation.x)
? leftStation
: rightStation;
state.beams.push({
startX: source.x,
startY: source.y,
endX: target.x,
endY: target.y,
age: 0,
maximumAge: 0.62
});
target.hit = 1;
state.reflections.push({
x: target.x,
y: target.y,
age: 0,
maximumAge: 0.9,
radius: randomBetween(8, 15)
});
}
function updateEffects(deltaSeconds) {
for (const beam of state.beams) {
beam.age += deltaSeconds;
}
state.beams = state.beams.filter(
(beam) => beam.age < beam.maximumAge
);
for (const reflection of state.reflections) {
reflection.age += deltaSeconds;
reflection.radius += deltaSeconds * 30;
}
state.reflections = state.reflections.filter(
(reflection) =>
reflection.age < reflection.maximumAge
);
}
function drawGrid(palette) {
context.save();
context.strokeStyle = palette.grid;
context.lineWidth = 1;
const spacing = 54;
for (
let x = spacing;
x < state.width;
x += spacing
) {
context.beginPath();
context.moveTo(x, 0);
context.lineTo(x, state.height);
context.stroke();
}
for (
let y = spacing;
y < state.height;
y += spacing
) {
context.beginPath();
context.moveTo(0, y);
context.lineTo(state.width, y);
context.stroke();
}
context.restore();
}
function drawRadarTarget(palette) {
const x = state.pointer.active
? state.pointer.x
: state.width * 0.56;
const y = state.pointer.active
? state.pointer.y
: state.height * 0.44;
context.save();
context.strokeStyle = palette.planeMuted;
context.lineWidth = 1;
for (const radius of [24, 48, 82]) {
context.globalAlpha = 0.24 - radius / 500;
context.beginPath();
context.arc(x, y, radius, 0, Math.PI * 2);
context.stroke();
}
context.globalAlpha = 0.2;
context.beginPath();
context.moveTo(x - 100, y);
context.lineTo(x + 100, y);
context.moveTo(x, y - 100);
context.lineTo(x, y + 100);
context.stroke();
context.restore();
}
function drawPlane(plane, palette) {
context.save();
context.translate(plane.x, plane.y);
context.rotate(plane.angle);
const alpha = clamp(
Math.min(
plane.age * 1.8,
(plane.maximumAge - plane.age) * 1.8
),
0,
1
);
context.globalAlpha = alpha;
context.strokeStyle = palette.trail;
context.lineWidth = 1;
context.beginPath();
context.moveTo(-plane.size * 4.5, 0);
context.lineTo(-plane.size * 1.4, 0);
context.stroke();
context.fillStyle =
plane.hit > 0
? palette.reflection
: palette.plane;
context.shadowColor =
plane.hit > 0
? palette.reflection
: palette.beamGlow;
context.shadowBlur =
plane.hit > 0
? 16
: 5;
const size = plane.size;
context.beginPath();
context.moveTo(size * 1.65, 0);
context.lineTo(-size * 0.7, size * 0.34);
context.lineTo(-size * 0.15, size * 1.05);
context.lineTo(-size * 0.65, size * 1.08);
context.lineTo(-size * 1.35, size * 0.3);
context.lineTo(-size * 1.7, size * 0.28);
context.lineTo(-size * 1.12, 0);
context.lineTo(-size * 1.7, -size * 0.28);
context.lineTo(-size * 1.35, -size * 0.3);
context.lineTo(-size * 0.65, -size * 1.08);
context.lineTo(-size * 0.15, -size * 1.05);
context.lineTo(-size * 0.7, -size * 0.34);
context.closePath();
context.fill();
context.restore();
}
function drawBeam(beam, palette) {
const progress = beam.age / beam.maximumAge;
const alpha = Math.sin(progress * Math.PI);
context.save();
context.globalAlpha = alpha;
context.strokeStyle = palette.beamGlow;
context.lineWidth = 6;
context.shadowColor = palette.beam;
context.shadowBlur = 15;
context.beginPath();
context.moveTo(beam.startX, beam.startY);
context.lineTo(beam.endX, beam.endY);
context.stroke();
context.strokeStyle = palette.beam;
context.lineWidth = 1.4;
context.shadowBlur = 5;
context.beginPath();
context.moveTo(beam.startX, beam.startY);
context.lineTo(beam.endX, beam.endY);
context.stroke();
context.restore();
}
function drawReflection(reflection, palette) {
const progress =
reflection.age / reflection.maximumAge;
context.save();
context.globalAlpha = 1 - progress;
context.strokeStyle = palette.reflection;
context.lineWidth = 1.5;
context.shadowColor = palette.beam;
context.shadowBlur = 12;
context.beginPath();
context.arc(
reflection.x,
reflection.y,
reflection.radius,
0,
Math.PI * 2
);
context.stroke();
context.restore();
}
function drawStaticScene() {
const palette = getPalette();
context.clearRect(
0,
0,
state.width,
state.height
);
drawGrid(palette);
drawRadarTarget(palette);
}
function drawScene() {
const palette = getPalette();
context.clearRect(
0,
0,
state.width,
state.height
);
drawGrid(palette);
drawRadarTarget(palette);
for (const plane of state.planes) {
drawPlane(plane, palette);
}
for (const beam of state.beams) {
drawBeam(beam, palette);
}
for (const reflection of state.reflections) {
drawReflection(reflection, palette);
}
}
function animate(timestamp) {
if (!state.running) {
return;
}
const deltaSeconds = clamp(
(timestamp - state.lastFrame) / 1000 || 0,
0,
0.05
);
state.lastFrame = timestamp;
if (!state.visible || document.hidden) {
state.animationFrameId =
requestAnimationFrame(animate);
return;
}
state.spawnAccumulator += deltaSeconds;
state.beamAccumulator += deltaSeconds;
const planeLimit = getPlaneLimit();
const spawnInterval =
coarsePointer.matches ? 2.2 : 1.15;
if (
state.spawnAccumulator >= spawnInterval &&
state.planes.length < planeLimit
) {
state.spawnAccumulator = 0;
state.planes.push(createPlane());
}
const beamInterval =
coarsePointer.matches ? 3.3 : 1.8;
if (
state.beamAccumulator >= beamInterval &&
state.planes.length > 0
) {
state.beamAccumulator = 0;
const target = chooseBeamTarget();
if (target) {
createBeam(target);
}
}
for (const plane of state.planes) {
updatePlane(plane, deltaSeconds);
}
state.planes = state.planes.filter(
(plane) => !isPlaneExpired(plane)
);
updateEffects(deltaSeconds);
drawScene();
state.animationFrameId =
requestAnimationFrame(animate);
}
function start() {
cancelAnimationFrame(state.animationFrameId);
resizeCanvas();
if (reducedMotion.matches) {
state.running = false;
drawStaticScene();
return;
}
state.running = true;
state.lastFrame = performance.now();
if (state.planes.length === 0) {
const initialPlanes =
coarsePointer.matches ? 2 : 5;
for (let index = 0; index < initialPlanes; index++) {
const plane = createPlane();
plane.age = Math.random() * 2;
state.planes.push(plane);
}
}
state.animationFrameId =
requestAnimationFrame(animate);
}
function updatePointer(event) {
const bounds = hero.getBoundingClientRect();
state.pointer.x = clamp(
event.clientX - bounds.left,
0,
bounds.width
);
state.pointer.y = clamp(
event.clientY - bounds.top,
0,
bounds.height
);
state.pointer.active = true;
}
hero.addEventListener("pointermove", updatePointer, {
passive: true
});
hero.addEventListener("pointerleave", () => {
state.pointer.active = false;
});
window.addEventListener("resize", resizeCanvas, {
passive: true
});
document.addEventListener("visibilitychange", () => {
state.lastFrame = performance.now();
});
reducedMotion.addEventListener("change", start);
const resizeObserver = new ResizeObserver(() => {
resizeCanvas();
if (reducedMotion.matches) {
drawStaticScene();
}
});
resizeObserver.observe(hero);
const intersectionObserver = new IntersectionObserver(
(entries) => {
state.visible =
entries[0]?.isIntersecting ?? true;
state.lastFrame = performance.now();
},
{
threshold: 0.05
}
);
intersectionObserver.observe(hero);
start();
})();
+9
View File
@@ -2,9 +2,18 @@
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">
<canvas
class="hero-radio-fx"
data-hero-radio-fx
aria-hidden="true">
</canvas>
<div class="hero-fx-vignette" aria-hidden="true"></div>
<div>
<p class="badge">The contest-optimized ON4KST client</p>
<h1>KST4Contest</h1>