<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Josiah Wiebe – Blog</title>
    <link>https://jwww.me/blog</link>
    <atom:link href="https://jwww.me/feed.xml" rel="self" type="application/rss+xml"/>
    <description>Feed of blog posts from jwww.me</description>
    <pubDate>Thu, 21 May 2026 18:35:30 GMT</pubDate>
    <lastBuildDate>Thu, 21 May 2026 18:35:30 GMT</lastBuildDate>
    <copyright>2011-2026 Josiah Wiebe</copyright>
    <language>en-CA</language>
    <item>
      <title>Introducing Open Session</title>
      <link>https://jwww.me/blog/introducing-open-session</link>
      <pubDate>Sat, 07 Mar 2026 21:39:12 GMT</pubDate>
      <content:encoded><![CDATA[<p>If you're like me, you probably bounce around between coding tools – Cursor, Opencode, Codex etc.</p>
<p>These sessions exist all over the place, with a multitude of working directories, and I often forget which tool I started a session with.</p>
<p>So I built <code>opensession</code> – a simple TUI for managing your past chat sessions – making it easy to recall a session and jump right back in.</p>
<figure>
<p><img src="/img/introducing-open-session/cleanshot-2026-03-07-at-15-15-44-2x.png" alt=""></p>
<figcaption>Screenshot of the Open Session TUI</figcaption></figure>
<h3>Install</h3>
<pre><code class="language-bash">curl -fsSL https://jwww.me/opensession/install | bash
</code></pre>
<h3>Support</h3>
<p>It currently supports:</p>
<ul>
<li>Claude Code</li>
<li>OpenAI Codex</li>
<li>Cursor</li>
<li>Gemini</li>
<li>OpenCode</li>
</ul>
<p>Check it out on <a href="https://github.com/josiahwiebe/opensession" rel="noopener noreferrer" target="_blank">GitHub</a>. Open a PR if you want to add another app!</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/introducing-open-session</guid>
    </item>
    <item>
      <title>On getting LASIK</title>
      <link>https://jwww.me/blog/on-getting-lasik</link>
      <pubDate>Sun, 07 Dec 2025 22:55:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><em>Technically, I got "TransPRK+ with iDesign Studio" but LASIK is the lingua franca for laser optical vision correction</em></p>
<p>I've had glasses for as long as I can remember. I don't think there's a moment of my life when I remember waking up and just simply "seeing". I've tried contacts and never enjoyed them as they didn't feel like a full vision correction. As a matter of fact, I've actually always loved glasses – I probably have a dozen pairs or so.</p>
<p>However, I wanted to experience the feeling that so many people do without even thinking about it, so I scheduled a consult.</p>
<p>Initially, my consult ruled out LASIK due to thinner corneas, but after my final in-depth assessment, I was deemed eligible for either LASIK or PRK. I settled on PRK, partly due to this being my initial choice, and partly for some of the dry-eye, night vision, and corneal strength benefits.</p>
<p>I found myself approaching the surgery with nonchalance – and when the actual procedure came about, my feelings felt validated. It truly doesn't feel like a surgery at all. Everything was pretty much a walk in the park.</p>
<p>0 day - Slept for about 45 minutes right after the procedure, woke up feeling pretty good. Iced my eyes for 15 minute intervals to prevent swelling. Rigorous drop schedule. Eyes feel gritty and vision is not great.</p>
<p>+1 day - Piece of cake. made some soup, went to a company Christmas event. Eyes are still gritty. Continuing with the drop and icing regimen.</p>
<p>+2 days - This was the only truly bad day. There were moments I regretted the whole thing. My surgeon swapped the protective contact bandage and there was a fuzz or some debris under it that was causing my pain. Once he removed it (which was an ordeal – nearly passed out), I had immediate relief.</p>
<p>+ 3 days - Got the protective contact lens removed today. Vision feeling decent, no grittiness.</p>
<p>+7 days - One week consult. Vision test showing good results and re-growth of the corneal epithelium. I was able to get my driving license restriction removed and can now drive again!</p>
<p>+14 days - Vision is feeling like it's stabilizing a bit more but still has some room to improve.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/on-getting-lasik</guid>
    </item>
    <item>
      <title>Raspberry Ghost + Vercel</title>
      <link>https://jwww.me/blog/raspberry-ghost-vercel</link>
      <pubDate>Mon, 18 Aug 2025 21:59:52 GMT</pubDate>
      <content:encoded><![CDATA[<p><em>Ghost + Raspberry Pi + Cloudflare Tunnel + GitHub Actions + Vercel</em></p>
<p>Late last week, I was looking through my desk and stumbled across an older Raspberry Pi 3 B model. Not a particularly powerful unit but I thought I could possibly still make use of it.</p>
<p>Ghost recently released their <a href="https://docs.ghost.org/install/docker#why-docker%3F" rel="noopener noreferrer" target="_blank">Docker (preview)</a> in version 6, and I've been looking for an excuse to try it. So I cooked up this idea to host a Ghost version of my site on the Raspberry Pi.</p>
<h2>My Requirements</h2>
<ul>
<li>No new hardware - I could easily get a $5 VPS and run it there, but that's no fun.</li>
<li>Offline tolerant - the Raspberry Pi is not intended to be production grade and could go offline at any time. The same could be said about my home internet connection.</li>
<li>Maintains existing structure - my website is a weird mix of PHP running on Vercel, basically an SSR-markdown-powered static site. I'd like to keep that for now.</li>
</ul>
<h2>Plan</h2>
<p>Here's where I landed. There's probably better ways to do a lot of this, but this is what it looked like after spending a couple hours on it over the weekend.</p>
<p><img src="/img/raspberry-ghost-vercel/ghost-rpi-15cda15d.png" alt=""></p>
<p>The Raspberry Pi would run the Ghost instance, which is made available to the outside world using the excellent <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" rel="noopener noreferrer" target="_blank">Cloudflare Tunnel</a> (could also use Tailscale). Even if the RPi was offline, it wouldn't matter as it's not actually serving the site – it's simply used for authoring.</p>
<p>The Ghost instance would have a webhook configured to target the Vercel webhook. On publish, the webhook would initiate a GitHub action, which in turn would export the content from Ghost, convert to Markdown, and it commit to the Git repository. This commit would trigger a new Vercel build, which would subsequently be deployed.</p>
<p>Outside of the GitHub action, there is no further requirement for the Ghost server to be online – I can disable the tunnel any time with no consequence for the running website. Plus, I can still author content using plain markdown; I just have to run the upload script to sync it back to Ghost whenever the server is back online.</p>
<h2>Installing Ghost</h2>
<p>Honestly, this was the most trivial part of the step. The new Docker setup is super simple, and I had very little trouble getting it going.</p>
<pre><code class="language-yml">// docker-compose.yml
volumes:
  ghost_content:

services:
  ghost:
    image: ghost:5-alpine
    platform: linux/arm64/v8
    ports:
      - "2368:2368"
    environment:
      NODE_ENV: development
      url: ${GHOST_URL}

      database__connection__filename: /var/lib/ghost/content/data/ghost.db
      security__staffDeviceVerification: "false"
    volumes:
      - ghost_content:/var/lib/ghost/content
</code></pre>
<p>The one caveat is that I wanted the simplest, most portable Ghost install possible, so I'm running it in development mode. This way, my database is just a simple SQLite file. I also had to disable <code>staffDeviceVerification</code> as the device is unable to send email.</p>
<h2>Creating the Import Script</h2>
<p>The conversion of Markdown to Ghost-friendly HTML is pretty straightforward. However, I'm using some non-standard <a href="https://github.com/josiahwiebe/jwie.be/blob/main/util/markdown.php#L5-L18" rel="noopener noreferrer" target="_blank">CommonMark extensions in PHP</a> and I wanted to make sure those things were handled both on the Markdown→Ghost import as well as the Ghost→Markdown export. This section really just discusses the setup of getting my posts <em>into</em> Ghost – if you're more curious about the publishing workflow, keep scrolling.</p>
<h3><code>:::image-half</code></h3>
<p>I'm using a CommonMark Attributes extension to allow me to create Markdown blocks like this:</p>
<pre><code class="language-html">&#x3C;figure class="kg-card kg-gallery-card kg-width-wide">
  &#x3C;div class="kg-gallery-container">
    &#x3C;div class="kg-gallery-row">
        &#x3C;div class="kg-gallery-image">
          &#x3C;img src="img/image1.jpg" width="1200" height="800" loading="lazy" alt="" />
        &#x3C;/div>
        &#x3C;div class="kg-gallery-image">
          &#x3C;img src="/img/image2.jpg" width="1200" height="800" loading="lazy" alt="" />
        &#x3C;/div>
    &#x3C;/div>
  &#x3C;/div>
  &#x3C;figcaption>First image caption | Second image caption&#x3C;/figcaption>
&#x3C;/figure>
</code></pre>
<p>Ghost doesn't natively support a layout like that, but they do have a gallery block. However, their galleries don't support videos and photos mixed, so we have to handle those a bit differently. Here's what I landed on to parse those Markdown blocks to galleries:</p>
<pre><code class="language-ts">function imageHalf(md: string) {
  // :::image-half \n ![...](...) \n ^^^ caption \n ![...](...) \n ^^^ caption \n :::
  return md.replace(
    /:::image-half\s*\n([\s\S]*?)\n:::/g,
    (_m, content) => {
      // Check if content contains video elements or video file references
      const hasVideo = content.includes('&#x3C;video') || /!\[[^\]]*\]\([^)]*\.(mp4|webm|mov)\)/i.test(content);

      if (hasVideo) {
        // Preserve as markdown block if videos are detected
        return `\`\`\`markdown\n:::image-half\n${content}\n:::\n\`\`\``;
      }

      const images: { src: string; alt: string; caption?: string }[] = [];

      // Split content into lines and process
      const lines = content.split('\n').map(line => line.trim()).filter(Boolean);

      for (let i = 0; i &#x3C; lines.length; i++) {
        const line = lines[i];

        // Check if this line is an image
        const imgMatch = line.match(/!\[([^\]]*)\]\(([^)]+)\)/);
        if (imgMatch) {
          const [, alt, src] = imgMatch;

          // Check if the next line is a caption (starts with ^^^)
          const nextLine = lines[i + 1];
          let caption: string | undefined;

          if (nextLine &#x26;&#x26; nextLine.startsWith('^^^')) {
            caption = convertMarkdownCaptions(nextLine.replace(/^\^\^\^\s*/, ''));
            i++; // Skip the caption line since we've processed it
          }

          images.push({ src, alt, caption });
        }
        // Skip standalone ^^^ lines that aren't image captions
      }

      if (images.length === 0) return _m; // Return original if no images found

      // Create Ghost gallery card HTML structure
      const galleryImages = images.map(img =>
        `&#x3C;div class="kg-gallery-image">
          &#x3C;img src="${img.src}" width="1200" height="800" loading="lazy" alt="${img.alt}" />
        &#x3C;/div>`
      ).join('\n        ');

      // Combine all captions for overall gallery caption
      const galleryCaption = images.map(img => img.caption).filter(Boolean).join(' | ');

      return `&#x3C;figure class="kg-card kg-gallery-card kg-width-wide">
  &#x3C;div class="kg-gallery-container">
    &#x3C;div class="kg-gallery-row">
        ${galleryImages}
    &#x3C;/div>
  &#x3C;/div>${galleryCaption ? `\n  &#x3C;figcaption>${galleryCaption}&#x3C;/figcaption>` : ''}
&#x3C;/figure>`;
    }
  );
}
</code></pre>
<p>For blocks that have videos and photos mixed, we actually just preserve the Markdown as a Ghost markdown block, so that our export script can simply export it as-is. Feels like a bit of a workaround, because it is!</p>
<h3>Images &#x26; Captions</h3>
<p>These were pretty easy! We'll need to make sure we also preserve image caption Markdown. Here's a Regex that targets captions but excludes Ghost Markdown blocks.</p>
<pre><code class="language-ts">function mdCaptionToFigure(md: string) {
  // Convert ![alt](src)\n^^^ caption to &#x3C;figure>&#x3C;img />&#x3C;figcaption>... or &#x3C;figure>&#x3C;video />&#x3C;figcaption>...
  // But skip content inside markdown code blocks
  return md.replace(
    /```markdown\n([\s\S]*?)\n```|!\[([^\]]*)\]\(([^)\s]+)\)\s*\n\^\^\^\s*([^\n]+)/g,
    (match, codeContent, alt, src, cap) => {
      // If this is a markdown code block, return it unchanged
      if (codeContent !== undefined) {
        return match;
      }

      // Otherwise process the image/video caption
      const isVideo = /\.(mp4|webm|mov)$/i.test(src);
      if (isVideo) {
        return `&#x3C;figure>&#x3C;video src="${src}" controls>&#x3C;/video>&#x3C;figcaption>${convertMarkdownCaptions(cap)}&#x3C;/figcaption>&#x3C;/figure>`;
      } else {
        return `&#x3C;figure>&#x3C;img alt="${alt}" src="${src}" />&#x3C;figcaption>${convertMarkdownCaptions(cap)}&#x3C;/figcaption>&#x3C;/figure>`;
      }
    }
  );
}
</code></pre>
<h3>Processing Pipeline</h3>
<p>There's a few steps to transforming the Markdown to Ghost-ready content. Some is handled with my own custom parsing functions, the majority is then converted using <a href="https://github.com/micromark/micromark" rel="noopener noreferrer" target="_blank">micromark</a>, and finally converted to Lexical format using Ghost's own <a href="https://github.com/TryGhost/Koenig/tree/main/packages/kg-html-to-lexical" rel="noopener noreferrer" target="_blank">@tryghost/kg-html-to-lexical</a> package. The sequence roughly follows this progression:</p>
<ol>
<li>Read the Markdown files (I pass a directory as an argument to the script)</li>
<li>Parse the front matter and title, slugify it (I should probably use <a href="https://github.com/TryGhost/Koenig/blob/main/packages/kg-utils/lib/slugify.js" rel="noopener noreferrer" target="_blank">the Ghost slug function</a> instead of my own, but I don't have any collision issues yet)</li>
<li>Start converting the content to HTML</li>
<li>Handle <code>:::image-half</code></li>
<li>Handle image captions</li>
<li>Convert to HTML using <a href="https://github.com/micromark/micromark" rel="noopener noreferrer" target="_blank">micromark</a></li>
<li>Absolute-ize the images (since they live in the GitHub repo and not on Ghost)</li>
<li>Convert to Lexical format</li>
<li>Upload to Ghost using the <a href="https://docs.ghost.org/admin-api/javascript" rel="noopener noreferrer" target="_blank">Ghost Admin API JavaScript client</a></li>
</ol>
<p>That's it! All of my existing content is now in Ghost.</p>
<h2>Export Script</h2>
<p>Now that the content is all in Ghost, we can get to work with the publishing flow. Since we've already built the import functionality, we simply need to work in reverse to create the export functionality.</p>
<p>Unfortunately, it's not quite that simple, but here's a couple of solutions I ended up with.</p>
<h3>Lexical Content</h3>
<p>Some types of content felt a bit easier to parse directly from the Lexical format, such as code blocks and images.</p>
<pre><code class="language-ts">function processLexicalContent(lexicalStr?: string): { videos: Array&#x3C;{src: string, caption: string, alt: string}>, markdownBlocks: string[] } {
  const videos: Array&#x3C;{src: string, caption: string, alt: string}> = [];
  const markdownBlocks: string[] = [];

  if (!lexicalStr) return { videos, markdownBlocks };

  try {
    const lexical = JSON.parse(lexicalStr);

    function processNode(node: any) {
      // Handle video blocks
      if (node.type === 'video') {
        videos.push({
          src: node.src || '',
          caption: node.caption || '',
          alt: node.alt || node.fileName || ''
        });
      }

      // Handle code blocks that might contain preserved markdown (both 'code' and 'codeblock' types)
      // We're using this to support :::image-half blocks that contain videos (workaround since Ghost doesn't support videos in galleries)
      if ((node.type === 'code' || node.type === 'codeblock') &#x26;&#x26; node.language === 'markdown') {
        markdownBlocks.push(node.code || '');
      }

      // Recursively process children
      if (node.children &#x26;&#x26; Array.isArray(node.children)) {
        node.children.forEach(processNode);
      }
    }

    if (lexical?.root?.children) {
      lexical.root.children.forEach(processNode);
    }
  } catch (e) {
    console.error('Error processing lexical content:', e);
  }

  return { videos, markdownBlocks };
}
</code></pre>
<p>We'll re-use the result of this function later.</p>
<h3>Galleries</h3>
<p>While the Ghost gallery block was an excellent solution for my <code>:::image-half</code> blocks, unfortunately Ghost does not support captions on a per-image basis for these. Thankfully, we just saved our caption with a pipe <code>|</code> separator in our import script, so we can now easily just split it on that.</p>
<p>Since we're going to re-process the HTML later, we'll add placeholders for all of the converted content so it's easier to find our place again. For example, for our <code>:::image-half</code> component:</p>
<pre><code class="language-ts">function preprocessGhostGallery(html: string): { html: string, placeholders: Record&#x3C;string, string> } {
  const doc = parse(html);
  const nhm = new NodeHtmlMarkdown();
  const placeholders: Record&#x3C;string, string> = {};

  const galleryCards = doc.querySelectorAll('figure.kg-gallery-card');
  galleryCards.forEach((figure, index) => {
    const images = figure.querySelectorAll('img');
    if (images.length === 0) return;

    // Extract and convert gallery caption to markdown if it exists
    const figcaption = figure.querySelector('figcaption');
    let galleryCaption = '';
    if (figcaption) {
      // Convert caption HTML to markdown to preserve formatting
      galleryCaption = nhm.translate(figcaption.innerHTML).trim();
    }

    // Split caption by | for individual images
    const splitCaptions = galleryCaption ? galleryCaption.split('|').map(c => c.trim()) : [];

    let result = ':::image-half\n';

    images.forEach((img, imgIndex) => {
      // Start each image block with ^^^
      result += '^^^\n';

      const src = img.getAttribute('src') || '';
      const alt = img.getAttribute('alt') || '';
      result += `![${alt}](${src})\n`;

      // Add caption if exists (this also serves as the closing ^^^)
      const caption = splitCaptions[imgIndex] || '';
      if (caption) {
        result += `^^^ ${caption}\n`;
      } else {
        // No caption, but we still need the closing ^^^
        result += '^^^\n';
      }
    });

    result += ':::';

    // Create a unique placeholder
    const placeholder = `GHOSTGALLERY${index}`;
    placeholders[placeholder] = result;

    // Replace the figure with a paragraph containing the placeholder to preserve spacing
    const placeholderNode = parse(`&#x3C;p>${placeholder}&#x3C;/p>`);
    figure.replaceWith(placeholderNode);
  });
}
</code></pre>
<h3>Putting it all together</h3>
<p>I won't go into all of the details – if you want to check out the source code, you can see the <a href="https://github.com/josiahwiebe/jwie.be/blob/main/tools/ghost-export-md.ts" rel="noopener noreferrer" target="_blank">Ghost export script here</a>.</p>
<p>The sequence is basically this:</p>
<ol>
<li>Fetch all posts from Ghost</li>
<li>Process Lexical content (videos, markdown blocks that contain videos)</li>
<li>Download and rewrite images (these need to be in the repo as that's how they get added to the public site)</li>
<li>Pre-process HTML (images, captions, markdown code blocks)</li>
<li>Convert to Markdown using <code>node-html-markdown</code></li>
<li>Replace placeholders and our Markdown video blocks</li>
<li>Final post-processing of Markdown (some node-html-markdown escaping quirks that didn't match the format</li>
<li>Save or update <code>.md</code> files in the GitHub repo</li>
</ol>
<h2>Publishing Flow</h2>
<p>Now that we've got all of the pieces, let's put them together!</p>
<p>First, we'll create the GitHub Action:</p>
<pre><code class="language-yml">// .github/workflows/sync-blog.yml
name: Sync blog (Ghost → MD)

on:
  workflow_dispatch:
    inputs:
      event_type:
        description: 'Event Type'
        required: true
        default: 'ghost-publish'
        type: string

jobs:
  export:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2

      - run: bun install

      - name: Export posts → content/blog
        run: bun tools/ghost-export-md.ts
        env:
          GHOST_URL: ${{ secrets.GHOST_URL }}
          GHOST_CONTENT_KEY: ${{ secrets.GHOST_CONTENT_KEY }}
          GHOST_ADMIN_KEY: ${{ secrets.GHOST_ADMIN_KEY }}

      - name: Commit changes (if any)
        run: |
          git config user.name "ghost-bot"
          git config user.email "ghost-bot@users.noreply.github.com"
          git add content/blog public/img
          git diff --cached --quiet || git commit -m "chore: export blog"
          git push
</code></pre>
<p>This action will setup Bun and directly execute the export script, which will cause any changed or new posts to be added to VCS. The commit and push will automatically trigger a Vercel deployment.</p>
<p>Now, we need a webhook. I'll create a new file in the <code>/api</code> directory of my repository, which Vercel will automatically turn into a serverless function:</p>
<pre><code class="language-js">// /api/ghost-hook.mjs

export default async function handler(req, res) {
  if (req.method !== "POST") return res.status(405).json({ error: "Method not allowed" });

  try {
    const repo = process.env.GH_REPO;
    const token = process.env.GH_TOKEN;
    if (!repo || !token) {
      return res.status(500).json({ error: "Missing GH_REPO or GH_TOKEN environment variables" });
    }

    const response = await fetch(`https://api.github.com/repos/${repo}/actions/workflows/sync-blog.yml/dispatches`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Accept': 'application/vnd.github.v3+json',
        'Content-Type': 'application/json',
        'User-Agent': 'vercel-webhook'
      },
      body: JSON.stringify({
        ref: 'main',
        inputs: {
          event_type: 'ghost-publish'
        }
      })
    });

    if (response.status === 204) {
      console.log('Workflow triggered successfully');
      return res.status(200).json({ success: true, message: "Workflow triggered" });
    } else {
      const errText = await response.text();
      console.error('GitHub API error:', response.status, errText);
      throw new Error(`GitHub API returned ${response.status}: ${errText}`);
    }
  } catch (error) {
    console.error('Error triggering workflow:', error);
    return res.status(500).json({ error: error.message });
  }
}
</code></pre>
<p>Let's hook this up to Ghost. In the Ghost admin, we can create a new "custom integration". This is also where we'll get the Content API Key and the Admin API Key that are needed by our import and export scripts.</p>
<p><img src="/img/raspberry-ghost-vercel/cleanshot-2025-08-18-at-16-56-34-2x-33b7fbf5.jpg" alt=""></p>
<p>I created a webhook for the <code>Post published</code> and <code>Post updated</code> events, but I could add more if needed.</p>
<h3>That's it!</h3>
<p>Now, I hit publish on Ghost, which fires that webhook (the Vercel serverless function). In turn, that function triggers the GitHub Action that will export the Ghost content, add it to VCS, and create a new commit. Finally, that commit automatically triggers the new Vercel build.</p>
<p>You can check out the <a href="https://github.com/josiahwiebe/jwie.be/tree/main/tools" rel="noopener noreferrer" target="_blank">full code on GitHub</a>.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/raspberry-ghost-vercel</guid>
    </item>
    <item>
      <title>Adding a Vite-Powered Playground to my Website</title>
      <link>https://jwww.me/blog/adding-a-react-playground</link>
      <pubDate>Sat, 04 Jan 2025 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I was playing around with a React project locally and I wanted a way to share it within the context of my own website.</p>
<p>In this particular case, I'm configuring React, but you could use Vite to configure any other frontend framework.</p>
<p>This website has a bit of an unconventional setup, so I thought I'd share how I added a React application within the site. The concepts in this article could be applied to most PHP-based websites, where you handle the routing and rendering of the page on the server side.</p>
<p>First, we'll need <a href="https://vite.dev/" rel="noopener noreferrer" target="_blank">Vite</a>. I was already using TailwindCSS on the project, but using the Tailwind CLI, so let's replace that with Vite. We'll also use the <a href="https://tailwindcss.com/docs/v4-beta" rel="noopener noreferrer" target="_blank">new Tailwind v4 beta</a>. Let's install these new dependencies.</p>
<pre><code class="language-bash">npm i vite tailwindcss@next @tailwindcss/vite@next
</code></pre>
<p>We'll need to create a <code>vite.config.ts</code> file in the root of the project to handle the build process.</p>
<pre><code class="language-ts">import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
import tailwindcss from '@tailwindcss/vite'

export default defineConfig({
  plugins: [react(), tailwindcss()],
  build: {
    // we'll need the manifest so that we can load the assets into the PHP template
    manifest: true,
    rollupOptions: {
      input: {
        // handle the styles for the whole website
        style: resolve(__dirname, 'src/style.css'),
        // handle the styles for the playground
        playground: resolve(__dirname, 'src/playground/index.tsx'),
      },
    },
    outDir: 'dist',
    emptyOutDir: false,
  },
  server: {
    origin: 'http://localhost:5173',
  },
})
</code></pre>
<p>Vite's <a href="https://vite.dev/guide/backend-integration.html" rel="noopener noreferrer" target="_blank">documentation for this</a> is very good, but I just thought I'd get into a specific PHP example.</p>
<p>I'm going to assume you already have a React application created. Put that application in the <code>src/playground</code> directory, where <code>index.tsx</code> is the entry point for the application. If you're moving an existing application into this project, you'll need to update the <code>index.tsx</code> file with the module preload polyfill.</p>
<pre><code class="language-ts">import 'vite/modulepreload-polyfill'

// ...existing react application code
</code></pre>
<p>We're going to create a PHP function that loads the Vite server and returns the HTML for the application in development mode.</p>
<pre><code class="language-php">&#x3C;?php

function dev_load_vite_module($path) {
  $vite_host = 'http://localhost:5173';
  $response = @file_get_contents($vite_host . $path);

  if ($response === false) {
    http_response_code(404);
    return;
  }

  if (str_contains($path, '.css')) {
    header('Content-Type: text/css');
  } else {
    header('Content-Type: application/javascript');
  }

  foreach ($http_response_header as $header) {
    if (str_contains(strtolower($header), 'access-control-')) {
      header($header);
    }
  }

  echo $response;
}
</code></pre>
<p>Hook this function into our router, perhaps with something like this:</p>
<pre><code class="language-php">&#x3C;?php

if (isset($_ENV['DEV'])) {
  dev_load_vite_module($_SERVER['REQUEST_URI']);
}

// ...existing template code
</code></pre>
<p>This will load the Vite server and return the HTML for the application in development mode.</p>
<p>Now we'll need to create a function to handle the assets in production mode.</p>
<pre><code class="language-php">&#x3C;?php

function vite_assets() {
  $isDev = isset($_ENV['DEV']);

  if ($isDev) {
    return [
      'css' => 'http://localhost:5173/src/style.css',
      'js' => 'http://localhost:5173/src/playground/index.tsx'
    ];
  }

  $manifest = json_decode(file_get_contents(__DIR__ . '/../dist/.vite/manifest.json'), true);

  return [
    'css' => '/dist/' . $manifest['src/style.css']['file'],
    'js' => '/dist/' . $manifest['src/playground/index.tsx']['file']
  ];
}
</code></pre>
<p>Update your root template to include the Vite assets in the <code>&#x3C;head></code> tag. You'll also need to add a root element for the React application to mount into.</p>
<pre><code class="language-html">&#x3C;?php $assets = vite_assets(); ?>
&#x3C;head>
  &#x3C;?php $isDev = isset($_ENV['DEV']); ?>
  &#x3C;?php if ($isDev): ?>
  &#x3C;script type="module" src="http://localhost:5173/@vite/client">&#x3C;/script>
  &#x3C;?php endif; ?>
  &#x3C;link rel="stylesheet" href="&#x3C;?= $assets['css'] ?>" />
&#x3C;/head>
&#x3C;body>
  &#x3C;div id="root">&#x3C;/div>
  &#x3C;?php if ($isDev): ?>
  &#x3C;!-- since it's a react application, we need to load the react refresh runtime for development. -->
  &#x3C;script type="module">
    import RefreshRuntime from 'http://localhost:5173/@react-refresh'
    RefreshRuntime.injectIntoGlobalHook(window)
    window.$RefreshReg$ = () => {}
    window.$RefreshSig$ = () => (type) => type
    window.__vite_plugin_react_preamble_installed__ = true
  &#x3C;/script>

  &#x3C;script type="module" src="http://localhost:5173/@vite/client">&#x3C;/script>
  &#x3C;script type="module" src="http://localhost:5173/src/playground/index.tsx">&#x3C;/script>
  &#x3C;?php else: ?>
  &#x3C;script type="module" src="&#x3C;?= $assets['js'] ?>">&#x3C;/script>
  &#x3C;?php endif; ?>
&#x3C;/body>
</code></pre>
<p>Since TailwindCSS v4 uses CSS for configuration, you'll want to update your root <code>src/style.css</code> file to use the new directives. You'll also want to add the <code>@source</code> directive to include the PHP files and the React application files.</p>
<pre><code class="language-css">@import 'tailwindcss';

@source '../**/*.php';
@source './playground/**/*.tsx';
</code></pre>
<p>Now, simply update your <code>package.json</code> to use Vite.</p>
<pre><code class="language-json">"scripts": {
  "dev": "vite",
  "build": "vite build"
}
</code></pre>
<p>Now you can run <code>npm run dev</code> to start the Vite server and <code>npm run build</code> to build the application. You should see the playground running on your site.</p>
<p>That's it! You can see my React application in action here: <a href="/playground/lastfm">Last.fm Listening Stats</a></p>
<p>The great thing about using Vite is that you could easily swap out the React application for another frontend framework. For example, you could use Solid or Vue instead of React and the same setup would work.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/adding-a-react-playground</guid>
    </item>
    <item>
      <title>2024 - Haves and Have Nots</title>
      <link>https://jwww.me/blog/2024-in-review</link>
      <pubDate>Thu, 02 Jan 2025 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I thought I'd try a different format for this year's review. I'm simply going to list out a few things I've done and haven't done, and then a few favourites.</p>
<h1>Haves</h1>
<p>While travel was definitely a highlight, we also spent a lot of quality time at home. We did a lot bike riding, a bit of skiing, and enjoying those glorious Manitoba summers in the backyard.</p>
<p>It was a really mild winter, so skiing was super scarce, but thankfully we got some good snow starting in November, so 🤞 for this season. The mild winter led into a mild spring, so I was able to get out on the bike earlier. Plus I got a new bike this year which was a lot of fun!</p>
<div class="image-half">
<figure>
<p><img src="/img/2024-in-review/new-canyon.jpg" alt="Canyon Grizl"></p>
<figcaption>Post-ride shot of my new Canyon Grizl - my first carbon fibre bike</figcaption></figure>
<figure>
<p><img src="/img/2024-in-review/bike-ride.jpg" alt="Going for a ride"></p>
<figcaption>What a weirdo</figcaption></figure>
</div>
<p>In addition to the usual work trips, we were fortunate to be able to get away for a few family vacations, as well as two couples trips. We got away to Cancun, Revelstoke, and Vancouver with the kids, aside from other more local getaways and camping outings. The two of us managed to get away to Toronto and New York City for two different long weekends as well.</p>
<p>As I'm writing this, I'm looking back at my year and I'm realizing there was a lot of new places! My first time at:</p>
<ul>
<li>Universal Studios</li>
<li>New Mexico</li>
<li>New York City</li>
<li>Revelstoke</li>
<li>Toronto</li>
<li>Winnipeg</li>
</ul>
<p>Maybe not mind-blowing destinations but surprising to me that I've never been to any of these places before. Here's to more new places in 2025!</p>
<p>We were fortunate to be able to visit a couple of restaurants that I've been wanting to try for a while.</p>
<ul>
<li><a href="https://primeseafoodpalace.ca/" rel="noopener noreferrer" target="_blank">Prime Seafood Palace</a> - Toronto</li>
<li><a href="https://www.saga-nyc.com/" rel="noopener noreferrer" target="_blank">SAGA</a> - NYC</li>
<li><a href="https://osteriagiulia.ca/" rel="noopener noreferrer" target="_blank">Osteria Giulia</a> - Toronto</li>
</ul>
<figure>
<p><img src="/img/2024-in-review/psp.jpg" alt="Prime Seafood Palace"></p>
<figcaption>Prime Seafood Palace</figcaption></figure>
<p>The interior of Prime Seafood Palace is gorgeous.</p>
<div class="image-half">
<figure>
<p><img src="/img/2024-in-review/psp-2.jpg" alt="Prime Seafood Palace"></p>
<figcaption>The level of detail is astonishing.</figcaption></figure>
<figure>
<p><img src="/img/2024-in-review/psp-3.jpg" alt="Prime Seafood Palace"></p>
<figcaption>The bar at PSP</figcaption></figure>
</div>
<p>SAGA has a set menu, and there was a couple of winners in there. The atmosphere and view are what really sets it apart.</p>
<div class="image-half">
<figure>
<p><img src="/img/2024-in-review/saga.jpg" alt="SAGA"></p>
<figcaption>The duck breast at SAGA was excellent. This dish also got me hooked on persimmons.</figcaption></figure>
<figure>
<p><img src="/img/2024-in-review/saga-view.jpg" alt="SAGA view"></p>
<figcaption>The view from SAGA is also pretty legit.</figcaption></figure>
</div>
<p>Went to <a href="https://www.reactmiami.com/" rel="noopener noreferrer" target="_blank">React Miami</a> in April which was probably the best conference I've ever attended. Met a lot of internet friends and made some new ones. Looking forward to attending again in 2025.</p>
<figure>
<p><img src="/img/2024-in-review/react-miami.jpg" alt="React Miami"></p>
<figcaption>[Some](https://x.com/ken_wheeler) of the [folks](https://x.com/camtheperson?lang=en) from React Miami at the after party</figcaption></figure>
<h1>Have Nots</h1>
<p>I wasn't able to reach my running goal, but managed to get in a couple of runs (mostly while travelling - somehow it's easier when you're not at home).</p>
<p>I have not blogged at all, but that's okay. I was more active on Twitter but I'm not sure that counts. It's fun though!</p>
<p>Have not redesigned my website! This is probably a good thing to have on the <em>have nots</em> list.</p>
<p>We didn't do a lot of camping this year. We did a few small trips closer to home, but nothing extensive. Planning out something bigger for 2025.</p>
<p>I'm not yet fluent in French, but I'm improving. I still don't believe in Duolingo's method (it's mostly a game), but I did break a 365 day streak in fall.</p>
<p>We have not yet resumed our Hawai'i tradition (it was interrupted by the pandemic). We were going strong for 8 years, but time just hasn't allowed for it. Perhaps in 2025.</p>
<h1>Favourites of 2024</h1>
<p>Things I enjoyed this year.</p>
<p><strong>Favourite Bar</strong> - <a href="https://www.milkywaycocktails.com/" rel="noopener noreferrer" target="_blank">Milky Way</a> - Montreal</p>
<p>Tried a variety of cocktails here. Drinks were excellent but the vibes were immaculate.</p>
<div class="image-half">
<figure>
<p><img src="/img/2024-in-review/entering-milky-way.jpg" alt="Milky Way"></p>
<figcaption>Entering Milky Way</figcaption></figure>
<figure>
<p><img src="/img/2024-in-review/milky-way.jpg" alt="Milky Way"></p>
<figcaption>[The Milky Way Cocktail Bar](https://www.milkywaycocktails.com/)</figcaption></figure>
</div>
<p><strong>Favourite Dish</strong> - <em>Cabbage w/ BBQ Chicken Skin</em> at <a href="https://primeseafoodpalace.ca/" rel="noopener noreferrer" target="_blank">Prime Seafood Palace</a> - Toronto, or the <em>Pomelo Salad</em> at <a href="https://www.baraccanto.com/" rel="noopener noreferrer" target="_blank">Bar Accanto</a> - Winnipeg</p>
<p>Unfortunately I don't have any photos of these, but they were both excellent.</p>
<p><strong>Favourite Ride</strong> - <a href="https://www.strava.com/activities/12343906285" rel="noopener noreferrer" target="_blank">LTV Cider Cycle</a> - RM of Stanley.</p>
<p>Not a long ride but so fun taking all of the LTV owners out for a ride.</p>
<p><strong>Most Surprising Place</strong> - Las Cruces, New Mexico.</p>
<p>First time in New Mexico (also in west Texas to get there), and I had zero expectations. It was a great surprise. Will be back.</p>
<figure>
<p><img src="/img/2024-in-review/las-cruces.jpg" alt="Las Cruces"></p>
<figcaption>Hiking in the Organ Mountains</figcaption></figure>
<p><strong>Favourite Podcast</strong> - The live episode of <a href="https://syntax.fm/" rel="noopener noreferrer" target="_blank">Syntax.fm</a> at React Miami.</p>
<p>I'm not a podcast listener (like, at all), but I used to listen to Syntax.fm a lot. It was a blast to participate in this live – Wes and Scott are true gems and great hosts.</p>
<figure>
<p><img src="/img/2024-in-review/syntax-fm.jpg" alt="Syntax.fm"></p>
<figcaption>Syntax.fm at React Miami</figcaption></figure>
<p><strong>Best Paddleboarding</strong> - <a href="https://www.strava.com/activities/11817838597" rel="noopener noreferrer" target="_blank">Revelstoke Reservoir</a> - Revelstoke, BC.</p>
<p>Spent the best day paddling with the family and some friends on the Revelstoke Reservoir.</p>
<!-- GHOST_VIDEO_CONTENT -->
<div class="image-half">
<figure>
<p><video src="/img/2024-in-review/revelstoke.mp4" autoplay loop muted playsinline></video></p>
<figcaption>Paddling down Carnes Creek</figcaption></figure>
<figure>
<p><img src="/img/2024-in-review/revelstoke-reservoir.jpg" alt="Revelstoke Reservoir"></p>
<figcaption>Looking back toward the Revelstoke Reservoir</figcaption></figure>
</div>
<p><strong>Best Late-Night Snack</strong> - seared scallops w/ brown butter and broccolini</p>
<figure>
<p><img src="/img/2024-in-review/snack.jpg" alt="Seared Scallops"></p>
<figcaption>Seared Scallops</figcaption></figure>
<p><strong>Most Insane Lasagna</strong> - Porzia's - Toronto</p>
<figure>
<p><img src="/img/2024-in-review/porzias.jpg" alt="Porzia&#x27;s"></p>
<figcaption>This is ridiculous. Those layers 👨‍🍳😘</figcaption></figure>
<p><strong>Favourite Song</strong> - <a href="https://open.spotify.com/track/2IPshLLh6a1bzS9hDHqJ36?si=8093e701a62c46fb" rel="noopener noreferrer" target="_blank">Backfire</a> by Wild Rivers. We were in Toronto to see them play at HISTORY. I also had <a href="https://soundcloud.com/bigbootiemix" rel="noopener noreferrer" target="_blank">Two Friends</a> on the speakers a lot this year, especially out on the golf course.</p>
<p><strong>Favourite Apps</strong></p>
<ul>
<li><a href="https://halide.cam/" rel="noopener noreferrer" target="_blank">Halide Camera</a>. - This is by no means a new app, but I got into using it again. Just makes taking photos fun again.</li>
<li><a href="https://overseer.dev/" rel="noopener noreferrer" target="_blank">Overseer</a> - This is a fantastic tool for managing your media library requests (especially if you have a Plex server).</li>
</ul>
<p><strong>Favourite Movies</strong></p>
<ul>
<li>Wicked - 🤷‍♂️</li>
<li>The Fall Guy</li>
<li>White Bird</li>
</ul>
<p><strong>Favourite TV Series</strong></p>
<ul>
<li>The Bear</li>
<li>The Day of the Jackal</li>
<li>Little Drummer Girl - not from 2024 so it doesn't really count</li>
<li>Death and Other Details</li>
<li>Silo - only season 1 though</li>
</ul>
<p><strong>Favourite Books</strong></p>
<p>I surpassed <a href="https://www.goodreads.com/user/year_in_books/2024/3921541" rel="noopener noreferrer" target="_blank">my reading goal</a> of 32 books. Here's my top picks:</p>
<ul>
<li>Hyperion by Dan Simmons (and the rest of the series)</li>
<li>The Midnight Feast by Lucy Foley</li>
<li>Observer by Robert Lanza, Nancy Kress</li>
<li>The Three Body Problem by Cixin Liu</li>
<li>Wool by Hugh Howey</li>
</ul>
<p>I read three full series this year, and all of them were excellent.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/2024-in-review</guid>
    </item>
    <item>
      <title>Using @vercel/og without Next.js</title>
      <link>https://jwww.me/blog/using-vercel-og-without-nextjs</link>
      <pubDate>Mon, 12 Feb 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I've used the helpful <a href="https://vercel.com/docs/functions/og-image-generation" rel="noopener noreferrer" target="_blank">@vercel/og</a> package before to generate OpenGraph images within Next.js projects, but since I re-wrote this blog using PHP, this was no longer possible.</p>
<p>The documentation has some hints of "using @vercel/og without Next.js" but there is no real documentation for it.</p>
<p>After some scouring, I discovered <code>unstable_createNodejsStream</code> in the TypeScript definitions</p>
<p>First, let's setup the route in the <code>vercel.json</code> config file. This step is optional, depending on your configuration. Since I have PHP endpoints in my <code>/api</code> directory as well, I need to make sure that accessing <code>/api/og</code> points to the right place.</p>
<pre><code class="language-json">// vercel.json
{
  "routes": [
    {
      "src": "/api/og",
      "dest": "/api/og.mjs"
    }
  ]
}
</code></pre>
<p>Now, let's install the dependencies.</p>
<pre><code class="language-bash">npm install @vercel/og -S
</code></pre>
<p>We're going to use the undocumented <code>unstable_createNodejsStream</code> method to generate our OG images. This is at odds with some of what the <a href="https://vercel.com/docs/functions/og-image-generation/og-image-api" rel="noopener noreferrer" target="_blank">documentation says</a>:</p>
<blockquote>
<p><code>@vercel/og</code> only supports the Edge runtime. The default Node.js runtime will not work.</p>
</blockquote>
<p>However, a <a href="https://vercel.com/docs/functions/og-image-generation#runtime-caveats" rel="noopener noreferrer" target="_blank">different page</a> of the docs say that the Node.js runtime <em>is</em> supported 🤷‍♂️</p>
<p>I'd prefer to use the documented <code>ImageResponse</code> method that runs on Vercel Edge Functions, but I wasn't able to get it working locally without Next.js. Using <code>unstable_createNodejsStream</code> allowed me to work with it both locally and in production.</p>
<p>We'll setup our function handler and some of the base config stuff first. From the <code>@vercel/og</code> documentation:</p>
<blockquote>
<p>If you're not using a framework, you must either add <code>"type": "module"</code> to your <code>package.json</code> or change your JavaScript Functions' file extensions from <code>.js</code> to <code>.mjs</code></p>
</blockquote>
<pre><code class="language-js">// api/og.mjs
import fs from 'fs'
import path from 'path'
import { unstable_createNodejsStream } from '@vercel/og'

export default async function handler(req, res) {
  try {
    const searchParams = new URL(req.url, `https://${req.headers.host}`).searchParams

    // this will look for the title query param as such ?title=&#x3C;title>
    const hasTitle = searchParams.has('title')
    const title = hasTitle ? searchParams.get('title')?.slice(0, 100) : 'Blog Title'

    // since we're using the Node.js runtime, we can read fonts using fs
    const fontBold = fs.readFileSync(path.resolve('./public/fonts/Font-Bold.ttf'))
    const fontRegular = fs.readFileSync(path.resolve('./public/fonts/Font-Regular.ttf'))

    // setup the stream. the `html` variable will be undefined so far
    const stream = await unstable_createNodejsStream(html, {
      width: 1200,
      height: 630,
      fonts: [
        {
          data: FontBold,
          name: 'Sans Bold',
          style: 'normal',
        },
        {
          data: FoldRegular,
          name: 'Sans Regular',
          style: 'normal',
        },
      ],
    })
    res.setHeader('Content-Type', 'image/png')
    res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
    res.statusCode = 200
    res.statusMessage = 'OK'
    stream.pipe(res)
  } catch (e) {
    console.error(e)
    console.log(`${e.message}`)
    return new Response('Failed to generate the image', {
      status: 500,
    })
  }
}
</code></pre>
<p>Both the <code>unstable_createNodeJsStream</code> and <code>ImageResponse</code> methods expect a JSX <code>ReactElement</code> property to be passed. However, we're not using Next.js here, and we don't have React installed in the project. Luckily, we can simply manually create the structure of the expected JSX output:</p>
<pre><code class="language-js">const html = {
  type: 'div',
  props: {
    children: [
      {
        type: 'div',
        props: {
          tw: 'pl-10 shrink flex -mt-20',
          children: [
            {
              type: 'div',
              props: {
                tw: 'text-white text-8xl',
                style: {
                  fontFamily: 'Sans Bold',
                },
                children: title,
              },
            },
          ],
        },
      },
      {
        type: 'div',
        props: {
          tw: 'absolute left-12 bottom-12 flex items-center pl-12',
          children: [
            {
              type: 'div',
              props: {
                tw: 'text-white text-4xl',
                style: {
                  fontFamily: 'Sans Bold',
                },
                children: 'Josiah Wiebe',
              },
            },
            {
              type: 'div',
              props: {
                tw: 'px-2 text-4xl text-white',
                style: {
                  fontSize: '30px',
                },
                children: '—',
              },
            },
            {
              type: 'div',
              props: {
                tw: 'text-4xl text-gray-200',
                children: '@josiahwiebe',
              },
            },
          ],
        },
      },
    ],
    tw: 'w-full h-full flex items-center relative px-12 rounded-3xl',
    style: {
      background: 'linear-gradient(230deg, #f0ecc1 0%, #f2787c 100%)',
      fontFamily: 'Vulf Sans Regular',
    },
  },
}
</code></pre>
<p>The library also supports TailwindCSS through the use of the <code>tw</code> property, so we can simply pass any desired Tailwind classes there.</p>
<p>Put that all together, then access <code>/api/og?title=Your Title Here</code> and it should return a PNG of your freshly minted on-demand OG image!</p>
<figure>
<p><img src="/img/using-vercel-og-without-nextjs/og.png" alt=""></p>
<figcaption>Our generated OG image</figcaption></figure>
<p>To add this OG image to your HTML, simply include this HTML in the <code>&#x3C;head></code> of your page:</p>
<pre><code class="language-html">&#x3C;meta name="og:image" content="/api/og?title=Using @vercel/og without Next.js" />
</code></pre>
<p>That's it!</p>
<p>If you have any questions about this implementation, hit me up on <a href="https://twitter.com/josiahwiebe" rel="noopener noreferrer" target="_blank">Twitter</a> or <a href="https://mastodon.social/@josiahwiebe" rel="noopener noreferrer" target="_blank">Mastodon</a>.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/using-vercel-og-without-nextjs</guid>
    </item>
    <item>
      <title>Goodbye, Next.js 👋</title>
      <link>https://jwww.me/blog/goodbye-nextjs</link>
      <pubDate>Sun, 11 Feb 2024 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>A quick disclaimer. This article is not intended to disparage Next.js – I work with Next.js every day, and really quite enjoy it. This is simply a study in using an outsized solution for a trivial problem.</p>
</blockquote>
<p>In 2023, I decided to re-create my personal website using Next.js. I even <a href="https://jwww.me/blog/building-a-personal-blog-with-next-13-part-1" rel="noopener noreferrer" target="_blank">wrote about it</a>. The building process was a lot of fun, and I even built a fun personal <a href="https://jwww.me/feed" rel="noopener noreferrer" target="_blank">Twitter feed alternative</a>.</p>
<p>Unfortunately, when it came to updating the blog, things didn't quite go so well. It can be hard enough to motivate yourself to write on your blog (especially if you're just getting back into it), and having a site that doesn't build correctly due to a dependency issue or a platform issues doesn't make it any easier.</p>
<p>For example, I recently tried to update the Twitter authentication (used solely by me to post statuses to the aforementioned feed page) to use Mastodon. This meant I had to update NextAuth.js, which meant I had to rewrite most of the session config for the whole site. The latest version of NextAuth.js is in beta, and I ran into a number of bugs that prevented me from proceeding.</p>
<p>This sentiment kind of echos my overall experience of using the Next.js App Router has been – feels like beta and a lot of unreconcilable issues. I'm not <a href="https://www.flightcontrol.dev/blog/nextjs-app-router-migration-the-good-bad-and-ugly" rel="noopener noreferrer" target="_blank">the only one</a>. I'm a huge fan of their Pages Router though, and will continue to use and recommend it.</p>
<p>I love JavaScript. I love Deno, Express, Remix, Bun, Vite, and jQuery. I love Next.js. I might even say I love TypeScript. Those, and so many more JavaScript frameworks, tools, and libraries have pushed the web forward, along with my career. However, it didn't make a whole lot of sense to me to use a JS based tool to build and serve my website – particularly since this site has no client facing JS.</p>
<p>In the end, I rewrote the blog in PHP, hosted on Vercel using the unsupported <a href="https://github.com/vercel-community/php" rel="noopener noreferrer" target="_blank">vercel-php runtime</a>. It's probably brittle and is not really production battle-tested, but I don't forsee running into a whole lot of scaling issues on my personal website.</p>
<p>This was a lot of fun. This made me feel like developing for the web felt like when I started two decades ago. No frameworks or metaframeworks, just a few PHP files and a PHP dev server. Sure, there was some platform quirks I had to work around (a result of my own architectural choice), and I reached for a few libraries to simplify the process, but overall, it felt a bit closer to bare metal that I have been for a while.</p>
<p>Props to the Next.js team for the work they've done to make developing full-stack applications as simple as possible (in their own flavour) in a world jam-packed with tools, practices, and tutorials.</p>
<p>This is goodbye, but not really.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/goodbye-nextjs</guid>
    </item>
    <item>
      <title>Keychron K3: The Ideal First Mechanical Keyboard</title>
      <link>https://jwww.me/blog/keychron-k3</link>
      <pubDate>Thu, 21 Dec 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>My first foray into the world of mechanical keyboards actually started with the Keychron K8. I backed their <a href="https://www.kickstarter.com/projects/keytron/keychron-k8-a-tenkeyless-wireless-mechanical-keyboard" rel="noopener noreferrer" target="_blank">Kickstarter launch</a> in July of 2020, and received it in September. Unfortunately, I was not a fan, and I sold it on Kijiji a few months later.</p>
<figure>
<p><img src="/img/keychron-k3/keychron-k8.jpg" alt="Keychron K8"></p>
<figcaption>The tenkeyless Keychron K8</figcaption></figure>
<p>During this time, Keychron had another Kickstarter launch, this time for a low-profile mechanical keyboard. Since I didn't enjoy the experience of the K8, I thought that maybe the transition to a low-profile mechanical would be easier (I was using a Magic Keyboard at the time). So I <a href="https://www.kickstarter.com/projects/keytron/keychron-k3-ultra-slim-compact-wireless-mechanical-keyboard" rel="noopener noreferrer" target="_blank">backed this project</a> as well. I ordered the K3 with the RGB backlight and white optical hot-swappable switches.</p>
<p>Since this keyboard has been out for a few years and has been essentially replaced in the product lineup by the <a href="https://www.keychron.com/products/keychron-k3-wireless-mechanical-keyboard?variant=32220198994009" rel="noopener noreferrer" target="_blank">K3 v2</a>, the <a href="https://www.keychron.com/products/keychron-k3-pro-qmk-via-wireless-custom-mechanical-keyboard?variant=40283618738265" rel="noopener noreferrer" target="_blank">K3 Pro</a>, and the <a href="https://www.keychron.com/products/keychron-k3-max-qmk-via-wireless-custom-mechanical-keyboard" rel="noopener noreferrer" target="_blank">K3 Max</a>, I'm not going to thoroughly review it. I'll consider this more of a "post-mortem".</p>
<p>Firstly, this was my <em>first real try</em> at a mechanical keyboard. I'm not counting the K8 because I sold it so quickly and didn't really give it a chance. This K1 was my daily driver for nearly three years – so it's safe to say this is what sold me on mechanical keyboards.</p>
<figure>
<p><img src="/img/keychron-k3/keychron-k3-workspace.jpg" alt=""></p>
<figcaption>One of my workspace configurations with the Keychron K3</figcaption></figure>
<h2>First Impressions</h2>
<p>This keyboard is <em>light</em>. The body is aluminum framed with a plastic body. I wouldn't say it feels overly solid, but it's nice and portable – you can easily tuck it into your backpack or bag for moving to/from the office.</p>
<p>Since this is the first version, it doesn't have adjustable feet – just taller rubber bumpers on the top edge. I prefer to use my keyboard with a bit of an incline, so this isn't ideal, but has been corrected with the K3 v2.</p>
<p>Swapping the switches and keycaps is as easy as one would expect. The MX-style stem on the switches and keycaps makes it easier to customize your K3 as well.</p>
<p>The battery is rated for 130 hours on Bluetooth or 68 hours with the backlight on, but I never really tested that capability as I use it almost exclusively plugged in.</p>
<p>Since this keyboard doesn't support QMK, I re-programmed some of the keys using <a href="https://karabiner-elements.pqrs.org/" rel="noopener noreferrer" target="_blank">Karabiner-Elements</a>. It works quite well, but is dependent on that software being running on your computer. For example, I moved the <code>delete</code> key to a non-standard location, so if I tried to use the keyboard with a different computer, that key would not work as expected.</p>
<h2>Switches</h2>
<p>This was the biggest regret about buying the optical hot-swappable version. Since this was my first keeb, I didn't really know what I was getting into. Knowing that now, I probably should have bought the Gateron fully mechanical switch version – there's simply very few optical switch options out there.</p>
<ol>
<li><a href="https://www.keychron.com/products/low-profile-keychron-optical-switch-set-87-pcs?variant=32264204517465" rel="noopener noreferrer" target="_blank">Optical White Switches</a> - a very light force linear switch.</li>
<li><a href="https://www.keychron.com/products/low-profile-keychron-optical-switch-set-87-pcs?variant=32264204484697" rel="noopener noreferrer" target="_blank">Optical Brown Switches</a> - my first tactile switch. I found them mushy and too noisy for the office.</li>
<li><a href="https://www.keychron.com/products/low-profile-keychron-optical-switch-set-87-pcs?variant=32264204550233" rel="noopener noreferrer" target="_blank">Optical Black Switches</a> - another linear switch, but requiring a bit more force. I used these for a long time.</li>
<li><a href="https://www.keychron.com/products/low-profile-keychron-optical-switch-set-87-pcs?variant=39401450405977" rel="noopener noreferrer" target="_blank">Optical Mint Switches</a> - an early bump tactile switch that requires a bit more operating force. My personal favourite – a good balance between feeling and noise level. This is what I currently have installed.</li>
</ol>
<p>^^^ Changing the switches from the optical white to the optical black, as well as replacing the default ABS keycaps with double shot</p>
<h2>Keycaps</h2>
<p>The keycaps that came with the keyboard were PBT shine-through. They were terrible – the finish started rubbing off of a handful of the keys within a few months. Thankfully, Keychron released a <a href="https://www.keychron.com/collections/keychron-low-profile-keycaps/products/low-profile-double-shot-pbt-keycap-set" rel="noopener noreferrer" target="_blank">low-profile double shot keycap set</a>, so I ordered those on my next switch order.</p>
<h2>Layout</h2>
<p>I've always used a full-sized keyboard, and largely with a number pad, so I expected this to be a difficult shift. I didn't really have time to get used to the tenkeyless format of the K8, but this is basically a more compact version of that layout. The most challenging adaptation for me was the right shift key size – it's about 2/3 the size of a full sized shift. Once I got used to that, I've decided that 75% is the perfect format for me. I'd also like to try some even smaller formats in the future.</p>
<h2>Overall</h2>
<p>There's a number of folks who have done foam or <a href="https://jakeroid.com/blog/how-did-i-upgrade-keychron-k3" rel="noopener noreferrer" target="_blank">tape mods</a> to the K3. Unfortunately, I only discovered these a few months ago, but I did end up implementing a foam mod. Basically, I just opened the case and put one of the foam pieces that shipped in the box inside the case. It made a fairly reasonable difference in the pitch of the keyboard sounds.</p>
<figure>
<p><img src="/img/keychron-k3/keychron-k3-workspace-2.jpg" alt=""></p>
<figcaption>It's somewhat of a testament to the experience of using this keyboard that it has made its way through several workspace & office changes</figcaption></figure>
<p>If you're thinking about getting a mechanical keyboard for the first time, I would definitely recommend trying the Keychron K3. It's a solid entry-level mechanical keyboard that is perfect for transitioning into (somewhat daunting) world of mechanical keyboards. With the exception of the adjustable feet, the K3 V2 seems like it's pretty much the same experience. For QMK programming ability as well as compatibility with the wider range of low-profile Gateron hot-swappable switches, I'd recommend giving the K3 Pro a try. The K3 Max also has acoustic foam which addresses some of the mods, as well as 2.4GHz connectivity for faster polling rates and reduced interference.</p>
<p>I've since replaced it with the NuPhy Air75 V2.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/keychron-k3</guid>
    </item>
    <item>
      <title>Building A Personal Blog With Next.js 13 - Part 2</title>
      <link>https://jwww.me/blog/building-a-personal-blog-with-next-13-part-2</link>
      <pubDate>Thu, 23 Feb 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1>Building The Blog</h1>
<p>This article is going to be a multi-part series. I'm going to be building a personal blog using Next.js 13, Tailwind CSS, next-mdx-remote, Planetscale, and Prisma. I'm going to be writing about the process of building the site, as well as the tools I'm using.</p>
<ol>
<li><a href="/blog/building-a-personal-blog-with-nextjs-13-part-1">Getting Started with Next.js 13</a></li>
<li><a href="/blog/building-a-personal-blog-with-nextjs-13-part-1">Building the Blog</a> (this post)</li>
<li>Creating a Personal Feed with Planetscale and Prisma</li>
<li>Adding Authentication with Next-Auth.js</li>
</ol>
<p>In my next post, I'll walk through the method I used to setup the blog and pages routes. I'll also show you how I use <code>next-mdx-remote</code> to render the blog posts and pages, combined with the power of dynamic catch-all route segments.</p>
<p>To keep up to date with my latest posts, you can follow me on <a href="https://twitter.com/josiahwiebe" rel="noopener noreferrer" target="_blank">Twitter</a> or <a href="https://mastodon.social/@josiahwiebe" rel="noopener noreferrer" target="_blank">Mastodon</a>, or subscribe to my <a href="https://jwie.be/feed.xml" rel="noopener noreferrer" target="_blank">RSS feed</a>.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/building-a-personal-blog-with-next-13-part-2</guid>
    </item>
    <item>
      <title>Using Vite to Develop Wordpress Themes</title>
      <link>https://jwww.me/blog/using-vite-to-develop-wordpress-themes</link>
      <pubDate>Tue, 07 Feb 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<h1>Using Vite to Develop Wordpress Themes</h1>
<p>At Leisure Travel Vans, we've been using a custom build system to compile our SCSS and JS files for our Wordpress themes, as well as custom plugins we've developed. We've been using this system for a few years now, and it's worked well for us. However, with modern browser support aligning well with our user demographic, I wanted to move to a more modern build system, ideally with less dependencies and a faster build time. I also wanted to be able to use modern JS features like ES6 modules, and I wanted to be able to use SCSS.</p>
<p>I'm going to walk through the steps I took to get this working, and I'll also share some of the challenges I ran into along the way.</p>
<h2>Why Vite?</h2>
<ol>
<li>Vite is fast. It's a lot faster than our previous build system, which was based on Webpack.</li>
<li>Vite is easy to use.</li>
</ol>
<h2>Getting Started</h2>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/using-vite-to-develop-wordpress-themes</guid>
    </item>
    <item>
      <title>Building A Personal Blog With Next.js 13 - Part 1</title>
      <link>https://jwww.me/blog/building-a-personal-blog-with-next-13-part-1</link>
      <pubDate>Thu, 26 Jan 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>In the last number of years, I'd completely come to rely on using Twitter as a way to "put things out into the world", as well as keep up to date with what's going on in the world.</p>
<p>The sale of Twitter had folks thinking about "owning their own platform" and I was no different. In late 2022, I decided to re-vitalize my website by building my own personal feed. Next.js 13 had just launched with the new <code>appDir</code> beta, and I thought it would be a a great opportunity to test out some new tools.</p>
<h1>Getting Started with Next.js 13</h1>
<p>This article is going to be a multi-part series. I'm going to be building a personal blog using Next.js 13, Tailwind CSS, next-mdx-remote, Planetscale, and Prisma. I'm going to be writing about the process of building the site, as well as the tools I'm using.</p>
<ol>
<li><a href="/blog/building-a-personal-blog-with-nextjs-13-part-1">Getting Started with Next.js 13</a> (this post)</li>
<li>Building the Blog</li>
<li>Creating a Personal Feed with Planetscale and Prisma</li>
<li>Adding Authentication with Next-Auth.js</li>
</ol>
<h2>The Stack</h2>
<ul>
<li><a href="https://nextjs.org/blog/next-13" rel="noopener noreferrer" target="_blank">Next.js 13</a> (using <code>appDir</code> beta)</li>
<li><a href="https://tailwindcss.com/" rel="noopener noreferrer" target="_blank">Tailwind CSS</a></li>
<li><a href="https://planetscale.com/" rel="noopener noreferrer" target="_blank">Planetscale</a></li>
<li><a href="https://www.prisma.io/" rel="noopener noreferrer" target="_blank">Prisma</a></li>
<li><a href="https://next-auth.js.org/" rel="noopener noreferrer" target="_blank">Next-Auth.js</a></li>
<li>Vercel</li>
</ul>
<h2>The Process</h2>
<h3>Setup Next.js 13</h3>
<p>My personal website was already built using Next.js, so the first step was to upgrade to Next.js 13. I followed the <a href="https://nextjs.org/docs/upgrading" rel="noopener noreferrer" target="_blank">upgrade guide</a> and everything went smoothly. They allow you to move to the new <code>appDir</code> beta incrementally by adding a <code>next.config.js</code> file to the root of your project.</p>
<pre><code class="language-js">module.exports = {
  experimental: {
    appDir: true,
  },
}
</code></pre>
<p>With that configured, I could start to move my <code>pages</code> directory into the <code>app</code> directory. I would also start converting my components to React Server Components.</p>
<p>I was previously using the <code>@next/mdx</code> package for MDX support, but I wanted some additional flexibility to be able to work with MDX in a more dynamic way. I also wanted to be able to use traditional Markdown style front-matter. I decided to use <a href="https://github.com/hashicorp/next-mdx-remote" rel="noopener noreferrer" target="_blank">next-mdx-remote</a> instead. As a result, I removed the <code>withMDX</code> config from my <code>next.config.js</code> file.</p>
<p>Since I was already using Tailwind CSS on the site, the only change I had to make to <code>tailwind.config.js</code> was to add the <code>app</code> directory to the <code>content</code> array.</p>
<pre><code class="language-js">module.exports = {
  content: [
    './app/**/*.{js,jsx,ts,tsx}', // added this line
    './pages/**/*.{js,jsx,ts,tsx}',
  ],
},
</code></pre>
<h3>Converting Pages to React Server Components</h3>
<h4>Setting Up The New <code>app</code> Directory</h4>
<p>In previous versions of Next.js, you created a file in the <code>pages</code> directory and exported a React component. With Next.js 13, you can now create a file in the <code>app</code> directory and export a React Server Component.</p>
<p>With this new approach, routes are defined using <strong>folders</strong> rather than files, and you can continue to use the <code>pages</code> directory for other things like API routes.</p>
<p>You can also create "route groups" using folder names with (parentheses). This is a great wya to organize your files in a meaninful way, without affecting the routing.</p>
<p>Here's how my <code>pages</code> directory looked before:</p>
<pre><code class="language-bash">/pages
├── _app.tsx
├── index.tsx
├── uses.mdx
├── posts
│   ├── post-name.mdx
│   └── another-post.mdx
</code></pre>
<p>And here's how it looks now:</p>
<pre><code class="language-bash">/app
├── head.tsx
├── layout.tsx
├── page.tsx
├── blog
│   ├── [...slug]
│   │   ├── head.tsx
│   │   └── page.tsx
│   ├── head.tsx
│   └── page.tsx
├── (pages)
│   ├── [...slug]
│   │   ├── head.tsx
│   │   └── page.tsx
│   └── denied
│       └── page.tsx
</code></pre>
<p>Note the <code>layout.tsx</code>, and <code>page.tsx</code> files at the top of the directory. These are the bare minimum files you need to create a React Server Component route. I've also used the <code>head.tsx</code> special file to define the <code>&#x3C;Head></code> component for each route.</p>
<p>If there is no <code>layout.tsx</code> file in a folder, it will bubble up to the root layout in the <code>app</code> directory. If there is no <code>page.tsx</code> file in a folder, it will bubble up to the root page in the <code>app</code> directory.</p>
<p>Also note the <code>(pages)</code> directory. Since this one is wrapped in parentheses, it will not be used for routing. It's just a way to organize your files.</p>
<p>I used dynamic catch-all segments with a <code>[...slug]</code> for the blog sub-routes as well as the pages sub-routes. This allows me to create a single <code>page.tsx</code> file for each route, and then use the <code>slug</code> parameter to determine which page to render.</p>
<h4>Converting Pages to React Server Components</h4>
<p>Now that the new directory structure has been configured, it's time to start converting the pages to React Server Components.</p>
<p>I started with the root layout file, since this is going to define the overall base layout for the site. For brevity, I'm not going to post the entire contents of these files, but you can find the full source code on <a href="https://github.com/josiahwiebe/jwie.be" rel="noopener noreferrer" target="_blank">GitHub</a>.</p>
<pre><code class="language-jsx">import '@styles/styles.css'
import Link from 'next/link'

const getYear = () => {
  const now = new Date()
  return now.getFullYear()
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    &#x3C;html lang='en'>
      &#x3C;body class='mx-auto my-0 p-4 max-w-5xl font-sans text-black bg-white'>
        &#x3C;header class='mb-8 pb-4 border-b'>
          &#x3C;h1>
            &#x3C;Link href='/' class='text-black no-underline'>
              Josiah Wiebe
            &#x3C;/Link>
          &#x3C;/h1>
        &#x3C;/header>
        &#x3C;main>{children}&#x3C;/main>
        &#x3C;footer class='mt-12 pt-8 w-full'>
          &#x3C;p>&#x26;copy; 2011—{getYear()} Josiah Wiebe. All rights reserved.&#x3C;/p>
        &#x3C;/footer>
      &#x3C;/body>
    &#x3C;/html>
  )
}
</code></pre>
<p>Note the <code>{children}</code> prop. This is where the content for each page will be rendered.</p>
<p>Next, I created the <code>page.tsx</code> file for the root route. This will be our index page. Just like the convention for the previous <code>pages</code> directory, this file should simply export a React component.</p>
<pre><code class="language-jsx">export default async function IndexPage() {
  return (
    &#x3C;section class='page-content grid items-center'>
      &#x3C;div class='flex flex-col gap-4'>
        &#x3C;h1 class='text-2xl font-bold'>Hello, world!&#x3C;/h1>
        &#x3C;p class='text-lg text-gray-500'>Welcome to my brand new website, built with Next.js 13.&#x3C;/p>
      &#x3C;/div>
    &#x3C;/section>
  )
}
</code></pre>
<p>The <code>head.tsx</code> file is a special file that will be used to define the <code>&#x3C;Head></code> component for the route. This is where you can define the title, meta tags, and other things that will be rendered in the <code>&#x3C;head></code> element of the page.</p>
<p>I created a custom <code>PageHead</code> component that will be used to define the <code>&#x3C;Head></code> component for each page. This component will accept a <code>title</code> prop, and will automatically append the site name to the end of the title.</p>
<pre><code class="language-jsx">interface PageHeadProps {
  params: {
    title: string,
  };
}

export default function PageHead({ params = { title: '' } }: PageHeadProps) {
  const title = `${params.title} - Josiah Wiebe`

  return (
    &#x3C;>
      &#x3C;meta charSet='utf-8' />
      &#x3C;title>{title}&#x3C;/title>
      &#x3C;meta httpEquiv='X-UA-Compatible' content='IE=edge' />
      &#x3C;meta name='viewport' content='width=device-width, initial-scale=1' />
    &#x3C;/>
  )
}
</code></pre>
<p>Now we can load that component in the <code>head.tsx</code> file for the root route.</p>
<pre><code class="language-jsx">import PageHead from '@components/page-head'

export default function Head() {
  return &#x3C;PageHead params={{ title: 'Josiah Wiebe', overrideTitle: true }} />
}
</code></pre>
<p>Since we'll want each page to have a unique title, we'll have to create a <code>head.tsx</code> special file in each route directory. We can re-use the <code>PageHead</code> component, but we'll need to pass in the title for each page. This is probably my least favourite convention with the new <code>appDir</code> directory structure, but creating a reusable component makes it easier.</p>
<p>That's all for the base layout and index page. At this point, we should have a functioning site.</p>
<p>In my next post, I'll walk through the method I used to setup the blog and pages routes. I'll also show you how I use <code>next-mdx-remote</code> to render the blog posts and pages, combined with the power of dynamic catch-all route segments.</p>
<p>To keep up to date with my latest posts, you can follow me on <a href="https://twitter.com/josiahwiebe" rel="noopener noreferrer" target="_blank">Twitter</a> or <a href="https://mastodon.social/@josiahwiebe" rel="noopener noreferrer" target="_blank">Mastodon</a>, or subscribe to my <a href="https://jwww.me/feed.xml" rel="noopener noreferrer" target="_blank">RSS feed</a>.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/building-a-personal-blog-with-next-13-part-1</guid>
    </item>
    <item>
      <title>2022 In Review</title>
      <link>https://jwww.me/blog/2022-in-review</link>
      <pubDate>Sun, 22 Jan 2023 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Now that we're a good three weeks into the new year, and all of the dust has settled from the other 2022 recaps, I figured it's as good a time as any to post my own.</p>
<p>Since I have to make up for publishing exactly zero blog posts in 2022, this is a pretty long post. <em>TL;DR: 2022 was a great year, and I'm looking forward to 2023.</em></p>
<p>In 2022, I began to settle into my new role at Leisure Travel Vans as the Marketing Manager. Travel, both for business and personally, started trickling back. Our oldest started kindergarten and our youngest turned two. We had a lot of quality family time, and lamented the departure of some of our closest friends.</p>
<h2>First Quarter</h2>
<p>Winter felt a little bit more challenging than past years, but we made the most of it and got outside almost daily, regardless of the weather. We typically like to break up our winter with a Hawaiian vacation (this was an ongoing tradition pre-pandemic), but we didn't quite feel ready for travel at that time – Olsen was still 1yo and travel was still in a bit of a state of disarray.</p>
<p><img src="/img/2022-in-review/forest-walk.jpg" alt="forest walk"></p>
<p>We spent a lot of time trekking through my parents woods, just exploring and enjoying the natural beauty of winter. There's always a few deer or foxes to be seen, and plenty of sunshine (even though it is a bit cold).</p>
<div class="image-half">
<figure>
<p><img src="/img/2022-in-review/lainey-assiniboine-skating.jpg" alt=""></p>
<figcaption>Lainey Skating on the Assiniboine River</figcaption></figure>
<figure>
<p><img src="/img/2022-in-review/river-skating.gif" alt=""></p>
<figcaption>Skating on the Boyne River in Carman</figcaption></figure>
</div>
<p>Skating on our frozen rivers and creeks is a truly Canadian pastime, and we take advantage to the fullest. Lainey loves to skate, and Olsen was content to just enjoy the ride in the stroller.</p>
<h2>Second Quarter</h2>
<p>As the spring came, along with it came a lot of hope for the year. The snow starts to melt, and with the flowing water washes away all of the challenges of winter. I was able to take my first work trip in a couple of years, where I visited one of our Travelers Clubs in the Outer Banks, North Carolina.</p>
<p>I had the opportunity to plan a new office for myself in our marketing space, as we planned to grow the team in within the year. I was able to get my hands dirty with some of the design and ideation, and I'm quite pleased with the end result.</p>
<div class="image-half">
<figure>
<p><img src="/img/2022-in-review/office-reno-planning.jpg" alt=""></p>
<figcaption>Planning out the walls</figcaption></figure>
<figure>
<p><img src="/img/2022-in-review/office-reno-wip.jpg" alt=""></p>
<figcaption>Studs and drywall going up</figcaption></figure>
</div>
<figure>
<p><img src="/img/2022-in-review/office-reno-complete.jpg" alt=""></p>
<figcaption>The (pretty much) finished product</figcaption></figure>
<p>I also (reluctantly) became a truck owner, taking delivery of the model I had ordered the previous December. We were planning on taking a long road trip in the summer and needed something more substantial than our XC90 to pull the camper.</p>
<p><img src="/img/2022-in-review/new-truck.jpg" alt=""></p>
<p>Spring felt like it settled in nicely, and we took full advantage of the outdoors whenever we could. Olsen was still waking up very early, so I started taking advantage of that time and took him golfing with a friend and their son.</p>
<div class="image-half">
<figure>
<p><img src="/img/2022-in-review/stroller-golfing.jpg" alt=""></p>
<figcaption>^^^</figcaption></figure>
![](/img/2022-in-review/morning-golfing.jpg)
<figure>
</div>
<p><img src="/img/2022-in-review/backyard-work.jpg" alt=""></p>
<p>We took a family trip down to Minneapolis for the May long weekend – our first family trip for the year. It was a great little getaway, and the hotel pool was a hit with the kids. Minneapolis is a fantastic city, I'd liken it to a Portland of the Midwest.</p>
<h3>Third Quarter</h3>
<p>Settling into the full swing of summer. The kids (meaning me) got Katie a paddleboard for Mother's Day, so we spent a lot of time out on the water with it. I continued my 6am golf dates with Olsen.</p>
<p>We planned and booked a camping trip out to Vancouver for the end of summer, so I began eagerly working on some upgrades to our camper. We have a 2021 Winnebago Micro Minnie 2100BH. Most of the sites we booked were unserviced, so I wanted to do some power upgrades to ensure we had enough capacity to handle our off-grid sites. You can read more about those upgrades in the <a href="/logbook/2021-winnebago-micro-minnie-2100bh">logbook entry</a>.</p>
<p><img src="/img/2022-in-review/winnebago-electrical-upgrade.jpg" alt=""></p>
<p>I spent some time on my bicycle throughout the summer, but not as much as previous years. I tried to balance that deficit out with some running. Even still, I made the most of my time in the saddle and got a few solid rides in.</p>
<p><img src="/img/2022-in-review/bike-ride.jpg" alt=""></p>
<p>Top purchase for 2022 was probably our Arctic Spas Summit hot tub. It really helps makes our backyard into even more of an oasis. We enjoy a soak in it every evening, bring the Kindles and read a few chapters when the kids are in bed.</p>
<p><img src="/img/2022-in-review/hot-tub.jpg" alt=""></p>
<p>Summer is also when things get into full swing at Leisure Travel Vans, as we have to take advantage of the limited good weather that we get in southern Manitoba. Our video team headed out on our annual product video shoots, which tend to take us all around Manitoba.</p>
<p>We also launched two all-new products this year, and we made a plan to head to Kelowna to shoot the launch videos.</p>
<div class="image-half">
<figcaption>![](/img/2022-in-review/kelowna-shoot-rig.jpg)</figcaption></figure>
^^^ Our [car-to-car rig](https://tilta.com/shop/hydra-alien-car-mounting-system-pro-kit/)
<figure>
<p><img src="/img/2022-in-review/kelowna-shoot-temperature.jpg" alt=""></p>
<figcaption>Temperatures in the coach climbed over 40C</figcaption></figure>
</div>
<figure>
<p><img src="/img/2022-in-review/kelowna-shoot-selfie.jpg" alt=""></p>
<figcaption>The Film Boyz (self-titled)</figcaption></figure>
<figure>
<p><img src="/img/2022-in-review/kelowna-shoot-drone.jpg" alt=""></p>
<figcaption>One of the drone shots from the shoot</figcaption></figure>
<p>In August, we headed out on a three-week family camping trip, intending to end up in Squamish, BC. Our plans changed a little bit due to the extreme heat of the Okanagan Valley combined with a lack of serviced campgrounds, but I think the trip was better for it. We ended up spending a bit less time in Kelowna, and then made our way directly to Vancouver Island, to reconnect with the friends that had just moved away. I'll detail the trip in a <a href="/logbook">logbook</a> entry soon.</p>
<figure>
<p><img src="/img/2022-in-review/squamish-trip-departure.jpg" alt=""></p>
<figcaption>Preparing to depart on our 3-week trip</figcaption></figure>
<p>Highlight of the trip was probably the <a href="https://www.strava.com/activities/7705952028" rel="noopener noreferrer" target="_blank">Triple Ferry Ride</a>, starting in Victoria, BC. It's a 120 kilometre bike ride that takes you on three different ferries, from Vancouver Island to Salt Spring Island, and then back to Vancouver Island. A full day affair and an absolute blast — wine tasting, coffee stops and all.</p>
<div class="image-half">
<figure>
<p><img src="/img/2022-in-review/triple-ferry-1.jpg" alt=""></p>
<figcaption>Crossing Salt Spring Island</figcaption></figure>
<figure>
<p><img src="/img/2022-in-review/triple-ferry-2.gif" alt=""></p>
<figcaption>:::</figcaption></figure>
</div>image-half
<figure>
<p><img src="/img/2022-in-review/triple-ferry-4.jpg" alt=""></p>
<figcaption>Waiting for the ferry</figcaption></figure>
<figure>
<p><img src="/img/2022-in-review/triple-ferry-3.gif" alt=""></p>
<figcaption>Wine tasting at [Enrico](https://www.enricowinery.com/)</figcaption></figure>
<div>
<p>We returned home just in time for Lainey to start kindergarten, and then I headed off with the team to Hershey, Pennsylvania to launch our brand new Murphy Bed Lounge floorplan.</p>
<figure>
<p><img src="/img/2022-in-review/hershey-rv-show.jpg" alt="Hershey RV Show"></p>
<figcaption>Preparing for the [livestream](https://www.youtube.com/watch?v=cwJEpB2wBG0) at the Hershey RV Show</figcaption></figure>
<h2>Third Quarter</h2>
<p>Much like the rest of the year, we spent a lot of autumn getting outside and enjoying the changing of the seasons. Fall is short lived here, but we make the most of it.</p>
<p>Since Lainey started school, we went through (what sounds like it's the usual) bouts of sickness for the first few months of the year, and finally all got COVID-19 for the first time. Thankfully it was very mild for the kids, and pretty non-consequential for the (fully vaccinated) adults.</p>
<p>We celebrated both Lainey and Olsen's birthdays in October, and had a rare snow-free Halloween (also Olsen's birthday).</p>
<figure>
<p><img src="/img/2022-in-review/no-name.jpg" alt=""></p>
<figcaption>I made [no name](https://www.noname.ca/en_ca/about/) costumes for Halloween.</figcaption></figure>
<h2>Fourth Quarter</h2>
<p>The last part of the year was a bit of a blur, as we settled into new routines with school, and I had the opportunity for a couple more work trips.</p>
<p>We spent a lot of time with family and friends, eating lots of good food and <a href="https://www.vivino.com/users/jwiebe" rel="noopener noreferrer" target="_blank">drinking good wine</a>. I made a lot of charcuterie boards, both at home and at work.</p>
</div>image-half
<figure>
<p><img src="/img/2022-in-review/charcuterie-2.jpg" alt=""></p>
<figcaption>We hosted an office holiday party with a charcuterie table</figcaption></figure>
<figure>
<p><img src="/img/2022-in-review/charcuterie-1.jpg" alt=""></p>
<figcaption>Another evening, another spread</figcaption></figure>
<div>
<p>After a long hiatus from a lifetime growing up Nordic Skiing, I finally got back on the skis this year. Katie and I both purchased a new set of skis which we've been really happy with. They are the <a href="https://www.salomon.com/en-int/shop-int/product/escape-6-skin-and-prolink-access.html#color=67766" rel="noopener noreferrer" target="_blank">Saloman Escape 6 Skin</a> – I've been really impressed with the skin technology.</p>
</div>image-half
<figure>
<p><img src="/img/2022-in-review/skiing-1.jpg" alt="Skiing through the snowy Burwalde woods"></p>
<figcaption>Skiing through the snowy Burwalde woods</figcaption></figure>
<figure>
<p><img src="/img/2022-in-review/skiing-2.jpg" alt="Nordic skiing with the family"></p>
<figcaption>Lainey & Olsen came to love skiing, so we often make it a whole family affair</figcaption></figure>
:::
<figure>
<p><img src="/img/2022-in-review/skiing-3.jpg" alt="Josiah and Katie out on the ski trails"></p>
<figcaption>We skiied every day during the week of Christmas</figcaption></figure>
<p>If you made it this far, thanks for reading. I hope you enjoyed the review, and I hope you have a great 2023. I'm looking forward to breathing new life into my personal site, and I hope you'll join me.</p>
<p>You can also follow me on <a href="https://twitter.com/josiahwiebe" rel="noopener noreferrer" target="_blank">Twitter</a>/<a href="https://mastodon.social/@josiahwiebe" rel="noopener noreferrer" target="_blank">Mastodon</a> and <a href="https://instagram.com/josiahwiebe" rel="noopener noreferrer" target="_blank">Instagram</a>.</p>
<p><img src="/img/2022-in-review/cheers.gif" alt="Cheers to 2023"></p>
<h2>2022: In Statistics</h2>
<h3>Travel:</h3>
<ul>
<li>26,014 kilometres flown on 22 flights</li>
<li>6,500+ kilometres driven with the camper, across three provinces</li>
<li>Stayed at 13 different campsites</li>
<li>Commuted over 300 kilometres via e-scooter (I have the Apollo Phantom v2)</li>
</ul>
<h3>Activity:</h3>
<ul>
<li>Cycled 1,201 kilometres</li>
<li>Ran 91 kilometres</li>
<li>Walked 2,708,130 steps (about 2,400 kilometres)</li>
</ul>
<h3>Media:</h3>
<ul>
<li><a href="https://www.goodreads.com/user/year_in_books/2022/3921541" rel="noopener noreferrer" target="_blank">Read 24 books</a></li>
<li>Listened to as little podcasts as possible</li>
<li>Wrote 0 blog posts (but that changes now)</li>
<li><a href="https://www.last.fm/user/josiahbenjamin/listening-report/year" rel="noopener noreferrer" target="_blank">Listened to 7,481 songs</a></li>
</ul>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/blog/2022-in-review</guid>
    </item>
    <item>
      <title>Launching the Murphy Bed Lounge</title>
      <link>https://jwww.me/logbook/murphy-bed-lounge-launch</link>
      <pubDate>Tue, 13 Sep 2022 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>In the spring of 2022, the first prototypes of our brand new <a href="https://leisurevans.com/unity/features/murphy-bed-lounge" rel="noopener noreferrer" target="_blank">Unity Murphy Bed Lounge</a> and <a href="https://leisurevans.com/wonder/features/murphy-bed-lounge/" rel="noopener noreferrer" target="_blank">Wonder Murphy Bed Lounge</a> were completed at our factory in Winkler, Canada. This was a milestone for us – the first time launching a new floorplan on both our <a href="https://leisurevans.com/unity/" rel="noopener noreferrer" target="_blank">Unity</a> &#x26; <a href="https://leisurevans.com/wonder/" rel="noopener noreferrer" target="_blank">Wonder</a> product lines simultaneously.</p>
<p>We wanted to do something special to launch this new twin floorplan, so we came up with a plan to create some video content and host a live launch at the Hershey RV Show in Hershey, PA.</p>
<p>In summer, we shipped the coaches to Fraserway RV, one of our selling dealers. Our team flew to the Okanagan to film a product walkthrough video on each model, as well as capture some scenic aerial and car-to-car driving shots.</p>
<p>We like to keep our gear loadout lean and portable, and we've fine-tuned our setup pretty well over the years.</p>
<p>For our walkthroughs, we have two each of the following:</p>
<ul>
<li><a href="https://www.canon.ca/en/product?name=EOS_R5" rel="noopener noreferrer" target="_blank">Canon R5</a></li>
<li>Canon EF 14mm f/2.8L II - the widest rectilinear lens in the Canon lineup</li>
<li>Canon RF 50mm f/1.2L USM - for the tight angle</li>
<li><a href="https://www.dji.com/ca/rs-2" rel="noopener noreferrer" target="_blank">DJI Ronin-S2</a></li>
</ul>
<p>For this shoot, we recorded audio onto two independent on-talent recorders. Our audio setup has changed since this shoot – we're now using:</p>
<ul>
<li><a href="https://rode.com/en/microphones/wireless/wirelessgo" rel="noopener noreferrer" target="_blank">Rode Wireless Go</a></li>
<li>Zoom F2-BT Field Recorder - for on-talent recording</li>
<li>Zoom F3 Field Recorder - connected to the Rode system for remote recording with live monitoring</li>
</ul>
<p>For aerial and car-to-car, we use the following:</p>
<ul>
<li><a href="https://www.dji.com/ca/mavic-2" rel="noopener noreferrer" target="_blank">DJI Mavic 2 Pro</a></li>
<li><a href="https://tilta.com/shop/hydra-alien-car-mounting-system-pro-kit/" rel="noopener noreferrer" target="_blank">Tilta Hydra Alien Car Mount</a></li>
</ul>
<p>The beauty of this setup is that it all fits into two Pelican cases, making it super easy to transport, and we can get it all set up in under 20 minutes.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/logbook/murphy-bed-lounge-launch</guid>
    </item>
    <item>
      <title>Summer 2022 - BC Camping Trip</title>
      <link>https://jwww.me/logbook/summer-2022-bc-camping-trip</link>
      <pubDate>Fri, 19 Aug 2022 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>TBD</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/logbook/summer-2022-bc-camping-trip</guid>
    </item>
    <item>
      <title>Winnebago Micro Minnie 2100BH - Changelog</title>
      <link>https://jwww.me/logbook/2021-winnebago-micro-minnie-2100bh</link>
      <pubDate>Fri, 14 May 2021 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>In the spirit of software development, I thought I'd keep a record of the changes and upgrades I make to our 2022 Winnebago Micro Minnie 2100BH – a sort of "release notes" if you will.</p>
<h3>14 May 2021</h3>
<p>Took delivery.</p>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/winnie-delivery-cb9476e6.jpg" alt="New trailer setup on our driveway"></p>
<h3>17 May 2022</h3>
<p>Received our Westinghouse iGen4500 Inverter Generator. This is a 4500W inverter generator that runs on gasoline. It's a bit larger than I would have liked, but it's the only one that can run the air conditioner. I have yet to discover that later upgrades will allow me to run the A/C off of a smaller generator.</p>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/generator-35cbf31b.jpg" alt="Generator in rear storage compartment"></p>
<p>** Test fitting the generator in the rear storage compartment **</p>
<h3>26 May 2021</h3>
<p>Installed <a href="https://a.co/d/7bFckYf" rel="noopener noreferrer" target="_blank">this rear camera</a> on the back of the trailer. Easily tied into the running lights of the trailer for power.</p>
<h3>27 May 2021</h3>
<p>Our first trip with the camper. We took it to Falcon Lake, Manitoba for a long weekend.</p>
<div class="image-half">
<figure>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/winnie-first-trip-b1a28cdf.jpg" alt="Prepared to depart for our inaugural trip"></p>
<figcaption>Ready for our maiden voyage!</figcaption></figure>
<figure>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/winnie-falcon-7f84a6f5.jpg" alt="Camper at Falcon Lake Beach Campground"></p>
<figcaption>Setup at [Falcon Lake Beach Campground](https://www.travelmanitoba.com/directory/falcon-beach-campground/)</figcaption></figure>
</div>
<h3>3 Feb 2022 - Memory Foam Mattress</h3>
<p>Replaced the mattress with <a href="https://a.co/d/3J3dP5c" rel="noopener noreferrer" target="_blank">this one</a>.</p>
<h3>17 April 2022 - Lithium Battery</h3>
<p>Purchased a <a href="https://ca.renogy.com/200ah-12-volt-lithium-iron-phosphate-battery-w-bluetooth/" rel="noopener noreferrer" target="_blank">Renogy 200aH Smart Lithium battery</a>, with no real plan for how to implement it yet.</p>
<h3>10 May 2022 - 2000W Inverter</h3>
<p>Acquired a <a href="https://xantrex.com/products/inverter-chargers/freedomxc/" rel="noopener noreferrer" target="_blank">Xantrex Freedom XC 2000W Pure Sine Wave Inverter</a>. Still planning on the right battery configuration as well as how to wire it up.</p>
<h3>15 June 2022 - Inverter &#x26; Battery Install</h3>
<p>Dry mounted the inverter and battery in the rear storage compartment. With the help of my very knowledgeable coworker Brian, we sketched out a few scenarios and landed on this one.</p>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/winnie-inverter-plan-81f1104e.jpg" alt="A sketch showing the plan for installing the inverter"></p>
<p>One of the challenges we had to overcome was the assumption by Winnebago that the battery would be mounted on the trailer tongue (standard on most travel trailers). We wanted to limit the interruption of the existing wiring as much as possible.</p>
<p>Looking at the <a href="https://www.winnieowners.com/forums/" rel="noopener noreferrer" target="_blank">Winnie Owners forums</a>, it seems that many folks decided to install their equipment in the pass through storage under the primary bed at the front. So we had two options:</p>
<ol>
<li>Pass-through storage</li>
</ol>
<ul>
<li>Pro: closer to 12V distribution on tongue</li>
<li>Pro: shorter DC cable runs (less voltage drop)</li>
<li>Con: fan noise from inverter directly below primary bed</li>
<li>Con: takes up our most valuable storage area</li>
<li>Con: un-conditioned space</li>
</ul>
<ol start="2">
<li>Rear storage</li>
</ol>
<ul>
<li>Pro: closer to 120V shore power input</li>
<li>Pro: inverter fan far from primary bed</li>
<li>Pro: conditioned space - can use in sub-zero temperatures</li>
<li>Con: long 12V DC cable run</li>
</ul>
<p>In the end, we settled on the rear storage area. Using 2 AWG cable over a 13 metre cable run only results in a 1% voltage drop at 10 amps, well within acceptable range. We ended up using 4 AWG as there was no 2 AWG available, but even that resulted in only 1.65% voltage drop.</p>
<figure>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/winnie-shore-power-d9e27f0c.jpg" alt="Showing how the shore power enters the RV"></p>
<figcaption>Shore power enters the trailer through the rear storage compartment.</figcaption></figure>
<p>The Freedom XC has a pass-through mode, which will simply allow it to operate as a (relatively) neutral part of the circuit when plugged into shore power once the battery is fully charged. To accomplish this, we split the shore power wire where it entered the trailer and installed a junction box. From this box, we connected the shore power wire to the inverter IN, then ran a cable from the inverter OUT which connected inside the junction box to the wire we originally split (which continues on to our distribution and breaker panel).</p>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/inverter-final-install-4ab4f8f0.jpg" alt="Final installation of the inverter and lithium battery"></p>
<p>Here's what the final installation looks like. There's enough space to easily accommodate an additional 200aH battery beside this one, which could prove useful in the future.</p>
<h3>13 July 2022- MicroAir Easy Start</h3>
<p>Due to the nature of the way I installed the inverter, it allowed the entire trailer to run off of inverter power. That meant that every component that normally relied on 120V shore power would be able to run off of battery power, via the inverter.</p>
<p>One component I was really hoping to test was the air conditioner. Our summer had already been extremely hot, and we knew we would need the A/C as we headed west to the Okanagan. Theoretically, I should be able to run this air conditioner via the inverter (it's a GE 13k BTU ARC13AACBL), but I wanted to test it.</p>
<p>I acquired a MicroAir Easy Start (soft start capacitor) and set about installing it. Specifically, the <a href="https://www.microair.net/collections/easystart-soft-starters/products/easystart-364-3-ton-single-phase-soft-starter-for-air-conditioners?variant=29181121483" rel="noopener noreferrer" target="_blank">ASY-364-X36-BLUE</a> model.</p>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/soft-start-install-83baa3b6.jpg" alt="Installing the Easy Start under the air conditioner shroud"></p>
<p>After a few tests, I was able to successfully run the air conditioner off of battery power! It ran at about 5.5A, so that only gave me about 2 hours of run time, but that didn't matter. I simply wanted to be able to do it, and have the option available to me in a pinch.</p>
<p>:::image-half</p>
<figure>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/soft-start-status-ced628a9.png" alt="Screenshot of Easy Start app status"></p>
<figcaption>The Easy Start app shows the current voltage and starting voltage of the A/C</figcaption></figure>
<figure>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/battery-status-93125523.png" alt="Screenshot of Renogy app"></p>
<figcaption>The Renogy app shows the remaining battery capacity under load</figcaption></figure>
<h3>10 August 2022 - IRVWPC</h3>
<p>After seeing numerous installations of them at the LTV Quebec Rally in June, I was inspired to install my own <a href="https://www.irvwpc.com/" rel="noopener noreferrer" target="_blank">IRVWPC</a>. This allows my water pump to run virtually silently, without the usual knocking and banging you get from an RV pump.</p>
<p>Made in Canada, this control board sits between the 12V DC power supply and your water pump, and slowly ramps the supplied voltage up and down as a means to control the speed of the pump.</p>
<p><img src="/img/2021-winnebago-micro-minnie-2100bh/irvwpc-install-55b208a2.jpg" alt="The IRVWPC installed"></p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/logbook/2021-winnebago-micro-minnie-2100bh</guid>
    </item>
    <item>
      <title>Big Island, Hawaii</title>
      <link>https://jwww.me/logbook/big-island-hawaii</link>
      <pubDate>Mon, 23 Jan 2017 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><em>Originally published January 23, 2017. Information may be out of date.</em></p>
<p>The Big Island of Hawaii has always been a very special place for us. We first visited in 2012 and returned every single year after that, up until the COVID-19 pandemic.</p>
<p>This document is intended to be living document, documenting our favourite places and things to do on the Big Island – designed with the intention of being shared with friends and family.</p>
<h1>Contents</h1>
<ul>
<li><a href="#background">Background</a></li>
<li><a href="#regions">Regions</a></li>
<li><a href="#activities">Activities</a></li>
<li><a href="#hikes--activities">Hikes &#x26; Activities</a></li>
<li><a href="#beaches">Beaches</a></li>
<li><a href="#snorkelling--paddleboarding">Snorkelling &#x26; Paddleboarding</a></li>
<li><a href="#happy-hour">Happy Hour</a></li>
<li><a href="#restaurants--groceries">Restaurants &#x26; Groceries</a></li>
</ul>
<h1>Background</h1>
<ul>
<li>The primary airport (KOA) is located on the Kohala Coast, about 15 kilometres (30 minutes) north of Kona. A few options:
<ul>
<li>Rent a car - this is pretty much required on the Big Island</li>
<li>Taxi</li>
<li>Hitchhiking (mixed results, but can be fun)</li>
</ul>
</li>
<li>Trespassing &#x26; private property. The Big Island is a very special place, but keep in mind that you are a visitor on native Hawaiian land. Respect the land and the people.</li>
<li>Kona coffee isn't all that good 😅 People think that it is, but they are wrong. Still fun to go on a free coffee tour though.</li>
<li>The Big Island has eight of our planet's 13 different climate zones. Be prepared for all of them.</li>
</ul>
<figure>
<p><img src="/img/big-island-hawaii/climate-map-de77b5bd.jpg" alt=""></p>
<figcaption>Climate zones on the Big Island</figcaption></figure>
<h1>Regions</h1>
<p>It's simplest to divide the Big Island up into a few distinct regions:</p>
<ul>
<li><strong>Kona</strong>
<ul>
<li>the most popular region, known for its beaches, coffee, and the town of Kona.</li>
<li>the Kailua-Kona town area extends a fair ways down the western coast, and includes Kailua-Kona proper, Kealakekua Bay, Captain Cook, etc</li>
</ul>
</li>
<li><strong>Kohala</strong>
<ul>
<li>the northernmost region, known for its beaches, golf courses, and the town of Waimea.</li>
<li>includes the <strong>Waikoloa Beach</strong> resort area (not to be confused with Waikoloa Village), the Mauna Lani Resort, and the <strong>Hapuna Beach</strong> State Recreation Area.</li>
<li>home to some of the best beaches and sunny weather on the island.</li>
<li>the town of Waimea is also in northeast Kohala - Hawaii's "cowboy" town.</li>
</ul>
</li>
<li><strong>Hamakua</strong>
<ul>
<li>the easternmost region, known for its waterfalls, rainforests, and awe-inspiring views.</li>
<li>rolling green hills as well as the summit of Mauna Kea, the tallest mountain in the world.</li>
<li>massive Jurassic Park-like cliffs and waterfalls on the north side, including Waipio Valley.</li>
</ul>
</li>
<li><strong>Hilo</strong>
<ul>
<li>the second most popular region, known for its rainforests, waterfalls, and the town of Hilo.</li>
<li>the town of Hilo is the second largest city on the island.</li>
<li>this is the wettest, or "windward" side of the island, with the most rainfall.</li>
<li>waterfalls (including Akaka Falls) and lush rainforests.</li>
</ul>
</li>
<li><strong>Puna</strong>
<ul>
<li>the eastern side of the island, between Hilo and the active lava flows in the National Park.</li>
<li>the Puna side of the island is quite rural, with a lot of farmland and the town of Pahoa.</li>
<li>a number of black sand beach, including (clothing optional) <strong>Kehena Beach</strong>.</li>
</ul>
</li>
<li><strong>Ka'u</strong>
<ul>
<li>the southernmost region, probably the least popular region.</li>
<li>the Ka'u side of the island is the most remote, with a lot of farmland and the town of Ka'u.</li>
<li>home to <strong>South Point</strong>, the southernmost point in the United States.</li>
<li>also home to <strong>Papakolea Beach</strong>, one of four green sand beaches in the world.</li>
</ul>
</li>
</ul>
<h1>Activities</h1>
<h2>Hikes &#x26; Activities</h2>
<p><a href="https://www.bigislandhiking.com/" rel="noopener noreferrer" target="_blank">Big Island Hiking</a> is a decent resource for hiking on the Big Island.</p>
<h4><a href="https://www.google.ca/maps/place/Hawai%CA%BBi+Volcanoes+National+Park/@19.3091251,-155.5758142,11z/data=!3m1!4b1!4m5!3m4!1s0x795161b6c0b9c9b1:0x3e5b7b263c84ae26!8m2!3d19.4193697!4d-155.2884969" rel="noopener noreferrer" target="_blank">Volcanoes National Park</a></h4>
<ul>
<li>has a number of interesting day hikes, lava fields and lava tubes included</li>
<li>spend the day here just exploring if you need a break from the beach</li>
</ul>
<h4><a href="https://maps.app.goo.gl/Ph3v5QpJCFSbgKas7" rel="noopener noreferrer" target="_blank">Waimea Reservoir hike</a></h4>
<ul>
<li>One of our favourite hikes is in the Waimea area called the Waimea Reservoir hike. It starts <a href="https://maps.app.goo.gl/Ph3v5QpJCFSbgKas7" rel="noopener noreferrer" target="_blank">here</a></li>
<li>You'll have to climb over a gate at the beginning with some warning signs</li>
<li>There are these irrigation tunnels and chutes that you can walk through, as well as slide down</li>
<li><a href="https://www.gaiagps.com/public/AJdD64SMoHWJgIq2kGkSuXXS" rel="noopener noreferrer" target="_blank">GPS route</a> - 8km out and back, 105m elevation gain</li>
</ul>
<div class="image-half">
<figure>
<p><img src="/img/big-island-hawaii/waimea-reservoir-6d3380b6.jpg" alt=""></p>
<figcaption>Waimea Reservoir hike</figcaption></figure>
<figure>
<p><img src="/img/big-island-hawaii/waimea-chutes-20a166d4.jpg" alt=""></p>
<figcaption>The chutes at the end of the hike</figcaption></figure>
</div>
<h4><a href="https://www.google.ca/maps/place/Polol%C5%AB+Valley/@20.2069494,-155.7357998,17z/data=!3m1!4b1!4m5!3m4!1s0x79537bbce52f757d:0xd0c91adb141d0e8!8m2!3d20.2069444!4d-155.7336111" rel="noopener noreferrer" target="_blank">Pololu Valley</a></h4>
<ul>
<li>lush valley</li>
<li>sometimes you can see whales from the shore here</li>
<li>you can keep going on and on – there's no real "destination"</li>
<li><a href="https://www.gaiagps.com/public/XMnRO3oER2jiU1czc7Boedg0" rel="noopener noreferrer" target="_blank">GPS route</a> - 5km+ out and back, 275m elevation gain</li>
</ul>
<div class="image-half">
<figure>
<p><img src="/img/big-island-hawaii/pololu-2-748fd358.jpg" alt=""></p>
<figcaption>Pololu Valley</figcaption></figure>
<figure>
<p><img src="/img/big-island-hawaii/top-of-pololu-64b953ca.jpg" alt=""></p>
<figcaption>Top of Pololu Valley</figcaption></figure>
</div>
<h4><a href="https://www.google.ca/maps/place/Waipio+Valley/@20.1114675,-155.5972385,17z/data=!3m1!4b1!4m5!3m4!1s0x7953768b433a794f:0xbf30be6da781af6!8m2!3d20.1114625!4d-155.5950498" rel="noopener noreferrer" target="_blank">Waipi'o Valley</a></h4>
<ul>
<li>this is an amazing valley, but you shouldn't really drive down – it's too steep for most cars.</li>
<li>the view even from the top is impressive</li>
<li>you can try hitching a ride down from some locals or walking down the road. You'll feel like a champ if you walk back up.</li>
<li>there used to be a hike to Hi’ilawe Falls at the back of the valley, but it crossed through private property and is no longer accessible. Do not attempt it.</li>
</ul>
<h4><a href="https://goo.gl/maps/W45NTRzfuQqfPNoPA" rel="noopener noreferrer" target="_blank">Kiholo Bay</a></h4>
<ul>
<li>pretty much guaranteed to see turtles here</li>
<li>two options
<ol>
<li>Park by the highway and hike down on gravel</li>
<li>Park near the sea and hike along soft sand</li>
</ol>
</li>
<li>we generally choose to hike down from the highway, <a href="https://goo.gl/maps/TCrFFx4HNmGnJvoA9" rel="noopener noreferrer" target="_blank">parking right here</a></li>
<li><a href="https://www.gaiagps.com/public/ypRjeWFj4j8TFAJr2pVFBl77" rel="noopener noreferrer" target="_blank">GPS route</a> - 4km one way, nominal elevation gain</li>
</ul>
<figure>
<p><img src="/img/big-island-hawaii/kiholo-bay-53a1556f.jpg" alt=""></p>
<figcaption>Getting to Kiholo Bay</figcaption></figure>
<div class="image-half">
<figure>
<p><img src="/img/big-island-hawaii/hike-to-kiholo-ff3f3014.jpg" alt=""></p>
<figcaption>Hiking to Kiholo Bay</figcaption></figure>
<figure>
<p><img src="/img/big-island-hawaii/turtle-kiholo-0bccd9bc.jpg" alt=""></p>
<figcaption>Turtle at Kiholo Bay</figcaption></figure>
</div>
<h4>Mauna Kea Summit</h4>
<ul>
<li>it's a long drive, but the views are absolutely incredible. 4WD is recommended, and your car might struggle with the altitude.</li>
<li>go at sunset for the best views.</li>
</ul>
<figure>
<p><img src="/img/big-island-hawaii/mauna-kea-clouds-e1bc8a81.jpg" alt=""></p>
<figcaption>Views from Mauna Kea at sunset. We couldn't make it to the top this time as the car struggled with the altitude.</figcaption></figure>
<div>
<h4><a href="https://maps.app.goo.gl/pfdkSU72EATG7x1MA" rel="noopener noreferrer" target="_blank">End of the World Cliff Jumping</a></h4>
<ul>
<li>a popular spot for cliff jumping at the end of Ali'i drive</li>
</ul>
<h4><a href="https://maps.app.goo.gl/hYaDUzgEX8oD39JBA" rel="noopener noreferrer" target="_blank">South Point</a></h4>
<ul>
<li>the southernmost point in the United States</li>
<li>some folks do cliff jumping here</li>
</ul>
<h2>Beaches</h2>
<p>As you would expect, the Big Island has a lot of beaches. It also features some unique types of beaches, such as green and black sand beaches.</p>
<h3>White Sand Beaches</h3>
<h4>Kekaha Kai State Park</h4>
<p>There are three excellent beaches in this state park. Some are accessible easily, others require a vehicle with a decent amount of ground clearance. You can also hike to the beaches from the parking lot.</p>
<p>I'd recommend hitting up both Mahaiula Beach and Makalawena Beach back to back, as you can park at Mahaiula and walk to Makalawena.</p>
<figure>
<p><img src="/img/big-island-hawaii/kekaha-kai-5a06b9d2.jpg" alt=""></p>
<figcaption>Getting to the beaches in Kekaha Kai State Park</figcaption></figure>
<h4><a href="https://maps.app.goo.gl/yFuQRnSxJ6rMKkxY8" rel="noopener noreferrer" target="_blank">Mahai'ula Beach</a></h4>
<ul>
<li>it's a bit of a rough ride getting here, but you can do it with a normal rental car</li>
</ul>
<figure>
<p><img src="/img/big-island-hawaii/mahaiula-beach-6f32aa49.jpg" alt=""></p>
<figcaption>Mahai'ula Beach</figcaption></figure>
<h4><a href="https://maps.app.goo.gl/BP1vP4Qpe5r1z6DH9" rel="noopener noreferrer" target="_blank">Makalawena Beach</a></h4>
<ul>
<li>this one requires a high clearance vehicle, or a bit of a walk.</li>
<li>since it's a bit less accessible, it can be less crowded</li>
</ul>
<h4><a href="#">Maniniowali Beach</a> (also known as Kua Bay or Mile 88)</h4>
<ul>
<li>one of the more popular beaches, as it's very easy to access.</li>
<li>super fun to play in the waves here</li>
</ul>
<h4><a href="https://maps.app.goo.gl/MTyuJ9JjTC3pAwRP8" rel="noopener noreferrer" target="_blank">Kikaua Beach</a></h4>
<ul>
<li>this is a public beach, but you have to access it via private property.</li>
<li>it's generally very quiet as the parking is so limited. It's also very calm as it's protected by the reef and breakwater.</li>
<li>first, get a parking pass from the guard shack at the gate (see map)</li>
<li>if the parking lot is full, you'll have to come back later</li>
</ul>
<figure>
<p><img src="/img/big-island-hawaii/kikaua-beach-aa3bdf42.jpg" alt=""></p>
<figcaption>How to access Kikaua Beach</figcaption></figure>
<h4><a href="https://maps.app.goo.gl/KfpLYdhuWWuLN29F8" rel="noopener noreferrer" target="_blank">ʻAnaehoʻomalu Bay</a></h4>
<ul>
<li>this is a very small beach, just a walk south from the Lava Lava Beach Club in Waikoloa</li>
<li>not labelled as a beach on most maps, so it's generally quite empty</li>
<li>easiest to get to at low tide</li>
</ul>
<figure>
<p><img src="https://jwie.be/img/hawaii/anaehoomalu-bay.jpg" alt=""></p>
<figcaption>ʻAnaehoʻomalu Bay</figcaption></figure>
<h3>Green Sand Beach</h3>
<h4><a href="https://maps.app.goo.gl/91qgW1hofd8brhNG7" rel="noopener noreferrer" target="_blank">Papakolea Beach</a></h4>
<ul>
<li>one of four green sand beaches in the world</li>
<li>you can access it by walking from the <a href="https://maps.app.goo.gl/qjYKbQb7vUsvpLaz9" rel="noopener noreferrer" target="_blank">parking lot</a> or paying for a ride from some locals</li>
</ul>
</div>image-half
<figure>
<p><img src="/img/big-island-hawaii/green-sand-2cb4893f.jpg" alt=""></p>
<figcaption>Walking to Papakolea Beach</figcaption></figure>
<figure>
<p><img src="/img/big-island-hawaii/green-sand-2-4f3dd801.jpg" alt=""></p>
<figcaption>Papakolea Beach</figcaption></figure>
:::
<h3>Black Sand Beach</h3>
<p>Black sand beaches are common on the Big Island, due to the volcanic activity. They are great to visit, but be sure to wear sandals or water shoes as the sand gets extra hot.</p>
<h4><a href="https://maps.app.goo.gl/EPmApMRDKB2UJJYY8" rel="noopener noreferrer" target="_blank">49 Black Sand Beach</a></h4>
<ul>
<li>this is a small black sand beach, but very accessible in the Kohala coast</li>
<li>it's public, but requires access via private property</li>
<li>get a pass from the guard shack at the gate, then proceed to the beach (see map)</li>
</ul>
<figure>
<p><img src="/img/big-island-hawaii/access-49-black-sand-71ac6907.jpg" alt=""></p>
<figcaption>Accessing 49 Black Sand Beach</figcaption></figure>
<h4><a href="https://maps.app.goo.gl/7avnXgCH4rKxgnVR9" rel="noopener noreferrer" target="_blank">Punalu'u Beach</a></h4>
<ul>
<li>located in Ka'u, this beach has a very "old Hawaii" vibe to me</li>
<li>a bit out of the way, but if you're in the area, it's worth a visit</li>
<li>not the best for swimming</li>
<li><a href="https://maps.app.goo.gl/uxoGewBoamd8h5Zq8" rel="noopener noreferrer" target="_blank">Kehena Beach</a>
<ul>
<li>this beach is in Puna, and clothing optional</li>
<li>it's a fairly steep walk down to the beach</li>
</ul>
</li>
</ul>
<h2>Snorkelling &#x26; Paddleboarding</h2>
<h3>Snorkelling</h3>
<p>The lava rock really attracts sea urchins, so be careful. It's best to wear some sort of water shoes when walking around lava rocks, otherwise you have a pretty good chance of getting pierced (lava is also very sharp!).</p>
<h4><a href="#">Two Step (Pae'a)</a></h4>
<ul>
<li>one of our favourite spots</li>
<li>we're often able to swim with dolphins here (best chance is earlier in the morning) - don't touch them!</li>
<li>quite popular so arrive early</li>
<li>you can park on the road and walk down</li>
</ul>
<figure>
<p><img src="/img/big-island-hawaii/snorkelling-two-step-30f3b3a5.jpg" alt=""></p>
<figcaption>Two Step (Pae'a)</figcaption></figure>
<h4><a href="https://maps.app.goo.gl/pWRup8M3jivM2Toc9" rel="noopener noreferrer" target="_blank">Kealakekua Bay (Captain Cook)</a></h4>
<ul>
<li>this is a popular destination for boat tours, but you can also hike down - it's not too difficult of a hike</li>
<li>park on Napoopoo Road, <a href="https://goo.gl/maps/tfyotJFMnnZ3dp8c9" rel="noopener noreferrer" target="_blank">near the trailhead</a> (you'll probably see other cars parked)</li>
<li>go early in the morning to beat the heat</li>
<li>bring lots of water and food – there's no real shade or services</li>
</ul>
<p><img src="/img/big-island-hawaii/captain-cook-0f0aa7fd.jpg" alt=""></p>
<h3>Other snorkelling</h3>
<ul>
<li>If you want to do any snorkelling or scuba diving, I'd recommend <a href="https://www.jackdivinglocker.com/" rel="noopener noreferrer" target="_blank">Jack's Diving Locker</a> - they are a great local company.
<ul>
<li>night snorkelling with Manta rays is a truly unforgettable experience</li>
</ul>
</li>
</ul>
<figure>
<p><img src="/img/big-island-hawaii/night-mantas-2b14aa52.jpg" alt=""></p>
<figcaption>Night snorkelling with Manta rays</figcaption></figure>
<h4><a href="https://maps.app.goo.gl/GpBKh22y1JeEjTFQ8" rel="noopener noreferrer" target="_blank">Kahaluu Beach Park</a></h4>
<ul>
<li>you can take the Kona Trolley here if you're staying in town</li>
<li>good spot to see turtles as well</li>
</ul>
<h3>Paddleboarding</h3>
<p>We last rented from <a href="https://maps.app.goo.gl/bKc8PxJA4mkm88cF9" rel="noopener noreferrer" target="_blank">Ali'i Adventures in Kona</a>. It's simple to rent for a week and just strap it to the roof of your car.</p>
<p>Our top spot for paddleboarding is actually just the Kona Bay. It's relatively calm, and you're pretty much guaranteed to see dolphins at some point. You can also bring your paddleboard to any of the beaches that are easy to access by car.</p>
<h2>Happy Hour</h2>
<ul>
<li><a href="https://maps.app.goo.gl/BzNhqTz3NbzcDoTUA" rel="noopener noreferrer" target="_blank">Don's Mai Tai</a>
<ul>
<li>claims to have invented the Mai Tai</li>
<li>happy hour from 4–6 p.m.</li>
</ul>
</li>
<li><a href="https://maps.app.goo.gl/jJzbZZNJpmksBAwc7" rel="noopener noreferrer" target="_blank">Kona Brewing Company</a>
<ul>
<li>no happy hour, but they have a good selection of beers</li>
<li>daily tours at 10:30 a.m. and 3 p.m.</li>
</ul>
</li>
<li><a href="https://maps.app.goo.gl/22f6i9EnR7ZqhjPe9" rel="noopener noreferrer" target="_blank">Tropics Ale House</a>
<ul>
<li>located in Waikoloa Beach</li>
<li>happy hour from 2–5 p.m.</li>
<li>excellent mai tais</li>
</ul>
</li>
</ul>
<h2>Restaurants &#x26; Groceries</h2>
<p>The Big Island of Hawaii is not a foodie destination, but here's a few spots we've enjoyed.</p>
<h3>Groceries</h3>
<ul>
<li><a href="https://maps.app.goo.gl/wpdUMsCoBKybSmdm9" rel="noopener noreferrer" target="_blank">Costco</a>
<ul>
<li>best place to pick up groceries for your stay</li>
<li>also a great opportunity to stock up on booze – cheapest place on the island</li>
</ul>
</li>
<li>downtown Kona hosts a <a href="https://maps.app.goo.gl/an3WqmWzQ15kN5oDA" rel="noopener noreferrer" target="_blank">small open-air market</a> from Tuesday–Sunday, 7am–4pm
<ul>
<li>there's a lot of trinkety crap but you can usually find some decent produce for reasonable prices</li>
</ul>
</li>
<li><a href="https://maps.app.goo.gl/d4T9YZMjzRprvkCt9" rel="noopener noreferrer" target="_blank">Target</a> on the northwest side of Kona is a good bet</li>
<li><a href="https://maps.app.goo.gl/vRwW8nyTJ4F84Jfz5" rel="noopener noreferrer" target="_blank">Safeway</a> is also an option</li>
</ul>
<h3>Restaurants</h3>
<ul>
<li>Fosters Kitchen
<ul>
<li>nicer place to eat</li>
<li>location in <a href="https://maps.app.goo.gl/tGrauujPcwVHBTK16" rel="noopener noreferrer" target="_blank">downtown Kona</a> and <a href="https://maps.app.goo.gl/8yVeKbhMdEQNuN6t7" rel="noopener noreferrer" target="_blank">Waikoloa Beach</a></li>
</ul>
</li>
<li><a href="https://maps.app.goo.gl/sjw2GQMGzn2L7EKd8" rel="noopener noreferrer" target="_blank">Umeke's Poke</a>
<ul>
<li>this is raw fish and it's SO GOOD</li>
<li>make sure to get a Ho'io salad on the side</li>
</ul>
</li>
<li><a href="https://maps.app.goo.gl/FkHBgZcKm7CvkV4v5" rel="noopener noreferrer" target="_blank">Hayashi's (You Make The Roll)</a>
<ul>
<li>massive rolls for a bargain</li>
<li>not amazing but good value</li>
</ul>
</li>
<li><a href="https://maps.app.goo.gl/iua2VHAS79V1CW846" rel="noopener noreferrer" target="_blank">Kamana Kitchen</a>
<ul>
<li>great Indian food option</li>
</ul>
</li>
<li><a href="https://maps.app.goo.gl/8CssZ3PfcXu5MpuE6" rel="noopener noreferrer" target="_blank">Tex Drive In</a>
<ul>
<li>famously known for malasadas - a type of Hawaiian/Portuguese "doughnut"</li>
<li>they do run out, so you might be disappointed</li>
<li>I've been known to make the late night trek for these</li>
</ul>
</li>
<li><a href="https://maps.app.goo.gl/vhER2TFuCati6z7LA" rel="noopener noreferrer" target="_blank">Basik Cafe</a>
<ul>
<li>the best damn acai bowls</li>
</ul>
</li>
<li><a href="https://maps.app.goo.gl/LFYFfaBKxeaqvVHG6" rel="noopener noreferrer" target="_blank">Island Lava Java</a>
<ul>
<li>great eggs benedict</li>
</ul>
</li>
<li><a href="https://maps.app.goo.gl/DP7y5xKGh5DQd3YH9" rel="noopener noreferrer" target="_blank">Ken's House of Pancakes</a>
<ul>
<li>if you make the trek to Hilo, you'll want to stop here</li>
</ul>
</li>
</ul>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/logbook/big-island-hawaii</guid>
    </item>
    <item>
      <title>Iceland on Exposure</title>
      <link>https://jwww.me/archive/iceland-on-exposure</link>
      <pubDate>Tue, 16 Dec 2014 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><img src="/img/iceland-on-exposure/hringvegur-32600fef.jpg" alt="photo taken from Hringvegur in Iceland"></p>
<p>I’ve been reminsicing about my trip to Iceland for the past week, so I thought I’d share some of the photos I took there.</p>
<p>They were all taken with a Nikon FM using Fujifilm Xperia 400 film.</p>
<p>Check them out on <a href="https://jwiebe.exposure.co/iceland" rel="noopener noreferrer" target="_blank">Exposure</a>.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/iceland-on-exposure</guid>
    </item>
    <item>
      <title>Building The Newsprint</title>
      <link>https://jwww.me/archive/building-the-newsprint</link>
      <pubDate>Tue, 02 Dec 2014 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>This past week, Josh approached me about re-building his site, <a href="https://thenewsprint.co" rel="noopener noreferrer" target="_blank">The Newsprint</a>. We've kind of been in an ongoing discussing since he <a href="https://thenewsprint.co/2013/12/14/the-newsprint-an-introduction/" rel="noopener noreferrer" target="_blank">started</a> the column a year ago. He's asked for various advice and I've given it as best as I can.</p>
<p>If you've been following The Newsprint, you'll have noticed how much Josh likes to tinker with his site. In the short span of a year, he's gone from Squarespace to Ghost; updated the typography countless times, and tested numerous new layouts. I can still recall when <a href="https://typography.com/cloud" rel="noopener noreferrer" target="_blank">cloud.typography</a> was released, and him drooling over the excellent Hoefler &#x26; Co typefaces — Ideal Sans in particular. Regardless of the piece of advice that I was giving, there was always one little thing that came up that was limited by the system that Josh was currently using. As a result, Josh often voiced his desire for me to build him a custom site, eventually.</p>
<p>As the first year anniversary of the site was coming up, Josh thought it would be good timing to rebuild the site. He sent me a text telling me that he was looking into it and wasn't sure if I'd be able to do it for him, based on timeline. Immediately after receiving that text, I decided to do a quick mockup in Sketch, pretty much just for the fun of it. He had talked often of the style of site that he enjoyed, so I based the mockup off of those discussions.</p>
<p><img src="/img/building-the-newsprint/the-newsprint-screenshot.png" alt="Screenshot of The Newsprint"></p>
<p>I sent it over to him, and he immediately thought that it was almost exactly how he had imagined it in his mind. It's pretty awesome when your design can align so well with client expectations so quickly. I spent a bit of time tweaking things; refining the main brand colour, tweaking the typography, and cleaning up the layout.</p>
<p>I decided that the mockup was close to what I was looking for, so I jumped into Sublime Text to start building. This is the first site that I've built with <a href="https://jekyllrb.com" rel="noopener noreferrer" target="_blank">Jekyll</a>, but I'm already very familiar with the <a href="https://liquidmarkup.org" rel="noopener noreferrer" target="_blank">Liquid</a> syntax because at <a href="https://clc.tf" rel="noopener noreferrer" target="_blank">Collectif</a> we've primarily been using the fantastic <a href="https://siteleaf.com" rel="noopener noreferrer" target="_blank">Siteleaf</a> CMS.</p>
<p>The first thing that I had to do was get the content over from Josh's existing Ghost instance. I exported the Ghost data in JSON format from Ghost's debug menu. I used the excellent <a href="http://rubygems.org/gems/jekyll_ghost_importer" rel="noopener noreferrer" target="_blank">jekyll_ghost_importer</a> gem to convert the posts from JSON to individual Markdown files. This process went very smoothly.</p>
<p>With all of the posts imported, I still needed to ensure they displayed properly. One of Josh's biggest complaints about previous CMS limitations was their support for proper link posts. I decided to start with this. I merely created a link layout, then added the necessary YAML headers to link posts:</p>
<pre><code class="language-yaml">---
layout: post
title: 'Example Title'
date: 2 December 2014
link: http://example.com/link
---
</code></pre>
<p>To show the permalink icon and make the title link, I added an if statement to the post waterfall. I also wanted to show the entire post's content if it was a link post, rather than the standard excerpt. I also used a simple Unicode circle for the permalink to ensure cross-compatibility.</p>
<pre><code class="language-liquid">&#x3C;h2 class="post-title">
   &#x3C;a href="
      {% if post.link %}
         {{ post.link }}
      {% else %}
         {{ post.url }}
     {% endif %}">{{ post.title }}&#x3C;/a>
   {% if post.link %}
      &#x3C;span class="permalink">
         &#x3C;a href="{{ post.url | prepend: site.url }}">&#x26;#9679;&#x3C;/a>
      &#x3C;/span>
   {% endif %}
&#x3C;/h2>
{% if post.link %}
  {{ post.content }}
{% else %}
   {{ post.excerpt | strip_html }}
{% endif %}
</code></pre>
<p>Link posts are now a reality!</p>
<p>Since I was going to roll a CDN for the media, I needed it all in the same folder structure to make things easier. Hosted Ghost instances are pretty limited in what you have access to, so I had Josh request a structured media archive from Ghost support. I decided that was taking too long, so I just used <a href="https://itunes.apple.com/us/app/sitesucker/id442168834?mt=12&#x26;uo=4&#x26;at=11lwLJ" rel="noopener noreferrer" target="_blank">SiteSucker</a> to download all of the media from the existing Ghost site. This also went rather smoothly.</p>
<p>For the CDN, I chose Amazon <a href="http://aws.amazon.com/cloudfront/" rel="noopener noreferrer" target="_blank">CloudFront</a> and S3 for storage. Working with AWS is always a pleasure, and I had the CDN up and running smoothly within minutes.</p>
<p>At this point, the structure of the site and design was pretty much complete. The project was now at a critical stage: how would Josh create new posts. Generally, using Jekyll requires a bit of Terminal.app usage, combined with Git (if you're using GitHub Pages). This can be daunting for new users. Since I knew Josh uses the excellent Editorial app, both for iPad and for iPhone, I decided to build a few workflows for him.</p>
<p>The first workflow generates the necessary YAML header data for Jekyll to display the correct layout, title, and post date. This workflow is public, and you can download it here. Once the post is all written out, we needed to post it to the correct branch (gh-pages) of the GitHub Pages repository (thenewsprint/thenewsprint.co). I found part of a Python script somewhere and adapted and tweaked it to fit Josh's needs. You can see the script here.</p>
<p>That's it!</p>
<p>If you're curious, the entire site lives on this GitHub repository. Feel free to browse the code and let me know how I can improve it!</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/building-the-newsprint</guid>
    </item>
    <item>
      <title>Short Hiatus</title>
      <link>https://jwww.me/archive/short-hiatus</link>
      <pubDate>Wed, 15 Oct 2014 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I’ve been negligent in writing here, as well as keeping you informed about what’s happening in my life right now.</p>
<p>I’m sure most of my semi-regular readers have already abandoned me, but I thought I’d give a quick update here anyway.</p>
<p>My wife and I are currently living in Hawaii, volunteering at the YWAM base here. During this time, I won’t really be writing about much here. I’ll be continuing to work on <a href="https://collectif.co" rel="noopener noreferrer" target="_blank">Collectif</a> projects during my time here. We do have some availability at the moment, so get in touch if you’re interested.</p>
<p>I’m keeping family and friends updated on our time here over at our <a href="https://thewiebes.ca" rel="noopener noreferrer" target="_blank">family website</a>, so feel free to follow along if you please.</p>
<p>Mahalo for now!</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/short-hiatus</guid>
    </item>
    <item>
      <title>Field Notes Arts &amp; Sciences</title>
      <link>https://jwww.me/archive/field-notes-arts-sciences</link>
      <pubDate>Sun, 01 Jun 2014 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I can’t say I’ve been more excited for a pack of notebooks. That grid is going to be beautiful. The colour choices are lovely as well.</p>
<p>I’ve always loved the dot-grid for my sketches, but I think that the blank page will be good for me. I often have little sketches or diagrams that accompany my notes, and it will be interesting to try not having them inline with the actual notes.</p>
<p>My only gripes are the ruled pages and size choice. I find the ruling to be too large for my tiny scrawlings. I’ve always thought it odd that they didn’t choose the standard B7 for their notebooks (standard passport size); it seems that trying a new size would be a good opportunity to go to a standard.</p>
<p>In any case, I’ll be eagerly awaiting my Arts &#x26; Sciences pack (whilst trying to fill my existing Colors editions).</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/field-notes-arts-sciences</guid>
    </item>
    <item>
      <title>Status Update</title>
      <link>https://jwww.me/archive/status-update</link>
      <pubDate>Fri, 25 Apr 2014 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>This is a follow-up post to my earlier post; <a href="http://jwww.me/choosing-blog-format" rel="noopener noreferrer" target="_blank">Choosing a Web-Presence</a>.</p>
<p>Firstly, I'd like to apologise for my lack of keeping up-to-date. It's not like I haven't been writing; <a href="http://thenewsprint.co" rel="noopener noreferrer" target="_blank">Josh</a> got me into Field Notes, but it's primarily been for <a href="http://collectif.co" rel="noopener noreferrer" target="_blank">Collectif</a> or for <a href="http://thewieb.es" rel="noopener noreferrer" target="_blank">trip planning</a>.</p>
<p>I've chosen a web-presence! In the near future, I'll be saying good-bye to Svbtle as my primary blog and moving all of my online presence over to a site built using Siteleaf (the domain will still be <a href="http://jwie.be" rel="noopener noreferrer" target="_blank">jwie.be</a>).</p>
<p><a href="http://jondueck.ca" rel="noopener noreferrer" target="_blank">Jon</a> and I have been using <a href="http://siteleaf.net" rel="noopener noreferrer" target="_blank">Siteleaf</a> over at <a href="http://collectif.co" rel="noopener noreferrer" target="_blank">Collectif</a> for quite some time now, and we <em>love</em> developing with it.</p>
<p>The site is coming along fairly well, I'm really focusing on the typography and simplicity of the site. Here's a screenshot of the style-guide for your viewing pleasure.</p>
<p>I hope to launch the new site in mid-May, but it will depend on whether my upcoming trip plans interfere with that or not.</p>
<p>If you'd like to keep up-to-date, you might find me over on Twitter as <a href="https://twitter.com/josiahwiebe" rel="noopener noreferrer" target="_blank">@josiahwiebe</a>, where I may post the occasional update.</p>
<p>Thanks for stopping by!</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/status-update</guid>
    </item>
    <item>
      <title>Thus Begins a New Era</title>
      <link>https://jwww.me/archive/thus-begins-a-new-era</link>
      <pubDate>Fri, 14 Mar 2014 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Today is my last day as a web designer at <a href="http://www.coopathome.ca" rel="noopener noreferrer" target="_blank">Co-op@Home</a>.</p>
<p>I’ve been working for this business for quite a while.</p>
<p>It started in 2008; I was in high-school and was interested obsessed with the workings of computers. I started part time at Something Digital, where I worked a junior technician. My dad, <a href="https://twitter.com/timwiebe" rel="noopener noreferrer" target="_blank">Tim</a>, had just left the business to go back to teaching, and I got to work with a number of amazing people (some of whom are still working at Co-op@Home). Things seemed to spiral up from there.</p>
<p>A number of positions and several years later, I’m finishing with a bang as their web &#x26; graphic designer. It’s been an amazing couple of years and it pains me to leave such an amazing team, but I’m eagerly anticipating some of the opportunities to come.</p>
<p>I’m excited to join the team at <a href="http://pixelsinc.ca/" rel="noopener noreferrer" target="_blank">Pixels</a> this coming Monday as the Creative Director.</p>
<p>A special thanks to everybody who helped make my time with Co-op an amazing time!</p>
<p>– Josiah</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/thus-begins-a-new-era</guid>
    </item>
    <item>
      <title>Choosing a Web Presence</title>
      <link>https://jwww.me/archive/choosing-a-web-presence</link>
      <pubDate>Mon, 03 Mar 2014 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Edit: I’ve chosen a web-presence and written about it <a href="/archive/status-update">here</a>.</p>
<p>Over the years, I’ve had my personal web presence hosted on a myriad of platforms and services. Things have changed a lot since Geocities.</p>
<p>I’ve experimented with a few different publishing platforms, and hope to commit to a few services for my specific needs.</p>
<p>jwie.be is my primary personal &#x26; tech presence on the web. It’s currently hosted on <a href="#">Svbtle</a>. I’ve been enjoying the format so far, but as a designer, I feel constricted by the lack of customisation options. I could just as easily use josiah.svbtle.com and point jwie.be to something else.</p>
<p>josiahwiebe.net currently points to my Tumblr, which has been around for quite some time. I’ve gone through many theme changes and upgrades. It seems to be more of a gathering place; I collect images, post random thoughts, links, etc. Not all of the content is original (and this seems to be the norm for the common Tumblr user). This could easily use josiahwiebe.tumblr.com and use josiahwiebe.net for something else.</p>
<h2>My requirements:</h2>
<ol>
<li>Easy to use</li>
</ol>
<ul>
<li>It’s not difficult for me to operate complex CMS setups. I just don’t want to. One of the things I’ve loved about Svbtle so far is the focus on pure writing. No fluff.</li>
</ul>
<ol>
<li>Markdown Editor</li>
</ol>
<ul>
<li>Since I’ve started using Markdown, I don’t think I can go back. Beautifully simple syntax.</li>
</ul>
<ol>
<li>Customisation</li>
</ol>
<ul>
<li>I like using third-party services on my websites. It needs to be simple to integrate things such as Cloud.typography and Bigfoot.js.</li>
</ul>
<ol>
<li>Portfolio</li>
</ol>
<ul>
<li>None of my current web presences have my portfolio displayed; it’s currently scattered across Bēhance, Dribbble and Collectif.</li>
</ul>
<ol>
<li>Price</li>
</ol>
<ul>
<li>This isn’t a key factor, but I don’t really want to spend too much. Svbtle and Tumblr are both currently hosting my sites for free. Hosting a static website on Siteleaf &#x26; AWS can be very cost effective.</li>
</ul>
<p>I’ve narrowed my options down to four services; Svbtle, Tumblr, Siteleaf / Statamic (static file CMS) or Squarespace. Here’s a brief table of some of the pros &#x26; cons.</p>
<table>
<thead>
<tr>
<th>Service</th>
<th>Pros</th>
<th>Cons</th>
</tr>
</thead>
<tbody>
<tr>
<td>Svbtle</td>
<td>Fast, clean, fairly well known.</td>
<td>No customisation. I don’t feel like I own my content.</td>
</tr>
<tr>
<td>Tumblr</td>
<td>Social features. Popular, so lots of themes &#x26; support.</td>
<td>Social features. No full control.</td>
</tr>
<tr>
<td>Siteleaf / Statamic</td>
<td>Total control. Beautiful editor.</td>
<td>Time required to create the website. Cost (hosting + CMS cost)</td>
</tr>
<tr>
<td>Squarespace</td>
<td>Great themes. Easy to use.</td>
<td>Price. Lack of customisation (difficult to implement Bigfoot.js, cloud.typography, etc.</td>
</tr>
</tbody>
</table>
<p>I’ve decided that there are two routes to take:</p>
<h2>Unified Website</h2>
<p>This would display both the blog, portfolio, contact info, etc all on one site. I could use any of my existing domains or purchase an additional one. We’re currently using Siteleaf for a number of sites at Collectif, and it’s proven to be amazing. The developers are wonderful and it’s just a joy to use.</p>
<p><strong>Services: Siteleaf, Statamic or Squarespace</strong></p>
<h2>Separate Entities</h2>
<p>Implement a static site for my portfolio, cheaply hosted on S3 and keep my blog on a hosted platform.</p>
<p><strong>Services: Siteleaf or custom build + Svbtle or Tumblr</strong></p>
<p>I’m currently leaning toward the Unified option, but my time is limited and I’m still finishing up my new family website, so it might have to wait. Another post detailing the development and deployment of that site is in the works, so stay tuned!</p>
<p>I feel like there’s a lot on the table here– so many good options to choose from. I haven’t made a decision yet, but I hope to soon. Let me know what your thoughts at hi [at] jwie.be or on Twitter @josiahwiebe.</p>
<hr>
<p>Social features are both a negative and a positive. They can be nice, but also intrusive.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/choosing-a-web-presence</guid>
    </item>
    <item>
      <title>Launch Center Pro &amp; Daily Journaling</title>
      <link>https://jwww.me/archive/launch-center-pro-daily-journaling</link>
      <pubDate>Mon, 03 Feb 2014 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p><strong>Update</strong>: Launch Center Pro was updated to version 2.3 with all kinds of amazing features! Read more about it <a href="/archive/launch-center-pro-updated">here</a></p>
<p>I was recently reading about <a href="https://thenewsprint.co/blog/launch-center-pro-action-to-log-meals/" rel="noopener noreferrer" target="_blank">meal logging</a> with <a href="https://dayoneapp.com/" rel="noopener noreferrer" target="_blank">Day One</a> &#x26; <a href="https://contrast.co/launch-center-pro/" rel="noopener noreferrer" target="_blank">Launch Center Pro</a> on <a href="https://thenewsprint.co" rel="noopener noreferrer" target="_blank">The Newsprint</a>. I’m not very disciplined at digital journaling, and <a href="https://twitter.com/joshuaginter" rel="noopener noreferrer" target="_blank">Josh</a> got me thinking. I began to devise a few of my own actions, generally based around menial events that had a daily significance.</p>
<p>Most of these actions are based around things that I’ve enjoyed throughout the day– a good glass of wine, a favourite beer, a perfect coffee or a great book. In fact, I compiled so many actions that I created a dedicated group in Launch Center Pro; Input. Here’s what it looks like right now:</p>
<p><img src="/img/launch-center-pro-daily-journaling/lcp-daily-ceba3082.png" alt="screenshot of launch center pro on iOS"></p>
<p>It’s far easier for me to keep on top of documenting these very tangible things throughout my day. My Daily Summary action has a very important “Physical Notebook Reference” field at the end. This is because I still primarily write my thoughts down on physical paper, usually in <a href="http://fieldnotesbrand.com" rel="noopener noreferrer" target="_blank">Field Notes</a> Brand notebooks or <a href="http://shop.moleskine.com/en-us/notebooks-journals/cahier/" rel="noopener noreferrer" target="_blank">Moleskine Cahiers</a>. This field allows me to easily make reference to which notebook I’ve written in that day (thanks to Josh for giving me this idea).</p>
<p>The list so far:</p>
<ul>
<li><a href="http://launchcenterpro.com/5gc7mm" rel="noopener noreferrer" target="_blank">Daily Summary</a> - intended to give a brief summary of my day. I’ll refine it a bit more, perhaps to abandon the table format.</li>
<li><a href="http://launchcenterpro.com/k9k19d" rel="noopener noreferrer" target="_blank">Book Log</a> - via The Newsprint</li>
<li><a href="http://launchcenterpro.com/4nszwt" rel="noopener noreferrer" target="_blank">Meal Log</a> - via The Newsprint</li>
<li><a href="http://launchcenterpro.com/jpbqrv" rel="noopener noreferrer" target="_blank">Cocktails Log</a></li>
<li><a href="http://launchcenterpro.com/4k5z63" rel="noopener noreferrer" target="_blank">Film Log</a> - a movie entry action</li>
<li><a href="http://launchcenterpro.com/hs5rtm" rel="noopener noreferrer" target="_blank">Wine Log</a></li>
<li><a href="http://launchcenterpro.com/90gtdq" rel="noopener noreferrer" target="_blank">Beer Log</a> - via <a href="http://jeffmueller.net/post/77956163976/beer-journaling-with-day-one-and-launch-center-pro" rel="noopener noreferrer" target="_blank">Jeff Mueller</a></li>
<li><a href="http://launchcenterpro.com/hpn81v" rel="noopener noreferrer" target="_blank">Coffee Log</a> - adapted from <a href="http://bentsai.wordpress.com/2014/02/26/day-one-templates-using-launch-center-pro/" rel="noopener noreferrer" target="_blank">Ben Tsai</a>’s coffee log</li>
</ul>
<p>Edit:</p>
<p>Upon Josh’s request, I’ve added a <a href="http://launchcenterpro.com/m6yyt9" rel="noopener noreferrer" target="_blank">Sleep Log</a>.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/launch-center-pro-daily-journaling</guid>
    </item>
    <item>
      <title>Using Two-Factor Authentication</title>
      <link>https://jwww.me/archive/using-two-factor-authentication</link>
      <pubDate>Wed, 29 Jan 2014 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<blockquote>
<p>I had a rare Twitter username, @N. Yep, just one letter. I’ve been offered as much as $50,000 for it. People have tried to steal it. Password reset instructions are a regular sight in my email inbox. As of today, I no longer control @N. I was extorted into giving it up.</p>
</blockquote>
<p>A slightly terrifying account of how <a href="https://twitter.com/N_is_stolen" rel="noopener noreferrer" target="_blank">Naoki Hiroshima</a>’s Twitter account; <a href="https://twitter.com/N" rel="noopener noreferrer" target="_blank">@N</a>, was stolen. It’s astounding how one weak link in your digital identity (PayPal in this case) can allow access to everything.</p>
<p>I took the time to turn on two-factor authentication for as many accounts as possible a while back. It does take some time, but I think it’s well worth it. I also moved everything over to <a href="https://iwantmyname.com/" rel="noopener noreferrer" target="_blank">iwantmyname</a> months ago, and all of my MX records are (slightly) safer with a TTL of 604800. I still have yet to leave PayPal. Stripe covers a lot of ground for me, but PayPal seems to remain a necessary evil.</p>
<p>Naoki has received an astonishing amount of press for his account; other <a href="https://www.tumblr.com/ZKMKby160bykY" rel="noopener noreferrer" target="_blank">tales</a> of similar situations arose following his story. I like to hope that Twitter would do something to rectify the situation, but I don’t have too much faith.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/using-two-factor-authentication</guid>
    </item>
    <item>
      <title>Thank You, Redwall</title>
      <link>https://jwww.me/archive/beginnings</link>
      <pubDate>Tue, 28 Jan 2014 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>I’ve been working with the web for a long time.</p>
<p>My earliest memories of “creating websites” began in the year 2004. I was an avid <a href="http://redwallabbey.com" rel="noopener noreferrer" target="_blank">Redwall</a> fan and had just started learning about the web. I believe I started with Geocities, where there was an avid Redwall community springing up. I decided to start a “fact” website. There was a great deal of fan-fiction sites springing up, as well as countless numbers of phpBB forums. What I thought the community really needed was a wiki type site that would allow fans to find information about any Redwall character. This was before the actual Redwall <a href="http://redwall.wikia.com" rel="noopener noreferrer" target="_blank">wiki</a> was started, and it was fairly well received by the community. I continued to move from technology to technology, always trying something new. I learned HTML, CSS, PHP, basic JavaScript and so on. My first non-GeoCities site used frames (yikes!), and then I moved to CSS divs (that was amazing). I tried out WYSIWYG editors, including RapidWeaver, iWeb etc. I probably spent almost every waking moment on this project, called “Redfacts.” That’s fascinating, because I can hardly find a trace of it anywhere these days. Most of the sites I frequented every day have all long since been removed from their servers. I don’t think I even have any archived versions of my site.</p>
<p>It’s amazing how the web has changed since then. We’ve seen the death of frames, Flash, and so on. It’s been a grand adventure so far, and I cannot wait to see what will happen next.</p>
<p>I’ll always be grateful for Redwall for igniting the creativity in me and spurring me to create websites.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/beginnings</guid>
    </item>
    <item>
      <title>Apple&#39;s Isolated Design</title>
      <link>https://jwww.me/archive/apples-isolated-design</link>
      <pubDate>Wed, 12 Jun 2013 00:00:00 GMT</pubDate>
      <content:encoded><![CDATA[<p>Following the announcement and subsequent release of iOS 7 beta, there have been mockups, complaints and criticisms flying rampantly around the design community. I’ve actually seen a lot of mockups that I’ve quite liked, and there are parts of iOS 7 that I’m fond of, but I’m not here to share my opinion of it. I’m here to discuss how Apple arrived at their design decisions.</p>
<p>There’s been heaps of mockups that have surfaced following the release, notably <a href="https://dribbble.com/shots/1109343-iOS-7-Redesign" rel="noopener noreferrer" target="_blank">this</a> one by <a href="https://twitter.com/leodrapeau" rel="noopener noreferrer" target="_blank">Leo Drapeau</a>. iOS 7 is almost entirely devoid of shadows; these new mockups are still “flat” but retain depth and clarity through the use of shadows and other methods.</p>
<p>This method of flat design has been popular for quite some time; it’s a style many designers are familiar with and employ often. After the announcement of iOS 7, “re-design mockups” began to surface almost immediately.</p>
<p>How can freelance designers churn out a beautiful product in such a short span, while it takes Apple’s assumed crack team so much longer? How can Apple’s product look so much different than the theme persisting throughout the design community?</p>
<p>This is not a complaint, merely an observation. I’m excited to see how iOS develops further. Feel free to respond.</p>]]></content:encoded>
      <guid isPermaLink="true">https://jwww.me/archive/apples-isolated-design</guid>
    </item>
  </channel>
</rss>