148 lines
3.7 KiB
Svelte
148 lines
3.7 KiB
Svelte
<script lang="ts">
|
|
import Avatar from '$lib/components/Avatar.svelte';
|
|
import ActivityItem from '$lib/components/ActivityItem.svelte';
|
|
import StatTile from '$lib/components/StatTile.svelte';
|
|
import { fmtDistance, fmtDuration, fmtElapsed, fmtElevation } from '$lib/format';
|
|
|
|
let { data } = $props();
|
|
const p = $derived(data.profile);
|
|
const memberSince = $derived(
|
|
new Date(p.created_at + 'Z').toLocaleDateString('en-GB', { month: 'long', year: 'numeric' })
|
|
);
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>{p.username} · Streba</title>
|
|
</svelte:head>
|
|
|
|
<div class="head card">
|
|
<Avatar avatar={p.avatar} username={p.username} size={88} />
|
|
<div class="who">
|
|
<h1>{p.username}</h1>
|
|
<p class="meta">
|
|
{#if p.location}{p.location} · {/if}member since {memberSince}
|
|
</p>
|
|
{#if p.bio}<p class="bio">{p.bio}</p>{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpis">
|
|
<StatTile label="Activities" value={String(data.stats.activities)} />
|
|
<StatTile label="Distance" value={fmtDistance(data.stats.distance_m)} />
|
|
<StatTile label="Ascent" value={fmtElevation(data.stats.elev_gain_m)} />
|
|
<StatTile label="Moving time" value={fmtDuration(data.stats.moving_s)} />
|
|
</div>
|
|
|
|
{#if data.peaksReached > 0}
|
|
<div class="card peak-strip">
|
|
⛰ {data.peaksReached} peak{data.peaksReached === 1 ? '' : 's'} reached{#if data.highestAscent},
|
|
highest: <strong>{data.highestAscent.name}</strong>
|
|
({Math.round(data.highestAscent.elevation_m ?? 0).toLocaleString('en-US')} m){/if}.
|
|
</div>
|
|
{/if}
|
|
|
|
{#if data.segments.length > 0}
|
|
<h2>Segments</h2>
|
|
<div class="card seg-list">
|
|
{#each data.segments as segment (segment.id)}
|
|
<a class="seg-row" href="/segments/{segment.id}">
|
|
<span class="seg-name">
|
|
{#if segment.rank === 1}<span class="crown" title="Course record">👑</span>{/if}
|
|
{segment.name}
|
|
</span>
|
|
<span class="seg-meta">
|
|
{fmtDistance(segment.distance_m)} · {segment.attempts}
|
|
{segment.attempts === 1 ? 'attempt' : 'attempts'}
|
|
{#if segment.best_s !== null}
|
|
· best {fmtElapsed(segment.best_s)}{#if segment.best_type} ({segment.best_type}){/if}
|
|
{#if segment.rank !== null}· #{segment.rank}{/if}
|
|
{/if}
|
|
</span>
|
|
</a>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
<h2>Recent activities</h2>
|
|
{#if data.activities.length === 0}
|
|
<div class="card empty">No activities yet.</div>
|
|
{:else}
|
|
<div class="card list">
|
|
{#each data.activities as activity (activity.id)}
|
|
<ActivityItem {activity} />
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
|
|
<style>
|
|
.head {
|
|
display: flex;
|
|
gap: 1.25rem;
|
|
align-items: flex-start;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.25rem;
|
|
}
|
|
.who h1 {
|
|
margin: 0;
|
|
}
|
|
.meta {
|
|
margin: 0.15rem 0 0;
|
|
color: var(--text-muted);
|
|
font-size: 0.85rem;
|
|
}
|
|
.bio {
|
|
margin: 0.6rem 0 0;
|
|
color: var(--text-secondary);
|
|
font-size: 0.92rem;
|
|
white-space: pre-line;
|
|
}
|
|
.kpis {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
gap: 0.75rem;
|
|
}
|
|
.peak-strip {
|
|
margin-top: 1.25rem;
|
|
padding: 0.85rem 1.15rem;
|
|
color: var(--text-secondary);
|
|
font-size: 0.9rem;
|
|
}
|
|
.peak-strip strong {
|
|
color: var(--text-primary);
|
|
}
|
|
.list :global(.item:not(:last-child)) {
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.seg-list > .seg-row:not(:last-child) {
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.seg-row {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 0.75rem 1.15rem;
|
|
text-decoration: none;
|
|
color: inherit;
|
|
transition: background 120ms;
|
|
}
|
|
.seg-row:hover {
|
|
background: var(--wash);
|
|
}
|
|
.seg-name {
|
|
font-weight: 600;
|
|
font-size: 0.92rem;
|
|
}
|
|
.crown {
|
|
font-size: 0.85rem;
|
|
}
|
|
.seg-meta {
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.empty {
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
color: var(--text-secondary);
|
|
}
|
|
</style>
|