diff --git a/package.json b/package.json
index 95e785c..08a1d82 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "siteforge",
"private": true,
- "version": "2.10.6",
+ "version": "2.10.7",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src/App.css b/src/App.css
index 2f226b3..f94fe9d 100644
--- a/src/App.css
+++ b/src/App.css
@@ -4033,6 +4033,129 @@
padding: 0;
}
+/* ── Study index: progress, chapters, lesson badges ── */
+.study-course-meta { margin-bottom: 1.25rem; }
+
+.study-progress {
+ max-width: 460px;
+ margin: 0 0 1.75rem;
+}
+
+.study-progress-label {
+ display: flex;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 0.45rem;
+ font-size: 0.82rem;
+ color: var(--brand-muted);
+ letter-spacing: 0.04em;
+}
+
+.study-progress-label strong {
+ color: var(--brand-warm-white);
+ font-weight: 600;
+}
+
+.study-progress-track {
+ height: 8px;
+ border-radius: 999px;
+ background: rgba(201, 168, 76, 0.16);
+ overflow: hidden;
+}
+
+.study-progress-fill {
+ height: 100%;
+ border-radius: 999px;
+ background: linear-gradient(90deg, #c9a84c, #e0c67a);
+ transition: width 0.4s ease;
+}
+
+.study-chapter + .study-chapter { margin-top: 1.25rem; }
+
+.study-chapter > summary {
+ list-style: none;
+ cursor: pointer;
+}
+
+.study-chapter > summary::-webkit-details-marker { display: none; }
+
+.study-chapter > summary h3::before {
+ content: '▸';
+ display: inline-block;
+ margin-right: 0.55rem;
+ color: var(--brand-gold);
+ font-size: 0.9em;
+ transition: transform 0.15s ease;
+}
+
+.study-chapter[open] > summary h3::before { transform: rotate(90deg); }
+
+/* Upcoming lessons stay compact until they release */
+.study-module-row--coming {
+ grid-template-columns: 1fr;
+ padding: 0.7rem 1.05rem;
+}
+
+.study-module-row--coming .study-module-row-right,
+.study-module-row--coming .study-module-row-left > p { display: none; }
+
+.study-module-row--coming .study-module-row-left h3 { margin: 0.1rem 0 0; font-size: 1.05rem; }
+
+
+.study-chapter-head {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 1rem;
+ margin: 0 0 0.75rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid rgba(201, 168, 76, 0.2);
+}
+
+.study-chapter-head h3 {
+ margin: 0;
+ font-family: var(--brand-font-heading);
+ font-size: 1.3rem;
+ color: #e8d9b5;
+}
+
+.study-chapter-head span {
+ font-size: 0.8rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--brand-muted);
+}
+
+.study-lesson-meta {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ margin-bottom: 0.25rem;
+}
+
+.lesson-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.3rem;
+ padding: 0.18rem 0.6rem;
+ border-radius: 999px;
+ font-size: 0.72rem;
+ font-weight: 600;
+ letter-spacing: 0.04em;
+ border: 1px solid transparent;
+}
+
+.lesson-badge--new { background: rgba(76, 175, 80, 0.16); color: #7fd484; border-color: rgba(76, 175, 80, 0.4); }
+.lesson-badge--coming { background: rgba(255, 183, 77, 0.14); color: #f0b45c; border-color: rgba(255, 183, 77, 0.4); }
+.lesson-badge--enroll { background: rgba(201, 168, 76, 0.12); color: var(--brand-gold); border-color: rgba(201, 168, 76, 0.4); }
+.lesson-badge--locked { background: rgba(255, 255, 255, 0.05); color: var(--brand-muted); border-color: rgba(255, 255, 255, 0.14); }
+.lesson-badge--done { background: rgba(111, 207, 151, 0.12); color: #6fcf97; border-color: rgba(111, 207, 151, 0.4); }
+
+.study-module-row--done {
+ border-left: 3px solid rgba(111, 207, 151, 0.7);
+}
+
/* ── Sign in / Create account (split layout) ── */
.auth-page {
min-height: 70vh;
@@ -4136,6 +4259,10 @@
.auth-card-eyebrow { margin: 0; }
+.auth-card--center { text-align: center; }
+
+.auth-card--center .study-signup-actions { justify-content: center; }
+
.auth-card .study-signup-form { margin-top: 0.5rem; }
.auth-card .study-signup-actions { margin-top: 0.75rem; }
diff --git a/src/studyPages.tsx b/src/studyPages.tsx
index 93ff50a..e8e89e2 100644
--- a/src/studyPages.tsx
+++ b/src/studyPages.tsx
@@ -1639,7 +1639,9 @@ export function ColossiansStudyIndexPage({ content }: Props) {
// Resume at the first released lesson the student hasn't finished yet
const resumeSection = releasedSections.find(item => !studyProgress.completedSectionIds.includes(item.id)) ?? firstReleasedSection
const hasStarted = enrolled && studyProgress.completedSectionIds.length > 0
- const populatedChapterCount = chapterSummaries.filter(chapter => chapter.lessonCount > 0).length
+ const resumeChapter = resumeSection?.chapter ?? chapterSummaries.find(chapter => chapter.lessonCount > 0)?.chapter
+ const completedCount = sections.filter(item => studyProgress.completedSectionIds.includes(item.id)).length
+ const chaptersWithLessons = chapterSummaries.filter(chapter => chapter.lessonCount > 0)
async function enrollInStudy() {
if (!study) return
@@ -1670,16 +1672,22 @@ export function ColossiansStudyIndexPage({ content }: Props) {
{study.title}
{study.description}
- {releasedSections.length} lessons available now
- {chapterSummaries.length} chapters
- {populatedChapterCount} chapters with lessons
- {enrolled && !progressLoading ? (
- {studyProgress.completedSectionIds.length} lessons completed
- ) : null}
+ {releasedSections.length} of {sections.length} lessons available
Text + commentary + discussion
Student notes enabled
{isSelfPaced && Finished study — go at your own pace }
+ {enrolled && !progressLoading && sections.length > 0 && (
+
+
+ Your progress
+ {completedCount} of {sections.length} lessons completed
+
+
+
+ )}
{enrolled && resumeSection && {hasStarted ? 'Resume Where You Left Off' : 'Start Class'}}
{!auth.authenticated && Sign In to Enroll}
@@ -1688,10 +1696,9 @@ export function ColossiansStudyIndexPage({ content }: Props) {
{enrolling ? 'Enrolling...' : 'Enroll in This Study'}
)}
- {auth.authenticated && My Account}
{enrolled && My Notes}
{enrolled && Community}
- Back to Studies
+ {auth.authenticated && My Account}
{enrollMessage && {enrollMessage}
}
@@ -1737,47 +1744,65 @@ export function ColossiansStudyIndexPage({ content }: Props) {
Course Lessons
{study.title}
-
- {sections.map((section, sectionIdx) => {
- const lessonNumber = getLessonNumber(sections, section.id)
- const isNew = isNewLesson(section)
- const coming = isComingSoon(section)
- const prevSection = sectionIdx > 0 ? sections[sectionIdx - 1] : null
- const prevCompleted = !prevSection || studyProgress.completedSectionIds.includes(prevSection.id)
- const lockedByProgress = enrolled && !isSelfPaced && !coming && sectionIdx > 0 && !prevCompleted && !studyProgress.completedSectionIds.includes(section.id)
- const isDisabled = coming || !enrolled || lockedByProgress
+ {chaptersWithLessons.map(({ chapter }) => {
+ const chapterSections = sections.filter(item => item.chapter === chapter)
+ const chapterDone = chapterSections.filter(item => studyProgress.completedSectionIds.includes(item.id)).length
+ return (
+
+
+ Chapter {chapter}
+
+ {enrolled && !progressLoading
+ ? `${chapterDone} of ${chapterSections.length} completed`
+ : `${chapterSections.length} ${chapterSections.length === 1 ? 'lesson' : 'lessons'}`}
+
+
+
+ {chapterSections.map(section => {
+ const sectionIdx = sections.indexOf(section)
+ const lessonNumber = sectionIdx + 1
+ const isNew = isNewLesson(section)
+ const coming = isComingSoon(section)
+ const prevSection = sectionIdx > 0 ? sections[sectionIdx - 1] : null
+ const prevCompleted = !prevSection || studyProgress.completedSectionIds.includes(prevSection.id)
+ const completed = studyProgress.completedSectionIds.includes(section.id)
+ const lockedByProgress = enrolled && !isSelfPaced && !coming && sectionIdx > 0 && !prevCompleted && !completed
+ const isDisabled = coming || !enrolled || lockedByProgress
- return (
-
isDisabled && e.preventDefault()}>
-
-
-
Lesson {lessonNumber}
- {isNew &&
New }
- {coming &&
Coming {getReleaseDateDisplay(section)} }
- {!coming && !enrolled &&
Enroll to open }
- {lockedByProgress &&
Complete previous lesson }
- {enrolled && !lockedByProgress && studyProgress.completedSectionIds.includes(section.id) && (
-
-
-
-
-
- Completed
-
- )}
-
-
{section.title}
-
{section.summary}
-
-
-
{section.reference}
-
Focus question
-
{getPrimaryQuestion(section)}
-
-
- )
- })}
-
+ return (
+ isDisabled && e.preventDefault()}>
+
+
+
Lesson {lessonNumber}
+ {isNew &&
New }
+ {coming &&
Coming {getReleaseDateDisplay(section)} }
+ {!coming && !enrolled &&
Enroll to open }
+ {lockedByProgress &&
Finish the previous lesson first }
+ {enrolled && !lockedByProgress && completed && (
+
+
+
+
+
+ Completed
+
+ )}
+
+
{section.title}
+
{section.summary}
+
+
+
{section.reference}
+
Focus question
+
{getPrimaryQuestion(section)}
+
+
+ )
+ })}
+
+
+ )
+ })}
)}
@@ -2169,15 +2194,16 @@ export function ColossiansStudySectionPage({ content }: Props) {
if (!auth.authenticated) {
return (
-
-
-
{study.title}
-
Sign In to Continue
-
Create a free account or sign in to access lessons and save your personal notes.
-
-
Create Account or Sign In
-
My Account
-
Back to Lessons
+
+
+
+
{study.title}
+
Sign In to Continue
+
Create a free account or sign in to open lessons and save your personal notes.
+
+ Create Account or Sign In
+
+
← Back to lessons
@@ -2186,19 +2212,20 @@ export function ColossiansStudySectionPage({ content }: Props) {
if (!isEnrolled) {
return (
-
-
-
{study.title}
-
Enrollment Required
-
You need to enroll in this study before entering lessons.
-
-
- {enrollBusy ? 'Enrolling...' : `Enroll in ${study.title}`}
-
-
My Account
-
Back to Study
+
+
+
+
{study.title}
+
Enroll to Begin
+
Enroll in this study to open its lessons and keep your notes and progress.
+
+
+ {enrollBusy ? 'Enrolling...' : `Enroll in ${study.title}`}
+
+
+ {enrollMessage &&
{enrollMessage}
}
+
← Back to study
- {enrollMessage &&
{enrollMessage}
}
)