Help us improve
Share bugs, ideas, or general feedback.
From chu-gallery
Generate Astro page files from an existing gallery manifest. Internal step of /gallery workflow - use directly only to regenerate from completed manifests.
npx claudepluginhub joshuarweaver/cascade-content-creation-misc-1 --plugin chufucious-chu-gallery-pluginHow this skill is triggered — by the user, by Claude, or both
Slash command
/chu-gallery:skills/generate-astro-galleryThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generates Astro gallery files from a completed manifest. This is typically the final step of `/gallery` workflow.
Creates, curates, and publishes Immich albums organized by geography, theme, or custom criteria. Automates album creation from user prompts like 'create an album from my trip to Italy'.
Image and asset optimization in Astro 6 — <Image />, <Picture />, getImage(), remote images, @astrojs/sharp, Fonts API, OG image generation with Satori, Cloudinary/Imgix. Use for any image optimization or asset handling task.
Comprehensive best practices, routing patterns, component architecture, and static site generation techniques for building high-performance Astro websites.
Share bugs, ideas, or general feedback.
Generates Astro gallery files from a completed manifest. This is typically the final step of /gallery workflow.
src/data/gallery-manifests/{folder}.json with all blocks populatedsrc/assets/images/photos/{folder}//generate-astro-gallery {manifest.json}
Example: /generate-astro-gallery src/data/gallery-manifests/cabo-2025.json
~/.claude/skills/review-image-batch/references/layout-guide.md to understand component constraints{
"gallery": "cabo-2025",
"title": "CABO 2025",
"slug": "cabo",
"year": "2025",
"images": [...],
"blocks": [...]
}
Before generating, run the validation script to fix common agent errors:
bun scripts/validate-gallery-manifest.ts src/data/gallery-manifests/{folder}.json
This script automatically fixes:
type → layoutnote → notesAnd validates layout values are valid component names.
Create src/data/{folder}.ts:
import type { Chapter } from "../consts";
export const title = "{TITLE}";
export const chapters: Chapter[] = [];
For multi-chapter galleries:
export const chapters: Chapter[] = [
{ name: "CH I", href: "/" },
{ name: "CH II", href: "part2" },
];
Create src/pages/photographing/{slug}/index.astro:
---
import BaseLayout from "../../../layouts/BaseLayout.astro";
import {
FullBleedImage,
WideImage,
TwoUpLayout,
ThreeUpLayout,
FourUpGrid,
SplitLayout,
OffsetImage,
InsetImage,
Spacer
} from "../../../components/photos";
import { title, chapters } from "../../../data/{folder}";
import { getPageTitle } from "../../../lib/navigation";
const pageTitle = getPageTitle(title, Astro.url.pathname, chapters);
// Image imports - one per image in manifest
import img001 from "../../../assets/images/photos/{folder}/DSCF1234.jpeg";
import img002 from "../../../assets/images/photos/{folder}/DSCF1235.jpeg";
// ...
---
<BaseLayout title={pageTitle} navTitle={title} chapters={chapters}>
<!-- Layout blocks from manifest -->
</BaseLayout>
Add entry to src/pages/photographing/~/index.astro:
<a href="/photographing/{slug}/">{TITLE}</a>
| Manifest Layout | Astro Component | Image Count | Props |
|---|---|---|---|
| FullBleed | <FullBleedImage> | 1 | priority |
| WideImage | <WideImage> | 1 | priority |
| TwoUp | <TwoUpLayout> | 2 | — |
| ThreeUp | <ThreeUpLayout> | 3 | — |
| FourUp | <FourUpGrid> | 4 | — |
| SplitLayout | <SplitLayout> | 2 | ratio |
| OffsetImage | <OffsetImage> | 1 | align, size, priority |
| InsetImage | <InsetImage> | 1 | priority |
| Spacer | <Spacer> | 0 | size (small/medium/large/xl) |
| Chapter | (structural) | 0 | name, slug - splits into pages |
<!-- Single image layouts -->
<FullBleedImage src={img} alt="description" priority={true} />
<WideImage src={img} alt="description" />
<!-- Whitespace components -->
<OffsetImage src={img} alt="description" align="left" size="medium" />
<OffsetImage src={img} alt="description" align="right" size="small" priority={true} />
<InsetImage src={img} alt="description" />
<InsetImage src={img} alt="description" priority={true} />
<Spacer size="medium" />
<!-- Multi-image layouts -->
<TwoUpLayout images={[
{ src: img1, alt: "description 1" },
{ src: img2, alt: "description 2" },
]} />
<ThreeUpLayout images={[
{ src: img1, alt: "description 1" },
{ src: img2, alt: "description 2" },
{ src: img3, alt: "description 3" },
]} />
<FourUpGrid images={[
{ src: img1, alt: "description 1" },
{ src: img2, alt: "description 2" },
{ src: img3, alt: "description 3" },
{ src: img4, alt: "description 4" },
]} />
<SplitLayout
images={[
{ src: img1, alt: "description 1" },
{ src: img2, alt: "description 2" },
]}
ratio="2/3"
/>
When a block has props in the manifest, pass them to the component:
Manifest:
{
"layout": "OffsetImage",
"images": ["portrait.jpeg"],
"props": { "align": "left", "size": "small" }
}
Generated:
<OffsetImage src={portrait} alt="..." align="left" size="small" />
Generate semantic import names from block notes:
sunsetHero or baySunsetOr use sequential naming: img001, img002, etc.
Add priority={true} to the first <FullBleedImage> or <WideImage> to preload above-the-fold content.
This skill is used by:
/gallery - Final step after batch review is completeWhen manifest contains Chapter blocks, generate multiple pages instead of one.
const chapters = manifest.blocks.filter(b => b.layout === "Chapter");
const hasChapters = chapters.length > 0;
const chapterData = chapters.map(c => ({
name: c.props.name,
slug: c.props.slug,
href: c.props.slug === "/" ? "/" : c.props.slug
}));
// Group blocks between Chapter markers
const chapterBlocks = [];
let current = [];
for (const block of manifest.blocks) {
if (block.layout === "Chapter") {
if (current.length) chapterBlocks.push(current);
current = [];
} else {
current.push(block);
}
}
if (current.length) chapterBlocks.push(current);
export const chapters: Chapter[] = [
{ name: "Celestún", href: "/" },
{ name: "Uxmal", href: "uxmal" },
{ name: "Haciendas", href: "haciendas" },
];
| Chapter Slug | Generated File |
|---|---|
/ | index.astro |
uxmal | uxmal.astro |
haciendas | haciendas.astro |
Each page imports only the images used in that chapter.
Same as single-page, but only includes blocks for that chapter:
---
// ... imports ...
import { title, chapters } from "../../../data/{folder}";
// Only images for THIS chapter
import img001 from "...";
import img002 from "...";
---
<BaseLayout title={pageTitle} navTitle={title} chapters={chapters}>
<!-- Only blocks for this chapter -->
</BaseLayout>
Add priority={true} to the first FullBleed/WideImage in each chapter page.