Generate Astro page files from an existing gallery manifest. Internal step of /gallery workflow - use directly only to regenerate from completed manifests.
Generates Astro gallery pages from completed manifests with layout component mapping.
/plugin marketplace add chufucious/chu-gallery-plugin/plugin install chufucious-chu-gallery@chufucious/chu-gallery-pluginThis skill inherits all available tools. When active, it can use any tool Claude has access to.
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.
This skill should be used when the user asks about libraries, frameworks, API references, or needs code examples. Activates for setup questions, code generation involving libraries, or mentions of specific frameworks like React, Vue, Next.js, Prisma, Supabase, etc.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.