jackherd 1.7.1 → 1.8.0

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 (153) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/INSTALL.md +2 -0
  3. package/{NOTICE → NOTICE.txt} +1 -1
  4. package/README.md +94 -1033
  5. package/assets/icons/bark-dark.svg +1 -1
  6. package/assets/icons/bark.svg +1 -1
  7. package/assets/icons/bowl-dark.svg +1 -1
  8. package/assets/icons/bowl.svg +1 -1
  9. package/assets/icons/decide-dark.svg +1 -1
  10. package/assets/icons/decide.svg +1 -1
  11. package/assets/icons/ear-dark.svg +1 -1
  12. package/assets/icons/ear.svg +1 -1
  13. package/assets/icons/lanes-dark.svg +1 -1
  14. package/assets/icons/lanes.svg +1 -1
  15. package/assets/icons/plan-dark.svg +1 -1
  16. package/assets/icons/plan.svg +1 -1
  17. package/dist/{chunk-OBUWTQ6Z.js → chunk-M6DDZEW4.js} +24 -10
  18. package/dist/config.d.ts +11 -5
  19. package/dist/config.js +53 -21
  20. package/dist/plan-format.d.ts +73 -7
  21. package/dist/plan-format.js +180 -100
  22. package/docs/workflow-dark.svg +1 -1
  23. package/docs/workflow.svg +1 -1
  24. package/herdr-plugin/herdr-plugin.toml +1 -1
  25. package/install.sh +1 -1
  26. package/kit/hooks/hooks/approval-gate/herdr-panes.ts +2 -12
  27. package/kit/hooks/hooks/approval-gate/index.ts +10 -8
  28. package/kit/hooks/hooks/approval-gate/run.ts +21 -12
  29. package/kit/hooks/hooks/ask-hint/decision.ts +3 -3
  30. package/kit/hooks/hooks/git-guard/decision.ts +189 -15
  31. package/kit/hooks/hooks/git-guard/index.ts +13 -3
  32. package/kit/hooks/hooks/git-guard/push-target.ts +85 -0
  33. package/kit/hooks/hooks/git-guard/repository.ts +63 -0
  34. package/kit/hooks/hooks/merge-guard/check-free.ts +1 -13
  35. package/kit/hooks/hooks/merge-guard/decision.ts +342 -102
  36. package/kit/hooks/hooks/merge-guard/index.ts +6 -4
  37. package/kit/hooks/hooks/merge-guard/plain-path.ts +15 -0
  38. package/kit/hooks/hooks/merge-guard/plan-approval-record.ts +23 -0
  39. package/kit/hooks/hooks/merge-guard/plan-approval-settings.ts +142 -0
  40. package/kit/hooks/hooks/merge-guard/plan-approval-sources.ts +196 -0
  41. package/kit/hooks/hooks/merge-guard/plan-approval.ts +168 -0
  42. package/kit/hooks/hooks/merge-guard/plan-growth.ts +40 -0
  43. package/kit/hooks/hooks/merge-guard/plan-pointers.ts +194 -0
  44. package/kit/hooks/hooks/merge-guard/pull-request.ts +67 -21
  45. package/kit/hooks/hooks/merge-guard/run.ts +118 -29
  46. package/kit/hooks/hooks/merge-guard/settings.ts +8 -8
  47. package/kit/hooks/hooks/merge-guard/worker.ts +3 -3
  48. package/kit/hooks/hooks/shared/base-branch.ts +23 -0
  49. package/kit/hooks/hooks/shared/board-lanes.ts +93 -0
  50. package/kit/hooks/hooks/shared/command-text-runners.ts +71 -0
  51. package/kit/hooks/hooks/shared/command.ts +14 -2
  52. package/kit/hooks/hooks/shared/commit.ts +2 -0
  53. package/kit/hooks/hooks/shared/dialog-answers.ts +4 -1
  54. package/kit/hooks/hooks/shared/json.ts +10 -0
  55. package/kit/hooks/hooks/shared/lane.ts +57 -0
  56. package/kit/hooks/hooks/shared/once.ts +8 -0
  57. package/kit/hooks/hooks/shared/own-pane.ts +31 -0
  58. package/kit/hooks/hooks/shared/pane-agent.ts +24 -0
  59. package/kit/hooks/hooks/shared/plan-open-phases.ts +114 -0
  60. package/kit/hooks/hooks/shared/program-position.ts +148 -5
  61. package/kit/hooks/hooks/shared/shell-aliases.ts +258 -0
  62. package/kit/hooks/hooks/shared/shell-snapshots.ts +68 -0
  63. package/kit/hooks/hooks/shared/shell.ts +167 -20
  64. package/kit/hooks/hooks.json +11 -2
  65. package/kit/hooks/statusline.sh +29 -16
  66. package/kit/review/skills/diagnose/SKILL.md +1 -1
  67. package/kit/review/skills/review/SKILL.md +2 -0
  68. package/kit/rules/rules/kit/testing.md +2 -5
  69. package/kit/rules/rules/kit/writing-for-agents.md +3 -1
  70. package/kit/workflow/agents/spec-review.md +1 -1
  71. package/kit/workflow/skills/follow/SKILL.md +3 -3
  72. package/kit/workflow/skills/grill/SKILL.md +1 -1
  73. package/kit/workflow/skills/implement/SKILL.md +10 -11
  74. package/kit/workflow/skills/orchestrate/SKILL.md +155 -117
  75. package/kit/workflow/skills/orchestrate/references/coordinator-handover.md +17 -0
  76. package/kit/workflow/skills/orchestrate/references/herd.md +27 -0
  77. package/kit/workflow/skills/orchestrate/references/plan-commit.md +22 -0
  78. package/kit/workflow/skills/orchestrate/references/sensitive-input.md +23 -0
  79. package/kit/workflow/skills/plan/SKILL.md +5 -2
  80. package/kit/workflow/skills/plan/references/template-English.md +6 -0
  81. package/kit/workflow/skills/plan/references/template-German.md +6 -0
  82. package/package.json +3 -2
  83. package/recipes/README.md +21 -0
  84. package/recipes/github-issues.md +23 -0
  85. package/recipes/github-projects.md +22 -0
  86. package/recipes/jira-cli.md +26 -0
  87. package/recipes/jira-rest.md +24 -0
  88. package/recipes/linear.md +25 -0
  89. package/recipes/sentry.md +18 -0
  90. package/recipes/sources/jira.ts +51 -0
  91. package/recipes/sources/linear.ts +19 -0
  92. package/recipes/sources/sentry.ts +12 -0
  93. package/recipes/sources.md +80 -0
  94. package/recipes/status-page.md +19 -0
  95. package/schemas/jackherd.schema.json +42 -18
  96. package/src/check/command.ts +2 -2
  97. package/src/check/parallel-report.ts +19 -9
  98. package/src/cli.ts +1 -0
  99. package/src/config/context-shares.ts +130 -0
  100. package/src/config/read-config.ts +12 -4
  101. package/src/config/schema.ts +75 -20
  102. package/src/context/auto-compact.ts +68 -0
  103. package/src/context/command.ts +68 -17
  104. package/src/context/coordinator-name.ts +27 -9
  105. package/src/context/index.ts +0 -1
  106. package/src/context/level.ts +18 -11
  107. package/src/context/report.ts +6 -3
  108. package/src/plan-format/dependencies.ts +144 -32
  109. package/src/plan-format/index.ts +6 -0
  110. package/src/plan-format/lanes.ts +6 -0
  111. package/src/plan-format/progress.ts +19 -8
  112. package/src/plans/command.ts +14 -5
  113. package/src/plans/current-phase.ts +63 -7
  114. package/src/plans/current-task.ts +51 -0
  115. package/src/plans/ensure-panes.ts +25 -8
  116. package/src/plans/frame.ts +99 -0
  117. package/src/plans/git.ts +8 -0
  118. package/src/plans/herd-memory.ts +107 -0
  119. package/src/plans/lane-plans.ts +5 -3
  120. package/src/plans/lane-summary.ts +222 -0
  121. package/src/plans/layout-phase.ts +148 -0
  122. package/src/plans/layout-summary.ts +140 -0
  123. package/src/plans/layout.ts +164 -420
  124. package/src/plans/live.ts +11 -6
  125. package/src/plans/plan-panes.ts +15 -0
  126. package/src/plans/planned.ts +9 -42
  127. package/src/plans/selection.ts +2 -6
  128. package/src/plans/settings.ts +2 -7
  129. package/src/plans/snapshot.ts +38 -39
  130. package/src/plans/symbols.ts +2 -2
  131. package/src/plans/texts.ts +32 -31
  132. package/src/plans/view.ts +0 -1
  133. package/src/plans/worked-phases.ts +2 -2
  134. package/src/settled/board-row.ts +133 -0
  135. package/src/settled/check.ts +129 -0
  136. package/src/settled/command.ts +65 -0
  137. package/src/settled/evidence-version.ts +14 -0
  138. package/src/settled/options.ts +69 -0
  139. package/src/settled/send.ts +27 -0
  140. package/src/settled/settle.ts +87 -0
  141. package/src/settled/topics.ts +56 -0
  142. package/src/sync/render.ts +13 -4
  143. package/src/sync/sync.ts +25 -5
  144. package/src/terminal/herdr.ts +46 -10
  145. package/src/terminal/none.ts +1 -0
  146. package/src/terminal/types.ts +15 -2
  147. package/src/vocabulary/index.ts +47 -11
  148. package/src/watch/appointments.ts +205 -0
  149. package/src/watch/command.ts +62 -19
  150. package/src/watch/lane-report.ts +11 -3
  151. package/src/watch/texts.ts +35 -59
  152. package/src/watch/watch-events.ts +21 -28
  153. package/kit/hooks/hooks/approval-gate/lane.ts +0 -14
@@ -22,9 +22,19 @@ declare const ADDRESS_FORM: Record<PlanLanguage, Record<ResponseAddress, string>
22
22
  * a config value.
23
23
  */
24
24
  declare const SELF_MERGE_LABEL: Record<PlanLanguage, string>;
25
+ /**
26
+ * The options of the question that approves a plan for all its lanes (``Plan `<path>` ``) and of the one that revokes
27
+ * that approval. The merge guard hook lifts its question for a plain merge of such a lane only when the newest answer
28
+ * to a question naming the plan is exactly `approve`, so the labels are fixed per language and not a config value.
29
+ */
30
+ declare const PLAN_APPROVAL_LABELS: Record<PlanLanguage, {
31
+ approve: string;
32
+ revoke: string;
33
+ }>;
25
34
  /**
26
35
  * The words of an approval: the two option labels of the coordinator's question about a lane's call, which the
27
- * approval gate hook matches exactly, and what the hook tells the lane when it denies. Fixed per language, not config.
36
+ * approval gate hook matches exactly and which follow the response language, and what the hook tells the lane when it
37
+ * denies, which is English. Fixed per language, not config.
28
38
  */
29
39
  declare const APPROVAL_TEXTS: Record<PlanLanguage, {
30
40
  allow: string;
@@ -32,6 +42,21 @@ declare const APPROVAL_TEXTS: Record<PlanLanguage, {
32
42
  pending: string;
33
43
  denied: string;
34
44
  }>;
45
+ /**
46
+ * The board section of agreed times, which the watcher reads: its heading, the header names of its columns (when,
47
+ * action, origin, status; found by name, in any order) and the lower-case words a status cell of a still open
48
+ * appointment starts with.
49
+ */
50
+ declare const APPOINTMENT_VOCABULARY: Record<PlanLanguage, {
51
+ heading: string;
52
+ columns: {
53
+ when: string;
54
+ action: string;
55
+ origin: string;
56
+ status: string;
57
+ };
58
+ open: readonly string[];
59
+ }>;
35
60
  declare const isPlanLanguage: (value: string) => value is PlanLanguage;
36
61
  declare const PLAN_STATUS: {
37
62
  readonly done: "done";
@@ -76,6 +101,8 @@ type PlanVocabulary = {
76
101
  noReviewGate: string;
77
102
  /** Marks a lane as closed when its name cell contains it. */
78
103
  laneClosed: string;
104
+ /** Opens the line a lane's hand-over file must hold as `<word>: 0`, so the coordinator hands over only after a wave. */
105
+ openSubagents: string;
79
106
  /** Words a starts-after cell begins with when the lane may start at once. */
80
107
  startsNow: readonly string[];
81
108
  /** Opens the parenthesis by which a board row's phases cell records a user's side order in prose. */
@@ -190,7 +217,8 @@ declare const parsePlanPhases: (markdown: string, language: PlanLanguage) => Pla
190
217
  * A row of the dependency table. `after` holds the phases it waits for, `external` the entries that name no phase
191
218
  * (`User: Zuruf`) or a range that cannot be expanded; `writes` are the paths of the writes cell (`''` is the whole
192
219
  * repository), `writesLike` the phases whose sets the cell takes over (`wie 15 und 16`); `serialMarkers` are the
193
- * entries of the marker cell that keep two phases apart.
220
+ * entries of the marker cell that keep two phases apart, `releases` the packages its release markers name
221
+ * (`Release jackherd 1.8.0` is `jackherd`).
194
222
  */
195
223
  type PhaseDependency = {
196
224
  id: string;
@@ -199,6 +227,13 @@ type PhaseDependency = {
199
227
  writes: string[];
200
228
  writesLike: string[];
201
229
  serialMarkers: string[];
230
+ releases: string[];
231
+ };
232
+ /** The open phases in waves (each starts once the ones before it are done), the longest chain, and the open phases no wave holds. */
233
+ type PhaseSchedule = {
234
+ waves: string[][];
235
+ criticalPath: string[];
236
+ unplaced: string[];
202
237
  };
203
238
  /** A phase heading (1-based line) the dependency table has no row for. */
204
239
  type PhaseWithoutRow = PhaseHeading;
@@ -215,15 +250,32 @@ declare const phasesWithoutDependencyRow: (markdown: string, language: PlanLangu
215
250
  * every phase in its `Depends on` cell is, and the cell names no external gate: nothing in the plan says such a gate
216
251
  * holds, so the phase stays out and the caller asks. Two phases share a group only when no path of one lies at or
217
252
  * below a path of the other (a root path lies below nothing and above everything) and they share no serial marker,
218
- * compared without case (a release marker does not count). A `wie N` writes cell takes the set of phase N; a phase
219
- * whose set is empty, whose `wie` target is unknown, or whose `wie` chain is a cycle, cannot be shown disjoint and
220
- * stays out. Whether two phases collide in content, or in the release files, is not decided here.
253
+ * compared without case, and release no package the other releases: a release marker naming it (`Release jackherd
254
+ * 1.8.0`) or a write of its release files (`CHANGELOG.md`, `package.json` in its directory), the package compared by
255
+ * its name without scope and the last segment of its directory. A release marker without a name keeps nobody apart.
256
+ * A `wie N` writes cell takes the set of phase N; a phase whose set is empty, whose `wie` target is unknown, or whose
257
+ * `wie` chain is a cycle, cannot be shown disjoint and stays out. Whether two phases collide in content is not decided here.
221
258
  */
222
259
  declare const parallelGroups: (rows: readonly PhaseDependency[], doneIds: ReadonlySet<string>) => string[][];
260
+ /**
261
+ * The whole sequence of parallel groups, computed ahead: the first wave is the first group of `parallelGroups`, each
262
+ * later wave the first group once the waves before it are done. A phase `parallelGroups` never offers (an external
263
+ * gate, an empty or unresolvable writes set) is in no wave, and neither is a phase waiting for it.
264
+ */
265
+ declare const phaseWaves: (rows: readonly PhaseDependency[], doneIds: ReadonlySet<string>) => string[][];
266
+ /**
267
+ * The longest chain of open phases in `waves`, counted in phases: a phase follows another that it depends on, or one
268
+ * of an earlier wave it shares a serial marker or a package release with. A shared path alone does not chain, so a
269
+ * plan with more waves than its critical path is long loses the difference to overlapping writes. The first longest
270
+ * chain in wave order; empty without waves.
271
+ */
272
+ declare const criticalPath: (rows: readonly PhaseDependency[], waves: readonly string[][]) => string[];
223
273
  /** The phases as a report names them, side by side: `P19 ∥ P20`. */
224
274
  declare const phasesText: (phases: readonly string[]) => string;
225
- /** `parallelGroups` of the plan's own dependency table and ticks (a phase is done when every box is ticked); null for a plan without a table. */
275
+ /** `parallelGroups` of the plan's own dependency table and ticks (a phase is done once `isPhaseFinished`); null for a plan without a table. */
226
276
  declare const parallelGroupsOfPlan: (markdown: string, language: PlanLanguage) => string[][] | null;
277
+ /** The waves, the critical path and the open phases in no wave, of the plan's own table and ticks; null for a plan without a table. */
278
+ declare const wavesOfPlan: (markdown: string, language: PlanLanguage) => PhaseSchedule | null;
227
279
 
228
280
  /** The review-gate box `Dokumentation aktualisiert …` (`Documentation updated …`): a gate item starting with the documentation word. */
229
281
  declare const isDocumentationGateItem: ({ gate, text }: ChecklistItem) => boolean;
@@ -282,6 +334,10 @@ type OpenLaneRow = {
282
334
  };
283
335
  /** Sprint tables name a phase `P4`; the sidebar title prints it the same way. */
284
336
  declare const PHASE_ID_PREFIX = "P";
337
+ /** A phase as plans and reports name it: `P4`. */
338
+ declare const phaseLabel: (id: string) => string;
339
+ /** Phases as plans and reports name them, joined by `separator`: `P3 P4`, `P19 ∥ P20`. */
340
+ declare const phaseLabels: (ids: readonly string[], separator: string) => string;
285
341
  /** The phase ids a phases cell of the lanes table names, read as `parseSprintLanes` reads them. */
286
342
  declare const phaseIdsOfCell: (cell: string, language: PlanLanguage) => string[];
287
343
  declare const laneNameOf: (cell: string, previous: string | null) => string | null;
@@ -396,6 +452,16 @@ declare const sumPhases: (phases: readonly PhaseProgress[]) => {
396
452
  done: number;
397
453
  total: number;
398
454
  };
455
+ /** The progress of the phase `id` of a plan; a phase the plan lacks has a null title and no checkboxes. */
456
+ declare const phaseProgress: (phases: readonly PlanPhase[], id: string) => PhaseProgress;
457
+ /**
458
+ * A phase is finished once every checkbox it has is ticked, its tasks and its review gate alike: `/implement` and
459
+ * `jh check --parallel` read it so, and an open documentation box keeps the phase open. A phase without a checkbox is
460
+ * never finished.
461
+ */
462
+ declare const isPhaseFinished: ({ items }: Pick<PlanPhase, "items">) => boolean;
463
+ /** A phase with a checkbox that is not finished; a phase without a checkbox is neither. */
464
+ declare const isPhaseOpen: (phase: Pick<PlanPhase, "items">) => boolean;
399
465
  /**
400
466
  * A phase the lane ticked to the end in its worktree while the base branch does not carry those ticks yet: still
401
467
  * the lane's work until it is merged, not a finished phase.
@@ -444,4 +510,4 @@ declare const readSprintDocument: (markdown: string, language: PlanLanguage) =>
444
510
  */
445
511
  declare const readPhaseDependencies: (planMarkdown: string, language: PlanLanguage) => Record<string, string>;
446
512
 
447
- export { ADDRESS_FORM, APPROVAL_TEXTS, type ChecklistItem, type ColumnModel, type DeferredItem, type DriftFinding, LANGUAGE_NAME, type LaneProgress, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseTally, type PhaseWithoutGate, type PhaseWithoutRow, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RESPONSE_ADDRESS, type ResponseAddress, type RowWithProse, type RowWithoutPhase, SELF_MERGE_LABEL, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusWord, type StepProgress, activeItem, boardRowsWithProse, boardRowsWithoutPhase, classifyStatus, columnModels, countDecisions, currentPhase, currentPhaseOf, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPlanLanguage, isReviewGateLine, isTickedUnmerged, laneNameOf, laneProgress, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutReviewGate, onlyDocumentationOpen, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parsePlanPhases, parseSprintLanes, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdsOfCell, phaseIdsOfPlan, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortTitle, sprintTitleOf, sumPhases, tallyPhases, withoutRepeats };
513
+ export { ADDRESS_FORM, APPOINTMENT_VOCABULARY, APPROVAL_TEXTS, type ChecklistItem, type ColumnModel, type DeferredItem, type DriftFinding, LANGUAGE_NAME, type LaneProgress, type MergedPhaseWithOpenBoxes, type MisplacedDeferral, PHASE_CELL_LIMIT, PHASE_ID_PREFIX, PLAN_APPROVAL_LABELS, PLAN_LANGUAGE, PLAN_STATUS, PLAN_VOCABULARY, PROSE_REASON, type PhaseDependency, type PhaseHeading, type PhaseProgress, type PhaseSchedule, type PhaseTally, type PhaseWithoutGate, type PhaseWithoutRow, type PlanHeader, type PlanLanguage, type PlanPhase, type PlanStatus, type PlanStep, type PlanVocabulary, type PlansByHref, type ProseReason, RESPONSE_ADDRESS, type ResponseAddress, type RowWithProse, type RowWithoutPhase, SELF_MERGE_LABEL, type SprintDocument, type SprintLane, type SprintPlanRow, type SprintPlanTable, type StatusWord, type StepProgress, activeItem, boardRowsWithProse, boardRowsWithoutPhase, classifyStatus, columnModels, countDecisions, criticalPath, currentPhase, currentPhaseOf, deferralTarget, differsFromColumn, endsPhase, findDrift, hasLanesTable, isCheckboxLine, isDocumentationGateItem, isOpenCheckboxLine, isPhaseFinished, isPhaseOpen, isPlanLanguage, isReviewGateLine, isTickedUnmerged, laneNameOf, laneProgress, mergedPhasesWithOpenBoxes, misplacedDeferrals, newPhasesWithoutReviewGate, onlyDocumentationOpen, parallelGroups, parallelGroupsOfPlan, parseDependencyTable, parsePlanPhases, parseSprintLanes, phaseHeading, phaseHeadingPattern, phaseHeadings, phaseIdsOfCell, phaseIdsOfPlan, phaseLabel, phaseLabels, phaseProgress, phaseWaves, phasesText, phasesWithoutDependencyRow, readPhaseDependencies, readPlanHeader, readSprintDocument, resolvePlanPath, sectionHeading, shortTitle, sprintTitleOf, sumPhases, tallyPhases, wavesOfPlan, withoutRepeats };
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  ADDRESS_FORM,
3
+ APPOINTMENT_VOCABULARY,
3
4
  APPROVAL_TEXTS,
4
5
  LANGUAGE_NAME,
6
+ PLAN_APPROVAL_LABELS,
5
7
  PLAN_LANGUAGE,
6
8
  PLAN_STATUS,
7
9
  PLAN_VOCABULARY,
@@ -11,7 +13,7 @@ import {
11
13
  isPlanLanguage,
12
14
  phaseHeading,
13
15
  sectionHeading
14
- } from "./chunk-OBUWTQ6Z.js";
16
+ } from "./chunk-M6DDZEW4.js";
15
17
 
16
18
  // src/plan-format/lanes.ts
17
19
  import { posix } from "path";
@@ -97,6 +99,8 @@ var LANE_NAME = /`([^`]+)`/;
97
99
  var CONTINUATION = "\u21B3";
98
100
  var PLAN_LINK = /\[([^\]]+)\]\(([^)]+)\)/;
99
101
  var PHASE_ID_PREFIX = "P";
102
+ var phaseLabel = (id) => `${PHASE_ID_PREFIX}${id}`;
103
+ var phaseLabels = (ids, separator) => ids.map(phaseLabel).join(separator);
100
104
  var PHASES_COLUMN = 3;
101
105
  var CLAUSE_SEPARATOR = /([;,]|\.(?=\s))\s*/;
102
106
  var ENTRY_END = /* @__PURE__ */ new Set([";", "."]);
@@ -348,6 +352,94 @@ var parsePlanPhases = (markdown, language) => {
348
352
  return withArrivedItems(withPointedSections(phases, pointers, markdown));
349
353
  };
350
354
 
355
+ // src/plan-format/progress.ts
356
+ var sumPhases = (phases) => ({
357
+ done: phases.reduce((sum, phase) => sum + phase.done, 0),
358
+ total: phases.reduce((sum, phase) => sum + phase.total, 0)
359
+ });
360
+ var phaseById = (phases, id) => phases.find((phase) => phase.id === id);
361
+ var phaseProgress = (phases, id) => {
362
+ const phase = phaseById(phases, id);
363
+ const items = phase?.items ?? [];
364
+ return {
365
+ id,
366
+ title: phase?.title ?? null,
367
+ done: items.filter((item) => item.done).length,
368
+ total: items.length,
369
+ items,
370
+ ...phase?.deferred ? { deferred: phase.deferred } : {},
371
+ ...phase?.touched ? { touched: true } : {}
372
+ };
373
+ };
374
+ var isPhaseFinished = ({ items }) => items.length > 0 && items.every(({ done }) => done);
375
+ var isPhaseOpen = (phase) => phase.items.length > 0 && !isPhaseFinished(phase);
376
+ var isWritten = ({ total }) => total > 0;
377
+ var newestWrittenStepIndex = (steps) => steps.reduce((last, { phases }, index) => phases.some(isWritten) ? index : last, -1);
378
+ var newestRowIndex = (steps) => steps.reduce((last, { step }, index) => step.withoutRow ? last : index, -1);
379
+ var isTouched = (phase) => phase.touched === true && isWritten(phase);
380
+ var isTickedUnmerged = (phase) => isTouched(phase) && isPhaseFinished(phase);
381
+ var newestStepWith = (steps, pick) => {
382
+ for (let stepIndex = steps.length - 1; stepIndex >= 0; stepIndex--) {
383
+ const phase = pick(steps[stepIndex].phases);
384
+ if (phase) return { stepIndex, phaseId: phase.id };
385
+ }
386
+ return null;
387
+ };
388
+ var earlierPhase = (steps, before, id) => {
389
+ const planHref = steps[before].step.planHref;
390
+ for (let stepIndex = before - 1; stepIndex >= 0; stepIndex--) {
391
+ if (steps[stepIndex].step.planHref !== planHref) continue;
392
+ const phase = phaseById(steps[stepIndex].phases, id);
393
+ if (phase) return { phase, current: { stepIndex, phaseId: phase.id } };
394
+ }
395
+ return null;
396
+ };
397
+ var rowPhases = (steps, stepIndex) => {
398
+ const row = steps[stepIndex];
399
+ if (!row) return [];
400
+ const own = row.phases.map((phase) => ({ phase, current: { stepIndex, phaseId: phase.id } }));
401
+ const named = row.step.namedPhaseIds;
402
+ if (!named) return own;
403
+ return named.flatMap((id) => {
404
+ const located = own.find((entry) => entry.phase.id === id) ?? earlierPhase(steps, stepIndex, id);
405
+ return located ? [located] : [];
406
+ });
407
+ };
408
+ var touchedOpenPhaseOf = (phases) => phases.find((phase) => isTouched(phase) && isPhaseOpen(phase));
409
+ var lastTickedUnmergedOf = (phases) => phases.filter(isTickedUnmerged).at(-1);
410
+ var newestRowPhase = (steps) => {
411
+ const located = rowPhases(steps, newestRowIndex(steps)).filter(({ phase: phase2 }) => isWritten(phase2));
412
+ const phases = located.map(({ phase: phase2 }) => phase2);
413
+ const phase = touchedOpenPhaseOf(phases) ?? lastTickedUnmergedOf(phases) ?? phases.find(isPhaseOpen);
414
+ return located.find((entry) => entry.phase === phase)?.current ?? null;
415
+ };
416
+ var phaseFromTicks = (steps) => {
417
+ const touched = newestStepWith(steps, touchedOpenPhaseOf) ?? newestStepWith(steps, lastTickedUnmergedOf);
418
+ if (touched) return touched;
419
+ const lastIndex = newestWrittenStepIndex(steps);
420
+ const phase = steps[lastIndex]?.phases.find(isPhaseOpen);
421
+ return phase ? { stepIndex: lastIndex, phaseId: phase.id } : null;
422
+ };
423
+ var currentPhaseOf = (steps) => newestRowPhase(steps) ?? phaseFromTicks(steps);
424
+ var laneProgress = (lane, plans) => {
425
+ const steps = lane.steps.map((step) => {
426
+ const phases = step.planHref ? plans[step.planHref] : void 0;
427
+ return { step, phases: phases ? step.phaseIds.map((id) => phaseProgress(phases, id)) : [] };
428
+ });
429
+ return { lane, steps, current: currentPhaseOf(steps), ...sumPhases(steps.flatMap((step) => step.phases)) };
430
+ };
431
+ var currentPhase = (progress) => {
432
+ if (!progress.current) return null;
433
+ const { stepIndex, phaseId } = progress.current;
434
+ return phaseById(progress.steps[stepIndex].phases, phaseId) ?? null;
435
+ };
436
+ var activeItem = (phase) => phase.items.find((item) => !item.done) ?? null;
437
+ var shortTitle = (progress) => {
438
+ const phase = currentPhase(progress);
439
+ if (phase) return `${phaseLabel(phase.id)} \xB7 ${phase.done}/${phase.total}`;
440
+ return progress.total > 0 ? `\u2713 ${progress.done}/${progress.total}` : null;
441
+ };
442
+
351
443
  // src/plan-format/dependencies.ts
352
444
  var ID = "(\\d+[a-z]?)";
353
445
  var PHASE_ID = new RegExp(`^\\s*${PHASE_ID_PREFIX}?${ID}\\b`, "i");
@@ -356,6 +448,9 @@ var BACKTICKED = /`([^`]+)`/g;
356
448
  var NO_VALUE = /^[—–-]?$/;
357
449
  var TRAILING_NOTE = /\s*\([^)]*\)\s*$/;
358
450
  var GLOB_CHARACTER = /[*?{[]/;
451
+ var VERSION = /^v?\d+(?:[.\-+][\w.-]*)?$/i;
452
+ var PACKAGE_SCOPE = /^@[^/]+\//;
453
+ var RELEASE_FILE = /(?:^|\/)(?:CHANGELOG\.md|package\.json)$/;
359
454
  var MAX_RANGE = 50;
360
455
  var phaseIdOf = (cell) => PHASE_ID.exec(cell)?.[1]?.toLowerCase() ?? null;
361
456
  var splitOutsideParentheses = (cell, separator) => {
@@ -417,10 +512,26 @@ var writesOf = (cell, language) => ({
417
512
  writes: [...cell.matchAll(BACKTICKED)].flatMap((match) => pathOf(match[1] ?? "") ?? []),
418
513
  writesLike: writesLikeOf(cell, language)
419
514
  });
420
- var markersOf = (cell, language) => {
421
- const release = new RegExp(`^${escapeRegExp(PLAN_VOCABULARY[language].releaseMarker)}(?:\\s|$)`, "i");
422
- return cell.split(";").map((entry) => entry.replace(TRAILING_NOTE, "").trim()).filter((entry) => !NO_VALUE.test(entry) && !release.test(entry));
515
+ var releasePattern = (language) => new RegExp(`^${escapeRegExp(PLAN_VOCABULARY[language].releaseMarker)}(?:\\s|$)`, "i");
516
+ var markerEntries = (cell) => cell.split(";").map((entry) => entry.replace(TRAILING_NOTE, "").trim()).filter((entry) => !NO_VALUE.test(entry));
517
+ var packageKey = (name) => name.replace(PACKAGE_SCOPE, "").toLowerCase();
518
+ var releasedPackageOf = (entry) => {
519
+ const name = entry.split(/\s+/).slice(1).filter((word) => !VERSION.test(word)).join(" ");
520
+ return name ? [packageKey(name)] : [];
521
+ };
522
+ var markerCellOf = (cell, language) => {
523
+ const release = releasePattern(language);
524
+ const entries = markerEntries(cell);
525
+ return {
526
+ serialMarkers: entries.filter((entry) => !release.test(entry)),
527
+ releases: entries.filter((entry) => release.test(entry)).flatMap(releasedPackageOf)
528
+ };
423
529
  };
530
+ var releasedByFiles = (writes) => writes.flatMap((path) => {
531
+ if (!RELEASE_FILE.test(path)) return [];
532
+ const directory = path.split("/").slice(0, -1);
533
+ return [packageKey(directory.at(-1) ?? ".")];
534
+ });
424
535
  var parseDependencyTable = (markdown, language) => {
425
536
  const { sections, columns } = PLAN_VOCABULARY[language];
426
537
  const { header, rows } = firstTableWithHeader(subsectionLines(markdown, sections.dependencies));
@@ -436,7 +547,7 @@ var parseDependencyTable = (markdown, language) => {
436
547
  id,
437
548
  ...dependsOf(cells[after] ?? "", language),
438
549
  ...writesOf(cells[writes] ?? "", language),
439
- serialMarkers: markersOf(cells[marker] ?? "", language)
550
+ ...markerCellOf(cells[marker] ?? "", language)
440
551
  }
441
552
  ];
442
553
  });
@@ -447,6 +558,7 @@ var phasesWithoutDependencyRow = (markdown, language) => {
447
558
  const listed = new Set(table.map(({ id }) => id));
448
559
  return phaseHeadings(markdown, language).filter(({ id }) => !listed.has(id));
449
560
  };
561
+ var longestChain = (chains) => chains.reduce((longest, chain) => chain.length > longest.length ? chain : longest, []);
450
562
  var mergeSets = (sets) => sets.some((set) => set === null) ? null : sets.flatMap((set) => set ?? []);
451
563
  var writeSetOf = (row, byId, chain) => {
452
564
  if (chain.includes(row.id)) return null;
@@ -455,16 +567,19 @@ var writeSetOf = (row, byId, chain) => {
455
567
  return like === null ? null : [...row.writes, ...like];
456
568
  };
457
569
  var overlaps = (left, right) => left === "" || right === "" || left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
458
- var sharesMarker = (first, second) => {
459
- const markers = new Set(second.serialMarkers.map((marker) => marker.trim().toLowerCase()));
460
- return first.serialMarkers.some((marker) => markers.has(marker.trim().toLowerCase()));
570
+ var sharesEntry = (first, second) => {
571
+ const entries = new Set(second.map((entry) => entry.trim().toLowerCase()));
572
+ return first.some((entry) => entries.has(entry.trim().toLowerCase()));
461
573
  };
574
+ var sharesMarker = (first, second) => sharesEntry(first.serialMarkers, second.serialMarkers) || sharesEntry(first.releases, second.releases);
462
575
  var collides = (first, second) => sharesMarker(first, second) || first.writes.some((path) => second.writes.some((other) => overlaps(path, other)));
463
576
  var candidateOf = (row, byId) => {
464
577
  const writes = writeSetOf(row, byId, []);
465
- return writes === null || writes.length === 0 ? [] : [{ id: row.id, writes, serialMarkers: row.serialMarkers }];
578
+ if (writes === null || writes.length === 0) return [];
579
+ const releases = [.../* @__PURE__ */ new Set([...row.releases, ...releasedByFiles(writes)])];
580
+ return [{ id: row.id, writes, serialMarkers: row.serialMarkers, releases }];
466
581
  };
467
- var parallelGroups = (rows, doneIds) => {
582
+ var candidateGroups = (rows, doneIds) => {
468
583
  const byId = new Map(rows.map((row) => [row.id, row]));
469
584
  const ready = rows.filter((row) => !doneIds.has(row.id) && row.external.length === 0 && row.after.every((id) => doneIds.has(id))).flatMap((row) => candidateOf(row, byId));
470
585
  const groups = [];
@@ -473,16 +588,58 @@ var parallelGroups = (rows, doneIds) => {
473
588
  if (group) group.push(candidate);
474
589
  else groups.push([candidate]);
475
590
  }
476
- return groups.map((group) => group.map(({ id }) => id));
591
+ return groups;
592
+ };
593
+ var parallelGroups = (rows, doneIds) => candidateGroups(rows, doneIds).map((group) => group.map(({ id }) => id));
594
+ var phaseWaves = (rows, doneIds) => {
595
+ const waves = [];
596
+ const done = new Set(doneIds);
597
+ let wave = parallelGroups(rows, done)[0];
598
+ while (wave) {
599
+ waves.push(wave);
600
+ for (const id of wave) done.add(id);
601
+ wave = parallelGroups(rows, done)[0];
602
+ }
603
+ return waves;
604
+ };
605
+ var criticalPath = (rows, waves) => {
606
+ const byId = new Map(rows.map((row) => [row.id, row]));
607
+ const candidates = new Map(rows.flatMap((row) => candidateOf(row, byId)).map((each) => [each.id, each]));
608
+ const chains = /* @__PURE__ */ new Map();
609
+ const follows = (later, earlier) => {
610
+ const [first, second] = [candidates.get(earlier), candidates.get(later)];
611
+ const chained = first !== void 0 && second !== void 0 && sharesMarker(first, second);
612
+ return chained || (byId.get(later)?.after.includes(earlier) ?? false);
613
+ };
614
+ waves.forEach((wave, index) => {
615
+ const earlier = waves.slice(0, index).flat();
616
+ for (const id of wave) {
617
+ const before = longestChain(earlier.filter((other) => follows(id, other)).map((other) => chains.get(other) ?? []));
618
+ chains.set(id, [...before, id]);
619
+ }
620
+ });
621
+ return longestChain([...chains.values()]);
477
622
  };
478
- var phasesText = (phases) => phases.map((id) => `${PHASE_ID_PREFIX}${id}`).join(" \u2225 ");
623
+ var phasesText = (phases) => phaseLabels(phases, " \u2225 ");
479
624
  var donePhaseIds = (markdown, language) => new Set(
480
- parsePlanPhases(markdown, language).filter(({ items }) => items.length > 0 && items.every((item) => item.done)).map(({ id }) => id)
625
+ parsePlanPhases(markdown, language).filter(isPhaseFinished).map(({ id }) => id)
481
626
  );
482
627
  var parallelGroupsOfPlan = (markdown, language) => {
483
628
  const table = parseDependencyTable(markdown, language);
484
629
  return table === null ? null : parallelGroups(table, donePhaseIds(markdown, language));
485
630
  };
631
+ var wavesOfPlan = (markdown, language) => {
632
+ const table = parseDependencyTable(markdown, language);
633
+ if (table === null) return null;
634
+ const done = donePhaseIds(markdown, language);
635
+ const waves = phaseWaves(table, done);
636
+ const placed = new Set(waves.flat());
637
+ return {
638
+ waves,
639
+ criticalPath: criticalPath(table, waves),
640
+ unplaced: table.filter(({ id }) => !done.has(id) && !placed.has(id)).map(({ id }) => id)
641
+ };
642
+ };
486
643
 
487
644
  // src/plan-format/documentation-gate.ts
488
645
  var DOCUMENTATION_WORDS = Object.values(PLAN_VOCABULARY).map(
@@ -654,93 +811,6 @@ var boardRowsWithoutPhase = (markdown, language, previousLines) => changedOpenLa
654
811
  ({ phases }) => phaseIdsOfCell(phases, language).length === 0
655
812
  );
656
813
 
657
- // src/plan-format/progress.ts
658
- var sumPhases = (phases) => ({
659
- done: phases.reduce((sum, phase) => sum + phase.done, 0),
660
- total: phases.reduce((sum, phase) => sum + phase.total, 0)
661
- });
662
- var phaseById = (phases, id) => phases.find((phase) => phase.id === id);
663
- var phaseProgress = (phases, id) => {
664
- const phase = phaseById(phases, id);
665
- const items = phase?.items ?? [];
666
- return {
667
- id,
668
- title: phase?.title ?? null,
669
- done: items.filter((item) => item.done).length,
670
- total: items.length,
671
- items,
672
- ...phase?.deferred ? { deferred: phase.deferred } : {},
673
- ...phase?.touched ? { touched: true } : {}
674
- };
675
- };
676
- var isOpen = ({ done, total }) => done < total;
677
- var isWritten = ({ total }) => total > 0;
678
- var newestWrittenStepIndex = (steps) => steps.reduce((last, { phases }, index) => phases.some(isWritten) ? index : last, -1);
679
- var newestRowIndex = (steps) => steps.reduce((last, { step }, index) => step.withoutRow ? last : index, -1);
680
- var isTouched = (phase) => phase.touched === true && isWritten(phase);
681
- var isTickedUnmerged = (phase) => isTouched(phase) && !isOpen(phase);
682
- var newestStepWith = (steps, pick) => {
683
- for (let stepIndex = steps.length - 1; stepIndex >= 0; stepIndex--) {
684
- const phase = pick(steps[stepIndex].phases);
685
- if (phase) return { stepIndex, phaseId: phase.id };
686
- }
687
- return null;
688
- };
689
- var earlierPhase = (steps, before, id) => {
690
- const planHref = steps[before].step.planHref;
691
- for (let stepIndex = before - 1; stepIndex >= 0; stepIndex--) {
692
- if (steps[stepIndex].step.planHref !== planHref) continue;
693
- const phase = phaseById(steps[stepIndex].phases, id);
694
- if (phase) return { phase, current: { stepIndex, phaseId: phase.id } };
695
- }
696
- return null;
697
- };
698
- var rowPhases = (steps, stepIndex) => {
699
- const row = steps[stepIndex];
700
- if (!row) return [];
701
- const own = row.phases.map((phase) => ({ phase, current: { stepIndex, phaseId: phase.id } }));
702
- const named = row.step.namedPhaseIds;
703
- if (!named) return own;
704
- return named.flatMap((id) => {
705
- const located = own.find((entry) => entry.phase.id === id) ?? earlierPhase(steps, stepIndex, id);
706
- return located ? [located] : [];
707
- });
708
- };
709
- var touchedOpenPhaseOf = (phases) => phases.find((phase) => isTouched(phase) && isOpen(phase));
710
- var lastTickedUnmergedOf = (phases) => phases.filter(isTickedUnmerged).at(-1);
711
- var newestRowPhase = (steps) => {
712
- const located = rowPhases(steps, newestRowIndex(steps)).filter(({ phase: phase2 }) => isWritten(phase2));
713
- const phases = located.map(({ phase: phase2 }) => phase2);
714
- const phase = touchedOpenPhaseOf(phases) ?? lastTickedUnmergedOf(phases) ?? phases.find(isOpen);
715
- return located.find((entry) => entry.phase === phase)?.current ?? null;
716
- };
717
- var phaseFromTicks = (steps) => {
718
- const touched = newestStepWith(steps, touchedOpenPhaseOf) ?? newestStepWith(steps, lastTickedUnmergedOf);
719
- if (touched) return touched;
720
- const lastIndex = newestWrittenStepIndex(steps);
721
- const phase = steps[lastIndex]?.phases.find(isOpen);
722
- return phase ? { stepIndex: lastIndex, phaseId: phase.id } : null;
723
- };
724
- var currentPhaseOf = (steps) => newestRowPhase(steps) ?? phaseFromTicks(steps);
725
- var laneProgress = (lane, plans) => {
726
- const steps = lane.steps.map((step) => {
727
- const phases = step.planHref ? plans[step.planHref] : void 0;
728
- return { step, phases: phases ? step.phaseIds.map((id) => phaseProgress(phases, id)) : [] };
729
- });
730
- return { lane, steps, current: currentPhaseOf(steps), ...sumPhases(steps.flatMap((step) => step.phases)) };
731
- };
732
- var currentPhase = (progress) => {
733
- if (!progress.current) return null;
734
- const { stepIndex, phaseId } = progress.current;
735
- return phaseById(progress.steps[stepIndex].phases, phaseId) ?? null;
736
- };
737
- var activeItem = (phase) => phase.items.find((item) => !item.done) ?? null;
738
- var shortTitle = (progress) => {
739
- const phase = currentPhase(progress);
740
- if (phase) return `${PHASE_ID_PREFIX}${phase.id} \xB7 ${phase.done}/${phase.total}`;
741
- return progress.total > 0 ? `\u2713 ${progress.done}/${progress.total}` : null;
742
- };
743
-
744
814
  // src/plan-format/sprint-document.ts
745
815
  var PHASE_ID_IN_CELL = /^(\d+[a-z]?)\b/;
746
816
  var paragraphs = (lines) => lines.join("\n").split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
@@ -781,10 +851,12 @@ var readPhaseDependencies = (planMarkdown, language) => {
781
851
  };
782
852
  export {
783
853
  ADDRESS_FORM,
854
+ APPOINTMENT_VOCABULARY,
784
855
  APPROVAL_TEXTS,
785
856
  LANGUAGE_NAME,
786
857
  PHASE_CELL_LIMIT,
787
858
  PHASE_ID_PREFIX,
859
+ PLAN_APPROVAL_LABELS,
788
860
  PLAN_LANGUAGE,
789
861
  PLAN_STATUS,
790
862
  PLAN_VOCABULARY,
@@ -797,6 +869,7 @@ export {
797
869
  classifyStatus,
798
870
  columnModels,
799
871
  countDecisions,
872
+ criticalPath,
800
873
  currentPhase,
801
874
  currentPhaseOf,
802
875
  deferralTarget,
@@ -807,6 +880,8 @@ export {
807
880
  isCheckboxLine,
808
881
  isDocumentationGateItem,
809
882
  isOpenCheckboxLine,
883
+ isPhaseFinished,
884
+ isPhaseOpen,
810
885
  isPlanLanguage,
811
886
  isReviewGateLine,
812
887
  isTickedUnmerged,
@@ -826,6 +901,10 @@ export {
826
901
  phaseHeadings,
827
902
  phaseIdsOfCell,
828
903
  phaseIdsOfPlan,
904
+ phaseLabel,
905
+ phaseLabels,
906
+ phaseProgress,
907
+ phaseWaves,
829
908
  phasesText,
830
909
  phasesWithoutDependencyRow,
831
910
  readPhaseDependencies,
@@ -837,5 +916,6 @@ export {
837
916
  sprintTitleOf,
838
917
  sumPhases,
839
918
  tallyPhases,
919
+ wavesOfPlan,
840
920
  withoutRepeats
841
921
  };
@@ -1,4 +1,4 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="960" height="708" viewBox="0 0 960 708" role="img" aria-labelledby="t d"><title id="t">jackherd in herdr</title><desc id="d">Animated schematic of one herdr window, told in five numbered steps. 1, Lane search works: its state is a teal dot and its progress counts up. 2, It hits an open question: search turns to an amber diamond and sends the question as text. 3, The coordinator asks you: the question appears in the coordinator pane as a choice. 4, You pick: option 1 is selected. 5, The lane works on: the answer goes back to search, which is a teal dot again, and the herd counts one more box.</desc><defs><style>.s{font-family:ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif}.m{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace}@keyframes k1{0%{opacity:1.0}0%{opacity:1.0}38.12%{opacity:1.0}40%{opacity:0.42}80%{opacity:0.42}81.88%{opacity:1.0}100%{opacity:1.0}}.k1{animation:k1 16s linear infinite both}@keyframes k2{0%{opacity:0.42}40%{opacity:0.42}41.88%{opacity:1.0}81.88%{opacity:1.0}83.75%{opacity:0.42}100%{opacity:0.42}}.k2{animation:k2 16s linear infinite both}@keyframes k3{0%{opacity:0.42}100%{opacity:0.42}}.k3{animation:k3 16s linear infinite both}@keyframes k4{0%{opacity:0}21.25%{opacity:0}23.75%{opacity:1}82.5%{opacity:1}85%{opacity:0}100%{opacity:0}}.k4{animation:k4 16s linear infinite both}@keyframes k5{0%{opacity:0;transform:translate(64px,198px)}23.75%{opacity:0;transform:translate(64px,198px)}23.94%{opacity:1;transform:translate(64px,198px)}24.32%{opacity:1;transform:translate(60.4px,197.5px)}24.9%{opacity:1;transform:translate(57.1px,196.1px)}25.47%{opacity:1;transform:translate(54.2px,193.9px)}26.04%{opacity:1;transform:translate(51.6px,191.1px)}26.61%{opacity:1;transform:translate(49.4px,187.6px)}27.19%{opacity:1;transform:translate(47.6px,183.8px)}27.76%{opacity:1;transform:translate(46.3px,179.6px)}28.33%{opacity:1;transform:translate(45.4px,175.2px)}28.91%{opacity:1;transform:translate(45px,170.7px)}29.48%{opacity:1;transform:translate(45.1px,166.3px)}30.05%{opacity:1;transform:translate(45.8px,162px)}30.63%{opacity:0;transform:translate(47px,158px)}100%{opacity:0;transform:translate(47px,158px)}}.k5{animation:k5 16s linear infinite both}@keyframes k6{0%{opacity:0;transform:translate(47px,158px)}81.25%{opacity:0;transform:translate(47px,158px)}81.44%{opacity:1;transform:translate(47px,158px)}81.67%{opacity:1;transform:translate(45.8px,162px)}82.08%{opacity:1;transform:translate(45.1px,166.3px)}82.5%{opacity:1;transform:translate(45px,170.7px)}82.92%{opacity:1;transform:translate(45.4px,175.2px)}83.33%{opacity:1;transform:translate(46.3px,179.6px)}83.75%{opacity:1;transform:translate(47.6px,183.8px)}84.17%{opacity:1;transform:translate(49.4px,187.6px)}84.58%{opacity:1;transform:translate(51.6px,191.1px)}85%{opacity:1;transform:translate(54.2px,193.9px)}85.42%{opacity:1;transform:translate(57.1px,196.1px)}85.83%{opacity:1;transform:translate(60.4px,197.5px)}86.25%{opacity:0;transform:translate(64px,198px)}100%{opacity:0;transform:translate(64px,198px)}}.k6{animation:k6 16s linear infinite both}@keyframes k7{0%{opacity:0}31.25%{opacity:0}33.75%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k7{animation:k7 16s linear infinite both}@keyframes k8{0%{opacity:0}41.25%{opacity:0}43.75%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k8{animation:k8 16s linear infinite both}@keyframes k9{0%{opacity:0}63.75%{opacity:0}66.25%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k9{animation:k9 16s linear infinite both}@keyframes k10{0%{opacity:0}68.75%{opacity:0}71.25%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k10{animation:k10 16s linear infinite both}@keyframes k11{0%{opacity:0}81.25%{opacity:0}83.75%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k11{animation:k11 16s linear infinite both}@keyframes k12{0%{opacity:1}21.25%{opacity:1}23.75%{opacity:0}82.5%{opacity:0}85%{opacity:1}100%{opacity:1}}.k12{animation:k12 16s linear infinite both}@keyframes k13{0%{opacity:0}8.75%{opacity:0}10.62%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k13{animation:k13 16s linear infinite both}@keyframes k14{0%{opacity:0}88.75%{opacity:0}90.62%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k14{animation:k14 16s linear infinite both}@keyframes k15{0%{opacity:1}8.75%{opacity:1}10.62%{opacity:0}96.25%{opacity:0}100%{opacity:1}}.k15{animation:k15 16s linear infinite both}@keyframes k16{0%{opacity:0}8.75%{opacity:0}10.62%{opacity:1}88.75%{opacity:1}90.62%{opacity:0}100%{opacity:0}}.k16{animation:k16 16s linear infinite both}@keyframes k17{0%{opacity:0}88.75%{opacity:0}90.62%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k17{animation:k17 16s linear infinite both}@keyframes k18{0%{opacity:1}8.75%{opacity:1}10.62%{opacity:0}96.25%{opacity:0}100%{opacity:1}}.k18{animation:k18 16s linear infinite both}@keyframes k19{0%{opacity:0}8.75%{opacity:0}10.62%{opacity:1}88.75%{opacity:1}90.62%{opacity:0}100%{opacity:0}}.k19{animation:k19 16s linear infinite both}@keyframes k20{0%{opacity:0}88.75%{opacity:0}90.62%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k20{animation:k20 16s linear infinite both}@keyframes k21{0%{opacity:1}18.13%{opacity:1}20%{opacity:0}100%{opacity:0}}.k21{animation:k21 16s linear infinite both}@keyframes k22{0%{opacity:1}18.13%{opacity:1}20%{opacity:0.62}100%{opacity:0.62}}.k22{animation:k22 16s linear infinite both}@keyframes k23{0%{opacity:0}20%{opacity:0}21.88%{opacity:1}38.12%{opacity:1}40%{opacity:0}100%{opacity:0}}.k23{animation:k23 16s linear infinite both}@keyframes k24{0%{opacity:0.62}20%{opacity:0.62}21.88%{opacity:1.0}38.12%{opacity:1.0}40%{opacity:0.62}100%{opacity:0.62}}.k24{animation:k24 16s linear infinite both}@keyframes k25{0%{opacity:0}40%{opacity:0}41.88%{opacity:1}58.12%{opacity:1}60%{opacity:0}100%{opacity:0}}.k25{animation:k25 16s linear infinite both}@keyframes k26{0%{opacity:0.62}40%{opacity:0.62}41.88%{opacity:1.0}58.12%{opacity:1.0}60%{opacity:0.62}100%{opacity:0.62}}.k26{animation:k26 16s linear infinite both}@keyframes k27{0%{opacity:0}60%{opacity:0}61.88%{opacity:1}78.12%{opacity:1}80%{opacity:0}100%{opacity:0}}.k27{animation:k27 16s linear infinite both}@keyframes k28{0%{opacity:0.62}60%{opacity:0.62}61.88%{opacity:1.0}78.12%{opacity:1.0}80%{opacity:0.62}100%{opacity:0.62}}.k28{animation:k28 16s linear infinite both}@keyframes k29{0%{opacity:0}80%{opacity:0}81.88%{opacity:1}98.12%{opacity:1}100%{opacity:0}}.k29{animation:k29 16s linear infinite both}@keyframes k30{0%{opacity:0.62}80%{opacity:0.62}81.88%{opacity:1}98.12%{opacity:1}100%{opacity:0.62}}.k30{animation:k30 16s linear infinite both}@media (prefers-reduced-motion:reduce){*{animation:none!important}}</style><clipPath id="jack-clip"><rect width="240" height="240" rx="50"/></clipPath><symbol id="jack" viewBox="0 0 240 240"><rect width="240" height="240" rx="50" fill="#212E34"/><g clip-path="url(#jack-clip)"><g transform="translate(34 8)"><path fill="#4A4E51" d="M80 50.5c.2 .1 .2 .1 1 1c-.2 .6-.3 1.3-.5 2c-.7 .4-.7 .4-4 2c-6 3.3-6 3.3-7 6.5c1.2-.4 2.3-.6 3.5-1c.8 0 1.7 0 2.5 0c.2 .1 .2 .1 1 1c-.5 2-.5 2-5.5 6.5c-3 5-3 5-5.5 11c.2 .3 .2 .3 1 2c.2 .3 .3 .7 .5 1c-.7 .7-1.3 1.3-2 2c-1.3-.2-2.6-.3-4-.5c0-1.2 0-2.3 0-3.5c-.2 .1-.2 .1-1.5 .5c-1.2 2.7-2.4 5.3-3.5 8c-1 2.1-2 4.2-3 6.3c-2.5 5.7-2.5 5.7-3 8.2c.5 0 1 0 1.5 0c.1-.8 .4-1.7 .5-2.5c.6 0 1.3 0 2 0c.5 2 1 3.9 1.6 6c1 4 2.2 8 3.4 11.9c2.1 8.4 1.1 15.5-3 23.1c-.6-.2-1.3-.3-2-.5c-.1-1.5-.3-3-.5-4.6c-.3-2.6-.5-5.2-1-7.9c-.5 0-1 0-1.5 0c-.1 1.7-.4 3.3-.5 5c-2.7 10.2-2.7 10.2-6 13.5c-6.1-3.1-9-14.9-11-21c-1.1-8.2 .9-14.7 3.5-22.5c-8.4 8.3-8.3 20.5-9 31.5c-.6-.2-1.3-.3-2-.5c-5.4-8.9-5.1-15-3-25c.5-1 1-2 1.5-3c-3 1.5-3 1.5-6.5 5c-.4 0-.6 0-1 0c.8-6.5 3.2-10.2 7.5-15c.6-.8 .6-.8 3.5-5c2.6-2.6 5.2-5.1 8-7.5c.6-.6 .6-.6 3.5-3.5c-.5-2-.5-2-1-3.5c3.6-6 6.5-9.2 12.5-13c.4-.6 .6-1.3 1-2c-.5-.6-1-1.3-1.5-2c2-3 2-3 4.5-4.5c.5 1 .5 1 1 3.5c2.5-1 2.5-1 8.5-5.5c.3 0 .7 0 1 0c-.1-.4-.1-.4-.5-2.5c4.3-3.3 10-2.5 15-1zM153 69.5c1.2 .2 2.3 .3 3.5 .5c.3 .7 .7 1.3 1 2c.3 0 .7 0 1 0c.5 1.2 1 2.3 1.5 3.5c3.4 7.6 6.2 13.7 12.2 19.6c3.3 3.4 3.3 3.4 3.8 5.9c-.2 .2-.3 .3-.5 .5c.1 .7 .1 .7 .5 4.5c-.5 .3-1 .7-1.5 1c-.8-.3-1.7-.7-2.5-1c.3 .7 .7 1.3 1 2c1.2 5.8 1.4 10 1 16c-.7 0-1.3 0-2 0c-.4-1.3-.8-2.7-1.3-4.1c-2.1-6.1-5.2-11.4-8.7-16.9c-.2-.8-.2-.8-1.5-5c-.3-.6-.3-.6-2.1-3.5c-1.9-3.5-1.9-3.5-2.9-8.5c.7 0 1.3 0 2 0c.5 .5 1 1 1.5 1.5c-.8-2.3-1.7-4.6-2.5-7c-.7-2-1.3-4-2-6c-.7-1-1.3-2-2-3c.2-.7 .3-1.3 .5-2zM86.5 100.5c1 0 2 0 3 0c1.3 .8 2.7 1.7 4 2.5c-2.5 2.5-2.5 2.5-8 5.5c-5.5 4.3-8.6 7-10 14c-.8 2.2-1.7 4.3-2.5 6.5c-1.2 4.6-.5 9.3 0 14c-.5 0-1 0-1.5 0c-.2 2-.3 3.9-.5 6c-.7 0-1.3 0-2 0c-.3-.6-.3-.6-2-3.5c-1-.8-2-1.7-3-2.5c-.5-1.5-.5-1.5 1.5-8.5c.3-10.3-1-18.2-4-28c0-4 0-4 1-6c2 2 2 2 3 6.5c2.5-.5 2.5-.5 8-4c9.8-3.5 9.8-3.5 13-2.5zM43 161.5c2.5 2.5 2.5 2.5 3 5c-1.5 2.5-1.5 2.5-4 5c.5 3.5 .5 3.5 4 6.5c-.1 1.2-.4 2.3-.5 3.5c-5.3 3.5-5.3 3.5-8.5 3.5c-6-2.5-6-2.5-11.5-7c0-3 0-3 4-8c9.5-8.5 9.5-8.5 13.5-8.5z"/><path fill="#9B9D9E" d="M142.5 60.5c.5 1 .5 1-.5 4.5c1.2 1.5 2.3 3 3.5 4.5c2 4.5 2 4.5 2.5 9.5c-.5 .2-1 .3-1.5 .5c-.1 .2-.1 .2-.5 1c-2-1-3.9-2-6-3c-.8-.3-1.7-.7-2.5-1c-.3-.7-.7-1.3-1-2c.3-.5 .7-1 1-1.5c-.2-.7-.3-1.3-.5-2c-3.5 .5-3.5 .5-7.5 4c-.5 3-.5 3 0 5.5c-2 4-2 4-3.5 5.5c-.3 0-.7 0-1 0c0 .7 0 1.3 0 2c-2-1-2-1-2.5-2.5c.3-13.7 .3-13.7 4.8-18.5c4.1-3.9 6.3-4.8 11.7-6c1-.5 1-.5 3.5-.5zM98 70.5c.5 .3 1 .7 1.5 1c1.3 .3 2.7 .7 4 1c4.4 5.8 6.5 10.3 6.5 17.5c-2 2-2 2-5 2.5c-.3 .2-.3 .2-2 1c-.2-.2-.2-.2-1.5-1c.5-3 .5-3 1-5c-.7-.8-1.3-1.7-2-2.5c-.2-1.7-.3-3.3-.5-5c-4.5-2.7-8.5-3-13.5-2.5c-.1 .3-.1 .3-.5 1.5c1.3 .8 2.7 1.7 4 2.5c-.1 .4-.1 .4-.5 2.5c-2.3 1.2-4.6 2.3-7 3.5c-5.5 5.5-5.5 5.5-6.5 7c0 .5 0 1 0 1.5c.9-.1 .9-.1 5.5-.5c1 1.3 2 2.7 3 4c-.2 .5-.3 1-.5 1.5c-1.7 .5-3.2 1-5 1.6c-5.5 1.9-5.5 1.9-11 5.4c-.4 .1-.4 .1-2.5 .5c-1.5-1.5-1.5-1.5-2.5-6c-.4-.3-.6-.7-1-1c0-6.7 .9-11.7 3-18c.2-.6 .2-.6 1-3.5c7.8-9.7 20.5-14.1 32-9.5z"/><path fill="#FAF9F8" d="M103.5 40c0 .4 0 .6 0 1c2.2-.1 4.3-.4 6.5-.5c7 0 7 0 9.5 1c-.2 .6-.3 1.3-.5 2c-1.8 .6-3.7 1.3-5.5 2c-.2 .5-.3 1-.5 1.5c.7-.1 1.3-.4 2-.5c13.4-.3 13.4-.3 19.5 3.5c-.3 .6-.7 1.3-1 2c-1 0-2 0-3 0c0 .5 0 1 0 1.5c1.2 .1 1.2 .1 7 .5c3.5 2 3.5 2 5.5 4.5c0 1 0 2 0 3c-3.3 .9-6.7 1.6-10 2.5c-7.5 5-7.5 5-8.5 9c-.3 4.5-.4 9-.5 13.5c1.5 1 1.5 1 7.5 2.5c.9 .6 .9 .6 5.5 3.5c2 1 2 1 9 2.5c7.9 4 11.3 8.3 15 16.5c0 .5 0 1 0 1.5c-1 .2-2 .3-3 .5c.5 1.8 1 3.7 1.5 5.5c.3 8.5-.8 13.6-6 20.5c-.7-.2-1.3-.3-2-.5c0-1.7 0-3.3 0-5c-.7 .2-1.3 .3-2 .5c-1.3 2.7-2.7 5.3-4 8c-3.2 3.7-6.4 6.2-11 8c-1-1-1-1-1.5-3c-2.5 1.3-4.8 2.9-7 4.5c-6.3 2.8-10.8 3.2-17.6 1c-1.3-.5-2.6-1-3.9-1.5c.2 1.2 .3 2.3 .5 3.5c-.2 .3-.3 .7-.5 1c-5.9 0-9-1.8-14-5c-3-3.5-3-3.5-3-4.5c-.7 0-1.3 0-2 0c.5 2.2 1 4.3 1.5 6.5c-.3 .3-.7 .7-1 1c-4.5-.9-7.6-3.6-11-6.5c-4-6-4-9.3-4-16.5c2.5-7.5 2.5-7.5 3.5-9.5c.3-1.7 .7-3.3 1-5c6.4-8 14.7-13 24.5-16c6.4-6.3 6.4-6.3 8.5-10.5c0-6-1.6-9.8-4.5-15c-.2-.5-.3-1-.5-1.5c-1.7-.3-3.3-.7-5-1c-1.3-.7-2.7-1.3-4-2c-5.7-1.9-10.2-.3-15.5 1.5c-10 5.5-10 5.5-13 10c-.3-.2-.3-.2-2-1c1.3-6.8 4.6-11.7 9.5-16.5c.5-.3 1-.6 1.5-1c.2-.5 .3-1 .5-1.5c-1 .1-1 .1-6 1c-.3-.4-.7-.6-1-1c.5-2 .5-2 4-6c5-2.5 5-2.5 7.5-3c0-.5 0-1 0-1.5c-10.5-1.5-18.5 1-27 7c-.5 .9-1 1.6-1.5 2.5c-.9 .6-.9 .6-5.5 4c-4 3.5-7.6 7-11 11c-4.6 3.8-8.7 6.8-14 9.5c-.7-.3-1.3-.7-2-1c.2-.7 .3-1.3 .5-2c1.8-1 3.7-2 5.5-3c5.2-4.2 9.7-8.5 14-13.5c3.1-2.8 6.2-5.5 9.5-8c1.3-1.3 2.6-2.6 4-4c5.6-3.6 10.4-6.1 17-7c.5-1 1-2 1.5-3c7.2-2.4 12.5-2.2 20-.5c.2 .1 .2 .1 1 .5c3.5 0 3.5 0 8.5-3.5c1-.5 1-.5 3-.5zM118 92.5c-1 1.2-2 2.3-3 3.5c.5 2.5 .5 2.5 3 5c1.8 .8 3.7 1.7 5.5 2.5c.5 1 .5 1 .5 4.5c-4 4.8-6.1 5-12.5 6c0 .7 0 1.3 0 2c4.7 1.5 7.7 .2 12-1.5c1.2-.7 2.3-1.3 3.5-2c1.3 .3 2.7 .7 4 1c3.5 0 3.5 0 5.5-1.5c-.2-.5-.3-1-.5-1.5c-1.7-.3-3.3-.7-5-1c-2.5-2-2.5-2-3-5.5c2-3 2-3 4-5.5c.5-3.5 .5-3.5-1.5-6.5c-4.3-2.2-8.2-1-12.5 .5zM146 58.5c5.8 1.1 8.3 3.9 12.5 8c3.7 5.9 4.4 7.3 5 14c2.8 6.2 5.4 10 10.5 14.5c5.2 5.2 7.2 10.3 9 17.5c0 3.5 0 3.5-2 11c-.2 1.3-.2 1.3-1 7.5c-3.8 10.7-3.8 10.7-8.5 13.5c-.5 .2-.5 .2-3 1c-1 1.3-2 2.7-3 4c-4.5 3-4.5 3-10.5 5c-2.7-.6-5.3-1.3-8-2c-1.7 1.2-3.3 2.3-5 3.5c-2 1-2 1-4 .5c.5-1.5 .5-1.5 6-5c.3-.3 .3-.3 2-2c2.4 0 4.9 0 7.5 0c.5 .5 1 1 1.5 1.5c4.3-.6 7.1-2.3 10.5-5c3.5-5.5 3.5-5.5 4-7c1.5 0 2.9 0 4.5 0c4.3-8.7 4.6-14 2.5-23.5c-.3-1-.7-2-1-3c.3 .1 .3 .1 2 .5c1.5 2.5 1.5 2.5 1.5 4c.5-.2 1-.3 1.5-.5c-.8-10-4.3-16-11.4-22.8c-3.7-3.8-5.5-6.9-7.6-11.7c-.7-1.5-1.5-2.9-2.3-4.4c-1.7-3.6-1.7-3.6-1.7-5.6c.3-.2 .7-.3 1-.5c-.5-3-.5-3-4.5-7c-2-1-3.9-2-6-3c-.7-.4-1.3-.6-2-1c0-.6 0-1.3 0-2zM33.5 83.5c-1 4-5.1 6.4-8 9c-8.6 8.1-14.6 17.2-15.2 29.2c.3 8.1 .3 8.1 2.2 11.8c.5 0 1 0 1.5 0c.4-2.9 .6-6 1-9c.6 0 1.3 0 2 0c.2 1.3 .4 2.7 .6 4.1c1.8 9.3 5.3 14.8 12.9 20.4c2.3 1.2 4.6 2.3 7 3.5c.7 .6 .7 .6 4.5 3.5c3.5 0 3.5 0 8.5-1.5c1 0 2 0 3 0c-1 2-1 2-2.5 3.5c-6.5 1.7-9.6 .9-16-1.5c-1.1-1-2.3-2-3.5-3c-1.5-.7-2.9-1.3-4.5-2c-3.9-3.4-6.9-6.5-9.5-11c-1.1-.5-2.3-1-3.5-1.5c-5.9-6.7-8-12.7-8-21.5c2.4-12.2 7.2-19.8 16.5-28c4.5-4 4.5-4 6-5.5c3.5-1 3.5-1 5-.5zM50.5 172c2.5 1 2.5 1 7 6.5c6 6.2 12 11.8 18.8 17.1c5.7 4.5 11.3 9.1 16.7 13.9c1.3 1.2 2.7 2.3 4 3.5c.7 .7 1.3 1.3 2 2c-.2 .8-.3 1.7-.5 2.5c-6 2.9-11.7 4-18.5 4c-2.6-2.6-4.3-5.5-5.5-9c-.3 .1-.3 .1-1.5 .5c0 2.3 0 4.6 0 7c-.3 .5-.7 1-1 1.5c-13.3 0-24.6-4.6-34.5-13.5c-4.5-4.5-4.5-4.5-8.5-11c.5-2.5 .5-2.5 4.5-7c5-3 5-3 8-3.5c.5 .3 1 .7 1.5 1c2-.5 2-.5 4-2c.9-4.2 1.7-8.3 2.5-12.5c.4-.3 .6-.7 1-1z"/><path fill="#03B5BD" d="M58.5 149.5c2.5 .5 2.5 .5 7 3.5c9.8 5.2 18.9 7.9 30 8.5c2.2 .5 4.3 1 6.5 1.5c7 0 7 0 9-.5c2.3-.2 4.6-.3 7-.5c3.2-.9 6.3-1.9 9.5-3c1.3-.2 2.7-.3 4-.5c.2 .2 .3 .3 .5 .5c-.8 6.3-4.1 10.2-8 15c-.2 .5-.3 1-.5 1.5c1.7-1 3.3-2 5-3c.7 .2 1.3 .3 2 .5c-3 12.2-9.3 20.3-17.5 29.5c-.7 .7-.7 .7-4 4.5c-1 .8-2 1.7-3 2.5c-3.5-.5-3.5-.5-8-5c-2.3-1.8-4.7-3.7-7-5.5c-2.7-2.2-5.3-4.3-8-6.5c-.8-.6-.8-.6-4.5-3.5c-11-9.3-20.7-18.8-28.5-31c3-4 3-4 8.5-8.5z"/><ellipse cx="94" cy="94" rx="6.5" ry="6.5" fill="#212E34"/><ellipse cx="135" cy="85.6" rx="6.2" ry="5.8" fill="#212E34"/><g fill="#FAF9F8"><circle cx="91.5" cy="91.5" r="2.1"/><circle cx="134.6" cy="84.3" r="2"/></g><ellipse cx="124" cy="93.8" rx="3.4" ry="1.1" fill="#9B9D9E" transform="rotate(-6 124 93.8)"/></g></g><rect x="5" y="5" width="230" height="230" rx="45" fill="none" stroke="#5B6B73" stroke-width="10"/></symbol></defs>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="960" height="724" viewBox="0 0 960 724" role="img" aria-labelledby="t d"><title id="t">jackherd in herdr</title><desc id="d">Animated schematic of one herdr window, told in five numbered steps. 1, Lane search works: its state is a teal dot and its progress counts up. 2, It hits an open question: search turns to an amber diamond and sends the question as text. 3, The coordinator asks you: the question appears in the coordinator pane as a choice. 4, You pick: option 1 is selected. 5, The lane works on: the answer goes back to search, which is a teal dot again, and the herd counts one more box.</desc><defs><style>.s{font-family:ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif}.m{font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace}@keyframes k1{0%{opacity:1.0}0%{opacity:1.0}38.12%{opacity:1.0}40%{opacity:0.42}80%{opacity:0.42}81.88%{opacity:1.0}100%{opacity:1.0}}.k1{animation:k1 16s linear infinite both}@keyframes k2{0%{opacity:0.42}40%{opacity:0.42}41.88%{opacity:1.0}81.88%{opacity:1.0}83.75%{opacity:0.42}100%{opacity:0.42}}.k2{animation:k2 16s linear infinite both}@keyframes k3{0%{opacity:0.42}100%{opacity:0.42}}.k3{animation:k3 16s linear infinite both}@keyframes k4{0%{opacity:0}21.25%{opacity:0}23.75%{opacity:1}82.5%{opacity:1}85%{opacity:0}100%{opacity:0}}.k4{animation:k4 16s linear infinite both}@keyframes k5{0%{opacity:0;transform:translate(64px,198px)}23.75%{opacity:0;transform:translate(64px,198px)}23.94%{opacity:1;transform:translate(64px,198px)}24.32%{opacity:1;transform:translate(60.4px,197.5px)}24.9%{opacity:1;transform:translate(57.1px,196.1px)}25.47%{opacity:1;transform:translate(54.2px,193.9px)}26.04%{opacity:1;transform:translate(51.6px,191.1px)}26.61%{opacity:1;transform:translate(49.4px,187.6px)}27.19%{opacity:1;transform:translate(47.6px,183.8px)}27.76%{opacity:1;transform:translate(46.3px,179.6px)}28.33%{opacity:1;transform:translate(45.4px,175.2px)}28.91%{opacity:1;transform:translate(45px,170.7px)}29.48%{opacity:1;transform:translate(45.1px,166.3px)}30.05%{opacity:1;transform:translate(45.8px,162px)}30.63%{opacity:0;transform:translate(47px,158px)}100%{opacity:0;transform:translate(47px,158px)}}.k5{animation:k5 16s linear infinite both}@keyframes k6{0%{opacity:0;transform:translate(47px,158px)}81.25%{opacity:0;transform:translate(47px,158px)}81.44%{opacity:1;transform:translate(47px,158px)}81.67%{opacity:1;transform:translate(45.8px,162px)}82.08%{opacity:1;transform:translate(45.1px,166.3px)}82.5%{opacity:1;transform:translate(45px,170.7px)}82.92%{opacity:1;transform:translate(45.4px,175.2px)}83.33%{opacity:1;transform:translate(46.3px,179.6px)}83.75%{opacity:1;transform:translate(47.6px,183.8px)}84.17%{opacity:1;transform:translate(49.4px,187.6px)}84.58%{opacity:1;transform:translate(51.6px,191.1px)}85%{opacity:1;transform:translate(54.2px,193.9px)}85.42%{opacity:1;transform:translate(57.1px,196.1px)}85.83%{opacity:1;transform:translate(60.4px,197.5px)}86.25%{opacity:0;transform:translate(64px,198px)}100%{opacity:0;transform:translate(64px,198px)}}.k6{animation:k6 16s linear infinite both}@keyframes k7{0%{opacity:0}31.25%{opacity:0}33.75%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k7{animation:k7 16s linear infinite both}@keyframes k8{0%{opacity:0}41.25%{opacity:0}43.75%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k8{animation:k8 16s linear infinite both}@keyframes k9{0%{opacity:0}63.75%{opacity:0}66.25%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k9{animation:k9 16s linear infinite both}@keyframes k10{0%{opacity:0}68.75%{opacity:0}71.25%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k10{animation:k10 16s linear infinite both}@keyframes k11{0%{opacity:0}81.25%{opacity:0}83.75%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k11{animation:k11 16s linear infinite both}@keyframes k12{0%{opacity:1}21.25%{opacity:1}23.75%{opacity:0}82.5%{opacity:0}85%{opacity:1}100%{opacity:1}}.k12{animation:k12 16s linear infinite both}@keyframes k13{0%{opacity:0}8.75%{opacity:0}10.62%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k13{animation:k13 16s linear infinite both}@keyframes k14{0%{opacity:0}88.75%{opacity:0}90.62%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k14{animation:k14 16s linear infinite both}@keyframes k15{0%{opacity:1}8.75%{opacity:1}10.62%{opacity:0}96.25%{opacity:0}100%{opacity:1}}.k15{animation:k15 16s linear infinite both}@keyframes k16{0%{opacity:0}8.75%{opacity:0}10.62%{opacity:1}88.75%{opacity:1}90.62%{opacity:0}100%{opacity:0}}.k16{animation:k16 16s linear infinite both}@keyframes k17{0%{opacity:0}88.75%{opacity:0}90.62%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k17{animation:k17 16s linear infinite both}@keyframes k18{0%{opacity:1}8.75%{opacity:1}10.62%{opacity:0}96.25%{opacity:0}100%{opacity:1}}.k18{animation:k18 16s linear infinite both}@keyframes k19{0%{opacity:0}8.75%{opacity:0}10.62%{opacity:1}88.75%{opacity:1}90.62%{opacity:0}100%{opacity:0}}.k19{animation:k19 16s linear infinite both}@keyframes k20{0%{opacity:0}88.75%{opacity:0}90.62%{opacity:1}96.25%{opacity:1}100%{opacity:0}}.k20{animation:k20 16s linear infinite both}@keyframes k21{0%{opacity:1}18.13%{opacity:1}20%{opacity:0}100%{opacity:0}}.k21{animation:k21 16s linear infinite both}@keyframes k22{0%{opacity:1}18.13%{opacity:1}20%{opacity:0.62}100%{opacity:0.62}}.k22{animation:k22 16s linear infinite both}@keyframes k23{0%{opacity:0}20%{opacity:0}21.88%{opacity:1}38.12%{opacity:1}40%{opacity:0}100%{opacity:0}}.k23{animation:k23 16s linear infinite both}@keyframes k24{0%{opacity:0.62}20%{opacity:0.62}21.88%{opacity:1.0}38.12%{opacity:1.0}40%{opacity:0.62}100%{opacity:0.62}}.k24{animation:k24 16s linear infinite both}@keyframes k25{0%{opacity:0}40%{opacity:0}41.88%{opacity:1}58.12%{opacity:1}60%{opacity:0}100%{opacity:0}}.k25{animation:k25 16s linear infinite both}@keyframes k26{0%{opacity:0.62}40%{opacity:0.62}41.88%{opacity:1.0}58.12%{opacity:1.0}60%{opacity:0.62}100%{opacity:0.62}}.k26{animation:k26 16s linear infinite both}@keyframes k27{0%{opacity:0}60%{opacity:0}61.88%{opacity:1}78.12%{opacity:1}80%{opacity:0}100%{opacity:0}}.k27{animation:k27 16s linear infinite both}@keyframes k28{0%{opacity:0.62}60%{opacity:0.62}61.88%{opacity:1.0}78.12%{opacity:1.0}80%{opacity:0.62}100%{opacity:0.62}}.k28{animation:k28 16s linear infinite both}@keyframes k29{0%{opacity:0}80%{opacity:0}81.88%{opacity:1}98.12%{opacity:1}100%{opacity:0}}.k29{animation:k29 16s linear infinite both}@keyframes k30{0%{opacity:0.62}80%{opacity:0.62}81.88%{opacity:1}98.12%{opacity:1}100%{opacity:0.62}}.k30{animation:k30 16s linear infinite both}@media (prefers-reduced-motion:reduce){*{animation:none!important}}</style><clipPath id="jack-clip"><rect width="240" height="240" rx="50"/></clipPath><symbol id="jack" viewBox="0 0 240 240"><rect width="240" height="240" rx="50" fill="#212E34"/><g clip-path="url(#jack-clip)"><g transform="translate(34 8)"><path fill="#4A4E51" d="M80 50.5c.2 .1 .2 .1 1 1c-.2 .6-.3 1.3-.5 2c-.7 .4-.7 .4-4 2c-6 3.3-6 3.3-7 6.5c1.2-.4 2.3-.6 3.5-1c.8 0 1.7 0 2.5 0c.2 .1 .2 .1 1 1c-.5 2-.5 2-5.5 6.5c-3 5-3 5-5.5 11c.2 .3 .2 .3 1 2c.2 .3 .3 .7 .5 1c-.7 .7-1.3 1.3-2 2c-1.3-.2-2.6-.3-4-.5c0-1.2 0-2.3 0-3.5c-.2 .1-.2 .1-1.5 .5c-1.2 2.7-2.4 5.3-3.5 8c-1 2.1-2 4.2-3 6.3c-2.5 5.7-2.5 5.7-3 8.2c.5 0 1 0 1.5 0c.1-.8 .4-1.7 .5-2.5c.6 0 1.3 0 2 0c.5 2 1 3.9 1.6 6c1 4 2.2 8 3.4 11.9c2.1 8.4 1.1 15.5-3 23.1c-.6-.2-1.3-.3-2-.5c-.1-1.5-.3-3-.5-4.6c-.3-2.6-.5-5.2-1-7.9c-.5 0-1 0-1.5 0c-.1 1.7-.4 3.3-.5 5c-2.7 10.2-2.7 10.2-6 13.5c-6.1-3.1-9-14.9-11-21c-1.1-8.2 .9-14.7 3.5-22.5c-8.4 8.3-8.3 20.5-9 31.5c-.6-.2-1.3-.3-2-.5c-5.4-8.9-5.1-15-3-25c.5-1 1-2 1.5-3c-3 1.5-3 1.5-6.5 5c-.4 0-.6 0-1 0c.8-6.5 3.2-10.2 7.5-15c.6-.8 .6-.8 3.5-5c2.6-2.6 5.2-5.1 8-7.5c.6-.6 .6-.6 3.5-3.5c-.5-2-.5-2-1-3.5c3.6-6 6.5-9.2 12.5-13c.4-.6 .6-1.3 1-2c-.5-.6-1-1.3-1.5-2c2-3 2-3 4.5-4.5c.5 1 .5 1 1 3.5c2.5-1 2.5-1 8.5-5.5c.3 0 .7 0 1 0c-.1-.4-.1-.4-.5-2.5c4.3-3.3 10-2.5 15-1zM153 69.5c1.2 .2 2.3 .3 3.5 .5c.3 .7 .7 1.3 1 2c.3 0 .7 0 1 0c.5 1.2 1 2.3 1.5 3.5c3.4 7.6 6.2 13.7 12.2 19.6c3.3 3.4 3.3 3.4 3.8 5.9c-.2 .2-.3 .3-.5 .5c.1 .7 .1 .7 .5 4.5c-.5 .3-1 .7-1.5 1c-.8-.3-1.7-.7-2.5-1c.3 .7 .7 1.3 1 2c1.2 5.8 1.4 10 1 16c-.7 0-1.3 0-2 0c-.4-1.3-.8-2.7-1.3-4.1c-2.1-6.1-5.2-11.4-8.7-16.9c-.2-.8-.2-.8-1.5-5c-.3-.6-.3-.6-2.1-3.5c-1.9-3.5-1.9-3.5-2.9-8.5c.7 0 1.3 0 2 0c.5 .5 1 1 1.5 1.5c-.8-2.3-1.7-4.6-2.5-7c-.7-2-1.3-4-2-6c-.7-1-1.3-2-2-3c.2-.7 .3-1.3 .5-2zM86.5 100.5c1 0 2 0 3 0c1.3 .8 2.7 1.7 4 2.5c-2.5 2.5-2.5 2.5-8 5.5c-5.5 4.3-8.6 7-10 14c-.8 2.2-1.7 4.3-2.5 6.5c-1.2 4.6-.5 9.3 0 14c-.5 0-1 0-1.5 0c-.2 2-.3 3.9-.5 6c-.7 0-1.3 0-2 0c-.3-.6-.3-.6-2-3.5c-1-.8-2-1.7-3-2.5c-.5-1.5-.5-1.5 1.5-8.5c.3-10.3-1-18.2-4-28c0-4 0-4 1-6c2 2 2 2 3 6.5c2.5-.5 2.5-.5 8-4c9.8-3.5 9.8-3.5 13-2.5zM43 161.5c2.5 2.5 2.5 2.5 3 5c-1.5 2.5-1.5 2.5-4 5c.5 3.5 .5 3.5 4 6.5c-.1 1.2-.4 2.3-.5 3.5c-5.3 3.5-5.3 3.5-8.5 3.5c-6-2.5-6-2.5-11.5-7c0-3 0-3 4-8c9.5-8.5 9.5-8.5 13.5-8.5z"/><path fill="#9B9D9E" d="M142.5 60.5c.5 1 .5 1-.5 4.5c1.2 1.5 2.3 3 3.5 4.5c2 4.5 2 4.5 2.5 9.5c-.5 .2-1 .3-1.5 .5c-.1 .2-.1 .2-.5 1c-2-1-3.9-2-6-3c-.8-.3-1.7-.7-2.5-1c-.3-.7-.7-1.3-1-2c.3-.5 .7-1 1-1.5c-.2-.7-.3-1.3-.5-2c-3.5 .5-3.5 .5-7.5 4c-.5 3-.5 3 0 5.5c-2 4-2 4-3.5 5.5c-.3 0-.7 0-1 0c0 .7 0 1.3 0 2c-2-1-2-1-2.5-2.5c.3-13.7 .3-13.7 4.8-18.5c4.1-3.9 6.3-4.8 11.7-6c1-.5 1-.5 3.5-.5zM98 70.5c.5 .3 1 .7 1.5 1c1.3 .3 2.7 .7 4 1c4.4 5.8 6.5 10.3 6.5 17.5c-2 2-2 2-5 2.5c-.3 .2-.3 .2-2 1c-.2-.2-.2-.2-1.5-1c.5-3 .5-3 1-5c-.7-.8-1.3-1.7-2-2.5c-.2-1.7-.3-3.3-.5-5c-4.5-2.7-8.5-3-13.5-2.5c-.1 .3-.1 .3-.5 1.5c1.3 .8 2.7 1.7 4 2.5c-.1 .4-.1 .4-.5 2.5c-2.3 1.2-4.6 2.3-7 3.5c-5.5 5.5-5.5 5.5-6.5 7c0 .5 0 1 0 1.5c.9-.1 .9-.1 5.5-.5c1 1.3 2 2.7 3 4c-.2 .5-.3 1-.5 1.5c-1.7 .5-3.2 1-5 1.6c-5.5 1.9-5.5 1.9-11 5.4c-.4 .1-.4 .1-2.5 .5c-1.5-1.5-1.5-1.5-2.5-6c-.4-.3-.6-.7-1-1c0-6.7 .9-11.7 3-18c.2-.6 .2-.6 1-3.5c7.8-9.7 20.5-14.1 32-9.5z"/><path fill="#FAF9F8" d="M103.5 40c0 .4 0 .6 0 1c2.2-.1 4.3-.4 6.5-.5c7 0 7 0 9.5 1c-.2 .6-.3 1.3-.5 2c-1.8 .6-3.7 1.3-5.5 2c-.2 .5-.3 1-.5 1.5c.7-.1 1.3-.4 2-.5c13.4-.3 13.4-.3 19.5 3.5c-.3 .6-.7 1.3-1 2c-1 0-2 0-3 0c0 .5 0 1 0 1.5c1.2 .1 1.2 .1 7 .5c3.5 2 3.5 2 5.5 4.5c0 1 0 2 0 3c-3.3 .9-6.7 1.6-10 2.5c-7.5 5-7.5 5-8.5 9c-.3 4.5-.4 9-.5 13.5c1.5 1 1.5 1 7.5 2.5c.9 .6 .9 .6 5.5 3.5c2 1 2 1 9 2.5c7.9 4 11.3 8.3 15 16.5c0 .5 0 1 0 1.5c-1 .2-2 .3-3 .5c.5 1.8 1 3.7 1.5 5.5c.3 8.5-.8 13.6-6 20.5c-.7-.2-1.3-.3-2-.5c0-1.7 0-3.3 0-5c-.7 .2-1.3 .3-2 .5c-1.3 2.7-2.7 5.3-4 8c-3.2 3.7-6.4 6.2-11 8c-1-1-1-1-1.5-3c-2.5 1.3-4.8 2.9-7 4.5c-6.3 2.8-10.8 3.2-17.6 1c-1.3-.5-2.6-1-3.9-1.5c.2 1.2 .3 2.3 .5 3.5c-.2 .3-.3 .7-.5 1c-5.9 0-9-1.8-14-5c-3-3.5-3-3.5-3-4.5c-.7 0-1.3 0-2 0c.5 2.2 1 4.3 1.5 6.5c-.3 .3-.7 .7-1 1c-4.5-.9-7.6-3.6-11-6.5c-4-6-4-9.3-4-16.5c2.5-7.5 2.5-7.5 3.5-9.5c.3-1.7 .7-3.3 1-5c6.4-8 14.7-13 24.5-16c6.4-6.3 6.4-6.3 8.5-10.5c0-6-1.6-9.8-4.5-15c-.2-.5-.3-1-.5-1.5c-1.7-.3-3.3-.7-5-1c-1.3-.7-2.7-1.3-4-2c-5.7-1.9-10.2-.3-15.5 1.5c-10 5.5-10 5.5-13 10c-.3-.2-.3-.2-2-1c1.3-6.8 4.6-11.7 9.5-16.5c.5-.3 1-.6 1.5-1c.2-.5 .3-1 .5-1.5c-1 .1-1 .1-6 1c-.3-.4-.7-.6-1-1c.5-2 .5-2 4-6c5-2.5 5-2.5 7.5-3c0-.5 0-1 0-1.5c-10.5-1.5-18.5 1-27 7c-.5 .9-1 1.6-1.5 2.5c-.9 .6-.9 .6-5.5 4c-4 3.5-7.6 7-11 11c-4.6 3.8-8.7 6.8-14 9.5c-.7-.3-1.3-.7-2-1c.2-.7 .3-1.3 .5-2c1.8-1 3.7-2 5.5-3c5.2-4.2 9.7-8.5 14-13.5c3.1-2.8 6.2-5.5 9.5-8c1.3-1.3 2.6-2.6 4-4c5.6-3.6 10.4-6.1 17-7c.5-1 1-2 1.5-3c7.2-2.4 12.5-2.2 20-.5c.2 .1 .2 .1 1 .5c3.5 0 3.5 0 8.5-3.5c1-.5 1-.5 3-.5zM118 92.5c-1 1.2-2 2.3-3 3.5c.5 2.5 .5 2.5 3 5c1.8 .8 3.7 1.7 5.5 2.5c.5 1 .5 1 .5 4.5c-4 4.8-6.1 5-12.5 6c0 .7 0 1.3 0 2c4.7 1.5 7.7 .2 12-1.5c1.2-.7 2.3-1.3 3.5-2c1.3 .3 2.7 .7 4 1c3.5 0 3.5 0 5.5-1.5c-.2-.5-.3-1-.5-1.5c-1.7-.3-3.3-.7-5-1c-2.5-2-2.5-2-3-5.5c2-3 2-3 4-5.5c.5-3.5 .5-3.5-1.5-6.5c-4.3-2.2-8.2-1-12.5 .5zM146 58.5c5.8 1.1 8.3 3.9 12.5 8c3.7 5.9 4.4 7.3 5 14c2.8 6.2 5.4 10 10.5 14.5c5.2 5.2 7.2 10.3 9 17.5c0 3.5 0 3.5-2 11c-.2 1.3-.2 1.3-1 7.5c-3.8 10.7-3.8 10.7-8.5 13.5c-.5 .2-.5 .2-3 1c-1 1.3-2 2.7-3 4c-4.5 3-4.5 3-10.5 5c-2.7-.6-5.3-1.3-8-2c-1.7 1.2-3.3 2.3-5 3.5c-2 1-2 1-4 .5c.5-1.5 .5-1.5 6-5c.3-.3 .3-.3 2-2c2.4 0 4.9 0 7.5 0c.5 .5 1 1 1.5 1.5c4.3-.6 7.1-2.3 10.5-5c3.5-5.5 3.5-5.5 4-7c1.5 0 2.9 0 4.5 0c4.3-8.7 4.6-14 2.5-23.5c-.3-1-.7-2-1-3c.3 .1 .3 .1 2 .5c1.5 2.5 1.5 2.5 1.5 4c.5-.2 1-.3 1.5-.5c-.8-10-4.3-16-11.4-22.8c-3.7-3.8-5.5-6.9-7.6-11.7c-.7-1.5-1.5-2.9-2.3-4.4c-1.7-3.6-1.7-3.6-1.7-5.6c.3-.2 .7-.3 1-.5c-.5-3-.5-3-4.5-7c-2-1-3.9-2-6-3c-.7-.4-1.3-.6-2-1c0-.6 0-1.3 0-2zM33.5 83.5c-1 4-5.1 6.4-8 9c-8.6 8.1-14.6 17.2-15.2 29.2c.3 8.1 .3 8.1 2.2 11.8c.5 0 1 0 1.5 0c.4-2.9 .6-6 1-9c.6 0 1.3 0 2 0c.2 1.3 .4 2.7 .6 4.1c1.8 9.3 5.3 14.8 12.9 20.4c2.3 1.2 4.6 2.3 7 3.5c.7 .6 .7 .6 4.5 3.5c3.5 0 3.5 0 8.5-1.5c1 0 2 0 3 0c-1 2-1 2-2.5 3.5c-6.5 1.7-9.6 .9-16-1.5c-1.1-1-2.3-2-3.5-3c-1.5-.7-2.9-1.3-4.5-2c-3.9-3.4-6.9-6.5-9.5-11c-1.1-.5-2.3-1-3.5-1.5c-5.9-6.7-8-12.7-8-21.5c2.4-12.2 7.2-19.8 16.5-28c4.5-4 4.5-4 6-5.5c3.5-1 3.5-1 5-.5zM50.5 172c2.5 1 2.5 1 7 6.5c6 6.2 12 11.8 18.8 17.1c5.7 4.5 11.3 9.1 16.7 13.9c1.3 1.2 2.7 2.3 4 3.5c.7 .7 1.3 1.3 2 2c-.2 .8-.3 1.7-.5 2.5c-6 2.9-11.7 4-18.5 4c-2.6-2.6-4.3-5.5-5.5-9c-.3 .1-.3 .1-1.5 .5c0 2.3 0 4.6 0 7c-.3 .5-.7 1-1 1.5c-13.3 0-24.6-4.6-34.5-13.5c-4.5-4.5-4.5-4.5-8.5-11c.5-2.5 .5-2.5 4.5-7c5-3 5-3 8-3.5c.5 .3 1 .7 1.5 1c2-.5 2-.5 4-2c.9-4.2 1.7-8.3 2.5-12.5c.4-.3 .6-.7 1-1z"/><path fill="#03B5BD" d="M58.5 149.5c2.5 .5 2.5 .5 7 3.5c9.8 5.2 18.9 7.9 30 8.5c2.2 .5 4.3 1 6.5 1.5c7 0 7 0 9-.5c2.3-.2 4.6-.3 7-.5c3.2-.9 6.3-1.9 9.5-3c1.3-.2 2.7-.3 4-.5c.2 .2 .3 .3 .5 .5c-.8 6.3-4.1 10.2-8 15c-.2 .5-.3 1-.5 1.5c1.7-1 3.3-2 5-3c.7 .2 1.3 .3 2 .5c-3 12.2-9.3 20.3-17.5 29.5c-.7 .7-.7 .7-4 4.5c-1 .8-2 1.7-3 2.5c-3.5-.5-3.5-.5-8-5c-2.3-1.8-4.7-3.7-7-5.5c-2.7-2.2-5.3-4.3-8-6.5c-.8-.6-.8-.6-4.5-3.5c-11-9.3-20.7-18.8-28.5-31c3-4 3-4 8.5-8.5z"/><ellipse cx="94" cy="94" rx="6.5" ry="6.5" fill="#212E34"/><ellipse cx="135" cy="85.6" rx="6.2" ry="5.8" fill="#212E34"/><g fill="#FAF9F8"><circle cx="91.5" cy="91.5" r="2.1"/><circle cx="134.6" cy="84.3" r="2"/></g><ellipse cx="124" cy="93.8" rx="3.4" ry="1.1" fill="#9B9D9E" transform="rotate(-6 124 93.8)"/></g></g><rect x="5" y="5" width="230" height="230" rx="45" fill="none" stroke="#5B6B73" stroke-width="10"/></symbol></defs>
2
2
  <rect x="4" y="4" width="952" height="632" rx="18" fill="#151D22" stroke="#34434B" stroke-width="1.5"/>
3
3
  <path d="M4 22a18 18 0 0 1 18-18h916a18 18 0 0 1 18 18v24H4z" fill="#212E34"/>
4
4
  <circle cx="26" cy="25" r="5" fill="#9B9D9E" opacity="0.55"/>