Create a rich, custom-built presentation page for an MDX article. Each presentation is a unique static React page with bespoke slides, inline SVG visuals, animated flowcharts, diagrams, and varied layouts — NOT a generic bullet-point template. Use this skill whenever the user wants to create a presentation for an article, add slides to an article, make a slideshow, or mentions 'presentazione' in the context of articles. If no specific article is referenced, ask the user which article they want.
Create a unique, high-value presentation page for an MDX article. Each presentation is a custom static page with its own React components, inline SVG graphics, animated flowcharts, and slide layouts designed specifically for that article's content.
The goal is to add value beyond the article text — not to summarize it into bullet points. A presentation should make complex ideas click through visual storytelling: flowcharts that reveal step by step, diagrams that build up, comparisons that animate side by side, code that highlights incrementally.
This skill requires thoughtful analysis. Do not jump into implementation.
If the user didn't specify which article, ask. Articles live in content/articles/*.mdx.
Read the full article. As you read, identify:
Before writing any code, present the user with:
Ask the user: "Does this plan work? Want me to adjust any slides before I build it?"
Wait for confirmation before proceeding.
Think of these as layout patterns you can mix and match. Each presentation will use a different combination depending on the content. You are not limited to these — invent new layouts when the content calls for it.
Each presentation is a static page at src/app/articles/[slug]/presentazione/page.tsx. All presentations use the same standard layout via the reusable PresentationShell component (src/components/presentation/presentation-shell.tsx) to ensure consistency and eliminate repetition. This component handles:
Slides only need to define their visual content — all UI chrome is handled by the shell.
The page should:
<PresentationSlides slug={SLUG} />generateStaticParams and dynamicParams = false so the route is fully static"use client" component in slides.tsx that:
speech.json (if narration exists)slides array of { key, component } entries in narrative order<PresentationShell slug={slug} speechData={speechData} slides={slides} />speechData={null} (or omits the import) when there is no narrationpresentazione/ folder (one slide component per file)src/app/articles/[slug]/presentazione/
├── page.tsx # Server component — metadata, static params, simple render
├── slides.tsx # "use client" — imports PresentationShell from shared components
├── slide-01-*.tsx # Slide 1 component
├── slide-02-*.tsx # Slide 2 component
├── speech.json # Narration text for each slide (optional, required for voice)
└── ... # Continue with one file per slide in this folder
motion/react and motion/react-client (already installed). Every slide should have entrance animations. Flowcharts and diagrams should build incrementally — elements appearing with staggered delays.presentazione/ folder. Avoid monolithic slides.tsx files containing all slide JSX.width/height attributes on main diagram SVGs; use className="w-full" or className="w-full max-w-xs sm:max-w-sm" on the wrapper <div> instead, paired with viewBox. Icon-only SVGs (small decorative icons inside cards) may keep fixed pixel dimensions.max-w-3xl, preferably max-w-4xl or max-w-5xl. Never constrain a diagram to max-w-xl or smaller — this makes visuals feel cramped on large screens. The SVG viewBox should use a generous coordinate space (e.g. 0 0 700 400 for complex flowcharts) so nodes and labels have room to breathe. Overall slide content wrappers should use max-w-5xl or max-w-6xl, not max-w-4xl.flex flex-col h-full py-6 on the outer container (not justify-center) so content fills the full slide height. Add flex-1 to the content grid/list so it expands to fill remaining space after the title. This prevents content from clustering in the vertical center of large screens.text-3xl sm:text-4xl minimum. Subtitles/descriptions use text-base minimum. Card labels use text-sm minimum — never text-xs for primary labels. Eyebrow/tag labels (font-mono uppercase) use text-sm. Inner max-w-5xl wrappers inside an outer max-w-6xl container are redundant — remove them./articles/[slug].Use the theme-aware CSS variables defined in globals.css (--pres-*). Do not use hardcoded hex colors or Tailwind generic colors for presentation elements, especially inside SVGs, to ensure proper light/dark mode support.
var(--pres-bg) /* Main background */
var(--pres-bg-surface) /* Slightly elevated background */
var(--pres-bg-card) /* Card background */
var(--pres-bg-node) /* SVG node background */
var(--pres-text) /* Primary text */
var(--pres-text-sub) /* Secondary text */
var(--pres-muted) /* Muted text/borders */
var(--pres-border) /* Default borders */
var(--pres-accent) /* Primary brand color (purple) */
var(--pres-blue) /* Sky blue for links/accents */
var(--pres-success) /* Emerald green */
var(--pres-warning) /* Amber */
var(--pres-danger) /* Rose red */
For translucent background colors, use tailwind opacity modifiers like bg-[var(--pres-accent)]/10 or use the CSS color-mix function, such as color-mix(in srgb, var(--pres-success) 10%, transparent).
Connectors between boxes (arrows, flow lines) are the most bug-prone part of any SVG diagram. These pitfalls come up every time — internalize them before writing a single path.
Before drawing connectors, make the box layout stable: boxes should sit on a predictable grid, have enough width/height for their labels, and preserve the same gap rhythm horizontally and vertically whenever possible. Clean spacing is not cosmetic here; it is what keeps arrows legible and pointing in an unambiguous direction.
linearGradient on horizontal or vertical pathsSVG <linearGradient> defaults to gradientUnits="objectBoundingBox". For a path like M 302 110 L 496 110 the bounding box has zero height, so the gradient collapses and the stroke renders as completely invisible (even though arrowhead markers still render correctly — which misleads you into thinking the connector works).
Rule: do not use gradient strokes for horizontal/vertical connector lines. Use solid colors:
// ❌ Invisible — bbox collapses to 0 height
<motion.path d="M302 110 L 496 110" stroke="url(#my-gradient)" strokeDasharray="8 8" />
// ✅ Visible
<motion.path d="M302 110 L 496 110" stroke="var(--pres-accent)" strokeDasharray="8 8" />
If you truly need a gradient on a straight line, set gradientUnits="userSpaceOnUse" with explicit x1/y1/x2/y2 in viewBox coordinates.
pathLength animation destroys strokeDasharrayWhen you animate pathLength from 0 to 1 on a motion.path, Motion internally sets stroke-dasharray="1 1" and pathLength="1" to drive the draw-in effect. Any strokeDasharray prop you set is overwritten, so the path renders as a continuous solid line or — worse — invisible dashes.
Rule: pick one:
pathLength, omit strokeDasharray.opacity only, keep strokeDasharray.// ❌ Dashes disappear — pathLength wins
<motion.path strokeDasharray="8 8" initial={{ pathLength: 0 }} animate={{ pathLength: 1 }} />
// ✅ Dashes preserved
<motion.path strokeDasharray="8 8" initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
fill and a matching refXArrowheads via <marker> only show if the marker's <path> has a solid fill (not inherited from the stroke) and the marker's refX/refY positions the tip at the path endpoint. Define one marker per color you need — markers do not inherit the stroke color.
<marker
id="arrow-accent"
viewBox="0 0 10 10"
refX="9"
refY="5"
markerWidth="6"
markerHeight="6"
orient="auto-start-reverse"
>
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--pres-accent)" />
</marker>
The line itself must stay clearly visible, not just the arrowhead. A connector is broken if the marker shows but the stroke is faint, collapsed, hidden behind other shapes, or points in the wrong direction.
If a connector starts/ends exactly on a box's border coordinate, the arrowhead tip is hidden under the rectangle stroke. Leave a 4–8px gap so the arrow visibly enters the next box. For a box at x=500, width=280 (right edge at 780), an incoming arrow should end at x=496, not x=500.
Do not improvise connector coordinates. Before writing paths:
x=40/500, y=50/220).Diagonal connectors between misaligned boxes look messy and are hard to read. Align the boxes first, then the lines are trivial.
A diagram that compiles is not a diagram that works. Always:
pathLength vs strokeDasharray conflict, (c) marker fill color, (d) endpoint hidden under a box edge.The page must work with Next.js static generation. Add generateStaticParams that returns the slug, and set dynamicParams = false. Look at how the article page does it in src/app/articles/[slug]/page.tsx for the pattern.
The article page at src/app/articles/[slug]/page.tsx shows the "Inizia presentazione" button when hasPresentation(slug) returns true. In this project, hasPresentation should check if src/app/articles/[slug]/presentazione/ exists.
After the presentation is built and verified, ask the user:
"Vuoi aggiungere la narrazione vocale a questa presentazione?"
If yes, use the presentation-speech skill (.claude/skills/presentation-speech/SKILL.md) to:
speech.json file from the article textslides.tsxAfter the presentation (and optionally narration) is complete, check if this article already has an ASCII cover in src/components/covers/{slug}.tsx. If not, ask the user:
"Vuoi generare anche la cover ASCII per la card di questo articolo?"
If yes, use the ascii-cover skill (.claude/skills/ascii-cover/SKILL.md) to generate a colored ASCII art component for the article card.
After building:
pnpm build or at minimum check for TypeScript errors