From e1d53af11d2baa92e4602814e32aba14e4b1bff8 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 01:36:48 -0700 Subject: [PATCH 01/16] feat(site): add CI workflow, local dev script, SEO plugins, and Ruby pin - .github/workflows/ci.yml: build + htmlproofer on every non-master push/PR - scripts/serve.sh: local Jekyll dev server at localhost:4000 - jekyll-seo-tag + jekyll-sitemap added to Gemfile and _config.yml - .ruby-version pinned to 3.1.0 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 31 +++++++++++++++++++++++++++++++ .ruby-version | 1 + Gemfile | 2 ++ _config.yml | 2 ++ scripts/serve.sh | 4 ++++ 5 files changed, 40 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .ruby-version create mode 100755 scripts/serve.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..c553832da --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches-ignore: + - master + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + bundler-cache: true + + - name: Build site + run: bundle exec jekyll build + + - name: Check HTML + run: | + bundle exec htmlproofer ./_site \ + --disable-external \ + --ignore-urls "/localhost/" \ + --allow-hash-href + continue-on-error: true diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 000000000..fd2a01863 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.1.0 diff --git a/Gemfile b/Gemfile index 1498e641c..9236e7de6 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,6 @@ source "https://rubygems.org" gem "github-pages", group: :jekyll_plugins +gem "jekyll-seo-tag" +gem "jekyll-sitemap" gem 'wdm', '>= 0.1.0' if Gem.win_platform? \ No newline at end of file diff --git a/_config.yml b/_config.yml index 1d96b5394..7b9f95616 100644 --- a/_config.yml +++ b/_config.yml @@ -12,6 +12,8 @@ open_new_tab : true # Opens external URLs in ### Plugins ### plugins: - jemoji + - jekyll-seo-tag + - jekyll-sitemap ### Navbar Settings ### diff --git a/scripts/serve.sh b/scripts/serve.sh new file mode 100755 index 000000000..1cbca5922 --- /dev/null +++ b/scripts/serve.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -e +cd "$(dirname "$0")/.." +bundle exec jekyll serve --livereload --open-url From 19aca307b80a61418a2c48a7845d3e1202a5aebf Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 01:39:21 -0700 Subject: [PATCH 02/16] feat(build): add build/ folder with BUILD_INSTRUCTIONS.md and serve.sh Moves serve.sh from scripts/ into build/ and adds BUILD_INSTRUCTIONS.md covering macOS prerequisites (Homebrew, rbenv, Ruby 3.1.0, Bundler), dependency install, and localhost serving with a troubleshooting table. Co-Authored-By: Claude Sonnet 4.6 --- build/BUILD_INSTRUCTIONS.md | 73 +++++++++++++++++++++++++++++++++++++ {scripts => build}/serve.sh | 0 2 files changed, 73 insertions(+) create mode 100644 build/BUILD_INSTRUCTIONS.md rename {scripts => build}/serve.sh (100%) diff --git a/build/BUILD_INSTRUCTIONS.md b/build/BUILD_INSTRUCTIONS.md new file mode 100644 index 000000000..b138d742a --- /dev/null +++ b/build/BUILD_INSTRUCTIONS.md @@ -0,0 +1,73 @@ +# Build Instructions + +Local setup for [vishalgattani.github.io](https://vishalgattani.github.io) — a Jekyll static site. + +--- + +## Prerequisites + +### 1. Homebrew + +```bash +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +### 2. rbenv (Ruby version manager) + +```bash +brew install rbenv ruby-build +echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc +source ~/.zshrc +``` + +### 3. Ruby 3.1.0 + +The project pins Ruby to `3.1.0` via `.ruby-version`. + +```bash +rbenv install 3.1.0 +rbenv local 3.1.0 +ruby --version # should print ruby 3.1.0 +``` + +### 4. Bundler + +```bash +gem install bundler +``` + +--- + +## Install Dependencies + +From the repo root: + +```bash +bundle install +``` + +This installs Jekyll and all plugins declared in `Gemfile`. + +--- + +## Run Locally + +```bash +bash build/serve.sh +``` + +Opens `http://localhost:4000` automatically with live-reload enabled. +Changes to any source file rebuild the site in place — no restart needed. + +To stop the server: `Ctrl+C` + +--- + +## Troubleshooting + +| Problem | Fix | +|---------|-----| +| `ruby: command not found` | Run `rbenv install 3.1.0 && rbenv local 3.1.0` | +| `bundle: command not found` | Run `gem install bundler` | +| Port 4000 already in use | `lsof -ti:4000 \| xargs kill` then retry | +| `cannot load such file -- webrick` | `bundle add webrick` (Ruby 3+ removed it from stdlib) | diff --git a/scripts/serve.sh b/build/serve.sh similarity index 100% rename from scripts/serve.sh rename to build/serve.sh From d79d77db3aaaa0361c4e6da7e392ec927f9f1252 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 01:43:34 -0700 Subject: [PATCH 03/16] =?UTF-8?q?chore(ruby):=20bump=20Ruby=203.1.0=20?= =?UTF-8?q?=E2=86=92=203.3.11=20(EOL=20fix)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 3.1.x reached end of life and no longer receives security updates. Updated .ruby-version, CI workflow, and BUILD_INSTRUCTIONS.md to 3.3.11. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 2 +- .ruby-version | 2 +- build/BUILD_INSTRUCTIONS.md | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c553832da..1610a672a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: '3.1' + ruby-version: '3.3' bundler-cache: true - name: Build site diff --git a/.ruby-version b/.ruby-version index fd2a01863..b9b3b0de0 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.1.0 +3.3.11 diff --git a/build/BUILD_INSTRUCTIONS.md b/build/BUILD_INSTRUCTIONS.md index b138d742a..002a6befc 100644 --- a/build/BUILD_INSTRUCTIONS.md +++ b/build/BUILD_INSTRUCTIONS.md @@ -20,14 +20,14 @@ echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc source ~/.zshrc ``` -### 3. Ruby 3.1.0 +### 3. Ruby 3.3.11 -The project pins Ruby to `3.1.0` via `.ruby-version`. +The project pins Ruby to `3.3.11` via `.ruby-version`. ```bash -rbenv install 3.1.0 -rbenv local 3.1.0 -ruby --version # should print ruby 3.1.0 +rbenv install 3.3.11 +rbenv local 3.3.11 +ruby --version # should print ruby 3.3.11 ``` ### 4. Bundler From eeaae372e4b0387149aa807356ee26fcde31f5d9 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 01:46:52 -0700 Subject: [PATCH 04/16] feat(build): add build/setup.sh idempotent macOS setup script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checks for and installs Homebrew, rbenv, Ruby 3.3.11, Bundler, and project gems in sequence. Safe to re-run — each step skips if already satisfied. Co-Authored-By: Claude Sonnet 4.6 --- build/setup.sh | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 build/setup.sh diff --git a/build/setup.sh b/build/setup.sh new file mode 100755 index 000000000..69829601f --- /dev/null +++ b/build/setup.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +set -e + +RUBY_VERSION="3.3.11" + +info() { echo "[info] $*"; } +success() { echo "[ok] $*"; } +warn() { echo "[warn] $*"; } + +# --------------------------------------------------------------------------- +# Homebrew +# --------------------------------------------------------------------------- +if command -v brew &>/dev/null; then + success "Homebrew already installed" +else + info "Installing Homebrew..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + # Apple Silicon: add brew to PATH for the rest of this script + [[ -f /opt/homebrew/bin/brew ]] && eval "$(/opt/homebrew/bin/brew shellenv)" + success "Homebrew installed" +fi + +# --------------------------------------------------------------------------- +# rbenv +# --------------------------------------------------------------------------- +if command -v rbenv &>/dev/null; then + success "rbenv already installed" +else + info "Installing rbenv..." + brew install rbenv ruby-build + eval "$(rbenv init - bash)" + + SHELL_RC="$HOME/.zshrc" + [[ "$SHELL" == */bash ]] && SHELL_RC="$HOME/.bashrc" + + if ! grep -q 'rbenv init' "$SHELL_RC" 2>/dev/null; then + echo 'eval "$(rbenv init - zsh)"' >> "$SHELL_RC" + warn "Added rbenv init to $SHELL_RC — open a new terminal after setup completes" + fi + success "rbenv installed" +fi + +# --------------------------------------------------------------------------- +# Ruby +# --------------------------------------------------------------------------- +eval "$(rbenv init - bash)" 2>/dev/null || true + +if rbenv versions --bare | grep -qx "$RUBY_VERSION"; then + success "Ruby $RUBY_VERSION already installed" +else + info "Installing Ruby $RUBY_VERSION (this takes a few minutes)..." + rbenv install "$RUBY_VERSION" + success "Ruby $RUBY_VERSION installed" +fi + +rbenv local "$RUBY_VERSION" +info "Ruby version set to $(ruby --version)" + +# --------------------------------------------------------------------------- +# Bundler +# --------------------------------------------------------------------------- +if gem list bundler -i &>/dev/null; then + success "Bundler already installed" +else + info "Installing Bundler..." + gem install bundler + rbenv rehash + success "Bundler installed" +fi + +# --------------------------------------------------------------------------- +# Project dependencies +# --------------------------------------------------------------------------- +cd "$(dirname "$0")/.." +info "Running bundle install..." +bundle install +success "Dependencies installed" + +echo "" +echo "Setup complete. Run the site locally with:" +echo " bash build/serve.sh" From 90c43d208dc5fc5f86f39d45ced363847496bdb2 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 01:48:53 -0700 Subject: [PATCH 05/16] fix(build): init rbenv in scripts to avoid system Ruby 2.6 being picked up Without explicit rbenv init, macOS falls back to the system Ruby at /System/Library/Frameworks/Ruby.framework/Versions/2.6, which doesn't have the right Bundler version. Adding PATH + rbenv init forces the pinned 3.3.11 version. Co-Authored-By: Claude Sonnet 4.6 --- build/serve.sh | 5 +++++ build/setup.sh | 2 ++ 2 files changed, 7 insertions(+) diff --git a/build/serve.sh b/build/serve.sh index 1cbca5922..192af63b9 100755 --- a/build/serve.sh +++ b/build/serve.sh @@ -1,4 +1,9 @@ #!/usr/bin/env bash set -e + +# Init rbenv so the pinned Ruby version is used instead of the system Ruby +export PATH="$HOME/.rbenv/bin:$PATH" +eval "$(rbenv init - bash)" + cd "$(dirname "$0")/.." bundle exec jekyll serve --livereload --open-url diff --git a/build/setup.sh b/build/setup.sh index 69829601f..a71fc9bac 100755 --- a/build/setup.sh +++ b/build/setup.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -e +export PATH="$HOME/.rbenv/bin:$PATH" + RUBY_VERSION="3.3.11" info() { echo "[info] $*"; } From 4c894ef1515e1d02a1d32d76333481b6f7aedf00 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 01:50:44 -0700 Subject: [PATCH 06/16] fix(config): exclude build/ from Jekyll processing Without this, Jekyll renders build/BUILD_INSTRUCTIONS.md as a site page. Co-Authored-By: Claude Sonnet 4.6 --- _config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_config.yml b/_config.yml index 7b9f95616..9a4e35cb9 100644 --- a/_config.yml +++ b/_config.yml @@ -119,3 +119,4 @@ exclude: - CONTRIBUTING.md - LICENSE - "*.log" + - build/ From 70590f5965fdb53d79fb05ee4bcbd3cef060b27e Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 02:06:31 -0700 Subject: [PATCH 07/16] feat(trips): add Trips collection with Leaflet.js maps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _config.yml: trips collection (permalink /trips/:name, layout trip) - _layouts/trip.html: Leaflet.js map at top of each trip page; renders dropped pin (place), polyline (road), or dashed great-circle arc (flight) from frontmatter stops array - _includes/trips/trip-card.html: card with OSM static tile thumbnail, trip type badge, date, and tags - _includes/trips/index.html: card-columns grid over site.trips - _includes/trips/map-section.html: reusable sub-section map include for use inside trip markdown bodies - pages/trips.html: /trips/ listing page in navbar - _trips/: two sample trips (Colorado place pin, MD→VT road trip with a Day 2 section map) Co-Authored-By: Claude Sonnet 4.6 --- _config.yml | 8 ++ _includes/trips/index.html | 5 + _includes/trips/map-section.html | 63 +++++++++++ _includes/trips/trip-card.html | 39 +++++++ _layouts/trip.html | 107 +++++++++++++++++++ _trips/2024-07-colorado.md | 23 ++++ _trips/2024-09-college-park-to-waitsfield.md | 39 +++++++ pages/trips.html | 8 ++ 8 files changed, 292 insertions(+) create mode 100644 _includes/trips/index.html create mode 100644 _includes/trips/map-section.html create mode 100644 _includes/trips/trip-card.html create mode 100644 _layouts/trip.html create mode 100644 _trips/2024-07-colorado.md create mode 100644 _trips/2024-09-college-park-to-waitsfield.md create mode 100644 pages/trips.html diff --git a/_config.yml b/_config.yml index 9a4e35cb9..3c7cf9338 100644 --- a/_config.yml +++ b/_config.yml @@ -63,6 +63,9 @@ collections: research: output: true permalink: /research/:name + trips: + output: true + permalink: /trips/:name elements: # For Documentation Only output: true # For Documentation Only @@ -101,6 +104,11 @@ defaults: type: "research" values: layout: "page" + - scope: + path: "" + type: "trips" + values: + layout: "trip" - scope: # For Documentation Only path: "" # For Documentation Only type: "elements" # For Documentation Only diff --git a/_includes/trips/index.html b/_includes/trips/index.html new file mode 100644 index 000000000..1ead4f75e --- /dev/null +++ b/_includes/trips/index.html @@ -0,0 +1,5 @@ +
+ {% for trip in site.trips reversed %} + {% include trips/trip-card.html %} + {% endfor %} +
diff --git a/_includes/trips/map-section.html b/_includes/trips/map-section.html new file mode 100644 index 000000000..e94b2de1f --- /dev/null +++ b/_includes/trips/map-section.html @@ -0,0 +1,63 @@ +{% comment %} + Usage in trip markdown: + {% include trips/map-section.html id="day2" stops=page.day2_stops type="road" %} + + Frontmatter example: + day2_stops: + - { label: "Burlington, VT", lat: 44.4759, lng: -73.2121 } + - { label: "Waitsfield, VT", lat: 44.1906, lng: -72.8317 } +{% endcomment %} + +
+ + diff --git a/_includes/trips/trip-card.html b/_includes/trips/trip-card.html new file mode 100644 index 000000000..f2438df9b --- /dev/null +++ b/_includes/trips/trip-card.html @@ -0,0 +1,39 @@ +{% assign stop = trip.stops | first %} +{% assign last_stop = trip.stops | last %} + +{% if trip.trip_type == "place" and stop %} + {% capture map_url %}https://staticmap.openstreetmap.de/staticmap.php?center={{ stop.lat }},{{ stop.lng }}&zoom=9&size=400x200&markers={{ stop.lat }},{{ stop.lng }},red-pushpin{% endcapture %} +{% elsif stop and last_stop %} + {% assign mid_lat = stop.lat | plus: last_stop.lat | divided_by: 2.0 %} + {% assign mid_lng = stop.lng | plus: last_stop.lng | divided_by: 2.0 %} + {% capture map_url %}https://staticmap.openstreetmap.de/staticmap.php?center={{ mid_lat }},{{ mid_lng }}&zoom=5&size=400x200&markers={{ stop.lat }},{{ stop.lng }},red-pushpin|{{ last_stop.lat }},{{ last_stop.lng }},red-pushpin{% endcapture %} +{% endif %} + + diff --git a/_layouts/trip.html b/_layouts/trip.html new file mode 100644 index 000000000..9242147d5 --- /dev/null +++ b/_layouts/trip.html @@ -0,0 +1,107 @@ +--- +layout: default +--- + + + + +
+ +

{{ page.title }}

+ +
+ {% if page.trip_type == "road" %} + 🚗 Road Trip + {% elsif page.trip_type == "flight" %} + ✈️ Flight + {% else %} + 📍 Place + {% endif %} + + {% if page.date %} + {{ page.date | date: "%B %Y" }} + {% endif %} + + {% for tag in page.tags %} + {{ tag }} + {% endfor %} +
+ +
+ +
+ {{ content }} +
+ +
+ + diff --git a/_trips/2024-07-colorado.md b/_trips/2024-07-colorado.md new file mode 100644 index 000000000..d5401ac2e --- /dev/null +++ b/_trips/2024-07-colorado.md @@ -0,0 +1,23 @@ +--- +title: "Colorado" +date: 2024-07-04 +trip_type: place +description: "A week in the Rockies — hiking, altitude, and too much coffee." +tags: [Colorado, Hiking, Mountains] +stops: + - label: "Denver, CO" + lat: 39.7392 + lng: -104.9903 +--- + +## Overview + +Spent a week exploring Colorado — Denver as a base, day trips into Rocky Mountain National Park, and a couple of nights up in Estes Park. + +## Rocky Mountain National Park + +Trail Ridge Road tops out above 12,000 ft. Worth it for the views even if your lungs disagree. + +## Estes Park + +Classic mountain town. Crowded in July but the Stanley Hotel is worth the detour if you're a Shining fan. diff --git a/_trips/2024-09-college-park-to-waitsfield.md b/_trips/2024-09-college-park-to-waitsfield.md new file mode 100644 index 000000000..62d486983 --- /dev/null +++ b/_trips/2024-09-college-park-to-waitsfield.md @@ -0,0 +1,39 @@ +--- +title: "College Park, MD → Waitsfield, VT" +date: 2024-09-14 +trip_type: road +description: "Fall foliage road trip up the East Coast — 10 hours, one tank of gas, zero regrets." +tags: [Road Trip, East Coast, Fall Foliage] +stops: + - label: "College Park, MD" + lat: 38.9807 + lng: -76.9369 + - label: "Waitsfield, VT" + lat: 44.1906 + lng: -72.8317 + +day2_stops: + - label: "Burlington, VT" + lat: 44.4759 + lng: -73.2121 + - label: "Stowe, VT" + lat: 44.4654 + lng: -72.6874 + - label: "Waitsfield, VT" + lat: 44.1906 + lng: -72.8317 +--- + +## Day 1 — College Park to Burlington + +Straight shot up I-95 then cut across to Vermont. The moment you cross into Vermont the highway disappears and the trees take over. + +## Day 2 — Burlington to Waitsfield + +{% include trips/map-section.html id="day2" stops=page.day2_stops type="road" %} + +Burlington farmers market in the morning, then wound south through Stowe and down into the Mad River Valley. Peak foliage was about a week out but the colors were already going. + +## Mad River Glen + +Famously co-op owned, no snowboards allowed. In September it's just a quiet gondola ride up to an empty summit. diff --git a/pages/trips.html b/pages/trips.html new file mode 100644 index 000000000..3102ca3bb --- /dev/null +++ b/pages/trips.html @@ -0,0 +1,8 @@ +--- +layout: default +title: Trips +permalink: /trips/ +weight: 5 +--- + +{% include trips/index.html %} From 54990ab6a37f07964ad169039649aaed46c197e4 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 02:13:14 -0700 Subject: [PATCH 08/16] fix(trips): OSRM road routing, Leaflet card maps, fixed 400px map heights - trip-card.html: replace broken static image with non-interactive Leaflet mini-map (pointer-events: none so card link still works); road trips fetch actual route from OSRM public API; flight uses great-circle arc - trip.html: road trips now fetch OSRM route geometry instead of straight line; falls back to straight line if OSRM unavailable; fixed 400px height - map-section.html: same OSRM routing; fixed 400px height (up from 300px) - index.html: load Leaflet CSS/JS once for all card maps on listing page Co-Authored-By: Claude Sonnet 4.6 --- _includes/trips/index.html | 3 ++ _includes/trips/map-section.html | 55 +++++++++++++-------- _includes/trips/trip-card.html | 82 +++++++++++++++++++++++++++----- _layouts/trip.html | 74 +++++++++++++--------------- 4 files changed, 141 insertions(+), 73 deletions(-) diff --git a/_includes/trips/index.html b/_includes/trips/index.html index 1ead4f75e..ec851790d 100644 --- a/_includes/trips/index.html +++ b/_includes/trips/index.html @@ -1,3 +1,6 @@ + + +
{% for trip in site.trips reversed %} {% include trips/trip-card.html %} diff --git a/_includes/trips/map-section.html b/_includes/trips/map-section.html index e94b2de1f..a18de41ec 100644 --- a/_includes/trips/map-section.html +++ b/_includes/trips/map-section.html @@ -8,24 +8,21 @@ - { label: "Waitsfield, VT", lat: 44.1906, lng: -72.8317 } {% endcomment %} -
+
diff --git a/_includes/trips/trip-card.html b/_includes/trips/trip-card.html index f2438df9b..a8c047ce5 100644 --- a/_includes/trips/trip-card.html +++ b/_includes/trips/trip-card.html @@ -1,19 +1,8 @@ -{% assign stop = trip.stops | first %} -{% assign last_stop = trip.stops | last %} - -{% if trip.trip_type == "place" and stop %} - {% capture map_url %}https://staticmap.openstreetmap.de/staticmap.php?center={{ stop.lat }},{{ stop.lng }}&zoom=9&size=400x200&markers={{ stop.lat }},{{ stop.lng }},red-pushpin{% endcapture %} -{% elsif stop and last_stop %} - {% assign mid_lat = stop.lat | plus: last_stop.lat | divided_by: 2.0 %} - {% assign mid_lng = stop.lng | plus: last_stop.lng | divided_by: 2.0 %} - {% capture map_url %}https://staticmap.openstreetmap.de/staticmap.php?center={{ mid_lat }},{{ mid_lng }}&zoom=5&size=400x200&markers={{ stop.lat }},{{ stop.lng }},red-pushpin|{{ last_stop.lat }},{{ last_stop.lng }},red-pushpin{% endcapture %} -{% endif %} +{% assign card_id = trip.slug %} + + diff --git a/_layouts/trip.html b/_layouts/trip.html index 9242147d5..cc97df49e 100644 --- a/_layouts/trip.html +++ b/_layouts/trip.html @@ -17,11 +17,9 @@

{{ page.title }}

{% else %} 📍 Place {% endif %} - {% if page.date %} {{ page.date | date: "%B %Y" }} {% endif %} - {% for tag in page.tags %} {{ tag }} {% endfor %} @@ -39,69 +37,65 @@

{{ page.title }}

(function () { var stops = {{ page.stops | jsonify }}; var tripType = {{ page.trip_type | jsonify }}; + if (!stops || stops.length === 0) return; var map = L.map('trip-map'); - L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 18 }).addTo(map); - if (!stops || stops.length === 0) return; - - var markerIcon = L.icon({ - iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png', - iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png', - shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png', - iconSize: [25, 41], - iconAnchor: [12, 41], - popupAnchor: [1, -34], - shadowSize: [41, 41] - }); - var latlngs = stops.map(function (s) { return [s.lat, s.lng]; }); - stops.forEach(function (s, i) { - var label = s.label || ('Stop ' + (i + 1)); - L.marker([s.lat, s.lng], { icon: markerIcon }) + L.marker([s.lat, s.lng]) .addTo(map) - .bindPopup('' + label + ''); + .bindPopup('' + (s.label || 'Stop ' + (i + 1)) + ''); }); if (stops.length === 1) { map.setView(latlngs[0], 10); - } else if (tripType === 'road') { - L.polyline(latlngs, { color: '#007bff', weight: 3, opacity: 0.8 }).addTo(map); map.fitBounds(L.latLngBounds(latlngs).pad(0.2)); - + var coords = latlngs.map(function (ll) { return ll[1] + ',' + ll[0]; }).join(';'); + fetch('https://router.project-osrm.org/route/v1/driving/' + coords + '?overview=full&geometries=geojson') + .then(function (r) { return r.json(); }) + .then(function (data) { + var route = (data.routes && data.routes[0]) + ? data.routes[0].geometry.coordinates.map(function (c) { return [c[1], c[0]]; }) + : latlngs; + L.polyline(route, { color: '#007bff', weight: 4, opacity: 0.85 }).addTo(map); + map.fitBounds(L.latLngBounds(route).pad(0.1)); + }) + .catch(function () { + L.polyline(latlngs, { color: '#007bff', weight: 4, opacity: 0.85 }).addTo(map); + }); } else if (tripType === 'flight') { - // Great-circle arc via interpolated points - var arc = []; - var n = 80; - var p1 = { lat: latlngs[0][0] * Math.PI / 180, lng: latlngs[0][1] * Math.PI / 180 }; - var p2 = { lat: latlngs[latlngs.length - 1][0] * Math.PI / 180, lng: latlngs[latlngs.length - 1][1] * Math.PI / 180 }; + var arc = tripGreatCircleArc(latlngs[0], latlngs[latlngs.length - 1]); + L.polyline(arc, { color: '#007bff', weight: 3, opacity: 0.85, dashArray: '8 8' }).addTo(map); + map.fitBounds(L.latLngBounds(latlngs).pad(0.3)); + } else { + map.fitBounds(L.latLngBounds(latlngs).pad(0.2)); + } + + function tripGreatCircleArc(from, to) { + var R2D = 180 / Math.PI, D2R = Math.PI / 180; + var p1 = { lat: from[0] * D2R, lng: from[1] * D2R }; + var p2 = { lat: to[0] * D2R, lng: to[1] * D2R }; var d = 2 * Math.asin(Math.sqrt( Math.pow(Math.sin((p2.lat - p1.lat) / 2), 2) + Math.cos(p1.lat) * Math.cos(p2.lat) * Math.pow(Math.sin((p2.lng - p1.lng) / 2), 2) )); - for (var i = 0; i <= n; i++) { - var f = i / n; - if (d === 0) { arc.push(latlngs[0]); continue; } - var A = Math.sin((1 - f) * d) / Math.sin(d); - var B = Math.sin(f * d) / Math.sin(d); + var pts = []; + for (var i = 0; i <= 80; i++) { + var f = i / 80; + if (d === 0) { pts.push(from); continue; } + var A = Math.sin((1 - f) * d) / Math.sin(d), B = Math.sin(f * d) / Math.sin(d); var x = A * Math.cos(p1.lat) * Math.cos(p1.lng) + B * Math.cos(p2.lat) * Math.cos(p2.lng); var y = A * Math.cos(p1.lat) * Math.sin(p1.lng) + B * Math.cos(p2.lat) * Math.sin(p2.lng); var z = A * Math.sin(p1.lat) + B * Math.sin(p2.lat); - var lat = Math.atan2(z, Math.sqrt(x * x + y * y)) * 180 / Math.PI; - var lng = Math.atan2(y, x) * 180 / Math.PI; - arc.push([lat, lng]); + pts.push([Math.atan2(z, Math.sqrt(x * x + y * y)) * R2D, Math.atan2(y, x) * R2D]); } - L.polyline(arc, { color: '#007bff', weight: 2, opacity: 0.8, dashArray: '6 6' }).addTo(map); - map.fitBounds(L.latLngBounds(latlngs).pad(0.3)); - - } else { - map.fitBounds(L.latLngBounds(latlngs).pad(0.2)); + return pts; } })(); From a370a9ae86b7f27c1a4b533bd0f63140a6b85b34 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 02:20:44 -0700 Subject: [PATCH 09/16] feat(trips): rich marker types + shared trip-map.js Extract all map rendering into assets/js/trip-map.js (TripMap.render). trip.html, map-section.html, trip-card.html are now thin wrappers. New stop types: numbered, emoji, circle, label, polygon, circle_area, freeform. All support `note` (popup body) and `color` overrides. Add _trips/2024-08-rockies-demo.md exercising every marker type. Co-Authored-By: Claude Sonnet 4.6 --- _includes/trips/map-section.html | 78 ++------------ _includes/trips/trip-card.html | 70 ++----------- _layouts/trip.html | 66 +----------- _trips/2024-08-rockies-demo.md | 133 +++++++++++++++++++++++ assets/js/trip-map.js | 174 +++++++++++++++++++++++++++++++ 5 files changed, 324 insertions(+), 197 deletions(-) create mode 100644 _trips/2024-08-rockies-demo.md create mode 100644 assets/js/trip-map.js diff --git a/_includes/trips/map-section.html b/_includes/trips/map-section.html index a18de41ec..1e4d1836b 100644 --- a/_includes/trips/map-section.html +++ b/_includes/trips/map-section.html @@ -2,77 +2,17 @@ Usage in trip markdown: {% include trips/map-section.html id="day2" stops=page.day2_stops type="road" %} - Frontmatter example: - day2_stops: - - { label: "Burlington, VT", lat: 44.4759, lng: -73.2121 } - - { label: "Waitsfield, VT", lat: 44.1906, lng: -72.8317 } + `id` — unique string, used as the HTML element ID (required) + `stops` — array from frontmatter (required) + `type` — road | flight | place (default: road) {% endcomment %} -
+
diff --git a/_includes/trips/trip-card.html b/_includes/trips/trip-card.html index a8c047ce5..2fab28988 100644 --- a/_includes/trips/trip-card.html +++ b/_includes/trips/trip-card.html @@ -28,68 +28,10 @@
{{ trip.title }}
diff --git a/_layouts/trip.html b/_layouts/trip.html index cc97df49e..38ec4602a 100644 --- a/_layouts/trip.html +++ b/_layouts/trip.html @@ -4,6 +4,7 @@ +
@@ -34,68 +35,5 @@

{{ page.title }}

diff --git a/_trips/2024-08-rockies-demo.md b/_trips/2024-08-rockies-demo.md new file mode 100644 index 000000000..d6c8f1c90 --- /dev/null +++ b/_trips/2024-08-rockies-demo.md @@ -0,0 +1,133 @@ +--- +title: "Colorado Rockies — Map Features Demo" +date: 2024-08-10 +trip_type: road +description: "A demo trip showing every marker type available — pins, emojis, numbered stops, labels, polygons, areas, and freeform routes." +tags: [Demo, Colorado, Hiking] + +# --- top-of-page overview map --- +# Only pin/circle/numbered/emoji/label stops are used to compute the OSRM route. +# polygon, circle_area, and freeform stops are drawn as overlays on top. +stops: + # plain dropped pin (default when type is omitted) + - label: "Denver, CO" + lat: 39.7392 + lng: -104.9903 + + # numbered stop — great for ordered itineraries + - label: "Boulder, CO" + lat: 40.0150 + lng: -105.2705 + type: numbered + number: 1 + note: "Pearl Street Mall, Chautauqua Park" + + # emoji marker — use any emoji as the icon + - label: "Estes Park" + lat: 40.3775 + lng: -105.5224 + type: emoji + icon: "🦌" + note: "Elk everywhere. Seriously." + + # circle dot — good for photo spots or minor waypoints + - label: "Bear Lake Trailhead" + lat: 40.3120 + lng: -105.6456 + type: circle + color: "#28a745" + note: "Arrive before 7am or the parking lot is full" + + # permanent text label — no pin, just a floating name + - label: "Rocky Mountain National Park" + lat: 40.4000 + lng: -105.6800 + type: label + + # shaded polygon — highlight a region + - label: "RMNP Boundary (approximate)" + lat: 40.3500 + lng: -105.6500 + type: polygon + color: "#6f42c1" + coords: + - [40.27, -105.75] + - [40.27, -105.55] + - [40.50, -105.55] + - [40.50, -105.75] + + # radius circle — "stayed within X metres of here" + - label: "Campsite (~500m radius)" + lat: 40.3760 + lng: -105.6200 + type: circle_area + radius: 500 + color: "#fd7e14" + note: "Moraine Park Campground" + + # freeform polyline — hike, bike path, ferry, anything non-road + - label: "Emerald Lake Trail" + lat: 40.3120 + lng: -105.6456 + type: freeform + color: "#dc3545" + dashed: false + coords: + - [40.3120, -105.6456] + - [40.3098, -105.6489] + - [40.3082, -105.6521] + - [40.3065, -105.6554] + - [40.3055, -105.6592] + +# --- section map used inside the markdown body --- +day2_stops: + - label: "Trail Ridge Road start" + lat: 40.3960 + lng: -105.6070 + type: numbered + number: 1 + - label: "Alpine Visitor Center (11,796 ft)" + lat: 40.4575 + lng: -105.7131 + type: emoji + icon: "🏔️" + note: "Highest paved road in the US" + - label: "Milner Pass" + lat: 40.4203 + lng: -105.8125 + type: numbered + number: 3 + note: "Continental Divide" +--- + +## Overview + +This is a **demo trip** — every marker type you can use in a trip markdown is shown on the map above and in the Day 2 section below. Use this as a reference when writing your own trips. + +--- + +## Marker types at a glance + +| Type | What it looks like | When to use | +|------|-------------------|-------------| +| `pin` (default) | Standard blue Leaflet marker | Main stops, towns | +| `numbered` | Colored circle with a number | Ordered itinerary steps | +| `emoji` | Any emoji as an icon | Restaurants 🍔, summits 🏔️, wildlife 🦌 | +| `circle` | Small filled dot | Photo spots, minor waypoints | +| `label` | Floating text, no pin | Region names, area labels | +| `polygon` | Shaded outline | Park boundaries, neighborhoods | +| `circle_area` | Radius bubble | Campsite, "stayed near here" | +| `freeform` | Arbitrary polyline | Hikes, bike routes, ferries | + +All types support a `note` field — it shows as a second line inside the popup when you click the marker. +All types support a `color` field to override the default blue. + +--- + +## Day 2 — Trail Ridge Road + +The highest continuous paved road in the US. Drive it west to east for the best light in the morning. + +{% include trips/map-section.html id="day2" stops=page.day2_stops type="road" %} + +The road closes from roughly October to late May depending on snowpack. Check [nps.gov/romo](https://www.nps.gov/romo) before going. diff --git a/assets/js/trip-map.js b/assets/js/trip-map.js new file mode 100644 index 000000000..33c54c199 --- /dev/null +++ b/assets/js/trip-map.js @@ -0,0 +1,174 @@ +/* trip-map.js — renders a Leaflet map from a stops array + trip_type. + * + * Stop types: + * pin — default dropped marker (default when type is omitted) + * circle — flat filled dot, good for photo spots / waypoints + * numbered — pin with a number badge (requires `number` field) + * emoji — custom emoji icon (requires `icon` field, e.g. "🍔") + * polygon — shaded area (requires `coords` array of [lat,lng] pairs) + * circle_area — radius around a point (requires `radius` in metres) + * label — permanent text label with no marker (requires `label`) + * freeform — polyline drawn through `coords` array (hike, bike, ferry) + * + * Any stop with a `note` field gets a richer popup body. + * Any stop with a `color` field overrides the default #007bff blue. + */ + +window.TripMap = { + + render: function (mapId, stops, tripType, opts) { + opts = opts || {}; + var interactive = opts.interactive !== false; + var height = opts.height || '400px'; + + var el = document.getElementById(mapId); + if (!el) return; + el.style.height = height; + + var mapOpts = interactive ? {} : { + dragging: false, zoomControl: false, scrollWheelZoom: false, + doubleClickZoom: false, keyboard: false, tap: false, attributionControl: false + }; + var map = L.map(mapId, mapOpts); + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: interactive + ? '© OpenStreetMap contributors' + : '', + maxZoom: 18 + }).addTo(map); + + if (!stops || stops.length === 0) return; + + var pinStops = stops.filter(function (s) { + return !s.type || s.type === 'pin' || s.type === 'circle' || + s.type === 'numbered' || s.type === 'emoji' || s.type === 'label'; + }); + var latlngs = pinStops.map(function (s) { return [s.lat, s.lng]; }); + if (latlngs.length === 0) latlngs = [[stops[0].lat, stops[0].lng]]; + + // --- draw each stop --- + stops.forEach(function (s, i) { + var color = s.color || '#007bff'; + var type = s.type || 'pin'; + var popup = TripMap._popupHtml(s); + + if (type === 'pin') { + L.marker([s.lat, s.lng]).addTo(map).bindPopup(popup); + + } else if (type === 'circle') { + L.circleMarker([s.lat, s.lng], { + radius: 8, color: '#fff', fillColor: color, fillOpacity: 0.9, weight: 2 + }).addTo(map).bindPopup(popup); + + } else if (type === 'numbered') { + var num = s.number || (i + 1); + var icon = L.divIcon({ + className: '', + html: '
' + num + '
', + iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -16] + }); + L.marker([s.lat, s.lng], { icon: icon }).addTo(map).bindPopup(popup); + + } else if (type === 'emoji') { + var icon = L.divIcon({ + className: '', + html: '
' + + (s.icon || '📍') + '
', + iconSize: [28, 28], iconAnchor: [14, 14], popupAnchor: [0, -16] + }); + L.marker([s.lat, s.lng], { icon: icon }).addTo(map).bindPopup(popup); + + } else if (type === 'label') { + var icon = L.divIcon({ + className: '', + html: '
' + (s.label || '') + '
', + iconAnchor: [0, 10], popupAnchor: [0, -10] + }); + L.marker([s.lat, s.lng], { icon: icon }).addTo(map); + + } else if (type === 'polygon') { + if (s.coords && s.coords.length) { + L.polygon(s.coords, { + color: color, fillColor: color, fillOpacity: 0.15, weight: 2 + }).addTo(map).bindPopup(popup); + } + + } else if (type === 'circle_area') { + L.circle([s.lat, s.lng], { + radius: s.radius || 1000, + color: color, fillColor: color, fillOpacity: 0.15, weight: 2 + }).addTo(map).bindPopup(popup); + + } else if (type === 'freeform') { + if (s.coords && s.coords.length) { + L.polyline(s.coords, { + color: s.color || '#28a745', weight: 3, opacity: 0.85, + dashArray: s.dashed ? '6 4' : null + }).addTo(map).bindPopup(popup); + } + } + }); + + // --- draw route / fit bounds --- + if (latlngs.length === 1) { + map.setView(latlngs[0], 10); + + } else if (tripType === 'road') { + map.fitBounds(L.latLngBounds(latlngs).pad(0.2)); + var coords = latlngs.map(function (ll) { return ll[1] + ',' + ll[0]; }).join(';'); + fetch('https://router.project-osrm.org/route/v1/driving/' + coords + '?overview=full&geometries=geojson') + .then(function (r) { return r.json(); }) + .then(function (data) { + var route = (data.routes && data.routes[0]) + ? data.routes[0].geometry.coordinates.map(function (c) { return [c[1], c[0]]; }) + : latlngs; + L.polyline(route, { color: '#007bff', weight: 4, opacity: 0.85 }).addTo(map); + map.fitBounds(L.latLngBounds(route).pad(0.1)); + }) + .catch(function () { + L.polyline(latlngs, { color: '#007bff', weight: 4, opacity: 0.85 }).addTo(map); + }); + + } else if (tripType === 'flight') { + var arc = TripMap._greatCircleArc(latlngs[0], latlngs[latlngs.length - 1]); + L.polyline(arc, { color: '#007bff', weight: 3, opacity: 0.85, dashArray: '8 8' }).addTo(map); + map.fitBounds(L.latLngBounds(latlngs).pad(0.3)); + + } else { + map.fitBounds(L.latLngBounds(latlngs).pad(0.2)); + } + }, + + _popupHtml: function (s) { + var html = s.label ? '' + s.label + '' : ''; + if (s.note) html += (html ? '
' : '') + '' + s.note + ''; + return html || ' '; + }, + + _greatCircleArc: function (from, to) { + var R2D = 180 / Math.PI, D2R = Math.PI / 180; + var p1 = { lat: from[0] * D2R, lng: from[1] * D2R }; + var p2 = { lat: to[0] * D2R, lng: to[1] * D2R }; + var d = 2 * Math.asin(Math.sqrt( + Math.pow(Math.sin((p2.lat - p1.lat) / 2), 2) + + Math.cos(p1.lat) * Math.cos(p2.lat) * Math.pow(Math.sin((p2.lng - p1.lng) / 2), 2) + )); + var pts = []; + for (var i = 0; i <= 80; i++) { + var f = i / 80; + if (d === 0) { pts.push(from); continue; } + var A = Math.sin((1 - f) * d) / Math.sin(d), B = Math.sin(f * d) / Math.sin(d); + var x = A * Math.cos(p1.lat) * Math.cos(p1.lng) + B * Math.cos(p2.lat) * Math.cos(p2.lng); + var y = A * Math.cos(p1.lat) * Math.sin(p1.lng) + B * Math.cos(p2.lat) * Math.sin(p2.lng); + var z = A * Math.sin(p1.lat) + B * Math.sin(p2.lat); + pts.push([Math.atan2(z, Math.sqrt(x * x + y * y)) * R2D, Math.atan2(y, x) * R2D]); + } + return pts; + } +}; From 4c5a171a383596298647601f21386cf628b61b1f Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 02:26:14 -0700 Subject: [PATCH 10/16] docs(trips): add how-to blog post + fix demo trip convention - _posts/2024-08-15-how-to-add-a-trip.md: full reference for trip frontmatter, all marker types, section map naming convention, and a minimal example - _trips/2024-08-rockies-demo.md: add day1_stops so both day1 and day2 section maps are shown; clarify that any name + _stops works Co-Authored-By: Claude Sonnet 4.6 --- _posts/2024-08-15-how-to-add-a-trip.md | 275 +++++++++++++++++++++++++ _trips/2024-08-rockies-demo.md | 78 ++++--- 2 files changed, 321 insertions(+), 32 deletions(-) create mode 100644 _posts/2024-08-15-how-to-add-a-trip.md diff --git a/_posts/2024-08-15-how-to-add-a-trip.md b/_posts/2024-08-15-how-to-add-a-trip.md new file mode 100644 index 000000000..242fc3b91 --- /dev/null +++ b/_posts/2024-08-15-how-to-add-a-trip.md @@ -0,0 +1,275 @@ +--- +title: How to Add a Trip +tags: [Site, Documentation, Maps] +style: fill +color: secondary +description: A complete reference for writing trip markdown files — frontmatter fields, all marker types, section maps, and naming conventions. +--- + +Every trip lives as a single markdown file in `_trips/`. The filename becomes the URL: `_trips/2024-09-my-road-trip.md` → `/trips/2024-09-my-road-trip`. + +--- + +## File naming + +``` +_trips/YYYY-MM-description.md +``` + +Examples: +``` +_trips/2024-07-colorado.md +_trips/2024-09-college-park-to-waitsfield.md +_trips/2025-03-tokyo.md +``` + +--- + +## Frontmatter — top-level fields + +```yaml +--- +title: "College Park, MD → Waitsfield, VT" +date: 2024-09-14 +trip_type: road # road | flight | place +description: "One-liner shown on the card." +tags: [Road Trip, East Coast, Fall Foliage] +--- +``` + +| Field | Required | Values | +|-------|----------|--------| +| `title` | yes | Free text. Use `→` for A-to-B trips. | +| `date` | yes | `YYYY-MM-DD` | +| `trip_type` | yes | `road` · `flight` · `place` | +| `description` | recommended | Shown on the card and in SEO tags | +| `tags` | optional | Array of strings | + +--- + +## The `stops` array — overview map + +`stops` drives the **top-of-page map**. For `road` trips it also provides the waypoints for the OSRM route line. + +```yaml +stops: + - label: "College Park, MD" + lat: 38.9807 + lng: -76.9369 + + - label: "Waitsfield, VT" + lat: 44.1906 + lng: -72.8317 +``` + +Every stop has `lat`, `lng`, and an optional `type`. When `type` is omitted it defaults to `pin`. + +### Stop types + +#### `pin` — dropped marker (default) + +```yaml +- label: "Denver, CO" + lat: 39.7392 + lng: -104.9903 +``` + +#### `numbered` — pin with a number badge + +```yaml +- label: "Boulder" + lat: 40.0150 + lng: -105.2705 + type: numbered + number: 1 + note: "Lunch on Pearl Street" +``` + +#### `emoji` — any emoji as the icon + +```yaml +- label: "Estes Park" + lat: 40.3775 + lng: -105.5224 + type: emoji + icon: "🦌" + note: "Elk everywhere" +``` + +#### `circle` — flat filled dot + +Good for photo spots and minor waypoints that don't need a full pin. + +```yaml +- label: "Bear Lake Trailhead" + lat: 40.3120 + lng: -105.6456 + type: circle + color: "#28a745" + note: "Arrive before 7am" +``` + +#### `label` — floating text, no pin + +Use for region names or area annotations. + +```yaml +- label: "Rocky Mountain National Park" + lat: 40.4000 + lng: -105.6800 + type: label +``` + +#### `polygon` — shaded region + +```yaml +- label: "RMNP Boundary" + lat: 40.35 + lng: -105.65 + type: polygon + color: "#6f42c1" + coords: + - [40.27, -105.75] + - [40.27, -105.55] + - [40.50, -105.55] + - [40.50, -105.75] +``` + +`lat`/`lng` on a polygon stop are ignored for routing — they're only used to set the popup anchor if you click the shape. + +#### `circle_area` — radius bubble + +```yaml +- label: "Moraine Park Campground" + lat: 40.3760 + lng: -105.6200 + type: circle_area + radius: 500 # metres + color: "#fd7e14" + note: "Site 14B" +``` + +#### `freeform` — arbitrary polyline + +For hikes, bike routes, ferries, ski runs — anything that isn't a road route. + +```yaml +- label: "Emerald Lake Trail" + lat: 40.3120 + lng: -105.6456 + type: freeform + color: "#dc3545" + dashed: false # true for dotted line + coords: + - [40.3120, -105.6456] + - [40.3082, -105.6521] + - [40.3055, -105.6592] +``` + +### Shared optional fields + +Any stop type accepts: + +| Field | Effect | +|-------|--------| +| `note` | Second line of text in the popup when the marker is clicked | +| `color` | Hex color — overrides the default blue `#007bff` | + +--- + +## Section maps inside the markdown body + +The top-of-page `stops` map is the **overview**. To show a focused map for a specific day or leg, define a named stops array in frontmatter and drop an include in the body. + +### Naming convention + +Name the array `_stops`. Common patterns: + +```yaml +day1_stops: [...] +day2_stops: [...] +morning_stops: [...] +hike_stops: [...] +flight_stops: [...] +``` + +There is no special meaning to `day1` vs `day2` — the names are just HTML element IDs. The only rules are: **end in `_stops`** (by convention) and **be unique within the file**. + +### Frontmatter + +```yaml +day1_stops: + - label: "Denver" + lat: 39.7392 + lng: -104.9903 + type: numbered + number: 1 + - label: "Boulder" + lat: 40.0150 + lng: -105.2705 + type: numbered + number: 2 + +day2_stops: + - label: "Trail Ridge Road" + lat: 40.3960 + lng: -105.6070 + type: emoji + icon: "🏔️" + - label: "Milner Pass" + lat: 40.4203 + lng: -105.8125 + type: numbered + number: 2 +``` + +### Include in markdown body + +```liquid +{% raw %}{% include trips/map-section.html id="day1" stops=page.day1_stops type="road" %}{% endraw %} +``` + +| Parameter | Required | Notes | +|-----------|----------|-------| +| `id` | yes | Must match the prefix of your `_stops` key — used as the HTML element ID. Must be unique on the page. | +| `stops` | yes | `page._stops` | +| `type` | optional | `road` (default) · `flight` · `place` | + +--- + +## Full minimal example + +```yaml +--- +title: "San Francisco → Portland" +date: 2025-03-15 +trip_type: road +description: "PCH the whole way up." +tags: [Road Trip, West Coast] + +stops: + - label: "San Francisco, CA" + lat: 37.7749 + lng: -122.4194 + - label: "Portland, OR" + lat: 45.5051 + lng: -122.6750 + +day1_stops: + - { label: "San Francisco", lat: 37.7749, lng: -122.4194, type: numbered, number: 1 } + - { label: "Santa Cruz", lat: 36.9741, lng: -122.0308, type: emoji, icon: "🏄" } + - { label: "Monterey", lat: 36.6002, lng: -121.8947, type: numbered, number: 3 } +--- + +## Day 1 — SF to Monterey + +{% raw %}{% include trips/map-section.html id="day1" stops=page.day1_stops type="road" %}{% endraw %} + +Took PCH the whole way. 17-Mile Drive is worth the $12 toll. +``` + +--- + +## Live demo + +The trip [Colorado Rockies — Map Features Demo](/trips/2024-08-rockies-demo) exercises every marker type and section map in one file. Read the source at [`_trips/2024-08-rockies-demo.md`](https://github.com/vishalgattani/vishalgattani.github.io/blob/feat/site-improvements/_trips/2024-08-rockies-demo.md). diff --git a/_trips/2024-08-rockies-demo.md b/_trips/2024-08-rockies-demo.md index d6c8f1c90..473f503a9 100644 --- a/_trips/2024-08-rockies-demo.md +++ b/_trips/2024-08-rockies-demo.md @@ -5,16 +5,15 @@ trip_type: road description: "A demo trip showing every marker type available — pins, emojis, numbered stops, labels, polygons, areas, and freeform routes." tags: [Demo, Colorado, Hiking] -# --- top-of-page overview map --- -# Only pin/circle/numbered/emoji/label stops are used to compute the OSRM route. -# polygon, circle_area, and freeform stops are drawn as overlays on top. +# `stops` is the overview map at the top of the page. +# It also drives the OSRM route line — only pin/circle/numbered/emoji/label +# types are used as routing waypoints. Everything else (polygon, circle_area, +# freeform) is drawn as an overlay on top. stops: - # plain dropped pin (default when type is omitted) - label: "Denver, CO" lat: 39.7392 lng: -104.9903 - # numbered stop — great for ordered itineraries - label: "Boulder, CO" lat: 40.0150 lng: -105.2705 @@ -22,7 +21,6 @@ stops: number: 1 note: "Pearl Street Mall, Chautauqua Park" - # emoji marker — use any emoji as the icon - label: "Estes Park" lat: 40.3775 lng: -105.5224 @@ -30,7 +28,6 @@ stops: icon: "🦌" note: "Elk everywhere. Seriously." - # circle dot — good for photo spots or minor waypoints - label: "Bear Lake Trailhead" lat: 40.3120 lng: -105.6456 @@ -38,13 +35,11 @@ stops: color: "#28a745" note: "Arrive before 7am or the parking lot is full" - # permanent text label — no pin, just a floating name - label: "Rocky Mountain National Park" lat: 40.4000 lng: -105.6800 type: label - # shaded polygon — highlight a region - label: "RMNP Boundary (approximate)" lat: 40.3500 lng: -105.6500 @@ -56,7 +51,6 @@ stops: - [40.50, -105.55] - [40.50, -105.75] - # radius circle — "stayed within X metres of here" - label: "Campsite (~500m radius)" lat: 40.3760 lng: -105.6200 @@ -65,13 +59,11 @@ stops: color: "#fd7e14" note: "Moraine Park Campground" - # freeform polyline — hike, bike path, ferry, anything non-road - label: "Emerald Lake Trail" lat: 40.3120 lng: -105.6456 type: freeform color: "#dc3545" - dashed: false coords: - [40.3120, -105.6456] - [40.3098, -105.6489] @@ -79,7 +71,29 @@ stops: - [40.3065, -105.6554] - [40.3055, -105.6592] -# --- section map used inside the markdown body --- +# Section maps: name them anything + _stops. +# day1_stops, day2_stops, morning_stops, hike_stops — all work the same way. +# Use {% include trips/map-section.html id="day1" stops=page.day1_stops type="road" %} +# The `id` must be unique within the page (it becomes the HTML element ID). +day1_stops: + - label: "Denver — start" + lat: 39.7392 + lng: -104.9903 + type: numbered + number: 1 + - label: "Boulder" + lat: 40.0150 + lng: -105.2705 + type: numbered + number: 2 + note: "Lunch at Chautauqua" + - label: "Estes Park" + lat: 40.3775 + lng: -105.5224 + type: numbered + number: 3 + note: "Check in, elk walk at dusk" + day2_stops: - label: "Trail Ridge Road start" lat: 40.3960 @@ -92,42 +106,42 @@ day2_stops: type: emoji icon: "🏔️" note: "Highest paved road in the US" - - label: "Milner Pass" + - label: "Milner Pass — Continental Divide" lat: 40.4203 lng: -105.8125 type: numbered number: 3 - note: "Continental Divide" --- ## Overview -This is a **demo trip** — every marker type you can use in a trip markdown is shown on the map above and in the Day 2 section below. Use this as a reference when writing your own trips. +This is a **demo trip** — every marker type and section map convention is shown here. See the blog post [How to Add a Trip](/blog/how-to-add-a-trip) for the full reference. --- ## Marker types at a glance -| Type | What it looks like | When to use | -|------|-------------------|-------------| -| `pin` (default) | Standard blue Leaflet marker | Main stops, towns | -| `numbered` | Colored circle with a number | Ordered itinerary steps | -| `emoji` | Any emoji as an icon | Restaurants 🍔, summits 🏔️, wildlife 🦌 | -| `circle` | Small filled dot | Photo spots, minor waypoints | -| `label` | Floating text, no pin | Region names, area labels | -| `polygon` | Shaded outline | Park boundaries, neighborhoods | -| `circle_area` | Radius bubble | Campsite, "stayed near here" | -| `freeform` | Arbitrary polyline | Hikes, bike routes, ferries | - -All types support a `note` field — it shows as a second line inside the popup when you click the marker. -All types support a `color` field to override the default blue. +| Type | When to use | +|------|-------------| +| `pin` (default) | Main stops, towns | +| `numbered` | Ordered itinerary steps | +| `emoji` | Restaurants 🍔, summits 🏔️, wildlife 🦌 | +| `circle` | Photo spots, minor waypoints | +| `label` | Region names, floating text — no pin | +| `polygon` | Park boundaries, neighborhoods | +| `circle_area` | Campsite, "stayed near here" with radius | +| `freeform` | Hikes, bike routes, ferries | --- -## Day 2 — Trail Ridge Road +## Day 1 — Denver → Boulder → Estes Park + +{% include trips/map-section.html id="day1" stops=page.day1_stops type="road" %} -The highest continuous paved road in the US. Drive it west to east for the best light in the morning. +Drive up US-36 from Denver to Boulder, grab lunch on Pearl Street, then follow Canyon Boulevard into the mountains. Estes Park is about 45 minutes from Boulder. + +## Day 2 — Trail Ridge Road {% include trips/map-section.html id="day2" stops=page.day2_stops type="road" %} -The road closes from roughly October to late May depending on snowpack. Check [nps.gov/romo](https://www.nps.gov/romo) before going. +The road closes roughly October to late May. Check [nps.gov/romo](https://www.nps.gov/romo) before going. From 82d2501ce8e7f1ef4b36dd6577643e2a98ac5080 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 02:28:33 -0700 Subject: [PATCH 11/16] fix(trips): load trip-map.js on listing page so card maps render TripMap.render was undefined on /trips/ because trip-map.js was only loaded in _layouts/trip.html, not in _includes/trips/index.html. Co-Authored-By: Claude Sonnet 4.6 --- _includes/trips/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/_includes/trips/index.html b/_includes/trips/index.html index ec851790d..505822363 100644 --- a/_includes/trips/index.html +++ b/_includes/trips/index.html @@ -1,5 +1,6 @@ +
{% for trip in site.trips reversed %} From a8e4e1d9586f56974b58947688a38ef655e862dd Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 02:48:48 -0700 Subject: [PATCH 12/16] chore: add jekyll-feed for RSS at /feed.xml Co-Authored-By: Claude Sonnet 4.6 --- Gemfile | 1 + _config.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 9236e7de6..d37e11d5e 100644 --- a/Gemfile +++ b/Gemfile @@ -3,4 +3,5 @@ source "https://rubygems.org" gem "github-pages", group: :jekyll_plugins gem "jekyll-seo-tag" gem "jekyll-sitemap" +gem "jekyll-feed" gem 'wdm', '>= 0.1.0' if Gem.win_platform? \ No newline at end of file diff --git a/_config.yml b/_config.yml index 3c7cf9338..244df87ab 100644 --- a/_config.yml +++ b/_config.yml @@ -14,6 +14,7 @@ plugins: - jemoji - jekyll-seo-tag - jekyll-sitemap + - jekyll-feed ### Navbar Settings ### From 2556cffdb3d6ec025b14fcf26474db632141c53d Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 02:49:01 -0700 Subject: [PATCH 13/16] fix: add missing usernames to social media URLs github, gitlab, instagram, linkedin were all missing /vishalgattani suffix. Co-Authored-By: Claude Sonnet 4.6 --- _data/social-media.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_data/social-media.yml b/_data/social-media.yml index 28934e475..8131bee9e 100644 --- a/_data/social-media.yml +++ b/_data/social-media.yml @@ -22,22 +22,22 @@ email: # color : 3b5998 github: - url : https://www.github.com/ + url : https://www.github.com/vishalgattani icon : fab fa-github color : 333333 gitlab: - url : https://gitlab.com/ + url : https://gitlab.com/vishalgattani icon : fab fa-gitlab color : fca326 instagram: - url : https://www.instagram.com/ + url : https://www.instagram.com/vishalgattani icon : fab fa-instagram color : 405de6 linkedin: - url : https://www.linkedin.com/in/ + url : https://www.linkedin.com/in/vishalgattani icon : fab fa-linkedin-in color : 007bb5 From d5e362b7f2e7e3ac42ebef4ba4ef430ff06f1e95 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 02:49:27 -0700 Subject: [PATCH 14/16] feat: add count badges to tags page and tag cloud index Top of /blog/tags now shows a clickable tag cloud with post counts. Each tag heading also shows a count badge. Co-Authored-By: Claude Sonnet 4.6 --- _includes/blog/tags.html | 48 ++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/_includes/blog/tags.html b/_includes/blog/tags.html index 23e66082e..d584dfd0d 100644 --- a/_includes/blog/tags.html +++ b/_includes/blog/tags.html @@ -1,17 +1,41 @@ - -{%- assign tags = blank -%} + +{%- assign all_tags = "" | split: "" -%} {%- for post in site.posts -%} - {%- assign post_tags = post.tags | join:'|' | append:'|' -%} - {%- if post_tags != '|' -%} - {%- assign tags = tags | append:post_tags -%} - {%- endif -%} + {%- for tag in post.tags -%} + {%- assign all_tags = all_tags | push: tag -%} + {%- endfor -%} {%- endfor -%} -{%- assign tags = tags | split:'|' | uniq | sort -%} +{%- assign unique_tags = all_tags | uniq | sort -%} + + +
+ {%- for tag in unique_tags -%} + {%- assign count = 0 -%} + {%- for t in all_tags -%} + {%- if t == tag -%}{%- assign count = count | plus: 1 -%}{%- endif -%} + {%- endfor -%} + + + {{ tag }} {{ count }} + + + {%- endfor -%} +
+ +
+ + +{% for tag in unique_tags %} + {%- assign count = 0 -%} + {%- for t in all_tags -%} + {%- if t == tag -%}{%- assign count = count | plus: 1 -%}{%- endif -%} + {%- endfor -%} - -{% for tag in tags %}
-

{{ tag }}

+

+ {{ tag }} + {{ count }} +

    {% for post in site.posts %} {%- if post.tags contains tag -%} @@ -21,11 +45,11 @@

    {{ tag }}

    {%- assign url = post.url | relative_url -%} {%- endif -%}
  1. {{ post.title }}
  2. - - {{ post.date | date_to_long_string }} + — {{ post.date | date_to_long_string }}
    {%- endif -%} {% endfor %}

-{% endfor %} \ No newline at end of file +{% endfor %} From 674970b464a1bc9f085711e2b8e400be09e0ef39 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 02:49:41 -0700 Subject: [PATCH 15/16] feat: add section quick-links to landing page Adds Projects / Blog / Trips / Research buttons below the bio so visitors have a clear call-to-action from the home page. Co-Authored-By: Claude Sonnet 4.6 --- _includes/landing.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/_includes/landing.html b/_includes/landing.html index 7c559c6e7..97d2fce7a 100644 --- a/_includes/landing.html +++ b/_includes/landing.html @@ -11,5 +11,12 @@

{{ site.description }}

+ +
\ No newline at end of file From 3c150e3ff49c1fc492e72d8280dfcf623746bb44 Mon Sep 17 00:00:00 2001 From: Vishal Gattani Date: Sat, 16 May 2026 02:52:54 -0700 Subject: [PATCH 16/16] fix: exclude vendor/ from Jekyll build to fix CI date parse error Jekyll was processing gem template files inside vendor/bundle/ and failing on a .erb file with an invalid date. Excluding vendor/ in _config.yml and setting BUNDLE_PATH in CI prevents this. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 4 +++- _config.yml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1610a672a..bdea30c85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,9 @@ jobs: bundler-cache: true - name: Build site - run: bundle exec jekyll build + run: bundle exec jekyll build --config _config.yml + env: + BUNDLE_PATH: vendor/bundle - name: Check HTML run: | diff --git a/_config.yml b/_config.yml index 244df87ab..92d107004 100644 --- a/_config.yml +++ b/_config.yml @@ -129,3 +129,4 @@ exclude: - LICENSE - "*.log" - build/ + - vendor/