<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://csswitch.github.io/jekyll-galaxy-theme/feed.xml" rel="self" type="application/atom+xml" /><link href="https://csswitch.github.io/jekyll-galaxy-theme/" rel="alternate" type="text/html" /><updated>2026-05-12T20:27:33+00:00</updated><id>https://csswitch.github.io/jekyll-galaxy-theme/feed.xml</id><title type="html">Galaxy Theme</title><subtitle>A deep-space Jekyll theme with starfield background, constellation navigation, and nebula accents.</subtitle><author><name>Your Name</name><email>you@example.com</email></author><entry><title type="html">Building a Canvas Starfield — requestAnimationFrame Deep Dive</title><link href="https://csswitch.github.io/jekyll-galaxy-theme/2024/01/22/canvas-starfield-tutorial/" rel="alternate" type="text/html" title="Building a Canvas Starfield — requestAnimationFrame Deep Dive" /><published>2024-01-22T00:00:00+00:00</published><updated>2024-01-22T00:00:00+00:00</updated><id>https://csswitch.github.io/jekyll-galaxy-theme/2024/01/22/canvas-starfield-tutorial</id><content type="html" xml:base="https://csswitch.github.io/jekyll-galaxy-theme/2024/01/22/canvas-starfield-tutorial/"><![CDATA[<p>The starfield in Galaxy Theme is ~80 lines of vanilla JS. Here’s exactly how it works.</p>

<h2 id="the-architecture">The Architecture</h2>

<p>Three principles drive the implementation:</p>

<ol>
  <li><strong>Single canvas element</strong> — never create DOM nodes per star</li>
  <li><strong>requestAnimationFrame</strong> — sync with the display refresh rate</li>
  <li><strong>Per-star state</strong> — each star has its own twinkle phase and speed</li>
</ol>

<h2 id="star-data-model">Star Data Model</h2>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">randomStar</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">{</span>
    <span class="na">x</span><span class="p">:</span>     <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="nx">W</span><span class="p">,</span>
    <span class="na">y</span><span class="p">:</span>     <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="nx">H</span><span class="p">,</span>
    <span class="na">r</span><span class="p">:</span>     <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mf">1.4</span> <span class="o">+</span> <span class="mf">0.2</span><span class="p">,</span>     <span class="c1">// radius 0.2–1.6px</span>
    <span class="na">alpha</span><span class="p">:</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mf">0.7</span> <span class="o">+</span> <span class="mf">0.2</span><span class="p">,</span>     <span class="c1">// base brightness</span>
    <span class="na">speed</span><span class="p">:</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="mf">0.003</span> <span class="o">+</span> <span class="mf">0.001</span><span class="p">,</span> <span class="c1">// twinkle speed</span>
    <span class="na">phase</span><span class="p">:</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</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="c1">// initial phase offset</span>
    <span class="na">depth</span><span class="p">:</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span>                   <span class="c1">// parallax depth 0=far, 1=close</span>
  <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">phase</code> offset is critical — without it, all stars twinkle in sync, which looks fake.</p>

<h2 id="the-draw-loop">The Draw Loop</h2>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">t</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

<span class="kd">function</span> <span class="nx">draw</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">ctx</span><span class="p">.</span><span class="nx">clearRect</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="nx">t</span> <span class="o">+=</span> <span class="mf">0.016</span><span class="p">;</span> <span class="c1">// ~60fps time increment</span>

  <span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">stars</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</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="nx">stars</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>

    <span class="c1">// Twinkle: sine wave modulates the base alpha</span>
    <span class="kd">var</span> <span class="nx">twinkle</span> <span class="o">=</span> <span class="nx">s</span><span class="p">.</span><span class="nx">alpha</span> <span class="o">*</span> <span class="p">(</span><span class="mf">0.75</span> <span class="o">+</span> <span class="mf">0.25</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sin</span><span class="p">(</span><span class="nx">t</span> <span class="o">*</span> <span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">speed</span> <span class="o">*</span> <span class="mi">200</span><span class="p">)</span> <span class="o">+</span> <span class="nx">s</span><span class="p">.</span><span class="nx">phase</span><span class="p">));</span>

    <span class="c1">// Parallax: deeper stars move more on scroll</span>
    <span class="kd">var</span> <span class="nx">dy</span> <span class="o">=</span> <span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">y</span> <span class="o">+</span> <span class="nx">scrollY</span> <span class="o">*</span> <span class="nx">s</span><span class="p">.</span><span class="nx">depth</span> <span class="o">*</span> <span class="mf">0.04</span><span class="p">)</span> <span class="o">%</span> <span class="nx">H</span><span class="p">;</span>

    <span class="nx">ctx</span><span class="p">.</span><span class="nx">beginPath</span><span class="p">();</span>
    <span class="nx">ctx</span><span class="p">.</span><span class="nx">arc</span><span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> <span class="nx">dy</span><span class="p">,</span> <span class="nx">s</span><span class="p">.</span><span class="nx">r</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">ctx</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">rgba(255,255,255,</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">twinkle</span><span class="p">.</span><span class="nx">toFixed</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">)</span><span class="dl">'</span><span class="p">;</span>
    <span class="nx">ctx</span><span class="p">.</span><span class="nx">fill</span><span class="p">();</span>

    <span class="c1">// Larger stars get a soft purple halo</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">r</span> <span class="o">&gt;</span> <span class="mf">1.0</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">ctx</span><span class="p">.</span><span class="nx">beginPath</span><span class="p">();</span>
      <span class="nx">ctx</span><span class="p">.</span><span class="nx">arc</span><span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> <span class="nx">dy</span><span class="p">,</span> <span class="nx">s</span><span class="p">.</span><span class="nx">r</span> <span class="o">*</span> <span class="mf">2.5</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">ctx</span><span class="p">.</span><span class="nx">fillStyle</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">rgba(200,180,255,</span><span class="dl">'</span> <span class="o">+</span> <span class="p">(</span><span class="nx">twinkle</span> <span class="o">*</span> <span class="mf">0.15</span><span class="p">).</span><span class="nx">toFixed</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">)</span><span class="dl">'</span><span class="p">;</span>
      <span class="nx">ctx</span><span class="p">.</span><span class="nx">fill</span><span class="p">();</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="nx">requestAnimationFrame</span><span class="p">(</span><span class="nx">draw</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="parallax-on-scroll">Parallax on Scroll</h2>

<p>The parallax is simple: each star has a <code class="language-plaintext highlighter-rouge">depth</code> value (0–1). On scroll, we offset the star’s y position by <code class="language-plaintext highlighter-rouge">scrollY * depth * factor</code>. Stars with <code class="language-plaintext highlighter-rouge">depth=0</code> don’t move (far away), stars with <code class="language-plaintext highlighter-rouge">depth=1</code> move the most (close).</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">scrollY</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">scroll</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
  <span class="nx">scrollY</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">scrollY</span><span class="p">;</span>
<span class="p">},</span> <span class="p">{</span> <span class="na">passive</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span> <span class="c1">// passive = no jank</span>
</code></pre></div></div>

<p>Using <code class="language-plaintext highlighter-rouge">passive: true</code> is essential — it tells the browser your scroll handler won’t call <code class="language-plaintext highlighter-rouge">preventDefault()</code>, enabling smoother scrolling.</p>

<h2 id="resizing">Resizing</h2>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">resize</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">W</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span>  <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span><span class="p">;</span>
  <span class="nx">H</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">innerHeight</span><span class="p">;</span>
<span class="p">}</span>

<span class="nb">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">resize</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
  <span class="nx">resize</span><span class="p">();</span>
  <span class="nx">initStars</span><span class="p">();</span> <span class="c1">// re-randomize after resize</span>
<span class="p">},</span> <span class="p">{</span> <span class="na">passive</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span>
</code></pre></div></div>

<p>Always call <code class="language-plaintext highlighter-rouge">initStars()</code> after resize — otherwise stars cluster at the edges of the old canvas size.</p>

<h2 id="performance-numbers">Performance Numbers</h2>

<p>On a MacBook M1, 320 stars run at &lt;0.1ms per frame. You can push to 1000+ stars before noticing any impact. The bottleneck is canvas <code class="language-plaintext highlighter-rouge">arc()</code> calls, not JS logic.</p>

<p>To profile: open Chrome DevTools → Performance tab → record while scrolling.</p>]]></content><author><name>Your Name</name><email>you@example.com</email></author><category term="javascript" /><category term="canvas" /><category term="animation" /><category term="tutorial" /><summary type="html"><![CDATA[How to build a performant twinkling starfield with parallax scroll using vanilla JS and the HTML5 Canvas API.]]></summary></entry><entry><title type="html">Welcome to Galaxy Theme — Deep Space Jekyll</title><link href="https://csswitch.github.io/jekyll-galaxy-theme/2024/01/15/welcome-to-galaxy-theme/" rel="alternate" type="text/html" title="Welcome to Galaxy Theme — Deep Space Jekyll" /><published>2024-01-15T00:00:00+00:00</published><updated>2024-01-15T00:00:00+00:00</updated><id>https://csswitch.github.io/jekyll-galaxy-theme/2024/01/15/welcome-to-galaxy-theme</id><content type="html" xml:base="https://csswitch.github.io/jekyll-galaxy-theme/2024/01/15/welcome-to-galaxy-theme/"><![CDATA[<p>Welcome to <strong>Galaxy Theme</strong> — a Jekyll theme set in deep space. Stars twinkle in a real-time canvas engine, shooting stars streak across the background, and nebula clouds drift behind the content.</p>

<h2 id="what-makes-this-theme-different">What Makes This Theme Different</h2>

<p>Most dark themes just use a dark background. Galaxy Theme builds an actual space environment:</p>

<ul>
  <li><strong>Canvas starfield</strong> — 320 stars rendered via <code class="language-plaintext highlighter-rouge">requestAnimationFrame</code>, each with individual twinkle speed and parallax depth relative to scroll</li>
  <li><strong>CSS nebula layers</strong> — three animated radial gradients create atmospheric depth without images</li>
  <li><strong>Shooting stars</strong> — pure CSS <code class="language-plaintext highlighter-rouge">@keyframes</code> animations, three lanes across the viewport</li>
  <li><strong>Pulsar featured card</strong> — your latest post pulses with a ring animation, like a magnetar</li>
</ul>

<h2 id="quick-configuration">Quick Configuration</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">galaxy</span><span class="pi">:</span>
  <span class="na">nebula</span><span class="pi">:</span> <span class="s2">"</span><span class="s">purple"</span>   <span class="c1"># purple | blue | gold | cyan | red</span>
  <span class="na">stars</span><span class="pi">:</span> <span class="s2">"</span><span class="s">normal"</span>    <span class="c1"># sparse | normal | dense</span>
  <span class="na">shooting_stars</span><span class="pi">:</span> <span class="no">true</span>
  <span class="na">comet_progress</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>

<p>Change <code class="language-plaintext highlighter-rouge">nebula: "blue"</code> for a blue-white star cluster feel. Change to <code class="language-plaintext highlighter-rouge">"gold"</code> for a warm nebula aesthetic.</p>

<h2 id="writing-posts">Writing Posts</h2>

<p>Create files in <code class="language-plaintext highlighter-rouge">_posts/</code> with filename <code class="language-plaintext highlighter-rouge">YYYY-MM-DD-title.md</code>:</p>

<div class="language-markdown 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">My</span><span class="nv"> </span><span class="s">Deep</span><span class="nv"> </span><span class="s">Dive"</span>
<span class="na">date</span><span class="pi">:</span> <span class="s">2024-01-15</span>
<span class="na">tags</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">tutorial</span><span class="pi">,</span> <span class="nv">css</span><span class="pi">]</span>
<span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Short</span><span class="nv"> </span><span class="s">excerpt</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">post</span><span class="nv"> </span><span class="s">cards."</span>
<span class="nn">---</span>

Content goes here...
</code></pre></div></div>

<p>The first post automatically gets the “Pulsar” featured treatment on the home page.</p>

<h2 id="performance">Performance</h2>

<p>The canvas starfield uses <code class="language-plaintext highlighter-rouge">requestAnimationFrame</code> with a single canvas — no DOM elements per star. On a mid-range device: ~0.1ms per frame for 320 stars.</p>

<p>Shooting stars use CSS animations only — zero JS.</p>]]></content><author><name>Your Name</name><email>you@example.com</email></author><category term="design" /><category term="space" /><category term="getting-started" /><summary type="html"><![CDATA[A deep-space Jekyll theme with canvas starfield, shooting stars, nebula backgrounds, and a comet reading progress bar.]]></summary></entry></feed>