mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:01:09 +02:00
- Rename dynamic routes from [slug].astro to [...slug].astro for multi-segment paths - Replace deprecated entry.slug with entry.id across all components and utils - Fix TypeScript implicit any types in TemplateFilters and prompt-templates - Add proper type narrowing for feature.note in pricing page - Remove unused marked import from FAQCard - Delete invalid placeholder content files - Add shared-landing-ui dependency and integrate StepsSection/PricingSection - Update tailwind config with shared-landing-ui content paths - Add global.css with Indigo/Violet dark theme variables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
305 lines
10 KiB
Text
305 lines
10 KiB
Text
---
|
|
import { getCollection } from 'astro:content';
|
|
import Layout from '../../layouts/Layout.astro';
|
|
import TutorialCard from '../../components/tutorials/TutorialCard.astro';
|
|
import StepIndicator from '../../components/tutorials/StepIndicator.astro';
|
|
import {
|
|
getDifficultyDisplayName,
|
|
getDifficultyIcon,
|
|
getDifficultyColor,
|
|
getCategoryDisplayName,
|
|
getCategoryIcon,
|
|
getRelatedTutorials,
|
|
} from '../../utils/tutorials';
|
|
|
|
export async function getStaticPaths() {
|
|
const tutorials = await getCollection('tutorials');
|
|
return tutorials.map((tutorial) => ({
|
|
params: { slug: tutorial.id },
|
|
props: { tutorial },
|
|
}));
|
|
}
|
|
|
|
const { tutorial } = Astro.props;
|
|
const { Content } = await tutorial.render();
|
|
const relatedTutorials = await getRelatedTutorials(tutorial, 3);
|
|
|
|
const { data } = tutorial;
|
|
const difficultyColor = getDifficultyColor(data.difficulty);
|
|
---
|
|
|
|
<Layout title={`${data.title} - Tutorial | Picture`} description={data.description}>
|
|
<div class="bg-dark-bg min-h-screen">
|
|
<!-- Breadcrumbs -->
|
|
<div class="max-w-4xl mx-auto px-6 pt-8">
|
|
<nav class="flex items-center gap-2 text-sm text-gray-400">
|
|
<a href="/" class="hover:text-primary">Home</a>
|
|
<span>/</span>
|
|
<a href="/tutorials" class="hover:text-primary">Tutorials</a>
|
|
<span>/</span>
|
|
<a href={`/tutorials?category=${data.category}`} class="hover:text-primary">
|
|
{getCategoryDisplayName(data.category)}
|
|
</a>
|
|
<span>/</span>
|
|
<span class="text-white">{data.title}</span>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Hero -->
|
|
<div class="max-w-4xl mx-auto px-6 py-12">
|
|
<div class="flex items-center gap-3 mb-6 flex-wrap">
|
|
<span class="text-3xl">{data.icon}</span>
|
|
<span
|
|
class="px-3 py-1 bg-dark-elevated border border-dark-border rounded-lg text-sm text-gray-300"
|
|
>
|
|
{getCategoryIcon(data.category)} {getCategoryDisplayName(data.category)}
|
|
</span>
|
|
<span
|
|
class={`px-3 py-1 bg-dark-elevated border border-dark-border rounded-lg text-sm ${difficultyColor}`}
|
|
>
|
|
{getDifficultyIcon(data.difficulty)} {getDifficultyDisplayName(data.difficulty)}
|
|
</span>
|
|
{data.hasVideo && (
|
|
<span
|
|
class="px-3 py-1 bg-dark-elevated border border-dark-border rounded-lg text-sm text-gray-300"
|
|
>
|
|
🎥 Video included
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<h1 class="text-4xl md:text-5xl font-bold text-white mb-6">{data.title}</h1>
|
|
<p class="text-xl text-gray-300 mb-8">{data.description}</p>
|
|
|
|
<!-- Meta Info -->
|
|
<div class="flex items-center gap-6 text-sm text-gray-400 flex-wrap mb-8">
|
|
<div class="flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
<span>{data.estimatedTime}</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
|
></path>
|
|
</svg>
|
|
<span>{data.steps.length} steps</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
></path>
|
|
</svg>
|
|
<span>Updated {data.lastUpdated.toLocaleDateString()}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- What you'll learn -->
|
|
{data.whatYouWillLearn.length > 0 && (
|
|
<div class="bg-dark-elevated border border-dark-border rounded-xl p-6 mb-8">
|
|
<h2 class="text-lg font-semibold text-white mb-4">What you'll learn:</h2>
|
|
<ul class="space-y-2">
|
|
{data.whatYouWillLearn.map((item) => (
|
|
<li class="flex items-start gap-3 text-gray-300">
|
|
<svg class="w-5 h-5 text-primary mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
clip-rule="evenodd"
|
|
></path>
|
|
</svg>
|
|
<span>{item}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
<!-- Prerequisites -->
|
|
{data.prerequisites.length > 0 && (
|
|
<div class="bg-yellow-500/10 border border-yellow-500/20 rounded-xl p-6 mb-8">
|
|
<h3 class="text-lg font-semibold text-yellow-400 mb-3 flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
|
clip-rule="evenodd"></path>
|
|
</svg>
|
|
Prerequisites
|
|
</h3>
|
|
<ul class="space-y-1">
|
|
{data.prerequisites.map((item) => (
|
|
<li class="text-yellow-100">• {item}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
<!-- Video -->
|
|
{data.hasVideo && data.videoUrl && (
|
|
<div class="mb-12">
|
|
<div class="aspect-video bg-dark-elevated rounded-xl overflow-hidden border border-dark-border">
|
|
<iframe
|
|
src={data.videoUrl}
|
|
class="w-full h-full"
|
|
allowfullscreen
|
|
title={data.title}
|
|
></iframe>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<!-- Step Indicator (Sticky) -->
|
|
<StepIndicator steps={data.steps} />
|
|
|
|
<!-- Tutorial Content -->
|
|
<div class="max-w-4xl mx-auto px-6 pb-12">
|
|
<article
|
|
class="prose prose-invert prose-lg max-w-none
|
|
prose-headings:text-white prose-headings:font-bold
|
|
prose-h2:text-3xl prose-h2:mt-12 prose-h2:mb-6
|
|
prose-h3:text-2xl prose-h3:mt-8 prose-h3:mb-4
|
|
prose-p:text-gray-300 prose-p:leading-relaxed
|
|
prose-a:text-primary prose-a:no-underline hover:prose-a:underline
|
|
prose-strong:text-white prose-strong:font-semibold
|
|
prose-code:text-primary prose-code:bg-dark-elevated prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded
|
|
prose-pre:bg-dark-elevated prose-pre:border prose-pre:border-dark-border
|
|
prose-ul:text-gray-300 prose-ol:text-gray-300
|
|
prose-li:marker:text-primary
|
|
prose-blockquote:border-l-primary prose-blockquote:bg-dark-elevated prose-blockquote:py-1"
|
|
>
|
|
<Content />
|
|
</article>
|
|
|
|
<!-- Tips -->
|
|
{data.tips.length > 0 && (
|
|
<div class="mt-12 bg-primary/10 border border-primary/20 rounded-xl p-6">
|
|
<h3 class="text-xl font-semibold text-primary mb-4 flex items-center gap-2">
|
|
<span>💡</span> Pro Tips
|
|
</h3>
|
|
<ul class="space-y-2">
|
|
{data.tips.map((tip) => (
|
|
<li class="text-gray-300">• {tip}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
<!-- Common Mistakes -->
|
|
{data.commonMistakes.length > 0 && (
|
|
<div class="mt-8 bg-red-500/10 border border-red-500/20 rounded-xl p-6">
|
|
<h3 class="text-xl font-semibold text-red-400 mb-4 flex items-center gap-2">
|
|
<span>⚠️</span> Common Mistakes to Avoid
|
|
</h3>
|
|
<ul class="space-y-2">
|
|
{data.commonMistakes.map((mistake) => (
|
|
<li class="text-gray-300">• {mistake}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
|
|
<!-- Troubleshooting -->
|
|
{data.troubleshooting.length > 0 && (
|
|
<div class="mt-8 bg-dark-elevated border border-dark-border rounded-xl p-6">
|
|
<h3 class="text-xl font-semibold text-white mb-4 flex items-center gap-2">
|
|
<span>🔧</span> Troubleshooting
|
|
</h3>
|
|
<div class="space-y-4">
|
|
{data.troubleshooting.map(({ problem, solution }) => (
|
|
<div>
|
|
<p class="font-medium text-yellow-400 mb-1">Problem: {problem}</p>
|
|
<p class="text-gray-300">Solution: {solution}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<!-- Downloadable Resources -->
|
|
{data.downloadableResources.length > 0 && (
|
|
<div class="mt-12 bg-dark-elevated border border-dark-border rounded-xl p-6">
|
|
<h3 class="text-xl font-semibold text-white mb-4 flex items-center gap-2">
|
|
<span>📥</span> Download Resources
|
|
</h3>
|
|
<div class="space-y-3">
|
|
{data.downloadableResources.map((resource) => (
|
|
<a
|
|
href={resource.url}
|
|
class="flex items-center justify-between p-4 bg-dark-bg border border-dark-border rounded-lg hover:border-primary transition group"
|
|
>
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center text-primary">
|
|
{resource.type === 'template' && '📄'}
|
|
{resource.type === 'preset' && '⚙️'}
|
|
{resource.type === 'example' && '🎨'}
|
|
{resource.type === 'cheatsheet' && '📋'}
|
|
</div>
|
|
<div>
|
|
<p class="text-white font-medium">{resource.title}</p>
|
|
<p class="text-sm text-gray-400 capitalize">{resource.type}</p>
|
|
</div>
|
|
</div>
|
|
<svg
|
|
class="w-5 h-5 text-gray-400 group-hover:text-primary transition"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
|
></path>
|
|
</svg>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<!-- CTA -->
|
|
<div class="mt-12 bg-gradient-to-r from-primary/10 to-purple-500/10 border border-primary/20 rounded-xl p-8 text-center">
|
|
<h3 class="text-2xl font-bold text-white mb-3">Ready to try it yourself?</h3>
|
|
<p class="text-gray-300 mb-6">
|
|
Put what you learned into practice with Picture's AI image generator.
|
|
</p>
|
|
<a
|
|
href="#"
|
|
class="px-8 py-3 bg-primary text-white font-medium rounded-lg inline-block hover:bg-primary/90 transition"
|
|
>
|
|
Start Creating
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Related Tutorials -->
|
|
{relatedTutorials.length > 0 && (
|
|
<div class="bg-dark-elevated py-16">
|
|
<div class="max-w-6xl mx-auto px-6">
|
|
<h2 class="text-2xl font-bold text-white mb-8">Related Tutorials</h2>
|
|
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{relatedTutorials.map((relatedTutorial) => (
|
|
<TutorialCard tutorial={relatedTutorial} />
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Layout>
|