Spaces:
Running
Running
Upload 5 files
Browse files- assets/components.js +244 -0
- assets/deck.css +238 -0
- assets/deck.js +215 -0
- assets/slides.json +241 -0
- index.html +67 -0
assets/components.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/*
|
| 2 |
+
components.js — Class-based UI builders that construct the exact HTML
|
| 3 |
+
markup used by index.html and styled by deck.css + Tailwind.
|
| 4 |
+
|
| 5 |
+
These utilities do not auto-mount or alter the DOM. Use them to create
|
| 6 |
+
or adopt the same UI pieces programmatically when needed.
|
| 7 |
+
*/
|
| 8 |
+
(function () {
|
| 9 |
+
const setAttrs = (el, attrs) => {
|
| 10 |
+
for (const [k, v] of Object.entries(attrs || {})) {
|
| 11 |
+
if (v === undefined || v === null) continue;
|
| 12 |
+
el.setAttribute(k, String(v));
|
| 13 |
+
}
|
| 14 |
+
return el;
|
| 15 |
+
};
|
| 16 |
+
|
| 17 |
+
// Sidebar Toggle
|
| 18 |
+
class SidebarToggleUI {
|
| 19 |
+
constructor({ id = 'sidebarToggle', el } = {}) {
|
| 20 |
+
this.el = el || document.getElementById(id) || setAttrs(document.createElement('button'), {
|
| 21 |
+
id,
|
| 22 |
+
'aria-label': 'Toggle sections sidebar',
|
| 23 |
+
'aria-expanded': 'false',
|
| 24 |
+
class: 'sidebar-toggle',
|
| 25 |
+
});
|
| 26 |
+
if (!this.el.querySelector('#sidebarToggleIcon')) {
|
| 27 |
+
this.el.innerHTML = `
|
| 28 |
+
<svg id="sidebarToggleIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5 h-5" aria-hidden="true">
|
| 29 |
+
<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2"></circle>
|
| 30 |
+
<rect x="11" y="10" width="2" height="7" rx="1" fill="currentColor"></rect>
|
| 31 |
+
<rect x="11" y="6" width="2" height="2" rx="1" fill="currentColor"></rect>
|
| 32 |
+
</svg>`;
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
mount(where) { (where || document.body).appendChild(this.el); return this; }
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
// Sidebar
|
| 39 |
+
class SidebarUI {
|
| 40 |
+
constructor({ id = 'sidebar', title = 'Sections', el } = {}) {
|
| 41 |
+
this.el = el || document.getElementById(id) || setAttrs(document.createElement('aside'), { id, class: 'sidebar', 'aria-hidden': 'true' });
|
| 42 |
+
if (!this.el.querySelector('.sidebar__inner')) {
|
| 43 |
+
this.el.innerHTML = `
|
| 44 |
+
<div class="sidebar__inner">
|
| 45 |
+
<header class="sidebar__header"><span class="sidebar__title">${title}</span></header>
|
| 46 |
+
<nav id="sidebarList" class="sidebar__list" aria-label="Slide sections"></nav>
|
| 47 |
+
</div>`;
|
| 48 |
+
}
|
| 49 |
+
this.list = this.el.querySelector('#sidebarList');
|
| 50 |
+
}
|
| 51 |
+
setSections(sections) {
|
| 52 |
+
const s = Array.isArray(sections) ? sections : [];
|
| 53 |
+
this.list.innerHTML = s.map((sec) => {
|
| 54 |
+
const sub = (sec.slides && sec.slides.length)
|
| 55 |
+
? `<div class="sidebar__sublist">${sec.slides.map(sl => `
|
| 56 |
+
<div class="sidebar__subitem" data-slide-index="${sl.index}">
|
| 57 |
+
<button type="button" class="sidebar__sublink">${sl.title}</button>
|
| 58 |
+
</div>`).join('')}
|
| 59 |
+
</div>`
|
| 60 |
+
: '';
|
| 61 |
+
return `<div class="sidebar__item" data-section-index="${sec.index}">
|
| 62 |
+
<button class="sidebar__button" type="button">
|
| 63 |
+
<span class="sidebar__bullet" aria-hidden="true"></span>
|
| 64 |
+
<span class="sidebar__label">${sec.title}</span>
|
| 65 |
+
<svg class="sidebar__chev" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path d="M7 6l6 4-6 4V6z"/></svg>
|
| 66 |
+
</button>
|
| 67 |
+
${sub}
|
| 68 |
+
</div>`;
|
| 69 |
+
}).join('');
|
| 70 |
+
return this;
|
| 71 |
+
}
|
| 72 |
+
open() { this.el.classList.add('open'); this.el.setAttribute('aria-hidden', 'false'); return this; }
|
| 73 |
+
close() { this.el.classList.remove('open'); this.el.setAttribute('aria-hidden', 'true'); return this; }
|
| 74 |
+
toggle() { return this.el.classList.contains('open') ? this.close() : this.open(); }
|
| 75 |
+
mount(where) { (where || document.body).appendChild(this.el); return this; }
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// Progress
|
| 79 |
+
class ProgressUI {
|
| 80 |
+
constructor({ id = 'progressBar', el } = {}) {
|
| 81 |
+
this.el = el || document.getElementById(id) || setAttrs(document.createElement('div'), { id, class: 'h-1 w-44 bg-neutral-200 rounded-full overflow-hidden' });
|
| 82 |
+
let inner = this.el.querySelector('#progressInner');
|
| 83 |
+
if (!inner) {
|
| 84 |
+
inner = setAttrs(document.createElement('div'), { id: 'progressInner', class: 'h-full w-0 bg-neutral-800 transition-all' });
|
| 85 |
+
this.el.appendChild(inner);
|
| 86 |
+
}
|
| 87 |
+
this.inner = inner;
|
| 88 |
+
}
|
| 89 |
+
set(position, total) {
|
| 90 |
+
const denom = Math.max(1, Number(total) || 1);
|
| 91 |
+
const pos = Math.max(0, Math.min(denom, Number(position) || 0));
|
| 92 |
+
this.inner.style.width = `${(pos / denom) * 100}%`;
|
| 93 |
+
return this;
|
| 94 |
+
}
|
| 95 |
+
mount(where) { (where || document.body).appendChild(this.el); return this; }
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// Counter
|
| 99 |
+
class CounterUI {
|
| 100 |
+
constructor({ id = 'counter', el } = {}) {
|
| 101 |
+
this.el = el || document.getElementById(id) || setAttrs(document.createElement('div'), { id, class: 'text-sm font-medium text-neutral-600' });
|
| 102 |
+
}
|
| 103 |
+
set(position, total) {
|
| 104 |
+
const denom = Math.max(0, Number(total) || 0);
|
| 105 |
+
const pos = Math.max(0, Math.min(denom, Number(position) || 0));
|
| 106 |
+
this.el.textContent = `${pos} / ${denom}`;
|
| 107 |
+
return this;
|
| 108 |
+
}
|
| 109 |
+
mount(where) { (where || document.body).appendChild(this.el); return this; }
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
// Navigation button
|
| 113 |
+
class NavButtonUI {
|
| 114 |
+
constructor({ id, rel = 'next', hidden = false, el } = {}) {
|
| 115 |
+
this.el = el || document.getElementById(id) || setAttrs(document.createElement('button'), {
|
| 116 |
+
id,
|
| 117 |
+
'aria-label': rel === 'prev' ? 'Previous slide' : 'Next slide',
|
| 118 |
+
class: 'group p-2 rounded-full bg-white/80 hover:bg-white shadow border border-neutral-200 backdrop-blur pointer-events-auto' + (hidden ? ' hidden' : ''),
|
| 119 |
+
});
|
| 120 |
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
| 121 |
+
svg.setAttribute('viewBox', '0 0 24 24');
|
| 122 |
+
svg.setAttribute('fill', 'currentColor');
|
| 123 |
+
svg.setAttribute('class', 'w-4 h-4 text-neutral-700 ' + (rel === 'prev' ? 'group-hover:-translate-x-0.5' : 'group-hover:translate-x-0.5') + ' transition-transform');
|
| 124 |
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
| 125 |
+
path.setAttribute('fill-rule', 'evenodd');
|
| 126 |
+
const d = rel === 'prev'
|
| 127 |
+
? 'M15.53 4.47a.75.75 0 0 1 0 1.06L9.06 12l6.47 6.47a.75.75 0 1 1-1.06 1.06l-7-7a.75.75 0 0 1 0-1.06l7-7a.75.75 0 0 1 1.06 0Z'
|
| 128 |
+
: 'M8.47 4.47a.75.75 0 0 1 1.06 0l7 7a.75.75 0 0 1 0 1.06l-7 7a.75.75 0 1 1-1.06-1.06L14.94 12 8.47 5.53a.75.75 0 0 1 0-1.06Z';
|
| 129 |
+
path.setAttribute('d', d);
|
| 130 |
+
path.setAttribute('clip-rule', 'evenodd');
|
| 131 |
+
svg.appendChild(path);
|
| 132 |
+
if (!this.el.querySelector('svg')) this.el.appendChild(svg);
|
| 133 |
+
}
|
| 134 |
+
setHidden(hidden) { this.el.classList.toggle('hidden', !!hidden); return this; }
|
| 135 |
+
mount(where) { (where || document.body).appendChild(this.el); return this; }
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
window.UI = {
|
| 139 |
+
SidebarToggleUI,
|
| 140 |
+
SidebarUI,
|
| 141 |
+
ProgressUI,
|
| 142 |
+
CounterUI,
|
| 143 |
+
NavButtonUI,
|
| 144 |
+
DeckUI: class DeckUI {
|
| 145 |
+
constructor({ deckEl, sidebarEl, toggleEl, prevEl, nextEl, progressEl, counterEl } = {}) {
|
| 146 |
+
this.el = deckEl || document.getElementById('deck') || document.body;
|
| 147 |
+
this.toggle = new SidebarToggleUI({ el: toggleEl || document.getElementById('sidebarToggle') });
|
| 148 |
+
this.sidebar = new SidebarUI({ el: sidebarEl || document.getElementById('sidebar') });
|
| 149 |
+
this.progress = new ProgressUI({ el: progressEl || document.getElementById('progressBar') });
|
| 150 |
+
this.counter = new CounterUI({ el: counterEl || document.getElementById('counter') });
|
| 151 |
+
this.prev = new NavButtonUI({ id: 'prevBtn', rel: 'prev', el: prevEl || document.getElementById('prevBtn') });
|
| 152 |
+
this.next = new NavButtonUI({ id: 'nextBtn', rel: 'next', el: nextEl || document.getElementById('nextBtn') });
|
| 153 |
+
this._sections = [];
|
| 154 |
+
this._openSections = new Set();
|
| 155 |
+
// Wire basic events
|
| 156 |
+
if (this.toggle.el) this.toggle.el.addEventListener('click', () => this._emit('toggle-sidebar'));
|
| 157 |
+
if (this.prev.el) this.prev.el.addEventListener('click', (e) => { e.stopPropagation(); this._emit('prev'); });
|
| 158 |
+
if (this.next.el) this.next.el.addEventListener('click', (e) => { e.stopPropagation(); this._emit('next'); });
|
| 159 |
+
}
|
| 160 |
+
_emit(type, detail) { this.el.dispatchEvent(new CustomEvent(type, { bubbles: true, detail })); }
|
| 161 |
+
isSidebarOpen() { return this.sidebar && this.sidebar.el && this.sidebar.el.classList.contains('open'); }
|
| 162 |
+
openSidebar() { if (this.sidebar) this.sidebar.open(); if (this.toggle && this.toggle.el) this.toggle.el.setAttribute('aria-expanded', 'true'); return this; }
|
| 163 |
+
closeSidebar() { if (this.sidebar) this.sidebar.close(); if (this.toggle && this.toggle.el) this.toggle.el.setAttribute('aria-expanded', 'false'); return this; }
|
| 164 |
+
toggleSidebar() { return this.isSidebarOpen() ? this.closeSidebar() : this.openSidebar(); }
|
| 165 |
+
setSections(sections) {
|
| 166 |
+
this._sections = Array.isArray(sections) ? sections : [];
|
| 167 |
+
if (!this.sidebar) return this;
|
| 168 |
+
this.sidebar.setSections(this._sections);
|
| 169 |
+
// Apply open state from our set
|
| 170 |
+
const items = this.sidebar.list ? Array.from(this.sidebar.list.children) : [];
|
| 171 |
+
items.forEach((node) => {
|
| 172 |
+
const si = Number(node.getAttribute('data-section-index'));
|
| 173 |
+
node.classList.toggle('sidebar__item--open', this._openSections.has(si));
|
| 174 |
+
});
|
| 175 |
+
// Delegate sidebar interactions once
|
| 176 |
+
if (this.sidebar.list && !this._delegated) {
|
| 177 |
+
this._delegated = true;
|
| 178 |
+
this.sidebar.list.addEventListener('click', (e) => {
|
| 179 |
+
const sectionBtn = e.target.closest('.sidebar__item > .sidebar__button');
|
| 180 |
+
if (sectionBtn) {
|
| 181 |
+
e.stopPropagation();
|
| 182 |
+
const parent = sectionBtn.parentElement;
|
| 183 |
+
const si = Number(parent.getAttribute('data-section-index'));
|
| 184 |
+
const isOpen = this._openSections.has(si);
|
| 185 |
+
const items = Array.from(this.sidebar.list.children);
|
| 186 |
+
if (isOpen) {
|
| 187 |
+
// Close current section
|
| 188 |
+
this._openSections.delete(si);
|
| 189 |
+
parent.classList.remove('sidebar__item--open');
|
| 190 |
+
} else {
|
| 191 |
+
// Accordion: close others, open only this one
|
| 192 |
+
this._openSections.clear();
|
| 193 |
+
this._openSections.add(si);
|
| 194 |
+
items.forEach((node) => {
|
| 195 |
+
const idx = Number(node.getAttribute('data-section-index'));
|
| 196 |
+
node.classList.toggle('sidebar__item--open', idx === si);
|
| 197 |
+
});
|
| 198 |
+
}
|
| 199 |
+
// Do not navigate on section header clicks; only toggle tree
|
| 200 |
+
return;
|
| 201 |
+
}
|
| 202 |
+
const subBtn = e.target.closest('.sidebar__subitem .sidebar__sublink');
|
| 203 |
+
if (subBtn) {
|
| 204 |
+
e.stopPropagation();
|
| 205 |
+
const parent = subBtn.parentElement;
|
| 206 |
+
const si = Number(parent.getAttribute('data-slide-index'));
|
| 207 |
+
if (!Number.isNaN(si)) this._emit('select', { index: si, source: 'slide' });
|
| 208 |
+
}
|
| 209 |
+
});
|
| 210 |
+
}
|
| 211 |
+
return this;
|
| 212 |
+
}
|
| 213 |
+
setActive(currentIndex) {
|
| 214 |
+
if (!this.sidebar || !this.sidebar.list || !this._sections.length) return this;
|
| 215 |
+
// Determine current section: last section header index <= currentIndex
|
| 216 |
+
let currentSection = 0;
|
| 217 |
+
for (let i = 0; i < this._sections.length; i++) {
|
| 218 |
+
if (this._sections[i].index <= currentIndex) currentSection = i; else break;
|
| 219 |
+
}
|
| 220 |
+
const items = Array.from(this.sidebar.list.children);
|
| 221 |
+
items.forEach((node, i) => node.classList.toggle('sidebar__item--active', i === currentSection));
|
| 222 |
+
// Highlight current slide inside the active section
|
| 223 |
+
const activeItem = items[currentSection];
|
| 224 |
+
if (activeItem) {
|
| 225 |
+
activeItem.querySelectorAll('.sidebar__subitem').forEach((n) => {
|
| 226 |
+
const si = Number(n.getAttribute('data-slide-index'));
|
| 227 |
+
n.classList.toggle('sidebar__subitem--active', si === currentIndex);
|
| 228 |
+
});
|
| 229 |
+
}
|
| 230 |
+
return this;
|
| 231 |
+
}
|
| 232 |
+
setPosition(position, total) {
|
| 233 |
+
if (this.progress && typeof this.progress.set === 'function') this.progress.set(position, total);
|
| 234 |
+
if (this.counter && typeof this.counter.set === 'function') this.counter.set(position, total);
|
| 235 |
+
return this;
|
| 236 |
+
}
|
| 237 |
+
setNavVisibility(prevHidden, nextHidden) {
|
| 238 |
+
if (this.prev) this.prev.setHidden(!!prevHidden);
|
| 239 |
+
if (this.next) this.next.setHidden(!!nextHidden);
|
| 240 |
+
return this;
|
| 241 |
+
}
|
| 242 |
+
},
|
| 243 |
+
};
|
| 244 |
+
})();
|
assets/deck.css
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Deck CSS — complements Tailwind utilities with stable class hooks */
|
| 2 |
+
|
| 3 |
+
:root {
|
| 4 |
+
--deck-bg-from: #fafafa;
|
| 5 |
+
--deck-bg-to: #f5f5f5;
|
| 6 |
+
--deck-text: #0a0a0a;
|
| 7 |
+
--deck-muted: #525252;
|
| 8 |
+
--deck-border: #e5e5e5;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
/* Stage: enforce 16:9 area that scales to viewport */
|
| 12 |
+
#slides {
|
| 13 |
+
width: min(100vw, calc(177.78vh)); /* 16:9 => width = 16/9 * height = 1.7778 * h */
|
| 14 |
+
height: min(100vh, calc(56.25vw)); /* height = 9/16 * width = 0.5625 * w */
|
| 15 |
+
position: relative;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
/* Slide base (fill stage) */
|
| 19 |
+
.slide {
|
| 20 |
+
min-height: 100%;
|
| 21 |
+
height: 100%;
|
| 22 |
+
display: flex;
|
| 23 |
+
align-items: center;
|
| 24 |
+
justify-content: center;
|
| 25 |
+
width: 100%;
|
| 26 |
+
padding-left: clamp(1rem, 3vw, 5rem);
|
| 27 |
+
padding-right: clamp(1rem, 3vw, 5rem);
|
| 28 |
+
/* Prevent text caret from appearing on click */
|
| 29 |
+
-webkit-user-select: none;
|
| 30 |
+
-moz-user-select: none;
|
| 31 |
+
-ms-user-select: none;
|
| 32 |
+
user-select: none;
|
| 33 |
+
caret-color: transparent;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
/* Also apply caret/selection rules to slide content */
|
| 37 |
+
.slide * {
|
| 38 |
+
-webkit-user-select: none;
|
| 39 |
+
-moz-user-select: none;
|
| 40 |
+
-ms-user-select: none;
|
| 41 |
+
user-select: none;
|
| 42 |
+
caret-color: transparent;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.slide__inner {
|
| 46 |
+
max-width: 72rem; /* ~1152px */
|
| 47 |
+
width: 100%;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.slide__header { margin-bottom: clamp(1rem, 3vw, 2rem); }
|
| 51 |
+
.slide__title {
|
| 52 |
+
font-size: clamp(2rem, 5vw, 4rem);
|
| 53 |
+
font-weight: 600;
|
| 54 |
+
letter-spacing: -0.01em;
|
| 55 |
+
}
|
| 56 |
+
.slide__subtitle {
|
| 57 |
+
margin-top: 0.5rem;
|
| 58 |
+
font-size: clamp(1.1rem, 2.2vw, 1.25rem);
|
| 59 |
+
color: var(--deck-muted);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
.slide__bullets {
|
| 63 |
+
list-style: none;
|
| 64 |
+
padding: 0;
|
| 65 |
+
margin: 0;
|
| 66 |
+
display: grid;
|
| 67 |
+
gap: clamp(0.6rem, 1.6vw, 1.1rem);
|
| 68 |
+
font-size: clamp(1.125rem, 2.4vw, 1.5rem);
|
| 69 |
+
line-height: 1.6;
|
| 70 |
+
}
|
| 71 |
+
.slide__bullet { position: relative; padding-left: 1.25rem; }
|
| 72 |
+
.slide__dot {
|
| 73 |
+
position: absolute; left: 0; top: 0.2rem; color: #a3a3a3; /* neutral-400 */
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.slide__rich { margin-top: 1.5rem; }
|
| 77 |
+
|
| 78 |
+
.slide__figure { margin-top: 1.25rem; }
|
| 79 |
+
.slide__image {
|
| 80 |
+
width: 100%;
|
| 81 |
+
max-height: 70vh;
|
| 82 |
+
object-fit: contain;
|
| 83 |
+
background: #fff;
|
| 84 |
+
border: 1px solid var(--deck-border);
|
| 85 |
+
border-radius: 0.375rem;
|
| 86 |
+
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
| 87 |
+
}
|
| 88 |
+
.slide__image--cover {
|
| 89 |
+
max-height: none;
|
| 90 |
+
height: 70vh;
|
| 91 |
+
object-fit: cover;
|
| 92 |
+
object-position: 50% 50%;
|
| 93 |
+
}
|
| 94 |
+
.slide__caption { margin-top: 0.5rem; font-size: 0.875rem; color: var(--deck-muted); }
|
| 95 |
+
.slide__footnote { margin-top: 2rem; font-size: 0.875rem; color: #d4d4d4; }
|
| 96 |
+
|
| 97 |
+
/* Section slide */
|
| 98 |
+
.slide-section {
|
| 99 |
+
background: linear-gradient(to bottom, var(--deck-bg-from), var(--deck-bg-to));
|
| 100 |
+
text-align: center;
|
| 101 |
+
}
|
| 102 |
+
.slide-section__inner { max-width: 72rem; width: 100%; }
|
| 103 |
+
.slide-section__title {
|
| 104 |
+
font-size: clamp(2.25rem, 7vw, 4.5rem);
|
| 105 |
+
font-weight: 600;
|
| 106 |
+
letter-spacing: -0.01em;
|
| 107 |
+
}
|
| 108 |
+
.slide-section__subtitle {
|
| 109 |
+
margin-top: 0.75rem;
|
| 110 |
+
font-size: clamp(1.25rem, 3.2vw, 1.6rem);
|
| 111 |
+
color: var(--deck-muted);
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
/* Title slide */
|
| 115 |
+
.slide-title {
|
| 116 |
+
background: linear-gradient(to bottom right, #1a202c, #2d3748); /* Dark gradient */
|
| 117 |
+
color: #ffffff; /* White text */
|
| 118 |
+
text-align: center;
|
| 119 |
+
}
|
| 120 |
+
.slide-title .slide__title {
|
| 121 |
+
font-size: clamp(3rem, 8vw, 6rem); /* Even larger title */
|
| 122 |
+
font-weight: 800; /* Extra bold */
|
| 123 |
+
letter-spacing: -0.02em;
|
| 124 |
+
line-height: 1.1;
|
| 125 |
+
}
|
| 126 |
+
.slide-title .slide__subtitle {
|
| 127 |
+
color: #cbd5e0; /* Lighter grey for subtitle */
|
| 128 |
+
font-size: clamp(1.5rem, 3vw, 2rem);
|
| 129 |
+
margin-top: 1rem;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/* Footer controls (progress + small arrows) */
|
| 133 |
+
#progressBar { height: 0.25rem; border-radius: 9999px; background: #e5e5e5; overflow: hidden; }
|
| 134 |
+
#progressInner { height: 100%; background: #171717; transition: width 200ms ease; }
|
| 135 |
+
#counter { font-size: 0.875rem; color: #525252; }
|
| 136 |
+
|
| 137 |
+
#prevBtn, #nextBtn {
|
| 138 |
+
padding: 0.5rem;
|
| 139 |
+
border-radius: 9999px;
|
| 140 |
+
background: rgba(255,255,255,0.8);
|
| 141 |
+
border: 1px solid var(--deck-border);
|
| 142 |
+
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
| 143 |
+
backdrop-filter: blur(2px);
|
| 144 |
+
}
|
| 145 |
+
#prevBtn svg, #nextBtn svg { width: 1rem; height: 1rem; color: #404040; }
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
/* Fixed footer bar for nav + progress */
|
| 149 |
+
.deck-footer {
|
| 150 |
+
position: fixed;
|
| 151 |
+
left: 0; right: 0; bottom: 1rem;
|
| 152 |
+
display: flex; align-items: center; justify-content: center; gap: 0.75rem;
|
| 153 |
+
z-index: 35;
|
| 154 |
+
pointer-events: none; /* allow clicks to pass unless on children */
|
| 155 |
+
}
|
| 156 |
+
.deck-footer > * { pointer-events: auto; }
|
| 157 |
+
|
| 158 |
+
/* Hide progress + counter on section slides to reduce clutter */
|
| 159 |
+
#deck.is-section #progressBar,
|
| 160 |
+
#deck.is-section #counter {
|
| 161 |
+
opacity: 0;
|
| 162 |
+
pointer-events: none;
|
| 163 |
+
transition: opacity 150ms ease;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
/* Sidebar */
|
| 167 |
+
.sidebar {
|
| 168 |
+
position: fixed;
|
| 169 |
+
inset: 0 auto 0 0; /* left side */
|
| 170 |
+
height: 100vh;
|
| 171 |
+
width: clamp(220px, 22vw, 320px);
|
| 172 |
+
background: rgba(255,255,255,0.85);
|
| 173 |
+
backdrop-filter: blur(6px);
|
| 174 |
+
border-right: 1px solid var(--deck-border);
|
| 175 |
+
box-shadow: 0 10px 30px rgba(0,0,0,0.08);
|
| 176 |
+
transform: translateX(-100%);
|
| 177 |
+
transition: transform 200ms ease;
|
| 178 |
+
z-index: 30;
|
| 179 |
+
pointer-events: auto;
|
| 180 |
+
}
|
| 181 |
+
.sidebar.open { transform: translateX(0); }
|
| 182 |
+
.sidebar__inner { height: 100%; display: flex; flex-direction: column; }
|
| 183 |
+
.sidebar__header { padding: 1rem 1rem 0.5rem 1rem; }
|
| 184 |
+
.sidebar__title { font-weight: 600; letter-spacing: -0.01em; }
|
| 185 |
+
.sidebar__list { padding: 0.25rem 0.5rem 1rem 0.5rem; overflow-y: auto; height: 100%; }
|
| 186 |
+
.sidebar__item { display: block; }
|
| 187 |
+
.sidebar__button {
|
| 188 |
+
width: 100%; text-align: left; display: flex; align-items: center; gap: 0.5rem;
|
| 189 |
+
padding: 0.5rem 0.75rem; border-radius: 0.5rem; border: 1px solid transparent;
|
| 190 |
+
color: var(--deck-text);
|
| 191 |
+
}
|
| 192 |
+
.sidebar__button:hover { background: #f5f5f5; border-color: var(--deck-border); }
|
| 193 |
+
.sidebar__bullet { width: 6px; height: 6px; border-radius: 9999px; background: #d4d4d4; }
|
| 194 |
+
.sidebar__label { font-size: 0.95rem; }
|
| 195 |
+
.sidebar__item--active .sidebar__button { background: #111827; color: white; border-color: #111827; }
|
| 196 |
+
.sidebar__item--active .sidebar__bullet { background: white; }
|
| 197 |
+
|
| 198 |
+
/* Section arrow and sublist */
|
| 199 |
+
.sidebar__chev {
|
| 200 |
+
margin-left: auto;
|
| 201 |
+
width: 0.875rem; height: 0.875rem; color: #6b7280; /* neutral-500 */
|
| 202 |
+
transition: transform 150ms ease;
|
| 203 |
+
}
|
| 204 |
+
.sidebar__item--open .sidebar__chev { transform: rotate(90deg); }
|
| 205 |
+
|
| 206 |
+
.sidebar__sublist { display: none; padding: 0.25rem 0.25rem 0.25rem 1.5rem; }
|
| 207 |
+
.sidebar__item--open .sidebar__sublist { display: block; }
|
| 208 |
+
.sidebar__subitem { margin: 0; }
|
| 209 |
+
.sidebar__sublink {
|
| 210 |
+
width: 100%; text-align: left; display: block;
|
| 211 |
+
padding: 0.4rem 0.6rem; border-radius: 0.375rem; font-size: 0.9rem;
|
| 212 |
+
color: #374151; /* neutral-700 */
|
| 213 |
+
}
|
| 214 |
+
.sidebar__sublink:hover { background: #f3f4f6; }
|
| 215 |
+
.sidebar__subitem--active .sidebar__sublink {
|
| 216 |
+
background: #e5e7eb; color: #111827; font-weight: 600;
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
/* Sidebar Toggle */
|
| 220 |
+
.sidebar-toggle {
|
| 221 |
+
position: fixed; bottom: 1rem; left: 1rem; z-index: 40;
|
| 222 |
+
padding: 0.5rem; border-radius: 0.5rem;
|
| 223 |
+
background: rgba(156,163,175,0.5); /* neutral-400 @ 50% */
|
| 224 |
+
border: 1px solid rgba(156,163,175,0.6);
|
| 225 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.10);
|
| 226 |
+
backdrop-filter: blur(6px);
|
| 227 |
+
}
|
| 228 |
+
.sidebar-toggle:hover { background: rgba(156,163,175,0.72); border-color: rgba(156,163,175,0.8); }
|
| 229 |
+
.sidebar-toggle svg { color: #ffffff; }
|
| 230 |
+
|
| 231 |
+
/* Disable deck click-to-advance when sidebar open */
|
| 232 |
+
#deck.has-sidebar-open { cursor: default; }
|
| 233 |
+
|
| 234 |
+
/* Shift stage when sidebar open so content isn't covered */
|
| 235 |
+
#deck.has-sidebar-open #slides {
|
| 236 |
+
transform: translateX(clamp(220px, 22vw, 320px));
|
| 237 |
+
transition: transform 200ms ease;
|
| 238 |
+
}
|
assets/deck.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 2 |
+
const slidesRoot = document.getElementById('slides');
|
| 3 |
+
const deck = document.getElementById('deck');
|
| 4 |
+
const sidebar = document.getElementById('sidebar');
|
| 5 |
+
|
| 6 |
+
let slides = [];
|
| 7 |
+
let index = 0;
|
| 8 |
+
let sections = [];
|
| 9 |
+
let sidebarOpen = false;
|
| 10 |
+
let contentIndices = [];
|
| 11 |
+
let cumulativePositions = [];
|
| 12 |
+
|
| 13 |
+
const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
|
| 14 |
+
|
| 15 |
+
// Adopt class-based UI facade (required)
|
| 16 |
+
const ui = new UI.DeckUI({ deckEl: deck });
|
| 17 |
+
deck.addEventListener('toggle-sidebar', (e) => { e.stopPropagation(); toggleSidebar(); });
|
| 18 |
+
deck.addEventListener('prev', (e) => { e.stopPropagation(); prev(); });
|
| 19 |
+
deck.addEventListener('next', (e) => { e.stopPropagation(); next(); });
|
| 20 |
+
deck.addEventListener('select', (e) => {
|
| 21 |
+
const si = Number(e.detail && e.detail.index);
|
| 22 |
+
if (!Number.isNaN(si)) { index = si; closeSidebar(); updateView(); }
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
const slideTemplate = (slide) => {
|
| 26 |
+
const hasSubtitle = slide.subtitle && slide.subtitle.length > 0;
|
| 27 |
+
const hasBullets = Array.isArray(slide.bullets) && slide.bullets.length > 0;
|
| 28 |
+
const hasFootnote = slide.footnote && slide.footnote.length > 0;
|
| 29 |
+
const hasImage = !!slide.image;
|
| 30 |
+
if (slide.type === 'section') {
|
| 31 |
+
return `
|
| 32 |
+
<section class="slide slide-section">
|
| 33 |
+
<div class="slide-section__inner">
|
| 34 |
+
<h2 class="slide-section__title">${slide.title || ''}</h2>
|
| 35 |
+
${hasSubtitle ? `<p class="slide-section__subtitle">${slide.subtitle}</p>` : ''}
|
| 36 |
+
</div>
|
| 37 |
+
</section>
|
| 38 |
+
`;
|
| 39 |
+
}
|
| 40 |
+
if (slide.type === 'title') {
|
| 41 |
+
return `
|
| 42 |
+
<section class="slide slide-title">
|
| 43 |
+
<div class="slide__inner">
|
| 44 |
+
<header class="slide__header">
|
| 45 |
+
<h1 class="slide__title">${slide.title || ''}</h1>
|
| 46 |
+
${hasSubtitle ? `<p class="slide__subtitle">${slide.subtitle}</p>` : ''}
|
| 47 |
+
</header>
|
| 48 |
+
</div>
|
| 49 |
+
</section>
|
| 50 |
+
`;
|
| 51 |
+
}
|
| 52 |
+
return `
|
| 53 |
+
<section class="slide">
|
| 54 |
+
<div class="slide__inner">
|
| 55 |
+
<header class="slide__header">
|
| 56 |
+
<h1 class="slide__title">${slide.title || ''}</h1>
|
| 57 |
+
${hasSubtitle ? `<p class="slide__subtitle">${slide.subtitle}</p>` : ''}
|
| 58 |
+
</header>
|
| 59 |
+
${hasBullets ? `
|
| 60 |
+
<ul class="slide__bullets">
|
| 61 |
+
${slide.bullets.map(b => `<li class="slide__bullet"><span class="slide__dot">•</span><span>${b}</span></li>`).join('')}
|
| 62 |
+
</ul>
|
| 63 |
+
` : ''}
|
| 64 |
+
${slide.html ? `<div class="slide__rich">${slide.html}</div>` : ''}
|
| 65 |
+
${hasImage ? (() => {
|
| 66 |
+
const cover = typeof slide.imageCropPercent === 'number';
|
| 67 |
+
const posY = cover ? Math.max(0, Math.min(100, slide.imageCropPercent)) : 50;
|
| 68 |
+
const cls = `slide__image${cover ? ' slide__image--cover' : ''}`;
|
| 69 |
+
const style = cover ? `style=\"object-position: 50% ${posY}%\"` : '';
|
| 70 |
+
return `
|
| 71 |
+
<figure class=\"slide__figure\">
|
| 72 |
+
<img src=\"${slide.image}\" alt=\"${slide.imageAlt || 'Slide image'}\" class=\"${cls}\" ${style} />
|
| 73 |
+
${slide.caption ? `<figcaption class=\"slide__caption\">${slide.caption}</figcaption>` : ''}
|
| 74 |
+
</figure>
|
| 75 |
+
`;
|
| 76 |
+
})() : ''}
|
| 77 |
+
${hasFootnote ? `<p class="slide__footnote">${slide.footnote}</p>` : ''}
|
| 78 |
+
</div>
|
| 79 |
+
</section>
|
| 80 |
+
`;
|
| 81 |
+
};
|
| 82 |
+
|
| 83 |
+
function renderSlides() {
|
| 84 |
+
slidesRoot.innerHTML = slides.map(slideTemplate).join('');
|
| 85 |
+
// Build sections for sidebar
|
| 86 |
+
// Build sections with nested slide lists
|
| 87 |
+
sections = [];
|
| 88 |
+
let current = null;
|
| 89 |
+
for (let i = 0; i < slides.length; i++) {
|
| 90 |
+
const s = slides[i];
|
| 91 |
+
if (s && s.type === 'section') {
|
| 92 |
+
// Start new section
|
| 93 |
+
current = { title: s.title || `Section ${sections.length + 1}`, index: i, slides: [] };
|
| 94 |
+
sections.push(current);
|
| 95 |
+
} else {
|
| 96 |
+
if (!current) {
|
| 97 |
+
// Create an implicit intro section for slides before the first section
|
| 98 |
+
current = { title: 'Intro', index: 0, slides: [] };
|
| 99 |
+
sections.push(current);
|
| 100 |
+
}
|
| 101 |
+
const label = s && s.title ? s.title : `Slide ${i + 1}`;
|
| 102 |
+
current.slides.push({ title: label, index: i });
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
renderSidebar();
|
| 106 |
+
// Compute content (non-section) slide indices for progress/counter
|
| 107 |
+
contentIndices = slides.map((s, i) => (s && s.type === 'section') ? null : i).filter((x) => x !== null);
|
| 108 |
+
// Precompute cumulative positions for O(1) lookups per slide index
|
| 109 |
+
cumulativePositions = new Array(slides.length);
|
| 110 |
+
let count = 0;
|
| 111 |
+
for (let i = 0; i < slides.length; i++) {
|
| 112 |
+
if (slides[i] && slides[i].type !== 'section') count++;
|
| 113 |
+
cumulativePositions[i] = count;
|
| 114 |
+
}
|
| 115 |
+
updateView();
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
function renderSidebar() {
|
| 119 |
+
ui.setSections(sections);
|
| 120 |
+
updateSidebarActive();
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
function updateView() {
|
| 124 |
+
const all = Array.from(slidesRoot.children);
|
| 125 |
+
all.forEach((el, i) => {
|
| 126 |
+
el.style.display = (i === index) ? 'flex' : 'none';
|
| 127 |
+
});
|
| 128 |
+
// Toggle section mode for CSS (hide progress on section slides)
|
| 129 |
+
const isSection = (slides[index] && slides[index].type === 'section');
|
| 130 |
+
deck.classList.toggle('is-section', !!isSection);
|
| 131 |
+
updateSidebarActive();
|
| 132 |
+
// Progress and counter excluding section slides
|
| 133 |
+
const totalContent = contentIndices.length || 1;
|
| 134 |
+
const position = cumulativePositions[index] || 0;
|
| 135 |
+
ui.setPosition(position, totalContent);
|
| 136 |
+
ui.setNavVisibility(index === 0, index === slides.length - 1);
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
function updateSidebarActive() {
|
| 140 |
+
ui.setActive(index);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
function openSidebar() {
|
| 144 |
+
if (sidebarOpen) return;
|
| 145 |
+
sidebarOpen = true;
|
| 146 |
+
ui.openSidebar();
|
| 147 |
+
deck.classList.add('has-sidebar-open');
|
| 148 |
+
}
|
| 149 |
+
function closeSidebar() {
|
| 150 |
+
if (!sidebarOpen) return;
|
| 151 |
+
sidebarOpen = false;
|
| 152 |
+
ui.closeSidebar();
|
| 153 |
+
deck.classList.remove('has-sidebar-open');
|
| 154 |
+
}
|
| 155 |
+
function toggleSidebar() { (sidebarOpen ? closeSidebar() : openSidebar()); }
|
| 156 |
+
|
| 157 |
+
function next() { index = clamp(index + 1, 0, slides.length - 1); updateView(); }
|
| 158 |
+
function prev() { index = clamp(index - 1, 0, slides.length - 1); updateView(); }
|
| 159 |
+
|
| 160 |
+
// Click anywhere on deck to advance (but not when clicking buttons)
|
| 161 |
+
deck.addEventListener('click', (e) => {
|
| 162 |
+
const isButton = e.target.closest('button');
|
| 163 |
+
if (isButton) return;
|
| 164 |
+
if (sidebarOpen) return; // don't advance when sidebar is open
|
| 165 |
+
next();
|
| 166 |
+
});
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
// Keyboard navigation
|
| 170 |
+
window.addEventListener('keydown', (e) => {
|
| 171 |
+
if (e.key === 'ArrowRight' || e.key === 'PageDown' || e.key === ' ') next();
|
| 172 |
+
if (e.key === 'ArrowLeft' || e.key === 'PageUp') prev();
|
| 173 |
+
if (e.key.toLowerCase() === 'h') prev();
|
| 174 |
+
if (e.key.toLowerCase() === 'l') next();
|
| 175 |
+
if (e.key.toLowerCase() === 's') toggleSidebar();
|
| 176 |
+
if (e.key === 'Escape') closeSidebar();
|
| 177 |
+
});
|
| 178 |
+
|
| 179 |
+
// Sidebar toggle handled via UI (DeckUI) events
|
| 180 |
+
|
| 181 |
+
// Prevent clicks inside sidebar from bubbling to deck
|
| 182 |
+
if (sidebar) {
|
| 183 |
+
sidebar.addEventListener('click', (e) => e.stopPropagation());
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
// Basic touch swipe navigation
|
| 187 |
+
let touchStartX = null;
|
| 188 |
+
deck.addEventListener('touchstart', (e) => {
|
| 189 |
+
touchStartX = e.changedTouches[0].screenX;
|
| 190 |
+
}, { passive: true });
|
| 191 |
+
deck.addEventListener('touchend', (e) => {
|
| 192 |
+
if (touchStartX == null) return;
|
| 193 |
+
const dx = e.changedTouches[0].screenX - touchStartX;
|
| 194 |
+
if (dx > 40) prev();
|
| 195 |
+
if (dx < -40) next();
|
| 196 |
+
touchStartX = null;
|
| 197 |
+
}, { passive: true });
|
| 198 |
+
|
| 199 |
+
// Load slides: always fetch from assets/slides.json
|
| 200 |
+
fetch('assets/slides.json')
|
| 201 |
+
.then((r) => r.json())
|
| 202 |
+
.then((data) => { slides = data; renderSlides(); })
|
| 203 |
+
.catch((err) => {
|
| 204 |
+
console.error('Failed to load slides.json', err);
|
| 205 |
+
slidesRoot.innerHTML = `
|
| 206 |
+
<section class="w-full min-h-screen flex items-center justify-center px-8">
|
| 207 |
+
<div class="max-w-xl text-center">
|
| 208 |
+
<h1 class="text-2xl font-semibold mb-3">Unable to load slides</h1>
|
| 209 |
+
<p class="text-neutral-600">Ensure this app is served over HTTP(S) and assets/slides.json is reachable.</p>
|
| 210 |
+
<p class="mt-2 text-neutral-600">Serve this folder with any static server, e.g.:</p>
|
| 211 |
+
<pre class="mt-2 text-left bg-neutral-100 p-3 rounded border border-neutral-200 text-sm">python3 -m http.server 8000</pre>
|
| 212 |
+
</div>
|
| 213 |
+
</section>`;
|
| 214 |
+
});
|
| 215 |
+
});
|
assets/slides.json
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"type": "title",
|
| 4 |
+
"title": "Behavioral Political Economy Lab",
|
| 5 |
+
"subtitle": "RA Orientation & Assignments"
|
| 6 |
+
},
|
| 7 |
+
{
|
| 8 |
+
"title": "Welcome & Overview",
|
| 9 |
+
"subtitle": "RA Assignments — Second Half of Semester",
|
| 10 |
+
"bullets": [
|
| 11 |
+
"The BPE Lab conducts <strong>incentivized, non-deceptive experiments</strong> with real payoffs.",
|
| 12 |
+
"Our research provides <strong>clean causal inference and direct measurement</strong>, often compared to game theory.",
|
| 13 |
+
"This orientation outlines <strong>RA tasks, standards, and deliverables</strong>, covering lab sessions, data work, testing, and reporting.",
|
| 14 |
+
"<strong>RA roles span research (reviews, coding), operations (session prep, monitoring), and support (recruitment, testing)</strong>."
|
| 15 |
+
],
|
| 16 |
+
"footnote": "From: BPE-Lab-Research-Themes-Digest.md; deck-outline.md"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"type": "section",
|
| 20 |
+
"title": "Orientation & Context",
|
| 21 |
+
"subtitle": "What we do and how you plug in"
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"title": "Orientation and Purpose",
|
| 25 |
+
"subtitle": "Meeting goals and expectations",
|
| 26 |
+
"bullets": [
|
| 27 |
+
"<strong>Clarify the operational expectations</strong> for the rest of the semester.",
|
| 28 |
+
"Define <strong>measurable deliverables</strong> and the weekly reporting structure.",
|
| 29 |
+
"Locate RA work within the lab’s research pipeline and session schedule.",
|
| 30 |
+
"Reporting: RAs primarily check in with the <strong>Lab Manager (Ignacio Urbina)</strong>."
|
| 31 |
+
]
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"title": "Admin Calendar (Fall)",
|
| 35 |
+
"subtitle": "Key semester dates — plan submissions accordingly",
|
| 36 |
+
"bullets": [
|
| 37 |
+
"<strong>Semester start: Mon, Aug 25; Last day of classes: Mon, Dec 8</strong>.",
|
| 38 |
+
"<strong>Finals: Dec 10–12 (Week I) and Dec 15–18 (Week II)</strong>.",
|
| 39 |
+
"<strong>Official end of term: Thu, Dec 18</strong> (Registrar calendar)."
|
| 40 |
+
],
|
| 41 |
+
"footnote": "From: Administrative Details.docx"
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"title": "PI Differences & Core Expectation",
|
| 45 |
+
"bullets": [
|
| 46 |
+
"Different Principal Investigators (PIs) may request different workflows; it helps to <strong>clarify early and follow the requested approach</strong>.",
|
| 47 |
+
"Most valuable help: be an <strong>extra team member on the lab floor during sessions</strong>.",
|
| 48 |
+
"Default: <strong>check with the PI before answering any study-specific question</strong>."
|
| 49 |
+
],
|
| 50 |
+
"footnote": "From: CBPE RA Training (SP 2024).md (Slide 7)"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"type": "section",
|
| 54 |
+
"title": "Expectations & Operational Details",
|
| 55 |
+
"subtitle": "Reporting, workload, workflow, and Q&A"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"title": "Expectations and Operational Details",
|
| 59 |
+
"bullets": [
|
| 60 |
+
"Key tasks: literature reviews, manual coding, lab monitoring, experiment/platform testing, recruitment outreach.",
|
| 61 |
+
"Expect <strong>~5 hrs/week for reviews (~15 hrs total)</strong>; hand coding and reviews are paced to term end, lab tasks are on-demand.",
|
| 62 |
+
"<strong>Mid-term check-in (Nov 19) and weekly status summaries</strong> are required; Ignacio Urbina is primary contact.",
|
| 63 |
+
"All deliverables must be stored in <strong>shared Drive with naming conventions</strong> and follow codebooks/protocols."
|
| 64 |
+
]
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"title": "Check-ins & Deadlines",
|
| 68 |
+
"bullets": [
|
| 69 |
+
"<strong>Midpoint check-in: brief Slack DM to Ignacio by Nov 19</strong> (progress, blockers, next steps).",
|
| 70 |
+
"<strong>Final submission: Dec 12</strong> (spreadsheet + PDF archive in shared Drive).",
|
| 71 |
+
"Lab monitoring/pilots are in-person and on-demand; <strong>reviews and hand coding are paced with end-of-semester completion</strong>."
|
| 72 |
+
],
|
| 73 |
+
"footnote": "From: Lit Review instruction docs; Administrative Details.docx"
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"type": "section",
|
| 77 |
+
"title": "Lab Procedures",
|
| 78 |
+
"subtitle": "Capacity, check-in, rules, payments"
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"title": "Arrival & Setup — Before Session",
|
| 82 |
+
"bullets": [
|
| 83 |
+
"<strong>Arrive 20 mins before session start</strong>; ensure readiness before participants arrive 15 mins early.",
|
| 84 |
+
"<strong>Turn on desktops and launch study software</strong> (zTree/oTree/Qualtrics) per PI instructions.",
|
| 85 |
+
"Sessions are <strong>45-60 mins, capped at 25 participants</strong>; roster defines headcount.",
|
| 86 |
+
"Participants sign up in advance; we <strong>over-recruit slightly to offset no-shows</strong>."
|
| 87 |
+
],
|
| 88 |
+
"footnote": "From: CBPE RA Training (SP 2024).md (Slide 9)"
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"title": "Wayfinding & Check-In — On Arrival",
|
| 92 |
+
"bullets": [
|
| 93 |
+
"<strong>Assist participants with wayfinding; verify ID and roster with PI</strong>; admit only registered invitees.",
|
| 94 |
+
"<strong>Hand out seat tag, receipt form, and pen</strong>; guide participants to their seats.",
|
| 95 |
+
"Ensure participants complete <strong>name, signature, address, and SBU ID on the receipt form (required for payment)</strong>.",
|
| 96 |
+
"<strong>Verify all receipt fields are complete</strong> during check-in; store securely and alphabetize post-session."
|
| 97 |
+
],
|
| 98 |
+
"footnote": "From: CBPE RA Training (SP 2024).md (Slide 10)"
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"title": "Extra Participants, Late Policy, No-Shows",
|
| 102 |
+
"bullets": [
|
| 103 |
+
"<strong>Manage extra participants (show-up fee/reschedule)</strong>; late arrivals cannot participate and are unpaid.",
|
| 104 |
+
"<strong>Two no-shows result in removal from the pool</strong>; PI may grant a 2–5 min grace period.",
|
| 105 |
+
"<strong>Close lab doors before experiments; politely but firmly deny entry to late subjects</strong>.",
|
| 106 |
+
"Maintain a <strong>quiet, orderly, and focused environment</strong>; defer to PI for difficult situations."
|
| 107 |
+
],
|
| 108 |
+
"footnote": "From: CBPE RA Training (SP 2024).md (Slides 13–14)"
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"title": "During Session — Your Role",
|
| 112 |
+
"bullets": [
|
| 113 |
+
"<strong>Answer procedural questions only</strong>; monitor participants at assigned computers.",
|
| 114 |
+
"<strong>Collect receipts and pens early</strong> to streamline payments.",
|
| 115 |
+
"<strong>Enforce house rules: no talking, eating, or devices</strong> during the study.",
|
| 116 |
+
"Assist participants who raise hands; <strong>PI introduces you as Lab Assistant</strong>."
|
| 117 |
+
],
|
| 118 |
+
"footnote": "From: CBPE RA Training (SP 2024).md (Slides 15–16); RA Training.md"
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"title": "End of Experiment — Line & Roles",
|
| 122 |
+
"bullets": [
|
| 123 |
+
"<strong>Payments begin post-experiment/survey</strong>; participants line up by computer number with seat tag.",
|
| 124 |
+
"<strong>PI handles payments in control room; RA maintains lab order and calls numbers, ensuring anonymity</strong>.",
|
| 125 |
+
"<strong>Verify receipt completeness; do not admit others to control room until called</strong>.",
|
| 126 |
+
"<strong>Post-session: alphabetize receipts, reset lab (desktops/software), and secure lost items</strong>."
|
| 127 |
+
],
|
| 128 |
+
"footnote": "From: CBPE RA Training (SP 2024).md (Slide 17)"
|
| 129 |
+
},
|
| 130 |
+
{
|
| 131 |
+
"type": "section",
|
| 132 |
+
"title": "Assignments — Testing & Recruitment",
|
| 133 |
+
"subtitle": "Outreach, pilots, and platform checks"
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"title": "Experiment & Platform Testing",
|
| 137 |
+
"bullets": [
|
| 138 |
+
"<strong>Pilot and verify experiment builds</strong> (zTree, oTree, Qualtrics) for flows, timers, and payoffs.",
|
| 139 |
+
"<strong>Log reproducible bugs</strong>, noting risk and cause; propose fixes or alternatives.",
|
| 140 |
+
"<strong>Conduct physical/digital recruitment outreach</strong> as instructed by supervisor/PI.",
|
| 141 |
+
"<strong>Document outreach locations, counts, dates</strong>, and update tracking sheets promptly."
|
| 142 |
+
]
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"type": "section",
|
| 146 |
+
"title": "Assignments — Literature Reviews",
|
| 147 |
+
"subtitle": "Topics, scope, workflow, and pairing"
|
| 148 |
+
},
|
| 149 |
+
{
|
| 150 |
+
"title": "Lit Review — Prep & Workload",
|
| 151 |
+
"bullets": [
|
| 152 |
+
"<strong>Review topic intros; study spreadsheet structure; follow instructions step-by-step</strong>.",
|
| 153 |
+
"<strong>Weekly workload per RA: ~5 hours; total ≈15 hours (~3 weeks)</strong>.",
|
| 154 |
+
"<strong>Store all outputs in shared Drive; use naming conventions</strong> and complete metadata fields."
|
| 155 |
+
],
|
| 156 |
+
"footnote": "From: Lit Review instruction docs"
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"title": "Literature Review — Scope & Standards",
|
| 160 |
+
"bullets": [
|
| 161 |
+
"<strong>Identify at least 20 unique peer-reviewed papers</strong> per assigned topic.",
|
| 162 |
+
"<strong>Deliver a PDF archive and a structured spreadsheet</strong> (authors, year, journal, design, measures, key results).",
|
| 163 |
+
"<strong>Use inclusion/exclusion criteria; avoid duplicates; record search strings and databases</strong>.",
|
| 164 |
+
"When possible, reviews are assigned in pairs; please <strong>split work evenly (≈10 entries each)</strong>."
|
| 165 |
+
]
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"title": "Lit Review — Workflow & Deliverables",
|
| 169 |
+
"bullets": [
|
| 170 |
+
"<strong>Source identification → screening → metadata entry → weekly verification</strong>.",
|
| 171 |
+
"<strong>Use version control; adopt strict file naming conventions</strong> for PDFs and data.",
|
| 172 |
+
"<strong>Cite with DOIs/URLs; ensure complete bibliographic fields</strong> and stable links."
|
| 173 |
+
]
|
| 174 |
+
},
|
| 175 |
+
{
|
| 176 |
+
"type": "section",
|
| 177 |
+
"title": "Assignments — Manual Coding",
|
| 178 |
+
"subtitle": "Climate tweet stance task and quality"
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"title": "Manual Climate Tweet Stance Coding",
|
| 182 |
+
"bullets": [
|
| 183 |
+
"<strong>Classify climate change stance</strong> (supportive, skeptical, neutral, other) using specified platforms (Google Form, Qualtrics, spreadsheet).",
|
| 184 |
+
"<strong>Follow codebook definitions</strong>, handling sarcasm and marking off-topic/ambiguous content.",
|
| 185 |
+
"<strong>Output .xlsx with tweet ID/text, coder ID, stance label, notes, and timestamp</strong>.",
|
| 186 |
+
"<strong>Complete practice rounds for training; double-code a subset to compute and resolve agreement</strong>."
|
| 187 |
+
]
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"title": "Hand Coding — Quality & Compliance",
|
| 191 |
+
"bullets": [
|
| 192 |
+
"<strong>Follow the codebook precisely; raise ambiguities before proceeding</strong>.",
|
| 193 |
+
"<strong>Maintain an error log with corrections and rationale</strong>; date all changes.",
|
| 194 |
+
"<strong>Schedule periodic reliability audits; target high inter-coder agreement</strong>."
|
| 195 |
+
]
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"type": "section",
|
| 199 |
+
"title": "Standards & Conduct",
|
| 200 |
+
"subtitle": "Performance, ethics, and growth"
|
| 201 |
+
},
|
| 202 |
+
{
|
| 203 |
+
"title": "Performance Standards",
|
| 204 |
+
"bullets": [
|
| 205 |
+
"<strong>Precision and consistency in handling data and receipts</strong>; no shortcuts.",
|
| 206 |
+
"<strong>Reliability and punctuality for sessions</strong>; arrive earlier than participants.",
|
| 207 |
+
"<strong>Adhere to instructions; when unsure, pause and ask the PI/supervisor</strong>."
|
| 208 |
+
]
|
| 209 |
+
},
|
| 210 |
+
{
|
| 211 |
+
"title": "Compliance & Conduct",
|
| 212 |
+
"bullets": [
|
| 213 |
+
"<strong>Maintain confidentiality</strong>; do not share participant or study info without authorization.",
|
| 214 |
+
"<strong>Human subjects ethics: uphold non-deceptive protocols and consent procedures</strong>.",
|
| 215 |
+
"<strong>Report incidents immediately</strong>; document actions taken to remediate."
|
| 216 |
+
],
|
| 217 |
+
"footnote": "From: BPE-Lab-Research-Themes-Digest.md; lab policies"
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"type": "section",
|
| 221 |
+
"title": "Timeline & Q&A",
|
| 222 |
+
"subtitle": "Sequencing, questions, and next steps"
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
"title": "Timeline & Task Sequencing",
|
| 226 |
+
"bullets": [
|
| 227 |
+
"<strong>Continuous work: coding, literature reviews, documentation, data housekeeping</strong>.",
|
| 228 |
+
"<strong>Event-driven: session monitoring, pilots, platform tests, recruitment pushes</strong>.",
|
| 229 |
+
"<strong>Mid-point and final checkpoints: verify deliverables and adjust allocations</strong>."
|
| 230 |
+
]
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"title": "Questions & Confirmation",
|
| 234 |
+
"bullets": [
|
| 235 |
+
"<strong>Confirm understanding of all policies</strong> (late, no-show, anonymity, receipts, device rules).",
|
| 236 |
+
"<strong>Review assigned topics/tasks and target dates</strong>; request clarifications as needed.",
|
| 237 |
+
"<strong>Commit to a weekly reporting time/channel; begin assigned tasks immediately</strong>.",
|
| 238 |
+
"<strong>Follow protocols, keep supervisors informed, escalate blockers, and meet milestones</strong>."
|
| 239 |
+
]
|
| 240 |
+
}
|
| 241 |
+
]
|
index.html
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
+
<title>BPE Lab — RA Assignments (Slide Deck)</title>
|
| 7 |
+
<!-- Tailwind CSS via CDN -->
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<!-- Deck styles -->
|
| 10 |
+
<link rel="stylesheet" href="assets/deck.css" />
|
| 11 |
+
<meta name="description" content="Behavioral Political Economy Lab — RA Assignments Slide Deck" />
|
| 12 |
+
</head>
|
| 13 |
+
<body class="bg-neutral-50 text-neutral-900 selection:bg-black selection:text-white">
|
| 14 |
+
<!-- Deck Container -->
|
| 15 |
+
<div id="deck" class="min-h-screen w-full flex items-center justify-center relative overflow-hidden cursor-pointer">
|
| 16 |
+
<!-- Slides Root -->
|
| 17 |
+
<div id="slides" class="w-full h-full"></div>
|
| 18 |
+
|
| 19 |
+
<!-- Sidebar Toggle -->
|
| 20 |
+
<button id="sidebarToggle" aria-label="Toggle sections sidebar" aria-expanded="false" class="sidebar-toggle">
|
| 21 |
+
<svg id="sidebarToggleIcon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-5 h-5" aria-hidden="true">
|
| 22 |
+
<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2" />
|
| 23 |
+
<rect x="11" y="10" width="2" height="7" rx="1" fill="currentColor" />
|
| 24 |
+
<rect x="11" y="6" width="2" height="2" rx="1" fill="currentColor" />
|
| 25 |
+
</svg>
|
| 26 |
+
</button>
|
| 27 |
+
|
| 28 |
+
<!-- Sidebar: Sections Navigation -->
|
| 29 |
+
<aside id="sidebar" class="sidebar" aria-hidden="true">
|
| 30 |
+
<div class="sidebar__inner">
|
| 31 |
+
<header class="sidebar__header">
|
| 32 |
+
<span class="sidebar__title">Sections</span>
|
| 33 |
+
</header>
|
| 34 |
+
<nav id="sidebarList" class="sidebar__list" aria-label="Slide sections"></nav>
|
| 35 |
+
</div>
|
| 36 |
+
</aside>
|
| 37 |
+
|
| 38 |
+
<!-- Footer: Navigation + Progress -->
|
| 39 |
+
<div class="deck-footer">
|
| 40 |
+
<!-- Prev button (small) -->
|
| 41 |
+
<button id="prevBtn" aria-label="Previous slide" class="group p-2 rounded-full bg-white/80 hover:bg-white shadow border border-neutral-200 backdrop-blur pointer-events-auto hidden">
|
| 42 |
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4 text-neutral-700 group-hover:-translate-x-0.5 transition-transform">
|
| 43 |
+
<path fill-rule="evenodd" d="M15.53 4.47a.75.75 0 0 1 0 1.06L9.06 12l6.47 6.47a.75.75 0 1 1-1.06 1.06l-7-7a.75.75 0 0 1 0-1.06l7-7a.75.75 0 0 1 1.06 0Z" clip-rule="evenodd" />
|
| 44 |
+
</svg>
|
| 45 |
+
</button>
|
| 46 |
+
|
| 47 |
+
<!-- Progress -->
|
| 48 |
+
<div id="progressBar" class="h-1 w-44 bg-neutral-200 rounded-full overflow-hidden">
|
| 49 |
+
<div id="progressInner" class="h-full w-0 bg-neutral-800 transition-all"></div>
|
| 50 |
+
</div>
|
| 51 |
+
<div id="counter" class="text-sm font-medium text-neutral-600"></div>
|
| 52 |
+
|
| 53 |
+
<!-- Next button (small) -->
|
| 54 |
+
<button id="nextBtn" aria-label="Next slide" class="group p-2 rounded-full bg-white/80 hover:bg-white shadow border border-neutral-200 backdrop-blur pointer-events-auto hidden">
|
| 55 |
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-4 h-4 text-neutral-700 group-hover:translate-x-0.5 transition-transform">
|
| 56 |
+
<path fill-rule="evenodd" d="M8.47 4.47a.75.75 0 0 1 1.06 0l7 7a.75.75 0 0 1 0 1.06l-7 7a.75.75 0 1 1-1.06-1.06L14.94 12 8.47 5.53a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
|
| 57 |
+
</svg>
|
| 58 |
+
</button>
|
| 59 |
+
</div>
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<!-- UI helpers (class-based) -->
|
| 63 |
+
<script defer src="assets/components.js"></script>
|
| 64 |
+
<!-- App JS -->
|
| 65 |
+
<script defer src="assets/deck.js"></script>
|
| 66 |
+
</body>
|
| 67 |
+
</html>
|