<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://50bvd.github.io/jekyll-infops-theme/feed.xml" rel="self" type="application/atom+xml" /><link href="https://50bvd.github.io/jekyll-infops-theme/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-03-31T00:43:28+02:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/feed.xml</id><title type="html">jekyll-infops-theme</title><subtitle>A modern Jekyll theme for DevOps and SysOps engineers.</subtitle><author><name>50bvd</name><email></email></author><entry><title type="html">Terminal — Creating Commands, Games &amp;amp; Interactive Programs</title><link href="https://50bvd.github.io/jekyll-infops-theme/2025/01/10/terminal-extending/" rel="alternate" type="text/html" title="Terminal — Creating Commands, Games &amp;amp; Interactive Programs" /><published>2025-01-10T00:00:00+01:00</published><updated>2025-01-10T00:00:00+01:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/2025/01/10/terminal-extending</id><content type="html" xml:base="https://50bvd.github.io/jekyll-infops-theme/2025/01/10/terminal-extending/"><![CDATA[<p>The terminal embedded in the hero section is fully extensible. You can add text commands, canvas games, or any interactive program in a few minutes.</p>

<h2 id="architecture-overview">Architecture overview</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>assets/js/
├── modules/
│   └── hero-terminal.js        ← Terminal engine (ctx API)
└── terminal-commands/
    ├── help.js                 ← Auto-built from registry
    ├── system.js               ← clear / cls
    ├── color.js                ← color command
    ├── fun.js                  ← matrix
    ├── pong.js                 ← Canvas game
    ├── pacman.js               ← Canvas game
    ├── snake.js                ← Canvas game
    └── tetris.js               ← Canvas game
</code></pre></div></div>

<p>Every command file calls <code class="language-plaintext highlighter-rouge">window.Terminal.register()</code> and is loaded via a <code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> tag in <code class="language-plaintext highlighter-rouge">_layouts/default.html</code>.</p>

<hr />

<h2 id="step-1--create-the-command-file">Step 1 — Create the command file</h2>

<p>Create <code class="language-plaintext highlighter-rouge">assets/js/terminal-commands/my-command.js</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nx">Terminal</span><span class="p">.</span><span class="nf">register</span><span class="p">({</span>
  <span class="na">name</span><span class="p">:</span>    <span class="dl">'</span><span class="s1">hello</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">aliases</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">hi</span><span class="dl">'</span><span class="p">],</span>
  <span class="na">help</span><span class="p">:</span>    <span class="p">[</span><span class="dl">'</span><span class="s1">hello [name]</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Say hello</span><span class="dl">'</span><span class="p">],</span>
  <span class="na">run</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">args</span><span class="p">,</span> <span class="nx">ctx</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">world</span><span class="dl">'</span><span class="p">;</span>
    <span class="nx">ctx</span><span class="p">.</span><span class="nf">printLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Hello, </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">name</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">!</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">term-out-bold</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<hr />

<h2 id="step-2--register-it-in-defaulthtml">Step 2 — Register it in default.html</h2>

<p>Open <code class="language-plaintext highlighter-rouge">_layouts/default.html</code> and add a script tag <strong>after</strong> <code class="language-plaintext highlighter-rouge">hero-terminal.js</code>:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"/assets/js/terminal-commands/my-command.js"</span> <span class="na">defer</span><span class="nt">&gt;&lt;/script&gt;</span>
</code></pre></div></div>

<p>That’s it. Rebuild Jekyll and type <code class="language-plaintext highlighter-rouge">hello</code> in the terminal.</p>

<hr />

<h2 id="the-ctx-api">The <code class="language-plaintext highlighter-rouge">ctx</code> API</h2>

<p>The <code class="language-plaintext highlighter-rouge">ctx</code> object is passed to every command’s <code class="language-plaintext highlighter-rouge">run</code> function.</p>

<table>
  <thead>
    <tr>
      <th>Method / Property</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.printLine(text, cssClass, delayMs)</code></td>
      <td>Print a line of text</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.printLines(array, cssClass, baseDelay)</code></td>
      <td>Print multiple lines</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.clearOutput()</code></td>
      <td>Clear the terminal</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.hideOutput()</code></td>
      <td>Hide output + input (for games)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.showOutput()</code></td>
      <td>Restore output + input</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.stopGame()</code></td>
      <td>Stop canvas loop, restore terminal</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.canvas</code></td>
      <td>The <code class="language-plaintext highlighter-rouge">&lt;canvas&gt;</code> element</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.getGameDimensions()</code></td>
      <td>Returns <code class="language-plaintext highlighter-rouge">{W, H}</code> — usable canvas size</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.resizeForGame(W, H)</code></td>
      <td>Resize terminal to fit game, returns Promise</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.getAccentColor()</code></td>
      <td>Returns <code class="language-plaintext highlighter-rouge">{r, g, b}</code> — current user/theme accent</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.isFullscreen()</code></td>
      <td><code class="language-plaintext highlighter-rouge">true</code> if terminal is in fullscreen mode</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ctx.gameLoop.set(handle)</code></td>
      <td>Set rAF handle</td>
    </tr>
  </tbody>
</table>

<h3 id="css-classes-for-printline">CSS classes for <code class="language-plaintext highlighter-rouge">printLine</code></h3>

<table>
  <thead>
    <tr>
      <th>Class</th>
      <th>Appearance</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">term-out</code></td>
      <td>Default accent color</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">term-out-bold</code></td>
      <td>Bright accent, bold</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">term-out-dim</code></td>
      <td>Dimmed accent</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">term-out-error</code></td>
      <td>Red</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">term-out-warn</code></td>
      <td>Yellow</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">term-out-info</code></td>
      <td>Light blue</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">term-out-ascii</code></td>
      <td>Small, dimmed — for ASCII art</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="text-commands">Text commands</h2>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">window</span><span class="p">.</span><span class="nx">Terminal</span><span class="p">.</span><span class="nf">register</span><span class="p">({</span>
  <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">uptime</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">help</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">uptime</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Show page uptime</span><span class="dl">'</span><span class="p">],</span>
  <span class="na">run</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">args</span><span class="p">,</span> <span class="nx">ctx</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">s</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="nx">performance</span><span class="p">.</span><span class="nf">now</span><span class="p">()</span> <span class="o">/</span> <span class="mi">1000</span><span class="p">);</span>
    <span class="nx">ctx</span><span class="p">.</span><span class="nf">printLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">up </span><span class="dl">'</span> <span class="o">+</span> <span class="nb">Math</span><span class="p">.</span><span class="nf">floor</span><span class="p">(</span><span class="nx">s</span><span class="o">/</span><span class="mi">60</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">m </span><span class="dl">'</span> <span class="o">+</span> <span class="p">(</span><span class="nx">s</span><span class="o">%</span><span class="mi">60</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">s</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">term-out</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<hr />

<h2 id="canvas-games">Canvas games</h2>

<h3 id="pattern-resizeforgame--getaccentcolor">Pattern: resizeForGame + getAccentColor</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">keyH</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
  <span class="kd">function</span> <span class="nf">cleanup</span><span class="p">(</span><span class="nx">ctx</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">keyH</span><span class="p">)</span> <span class="nb">document</span><span class="p">.</span><span class="nf">removeEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">keydown</span><span class="dl">'</span><span class="p">,</span> <span class="nx">keyH</span><span class="p">);</span>
    <span class="nx">keyH</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
    <span class="nx">ctx</span><span class="p">.</span><span class="nf">stopGame</span><span class="p">();</span>
  <span class="p">}</span>

  <span class="nb">window</span><span class="p">.</span><span class="nx">Terminal</span><span class="p">.</span><span class="nf">register</span><span class="p">({</span>
    <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">mygame</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">help</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">mygame</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">My canvas game</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">games</span><span class="dl">'</span><span class="p">],</span>
    <span class="na">run</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">args</span><span class="p">,</span> <span class="nx">ctx</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">ctx</span><span class="p">.</span><span class="nf">stopGame</span><span class="p">();</span> <span class="nx">ctx</span><span class="p">.</span><span class="nf">hideOutput</span><span class="p">();</span>

      <span class="c1">// Compute desired size from viewport</span>
      <span class="kd">var</span> <span class="nx">W</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nf">min</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span> <span class="o">-</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">800</span><span class="p">);</span>
      <span class="kd">var</span> <span class="nx">H</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nf">min</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span> <span class="o">-</span> <span class="mi">140</span><span class="p">,</span> <span class="mi">500</span><span class="p">);</span>

      <span class="nx">ctx</span><span class="p">.</span><span class="nf">resizeForGame</span><span class="p">(</span><span class="nx">W</span><span class="p">,</span> <span class="nx">H</span><span class="p">).</span><span class="nf">then</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
        <span class="kd">var</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nx">canvas</span><span class="p">;</span> <span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">canvas</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
        <span class="kd">var</span> <span class="nx">dim</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nf">getGameDimensions</span><span class="p">();</span>
        <span class="nx">W</span> <span class="o">=</span> <span class="nx">dim</span><span class="p">.</span><span class="nx">W</span><span class="p">;</span> <span class="nx">H</span> <span class="o">=</span> <span class="nx">dim</span><span class="p">.</span><span class="nx">H</span><span class="p">;</span>
        <span class="nx">canvas</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">display</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">block</span><span class="dl">'</span><span class="p">;</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="nx">H</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">px</span><span class="dl">'</span><span class="p">;</span>
        <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="nx">W</span><span class="p">;</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="nx">H</span><span class="p">;</span>

        <span class="kd">var</span> <span class="nx">c</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nf">getContext</span><span class="p">(</span><span class="dl">'</span><span class="s1">2d</span><span class="dl">'</span><span class="p">);</span>
        <span class="kd">var</span> <span class="nx">x</span> <span class="o">=</span> <span class="nx">W</span><span class="o">/</span><span class="mi">2</span><span class="p">,</span> <span class="nx">y</span> <span class="o">=</span> <span class="nx">H</span><span class="o">/</span><span class="mi">2</span><span class="p">;</span>

        <span class="nx">keyH</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
          <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">Escape</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span> <span class="nf">cleanup</span><span class="p">(</span><span class="nx">ctx</span><span class="p">);</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
          <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">ArrowLeft</span><span class="dl">'</span><span class="p">)</span>  <span class="nx">x</span> <span class="o">-=</span> <span class="mi">8</span><span class="p">;</span>
          <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">ArrowRight</span><span class="dl">'</span><span class="p">)</span> <span class="nx">x</span> <span class="o">+=</span> <span class="mi">8</span><span class="p">;</span>
          <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">ArrowUp</span><span class="dl">'</span><span class="p">)</span>    <span class="nx">y</span> <span class="o">-=</span> <span class="mi">8</span><span class="p">;</span>
          <span class="k">if </span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">ArrowDown</span><span class="dl">'</span><span class="p">)</span>  <span class="nx">y</span> <span class="o">+=</span> <span class="mi">8</span><span class="p">;</span>
        <span class="p">};</span>
        <span class="nb">document</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">keydown</span><span class="dl">'</span><span class="p">,</span> <span class="nx">keyH</span><span class="p">);</span>
        <span class="nx">ctx</span><span class="p">.</span><span class="nf">printLine</span><span class="p">(</span><span class="dl">'</span><span class="s1">Arrow keys · ESC to quit</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">term-out-bold</span><span class="dl">'</span><span class="p">);</span>

        <span class="kd">function</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>
          <span class="c1">// Read accent color every frame — changes instantly with color command</span>
          <span class="kd">var</span> <span class="nx">ac</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">.</span><span class="nf">getAccentColor</span><span class="p">();</span>
          <span class="kd">var</span> <span class="nx">light</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nf">getAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">data-theme</span><span class="dl">'</span><span class="p">)</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">light</span><span class="dl">'</span><span class="p">;</span>

          <span class="nx">c</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="nx">light</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">#f0f4fc</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">#070d18</span><span class="dl">'</span><span class="p">;</span>
          <span class="nx">c</span><span class="p">.</span><span class="nf">fillRect</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nx">W</span><span class="p">,</span> <span class="nx">H</span><span class="p">);</span>

          <span class="kd">var</span> <span class="nx">color</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">rgba(</span><span class="dl">'</span><span class="o">+</span><span class="nx">ac</span><span class="p">.</span><span class="nx">r</span><span class="o">+</span><span class="dl">'</span><span class="s1">,</span><span class="dl">'</span><span class="o">+</span><span class="nx">ac</span><span class="p">.</span><span class="nx">g</span><span class="o">+</span><span class="dl">'</span><span class="s1">,</span><span class="dl">'</span><span class="o">+</span><span class="nx">ac</span><span class="p">.</span><span class="nx">b</span><span class="o">+</span><span class="dl">'</span><span class="s1">,.9)</span><span class="dl">'</span><span class="p">;</span>
          <span class="nx">c</span><span class="p">.</span><span class="nf">beginPath</span><span class="p">();</span> <span class="nx">c</span><span class="p">.</span><span class="nf">arc</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
          <span class="nx">c</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="nx">color</span><span class="p">;</span> <span class="nx">c</span><span class="p">.</span><span class="nx">shadowColor</span> <span class="o">=</span> <span class="nx">color</span><span class="p">;</span> <span class="nx">c</span><span class="p">.</span><span class="nx">shadowBlur</span> <span class="o">=</span> <span class="mi">12</span><span class="p">;</span>
          <span class="nx">c</span><span class="p">.</span><span class="nf">fill</span><span class="p">();</span> <span class="nx">c</span><span class="p">.</span><span class="nx">shadowBlur</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

          <span class="nx">ctx</span><span class="p">.</span><span class="nx">gameLoop</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nf">requestAnimationFrame</span><span class="p">(</span><span class="nx">loop</span><span class="p">));</span>
        <span class="p">}</span>
        <span class="nf">loop</span><span class="p">();</span>
      <span class="p">});</span>
    <span class="p">}</span>
  <span class="p">});</span>
<span class="p">})();</span>
</code></pre></div></div>

<hr />

<h2 id="adding-to-help">Adding to <code class="language-plaintext highlighter-rouge">/help</code></h2>

<p>The <code class="language-plaintext highlighter-rouge">help</code> array:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">help</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">command syntax</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Description</span><span class="dl">'</span><span class="p">]</span>          <span class="c1">// regular command</span>
<span class="nx">help</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">command syntax</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Description</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">games</span><span class="dl">'</span><span class="p">]</span> <span class="c1">// listed under Games section</span>
</code></pre></div></div>

<hr />

<h2 id="customizing-the-neofetch-boot-screen">Customizing the neofetch boot screen</h2>

<p>Edit <code class="language-plaintext highlighter-rouge">_config.yml</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">theme_config</span><span class="pi">:</span>
  <span class="na">terminal_user</span><span class="pi">:</span> <span class="s2">"</span><span class="s">user@infops"</span>   <span class="c1"># header label</span>
  <span class="na">terminal_boot</span><span class="pi">:</span>
    <span class="na">user</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">loup"</span>
    <span class="na">host</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">infops"</span>
    <span class="na">os</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">AlmaLinux</span><span class="nv"> </span><span class="s">10"</span>
    <span class="na">shell</span><span class="pi">:</span> <span class="s2">"</span><span class="s">bash</span><span class="nv"> </span><span class="s">5.2"</span>
    <span class="na">role</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">SysOps"</span>
    <span class="na">line1</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Org:</span><span class="nv"> </span><span class="s">My</span><span class="nv"> </span><span class="s">Company"</span>
    <span class="na">line2</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Stack:</span><span class="nv"> </span><span class="s">Docker</span><span class="nv"> </span><span class="s">·</span><span class="nv"> </span><span class="s">Proxmox"</span>
    <span class="na">motd</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">Type</span><span class="nv"> </span><span class="s">/help</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">commands."</span>
    <span class="na">ascii</span><span class="pi">:</span> <span class="pi">|</span>
      <span class="s">██╗███╗   ██╗███████╗</span>
      <span class="s">██║████╗  ██║██╔════╝</span>
      <span class="s">██║██╔██╗ ██║█████╗</span>
      <span class="s">██║██║╚██╗██║██╔══╝</span>
      <span class="s">██║██║ ╚████║██║</span>
      <span class="s">╚═╝╚═╝  ╚═══╝╚═╝</span>
</code></pre></div></div>

<p>Leave any field as <code class="language-plaintext highlighter-rouge">""</code> to hide that line. Leave <code class="language-plaintext highlighter-rouge">ascii</code> empty to use the default Ubuntu-style logo.</p>]]></content><author><name>50bvd</name></author><category term="Documentation" /><category term="terminal" /><category term="javascript" /><category term="games" /><category term="customization" /><summary type="html"><![CDATA[How to add your own commands, games, and interactive canvas programs to the jekyll-infops-theme terminal.]]></summary></entry><entry><title type="html">FAQ &amp;amp; Troubleshooting</title><link href="https://50bvd.github.io/jekyll-infops-theme/2025/01/09/faq-troubleshooting/" rel="alternate" type="text/html" title="FAQ &amp;amp; Troubleshooting" /><published>2025-01-09T10:00:00+01:00</published><updated>2025-01-09T10:00:00+01:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/2025/01/09/faq-troubleshooting</id><content type="html" xml:base="https://50bvd.github.io/jekyll-infops-theme/2025/01/09/faq-troubleshooting/"><![CDATA[<p>Collected answers to everything that tends to go wrong during setup, customisation and deployment.</p>

<hr />

<h2 id="build--installation">Build &amp; installation</h2>

<h3 id="bundle-install-fails-on-windows"><code class="language-plaintext highlighter-rouge">bundle install</code> fails on Windows</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Force native Ruby platform (no native extensions)</span>
bundle config <span class="nb">set</span> <span class="nt">--local</span> force_ruby_platform <span class="nb">true
</span>bundle <span class="nb">install</span>
</code></pre></div></div>

<p>If you get SSL errors:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem update <span class="nt">--system</span>
gem <span class="nb">install </span>bundler
bundle <span class="nb">install</span>
</code></pre></div></div>

<h3 id="sass-import-deprecation-warning">Sass <code class="language-plaintext highlighter-rouge">@import</code> deprecation warning</h3>

<p>This theme uses Dart Sass <code class="language-plaintext highlighter-rouge">@use</code> / <code class="language-plaintext highlighter-rouge">@forward</code>. The warning appears if your environment falls back to LibSass (<code class="language-plaintext highlighter-rouge">sassc</code> gem):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Ensure jekyll-sass-converter 3.x is installed</span>
bundle update jekyll-sass-converter
bundle <span class="nb">exec </span>jekyll serve
</code></pre></div></div>

<p>Check which converter is active: <code class="language-plaintext highlighter-rouge">bundle info jekyll-sass-converter</code>.</p>

<h3 id="line-numbers-appear-inside-code-blocks">Line numbers appear inside code blocks</h3>

<p>Set <code class="language-plaintext highlighter-rouge">line_numbers: false</code> in <code class="language-plaintext highlighter-rouge">_config.yml</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">kramdown</span><span class="pi">:</span>
  <span class="na">syntax_highlighter_opts</span><span class="pi">:</span>
    <span class="na">block</span><span class="pi">:</span>
      <span class="na">line_numbers</span><span class="pi">:</span> <span class="kc">false</span>
</code></pre></div></div>

<p>Then restart Jekyll. Existing <code class="language-plaintext highlighter-rouge">_site/</code> files may be stale — run <code class="language-plaintext highlighter-rouge">bundle exec jekyll clean</code> first.</p>

<h3 id="could-not-locate-gemfile-or-missing-include"><code class="language-plaintext highlighter-rouge">"Could not locate Gemfile"</code> or missing include</h3>

<p>Ensure you’re in the project root when running <code class="language-plaintext highlighter-rouge">bundle exec jekyll serve</code>. Double-check that all files referenced by Liquid <code class="language-plaintext highlighter-rouge">include</code> tags exist in <code class="language-plaintext highlighter-rouge">_includes/</code>.</p>

<h3 id="jekyll-builds-successfully-but-the-site-looks-broken">Jekyll builds successfully but the site looks broken</h3>

<ol>
  <li>Check <code class="language-plaintext highlighter-rouge">baseurl</code> in <code class="language-plaintext highlighter-rouge">_config.yml</code> — for a GitHub Pages project site it must be <code class="language-plaintext highlighter-rouge">/repo-name</code>, for a user/org site it must be <code class="language-plaintext highlighter-rouge">""</code></li>
  <li>Open browser DevTools → Console for 404s on CSS/JS</li>
  <li>Run <code class="language-plaintext highlighter-rouge">bundle exec jekyll build --verbose</code> for the full build log</li>
</ol>

<hr />

<h2 id="layout--display">Layout &amp; display</h2>

<h3 id="sidebar-disappears-on-mobile">Sidebar disappears on mobile</h3>

<p>Expected — at &lt; 1100px the sidebar collapses below the content and switches to a 2-column grid. At &lt; 768px it becomes a single column. This is by design and controlled in <code class="language-plaintext highlighter-rouge">_sass/_responsive.scss</code>.</p>

<h3 id="canvas-background-is-invisible">Canvas background is invisible</h3>

<ol>
  <li>Check <code class="language-plaintext highlighter-rouge">canvas.enabled: true</code> in <code class="language-plaintext highlighter-rouge">_config.yml</code></li>
  <li>DevTools → Console: look for JS errors in <code class="language-plaintext highlighter-rouge">canvas.js</code></li>
  <li>Reduce <code class="language-plaintext highlighter-rouge">particle_count</code> to <code class="language-plaintext highlighter-rouge">40</code> and lower <code class="language-plaintext highlighter-rouge">max_distance</code> to <code class="language-plaintext highlighter-rouge">80</code> to rule out performance issues</li>
  <li>Some ad-blockers block canvas API calls — test in a private window</li>
</ol>

<h3 id="hero-section-is-too-tall">Hero section is too tall</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># _data/theme.yml</span>
<span class="na">hero</span><span class="pi">:</span>
  <span class="na">min_height</span><span class="pi">:</span> <span class="m">40</span>   <span class="c1"># vh — reduce this; set to 0 for auto height</span>
</code></pre></div></div>

<h3 id="post-card-left-border-missing">Post card left border missing</h3>

<p>The coloured left border comes from <code class="language-plaintext highlighter-rouge">.post-card::before</code> in <code class="language-plaintext highlighter-rouge">_sass/_layout.scss</code>. If you’ve customised SCSS, verify this rule exists and isn’t overridden by a <code class="language-plaintext highlighter-rouge">display: none</code> or <code class="language-plaintext highlighter-rouge">content: none</code> somewhere.</p>

<h3 id="search-bar-does-nothing">Search bar does nothing</h3>

<p>The search page is at <code class="language-plaintext highlighter-rouge">/search/</code>. Confirm:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">pages/search.html</code> has the front matter block <strong>at the very top</strong> of the file (no Liquid tags before the <code class="language-plaintext highlighter-rouge">---</code>)</li>
  <li><code class="language-plaintext highlighter-rouge">search.json</code> exists at the repo root with a <code class="language-plaintext highlighter-rouge">layout: null</code> front matter</li>
  <li>The <code class="language-plaintext highlighter-rouge">search-json-url</code> meta tag is present in <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> (check <code class="language-plaintext highlighter-rouge">_site/search/index.html</code>)</li>
  <li>Open DevTools → Network — the fetch to <code class="language-plaintext highlighter-rouge">/search.json</code> should return 200 with valid JSON</li>
</ol>

<hr />

<h2 id="terminal">Terminal</h2>

<h3 id="terminal-color-doesnt-persist-across-pages">Terminal color doesn’t persist across pages</h3>

<p>The terminal color is stored in <code class="language-plaintext highlighter-rouge">sessionStorage</code>, not <code class="language-plaintext highlighter-rouge">localStorage</code>. It persists within the same tab session but resets when the tab is closed or a new tab is opened. This is intentional — it’s a visual preference for the current session, not a permanent setting.</p>

<h3 id="games-feel-too-fast-or-too-slow">Games feel too fast or too slow</h3>

<p>Game speed is frame-rate dependent (60fps assumed). On very high refresh-rate monitors (144Hz+), games run faster because <code class="language-plaintext highlighter-rouge">requestAnimationFrame</code> fires more frequently. A fix using <code class="language-plaintext highlighter-rouge">performance.now()</code> delta timing could be added to each game file if needed.</p>

<h3 id="terminal-doesnt-respond-to-keyboard-input">Terminal doesn’t respond to keyboard input</h3>

<p>Click inside the terminal window first — the hidden <code class="language-plaintext highlighter-rouge">&lt;input&gt;</code> needs focus. If the terminal is minimised (yellow dot), click the dot to restore it.</p>

<hr />

<h2 id="analytics">Analytics</h2>

<h3 id="goatcounter-shows--for-visitor-count">GoatCounter shows <code class="language-plaintext highlighter-rouge">—</code> for visitor count</h3>

<p>Two causes:</p>

<p><strong>Public stats disabled:</strong> GoatCounter dashboard → <strong>Settings → Allow public access to stats → Enable</strong>.</p>

<p><strong>CORS blocked:</strong> Open DevTools → Network → filter <code class="language-plaintext highlighter-rouge">goatcounter.com</code>. A <code class="language-plaintext highlighter-rouge">403</code> means public stats are off. A <code class="language-plaintext highlighter-rouge">CORS</code> error means a browser extension is blocking the request. Check the console for <code class="language-plaintext highlighter-rouge">[infops] GoatCounter stats: auth-required</code>.</p>

<h3 id="analytics-fire-in-development">Analytics fire in development</h3>

<p>Check that <code class="language-plaintext highlighter-rouge">analytics.html</code> wraps all script tags inside a <code class="language-plaintext highlighter-rouge">if jekyll.environment == "production"</code> Liquid condition. Run <code class="language-plaintext highlighter-rouge">bundle exec jekyll serve</code> (not <code class="language-plaintext highlighter-rouge">--env production</code> or <code class="language-plaintext highlighter-rouge">JEKYLL_ENV=production</code>).</p>

<h3 id="ga4-shows-no-data-for-2448-hours">GA4 shows no data for 24–48 hours</h3>

<p>Normal — GA4 processes data asynchronously with up to 48h delay. Verify the Measurement ID starts with <code class="language-plaintext highlighter-rouge">G-</code> (not the old <code class="language-plaintext highlighter-rouge">UA-</code> Universal Analytics format).</p>

<hr />

<h2 id="github-pages">GitHub Pages</h2>

<h3 id="build-fails-in-github-actions">Build fails in GitHub Actions</h3>

<p><strong>Check the Actions tab</strong> for the error. Common causes:</p>

<table>
  <thead>
    <tr>
      <th>Error</th>
      <th>Fix</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Gem not found</code></td>
      <td>Add the gem to <code class="language-plaintext highlighter-rouge">Gemfile</code> and commit <code class="language-plaintext highlighter-rouge">Gemfile.lock</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Plugin not whitelisted</code></td>
      <td>The <code class="language-plaintext highlighter-rouge">.github/workflows/pages.yml</code> Action-based workflow bypasses the whitelist — ensure Source is set to <strong>GitHub Actions</strong>, not <strong>Deploy from branch</strong></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">baseurl</code> mismatch</td>
      <td>Match <code class="language-plaintext highlighter-rouge">baseurl</code> in <code class="language-plaintext highlighter-rouge">_config.yml</code> to your repo name</td>
    </tr>
    <tr>
      <td>Jekyll build error in a post</td>
      <td>Run <code class="language-plaintext highlighter-rouge">bundle exec jekyll build</code> locally to see the exact error</td>
    </tr>
  </tbody>
</table>

<h3 id="site-deploys-but-shows-old-content">Site deploys but shows old content</h3>

<p>GitHub Actions build + deploy takes 1–3 minutes. Check <strong>Actions → latest workflow run</strong> for status. If the run succeeded but the site is still stale, try a hard refresh (<code class="language-plaintext highlighter-rouge">Ctrl+Shift+R</code>) — GitHub Pages serves behind a CDN with some caching.</p>

<h3 id="custom-domain-returns-404">Custom domain returns 404</h3>

<ol>
  <li>Ensure a <code class="language-plaintext highlighter-rouge">CNAME</code> file exists at the repo root containing your domain (e.g. <code class="language-plaintext highlighter-rouge">blog.yourdomain.com</code>)</li>
  <li>Add a <code class="language-plaintext highlighter-rouge">CNAME</code> DNS record pointing to <code class="language-plaintext highlighter-rouge">your-username.github.io</code></li>
  <li>Wait up to 24h for DNS propagation</li>
  <li>In repo <strong>Settings → Pages → Custom domain</strong> — GitHub checks DNS and provisions a TLS certificate automatically</li>
</ol>

<hr />

<h2 id="docker">Docker</h2>

<h3 id="docker-compose-up-fails-on-first-run"><code class="language-plaintext highlighter-rouge">docker compose up</code> fails on first run</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Full reset — remove containers, volumes and cached layers</span>
docker compose down <span class="nt">-v</span>
docker compose build <span class="nt">--no-cache</span>
docker compose up
</code></pre></div></div>

<h3 id="changes-not-reflected-after-docker-compose-up">Changes not reflected after <code class="language-plaintext highlighter-rouge">docker compose up</code></h3>

<p>Confirm the source directory is mounted as a volume in <code class="language-plaintext highlighter-rouge">docker-compose.yml</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">volumes</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">.:/srv/jekyll</span>
</code></pre></div></div>

<p>If you edited <code class="language-plaintext highlighter-rouge">_config.yml</code>, livereload is not sufficient — restart the container: <code class="language-plaintext highlighter-rouge">docker compose restart jekyll</code>.</p>]]></content><author><name>50bvd</name></author><category term="documentation" /><category term="faq" /><category term="troubleshooting" /><category term="debug" /><category term="build" /><category term="github-pages" /><summary type="html"><![CDATA[Answers to the most common questions and build errors for jekyll-infops-theme — Sass, Ruby, Docker, GitHub Pages and browser quirks.]]></summary></entry><entry><title type="html">Analytics Setup — GoatCounter, GA4, Plausible &amp;amp; Umami</title><link href="https://50bvd.github.io/jekyll-infops-theme/2025/01/08/analytics-setup/" rel="alternate" type="text/html" title="Analytics Setup — GoatCounter, GA4, Plausible &amp;amp; Umami" /><published>2025-01-08T10:00:00+01:00</published><updated>2025-01-08T10:00:00+01:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/2025/01/08/analytics-setup</id><content type="html" xml:base="https://50bvd.github.io/jekyll-infops-theme/2025/01/08/analytics-setup/"><![CDATA[<p>jekyll-infops-theme supports four analytics providers simultaneously. All scripts are injected only when <code class="language-plaintext highlighter-rouge">JEKYLL_ENV=production</code> — running <code class="language-plaintext highlighter-rouge">jekyll serve</code> locally never fires any tracker.</p>

<hr />

<h2 id="how-production-mode-works">How production mode works</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Development — no analytics injected</span>
bundle <span class="nb">exec </span>jekyll serve

<span class="c"># Production build — all configured providers active</span>
<span class="nv">JEKYLL_ENV</span><span class="o">=</span>production bundle <span class="nb">exec </span>jekyll build

<span class="c"># Docker production — sets JEKYLL_ENV=production automatically</span>
docker compose <span class="nt">-f</span> docker-compose.prod.yml up <span class="nt">-d</span> <span class="nt">--build</span>

<span class="c"># GitHub Actions — pages.yml sets JEKYLL_ENV=production in the build step</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">analytics.html</code> include evaluates <code class="language-plaintext highlighter-rouge">jekyll.environment</code> at build time. If it’s not <code class="language-plaintext highlighter-rouge">production</code>, the output is an empty string — no <code class="language-plaintext highlighter-rouge">&lt;script&gt;</code> tags, no network requests.</p>

<hr />

<h2 id="goatcounter-recommended">GoatCounter (recommended)</h2>

<p><strong>Why GoatCounter:</strong> free up to 100k pageviews/month, open-source (MIT), GDPR-compliant, no cookies, no consent banner required, and provides a public JSON API that the sidebar visitor counter reads.</p>

<h3 id="setup">Setup</h3>

<ol>
  <li>Create an account at <a href="https://www.goatcounter.com">goatcounter.com</a></li>
  <li>Choose a code (e.g. <code class="language-plaintext highlighter-rouge">myblog</code> → <code class="language-plaintext highlighter-rouge">https://myblog.goatcounter.com</code>)</li>
  <li>Add to <code class="language-plaintext highlighter-rouge">_config.yml</code>:</li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">analytics</span><span class="pi">:</span>
  <span class="na">goatcounter_code</span><span class="pi">:</span> <span class="s2">"</span><span class="s">myblog"</span>
</code></pre></div></div>

<ol>
  <li>Rebuild and deploy</li>
</ol>

<h3 id="enabling-the-live-visitor-counter">Enabling the live visitor counter</h3>

<p>The sidebar widget reads from GoatCounter’s <code class="language-plaintext highlighter-rouge">/api/v0/stats/total</code> endpoint. This requires public stats access:</p>

<ol>
  <li>GoatCounter dashboard → <strong>Settings → Allow public access to stats → Save</strong></li>
  <li>No rebuild needed — the JS fetch happens at runtime</li>
</ol>

<p>Without public stats, the sidebar shows <code class="language-plaintext highlighter-rouge">—</code> and a link to your private dashboard.</p>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">GoatCounter is enough for most blogs</div>
    <div class="callout-content"><p>For a personal or team tech blog, GoatCounter’s dashboard covers everything you need: pageviews, referrers, browsers, countries, screen sizes — all in a clean single-page UI with no sampling.</p>
</div>
  </div>
</div>

<hr />

<h2 id="google-analytics-4">Google Analytics 4</h2>

<p>GA4 provides audience demographics, user journeys, conversion funnels, and deep cohort analysis — worth it if you need detailed marketing insights.</p>

<h3 id="setup-1">Setup</h3>

<ol>
  <li><a href="https://analytics.google.com">analytics.google.com</a> → <strong>Admin → Create property → Web</strong></li>
  <li>Enter your site URL → <strong>Create data stream</strong></li>
  <li>Copy the Measurement ID (<code class="language-plaintext highlighter-rouge">G-XXXXXXXXXX</code>)</li>
  <li>Add to <code class="language-plaintext highlighter-rouge">_config.yml</code>:</li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">analytics</span><span class="pi">:</span>
  <span class="na">google_analytics_id</span><span class="pi">:</span> <span class="s2">"</span><span class="s">G-XXXXXXXXXX"</span>
</code></pre></div></div>

<div class="callout callout-warning" role="note">
  <div class="callout-icon">
    <i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">GDPR &amp; cookies</div>
    <div class="callout-content"><p>GA4 sets third-party cookies (<code class="language-plaintext highlighter-rouge">_ga</code>, <code class="language-plaintext highlighter-rouge">_ga_XXXXXXXX</code>). A cookie consent banner is legally required for EU/EEA visitors (GDPR) and UK visitors (UK GDPR). Consider GoatCounter or Plausible for a cookie-free alternative.</p>
</div>
  </div>
</div>

<p>The sidebar shows a direct link to your GA4 property when configured.</p>

<hr />

<h2 id="plausible-analytics">Plausible Analytics</h2>

<p>Lightweight (&lt; 1KB script), privacy-first, no cookies, no consent banner. Paid SaaS ($9/mo for up to 10k pageviews), with a self-hosted option.</p>

<h3 id="setup-2">Setup</h3>

<ol>
  <li><a href="https://plausible.io">plausible.io</a> → Add website</li>
  <li>Enter your domain exactly as it appears in the browser (e.g. <code class="language-plaintext highlighter-rouge">blog.yourdomain.com</code>)</li>
  <li>Add to <code class="language-plaintext highlighter-rouge">_config.yml</code>:</li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">analytics</span><span class="pi">:</span>
  <span class="na">plausible_domain</span><span class="pi">:</span> <span class="s2">"</span><span class="s">blog.yourdomain.com"</span>
</code></pre></div></div>

<h3 id="self-hosting-plausible">Self-hosting Plausible</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/plausible/community-edition.git plausible
<span class="nb">cd </span>plausible
<span class="nb">cp</span> .env.example .env
<span class="c"># Edit .env — set BASE_URL, SECRET_KEY_BASE, DATABASE_URL</span>
docker compose up <span class="nt">-d</span>
</code></pre></div></div>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Self-hosting</div>
    <div class="callout-content"><p>The Plausible Community Edition is free and MIT-licensed. A VPS with 1GB RAM is sufficient for up to ~1M monthly pageviews.</p>
</div>
  </div>
</div>

<hr />

<h2 id="umami-self-hosted">Umami (self-hosted)</h2>

<p>100% self-hosted, open-source, no data sent to third parties. Free forever on your own server.</p>

<h3 id="deploy-with-docker-compose">Deploy with Docker Compose</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># umami/docker-compose.yml</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">umami</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/umami-software/umami:postgresql-latest</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">3000:3000"</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">DATABASE_URL</span><span class="pi">:</span> <span class="s">postgresql://umami:umami@db:5432/umami</span>
      <span class="na">APP_SECRET</span><span class="pi">:</span> <span class="s">replace-with-a-long-random-string</span>

  <span class="na">db</span><span class="pi">:</span>
    <span class="na">image</span><span class="pi">:</span> <span class="s">postgres:16-alpine</span>
    <span class="na">volumes</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">umami-db:/var/lib/postgresql/data</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">POSTGRES_DB</span><span class="pi">:</span>       <span class="s">umami</span>
      <span class="na">POSTGRES_USER</span><span class="pi">:</span>     <span class="s">umami</span>
      <span class="na">POSTGRES_PASSWORD</span><span class="pi">:</span> <span class="s">umami</span>

<span class="na">volumes</span><span class="pi">:</span>
  <span class="na">umami-db</span><span class="pi">:</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker compose up <span class="nt">-d</span>
<span class="c"># → http://your-server:3000</span>
<span class="c"># Default login: admin / umami  ← change immediately</span>
</code></pre></div></div>

<h3 id="get-your-website-id">Get your Website ID</h3>

<ol>
  <li>Umami dashboard → <strong>Settings → Websites → Add website</strong></li>
  <li>Copy the <strong>Website ID</strong> (UUID format)</li>
</ol>

<h3 id="configure">Configure</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">analytics</span><span class="pi">:</span>
  <span class="na">umami_website_id</span><span class="pi">:</span> <span class="s2">"</span><span class="s">xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"</span>
  <span class="na">umami_src</span><span class="pi">:</span>        <span class="s2">"</span><span class="s">https://umami.yourdomain.com/script.js"</span>
</code></pre></div></div>

<hr />

<h2 id="comparison">Comparison</h2>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>GoatCounter</th>
      <th>GA4</th>
      <th>Plausible</th>
      <th>Umami</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Cost</td>
      <td>Free</td>
      <td>Free</td>
      <td>$9+/mo (or self-host)</td>
      <td>Free (self-hosted)</td>
    </tr>
    <tr>
      <td>Cookies</td>
      <td>❌ None</td>
      <td>✅ Yes</td>
      <td>❌ None</td>
      <td>❌ None</td>
    </tr>
    <tr>
      <td>GDPR banner needed</td>
      <td>No</td>
      <td>Yes</td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td>Self-hostable</td>
      <td>No</td>
      <td>No</td>
      <td>Yes (CE)</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>Sidebar visitor counter</td>
      <td>✅</td>
      <td>❌</td>
      <td>❌</td>
      <td>❌</td>
    </tr>
    <tr>
      <td>Data stays yours</td>
      <td>❌</td>
      <td>❌</td>
      <td>Depends</td>
      <td>✅</td>
    </tr>
    <tr>
      <td>Script size</td>
      <td>~4 KB</td>
      <td>~47 KB</td>
      <td>&lt; 1 KB</td>
      <td>~2 KB</td>
    </tr>
  </tbody>
</table>

<div class="callout callout-info" role="note">
  <div class="callout-icon">
    <i class="fas fa-info-circle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Mix and match</div>
    <div class="callout-content"><p>All four providers are independent. You can run GoatCounter for the sidebar counter + Plausible for detailed reporting at the same time — just add both keys to <code class="language-plaintext highlighter-rouge">_config.yml</code>.</p>
</div>
  </div>
</div>]]></content><author><name>50bvd</name></author><category term="documentation" /><category term="analytics" /><category term="goatcounter" /><category term="google-analytics" /><category term="plausible" /><category term="umami" /><category term="gdpr" /><summary type="html"><![CDATA[Step-by-step setup for all four supported analytics providers. Scripts load only in production — local development stats are never polluted.]]></summary></entry><entry><title type="html">Color Themes — Palettes, Fonts &amp;amp; Code Highlighting</title><link href="https://50bvd.github.io/jekyll-infops-theme/2025/01/07/color-themes/" rel="alternate" type="text/html" title="Color Themes — Palettes, Fonts &amp;amp; Code Highlighting" /><published>2025-01-07T10:00:00+01:00</published><updated>2025-01-07T10:00:00+01:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/2025/01/07/color-themes</id><content type="html" xml:base="https://50bvd.github.io/jekyll-infops-theme/2025/01/07/color-themes/"><![CDATA[<p>jekyll-infops-theme separates <strong>behaviour</strong> (<code class="language-plaintext highlighter-rouge">_config.yml</code>) from <strong>aesthetics</strong> (<code class="language-plaintext highlighter-rouge">_data/theme.yml</code>). This post covers everything in <code class="language-plaintext highlighter-rouge">_data/theme.yml</code>.</p>

<hr />

<h2 id="how-theme-variables-propagate">How theme variables propagate</h2>

<p><code class="language-plaintext highlighter-rouge">_includes/custom-styles.html</code> runs at every Jekyll build and produces a <code class="language-plaintext highlighter-rouge">&lt;style id="infops-custom-vars"&gt;</code> block injected into <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code>. That block sets or overrides CSS custom properties on <code class="language-plaintext highlighter-rouge">:root</code> and <code class="language-plaintext highlighter-rouge">[data-theme="dark"]</code>. Because the entire UI is built on these variables, one hex value change cascades everywhere: cards, code blocks, tags, buttons, the terminal, the sidebar.</p>

<p><strong>Dark / Light mode</strong> is toggled by a class on <code class="language-plaintext highlighter-rouge">&lt;html data-theme="..."&gt;</code> — no full page reload, no flash. The terminal accent color is stored separately in <code class="language-plaintext highlighter-rouge">sessionStorage</code> so it survives soft navigation but resets on a new tab.</p>

<hr />

<h2 id="changing-colors">Changing colors</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># _data/theme.yml</span>
<span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#58a6ff"</span>   <span class="c1"># primary — links, buttons, code highlights</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#56d364"</span>   <span class="c1"># success states, tip callouts, "online" badge</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#a5a5ff"</span>   <span class="c1"># secondary — hover states, gradient mid-point</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#f85149"</span>   <span class="c1"># errors, danger callouts</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#e3b341"</span>   <span class="c1"># warnings, maintenance badges</span>

  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#0d1117"</span>   <span class="c1"># page background</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#161b22"</span>   <span class="c1"># cards, header, sidebar panels</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#21262d"</span>   <span class="c1"># inputs, widget headers, code background</span>
</code></pre></div></div>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Contrast check</div>
    <div class="callout-content"><p>Run your accent colour through <a href="https://webaim.org/resources/contrastchecker">webaim.org/resources/contrastchecker</a> against your background. Aim for ≥ 4.5:1 for body text and ≥ 3:1 for large text/icons.</p>
</div>
  </div>
</div>

<hr />

<h2 id="5-ready-to-use-palettes">5 ready-to-use palettes</h2>

<p>Copy any block, paste under <code class="language-plaintext highlighter-rouge">colors:</code> in <code class="language-plaintext highlighter-rouge">_data/theme.yml</code>, rebuild.</p>

<h3 id="default--github-dark">Default — GitHub dark</h3>

<p>The shipping default. Familiar, readable, high-contrast:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#58a6ff"</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#56d364"</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#a5a5ff"</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#f85149"</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#e3b341"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#0d1117"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#161b22"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#21262d"</span>
</code></pre></div></div>

<h3 id="cyberpunk--violet--pink">Cyberpunk — violet + pink</h3>

<p>Vivid, high contrast, cyberpunk aesthetic:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#b57bee"</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#f72585"</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#7209b7"</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#f72585"</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#ffd60a"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#0a0a0f"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#13131a"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#1c1c2e"</span>
</code></pre></div></div>

<h3 id="ocean--deep-blue">Ocean — deep blue</h3>

<p>Cool deep-sea blues with warm accent:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#00b4d8"</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#48cae4"</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#0077b6"</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#ef233c"</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#f4a261"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#03045e"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#023e8a"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#0077b6"</span>
</code></pre></div></div>

<h3 id="forest--dark-green">Forest — dark green</h3>

<p>Earthy, calm, nature-inspired:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#52b788"</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#95d5b2"</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#b7e4c7"</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#e63946"</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#ffd166"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#081c15"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#1b4332"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#2d6a4f"</span>
</code></pre></div></div>

<h3 id="solarized-dark">Solarized Dark</h3>

<p>The classic Ethan Schoonover palette:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#268bd2"</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#859900"</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#6c71c4"</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#dc322f"</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#b58900"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#002b36"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#073642"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#586e75"</span>
</code></pre></div></div>

<hr />

<h2 id="fonts">Fonts</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">fonts</span><span class="pi">:</span>
  <span class="na">sans</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>   <span class="c1"># empty = Inter</span>
  <span class="na">mono</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>   <span class="c1"># empty = JetBrains Mono</span>
</code></pre></div></div>

<p>Any Google Fonts name works. The theme builds the font URL with only the weights it uses and appends <code class="language-plaintext highlighter-rouge">&amp;display=swap</code>.</p>

<p><strong>Recommended pairings:</strong></p>

<table>
  <thead>
    <tr>
      <th>Sans</th>
      <th>Mono</th>
      <th>Vibe</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Plus Jakarta Sans</td>
      <td>Fira Code</td>
      <td>Modern SaaS</td>
    </tr>
    <tr>
      <td>Outfit</td>
      <td>Cascadia Code</td>
      <td>Friendly, geometric</td>
    </tr>
    <tr>
      <td>DM Sans</td>
      <td>IBM Plex Mono</td>
      <td>Technical, editorial</td>
    </tr>
    <tr>
      <td>Geist</td>
      <td>JetBrains Mono (default)</td>
      <td>Vercel-inspired</td>
    </tr>
    <tr>
      <td>Inter (default)</td>
      <td>Source Code Pro</td>
      <td>Classic, neutral</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="prismjs-code-theme">Prism.js code theme</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">code</span><span class="pi">:</span>
  <span class="na">theme</span><span class="pi">:</span> <span class="s2">"</span><span class="s">prism-tomorrow"</span>
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Value</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">prism-tomorrow</code></td>
      <td>Dark, muted — default. Pairs well with all dark palettes</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">prism-okaidia</code></td>
      <td>Dark, saturated — Monokai-inspired</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">prism-solarizedlight</code></td>
      <td>Warm light background</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">prism-twilight</code></td>
      <td>Dark with purple tones</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">prism-coy</code></td>
      <td>Minimal light</td>
    </tr>
  </tbody>
</table>

<p>The CRT scanline effect and blue phosphor glow in code blocks are rendered in CSS on top of Prism’s base theme — they persist regardless of which Prism theme you choose.</p>

<hr />

<h2 id="hero-gradient">Hero gradient</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">hero</span><span class="pi">:</span>
  <span class="na">title_gradient</span><span class="pi">:</span> <span class="s2">"</span><span class="s">linear-gradient(135deg,</span><span class="nv"> </span><span class="s">#58a6ff</span><span class="nv"> </span><span class="s">0%,</span><span class="nv"> </span><span class="s">#a5a5ff</span><span class="nv"> </span><span class="s">50%,</span><span class="nv"> </span><span class="s">#56d364</span><span class="nv"> </span><span class="s">100%)"</span>
</code></pre></div></div>

<p>The gradient applies to the site title text via <code class="language-plaintext highlighter-rouge">-webkit-text-fill-color: transparent</code> + <code class="language-plaintext highlighter-rouge">background-clip: text</code>. Use <a href="https://cssgradient.io">cssgradient.io</a> to create and preview gradients interactively, then paste the CSS value here.</p>

<hr />

<h2 id="live-terminal-color">Live terminal color</h2>

<p>The <code class="language-plaintext highlighter-rouge">color</code> command lets visitors recolour the entire terminal at runtime — no rebuild needed:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>color green        # preset: green, blue, purple, orange, red, cyan, yellow, pink, white
color #e879f9      # any 6-digit hex
color reset        # back to the theme's accent_blue
</code></pre></div></div>

<p>The chosen color is saved in <code class="language-plaintext highlighter-rouge">sessionStorage</code> and restored on page reload. It drives every terminal element: text output, cursor, prompt, pulse dot, scanline tint, and window border — all via <code class="language-plaintext highlighter-rouge">rgba(var(--term-color), ...)</code>.</p>]]></content><author><name>50bvd</name></author><category term="documentation" /><category term="colors" /><category term="themes" /><category term="customization" /><category term="fonts" /><category term="prism" /><category term="_data" /><summary type="html"><![CDATA[Switch color palettes, change fonts and Prism syntax themes — all from _data/theme.yml, no SCSS needed. Includes 5 ready-to-use palettes.]]></summary></entry><entry><title type="html">Sidebar Widgets — Configuration &amp;amp; Live Metrics</title><link href="https://50bvd.github.io/jekyll-infops-theme/2025/01/06/sidebar-configuration/" rel="alternate" type="text/html" title="Sidebar Widgets — Configuration &amp;amp; Live Metrics" /><published>2025-01-06T10:00:00+01:00</published><updated>2025-01-06T10:00:00+01:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/2025/01/06/sidebar-configuration</id><content type="html" xml:base="https://50bvd.github.io/jekyll-infops-theme/2025/01/06/sidebar-configuration/"><![CDATA[<p>The sidebar contains five independent widgets, each toggleable from <code class="language-plaintext highlighter-rouge">_config.yml</code>. On viewports narrower than 1100px, the sidebar collapses below the main content and becomes a horizontal grid.</p>

<hr />

<h2 id="enable--disable-widgets">Enable / disable widgets</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">theme_config</span><span class="pi">:</span>
  <span class="na">show_stats</span><span class="pi">:</span>         <span class="kc">true</span>   <span class="c1"># Site Stats card</span>
  <span class="na">show_system_status</span><span class="pi">:</span> <span class="kc">true</span>   <span class="c1"># Live browser metrics</span>
  <span class="na">show_recent_posts</span><span class="pi">:</span>  <span class="kc">true</span>   <span class="c1"># Recent Posts list</span>
  <span class="na">show_categories</span><span class="pi">:</span>    <span class="kc">true</span>   <span class="c1"># Category tag cloud</span>
  <span class="na">show_tags</span><span class="pi">:</span>          <span class="kc">true</span>   <span class="c1"># Tag cloud</span>
  <span class="na">recent_posts_count</span><span class="pi">:</span> <span class="m">5</span>      <span class="c1"># items in Recent Posts</span>
</code></pre></div></div>

<p>Set any flag to <code class="language-plaintext highlighter-rouge">false</code> to hide the widget entirely — the sidebar reflows automatically.</p>

<hr />

<h2 id="site-stats-widget">Site Stats widget</h2>

<p>Shows four counters:</p>

<table>
  <thead>
    <tr>
      <th>Counter</th>
      <th>Source</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Articles</td>
      <td>Jekyll <code class="language-plaintext highlighter-rouge">site.posts | size</code> — always accurate</td>
    </tr>
    <tr>
      <td>Tags</td>
      <td>Jekyll <code class="language-plaintext highlighter-rouge">site.tags | size</code> — always accurate</td>
    </tr>
    <tr>
      <td>Words</td>
      <td>Sum of all post word counts — computed at build time</td>
    </tr>
    <tr>
      <td>Visitors / month</td>
      <td>Live from GoatCounter API — requires configuration</td>
    </tr>
  </tbody>
</table>

<h3 id="enabling-the-live-visitor-count">Enabling the live visitor count</h3>

<p>The visitor counter fetches from GoatCounter’s <code class="language-plaintext highlighter-rouge">/api/v0/stats/total</code> endpoint. This endpoint requires either an API token (not practical for a public site) or <strong>public stats</strong> to be enabled:</p>

<ol>
  <li>Create a free account at <a href="https://www.goatcounter.com">goatcounter.com</a></li>
  <li>Add your <code class="language-plaintext highlighter-rouge">goatcounter_code</code> to <code class="language-plaintext highlighter-rouge">_config.yml</code></li>
  <li>In the GoatCounter dashboard: <strong>Settings → Allow public access to stats → Save</strong></li>
  <li>Rebuild the site</li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">analytics</span><span class="pi">:</span>
  <span class="na">goatcounter_code</span><span class="pi">:</span> <span class="s2">"</span><span class="s">myblog"</span>   <span class="c1"># https://myblog.goatcounter.com</span>
</code></pre></div></div>

<p>If public stats are disabled (or if GoatCounter isn’t configured), the counter shows <code class="language-plaintext highlighter-rouge">—</code> with a link to your dashboard. All other stats still display correctly.</p>

<hr />

<h2 id="system-status-widget--live-browser-metrics">System Status widget — live browser metrics</h2>

<p>Unlike a traditional status page, the System Status widget here reports <strong>real-time browser metrics</strong> — not a static configuration. The data is read from browser APIs and refreshes every 12 seconds.</p>

<h3 id="metric-1--network">Metric 1 — Network</h3>

<p>Uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation">Network Information API</a>:</p>

<table>
  <thead>
    <tr>
      <th>Condition</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">navigator.onLine === false</code></td>
      <td>🔴 error — offline</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">effectiveType === '2g'</code> or <code class="language-plaintext highlighter-rouge">rtt &gt; 400ms</code></td>
      <td>🟡 warning</td>
    </tr>
    <tr>
      <td>Everything else</td>
      <td>🟢 online + connection type (<code class="language-plaintext highlighter-rouge">4g</code>, <code class="language-plaintext highlighter-rouge">wifi</code>…)</td>
    </tr>
  </tbody>
</table>

<h3 id="metric-2--load-time">Metric 2 — Load time</h3>

<p>Reads <code class="language-plaintext highlighter-rouge">performance.timing.loadEventEnd - performance.timing.navigationStart</code>:</p>

<table>
  <thead>
    <tr>
      <th>Time</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>&lt; 2 s</td>
      <td>🟢 online</td>
    </tr>
    <tr>
      <td>2 – 5 s</td>
      <td>🟡 warning</td>
    </tr>
    <tr>
      <td>&gt; 5 s</td>
      <td>🔴 error</td>
    </tr>
  </tbody>
</table>

<h3 id="metric-3--protocol">Metric 3 — Protocol</h3>

<p><code class="language-plaintext highlighter-rouge">location.protocol === 'https:'</code> → 🟢 HTTPS, otherwise 🟡 HTTP. Useful as a quick sanity check in production.</p>

<h3 id="metric-4--js-heap-chrome-only">Metric 4 — JS heap (Chrome only)</h3>

<p><code class="language-plaintext highlighter-rouge">performance.memory.usedJSHeapSize / totalJSHeapSize</code>:</p>

<table>
  <thead>
    <tr>
      <th>Usage</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>&lt; 70%</td>
      <td>🟢</td>
    </tr>
    <tr>
      <td>70 – 90%</td>
      <td>🟡</td>
    </tr>
    <tr>
      <td>&gt; 90%</td>
      <td>🔴</td>
    </tr>
  </tbody>
</table>

<p>This metric only appears in Chromium-based browsers — Firefox and Safari don’t expose <code class="language-plaintext highlighter-rouge">performance.memory</code>.</p>

<div class="callout callout-note" role="note">
  <div class="callout-icon">
    <i class="fas fa-sticky-note" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Why live metrics?</div>
    <div class="callout-content"><p>A static status config (<code class="language-plaintext highlighter-rouge">online | warning | error</code>) is useful for communicating infra state to readers. Real-time browser metrics are more interesting on a tech blog — they demonstrate what the browser APIs can actually tell you, and change as the user’s conditions change.</p>
</div>
  </div>
</div>

<hr />

<h2 id="recent-posts-widget">Recent Posts widget</h2>

<p>Lists the N most recent posts (controlled by <code class="language-plaintext highlighter-rouge">recent_posts_count</code>). Each entry links to the post with its publication date.</p>

<hr />

<h2 id="categories--tags">Categories &amp; Tags</h2>

<p>Both widgets display alphabetically sorted lists as tag pills. Each pill links to the corresponding category page (<code class="language-plaintext highlighter-rouge">/categories/category-name/</code>) or tag anchor (<code class="language-plaintext highlighter-rouge">/tags/#tag-name</code>).</p>

<p>Tag and category pages are generated at build time from the posts’ front matter — no extra configuration needed.</p>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Keeping tags tidy</div>
    <div class="callout-content"><p>Use lowercase, hyphenated tags consistently (<code class="language-plaintext highlighter-rouge">docker-compose</code>, not <code class="language-plaintext highlighter-rouge">Docker Compose</code> or <code class="language-plaintext highlighter-rouge">dockercompose</code>). Jekyll treats tags as case-sensitive — inconsistent casing creates duplicate entries in the cloud.</p>
</div>
  </div>
</div>]]></content><author><name>50bvd</name></author><category term="documentation" /><category term="sidebar" /><category term="widgets" /><category term="metrics" /><category term="system-status" /><category term="configuration" /><summary type="html"><![CDATA[Configure the sidebar: enable/disable widgets, understand the live browser metrics, and set up the GoatCounter visitor counter.]]></summary></entry><entry><title type="html">Docker Deployment — Development &amp;amp; Production</title><link href="https://50bvd.github.io/jekyll-infops-theme/2025/01/05/docker-deployment/" rel="alternate" type="text/html" title="Docker Deployment — Development &amp;amp; Production" /><published>2025-01-05T10:00:00+01:00</published><updated>2025-01-05T10:00:00+01:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/2025/01/05/docker-deployment</id><content type="html" xml:base="https://50bvd.github.io/jekyll-infops-theme/2025/01/05/docker-deployment/"><![CDATA[<p>jekyll-infops-theme ships with a complete Docker setup for both development and production. No Ruby installation required on your machine.</p>

<hr />

<h2 id="files-overview">Files overview</h2>

<table>
  <thead>
    <tr>
      <th>File</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">Dockerfile</code></td>
      <td>Multi-stage: <code class="language-plaintext highlighter-rouge">dev</code> target (Jekyll + livereload) and <code class="language-plaintext highlighter-rouge">prod</code> target (nginx)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">docker-compose.yml</code></td>
      <td>Development mode — mounts sources as a live volume</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">docker-compose.prod.yml</code></td>
      <td>Production mode — builds static site, serves with nginx</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">docker/nginx.conf</code></td>
      <td>nginx config with gzip, caching and security headers</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="development-mode">Development mode</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker compose up
</code></pre></div></div>

<ul>
  <li>Starts Jekyll with <code class="language-plaintext highlighter-rouge">--livereload</code></li>
  <li>Mounts the entire project as a volume — file changes trigger instant rebuilds</li>
  <li>Opens at <code class="language-plaintext highlighter-rouge">http://localhost:4000</code></li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># First launch or after Gemfile changes — rebuild the image</span>
docker compose up <span class="nt">--build</span>

<span class="c"># Run in background</span>
docker compose up <span class="nt">-d</span>

<span class="c"># Tail logs</span>
docker compose logs <span class="nt">-f</span> jekyll
</code></pre></div></div>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Gem cache</div>
    <div class="callout-content"><p>The dev Compose file mounts a named volume for the bundler gem cache. After the first build, gems are cached across container restarts — startup goes from ~60s to ~3s.</p>
</div>
  </div>
</div>

<div class="callout callout-info" role="note">
  <div class="callout-icon">
    <i class="fas fa-info-circle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Analytics in development</div>
    <div class="callout-content"><p><code class="language-plaintext highlighter-rouge">JEKYLL_ENV</code> is not set in dev mode, so <code class="language-plaintext highlighter-rouge">jekyll.environment</code> evaluates to <code class="language-plaintext highlighter-rouge">development</code>. Analytics scripts in <code class="language-plaintext highlighter-rouge">_includes/analytics.html</code> are guarded by a production environment check and are never injected during local development.</p>
</div>
  </div>
</div>

<hr />

<h2 id="production-mode">Production mode</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker compose <span class="nt">-f</span> docker-compose.prod.yml up <span class="nt">-d</span> <span class="nt">--build</span>
<span class="c"># → http://localhost (port 80)</span>
</code></pre></div></div>

<p>The production build:</p>

<ol>
  <li>Runs <code class="language-plaintext highlighter-rouge">bundle exec jekyll build</code> with <code class="language-plaintext highlighter-rouge">JEKYLL_ENV=production</code> — activates analytics</li>
  <li>Copies <code class="language-plaintext highlighter-rouge">_site/</code> into an nginx Alpine image (~20MB final image)</li>
  <li>Serves the static site with gzip, long-term asset caching, and a set of security headers</li>
</ol>

<h3 id="nginx-security-headers">nginx security headers</h3>

<p>The <code class="language-plaintext highlighter-rouge">docker/nginx.conf</code> sets:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">add_header</span> <span class="s">X-Frame-Options</span> <span class="s">"SAMEORIGIN"</span><span class="p">;</span>
<span class="k">add_header</span> <span class="s">X-Content-Type-Options</span> <span class="s">"nosniff"</span><span class="p">;</span>
<span class="k">add_header</span> <span class="s">X-XSS-Protection</span> <span class="s">"1</span><span class="p">;</span> <span class="k">mode=block"</span><span class="p">;</span>
<span class="k">add_header</span> <span class="s">Referrer-Policy</span> <span class="s">"strict-origin-when-cross-origin"</span><span class="p">;</span>
<span class="k">add_header</span> <span class="s">Permissions-Policy</span> <span class="s">"geolocation=(),</span> <span class="s">microphone=(),</span> <span class="s">camera=()"</span><span class="p">;</span>
</code></pre></div></div>

<p>Edit <code class="language-plaintext highlighter-rouge">docker/nginx.conf</code> to add a Content-Security-Policy if needed for your analytics/comments providers.</p>

<h3 id="caching-strategy">Caching strategy</h3>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># HTML — no cache (always fresh)</span>
<span class="k">location</span> <span class="p">~</span><span class="sr">*</span> <span class="err">\</span><span class="s">.html</span>$ <span class="p">{</span> <span class="kn">add_header</span> <span class="s">Cache-Control</span> <span class="s">"no-store"</span><span class="p">;</span> <span class="p">}</span>

<span class="c1"># Assets — 1 year (Jekyll fingerprints filenames)</span>
<span class="k">location</span> <span class="p">~</span><span class="sr">*</span> <span class="err">\</span><span class="s">.(css|js|woff2?|jpg|png|svg|ico)</span>$ <span class="p">{</span>
  <span class="kn">add_header</span> <span class="s">Cache-Control</span> <span class="s">"public,</span> <span class="s">max-age=31536000,</span> <span class="s">immutable"</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<hr />

<h2 id="useful-commands">Useful commands</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Check running containers</span>
docker compose ps

<span class="c"># Stop cleanly</span>
docker compose down

<span class="c"># Destroy containers AND gem cache volume (full reset)</span>
docker compose down <span class="nt">-v</span>

<span class="c"># Rebuild without layer cache</span>
docker compose build <span class="nt">--no-cache</span>

<span class="c"># Production: view nginx access logs</span>
docker compose <span class="nt">-f</span> docker-compose.prod.yml logs <span class="nt">-f</span> nginx
</code></pre></div></div>

<hr />

<h2 id="running-behind-a-reverse-proxy">Running behind a reverse proxy</h2>

<p>If you place nginx behind Traefik, Caddy or another reverse proxy, edit <code class="language-plaintext highlighter-rouge">docker-compose.prod.yml</code> to remove the host port binding and let the proxy handle TLS:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
  <span class="na">nginx</span><span class="pi">:</span>
    <span class="c1"># remove: ports: ["80:80"]</span>
    <span class="na">expose</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">80"</span><span class="pi">]</span>
    <span class="na">labels</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.enable=true"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.routers.blog.rule=Host(`blog.yourdomain.com`)"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.routers.blog.entrypoints=websecure"</span>
      <span class="pi">-</span> <span class="s2">"</span><span class="s">traefik.http.routers.blog.tls.certresolver=letsencrypt"</span>
</code></pre></div></div>

<div class="callout callout-warning" role="note">
  <div class="callout-icon">
    <i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">update url in _config.yml</div>
    <div class="callout-content"><p>Set <code class="language-plaintext highlighter-rouge">url: 'https://blog.yourdomain.com'</code> in <code class="language-plaintext highlighter-rouge">_config.yml</code> so absolute URLs in feeds, sitemaps and SEO tags resolve correctly.</p>
</div>
  </div>
</div>

<hr />

<h2 id="github-pages-no-docker">GitHub Pages (no Docker)</h2>

<p>For public hosting, GitHub Actions is simpler than Docker:</p>

<ol>
  <li>Push the repo to GitHub</li>
  <li><strong>Settings → Pages → Source → GitHub Actions</strong></li>
  <li>The <code class="language-plaintext highlighter-rouge">.github/workflows/pages.yml</code> workflow builds with <code class="language-plaintext highlighter-rouge">JEKYLL_ENV=production</code> and deploys on every push to <code class="language-plaintext highlighter-rouge">main</code></li>
  <li>Site is live at <code class="language-plaintext highlighter-rouge">https://your-username.github.io/repo-name/</code></li>
</ol>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Custom domain on GitHub Pages</div>
    <div class="callout-content"><p>Create a <code class="language-plaintext highlighter-rouge">CNAME</code> file at the repo root containing your domain (e.g. <code class="language-plaintext highlighter-rouge">blog.yourdomain.com</code>). Then add a CNAME DNS record pointing to <code class="language-plaintext highlighter-rouge">your-username.github.io</code>. GitHub issues the TLS certificate automatically via Let’s Encrypt.</p>
</div>
  </div>
</div>]]></content><author><name>50bvd</name></author><category term="documentation" /><category term="docker" /><category term="deployment" /><category term="nginx" /><category term="self-hosting" /><category term="production" /><summary type="html"><![CDATA[Run jekyll-infops-theme with Docker — zero Ruby required. Dev mode with live reload, production mode with nginx, gzip and security headers.]]></summary></entry><entry><title type="html">Writing Posts — Markdown, Front Matter &amp;amp; Callouts</title><link href="https://50bvd.github.io/jekyll-infops-theme/2025/01/04/writing-posts-reference/" rel="alternate" type="text/html" title="Writing Posts — Markdown, Front Matter &amp;amp; Callouts" /><published>2025-01-04T10:00:00+01:00</published><updated>2025-01-04T10:00:00+01:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/2025/01/04/writing-posts-reference</id><content type="html" xml:base="https://50bvd.github.io/jekyll-infops-theme/2025/01/04/writing-posts-reference/"><![CDATA[<p>Everything you need to write posts in jekyll-infops-theme. Bookmark this page and come back to it — it covers every supported element.</p>

<hr />

<h2 id="front-matter">Front matter</h2>

<p>Every post starts with a YAML block between <code class="language-plaintext highlighter-rouge">---</code> delimiters:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">post</span>
<span class="na">title</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">Monitoring</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">Linux</span><span class="nv"> </span><span class="s">fleet</span><span class="nv"> </span><span class="s">with</span><span class="nv"> </span><span class="s">Prometheus</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">Grafana"</span>
<span class="na">date</span><span class="pi">:</span>   <span class="s">2025-03-15 09:00:00 +0100</span>
<span class="na">author</span><span class="pi">:</span> <span class="s2">"</span><span class="s">50bvd"</span>

<span class="c1"># SEO &amp; listing</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Step-by-step</span><span class="nv"> </span><span class="s">guide</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">deploying</span><span class="nv"> </span><span class="s">Prometheus</span><span class="nv"> </span><span class="s">node_exporter</span><span class="nv"> </span><span class="s">across</span><span class="nv"> </span><span class="s">40+</span><span class="nv"> </span><span class="s">servers."</span>
<span class="na">categories</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">monitoring</span><span class="pi">,</span> <span class="nv">linux</span><span class="pi">]</span>
<span class="na">tags</span><span class="pi">:</span>       <span class="pi">[</span><span class="nv">prometheus</span><span class="pi">,</span> <span class="nv">grafana</span><span class="pi">,</span> <span class="nv">alertmanager</span><span class="pi">,</span> <span class="nv">node-exporter</span><span class="pi">]</span>

<span class="c1"># Features</span>
<span class="na">toc</span><span class="pi">:</span>      <span class="kc">true</span>     <span class="c1"># show table of contents (default: true from _config.yml)</span>
<span class="na">comments</span><span class="pi">:</span> <span class="kc">true</span>     <span class="c1"># enable Utterances/Disqus (default: true)</span>
<span class="na">mermaid</span><span class="pi">:</span>  <span class="kc">false</span>    <span class="c1"># load Mermaid.js for diagrams</span>
<span class="na">math</span><span class="pi">:</span>     <span class="kc">false</span>    <span class="c1"># load MathJax for LaTeX equations</span>
<span class="nn">---</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">layout</code>, <code class="language-plaintext highlighter-rouge">title</code> and <code class="language-plaintext highlighter-rouge">date</code> are required. Everything else uses the <code class="language-plaintext highlighter-rouge">defaults:</code> from <code class="language-plaintext highlighter-rouge">_config.yml</code> as fallback.</p>

<div class="callout callout-warning" role="note">
  <div class="callout-icon">
    <i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Filename date</div>
    <div class="callout-content"><p>The filename must start with the same date as the <code class="language-plaintext highlighter-rouge">date:</code> field: <code class="language-plaintext highlighter-rouge">2025-03-15-my-post.md</code> → <code class="language-plaintext highlighter-rouge">date: 2025-03-15</code>. A mismatch causes Jekyll to skip or misroute the file.</p>
</div>
  </div>
</div>

<hr />

<h2 id="callouts">Callouts</h2>

<p>Callouts are the most-used include in this theme. They add visually distinct contextual blocks anywhere in Markdown:</p>

<div class="language-liquid highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{%</span><span class="w"> </span><span class="nt">include</span><span class="w"> </span>callout.html<span class="w"> </span><span class="na">type</span><span class="o">=</span><span class="s2">"tip"</span><span class="w"> </span><span class="na">title</span><span class="o">=</span><span class="s2">"Pro tip"</span><span class="w"> </span><span class="na">content</span><span class="o">=</span><span class="s2">"Your advice here."</span><span class="w"> </span><span class="cp">%}</span>
</code></pre></div></div>

<p>Six types available:</p>

<div class="callout callout-note" role="note">
  <div class="callout-icon">
    <i class="fas fa-sticky-note" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">note</div>
    <div class="callout-content"><p>Neutral context, extra detail, or a side remark that doesn’t fit the main flow.</p>
</div>
  </div>
</div>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">tip</div>
    <div class="callout-content"><p>Best practice, shortcut, or experience-based advice the reader shouldn’t miss.</p>
</div>
  </div>
</div>

<div class="callout callout-info" role="note">
  <div class="callout-icon">
    <i class="fas fa-info-circle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">info</div>
    <div class="callout-content"><p>Technical detail, link to external documentation, or a clarification.</p>
</div>
  </div>
</div>

<div class="callout callout-warning" role="note">
  <div class="callout-icon">
    <i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">warning</div>
    <div class="callout-content"><p>Unexpected behaviour, common mistake, or a prerequisite to verify before proceeding.</p>
</div>
  </div>
</div>

<div class="callout callout-danger" role="note">
  <div class="callout-icon">
    <i class="fas fa-skull-crossbones" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">danger</div>
    <div class="callout-content"><p>Risk of data loss, destructive command, or security vulnerability.</p>
</div>
  </div>
</div>

<div class="callout callout-success" role="note">
  <div class="callout-icon">
    <i class="fas fa-check-circle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">success</div>
    <div class="callout-content"><p>Confirmed working configuration, expected result, or completion of a critical step.</p>
</div>
  </div>
</div>

<p>The <code class="language-plaintext highlighter-rouge">content</code> parameter supports inline Markdown: <code class="language-plaintext highlighter-rouge">**bold**</code>, <code class="language-plaintext highlighter-rouge">`code`</code>, and <code class="language-plaintext highlighter-rouge">[links](url)</code> all work.</p>

<hr />

<h2 id="code-blocks">Code blocks</h2>

<p>Wrap code in triple backticks and add a language identifier:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">bash
</span>systemctl status prometheus.service
journalctl <span class="nt">-u</span> prometheus <span class="nt">-f</span> <span class="nt">--since</span> <span class="s2">"10 minutes ago"</span>
<span class="p">```</span>
</code></pre></div></div>

<p>The theme wraps every block in a styled header (language label + Copy button) and a CRT-style dark body. The copy button captures the raw source text — Prism tokenisation doesn’t leak into the clipboard.</p>

<p><strong>Supported identifiers</strong> (non-exhaustive):</p>

<p><code class="language-plaintext highlighter-rouge">bash</code> · <code class="language-plaintext highlighter-rouge">sh</code> · <code class="language-plaintext highlighter-rouge">zsh</code> · <code class="language-plaintext highlighter-rouge">powershell</code> · <code class="language-plaintext highlighter-rouge">yaml</code> · <code class="language-plaintext highlighter-rouge">json</code> · <code class="language-plaintext highlighter-rouge">toml</code> · <code class="language-plaintext highlighter-rouge">ini</code> · <code class="language-plaintext highlighter-rouge">nginx</code> · <code class="language-plaintext highlighter-rouge">dockerfile</code> · <code class="language-plaintext highlighter-rouge">python</code> · <code class="language-plaintext highlighter-rouge">ruby</code> · <code class="language-plaintext highlighter-rouge">javascript</code> · <code class="language-plaintext highlighter-rouge">typescript</code> · <code class="language-plaintext highlighter-rouge">go</code> · <code class="language-plaintext highlighter-rouge">rust</code> · <code class="language-plaintext highlighter-rouge">sql</code> · <code class="language-plaintext highlighter-rouge">html</code> · <code class="language-plaintext highlighter-rouge">css</code> · <code class="language-plaintext highlighter-rouge">scss</code> · <code class="language-plaintext highlighter-rouge">diff</code> · <code class="language-plaintext highlighter-rouge">markdown</code></p>

<p>Full list: <a href="https://prismjs.com/#supported-languages">prismjs.com/#supported-languages</a></p>

<hr />

<h2 id="inline-elements">Inline elements</h2>

<table>
  <thead>
    <tr>
      <th>Element</th>
      <th>Markdown</th>
      <th>Output</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Bold</td>
      <td><code class="language-plaintext highlighter-rouge">**bold**</code></td>
      <td><strong>bold</strong></td>
    </tr>
    <tr>
      <td>Italic</td>
      <td><code class="language-plaintext highlighter-rouge">*italic*</code></td>
      <td><em>italic</em></td>
    </tr>
    <tr>
      <td>Strikethrough</td>
      <td><code class="language-plaintext highlighter-rouge">~~strike~~</code></td>
      <td><del>strike</del></td>
    </tr>
    <tr>
      <td>Inline code</td>
      <td><code class="language-plaintext highlighter-rouge">`code`</code></td>
      <td><code class="language-plaintext highlighter-rouge">code</code></td>
    </tr>
    <tr>
      <td>Link</td>
      <td><code class="language-plaintext highlighter-rouge">[text](url)</code></td>
      <td><a href="#">text</a></td>
    </tr>
    <tr>
      <td>Image</td>
      <td><code class="language-plaintext highlighter-rouge">![alt](path)</code></td>
      <td><em>(image)</em></td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="tables">Tables</h2>

<p>Standard GFM tables get full styling automatically — zebra rows, hover highlight, horizontal scroll on mobile:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code>| Service    | Port | Protocol | Status |
|---|---|---|---|
| Prometheus | 9090 | HTTP     | ✅ online |
| Grafana    | 3000 | HTTP     | ✅ online |
| Alertmanager | 9093 | HTTP  | ⚠️ degraded |
| node_exporter | 9100 | HTTP | ✅ online |
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Service</th>
      <th>Port</th>
      <th>Protocol</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Prometheus</td>
      <td>9090</td>
      <td>HTTP</td>
      <td>✅ online</td>
    </tr>
    <tr>
      <td>Grafana</td>
      <td>3000</td>
      <td>HTTP</td>
      <td>✅ online</td>
    </tr>
    <tr>
      <td>Alertmanager</td>
      <td>9093</td>
      <td>HTTP</td>
      <td>⚠️ degraded</td>
    </tr>
    <tr>
      <td>node_exporter</td>
      <td>9100</td>
      <td>HTTP</td>
      <td>✅ online</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="blockquotes">Blockquotes</h2>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gt">&gt; In theory, theory and practice are the same. In practice, they aren't.</span>
<span class="gt">&gt;</span>
<span class="gt">&gt; &lt;cite&gt;Yogi Berra (attributed)&lt;/cite&gt;</span>
</code></pre></div></div>

<blockquote>
  <p>In theory, theory and practice are the same. In practice, they aren’t.</p>

  <p><cite>Yogi Berra (attributed)</cite></p>
</blockquote>

<hr />

<h2 id="table-of-contents">Table of Contents</h2>

<p>The TOC is auto-generated from all <code class="language-plaintext highlighter-rouge">h2</code>, <code class="language-plaintext highlighter-rouge">h3</code>, and <code class="language-plaintext highlighter-rouge">h4</code> headings in the post body. An IntersectionObserver highlights the active section as the reader scrolls. A floating TOC toggle also appears in the right margin on wide screens.</p>

<p>Disable for a specific post: <code class="language-plaintext highlighter-rouge">toc: false</code> in front matter.</p>

<hr />

<h2 id="mermaid-diagrams">Mermaid diagrams</h2>

<p>Add <code class="language-plaintext highlighter-rouge">mermaid: true</code> to front matter, then use a fenced block:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">```</span><span class="nl">mermaid
</span><span class="sb">graph TD
  A[Prometheus] --&gt;|scrape| B[node_exporter]
  A --&gt;|alert| C[Alertmanager]
  C --&gt;|notify| D[PagerDuty]
  A --&gt;|datasource| E[Grafana]</span>
<span class="p">```</span>
</code></pre></div></div>

<p>Mermaid supports flowcharts, sequence diagrams, Gantt charts, ER diagrams and more. See <a href="https://mermaid.js.org/intro/">mermaid.js.org</a> for the full syntax.</p>

<hr />

<h2 id="lazy-loaded-images">Lazy-loaded images</h2>

<p>Regular Markdown images load immediately. For images below the fold, use <code class="language-plaintext highlighter-rouge">data-src</code> to defer loading until they enter the viewport:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;img</span> <span class="na">data-src=</span><span class="s">"/assets/images/grafana-dashboard.png"</span>
     <span class="na">alt=</span><span class="s">"Grafana dashboard showing node CPU usage"</span>
     <span class="na">loading=</span><span class="s">"lazy"</span><span class="nt">&gt;</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">lazy-load.js</code> module uses <code class="language-plaintext highlighter-rouge">IntersectionObserver</code> with a 200px rootMargin, so images start loading just before they scroll into view.</p>

<hr />

<h2 id="mathjax-equations">MathJax equations</h2>

<p>Add <code class="language-plaintext highlighter-rouge">math: true</code> to front matter to load MathJax:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Inline: $E = mc^2$

Display:
$$<span class="se">\n</span>abla <span class="se">\c</span>dot <span class="se">\m</span>athbf{E} = <span class="se">\f</span>rac{<span class="se">\r</span>ho}{<span class="se">\v</span>arepsilon_0}$$
</code></pre></div></div>

<p>MathJax v3 is loaded from jsDelivr, only on pages that need it.</p>]]></content><author><name>50bvd</name></author><category term="documentation" /><category term="writing" /><category term="markdown" /><category term="callouts" /><category term="syntax" /><category term="front-matter" /><summary type="html"><![CDATA[Complete reference for writing posts: front matter, callout blocks, code highlighting, tables, Mermaid diagrams and all supported Markdown elements.]]></summary></entry><entry><title type="html">Visual Customization — Colors, Fonts &amp;amp; Layout</title><link href="https://50bvd.github.io/jekyll-infops-theme/2025/01/03/visual-customization/" rel="alternate" type="text/html" title="Visual Customization — Colors, Fonts &amp;amp; Layout" /><published>2025-01-03T10:00:00+01:00</published><updated>2025-01-03T10:00:00+01:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/2025/01/03/visual-customization</id><content type="html" xml:base="https://50bvd.github.io/jekyll-infops-theme/2025/01/03/visual-customization/"><![CDATA[<p>The entire visual appearance of jekyll-infops-theme is controlled by two files: <code class="language-plaintext highlighter-rouge">_config.yml</code> (behaviour) and <code class="language-plaintext highlighter-rouge">_data/theme.yml</code> (aesthetics). You never need to touch the SCSS.</p>

<hr />

<h2 id="how-the-theming-pipeline-works">How the theming pipeline works</h2>

<p>At build time, <code class="language-plaintext highlighter-rouge">_includes/custom-styles.html</code> reads <code class="language-plaintext highlighter-rouge">_data/theme.yml</code> and injects a <code class="language-plaintext highlighter-rouge">&lt;style id="infops-custom-vars"&gt;</code> block that overrides CSS custom properties defined in <code class="language-plaintext highlighter-rouge">_sass/_themes.scss</code>. Because CSS variables cascade, every component picks up the change automatically — cards, code blocks, tags, the terminal, the sidebar widget.</p>

<p>The result: <strong>change a hex value → rebuild → done.</strong></p>

<hr />

<h2 id="fonts">Fonts</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">fonts</span><span class="pi">:</span>
  <span class="na">sans</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>   <span class="c1"># leave empty for Inter (default)</span>
  <span class="na">mono</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>   <span class="c1"># leave empty for JetBrains Mono (default)</span>
</code></pre></div></div>

<p>Any <a href="https://fonts.google.com">Google Fonts</a> name works. The theme requests only the weights it actually uses (300–900 for sans, 400/500/700 for mono), so page weight stays minimal.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Examples</span>
<span class="na">fonts</span><span class="pi">:</span>
  <span class="na">sans</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Plus</span><span class="nv"> </span><span class="s">Jakarta</span><span class="nv"> </span><span class="s">Sans"</span>
  <span class="na">mono</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Fira</span><span class="nv"> </span><span class="s">Code"</span>
</code></pre></div></div>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Font pairing ideas</div>
    <div class="callout-content"><p><strong>Sans</strong>: Plus Jakarta Sans, Outfit, DM Sans, Geist — all read well at small sizes. <strong>Mono</strong>: Fira Code and Cascadia Code have ligatures (<code class="language-plaintext highlighter-rouge">!=</code> → <code class="language-plaintext highlighter-rouge">≠</code>, <code class="language-plaintext highlighter-rouge">-&gt;</code> → <code class="language-plaintext highlighter-rouge">→</code>) that look great in code blocks.</p>
</div>
  </div>
</div>

<hr />

<h2 id="colors">Colors</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="c1"># Accents</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#58a6ff"</span>   <span class="c1"># links, buttons, primary highlights</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#56d364"</span>   <span class="c1"># success, tips callouts, "online" status</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#a5a5ff"</span>   <span class="c1"># secondary accent</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#f85149"</span>   <span class="c1"># errors, danger callouts</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#e3b341"</span>   <span class="c1"># warnings, maintenance status</span>

  <span class="c1"># Backgrounds (dark mode — ignored in light mode)</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#0d1117"</span>   <span class="c1"># page background</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#161b22"</span>   <span class="c1"># cards, header, sidebar</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#21262d"</span>   <span class="c1"># inputs, hover states, code background</span>
</code></pre></div></div>

<div class="callout callout-info" role="note">
  <div class="callout-icon">
    <i class="fas fa-info-circle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Light mode backgrounds</div>
    <div class="callout-content"><p>Light mode surfaces are built into <code class="language-plaintext highlighter-rouge">_sass/_themes.scss</code> as blue-grey tints. They are not controlled by <code class="language-plaintext highlighter-rouge">_data/theme.yml</code> — override them in <code class="language-plaintext highlighter-rouge">_sass/_themes.scss</code> if needed.</p>
</div>
  </div>
</div>

<h3 id="terminal-color-at-runtime">Terminal color at runtime</h3>

<p>The accent color also drives the terminal — every element (text, cursor, prompt, pulse dot) uses <code class="language-plaintext highlighter-rouge">rgba(var(--term-color), ...)</code>. Users can change it live without rebuilding:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>color green        # named preset
color #FF6B35      # any hex
color reset        # back to theme default
</code></pre></div></div>

<hr />

<h2 id="ready-to-use-palettes">Ready-to-use palettes</h2>

<p>Paste any block into <code class="language-plaintext highlighter-rouge">_data/theme.yml</code> under <code class="language-plaintext highlighter-rouge">colors:</code> and rebuild.</p>

<h3 id="default--github-style-dark">Default — GitHub-style dark</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#58a6ff"</span>
  <span class="na">accent_green</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#56d364"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#0d1117"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#161b22"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#21262d"</span>
</code></pre></div></div>

<h3 id="cyberpunk-violet--pink">Cyberpunk (violet + pink)</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#b57bee"</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#f72585"</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#7209b7"</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#f72585"</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#ffd60a"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#0a0a0f"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#13131a"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#1c1c2e"</span>
</code></pre></div></div>

<h3 id="ocean-deep-blue">Ocean (deep blue)</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#00b4d8"</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#48cae4"</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#0077b6"</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#ef233c"</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#f4a261"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#03045e"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#023e8a"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#0077b6"</span>
</code></pre></div></div>

<h3 id="forest-dark-green">Forest (dark green)</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#52b788"</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#95d5b2"</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#b7e4c7"</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#e63946"</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#ffd166"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#081c15"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#1b4332"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#2d6a4f"</span>
</code></pre></div></div>

<h3 id="solarized-dark">Solarized Dark</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#268bd2"</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#859900"</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#6c71c4"</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#dc322f"</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#b58900"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#002b36"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#073642"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#586e75"</span>
</code></pre></div></div>

<h3 id="light-mode">Light mode</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">colors</span><span class="pi">:</span>
  <span class="na">accent_blue</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#0969da"</span>
  <span class="na">accent_green</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#1a7f37"</span>
  <span class="na">accent_purple</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#6e40c9"</span>
  <span class="na">accent_red</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#cf222e"</span>
  <span class="na">accent_yellow</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#9a6700"</span>
  <span class="na">bg_primary</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">#ffffff"</span>
  <span class="na">bg_secondary</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">#f6f8fa"</span>
  <span class="na">bg_tertiary</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">#eaeef2"</span>
</code></pre></div></div>

<hr />

<h2 id="code-block-syntax-theme">Code block syntax theme</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">code</span><span class="pi">:</span>
  <span class="na">theme</span><span class="pi">:</span> <span class="s2">"</span><span class="s">prism-tomorrow"</span>   <span class="c1"># default</span>
</code></pre></div></div>

<p>Available Prism themes (loaded from cdnjs):</p>

<table>
  <thead>
    <tr>
      <th>Value</th>
      <th>Style</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">prism-tomorrow</code></td>
      <td>Dark, muted — default</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">prism-okaidia</code></td>
      <td>Dark, vivid — Monokai-like</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">prism-solarizedlight</code></td>
      <td>Light, warm</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">prism-twilight</code></td>
      <td>Dark, purple tones</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">prism-coy</code></td>
      <td>Light, minimal</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="layout-dimensions">Layout dimensions</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">layout</span><span class="pi">:</span>
  <span class="na">max_width</span><span class="pi">:</span>     <span class="m">1400</span>   <span class="c1"># container max-width in px (1200–1600)</span>
  <span class="na">border_radius</span><span class="pi">:</span> <span class="m">12</span>     <span class="c1"># card corner radius in px (0 = sharp squares)</span>
  <span class="na">section_gap</span><span class="pi">:</span>   <span class="m">2</span>      <span class="c1"># vertical spacing between sections in rem</span>

<span class="na">sidebar</span><span class="pi">:</span>
  <span class="na">width</span><span class="pi">:</span> <span class="m">320</span>   <span class="c1"># sidebar width in px when visible</span>

<span class="na">hero</span><span class="pi">:</span>
  <span class="na">title_gradient</span><span class="pi">:</span> <span class="s2">"</span><span class="s">linear-gradient(135deg,</span><span class="nv"> </span><span class="s">#58a6ff</span><span class="nv"> </span><span class="s">0%,</span><span class="nv"> </span><span class="s">#a5a5ff</span><span class="nv"> </span><span class="s">50%,</span><span class="nv"> </span><span class="s">#56d364</span><span class="nv"> </span><span class="s">100%)"</span>
  <span class="na">min_height</span><span class="pi">:</span> <span class="m">58</span>   <span class="c1"># vh</span>
</code></pre></div></div>

<p>Use <a href="https://cssgradient.io">cssgradient.io</a> to generate gradient strings visually.</p>

<hr />

<h2 id="social-links">Social links</h2>

<p>Edit <code class="language-plaintext highlighter-rouge">_data/social.yml</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">links</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">GitHub"</span>
    <span class="na">url</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">https://github.com/your-username"</span>
    <span class="na">icon</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">fab</span><span class="nv"> </span><span class="s">fa-github"</span>
    <span class="na">color</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#e6edf3"</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">LinkedIn"</span>
    <span class="na">url</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">https://linkedin.com/in/your-slug"</span>
    <span class="na">icon</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">fab</span><span class="nv"> </span><span class="s">fa-linkedin"</span>
    <span class="na">color</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#0a66c2"</span>

  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">Mastodon"</span>
    <span class="na">url</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">https://fosstodon.org/@you"</span>
    <span class="na">icon</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">fab</span><span class="nv"> </span><span class="s">fa-mastodon"</span>
    <span class="na">color</span><span class="pi">:</span> <span class="s2">"</span><span class="s">#6364ff"</span>
</code></pre></div></div>

<p>Set <code class="language-plaintext highlighter-rouge">url: ""</code> to hide any link. The <code class="language-plaintext highlighter-rouge">color</code> value drives the hover color of the icon in the footer.</p>]]></content><author><name>50bvd</name></author><category term="documentation" /><category term="customization" /><category term="colors" /><category term="fonts" /><category term="_data" /><category term="themes" /><summary type="html"><![CDATA[Customize the theme's colors, fonts and layout without touching any SCSS — everything lives in _data/theme.yml.]]></summary></entry><entry><title type="html">Configuration Reference — _config.yml</title><link href="https://50bvd.github.io/jekyll-infops-theme/2025/01/02/configuration-reference/" rel="alternate" type="text/html" title="Configuration Reference — _config.yml" /><published>2025-01-02T10:00:00+01:00</published><updated>2025-01-02T10:00:00+01:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/2025/01/02/configuration-reference</id><content type="html" xml:base="https://50bvd.github.io/jekyll-infops-theme/2025/01/02/configuration-reference/"><![CDATA[<p>All theme behaviour is driven by a single file: <code class="language-plaintext highlighter-rouge">_config.yml</code> at the repo root. This post documents every key.</p>

<div class="callout callout-info" role="note">
  <div class="callout-icon">
    <i class="fas fa-info-circle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Restart required</div>
    <div class="callout-content"><p>Jekyll does not watch <code class="language-plaintext highlighter-rouge">_config.yml</code> for changes. Restart <code class="language-plaintext highlighter-rouge">jekyll serve</code> (or <code class="language-plaintext highlighter-rouge">docker compose restart</code>) after every edit.</p>
</div>
  </div>
</div>

<hr />

<h2 id="site-identity">Site identity</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">title</span><span class="pi">:</span>       <span class="s2">"</span><span class="s">My</span><span class="nv"> </span><span class="s">InfOps</span><span class="nv"> </span><span class="s">Blog"</span>
<span class="na">tagline</span><span class="pi">:</span>     <span class="s2">"</span><span class="s">DevOps</span><span class="nv"> </span><span class="s">·</span><span class="nv"> </span><span class="s">SysOps</span><span class="nv"> </span><span class="s">·</span><span class="nv"> </span><span class="s">Infrastructure"</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">A</span><span class="nv"> </span><span class="s">short</span><span class="nv"> </span><span class="s">description</span><span class="nv"> </span><span class="s">used</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">SEO</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">footer."</span>
<span class="na">url</span><span class="pi">:</span>         <span class="s2">"</span><span class="s">https://your-username.github.io"</span>   <span class="c1"># no trailing slash</span>
<span class="na">baseurl</span><span class="pi">:</span>     <span class="s2">"</span><span class="s">"</span>   <span class="c1"># leave empty for root sites; use "/repo-name" for project sites</span>
<span class="na">lang</span><span class="pi">:</span>        <span class="s2">"</span><span class="s">en"</span>
<span class="na">timezone</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">Europe/Paris"</span>   <span class="c1"># any tz database name</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">url</code> + <code class="language-plaintext highlighter-rouge">baseurl</code> together form the absolute base of every link. For a GitHub Pages user site (<code class="language-plaintext highlighter-rouge">your-username.github.io</code>), both <code class="language-plaintext highlighter-rouge">url: "https://your-username.github.io"</code> and <code class="language-plaintext highlighter-rouge">baseurl: ""</code> are correct.</p>

<hr />

<h2 id="author">Author</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">author</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span>     <span class="s2">"</span><span class="s">Your</span><span class="nv"> </span><span class="s">Name"</span>           <span class="c1"># used in post meta and footer</span>
  <span class="na">email</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">you@example.com"</span>     <span class="c1"># never displayed publicly</span>
  <span class="na">bio</span><span class="pi">:</span>      <span class="s2">"</span><span class="s">Short</span><span class="nv"> </span><span class="s">bio</span><span class="nv"> </span><span class="s">text."</span>     <span class="c1"># shown on the About page</span>
  <span class="na">avatar</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">/assets/images/avatar.jpg"</span>
  <span class="na">github</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">your-username"</span>
  <span class="na">twitter</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">yourhandle"</span>          <span class="c1"># without @</span>
  <span class="na">linkedin</span><span class="pi">:</span> <span class="s2">"</span><span class="s">your-slug"</span>
  <span class="na">mastodon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://fosstodon.org/@you"</span>   <span class="c1"># full URL</span>
</code></pre></div></div>

<p>Social links with empty values are hidden. The footer reads from <code class="language-plaintext highlighter-rouge">_data/social.yml</code> first; these <code class="language-plaintext highlighter-rouge">author.*</code> fields are the fallback.</p>

<hr />

<h2 id="analytics">Analytics</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">analytics</span><span class="pi">:</span>
  <span class="na">goatcounter_code</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">"</span>   <span class="c1"># your-code → https://your-code.goatcounter.com</span>
  <span class="na">google_analytics_id</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>   <span class="c1"># G-XXXXXXXXXX</span>
  <span class="na">plausible_domain</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">"</span>   <span class="c1"># yourdomain.com (no https://)</span>
  <span class="na">umami_website_id</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">"</span>   <span class="c1"># UUID from your Umami instance</span>
  <span class="na">umami_src</span><span class="pi">:</span>           <span class="s2">"</span><span class="s">"</span>   <span class="c1"># https://umami.yourdomain.com/script.js</span>
</code></pre></div></div>

<p>Scripts are injected <strong>only when <code class="language-plaintext highlighter-rouge">JEKYLL_ENV=production</code></strong>. Running <code class="language-plaintext highlighter-rouge">jekyll serve</code> locally never fires any tracker. Multiple providers can be active simultaneously.</p>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Recommended: GoatCounter</div>
    <div class="callout-content"><p>GoatCounter is free (up to 100k pageviews/month), open-source, requires no cookie banner, and exposes a public JSON API that the sidebar widget reads to display a live visitor count.</p>
</div>
  </div>
</div>

<hr />

<h2 id="comments">Comments</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">comments</span><span class="pi">:</span>
  <span class="na">provider</span><span class="pi">:</span>         <span class="s2">"</span><span class="s">utterances"</span>           <span class="c1"># utterances | disqus | none</span>
  <span class="na">utterances_repo</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">your-username/repo"</span>   <span class="c1"># GitHub repo for issue threads</span>
  <span class="na">utterances_theme</span><span class="pi">:</span> <span class="s2">"</span><span class="s">github-dark"</span>          <span class="c1"># github-dark | github-light | preferred-color-scheme</span>
  <span class="na">disqus_shortname</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>                     <span class="c1"># Disqus site shortname</span>
</code></pre></div></div>

<p>Utterances maps each post URL to a GitHub Issue in the specified repo. Visitors need a GitHub account to comment. Disable comments on a specific post with <code class="language-plaintext highlighter-rouge">comments: false</code> in its front matter.</p>

<hr />

<h2 id="terminal">Terminal</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">theme_config</span><span class="pi">:</span>
  <span class="na">terminal_user</span><span class="pi">:</span> <span class="s2">"</span><span class="s">user@infops"</span>   <span class="c1"># label in the terminal header bar</span>

  <span class="na">terminal_boot</span><span class="pi">:</span>
    <span class="na">user</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">YOUR_USER"</span>     <span class="c1"># shown as user@host in neofetch</span>
    <span class="na">host</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">YOUR_HOST"</span>
    <span class="na">os</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">YOUR_OS"</span>       <span class="c1"># e.g. "Jekyll 4.3 · AlmaLinux 10"</span>
    <span class="na">shell</span><span class="pi">:</span> <span class="s2">"</span><span class="s">YOUR_SHELL"</span>    <span class="c1"># e.g. "bash 5.2"</span>
    <span class="na">role</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">YOUR_ROLE"</span>     <span class="c1"># e.g. "SysOps · Infrastructure"</span>
    <span class="na">line1</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>              <span class="c1"># extra info line (leave empty to hide)</span>
    <span class="na">line2</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span>
    <span class="na">motd</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">Type</span><span class="nv"> </span><span class="s">/help</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">commands."</span>

    <span class="na">ascii</span><span class="pi">:</span> <span class="pi">|</span>               <span class="c1"># custom ASCII art (leave empty for Ubuntu logo)</span>
      <span class="s">██╗███╗   ██╗███████╗</span>
      <span class="s">██║████╗  ██║██╔════╝</span>
      <span class="s">██║██╔██╗ ██║█████╗</span>
      <span class="s">██║██║╚██╗██║██╔══╝</span>
      <span class="s">██║██║ ╚████║██║</span>
      <span class="s">╚═╝╚═╝  ╚═══╝╚═╝</span>
</code></pre></div></div>

<p>All <code class="language-plaintext highlighter-rouge">terminal_boot</code> fields accept empty strings to hide that line. The <code class="language-plaintext highlighter-rouge">ascii</code> block (9 lines max, ~32 chars wide) replaces the default Ubuntu-style logo at boot. Generate art at <a href="https://patorjk.com/software/taag/">patorjk.com/software/taag</a>.</p>

<hr />

<h2 id="particle-canvas">Particle canvas</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">canvas</span><span class="pi">:</span>
    <span class="na">enabled</span><span class="pi">:</span>         <span class="kc">true</span>
    <span class="na">particle_count</span><span class="pi">:</span>  <span class="m">80</span>     <span class="c1"># 20–150 recommended</span>
    <span class="na">max_distance</span><span class="pi">:</span>    <span class="m">130</span>    <span class="c1"># px — max distance for inter-particle lines</span>
    <span class="na">cursor_distance</span><span class="pi">:</span> <span class="m">160</span>    <span class="c1"># px — cursor attraction radius</span>
</code></pre></div></div>

<p>Set <code class="language-plaintext highlighter-rouge">enabled: false</code> to disable the canvas entirely (good for accessibility or battery-conscious deploys).</p>

<hr />

<h2 id="sidebar-widgets">Sidebar widgets</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">show_stats</span><span class="pi">:</span>         <span class="kc">true</span>   <span class="c1"># Site Stats card (articles, tags, visitors, words)</span>
  <span class="na">show_system_status</span><span class="pi">:</span> <span class="kc">true</span>   <span class="c1"># 4 live browser metrics</span>
  <span class="na">show_recent_posts</span><span class="pi">:</span>  <span class="kc">true</span>   <span class="c1"># Recent Posts list</span>
  <span class="na">show_categories</span><span class="pi">:</span>    <span class="kc">true</span>   <span class="c1"># Categories tag cloud</span>
  <span class="na">show_tags</span><span class="pi">:</span>          <span class="kc">true</span>   <span class="c1"># Tags cloud</span>
  <span class="na">recent_posts_count</span><span class="pi">:</span> <span class="m">5</span>      <span class="c1"># how many posts to show</span>
</code></pre></div></div>

<hr />

<h2 id="post-level-options">Post-level options</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">reading_time</span><span class="pi">:</span>       <span class="kc">true</span>   <span class="c1"># "X min read" in post header</span>
  <span class="na">word_count</span><span class="pi">:</span>         <span class="kc">true</span>   <span class="c1"># word count in post header</span>
  <span class="na">show_toc</span><span class="pi">:</span>           <span class="kc">true</span>   <span class="c1"># inline table of contents</span>
  <span class="na">toc_min_headings</span><span class="pi">:</span>   <span class="m">2</span>      <span class="c1"># minimum headings before TOC appears</span>
  <span class="na">code_copy_button</span><span class="pi">:</span>   <span class="kc">true</span>   <span class="c1"># Copy button on every code block</span>
  <span class="na">show_share_buttons</span><span class="pi">:</span> <span class="kc">true</span>   <span class="c1"># Twitter / LinkedIn / Email / Copy link</span>
  <span class="na">show_post_nav</span><span class="pi">:</span>      <span class="kc">true</span>   <span class="c1"># prev / next post navigation</span>
  <span class="na">show_related_posts</span><span class="pi">:</span> <span class="kc">true</span>   <span class="c1"># related posts section at bottom</span>
  <span class="na">show_reading_progress</span><span class="pi">:</span> <span class="kc">true</span> <span class="c1"># progress bar at top of viewport</span>
</code></pre></div></div>

<p>All of these can be overridden per post in front matter:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">My</span><span class="nv"> </span><span class="s">Post"</span>
<span class="na">toc</span><span class="pi">:</span>      <span class="kc">false</span>
<span class="na">comments</span><span class="pi">:</span> <span class="kc">false</span>
<span class="nn">---</span>
</code></pre></div></div>

<hr />

<h2 id="navigation">Navigation</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">navigation</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Home"</span>
      <span class="na">url</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">/"</span>
      <span class="na">icon</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">fas</span><span class="nv"> </span><span class="s">fa-home"</span>
    <span class="pi">-</span> <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">GitHub"</span>
      <span class="na">url</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">https://github.com/your-username"</span>
      <span class="na">icon</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">fab</span><span class="nv"> </span><span class="s">fa-github"</span>
      <span class="na">external</span><span class="pi">:</span> <span class="kc">true</span>   <span class="c1"># opens in new tab + shows ↗ icon</span>
</code></pre></div></div>

<p>Icons are <a href="https://fontawesome.com/icons">Font Awesome 6</a> class strings. Free solid icons use <code class="language-plaintext highlighter-rouge">fas</code>, brand icons use <code class="language-plaintext highlighter-rouge">fab</code>.</p>

<hr />

<h2 id="pagination">Pagination</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">paginate</span><span class="pi">:</span>      <span class="m">8</span>      <span class="c1"># posts per page on the homepage</span>
<span class="na">paginate_path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/page:num/"</span>
</code></pre></div></div>

<p>With 10 posts and <code class="language-plaintext highlighter-rouge">paginate: 8</code>, the homepage shows 8 posts and a <code class="language-plaintext highlighter-rouge">/page2/</code> link appears automatically.</p>

<hr />

<h2 id="defaults">Defaults</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">defaults</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">scope</span><span class="pi">:</span> <span class="pi">{</span> <span class="nv">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">"</span><span class="pi">,</span> <span class="nv">type</span><span class="pi">:</span> <span class="s2">"</span><span class="s">posts"</span> <span class="pi">}</span>
    <span class="na">values</span><span class="pi">:</span>
      <span class="na">layout</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">post"</span>
      <span class="na">author</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">50bvd"</span>
      <span class="na">comments</span><span class="pi">:</span> <span class="kc">true</span>
      <span class="na">toc</span><span class="pi">:</span>      <span class="kc">true</span>
</code></pre></div></div>

<p>Front matter values on individual posts override these defaults. Use defaults to avoid repeating boilerplate across every post file.</p>]]></content><author><name>50bvd</name></author><category term="documentation" /><category term="configuration" /><category term="_config.yml" /><category term="reference" /><summary type="html"><![CDATA[Complete annotated reference for every option in jekyll-infops-theme's _config.yml.]]></summary></entry><entry><title type="html">Getting Started with jekyll-infops-theme</title><link href="https://50bvd.github.io/jekyll-infops-theme/2025/01/01/welcome/" rel="alternate" type="text/html" title="Getting Started with jekyll-infops-theme" /><published>2025-01-01T10:00:00+01:00</published><updated>2025-01-01T10:00:00+01:00</updated><id>https://50bvd.github.io/jekyll-infops-theme/2025/01/01/welcome</id><content type="html" xml:base="https://50bvd.github.io/jekyll-infops-theme/2025/01/01/welcome/"><![CDATA[<p>Welcome to <strong>jekyll-infops-theme</strong> — a modern Jekyll theme built for DevOps, SysOps and infrastructure engineers who want a clean, fast, opinionated technical blog without fighting the toolchain.</p>

<h2 id="what-you-get-out-of-the-box">What you get out of the box</h2>

<ul>
  <li>CRT-style interactive terminal with 4 canvas games (Pong, Pac-Man, Snake, Tetris)</li>
  <li>Dark / light mode with zero flash on load</li>
  <li>Particle canvas background, cursor-reactive</li>
  <li>Prism.js syntax highlighting with Copy button on every block</li>
  <li>Auto-generated Table of Contents with scroll-aware active link</li>
  <li>Client-side search (no external service)</li>
  <li>Callout blocks (<code class="language-plaintext highlighter-rouge">note</code>, <code class="language-plaintext highlighter-rouge">tip</code>, <code class="language-plaintext highlighter-rouge">info</code>, <code class="language-plaintext highlighter-rouge">warning</code>, <code class="language-plaintext highlighter-rouge">danger</code>, <code class="language-plaintext highlighter-rouge">success</code>)</li>
  <li>Analytics: GoatCounter, GA4, Plausible, Umami — production only</li>
  <li>Comments via Utterances or Disqus</li>
  <li>GitHub Actions workflow for GitHub Pages — push to <code class="language-plaintext highlighter-rouge">main</code>, site deploys in ~2 min</li>
</ul>

<hr />

<h2 id="prerequisites">Prerequisites</h2>

<p><strong>Option A — Ruby:</strong></p>
<ul>
  <li>Ruby ≥ 3.1</li>
  <li>Bundler (<code class="language-plaintext highlighter-rouge">gem install bundler</code>)</li>
  <li>Git</li>
</ul>

<p><strong>Option B — Docker (recommended, no Ruby needed):</strong></p>
<ul>
  <li>Docker Desktop or Docker + Compose plugin</li>
</ul>

<hr />

<h2 id="installation">Installation</h2>

<h3 id="option-a--ruby-local">Option A — Ruby local</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/50bvd/jekyll-infops-theme.git my-blog
<span class="nb">cd </span>my-blog
bundle <span class="nb">install
</span>bundle <span class="nb">exec </span>jekyll serve <span class="nt">--livereload</span>
</code></pre></div></div>

<p>Open <code class="language-plaintext highlighter-rouge">http://localhost:4000</code>. The <code class="language-plaintext highlighter-rouge">--livereload</code> flag auto-refreshes the browser on any file save.</p>

<h3 id="option-b--docker">Option B — Docker</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/50bvd/jekyll-infops-theme.git my-blog
<span class="nb">cd </span>my-blog
docker compose up
</code></pre></div></div>

<p>Open <code class="language-plaintext highlighter-rouge">http://localhost:4000</code>. Sources are mounted as a volume — changes reflect instantly without rebuilding the image.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Rebuild after Gemfile changes</span>
docker compose up <span class="nt">--build</span>
</code></pre></div></div>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">First build</div>
    <div class="callout-content"><p>The first <code class="language-plaintext highlighter-rouge">docker compose up</code> downloads the Ruby base image and installs gems — this takes 2–3 minutes. Subsequent starts are instant thanks to the bundler cache volume.</p>
</div>
  </div>
</div>

<hr />

<h2 id="first-configuration">First configuration</h2>

<p>Open <code class="language-plaintext highlighter-rouge">_config.yml</code> and replace the <code class="language-plaintext highlighter-rouge">YOUR_*</code> placeholders:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">title</span><span class="pi">:</span>       <span class="s2">"</span><span class="s">My</span><span class="nv"> </span><span class="s">InfOps</span><span class="nv"> </span><span class="s">Blog"</span>
<span class="na">tagline</span><span class="pi">:</span>     <span class="s2">"</span><span class="s">DevOps</span><span class="nv"> </span><span class="s">·</span><span class="nv"> </span><span class="s">SysOps</span><span class="nv"> </span><span class="s">·</span><span class="nv"> </span><span class="s">Infrastructure"</span>
<span class="na">url</span><span class="pi">:</span>         <span class="s2">"</span><span class="s">https://your-username.github.io"</span>

<span class="na">author</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span>   <span class="s2">"</span><span class="s">Your</span><span class="nv"> </span><span class="s">Name"</span>
  <span class="na">github</span><span class="pi">:</span> <span class="s2">"</span><span class="s">your-username"</span>

<span class="c1"># Terminal boot screen</span>
<span class="na">theme_config</span><span class="pi">:</span>
  <span class="na">terminal_user</span><span class="pi">:</span> <span class="s2">"</span><span class="s">user@myblog"</span>
  <span class="na">terminal_boot</span><span class="pi">:</span>
    <span class="na">user</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">loup"</span>
    <span class="na">host</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">myblog"</span>
    <span class="na">os</span><span class="pi">:</span>    <span class="s2">"</span><span class="s">AlmaLinux</span><span class="nv"> </span><span class="s">10"</span>
    <span class="na">shell</span><span class="pi">:</span> <span class="s2">"</span><span class="s">bash</span><span class="nv"> </span><span class="s">5.2"</span>
    <span class="na">role</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">SysOps"</span>
    <span class="na">motd</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">Type</span><span class="nv"> </span><span class="s">/help</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">commands."</span>
</code></pre></div></div>

<div class="callout callout-info" role="note">
  <div class="callout-icon">
    <i class="fas fa-info-circle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Live reload &amp; _config.yml</div>
    <div class="callout-content"><p>Jekyll does not hot-reload <code class="language-plaintext highlighter-rouge">_config.yml</code> — you need to restart <code class="language-plaintext highlighter-rouge">jekyll serve</code> after any change to this file. All other files (<code class="language-plaintext highlighter-rouge">_posts/</code>, <code class="language-plaintext highlighter-rouge">_includes/</code>, SCSS…) reload automatically.</p>
</div>
  </div>
</div>

<hr />

<h2 id="writing-your-first-post">Writing your first post</h2>

<p>Create <code class="language-plaintext highlighter-rouge">_posts/YYYY-MM-DD-my-post.md</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">post</span>
<span class="na">title</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">Deploying</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">Proxmox</span><span class="nv"> </span><span class="s">cluster</span><span class="nv"> </span><span class="s">—</span><span class="nv"> </span><span class="s">step</span><span class="nv"> </span><span class="s">by</span><span class="nv"> </span><span class="s">step"</span>
<span class="na">date</span><span class="pi">:</span>   <span class="s">2025-03-01 09:00:00 +0100</span>
<span class="na">tags</span><span class="pi">:</span>   <span class="pi">[</span><span class="nv">proxmox</span><span class="pi">,</span> <span class="nv">virtualization</span><span class="pi">,</span> <span class="nv">homelab</span><span class="pi">]</span>
<span class="na">categories</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">infrastructure</span><span class="pi">]</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">A</span><span class="nv"> </span><span class="s">practical</span><span class="nv"> </span><span class="s">guide</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">bootstrapping</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">3-node</span><span class="nv"> </span><span class="s">Proxmox</span><span class="nv"> </span><span class="s">VE</span><span class="nv"> </span><span class="s">cluster."</span>
<span class="na">toc</span><span class="pi">:</span> <span class="kc">true</span>
<span class="nn">---</span>

<span class="s">Your Markdown content here.</span>
</code></pre></div></div>

<div class="callout callout-warning" role="note">
  <div class="callout-icon">
    <i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">Date in filename vs front matter</div>
    <div class="callout-content"><p>The date in the filename (<code class="language-plaintext highlighter-rouge">2025-03-01-my-post.md</code>) must match the <code class="language-plaintext highlighter-rouge">date:</code> field in front matter. A mismatch causes Jekyll to silently skip the file or publish it at the wrong URL.</p>
</div>
  </div>
</div>

<p>The post appears on the homepage immediately after a rebuild. Tags and categories are indexed automatically.</p>

<hr />

<h2 id="deploying-to-github-pages">Deploying to GitHub Pages</h2>

<p>The <code class="language-plaintext highlighter-rouge">.github/workflows/pages.yml</code> workflow handles everything:</p>

<ol>
  <li>Create a public GitHub repository</li>
  <li>Push: <code class="language-plaintext highlighter-rouge">git push -u origin main</code></li>
  <li>Go to <strong>Settings → Pages → Source → GitHub Actions</strong></li>
  <li>The workflow triggers on every push to <code class="language-plaintext highlighter-rouge">main</code></li>
  <li>Site is live at <code class="language-plaintext highlighter-rouge">https://your-username.github.io/repo-name/</code> in ~2 minutes</li>
</ol>

<div class="callout callout-tip" role="note">
  <div class="callout-icon">
    <i class="fas fa-lightbulb" aria-hidden="true"></i>
  </div>
  <div class="callout-body">
    <div class="callout-title">User/org site</div>
    <div class="callout-content"><p>For a root site at <code class="language-plaintext highlighter-rouge">your-username.github.io</code>, name the repo exactly <code class="language-plaintext highlighter-rouge">your-username.github.io</code>. Leave <code class="language-plaintext highlighter-rouge">baseurl</code> empty in <code class="language-plaintext highlighter-rouge">_config.yml</code>.</p>
</div>
  </div>
</div>

<hr />

<h2 id="project-tour">Project tour</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll-infops-theme/
├── _config.yml          ← all theme options
├── _data/
│   ├── theme.yml        ← colors, fonts, layout (no SCSS needed)
│   └── social.yml       ← footer social links
├── _posts/              ← your articles (Markdown)
├── _sass/               ← 6 SCSS modules (Dart Sass @use)
├── assets/js/
│   ├── modules/         ← 12 JS modules (search, TOC, canvas…)
│   └── terminal-commands/  ← 8 terminal commands + games
└── pages/               ← about, archives, tags, search, test
</code></pre></div></div>

<p>Check the <strong>documentation posts</strong> (this series) for a deep dive into each subsystem. Type <code class="language-plaintext highlighter-rouge">/help</code> in the terminal for the full command list.</p>]]></content><author><name>50bvd</name></author><category term="documentation" /><category term="setup" /><category term="installation" /><category term="quickstart" /><category term="docker" /><category term="github-pages" /><summary type="html"><![CDATA[Install and configure jekyll-infops-theme in under 5 minutes — Ruby, Docker, or fork directly on GitHub.]]></summary></entry></feed>