mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 02:53:51 +00:00
Thinking of ways to build out a presentation system
This commit is contained in:
24
src/src/components/presentation/FadeIn.tsx
Normal file
24
src/src/components/presentation/FadeIn.tsx
Normal file
@@ -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<FadeInProps> = ({
|
||||
delay = 0,
|
||||
children,
|
||||
className = ""
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
data-animate="fade-in"
|
||||
style={delay ? { animationDelay: `${delay}ms` } : undefined}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
30
src/src/components/presentation/Highlight.tsx
Normal file
30
src/src/components/presentation/Highlight.tsx
Normal file
@@ -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<HighlightProps> = ({
|
||||
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 (
|
||||
<span
|
||||
data-animate="highlight"
|
||||
style={delay ? { animationDelay: `${delay}ms` } : undefined}
|
||||
className={`px-2 py-1 rounded ${colorClasses[color]}`}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
36
src/src/components/presentation/ImageWithCaption.tsx
Normal file
36
src/src/components/presentation/ImageWithCaption.tsx
Normal file
@@ -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<ImageWithCaptionProps> = ({
|
||||
src,
|
||||
alt,
|
||||
caption,
|
||||
width = "100%",
|
||||
delay = 0
|
||||
}) => {
|
||||
return (
|
||||
<figure
|
||||
data-animate="fade-in"
|
||||
style={delay ? { animationDelay: `${delay}ms` } : undefined}
|
||||
className="text-center my-8"
|
||||
>
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
style={{ width, maxWidth: '100%' }}
|
||||
className="rounded-lg shadow-lg mx-auto"
|
||||
/>
|
||||
{caption && (
|
||||
<figcaption className="mt-4 text-sm italic text-gray-400">
|
||||
{caption}
|
||||
</figcaption>
|
||||
)}
|
||||
</figure>
|
||||
);
|
||||
};
|
||||
386
src/src/components/presentation/Presentation.tsx
Normal file
386
src/src/components/presentation/Presentation.tsx
Normal file
@@ -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<PresentationProps> = ({
|
||||
title = "Start Presentation"
|
||||
}) => {
|
||||
const [slides, setSlides] = useState<string[]>([]);
|
||||
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<Element[]>([]);
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const contentRef = useRef<HTMLDivElement>(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 */}
|
||||
<button
|
||||
onClick={startPresentation}
|
||||
className="mb-6 px-4 py-2 bg-blue-bright text-background rounded-lg hover:bg-blue transition-colors font-medium"
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
|
||||
{/* Presentation Container */}
|
||||
{isPresenting && (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="fixed inset-0 z-[9999] bg-black text-foreground flex flex-col"
|
||||
>
|
||||
{/* Main Content */}
|
||||
<div
|
||||
ref={contentRef}
|
||||
className="flex-1 flex items-center justify-center p-8 overflow-auto"
|
||||
>
|
||||
<div className="text-4xl text-center text-yellow-bright">
|
||||
Loading slide...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Slide Counter */}
|
||||
<div className="fixed bottom-8 right-8 bg-gray-800 bg-opacity-80 text-foreground px-4 py-2 rounded-lg font-mono">
|
||||
{currentSlide + 1} / {slides.length}
|
||||
</div>
|
||||
|
||||
{/* Help Toggle */}
|
||||
<button
|
||||
onClick={() => setShowHelp(prev => !prev)}
|
||||
className="fixed bottom-8 left-8 bg-gray-800 bg-opacity-80 text-blue-bright border border-gray-600 px-4 py-2 rounded-lg font-mono hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
?
|
||||
</button>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="fixed bottom-0 left-0 h-1 bg-yellow-bright transition-all duration-300"
|
||||
style={{ width: `${progress}%` }} />
|
||||
|
||||
{/* Blackout Overlay */}
|
||||
{isBlackedOut && (
|
||||
<div className="fixed inset-0 bg-black z-[10000] transition-opacity duration-300" />
|
||||
)}
|
||||
|
||||
{/* Help Overlay */}
|
||||
{showHelp && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-[10001]">
|
||||
<div className="bg-gray-800 p-8 rounded-lg max-w-md">
|
||||
<h3 className="text-xl font-bold text-yellow-bright mb-6 text-center">
|
||||
🎮 Presentation Controls
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<kbd className="bg-gray-900 px-2 py-1 rounded text-blue-bright font-mono text-sm">→</kbd>
|
||||
<kbd className="bg-gray-900 px-2 py-1 rounded text-blue-bright font-mono text-sm">Space</kbd>
|
||||
<span>Next slide/animation</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<kbd className="bg-gray-900 px-2 py-1 rounded text-blue-bright font-mono text-sm">←</kbd>
|
||||
<span>Previous slide/animation</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<kbd className="bg-gray-900 px-2 py-1 rounded text-blue-bright font-mono text-sm">B</kbd>
|
||||
<span>Toggle blackout</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<kbd className="bg-gray-900 px-2 py-1 rounded text-blue-bright font-mono text-sm">Home</kbd>
|
||||
<span>First slide</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<kbd className="bg-gray-900 px-2 py-1 rounded text-blue-bright font-mono text-sm">End</kbd>
|
||||
<span>Last slide</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<kbd className="bg-gray-900 px-2 py-1 rounded text-blue-bright font-mono text-sm">Esc</kbd>
|
||||
<span>Exit presentation</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<kbd className="bg-gray-900 px-2 py-1 rounded text-blue-bright font-mono text-sm">?</kbd>
|
||||
<span>Toggle this help</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Global styles for presentation mode */}
|
||||
{isPresenting && (
|
||||
<style jsx global>{`
|
||||
.presentation-active {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.presentation-active > *:not(.fixed) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.presentation-slide {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.presentation-slide.centered {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.slide-title-overlay {
|
||||
position: absolute;
|
||||
top: 2rem;
|
||||
left: 2rem;
|
||||
right: 2rem;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.slide-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.presentation-slide h1 {
|
||||
font-size: 3rem;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #fabd2f;
|
||||
}
|
||||
|
||||
.presentation-slide h2 {
|
||||
font-size: 2.5rem;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 1rem;
|
||||
color: #83a598;
|
||||
}
|
||||
|
||||
.presentation-slide h3 {
|
||||
font-size: 2rem;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #b8bb26;
|
||||
}
|
||||
|
||||
.presentation-slide p {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.presentation-slide ul, .presentation-slide ol {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.8;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
.presentation-slide li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.presentation-slide code {
|
||||
background: #282828;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25rem;
|
||||
font-family: 'Comic Code', monospace;
|
||||
color: #d3869b;
|
||||
}
|
||||
|
||||
.presentation-slide pre {
|
||||
background: #282828;
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.presentation-slide pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: #ebdbb2;
|
||||
}
|
||||
|
||||
[data-animate="fade-in"] {
|
||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.presentation-slide h1 { font-size: 2rem; }
|
||||
.presentation-slide h2 { font-size: 1.75rem; }
|
||||
.presentation-slide h3 { font-size: 1.5rem; }
|
||||
.presentation-slide p { font-size: 1.25rem; }
|
||||
.presentation-slide ul, .presentation-slide ol { font-size: 1.1rem; }
|
||||
}
|
||||
`}</style>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
34
src/src/components/presentation/Slide.tsx
Normal file
34
src/src/components/presentation/Slide.tsx
Normal file
@@ -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<SlideProps> = ({
|
||||
title,
|
||||
centered = false,
|
||||
children,
|
||||
className = ""
|
||||
}) => {
|
||||
return (
|
||||
<section
|
||||
className={`presentation-slide ${centered ? 'centered' : ''} ${className}`}
|
||||
data-title={title}
|
||||
>
|
||||
{title && (
|
||||
<div className="slide-title-overlay">
|
||||
<h1 className="text-4xl md:text-6xl font-bold text-yellow-bright mb-8">
|
||||
{title}
|
||||
</h1>
|
||||
</div>
|
||||
)}
|
||||
<div className="slide-content">
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
25
src/src/components/presentation/SlideIn.tsx
Normal file
25
src/src/components/presentation/SlideIn.tsx
Normal file
@@ -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<SlideInProps> = ({
|
||||
direction = 'up',
|
||||
delay = 0,
|
||||
children,
|
||||
className = ""
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
data-animate="slide-in"
|
||||
data-direction={direction}
|
||||
style={delay ? { animationDelay: `${delay}ms` } : undefined}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
33
src/src/components/presentation/TwoColumn.tsx
Normal file
33
src/src/components/presentation/TwoColumn.tsx
Normal file
@@ -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<TwoColumnProps> = ({
|
||||
leftWidth = "1fr",
|
||||
rightWidth = "1fr",
|
||||
gap = "2rem",
|
||||
left,
|
||||
right
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className="grid items-start w-full md:grid-cols-2 grid-cols-1"
|
||||
style={{
|
||||
gridTemplateColumns: `${leftWidth} ${rightWidth}`,
|
||||
gap: gap
|
||||
}}
|
||||
>
|
||||
<div className="left-column">
|
||||
{left}
|
||||
</div>
|
||||
<div className="right-column">
|
||||
{right}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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()),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
181
src/src/content/resources/curriculum/python/intro-to-python.mdx
Normal file
181
src/src/content/resources/curriculum/python/intro-to-python.mdx
Normal file
@@ -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";
|
||||
|
||||
<Slide title="Introduction to Python Programming" centered>
|
||||
|
||||
# Welcome to Python! 🐍
|
||||
|
||||
**A beginner-friendly programming language**
|
||||
|
||||
<FadeIn delay={500}>
|
||||
|
||||
- Easy to learn and read
|
||||
- Powerful and versatile
|
||||
- Great for beginners
|
||||
- Used by major companies
|
||||
|
||||
</FadeIn>
|
||||
|
||||
</Slide>
|
||||
|
||||
<Slide title="What is Python?">
|
||||
|
||||
## What is Python?
|
||||
|
||||
<FadeIn>
|
||||
|
||||
Python is a <Highlight color="yellow">high-level programming language</Highlight> that emphasizes:
|
||||
|
||||
- **Readability** - Code that looks like English
|
||||
- **Simplicity** - Easy to learn and use
|
||||
- **Versatility** - Can be used for many things
|
||||
|
||||
</FadeIn>
|
||||
|
||||
<FadeIn delay={600}>
|
||||
|
||||
### Created by Guido van Rossum in 1991
|
||||
|
||||
Named after "Monty Python's Flying Circus" 🎭
|
||||
|
||||
</FadeIn>
|
||||
|
||||
</Slide>
|
||||
|
||||
<Slide title="Why Learn Python?">
|
||||
|
||||
## Why Learn Python?
|
||||
|
||||
<TwoColumn
|
||||
left={
|
||||
<FadeIn>
|
||||
<h3>🌐 Web Development</h3>
|
||||
<p>Build websites and web applications</p>
|
||||
|
||||
<h3>🤖 Data Science & AI</h3>
|
||||
<p>Analyze data and build machine learning models</p>
|
||||
</FadeIn>
|
||||
}
|
||||
right={
|
||||
<FadeIn delay={300}>
|
||||
<h3>🎮 Game Development</h3>
|
||||
<p>Create games and interactive applications</p>
|
||||
|
||||
<h3>🔧 Automation</h3>
|
||||
<p>Automate repetitive tasks</p>
|
||||
</FadeIn>
|
||||
}
|
||||
/>
|
||||
|
||||
</Slide>
|
||||
|
||||
<Slide title="Your First Python Program">
|
||||
|
||||
## Your First Python Program
|
||||
|
||||
<FadeIn>
|
||||
|
||||
Let's write the classic "Hello, World!" program:
|
||||
|
||||
```python
|
||||
print("Hello, World!")
|
||||
```
|
||||
|
||||
</FadeIn>
|
||||
|
||||
<FadeIn delay={500}>
|
||||
|
||||
### 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`
|
||||
|
||||
</FadeIn>
|
||||
|
||||
</Slide>
|
||||
|
||||
<Slide title="Variables and Data Types">
|
||||
|
||||
## Variables and Data Types
|
||||
|
||||
<FadeIn>
|
||||
|
||||
### Creating Variables
|
||||
```python
|
||||
name = "Alice" # String
|
||||
age = 25 # Integer
|
||||
height = 5.6 # Float
|
||||
is_student = True # Boolean
|
||||
```
|
||||
|
||||
</FadeIn>
|
||||
|
||||
<FadeIn delay={400}>
|
||||
|
||||
### Python is <Highlight color="green">dynamically typed</Highlight>
|
||||
You don't need to declare the type!
|
||||
|
||||
```python
|
||||
x = 42 # x is an integer
|
||||
x = "Hello" # Now x is a string
|
||||
```
|
||||
|
||||
</FadeIn>
|
||||
|
||||
</Slide>
|
||||
|
||||
<Slide title="Next Steps" centered>
|
||||
|
||||
## 🎉 Congratulations!
|
||||
|
||||
<FadeIn>
|
||||
|
||||
You've learned the basics of Python programming!
|
||||
|
||||
</FadeIn>
|
||||
|
||||
<FadeIn delay={400}>
|
||||
|
||||
### What's Next?
|
||||
- Practice with small projects
|
||||
- Learn about modules and packages
|
||||
- Explore Python libraries
|
||||
- Build something cool!
|
||||
|
||||
</FadeIn>
|
||||
|
||||
<FadeIn delay={800}>
|
||||
|
||||
**Ready to code? Let's build something amazing! 🚀**
|
||||
|
||||
</FadeIn>
|
||||
|
||||
</Slide>
|
||||
|
||||
## 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!
|
||||
@@ -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";
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{title}</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<!-- OpenGraph -->
|
||||
<meta property="og:image" content={ogImage} />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<!-- Twitter -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:image" content={ogImage} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<!-- Basic meta description for search engines -->
|
||||
<meta name="description" content={description} />
|
||||
<!-- Also used in OpenGraph for social media sharing -->
|
||||
<meta property="og:description" content={description} />
|
||||
<link rel="icon" type="image/jpeg" href="/me.jpeg" />
|
||||
<ClientRouter
|
||||
defaultTransition={false}
|
||||
handleFocus={false}
|
||||
/>
|
||||
<style>
|
||||
::view-transition-new(:root) {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
::view-transition-old(:root) {
|
||||
animation: 90ms ease-out both fade-out;
|
||||
}
|
||||
@keyframes fade-out {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-background text-foreground min-h-screen flex flex-col">
|
||||
<main class="flex-1 flex flex-col">
|
||||
<div class="max-w-5xl mx-auto pt-12 px-4 py-8 flex-1">
|
||||
<Background layout="content" position="right" client:only="react" transition:persist />
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
<Background layout="content" position="left" client:only="react" transition:persist />
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener("astro:after-navigation", () => {
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
73
src/src/pages/resources/[...slug].astro
Normal file
73
src/src/pages/resources/[...slug].astro
Normal file
@@ -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"
|
||||
});
|
||||
---
|
||||
|
||||
<ContentLayout
|
||||
title={`${resource.data.title} | Timothy Pidashev`}
|
||||
description={resource.data.description}
|
||||
>
|
||||
<Presentation title="Start Presentation" />
|
||||
|
||||
<div class="relative max-w-8xl mx-auto">
|
||||
<article class="prose prose-invert prose-lg mx-auto max-w-4xl">
|
||||
|
||||
{resource.data.image && (
|
||||
<div class="-mx-4 sm:mx-0 mb-8">
|
||||
<img
|
||||
src={resource.data.image}
|
||||
alt={resource.data.title}
|
||||
class="w-full h-auto rounded-lg object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h1 class="text-3xl pt-4">{resource.data.title}</h1>
|
||||
<p class="lg:text-2xl sm:text-lg">{resource.data.description}</p>
|
||||
|
||||
<div class="mt-4 md:mt-6">
|
||||
<div class="flex flex-wrap items-center gap-2 md:gap-3 text-sm md:text-base text-foreground/80">
|
||||
<span class="text-purple">{resource.data.duration}</span>
|
||||
<span class="text-foreground/50">•</span>
|
||||
<time dateTime={resource.data.date instanceof Date ? resource.data.date.toISOString() : resource.data.date} class="text-blue">
|
||||
{formattedDate}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 mt-4 md:mt-6">
|
||||
{resource.data.tags.map((tag) => (
|
||||
<span class="text-xs md:text-base text-aqua hover:text-aqua-bright transition-colors duration-200">
|
||||
#{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div class="prose prose-invert prose-lg max-w-none">
|
||||
<Content />
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</ContentLayout>
|
||||
@@ -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()
|
||||
);
|
||||
---
|
||||
|
||||
<ResourcesLayout title={title} description={description}>
|
||||
</ResourcesLayout>
|
||||
<ContentLayout
|
||||
title="Resources | Timothy Pidashev"
|
||||
description="Educational resources, curriculum materials, and presentation slides."
|
||||
>
|
||||
<div class="max-w-6xl mx-auto pt-24 sm:pt-32 px-4">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold text-purple mb-12 text-center leading-relaxed">
|
||||
Educational Resources <br className="sm:hidden" />
|
||||
& Materials
|
||||
</h1>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{allResources.map((resource) => (
|
||||
<a
|
||||
href={`/resources/${resource.slug}`}
|
||||
class="group block p-6 bg-background border border-foreground/20 rounded-lg hover:border-purple-bright/50 transition-all duration-300"
|
||||
>
|
||||
{resource.data.image && (
|
||||
<div class="aspect-video w-full mb-4 rounded-md overflow-hidden bg-foreground/5">
|
||||
<img
|
||||
src={resource.data.image}
|
||||
alt={resource.data.title}
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h3 class="text-xl font-bold text-foreground/90 group-hover:text-purple-bright transition-colors mb-2">
|
||||
{resource.data.title}
|
||||
</h3>
|
||||
|
||||
<p class="text-foreground/70 mb-4 line-clamp-3">
|
||||
{resource.data.description}
|
||||
</p>
|
||||
|
||||
<div class="flex items-center gap-4 text-sm text-foreground/60 mb-4">
|
||||
<span>⏱️ {resource.data.duration}</span>
|
||||
<span>📅 {resource.data.date.toLocaleDateString()}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{resource.data.tags.map((tag) => (
|
||||
<span class="text-xs px-2 py-1 bg-foreground/10 text-foreground/70 rounded">
|
||||
#{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ContentLayout>
|
||||
|
||||
Reference in New Issue
Block a user