coursecode 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +1 -0
- package/framework/css/02-layout.css +8 -1
- package/framework/css/components/audio-player.css +4 -3
- package/framework/css/components/buttons.css +7 -6
- package/framework/css/components/footer.css +5 -2
- package/framework/css/components/hero.css +31 -4
- package/framework/css/framework.css +2 -1
- package/framework/css/layouts/article.css +1 -1
- package/framework/css/responsive-structure.css +210 -0
- package/framework/css/responsive.css +15 -170
- package/framework/docs/COURSE_AUTHORING_GUIDE.md +8 -15
- package/framework/docs/DATA_MODEL.md +1 -1
- package/framework/docs/FRAMEWORK_GUIDE.md +31 -10
- package/framework/docs/USER_GUIDE.md +3 -1
- package/framework/js/utilities/icons.js +4 -2
- package/lib/cloud.js +4 -0
- package/lib/headless-browser.js +1 -1
- package/lib/mcp-prompts.js +22 -22
- package/lib/mcp-server.js +1 -1
- package/lib/preview-routes-api.js +59 -1
- package/lib/stub-player/outline-mode.js +187 -13
- package/lib/stub-player/styles/_header-bar.css +18 -0
- package/lib/stub-player/styles/_outline-mode.css +257 -20
- package/package.json +10 -1
- package/template/course/slides/example-course-structure.js +1 -1
- package/template/course/slides/example-finishing.js +4 -4
- package/template/course/slides/example-preview-tour.js +1 -1
- package/template/course/slides/example-ui-showcase.js +1 -1
- package/template/course/slides/example-welcome.js +4 -4
- package/template/course/slides/example-workflow.js +1 -1
package/bin/cli.js
CHANGED
|
@@ -322,6 +322,7 @@ program
|
|
|
322
322
|
.description('Build and deploy course to CourseCode Cloud')
|
|
323
323
|
.option('--preview', 'Deploy as preview (expires in 7 days)')
|
|
324
324
|
.option('--password', 'Password-protect preview (interactive prompt)')
|
|
325
|
+
.option('-m, --message <message>', 'Deploy reason (e.g. "Fixed accessibility issues")')
|
|
325
326
|
.option('--local', 'Use local Cloud instance (http://localhost:3000)')
|
|
326
327
|
.action(async (options) => {
|
|
327
328
|
const { deploy, setLocalMode } = await import('../lib/cloud.js');
|
|
@@ -298,7 +298,14 @@ main#content {
|
|
|
298
298
|
position: relative;
|
|
299
299
|
flex: 1;
|
|
300
300
|
overflow-y: auto;
|
|
301
|
-
overflow-x:
|
|
301
|
+
overflow-x: clip;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* Remove top padding when a flush-top hero is present (the hero wants to
|
|
305
|
+
sit directly against the header). Negative-margin tricks can't escape
|
|
306
|
+
the intermediate flex containers, so we remove padding at the source. */
|
|
307
|
+
main#content:has([data-component="hero"].hero-flush-top) {
|
|
308
|
+
padding-top: 0;
|
|
302
309
|
}
|
|
303
310
|
|
|
304
311
|
/* Content Sections - Clean Card Design with Animation */
|
|
@@ -545,9 +545,10 @@ html.bp-max-mobile-landscape[data-layout="article"] #audio-player .audio-player-
|
|
|
545
545
|
}
|
|
546
546
|
|
|
547
547
|
html.bp-max-mobile-landscape[data-layout="article"] #audio-player .audio-btn {
|
|
548
|
-
width:
|
|
549
|
-
height:
|
|
550
|
-
min-height:
|
|
548
|
+
width: 44px;
|
|
549
|
+
height: 44px;
|
|
550
|
+
min-height: 44px;
|
|
551
|
+
touch-action: manipulation;
|
|
551
552
|
}
|
|
552
553
|
|
|
553
554
|
html.bp-max-mobile-landscape[data-layout="article"] #audio-player .audio-progress-container {
|
|
@@ -610,19 +610,20 @@ html:not([data-layout="article"]):not([data-layout="focused"]):not([data-layout=
|
|
|
610
610
|
|
|
611
611
|
html.bp-max-mobile-landscape[data-layout="article"] footer #prevBtn,
|
|
612
612
|
html.bp-max-mobile-landscape[data-layout="article"] footer #nextBtn {
|
|
613
|
-
width:
|
|
614
|
-
max-width:
|
|
615
|
-
height:
|
|
616
|
-
min-height:
|
|
613
|
+
width: 44px;
|
|
614
|
+
max-width: 44px;
|
|
615
|
+
height: 44px;
|
|
616
|
+
min-height: 44px;
|
|
617
617
|
padding: 0;
|
|
618
618
|
overflow: hidden;
|
|
619
|
+
touch-action: manipulation;
|
|
619
620
|
}
|
|
620
621
|
|
|
621
622
|
html.bp-max-mobile-landscape[data-layout="article"] footer #prevBtn>svg,
|
|
622
623
|
html.bp-max-mobile-landscape[data-layout="article"] footer #nextBtn>svg {
|
|
623
624
|
display: block;
|
|
624
|
-
width:
|
|
625
|
-
height:
|
|
625
|
+
width: 20px;
|
|
626
|
+
height: 20px;
|
|
626
627
|
margin: 0;
|
|
627
628
|
}
|
|
628
629
|
|
|
@@ -269,7 +269,10 @@ html.bp-max-mobile-landscape[data-layout="article"] footer {
|
|
|
269
269
|
max-width: 100vw;
|
|
270
270
|
box-sizing: border-box;
|
|
271
271
|
border-radius: 0;
|
|
272
|
-
padding: var(--space-2)
|
|
272
|
+
padding-top: var(--space-2);
|
|
273
|
+
padding-right: calc(var(--space-3) + env(safe-area-inset-right, 0px));
|
|
274
|
+
padding-bottom: calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
|
|
275
|
+
padding-left: calc(var(--space-3) + env(safe-area-inset-left, 0px));
|
|
273
276
|
display: flex;
|
|
274
277
|
flex-direction: row;
|
|
275
278
|
align-items: center;
|
|
@@ -289,7 +292,7 @@ html.bp-max-mobile-landscape[data-layout="article"] .nav-controls {
|
|
|
289
292
|
html.bp-max-mobile-landscape[data-layout="article"] .nav-nav-buttons {
|
|
290
293
|
order: 2;
|
|
291
294
|
margin-left: auto;
|
|
292
|
-
margin-right:
|
|
295
|
+
margin-right: 0;
|
|
293
296
|
display: flex;
|
|
294
297
|
flex-direction: row;
|
|
295
298
|
gap: var(--space-2);
|
|
@@ -43,8 +43,10 @@
|
|
|
43
43
|
|
|
44
44
|
/* Hero content container */
|
|
45
45
|
[data-component="hero"] .hero-content {
|
|
46
|
+
width: 100%;
|
|
46
47
|
max-width: 800px;
|
|
47
48
|
margin: 0 auto;
|
|
49
|
+
box-sizing: border-box;
|
|
48
50
|
position: relative;
|
|
49
51
|
z-index: 2;
|
|
50
52
|
}
|
|
@@ -108,6 +110,28 @@
|
|
|
108
110
|
border-color: transparent;
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
/* Hero badge color variants - solid backgrounds */
|
|
114
|
+
.hero-badge.hero-badge-primary,
|
|
115
|
+
[data-component="hero"] .hero-badge.hero-badge-primary {
|
|
116
|
+
background: var(--color-primary);
|
|
117
|
+
color: var(--color-white);
|
|
118
|
+
border-color: transparent;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.hero-badge.hero-badge-secondary,
|
|
122
|
+
[data-component="hero"] .hero-badge.hero-badge-secondary {
|
|
123
|
+
background: var(--color-secondary);
|
|
124
|
+
color: var(--color-gray-900);
|
|
125
|
+
border-color: transparent;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.hero-badge.hero-badge-accent,
|
|
129
|
+
[data-component="hero"] .hero-badge.hero-badge-accent {
|
|
130
|
+
background: var(--color-accent);
|
|
131
|
+
color: var(--color-gray-900);
|
|
132
|
+
border-color: transparent;
|
|
133
|
+
}
|
|
134
|
+
|
|
111
135
|
[data-component="hero"].hero-overlay .hero-badge,
|
|
112
136
|
[data-component="hero"].hero-dark .hero-badge {
|
|
113
137
|
background: var(--color-white-alpha-25);
|
|
@@ -139,14 +163,17 @@
|
|
|
139
163
|
|
|
140
164
|
/* Full bleed - breaks out of container to span full viewport width */
|
|
141
165
|
[data-component="hero"].hero-full-bleed {
|
|
142
|
-
width:
|
|
166
|
+
width: auto;
|
|
143
167
|
margin-left: calc(50% - 50vw);
|
|
144
|
-
|
|
168
|
+
margin-right: calc(50% - 50vw);
|
|
169
|
+
border-radius: 0;
|
|
145
170
|
}
|
|
146
171
|
|
|
147
|
-
/* Flush top -
|
|
172
|
+
/* Flush top - hero sits directly against the header.
|
|
173
|
+
Padding removal is handled by main#content:has() in 02-layout.css.
|
|
174
|
+
This class is retained as the semantic marker that triggers the :has() rule. */
|
|
148
175
|
[data-component="hero"].hero-flush-top {
|
|
149
|
-
margin-top:
|
|
176
|
+
margin-top: 0;
|
|
150
177
|
}
|
|
151
178
|
|
|
152
179
|
/* Split hero - Image on one side, content on other */
|
|
@@ -254,5 +254,5 @@ html.bp-max-mobile-landscape[data-layout="article"] .sidebar {
|
|
|
254
254
|
|
|
255
255
|
/* Add bottom padding to content */
|
|
256
256
|
html.bp-max-mobile-landscape[data-layout="article"] main#content {
|
|
257
|
-
padding-bottom: calc(var(--space-8) +
|
|
257
|
+
padding-bottom: calc(var(--space-8) + 68px + env(safe-area-inset-bottom, 0px));
|
|
258
258
|
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
RESPONSIVE STRUCTURE - App shell, header chrome, footer/nav/audio chrome
|
|
3
|
+
============================================================================
|
|
4
|
+
|
|
5
|
+
PURPOSE:
|
|
6
|
+
- Holds responsive behavior for framework chrome and shell structure.
|
|
7
|
+
- Keeps `responsive.css` focused on shared content/component responsiveness.
|
|
8
|
+
|
|
9
|
+
IMPORTANT:
|
|
10
|
+
- If a layout (article/focused/presentation/traditional) owns a responsive
|
|
11
|
+
behavior in layout/component DATA-LAYOUT rules, generic structure selectors
|
|
12
|
+
here must exclude that layout to avoid cascade conflicts.
|
|
13
|
+
============================================================================ */
|
|
14
|
+
|
|
15
|
+
/* ============================================================================
|
|
16
|
+
LARGE DESKTOP (>= 1440px)
|
|
17
|
+
============================================================================ */
|
|
18
|
+
|
|
19
|
+
html.bp-min-large-desktop #app {
|
|
20
|
+
max-width: 1440px;
|
|
21
|
+
height: 100%;
|
|
22
|
+
margin: 0 auto;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* ============================================================================
|
|
26
|
+
DESKTOP (<= 1439px)
|
|
27
|
+
============================================================================ */
|
|
28
|
+
|
|
29
|
+
html.bp-max-desktop #app {
|
|
30
|
+
margin: 0;
|
|
31
|
+
max-width: none;
|
|
32
|
+
height: 100%;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* ============================================================================
|
|
36
|
+
TABLET PORTRAIT (<= 1023px)
|
|
37
|
+
============================================================================ */
|
|
38
|
+
|
|
39
|
+
html.bp-max-tablet-portrait #app {
|
|
40
|
+
margin: 0;
|
|
41
|
+
border-radius: 0;
|
|
42
|
+
height: 100%;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Header at tablet portrait: tighter spacing but keep row layout */
|
|
46
|
+
html.bp-max-tablet-portrait header {
|
|
47
|
+
gap: var(--space-2);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
html.bp-max-tablet-portrait #app > header h1 {
|
|
51
|
+
font-size: var(--font-size-lg);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
html.bp-max-tablet-portrait #accessibility-menu {
|
|
55
|
+
right: var(--space-4);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Footer at tablet portrait: tighter gaps but keep row layout
|
|
59
|
+
Exclude article/focused - these layouts own footer behavior. */
|
|
60
|
+
html.bp-max-tablet-portrait:not([data-layout="article"]):not([data-layout="focused"]) .nav-controls {
|
|
61
|
+
gap: var(--space-2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ============================================================================
|
|
65
|
+
MOBILE LANDSCAPE (<= 767px)
|
|
66
|
+
============================================================================ */
|
|
67
|
+
|
|
68
|
+
html.bp-max-mobile-landscape #app {
|
|
69
|
+
margin: 0;
|
|
70
|
+
border-radius: 0;
|
|
71
|
+
height: 100%;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Mobile header: compact single-row layout */
|
|
75
|
+
html.bp-max-mobile-landscape header {
|
|
76
|
+
flex-wrap: nowrap;
|
|
77
|
+
justify-content: flex-start;
|
|
78
|
+
gap: var(--space-2);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
html.bp-max-mobile-landscape #brand {
|
|
82
|
+
flex: 1 1 auto;
|
|
83
|
+
justify-content: flex-start;
|
|
84
|
+
min-width: 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
html.bp-max-mobile-landscape #brand img.logo {
|
|
88
|
+
height: 36px;
|
|
89
|
+
padding: var(--space-1);
|
|
90
|
+
padding-left: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
html.bp-max-mobile-landscape #brand .brand-title {
|
|
94
|
+
font-size: var(--font-size-base);
|
|
95
|
+
white-space: nowrap;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
text-overflow: ellipsis;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
html.bp-max-mobile-landscape #app > header h1 {
|
|
101
|
+
font-size: var(--font-size-base);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Hide header progress and breadcrumbs on mobile to save space */
|
|
105
|
+
html.bp-max-mobile-landscape .header-progress {
|
|
106
|
+
display: none !important;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
html.bp-max-mobile-landscape .breadcrumbs {
|
|
110
|
+
display: none !important;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Generic mobile footer/nav/audio structure (traditional/default-like layouts).
|
|
114
|
+
Exclude article/focused - they define mobile footer/nav/audio in DATA-LAYOUT rules. */
|
|
115
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .nav-controls {
|
|
116
|
+
flex-direction: row;
|
|
117
|
+
flex-wrap: nowrap;
|
|
118
|
+
gap: var(--space-2);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Mobile footer: Previous/Next fill the row, Exit hidden (moved to sidebar) */
|
|
122
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .nav-nav-buttons {
|
|
123
|
+
display: flex;
|
|
124
|
+
gap: var(--space-2);
|
|
125
|
+
flex: 1;
|
|
126
|
+
width: auto;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .nav-nav-buttons button {
|
|
130
|
+
flex: 1;
|
|
131
|
+
width: auto;
|
|
132
|
+
padding: var(--space-2) var(--space-3);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .nav-exit-button {
|
|
136
|
+
display: none;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .app-footer button {
|
|
140
|
+
width: auto;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Mobile footer: stacked layout with audio on top */
|
|
144
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .app-footer {
|
|
145
|
+
flex-direction: column;
|
|
146
|
+
gap: var(--space-2);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .nav-controls {
|
|
150
|
+
width: 100%;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
gap: var(--space-2);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Audio player: full width, above nav buttons, compact variant */
|
|
156
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) #audio-player {
|
|
157
|
+
width: 100%;
|
|
158
|
+
order: -1;
|
|
159
|
+
margin-left: 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .audio-player-controls {
|
|
163
|
+
width: 100%;
|
|
164
|
+
justify-content: center;
|
|
165
|
+
gap: var(--space-1);
|
|
166
|
+
background: var(--bg-surface);
|
|
167
|
+
border: var(--border-width-md) solid var(--border-default);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Hide progress bar and time on mobile (compact variant) */
|
|
171
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .audio-progress-container,
|
|
172
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .audio-time {
|
|
173
|
+
display: none;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* Nav buttons: row layout filling width */
|
|
177
|
+
html.bp-max-mobile-landscape:not([data-layout="article"]):not([data-layout="focused"]) .nav-nav-buttons {
|
|
178
|
+
width: 100%;
|
|
179
|
+
justify-content: center;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ============================================================================
|
|
183
|
+
MOBILE PORTRAIT (<= 479px)
|
|
184
|
+
============================================================================ */
|
|
185
|
+
|
|
186
|
+
html.bp-max-mobile-portrait #app {
|
|
187
|
+
margin: 0;
|
|
188
|
+
border-radius: 0;
|
|
189
|
+
height: 100%;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/* Mobile portrait: even tighter header */
|
|
193
|
+
html.bp-max-mobile-portrait #brand img.logo {
|
|
194
|
+
height: 32px;
|
|
195
|
+
padding-left: 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
html.bp-max-mobile-portrait #brand .brand-title {
|
|
199
|
+
font-size: var(--font-size-sm);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
html.bp-max-mobile-portrait #app > header h1 {
|
|
203
|
+
font-size: var(--font-size-sm);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Exclude article/focused - layout-specific button sizing takes precedence */
|
|
207
|
+
html.bp-max-mobile-portrait:not([data-layout="article"]):not([data-layout="focused"]) .app-footer button {
|
|
208
|
+
padding: var(--space-3) var(--space-4);
|
|
209
|
+
font-size: var(--font-size-sm);
|
|
210
|
+
}
|
|
@@ -57,6 +57,14 @@
|
|
|
57
57
|
html.bp-max-tablet-portrait - Tablets and below (portrait)
|
|
58
58
|
html.bp-max-mobile-landscape - Mobile devices (landscape)
|
|
59
59
|
html.bp-max-mobile-portrait - Mobile devices (portrait)
|
|
60
|
+
|
|
61
|
+
MAINTAINER NOTE (layout-owned responsive behavior):
|
|
62
|
+
- Keep generic responsive.css rules focused on shared component/content patterns.
|
|
63
|
+
- When a layout (article/focused/presentation/traditional) has its own responsive
|
|
64
|
+
rules in framework/css/layouts/* or component DATA-LAYOUT sections, scope
|
|
65
|
+
generic structural rules to exclude that layout to avoid cascade conflicts.
|
|
66
|
+
- High-risk generic selectors: .app-footer, .nav-controls, .nav-nav-buttons,
|
|
67
|
+
.nav-exit-button, #audio-player, .audio-player-controls.
|
|
60
68
|
|
|
61
69
|
LAST UPDATED: 2024-12-10
|
|
62
70
|
============================================================================ */
|
|
@@ -75,11 +83,7 @@ html.bp-min-large-desktop {
|
|
|
75
83
|
--footer-padding-y: var(--space-3);
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
max-width: 1440px;
|
|
80
|
-
height: 100%;
|
|
81
|
-
margin: 0 auto;
|
|
82
|
-
}
|
|
86
|
+
/* App shell sizing moved to responsive-structure.css */
|
|
83
87
|
|
|
84
88
|
/* ============================================================================
|
|
85
89
|
DESKTOP (<= 1439px)
|
|
@@ -95,11 +99,7 @@ html.bp-max-desktop {
|
|
|
95
99
|
--footer-padding-y: var(--space-5);
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
margin: 0;
|
|
100
|
-
max-width: none;
|
|
101
|
-
height: 100%;
|
|
102
|
-
}
|
|
102
|
+
/* App shell sizing moved to responsive-structure.css */
|
|
103
103
|
|
|
104
104
|
/* ============================================================================
|
|
105
105
|
TABLET LANDSCAPE (<= 1199px)
|
|
@@ -135,29 +135,7 @@ html.bp-max-tablet-portrait {
|
|
|
135
135
|
--nav-padding-y: var(--space-2);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
margin: 0;
|
|
140
|
-
border-radius: 0;
|
|
141
|
-
height: 100%;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/* Header at tablet portrait: tighter spacing but keep row layout */
|
|
145
|
-
html.bp-max-tablet-portrait header {
|
|
146
|
-
gap: var(--space-2);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
html.bp-max-tablet-portrait #app>header h1 {
|
|
150
|
-
font-size: var(--font-size-lg);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
html.bp-max-tablet-portrait #accessibility-menu {
|
|
154
|
-
right: var(--space-4);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/* Footer at tablet portrait: tighter gaps but keep row layout */
|
|
158
|
-
html.bp-max-tablet-portrait .nav-controls {
|
|
159
|
-
gap: var(--space-2);
|
|
160
|
-
}
|
|
138
|
+
/* Tablet portrait shell/header/footer chrome responsive rules moved to responsive-structure.css */
|
|
161
139
|
|
|
162
140
|
html.bp-max-tablet-portrait .stats-grid,
|
|
163
141
|
html.bp-max-tablet-portrait .features-grid {
|
|
@@ -210,51 +188,7 @@ html.bp-max-mobile-landscape {
|
|
|
210
188
|
--nav-padding-y: var(--space-2);
|
|
211
189
|
}
|
|
212
190
|
|
|
213
|
-
|
|
214
|
-
margin: 0;
|
|
215
|
-
border-radius: 0;
|
|
216
|
-
height: 100%;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/* Mobile header: compact single-row layout */
|
|
220
|
-
html.bp-max-mobile-landscape header {
|
|
221
|
-
flex-wrap: nowrap;
|
|
222
|
-
justify-content: flex-start;
|
|
223
|
-
gap: var(--space-2);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
html.bp-max-mobile-landscape #brand {
|
|
227
|
-
flex: 1 1 auto;
|
|
228
|
-
justify-content: flex-start;
|
|
229
|
-
min-width: 0;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
html.bp-max-mobile-landscape #brand img.logo {
|
|
233
|
-
height: 36px;
|
|
234
|
-
padding: var(--space-1);
|
|
235
|
-
padding-left: 0;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
html.bp-max-mobile-landscape #brand .brand-title {
|
|
239
|
-
font-size: var(--font-size-base);
|
|
240
|
-
white-space: nowrap;
|
|
241
|
-
overflow: hidden;
|
|
242
|
-
text-overflow: ellipsis;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
html.bp-max-mobile-landscape #app>header h1 {
|
|
246
|
-
font-size: var(--font-size-base);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/* Hide header progress on mobile to save space */
|
|
250
|
-
html.bp-max-mobile-landscape .header-progress {
|
|
251
|
-
display: none !important;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/* Hide breadcrumbs on mobile */
|
|
255
|
-
html.bp-max-mobile-landscape .breadcrumbs {
|
|
256
|
-
display: none !important;
|
|
257
|
-
}
|
|
191
|
+
/* Mobile shell/header/footer/audio chrome responsive rules moved to responsive-structure.css */
|
|
258
192
|
|
|
259
193
|
/* Tabs: stack vertically on mobile */
|
|
260
194
|
html.bp-max-mobile-landscape .content-tabs,
|
|
@@ -297,75 +231,7 @@ html.bp-max-mobile-landscape .assessment-nav-center {
|
|
|
297
231
|
margin-bottom: var(--space-2);
|
|
298
232
|
}
|
|
299
233
|
|
|
300
|
-
|
|
301
|
-
flex-direction: row;
|
|
302
|
-
flex-wrap: nowrap;
|
|
303
|
-
gap: var(--space-2);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/* Mobile footer: Previous/Next fill the row, Exit hidden (moved to sidebar) */
|
|
307
|
-
html.bp-max-mobile-landscape .nav-nav-buttons {
|
|
308
|
-
display: flex;
|
|
309
|
-
gap: var(--space-2);
|
|
310
|
-
flex: 1;
|
|
311
|
-
width: auto;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
html.bp-max-mobile-landscape .nav-nav-buttons button {
|
|
315
|
-
flex: 1;
|
|
316
|
-
width: auto;
|
|
317
|
-
padding: var(--space-2) var(--space-3);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
html.bp-max-mobile-landscape .nav-exit-button {
|
|
321
|
-
display: none;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
html.bp-max-mobile-landscape .app-footer button {
|
|
327
|
-
width: auto;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/* Mobile footer: stacked layout with audio on top */
|
|
331
|
-
html.bp-max-mobile-landscape .app-footer {
|
|
332
|
-
flex-direction: column;
|
|
333
|
-
gap: var(--space-2);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
html.bp-max-mobile-landscape .nav-controls {
|
|
337
|
-
width: 100%;
|
|
338
|
-
flex-direction: column;
|
|
339
|
-
gap: var(--space-2);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/* Audio player: full width, above nav buttons, compact variant */
|
|
343
|
-
html.bp-max-mobile-landscape #audio-player {
|
|
344
|
-
width: 100%;
|
|
345
|
-
order: -1;
|
|
346
|
-
margin-left: 0;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
html.bp-max-mobile-landscape .audio-player-controls {
|
|
350
|
-
width: 100%;
|
|
351
|
-
justify-content: center;
|
|
352
|
-
/* Apply compact variant styles on mobile */
|
|
353
|
-
gap: var(--space-1);
|
|
354
|
-
background: var(--bg-surface);
|
|
355
|
-
border: var(--border-width-md) solid var(--border-default);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/* Hide progress bar and time on mobile (compact variant) */
|
|
359
|
-
html.bp-max-mobile-landscape .audio-progress-container,
|
|
360
|
-
html.bp-max-mobile-landscape .audio-time {
|
|
361
|
-
display: none;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/* Nav buttons: row layout filling width */
|
|
365
|
-
html.bp-max-mobile-landscape .nav-nav-buttons {
|
|
366
|
-
width: 100%;
|
|
367
|
-
justify-content: center;
|
|
368
|
-
}
|
|
234
|
+
/* Generic mobile footer/nav/audio structure moved to responsive-structure.css */
|
|
369
235
|
|
|
370
236
|
html.bp-max-mobile-landscape .review-actions {
|
|
371
237
|
flex-direction: column;
|
|
@@ -394,25 +260,7 @@ html.bp-max-mobile-portrait {
|
|
|
394
260
|
--nav-padding-y: var(--space-2);
|
|
395
261
|
}
|
|
396
262
|
|
|
397
|
-
|
|
398
|
-
margin: 0;
|
|
399
|
-
border-radius: 0;
|
|
400
|
-
height: 100%;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/* Mobile portrait: even tighter header */
|
|
404
|
-
html.bp-max-mobile-portrait #brand img.logo {
|
|
405
|
-
height: 32px;
|
|
406
|
-
padding-left: 0;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
html.bp-max-mobile-portrait #brand .brand-title {
|
|
410
|
-
font-size: var(--font-size-sm);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
html.bp-max-mobile-portrait #app>header h1 {
|
|
414
|
-
font-size: var(--font-size-sm);
|
|
415
|
-
}
|
|
263
|
+
/* Mobile portrait shell/header/footer chrome responsive rules moved to responsive-structure.css */
|
|
416
264
|
|
|
417
265
|
html.bp-max-mobile-portrait main#content>section {
|
|
418
266
|
margin-bottom: var(--space-6);
|
|
@@ -428,10 +276,7 @@ html.bp-max-mobile-portrait .interaction {
|
|
|
428
276
|
padding: var(--space-5);
|
|
429
277
|
}
|
|
430
278
|
|
|
431
|
-
|
|
432
|
-
padding: var(--space-3) var(--space-4);
|
|
433
|
-
font-size: var(--font-size-sm);
|
|
434
|
-
}
|
|
279
|
+
/* Mobile portrait generic footer button sizing moved to responsive-structure.css */
|
|
435
280
|
|
|
436
281
|
/* Keep traditional footer compact across breakpoints */
|
|
437
282
|
html[data-layout="traditional"] {
|
|
@@ -86,7 +86,6 @@ my-course/
|
|
|
86
86
|
|
|
87
87
|
**Progressive enhancement:** After import, AI agents or authors can replace image slides with interactive HTML, add assessments, or add engagement tracking.
|
|
88
88
|
|
|
89
|
-
|
|
90
89
|
## CLI Preview
|
|
91
90
|
|
|
92
91
|
Test your course with a stub LMS wrapper.
|
|
@@ -182,7 +181,6 @@ coursecode narration --force # Regenerate all (ignore cache)
|
|
|
182
181
|
coursecode narration --dry-run # Preview without generating
|
|
183
182
|
```
|
|
184
183
|
|
|
185
|
-
|
|
186
184
|
## CLI Export Content
|
|
187
185
|
|
|
188
186
|
Extracts reviewable text content from SCORM course source files into structured Markdown.
|
|
@@ -320,17 +318,17 @@ coursecode build # Creates dist/ + *_<client>_proxy.zip per client
|
|
|
320
318
|
|
|
321
319
|
## Data Persistence
|
|
322
320
|
|
|
323
|
-
|
|
324
|
-
**Never call SCORM API directly** - use these managers:
|
|
321
|
+
**Never call SCORM API directly** - use the framework APIs exposed on `CourseCode`:
|
|
325
322
|
|
|
326
323
|
| Manager | Purpose | Key Methods |
|
|
327
324
|
|---------|---------|-------------|
|
|
328
|
-
| `
|
|
329
|
-
| `
|
|
330
|
-
| `
|
|
331
|
-
| `
|
|
332
|
-
| `
|
|
333
|
-
|
|
325
|
+
| `stateManager` | LMS + persistent course state (sole gateway) | `.getDomainState(domain)`, `.setDomainState(domain, value)`, `.reportScore(...)`, `.reportCompletion(...)`, `.setBookmark(...)` |
|
|
326
|
+
| `objectiveManager` | Track learning objectives | Auto: built-in criteria + assessment-linked<br>Manual: `.setCompletionStatus(id, status)`, `.setSuccessStatus(id, status)`, `.setScore(id, score)` |
|
|
327
|
+
| `interactionManager` | Record/report learner interactions | Usually auto-logged by built-in interactions |
|
|
328
|
+
| `flagManager` | Boolean/primitive flags in suspend data | `.setFlag(name, value)`, `.getFlag(name)`, `.removeFlag(name)` |
|
|
329
|
+
| `scoreManager` | Calculate/report course scores | Usually configured in `course-config.js` (auto) |
|
|
330
|
+
|
|
331
|
+
**Note:** Engagement tracking is normally configured in `course-config.js` (`slide.engagement`) and handled automatically by built-in components. Authors typically do not call an `engagementManager` directly.
|
|
334
332
|
|
|
335
333
|
### Objectives (in `course-config.js`)
|
|
336
334
|
|
|
@@ -1455,8 +1453,6 @@ parent.postMessage({ type: 'coursecode:resize', height: 400 }, '*');
|
|
|
1455
1453
|
```
|
|
1456
1454
|
|
|
1457
1455
|
**Engagement tracking:** Use `flag` requirement with the key set by the embedded widget.
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
1456
|
### Dropdown
|
|
1461
1457
|
|
|
1462
1458
|
```html
|
|
@@ -1511,9 +1507,6 @@ Hotspots open modals by default. Link to accordion with `data-accordion-id="acco
|
|
|
1511
1507
|
</div>
|
|
1512
1508
|
</div>
|
|
1513
1509
|
```
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
1510
|
### Value Display (Reactive Text)
|
|
1518
1511
|
|
|
1519
1512
|
```html
|
|
@@ -118,7 +118,7 @@ Slide visit tracking. Managed by [NavigationState.js](../js/navigation/Navigatio
|
|
|
118
118
|
|
|
119
119
|
### `engagement`
|
|
120
120
|
|
|
121
|
-
Engagement tracking for navigation gating. Managed by [engagement-manager.js](../js/
|
|
121
|
+
Engagement tracking for navigation gating. Managed by [engagement-manager.js](../js/engagement/engagement-manager.js).
|
|
122
122
|
|
|
123
123
|
```js
|
|
124
124
|
{
|