diff --git a/script/mapbox.js b/script/mapbox.js
new file mode 100644
index 0000000..9993d45
--- /dev/null
+++ b/script/mapbox.js
@@ -0,0 +1,160 @@
+// script.js
+
+// 1️⃣ Your Mapbox access token
+mapboxgl.accessToken = 'pk.eyJ1IjoiY2FiYmJ5eSIsImEiOiJjbWRpYzY1MGgwYzA5Mm1xM25udDUzbGtpIn0.RX6zv4VI6r2vpj7IWidz0w';
+
+// 2️⃣ Initialize the map zoomed fully out (globe‑like)
+const map = new mapboxgl.Map({
+ container: 'map',
+ style: 'mapbox://styles/mapbox/streets-v12',
+ center: [0, 0],
+ zoom: 1,
+ attributionControl: false
+});
+
+// 3️⃣ Define your companies ([lat, lng], zoom, imageUrl, description)
+const companies = [
+ {
+ id: 'overview',
+ name: 'Overview',
+ coords: [40.14010599415405, -107.69483342077913],
+ zoom: 3,
+ imageUrl: 'images/wip.jpg',
+ description: `
+
Overview
+ Use the controls in the top left corner to jump to a spot!
+ `
+ },
+ {
+ id: 'tancha',
+ name: 'Tan‑Cha',
+ coords: [37.325382569107695, -122.01149036275054],
+ zoom: 9,
+ imageUrl: 'images/tancha.jpg',
+ description: `
+ Tan‑Cha
+ Wip.
+ `
+ },
+ {
+ id: 'pokehouse',
+ name: 'Poke House',
+ coords: [37.301113384436285, -121.9493462690826],
+ zoom: 9,
+ imageUrl: 'images/wip.jpg',
+ description: `
+ Poke House
+ Wip.
+ `
+ },
+ {
+ id: 'nanobase',
+ name: 'NanoBase',
+ coords: [34.68104506798117, 135.49167196036512],
+ zoom: 9,
+ imageUrl: 'images/work.jpg',
+ description: `
+ NanoBase
+ Developed automated payroll processing scripts in Python, reducing errors by 85%.
+ NanoBase Blog
+ `
+ },
+ {
+ id: 'lanner',
+ name: 'Lanner Electronics',
+ coords: [43.69824981965482, -79.62570806485385],
+ zoom: 9,
+ imageUrl: 'images/wip.jpg',
+ description: `
+ Lanner Electronics
+ Wip.
+ `
+ }
+];
+
+// 4️⃣ Populate the company list
+const listEl = document.getElementById('company-list');
+companies.forEach((company, idx) => {
+ const btn = document.createElement('button');
+ btn.textContent = company.name;
+ btn.id = company.id;
+ btn.addEventListener('click', () => selectCompany(idx));
+ listEl.appendChild(btn);
+});
+
+// Holder for mapboxgl.Marker instances
+const markers = [];
+
+// Reference to the .map-wrapper for our fixed popups
+const mapWrapper = document.querySelector('.map-wrapper');
+
+/**
+ * Renders a fixed-position popup (does not move with the map)
+ */
+function showFixedPopup(company, [lng, lat]) {
+ // Remove any existing fixed popups
+ document.querySelectorAll('.fixed-popup').forEach(el => el.remove());
+
+ // Build the popup element
+ const popupEl = document.createElement('div');
+ popupEl.className = 'fixed-popup';
+ popupEl.innerHTML = `
+
+ ${company.name}
+ `;
+
+ // Attach into the map wrapper (above the map canvas)
+ mapWrapper.appendChild(popupEl);
+}
+
+/**
+ * Handles company selection:
+ * - cleans up old markers/popups
+ * - flies the map
+ * - updates the blurb
+ * - adds marker + fixed popup (if not overview)
+ */
+function selectCompany(index) {
+ const { coords, zoom, description, id } = companies[index];
+ const [lat, lng] = coords;
+
+ // Remove existing markers
+ markers.forEach(m => m.remove());
+ markers.length = 0;
+
+ // Remove any existing popups
+ document.querySelectorAll('.mapboxgl-popup, .fixed-popup').forEach(el => el.remove());
+
+ // Fly to the new location
+ map.flyTo({
+ center: [lng, lat],
+ zoom,
+ speed: 1.5,
+ essential: true
+ });
+
+ // Update the description blurb
+ document.getElementById('blurb').innerHTML = description;
+
+ // Highlight the active button
+ document.querySelectorAll('.company-list button')
+ .forEach(b => b.classList.toggle('active', b.id === id));
+
+ // If it's the overview, skip marker + popup
+ if (index === 0) return;
+
+ // Add a marker
+ const marker = new mapboxgl.Marker()
+ .setLngLat([lng, lat])
+ .addTo(map);
+ markers.push(marker);
+
+ // Show our custom fixed popup
+ showFixedPopup(companies[index], [lng, lat]);
+}
+
+// Initialize on load with the overview
+selectCompany(0);
diff --git a/script/scroll.js b/script/scroll.js
new file mode 100644
index 0000000..e5ded73
--- /dev/null
+++ b/script/scroll.js
@@ -0,0 +1,59 @@
+// scroll.js
+
+document.addEventListener('DOMContentLoaded', () => {
+ // ─────────────────────────────────────────────────────────────────────────────
+ // 1) Vertical “zoom‑in/out” on .scroll‑section using IntersectionObserver
+ // ─────────────────────────────────────────────────────────────────────────────
+ const sections = document.querySelectorAll('.scroll-section');
+ const io = new IntersectionObserver((entries) => {
+ entries.forEach(entry => {
+ entry.target.classList.toggle('in-view', entry.isIntersecting);
+ });
+ }, { threshold: 0.15 });
+ sections.forEach(sec => io.observe(sec));
+
+ // ─────────────────────────────────────────────────────────────────────────────
+ // 2) Horizontal‑scroll “pin & pan” for the Hobbies section
+ // ─────────────────────────────────────────────────────────────────────────────
+ const wrapper = document.getElementById('hobbies');
+ if (!wrapper) return; // no Hobbies section → exit
+
+ // The element that will stay stuck in the viewport:
+ const stickySection = wrapper.querySelector('.horizontal-section');
+ // The inner flex row we translate left/right:
+ const inner = stickySection.querySelector('.horizontal-section__inner');
+
+ // A) Make the section itself “sticky” at the top of the viewport
+ stickySection.style.position = 'sticky';
+ stickySection.style.top = '3rem';
+ stickySection.style.height = '100vh';
+ stickySection.style.overflow = 'hidden';
+
+ // B) Compute how far we need to scroll to reveal all cards
+ let containerW = wrapper.clientWidth; // e.g. 70vw in px
+ let scrollableDist = inner.scrollWidth - containerW; // total horizontal span minus one screen
+
+ // C) Stretch the wrapper’s vertical height so that
+ // scrolling wrapperHeight(px) → moving inner by scrollableDist(px)
+ wrapper.style.height = `${window.innerHeight + scrollableDist}px`;
+
+ // D) On scroll, when within the wrapper’s “pin zone,”
+ // translate the inner row instead of moving vertically
+ window.addEventListener('scroll', () => {
+ const y = window.scrollY;
+ const top = wrapper.offsetTop;
+ const end = top + scrollableDist;
+
+ if (y >= top && y <= end) {
+ // map vertical scroll → horizontal translate
+ inner.style.transform = `translateX(-${y - top}px)`;
+ }
+ });
+
+ // E) Recompute on resize
+ window.addEventListener('resize', () => {
+ containerW = wrapper.clientWidth;
+ scrollableDist = inner.scrollWidth - containerW;
+ wrapper.style.height = `${window.innerHeight + scrollableDist}px`;
+ });
+});