Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/img/blog/2025/10/banner-ddev-10-minutes-linux.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/img/blog/2026/02/banner-ddev-v1.25.0-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/img/blog/2026/02/banner-ddev-v1.25.0.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/img/blog/2026/07/banner-ddev-v1-25-3-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/img/blog/2026/07/banner-ddev-v1-25-3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion src/components/AnimatedTerminal.astro
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,15 @@ Waiting for ddev-router to become ready... ready in 1.0s
* so these rules apply on the very first paint with no flash. */
html[data-theme] #noscript-pre { display: none; }
html:not([data-theme]) #animated-pre { display: none; }

/* The output is a fixed 87x32 character grid: this font size fits 87 columns
* inside the 40rem the caller gives it, and the height is in `em` so the box
* stays in step with the font. */
.terminal-body {
font-size: 0.6875rem;
line-height: 1.35;
height: calc(32 * 1.35em + 2rem);
}
</style>

<div class="terminal-wrapper rounded-lg shadow-lg" style="background: #2e3440;" data-describe-output={describeOutput}>
Expand All @@ -261,7 +270,7 @@ Waiting for ddev-router to become ready... ready in 1.0s
<div class="block rounded-full bg-slate-500 w-3 h-3"></div>
</div>
<div class="overflow-hidden my-0">
<div class="h-full p-4 text-slate-300 text-xs" style="height: 540px;">
<div class="terminal-body p-4 text-slate-300">
<pre id="animated-pre"><span class="select-none text-slate-400">→ </span><span id="animation-stage" /></pre>
<pre id="noscript-pre"><span class="select-none text-slate-400">→ </span><span set:html={describeOutput} /></pre>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/AppLogos.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const logos = [
---

<div class="opacity-60">
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
<div class="site-container py-12 site-gutter">
<div class="flex justify-center content-center flex-wrap">
{
logos.map((logo) => (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Benefits.astro
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const features = [
---

<div class="bg-white dark:bg-transparent">
<div class="max-w-6xl mx-auto py-16 px-4 sm:px-6 lg:py-24 lg:px-8">
<div class="site-container py-16 px-4 sm:px-6 lg:py-24 lg:px-8">
<div class="max-w-3xl mx-auto text-center">
<h2 class="text-base font-mono text-navy-300 mb-3 dark:text-navy-100">Benefits</h2>
<h3 class="text-3xl font-extrabold text-gray-900 dark:text-white">
Expand Down
4 changes: 2 additions & 2 deletions src/components/BlogFeatures.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const recentPosts = posts
.slice(0, 3)
---

<div class="relative my-24 px-8">
<div class="relative max-w-5xl mx-auto">
<div class="relative my-24">
<div class="site-container site-gutter relative">
<div class="text-center">
<h2 class="text-3xl font-bold text-gray-900 sm:text-4xl dark:text-white">
Latest Blog Posts
Expand Down
35 changes: 12 additions & 23 deletions src/components/BlogPostCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
import type { CollectionEntry } from "astro:content"
import { render } from "astro:content"
import { Picture } from 'astro:assets'
import type { ImageMetadata } from 'astro'
import { resolveImage } from "../lib/images"
interface Props {
post: CollectionEntry<"blog">
}

import CategoryPills from "./CategoryPills.astro"
import { getEntry } from "astro:content"
import { getSlug, formatDate, getCategoryUrl } from "../lib/api"
import { getSlug, formatDate } from "../lib/api"

const { post } = Astro.props
const { remarkPluginFrontmatter } = await render(post)
Expand All @@ -25,13 +26,6 @@ const featureImageUrl = featureImage?.src ?? `/img/generic-thumbnail.png`
const featureImageAlt = featureImage?.alt ?? ``
const featureImageDarkUrl = featureImage?.srcDark ?? null

const images = import.meta.glob<{ default: ImageMetadata }>(
'/public/img/**/*.{png,jpg,jpeg,webp,gif}'
)
const resolveImage = async (path: string) => {
const key = `/public${path}`
return images[key] ? (await images[key]()).default : path
}
const resolvedSrc = await resolveImage(featureImageUrl)
const resolvedSrcDark = featureImageDarkUrl ? await resolveImage(featureImageDarkUrl) : null

Expand All @@ -42,15 +36,19 @@ const categories = post.data.categories.length ? post.data.categories : []
/* `isolation: isolate` is here to convince Safari not to display un-rounded corners during animation */
}
<div
class="relative flex flex-col rounded-lg overflow-hidden"
class="relative flex flex-col rounded-lg overflow-hidden border border-gray-200 dark:border-slate-500"
style="isolation: isolate;"
>
<div class="shrink-0">
{/* No background of its own: logo SVGs are drawn to sit on the page. The rule
is what separates the image from the text below it. */}
<div class="shrink-0 border-b border-gray-200 dark:border-slate-500">
{
featureImageUrl.includes(".svg") ? (
/* The same box, filled the same way, as the raster images below, so the
cards in a row line up. */
<>
<img src={featureImageUrl} alt={featureImageAlt} class={featureImageDarkUrl ? "dark:hidden" : ""} />
{featureImageDarkUrl && <img src={featureImageDarkUrl} alt={featureImageAlt} class="hidden dark:block" />}
<img src={featureImageUrl} alt={featureImageAlt} class={`block w-full aspect-3/2 object-cover${featureImageDarkUrl ? " dark:hidden" : ""}`} />
{featureImageDarkUrl && <img src={featureImageDarkUrl} alt={featureImageAlt} class="hidden dark:block w-full aspect-3/2 object-cover" />}
</>
) : (
<>
Expand All @@ -76,16 +74,7 @@ const categories = post.data.categories.length ? post.data.categories : []
</div>
<div class="flex-1 bg-gray-100 p-6 flex flex-col justify-between dark:bg-slate-600">
<div class="flex-1">
<div class="flex flex-wrap gap-1">
{categories.map((category) => (
<a
href={getCategoryUrl(category)}
class="relative z-10 text-xs font-mono px-2 py-0.5 rounded-full border border-indigo-400 text-indigo-600 hover:border-indigo-600 dark:border-cyan-500 dark:text-cyan-300 dark:hover:border-cyan-300"
>
{category}
</a>
))}
</div>
<CategoryPills categories={categories} />
<a href={`/blog/${post.id}`} class="block mt-2 after:absolute after:inset-0">
<p class="text-xl font-semibold text-gray-900 dark:text-white">
{post.data.title}
Expand Down
27 changes: 5 additions & 22 deletions src/components/BlogPostFooter.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,13 @@ export interface Props {
}

const { activeSlug } = Astro.props
import { getCollection } from "astro:content"
import { getAdjacentPosts } from "../lib/posts"

let next, prev

const posts = await getCollection("blog")
const orderedPosts = posts.sort((a, b) => {
return new Date(a.data.pubDate) > new Date(b.data.pubDate) ? -1 : 1
})

orderedPosts.forEach((post, index) => {
if (post.id === activeSlug) {
if (posts[index - 1]) {
prev = posts[index - 1]
}

if (posts[index + 1]) {
next = posts[index + 1]
}
}
})
const { prev, next } = await getAdjacentPosts(activeSlug)
---

<nav
class="max-w-4xl mx-auto px-6 lg:px-0 md:flex md:gap-2 md:columns-3 mt-32 py-4"
class="site-container site-gutter md:flex md:gap-2 md:columns-3 mt-32 py-4"
>
{
prev && (
Expand All @@ -38,7 +21,7 @@ orderedPosts.forEach((post, index) => {
title={`Previous: ${prev.data.title}`}
>
<div>
<span class="font-mono text-sm block text-navy-300 tracking-wider dark:text-navy-100">
<span class="sidebar-label">
Previous Post
</span>
<span class="py-3 md:w-full font-bold dark:text-slate-400">{prev.data.title}</span>
Expand All @@ -55,7 +38,7 @@ orderedPosts.forEach((post, index) => {
title={`Next: ${next.data.title}`}
>
<div>
<span class="font-mono text-sm block text-navy-300 tracking-wider dark:text-navy-100">
<span class="sidebar-label">
Next Post
</span>
<span class="py-3 md:w-full font-bold dark:text-slate-400">{next.data.title}</span>
Expand Down
100 changes: 53 additions & 47 deletions src/components/BlogPostGrid.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,55 +17,61 @@ interface Props {
const { posts, nextUrl, prevUrl } = Astro.props
---

<div
id="post-grid"
data-next-url={nextUrl}
class="mt-12 px-6 sm:px-4 lg:px-0 grid gap-5 sm:grid-cols-2 lg:grid-cols-3"
>
{posts.map((post) => <BlogPostCard post={post} />)}
</div>

<div
id="load-mode-toggle"
class="flex justify-end mt-6 px-6 lg:px-0"
style="display: none"
>
<label class="flex items-center gap-2 text-sm text-gray-500 dark:text-slate-400 cursor-pointer select-none">
<input type="checkbox" id="scroll-toggle" class="cursor-pointer" />
Auto-load on scroll
</label>
{/* One gutter for the whole listing, so the cards, the toggle and the button
share their left and right edges. */}
<div class="site-gutter">
<div>
<div
id="post-grid"
data-next-url={nextUrl}
class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3"
>
{posts.map((post) => <BlogPostCard post={post} />)}
</div>

<div
id="load-mode-toggle"
class="flex justify-end mt-6"
style="display: none"
>
<label class="flex items-center gap-2 text-sm text-gray-500 dark:text-slate-400 cursor-pointer select-none">
<input type="checkbox" id="scroll-toggle" class="cursor-pointer" />
Auto-load on scroll
</label>
</div>

{
nextUrl && (
<>
<div
id="load-more-container"
class="flex justify-center my-8 pb-16"
style="display: none"
>
<button
id="load-more-btn"
class="px-6 py-2 border border-blue-650 text-blue-650 hover:border-blue-800 hover:text-blue-800 dark:border-slate-400 dark:text-slate-300 dark:hover:border-white dark:hover:text-white font-bold rounded-md transition-colors cursor-pointer"
>
Load More
</button>
</div>
<div id="scroll-sentinel" class="h-16" />
</>
)
}
{
(prevUrl || nextUrl) && (
<noscript>
<nav class="my-8 pb-24 flex">
{prevUrl && <a href={prevUrl} class="font-bold text-black">← Newer Posts</a>}
{nextUrl && <a href={nextUrl} class="font-bold text-black ml-auto">Older Posts →</a>}
</nav>
</noscript>
)
}
</div>
</div>

{
nextUrl && (
<>
<div
id="load-more-container"
class="flex justify-center my-8 pb-16"
style="display: none"
>
<button
id="load-more-btn"
class="px-6 py-2 border border-blue-650 text-blue-650 hover:border-blue-800 hover:text-blue-800 dark:border-slate-400 dark:text-slate-300 dark:hover:border-white dark:hover:text-white font-bold rounded-md transition-colors cursor-pointer"
>
Load More
</button>
</div>
<div id="scroll-sentinel" class="h-16" />
</>
)
}
{
(prevUrl || nextUrl) && (
<noscript>
<nav class="max-w-4xl mx-auto my-8 pb-24 flex px-6 lg:px-0">
{prevUrl && <a href={prevUrl} class="font-bold text-black">← Newer Posts</a>}
{nextUrl && <a href={nextUrl} class="font-bold text-black ml-auto">Older Posts →</a>}
</nav>
</noscript>
)
}

<script>
const STORAGE_KEY = "blogLoadMode"

Expand Down
29 changes: 29 additions & 0 deletions src/components/BlogPostLinks.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
/**
* Where to go next from a blog post. Previous and next live in the footer, at
* the point where someone has finished reading; this is what's worth having
* beside the post the whole way down.
*/
import type { CollectionEntry } from "astro:content"
import SimilarPosts from "./SimilarPosts.astro"
import { getSimilarPosts } from "../lib/posts"

export interface Props {
entry: CollectionEntry<"blog">
}

const { entry } = Astro.props

const similar = await getSimilarPosts(entry.id)
---

{
similar.length > 0 && (
<div>
<span class="sidebar-label">Similar Posts</span>
<div class="mt-3">
<SimilarPosts posts={similar} />
</div>
</div>
)
}
27 changes: 27 additions & 0 deletions src/components/CategoryPills.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
/**
* Category tags, rendered the same way on post cards and on the post itself.
*/
import { getCategoryUrl } from "../lib/api"

interface Props {
categories: string[]
}

const { categories } = Astro.props
---

{
categories.length > 0 && (
<div class="flex flex-wrap gap-1">
{categories.map((category) => (
<a
href={getCategoryUrl(category)}
class="relative z-10 text-xs font-mono px-2 py-0.5 rounded-full border border-indigo-400 text-indigo-600 hover:border-indigo-600 dark:border-cyan-500 dark:text-cyan-300 dark:hover:border-cyan-300"
>
{category}
</a>
))}
</div>
)
}
4 changes: 2 additions & 2 deletions src/components/Contributors.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import Sponsors from "./Sponsors.astro"
const contributors = await getContributors()
---

<div class="relative mt-36 mb-24 px-8">
<div class="relative max-w-5xl mx-auto">
<div class="relative mt-36 mb-24">
<div class="site-container site-gutter relative">
<div id="supporters" class="text-center mb-12">
<h2 class="text-3xl font-bold text-gray-900 sm:text-4xl dark:text-white">
Our Wonderful Supporters
Expand Down
Loading