steppy-dev 0.3.0__py3-none-any.whl

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.
Files changed (177) hide show
  1. steppy/CHANGELOG.md +862 -0
  2. steppy/__init__.py +9 -0
  3. steppy/__main__.py +13 -0
  4. steppy/_cli_common.py +183 -0
  5. steppy/_docker/Dockerfile.installed +53 -0
  6. steppy/_parallel/__init__.py +7 -0
  7. steppy/_parallel/compose.py +349 -0
  8. steppy/_parallel/event_bus.py +418 -0
  9. steppy/_parallel/failures.py +267 -0
  10. steppy/_parallel/fingerprint.py +141 -0
  11. steppy/_parallel/image.py +380 -0
  12. steppy/_parallel/lastfailed.py +125 -0
  13. steppy/_parallel/lifecycle.py +90 -0
  14. steppy/_parallel/preauth.py +109 -0
  15. steppy/_parallel/progress.py +101 -0
  16. steppy/_parallel/queue.py +385 -0
  17. steppy/_parallel/renderer.py +489 -0
  18. steppy/_parallel/retry.py +316 -0
  19. steppy/_player_bundle/THIRD_PARTY_LICENSES.txt +158 -0
  20. steppy/_player_bundle/__init__.py +100 -0
  21. steppy/_player_bundle/steppy-cursor-legacy.js +2 -0
  22. steppy/_player_bundle/steppy-cursor-legacy.js.map +1 -0
  23. steppy/_player_bundle/steppy-player.js +5419 -0
  24. steppy/_player_bundle/steppy-player.js.map +1 -0
  25. steppy/_player_bundle/steppy-video.js +472 -0
  26. steppy/_player_bundle/steppy-video.js.map +1 -0
  27. steppy/_templating.py +117 -0
  28. steppy/assertions.py +251 -0
  29. steppy/auth.py +493 -0
  30. steppy/build.py +2798 -0
  31. steppy/build_models.py +669 -0
  32. steppy/buildid.py +134 -0
  33. steppy/bundle/__init__.py +47 -0
  34. steppy/bundle/archive.py +239 -0
  35. steppy/bundle/classify.py +329 -0
  36. steppy/bundle/create.py +490 -0
  37. steppy/bundle/hashing.py +28 -0
  38. steppy/bundle/manifest.py +334 -0
  39. steppy/bundle/results.py +546 -0
  40. steppy/bundle_cli.py +455 -0
  41. steppy/captures.py +207 -0
  42. steppy/cli.py +2489 -0
  43. steppy/cloud/__init__.py +58 -0
  44. steppy/cloud/client.py +267 -0
  45. steppy/cloud/upload.py +497 -0
  46. steppy/compat.py +109 -0
  47. steppy/config.py +231 -0
  48. steppy/config_schema.py +87 -0
  49. steppy/contracts/__init__.py +54 -0
  50. steppy/contracts/bundle.py +215 -0
  51. steppy/contracts/report.py +346 -0
  52. steppy/contracts/tours.py +505 -0
  53. steppy/coordinator.py +1352 -0
  54. steppy/coverage/__init__.py +9 -0
  55. steppy/coverage/gaps.py +190 -0
  56. steppy/coverage/manifest.py +193 -0
  57. steppy/coverage/merge.py +363 -0
  58. steppy/coverage/normalize.py +174 -0
  59. steppy/coverage/report.py +178 -0
  60. steppy/coverage/sourcemap.py +219 -0
  61. steppy/coverage/store.py +473 -0
  62. steppy/coverage/triage.py +151 -0
  63. steppy/coverage_cli.py +265 -0
  64. steppy/durations.py +48 -0
  65. steppy/exclusive.py +178 -0
  66. steppy/flaky_cli.py +169 -0
  67. steppy/generator.py +633 -0
  68. steppy/guide.py +1467 -0
  69. steppy/history.py +352 -0
  70. steppy/init_cli.py +330 -0
  71. steppy/locales.py +282 -0
  72. steppy/migrate/__init__.py +35 -0
  73. steppy/migrate/apply.py +194 -0
  74. steppy/migrate/plan.py +199 -0
  75. steppy/migrate/rules.py +465 -0
  76. steppy/migrate/verify.py +163 -0
  77. steppy/migrate_cli.py +152 -0
  78. steppy/models.py +2539 -0
  79. steppy/naming.py +37 -0
  80. steppy/parser.py +475 -0
  81. steppy/pdf.py +193 -0
  82. steppy/presets.py +284 -0
  83. steppy/record_cli.py +796 -0
  84. steppy/report/__init__.py +37 -0
  85. steppy/report/assets.py +197 -0
  86. steppy/report/build.py +261 -0
  87. steppy/report/coverage_source.py +277 -0
  88. steppy/report/model.py +423 -0
  89. steppy/report/render.py +303 -0
  90. steppy/report/source.py +238 -0
  91. steppy/report_cli.py +173 -0
  92. steppy/runmeta.py +114 -0
  93. steppy/selection.py +183 -0
  94. steppy/sitemap.py +205 -0
  95. steppy/skills/steppy-app-tours/SKILL.md +448 -0
  96. steppy/skills/steppy-ci-report/SKILL.md +567 -0
  97. steppy/skills/steppy-scenario-author/SKILL.md +2176 -0
  98. steppy/skills/steppy-template/SKILL.md +676 -0
  99. steppy/templates/_steppy_events.py +291 -0
  100. steppy/templates/_steppy_tours.py +404 -0
  101. steppy/templates/angular/component-interactive.ts.j2 +109 -0
  102. steppy/templates/angular/component-video.ts.j2 +112 -0
  103. steppy/templates/angular/component.ts.j2 +229 -0
  104. steppy/templates/angular/content.ts.j2 +31 -0
  105. steppy/templates/angular/gitignore.j2 +3 -0
  106. steppy/templates/angular/group.component.ts.j2 +39 -0
  107. steppy/templates/angular/onboarding.component.ts.j2 +43 -0
  108. steppy/templates/angular/routes.ts.j2 +34 -0
  109. steppy/templates/ci/github-actions.yml +284 -0
  110. steppy/templates/ci/gitlab-ci.yml +178 -0
  111. steppy/templates/conftest.py.j2 +2405 -0
  112. steppy/templates/guide.book.html.j2 +145 -0
  113. steppy/templates/guide.fragment.html.j2 +257 -0
  114. steppy/templates/guide.html.j2 +301 -0
  115. steppy/templates/guide.md.j2 +18 -0
  116. steppy/templates/init/config.yaml +124 -0
  117. steppy/templates/init/credentials.yaml.example +30 -0
  118. steppy/templates/init/scenarios/example--first-flow.md +53 -0
  119. steppy/templates/report/_head.html.j2 +14 -0
  120. steppy/templates/report/_mark.html.j2 +3 -0
  121. steppy/templates/report/coverage.html.j2 +124 -0
  122. steppy/templates/report/index.html.j2 +203 -0
  123. steppy/templates/report/report.css +401 -0
  124. steppy/templates/report/scenario.html.j2 +137 -0
  125. steppy/templates/static/_headers.j2 +30 -0
  126. steppy/templates/static/_mode_links.html.j2 +22 -0
  127. steppy/templates/static/_seo_head.html.j2 +37 -0
  128. steppy/templates/static/cursor.js.j2 +10 -0
  129. steppy/templates/static/group.html.j2 +78 -0
  130. steppy/templates/static/guides.css +678 -0
  131. steppy/templates/static/index.html.j2 +79 -0
  132. steppy/templates/static/interactive.html.j2 +100 -0
  133. steppy/templates/static/onboarding.html.j2 +64 -0
  134. steppy/templates/static/page.html.j2 +145 -0
  135. steppy/templates/static/robots.txt.j2 +7 -0
  136. steppy/templates/static/video.html.j2 +135 -0
  137. steppy/templates/test_scenario.py.j2 +767 -0
  138. steppy/testids.py +166 -0
  139. steppy/totp.py +26 -0
  140. steppy/tour_spec.py +163 -0
  141. steppy/tours/__init__.py +22 -0
  142. steppy/tours/actions.py +402 -0
  143. steppy/tours/compiler.py +144 -0
  144. steppy/tours/diagnostics.py +137 -0
  145. steppy/tours/emit.py +239 -0
  146. steppy/tours/ids.py +40 -0
  147. steppy/tours/inference.py +399 -0
  148. steppy/tours/model.py +120 -0
  149. steppy/tours/paths.py +126 -0
  150. steppy/tours/pipeline.py +277 -0
  151. steppy/tours/publish.py +124 -0
  152. steppy/tours/replay.py +234 -0
  153. steppy/tours/routes.py +175 -0
  154. steppy/tours/settings.py +164 -0
  155. steppy/tours/text.py +237 -0
  156. steppy/tours_cli.py +219 -0
  157. steppy/tts/__init__.py +280 -0
  158. steppy/tts/cache.py +185 -0
  159. steppy/tts/presets.py +125 -0
  160. steppy/tts/providers/__init__.py +6 -0
  161. steppy/tts/providers/http.py +184 -0
  162. steppy/tts/providers/local.py +207 -0
  163. steppy/tts/providers/null.py +28 -0
  164. steppy/tts/registry.py +201 -0
  165. steppy/tts/track.py +158 -0
  166. steppy/tts/types.py +94 -0
  167. steppy/update_cli.py +460 -0
  168. steppy/video.py +730 -0
  169. steppy/video_cache.py +239 -0
  170. steppy/video_cli.py +271 -0
  171. steppy/worker.py +301 -0
  172. steppy_dev-0.3.0.dist-info/METADATA +1335 -0
  173. steppy_dev-0.3.0.dist-info/RECORD +177 -0
  174. steppy_dev-0.3.0.dist-info/WHEEL +4 -0
  175. steppy_dev-0.3.0.dist-info/entry_points.txt +2 -0
  176. steppy_dev-0.3.0.dist-info/licenses/LICENSE +202 -0
  177. steppy_dev-0.3.0.dist-info/licenses/NOTICE +13 -0
@@ -0,0 +1,145 @@
1
+ {# guide.book.html.j2 — the "handbook": every guide for one (role, locale, layout)
2
+ in a single self-contained document, designed to be read on screen and
3
+ printed to PDF (`steppy guide --handbook`, `steppy build --handbook`).
4
+
5
+ The guide bodies are pre-rendered `guide.fragment.html.j2` fragments passed in
6
+ as `guides[].html`, and the styling is the shared static stylesheet
7
+ (`guides_css`) so a handbook always looks like the site it came from — there
8
+ is no fourth copy of the guide CSS to keep in sync.
9
+
10
+ Chromium's print-to-PDF cannot emit PDF bookmarks or resolve page numbers in
11
+ a table of contents, so the TOC links to in-document anchors only. #}
12
+ <!DOCTYPE html>
13
+ <html lang="{{ html_lang | default(locale, true) | default('en', true) }}">
14
+ <head>
15
+ <meta charset="UTF-8">
16
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
17
+ <meta name="robots" content="noindex,nofollow,noarchive">
18
+ <title>{{ title }}</title>
19
+ <style>
20
+ {{ guides_css }}
21
+
22
+ /* ── Handbook ────────────────────────────────────────────────────────── */
23
+
24
+ .steppy-book {
25
+ max-width: 900px;
26
+ margin: 0 auto;
27
+ padding: 32px 16px;
28
+ }
29
+
30
+ .steppy-book-cover {
31
+ text-align: center;
32
+ padding: 64px 0 48px;
33
+ border-bottom: 3px solid #3498db;
34
+ margin-bottom: 32px;
35
+ }
36
+
37
+ .steppy-book-cover h1 {
38
+ font-size: 40px;
39
+ color: #2c3e50;
40
+ margin: 0 0 12px;
41
+ }
42
+
43
+ .steppy-book-subtitle {
44
+ font-size: 18px;
45
+ color: #7f8c8d;
46
+ margin: 0 0 20px;
47
+ }
48
+
49
+ .steppy-book-toc {
50
+ margin-bottom: 32px;
51
+ }
52
+
53
+ .steppy-book-toc h2 {
54
+ font-size: 22px;
55
+ color: #2c3e50;
56
+ margin: 0 0 12px;
57
+ }
58
+
59
+ .steppy-book-toc ol {
60
+ margin: 0;
61
+ padding-left: 24px;
62
+ line-height: 2;
63
+ }
64
+
65
+ .steppy-book-toc a {
66
+ color: #2c3e50;
67
+ text-decoration: none;
68
+ }
69
+
70
+ .steppy-book-toc a:hover {
71
+ text-decoration: underline;
72
+ }
73
+
74
+ .steppy-book-section {
75
+ font-size: 14px;
76
+ text-transform: uppercase;
77
+ letter-spacing: 0.08em;
78
+ color: #7f8c8d;
79
+ margin: 0 0 8px;
80
+ }
81
+
82
+ .steppy-book-guide {
83
+ margin-bottom: 48px;
84
+ }
85
+
86
+ @media print {
87
+ .steppy-book {
88
+ padding: 0;
89
+ max-width: none;
90
+ }
91
+
92
+ /* Cover and contents each own a page; every guide starts on a fresh one.
93
+ Adjacent forced breaks collapse into one, so the first guide does not
94
+ get a blank page after the cover/contents break. */
95
+ .steppy-book-cover,
96
+ .steppy-book-toc {
97
+ page-break-after: always;
98
+ }
99
+
100
+ .steppy-book-section,
101
+ .steppy-book-guide {
102
+ page-break-before: always;
103
+ }
104
+
105
+ /* A section heading already opened the page its first guide starts on. */
106
+ .steppy-book-section + .steppy-book-guide {
107
+ page-break-before: auto;
108
+ }
109
+ }
110
+ </style>
111
+ </head>
112
+ <body>
113
+ <div class="steppy-book">
114
+ <header class="steppy-book-cover">
115
+ <h1>{{ title }}</h1>
116
+ {% if subtitle %}
117
+ <p class="steppy-book-subtitle">{{ subtitle }}</p>
118
+ {% endif %}
119
+ {% if role %}
120
+ <div class="steppy-role-badge">{{ role }}</div>
121
+ {% endif %}
122
+ </header>
123
+
124
+ {% if guides | length > 1 %}
125
+ <nav class="steppy-book-toc" aria-label="{{ toc_label | default('Contents', true) }}">
126
+ <h2>{{ toc_label | default('Contents', true) }}</h2>
127
+ <ol>
128
+ {% for guide in guides %}
129
+ <li><a href="#{{ guide.slug }}">{{ guide.title }}</a></li>
130
+ {% endfor %}
131
+ </ol>
132
+ </nav>
133
+ {% endif %}
134
+
135
+ {% for guide in guides %}
136
+ {% if guide.section %}
137
+ <h2 class="steppy-book-section">{{ guide.section }}</h2>
138
+ {% endif %}
139
+ <section class="steppy-book-guide" id="{{ guide.slug }}">
140
+ {{ guide.html | safe }}
141
+ </section>
142
+ {% endfor %}
143
+ </div>
144
+ </body>
145
+ </html>
@@ -0,0 +1,257 @@
1
+ {# guide.fragment.html.j2 — innerHTML-safe HTML fragment for Angular embedding.
2
+ No <!DOCTYPE>, <html>, <head>, <body>, or <script> tags.
3
+ CSS is scoped under .steppy-guide to avoid conflicts with the host app.
4
+ Pass include_styles=false to suppress the inline <style> block when the host
5
+ page already links the shared guides.css. #}
6
+ {% if include_styles | default(true) %}
7
+ <style>
8
+ .steppy-guide {
9
+ --steppy-cursor-size: 24px;
10
+ --steppy-cursor-color: #0f172a;
11
+ --steppy-cursor-accent: #2563eb;
12
+ --steppy-cursor-ring: rgba(37, 99, 235, 0.55);
13
+ --steppy-cursor-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
14
+ --steppy-z-screenshot: 1;
15
+ --steppy-z-cursor: 10;
16
+ --steppy-z-ripple: 11;
17
+ --steppy-z-glyph: 12;
18
+ /* Print/PDF: cap how tall a screenshot may render. Chromium cannot
19
+ split an image across a page break — an uncapped full-page or mobile
20
+ capture is clipped instead. Scaled down, it fits. */
21
+ --steppy-print-max-image-height: 180mm;
22
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
23
+ line-height: 1.6;
24
+ color: #333;
25
+ max-width: 900px;
26
+ margin: 0 auto;
27
+ }
28
+
29
+ .steppy-guide h1 {
30
+ font-size: 32px;
31
+ color: #2c3e50;
32
+ margin-bottom: 10px;
33
+ border-bottom: 3px solid #3498db;
34
+ padding-bottom: 10px;
35
+ }
36
+
37
+ .steppy-guide .steppy-role-badge {
38
+ display: inline-block;
39
+ background-color: #3498db;
40
+ color: white;
41
+ padding: 6px 16px;
42
+ border-radius: 20px;
43
+ font-size: 14px;
44
+ font-weight: 600;
45
+ margin-bottom: 30px;
46
+ }
47
+
48
+ .steppy-guide .steppy-step {
49
+ margin-bottom: 50px;
50
+ }
51
+
52
+ .steppy-guide .steppy-step h2 {
53
+ font-size: 24px;
54
+ color: #2c3e50;
55
+ margin-bottom: 15px;
56
+ display: flex;
57
+ align-items: center;
58
+ }
59
+
60
+ .steppy-guide .steppy-step-number {
61
+ display: inline-flex;
62
+ align-items: center;
63
+ justify-content: center;
64
+ width: 36px;
65
+ height: 36px;
66
+ background-color: #3498db;
67
+ color: white;
68
+ border-radius: 50%;
69
+ font-weight: bold;
70
+ font-size: 18px;
71
+ margin-right: 12px;
72
+ flex-shrink: 0;
73
+ }
74
+
75
+ .steppy-guide .steppy-step-subtitle {
76
+ font-size: 18px;
77
+ color: #7f8c8d;
78
+ font-weight: 500;
79
+ margin-top: -10px;
80
+ margin-bottom: 15px;
81
+ font-style: italic;
82
+ }
83
+
84
+ .steppy-guide .steppy-step-content {
85
+ font-size: 16px;
86
+ color: #555;
87
+ margin-bottom: 20px;
88
+ line-height: 1.8;
89
+ }
90
+
91
+ .steppy-guide .steppy-stage {
92
+ position: relative;
93
+ line-height: 0;
94
+ }
95
+
96
+ .steppy-guide .steppy-stage img {
97
+ /* width:auto (not 100%) caps display at the screenshot's intrinsic pixel
98
+ width: a dense capture (e.g. mobile at device_scale_factor=3) still
99
+ fills the column, but a low-res capture renders at native size instead
100
+ of being upscaled and blurred. max-width:100% keeps it responsive. */
101
+ width: auto;
102
+ max-width: 100%;
103
+ height: auto;
104
+ border: 1px solid #ddd;
105
+ border-radius: 4px;
106
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
107
+ display: block;
108
+ position: relative;
109
+ z-index: var(--steppy-z-screenshot);
110
+ }
111
+
112
+ .steppy-guide .steppy-cursor {
113
+ position: absolute;
114
+ top: 0;
115
+ left: 0;
116
+ width: var(--steppy-cursor-size);
117
+ height: var(--steppy-cursor-size);
118
+ pointer-events: none;
119
+ z-index: var(--steppy-z-cursor);
120
+ opacity: 0;
121
+ transition: opacity 200ms ease-in-out;
122
+ background: var(--steppy-cursor-color);
123
+ clip-path: polygon(0 0, 0 75%, 25% 55%, 45% 95%, 60% 85%, 40% 50%, 75% 40%);
124
+ filter: drop-shadow(var(--steppy-cursor-shadow));
125
+ transform-origin: 0 0;
126
+ }
127
+
128
+ .steppy-guide .steppy-cursor.visible {
129
+ opacity: 1;
130
+ }
131
+
132
+ .steppy-guide .steppy-cursor.pulse {
133
+ animation: steppy-pulse-anim 200ms ease-in-out;
134
+ }
135
+
136
+ @keyframes steppy-pulse-anim {
137
+ 0%, 100% { transform: scale(1); }
138
+ 50% { transform: scale(1.25); }
139
+ }
140
+
141
+ .steppy-guide .steppy-ripple {
142
+ position: absolute;
143
+ top: 0;
144
+ left: 0;
145
+ width: 12px;
146
+ height: 12px;
147
+ margin-left: -6px;
148
+ margin-top: -6px;
149
+ border-radius: 50%;
150
+ border: 2px solid var(--steppy-cursor-ring);
151
+ background: transparent;
152
+ z-index: var(--steppy-z-ripple);
153
+ pointer-events: none;
154
+ opacity: 0;
155
+ }
156
+
157
+ .steppy-guide .steppy-ripple.fire {
158
+ animation: steppy-ripple-anim 500ms ease-out forwards;
159
+ }
160
+
161
+ @keyframes steppy-ripple-anim {
162
+ 0% { width: 12px; height: 12px; margin-left: -6px; margin-top: -6px; opacity: 0.9; }
163
+ 100% { width: 72px; height: 72px; margin-left: -36px; margin-top: -36px; opacity: 0; }
164
+ }
165
+
166
+ .steppy-guide .steppy-kbd {
167
+ position: absolute;
168
+ top: 0;
169
+ left: 0;
170
+ background: #111827;
171
+ color: #fff;
172
+ font: 12px/1 ui-monospace, Menlo, Consolas, monospace;
173
+ padding: 5px 7px;
174
+ border-radius: 4px;
175
+ z-index: var(--steppy-z-glyph);
176
+ opacity: 0;
177
+ pointer-events: none;
178
+ white-space: nowrap;
179
+ transition: opacity 150ms ease-in-out;
180
+ }
181
+
182
+ .steppy-guide .steppy-kbd.visible {
183
+ opacity: 1;
184
+ }
185
+
186
+ @media print {
187
+ .steppy-guide {
188
+ /* Keep the step badges / role chip coloured even when the viewer
189
+ disables background graphics. */
190
+ -webkit-print-color-adjust: exact;
191
+ print-color-adjust: exact;
192
+ }
193
+
194
+ .steppy-guide .steppy-step {
195
+ page-break-inside: avoid;
196
+ }
197
+
198
+ .steppy-guide .steppy-stage img {
199
+ max-height: var(--steppy-print-max-image-height);
200
+ box-shadow: none;
201
+ page-break-inside: avoid;
202
+ }
203
+
204
+ .steppy-guide .steppy-cursor,
205
+ .steppy-guide .steppy-ripple,
206
+ .steppy-guide .steppy-kbd {
207
+ display: none;
208
+ }
209
+ }
210
+
211
+ @media (max-width: 768px) {
212
+ .steppy-guide h1 {
213
+ font-size: 24px;
214
+ }
215
+
216
+ .steppy-guide .steppy-step h2 {
217
+ font-size: 20px;
218
+ }
219
+
220
+ .steppy-guide .steppy-step-number {
221
+ width: 30px;
222
+ height: 30px;
223
+ font-size: 16px;
224
+ }
225
+ }
226
+ </style>
227
+ {% endif %}
228
+
229
+ <div class="steppy-guide">
230
+ <h1>{{ title }}</h1>
231
+ {% if role %}
232
+ <div class="steppy-role-badge">{{ role }}</div>
233
+ {% endif %}
234
+
235
+ {% for step in steps %}
236
+ <div class="steppy-step" data-step-index="{{ step.index }}">
237
+ <h2>
238
+ <span class="steppy-step-number">{{ step.index }}</span>
239
+ <span>{{ step.title }}</span>
240
+ </h2>
241
+ {% if step.subtitle %}
242
+ <div class="steppy-step-subtitle">{{ step.subtitle }}</div>
243
+ {% endif %}
244
+ {% if step.text %}
245
+ <div class="steppy-step-content">{{ step.text | markdown }}</div>
246
+ {% endif %}
247
+ {% for frame in step.frames %}
248
+ <div class="steppy-stage" data-frame-index="{{ loop.index0 }}">
249
+ <img src="{{ frame.screenshot }}" alt="Step {{ step.index }}" loading="lazy">
250
+ <div class="steppy-cursor" aria-hidden="true"></div>
251
+ <div class="steppy-ripple" aria-hidden="true"></div>
252
+ <div class="steppy-kbd" aria-hidden="true"></div>
253
+ </div>
254
+ {% endfor %}
255
+ </div>
256
+ {% endfor %}
257
+ </div>
@@ -0,0 +1,301 @@
1
+ <!DOCTYPE html>
2
+ <html lang="{{ html_lang | default(locale, true) | default('fr', true) }}">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{ title }}</title>
7
+ <style>
8
+ :root {
9
+ /* Cursor design tokens — override these to retheme the guided tour. */
10
+ --steppy-cursor-size: 24px;
11
+ --steppy-cursor-color: #0f172a;
12
+ --steppy-cursor-accent: #2563eb;
13
+ --steppy-cursor-ring: rgba(37, 99, 235, 0.55);
14
+ --steppy-cursor-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
15
+ --steppy-z-screenshot: 1;
16
+ --steppy-z-cursor: 10;
17
+ --steppy-z-ripple: 11;
18
+ --steppy-z-glyph: 12;
19
+ --steppy-z-tooltip: 20;
20
+ /* Print/PDF: cap how tall a screenshot may render. Chromium cannot
21
+ split an image across a page break — an uncapped full-page or
22
+ mobile capture is clipped instead. Scaled down, it fits. */
23
+ --steppy-print-max-image-height: 180mm;
24
+ }
25
+
26
+ * {
27
+ margin: 0;
28
+ padding: 0;
29
+ box-sizing: border-box;
30
+ }
31
+
32
+ body {
33
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
34
+ line-height: 1.6;
35
+ color: #333;
36
+ background-color: #f5f5f5;
37
+ padding: 20px;
38
+ }
39
+
40
+ .container {
41
+ max-width: 900px;
42
+ margin: 0 auto;
43
+ background-color: white;
44
+ padding: 40px;
45
+ border-radius: 8px;
46
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
47
+ }
48
+
49
+ h1 {
50
+ font-size: 32px;
51
+ color: #2c3e50;
52
+ margin-bottom: 10px;
53
+ border-bottom: 3px solid #3498db;
54
+ padding-bottom: 10px;
55
+ }
56
+
57
+ .role-badge {
58
+ display: inline-block;
59
+ background-color: #3498db;
60
+ color: white;
61
+ padding: 6px 16px;
62
+ border-radius: 20px;
63
+ font-size: 14px;
64
+ font-weight: 600;
65
+ margin-bottom: 30px;
66
+ }
67
+
68
+ .step {
69
+ margin-bottom: 50px;
70
+ page-break-inside: avoid;
71
+ }
72
+
73
+ .step h2 {
74
+ font-size: 24px;
75
+ color: #2c3e50;
76
+ margin-bottom: 15px;
77
+ display: flex;
78
+ align-items: center;
79
+ }
80
+
81
+ .step-number {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ width: 36px;
86
+ height: 36px;
87
+ background-color: #3498db;
88
+ color: white;
89
+ border-radius: 50%;
90
+ font-weight: bold;
91
+ font-size: 18px;
92
+ margin-right: 12px;
93
+ flex-shrink: 0;
94
+ }
95
+
96
+ .step-subtitle {
97
+ font-size: 18px;
98
+ color: #7f8c8d;
99
+ font-weight: 500;
100
+ margin-top: -10px;
101
+ margin-bottom: 15px;
102
+ font-style: italic;
103
+ }
104
+
105
+ .step-content {
106
+ font-size: 16px;
107
+ color: #555;
108
+ margin-bottom: 20px;
109
+ line-height: 1.8;
110
+ }
111
+
112
+ .steppy-stage {
113
+ position: relative;
114
+ line-height: 0;
115
+ }
116
+
117
+ .steppy-stage img {
118
+ /* width:auto caps display at the screenshot's intrinsic pixel width
119
+ so dense captures fill the column and low-res captures render at
120
+ native size instead of upscaled/blurry. max-width keeps responsive. */
121
+ width: auto;
122
+ max-width: 100%;
123
+ height: auto;
124
+ border: 1px solid #ddd;
125
+ border-radius: 4px;
126
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
127
+ display: block;
128
+ position: relative;
129
+ z-index: var(--steppy-z-screenshot);
130
+ }
131
+
132
+ .steppy-cursor {
133
+ position: absolute;
134
+ top: 0;
135
+ left: 0;
136
+ width: var(--steppy-cursor-size);
137
+ height: var(--steppy-cursor-size);
138
+ pointer-events: none;
139
+ z-index: var(--steppy-z-cursor);
140
+ opacity: 0;
141
+ transition: opacity 200ms ease-in-out;
142
+ background: var(--steppy-cursor-color);
143
+ clip-path: polygon(0 0, 0 75%, 25% 55%, 45% 95%, 60% 85%, 40% 50%, 75% 40%);
144
+ filter: drop-shadow(var(--steppy-cursor-shadow));
145
+ transform-origin: 0 0;
146
+ }
147
+
148
+ .steppy-cursor.visible {
149
+ opacity: 1;
150
+ }
151
+
152
+ .steppy-cursor.pulse {
153
+ animation: steppy-pulse-anim 200ms ease-in-out;
154
+ }
155
+
156
+ @keyframes steppy-pulse-anim {
157
+ 0%, 100% { transform: scale(1); }
158
+ 50% { transform: scale(1.25); }
159
+ }
160
+
161
+ .steppy-ripple {
162
+ position: absolute;
163
+ top: 0;
164
+ left: 0;
165
+ width: 12px;
166
+ height: 12px;
167
+ margin-left: -6px;
168
+ margin-top: -6px;
169
+ border-radius: 50%;
170
+ border: 2px solid var(--steppy-cursor-ring);
171
+ background: transparent;
172
+ z-index: var(--steppy-z-ripple);
173
+ pointer-events: none;
174
+ opacity: 0;
175
+ }
176
+
177
+ .steppy-ripple.fire {
178
+ animation: steppy-ripple-anim 500ms ease-out forwards;
179
+ }
180
+
181
+ @keyframes steppy-ripple-anim {
182
+ 0% { width: 12px; height: 12px; margin-left: -6px; margin-top: -6px; opacity: 0.9; }
183
+ 100% { width: 72px; height: 72px; margin-left: -36px; margin-top: -36px; opacity: 0; }
184
+ }
185
+
186
+ .steppy-kbd {
187
+ position: absolute;
188
+ top: 0;
189
+ left: 0;
190
+ background: #111827;
191
+ color: #fff;
192
+ font: 12px/1 ui-monospace, Menlo, Consolas, monospace;
193
+ padding: 5px 7px;
194
+ border-radius: 4px;
195
+ z-index: var(--steppy-z-glyph);
196
+ opacity: 0;
197
+ pointer-events: none;
198
+ white-space: nowrap;
199
+ transition: opacity 150ms ease-in-out;
200
+ }
201
+
202
+ .steppy-kbd.visible {
203
+ opacity: 1;
204
+ }
205
+
206
+ @media print {
207
+ body {
208
+ background-color: white;
209
+ padding: 0;
210
+ /* Keep the step badges / role chip coloured even when the
211
+ viewer disables background graphics. */
212
+ -webkit-print-color-adjust: exact;
213
+ print-color-adjust: exact;
214
+ }
215
+
216
+ .container {
217
+ box-shadow: none;
218
+ padding: 20px;
219
+ }
220
+
221
+ .step {
222
+ page-break-inside: avoid;
223
+ }
224
+
225
+ .steppy-stage img {
226
+ max-height: var(--steppy-print-max-image-height);
227
+ box-shadow: none;
228
+ page-break-inside: avoid;
229
+ }
230
+
231
+ .steppy-cursor,
232
+ .steppy-ripple,
233
+ .steppy-kbd {
234
+ display: none;
235
+ }
236
+ }
237
+
238
+ @media (max-width: 768px) {
239
+ .container {
240
+ padding: 20px;
241
+ }
242
+
243
+ h1 {
244
+ font-size: 24px;
245
+ }
246
+
247
+ .step h2 {
248
+ font-size: 20px;
249
+ }
250
+
251
+ .step-number {
252
+ width: 30px;
253
+ height: 30px;
254
+ font-size: 16px;
255
+ }
256
+ }
257
+ </style>
258
+ </head>
259
+ <body>
260
+ <div class="container">
261
+ <h1>{{ title }}</h1>
262
+ <div class="role-badge">{{ role }}</div>
263
+
264
+ {% for step in steps %}
265
+ <div class="step" data-step-index="{{ step.index }}">
266
+ <h2>
267
+ <span class="step-number">{{ step.index }}</span>
268
+ <span>{{ step.title }}</span>
269
+ </h2>
270
+ {% if step.subtitle %}
271
+ <div class="step-subtitle">{{ step.subtitle }}</div>
272
+ {% endif %}
273
+ {% if step.text %}
274
+ <div class="step-content">
275
+ {{ step.text | markdown }}
276
+ </div>
277
+ {% endif %}
278
+ {% for frame in step.frames %}
279
+ <div class="steppy-stage" data-frame-index="{{ loop.index0 }}">
280
+ <img src="{{ frame.screenshot }}" alt="Étape {{ step.index }}" loading="lazy">
281
+ <div class="steppy-cursor" aria-hidden="true"></div>
282
+ <div class="steppy-ripple" aria-hidden="true"></div>
283
+ <div class="steppy-kbd" aria-hidden="true"></div>
284
+ </div>
285
+ {% endfor %}
286
+ </div>
287
+ {% endfor %}
288
+ </div>
289
+
290
+ <script>
291
+ window.__STEPPY_STEPS__ = {{ steps | tojson }};
292
+ window.__STEPPY_TIMINGS__ = {{ cursor_timings | tojson }};
293
+ </script>
294
+ <!--
295
+ Cursor IIFE — sourced from packages/player/src/cursor/legacy-iife.ts and
296
+ bundled to src/steppy/_player_bundle/steppy-cursor-legacy.js. Inlining
297
+ keeps the standalone HTML self-contained (the spec's "works offline").
298
+ -->
299
+ <script>{{ cursor_iife | safe }}</script>
300
+ </body>
301
+ </html>