diff --git a/web/src/components/sections/InsideGlobe/BuildFilters.module.css b/web/src/components/sections/InsideGlobe/BuildFilters.module.css
new file mode 100644
index 00000000..26ae5ca9
--- /dev/null
+++ b/web/src/components/sections/InsideGlobe/BuildFilters.module.css
@@ -0,0 +1,49 @@
+.filters {
+ position: absolute;
+ top: 4px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 4;
+ display: flex;
+ gap: 2px;
+ padding: 2px;
+ background: var(--pf-cream);
+}
+
+.filters button {
+ appearance: none;
+ border: 0;
+ background: transparent;
+ color: var(--pf-ink-muted);
+ padding: 6px 12px;
+ font-family: var(--pf-mono);
+ font-size: 11px;
+ line-height: 16px;
+ white-space: nowrap;
+ cursor: pointer;
+}
+
+.filters button:hover {
+ color: var(--pf-ink);
+}
+
+.filters button[aria-pressed='true'] {
+ background: var(--pf-ink);
+ color: var(--pf-cream);
+}
+
+.filters button:focus-visible {
+ outline: 2px solid var(--pf-led);
+ outline-offset: 2px;
+}
+
+@media (max-width: 900px) {
+ .filters {
+ top: auto;
+ bottom: 8px;
+ }
+ .filters button {
+ min-height: 44px;
+ padding: 8px 12px;
+ }
+}
diff --git a/web/src/components/sections/InsideGlobe/BuildFilters.tsx b/web/src/components/sections/InsideGlobe/BuildFilters.tsx
new file mode 100644
index 00000000..8c11e6d8
--- /dev/null
+++ b/web/src/components/sections/InsideGlobe/BuildFilters.tsx
@@ -0,0 +1,23 @@
+'use client';
+
+import { BUILD_FILTERS } from './builds';
+import { useBuildSelection } from './useBuildSelection';
+import styles from './BuildFilters.module.css';
+
+export default function BuildFilters() {
+ const { filter, setFilter } = useBuildSelection();
+ return (
+
+ {BUILD_FILTERS.map(({ value, label }) => (
+
+ ))}
+
+ );
+}
diff --git a/web/src/components/sections/InsideGlobe/BuildIndex.test.tsx b/web/src/components/sections/InsideGlobe/BuildIndex.test.tsx
new file mode 100644
index 00000000..958366bf
--- /dev/null
+++ b/web/src/components/sections/InsideGlobe/BuildIndex.test.tsx
@@ -0,0 +1,64 @@
+import { beforeEach, describe, expect, it } from 'vitest';
+import { act, fireEvent, render, screen, within } from '@testing-library/react';
+import { useAppStore } from '@/store/useAppStore';
+import BuildFilters from './BuildFilters';
+import BuildIndex from './BuildIndex';
+import BuildRouteSync from './BuildRouteSync';
+import { builds } from './builds';
+
+beforeEach(() => {
+ useAppStore.setState({ selectedBuildId: null, insideFilter: 'all' });
+ window.history.replaceState(null, '', '/inside');
+});
+
+function mount(buildId: string | null = null) {
+ render(<>>);
+ return within(screen.getByRole('list', { name: 'Patternflow around the world' }));
+}
+
+describe('Inside browsing', () => {
+ it('shows every entry as a text link and filters to projects', () => {
+ const list = mount();
+ expect(list.getAllByRole('link')).toHaveLength(builds.length);
+ expect(list.queryByRole('img')).not.toBeInTheDocument();
+ fireEvent.click(screen.getByRole('button', { name: 'Projects' }));
+ expect(list.getAllByRole('link')).toHaveLength(2);
+ expect(list.getByRole('link', { name: /MOTIFLOW/ })).toHaveAttribute('href', '/inside/azmano-iran');
+ expect(list.getByRole('link', { name: /Raspberry Pi port/ })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Projects' })).toHaveAttribute('aria-pressed', 'true');
+ });
+
+ it('preserves a matching selection and clears a hidden pin and its URL', () => {
+ const list = mount();
+ fireEvent.click(list.getByRole('link', { name: /MOTIFLOW/ }));
+ fireEvent.click(screen.getByRole('button', { name: 'Projects' }));
+ expect(useAppStore.getState().selectedBuildId).toBe('iran-azmano');
+ expect(window.location.pathname).toBe('/inside/azmano-iran');
+ fireEvent.click(screen.getByRole('button', { name: 'In use' }));
+ expect(useAppStore.getState().selectedBuildId).toBeNull();
+ expect(window.location.pathname).toBe('/inside');
+ expect(list.queryAllByRole('link')).toHaveLength(0);
+ expect(screen.getByText('No entries in this category yet.')).toBeInTheDocument();
+ fireEvent.click(screen.getByRole('button', { name: 'Builds' }));
+ expect(list.getByRole('link', { name: /In the DJ booth/ })).toBeInTheDocument();
+ });
+
+ it('reveals a directly linked entry even after browsing another category', () => {
+ useAppStore.setState({ insideFilter: 'builds' });
+ const list = mount('iran-azmano');
+ expect(screen.getByRole('button', { name: 'All' })).toHaveAttribute('aria-pressed', 'true');
+ expect(list.getByRole('link', { name: /MOTIFLOW/ })).toHaveAttribute('aria-current', 'true');
+ });
+
+ it('reveals a pin restored by browser history without pushing another URL', () => {
+ const list = mount();
+ fireEvent.click(screen.getByRole('button', { name: 'In use' }));
+ act(() => {
+ window.history.replaceState(null, '', '/inside/day-france');
+ window.dispatchEvent(new PopStateEvent('popstate'));
+ });
+ expect(window.location.pathname).toBe('/inside/day-france');
+ expect(list.getByRole('link', { name: /Raspberry Pi port/ })).toHaveAttribute('aria-current', 'true');
+ expect(screen.getByRole('button', { name: 'All' })).toHaveAttribute('aria-pressed', 'true');
+ });
+});