In 2011 I was posting sky textures to Facepunch with raw Dropbox links. That mess grew into Source Skyboxes — a central download hub for my HDR skies. Today it's rebuilt in Next.js + Tailwind and served as a static site on GitHub Pages. ~70 skies made over the years; the curated library keeps growing.
Origin: Why I Started This
When I started mapping for Source I hung out on the old Facepunch Garry's Mod forums and kept making new skies in Terragen. The hard part wasn't the art—it was sharing them. Every release meant another forum post, another Dropbox link, and another round of "did I name these correctly?" I wanted a single place people could browse, grab downloads, and trust the files wouldn't vanish. That desire for a better distribution hub is what grew into Source Skyboxes.
I didn't have proper hosting. Releases lived on Dropbox public links. I'd post a new sky in a Facepunch thread with a download URL, hope I didn't move the file, and repeat for the next one. Eventually a tiny HTML "hub" page (thanks to a high-school HTML/CSS class) listed the links. It was ugly. It worked.
Behind the scenes I eventually wrote automation to standardize file names and build proper Source-ready packs. That tooling has since grown into a separate internal project (working name: CloudPacker). I'll cover the build pipeline in its own write-up; this page is about the library itself and how I've hosted it over the years.
Dropbox Era (2011-2016)
Manual HTML & never-ending link headaches
Back in the Dropbox days, I didn't have a real web host — just a public folder full of hand-edited HTML files. Each sky category (Sunny, Cloudy, Overcast, etc.) was its own copy-and-paste job: wrap everything in a <div id="wrapper">, slap on a header, then a grid of <div class="image-box"> entries. One stray capital letter in `Sky_cloudy003_hdr.7z` (instead of the lowercase filename) and that link would 404, causing a flood of complaints on the forums.

Updating a sky meant touching three separate spots:
- The thumbnail path in the
<img> - The
<a href>download link - The visible title inside
<span class="categorytitle">
Repeat that for ~10 skies per category and you see the problem—one missed character and half the list broke. No templating, no CI, just copy-paste discipline (or lack thereof).
<div class="image-box">
<span class="categorytitle">sky_cloudy001</span>
<div class="image">
<img src="<DROPBOX>/Screenshots/sky_cloudy001_ingame.jpg" width="425" height="240" />
</div>
<div class="downloader">
<a href="<DROPBOX>/Textures/sky_cloudy001.rar">
<img src="images/download_icon2.png" width="139" height="82" />
</a>
</div>
</div>- Case-sensitive URLs vs. Windows filenames.
- No shared template. Any layout tweak meant editing every file.
- Dropbox quirks. Moving/renaming regenerated public URLs and killed old links.
- Zero validation. I learned about bad links only after complaints.
By 2016 these pitfalls finally pushed me to abandon Dropbox and swap script-free HTML for a GitHub Pages repo.
Static GitHub Pages Era (2016-2022)
Same old HTML, brand-new home in version control
In 2016 I finally ditched Dropbox and pushed the site to aGitHub Pages branch. It felt magical — real version control and a predictable URL — but under the hood nothing changed: every category still lived in its own .html file and every link still had to be renamed by hand.
The migration boiled down to a global search-and-replace:
<!-- Dropbox link -->
<a href="https://dl.dropboxusercontent.com/u/12345/Text.../sky_cloudy001.rar">
<!-- replaced by -->
<!-- GitHub Pages link -->
<a href="https://jacobdeanr.github.io/Source_Skyboxes/sky_cloudy001.rar">Key pain points persisted:
- Manual link rewrites. Every file moved from
Dropbox/Textures/…to the repo root; typos were still rampant. - No templating. Layout tweaks required opening 5 category pages and copy - pasting changes.
- Zero build step. GitHub Pages simply served whatever HTML I committed — no asset optimization, no validation, no automation.
- Case-sensitive URLs Exactly the same 404 trap as the Dropbox days.
Version control at least gave me history and diff-view, but after six years of copy-paste fatigue I needed something smarter. That "something" arrived with a YAML catalog and Jekyll — my first real build step.
JSON-Driven Era (2022 - mid-2024)
One index.html · vanilla JS · categories.json catalog
By 2022 I ditched the pile of category pages and rewrote the site around asingle index.html. A helper script pulled acategories.json catalog at runtime and rendered the gallery plus on-the-fly detail views. No more copy-pasting HTML; filters for "Morning / Afternoon / Night" and "Clear / Cloudy / Overcast" came along for the ride.

<!-- index.html (excerpt) -->
<select id="time-select">
<option value="">All Times</option>
<option value="Morning">Morning</option>
<option value="Afternoon">Afternoon</option>
<option value="Evening">Evening</option>
</select>
<div id="skybox-container"></div>
<script defer src="fetch_skyboxes.js"></script>// fetch_skyboxes.js (simplified)
document.addEventListener("DOMContentLoaded", async () => {
const catalog = await fetch("categories.json").then(r => r.json());
renderGallery(catalog);
});categories.json is the single source of truth: each key is a 7z archive, and the nested object drives gallery filters, and detail page information, such as: author, publish date, license, community maps, and lighting values.
{
"sky_cloudy001_hdr.7z": {
"author": "Jacob Robbins",
"publishDate": "2012-06-23",
"license": "CC BY 4.0",
"categories": ["Afternoon", "Cloudy"],
/* Community maps that use this sky */
"steamMaps": [
{ "name": "...", "url": "..." }
],
/* Hammer lighting values — surfaced in the detail view */
"sunParameters": {
"sunAngle": "0 30 0",
"pitch": "-65",
"brightness": [254, 249, 218, 600],
"ambience": [ 77, 96, 130, 400]
},
"fogParameters": {
"primaryFogColor": [ 77, 96, 130 ],
"secondaryFogColor": [ 30, 42, 58 ]
}
},
"sky_cloudy002_hdr.7z": { /* same shape, different values */ }
/* …more skies… */
}What got better:
- Single source of truth. All metadata lived in one JSON file.
- Zero manual HTML. New sky? Just append JSON and push.
- Filters & detail pages. Users could drill down without page reloads.
…and what still hurt:
- SEO blind spot. Search engines missed JS-rendered content.
- Performance tax. Full catalog downloaded on every visit.
- Hand-rolled layout. Header, footer, and cards still hard-coded.
By summer 2024 it was clear I needed a real static-site generator with templates and a build step, setting the stage for the Jekyll migration later that year.
Jekyll Migration (2024 - Early 2025)
YAML catalog · Ruby code-gen · real templates & SEO meta
The JSON-driven single-page setup worked, but I wanted proper URLs (/sky/sky_cloudy003), automatic HTML generation, and metadata that search engines could actually read. Enter Jekyll: a static-site generator that converts Markdown (with YAML front-matter) into plain HTML during the GitHub Actions build.
I swapped categories.json for a more legible_data/categories.yaml file:
# _data/categories.yaml (excerpt)
sky_cloudy003_hdr.7z:
author: Jacob Robbins
publishDate: "2012-07-03"
categories: [Afternoon, Cloudy]
steamMaps:
- { name: "...", url: "..." }
sunParameters:
sunAngle: "0 30 0"
pitch: "-65"
brightness: [254, 249, 218, 600]
ambience: [ 77, 96, 130, 400]
fogParameters:
primaryFogColor: [ 77, 96, 130 ]
secondaryFogColor: [ 30, 42, 58 ]A tiny Ruby helper converts that YAML into one Markdown file per skybox (_skyboxes/*.md) during the build:
# build_skyboxes.rb (simplified)
require "yaml"
data = YAML.load_file("_data/categories.yaml")
data.each do |file, meta|
name = File.basename(file, ".7z")
File.write("_skyboxes/#{name}.md", <<~MD)
---
layout: skybox
title: "#{name} - Skybox Texture"
skybox_name: #{name}
#{meta.to_yaml.gsub(/^/, " ")}
download_link: "<raw_github_url>/#{file}"
---
MD
endThe Jekyll layouts (`layout: skybox` and `layout: default`) gave me:
- Per-sky pages. Each skybox now lives at its own stable URL.
- SEO meta tags. Open Graph, Twitter cards, and per-page descriptions.
- Better 404s & redirects. Old Dropbox / JSON links funnel to the new pages.
- GitHub Actions build. One job runs the Ruby script, then
jekyll build, then pushes togh-pages.
Immediate wins over the JSON era:
- No client-side catalog download ≈ faster first paint.
- Search engines finally see every skybox.
- Layouts & partials mean one-line tweaks instead of copy-paste edits.
Downsides? YAML vs. JSON was mostly a stylistic pick, and Ruby in CI was one more dependency. But it got the job done and set the stage for the later Next.js + Tailwind rebuild.
Next.js + Tailwind Rebuild (2025 - now)
React components · static export · GitHub Releases for heavy assets
Jekyll served its purpose, but I wanted to sharpen my modern TS/JS skills, give the site a cleaner desktop layout, and keep a clear path to self-hosting later. Rebuilding in Next.js + Tailwind CSS let me break the UI into reusable components, enjoy TypeScript safety, and still ship a fully static bundle that GitHub Pages can host today—or any server I point it at tomorrow (next export in CI).

The old monolithic YAML file is gone—each sky now lives in its own JSON file inside/data/. Tiny diffs, type-checked at build time.
// /data/sky_wildfire01_hdr.json (excerpt)
{
"author": "Jacob Robbins",
"publishDate": "2024-08-11",
"categories": ["Evening", "Cloudy"],
"downloads": {
"source": { "file": "sky_wildfire01_hdr.7z", "format": "Source engine" },
"exr": { "file": "sky_wildfire01_hdr.exr", "format": "EXR" }
}
}// app/skyboxes/[slug]/page.tsx -- server component
export default function Page({ params }: { params: { slug: string } }) {
const { slug } = params;
const skyboxData = getMeta(slug); // pulls JSON for this sky
const previewCount = getPreviewCount(slug); // how many thumbnail previews exist
return (
<SkyboxClient
slug={slug}
skyboxData={skyboxData}
previewCount={previewCount}
/>
}// app/skyboxes/[slug]/skybox-client.tsx -- client component (simplified)
export default function SkyboxClient({ slug, skyboxData, previewCount }) {
const imgBase = withBase({slug}/images);
const [activePreview, setActivePreview] = useState(1);
return (
<section className="space-y-8">
{/* Hero image */}
<img
src={previews/{activePreview}.webp}
alt={\${slug} preview\}
className="w-full rounded-xl"
/>
{/* Thumbnail strip */}
<div className="flex gap-2">
{Array.from({ length: previewCount }, (_, i) => i + 1).map((n) => (
<button key={n} onClick={() => setActivePreview(n)}>
<img
src={imgBase}/previews/{n}.webp}
alt={thumb {n}}
className="h-16 w-16 rounded-lg"
/>
</button>
))}
</div>
{/* Downloads and environment tables */}
<DownloadButtons downloads={skyboxData.downloads} />
<EnvTable sun={skyboxData.sunParameters} fog={skyboxData.fogParameters} />
</section>
);
}Downloads now live in GitHub Releases. Each skybox gets two assets (.7z & .exr). (maybe more in the future?) With a 2 GiB/file and 1 000-asset cap, that's ~2 TB of free storage—plenty of headroom for my 70-odd skies.
What improved over the Jekyll build:
- Clean, desktop-first layout that still holds up on phones. Tailwind grids/flex fixed the old mis-alignments without chasing pure mobile-first design.
- True component reuse. Cards, tables, and CTA rows share one TSX source; fixes propagate site-wide.
- Type-safe data. Each JSON file passes a Zod
SkyboxSchemain CI; malformed data blocks the build. - Sitemap + automated index. Helper scripts generate an SEO-friendly
sitemap.xmland keep the gallery list in sync. - Heavy assets off-loaded.
.7z/.exrbundles in Releases avoid the 2 GB GitHub Pages limit.
This rebuild also birthed CloudPacker—a utility that bundles, uploads, and writes the matching JSON. Not the article's focus, but it keeps my release workflow simple.
The design looks sharp, deployment is automated, and I'm future-proofed if I ever move to my own server.
Evolution Timeline
- 2011Dropbox + Raw HTMLDropbox links, hand made category pages.
- 2016Static GitHub PagesSame copy-pasted HTML, but now version-controlled; Dropbox links replaced with repo URLs.
- 2022JSON-Driven SPASingle index.html + vanilla JS; categories.json powers filters & on-the-fly detail pages.
- 2024Jekyll + YAML BuildRuby script turns YAML catalog into per-sky pages; templates, SEO meta, CI deploy.
- 2025Next.js + TailwindComponentized static export, mobile-friendly UI; per-sky JSON & assets served via GitHub Releases.
| Year | Milestone | Notes |
|---|---|---|
| 2011 | Dropbox + Raw HTML | Dropbox links, hand made category pages. |
| 2016 | Static GitHub Pages | Same copy-pasted HTML, but now version-controlled; Dropbox links replaced with repo URLs. |
| 2022 | JSON-Driven SPA | Single index.html + vanilla JS; categories.json powers filters & on-the-fly detail pages. |
| 2024 | Jekyll + YAML Build | Ruby script turns YAML catalog into per-sky pages; templates, SEO meta, CI deploy. |
| 2025 | Next.js + Tailwind | Componentized static export, mobile-friendly UI; per-sky JSON & assets served via GitHub Releases. |
