Handing this off to an AI?
Give it SYSTEM_REFERENCE.md instead of re-explaining the system - it's written specifically so a fresh AI session can understand the whole architecture with zero prior context.
Two Visual Themes
This site deliberately uses two different looks for two different kinds of pages. Don't mix them when building something new.
DARK / TERMINAL
Near-black background, JetBrains Mono, amber + teal accents. Where students actually do the day's work.
Light / Friendly
Soft lavender background, Fredoka headings, warm orange accent, white rounded cards. Landing/reference/utility pages.
Every File On This Site
Direct links to everything - the actual live files, not a description of them.
Installing the Scripts
Everything here is plain HTML/CSS/JS - no build step, no dependencies to install. This is the order that actually works, start to finish.
- Upload the site files, preserving the folder structure exactly -
js/,css/, andlabs/(with its ownjs//css/) all need to sit where the HTML expects them. - Create the Google Sheet(s). One public spreadsheet for pacing/vocab/quiz/resources tabs, and one separate, never-published private spreadsheet for
roster,help_queue,star_log,star_totals, andstudent_work. - Publish each public tab to web individually - File → Share → Publish to web → pick the specific tab (never "Entire Document") → format Tab-separated values (.tsv) → copy the resulting URL.
- Paste each published URL into its matching constant at the top of
index.html/day.html(see the comment above each URL constant for which tab goes where). - Deploy
Code.gsfrom the private spreadsheet: Extensions → Apps Script → paste inCode.gs→ Deploy → New deployment → Web app → execute as yourself, accessible to "Anyone" → copy the deployment URL. - Paste that one deployment URL into
STAR_API_URLinjs/student-id.jsandAPI_URLinask-a-question.html- same URL in both places, one backend for the whole site. - Lab tools reuse that same URL. Each lab's own script calls
PCSCertificate.render({ webAppUrl: ... })with that identical deployment URL - don't stand up a second Apps Script deployment just for labs. - Test before going live with
test.html(hardcoded example data, no spreadsheet needed) or a realday = -1row in the daily content tab (reserved for test data).
roster, help_queue, star_log, star_totals, or student_work at all. Those four (five, with student_work) hold live per-student data and are only ever read/written by Code.gs.
How day.html Actually Builds a Page
day.html has almost no fixed content - it's a thin shell that day-loader.js fills in dynamically at load time, entirely from spreadsheet data.
- Reads
?course=and?day=from the URL. - Shows the full-screen loading overlay immediately.
- Fetches the pacing tab, the daily content tab, and (if needed) the terminal-steps tab, in parallel.
- Finds the one pacing row matching both the course and the day - not day alone, since the pacing tab now covers all 3 courses.
- Builds the hero: title, small Learning Objective / Essential Knowledge reference lines, day/date pills.
- Builds every widget for that day in the exact order the rows appear in the spreadsheet. Reorder widgets by reordering rows - there's no separate ordering column.
- Builds the certificate, tracking every trackable widget on the page.
- Fades out the loading overlay and draws in the four corner-flourish decorations.
instruction row with field_d set to exactly after_certificate renders in its own separate slot below the certificate, regardless of where the row sits in the sheet. That's the mechanism for "here's exactly what to do with the rest of class" - the literal last thing a student sees on the page.
(1)/(2) only when more than one exists that day.
Widget Reference (16 types)
Every widget is one row in the daily content tab: course | day | widgetname | field_a | field_b | field_c | field_d | notes. The same 4 generic fields mean different things depending on widgetname. Click any widget to expand its exact field usage.
;; separates list items; :: separates sub-fields within one item.
Every Spreadsheet Tab
Public tabs are published to web (File โ Share โ Publish to web โ choose the specific tab, format TSV) and read by the website. Private tabs are read only by the backend Apps Script and should never be published.
| Tab | Tier | Read by | Columns | Notes |
|---|---|---|---|---|
pacing timeline | public | index.html, day.html | course, day, date, weekday, term, unit, topic_activity, day_type, learning_objective, essential_knowledge, mapping_note, possible_lab_project (+Cyber-only: scenario, hands_on_activity, hands_on_note) |
Merged - all 3 courses, one tab. course = "ap csa"/"ap csp"/"ap cyber" (case/spacing-insensitive). |
daily content | public | day.html | course, day, widgetname, field_a, field_b, field_c, field_d, notes |
The master content tab. day = -1 reserved for test data. |
vocabulary | public | index.html | type, course, category, term, description, example |
Merged with Cheat Sheets. type = "vocab" or "cheat...". Blank course matches nothing - list every applicable course explicitly, semicolon-separated. |
frqs | public | index.html | course, unit, question, excellent student response, weak student response |
Header must say exactly course. |
quiz questions | public | index.html | course, unit, question, option_a...option_h |
Standalone MCQ tab - distinct from the daily-content quiz widget. option_a always correct; 3 of the remaining up-to-7 wrong options chosen at random each attempt. |
resources | public | index.html | name/description/url | |
terminal-steps | public | day.html | step_id, label, command, arg, require_arg_match, require_cwd, notes, fs_template |
~77 steps, 8 filesystem templates. Never set require_cwd on a cd step. |
classroom rules | public | day.html, policies.html | One column: rule |
Read live by both the per-day rule flag-row widget and policies.html. |
rank_titles | private | Code.gs only | course, title, stars_to_get_title |
Not a published TSV - read via the Sheets API. course uses the plain key with no spaces (apcsa/apcsp/apcyber), a different convention from the pacing tab, intentionally. |
bulletquestions | private | Code.gs only | course, question |
Powers the Quick Logistics buttons. Known bug: a stray "ap apcyber" typo currently breaks 2 rows for Cybersecurity. |
help_queue, roster, star_log, star_totals | private | Code.gs only | — | Live operational student data. Never publish these to web. |
ask-a-question.html
A fully functional help-queue page backed by Code.gs (deployed separately as a Google Apps Script Web App) - not a spreadsheet-TSV page. Everything goes through the Apps Script's own API.
Pending Items & Known Limitations
GENERAL_MESSAGES_TSV_URL in day.html is a placeholder - the messages tab (date/message columns) doesn't exist yet.bulletquestions breaks 2 Cybersecurity quick-questions.