Skip to content

Commit e98ccb0

Browse files
authored
add examples grid (#1834)
1 parent 5a2fcf2 commit e98ccb0

File tree

5 files changed

+206
-3
lines changed

5 files changed

+206
-3
lines changed

docs/.vitepress/theme/CustomLayout.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup>
22
33
import DefaultTheme from "vitepress/theme-without-fonts";
4+
import ExamplesGrid from "./ExamplesGrid.vue";
45
import ObservablePromo from "./ObservablePromo.vue";
56
67
const {Layout} = DefaultTheme;
@@ -9,8 +10,29 @@ const {Layout} = DefaultTheme;
910

1011
<template>
1112
<Layout>
13+
<template #home-features-before>
14+
<ExamplesGrid />
15+
</template>
1216
<template #home-features-after>
1317
<ObservablePromo />
1418
</template>
1519
</Layout>
1620
</template>
21+
22+
<style>
23+
24+
.VPHome {
25+
overflow-x: hidden; /* iOS */
26+
}
27+
28+
.VPHome .VPFeature {
29+
background-color: rgba(246, 246, 247, 0.5);
30+
-webkit-backdrop-filter: blur(10px); /* Safari */
31+
backdrop-filter: blur(10px);
32+
}
33+
34+
.dark .VPHome .VPFeature {
35+
background-color: rgba(37, 37, 41, 0.5);
36+
}
37+
38+
</style>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<script setup>
2+
3+
import * as d3 from "d3";
4+
import {ref, shallowRef, onMounted, onUnmounted} from "vue";
5+
import {data} from "./gallery.data.js";
6+
7+
let observer;
8+
let pointerframe;
9+
let clientX;
10+
11+
const n = 60; // maximum number of examples to show
12+
const slice = d3.shuffler(d3.randomLcg(d3.utcDay()))(data.slice()).slice(0, n);
13+
const sample = shallowRef(slice.slice(0, 55)); // initial guess
14+
const container = ref();
15+
const xn = ref(10); // number of rows
16+
const yn = ref(5); // number of columns
17+
const x = ref(0.5); // normalized horizontal pointer position
18+
19+
// Some browsers trigger pointermove more frequently than desirable, so we
20+
// debounce events for a smooth transitions.
21+
function onpointermove(event) {
22+
if (!pointerframe) pointerframe = requestAnimationFrame(afterpointermove);
23+
clientX = event.clientX;
24+
}
25+
26+
function afterpointermove() {
27+
pointerframe = null;
28+
x.value = clientX / document.body.clientWidth;
29+
}
30+
31+
onMounted(() => {
32+
observer = new ResizeObserver(() => {
33+
const w = parseFloat(getComputedStyle(container.value).getPropertyValue("--grid-width"));
34+
xn.value = Math.ceil(document.body.clientWidth / w) + 3; // overflow columns
35+
yn.value = Math.min(Math.round(640 / w + 2), Math.floor(n / xn.value)); // 640 is grid height
36+
sample.value = slice.slice(0, yn.value * xn.value);
37+
});
38+
observer.observe(document.body);
39+
addEventListener("pointermove", onpointermove);
40+
});
41+
42+
onUnmounted(() => {
43+
observer.disconnect();
44+
removeEventListener("pointermove", onpointermove);
45+
});
46+
47+
</script>
48+
49+
<template>
50+
<div :class="$style.examples" ref="container" :style="`transform: translate(${60 - x * 10}vw, 33%);`">
51+
<div v-for="(d, i) in sample" :style="`--delay: ${((i % xn) / xn + (d3.randomLcg(1 / i)()) - 0.4) * 1}s;`">
52+
<a :href="`https://observablehq.com/${d.path}${d.path.includes('?') ? '&' : '?'}intent=fork`" :title="[d.title, d.author].filter(Boolean).join('\n')" target="_blank" :style="`--x: ${(i % xn) - xn / 2 + (Math.floor(i / xn) % 2) * 0.5}; --y: ${Math.floor(i / xn) - yn / 2};`">
53+
<img :src="`https://static.observableusercontent.com/thumbnail/${d.thumbnail}.jpg`" width="640" height="400" />
54+
</a>
55+
</div>
56+
</div>
57+
</template>
58+
59+
<style module>
60+
61+
.examples {
62+
position: relative;
63+
height: 640px;
64+
transition: transform 150ms ease-out;
65+
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.2));
66+
--grid-width: 140px;
67+
}
68+
69+
@media (min-width: 640px) {
70+
.examples {
71+
--grid-width: 160px;
72+
}
73+
}
74+
75+
@media (min-width: 960px) {
76+
.examples {
77+
--grid-width: 200px;
78+
}
79+
}
80+
81+
/* The drop-shadow should not be affected by the hexagon clip-path. */
82+
.examples div {
83+
position: relative;
84+
transition: filter 250ms ease-out;
85+
animation: fade-in 350ms cubic-bezier(0.215, 0.610, 0.355, 1.000) var(--delay) backwards;
86+
}
87+
88+
.examples div:hover {
89+
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.4));
90+
z-index: 3;
91+
}
92+
93+
.examples a {
94+
position: absolute;
95+
--transform: perspective(75em) rotateX(30deg) rotateZ(-7deg) translate(calc(var(--x) * 100%), calc(var(--y) * 86.67%)) scale(1.145);
96+
transform: var(--transform);
97+
animation: drop-in 350ms cubic-bezier(0.215, 0.610, 0.355, 1.000) var(--delay) backwards;
98+
transition: transform 250ms ease-out;
99+
clip-path: polygon(50.0% 100.0%, 93.3% 75.0%, 93.3% 25.0%, 50.0% 0.0%, 6.7% 25.0%, 6.7% 75.0%);
100+
}
101+
102+
.examples a:hover {
103+
transform: var(--transform) translateZ(10px) scale(1.1);
104+
}
105+
106+
.examples img {
107+
aspect-ratio: 1;
108+
object-fit: cover;
109+
width: var(--grid-width);
110+
}
111+
112+
@keyframes fade-in {
113+
from {
114+
filter: blur(20px);
115+
opacity: 0;
116+
}
117+
to {
118+
filter: none;
119+
opacity: 1;
120+
}
121+
}
122+
123+
@keyframes drop-in {
124+
from {
125+
transform: var(--transform) translateY(-100px) translateZ(400px);
126+
}
127+
to {
128+
transform: var(--transform);
129+
}
130+
}
131+
132+
</style>

docs/.vitepress/theme/gallery.data.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {Runtime} from "@observablehq/runtime";
2+
3+
export default {
4+
async load() {
5+
const runtime = new Runtime();
6+
const module = runtime.module((await import("https://api.observablehq.com/@observablehq/plot-gallery.js?v=4")).default);
7+
const data = [];
8+
module.define("md", () => String.raw);
9+
module.redefine("previews", () => (chunk) => data.push(...chunk));
10+
const values = [];
11+
for (const output of module._resolve("previews")._outputs) {
12+
if (output._name) {
13+
values.push(module.value(output._name));
14+
}
15+
}
16+
await Promise.all(values);
17+
return data;
18+
}
19+
};

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
"prepublishOnly": "rm -rf dist && rollup -c",
3737
"postpublish": "git push && git push --tags",
3838
"dev": "vite",
39-
"docs:dev": "vitepress dev docs",
40-
"docs:build": "vitepress build docs",
39+
"docs:dev": "node --experimental-network-imports node_modules/vitepress/dist/node/cli.js dev docs",
40+
"docs:build": "node --experimental-network-imports node_modules/vitepress/dist/node/cli.js build docs",
4141
"docs:preview": "vitepress preview docs"
4242
},
4343
"_moduleAliases": {
@@ -48,6 +48,7 @@
4848
],
4949
"devDependencies": {
5050
"@esbuild-kit/core-utils": "^3.1.0",
51+
"@observablehq/runtime": "^5.7.3",
5152
"@rollup/plugin-commonjs": "^25.0.2",
5253
"@rollup/plugin-json": "^6.0.0",
5354
"@rollup/plugin-node-resolve": "^15.0.1",

yarn.lock

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,30 @@
521521
"@nodelib/fs.scandir" "2.1.5"
522522
fastq "^1.6.0"
523523

524+
"@observablehq/inspector@^5.0.0":
525+
version "5.0.0"
526+
resolved "https://registry.yarnpkg.com/@observablehq/inspector/-/inspector-5.0.0.tgz#7dec08d4fa20dfb79977ce62f7cc4a814b44e595"
527+
integrity sha512-Vvg/TQdsZTUaeYbH0IKxYEz37FbRO6kdowoz2PrHLQif54NC1CjEihEjg+ZMSBn587GQxTFABu0CGkFZgtR1UQ==
528+
dependencies:
529+
isoformat "^0.2.0"
530+
531+
"@observablehq/runtime@^5.7.3":
532+
version "5.9.1"
533+
resolved "https://registry.yarnpkg.com/@observablehq/runtime/-/runtime-5.9.1.tgz#fd79f9cce8a165e123021ca2f72fe54e61f16d12"
534+
integrity sha512-ACNFixkIFVihIaWrDGXxgjxVj4/cHx26kdfGDVpEX1mEVqgP1SnfQMoJyZoqG23txs+uUcyr8LG37NkMEsNpdw==
535+
dependencies:
536+
"@observablehq/inspector" "^5.0.0"
537+
"@observablehq/stdlib" "^5.0.0"
538+
539+
"@observablehq/stdlib@^5.0.0":
540+
version "5.8.1"
541+
resolved "https://registry.yarnpkg.com/@observablehq/stdlib/-/stdlib-5.8.1.tgz#50002b0d2a021890052d6f96700d86d15ca18c7d"
542+
integrity sha512-ng6QQSzFbPQnMMeCUhUl/EPzpyrwfmGsujztGdPXS1ZYrLoAc9co4rhUC5Vv6dBh8E4yzZkxwNyTs73bLN4alQ==
543+
dependencies:
544+
d3-array "^3.2.0"
545+
d3-dsv "^3.0.1"
546+
d3-require "^1.3.0"
547+
524548
"@one-ini/wasm@0.1.1":
525549
version "0.1.1"
526550
resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323"
@@ -1434,7 +1458,7 @@ d3-delaunay@6:
14341458
d3-dispatch "1 - 3"
14351459
d3-selection "3"
14361460

1437-
"d3-dsv@1 - 3", d3-dsv@3:
1461+
"d3-dsv@1 - 3", d3-dsv@3, d3-dsv@^3.0.1:
14381462
version "3.0.1"
14391463
resolved "https://registry.yarnpkg.com/d3-dsv/-/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73"
14401464
integrity sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==
@@ -1517,6 +1541,11 @@ d3-random@3:
15171541
resolved "https://registry.yarnpkg.com/d3-random/-/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4"
15181542
integrity sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==
15191543

1544+
d3-require@^1.3.0:
1545+
version "1.3.0"
1546+
resolved "https://registry.yarnpkg.com/d3-require/-/d3-require-1.3.0.tgz#2b97f5e2ebcb64ac0c63c11f30056aea1c74f0ec"
1547+
integrity sha512-XaNc2azaAwXhGjmCMtxlD+AowpMfLimVsAoTMpqrvb8CWoA4QqyV12mc4Ue6KSoDvfuS831tsumfhDYxGd4FGA==
1548+
15201549
d3-scale-chromatic@3:
15211550
version "3.0.0"
15221551
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a"

0 commit comments

Comments
 (0)