diff --git a/index.html b/index.html
index 833d737c..c653d30d 100644
--- a/index.html
+++ b/index.html
@@ -67,12 +67,12 @@ <h3 style="margin-bottom: 5px">Other Pages</h3>
           </p>
         </div>
         <div id="sample" style="display: none;">
-          <div>
+          <div class="sampleInfo">
             <h1 id="title"></h1>
             <a id="src" target="_blank" rel="noreferrer" href="">See it on Github!</a>
             <p id="description"></p>
-            <div class="sampleContainer"></div>
           </div>
+          <div class="sampleContainer"></div>
         </div>
         <nav id="code" class="sourceFileNav">
           <div>
diff --git a/package-lock.json b/package-lock.json
index 0a91320a..3faa59c9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
         "@uiw/codemirror-theme-monokai": "^4.21.24",
         "codemirror": "^6.0.1",
         "dat.gui": "^0.7.6",
+        "showdown": "^2.1.0",
         "stats.js": "github:mrdoob/stats.js#b235d9c",
         "teapot": "^1.0.0",
         "wgpu-matrix": "^2.5.0"
@@ -25,6 +26,7 @@
         "@rollup/plugin-typescript": "^11.1.6",
         "@tsconfig/recommended": "^1.0.3",
         "@types/dat.gui": "^0.7.12",
+        "@types/showdown": "^2.0.6",
         "@types/stats.js": "^0.17.3",
         "@typescript-eslint/eslint-plugin": "^7.1.1",
         "@webgpu/types": "^0.1.40",
@@ -762,6 +764,12 @@
       "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
       "dev": true
     },
+    "node_modules/@types/showdown": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.6.tgz",
+      "integrity": "sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==",
+      "dev": true
+    },
     "node_modules/@types/stats.js": {
       "version": "0.17.3",
       "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz",
@@ -3782,6 +3790,29 @@
         "node": ">=8"
       }
     },
+    "node_modules/showdown": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz",
+      "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==",
+      "dependencies": {
+        "commander": "^9.0.0"
+      },
+      "bin": {
+        "showdown": "bin/showdown.js"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://www.paypal.me/tiviesantos"
+      }
+    },
+    "node_modules/showdown/node_modules/commander": {
+      "version": "9.5.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
+      "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
+      "engines": {
+        "node": "^12.20.0 || >=14"
+      }
+    },
     "node_modules/side-channel": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
diff --git a/package.json b/package.json
index d1d50e62..eadc0286 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
     "@uiw/codemirror-theme-monokai": "^4.21.24",
     "codemirror": "^6.0.1",
     "dat.gui": "^0.7.6",
+    "showdown": "^2.1.0",
     "stats.js": "github:mrdoob/stats.js#b235d9c",
     "teapot": "^1.0.0",
     "wgpu-matrix": "^2.5.0"
@@ -36,6 +37,7 @@
     "@rollup/plugin-typescript": "^11.1.6",
     "@tsconfig/recommended": "^1.0.3",
     "@types/dat.gui": "^0.7.12",
+    "@types/showdown": "^2.0.6",
     "@types/stats.js": "^0.17.3",
     "@typescript-eslint/eslint-plugin": "^7.1.1",
     "@webgpu/types": "^0.1.40",
diff --git a/public/css/SampleLayout.css b/public/css/SampleLayout.css
index e8fd6d94..fe186a40 100644
--- a/public/css/SampleLayout.css
+++ b/public/css/SampleLayout.css
@@ -1,3 +1,9 @@
+#sample {
+  flex: 1 1 auto;
+  display: flex;
+  flex-direction: column;
+}
+
 .sampleContainer {
   text-align: center;
   width: 100%;
diff --git a/public/css/styles.css b/public/css/styles.css
index 57618326..c96b0b41 100644
--- a/public/css/styles.css
+++ b/public/css/styles.css
@@ -30,6 +30,8 @@ a:hover {
 }
 
 main {
+  display: flex;
+  flex-direction: column;
   position: relative;
   flex: 1;
   background: black;
diff --git a/rollup.config.js b/rollup.config.js
index 33d1fa91..d58c8181 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -112,6 +112,7 @@ export default [
     ],
     plugins: [
       nodeResolve(),
+      commonjs(),
       filenamePlugin(),
       typescript({ tsconfig: './src/tsconfig.json' }),
     ],
diff --git a/sample/bundleCulling/meta.ts b/sample/bundleCulling/meta.ts
new file mode 100644
index 00000000..4aa14adb
--- /dev/null
+++ b/sample/bundleCulling/meta.ts
@@ -0,0 +1,10 @@
+export default {
+  name: 'Bundle Culling',
+  description: `A demonstration of using frustum culling with render bundles through indirect instanced draw calls.
+
+Source at https://github.com/toji/webgpu-bundle-culling/
+`,
+  filename: __DIRNAME__,
+  url: 'https://toji.github.io/webgpu-bundle-culling/',
+  sources: [],
+};
diff --git a/sample/clusteredShading/meta.ts b/sample/clusteredShading/meta.ts
new file mode 100644
index 00000000..ff79e373
--- /dev/null
+++ b/sample/clusteredShading/meta.ts
@@ -0,0 +1,10 @@
+export default {
+  name: 'Clustered Shading',
+  description: `Shows a simple clustered forward shading renderer.
+
+Source at https://github.com/toji/webgpu-clustered-shading/
+`,
+  filename: __DIRNAME__,
+  url: 'https://toji.github.io/webgpu-clustered-shading/',
+  sources: [],
+};
diff --git a/sample/metaballs/meta.ts b/sample/metaballs/meta.ts
new file mode 100644
index 00000000..e0b01b20
--- /dev/null
+++ b/sample/metaballs/meta.ts
@@ -0,0 +1,10 @@
+export default {
+  name: 'Metaballs',
+  description: `This example shows an implementation of metaballs with WebGPU.
+
+Source at https://github.com/toji/webgpu-metaballs/
+`,
+  filename: __DIRNAME__,
+  url: 'https://toji.github.io/webgpu-metaballs/',
+  sources: [],
+};
diff --git a/sample/pristineGrid/meta.ts b/sample/pristineGrid/meta.ts
new file mode 100644
index 00000000..55784fb9
--- /dev/null
+++ b/sample/pristineGrid/meta.ts
@@ -0,0 +1,10 @@
+export default {
+  name: 'Pristine Grid',
+  description: `A simple WebGPU implementation of the "Pristine Grid" technique described in this wonderful little blog post: https://bgolus.medium.com/the-best-darn-grid-shader-yet-727f9278b9d8.
+
+Source at https://github.com/toji/pristine-grid-webgpu/
+`,
+  filename: __DIRNAME__,
+  url: 'https://toji.github.io/pristine-grid-webgpu/',
+  sources: [],
+};
diff --git a/sample/spookyball/meta.ts b/sample/spookyball/meta.ts
new file mode 100644
index 00000000..b1145bcb
--- /dev/null
+++ b/sample/spookyball/meta.ts
@@ -0,0 +1,10 @@
+export default {
+  name: 'Spookyball',
+  description: `This example shows a simple game made with WebGPU.
+
+Source at https://github.com/toji/spookyball
+`,
+  filename: __DIRNAME__,
+  url: 'https://spookyball.com',
+  sources: [],
+};
diff --git a/src/main.ts b/src/main.ts
index c29f90a6..0eda53c2 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -5,6 +5,11 @@ import { EditorView } from '@codemirror/view';
 import { EditorState } from '@codemirror/state';
 import { javascript } from '@codemirror/lang-javascript';
 import { basicSetup } from 'codemirror';
+import { Converter } from 'showdown';
+
+const markdownConverter = new Converter({
+  simplifiedAutoLink: true,
+});
 
 /**
  * Gets an element unconditionally so TS doesn't complain.
@@ -84,6 +89,11 @@ function setSourceTabHash(event: PointerEvent, sourceInfo: SourceInfo) {
   setSourceTab(sourceInfo);
 }
 
+// Non authoritative test that url is for same domain
+function isSameDomain(url: string) {
+  return new URL(url, window.location.href).origin === window.location.origin;
+}
+
 // That current sample so we don't reload an iframe if the user picks the same sample.
 let currentSampleInfo: SampleInfo | undefined;
 
@@ -100,10 +110,10 @@ function setSampleIFrame(
     return;
   }
   sampleContainerElem.innerHTML = '';
-  descriptionElem.textContent = '';
+  descriptionElem.innerHTML = '';
 
   currentSampleInfo = sampleInfo;
-  const { name, description, filename, sources } = sampleInfo || {
+  const { name, description, filename, url, sources } = sampleInfo || {
     name: '',
     description: '',
     filename: '',
@@ -111,14 +121,15 @@ function setSampleIFrame(
   };
 
   titleElem.textContent = name;
-  descriptionElem.textContent = description;
+  descriptionElem.innerHTML = markdownConverter.makeHtml(description);
 
   // Replace the iframe because changing src adds to the user's history.
   sampleContainerElem.innerHTML = '';
   if (filename) {
-    sampleContainerElem.appendChild(
-      el('iframe', { src: `${filename}${search}`, style: { height: '600px' } })
-    );
+    const src = url || `${filename}${search}`;
+    sampleContainerElem.appendChild(el('iframe', { src }));
+    sampleContainerElem.style.height = sources.length > 0 ? '600px' : '100%';
+
     // hide intro and show sample
     introElem.style.display = 'none';
     sampleElem.style.display = '';
@@ -131,13 +142,15 @@ function setSampleIFrame(
   // create source tabs
   codeTabsElem.innerHTML = '';
   sourcesElem.innerHTML = '';
+  sourcesElem.style.display = sources.length > 0 ? '' : 'none';
   sources.forEach((source, i) => {
+    const { path } = source;
     const active = (i === 0).toString();
     const name = basename(source.path);
     codeTabsElem.appendChild(
       el('li', {}, [
         el('a', {
-          href: `#${source.path}`,
+          href: `#${path}`,
           textContent: name,
           dataset: {
             active,
@@ -157,7 +170,8 @@ function setSampleIFrame(
       },
     });
     sourcesElem.appendChild(elem);
-    makeCodeMirrorEditor(elem, `${filename}/${source.path}`);
+    const url = isSameDomain(path) ? `${filename}/${path}` : source.path;
+    makeCodeMirrorEditor(elem, url);
   });
 }
 
diff --git a/src/samples.ts b/src/samples.ts
index f7facdd0..212e2585 100644
--- a/src/samples.ts
+++ b/src/samples.ts
@@ -1,7 +1,9 @@
 import aBuffer from '../sample/a-buffer/meta';
 import animometer from '../sample/animometer/meta';
 import bitonicSort from '../sample/bitonicSort/meta';
+import bundleCulling from '../sample/bundleCulling/meta';
 import cameras from '../sample/cameras/meta';
+import clusteredShading from '../sample/clusteredShading/meta';
 import cornell from '../sample/cornell/meta';
 import computeBoids from '../sample/computeBoids/meta';
 import cubemap from '../sample/cubemap/meta';
@@ -12,8 +14,10 @@ import helloTriangle from '../sample/helloTriangle/meta';
 import helloTriangleMSAA from '../sample/helloTriangleMSAA/meta';
 import imageBlur from '../sample/imageBlur/meta';
 import instancedCube from '../sample/instancedCube/meta';
+import metaballs from '../sample/metaballs/meta';
 import normalMap from '../sample/normalMap/meta';
 import particles from '../sample/particles/meta';
+import pristineGrid from '../sample/pristineGrid/meta';
 import renderBundles from '../sample/renderBundles/meta';
 import resizeCanvas from '../sample/resizeCanvas/meta';
 import reversedZ from '../sample/reversedZ/meta';
@@ -21,6 +25,7 @@ import rotatingCube from '../sample/rotatingCube/meta';
 import samplerParameters from '../sample/samplerParameters/meta';
 import shadowMapping from '../sample/shadowMapping/meta';
 import skinnedMesh from '../sample/skinnedMesh/meta';
+import spookyball from '../sample/spookyball/meta';
 import texturedCube from '../sample/texturedCube/meta';
 import twoCubes from '../sample/twoCubes/meta';
 import videoUploading from '../sample/videoUploading/meta';
@@ -34,7 +39,8 @@ export type SampleInfo = {
   name: string;
   tocName?: string;
   description: string;
-  filename: string;
+  filename: string; // used if sample is local
+  url?: string; // used if sample is remote
   sources: SourceInfo[];
 };
 
@@ -74,6 +80,7 @@ export const pageCategories: PageCategory[] = [
       samplerParameters,
       reversedZ,
       renderBundles,
+      spookyball,
     },
   },
 
@@ -123,6 +130,19 @@ export const pageCategories: PageCategory[] = [
     },
   },
 
+  // External examples
+  {
+    title: 'External Samples',
+    description: `Samples from around the net.`,
+    samples: {
+      bundleCulling,
+      metaballs,
+      pristineGrid,
+      clusteredShading,
+      spookyball,
+    },
+  },
+
   // Samples whose primary purpose is to benchmark WebGPU performance.
   {
     title: 'Benchmarks',