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,79 @@
1
+ <!doctype html>
2
+ <html lang="{{ html_lang | default(current_locale, true) }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{ pick_locale(site_title, current_locale, default_locale) }}</title>
7
+ {% include "static/_seo_head.html.j2" %}
8
+ <link rel="stylesheet" href="./assets/guides.css">
9
+ </head>
10
+ <body>
11
+ <header class="steppy-site-header">
12
+ <span class="steppy-site-title">{{ pick_locale(site_title, current_locale, default_locale) }}</span>
13
+ {% if locales | length > 1 %}
14
+ <nav class="steppy-locale-switch" aria-label="Language">
15
+ <ul>
16
+ {% for link in lang_links %}
17
+ <li>
18
+ {% if link.active %}
19
+ <span aria-current="true">{{ link.label }}</span>
20
+ {% else %}
21
+ <a href="{{ link.href }}">{{ link.label }}</a>
22
+ {% endif %}
23
+ </li>
24
+ {% endfor %}
25
+ </ul>
26
+ </nav>
27
+ {% endif %}
28
+ </header>
29
+
30
+ <main class="steppy-main">
31
+ <section class="steppy-index">
32
+ <h1>{{ pick_locale(site_title, current_locale, default_locale) }}</h1>
33
+ <ul class="steppy-index-list">
34
+ {% for node in navigation %}
35
+ {% if node.kind == 'onboarding' %}
36
+ <li class="steppy-index-onboarding">
37
+ <a href="./{{ onboarding_slug }}/{{ current_locale }}.html">
38
+ <span class="steppy-index-eyebrow">{{ onboarding_eyebrow }}</span>
39
+ <span class="steppy-index-title">{{ pick_locale(node.title, current_locale, default_locale) }}</span>
40
+ <span class="steppy-index-meta">{{ node.guides | length }} {{ steps_label }}</span>
41
+ </a>
42
+ </li>
43
+ {% elif node.kind == 'group' %}
44
+ <li class="steppy-index-group">
45
+ <a href="./{{ node.slug }}/{{ current_locale }}.html">
46
+ <span class="steppy-index-eyebrow">{{ group_eyebrow }}</span>
47
+ <span class="steppy-index-title">{{ pick_locale(node.title, current_locale, default_locale) }}</span>
48
+ <span class="steppy-index-meta">{{ node.children | length }} {{ guides_label }}</span>
49
+ {% set desc = pick_locale(node.description, current_locale, default_locale) %}
50
+ {% if desc %}
51
+ <span class="steppy-index-desc">{{ desc }}</span>
52
+ {% endif %}
53
+ </a>
54
+ </li>
55
+ {% else %}
56
+ <li>
57
+ <a href="./{{ node.slug }}/{{ current_locale }}.html">
58
+ <span class="steppy-index-title">{{ pick_locale(node.title, current_locale, default_locale) }}</span>
59
+ </a>
60
+ </li>
61
+ {% endif %}
62
+ {% endfor %}
63
+ </ul>
64
+ {% if handbook_url %}
65
+ <p class="steppy-index-handbook">
66
+ <a href="{{ handbook_url }}">{{ handbook_label | default('Full handbook', true) }}</a>
67
+ {% if handbook_pdf_url %}
68
+ <a class="steppy-pdf-link" href="{{ handbook_pdf_url }}" download>PDF</a>
69
+ {% endif %}
70
+ </p>
71
+ {% endif %}
72
+ </section>
73
+ </main>
74
+
75
+ <footer class="steppy-site-footer">
76
+ <p>Generated by Steppy.</p>
77
+ </footer>
78
+ </body>
79
+ </html>
@@ -0,0 +1,100 @@
1
+ <!doctype html>
2
+ <html lang="{{ html_lang | default(default_locale, true) }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{ pick_locale(entry.title, default_locale, default_locale) }} — {{ pick_locale(site_title, default_locale, default_locale) }}</title>
7
+ {% include "static/_seo_head.html.j2" %}
8
+ <link rel="stylesheet" href="../assets/guides.css">
9
+ <style>
10
+ body {
11
+ margin: 0;
12
+ font-family: system-ui, -apple-system, sans-serif;
13
+ background: #f8fafc;
14
+ color: #0f172a;
15
+ }
16
+ /* Wider than the 900px editorial column the guide pages use, on purpose:
17
+ this page is an app-like widget rather than prose, and the player's
18
+ sidebar template spends a fixed 340px of it on the step rail before the
19
+ screenshot gets any. At 1024px a 1440px-wide desktop capture rendered at
20
+ 44%; here it reaches ~73%. */
21
+ .steppy-interactive-shell {
22
+ max-width: min(1440px, 100vw - 32px);
23
+ margin: 0 auto;
24
+ padding: 24px 16px;
25
+ }
26
+ .steppy-interactive-header {
27
+ display: flex;
28
+ align-items: baseline;
29
+ gap: 16px;
30
+ margin-bottom: 16px;
31
+ flex-wrap: wrap;
32
+ }
33
+ .steppy-interactive-header h1 {
34
+ margin: 0;
35
+ font-size: 24px;
36
+ font-weight: 600;
37
+ }
38
+ .steppy-interactive-back {
39
+ color: #2563eb;
40
+ text-decoration: none;
41
+ font-size: 14px;
42
+ }
43
+ .steppy-interactive-back:hover { text-decoration: underline; }
44
+ steppy-guide { display: block; }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <div class="steppy-interactive-shell">
49
+ <header class="steppy-interactive-header">
50
+ <a class="steppy-interactive-back" href="{{ index_href }}">‹ {{ pick_locale(site_title, default_locale, default_locale) }}</a>
51
+ <h1>{{ pick_locale(entry.title, default_locale, default_locale) }}</h1>
52
+ {% include "static/_mode_links.html.j2" %}
53
+ {% if layouts | length > 1 %}
54
+ <nav class="steppy-layout-switch" aria-label="Layout">
55
+ <ul>
56
+ {% for layout in layouts %}
57
+ <li>
58
+ <button type="button" data-layout="{{ layout }}" {% if layout == default_layout %}aria-current="true"{% endif %}>{{ layout | capitalize }}</button>
59
+ </li>
60
+ {% endfor %}
61
+ </ul>
62
+ </nav>
63
+ {% endif %}
64
+ </header>
65
+ <steppy-guide src="./guide.json" locale="{{ default_locale }}"{% if default_layout %} variant="{{ default_layout }}"{% endif %}{% if layouts | length > 1 %} hide-layout-switcher{% endif %}{% if cursor_mode %} cursor="{{ cursor_mode }}"{% endif %} template="{{ default_template }}"></steppy-guide>
66
+ </div>
67
+ {% if layouts | length > 1 %}
68
+ <script>
69
+ (function () {
70
+ const persistKey = 'steppy:active-layout';
71
+ const layouts = {{ layouts | tojson }};
72
+ const guide = document.querySelector('steppy-guide');
73
+ const persisted = (function () {
74
+ try { return localStorage.getItem(persistKey); } catch (_) { return null; }
75
+ })();
76
+ const initial = (persisted && layouts.indexOf(persisted) !== -1)
77
+ ? persisted
78
+ : {{ default_layout | tojson }};
79
+ function applyLayout(name) {
80
+ if (layouts.indexOf(name) === -1) return;
81
+ if (guide) guide.setAttribute('variant', name);
82
+ document.querySelectorAll('.steppy-layout-switch button').forEach(function (b) {
83
+ if (b.getAttribute('data-layout') === name) {
84
+ b.setAttribute('aria-current', 'true');
85
+ } else {
86
+ b.removeAttribute('aria-current');
87
+ }
88
+ });
89
+ try { localStorage.setItem(persistKey, name); } catch (_) {}
90
+ }
91
+ applyLayout(initial);
92
+ document.querySelectorAll('.steppy-layout-switch button').forEach(function (b) {
93
+ b.addEventListener('click', function () { applyLayout(b.getAttribute('data-layout')); });
94
+ });
95
+ })();
96
+ </script>
97
+ {% endif %}
98
+ <script type="module" src="../assets/steppy-player.js"></script>
99
+ </body>
100
+ </html>
@@ -0,0 +1,64 @@
1
+ <!doctype html>
2
+ <html lang="{{ html_lang | default(current_locale, true) }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{ pick_locale(onboarding.title, current_locale, default_locale) }} — {{ pick_locale(site_title, current_locale, default_locale) }}</title>
7
+ {% include "static/_seo_head.html.j2" %}
8
+ <link rel="stylesheet" href="../assets/guides.css">
9
+ </head>
10
+ <body>
11
+ <header class="steppy-site-header">
12
+ <a class="steppy-site-title" href="{{ index_href }}">{{ pick_locale(site_title, current_locale, default_locale) }}</a>
13
+ <nav class="steppy-breadcrumb" aria-label="Breadcrumb">
14
+ <a href="{{ index_href }}">Home</a>
15
+ <span class="steppy-breadcrumb-sep">›</span>
16
+ <span aria-current="page">{{ pick_locale(onboarding.title, current_locale, default_locale) }}</span>
17
+ </nav>
18
+ {% if locales | length > 1 %}
19
+ <nav class="steppy-locale-switch" aria-label="Language">
20
+ <ul>
21
+ {% for link in lang_links %}
22
+ <li>
23
+ {% if link.active %}
24
+ <span aria-current="true">{{ link.label }}</span>
25
+ {% else %}
26
+ <a href="{{ link.href }}">{{ link.label }}</a>
27
+ {% endif %}
28
+ </li>
29
+ {% endfor %}
30
+ </ul>
31
+ </nav>
32
+ {% endif %}
33
+ </header>
34
+
35
+ <main class="steppy-main">
36
+ <section class="steppy-onboarding">
37
+ <h1>{{ pick_locale(onboarding.title, current_locale, default_locale) }}</h1>
38
+ {% set desc = pick_locale(onboarding.description, current_locale, default_locale) %}
39
+ {% if desc %}
40
+ <p class="steppy-onboarding-description">{{ desc }}</p>
41
+ {% endif %}
42
+ <ol class="steppy-onboarding-steps">
43
+ {% for step in steps %}
44
+ <li>
45
+ <a href="../{{ step.slug }}/{{ current_locale }}.html?onboarding=1">
46
+ <span class="steppy-onboarding-step-number">{{ loop.index }}</span>
47
+ <span class="steppy-onboarding-step-title">{{ pick_locale(step.title, current_locale, default_locale) }}</span>
48
+ </a>
49
+ </li>
50
+ {% endfor %}
51
+ </ol>
52
+ {% if steps %}
53
+ <p class="steppy-onboarding-cta">
54
+ <a class="steppy-onboarding-start" href="../{{ steps[0].slug }}/{{ current_locale }}.html?onboarding=1">{{ start_label }}</a>
55
+ </p>
56
+ {% endif %}
57
+ </section>
58
+ </main>
59
+
60
+ <footer class="steppy-site-footer">
61
+ <p>Generated by Steppy.</p>
62
+ </footer>
63
+ </body>
64
+ </html>
@@ -0,0 +1,145 @@
1
+ <!doctype html>
2
+ <html lang="{{ html_lang | default(locale, true) }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{ pick_locale(entry.title, locale, default_locale) }} — {{ pick_locale(site_title, locale, default_locale) }}</title>
7
+ {% include "static/_seo_head.html.j2" %}
8
+ <link rel="stylesheet" href="../assets/guides.css">
9
+ </head>
10
+ <body{% if layouts %} data-active-layout="{{ default_layout }}"{% endif %}>
11
+ <header class="steppy-site-header">
12
+ <a class="steppy-site-title" href="{{ index_href }}">{{ pick_locale(site_title, locale, default_locale) }}</a>
13
+ <nav class="steppy-breadcrumb" aria-label="Breadcrumb">
14
+ <a href="{{ index_href }}">Home</a>
15
+ <span class="steppy-breadcrumb-sep">›</span>
16
+ {% if group %}
17
+ <a href="../{{ group.slug }}/{{ locale }}.html">{{ pick_locale(group.title, locale, default_locale) }}</a>
18
+ <span class="steppy-breadcrumb-sep">›</span>
19
+ {% endif %}
20
+ <span aria-current="page">{{ pick_locale(entry.title, locale, default_locale) }}</span>
21
+ </nav>
22
+ {% if locales | length > 1 %}
23
+ <nav class="steppy-locale-switch" aria-label="Language">
24
+ <ul>
25
+ {% for link in lang_links %}
26
+ <li>
27
+ {% if link.active %}
28
+ <span aria-current="true">{{ link.label }}</span>
29
+ {% else %}
30
+ <a href="{{ link.href }}">{{ link.label }}</a>
31
+ {% endif %}
32
+ </li>
33
+ {% endfor %}
34
+ </ul>
35
+ </nav>
36
+ {% endif %}
37
+ {% if pdf_url %}
38
+ <a class="steppy-pdf-link" href="{{ pdf_url }}" download>PDF</a>
39
+ {% endif %}
40
+ {% include "static/_mode_links.html.j2" %}
41
+ {% if layouts | length > 1 %}
42
+ <nav class="steppy-layout-switch" aria-label="Layout">
43
+ <ul>
44
+ {% for layout in layouts %}
45
+ <li>
46
+ <button type="button" data-layout="{{ layout }}" {% if layout == default_layout %}aria-current="true"{% endif %}>{{ layout | capitalize }}</button>
47
+ </li>
48
+ {% endfor %}
49
+ </ul>
50
+ </nav>
51
+ {% endif %}
52
+ </header>
53
+
54
+ {% if onboarding_strip %}
55
+ <nav class="steppy-onboarding-strip" aria-label="Onboarding progress">
56
+ <span class="steppy-onboarding-strip-progress">
57
+ <a href="../{{ onboarding_strip.path_slug }}/{{ locale }}.html">{{ pick_locale(onboarding_strip.title, locale, default_locale) }}</a>
58
+ — {{ onboarding_strip.position_label }}
59
+ </span>
60
+ <span class="steppy-onboarding-strip-nav">
61
+ {% if onboarding_strip.prev %}
62
+ <a class="steppy-onboarding-prev" href="../{{ onboarding_strip.prev.slug }}/{{ locale }}.html?onboarding=1">‹ {{ pick_locale(onboarding_strip.prev.title, locale, default_locale) }}</a>
63
+ {% else %}
64
+ <span class="steppy-onboarding-prev steppy-onboarding-disabled" aria-disabled="true">‹ {{ onboarding_strip.prev_label }}</span>
65
+ {% endif %}
66
+ {% if onboarding_strip.next %}
67
+ <a class="steppy-onboarding-next" href="../{{ onboarding_strip.next.slug }}/{{ locale }}.html?onboarding=1">{{ pick_locale(onboarding_strip.next.title, locale, default_locale) }} ›</a>
68
+ {% else %}
69
+ <span class="steppy-onboarding-next steppy-onboarding-disabled" aria-disabled="true">{{ onboarding_strip.done_label }}</span>
70
+ {% endif %}
71
+ </span>
72
+ </nav>
73
+ {% endif %}
74
+
75
+ <main class="steppy-main">
76
+ {% if layouts %}
77
+ {%- for layout in layouts %}
78
+ <div class="steppy-layout{% if layout == default_layout %} steppy-layout-active{% endif %}" data-layout="{{ layout }}">
79
+ {{ fragments_by_layout[layout] | safe }}
80
+ </div>
81
+ {%- endfor %}
82
+ {% else %}
83
+ {{ fragments_by_layout['default'] | safe }}
84
+ {% endif %}
85
+ </main>
86
+
87
+ <footer class="steppy-site-footer">
88
+ <p>Generated by Steppy.</p>
89
+ </footer>
90
+
91
+ <script>
92
+ {% if layouts %}
93
+ window.__STEPPY_STEPS_BY_LAYOUT__ = {{ steps_by_layout | tojson }};
94
+ window.__STEPPY_STEPS__ = window.__STEPPY_STEPS_BY_LAYOUT__[{{ default_layout | tojson }}] || [];
95
+ window.__STEPPY_ACTIVE_LAYOUT__ = {{ default_layout | tojson }};
96
+ {% else %}
97
+ window.__STEPPY_STEPS__ = {{ steps_by_layout['default'] | tojson }};
98
+ {% endif %}
99
+ window.__STEPPY_TIMINGS__ = {{ cursor_timings | tojson }};
100
+ </script>
101
+ {% if layouts | length > 1 %}
102
+ <script>
103
+ (function () {
104
+ const persistKey = 'steppy:active-layout';
105
+ const persisted = (function () {
106
+ try { return localStorage.getItem(persistKey); } catch (_) { return null; }
107
+ })();
108
+ const layouts = {{ layouts | tojson }};
109
+ const initial = (persisted && layouts.indexOf(persisted) !== -1)
110
+ ? persisted
111
+ : {{ default_layout | tojson }};
112
+ function applyLayout(name) {
113
+ if (layouts.indexOf(name) === -1) return;
114
+ document.body.setAttribute('data-active-layout', name);
115
+ document.querySelectorAll('.steppy-layout').forEach(function (el) {
116
+ if (el.getAttribute('data-layout') === name) {
117
+ el.classList.add('steppy-layout-active');
118
+ } else {
119
+ el.classList.remove('steppy-layout-active');
120
+ }
121
+ });
122
+ document.querySelectorAll('.steppy-layout-switch button').forEach(function (b) {
123
+ if (b.getAttribute('data-layout') === name) {
124
+ b.setAttribute('aria-current', 'true');
125
+ } else {
126
+ b.removeAttribute('aria-current');
127
+ }
128
+ });
129
+ if (window.__STEPPY_STEPS_BY_LAYOUT__) {
130
+ window.__STEPPY_STEPS__ = window.__STEPPY_STEPS_BY_LAYOUT__[name] || [];
131
+ window.__STEPPY_ACTIVE_LAYOUT__ = name;
132
+ }
133
+ try { localStorage.setItem(persistKey, name); } catch (_) {}
134
+ document.dispatchEvent(new CustomEvent('steppy:layout-change', { detail: { layout: name } }));
135
+ }
136
+ applyLayout(initial);
137
+ document.querySelectorAll('.steppy-layout-switch button').forEach(function (b) {
138
+ b.addEventListener('click', function () { applyLayout(b.getAttribute('data-layout')); });
139
+ });
140
+ })();
141
+ </script>
142
+ {% endif %}
143
+ <script src="../assets/cursor.js" defer></script>
144
+ </body>
145
+ </html>
@@ -0,0 +1,7 @@
1
+ User-agent: *
2
+ {% if public %}
3
+ Allow: /
4
+ Sitemap: {{ sitemap_url }}
5
+ {% else %}
6
+ Disallow: /
7
+ {% endif %}
@@ -0,0 +1,135 @@
1
+ <!doctype html>
2
+ <html lang="{{ html_lang | default(default_locale, true) }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{ pick_locale(entry.title, default_locale, default_locale) }} — {{ pick_locale(site_title, default_locale, default_locale) }}</title>
7
+ {% include "static/_seo_head.html.j2" %}
8
+ <link rel="stylesheet" href="../assets/guides.css">
9
+ <style>
10
+ body {
11
+ margin: 0;
12
+ font-family: system-ui, -apple-system, sans-serif;
13
+ background: #f8fafc;
14
+ color: #0f172a;
15
+ }
16
+ .steppy-video-shell {
17
+ max-width: 1024px;
18
+ margin: 0 auto;
19
+ padding: 24px 16px;
20
+ }
21
+ .steppy-video-header {
22
+ display: flex;
23
+ align-items: baseline;
24
+ gap: 16px;
25
+ margin-bottom: 16px;
26
+ flex-wrap: wrap;
27
+ }
28
+ .steppy-video-header h1 { margin: 0; font-size: 24px; font-weight: 600; }
29
+ .steppy-video-back { color: #2563eb; text-decoration: none; font-size: 14px; }
30
+ .steppy-video-back:hover { text-decoration: underline; }
31
+ .steppy-video-player {
32
+ display: block;
33
+ width: 100%;
34
+ max-width: 100%;
35
+ background: #000;
36
+ border-radius: 8px;
37
+ }
38
+ .steppy-video-switch ul {
39
+ list-style: none;
40
+ display: flex;
41
+ gap: 8px;
42
+ margin: 0;
43
+ padding: 0;
44
+ }
45
+ .steppy-video-switch button {
46
+ font: inherit;
47
+ font-size: 13px;
48
+ padding: 4px 10px;
49
+ border: 1px solid #cbd5e1;
50
+ border-radius: 999px;
51
+ background: #fff;
52
+ cursor: pointer;
53
+ }
54
+ .steppy-video-switch button[aria-current='true'] {
55
+ background: #0f172a;
56
+ border-color: #0f172a;
57
+ color: #fff;
58
+ }
59
+ .steppy-video-downloads {
60
+ margin-top: 12px;
61
+ font-size: 13px;
62
+ color: #64748b;
63
+ }
64
+ .steppy-video-downloads a { color: #2563eb; }
65
+ </style>
66
+ </head>
67
+ <body>
68
+ <div class="steppy-video-shell">
69
+ <header class="steppy-video-header">
70
+ <a class="steppy-video-back" href="{{ index_href }}">‹ {{ pick_locale(site_title, default_locale, default_locale) }}</a>
71
+ <h1>{{ pick_locale(entry.title, default_locale, default_locale) }}</h1>
72
+ {% include "static/_mode_links.html.j2" %}
73
+ {% if variants | length > 1 %}
74
+ <nav class="steppy-video-switch" aria-label="Version">
75
+ <ul>
76
+ {% for variant in variants %}
77
+ <li>
78
+ <button type="button" data-variant="{{ variant.key }}" {% if loop.first %}aria-current="true"{% endif %}>{{ variant.label }}</button>
79
+ </li>
80
+ {% endfor %}
81
+ </ul>
82
+ </nav>
83
+ {% endif %}
84
+ </header>
85
+
86
+ <video
87
+ id="steppy-video-player"
88
+ class="steppy-video-player"
89
+ controls
90
+ preload="metadata"
91
+ playsinline
92
+ poster="{{ variants[0].poster }}"
93
+ >
94
+ <source src="{{ variants[0].src }}" type="video/mp4">
95
+ {% for variant in variants %}
96
+ <track kind="captions" srclang="{{ variant.locale }}" label="{{ variant.label }}" src="{{ variant.captions }}"{% if loop.first %} default{% endif %}>
97
+ {% endfor %}
98
+ </video>
99
+
100
+ <p class="steppy-video-downloads">
101
+ <a id="steppy-video-download" href="{{ variants[0].src }}" download>Download MP4</a>
102
+ </p>
103
+ </div>
104
+ {% if variants | length > 1 %}
105
+ <script>
106
+ (function () {
107
+ // Swapping <source> alone does nothing once the element has loaded — the
108
+ // src has to move onto the <video> itself, followed by load().
109
+ const variants = {{ variants | tojson }};
110
+ const player = document.getElementById('steppy-video-player');
111
+ const download = document.getElementById('steppy-video-download');
112
+ function apply(key) {
113
+ const variant = variants.find(function (v) { return v.key === key; });
114
+ if (!variant || !player) return;
115
+ player.pause();
116
+ player.setAttribute('src', variant.src);
117
+ player.setAttribute('poster', variant.poster);
118
+ player.load();
119
+ if (download) download.setAttribute('href', variant.src);
120
+ document.querySelectorAll('.steppy-video-switch button').forEach(function (b) {
121
+ if (b.getAttribute('data-variant') === key) {
122
+ b.setAttribute('aria-current', 'true');
123
+ } else {
124
+ b.removeAttribute('aria-current');
125
+ }
126
+ });
127
+ }
128
+ document.querySelectorAll('.steppy-video-switch button').forEach(function (b) {
129
+ b.addEventListener('click', function () { apply(b.getAttribute('data-variant')); });
130
+ });
131
+ })();
132
+ </script>
133
+ {% endif %}
134
+ </body>
135
+ </html>