diff --git a/package.json b/package.json
index 9dad657..22148ec 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "siteforge",
"private": true,
- "version": "1.2.4",
+ "version": "1.2.5",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src/App.css b/src/App.css
index 8a862ef..82e6061 100644
--- a/src/App.css
+++ b/src/App.css
@@ -2668,17 +2668,20 @@
.note-anchor-btn {
position: absolute;
- top: 1rem;
- right: 1rem;
+ top: 0.85rem;
+ right: 0.85rem;
background: transparent;
border: 1px solid #3a3828;
border-radius: 6px;
color: #5a5440;
- padding: 0.2rem 0.55rem;
- font-size: 0.9rem;
+ padding: 0.22rem 0.65rem;
+ font-size: 0.78rem;
+ font-family: var(--brand-font-body);
+ font-weight: 500;
+ letter-spacing: 0.02em;
cursor: pointer;
transition: color 150ms, border-color 150ms, background 150ms;
- line-height: 1;
+ line-height: 1.4;
z-index: 1;
}
@@ -2699,6 +2702,184 @@
border-color: rgba(224, 192, 112, 0.5);
}
+/* ── Progress bar ────────────────────────────────────────────────── */
+.study-lesson-progress-bar {
+ height: 3px;
+ background: rgba(201, 168, 76, 0.12);
+ border-radius: 2px;
+ margin: 0.35rem 0 0.1rem;
+ overflow: hidden;
+}
+
+.study-lesson-progress-fill {
+ height: 100%;
+ background: #c9a84c;
+ border-radius: 2px;
+ transition: width 0.4s ease;
+ min-width: 3px;
+}
+
+/* ── Sidebar lesson navigator ────────────────────────────────────── */
+.study-lesson-nav-progress {
+ font-size: 0.72rem;
+ font-weight: 400;
+ color: #5a5440;
+ margin-left: 0.4rem;
+ letter-spacing: 0.02em;
+}
+
+.study-lesson-nav {
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+ margin-top: 0.25rem;
+ max-height: 320px;
+ overflow-y: auto;
+}
+
+.study-lesson-nav-item {
+ display: flex;
+ align-items: center;
+ gap: 0.55rem;
+ padding: 0.42rem 0.55rem;
+ border-radius: 7px;
+ border: 1px solid transparent;
+ text-decoration: none;
+ transition: background 120ms, border-color 120ms;
+ cursor: pointer;
+}
+
+.study-lesson-nav-item:hover {
+ background: rgba(201, 168, 76, 0.06);
+}
+
+.study-lesson-nav-item--current {
+ background: rgba(201, 168, 76, 0.1);
+ border-color: rgba(201, 168, 76, 0.2);
+}
+
+.study-lesson-nav-item--locked {
+ opacity: 0.45;
+ cursor: default;
+ pointer-events: none;
+}
+
+.study-lesson-nav-icon {
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.72rem;
+ font-weight: 700;
+ flex-shrink: 0;
+ background: rgba(255, 255, 255, 0.05);
+ color: #5a5440;
+}
+
+.study-lesson-nav-item--done .study-lesson-nav-icon {
+ background: rgba(201, 168, 76, 0.18);
+ color: #c9a84c;
+}
+
+.study-lesson-nav-item--current .study-lesson-nav-icon {
+ background: #c9a84c;
+ color: #0e0d0b;
+}
+
+.study-lesson-nav-label {
+ font-size: 0.8rem;
+ font-weight: 500;
+ color: #7a7060;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 0;
+}
+
+.study-lesson-nav-item--current .study-lesson-nav-label {
+ color: #e2d4a8;
+}
+
+.study-lesson-nav-item--done .study-lesson-nav-label {
+ color: #5a5040;
+}
+
+.study-lesson-nav-actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 0.5rem;
+ margin-top: 0.25rem;
+}
+
+/* ── Notes count badge ────────────────────────────────────────────── */
+.study-notes-count-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 18px;
+ height: 18px;
+ border-radius: 9px;
+ background: rgba(201, 168, 76, 0.18);
+ color: #c9a84c;
+ font-size: 0.7rem;
+ font-weight: 600;
+ padding: 0 5px;
+ margin-left: 0.4rem;
+ font-family: var(--brand-font-body);
+ vertical-align: middle;
+}
+
+/* ── Inline reflection question answers ─────────────────────────── */
+.study-detail-list--questions {
+ list-style: none;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 1.25rem;
+ margin-top: 0.25rem;
+}
+
+.study-question-item {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.study-question-text {
+ color: var(--brand-muted);
+ font-size: 0.93rem;
+ line-height: 1.6;
+ margin: 0;
+}
+
+.study-question-answer {
+ width: 100%;
+ box-sizing: border-box;
+ background: rgba(201, 168, 76, 0.04);
+ border: 1px solid rgba(201, 168, 76, 0.15);
+ border-radius: 8px;
+ padding: 0.65rem 0.85rem;
+ font-size: 0.88rem;
+ font-family: var(--brand-font-body);
+ color: #f0ead8;
+ resize: vertical;
+ min-height: 72px;
+ line-height: 1.6;
+ transition: border-color 150ms;
+}
+
+.study-question-answer:focus {
+ outline: none;
+ border-color: rgba(201, 168, 76, 0.4);
+}
+
+.study-question-answer::placeholder {
+ color: #3a3020;
+}
+
/* ── Sidebar anchor list ─────────────────────────────────────────── */
.note-anchor-list {
display: flex;
diff --git a/src/colossiansStudy.tsx b/src/colossiansStudy.tsx
index 982f7e2..169ee6b 100644
--- a/src/colossiansStudy.tsx
+++ b/src/colossiansStudy.tsx
@@ -1507,6 +1507,8 @@ export function ColossiansStudySectionPage({ content }: Props) {
const [progressSaving, setProgressSaving] = useState(false)
const [progressMessage, setProgressMessage] = useState('')
const [checkpointAnswers, setCheckpointAnswers] = useState>({})
+ const [quizAnswers, setQuizAnswers] = useState([])
+ const quizAnswersDirtyRef = useRef(false)
const [showCelebration, setShowCelebration] = useState(false)
const [bannerDismissed, setBannerDismissed] = useState(() => localStorage.getItem('study-banner-dismissed-v1') === '1')
const [autoSaveMsg, setAutoSaveMsg] = useState('')
@@ -1692,6 +1694,44 @@ export function ColossiansStudySectionPage({ content }: Props) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [noteMap, noteId, auth.authenticated, noteLoading])
+ useEffect(() => {
+ if (!auth.authenticated || !isEnrolled || !study || !section?.id) {
+ setQuizAnswers([])
+ quizAnswersDirtyRef.current = false
+ return
+ }
+ let cancelled = false
+ readJson<{ answers: string[] }>(`/api/study-quiz/${encodeURIComponent(currentStudySlug)}/${encodeURIComponent(section.id)}`)
+ .then(data => {
+ if (cancelled) return
+ setQuizAnswers(Array.isArray(data.answers) ? data.answers : [])
+ quizAnswersDirtyRef.current = false
+ })
+ .catch(() => { if (!cancelled) setQuizAnswers([]) })
+ return () => { cancelled = true }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [auth.authenticated, isEnrolled, currentStudySlug, section?.id])
+
+ useEffect(() => {
+ if (!auth.authenticated || !isEnrolled || !study || !section?.id) return
+ if (!quizAnswersDirtyRef.current) return
+ const timer = setTimeout(async () => {
+ if (!quizAnswersDirtyRef.current) return
+ quizAnswersDirtyRef.current = false
+ try {
+ await readJson<{ ok: boolean; answers: string[] }>(`/api/study-quiz/${encodeURIComponent(currentStudySlug)}/${encodeURIComponent(section.id)}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ answers: quizAnswers }),
+ })
+ } catch {
+ quizAnswersDirtyRef.current = true
+ }
+ }, 1500)
+ return () => clearTimeout(timer)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [quizAnswers, auth.authenticated, isEnrolled, currentStudySlug, section?.id])
+
useEffect(() => {
if (!showCelebration) return
function onKeyDown(e: KeyboardEvent) {
@@ -1896,6 +1936,11 @@ export function ColossiansStudySectionPage({ content }: Props) {
Back to {study.title}
Lesson {lessonNumber} of {sections.length}
+ {sections.length > 0 && (
+
+ )}
{section.title}
{section.reference}
{section.summary}
@@ -1927,9 +1972,8 @@ export function ColossiansStudySectionPage({ content }: Props) {
type="button"
className={`note-anchor-btn${noteMap['scripture']?.trim() ? ' note-anchor-btn--has-note' : ''}${openAnchor === 'scripture' ? ' note-anchor-btn--open' : ''}`}
onClick={() => setOpenAnchor(curr => curr === 'scripture' ? null : 'scripture')}
- title={noteMap['scripture']?.trim() ? 'Edit scripture note' : 'Add note to Scripture'}
aria-pressed={openAnchor === 'scripture'}
- >✎
+ >{noteMap['scripture']?.trim() ? '✎ Note' : '+ Note'}
)}
Scripture Text
{bsbPassageLoading
@@ -1943,15 +1987,6 @@ export function ColossiansStudySectionPage({ content }: Props) {
: Passage text not available.
}
{bsbPassage && Berean Standard Bible (BSB)
}
- {isEnrolled && openAnchor === 'scripture' && (
- updateNote('scripture', html)}
- onClose={() => setOpenAnchor(null)}
- />
- )}
@@ -1960,9 +1995,8 @@ export function ColossiansStudySectionPage({ content }: Props) {
type="button"
className={`note-anchor-btn${noteMap['commentary']?.trim() ? ' note-anchor-btn--has-note' : ''}${openAnchor === 'commentary' ? ' note-anchor-btn--open' : ''}`}
onClick={() => setOpenAnchor(curr => curr === 'commentary' ? null : 'commentary')}
- title={noteMap['commentary']?.trim() ? 'Edit commentary note' : 'Add note to Commentary'}
aria-pressed={openAnchor === 'commentary'}
- >✎
+ >{noteMap['commentary']?.trim() ? '✎ Note' : '+ Note'}
)}
Instructor Commentary
{section.commentary.split(/\n{2,}/).map((para, i) => (
@@ -1972,15 +2006,6 @@ export function ColossiansStudySectionPage({ content }: Props) {
))}
))}
- {isEnrolled && openAnchor === 'commentary' && (
- updateNote('commentary', html)}
- onClose={() => setOpenAnchor(null)}
- />
- )}
@@ -1989,33 +2014,35 @@ export function ColossiansStudySectionPage({ content }: Props) {
type="button"
className={`note-anchor-btn${noteMap['questions']?.trim() ? ' note-anchor-btn--has-note' : ''}${openAnchor === 'questions' ? ' note-anchor-btn--open' : ''}`}
onClick={() => setOpenAnchor(curr => curr === 'questions' ? null : 'questions')}
- title={noteMap['questions']?.trim() ? 'Edit reflection note' : 'Add note to Reflection Questions'}
aria-pressed={openAnchor === 'questions'}
- >✎
+ >{noteMap['questions']?.trim() ? '✎ Note' : '+ Note'}
)}
Reflection Questions
-
- Work through these on your own, then share your thoughts in the Discussion below.
-
-
+
{section.studyQuestions.map((question, index) => (
- - {question}
+ -
+
{question}
+ {isEnrolled && (
+
))}
- Write My Answers
Go to Community
Ask Nate
- {isEnrolled && openAnchor === 'questions' && (
- updateNote('questions', html)}
- onClose={() => setOpenAnchor(null)}
- />
- )}
{isEnrolled && !(section.checkpointQuestions && section.checkpointQuestions.length > 0) && (
@@ -2133,28 +2160,40 @@ export function ColossiansStudySectionPage({ content }: Props) {
{contextNoteMenu && (