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.

-
- - My Account - Back to Study +
+
+
+

{study.title}

+

Enroll to Begin

+

Enroll in this study to open its lessons and keep your notes and progress.

+
+ +
+ {enrollMessage &&

{enrollMessage}

} +

← Back to study

- {enrollMessage &&

{enrollMessage}

}
)