This commit is contained in:
2025-08-21 22:53:37 -07:00
parent 30f264a6bb
commit 9496030d41
12 changed files with 633 additions and 694 deletions

View File

@@ -1,24 +0,0 @@
// 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>
);
};

View File

@@ -1,30 +0,0 @@
// 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>
);
};

View File

@@ -1,36 +0,0 @@
// 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>
);
};

View File

@@ -0,0 +1,373 @@
---
// src/components/presentation/Presentation.astro
export interface Props {
autoStart?: boolean;
title?: string;
}
const { autoStart = false, title = "Start Presentation" } = Astro.props;
---
<button id="presentation-button" class="presentation-hidden mb-6 px-4 py-2 bg-blue-bright text-background rounded-lg hover:bg-blue transition-colors font-medium" type="button">
{title}
</button>
<div class="presentation-progress"></div>
<script define:vars={{ autoStart }}>
const button = document.getElementById("presentation-button");
let slides = [];
let slide = 0;
let presenting = false;
// Function to initialize slides
const initSlides = () => {
const slideElements = Array.from(document.querySelectorAll('.presentation-slide'));
console.log('Found slides:', slideElements.length); // Debug log
slides = slideElements.map((el) => el.outerHTML);
// Show button if we have slides and not auto-starting
if (slides.length && !autoStart) {
button.classList.remove("presentation-hidden");
}
// Auto-start if enabled and we have slides
if (autoStart && slides.length) {
setTimeout(() => {
startPresentation();
}, 100);
}
};
const nextSlide = () => {
if (slide === slides.length - 1) {
return slide;
}
return slide + 1;
};
const prevSlide = () => {
if (slide === 0) {
return slide;
}
return slide - 1;
};
const keyHandlers = {
ArrowRight: nextSlide,
ArrowLeft: prevSlide,
};
const setProgress = () => {
const progress = ((slide + 1) / slides.length) * 100;
document.body.style.setProperty('--presentation-progress', `${progress}%`);
};
const startPresentation = () => {
if (!slides.length) return;
button.innerHTML = "Resume presentation";
document.body.classList.add("presentation-overflow-hidden");
presenting = true;
// Create presentation container and content area
const container = document.createElement('div');
container.id = 'presentation-container';
container.className = 'presentation-container';
const content = document.createElement('main');
content.id = 'presentation-content';
// Initialize with first slide
const slideWrapper = document.createElement('div');
slideWrapper.innerHTML = slides[slide];
const slideElement = slideWrapper.querySelector('.presentation-slide');
if (slideElement) {
content.appendChild(slideElement);
} else {
content.innerHTML = slides[slide];
}
// Add slide counter
const counter = document.createElement('div');
counter.id = 'slide-counter';
counter.className = 'fixed bottom-8 right-8 bg-gray-800 bg-opacity-80 text-foreground px-4 py-2 rounded-lg font-mono z-20';
counter.textContent = `${slide + 1} / ${slides.length}`;
container.appendChild(content);
container.appendChild(counter);
document.body.appendChild(container);
setProgress();
initListeners();
console.log(`Presentation started with ${slides.length} slides`); // Debug log
};
const endPresentation = () => {
document.body.classList.remove("presentation-overflow-hidden");
presenting = false;
const container = document.getElementById('presentation-container');
if (container) {
container.remove();
}
};
const transition = (nextSlideIndex) => {
if (!presenting || nextSlideIndex === slide || !slides[nextSlideIndex]) {
return;
}
slide = nextSlideIndex;
const content = document.getElementById('presentation-content');
const counter = document.getElementById('slide-counter');
if (content) {
// Clear current content
content.innerHTML = '';
// Create a wrapper div and set the slide content
const slideWrapper = document.createElement('div');
slideWrapper.innerHTML = slides[slide];
// Ensure the slide has proper presentation styling
const slideElement = slideWrapper.querySelector('.presentation-slide');
if (slideElement) {
content.appendChild(slideElement);
} else {
// Fallback: just add the content directly
content.innerHTML = slides[slide];
}
}
if (counter) {
counter.textContent = `${slide + 1} / ${slides.length}`;
}
setProgress();
console.log(`Transitioned to slide ${slide + 1}/${slides.length}`); // Debug log
};
let listenersInitialized = false;
const initListeners = () => {
if (listenersInitialized) {
return;
}
listenersInitialized = true;
window.addEventListener("keyup", (ev) => {
console.log(`Key pressed: ${ev.key}`); // Debug log
ev.preventDefault();
const isEscape = ev.key === "Escape";
if (isEscape) {
endPresentation();
return;
}
const getSlide = keyHandlers[ev.key];
if (!getSlide) {
return;
}
const nextSlideIndex = getSlide();
console.log(`Current slide: ${slide}, Next slide: ${nextSlideIndex}`); // Debug log
transition(nextSlideIndex);
});
let touchstartX = 0;
let touchendX = 0;
const handleGesture = () => {
const magnitude = Math.abs(touchstartX - touchendX);
if (magnitude < 40) {
return;
}
if (touchendX < touchstartX) {
transition(nextSlide());
}
if (touchendX > touchstartX) {
transition(prevSlide());
}
};
document.addEventListener("touchstart", (ev) => {
touchstartX = ev.changedTouches[0].screenX;
}, false);
document.addEventListener("touchend", (event) => {
touchendX = event.changedTouches[0].screenX;
handleGesture();
}, false);
};
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSlides);
} else {
// DOM is already loaded
initSlides();
}
// Also try again after a short delay to catch any dynamically loaded content
setTimeout(initSlides, 500);
// Initialize button click handler
if (button) {
button.addEventListener("click", startPresentation);
}
</script>
<style is:global>
.presentation-progress {
display: none;
}
.presentation-overflow-hidden {
overflow: hidden;
.presentation-hidden {
display: none;
}
.presentation-progress {
transition: width 1000ms;
display: block;
position: fixed;
z-index: 21;
top: 0px;
left: 0px;
width: var(--presentation-progress);
height: 0.25rem;
background: #fabd2f;
}
}
.presentation-container {
z-index: 10;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: auto;
background-color: #000000;
}
#presentation-content {
display: flex;
flex-direction: column;
background-color: #000000;
color: #ebdbb2;
box-sizing: border-box;
min-height: 100vh;
width: 100%;
padding: 4rem;
}
.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;
}
.presentation-slide.highlight {
background-color: #fabd2f;
color: #000000;
}
.presentation-slide.large {
font-size: x-large;
}
.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;
}
@media (max-width: 768px) {
#presentation-content {
padding: 2rem;
}
.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; }
}
.presentation-hidden {
display: none;
}
</style>

View File

@@ -1,386 +0,0 @@
// 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>
)}
</>
);
};

View File

@@ -0,0 +1,13 @@
---
export interface Props {
centered?: boolean
highlight?: boolean
large?: boolean
}
const { centered, highlight, large} = Astro.props
---
<section class="presentation-slide" class:list={{ centered, highlight, large }}>
<slot />
</section>

View File

@@ -1,34 +0,0 @@
// 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>
);
};

View File

@@ -1,25 +0,0 @@
// 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>
);
};

View File

@@ -1,33 +0,0 @@
// 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>
);
};

View File

@@ -6,111 +6,68 @@ 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>
import Slide from "@/components/presentation/Slide.astro";
<Slide 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?">
<Slide>
## What is Python?
<FadeIn>
Python is a <Highlight color="yellow">high-level programming language</Highlight> that emphasizes:
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
</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?">
<Slide>
## Why Learn Python?
<TwoColumn
left={
<FadeIn>
<h3>🌐 Web Development</h3>
<p>Build websites and web applications</p>
### 🌐 Web Development
Build websites and web applications
<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>
### 🤖 Data Science & AI
Analyze data and build machine learning models
<h3>🔧 Automation</h3>
<p>Automate repetitive tasks</p>
</FadeIn>
}
/>
### 🎮 Game Development
Create games and interactive applications
### 🔧 Automation
Automate repetitive tasks
</Slide>
<Slide title="Your First Python Program">
<Slide>
## 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">
<Slide>
## Variables and Data Types
<FadeIn>
### Creating Variables
```python
name = "Alice" # String
@@ -119,48 +76,192 @@ height = 5.6 # Float
is_student = True # Boolean
```
</FadeIn>
<FadeIn delay={400}>
### Python is <Highlight color="green">dynamically typed</Highlight>
### 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
```
</FadeIn>
</Slide>
<Slide title="Next Steps" centered>
<Slide>
## Working with Strings
### String Operations
```python
# Creating strings
greeting = "Hello"
name = "World"
# Concatenation
message = greeting + ", " + name + "!"
print(message) # Output: Hello, World!
# String methods
text = "python programming"
print(text.upper()) # PYTHON PROGRAMMING
print(text.capitalize()) # Python programming
print(text.replace("python", "Python")) # Python programming
```
</Slide>
<Slide>
## Numbers and Math
### Basic Math Operations
```python
# Arithmetic operators
a = 10
b = 3
print(a + b) # Addition: 13
print(a - b) # Subtraction: 7
print(a * b) # Multiplication: 30
print(a / b) # Division: 3.333...
print(a // b) # Floor division: 3
print(a % b) # Modulo (remainder): 1
print(a ** b) # Exponentiation: 1000
```
</Slide>
<Slide>
## Lists - Storing Multiple Values
### Creating and Using Lists
```python
# Creating a list
fruits = ["apple", "banana", "orange"]
numbers = [1, 2, 3, 4, 5]
# Accessing items (indexing starts at 0)
print(fruits[0]) # apple
print(fruits[-1]) # orange (last item)
# Adding items
fruits.append("grape")
fruits.insert(1, "strawberry")
# List methods
print(len(fruits)) # Length of list
print("apple" in fruits) # Check if item exists
```
</Slide>
<Slide>
## Control Flow - Making Decisions
### If Statements
```python
age = 18
if age >= 18:
print("You can vote!")
elif age >= 16:
print("You can drive!")
else:
print("You're still young!")
# Comparison operators
# == (equal), != (not equal)
# > (greater), < (less)
# >= (greater or equal), <= (less or equal)
```
</Slide>
<Slide>
## Loops - Repeating Actions
### For Loops
```python
# Loop through a list
fruits = ["apple", "banana", "orange"]
for fruit in fruits:
print(f"I like {fruit}")
# Loop through numbers
for i in range(5):
print(f"Count: {i}") # 0, 1, 2, 3, 4
```
### While Loops
```python
count = 0
while count < 3:
print(f"Count is {count}")
count += 1 # Same as count = count + 1
```
</Slide>
<Slide>
## Functions - Organizing Your Code
### Creating Functions
```python
def greet(name):
"""This function greets someone"""
return f"Hello, {name}!"
def add_numbers(a, b):
"""Add two numbers and return the result"""
result = a + b
return result
# Using functions
message = greet("Alice")
print(message) # Hello, Alice!
sum_result = add_numbers(5, 3)
print(sum_result) # 8
```
</Slide>
<Slide>
## Practice Exercise
### Build a Simple Calculator
```python
def calculator():
print("Simple Calculator")
print("Operations: +, -, *, /")
num1 = float(input("Enter first number: "))
operation = input("Enter operation (+, -, *, /): ")
num2 = float(input("Enter second number: "))
if operation == "+":
result = num1 + num2
elif operation == "-":
result = num1 - num2
elif operation == "*":
result = num1 * num2
elif operation == "/":
if num2 != 0:
result = num1 / num2
else:
return "Error: Division by zero!"
else:
return "Error: Invalid operation!"
return f"Result: {result}"
# Run the calculator
print(calculator())
```
</Slide>
<Slide 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
@@ -169,7 +270,7 @@ This curriculum covers the fundamentals of Python programming in an interactive,
### 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.
The presentation above covers all the core concepts you need to start programming in Python. Each slide builds upon the previous one, taking you from basic concepts to writing your first functional program.
#### What You'll Need
@@ -178,4 +279,13 @@ Before we dive into the presentation slides above, let's set up our development
- 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!
#### Course Objectives
By the end of this presentation, you'll be able to:
- Understand Python syntax and basic programming concepts
- Work with variables, strings, numbers, and lists
- Use conditional statements and loops
- Create and use functions
- Build a simple calculator program
The presentation automatically starts when you visit this page. Use arrow keys to navigate between slides, or escape to exit presentati

View File

@@ -0,0 +1,43 @@
---
// src/layouts/presentation.astro
import "@/style/globals.css";
import { ClientRouter } from "astro:transitions";
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}
/>
</head>
<body class="bg-background text-foreground min-h-screen">
<main class="w-full h-screen">
<slot />
</main>
</body>
</html>

View File

@@ -1,11 +1,11 @@
---
import { getCollection } from "astro:content";
import ContentLayout from "@/layouts/content.astro";
import { Presentation } from "@/components/presentation/Presentation";
import PresentationLayout from "@/layouts/presentation.astro";
import Presentation from "@/components/presentation/Presentation.astro";
const { slug } = Astro.params;
// Same pattern as your blog
const resources = await getCollection("resources");
const resource = resources.find(item => item.slug === slug);
@@ -23,51 +23,19 @@ const formattedDate = new Date(resource.data.date).toLocaleDateString("en-US", {
month: "long",
day: "numeric"
});
// Check if this is a curriculum resource to auto-start presentation
const isCurriculum = resource.slug.includes('curriculum');
const LayoutComponent = isCurriculum ? PresentationLayout : ContentLayout;
---
<ContentLayout
<LayoutComponent
title={`${resource.data.title} | Timothy Pidashev`}
description={resource.data.description}
>
<Presentation title="Start Presentation" />
<Presentation autoStart={isCurriculum} 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>
</LayoutComponent>