coursecode 0.1.6 → 0.1.8
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/README.md +17 -11
- 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/interactions/interactive-image.css +2 -2
- 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 +12 -15
- package/framework/docs/DATA_MODEL.md +1 -1
- package/framework/docs/FRAMEWORK_GUIDE.md +31 -10
- package/framework/docs/USER_GUIDE.md +29 -1
- package/framework/docs/examples/cloudflare-channel-relay.js +3 -0
- package/framework/docs/examples/cloudflare-data-worker.js +3 -0
- package/framework/docs/examples/cloudflare-error-worker.js +3 -0
- package/framework/js/utilities/icons.js +4 -2
- package/lib/cloud.js +5 -1
- 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/_base.css +1 -1
- 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/course-config.js +2 -0
- 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/README.md
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# CourseCode
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Open-source, local-first course authoring with AI help — no coding required to start, full code control when you need it.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
CourseCode creates real project files you can inspect, version, and edit directly — with a predictable, file-based workflow instead of a black-box GUI.
|
|
6
|
+
|
|
7
|
+
Bring your own PDFs, Word docs, or PowerPoints, use AI to accelerate authoring, and deploy to any LMS format without vendor lock-in or subscriptions.
|
|
6
8
|
|
|
7
9
|
## Start Here
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
Start with the workflow that fits you:
|
|
10
12
|
|
|
11
|
-
- **Course authors (
|
|
12
|
-
- **Course authors (
|
|
13
|
+
- **Course authors (prefer buttons and guided setup):** Start with **[CourseCode Desktop](https://coursecodedesktop.com)** if you want the easiest path and do not want to deal with Node.js or terminal setup
|
|
14
|
+
- **Course authors (prefer editing files and running commands):** Start with the **[User Guide](framework/docs/USER_GUIDE.md)** in this repo for the framework workflow
|
|
13
15
|
- **AI assistants:** Use the AI-focused docs in `framework/docs/` (for example `COURSE_AUTHORING_GUIDE.md` and `FRAMEWORK_GUIDE.md`)
|
|
14
16
|
|
|
15
17
|
## Features
|
|
16
18
|
|
|
17
19
|
- **MCP integration**: AI connects directly to your course — previews, screenshots, linting, and testing without manual file sharing
|
|
18
|
-
- **No
|
|
20
|
+
- **No coding required to start**: Describe what you want and let AI help build slides, interactions, and structure
|
|
19
21
|
- **Full LMS integration**: SCORM 1.2, SCORM 2004, cmi5, and LTI with complete tracking records
|
|
20
22
|
- **AI-assisted authoring workflow**: Structured guides and MCP tools for faster course development
|
|
21
23
|
- **Rich UI components**: Images, video, accordions, tabs, and custom sandboxed HTML/JS embeds
|
|
@@ -26,13 +28,17 @@ If you're creating courses (non-technical or technical), use the **[User Guide](
|
|
|
26
28
|
- **Themeable design**: CSS custom properties for easy brand customization
|
|
27
29
|
- **Custom endpoints**: Optional webhooks for error reporting and learning record storage
|
|
28
30
|
- **Live preview**: Visual editing, status dashboard, config panels, catalog browser, and full LMS simulation with debug tools
|
|
29
|
-
- **[CourseCode Cloud](https://
|
|
30
|
-
- **CourseCode Desktop**: Native app for Mac and Windows with AI-assisted editing and built-in preview
|
|
31
|
+
- **[CourseCode Cloud](https://coursecodecloud.com)**: Deploy from CLI, share preview links, download any LMS format on demand — no rebuilds
|
|
32
|
+
- **[CourseCode Desktop](https://coursecodedesktop.com)**: Native app for Mac and Windows with AI-assisted editing and built-in preview
|
|
31
33
|
|
|
32
34
|
---
|
|
33
35
|
|
|
34
36
|
## Installation
|
|
35
37
|
|
|
38
|
+
This section is for the **CourseCode Framework CLI** (file-based workflow using Node.js and terminal commands).
|
|
39
|
+
|
|
40
|
+
If you want the easiest setup with buttons and guided steps, use **[CourseCode Desktop](https://coursecodedesktop.com)** instead.
|
|
41
|
+
|
|
36
42
|
### Required
|
|
37
43
|
|
|
38
44
|
Install [Node.js](https://nodejs.org/) (v18 or later), then run:
|
|
@@ -65,7 +71,7 @@ Open `http://localhost:4173` to view and edit your course.
|
|
|
65
71
|
|
|
66
72
|
The example course included with every new project is a complete guide to using CourseCode.
|
|
67
73
|
|
|
68
|
-
**New to CourseCode?**
|
|
74
|
+
**New to CourseCode?** If you want the easiest setup, start with **[CourseCode Desktop](https://coursecodedesktop.com)**. If you're using the framework CLI, start with the [User Guide](framework/docs/USER_GUIDE.md).
|
|
69
75
|
|
|
70
76
|
---
|
|
71
77
|
|
|
@@ -179,7 +185,7 @@ The preview server provides:
|
|
|
179
185
|
|
|
180
186
|
When ready, deploy:
|
|
181
187
|
|
|
182
|
-
**With [CourseCode Cloud](https://
|
|
188
|
+
**With [CourseCode Cloud](https://coursecodecloud.com)**: Push your course and get a live link. Cloud handles hosting, generates any LMS format on demand, and gives you sharable preview links with optional password protection. No ZIP files, no manual uploads.
|
|
183
189
|
|
|
184
190
|
```bash
|
|
185
191
|
coursecode deploy
|
|
@@ -218,7 +224,7 @@ For the full command list and deployment options, see the [User Guide](framework
|
|
|
218
224
|
|
|
219
225
|
## UI Components
|
|
220
226
|
|
|
221
|
-
Build engaging slides with interactive elements.
|
|
227
|
+
Build engaging slides with interactive elements. Start with AI assistance, then edit the generated files directly whenever you want more control.
|
|
222
228
|
|
|
223
229
|
### Media & Widgets
|
|
224
230
|
|
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 */
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
.interactive-image-hotspot[data-color="primary"] { --hotspot-color: var(--color-primary, #14213d); }
|
|
62
62
|
.interactive-image-hotspot[data-color="secondary"] { --hotspot-color: var(--color-secondary, #f18701); }
|
|
63
63
|
.interactive-image-hotspot[data-color="success"] { --hotspot-color: var(--color-success, #1d7648); }
|
|
64
|
-
.interactive-image-hotspot[data-color="danger"] { --hotspot-color: var(--color-danger, #
|
|
64
|
+
.interactive-image-hotspot[data-color="danger"] { --hotspot-color: var(--color-danger, #c7322b); }
|
|
65
65
|
.interactive-image-hotspot[data-color="warning"] { --hotspot-color: var(--color-warning, #f7b801); }
|
|
66
66
|
.interactive-image-hotspot[data-color="info"] { --hotspot-color: var(--color-info, #4a6fa5); }
|
|
67
67
|
.interactive-image-hotspot[data-color="light"] { --hotspot-color: var(--color-gray-50, #fafafa); }
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
.interactive-image-hotspot[data-viewed-color="primary"] { --viewed-color: var(--color-primary, #14213d); }
|
|
72
72
|
.interactive-image-hotspot[data-viewed-color="secondary"] { --viewed-color: var(--color-secondary, #f18701); }
|
|
73
73
|
.interactive-image-hotspot[data-viewed-color="success"] { --viewed-color: var(--color-success, #1d7648); }
|
|
74
|
-
.interactive-image-hotspot[data-viewed-color="danger"] { --viewed-color: var(--color-danger, #
|
|
74
|
+
.interactive-image-hotspot[data-viewed-color="danger"] { --viewed-color: var(--color-danger, #c7322b); }
|
|
75
75
|
.interactive-image-hotspot[data-viewed-color="warning"] { --viewed-color: var(--color-warning, #f7b801); }
|
|
76
76
|
.interactive-image-hotspot[data-viewed-color="info"] { --viewed-color: var(--color-info, #4a6fa5); }
|
|
77
77
|
.interactive-image-hotspot[data-viewed-color="light"] { --viewed-color: var(--color-gray-50, #fafafa); }
|
|
@@ -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
|
+
}
|