diff --git a/src/src/components/presentation/FadeIn.tsx b/src/src/components/presentation/FadeIn.tsx new file mode 100644 index 0000000..2f01c0f --- /dev/null +++ b/src/src/components/presentation/FadeIn.tsx @@ -0,0 +1,24 @@ +// src/components/presentation/FadeIn.tsx +import React from 'react'; + +interface FadeInProps { + delay?: number; + children: React.ReactNode; + className?: string; +} + +export const FadeIn: React.FC = ({ + delay = 0, + children, + className = "" +}) => { + return ( +
+ {children} +
+ ); +}; diff --git a/src/src/components/presentation/Highlight.tsx b/src/src/components/presentation/Highlight.tsx new file mode 100644 index 0000000..2fe2e19 --- /dev/null +++ b/src/src/components/presentation/Highlight.tsx @@ -0,0 +1,30 @@ +// src/components/presentation/Highlight.tsx +interface HighlightProps { + color?: 'yellow' | 'blue' | 'green' | 'red' | 'purple'; + delay?: number; + children: React.ReactNode; +} + +export const Highlight: React.FC = ({ + color = 'yellow', + delay = 0, + children +}) => { + const colorClasses = { + yellow: 'bg-yellow-bright/20 text-yellow-bright', + blue: 'bg-blue-bright/20 text-blue-bright', + green: 'bg-green-bright/20 text-green-bright', + red: 'bg-red-bright/20 text-red-bright', + purple: 'bg-purple-bright/20 text-purple-bright', + }; + + return ( + + {children} + + ); +}; diff --git a/src/src/components/presentation/ImageWithCaption.tsx b/src/src/components/presentation/ImageWithCaption.tsx new file mode 100644 index 0000000..e193cb1 --- /dev/null +++ b/src/src/components/presentation/ImageWithCaption.tsx @@ -0,0 +1,36 @@ +// src/components/presentation/ImageWithCaption.tsx +interface ImageWithCaptionProps { + src: string; + alt: string; + caption?: string; + width?: string; + delay?: number; +} + +export const ImageWithCaption: React.FC = ({ + src, + alt, + caption, + width = "100%", + delay = 0 +}) => { + return ( +
+ {alt} + {caption && ( +
+ {caption} +
+ )} +
+ ); +}; diff --git a/src/src/components/presentation/Presentation.tsx b/src/src/components/presentation/Presentation.tsx new file mode 100644 index 0000000..926ffb7 --- /dev/null +++ b/src/src/components/presentation/Presentation.tsx @@ -0,0 +1,386 @@ +// src/components/presentation/Presentation.tsx +import React, { useEffect, useState, useRef } from 'react'; + +interface PresentationProps { + title?: string; +} + +export const Presentation: React.FC = ({ + title = "Start Presentation" +}) => { + const [slides, setSlides] = useState([]); + const [currentSlide, setCurrentSlide] = useState(0); + const [isPresenting, setIsPresenting] = useState(false); + const [isBlackedOut, setIsBlackedOut] = useState(false); + const [showHelp, setShowHelp] = useState(false); + const [animationIndex, setAnimationIndex] = useState(0); + const [currentAnimations, setCurrentAnimations] = useState([]); + + const containerRef = useRef(null); + const contentRef = useRef(null); + + // Initialize slides when component mounts + useEffect(() => { + const initializeSlides = () => { + const slideElements = Array.from(document.querySelectorAll('.presentation-slide')); + console.log('Found slides:', slideElements.length); + + const slideHTML = slideElements.map(el => el.outerHTML); + setSlides(slideHTML); + }; + + // Run after a short delay to ensure all components are rendered + const timer = setTimeout(initializeSlides, 100); + return () => clearTimeout(timer); + }, []); + + // Setup slide animations + const setupSlideAnimations = () => { + if (!contentRef.current) return; + + const animations = Array.from(contentRef.current.querySelectorAll('[data-animate]')); + setCurrentAnimations(animations); + setAnimationIndex(0); + + // Hide all animated elements initially + animations.forEach(el => { + const element = el as HTMLElement; + element.style.opacity = '0'; + element.style.transform = 'translateY(20px)'; + }); + }; + + // Show next animation + const showNextAnimation = (): boolean => { + if (animationIndex < currentAnimations.length) { + const element = currentAnimations[animationIndex] as HTMLElement; + element.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; + element.style.opacity = '1'; + element.style.transform = 'translateY(0)'; + setAnimationIndex(prev => prev + 1); + return true; + } + return false; + }; + + // Show previous animation + const showPrevAnimation = (): boolean => { + if (animationIndex > 0) { + const newIndex = animationIndex - 1; + const element = currentAnimations[newIndex] as HTMLElement; + element.style.opacity = '0'; + element.style.transform = 'translateY(20px)'; + setAnimationIndex(newIndex); + return true; + } + return false; + }; + + // Display slide + const displaySlide = (index: number) => { + if (index >= 0 && index < slides.length && contentRef.current) { + contentRef.current.innerHTML = slides[index]; + setCurrentSlide(index); + setupSlideAnimations(); + } + }; + + // Navigation functions + const nextSlide = () => { + if (showNextAnimation()) return; + + if (currentSlide < slides.length - 1) { + displaySlide(currentSlide + 1); + } + }; + + const prevSlide = () => { + if (showPrevAnimation()) return; + + if (currentSlide > 0) { + displaySlide(currentSlide - 1); + // Show all animations on previous slide + setTimeout(() => { + currentAnimations.forEach(el => { + const element = el as HTMLElement; + element.style.opacity = '1'; + element.style.transform = 'translateY(0)'; + }); + setAnimationIndex(currentAnimations.length); + }, 50); + } + }; + + // Start presentation + const startPresentation = () => { + setIsPresenting(true); + displaySlide(0); + document.body.classList.add('presentation-active'); + }; + + // End presentation + const endPresentation = () => { + setIsPresenting(false); + setIsBlackedOut(false); + setShowHelp(false); + document.body.classList.remove('presentation-active'); + }; + + // Keyboard controls + useEffect(() => { + const handleKeyPress = (event: KeyboardEvent) => { + if (!isPresenting) return; + + switch (event.key) { + case 'Escape': + endPresentation(); + break; + case 'ArrowRight': + case ' ': + event.preventDefault(); + nextSlide(); + break; + case 'ArrowLeft': + event.preventDefault(); + prevSlide(); + break; + case 'b': + case 'B': + event.preventDefault(); + setIsBlackedOut(prev => !prev); + break; + case '?': + event.preventDefault(); + setShowHelp(prev => !prev); + break; + case 'Home': + event.preventDefault(); + displaySlide(0); + break; + case 'End': + event.preventDefault(); + displaySlide(slides.length - 1); + break; + } + }; + + document.addEventListener('keydown', handleKeyPress); + return () => document.removeEventListener('keydown', handleKeyPress); + }, [isPresenting, currentSlide, animationIndex, currentAnimations, slides.length]); + + const progress = slides.length > 0 ? ((currentSlide + 1) / slides.length) * 100 : 0; + + if (slides.length === 0) { + return null; // Don't show button if no slides + } + + return ( + <> + {/* Start Presentation Button */} + + + {/* Presentation Container */} + {isPresenting && ( +
+ {/* Main Content */} +
+
+ Loading slide... +
+
+ + {/* Slide Counter */} +
+ {currentSlide + 1} / {slides.length} +
+ + {/* Help Toggle */} + + + {/* Progress Bar */} +
+ + {/* Blackout Overlay */} + {isBlackedOut && ( +
+ )} + + {/* Help Overlay */} + {showHelp && ( +
+
+

+ 🎮 Presentation Controls +

+
+
+ + Space + Next slide/animation +
+
+ + Previous slide/animation +
+
+ B + Toggle blackout +
+
+ Home + First slide +
+
+ End + Last slide +
+
+ Esc + Exit presentation +
+
+ ? + Toggle this help +
+
+
+
+ )} +
+ )} + + {/* Global styles for presentation mode */} + {isPresenting && ( + + )} + + ); +}; diff --git a/src/src/components/presentation/Slide.tsx b/src/src/components/presentation/Slide.tsx new file mode 100644 index 0000000..612c106 --- /dev/null +++ b/src/src/components/presentation/Slide.tsx @@ -0,0 +1,34 @@ +// src/components/presentation/Slide.tsx +import React from 'react'; + +interface SlideProps { + title?: string; + centered?: boolean; + children: React.ReactNode; + className?: string; +} + +export const Slide: React.FC = ({ + title, + centered = false, + children, + className = "" +}) => { + return ( +
+ {title && ( +
+

+ {title} +

+
+ )} +
+ {children} +
+
+ ); +}; diff --git a/src/src/components/presentation/SlideIn.tsx b/src/src/components/presentation/SlideIn.tsx new file mode 100644 index 0000000..c18c8f4 --- /dev/null +++ b/src/src/components/presentation/SlideIn.tsx @@ -0,0 +1,25 @@ +// src/components/presentation/SlideIn.tsx +interface SlideInProps { + direction?: 'left' | 'right' | 'up' | 'down'; + delay?: number; + children: React.ReactNode; + className?: string; +} + +export const SlideIn: React.FC = ({ + direction = 'up', + delay = 0, + children, + className = "" +}) => { + return ( +
+ {children} +
+ ); +}; diff --git a/src/src/components/presentation/TwoColumn.tsx b/src/src/components/presentation/TwoColumn.tsx new file mode 100644 index 0000000..730ac9d --- /dev/null +++ b/src/src/components/presentation/TwoColumn.tsx @@ -0,0 +1,33 @@ +// src/components/presentation/TwoColumn.tsx +interface TwoColumnProps { + leftWidth?: string; + rightWidth?: string; + gap?: string; + left: React.ReactNode; + right: React.ReactNode; +} + +export const TwoColumn: React.FC = ({ + leftWidth = "1fr", + rightWidth = "1fr", + gap = "2rem", + left, + right +}) => { + return ( +
+
+ {left} +
+
+ {right} +
+
+ ); +}; diff --git a/src/src/content/config.ts b/src/src/content/config.ts index f36f0ce..0deb18b 100644 --- a/src/src/content/config.ts +++ b/src/src/content/config.ts @@ -25,4 +25,16 @@ export const collections = { image: z.string().optional(), }), }), + resources: defineCollection({ + schema: z.object({ + title: z.string(), + description: z.string(), + date: z.coerce.date().transform((date) => { + return new Date(date.setUTCHours(12, 0, 0, 0)); + }), + duration: z.string(), + image: z.string().optional(), + tags: z.array(z.string()), + }), + }), }; diff --git a/src/src/content/resources/curriculum/python/intro-to-python.mdx b/src/src/content/resources/curriculum/python/intro-to-python.mdx new file mode 100644 index 0000000..bb745f4 --- /dev/null +++ b/src/src/content/resources/curriculum/python/intro-to-python.mdx @@ -0,0 +1,181 @@ +--- +title: "Introduction to Python Programming" +description: "A comprehensive introduction to Python programming fundamentals for beginners" +date: 2025-01-20 +duration: "2 hours" +tags: ["python", "programming", "beginner", "fundamentals"] +--- + +import { Slide } from "@/components/presentation/Slide"; +import { FadeIn } from "@/components/presentation/FadeIn"; +import { Highlight } from "@/components/presentation/Highlight"; +import { TwoColumn } from "@/components/presentation/TwoColumn"; + + + +# Welcome to Python! 🐍 + +**A beginner-friendly programming language** + + + +- Easy to learn and read +- Powerful and versatile +- Great for beginners +- Used by major companies + + + + + + + +## What is Python? + + + +Python is a high-level programming language that emphasizes: + +- **Readability** - Code that looks like English +- **Simplicity** - Easy to learn and use +- **Versatility** - Can be used for many things + + + + + +### Created by Guido van Rossum in 1991 + +Named after "Monty Python's Flying Circus" 🎭 + + + + + + + +## Why Learn Python? + + +

🌐 Web Development

+

Build websites and web applications

+ +

🤖 Data Science & AI

+

Analyze data and build machine learning models

+ + } + right={ + +

🎮 Game Development

+

Create games and interactive applications

+ +

🔧 Automation

+

Automate repetitive tasks

+
+ } +/> + +
+ + + +## Your First Python Program + + + +Let's write the classic "Hello, World!" program: + +```python +print("Hello, World!") +``` + + + + + +### How to run it: +1. Open a text editor +2. Type the code above +3. Save as `hello.py` +4. Run with: `python hello.py` + + + + + + + +## Variables and Data Types + + + +### Creating Variables +```python +name = "Alice" # String +age = 25 # Integer +height = 5.6 # Float +is_student = True # Boolean +``` + + + + + +### Python is dynamically typed +You don't need to declare the type! + +```python +x = 42 # x is an integer +x = "Hello" # Now x is a string +``` + + + + + + + +## 🎉 Congratulations! + + + +You've learned the basics of Python programming! + + + + + +### What's Next? +- Practice with small projects +- Learn about modules and packages +- Explore Python libraries +- Build something cool! + + + + + +**Ready to code? Let's build something amazing! 🚀** + + + + + +## Course Materials + +This curriculum covers the fundamentals of Python programming in an interactive, hands-on format. Students will work through practical examples and complete coding exercises to reinforce their learning. + +### Getting Started + +Before we dive into the presentation slides above, let's set up our development environment and understand what we'll be covering in this course. + +#### What You'll Need + +- A computer with Python installed +- A text editor or IDE (like VS Code) +- Terminal/command prompt access +- About 2 hours of focused time + +Ready to start? Click the "Start Presentation" button above to begin with the interactive slides! diff --git a/src/src/layouts/resources.astro b/src/src/layouts/resources.astro deleted file mode 100644 index cc8b35d..0000000 --- a/src/src/layouts/resources.astro +++ /dev/null @@ -1,71 +0,0 @@ ---- -import "@/style/globals.css"; -import { ClientRouter } from "astro:transitions"; - -import Header from "@/components/header"; -import Footer from "@/components/footer"; -import Background from "@/components/background"; - -export interface Props { - title: string; - description: string; -} - -const { title, description } = Astro.props; -const ogImage = "https://timmypidashev.dev/og-image.jpg"; ---- - - - - {title} - - - - - - - - - - - - - - - - - - - -
-
- -
- -
- -
-
- - - - diff --git a/src/src/pages/resources/[...slug].astro b/src/src/pages/resources/[...slug].astro new file mode 100644 index 0000000..da075ca --- /dev/null +++ b/src/src/pages/resources/[...slug].astro @@ -0,0 +1,73 @@ +--- +import { getCollection } from "astro:content"; +import ContentLayout from "@/layouts/content.astro"; +import { Presentation } from "@/components/presentation/Presentation"; + +const { slug } = Astro.params; + +// Same pattern as your blog +const resources = await getCollection("resources"); +const resource = resources.find(item => item.slug === slug); + +if (!resource) { + return new Response(null, { + status: 404, + statusText: 'Not found' + }); +} + +const { Content } = await resource.render(); + +const formattedDate = new Date(resource.data.date).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric" +}); +--- + + + + +
+
+ + {resource.data.image && ( +
+ {resource.data.title} +
+ )} + +

{resource.data.title}

+

{resource.data.description}

+ +
+
+ {resource.data.duration} + + +
+
+ +
+ {resource.data.tags.map((tag) => ( + + #{tag} + + ))} +
+ +
+ +
+
+
+
diff --git a/src/src/pages/resources/curriculum/keep b/src/src/pages/resources/curriculum/keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/src/pages/resources/index.astro b/src/src/pages/resources/index.astro index 228ee67..37e5db1 100644 --- a/src/src/pages/resources/index.astro +++ b/src/src/pages/resources/index.astro @@ -1,9 +1,61 @@ --- -import ResourcesLayout from "@/layouts/resources.astro"; +// src/pages/resources/index.astro - SIMPLE LISTING +import { getCollection } from "astro:content"; +import ContentLayout from "@/layouts/content.astro"; -const title = "Resources Test"; -const description = "Testing the new polygon background styles"; +const allResources = (await getCollection("resources")).sort( + (a, b) => b.data.date.valueOf() - a.data.date.valueOf() +); --- - - + + + diff --git a/src/src/pages/resources/talks/keep b/src/src/pages/resources/talks/keep deleted file mode 100644 index e69de29..0000000