Skip to content

Instantly share code, notes, and snippets.

@alexknowshtml
Created March 12, 2026 00:00
Show Gist options
  • Select an option

  • Save alexknowshtml/002e0825b1959d4e19123dc2b5742a84 to your computer and use it in GitHub Desktop.

Select an option

Save alexknowshtml/002e0825b1959d4e19123dc2b5742a84 to your computer and use it in GitHub Desktop.
Good Neighbors Hackathon Welcome Slides
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Good Neighbors Hackathon — Welcome</title>
<!-- Fonts: Fraunces (display) + DM Mono (body) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Fraunces:opsz,wght@9..144,400;9..144,700;9..144,900&display=swap" rel="stylesheet">
<style>
/* ===========================================
GOOD NEIGHBORS — CSS CUSTOM PROPERTIES
=========================================== */
:root {
--paper: #F7F3ED;
--ink: #1C1C1C;
--red: #E63946;
--blue: #457B9D;
--yellow: #F4A261;
--green: #2A9D8F;
--cream: #FAF6EE;
--font-display: 'Fraunces', serif;
--font-body: 'DM Mono', monospace;
--title-size: clamp(2.5rem, 7vw, 6rem);
--h2-size: clamp(1.5rem, 4vw, 3rem);
--h3-size: clamp(1.1rem, 2.5vw, 1.75rem);
--body-size: clamp(0.8rem, 1.5vw, 1.125rem);
--small-size: clamp(0.7rem, 1.1vw, 0.875rem);
--slide-padding: clamp(1.5rem, 5vw, 5rem);
--content-gap: clamp(0.75rem, 2vw, 2rem);
--element-gap: clamp(0.25rem, 1vw, 1rem);
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--duration-normal: 0.6s;
}
/* ===========================================
BASE STYLES
=========================================== */
* { margin: 0; padding: 0; box-sizing: border-box; }
/* --- VIEWPORT BASE (from viewport-base.css) --- */
html, body {
height: 100%;
overflow-x: hidden;
}
html {
scroll-snap-type: y mandatory;
scroll-behavior: smooth;
}
.slide {
width: 100vw;
height: 100vh;
height: 100dvh;
overflow: hidden;
scroll-snap-align: start;
display: flex;
flex-direction: column;
position: relative;
}
.slide-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
max-height: 100%;
overflow: hidden;
padding: var(--slide-padding);
}
.card, .container, .content-box {
max-width: min(90vw, 1000px);
max-height: min(80vh, 700px);
}
.feature-list, .bullet-list {
gap: clamp(0.4rem, 1vh, 1rem);
}
.feature-list li, .bullet-list li {
font-size: var(--body-size);
line-height: 1.4;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
gap: clamp(0.5rem, 1.5vw, 1rem);
}
img, .image-container {
max-width: 100%;
max-height: min(50vh, 400px);
object-fit: contain;
}
/* Responsive breakpoints */
@media (max-height: 700px) {
:root {
--slide-padding: clamp(0.75rem, 3vw, 2rem);
--content-gap: clamp(0.4rem, 1.5vw, 1rem);
--title-size: clamp(1.75rem, 5vw, 3.5rem);
--h2-size: clamp(1.25rem, 3vw, 2rem);
}
}
@media (max-height: 600px) {
:root {
--slide-padding: clamp(0.5rem, 2.5vw, 1.5rem);
--content-gap: clamp(0.3rem, 1vw, 0.75rem);
--title-size: clamp(1.5rem, 4.5vw, 2.5rem);
--body-size: clamp(0.7rem, 1.2vw, 0.95rem);
}
.nav-dots, .keyboard-hint, .decorative { display: none; }
}
@media (max-height: 500px) {
:root {
--slide-padding: clamp(0.4rem, 2vw, 1rem);
--title-size: clamp(1.25rem, 4vw, 2rem);
--h2-size: clamp(1rem, 2.5vw, 1.5rem);
--body-size: clamp(0.65rem, 1vw, 0.85rem);
}
}
@media (max-width: 600px) {
:root { --title-size: clamp(1.75rem, 9vw, 3rem); }
.grid { grid-template-columns: 1fr; }
.sponsor-grid { grid-template-columns: 1fr !important; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.2s !important;
}
html { scroll-behavior: auto; }
}
/* --- END VIEWPORT BASE --- */
body {
font-family: var(--font-body);
background: var(--paper);
color: var(--ink);
}
/* ===========================================
GRAIN TEXTURE OVERLAY
Warm analog feel across all slides
=========================================== */
.slide::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0.08;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
background-size: 150px;
z-index: 1;
}
/* Ensure content sits above grain */
.slide-content, .slide > * { position: relative; z-index: 2; }
/* ===========================================
TYPOGRAPHY
=========================================== */
h1, h2, h3 {
font-family: var(--font-display);
font-weight: 900;
line-height: 1.1;
letter-spacing: -0.02em;
}
h1 { font-size: var(--title-size); }
h2 { font-size: var(--h2-size); }
h3 { font-size: var(--h3-size); }
p, li, span, .mono {
font-family: var(--font-body);
font-size: var(--body-size);
line-height: 1.6;
}
.label {
font-family: var(--font-body);
font-size: var(--small-size);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.1em;
}
/* ===========================================
COLOR ACCENT BLOCKS
Flat, confident color markers
=========================================== */
.accent-bar {
width: clamp(3rem, 8vw, 6rem);
height: clamp(4px, 0.5vh, 6px);
border-radius: 2px;
}
.accent-red { background: var(--red); }
.accent-blue { background: var(--blue); }
.accent-yellow { background: var(--yellow); }
.accent-green { background: var(--green); }
/* ===========================================
ANIMATIONS
Warm, editorial — staggered reveals
=========================================== */
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity var(--duration-normal) var(--ease-out-expo),
transform var(--duration-normal) var(--ease-out-expo);
}
.slide.visible .reveal {
opacity: 1;
transform: translateY(0);
}
.reveal:nth-child(1) { transition-delay: 0.1s; }
.reveal:nth-child(2) { transition-delay: 0.2s; }
.reveal:nth-child(3) { transition-delay: 0.3s; }
.reveal:nth-child(4) { transition-delay: 0.4s; }
.reveal:nth-child(5) { transition-delay: 0.5s; }
.reveal:nth-child(6) { transition-delay: 0.6s; }
/* ===========================================
SLIDE-SPECIFIC STYLES
=========================================== */
/* --- TITLE SLIDE --- */
.title-slide {
background: var(--ink);
color: var(--paper);
align-items: center;
text-align: center;
}
.title-slide .slide-content {
align-items: center;
gap: var(--content-gap);
}
.title-slide h1 {
font-size: clamp(3rem, 9vw, 8rem);
font-optical-sizing: auto;
}
.title-slide .event-date {
color: var(--yellow);
font-family: var(--font-body);
font-size: clamp(1rem, 2vw, 1.5rem);
font-weight: 500;
}
.title-slide .venue {
color: var(--paper);
opacity: 0.6;
}
/* --- SPONSOR SLIDE --- */
.sponsors-slide .slide-content {
gap: var(--content-gap);
}
.sponsor-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: clamp(1rem, 3vw, 2.5rem);
margin-top: var(--content-gap);
}
.sponsor-card {
background: var(--cream);
border-radius: clamp(8px, 1vw, 12px);
padding: clamp(1rem, 2.5vw, 2rem);
display: flex;
flex-direction: column;
gap: clamp(0.25rem, 0.5vw, 0.5rem);
}
.sponsor-card.title-sponsor {
grid-column: 1 / -1;
border-left: clamp(4px, 0.5vw, 6px) solid var(--red);
}
.sponsor-card .sponsor-tier {
color: var(--blue);
}
.sponsor-card h3 {
font-family: var(--font-display);
font-weight: 700;
}
.sponsor-card p {
font-size: var(--small-size);
opacity: 0.7;
}
/* --- WIFI SLIDE --- */
.wifi-slide {
background: var(--blue);
color: var(--paper);
}
.wifi-slide .slide-content {
align-items: center;
text-align: center;
gap: clamp(1.5rem, 4vw, 3rem);
}
.wifi-slide h2 {
font-size: clamp(2rem, 5vw, 4rem);
}
.wifi-box {
display: flex;
flex-direction: column;
gap: clamp(0.75rem, 2vw, 1.5rem);
background: rgba(255,255,255,0.12);
border-radius: clamp(12px, 2vw, 20px);
padding: clamp(1.5rem, 4vw, 3rem) clamp(2rem, 6vw, 5rem);
}
.wifi-field {
display: flex;
flex-direction: column;
gap: clamp(0.15rem, 0.3vw, 0.25rem);
}
.wifi-field .label {
opacity: 0.7;
}
.wifi-field .value {
font-family: var(--font-display);
font-size: clamp(1.5rem, 4vw, 3rem);
font-weight: 700;
letter-spacing: 0.05em;
}
/* --- CODE OF CONDUCT SLIDE --- */
.conduct-slide .slide-content {
gap: var(--content-gap);
}
.conduct-list {
list-style: none;
display: flex;
flex-direction: column;
gap: clamp(0.6rem, 1.5vh, 1.2rem);
margin-top: var(--element-gap);
}
.conduct-list li {
display: flex;
align-items: flex-start;
gap: clamp(0.5rem, 1vw, 1rem);
}
.conduct-list .dot {
width: clamp(8px, 1vw, 12px);
height: clamp(8px, 1vw, 12px);
border-radius: 50%;
flex-shrink: 0;
margin-top: clamp(4px, 0.5vw, 8px);
}
/* --- SCHEDULE SLIDE --- */
.schedule-slide .slide-content {
gap: var(--content-gap);
}
.schedule-list {
list-style: none;
display: flex;
flex-direction: column;
gap: clamp(0.4rem, 1vh, 0.8rem);
}
.schedule-list li {
display: flex;
align-items: baseline;
gap: clamp(0.75rem, 2vw, 1.5rem);
}
.schedule-list .time {
font-family: var(--font-body);
font-weight: 500;
font-size: var(--body-size);
color: var(--blue);
min-width: clamp(4rem, 8vw, 6rem);
flex-shrink: 0;
}
.schedule-list .event {
font-size: var(--body-size);
}
.schedule-list .event strong {
font-weight: 500;
}
.schedule-divider {
width: 100%;
height: 1px;
background: var(--ink);
opacity: 0.1;
margin: clamp(0.15rem, 0.3vh, 0.3rem) 0;
}
/* --- RULES SLIDE --- */
.rules-slide {
background: var(--red);
color: var(--paper);
}
.rules-slide .slide-content {
gap: var(--content-gap);
}
.rules-list {
list-style: none;
display: flex;
flex-direction: column;
gap: clamp(0.6rem, 1.5vh, 1.2rem);
}
.rules-list li {
display: flex;
align-items: flex-start;
gap: clamp(0.5rem, 1vw, 1rem);
}
.rules-list .number {
font-family: var(--font-display);
font-weight: 900;
font-size: clamp(1.25rem, 2.5vw, 2rem);
opacity: 0.4;
min-width: clamp(1.5rem, 3vw, 2.5rem);
line-height: 1;
}
.rules-list .rule-text {
padding-top: clamp(2px, 0.3vw, 4px);
}
/* --- CLOSING SLIDE --- */
.closing-slide {
background: var(--green);
color: var(--paper);
text-align: center;
}
.closing-slide .slide-content {
align-items: center;
gap: var(--content-gap);
}
.closing-slide h1 {
font-size: clamp(3rem, 10vw, 9rem);
}
.closing-slide p {
opacity: 0.8;
font-size: clamp(1rem, 2vw, 1.5rem);
}
/* ===========================================
PROGRESS BAR
=========================================== */
.progress-bar {
position: fixed;
top: 0;
left: 0;
height: 3px;
background: var(--red);
z-index: 100;
transition: width 0.3s ease;
}
/* ===========================================
NAV DOTS
=========================================== */
.nav-dots {
position: fixed;
right: clamp(0.75rem, 2vw, 1.5rem);
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: clamp(6px, 0.8vh, 10px);
z-index: 100;
}
.nav-dot {
width: clamp(6px, 0.8vw, 10px);
height: clamp(6px, 0.8vw, 10px);
border-radius: 50%;
background: var(--ink);
opacity: 0.2;
cursor: pointer;
transition: opacity 0.3s ease, transform 0.3s ease;
border: none;
padding: 0;
}
.nav-dot.active {
opacity: 0.8;
transform: scale(1.3);
}
/* Invert dots on dark slides */
.slide.dark-bg ~ .nav-dots .nav-dot,
.nav-dot.on-dark {
background: var(--paper);
}
</style>
</head>
<body>
<!-- Progress bar -->
<div class="progress-bar" id="progressBar"></div>
<!-- Navigation dots -->
<nav class="nav-dots" id="navDots" aria-label="Slide navigation"></nav>
<!-- ===========================================
SLIDE 1: TITLE
=========================================== -->
<section class="slide title-slide" data-theme="dark">
<div class="slide-content">
<span class="label reveal event-date">March 15, 2026</span>
<h1 class="reveal">Good<br>Neighbors</h1>
<span class="label reveal venue">Hackathon at Indy Hall</span>
</div>
</section>
<!-- ===========================================
SLIDE 2: SPONSORS
Thank the folks making this possible
=========================================== -->
<section class="slide sponsors-slide" data-theme="light">
<div class="slide-content">
<div class="accent-bar accent-yellow reveal"></div>
<h2 class="reveal">Made possible by</h2>
<div class="sponsor-grid">
<div class="sponsor-card title-sponsor reveal">
<span class="label sponsor-tier">Title Sponsor</span>
<h3>Supabase</h3>
<p>All projects built on Supabase</p>
</div>
<div class="sponsor-card reveal">
<span class="label sponsor-tier">Community</span>
<h3>Resilient Coders</h3>
</div>
<div class="sponsor-card reveal">
<span class="label sponsor-tier">Breakfast</span>
<h3>OmbuLabs</h3>
</div>
<div class="sponsor-card reveal">
<span class="label sponsor-tier">Sponsor</span>
<h3>Guru</h3>
</div>
</div>
</div>
</section>
<!-- ===========================================
SLIDE 3: WIFI
Big, readable credentials
=========================================== -->
<section class="slide wifi-slide" data-theme="dark">
<div class="slide-content">
<h2 class="reveal">Get Connected</h2>
<div class="wifi-box reveal">
<div class="wifi-field">
<span class="label">Network</span>
<span class="value">Indy Hall</span>
</div>
<div class="wifi-field">
<span class="label">Password</span>
<span class="value">coworking</span>
</div>
</div>
</div>
</section>
<!-- ===========================================
SLIDE 4: CODE OF CONDUCT
Be a good neighbor
=========================================== -->
<section class="slide conduct-slide" data-theme="light">
<div class="slide-content">
<div class="accent-bar accent-green reveal"></div>
<h2 class="reveal">Be a Good Neighbor</h2>
<ul class="conduct-list">
<li class="reveal">
<span class="dot accent-green"></span>
<span>Treat everyone with respect regardless of experience level</span>
</li>
<li class="reveal">
<span class="dot accent-blue"></span>
<span>Ask questions freely and help others when you can</span>
</li>
<li class="reveal">
<span class="dot accent-yellow"></span>
<span>All skill levels welcome. Solo attendees welcome.</span>
</li>
<li class="reveal">
<span class="dot accent-red"></span>
<span>Harassment of any kind means you're out. No warnings.</span>
</li>
<li class="reveal">
<span class="dot accent-green"></span>
<span>Clean up after yourself. Leave it better than you found it.</span>
</li>
</ul>
</div>
</section>
<!-- ===========================================
SLIDE 5: SCHEDULE
How the day works
=========================================== -->
<section class="slide schedule-slide" data-theme="light">
<div class="slide-content">
<div class="accent-bar accent-blue reveal"></div>
<h2 class="reveal">How the Day Works</h2>
<ul class="schedule-list">
<li class="reveal">
<span class="time">9:00am</span>
<span class="event">Doors open — coffee & breakfast</span>
</li>
<li class="reveal">
<span class="time">10:00am</span>
<span class="event"><strong>Kickoff</strong> — welcome, theme, team formation</span>
</li>
<li class="reveal">
<span class="time">10:30am</span>
<span class="event">Teams formed, hacking begins</span>
</li>
<li class="reveal"><div class="schedule-divider"></div></li>
<li class="reveal">
<span class="time">12:30pm</span>
<span class="event">Lunch</span>
</li>
<li class="reveal"><div class="schedule-divider"></div></li>
<li class="reveal">
<span class="time">6:00pm</span>
<span class="event"><strong>Keyboards down</strong></span>
</li>
<li class="reveal">
<span class="time">6:30pm</span>
<span class="event"><strong>Demos & judging</strong></span>
</li>
<li class="reveal">
<span class="time">7:30pm</span>
<span class="event">Awards + social hour</span>
</li>
</ul>
</div>
</section>
<!-- ===========================================
SLIDE 6: THE RULES
What you need to know
=========================================== -->
<section class="slide rules-slide" data-theme="dark">
<div class="slide-content">
<h2 class="reveal">The Rules</h2>
<ul class="rules-list">
<li class="reveal">
<span class="number">01</span>
<span class="rule-text">Every project must use <strong>Supabase</strong></span>
</li>
<li class="reveal">
<span class="number">02</span>
<span class="rule-text">Keyboards down at <strong>6:00pm sharp</strong></span>
</li>
<li class="reveal">
<span class="number">03</span>
<span class="rule-text">Every team demos — even if it's broken</span>
</li>
<li class="reveal">
<span class="number">04</span>
<span class="rule-text">Teams of 2–5 people. Solo is fine too.</span>
</li>
<li class="reveal">
<span class="number">05</span>
<span class="rule-text">Have fun. Ship something. Be a good neighbor.</span>
</li>
</ul>
</div>
</section>
<!-- ===========================================
SLIDE 7: LET'S BUILD
Closing hype slide
=========================================== -->
<section class="slide closing-slide" data-theme="dark">
<div class="slide-content">
<h1 class="reveal">Let's Build.</h1>
<p class="reveal">Neighbors building together, not a startup pitch competition.</p>
</div>
</section>
<script>
/* ===========================================
SLIDE PRESENTATION CONTROLLER
Handles navigation, animations, progress
=========================================== */
class SlidePresentation {
constructor() {
this.slides = document.querySelectorAll('.slide');
this.currentSlide = 0;
this.isScrolling = false;
this.scrollTimeout = null;
this.setupIntersectionObserver();
this.setupKeyboardNav();
this.setupTouchNav();
this.setupWheelNav();
this.setupProgressBar();
this.setupNavDots();
this.updateNavDotColors();
}
/* Trigger .visible class when slides enter viewport */
setupIntersectionObserver() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
const index = Array.from(this.slides).indexOf(entry.target);
this.currentSlide = index;
this.updateProgressBar();
this.updateNavDots();
this.updateNavDotColors();
}
});
}, { threshold: 0.5 });
this.slides.forEach(slide => observer.observe(slide));
}
/* Arrow keys, Space, Page Up/Down */
setupKeyboardNav() {
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown' || e.key === ' ' || e.key === 'PageDown' || e.key === 'ArrowRight') {
e.preventDefault();
this.goToSlide(this.currentSlide + 1);
} else if (e.key === 'ArrowUp' || e.key === 'PageUp' || e.key === 'ArrowLeft') {
e.preventDefault();
this.goToSlide(this.currentSlide - 1);
} else if (e.key === 'Home') {
e.preventDefault();
this.goToSlide(0);
} else if (e.key === 'End') {
e.preventDefault();
this.goToSlide(this.slides.length - 1);
}
});
}
/* Touch/swipe support */
setupTouchNav() {
let startY = 0;
document.addEventListener('touchstart', (e) => {
startY = e.touches[0].clientY;
}, { passive: true });
document.addEventListener('touchend', (e) => {
const diff = startY - e.changedTouches[0].clientY;
if (Math.abs(diff) > 50) {
this.goToSlide(this.currentSlide + (diff > 0 ? 1 : -1));
}
}, { passive: true });
}
/* Mouse wheel with debounce */
setupWheelNav() {
document.addEventListener('wheel', (e) => {
if (this.isScrolling) return;
this.isScrolling = true;
if (e.deltaY > 0) {
this.goToSlide(this.currentSlide + 1);
} else if (e.deltaY < 0) {
this.goToSlide(this.currentSlide - 1);
}
clearTimeout(this.scrollTimeout);
this.scrollTimeout = setTimeout(() => {
this.isScrolling = false;
}, 800);
}, { passive: true });
}
goToSlide(index) {
if (index < 0 || index >= this.slides.length) return;
this.currentSlide = index;
this.slides[index].scrollIntoView({ behavior: 'smooth' });
}
/* Progress bar at top */
setupProgressBar() {
this.progressBar = document.getElementById('progressBar');
this.updateProgressBar();
}
updateProgressBar() {
if (!this.progressBar) return;
const progress = ((this.currentSlide + 1) / this.slides.length) * 100;
this.progressBar.style.width = progress + '%';
}
/* Navigation dots on right side */
setupNavDots() {
const container = document.getElementById('navDots');
if (!container) return;
this.slides.forEach((_, i) => {
const dot = document.createElement('button');
dot.className = 'nav-dot' + (i === 0 ? ' active' : '');
dot.setAttribute('aria-label', `Go to slide ${i + 1}`);
dot.addEventListener('click', () => this.goToSlide(i));
container.appendChild(dot);
});
this.navDots = container.querySelectorAll('.nav-dot');
}
updateNavDots() {
if (!this.navDots) return;
this.navDots.forEach((dot, i) => {
dot.classList.toggle('active', i === this.currentSlide);
});
}
/* Invert dot color on dark-themed slides */
updateNavDotColors() {
if (!this.navDots) return;
const currentTheme = this.slides[this.currentSlide]?.dataset.theme;
this.navDots.forEach(dot => {
dot.classList.toggle('on-dark', currentTheme === 'dark');
});
}
}
/* Initialize on load */
new SlidePresentation();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment