Skip to content
42 changes: 40 additions & 2 deletions website/src/Home.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,16 @@
<Layout>
<ClickEffect />
<div id="info">
<CollapsableModal collapsedIcon={Info} isOpen={!state.isFirstInteraction}>
<CollapsableModal collapsedIcon={Info}
isOpen={state.isInfoOpen}
onOpen={() => {
state.isSearchOpen = false;
state.isInfoOpen = true;
}}
onClose={() => {
state.isInfoOpen = false;
}}
>
<h2>All The Views In The World</h2>
<p>We've calculated all the views on the planet.</p>
<p>
Expand Down Expand Up @@ -321,7 +330,8 @@
</div>
</Layout>

<style>
<style lang="scss">
@use "./styles/variables.scss" as *;
#info {
position: fixed;
top: 1em;
Expand Down Expand Up @@ -351,6 +361,34 @@
gap: 1em;
}

/* Mobile-only layout tweaks */
@media (max-width: $mobile-break) {
/* slightly smaller collapsed modal icons on Home only */
:global(#info .collapseable_modal .modal__open svg) {
width: 1rem;
height: 1rem;
}

/* align top of first info button roughly with search bar */
#info {
top: 1.08rem;
right: 0.75rem;
max-width: min(22rem, 80vw);
gap: 0.75rem;
}

:global(#info .collapseable_modal > div) {
padding: 0.75em;
}

#layout_toggles {
left: 0.5rem;
bottom: 0.5rem;
gap: 0.4rem;
flex-direction: column;
}
}

.unclickable_icon {
display: inline-block;
scale: 0.7;
Expand Down
38 changes: 3 additions & 35 deletions website/src/Layout.svelte
Original file line number Diff line number Diff line change
@@ -1,34 +1,9 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { onDestroy } from 'svelte';
import 'maplibre-gl/dist/maplibre-gl.css';
import 'accessible-nprogress/src/styles.css';
import { MapboxSearchBox } from '@mapbox/search-js-web';
import SearchBox from './components/SearchBox.svelte';
import { state } from './state.svelte.ts';
import { disablePointer } from './utils.ts';

onMount(() => {
const searchBox = new MapboxSearchBox();
searchBox.accessToken =
'pk.eyJ1IjoidG9tYmgiLCJhIjoiY2p4cWlqNnY1MDFhZDNscXc5YXJpcTJzciJ9.7gGR5t8KEAY0ZoXfTVBcng';
searchBox.options = {
types: 'poi,place,country',
poi_category: 'mountain,natural_feature',
};
searchBox.addEventListener('retrieve', (e) => {
const feature = e.detail;
const coordinates = feature.features[0]?.geometry.coordinates;
state.map?.flyTo({
center: [coordinates[0], coordinates[1]],
zoom: 11,
});
state.isFlying = true;

disablePointer();
});

// @ts-expect-error: `document` can't be null.
document.querySelector('#search-box').appendChild(searchBox);
});

onDestroy(() => {
state.map?.remove();
Expand All @@ -37,7 +12,7 @@

<div id="map"></div>

<div id="search-box"></div>
<SearchBox />

<main>
<slot />
Expand All @@ -49,11 +24,4 @@
height: 100%;
width: 100%;
}

#search-box {
position: absolute;
width: 300px;
margin-left: 17px;
margin-top: 17px;
}
</style>
18 changes: 16 additions & 2 deletions website/src/components/CollapsableModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,32 @@

export let collapsedIcon: Component;
export let isOpen = true;
export let onOpen: (() => void) | undefined = undefined;
export let onClose: (() => void) | undefined = undefined;
const __buttonSize = 18;
</script>

<div class="collapseable_modal {isOpen ? '' : 'modal__collapsed'}">
<div>
{#if isOpen}
<button class="modal__close" on:click={() => (isOpen = false)}>
<button
class="modal__close"
on:click={() => {
isOpen = false;
onClose?.();
}}
>
<Minimize2 size={__buttonSize} />
</button>
<slot />
{:else}
<button class="modal__open" on:click={() => (isOpen = true)}>
<button
class="modal__open"
on:click={() => {
isOpen = true;
onOpen?.();
}}
>
<svelte:component this={collapsedIcon} size={__buttonSize} />
</button>
{/if}
Expand Down
9 changes: 8 additions & 1 deletion website/src/components/LayerToggle.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
<img src={image} alt="Layer toggle" />
</button>

<style>
<style lang="scss">
@use "../styles/variables.scss" as *;
button {
all: unset;
cursor: pointer;
Expand Down Expand Up @@ -54,4 +55,10 @@
}
}
}
@media /* mobile-only */ (max-width: $mobile-break) {
.layer_toggle {
width: 2.75rem;
height: 2.75rem;
}
}
</style>
117 changes: 117 additions & 0 deletions website/src/components/SearchBox.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<script lang="ts">
import { Search } from '@lucide/svelte';
import { MapboxSearchBox } from '@mapbox/search-js-web';
import { onMount } from 'svelte';
import { state } from '../state.svelte.ts';
import { disablePointer } from '../utils.ts';

onMount(() => {
const searchBox = new MapboxSearchBox();
searchBox.accessToken =
'pk.eyJ1IjoidG9tYmgiLCJhIjoiY2p4cWlqNnY1MDFhZDNscXc5YXJpcTJzciJ9.7gGR5t8KEAY0ZoXfTVBcng';
searchBox.options = {
types: 'poi,place,country',
poi_category: 'mountain,natural_feature',
};
searchBox.addEventListener('retrieve', (e) => {
const feature = e.detail;
const coordinates = feature.features[0]?.geometry.coordinates;
state.map?.flyTo({
center: [coordinates[0], coordinates[1]],
zoom: 11,
});
state.isFlying = true;

disablePointer();
});

/* #search-widget is hidden (display: none) on mobile until the user expands search;
we still mount here so the box is ready. Guard in case the node isn't available yet. */
const container = document.querySelector('#search-widget');
if (container) {
// @ts-expect-error: MapboxSearchBox is a custom element
container.appendChild(searchBox);
}
});
</script>

<div id="search-box" class:is-open={state.isSearchOpen}>
<div id="search-widget"></div>
<button
id="search-toggle"
type="button"
on:click={() => {
state.isSearchOpen = true;
state.isInfoOpen = false;
}}
>
<Search size={18} />
</button>
</div>

<style lang="scss">
@use "../styles/variables.scss" as *;
#search-box {
position: absolute;
width: 300px;
margin-left: 17px;
margin-top: 17px;
}

#search-widget {
width: 100%;
}

#search-toggle {
all: unset;
cursor: pointer;
display: none;
}

/* Mobile: search behaves like a compact icon button that expands into the full input */
@media (max-width: $mobile-break) {
#search-box {
width: 2.75rem;
height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background-color: white;
border-radius: 3px;
z-index: 2;
}

#search-toggle {
display: flex;
width: 1rem;
height: 2.5rem;
align-items: center;
justify-content: center;
color: #141f41;
}

/* collapsed: only icon button visible */
#search-widget {
display: none;
}

/* expanded: show full widget, hide icon, grow container */
#search-box.is-open {
width: min(18rem, 80vw);
height: auto;
overflow: visible;
background-color: transparent;
border-radius: 0;
}

#search-box.is-open #search-widget {
display: block;
}

#search-box.is-open #search-toggle {
display: none;
}
}
</style>

5 changes: 5 additions & 0 deletions website/src/state.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ const heatmapConfig: HeatmapConfig = {
intensity: 1 - 0.5,
};
const isFlying = false;
// Mobile-only events
const isSearchOpen = false;
const isInfoOpen = true;

export const state = $state({
map,
Expand All @@ -28,4 +31,6 @@ export const state = $state({
bruteForceLoadingLine,
heatmapConfig,
isFlying,
isSearchOpen,
isInfoOpen,
});
15 changes: 14 additions & 1 deletion website/src/styles/index.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@use "sass:color";

@use "./variables.scss" as *;

body {
Expand Down Expand Up @@ -49,3 +48,17 @@ a {
.maplibregl-canvas {
background-color: #131e40;
}

/* Mobile-only tweaks */
@media (max-width: $mobile-break) {
/* Map attribution: make box narrower and text more compact */
.maplibregl-ctrl-attrib {
max-width: 200px;
}
.maplibregl-ctrl-attrib-inner {
white-space: normal; /* allow wrapping */
font-size: 10px;
line-height: 1.2;
padding: 2px 4px;
}
}
1 change: 1 addition & 0 deletions website/src/styles/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ $primary-colour: #fd6612;
$secondary-colour: #141f41;
$secondary-light: #b8bdd6;
$longest-line: #5bc0eb;
$mobile-break: 730px;

:root {
--primary-colour: #{$primary-colour};
Expand Down