changed the collapsible panel to top centered layout with consistent ui style
This commit is contained in:
@@ -38,6 +38,7 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-area {
|
.map-area {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import TopBar from './TopBar.svelte';
|
import TopBar from './TopBar.svelte';
|
||||||
import Footer from './Footer.svelte';
|
|
||||||
|
|
||||||
let { screen, onNavigate, hideTopBar = false, children } = $props();
|
let { screen, onNavigate, hideTopBar = false, children } = $props();
|
||||||
</script>
|
</script>
|
||||||
@@ -12,7 +11,6 @@
|
|||||||
<main class="main">
|
<main class="main">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -20,11 +18,11 @@
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: auto 1fr;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.layout.no-topbar {
|
.layout.no-topbar {
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import { CONTINENTS, getContinent, continentTotals } from './continents.js';
|
import { CONTINENTS, getContinent, continentTotals } from './continents.js';
|
||||||
import { getSelected, getTotalCount } from '../layout/selection.svelte.js';
|
import { getSelected } from '../layout/selection.svelte.js';
|
||||||
|
|
||||||
let collapsed = $state(false);
|
let hoveredSeg = $state(null);
|
||||||
|
|
||||||
const continentColors = {
|
const continentColors = {
|
||||||
'Europe': '#3b82f6',
|
'Europe': '#6366f1',
|
||||||
'Asia': '#ef4444',
|
'Asia': '#f43f5e',
|
||||||
'Africa': '#f97316',
|
'Africa': '#fb923c',
|
||||||
'N. America': '#22c55e',
|
'N. America': '#06b6d4',
|
||||||
'S. America': '#eab308',
|
'S. America': '#f59e0b',
|
||||||
'Oceania': '#a855f7'
|
'Oceania': '#8b5cf6'
|
||||||
};
|
};
|
||||||
|
|
||||||
let counts = $derived.by(() => {
|
let counts = $derived.by(() => {
|
||||||
@@ -36,11 +36,9 @@
|
|||||||
if (angle > 0) {
|
if (angle > 0) {
|
||||||
const startDeg = deg;
|
const startDeg = deg;
|
||||||
const endDeg = deg + angle;
|
const endDeg = deg + angle;
|
||||||
const midDeg = (startDeg + endDeg) / 2;
|
|
||||||
const rad = (midDeg - 90) * Math.PI / 180;
|
|
||||||
const sr = (startDeg - 90) * Math.PI / 180;
|
const sr = (startDeg - 90) * Math.PI / 180;
|
||||||
const er = (endDeg - 90) * Math.PI / 180;
|
const er = (endDeg - 90) * Math.PI / 180;
|
||||||
const cx = 90, cy = 90, outerR = 65, innerR = 30;
|
const cx = 50, cy = 50, outerR = 44, innerR = 22;
|
||||||
const x1 = cx + outerR * Math.cos(sr);
|
const x1 = cx + outerR * Math.cos(sr);
|
||||||
const y1 = cy + outerR * Math.sin(sr);
|
const y1 = cy + outerR * Math.sin(sr);
|
||||||
const x2 = cx + outerR * Math.cos(er);
|
const x2 = cx + outerR * Math.cos(er);
|
||||||
@@ -51,9 +49,7 @@
|
|||||||
const y4 = cy + innerR * Math.sin(sr);
|
const y4 = cy + innerR * Math.sin(sr);
|
||||||
const largeArc = angle > 180 ? 1 : 0;
|
const largeArc = angle > 180 ? 1 : 0;
|
||||||
const path = `M ${x1} ${y1} A ${outerR} ${outerR} 0 ${largeArc} 1 ${x2} ${y2} L ${x3} ${y3} A ${innerR} ${innerR} 0 ${largeArc} 0 ${x4} ${y4} Z`;
|
const path = `M ${x1} ${y1} A ${outerR} ${outerR} 0 ${largeArc} 1 ${x2} ${y2} L ${x3} ${y3} A ${innerR} ${innerR} 0 ${largeArc} 0 ${x4} ${y4} Z`;
|
||||||
const lx = cx + 82 * Math.cos(rad);
|
segs.push({ cont, color: continentColors[cont], path, angle });
|
||||||
const ly = cy + 82 * Math.sin(rad);
|
|
||||||
segs.push({ cont, color: continentColors[cont], path, lx, ly, angle });
|
|
||||||
deg += angle;
|
deg += angle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,247 +57,199 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="panel" class:collapsed>
|
<div class="card">
|
||||||
<button class="collapse-btn" onclick={() => collapsed = !collapsed} data-tip={collapsed ? 'see statistics' : 'close statistics'}>
|
<!-- count -->
|
||||||
{collapsed ? '◀' : '▶'}
|
<div class="stat-block">
|
||||||
</button>
|
<span class="big-num">{total}</span>
|
||||||
|
<span class="stat-sub">countries visited</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if !collapsed}
|
<div class="vdivider"></div>
|
||||||
<div class="panel-content">
|
|
||||||
<h2 class="headline">your statistics</h2>
|
|
||||||
|
|
||||||
<span class="bar-label">visited countries</span>
|
<!-- world % -->
|
||||||
<div class="total-bar-wrap">
|
<div class="stat-block">
|
||||||
<div class="total-bar-bg">
|
<span class="big-num accent">{pct}%</span>
|
||||||
<div class="total-bar-fill" style="width: {pct}%"></div>
|
<span class="stat-sub">of the world</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="vdivider"></div>
|
||||||
|
|
||||||
|
<!-- donut -->
|
||||||
|
<div class="donut-block">
|
||||||
|
<svg viewBox="0 0 100 100" class="donut-svg">
|
||||||
|
{#if segments.length > 0}
|
||||||
|
{#each segments as seg}
|
||||||
|
<g class="seg-group"
|
||||||
|
onmouseenter={() => hoveredSeg = seg}
|
||||||
|
onmouseleave={() => hoveredSeg = null}>
|
||||||
|
<path d={seg.path} fill={seg.color} />
|
||||||
|
</g>
|
||||||
|
{/each}
|
||||||
|
<circle cx="50" cy="50" r="22" fill="#fff" />
|
||||||
|
{:else}
|
||||||
|
<circle cx="50" cy="50" r="44" fill="#f1f5f9" />
|
||||||
|
<circle cx="50" cy="50" r="22" fill="#fff" />
|
||||||
|
{/if}
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<div class="donut-info">
|
||||||
|
<span class="section-label">by continent</span>
|
||||||
|
{#if hoveredSeg}
|
||||||
|
<div class="tooltip" style="--dot:{hoveredSeg.color}">
|
||||||
|
<span class="tt-name">{hoveredSeg.cont}</span>
|
||||||
|
<span class="tt-val">{counts[hoveredSeg.cont]} / {continentTotals[hoveredSeg.cont]}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="total-bar-text">{total} / {grandTotal}</span>
|
{:else}
|
||||||
</div>
|
<span class="hint">hover a slice</span>
|
||||||
|
{/if}
|
||||||
<div class="divider"></div>
|
|
||||||
|
|
||||||
<span class="bar-label">by continent</span>
|
|
||||||
{#each CONTINENTS as continent}
|
|
||||||
{@const contTotal = continentTotals[continent]}
|
|
||||||
<div class="row">
|
|
||||||
<span class="dot" style="background: {continentColors[continent]}"></span>
|
|
||||||
<span class="label">{continent}</span>
|
|
||||||
<span class="value">{counts[continent]}<span class="total">/{contTotal}</span></span>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
<div class="donut-wrap">
|
|
||||||
{#if segments.length > 0}
|
|
||||||
<svg viewBox="0 0 180 180" class="donut-svg">
|
|
||||||
{#each segments as seg}
|
|
||||||
<g class="seg-group">
|
|
||||||
<path d={seg.path} fill={seg.color} />
|
|
||||||
<text x={seg.lx} y={seg.ly} text-anchor="middle" dominant-baseline="middle" class="donut-label" style="font-size: {seg.angle < 20 ? 12 : 15}px">{seg.cont}</text>
|
|
||||||
</g>
|
|
||||||
{/each}
|
|
||||||
<circle cx="90" cy="90" r="30" fill="var(--bg-raised)" />
|
|
||||||
</svg>
|
|
||||||
{:else}
|
|
||||||
<svg viewBox="0 0 180 180" class="donut-svg">
|
|
||||||
<circle cx="90" cy="90" r="65" fill="var(--border)" />
|
|
||||||
<circle cx="90" cy="90" r="30" fill="var(--bg-raised)" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="divider"></div>
|
|
||||||
|
|
||||||
<div class="disclaimer">Contains all UN countries, Kosovo, Hong Kong and Taiwan</div>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
</div>
|
||||||
|
|
||||||
|
<div class="vdivider"></div>
|
||||||
|
|
||||||
|
<!-- progress bar -->
|
||||||
|
<div class="bar-block">
|
||||||
|
<span class="section-label" style="margin-bottom:6px">world coverage</span>
|
||||||
|
<div class="bar-bg">
|
||||||
|
<div class="bar-fill" style="width:{pct}%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="disclaimer">All UN countries · Kosovo · HK · Taiwan</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.panel {
|
.card {
|
||||||
flex: 0 0 min(360px, 25vw);
|
position: absolute;
|
||||||
background: var(--bg-raised);
|
top: 16px;
|
||||||
border-left: 1px solid var(--border);
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0,0,0,0.10), 0 1px 4px rgba(0,0,0,0.06);
|
||||||
|
border: 1px solid rgba(0,0,0,0.06);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
padding: 0 4px;
|
||||||
|
height: 110px;
|
||||||
|
z-index: 10;
|
||||||
font-family: var(--sans);
|
font-family: var(--sans);
|
||||||
transition: flex-basis 0.25s ease;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel.collapsed {
|
.stat-block {
|
||||||
flex: 0 0 28px;
|
display: flex;
|
||||||
border-left: none;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 0 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-content {
|
.big-num {
|
||||||
flex: 1;
|
font-size: 40px;
|
||||||
padding: 24px 28px;
|
font-weight: 300;
|
||||||
overflow-y: auto;
|
letter-spacing: -2px;
|
||||||
min-width: 0;
|
color: var(--text-h);
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-btn {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
align-self: flex-start;
|
|
||||||
background: var(--accent-bg);
|
|
||||||
border: none;
|
|
||||||
border-radius: 0 8px 8px 0;
|
|
||||||
padding: 14px 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: var(--accent);
|
|
||||||
transition: background 0.15s ease, padding 0.15s ease;
|
|
||||||
margin-top: 24px;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
.big-num.accent { color: var(--accent); }
|
||||||
|
|
||||||
.collapse-btn:hover {
|
.stat-sub {
|
||||||
background: var(--lavender-bg);
|
|
||||||
padding-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-btn::after {
|
|
||||||
content: attr(data-tip);
|
|
||||||
position: absolute;
|
|
||||||
right: calc(100% + 8px);
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
background: var(--text-h);
|
|
||||||
color: var(--bg-raised);
|
|
||||||
font-family: var(--sans);
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
white-space: nowrap;
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.15s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapse-btn:hover::after {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.headline {
|
|
||||||
font-family: var(--heading);
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
font-weight: 400;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.1em;
|
|
||||||
color: var(--accent);
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar-label {
|
|
||||||
font-family: var(--sans);
|
|
||||||
font-size: var(--text-xs);
|
|
||||||
font-weight: 400;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
color: var(--text-sub);
|
color: var(--text-sub);
|
||||||
display: block;
|
letter-spacing: 0.03em;
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-bar-wrap {
|
.vdivider {
|
||||||
display: flex;
|
width: 1px;
|
||||||
align-items: center;
|
height: 56px;
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-bar-bg {
|
|
||||||
flex: 1;
|
|
||||||
height: 18px;
|
|
||||||
background: var(--accent-bg);
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-bar-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, var(--accent-dark), var(--lavender));
|
|
||||||
border-radius: 10px;
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-bar-text {
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--text-h);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
height: 1px;
|
|
||||||
background: var(--border);
|
background: var(--border);
|
||||||
margin: 16px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 6px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dot {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
/* donut */
|
||||||
flex: 1;
|
.donut-block {
|
||||||
font-size: var(--text-sm);
|
|
||||||
font-weight: 300;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--text-h);
|
|
||||||
}
|
|
||||||
|
|
||||||
.total {
|
|
||||||
font-weight: 400;
|
|
||||||
color: var(--text-sub);
|
|
||||||
font-size: var(--text-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.donut-wrap {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
flex-direction: row;
|
||||||
margin: 20px 0;
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 0 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.donut-svg {
|
.donut-svg {
|
||||||
width: 160px;
|
width: 72px;
|
||||||
height: 160px;
|
height: 72px;
|
||||||
filter: drop-shadow(0 2px 8px rgba(99,102,241,0.15));
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.seg-group { cursor: pointer; }
|
||||||
|
.seg-group:hover path { opacity: 0.8; }
|
||||||
|
|
||||||
|
.donut-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 130px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.donut-label {
|
.tooltip {
|
||||||
fill: var(--text-h);
|
display: flex;
|
||||||
font-family: var(--sans);
|
align-items: center;
|
||||||
font-weight: 300;
|
gap: 7px;
|
||||||
pointer-events: none;
|
font-size: 13px;
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.15s ease;
|
|
||||||
}
|
}
|
||||||
|
.tooltip::before {
|
||||||
.seg-group:hover .donut-label {
|
content: '';
|
||||||
opacity: 1;
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--dot);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
.tt-name { font-weight: 400; color: var(--text-h); }
|
||||||
|
.tt-val { font-weight: 300; color: var(--text-sub); }
|
||||||
|
|
||||||
.disclaimer {
|
.section-label {
|
||||||
font-size: var(--text-xs);
|
font-size: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
color: var(--text-sub);
|
color: var(--text-sub);
|
||||||
line-height: 1.5;
|
}
|
||||||
text-align: center;
|
|
||||||
|
.hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-sub);
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bar */
|
||||||
|
.bar-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 28px;
|
||||||
|
gap: 0;
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
.bar-bg {
|
||||||
|
width: 100%;
|
||||||
|
height: 5px;
|
||||||
|
background: var(--bg-subtle);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, var(--accent), #a78bfa);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.disclaimer {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-sub);
|
||||||
|
opacity: 0.5;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user