mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 02:53:51 +00:00
Fixed
This commit is contained in:
@@ -1,373 +0,0 @@
|
|||||||
---
|
|
||||||
// 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>
|
|
||||||
326
src/src/components/resources/presentation.astro
Normal file
326
src/src/components/resources/presentation.astro
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
<button id="presentation-button" class="presentation-hidden" type="button"
|
||||||
|
>Start Presentation</button
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="presentation-progress"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const button = document.getElementById(
|
||||||
|
"presentation-button"
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
|
||||||
|
let slides = Array.from(document.querySelectorAll(".presentation-slide"));
|
||||||
|
|
||||||
|
let slide = 0;
|
||||||
|
let presenter = false;
|
||||||
|
|
||||||
|
const presentationId = window.location.href;
|
||||||
|
|
||||||
|
const nextSlide = () => {
|
||||||
|
if (slide === slides.length - 1) {
|
||||||
|
return slide;
|
||||||
|
}
|
||||||
|
|
||||||
|
return slide + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevSlide = () => {
|
||||||
|
if (slide === 0) {
|
||||||
|
return slide;
|
||||||
|
}
|
||||||
|
|
||||||
|
return slide - 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextClass = "presentation-next";
|
||||||
|
const currClass = "presentation-current";
|
||||||
|
const prevClass = "presentation-prev";
|
||||||
|
|
||||||
|
const transitionClasses = [nextClass, currClass, prevClass];
|
||||||
|
|
||||||
|
const keyHandlers: Record<string, () => number> = {
|
||||||
|
ArrowRight: nextSlide,
|
||||||
|
ArrowLeft: prevSlide,
|
||||||
|
};
|
||||||
|
|
||||||
|
const displaySlides = () => {
|
||||||
|
for (let i = 0; i < slides.length; i++) {
|
||||||
|
slides[i].classList.remove("active", "inactive", ...transitionClasses);
|
||||||
|
|
||||||
|
if (i === slide) {
|
||||||
|
slides[i].classList.add("active", currClass);
|
||||||
|
} else {
|
||||||
|
slides[i].classList.add("inactive");
|
||||||
|
|
||||||
|
if (i > slide) {
|
||||||
|
slides[i].classList.add(nextClass);
|
||||||
|
} else {
|
||||||
|
slides[i].classList.add(prevClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let presenting = false
|
||||||
|
const startPresentation = () => {
|
||||||
|
button.innerHTML = "Resume presentation";
|
||||||
|
document.body.classList.add("presentation-overflow-hidden");
|
||||||
|
|
||||||
|
presenting = true
|
||||||
|
displaySlides();
|
||||||
|
setProgress();
|
||||||
|
initListeners()
|
||||||
|
};
|
||||||
|
|
||||||
|
const endPresentation = () => {
|
||||||
|
document.body.classList.remove("presentation-overflow-hidden");
|
||||||
|
|
||||||
|
presenting = false
|
||||||
|
slides.map((s) =>
|
||||||
|
s.classList.remove("active", "inactive", ...transitionClasses)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPresenter = () => {
|
||||||
|
presenter = true;
|
||||||
|
document.body.classList.add("presentation-presenter")
|
||||||
|
};
|
||||||
|
|
||||||
|
const setProgress = () => {
|
||||||
|
const progress = ((slide+1)/slides.length)*100;
|
||||||
|
document.body.style.setProperty('--presentation-progress', `${progress}%`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const transition = (nextSlide: number) => {
|
||||||
|
if (!presenting) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slide === nextSlide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
slides.forEach((s) => s.classList.remove(...transitionClasses));
|
||||||
|
|
||||||
|
|
||||||
|
slide = nextSlide;
|
||||||
|
|
||||||
|
displaySlides();
|
||||||
|
setProgress();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let listenersInitialized = false
|
||||||
|
const initListeners = () => {
|
||||||
|
if (listenersInitialized) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listenersInitialized= true
|
||||||
|
window.addEventListener("keyup", (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
const isEscape = ev.key === "Escape";
|
||||||
|
if (isEscape) {
|
||||||
|
endPresentation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSpace = ev.key === " ";
|
||||||
|
if (isSpace) {
|
||||||
|
setPresenter();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSlide = keyHandlers[ev.key];
|
||||||
|
|
||||||
|
if (!getSlide) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextSlide = getSlide();
|
||||||
|
transition(nextSlide);
|
||||||
|
});
|
||||||
|
|
||||||
|
let touchstartX = 0;
|
||||||
|
let touchendX = 0;
|
||||||
|
const handleGesure = () => {
|
||||||
|
const magnitude = Math.abs(touchstartX - touchendX);
|
||||||
|
|
||||||
|
if (magnitude < 40) {
|
||||||
|
// Ignore since this could be a scroll up/down
|
||||||
|
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;
|
||||||
|
handleGesure();
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no presentation on the page then we don't initialize
|
||||||
|
if (slides.length) {
|
||||||
|
button.classList.remove("presentation-hidden");
|
||||||
|
button.addEventListener("click", startPresentation);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
.presentation-progress {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-overflow-hidden {
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
.presentation-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4 {
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-slide.large {
|
||||||
|
font-size: x-large;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-progress {
|
||||||
|
transition: width 1000ms;
|
||||||
|
display: block;
|
||||||
|
visibility: visible;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20;
|
||||||
|
top:0px;
|
||||||
|
left: 0px;
|
||||||
|
width: var(--presentation-progress);
|
||||||
|
height: .25rem;
|
||||||
|
background: var(--color-brand-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-slide {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
visibility: visible;
|
||||||
|
|
||||||
|
transition: transform 300ms ease-in-out;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background-color: var(--color-base);
|
||||||
|
color: var(--color-on-base);
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
padding: 2rem 4rem;
|
||||||
|
|
||||||
|
z-index: 10;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
&.centered {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.highlight{
|
||||||
|
background-color: var(--color-brand);
|
||||||
|
color: var(--color-on-brand)
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-slide-only {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.astro-code {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
&.presentation-presenter {
|
||||||
|
.presentation-slide {
|
||||||
|
border: none;
|
||||||
|
border-bottom: solid 8px var(--color-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-note {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 24px;
|
||||||
|
opacity: .8;
|
||||||
|
right: 24px;
|
||||||
|
left: 25%;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-slide-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-next {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-current {
|
||||||
|
transform: translateX(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-prev {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-note {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-presenter {
|
||||||
|
.presentation-slide {
|
||||||
|
border: dotted 8px var(--color-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ensure that notes are visible if presentation mode is active, even if
|
||||||
|
not presenting */
|
||||||
|
.presentation-note {
|
||||||
|
display: block;
|
||||||
|
/* intentionally obnoxios color to draw attention */
|
||||||
|
background-color: crimson;
|
||||||
|
padding: 24px;
|
||||||
|
color: white;
|
||||||
|
font-size: xx-large;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -4,8 +4,7 @@ export interface Props {
|
|||||||
highlight?: boolean
|
highlight?: boolean
|
||||||
large?: boolean
|
large?: boolean
|
||||||
}
|
}
|
||||||
|
const { centered = true, highlight, large } = Astro.props
|
||||||
const { centered, highlight, large} = Astro.props
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="presentation-slide" class:list={{ centered, highlight, large }}>
|
<section class="presentation-slide" class:list={{ centered, highlight, large }}>
|
||||||
@@ -6,9 +6,12 @@ duration: "2 hours"
|
|||||||
tags: ["python", "programming", "beginner", "fundamentals"]
|
tags: ["python", "programming", "beginner", "fundamentals"]
|
||||||
---
|
---
|
||||||
|
|
||||||
import Slide from "@/components/presentation/Slide.astro";
|
import Slide from "@/components/resources/slide.astro";
|
||||||
|
import Presentation from "@/components/resources/presentation.astro";
|
||||||
|
|
||||||
<Slide centered>
|
<Presentation />
|
||||||
|
|
||||||
|
<Slide large>
|
||||||
# Welcome to Python! 🐍
|
# Welcome to Python! 🐍
|
||||||
|
|
||||||
**A beginner-friendly programming language**
|
**A beginner-friendly programming language**
|
||||||
@@ -250,7 +253,7 @@ print(calculator())
|
|||||||
```
|
```
|
||||||
</Slide>
|
</Slide>
|
||||||
|
|
||||||
<Slide centered>
|
<Slide >
|
||||||
## 🎉 Congratulations!
|
## 🎉 Congratulations!
|
||||||
|
|
||||||
You've learned the basics of Python programming!
|
You've learned the basics of Python programming!
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
---
|
---
|
||||||
// src/layouts/presentation.astro
|
|
||||||
import "@/style/globals.css";
|
import "@/style/globals.css";
|
||||||
import { ClientRouter } from "astro:transitions";
|
import { ClientRouter } from "astro:transitions";
|
||||||
|
|
||||||
|
import Header from "@/components/header";
|
||||||
|
import Footer from "@/components/footer";
|
||||||
|
import Background from "@/components/background";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -34,10 +37,34 @@ const ogImage = "https://timmypidashev.dev/og-image.jpg";
|
|||||||
defaultTransition={false}
|
defaultTransition={false}
|
||||||
handleFocus={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>
|
</head>
|
||||||
<body class="bg-background text-foreground min-h-screen">
|
<body class="bg-background text-foreground min-h-screen flex flex-col">
|
||||||
<main class="w-full h-screen">
|
<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 />
|
<slot />
|
||||||
|
</div>
|
||||||
|
<Background layout="content" position="left" client:only="react" transition:persist />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("astro:after-navigation", () => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
|
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
|
|
||||||
import ContentLayout from "@/layouts/content.astro";
|
import ContentLayout from "@/layouts/content.astro";
|
||||||
import { Comments } from "@/components/blog/comments";
|
import { Comments } from "@/components/blog/comments";
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,31 @@
|
|||||||
---
|
---
|
||||||
|
export const prerender = true;
|
||||||
|
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import ContentLayout from "@/layouts/content.astro";
|
|
||||||
import PresentationLayout from "@/layouts/presentation.astro";
|
|
||||||
import Presentation from "@/components/presentation/Presentation.astro";
|
|
||||||
|
|
||||||
const { slug } = Astro.params;
|
import ResourceLayout from "@/layouts/resource.astro";
|
||||||
|
|
||||||
const resources = await getCollection("resources");
|
export async function getStaticPaths() {
|
||||||
const resource = resources.find(item => item.slug === slug);
|
const resources = await getCollection("resources");
|
||||||
|
return resources.map(resource => ({
|
||||||
if (!resource) {
|
params: { slug: resource.slug },
|
||||||
return new Response(null, {
|
props: { resource },
|
||||||
status: 404,
|
}));
|
||||||
statusText: 'Not found'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { resource } = Astro.props;
|
||||||
const { Content } = await resource.render();
|
const { Content } = await resource.render();
|
||||||
|
|
||||||
const formattedDate = new Date(resource.data.date).toLocaleDateString("en-US", {
|
|
||||||
year: "numeric",
|
|
||||||
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;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<LayoutComponent
|
<ResourceLayout title={`${resource.data.title} | Timothy Pidashev`}>
|
||||||
title={`${resource.data.title} | Timothy Pidashev`}
|
<article class="w-full mx-auto px-4 pt-6 sm:pt-12">
|
||||||
description={resource.data.description}
|
<header class="mb-8">
|
||||||
>
|
<h1 class="text-3xl sm:text-4xl font-bold text-yellow-bright mb-4">
|
||||||
<Presentation autoStart={isCurriculum} title="Start Presentation" />
|
{resource.data.title}
|
||||||
|
</h1>
|
||||||
<div class="relative max-w-8xl mx-auto">
|
</header>
|
||||||
|
<div class="prose prose-invert prose-lg max-w-none">
|
||||||
<Content />
|
<Content />
|
||||||
</div>
|
</div>
|
||||||
</LayoutComponent>
|
</article>
|
||||||
|
</ResourceLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user