iurbinah commited on
Commit
1735a4e
·
verified ·
1 Parent(s): 9334026

Upload 5 files

Browse files
Files changed (5) hide show
  1. assets/components.js +244 -0
  2. assets/deck.css +238 -0
  3. assets/deck.js +215 -0
  4. assets/slides.json +241 -0
  5. 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>