<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title></title>
    <link>https://www.trisphoto.com/</link>
    <description>Recent content on </description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Tue, 01 Sep 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.trisphoto.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Time for a long walk..</title>
      <link>https://www.trisphoto.com/trips/te-araroa/the-long-pathway/</link>
      <pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://www.trisphoto.com/trips/te-araroa/the-long-pathway/</guid>
      <description>&lt;h1 id=&#34;rultralight_jerk&#34;&gt;r/ultralight_jerk&lt;/h1&gt;
&lt;p&gt;Planning this trip was a great opportunity to make use of my love of spreadsheets&amp;hellip; As such, I spent way too long browsing different ultralight camping/hiking subreddits, meticulously planning every item I would carry on my back. I hyperfixated on the best sleep system and the pros and cons of different boxer materials. I even brought a titanium sh*t shovel. Here&amp;rsquo;s the fruits of my labour, all 8kg of it.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Day 3</title>
      <link>https://www.trisphoto.com/trips/gowercoastalpath/2026-08-02/</link>
      <pubDate>Sun, 02 Aug 2026 00:00:00 +0000</pubDate>
      <guid>https://www.trisphoto.com/trips/gowercoastalpath/2026-08-02/</guid>
      <description></description>
    </item>
    <item>
      <title>Live Track</title>
      <link>https://www.trisphoto.com/live-track/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://www.trisphoto.com/live-track/</guid>
      <description>&lt;h2 id=&#34;wheres-wally&#34;&gt;Where&amp;rsquo;s Wally&lt;/h2&gt;
&lt;p&gt;Track live location, sent from Garmin inReach.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://live.garmin.com/lutris&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;View my live tracker on Garmin →&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;wheres-my-kudos&#34;&gt;Where&amp;rsquo;s my Kudos?&lt;/h2&gt;
&lt;link rel=&#34;stylesheet&#34; href=&#34;https://unpkg.com/leaflet@1.9.4/dist/leaflet.css&#34; integrity=&#34;sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=&#34; crossorigin=&#34;&#34;&gt;
&lt;script src=&#34;https://unpkg.com/leaflet@1.9.4/dist/leaflet.js&#34; integrity=&#34;sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=&#34; crossorigin=&#34;&#34;&gt;&lt;/script&gt;

&lt;section class=&#34;strava-widget&#34;&gt;
  &lt;p id=&#34;strava-status&#34; class=&#34;strava-status&#34; role=&#34;status&#34; aria-live=&#34;polite&#34;&gt;Loading latest hike...&lt;/p&gt;
  &lt;article id=&#34;strava-activity&#34; class=&#34;strava-activity&#34; hidden&gt;
    &lt;div class=&#34;strava-activity-header&#34;&gt;
      &lt;span class=&#34;strava-profile-avatar&#34;&gt;
        &lt;img id=&#34;strava-profile-image&#34; class=&#34;strava-profile-image&#34; alt=&#34;Strava profile photo&#34; hidden&gt;
        &lt;span id=&#34;strava-profile-fallback&#34; class=&#34;strava-profile-fallback&#34; aria-hidden=&#34;true&#34;&gt;T&lt;/span&gt;
      &lt;/span&gt;
      &lt;a id=&#34;strava-profile-link&#34; class=&#34;strava-profile-link&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Tristan Smith&lt;/a&gt;
      &lt;time id=&#34;strava-activity-date&#34; class=&#34;strava-activity-date&#34;&gt;&lt;/time&gt;
    &lt;/div&gt;
    &lt;div class=&#34;strava-activity-details&#34;&gt;
      &lt;h3 id=&#34;strava-activity-name&#34; class=&#34;strava-activity-name&#34;&gt;&lt;/h3&gt;
      &lt;p id=&#34;strava-activity-description&#34; class=&#34;strava-activity-description&#34; hidden&gt;&lt;/p&gt;
      &lt;ul id=&#34;strava-activity-stats&#34; class=&#34;strava-activity-stats&#34;&gt;
        &lt;li class=&#34;strava-stat-action&#34;&gt;
          &lt;a id=&#34;strava-activity-link&#34; class=&#34;strava-activity-link&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;View on Strava&lt;/a&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;div id=&#34;strava-map&#34; class=&#34;strava-map&#34; aria-label=&#34;Map showing the hike route&#34;&gt;
      &lt;p id=&#34;strava-map-fallback&#34; class=&#34;strava-map-fallback&#34; hidden&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/article&gt;
&lt;/section&gt;

&lt;script&gt;
  async function updateStravaActivity() {
    const status = document.getElementById(&#34;strava-status&#34;);
    const card = document.getElementById(&#34;strava-activity&#34;);

    try {
      const response = await fetch(&#39;/strava.json?v=&#39; + Date.now());
      const data = await response.json();

      if (!response.ok || data.error) {
        status.textContent = data.error || &#34;Strava activity is unavailable.&#34;;
        card.hidden = true;
        return;
      }

      if (!data.configured) {
        status.textContent = &#34;Strava is not connected yet.&#34;;
        card.hidden = true;
        return;
      }

      if (!data.activity) {
        status.textContent = &#34;No recent public hike found.&#34;;
        card.hidden = true;
        return;
      }

      const activity = data.activity;
      const activityDate = new Date(activity.date);
      const distance = Number(activity.distanceMeters);
      const elevation = Number(activity.elevationGainMeters);
      const stats = [];

      document.getElementById(&#34;strava-activity-link&#34;).href = activity.url;
      const profileLink = document.getElementById(&#34;strava-profile-link&#34;);
      profileLink.href = activity.profileUrl || &#34;https://www.strava.com/athletes/31753850&#34;;
      profileLink.textContent = activity.profileName || &#34;Tristan Smith&#34;;
      const profileImage = document.getElementById(&#34;strava-profile-image&#34;);
      const profileFallback = document.getElementById(&#34;strava-profile-fallback&#34;);
      if (activity.profileImageUrl) {
        profileImage.src = activity.profileImageUrl;
        profileImage.hidden = false;
        profileFallback.hidden = true;
      } else {
        profileImage.removeAttribute(&#34;src&#34;);
        profileImage.hidden = true;
        profileFallback.hidden = false;
      }
      document.getElementById(&#34;strava-activity-name&#34;).textContent = activity.name;
      const description = document.getElementById(&#34;strava-activity-description&#34;);
      description.textContent = activity.description || &#34;&#34;;
      description.hidden = !activity.description;
      const date = document.getElementById(&#34;strava-activity-date&#34;);
      date.dateTime = activity.date;
      date.textContent = Number.isNaN(activityDate.getTime())
        ? &#34;Date unavailable&#34;
        : new Intl.DateTimeFormat(undefined, { dateStyle: &#34;medium&#34;, timeStyle: &#34;short&#34; }).format(activityDate);

      if (Number.isFinite(distance) &amp;&amp; distance &gt; 0) {
        stats.push({
          label: &#34;Distance&#34;,
          value: `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(distance / 1000)} km`,
        });
      }
      if (Number.isFinite(elevation) &amp;&amp; elevation &gt; 0) {
        stats.push({ label: &#34;Elevation&#34;, value: `${Math.round(elevation)} m` });
      }
      const statsList = document.getElementById(&#34;strava-activity-stats&#34;);
      const actionItem = statsList.querySelector(&#34;.strava-stat-action&#34;);
      statsList.replaceChildren(...stats.map(({ label, value }) =&gt; {
        const item = document.createElement(&#34;li&#34;);
        const statLabel = document.createElement(&#34;span&#34;);
        const statValue = document.createElement(&#34;strong&#34;);
        statLabel.className = &#34;strava-stat-label&#34;;
        statLabel.textContent = label;
        statValue.className = &#34;strava-stat-value&#34;;
        statValue.textContent = value;
        item.append(statLabel, statValue);
        return item;
      }), actionItem);

      status.textContent = &#34;&#34;;
      card.hidden = false;
      requestAnimationFrame(() =&gt; renderStravaMap(activity.summaryPolyline));
    } catch (error) {
      status.textContent = &#34;Strava activity is unavailable.&#34;;
      card.hidden = true;
    }
  }

  function renderStravaMap(encodedPolyline) {
    const mapElement = document.getElementById(&#34;strava-map&#34;);
    const fallback = document.getElementById(&#34;strava-map-fallback&#34;);
    if (!encodedPolyline) {
      fallback.textContent = &#34;No route map was shared for this hike.&#34;;
      fallback.hidden = false;
      return;
    }
    if (typeof L === &#34;undefined&#34;) {
      fallback.textContent = &#34;The route map could not be loaded.&#34;;
      fallback.hidden = false;
      return;
    }

    const coordinates = decodeStravaPolyline(encodedPolyline);
    if (!coordinates.length) {
      fallback.textContent = &#34;No route map was shared for this hike.&#34;;
      fallback.hidden = false;
      return;
    }

    const map = L.map(mapElement, { scrollWheelZoom: false });
    L.tileLayer(&#34;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&#34;, {
      maxZoom: 19,
      attribution: &#39;&amp;copy; &lt;a href=&#34;https://www.openstreetmap.org/copyright&#34;&gt;OpenStreetMap&lt;/a&gt; contributors&#39;,
    }).addTo(map);

    const route = L.polyline(coordinates, { color: &#34;#fc4c02&#34;, weight: 4, opacity: 0.9 }).addTo(map);
    const bounds = route.getBounds();
    if (bounds.isValid()) {
      map.fitBounds(bounds, { padding: [24, 24], maxZoom: 15 });
    } else {
      map.setView(coordinates[0], 13);
    }
    L.circleMarker(coordinates[0], { radius: 5, color: &#34;#ffffff&#34;, weight: 2, fillColor: &#34;#198754&#34;, fillOpacity: 1 }).addTo(map);
    L.circleMarker(coordinates[coordinates.length - 1], { radius: 5, color: &#34;#ffffff&#34;, weight: 2, fillColor: &#34;#fc4c02&#34;, fillOpacity: 1 }).addTo(map);
  }

  function decodeStravaPolyline(encoded) {
    const coordinates = [];
    let index = 0;
    let latitude = 0;
    let longitude = 0;

    while (index &lt; encoded.length) {
      let result = 0;
      let shift = 0;
      let byte;
      do {
        byte = encoded.charCodeAt(index++) - 63;
        result |= (byte &amp; 0x1f) &lt;&lt; shift;
        shift += 5;
      } while (byte &gt;= 0x20 &amp;&amp; index &lt; encoded.length);
      latitude += result &amp; 1 ? ~(result &gt;&gt; 1) : result &gt;&gt; 1;

      result = 0;
      shift = 0;
      do {
        byte = encoded.charCodeAt(index++) - 63;
        result |= (byte &amp; 0x1f) &lt;&lt; shift;
        shift += 5;
      } while (byte &gt;= 0x20 &amp;&amp; index &lt; encoded.length);
      longitude += result &amp; 1 ? ~(result &gt;&gt; 1) : result &gt;&gt; 1;

      coordinates.push([latitude / 1e5, longitude / 1e5]);
    }
    return coordinates;
  }

  updateStravaActivity();
&lt;/script&gt;
&lt;h2 id=&#34;on-air&#34;&gt;On-Air&lt;/h2&gt;
&lt;section class=&#34;spotify-widget&#34; aria-live=&#34;polite&#34;&gt;
  &lt;div id=&#34;spotify-status&#34; class=&#34;spotify-status&#34;&gt;Loading top tracks...&lt;/div&gt;
  &lt;ol id=&#34;spotify-top-tracks&#34; class=&#34;spotify-top-tracks&#34; hidden&gt;&lt;/ol&gt;
&lt;/section&gt;

&lt;script&gt;
  async function updateSpotifyStatus() {
    const status = document.getElementById(&#34;spotify-status&#34;);
    const topTracks = document.getElementById(&#34;spotify-top-tracks&#34;);

    try {
      const response = await fetch(&#39;/spotify.json?v=&#39; + Date.now());
      const data = await response.json();

      if (!response.ok || data.error) {
        status.textContent = data.error || &#34;Spotify listening status is unavailable.&#34;;
        topTracks.hidden = true;
        return;
      }

      if (!data.topTracks?.length) {
        status.textContent = &#34;No top tracks are available yet.&#34;;
        topTracks.hidden = true;
        return;
      }

      status.textContent = &#34;Spotify top listened to tracks this month&#34;;
      topTracks.replaceChildren(...data.topTracks.slice(0, 5).map((track, index) =&gt; {
        const item = document.createElement(&#34;li&#34;);
        const link = document.createElement(&#34;a&#34;);
        const image = document.createElement(&#34;img&#34;);
        const text = document.createElement(&#34;span&#34;);
        image.src = track.imageUrl;
        image.alt = &#34;&#34;;
        text.className = &#34;spotify-track-text&#34;;
        text.textContent = `${track.title} - ${track.creator}`;
        link.href = track.spotifyUrl;
        link.target = &#34;_blank&#34;;
        link.rel = &#34;noopener noreferrer&#34;;
        link.append(image, text);
        item.appendChild(link);
        return item;
      }));
      topTracks.hidden = false;
    } catch (error) {
      status.textContent = &#34;Spotify listening status is unavailable.&#34;;
      topTracks.hidden = true;
    }
  }

  updateSpotifyStatus();
  setInterval(updateSpotifyStatus, 60000);
&lt;/script&gt;</description>
    </item>
  </channel>
</rss>
