Files
Overheard/src/lib/components/GlobalCountPill.svelte

138 lines
4.7 KiB
Svelte

<script>
import { onMount } from 'svelte';
import { globalCount, refreshGlobalCount } from '$lib/stores/globalCountStore.js';
// --- mobile tap/fade easter egg ----------------------------------------
// On desktop, the secondary "places remember..." line is always visible
// (see .secondary-desktop in the stylesheet below, shown via the
// min-width media query). On mobile it's hidden by default and only
// fades in when the pill is tapped, holds for a few seconds, then fades
// back out on its own - an easter egg rather than a persistent UI
// element. `secondaryVisible` is local Svelte 5 rune state driving the
// opacity/max-height transition on .secondary-mobile; .secondary-desktop
// is unaffected by it.
let secondaryVisible = $state(false);
let fadeOutTimer;
onMount(() => {
refreshGlobalCount();
});
function handleTap() {
secondaryVisible = true;
// restart the hold timer on every tap, so a second tap while the
// text is already visible just extends how long it stays up rather
// than racing with the previous fade-out
clearTimeout(fadeOutTimer);
fadeOutTimer = setTimeout(() => {
secondaryVisible = false;
}, 3000);
}
</script>
<!-- only render once the first fetch resolves, so the pill never briefly
shows a misleading "0 messages have been left worldwide" before the
real count arrives -->
{#if $globalCount !== null}
<button type="button" class="count-pill" onclick={handleTap}>
<span class="count-main">{$globalCount.toLocaleString()} messages have been left worldwide</span>
<!-- desktop: always-visible secondary line (hidden on mobile via the
same media query that reveals it - see <style> below) -->
<span class="count-secondary secondary-desktop">
while not all of them are visible to you or have been echoed long enough to reach you, places remember and memories live on in intangible ways.
</span>
<!-- mobile: same text, but only fades in on tap (handleTap above)
and is hidden entirely on desktop -->
<span class="count-secondary secondary-mobile" class:visible={secondaryVisible}>
while not all of them are visible to you or have been echoed long enough to reach you, places remember and memories live on in intangible ways.
</span>
</button>
{/if}
<style>
/* small rounded pill, top-right of the app on every page - cream
background + subtle shadow + muted text, matching the "first here"
speech bubble and opening ritual overlay's palette */
.count-pill {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 150;
max-width: 60vw;
background: #f9f7f4;
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: 16px;
padding: 0.5rem 0.9rem;
text-align: left;
font-family: Georgia, 'Times New Roman', Times, serif;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
cursor: pointer;
}
.count-main {
display: block;
font-size: 0.7rem;
color: #888;
line-height: 1.4;
letter-spacing: 0.02em;
}
.count-secondary {
display: block;
font-size: 0.65rem;
color: #aaa;
line-height: 1.5;
}
/* desktop-only secondary line: hidden on mobile by default */
.secondary-desktop {
display: none;
}
/* mobile-only secondary line: hidden + collapsed until tapped, then
fades in/out via .visible (toggled by handleTap above). max-height
(rather than display) is what allows the opacity transition to
actually animate instead of snapping. */
.secondary-mobile {
opacity: 0;
max-height: 0;
overflow: hidden;
margin-top: 0;
transition: opacity 0.6s ease, max-height 0.6s ease;
}
.secondary-mobile.visible {
opacity: 1;
max-height: 6rem;
margin-top: 0.35rem;
}
@media (min-width: 768px) {
.count-pill {
max-width: 320px;
padding: 0.65rem 1rem;
/* tap-to-reveal is mobile-only; desktop already shows everything,
so the pointer cursor would be misleading here */
cursor: default;
}
.count-main {
font-size: 0.8rem;
}
.secondary-desktop {
display: block;
margin-top: 0.35rem;
}
/* desktop already shows .secondary-desktop, so the mobile
tap-to-reveal copy is never shown here regardless of state */
.secondary-mobile {
display: none;
}
}
</style>