merge main

This commit is contained in:
Vincent van der Wal
2026-08-10 09:15:04 +02:00
55 changed files with 1890 additions and 612 deletions
+27 -10
View File
@@ -14,7 +14,8 @@
import { href, routePath } from '$lib/i18n';
import * as m from '$lib/paraglide/messages';
import SupporterBadge from '$lib/paywall/SupporterBadge.svelte';
import SupporterBadge from '$lib/supporter/SupporterBadge.svelte';
import { SUPPORTER_ENABLED } from '$lib/supporter/config';
import SettingsMenu from './settings-menu.svelte';
import ThemeIcon from './theme-icon.svelte';
@@ -41,6 +42,21 @@
if (flagEl && !flagEl.src.endsWith(src)) flagEl.src = src;
});
// Built as a string rather than inline markup: the pieces are optional, and
// separators spelled out in the template lose their spacing to Svelte's
// whitespace trimming ("Canton of Schwyz,Switzerland").
let locationRegion = $derived([location?.admin1, location?.country].filter(Boolean).join(', '));
// elevation rides along in the pill's muted part ("· Canton of Schwyz,
// Switzerland · 465m"); a 0 m coastal town is a real reading, only a
// missing value is dropped
let locationDetail = $derived(
[locationRegion, location?.elevation != null ? `${Math.round(location.elevation)}m` : null]
.filter(Boolean)
.join(' · ')
);
// the pill ellipses, so the full name still has to be readable somewhere
let locationLine = $derived([location?.name, locationDetail].filter(Boolean).join(' · '));
const themeCycle: Theme[] = ['system', 'light', 'dark'];
const themeTitles: Record<Theme, () => string> = {
system: m.theme_follow_system,
@@ -99,8 +115,10 @@
<!-- Current location display -->
{#if location}
<!-- min-w-0 + a ceiling so a long "Sant Pere de Ribes, Catalonia, Spain"
ellipses inside the pill instead of pushing the search box off centre -->
<div
class="hidden items-center gap-2 rounded-full border border-border/70 bg-muted/40 py-1 ps-1 pe-3 lg:flex"
class="hidden min-w-0 max-w-70 items-center gap-2 rounded-full border border-border/70 bg-muted/40 py-1 ps-1 pe-3 lg:flex xl:max-w-96"
>
<img
bind:this={flagEl}
@@ -109,13 +127,10 @@
alt={location.country}
/>
<!-- full location (desktop); the page hero carries it on smaller screens -->
<span class="whitespace-nowrap text-sm font-semibold text-foreground">
<span class="min-w-0 truncate text-sm font-semibold text-foreground" title={locationLine}>
{location.name}
{#if location.admin1 || location.country}
<span class="font-normal text-muted-foreground">
· {#if location.admin1}{location.admin1},&nbsp;
{/if}{location.country ?? ''}
</span>
{#if locationDetail}
<span class="font-normal text-muted-foreground">· {locationDetail}</span>
{/if}
</span>
</div>
@@ -144,8 +159,10 @@
<!-- md+: the same settings as individual controls. Kept mounted (not `{#if}`)
so SupporterBadge still verifies the key on every viewport. -->
<div class="hidden items-center gap-3 md:flex">
<!-- Supporter status / unlock -->
<SupporterBadge />
<!-- Supporter status / unlock (hidden while supporter features are parked) -->
{#if SUPPORTER_ENABLED}
<SupporterBadge />
{/if}
<!-- Language: the locale lives in the URL, so this is a set of links -->
<LanguageSelector />
@@ -6,9 +6,10 @@
import UnitOptions from '$lib/components/unit-options.svelte';
import * as m from '$lib/paraglide/messages';
import SupporterIcon from '$lib/paywall/SupporterIcon.svelte';
import UnlockDialog from '$lib/paywall/UnlockDialog.svelte';
import { isSupporter } from '$lib/paywall/supporter';
import SupporterIcon from '$lib/supporter/SupporterIcon.svelte';
import UnlockDialog from '$lib/supporter/UnlockDialog.svelte';
import { SUPPORTER_ENABLED } from '$lib/supporter/config';
import { isSupporter } from '$lib/supporter/store';
import ThemeIcon from './theme-icon.svelte';
@@ -76,29 +77,34 @@
<LanguageOptions onSelect={() => (open = false)} />
<div class="border-t border-border/70 pt-3">
<button
type="button"
class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg px-2 py-2 text-left transition-colors hover:bg-muted"
onclick={() => {
open = false;
unlockOpen = true;
}}
>
<SupporterIcon
filled={$isSupporter}
class="h-4.5 w-4.5 shrink-0 {$isSupporter ? 'text-amber-500' : 'text-muted-foreground'}"
/>
<span class="min-w-0 flex-1">
<span class="block text-[13px] font-semibold">
{$isSupporter ? m.supporter_active() : m.supporter_support()}
<!-- Supporter status / unlock (hidden while supporter features are parked) -->
{#if SUPPORTER_ENABLED}
<div class="border-t border-border/70 pt-3">
<button
type="button"
class="flex w-full cursor-pointer items-center gap-2.5 rounded-lg px-2 py-2 text-left transition-colors hover:bg-muted"
onclick={() => {
open = false;
unlockOpen = true;
}}
>
<SupporterIcon
filled={$isSupporter}
class="h-4.5 w-4.5 shrink-0 {$isSupporter
? 'text-amber-500'
: 'text-muted-foreground'}"
/>
<span class="min-w-0 flex-1">
<span class="block text-[13px] font-semibold">
{$isSupporter ? m.supporter_active() : m.supporter_support()}
</span>
<span class="block text-[11px] text-muted-foreground">
{$isSupporter ? m.supporter_manage_key() : m.supporter_unlock()}
</span>
</span>
<span class="block text-[11px] text-muted-foreground">
{$isSupporter ? m.supporter_manage_key() : m.supporter_unlock()}
</span>
</span>
</button>
</div>
</button>
</div>
{/if}
</div>
</Popover.Content>
</Popover.Root>