DenMotion
Building a film and photography brand from scratch. The naming process, the hybrid site architecture, and the cinematic portfolio that came out of it.
I didn’t set out to build a website. It started much simpler than that. I just felt like I needed to name something.
The photos and videos came first, and they built up without me planning it. I bought the Sony A6700 originally for AWS demos, just to improve the quality of what I was already doing. Then I started taking photos properly, then editing them in Lightroom, then getting into video, then Premiere Pro, then going back to Lightroom again, and it turned into this loop I kept coming back to without really thinking about it too much.
Six Months of Stacking
It began with my first trip to Bodrum where I learned the fundamentals of shooting. Exposure, shutter speed, focal lengths, when to shoot, when not to shoot. I was figuring out the camera for the first time in a place with good light and interesting subjects, and everything felt like it was worth pointing the lens at.
When I came back to London I started experimenting with gym filming, especially in low light. The gym is a completely different environment to Bodrum. Overhead fluorescents, no natural light, movement everywhere. I was testing how the A6700 handles those conditions, what settings work, what falls apart, how lighting changes in a space I go to every day.
Then Montenegro for a full week of photography. Shooting every day, culling in Lightroom, editing properly, trying to understand what makes an image work instead of just taking photos and leaving them on the drive. That trip was where the editing workflow started to take shape, the culling process, the preset building, the export pipeline.
Back in the UK, more gym filming. Then Bodrum again, this time focused on video. Thinking about clips rather than frames, how movement looks, how sequences come together, when to cut. Throughout all of this, Lightroom became part of the daily routine. Every trip generated hundreds of files that needed culling, editing, and organising. The photography and the video were developing in parallel, feeding into each other.
| Period | Focus | What Changed |
|---|---|---|
| Bodrum (first trip) | Photography fundamentals | Exposure, composition, learning the camera |
| London gym sessions | Low light video | Manual settings, movement, consistent conditions |
| Montenegro | Photography workflow | Culling, Lightroom editing, preset building |
| London gym sessions | Gym content | Filming people, lighting, intent |
| Bodrum (second trip) | Video | Clips, sequences, Premiere Pro, colour |
Then HYROX at Olympia London. A PT at my gym told me he was competing and I said I’d come and get footage of him. That was the first time I showed up with intent. Not filming for myself, not experimenting, not learning. Filming for someone else with a specific outcome in mind.
He was very happy with the content I created. Asked me if I had socials. I’m not a fan of social media and I don’t have it. I told him my website was coming.
And that’s when I realised it actually was that time. I was no longer an amateur messing around with a camera. I had six months of stacking behind me, enough work to show, and someone had already asked where they could find it. This shouldn’t just sit on my hard drive or be scattered across random places. It’s time to put this somewhere properly, start showcasing it, and realistically start monetising it as well.
The niche I keep coming back to after all this experimenting is gym content. I’m someone who trains seriously, I’m familiar with the machines and the movements, I know what I’m doing in that environment, and everyone is trying to be a fitness influencer right now. There’s a market for someone who can actually film this stuff well.
What this post covers
The naming process, the decision between two finalists, and the full site architecture behind denmotion.com. This is a brand build documented from start to finish.
Starting with the Name
This needed its own identity. The work feels different. Sometimes it’s a single frame, sometimes it’s movement, sometimes it’s somewhere in between. So instead of thinking about a website, or pages, or structure, I started with the only thing that actually mattered at that point, which was the name.
The website comes after that.
For me the name is the most important step. The domain is the brand, and I’ve seen what happens when people skip this. Search “freelance videographer London” and you’ll find domains like freelancevideographerlondon.co.uk. Long, functional, forgettable. That’s fine for SEO, but it’s not a brand. I needed something short, clean, and impactful, something that sounds right when you say it out loud and looks right as a URL. I would only consider a .com. It’s the staple. Simple, short, clean. I already knew how to build the site. I’ve done it before. The name was the hard part, and I knew it would take time.
It took about two days of going back and forth.
I went through loads of names in that time. Different directions, some based on words like frame, film, visuals, some more abstract, some using “Den” as a base, some not. A lot of them sounded good in my head at first, but when I actually said them out loud they just didn’t hold, or I could tell straight away I’d have to repeat them or explain them every time.
| Name | Problem |
|---|---|
| DenLens | Too narrow, implies photography only |
| DenFrame | Same issue, static |
| DenCapture | Sounds like a product, not a brand |
| DenFilms | Limits to video only |
| DenPhotos | Limits to photography only |
| ArcMotion | Domain taken |
And even when something did feel decent, most of the time the domain was already gone, which made the whole process slower because you don’t just pick a name, you have to check if it actually exists as something you can buy.
So that two-day period was basically the same loop over and over. Searching names, saying them out loud, checking domains, closing tabs, then coming back to them later thinking maybe I was too quick to dismiss them.
The domain test
A name that sounds good in your head, but doesn’t exist as a .com you can buy is not a name yet. I lost ArcMotion this way. Sounded clean, had a good rhythm, felt like a proper brand, but the domain was taken.
The one word I kept coming back to through all of that was motion. I liked it straight away and it felt closer to what I was actually doing, because this wasn’t just photography and it wasn’t just video either. It was both, and more importantly it was that space in between where things move, where clips come together, where a still frame becomes part of something else.
At one point I landed on DarkMotion. I liked the sound of it. Checked the domain and it was listed for sale. I sent an offer of $250, which felt reasonable for a short .com that nobody was using.
The reply came back: $376,888 USD.
I responded: “Can I get a spatula with that?”
He didn’t get the joke. Anyways, delusional.
So I kept motion and started pairing it with other things. That’s when DenMotion came up. That was the first time it actually felt right, not just in my head, but when I said it out loud as well.
Once I had that, everything narrowed down quite quickly. I stopped looking at loads of different directions and it really just came down to two options.
VisualDen and DenMotion.
The Decision
VisualDen made sense straight away, which is why people liked it. It connects to DigitalDen, DinnertimeDen, everything lines up and it feels consistent. If someone sees it they understand it immediately without needing to think about it.
Most of my friends preferred that one.
At the same time, that’s what made me hesitate. It felt like I was still tying this new thing back to what I’d already built, like it was another branch instead of something separate.
DenMotion didn’t explain itself as quickly. You have to sit with it for a second. When I said it out loud it just sounded better, it had more movement in it, and it felt closer to what I’m actually doing now.
| VisualDen | DenMotion | |
|---|---|---|
| Immediate clarity | High, connects to existing brands | Lower, needs a moment |
| Independence | Feels like a branch of DigitalDen | Feels like its own thing |
| Scope | Implies visual content broadly | Implies movement, film, photography |
| Sound | Functional | Has rhythm |
| Domain | Available | Available |
I kept going back and forth between the two. VisualDen made more sense. DenMotion felt better.
And I noticed as well, because most people preferred VisualDen, I leaned the other way. Not to be difficult, but it felt like that was the safer option, and DenMotion felt more like an actual decision.
Anyways, 1 April, I just went for it.
I searched denmotion.com on Route 53, it was available, and I bought it in two clicks for around $15. That still feels like a bargain considering how short and clean the domain is.
The brand is the domain
For me the domain comes first. Everything else, the site, the structure, the landing page, sits on top of it. Once denmotion.com was locked in, the rest became a lot easier.
Building the Site
Once the domain was locked in, the site came together quickly. I’ve built Jekyll sites before, so this part was straightforward.
The challenge was not building a site. It was merging two completely different website architectures into one project without them fighting each other. The landing page runs on the HTML5 UP Dimensions template with particles.js. The inner pages run on Jekyll Chirpy. These are two separate design systems with their own CSS, their own JavaScript, and their own opinions about layout. Getting them to coexist in the same repo without a single CSS conflict was the actual work.
I use the same Dimensions + particles.js setup on digitalden.cloud and I’ve had it there for about two years. I knew I wanted that same full-screen, dark, minimal entrance for DenMotion. Two buttons. Films and Photos. Nothing else.
Isolated Asset Architecture
The first thing I did was create a dedicated assets/landing/ folder inside the Jekyll project. This folder contains only the Dimensions files I actually need: the CSS, the JavaScript, the particles.json config, and overlay.png for the textured background.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assets/
├── landing/
│ ├── css/
│ │ ├── main.css ← Dimensions stylesheet
│ │ └── noscript.css
│ ├── js/
│ │ ├── particles.min.js
│ │ ├── app.js ← particles config loader
│ │ ├── jquery.min.js
│ │ ├── browser.min.js
│ │ ├── breakpoints.min.js
│ │ ├── util.js
│ │ └── main.js
│ ├── particles.json
│ └── images/
│ └── overlay.png
Everything from the old digitalden.cloud repo that I didn’t need — terminal scripts, Python files, PDFs, raw Sass files — I left behind. The new repo is clean.
Why asset isolation matters
Chirpy loads its own global stylesheet. Dimensions has its own. If both load on the same page, they fight over typography, spacing, colours, and layout. By keeping the Dimensions assets in/assets/landing/and only loading them on the landing page, the two systems never see each other.
The Standalone Layout
I created _layouts/landing.html as a completely standalone HTML document. This is the key decision that makes the whole thing work. The landing layout does not extend Chirpy’s default.html or page.html or anything else. It has its own <head>, its own <body>, and it loads only the Dimensions CSS and particles.js scripts.
All asset paths are wrapped in Jekyll’s relative_url filter so they resolve correctly when deployed to GitHub Pages. This is one of those things that works fine locally, but breaks on deploy if you use relative paths instead.
The root index.html uses this layout:
1
2
3
---
layout: landing
---
When Jekyll builds the site, the landing page gets the Dimensions template. Every other page gets Chirpy. No bleed between the two.
No CSS bleed
Visiting/films/or/photos/shows zero Dimensions styling. Visiting/shows zero Chirpy styling. I tested both directions. The two systems are completely independent.
The Landing Page
The landing page is full-screen, dark, centred, with particles animating in the background. The title sits in the middle with two buttons below it: Films and Photos.
I kept the structural elements from the Dimensions template, including the empty <div id="main"> and <footer id="footer">, because they are what the template uses to calculate vertical centering. Removing them pushes the content off-centre. I also commented out the social media icons and the contact button rather than deleting them. They are sitting in the code ready to be turned on when I need them.
What’s not on the landing page
No grid, no portfolio preview, no bio, no scrolling. The landing page is just an entry point. The work lives one click deeper.
Contact Form
I brought over the serverless contact form from digitalden.cloud. It runs on the same AWS architecture, API Gateway fronting a Lambda function, no page reload on submit. I removed contact from the landing page deliberately to encourage people to come into the Chirpy side of the site. The form lives on the Contact tab instead.
The CSS classes needed updating so the Send Message and Reset buttons match the transparent hover effect of the main navigation buttons. I also restored the spinning spanner animation that plays while a message is sending, which is a small detail, but it makes the form feel responsive.
Bridging the Two Systems
This is where the two architectures connect. When someone clicks Films or Photos on the landing page, they leave the Dimensions template and enter Chirpy’s layout system.
I created films.md and photos.md inside Chirpy’s _tabs/ folder. Each one has a FontAwesome icon (fas fa-video for Films, fas fa-camera for Photos) and a specific permalink. Chirpy picks these up automatically and generates the sidebar navigation.
| Tab | Icon | Permalink | What it shows |
|---|---|---|---|
| Films | fas fa-video | /films/ | Film projects |
| Photos | fas fa-camera | /photos/ | Photo projects |
The loop works in both directions. Clicking Films on the dark landing page transitions you into the clean Chirpy layout. Clicking the Home icon in Chirpy’s sidebar drops you back to the particles splash screen. The user never feels like they are leaving the site, even though underneath they are moving between two completely different layout engines.
graph TD
A["denmotion.com"] --> B["Landing Page<br/>_layouts/landing.html<br/>Dimensions + particles.js"]
B --> C["Films"]
B --> D["Photos"]
C --> E["Chirpy Layout<br/>_tabs/films.md<br/>fas fa-video"]
D --> F["Chirpy Layout<br/>_tabs/photos.md<br/>fas fa-camera"]
E --> G["Individual Film Project<br/>Chirpy post layout"]
F --> H["Individual Photo Project<br/>Chirpy post layout"]
E --> B
F --> B
style A fill:#1a1a2e,stroke:#e94560,color:#fff
style B fill:#1a1a2e,stroke:#e94560,color:#fff
style C fill:#0f3460,stroke:#e94560,color:#fff
style D fill:#0f3460,stroke:#e94560,color:#fff
style E fill:#16213e,stroke:#533483,color:#fff
style F fill:#16213e,stroke:#533483,color:#fff
style G fill:#16213e,stroke:#533483,color:#fff
style H fill:#16213e,stroke:#533483,color:#fff
What Was Built
| Component | What it does | Status |
|---|---|---|
assets/landing/ | Isolated Dimensions CSS, JS, particles config | Done |
_layouts/landing.html | Standalone layout, bypasses Chirpy entirely | Done |
index.html | Full-screen particles landing page with two buttons | Done |
_tabs/films.md | Films tab in Chirpy sidebar with fas fa-video icon | Done |
_tabs/photos.md | Photos tab in Chirpy sidebar with fas fa-camera icon | Done |
| Contact form | AWS serverless form on Contact tab | Done |
| CSS isolation | Zero bleed between Dimensions and Chirpy | Verified |
overlay.png | Restored textured background for particles | Done |
Deploying to GitHub Pages
With the site built locally, the next step was getting it live on denmotion.com.
Repo Setup
I created the repo under my existing DigitalDenCloud GitHub account. I initially named it denmotion, but since I’m using a custom domain the repo name doesn’t matter, so I kept it simple.
The Chirpy starter template comes with a GitHub Actions workflow in .github/workflows/ that handles the build. First push triggered it automatically, but it failed because GitHub Pages wasn’t enabled yet.
Enable Pages before the first build
Go to the repo Settings > Pages and set the source to GitHub Actions. Without this, the Chirpy workflow fails on theconfigure-pagesstep. I hit this on the first push.
After enabling Pages, I added denmotion.com as the custom domain in the same settings page and re-ran the workflow. It built clean.
DNS in Route 53
I already had a hosted zone for denmotion.com in Route 53 from buying the domain. I added five records to point it at GitHub Pages.
| Record Type | Name | Value |
|---|---|---|
| A | denmotion.com | 185.199.108.153 |
| A | denmotion.com | 185.199.109.153 |
| A | denmotion.com | 185.199.110.153 |
| A | denmotion.com | 185.199.111.153 |
| CNAME | www.denmotion.com | digitaldencloud.github.io |
The four A records point the apex domain to GitHub Pages’ servers. GitHub runs multiple servers for redundancy and load balancing, so if one goes down the others still serve the site. The CNAME handles the www subdomain.
CNAME file in the repo root
GitHub Pages needs a file calledCNAMEin the root of the repo containing the bare domain:denmotion.com. Nohttps://, no trailing slash. Without it, Pages doesn’t know which custom domain to serve and the site deploys to the defaultdigitaldencloud.github.ioaddress instead.
This is different from how digitalden.cloud works. That site routes through CloudFront, so the A record is an alias to a CloudFront distribution. The Chirpy subdomains (docs, denizyilmaz.cloud, gallery) use CNAME records to digitaldencloud.github.io. DenMotion is the first time I’m pointing an apex domain directly at GitHub Pages with A records.
After a few minutes of DNS propagation, I enabled Enforce HTTPS in the Pages settings and denmotion.com was live.
Tidying Up Chirpy
With the site deployed, I went through the Chirpy defaults and stripped out everything a portfolio site doesn’t need.
Sidebar Tabs
Chirpy ships with Home, Categories, Tags, Archives, and About in the sidebar. Categories, Tags, and Archives are blog features. A portfolio site doesn’t need any of them. I deleted those three from _tabs/ and added a Contact tab.
| Tab | Icon | Order | Purpose |
|---|---|---|---|
| Home | fas fa-home | 1 | Back to the landing page |
| Films | fas fa-video | 2 | Film projects |
| Photos | fas fa-camera | 3 | Photo projects |
| About | fas fa-info-circle | 4 | Bio |
| Contact | fas fa-envelope | 5 | How to reach me |
Five items. Clean and explicit. Nobody has to guess where to find contact information.
I thought about putting contact details on the About page to keep the sidebar to four items. However, “About” doesn’t tell anyone that contact information is there. People looking to hire someone scan the sidebar for the word Contact. If they don’t see it, they might leave. A separate tab with fas fa-envelope makes it obvious from every page on the site.
Footer
Chirpy’s default footer shows the copyright, a link to the theme author’s GitHub, and a “Powered by Jekyll” line. That’s fine for a blog, but for a portfolio site it looks generic.
Chirpy uses a gem-based theme, so the footer template lives inside the gem rather than in the repo. To override it, I copied the footer file from the gem into my own _includes/ folder:
1
cp /path/to/jekyll-theme-chirpy/_includes/footer.html _includes/footer.html
Then I replaced the content with a clean two-column layout matching the style of my other Chirpy sites. Left side shows the copyright and year, right side shows my name. No theme attribution, no Jekyll branding.
Overriding gem-based theme files
When Chirpy is installed as a gem, all its layouts, includes, and assets live inside the gem directory. To customise any of them, copy the file into the matching path in your repo. Jekyll prioritises local files over gem files. Runbundle show jekyll-theme-chirpyto find the gem path.
PWA Notification
Chirpy has a Progressive Web App feature that caches the site locally. After every push, instead of just loading the new version, it shows a notification asking the user to click to update. For a portfolio site where I’m pushing changes frequently during the build, this is annoying. I disabled it in _config.yml:
1
2
3
4
pwa:
enabled: false
cache:
enabled: false
The site just loads the latest version every time now, like a normal website.
The Cinematic Grid
With the landing page and Chirpy integration working, I started thinking about how the actual portfolio pages should look. And this is where I had to be honest about something.
Chirpy is a reading theme. It’s built for blog posts: sidebar, text-heavy article wrappers, margins, pagination. It’s perfect for denizyilmaz.cloud where someone is reading a 3,000 word post about blueberry pie. When someone clicks Photos on a portfolio site and lands in a standard Chirpy page layout, it feels like moving from a cinema into a library.
A film project isn’t a blog post. A photo set isn’t an article. The inner pages need to feel as premium as the landing page.
So I built a third layout.
The Blank Canvas
I created _layouts/cinematic.html as a completely standalone layout, same approach as the landing page. It doesn’t extend Chirpy’s default layout. It has its own <head>, its own styles, and it strips away everything: no sidebar, no margins, no Chirpy UI. Just a pitch-black background (#050505), a minimal top navigation bar, and an edge-to-edge container for the work.
The nav bar has two elements. DENMOTION // PHOTOS on the left, and a Close ✕ link on the right that takes you back to the landing page. That’s it.
The
//separator
The double forward slash is a stylistic choice borrowed from camera UI overlays and technical readouts. It creates a wider visual break than a dash or pipe and fits the letter-spacing of the navigation text. Small detail, but it sets the tone.
The Photos and Films tabs in _tabs/ use this layout instead of Chirpy’s default page layout:
1
2
3
4
5
6
7
---
layout: cinematic
title: Photos
icon: fas fa-camera
order: 3
permalink: /photos/
---
The result is that clicking Photos or Films on the landing page drops you into a vast, dark, edge-to-edge screen. No sidebars, no distractions. Just a blank canvas ready for the work.
The Photo Grid
I considered masonry layout (staggered columns like Pinterest), but ruled it out for the same reason I documented in the gallery build: all my Sony A6700 photos are landscape at the same aspect ratio. Masonry is designed for mixed orientations. A standard CSS grid with uniform cells gives cleaner, more symmetrical results when every image is the same shape.
The grid uses three fixed columns on desktop and two on mobile. A 4px gap between cells keeps the images tight, almost seamless, which makes the grid feel like a single composition rather than a collection of separate thumbnails.
1
2
3
4
5
6
7
8
9
10
11
.photo-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
}
.photo-item {
overflow: hidden;
background-color: #111;
aspect-ratio: 3 / 2;
}
The aspect-ratio: 3/2 matches the A6700 sensor ratio exactly. Every cell is the same shape regardless of screen size, so the grid never has awkward gaps or misaligned rows.
Each thumbnail has a hover effect: a subtle 1.03x scale zoom with an opacity fade to 0.7. The zoom is small enough to feel premium rather than gimmicky, and the opacity fade gives a slight darkening effect that makes the hovered image feel like it’s being selected.
| Grid property | Value | Why |
|---|---|---|
| Columns | 3 (desktop), 2 (mobile) | Fills the screen without images being too small |
| Gap | 4px | Razor-thin, almost seamless |
| Aspect ratio | 3:2 | Matches Sony A6700 sensor |
| Hover zoom | scale(1.03) | Subtle, not distracting |
| Hover opacity | 0.7 | Slight darkening, feels like selection |
The thumbnails load from the /thumbs/ folder on CloudFront (800px, 75 quality). Fast, lightweight, and cached at edge locations.
Waterfall Fade-In
When the page loads, the grid items don’t appear all at once. Each one fades in sequentially with a slight upward slide, 100 milliseconds apart.
1
2
3
4
5
items.forEach((item, index) => {
setTimeout(() => {
item.classList.add('is-visible');
}, index * 100);
});
Each .photo-item starts with opacity: 0 and transform: translateY(20px). When the JavaScript adds the .is-visible class, it transitions to full opacity and its natural position over 0.6 seconds. The staggered timing creates a waterfall effect that flows across the grid. Small animation, but it makes the grid feel intentional rather than dumped onto the screen.
Fancybox Lightbox
Clicking a thumbnail opens Fancybox 5 in a full-screen lightbox that loads the full-resolution image from the /full/ folder on CloudFront (3000px, 93 quality). The background goes pitch black. No borders, no chrome, just the image.
Because the cinematic layout is a completely clean slate, there’s no conflict with Chirpy’s built-in image scripts. On denizyilmaz.cloud I had to use e.stopPropagation() to stop Chirpy from hijacking clicks and loading thumbnails instead of full-resolution images. That workaround isn’t needed here because Chirpy’s post layout never loads on this page.
The Fancybox configuration is stripped to the essentials:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Fancybox.bind('[data-fancybox="gallery"]', {
Thumbs: false,
Toolbar: {
display: {
left: [],
middle: [],
right: ["slideshow", "close"],
},
},
Images: { zoom: false },
Slideshow: {
timeout: 3000,
autoStart: false
},
backdropClick: "close"
});
No thumbnail strip, no zoom, just arrow navigation, a slideshow button, and a close button. The slideshow cycles through images at 3 seconds each. It doesn’t auto-start, the user clicks play when they want it. That pacing felt right for landscape photography, long enough to take in the frame, short enough to keep moving.
The Fancybox overflow bug
On denizyilmaz.cloud, closing the Fancybox lightbox sometimes left the page scrollable horizontally. This happened because Fancybox and Chirpy’s post layout were both fighting over theoverflowproperty on the<body>element. On DenMotion, the cinematic layout doesn’t have Chirpy’s post scripts running, so the bug doesn’t exist. The clean slate fixed it.
Each photo in the grid is an <a> tag wrapping a thumbnail <img>, with data-fancybox="gallery" to group them for navigation:
1
2
3
4
5
6
7
<div class="photo-item">
<a href="https://photos.digitalden.cloud/.../full/image.jpg"
data-fancybox="gallery">
<img src="https://photos.digitalden.cloud/.../thumbs/image.jpg"
alt="Description">
</a>
</div>
The <a href> points to the full-resolution image on CloudFront. The <img src> loads the thumbnail. Two different files, two different sizes, same CDN. The visitor never sees an S3 URL.
The Films Grid
The photos grid proved the cinematic layout works. The films page uses the same cinematic.html layout, but video has requirements that images don’t. A static thumbnail tells the viewer nothing about the motion, the pacing, or the edit. The grid needs to move.
The 3-File Strategy
Every film project on DenMotion requires three files hosted on S3 and served through CloudFront. Each file has a specific job.
| File | Format | Purpose | When it loads |
|---|---|---|---|
| Poster | _POSTER.png | Static first frame, displayed immediately | On page load |
| Thumbnail | _THUMB.mp4 | 5-second silent loop, 720p, ~2Mbps | On hover (desktop) or autoplay (mobile) |
| Master | _MASTER.mp4 | Full-length, full-resolution with audio | Only when clicked into lightbox |
The poster loads first because it’s a single image, fast and lightweight. It prevents the “black box” problem where video elements show an empty rectangle while the browser buffers. The thumbnail is a low-res, low-bitrate clip that plays silently in the grid. The master is the full film, 4K or 1080p with audio, and it only loads when someone clicks through to the Fancybox lightbox. The visitor never downloads a heavy file unless they choose to watch.
Why not YouTube embeds
YouTube compresses video to save bandwidth. Dark shadows get crushed, colour grades shift, and fine detail disappears. By hosting the master files on S3/CloudFront, the client sees the exact colours that came out of Premiere Pro. No recompression, no YouTube UI, no recommended videos at the end. Just the work.
Smart Video Behaviour
The grid needs to behave differently on desktop and mobile. On desktop, hover-to-play makes sense because the user has a cursor. On mobile, there’s no hover state, so the thumbnails autoplay silently.
The cinematic layout detects the device type and handles both:
1
2
3
4
5
6
7
8
9
10
11
12
const isTouchDevice = window.matchMedia("(any-pointer: coarse)").matches;
items.forEach(item => {
const vid = item.querySelector('video');
if (vid) {
if (isTouchDevice) {
vid.play().catch(err => {});
} else {
item.addEventListener('mouseenter', () => vid.play());
item.addEventListener('mouseleave', () => vid.pause());
}
}
});
On desktop, the grid is still. You hover over a thumbnail and the clip starts playing silently. Move your cursor away and it pauses. The grid feels alive without being overwhelming.
On mobile, all thumbnails autoplay on loop as soon as the page loads. This works because the clips are muted (browsers allow autoplay for muted video) and the file sizes are small enough that it doesn’t destroy mobile data.
The
autoplayattribute
The<video>tags in the grid markup do not have theautoplayattribute. The JavaScript handles playback instead. This gives full control over when and how videos start, rather than relying on inconsistent browser behaviour.
The Lightbox and Case Studies
Clicking a film thumbnail opens the Fancybox lightbox the same way photos do, but instead of a static image, it plays the full master MP4 with audio. The viewer gets a brandless, full-screen video player with no YouTube logos, no recommended videos, no channel avatars.
The lightbox also has a caption area at the bottom. For film projects, this shows the project title and a “View Case Study” button styled to match the cinematic nav.
1
2
3
4
5
6
7
8
9
10
<a href="https://cdn.denmotion.com/films/project_MASTER.mp4"
data-fancybox="gallery"
data-caption="<span class='case-study-title'>Project Name</span>
<a href='/posts/project-case-study/' class='case-study-btn'>View Case Study</a>">
<video
src="https://cdn.denmotion.com/films/project_THUMB.mp4"
poster="https://cdn.denmotion.com/films/project_POSTER.png"
muted loop playsinline>
</video>
</a>
The case study button links to a standard Chirpy blog post. Clicking it takes the viewer out of the cinematic layout and into Chirpy’s reading environment with the sidebar, typography, and full blog layout. That’s where the technical breakdown lives: the brief, the gear list, the lighting setup, the editing decisions, behind-the-scenes stills.
This is deliberate. The cinematic grid shows the work. The case study explains the work. They use different layouts because they serve different purposes.
The Case Study Post
Each case study is a standard Chirpy post in _posts/. It uses the poster image as the header so it looks right in social media previews when shared.
1
2
3
4
5
6
7
8
---
layout: post
title: "Project Name"
date: 2026-04-02
description: "One-line description of the project."
image:
path: https://cdn.denmotion.com/films/project_POSTER.png
---
I removed categories and tags from the front matter for now because I deleted the Categories and Tags tab files from _tabs/. If I want those features later I can re-add the tab files and the front matter will work again.
Inside the post, the full master video embeds using Chirpy’s built-in video include:
1
2
3
4
5
6
{%
include embed/video.html
src='https://cdn.denmotion.com/films/project_MASTER.mp4'
poster='https://cdn.denmotion.com/films/project_POSTER.png'
title='Project Name - Final Cut'
%}
Chirpy’s embed handles the responsive sizing and player controls. I also have a backup option using a custom HTML5 <video> tag with rounded corners and a drop shadow for a more cinematic look inside the post, but for now I’m leaning towards Chirpy’s include because it’s cleaner and more consistent with the rest of the blog layout.
The case study is where the visitor goes from “this looks good” to “this person knows what they’re doing.” The grid is the proof. The case study is the pitch.
Three Layouts, One Site
DenMotion now has three completely independent layout systems running inside the same Jekyll project.
graph LR
A["denmotion.com"] --> B["_layouts/landing.html<br/>Dimensions + particles.js"]
A --> C["_layouts/cinematic.html<br/>Edge-to-edge dark grid"]
A --> D["Chirpy default layout<br/>Sidebar, footer, blog UI"]
B --> E["Landing Page<br/>Full screen, two buttons"]
C --> F["Photos Grid<br/>No sidebar, pitch black"]
C --> G["Films Grid<br/>No sidebar, pitch black"]
D --> H["Case Studies"]
D --> I["About + Contact"]
style A fill:#1a1a2e,stroke:#e94560,color:#fff
style B fill:#e94560,stroke:#fff,color:#fff
style C fill:#e94560,stroke:#fff,color:#fff
style D fill:#e94560,stroke:#fff,color:#fff
style E fill:#0f3460,stroke:#e94560,color:#fff
style F fill:#0f3460,stroke:#e94560,color:#fff
style G fill:#0f3460,stroke:#e94560,color:#fff
style H fill:#16213e,stroke:#533483,color:#fff
style I fill:#16213e,stroke:#533483,color:#fff
| Layout | Used by | What it loads |
|---|---|---|
landing.html | Landing page | Dimensions CSS, particles.js only |
cinematic.html | Photos, Films | Custom dark CSS, Fancybox, video hover script only |
| Chirpy default | Case studies, About, Contact | Full Chirpy UI with sidebar and footer |
None of them share stylesheets. None of them conflict. The visitor moves between all three without noticing they exist, because the transitions feel intentional rather than broken.
The Client Funnel
Looking at the full architecture now, there’s a deliberate sequence built into how visitors move through the site.
graph TD
A["denmotion.com<br/>Particles landing page"] --> B["Films or Photos<br/>Binary choice"]
B --> C["Cinematic Grid<br/>Edge-to-edge, dark, immersive"]
C --> D["Fancybox Lightbox<br/>Full-screen, brandless player"]
D --> E["View Case Study<br/>Chirpy blog post"]
E --> F["Contact<br/>Sidebar link, always visible"]
style A fill:#1a1a2e,stroke:#e94560,color:#fff
style B fill:#1a1a2e,stroke:#e94560,color:#fff
style C fill:#0f3460,stroke:#e94560,color:#fff
style D fill:#0f3460,stroke:#e94560,color:#fff
style E fill:#16213e,stroke:#533483,color:#fff
style F fill:#e94560,stroke:#fff,color:#fff
The hook. They land on denmotion.com. Particles are moving, it’s dark and clean, and the only choice is Films or Photos. The branding is established in the first second.
The proof. They click Films. The UI disappears. Edge-to-edge grid, pitch-black background, video thumbnails playing silently on hover. It proves the aesthetic eye without forcing anyone to read a word.
The immersion. They click a thumbnail. The lightbox opens. Full-screen, full-resolution, full audio. No YouTube chrome, no recommended videos, no distractions. Just the work.
The pitch. They see “View Case Study” in the lightbox caption. They click through to a Chirpy blog post. Clean typography, structured breakdown, gear lists, behind-the-scenes details. They realise this isn’t just someone with a camera. There’s process and technical thinking behind the work.
The close. The Chirpy sidebar is right there. Contact tab with an envelope icon, visible from every page. One click to reach me.
Each transition moves the visitor deeper into the site while escalating trust. The landing page establishes the brand. The grid proves the quality. The lightbox immerses them in a single piece. The case study shows the thinking. The contact button converts.
- The hook: particles landing page
- The proof: cinematic grid with video hover
- The immersion: Fancybox lightbox, self-hosted video
- The pitch: case study posts in Chirpy
- The close: Contact tab in sidebar
The Full Architecture
This is everything running under denmotion.com.
| Layer | Technology | Purpose |
|---|---|---|
| Domain | Route 53 | DNS, A records to GitHub Pages |
| Hosting | GitHub Pages | Static site serving |
| Build | Jekyll + Chirpy + GitHub Actions | Compiles markdown to HTML, auto-deploys on push |
| Landing page | _layouts/landing.html | Dimensions template + particles.js, isolated from Chirpy |
| Portfolio grids | _layouts/cinematic.html | Edge-to-edge dark grid, Fancybox, video hover, waterfall animation |
| Case studies | Chirpy post layout | Full blog UI, sidebar, typography |
| About and Contact | Chirpy page layout | Standard pages with sidebar |
| Image hosting | S3 + CloudFront | Thumbnails (800px) and full-resolution (3000px) photos |
| Video hosting | S3 + CloudFront | Posters, 5-second thumbnail loops, full master files |
| Contact form | API Gateway + Lambda | Serverless, no page reload |
| TLS | GitHub Pages auto-provisioning | HTTPS enforced |
Three layout engines running inside one Jekyll project. A serverless AWS backend handling media delivery and contact. Zero databases, zero monthly hosting fees beyond S3 storage, zero compression on the delivered files.
What this costs to run
GitHub Pages is free. Route 53 is $0.50/month per hosted zone. S3 storage is pennies per gigabyte. CloudFront charges per request, but for a portfolio site the traffic is low enough that the free tier covers most of it. The entire infrastructure costs less than a single month of Squarespace.
Why Self-Hosted Over Platforms
Most creatives host their portfolios on Squarespace, Wix, or Adobe Portfolio and embed their videos using YouTube or Vimeo. That works, but it comes with trade-offs I wasn’t willing to accept.
| Concern | Hosted platforms | DenMotion architecture |
|---|---|---|
| Video quality | YouTube compresses, crushes dark shadows and colour grades | S3/CloudFront serves the exact file from Premiere Pro |
| Speed | Database-driven, heavy page loads | Static HTML, no database, CDN-cached |
| Control | Template constraints, limited layout options | Three independent layouts, full CSS control |
| Security | Platform dependent | No database to hack, static files only |
| Cost | £12-40/month | Pennies per month on AWS free tier |
| Ownership | Content on someone else’s platform | Everything in a Git repo I own |
By pairing GitHub Pages with S3 and CloudFront, I built a serverless, static architecture. The site has no database, CloudFront caches the master files at edge locations globally, and because I host the MP4s myself, the client sees the exact pristine colours that came out of the A6700 and Premiere Pro.
Adding New Work
When I finish a new shoot, the workflow is:
- Export the poster, thumbnail clip, and master from Premiere Pro
- Upload the three files to S3 with the AWS CLI
- Add a
<div class="photo-item">block to the films grid with the CloudFront URLs - Write a case study post in
_posts/if the project warrants it git push
GitHub Actions compiles the HTML, updates the sitemap for Google, and deploys it live. The whole process from finished edit to published portfolio piece takes minutes, not hours.
No CMS login, no drag-and-drop page builders, no database migrations. Just files, Git, and a push.
What Comes Next
The site is live and the architecture works. There are a few directions I’m thinking about for what comes next.
Data-Driven Grid
Right now, each film in the grid is a manually written HTML <div> block in films.md. This works, but it means editing raw HTML every time I add a project. I’m considering moving the film data into a Jekyll data file at _data/films.yml:
1
2
3
4
5
- title: Cool Chile Co
master: https://cdn.../C2018_MASTER.mp4
thumb: https://cdn.../C2018_THUMB.mp4
poster: https://cdn.../C2018_POSTER.png
case_study: /posts/cool-chile-commercial/
Then the films page just loops through it:
1
2
3
4
5
6
7
8
9
10
11
12
<div class="photo-grid">
{% for film in site.data.films %}
<div class="photo-item">
<a href="{{ film.master }}" data-fancybox="gallery"
data-caption="<span class='case-study-title'>{{ film.title }}</span>
<a href='{{ film.case_study }}' class='case-study-btn'>View Case Study</a>">
<video src="{{ film.thumb }}" poster="{{ film.poster }}"
muted loop playsinline></video>
</a>
</div>
{% endfor %}
</div>
Adding a new film becomes three lines of YAML instead of a block of HTML. More importantly, YAML is easy for a Lambda function to write to, which matters for the next step.
Automated Pipeline
Right now I export three files from Premiere Pro manually: the master, the thumbnail clip, and the poster. Then I upload them to S3, edit the grid HTML, and push. This works, but there are steps AWS can do for me.
Level 1: MediaConvert. I export one file from Premiere, the full master. I upload it to S3 with a single CLI command. An S3 event notification triggers a Lambda function, which triggers an AWS Elemental MediaConvert job. MediaConvert is the same transcoding service Netflix and Amazon Prime use. It takes the master and automatically generates the web-optimised MP4, extracts the first frame as the poster PNG, and cuts the first five seconds as a silent low-bitrate thumbnail loop. Three files from one upload.
Level 2: GitHub API committer. When MediaConvert finishes, it sends a success event to EventBridge. EventBridge triggers a second Lambda function that uses the GitHub REST API to append the new film’s YAML entry to _data/films.yml and commit it directly to the main branch. GitHub Actions picks up the commit and deploys. I never open VS Code.
Level 3: Bedrock integration. Inside that same Lambda function, I make an API call to Amazon Bedrock to generate a draft case study post. Bedrock takes the film metadata and writes a Jekyll markdown file with the front matter, the video embed, and placeholder sections for the technical breakdown. The Lambda commits it to a new branch and opens a Pull Request. I review the draft, fill in the details, and merge.
The end state: I drop an MP4 into an S3 bucket. Five minutes later, the site has the new film in the grid with a poster, a thumbnail loop, and a draft case study waiting for my review. One upload, zero manual web work.
The capstone project
Building this serverless MediaConvert to GitHub API pipeline would combine everything I know from six AWS certifications into one real production workflow. It’s also the kind of project that belongs in a course or a documentation series on docs.digitalden.cloud.
Selling the Architecture
I’ve been thinking about this since finishing the build. The architecture I created — the particles landing page, the cinematic grid, the self-hosted video with the 3-file strategy, the case study funnel, the automated pipeline — this could be a product.
The model I have in mind is simple. I own and maintain the infrastructure: Route 53, CloudFront distributions, the Jekyll repo, the GitHub Actions pipeline. The client gets an IAM user scoped to their own S3 bucket that I create for them. They upload their video or images to that bucket, and the pipeline handles the rest. Their website updates automatically. They never touch Jekyll, Git, DNS, or CloudFront. From their perspective, they drop a file into a folder and their portfolio updates.
The cost per client would be almost nothing. An S3 bucket, a CloudFront distribution, an IAM user with a policy scoped to their prefix. I could charge a monthly fee that covers the AWS costs ten times over and it would still be cheaper than Squarespace. The margin is in the setup and the maintenance, not the infrastructure.
Most creatives don’t know how to set up S3 and CloudFront. Most cloud engineers don’t know how to make a portfolio that looks cinematic. I happen to be both. That’s the niche.
Whether this becomes a product or stays as my own portfolio, the architecture is built to handle either.
What the Competition Charges
To understand the value of what I built, I looked at what a videographer currently pays to get a portfolio that’s even half as good.
| Platform | Monthly cost | What’s wrong with it |
|---|---|---|
| Squarespace | £20-35/month | Natively compresses uploaded video, looks muddy |
| Squarespace + Vimeo Pro | £40-100/month | Need Vimeo to bypass compression, two subscriptions |
| WordPress (WP Engine + premium theme) | £30-50/month | Constant plugin updates, database maintenance, security patches |
| Wix | £15-30/month | Template constraints, limited video control |
A professional filmmaker is paying £40 to £100 per month just to keep a basic, bloated portfolio online. They spend £5,000+ on a Sony or RED camera, hours colour grading in Premiere Pro, then upload to Squarespace or YouTube and watch the compression algorithm destroy their shadows.
My pitch is simple: zero-compression cinematic portfolios. No WordPress bloat, no Vimeo fees, no YouTube logos. Hosted on enterprise AWS edge networks. The client sends me their master file and I handle the rest.
The Numbers
Build fee (setup and customisation): I take my GitHub repo, duplicate it, change the brand colours, swap the logo, upload their initial portfolio.
| Tier | Fee | Who it’s for |
|---|---|---|
| Starter | £500 | Friends, small creators |
| Standard | £1,200-£2,500 | Professional videographers, production studios |
My cost: £0. A few hours of duplicating the Jekyll config and running AWS CLI commands.
Monthly retainer (hosting and updates): They don’t want to learn GitHub or AWS. I sell them peace of mind.
| Item | Detail |
|---|---|
| Rate | £50-100/month |
| Deliverable | Enterprise AWS hosting, SSL, zero-compression video delivery, up to 2 portfolio updates per month |
| How updates work | They send a Google Drive link, I run the MediaConvert/CLI script, it goes live |
| My AWS cost per client | £1-3/month (S3 storage + CloudFront bandwidth) |
| Profit margin | ~95% |
If I get 10 clients paying £75/month, that’s £750/month in recurring revenue. My total AWS bill for all 10 clients combined would be less than £15. Updating their sites takes maybe 2 hours a month total, because the upload and deploy process is scripted.
I spent years learning these skills. The build fee reflects the architecture, not the hours. The retainer reflects the infrastructure, not the bandwidth.
Locking It Down
Once I started thinking about this as a potential product, I realised the code needed to be private. Right now all my repos are public. Every layout, every CSS file, every JavaScript integration is visible. Anyone can fork the repo and clone the exact portfolio I just spent weeks architecting.
The video and images are safe regardless because they live on S3, not in the repo. What’s exposed is the site code: the three custom layouts, the cinematic grid CSS, the hover-to-play JavaScript, the Fancybox configuration, the particles.js integration. That’s the intellectual property. That’s what I’d be selling.
This is the first time I’m making a repo private, so I wanted to understand the options properly.
The GitHub Pages Problem
On a free GitHub account, GitHub Pages only works if the repository is public. Make it private and the site goes offline. There are a few ways around this.
| Option | How it works | Cost | Trade-off |
|---|---|---|---|
| GitHub Pro | Private repos can publish to GitHub Pages | $4/month | Easiest, nothing changes about the workflow |
| Netlify / Vercel | Connect private repo, they build and host | Free tier available | Different hosting platform, different dashboard |
| AWS Amplify | Connect private repo, AWS builds and hosts | Pay for build minutes | Managed service, slight premium on builds |
| S3 + CloudFront + GitHub Actions | Private repo, GitHub Action builds and syncs to S3 | Pennies/month | Raw infrastructure, full control |
Considering Amplify
My first thought was AWS Amplify. It connects to a private GitHub repo, runs the Jekyll build, and distributes the site across AWS edge locations. Everything stays inside the AWS ecosystem, which is appealing because all my other infrastructure is already there.
However, Amplify is a managed service. It’s AWS trying to build their own version of Netlify. It hides the infrastructure, charges for build minutes every time I push code, and adds a layer of abstraction I don’t need. For a static site that’s a few megabytes of HTML and CSS, the managed overhead doesn’t make sense.
That said, I might use Amplify for client sites. If I’m running multiple client portfolios, Amplify’s managed builds could be simpler to maintain at scale than custom GitHub Actions workflows for each one. Worth experimenting with.
Choosing S3 + CloudFront
For my own site, I’m going with raw infrastructure. S3 + CloudFront + GitHub Actions. I already know how to set this up because I built the exact same architecture for my photo and video hosting. This time I’m applying it to the website code itself.
The pipeline works like this:
- The GitHub repo is private. Nobody can see the code.
- When I
git push, a GitHub Action spins up a temporary server. - The Action runs
jekyll buildto generate the static HTML. - The Action syncs the built
_site/folder to an S3 bucket usingaws s3 sync. - The Action invalidates the CloudFront cache so the live site updates immediately.
GitHub Actions are free for private repos (up to 2,000 build minutes a month, which I’ll never hit). S3 storage for a few megabytes of HTML is practically free. CloudFront gives 1TB of data transfer free every month. The total cost is less than £1/month.
The cloud engineer flex
Building a fully automated CI/CD pipeline that deploys a static site from a private repo directly to edge locations via GitHub Actions is the kind of project that belongs on a CV. It’s also the same pattern used in production by companies running at scale. For an AWS content creator, this is a real-world demonstration of S3, CloudFront, IAM, and GitHub Actions working together.
AWS Setup
The infrastructure is familiar because it mirrors what I already built for photos.digitalden.cloud.
S3 bucket: Create a new bucket (e.g., denmotion-website-prod). Block all public access. The site files are only accessible through CloudFront.
CloudFront distribution: Point the origin at the S3 bucket with Origin Access Control (OAC). Set the default root object to index.html. Attach the denmotion.com SSL certificate from ACM. Add denmotion.com and www.denmotion.com as alternate domain names.
S3 bucket policy: CloudFront generates the policy. Copy it into the bucket permissions so only the distribution can read the files.
Route 53 update: Change the A records from pointing at GitHub Pages’ IPs to aliasing the new CloudFront distribution. Same pattern as digitalden.cloud, which already aliases a CloudFront distribution.
IAM Deployment User
GitHub needs permission to put files into S3 and invalidate the CloudFront cache. I create a dedicated IAM user called github-actions-deployer with no console access and a policy scoped to exactly two actions:
s3:PutObjectands3:DeleteObjecton thedenmotion-website-prodbucketcloudfront:CreateInvalidationon the DenMotion distribution
The access key and secret key go into the GitHub repo as encrypted secrets under Settings > Secrets and variables > Actions:
| Secret | Value |
|---|---|
AWS_ACCESS_KEY_ID | IAM access key |
AWS_SECRET_ACCESS_KEY | IAM secret key |
S3_BUCKET_NAME | denmotion-website-prod |
CLOUDFRONT_DIST_ID | Distribution ID |
The Deploy Script
This is the GitHub Actions workflow that builds the site and pushes it to AWS. It runs automatically every time I push to the main branch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
name: Deploy DenMotion to AWS
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
- name: Build Jekyll Site
run: JEKYLL_ENV=production bundle exec jekyll build
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: eu-west-2
- name: Sync files to S3
run: aws s3 sync _site/ s3://$ --delete
- name: Invalidate CloudFront Cache
run: |
aws cloudfront create-invalidation \
--distribution-id $ \
--paths "/*"
The --delete flag on s3 sync removes any files from the bucket that no longer exist in the build. This keeps the bucket clean and prevents stale pages from being served.
The CloudFront invalidation forces the CDN to fetch the latest files from S3 immediately instead of waiting for the cache TTL to expire. Without it, the live site could show old content for up to 24 hours after a push.
The workflow
git push→ GitHub Action builds Jekyll → syncs to S3 → invalidates CloudFront → site is live globally in under 2 minutes. The repo is private. The code is hidden. The site loads from AWS edge locations. Total monthly cost: pennies.
What Changes
| Before (GitHub Pages) | After (S3 + CloudFront) |
|---|---|
| Repo must be public | Repo is private |
| Site hosted on GitHub’s CDN | Site hosted on AWS edge network |
| GitHub manages SSL | ACM manages SSL |
| DNS A records to GitHub IPs | DNS alias to CloudFront distribution |
| Free, but code exposed | Pennies/month, code protected |
| Deploy via Chirpy’s GitHub Action | Deploy via custom GitHub Action to S3 |
The visitor experience is identical. The site looks the same, loads the same, behaves the same. The difference is entirely behind the scenes: the code is locked down and the hosting is on infrastructure I control completely.
How I Got Here
I’ve been thinking about something since finishing this build. I created the HTML5 UP Dimensions landing page two years ago. I added particles.js about a year ago. Jekyll Chirpy, also about a year ago. Photography started six months ago. Video more recently. And now all of those things are running together inside one project as if they were always meant to connect.
They weren’t. None of these skills were learned with DenMotion in mind. Each one was picked up for a completely different reason at a completely different time.
HTML5 UP was for a landing page for my cloud portfolio. Particles.js was because it looked cool and I wanted to learn how to integrate it. Chirpy was for blogging about AWS projects and personal stuff. The camera was for improving the quality of my AWS demo videos. Photography happened because I had the camera and started pointing it at things that weren’t AWS consoles. Video happened because I had the photography. Gym filming happened because I was already in the gym every day.
None of those decisions were connected when I made them. They were made independently over two years. What happened is that I accumulated enough separate skills that they started overlapping in ways I couldn’t have predicted.
Think about who could have built DenMotion the way I built it. They’d need to understand DNS and Route 53, S3 and CloudFront architecture, Jekyll and static site generators, Chirpy’s theme system and how to override it, HTML and CSS well enough to build custom layouts, JavaScript well enough to write hover-to-play logic, Fancybox and lightbox integration, video production and colour grading, photography and Lightroom workflows, and the UX instinct to know that a portfolio site shouldn’t feel like a blog.
That’s not a common combination. Cloud engineers don’t usually shoot video. Videographers don’t usually configure CloudFront distributions. The intersection is almost empty.
The reason it’s happening now and not a year ago is because the last skill I needed was the creative one. I had the cloud infrastructure, the web development, and the deployment pipelines already. Photography and video were the missing pieces, and they only reached the point of “good enough to showcase” in the last six months. Once they crossed that threshold, everything else I’d already built suddenly had a purpose it didn’t have before.
That’s why it feels like it came out of nowhere. It didn’t. It was always building towards this. I just couldn’t see the shape of it until the last piece landed.
And the fact that it started from a domain name and ended up here is the most honest version of the story. I didn’t sit down and architect a multi-layout serverless portfolio with a cinematic video pipeline. I needed to name something. Then I needed to put it somewhere. Then I needed it to look right. Then I needed it to perform right. Each step created the next problem, and each problem pulled in a skill I already had. The architecture emerged from the constraints, not from a blueprint.
| Skill | When I learned it | Original purpose | Role in DenMotion |
|---|---|---|---|
| HTML5 UP Dimensions | ~2024 | Cloud portfolio landing page | Landing page template |
| particles.js | ~2025 | Visual experiment | Landing page background animation |
| Jekyll Chirpy | ~2025 | AWS documentation blog | Case studies, About, Contact pages |
| S3 + CloudFront | Years of AWS work | Course content, demos | Self-hosted video and photo delivery |
| Route 53 | Years of AWS work | Domain management | DNS for denmotion.com |
| Lambda + API Gateway | Years of AWS work | Serverless projects | Contact form, future automation |
| Photography | Late 2025 | Improve AWS demo quality | Photo portfolio content |
| Lightroom | Early 2026 | Edit Montenegro photos | Photo editing workflow |
| Video + Premiere Pro | 2026 | Bodrum, gym filming | Film portfolio content |
| Fancybox | Early 2026 | Gallery on denizyilmaz.cloud | Full-screen lightbox viewer |
Two years of unrelated skills, converging into one project in the space of a week. That’s what compound skills look like when they finally intersect.
What This Actually Is
I took the aesthetic impact of a creative template, the scalability of a static site generator, and the media delivery of enterprise CDN infrastructure, and made them work together by isolating each one in its own layout with its own assets. None of these things were designed to coexist, but they do.
Most portfolio sites are either visually stunning with no way to maintain them (custom builds that cost thousands and break when you need to add content), or easy to maintain with no visual identity (Squarespace, Wix, template sites that all look the same). DenMotion is both. It looks custom-built, but adding a new project is a YAML entry and a git push.
The domain is set. The structure makes sense. The architecture scales. And it feels separate, which was the whole point.
Documented April 2026.