wizz-method 1.3.2 → 1.4.1

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 (225) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/README.md +1 -1
  3. package/evals/routing/dataset.json +290 -0
  4. package/evals/routing/run-routing-eval.mjs +89 -0
  5. package/package.json +11 -3
  6. package/skills-registry.yaml +72 -25
  7. package/src/modules/wizz/agents/wizz-ads/SKILL.md +1 -1
  8. package/src/modules/wizz/agents/wizz-copy/SKILL.md +1 -1
  9. package/src/modules/wizz/agents/wizz-designer/SKILL.md +6 -8
  10. package/src/modules/wizz/agents/wizz-growth/SKILL.md +1 -1
  11. package/src/modules/wizz/agents/wizz-maestro/SKILL.md +6 -5
  12. package/src/modules/wizz/agents/wizz-memoria/SKILL.md +1 -1
  13. package/src/modules/wizz/agents/wizz-qa/SKILL.md +1 -1
  14. package/src/modules/wizz/agents/wizz-seo/SKILL.md +1 -1
  15. package/src/modules/wizz/agents/wizz-social/SKILL.md +1 -1
  16. package/src/skills-lib/ai-product-design/SKILL.md +85 -0
  17. package/src/skills-lib/ai-product-design/references/LICENSE +21 -0
  18. package/src/skills-lib/ai-product-design/references/ai-alignment-reasoning/bias-detection-design/SKILL.md +37 -0
  19. package/src/skills-lib/ai-product-design/references/ai-alignment-reasoning/consent-and-agency/SKILL.md +37 -0
  20. package/src/skills-lib/ai-product-design/references/ai-alignment-reasoning/escalation-design/SKILL.md +40 -0
  21. package/src/skills-lib/ai-product-design/references/ai-alignment-reasoning/guardrail-design/SKILL.md +36 -0
  22. package/src/skills-lib/ai-product-design/references/ai-alignment-reasoning/harm-anticipation/SKILL.md +108 -0
  23. package/src/skills-lib/ai-product-design/references/ai-alignment-reasoning/transparency-patterns/SKILL.md +36 -0
  24. package/src/skills-lib/ai-product-design/references/ai-alignment-reasoning/trust-calibration/SKILL.md +102 -0
  25. package/src/skills-lib/ai-product-design/references/ai-alignment-reasoning/value-specification/SKILL.md +39 -0
  26. package/src/skills-lib/ai-product-design/references/design-agent-orchestration/agent-role-design/SKILL.md +37 -0
  27. package/src/skills-lib/ai-product-design/references/design-agent-orchestration/failure-recovery/SKILL.md +37 -0
  28. package/src/skills-lib/ai-product-design/references/design-agent-orchestration/handoff-protocols/SKILL.md +45 -0
  29. package/src/skills-lib/ai-product-design/references/design-agent-orchestration/human-in-the-loop/SKILL.md +39 -0
  30. package/src/skills-lib/ai-product-design/references/design-agent-orchestration/observability-design/SKILL.md +48 -0
  31. package/src/skills-lib/ai-product-design/references/design-agent-orchestration/state-management/SKILL.md +95 -0
  32. package/src/skills-lib/ai-product-design/references/design-agent-orchestration/task-decomposition/SKILL.md +36 -0
  33. package/src/skills-lib/ai-product-design/references/evaluation/comparative-evaluation/SKILL.md +39 -0
  34. package/src/skills-lib/ai-product-design/references/evaluation/failure-taxonomy/SKILL.md +47 -0
  35. package/src/skills-lib/ai-product-design/references/evaluation/heuristic-evaluation-ai/SKILL.md +46 -0
  36. package/src/skills-lib/ai-product-design/references/evaluation/longitudinal-measurement/SKILL.md +38 -0
  37. package/src/skills-lib/ai-product-design/references/evaluation/output-quality-rubrics/SKILL.md +41 -0
  38. package/src/skills-lib/ai-product-design/references/evaluation/task-success-metrics/SKILL.md +35 -0
  39. package/src/skills-lib/ai-product-design/references/evaluation/user-satisfaction-signals/SKILL.md +50 -0
  40. package/src/skills-lib/ai-product-design/references/model-interaction-design/context-window-design/SKILL.md +28 -0
  41. package/src/skills-lib/ai-product-design/references/model-interaction-design/conversation-patterns/SKILL.md +37 -0
  42. package/src/skills-lib/ai-product-design/references/model-interaction-design/feedback-loops/SKILL.md +35 -0
  43. package/src/skills-lib/ai-product-design/references/model-interaction-design/frustration-detection/SKILL.md +91 -0
  44. package/src/skills-lib/ai-product-design/references/model-interaction-design/generative-ui/SKILL.md +34 -0
  45. package/src/skills-lib/ai-product-design/references/model-interaction-design/mixed-initiative-flow/SKILL.md +39 -0
  46. package/src/skills-lib/ai-product-design/references/model-interaction-design/multimodal-orchestration/SKILL.md +94 -0
  47. package/src/skills-lib/ai-product-design/references/model-interaction-design/progressive-disclosure/SKILL.md +33 -0
  48. package/src/skills-lib/ai-product-design/references/prompt-architecture/chain-of-thought-design/SKILL.md +45 -0
  49. package/src/skills-lib/ai-product-design/references/prompt-architecture/constraint-specification/SKILL.md +50 -0
  50. package/src/skills-lib/ai-product-design/references/prompt-architecture/context-engineering/SKILL.md +45 -0
  51. package/src/skills-lib/ai-product-design/references/prompt-architecture/few-shot-patterns/SKILL.md +47 -0
  52. package/src/skills-lib/ai-product-design/references/prompt-architecture/prompt-versioning/SKILL.md +42 -0
  53. package/src/skills-lib/ai-product-design/references/prompt-architecture/system-prompt-structure/SKILL.md +50 -0
  54. package/src/skills-lib/ai-product-design/references/prompt-architecture/template-design/SKILL.md +42 -0
  55. package/src/skills-lib/ai-product-design/references/system-behavior-shaping/behavioral-consistency/SKILL.md +36 -0
  56. package/src/skills-lib/ai-product-design/references/system-behavior-shaping/cultural-adaptation/SKILL.md +38 -0
  57. package/src/skills-lib/ai-product-design/references/system-behavior-shaping/domain-voice/SKILL.md +51 -0
  58. package/src/skills-lib/ai-product-design/references/system-behavior-shaping/emotional-design/SKILL.md +51 -0
  59. package/src/skills-lib/ai-product-design/references/system-behavior-shaping/error-personality/SKILL.md +45 -0
  60. package/src/skills-lib/ai-product-design/references/system-behavior-shaping/persona-architecture/SKILL.md +38 -0
  61. package/src/skills-lib/ai-product-design/references/system-behavior-shaping/tone-calibration/SKILL.md +39 -0
  62. package/src/skills-lib/algorithmic-art/LICENSE.txt +202 -0
  63. package/src/skills-lib/algorithmic-art/SKILL.md +405 -0
  64. package/src/skills-lib/algorithmic-art/templates/generator_template.js +223 -0
  65. package/src/skills-lib/algorithmic-art/templates/viewer.html +599 -0
  66. package/src/skills-lib/animate/README.md +41 -0
  67. package/src/skills-lib/animate/SKILL.md +179 -0
  68. package/src/skills-lib/animate/examples/animate-height.tsx +75 -0
  69. package/src/skills-lib/animate/examples/app-store-card.tsx +164 -0
  70. package/src/skills-lib/animate/examples/card-hover.tsx +79 -0
  71. package/src/skills-lib/animate/examples/feedback-popover.tsx +119 -0
  72. package/src/skills-lib/animate/examples/multi-step-flow.tsx +128 -0
  73. package/src/skills-lib/animate/examples/shared-layout.tsx +64 -0
  74. package/src/skills-lib/animate/examples/text-reveal.tsx +70 -0
  75. package/src/skills-lib/animate/examples/toast-stacking.tsx +95 -0
  76. package/src/skills-lib/animate/references/css-animations.md +232 -0
  77. package/src/skills-lib/animate/references/easing-and-timing.md +105 -0
  78. package/src/skills-lib/animate/references/framer-motion.md +356 -0
  79. package/src/skills-lib/animate/references/performance-accessibility.md +185 -0
  80. package/src/skills-lib/auth-and-secrets/SKILL.md +38 -10
  81. package/src/skills-lib/canvas-design/LICENSE.txt +202 -0
  82. package/src/skills-lib/canvas-design/SKILL.md +130 -0
  83. package/src/skills-lib/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  84. package/src/skills-lib/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  85. package/src/skills-lib/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  86. package/src/skills-lib/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
  87. package/src/skills-lib/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  88. package/src/skills-lib/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
  89. package/src/skills-lib/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  90. package/src/skills-lib/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  91. package/src/skills-lib/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  92. package/src/skills-lib/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  93. package/src/skills-lib/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  94. package/src/skills-lib/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  95. package/src/skills-lib/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  96. package/src/skills-lib/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  97. package/src/skills-lib/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
  98. package/src/skills-lib/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  99. package/src/skills-lib/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
  100. package/src/skills-lib/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  101. package/src/skills-lib/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  102. package/src/skills-lib/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
  103. package/src/skills-lib/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  104. package/src/skills-lib/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
  105. package/src/skills-lib/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  106. package/src/skills-lib/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  107. package/src/skills-lib/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  108. package/src/skills-lib/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  109. package/src/skills-lib/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  110. package/src/skills-lib/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  111. package/src/skills-lib/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  112. package/src/skills-lib/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  113. package/src/skills-lib/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  114. package/src/skills-lib/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  115. package/src/skills-lib/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  116. package/src/skills-lib/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  117. package/src/skills-lib/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  118. package/src/skills-lib/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  119. package/src/skills-lib/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  120. package/src/skills-lib/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
  121. package/src/skills-lib/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  122. package/src/skills-lib/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  123. package/src/skills-lib/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  124. package/src/skills-lib/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  125. package/src/skills-lib/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  126. package/src/skills-lib/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  127. package/src/skills-lib/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
  128. package/src/skills-lib/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  129. package/src/skills-lib/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  130. package/src/skills-lib/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  131. package/src/skills-lib/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  132. package/src/skills-lib/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  133. package/src/skills-lib/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
  134. package/src/skills-lib/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  135. package/src/skills-lib/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  136. package/src/skills-lib/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
  137. package/src/skills-lib/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  138. package/src/skills-lib/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  139. package/src/skills-lib/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  140. package/src/skills-lib/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  141. package/src/skills-lib/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
  142. package/src/skills-lib/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  143. package/src/skills-lib/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  144. package/src/skills-lib/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
  145. package/src/skills-lib/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
  146. package/src/skills-lib/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  147. package/src/skills-lib/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  148. package/src/skills-lib/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
  149. package/src/skills-lib/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  150. package/src/skills-lib/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
  151. package/src/skills-lib/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  152. package/src/skills-lib/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  153. package/src/skills-lib/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
  154. package/src/skills-lib/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  155. package/src/skills-lib/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
  156. package/src/skills-lib/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  157. package/src/skills-lib/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  158. package/src/skills-lib/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  159. package/src/skills-lib/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  160. package/src/skills-lib/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
  161. package/src/skills-lib/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  162. package/src/skills-lib/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
  163. package/src/skills-lib/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  164. package/src/skills-lib/design-motion-principles/SKILL.md +122 -0
  165. package/src/skills-lib/design-motion-principles/references/accessibility.md +52 -0
  166. package/src/skills-lib/design-motion-principles/references/anti-checklist.md +274 -0
  167. package/src/skills-lib/design-motion-principles/references/audit-checklist.md +137 -0
  168. package/src/skills-lib/design-motion-principles/references/creation-gotchas.md +36 -0
  169. package/src/skills-lib/design-motion-principles/references/demo-shell.html +275 -0
  170. package/src/skills-lib/design-motion-principles/references/emil-kowalski.md +121 -0
  171. package/src/skills-lib/design-motion-principles/references/jakub-krehel.md +80 -0
  172. package/src/skills-lib/design-motion-principles/references/jhey-tompkins.md +122 -0
  173. package/src/skills-lib/design-motion-principles/references/motion-cookbook.md +529 -0
  174. package/src/skills-lib/design-motion-principles/references/output-format.md +346 -0
  175. package/src/skills-lib/design-motion-principles/references/performance.md +82 -0
  176. package/src/skills-lib/design-motion-principles/references/report-template.html +1006 -0
  177. package/src/skills-lib/design-motion-principles/workflows/audit.md +203 -0
  178. package/src/skills-lib/design-motion-principles/workflows/create.md +87 -0
  179. package/src/skills-lib/desktop-security/SKILL.md +35 -4
  180. package/src/skills-lib/premium-landing-ui-researcher/SKILL.md +52 -2083
  181. package/src/skills-lib/premium-landing-ui-researcher/references/audit-protocol.md +124 -0
  182. package/src/skills-lib/premium-landing-ui-researcher/references/component-sources.md +336 -0
  183. package/src/skills-lib/premium-landing-ui-researcher/references/core-goal.md +62 -0
  184. package/src/skills-lib/premium-landing-ui-researcher/references/dashboard-and-portfolio-modes.md +92 -0
  185. package/src/skills-lib/premium-landing-ui-researcher/references/handoffs.md +55 -0
  186. package/src/skills-lib/premium-landing-ui-researcher/references/landing-page-strategy.md +227 -0
  187. package/src/skills-lib/premium-landing-ui-researcher/references/mandatory-process.md +36 -0
  188. package/src/skills-lib/premium-landing-ui-researcher/references/output-format-and-quality.md +129 -0
  189. package/src/skills-lib/premium-landing-ui-researcher/references/prompt-templates.md +206 -0
  190. package/src/skills-lib/premium-landing-ui-researcher/references/site-levels.md +515 -0
  191. package/src/skills-lib/premium-landing-ui-researcher/references/source-first-protocol.md +144 -0
  192. package/src/skills-lib/premium-landing-ui-researcher/references/source-links.md +39 -0
  193. package/src/skills-lib/premium-landing-ui-researcher/references/stack-and-visual-direction.md +118 -0
  194. package/src/skills-lib/taste-skill/SKILL.md +33 -1177
  195. package/src/skills-lib/taste-skill/references/anti-slop-tells.md +110 -0
  196. package/src/skills-lib/taste-skill/references/architecture-conventions.md +39 -0
  197. package/src/skills-lib/taste-skill/references/block-library.md +61 -0
  198. package/src/skills-lib/taste-skill/references/brief-and-dials.md +89 -0
  199. package/src/skills-lib/taste-skill/references/dark-mode.md +23 -0
  200. package/src/skills-lib/taste-skill/references/design-directives.md +191 -0
  201. package/src/skills-lib/taste-skill/references/design-systems.md +267 -0
  202. package/src/skills-lib/taste-skill/references/motion-patterns.md +167 -0
  203. package/src/skills-lib/taste-skill/references/pattern-vocabulary.md +78 -0
  204. package/src/skills-lib/taste-skill/references/performance-a11y.md +33 -0
  205. package/src/skills-lib/taste-skill/references/preflight-checklist.md +73 -0
  206. package/src/skills-lib/taste-skill/references/redesign-protocol.md +52 -0
  207. package/src/skills-lib/web-security/SKILL.md +74 -6
  208. package/src/skills-lib/wizz-router/SKILL.md +21 -243
  209. package/src/skills-lib/wizz-router/references/auditoria-360.md +21 -0
  210. package/src/skills-lib/wizz-router/references/routing-table-flat.md +95 -0
  211. package/src/squads/advisory-board/agents/ray-dalio.md +0 -20
  212. package/tools/hooks/README.md +17 -0
  213. package/tools/hooks/security-defensive-context.js +70 -0
  214. package/tools/hooks/session-rules.js +38 -0
  215. package/tools/hooks/wizz-router-enforce.js +188 -0
  216. package/tools/installer/modules/skills-lib.js +70 -1
  217. package/tools/sync-global.mjs +84 -0
  218. package/.claude/settings.local.json +0 -8
  219. package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-314.pyc +0 -0
  220. package/src/skills-lib/ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
  221. package/src/skills-lib/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
  222. package/src/skills-lib/ui-ux-pro-max/scripts/__pycache__/search.cpython-314.pyc +0 -0
  223. package/src/skills-lib/ui-ux-pro-max/scripts/graphify-out/cache/0964cef300f753b5cee0cc49777ca97035c85d45c212099405d9ee4da36e523f.json +0 -1
  224. package/src/skills-lib/ui-ux-pro-max/scripts/graphify-out/cache/21a40f6810c3a6f17acc3580f106171daf78c7b41bbd066bc80448a821ea0adf.json +0 -1
  225. package/src/skills-lib/ui-ux-pro-max/scripts/graphify-out/cache/69ea15a020ac81b8459e37ef63654398a50c1035fc01c43d8069df217389f7f3.json +0 -1
@@ -0,0 +1,1006 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Tally — Motion Audit · design-motion-principles</title>
7
+
8
+ <!--
9
+ ════════════════════════════════════════════════════════════════════
10
+ design-motion-principles MOTION AUDIT — reference template
11
+ worked example: "Tally" (habit & streak tracker · React + Framer Motion)
12
+ ════════════════════════════════════════════════════════════════════
13
+ Canonical HTML report for the motion audit. Self-contained: one file,
14
+ no local asset dependencies. Fonts load from a CDN with a robust
15
+ fallback stack and degrade gracefully offline.
16
+
17
+ DISTINCT SIBLING of thumb-first's report-template.html — same system
18
+ architecture (OKLCH tokens, mono micro-labels, severity channel,
19
+ finding cards), but its OWN identity: COOL slate-graphite (hue 255)
20
+ instead of warm graphite, a magenta-rose accent, and the Familjen /
21
+ Onest / Geist Mono type family. The personality is time + movement.
22
+
23
+ COLOR CHANNELS — keep them separate:
24
+ • Fixed identity .... cool slate-graphite + chalk. Carries NO meaning.
25
+ • --accent .......... ONE color SAMPLED FROM THE AUDITED PRODUCT.
26
+ Brand chrome only (eyebrow tick, section
27
+ numerals, links, recommended-motion accents).
28
+ NEVER used for severity or the timing ramp.
29
+ • Severity .......... fixed: --crit / --imp / --opp. Never adaptive.
30
+ • Timing ramp ....... fixed: instant/responsive (green) · deliberate
31
+ (amber) · sluggish (red). Diagram + legend only.
32
+ • Verdict chips ..... fixed: strong / concern / problem / mixed.
33
+
34
+ THE DEMO CARDS are the centerpiece — the only animated elements in the
35
+ document. Each Critical or Important finding gets a live, looping CSS
36
+ demo of the RECOMMENDED motion, with a pure-CSS Light/Dark stage toggle
37
+ so the reader can check the motion on either surface. Opportunities
38
+ never get a demo card. The report itself has NO entrance, scroll, or
39
+ mount animation — anything else would reproduce the slop this skill
40
+ audits against.
41
+
42
+ MODULAR SECTIONS — render a section ONLY if the audit produced it.
43
+ -->
44
+
45
+ <link rel="preconnect" href="https://fonts.googleapis.com">
46
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
47
+ <link href="https://fonts.googleapis.com/css2?family=Familjen+Grotesk:wght@400;500;600;700&family=Public+Sans:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
48
+
49
+ <style>
50
+ :root {
51
+ /* ——— Fixed identity: COOL slate-graphite + chalk (near-achromatic) ——— */
52
+ --ink: oklch(0.155 0.006 255);
53
+ --surface: oklch(0.195 0.007 255);
54
+ --surface-2: oklch(0.235 0.008 255);
55
+ --paper: oklch(0.945 0.004 255);
56
+ --paper-dim: oklch(0.800 0.006 255);
57
+ --muted: oklch(0.680 0.008 255);
58
+ --faint: oklch(0.505 0.008 255);
59
+ --line: oklch(0.95 0.01 255 / 0.09);
60
+ --line-strong:oklch(0.95 0.01 255 / 0.16);
61
+
62
+ /* ——— Accent (NEUTRAL BY DEFAULT) ———
63
+ The report has no primary color. --accent aliases the contrast
64
+ neutral (--paper) so any element that historically wanted a brand
65
+ splash now reads as quiet chrome. An individual audit can repoint
66
+ these three to a sampled brand color if appropriate, but the
67
+ default — and the worked example — keeps the chrome neutral and
68
+ lets severity be the only color in the report. */
69
+ --accent: var(--paper);
70
+ --accent-soft: var(--paper-dim);
71
+ --accent-tint: color-mix(in oklch, var(--paper) 12%, transparent);
72
+
73
+ /* ——— Severity (FIXED — never adaptive) ——— */
74
+ --crit: oklch(0.655 0.185 25);
75
+ --crit-tint: oklch(0.655 0.185 25 / 0.15);
76
+ --imp: oklch(0.805 0.125 78);
77
+ --imp-tint: oklch(0.805 0.125 78 / 0.15);
78
+ --opp: oklch(0.745 0.135 152);
79
+ --opp-tint: oklch(0.745 0.135 152 / 0.15);
80
+
81
+ /* ——— Timing-budget ramp (FIXED — diagram + its legend only) ——— */
82
+ --t-good: oklch(0.745 0.135 152); /* instant + responsive */
83
+ --t-mid: oklch(0.805 0.125 78); /* deliberate */
84
+ --t-slow: oklch(0.655 0.185 25); /* sluggish */
85
+
86
+ /* ——— Verdict (FIXED — lens table) ——— */
87
+ --strong: var(--opp);
88
+ --concern: var(--imp);
89
+ --problem: var(--crit);
90
+
91
+ /* ——— Spacing (4pt scale, semantic) ——— */
92
+ --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px;
93
+ --s6:32px; --s7:48px; --s8:64px; --s9:96px;
94
+
95
+ --display: "Familjen Grotesk", "Hanken Grotesk", system-ui, sans-serif;
96
+ --sans: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
97
+ --mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
98
+ }
99
+
100
+ /* ═══════════════ LIGHT-MODE OVERRIDES ═══════════════
101
+ Activated when the global theme toggle is set to Light. Every token
102
+ above gets a counterpart here — same hue, mirrored L — so the report
103
+ reads cleanly on light backgrounds. Hue stays 255 (cool slate) so the
104
+ identity is the same character, just inverted. */
105
+ :root:has(#theme-light:checked) {
106
+ --ink: oklch(0.985 0.003 255); /* light page bg */
107
+ --surface: oklch(0.965 0.004 255);
108
+ --surface-2: oklch(0.940 0.005 255);
109
+ --paper: oklch(0.180 0.006 255); /* dark text */
110
+ --paper-dim: oklch(0.330 0.008 255);
111
+ --muted: oklch(0.475 0.010 255);
112
+ --faint: oklch(0.640 0.009 255);
113
+ --line: oklch(0.180 0.010 255 / 0.12);
114
+ --line-strong:oklch(0.180 0.010 255 / 0.22);
115
+
116
+ /* Accent stays aliased to --paper (now the dark ink in light mode),
117
+ so chrome inverts automatically. No additional override needed. */
118
+
119
+ /* Severity: same hues, deeper L for legibility on light bg */
120
+ --crit: oklch(0.555 0.195 25);
121
+ --crit-tint: oklch(0.555 0.195 25 / 0.12);
122
+ --imp: oklch(0.580 0.155 78);
123
+ --imp-tint: oklch(0.580 0.155 78 / 0.14);
124
+ --opp: oklch(0.555 0.140 152);
125
+ --opp-tint: oklch(0.555 0.140 152 / 0.13);
126
+
127
+ /* Timing ramp follows severity */
128
+ --t-good: oklch(0.555 0.140 152);
129
+ --t-mid: oklch(0.580 0.155 78);
130
+ --t-slow: oklch(0.555 0.195 25);
131
+ }
132
+
133
+ /* SVG dot label: light text in light-mode (deeper dots), dark text in dark-mode (lighter dots) */
134
+ :root:has(#theme-light:checked) .tf-dot-text { fill: oklch(0.985 0.003 255); }
135
+
136
+ * { box-sizing: border-box; }
137
+ html, body { margin: 0; padding: 0; }
138
+ body {
139
+ background: var(--ink);
140
+ color: var(--paper);
141
+ font-family: var(--sans);
142
+ font-size: 16px;
143
+ line-height: 1.65;
144
+ -webkit-font-smoothing: antialiased;
145
+ text-rendering: optimizeLegibility;
146
+ }
147
+ ::selection { background: var(--accent-tint); }
148
+ a { color: var(--accent-soft); text-underline-offset: 3px; text-decoration-thickness: 1px; }
149
+
150
+ .wrap { max-width: 1080px; margin: 0 auto; padding: clamp(40px, 6vw, 72px) clamp(20px, 5vw, 40px) 96px; }
151
+
152
+ /* shared micro-label */
153
+ .mono-label {
154
+ font-family: var(--mono);
155
+ font-size: 10.5px;
156
+ letter-spacing: 0.18em;
157
+ text-transform: uppercase;
158
+ color: var(--muted);
159
+ font-weight: 500;
160
+ }
161
+ code { font-family: var(--mono); font-size: 0.86em; color: var(--paper-dim); }
162
+
163
+ /* visually-hidden (theme + stage radios) */
164
+ .vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
165
+
166
+ /* ───────────── Global theme switch ───────────── */
167
+ .theme-switch { display: flex; justify-content: flex-end; margin-bottom: var(--s5); }
168
+ .theme-switch__pill { display: inline-flex; align-items: center; gap: 0; border: 1px solid var(--line-strong); border-radius: 100px; overflow: hidden; }
169
+ .theme-switch__pill label { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); padding: 6px 14px; cursor: pointer; user-select: none; transition: background 0.15s ease, color 0.15s ease; }
170
+ #theme-dark:checked ~ .theme-switch__pill label[for="theme-dark"],
171
+ #theme-light:checked ~ .theme-switch__pill label[for="theme-light"] { background: var(--accent-tint); color: var(--accent-soft); }
172
+
173
+ /* ───────────── Header ───────────── */
174
+ header.doc { padding-bottom: var(--s7); margin-bottom: var(--s8); border-bottom: 1px solid var(--line); }
175
+ .eyebrow {
176
+ font-family: var(--mono);
177
+ font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
178
+ color: var(--muted); display: inline-flex; align-items: center; gap: var(--s3);
179
+ }
180
+ .eyebrow::before { content: ""; width: 22px; height: 2px; background: var(--accent); display: inline-block; }
181
+ h1.title {
182
+ font-family: var(--display);
183
+ font-size: clamp(38px, 6vw, 60px);
184
+ font-weight: 700; letter-spacing: -0.025em; line-height: 1.02;
185
+ margin: var(--s5) 0 var(--s4); color: var(--paper); max-width: 17ch;
186
+ }
187
+ .lede { font-size: clamp(17px, 2.2vw, 19px); color: var(--paper-dim); max-width: 66ch; line-height: 1.6; }
188
+ .meta-row { display: flex; flex-wrap: wrap; gap: var(--s6); margin-top: var(--s6); }
189
+ .meta-row .k { display: block; margin-bottom: 4px; }
190
+ .meta-row .v { font-size: 15px; color: var(--paper); }
191
+
192
+ .stats { display: flex; gap: clamp(20px, 4vw, 44px); margin-top: var(--s7); flex-wrap: wrap; }
193
+ .stat .num { font-family: var(--display); font-size: 40px; font-weight: 600; letter-spacing: -0.02em; line-height: 1; font-variant-numeric: tabular-nums; }
194
+ .stat.crit .num { color: var(--crit); }
195
+ .stat.imp .num { color: var(--imp); }
196
+ .stat.opp .num { color: var(--opp); }
197
+ .stat .lbl { margin-top: var(--s2); }
198
+ .stat a { color: inherit; text-decoration: none; }
199
+ .stat a:hover .num { text-decoration: underline; text-underline-offset: 5px; text-decoration-thickness: 2px; }
200
+
201
+ /* ───────────── Section scaffolding ───────────── */
202
+ section { margin-bottom: var(--s9); }
203
+ .sec-index { font-family: var(--mono); font-size: 12px; letter-spacing: 0.16em; color: var(--accent-soft); }
204
+ h2.section-title {
205
+ font-family: var(--display);
206
+ font-size: clamp(24px, 3.4vw, 32px);
207
+ font-weight: 600; letter-spacing: -0.02em; line-height: 1.1;
208
+ margin: var(--s2) 0 var(--s4); color: var(--paper);
209
+ }
210
+ .section-sub { color: var(--paper-dim); max-width: 74ch; margin: 0 0 var(--s6); }
211
+ .section-sub em { color: var(--paper); font-style: italic; }
212
+
213
+ /* ───────────── Overall assessment ───────────── */
214
+ .assessment { border-top: 1px solid var(--line); padding-top: var(--s6); }
215
+ .assessment .mono-label { color: var(--accent-soft); }
216
+ .assessment p { font-family: var(--display); font-weight: 400; font-size: clamp(20px, 2.8vw, 26px); line-height: 1.42; letter-spacing: -0.01em; color: var(--paper); max-width: 34ch; margin: var(--s4) 0 0; }
217
+ .assessment p em { font-style: italic; color: var(--accent-soft); }
218
+
219
+ /* ───────────── Lens summary table ───────────── */
220
+ .lens-table { width: 100%; border-collapse: collapse; border: 1px solid var(--line); }
221
+ .lens-table th, .lens-table td { padding: var(--s4) var(--s5); text-align: left; border-bottom: 1px solid var(--line); vertical-align: top; }
222
+ .lens-table tr:last-child td { border-bottom: none; }
223
+ .lens-table thead th { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--faint); font-weight: 500; }
224
+ .lens-table tbody tr:hover { background: oklch(0.95 0.01 255 / 0.018); }
225
+ .lens-name { font-weight: 600; color: var(--paper); width: 28%; }
226
+ .lens-name .who { display: block; margin-top: 2px; color: var(--muted); font-weight: 400; font-size: 13.5px; }
227
+ .lens-name .weight { display: inline-block; margin-top: 6px; font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent-soft); }
228
+ .lens-read { color: var(--paper-dim); }
229
+ .lens-read em { color: var(--paper); font-style: italic; }
230
+ .verdict { display: inline-flex; align-items: center; gap: 7px; font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.12em; text-transform: uppercase; font-weight: 500; }
231
+ .verdict::before { content: ""; width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
232
+ .v-strong { color: var(--strong); } .v-strong::before { background: var(--strong); }
233
+ .v-concern { color: var(--concern); } .v-concern::before { background: var(--concern); }
234
+ .v-problem { color: var(--problem); } .v-problem::before { background: var(--problem); }
235
+ .v-mixed { color: var(--muted); } .v-mixed::before { background: var(--muted); }
236
+
237
+ /* ───────────── Timing-budget diagram ───────────── */
238
+ .timing-grid { display: grid; grid-template-columns: 1fr 320px; gap: clamp(28px, 5vw, 56px); align-items: start; }
239
+ @media (max-width: 860px) { .timing-grid { grid-template-columns: 1fr; } }
240
+ .timing-figure { width: 100%; height: auto; }
241
+ .timing-key h4 { margin: 0 0 var(--s4); }
242
+ .key-list { list-style: none; margin: 0 0 var(--s6); padding: 0; display: grid; gap: 2px; }
243
+ .key-list li { display: grid; grid-template-columns: 22px 1fr auto; align-items: baseline; gap: var(--s3); padding: var(--s2) 0; border-bottom: 1px solid var(--line); }
244
+ .key-list li:last-child { border-bottom: none; }
245
+ .key-list .kn { font-family: var(--mono); font-size: 12px; color: var(--paper); font-variant-numeric: tabular-nums; }
246
+ .key-list .ka { color: var(--paper-dim); }
247
+ .key-list .kt { font-family: var(--mono); font-size: 11px; letter-spacing: 0.04em; font-variant-numeric: tabular-nums; }
248
+ .kt.good { color: var(--t-good); }
249
+ .kt.mid { color: var(--t-mid); }
250
+ .kt.slow { color: var(--t-slow); }
251
+ .kt.none { color: var(--faint); }
252
+ .zone-defs { display: grid; gap: var(--s3); margin-top: var(--s5); }
253
+ .zone-def { display: grid; grid-template-columns: 12px 1fr; gap: var(--s3); align-items: start; }
254
+ .zone-def .dot { width: 11px; height: 11px; border-radius: 50%; margin-top: 6px; }
255
+ .zone-def .zt { font-weight: 600; color: var(--paper); }
256
+ .zone-def .zd { font-size: 13.5px; color: var(--muted); }
257
+ .whats-off { margin-top: var(--s5); padding-top: var(--s5); border-top: 1px solid var(--line); }
258
+ .whats-off .mono-label { color: var(--accent-soft); }
259
+ .whats-off p { margin: var(--s2) 0 0; color: var(--paper-dim); font-size: 14px; max-width: 60ch; }
260
+
261
+ /* ───────────── Per-lens sections ───────────── */
262
+ .lens-sec { margin-bottom: var(--s9); padding-top: var(--s6); border-top: 1px solid var(--line); }
263
+ .lens-sec__head { display: flex; align-items: baseline; gap: var(--s4); flex-wrap: wrap; margin-bottom: var(--s2); }
264
+ .lens-sec__head h3 { font-family: var(--display); font-size: clamp(22px, 3vw, 28px); font-weight: 600; letter-spacing: -0.02em; line-height: 1.1; margin: 0; color: var(--paper); }
265
+ .lens-sec__weight { font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent-soft); border: 1px solid var(--line-strong); border-radius: 100px; padding: 4px 10px; }
266
+ .lens-sec__verdict { margin: 0 0 var(--s6); }
267
+
268
+ .lens-block { margin: var(--s6) 0; }
269
+ .lens-block > .mono-label { display: block; margin-bottom: var(--s4); }
270
+ .lens-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s3); max-width: 78ch; }
271
+ .lens-list li { display: grid; grid-template-columns: 22px 1fr; gap: var(--s2); align-items: start; color: var(--paper-dim); }
272
+ .lens-list .mk { font-family: var(--mono); font-size: 13px; line-height: 1.5; }
273
+ .lens-list.good .mk { color: var(--strong); }
274
+ .lens-list.opp .mk { color: var(--accent-soft); }
275
+ .lens-list li em { color: var(--paper); font-style: italic; }
276
+
277
+ .lens-take { margin-top: var(--s6); background: var(--surface-2); border-radius: 10px; padding: var(--s5); }
278
+ .lens-take .mono-label { color: var(--accent-soft); display: block; margin-bottom: var(--s2); }
279
+ .lens-take p { margin: 0; color: var(--paper); max-width: 70ch; }
280
+
281
+ .lens-empty { font-style: italic; color: var(--faint); padding: var(--s3) 0; }
282
+
283
+ /* ───────────── Finding rows (prose + live demo card) ───────────── */
284
+ .findings { display: flex; flex-direction: column; gap: var(--s5); }
285
+ .finding-row { display: grid; grid-template-columns: 1fr 380px; gap: clamp(24px, 3vw, 36px); align-items: start; background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: clamp(22px, 3vw, 30px); }
286
+ .finding-row[data-sev="crit"] { border-color: var(--crit-tint); }
287
+ @media (max-width: 860px) { .finding-row { grid-template-columns: 1fr; } }
288
+
289
+ .find-tags { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: center; margin-bottom: var(--s3); }
290
+ .severity { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.16em; text-transform: uppercase; font-weight: 500; padding: 4px 9px; border-radius: 100px; }
291
+ .severity.crit { background: var(--crit-tint); color: var(--crit); }
292
+ .severity.imp { background: var(--imp-tint); color: var(--imp); }
293
+ .severity.opp { background: var(--opp-tint); color: var(--opp); }
294
+ .lens-chip { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); padding: 4px 9px; border: 1px solid var(--line-strong); border-radius: 100px; }
295
+ .find-title { font-family: var(--display); font-size: clamp(19px, 2.4vw, 23px); font-weight: 600; letter-spacing: -0.015em; line-height: 1.22; margin: 0 0 var(--s4); color: var(--paper); }
296
+ .find-body { display: grid; gap: var(--s4); }
297
+ .find-body p { margin: 0; color: var(--paper-dim); }
298
+ .find-body p em { color: var(--paper); font-style: italic; }
299
+ .find-body .label { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); display: block; margin-bottom: 3px; }
300
+ .fix { background: var(--surface-2); border-radius: 8px; padding: var(--s4) var(--s5); }
301
+ .fix .label { color: var(--accent-soft); }
302
+ .fix p { color: var(--paper); }
303
+ .find-loc { font-size: 13px; }
304
+
305
+ /* ───────────── Demo card (the centerpiece) ───────────── */
306
+ .demo { position: relative; align-self: start; position: sticky; top: var(--s5); }
307
+ @media (max-width: 860px) { .demo { position: static; } }
308
+ .demo__bar { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3); margin-bottom: var(--s3); }
309
+ .demo__meta { min-width: 0; }
310
+ .demo__title { display: block; font-size: 13.5px; font-weight: 600; color: var(--paper); line-height: 1.3; }
311
+ .demo__timing { display: block; font-family: var(--mono); font-size: 11px; color: var(--muted); margin-top: 2px; font-variant-numeric: tabular-nums; }
312
+ .demo__controls { display: flex; align-items: center; gap: var(--s3); flex-shrink: 0; }
313
+ .demo__loop { font-family: var(--mono); font-size: 12px; color: var(--faint); }
314
+
315
+ /* segmented Auto/Light/Dark stage toggle (pure CSS).
316
+ Auto = follow global theme via theme tokens. Light/Dark = locked overrides. */
317
+ .seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 100px; overflow: hidden; }
318
+ .seg label { font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); padding: 4px 9px; cursor: pointer; user-select: none; transition: background 0.15s ease, color 0.15s ease; }
319
+ .demo input[id$="-a"]:checked ~ .demo__bar .seg label[for$="-a"],
320
+ .demo input[id$="-l"]:checked ~ .demo__bar .seg label[for$="-l"],
321
+ .demo input[id$="-d"]:checked ~ .demo__bar .seg label[for$="-d"] { background: var(--accent-tint); color: var(--accent-soft); }
322
+
323
+ .demo__stage {
324
+ /* AUTO default — stage uses the report's elevated surface, follows theme */
325
+ --st-bg: var(--surface-2);
326
+ --st-fg: var(--paper);
327
+ --st-line: var(--line-strong);
328
+ --st-dim: color-mix(in oklch, var(--paper) 55%, transparent);
329
+ background: var(--st-bg); color: var(--st-fg);
330
+ border: 1px solid var(--line); border-radius: 10px;
331
+ padding: var(--s6) var(--s5); min-height: 168px;
332
+ display: flex; align-items: center; justify-content: center; overflow: hidden;
333
+ }
334
+ /* Locked Light: hardcoded light, regardless of report theme */
335
+ .demo input[id$="-l"]:checked ~ .demo__stage {
336
+ --st-bg: oklch(0.985 0.003 255);
337
+ --st-fg: oklch(0.180 0.006 255);
338
+ --st-line: oklch(0.180 0.010 255 / 0.18);
339
+ --st-dim: oklch(0.180 0.010 255 / 0.45);
340
+ }
341
+ /* Locked Dark: hardcoded dark, regardless of report theme */
342
+ .demo input[id$="-d"]:checked ~ .demo__stage {
343
+ --st-bg: oklch(0.180 0.006 255);
344
+ --st-fg: oklch(0.985 0.003 255);
345
+ --st-line: oklch(0.985 0.010 255 / 0.18);
346
+ --st-dim: oklch(0.985 0.010 255 / 0.50);
347
+ }
348
+
349
+ /* generic demo primitives, themed by the stage vars
350
+ (intentionally follow --st-fg/--st-bg, NOT the page accent — so they
351
+ contrast correctly even when a stage is locked to a different theme
352
+ than the page). */
353
+ .ui-btn { display: inline-flex; align-items: center; justify-content: center; padding: 9px 18px; border-radius: 8px; background: var(--st-fg); color: var(--st-bg); font-family: var(--sans); font-size: 13px; font-weight: 600; }
354
+ .ui-card { background: var(--st-bg); border: 1px solid var(--st-line); border-radius: 10px; padding: 14px 16px; min-width: 180px; box-shadow: 0 8px 24px oklch(0 0 0 / 0.12); }
355
+ .ui-row { display: flex; align-items: center; gap: 10px; font-size: 13px; color: var(--st-fg); }
356
+ .ui-check { width: 22px; height: 22px; border-radius: 7px; background: var(--st-fg); display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
357
+ .ui-check svg { width: 13px; height: 13px; stroke: var(--st-bg); }
358
+ .ui-num { font-family: var(--display); font-size: 34px; font-weight: 700; color: var(--st-fg); font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
359
+ .ui-label { font-family: var(--mono); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--st-dim); }
360
+ /* milestone badge + sparks read off stage tokens (--st-fg disc, --st-bg ink),
361
+ set inline on the elements — same contract as every other stage primitive,
362
+ never --accent, correct in any locked stage. */
363
+
364
+ /* ───────────── Combined recommendations ───────────── */
365
+ .rec { margin-bottom: var(--s7); }
366
+ .rec .tier-label { display: flex; align-items: center; gap: var(--s4); margin: 0 0 var(--s4); }
367
+ .rec .tier-label .mono-label { white-space: nowrap; }
368
+ .rec .tier-label .rule { height: 1px; background: var(--line); flex: 1; }
369
+ .rec .tier-label .count { font-family: var(--mono); font-size: 11px; color: var(--faint); }
370
+ .rec.crit .mono-label { color: var(--crit); }
371
+ .rec.imp .mono-label { color: var(--imp); }
372
+ .rec.opp .mono-label { color: var(--opp); }
373
+ .rec-table { width: 100%; border-collapse: collapse; border: 1px solid var(--line); font-size: 14px; }
374
+ .rec-table th, .rec-table td { text-align: left; padding: var(--s3) var(--s4); border-bottom: 1px solid var(--line); vertical-align: top; }
375
+ .rec-table tr:last-child td { border-bottom: none; }
376
+ .rec-table thead th { font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); font-weight: 500; }
377
+ .rec-table td:first-child { color: var(--paper); }
378
+ .rec-table td:nth-child(2) { white-space: nowrap; }
379
+ .rec-table td { color: var(--paper-dim); }
380
+
381
+ /* ───────────── Reference summary (closing) ───────────── */
382
+ .ref-summary { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: clamp(24px, 4vw, 40px); }
383
+ .ref-summary .big { font-family: var(--display); font-size: clamp(18px, 2.4vw, 22px); font-weight: 600; letter-spacing: -0.015em; color: var(--paper); margin: var(--s2) 0 0; }
384
+ .ref-summary .why { color: var(--paper-dim); margin: var(--s3) 0 var(--s6); max-width: 64ch; }
385
+ .ref-lean { display: grid; gap: var(--s3); margin: 0; padding: 0; list-style: none; }
386
+ .ref-lean li { display: grid; grid-template-columns: 130px 1fr; gap: var(--s4); padding: var(--s3) 0; border-top: 1px solid var(--line); color: var(--paper-dim); font-size: 14px; }
387
+ .ref-lean .nm { font-weight: 600; color: var(--paper); }
388
+
389
+ footer.doc { margin-top: var(--s8); padding-top: var(--s5); border-top: 1px solid var(--line); font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em; color: var(--faint); display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--s3); }
390
+
391
+ /* ───────────── Narrow screens ───────────── */
392
+ @media (max-width: 600px) {
393
+ .lens-table { display: block; padding: var(--s2) var(--s4); }
394
+ .lens-table thead { display: none; }
395
+ .lens-table tbody, .lens-table tr, .lens-table td { display: block; }
396
+ .lens-table tr { padding: var(--s4) 0; border-bottom: 1px solid var(--line); }
397
+ .lens-table tr:last-child { border-bottom: none; }
398
+ .lens-table td { padding: 0; border: none; }
399
+ .lens-table .lens-name { width: auto; margin-bottom: var(--s2); }
400
+ .lens-table .verdict { margin-bottom: var(--s2); }
401
+ .ref-lean li { grid-template-columns: 1fr; gap: var(--s1); }
402
+ }
403
+
404
+ /* ───────────── The ONLY animation in the report: the demo cards ───────────── */
405
+ @media (prefers-reduced-motion: no-preference) {
406
+ .lens-table tbody tr, .finding-row { transition: border-color 0.2s ease; }
407
+
408
+ /* 01 · quick tab crossfade — 180ms ease-out */
409
+ @keyframes m1 {
410
+ 0% { opacity: 0; transform: translateX(7px); }
411
+ 55% { opacity: 1; transform: translateX(0); }
412
+ 100% { opacity: 1; transform: translateX(0); }
413
+ }
414
+ .demo-1__mt { animation: m1 3s cubic-bezier(0.22,1,0.36,1) infinite; }
415
+
416
+ /* 02 · sheet enter (mirror for exit) — 300ms ease-out-quint */
417
+ @keyframes m2 {
418
+ 0% { opacity: 0; transform: translateY(115%); }
419
+ 45% { opacity: 1; transform: translateY(0); }
420
+ 100% { opacity: 1; transform: translateY(0); }
421
+ }
422
+ .demo-2__mt { animation: m2 3s cubic-bezier(0.22,1,0.36,1) infinite; }
423
+
424
+ /* 03 · calm check — 200ms ease-out, no bounce */
425
+ @keyframes m3 {
426
+ 0% { opacity: 0; transform: scale(0.9); }
427
+ 55% { opacity: 1; transform: scale(1); }
428
+ 100% { opacity: 1; transform: scale(1); }
429
+ }
430
+ .demo-3__mt .ui-check { animation: m3 3s cubic-bezier(0.33,1,0.68,1) infinite; transform-origin: center; }
431
+
432
+ /* 04 · number roll — 220ms, opacity + translateY + blur */
433
+ @keyframes m4 {
434
+ 0% { opacity: 0; transform: translateY(10px); filter: blur(5px); }
435
+ 55% { opacity: 1; transform: translateY(0); filter: blur(0); }
436
+ 100% { opacity: 1; transform: translateY(0); filter: blur(0); }
437
+ }
438
+ .demo-4__mt .ui-num { animation: m4 3s cubic-bezier(0.22,1,0.36,1) infinite; display: inline-block; }
439
+
440
+ /* 05 · milestone badge enter — 260ms ease-out + one-shot sparkle */
441
+ @keyframes m5 {
442
+ 0% { opacity: 0; transform: scale(0.8); }
443
+ 50% { opacity: 1; transform: scale(1); }
444
+ 100% { opacity: 1; transform: scale(1); }
445
+ }
446
+ @keyframes m5spark {
447
+ 0% { opacity: 0; transform: translate(0,0) scale(0.4); }
448
+ 35% { opacity: 1; }
449
+ 70% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(1); }
450
+ 100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(1); }
451
+ }
452
+ .demo-5__mt .badge { animation: m5 3s cubic-bezier(0.34,1.0,0.5,1) infinite; transform-origin: center; }
453
+ .demo-5__mt .spark { animation: m5spark 3s ease-out infinite; }
454
+ }
455
+
456
+ @media (prefers-reduced-motion: reduce) {
457
+ .demo__loop { display: none; }
458
+ [class*="__mt"], [class*="__mt"] * { animation: none !important; transition: none !important; }
459
+ .demo-5__mt .spark { opacity: 0; }
460
+ }
461
+ </style>
462
+ </head>
463
+ <body>
464
+ <div class="wrap">
465
+
466
+ <!-- ═══════════ GLOBAL THEME TOGGLE ═══════════ -->
467
+ <div class="theme-switch" role="radiogroup" aria-label="Report theme">
468
+ <input class="vh" type="radio" name="theme" id="theme-dark" checked>
469
+ <input class="vh" type="radio" name="theme" id="theme-light">
470
+ <div class="theme-switch__pill">
471
+ <label for="theme-dark">Dark</label>
472
+ <label for="theme-light">Light</label>
473
+ </div>
474
+ </div>
475
+
476
+ <!-- ═══════════ HEADER ═══════════ -->
477
+ <header class="doc">
478
+ <div class="eyebrow">Motion audit · design-motion-principles</div>
479
+ <h1 class="title">Tally — what to slow down, speed up, and finish</h1>
480
+ <p class="lede">
481
+ A pass over Tally's core loop — check-off, tab switches, the add-habit sheet, and the streak milestones —
482
+ read through three motion practitioners' lenses, ordered by what users feel most and the order to fix it.
483
+ </p>
484
+ <div class="meta-row">
485
+ <div><span class="mono-label k">What it is</span><span class="v">Habit &amp; streak tracker, used in quick daily bursts</span></div>
486
+ <div><span class="mono-label k">Stack</span><span class="v">Mobile web · React + Framer Motion</span></div>
487
+ </div>
488
+ <div class="stats">
489
+ <div class="stat"><div class="num">7</div><div class="mono-label lbl">Findings</div></div>
490
+ <div class="stat crit"><a href="#rec-crit"><div class="num">2</div><div class="mono-label lbl">Critical</div></a></div>
491
+ <div class="stat imp"><a href="#rec-imp"><div class="num">3</div><div class="mono-label lbl">Important</div></a></div>
492
+ <div class="stat opp"><a href="#rec-opp"><div class="num">2</div><div class="mono-label lbl">Opportunities</div></a></div>
493
+ </div>
494
+ </header>
495
+
496
+ <!-- ═══════════ ASSESSMENT ═══════════ -->
497
+ <section class="assessment">
498
+ <span class="mono-label">Overall</span>
499
+ <p>
500
+ Tally has real motion personality — but it's <em>uneven</em>. The highest-frequency action, the check-off,
501
+ is the most over-animated; the add-habit sheet that should glide just snaps shut. Tighten the frequent
502
+ moments toward speed, finish the half-built transitions, then spend delight where it's earned: the streaks.
503
+ </p>
504
+ </section>
505
+
506
+ <!-- ═══════════ 01 · LENS SUMMARY ═══════════ -->
507
+ <section>
508
+ <div class="sec-index">01</div>
509
+ <h2 class="section-title">How each practitioner reads the motion</h2>
510
+ <p class="section-sub">
511
+ Three lenses, weighted for this context — a frequently-opened utility where most motion should get out of
512
+ the way, and a little should be memorable. The read is what each would push on hardest today.
513
+ </p>
514
+ <table class="lens-table">
515
+ <thead><tr><th>Lens</th><th>Verdict</th><th>One-line read</th></tr></thead>
516
+ <tbody>
517
+ <tr>
518
+ <td class="lens-name">Restraint &amp; Speed <span class="who">Emil Kowalski</span><span class="weight">Secondary</span></td>
519
+ <td><span class="verdict v-concern">Concern</span></td>
520
+ <td class="lens-read">The check-off and tab switches are over-animated for actions this frequent — durations run long.</td>
521
+ </tr>
522
+ <tr>
523
+ <td class="lens-name">Production Polish <span class="who">Jakub Krehel</span><span class="weight">Primary</span></td>
524
+ <td><span class="verdict v-problem">Problem</span></td>
525
+ <td class="lens-read">Several transitions are half-built: <em>enters without exits</em>, state changes that snap. Craft is uneven.</td>
526
+ </tr>
527
+ <tr>
528
+ <td class="lens-name">Experimentation &amp; Delight <span class="who">Jhey Tompkins</span><span class="weight">Selective</span></td>
529
+ <td><span class="verdict v-strong">Strong</span></td>
530
+ <td class="lens-read">Restraint is mostly right. The open prize is making the streak milestones actually feel earned.</td>
531
+ </tr>
532
+ </tbody>
533
+ </table>
534
+ </section>
535
+
536
+ <!-- ═══════════ 02 · TIMING-BUDGET DIAGRAM ═══════════
537
+ The motion-native analog of thumb-first's thumb-zone map.
538
+ Each dot is one of Tally's animations, plotted by duration.
539
+ Zone bands describe the COST of a duration, not a hard rule. -->
540
+ <section>
541
+ <div class="sec-index">02</div>
542
+ <h2 class="section-title">Where the timings land</h2>
543
+ <p class="section-sub">
544
+ Duration is a budget. A 320ms sheet is correct — a large surface earns the time. A 600ms tab switch on an
545
+ action you fire dozens of times a session is the mistake. Each dot is one of Tally's animations, plotted
546
+ where it runs today.
547
+ </p>
548
+ <div class="timing-grid">
549
+ <svg class="timing-figure" viewBox="0 0 660 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Duration scale from 0 to 600 milliseconds showing where Tally's animations fall across instant, responsive, deliberate and sluggish zones">
550
+ <style>
551
+ /* Theme-aware: every fill below resolves via the report's CSS vars,
552
+ so the diagram re-tones when the global Light/Dark toggle flips. */
553
+ .tf-track-stop-good { stop-color: var(--t-good); }
554
+ .tf-track-stop-mid { stop-color: var(--t-mid); }
555
+ .tf-track-stop-slow { stop-color: var(--t-slow); }
556
+ .tf-track-stroke { stroke: var(--line-strong); }
557
+ .tf-grid { stroke: var(--line-strong); }
558
+ .tf-axis { fill: var(--faint); }
559
+ .tf-freq { fill: var(--muted); }
560
+ .tf-bracket { stroke: var(--faint); fill: none; }
561
+ .tf-zone-good { fill: var(--t-good); }
562
+ .tf-zone-mid { fill: var(--t-mid); }
563
+ .tf-zone-slow { fill: var(--t-slow); }
564
+ .tf-dot-good { fill: var(--t-good); }
565
+ .tf-dot-mid { fill: var(--t-mid); }
566
+ .tf-dot-slow { fill: var(--t-slow); }
567
+ /* Dot label flips per theme — dot fills are lighter in dark mode
568
+ (dark text reads on them) and deeper in light mode (light text reads). */
569
+ .tf-dot-text { fill: oklch(0.165 0.006 255); }
570
+ .tf-missing-ring { stroke: var(--faint); }
571
+ .tf-missing-text { fill: var(--muted); }
572
+ </style>
573
+ <defs>
574
+ <linearGradient id="track" x1="0" y1="0" x2="1" y2="0">
575
+ <stop offset="0%" class="tf-track-stop-good" stop-opacity="0.30"/>
576
+ <stop offset="50%" class="tf-track-stop-good" stop-opacity="0.22"/>
577
+ <stop offset="58%" class="tf-track-stop-mid" stop-opacity="0.26"/>
578
+ <stop offset="83%" class="tf-track-stop-mid" stop-opacity="0.26"/>
579
+ <stop offset="90%" class="tf-track-stop-slow" stop-opacity="0.36"/>
580
+ <stop offset="100%" class="tf-track-stop-slow" stop-opacity="0.42"/>
581
+ </linearGradient>
582
+ </defs>
583
+
584
+ <!-- track: x 40..640 maps ms 0..600 -->
585
+ <rect x="40" y="118" width="600" height="58" rx="10" fill="url(#track)" class="tf-track-stroke"/>
586
+
587
+ <!-- zone boundary ticks at 100 / 300 / 500 ms -->
588
+ <g class="tf-grid" stroke-dasharray="2 5">
589
+ <line x1="140" y1="112" x2="140" y2="182"/>
590
+ <line x1="340" y1="112" x2="340" y2="182"/>
591
+ <line x1="540" y1="112" x2="540" y2="182"/>
592
+ </g>
593
+
594
+ <!-- axis labels -->
595
+ <g font-family="Geist Mono, monospace" font-size="11" class="tf-axis" text-anchor="middle" font-variant-numeric="tabular-nums">
596
+ <text x="40" y="204">0</text>
597
+ <text x="140" y="204">100</text>
598
+ <text x="340" y="204">300</text>
599
+ <text x="540" y="204">500</text>
600
+ <text x="636" y="204" text-anchor="end">600ms</text>
601
+ </g>
602
+
603
+ <!-- zone band names -->
604
+ <g font-family="Geist Mono, monospace" font-size="9.5" letter-spacing="1.6">
605
+ <text x="90" y="106" text-anchor="middle" class="tf-zone-good">INSTANT</text>
606
+ <text x="240" y="106" text-anchor="middle" class="tf-zone-good">RESPONSIVE</text>
607
+ <text x="440" y="106" text-anchor="middle" class="tf-zone-mid">DELIBERATE</text>
608
+ <text x="592" y="106" text-anchor="middle" class="tf-zone-slow">SLUGGISH</text>
609
+ </g>
610
+
611
+ <!-- "missing" marker for the no-transition streak counter, parked at 0 -->
612
+ <circle cx="40" cy="147" r="11" fill="none" class="tf-missing-ring" stroke-width="1.5" stroke-dasharray="3 3"/>
613
+ <text x="40" y="151" font-family="Geist Mono, monospace" font-size="11" font-weight="600" text-anchor="middle" class="tf-missing-text">5</text>
614
+
615
+ <!-- plotted animation dots (color = zone). text carried by key list. -->
616
+ <g font-family="Geist Mono, monospace" font-size="11" font-weight="600" text-anchor="middle">
617
+ <!-- 3 · card hover 80ms (instant) -->
618
+ <circle cx="120" cy="147" r="12" class="tf-dot-good"/><text x="120" y="151" class="tf-dot-text">3</text>
619
+ <!-- 7 · toast 200ms (responsive) -->
620
+ <circle cx="240" cy="147" r="12" class="tf-dot-good"/><text x="240" y="151" class="tf-dot-text">7</text>
621
+ <!-- 6 · page route 250ms (responsive) -->
622
+ <circle cx="290" cy="147" r="12" class="tf-dot-good"/><text x="290" y="151" class="tf-dot-text">6</text>
623
+ <!-- 4 · sheet enter 320ms (deliberate, correct) -->
624
+ <circle cx="360" cy="147" r="12" class="tf-dot-mid"/><text x="360" y="151" class="tf-dot-text">4</text>
625
+ <!-- 2 · check-off bounce 450ms (deliberate, too long for frequency) -->
626
+ <circle cx="490" cy="147" r="12" class="tf-dot-mid"/><text x="490" y="151" class="tf-dot-text">2</text>
627
+ <!-- 1 · tab switch 600ms (sluggish) -->
628
+ <circle cx="632" cy="147" r="12" class="tf-dot-slow"/><text x="632" y="151" class="tf-dot-text">1</text>
629
+ </g>
630
+
631
+ <!-- frequency brace: groups the two rightmost (slowest) dots — check-off (2)
632
+ and tab switch (1) — which are also the app's most-fired actions. The
633
+ upward end-ticks sit directly under those dots; one caption carries it.
634
+ Lands the core mismatch: most-fired should be fastest, but these are slowest. -->
635
+ <path class="tf-bracket" stroke-width="1" d="M490 218 L490 224 L632 224 L632 218 M561 224 L561 230"/>
636
+ <text x="561" y="244" font-family="Public Sans, sans-serif" font-size="11" text-anchor="middle" class="tf-freq">fire most often</text>
637
+ </svg>
638
+
639
+ <div class="timing-key">
640
+ <h4 class="mono-label">The animations</h4>
641
+ <ol class="key-list">
642
+ <li><span class="kn">1</span><span class="ka">Tab switch</span><span class="kt slow">600ms</span></li>
643
+ <li><span class="kn">2</span><span class="ka">Check-off (bounce)</span><span class="kt mid">450ms</span></li>
644
+ <li><span class="kn">3</span><span class="ka">Card hover lift</span><span class="kt good">80ms</span></li>
645
+ <li><span class="kn">4</span><span class="ka">Add-habit sheet enter</span><span class="kt mid">320ms</span></li>
646
+ <li><span class="kn">5</span><span class="ka">Streak counter change</span><span class="kt none">none</span></li>
647
+ <li><span class="kn">6</span><span class="ka">Page route</span><span class="kt good">250ms</span></li>
648
+ <li><span class="kn">7</span><span class="ka">Toast</span><span class="kt good">200ms</span></li>
649
+ </ol>
650
+
651
+ <div class="zone-defs">
652
+ <div class="zone-def"><span class="dot" style="background:var(--t-good)"></span><div><span class="zt">Instant / Responsive</span> · 0–300ms — <span class="zd">where almost everything belongs. Taps, toggles, tabs, hovers.</span></div></div>
653
+ <div class="zone-def"><span class="dot" style="background:var(--t-mid)"></span><div><span class="zt">Deliberate</span> · 300–500ms — <span class="zd">earned by large surfaces: sheets, modals, full-screen routes.</span></div></div>
654
+ <div class="zone-def"><span class="dot" style="background:var(--t-slow)"></span><div><span class="zt">Sluggish</span> · 500ms+ — <span class="zd">feels laggy. Reserve for rare, deliberately cinematic moments — or nothing.</span></div></div>
655
+ </div>
656
+
657
+ <div class="whats-off">
658
+ <span class="mono-label">What's off</span>
659
+ <p>The two dots furthest right — tab switch (1) and check-off (2) — are the actions that fire most. Frequency and duration are inversely related: the more often it runs, the faster it should be. And the streak counter (5) has no transition at all.</p>
660
+ </div>
661
+ </div>
662
+ </div>
663
+ </section>
664
+
665
+ <!-- ═══════════ 03 · PER-LENS — JAKUB (primary) ═══════════ -->
666
+ <section class="lens-sec">
667
+ <div class="sec-index">03</div>
668
+ <div class="lens-sec__head">
669
+ <h3>Jakub Krehel — Production Polish</h3>
670
+ <span class="lens-sec__weight">Primary lens</span>
671
+ </div>
672
+ <p class="lens-sec__verdict"><span class="verdict v-problem">Problem</span></p>
673
+
674
+ <div class="lens-block">
675
+ <span class="mono-label">What's working well</span>
676
+ <ul class="lens-list good">
677
+ <li><span class="mk">✓</span><span>Page routes use a clean opacity + 8px translate at 250ms — the right shape and duration. <code>routes/transition.tsx:14</code></span></li>
678
+ <li><span class="mk">✓</span><span>Toasts enter and <em>exit</em> symmetrically — the exit isn't an afterthought. <code>ui/Toast.tsx:31</code></span></li>
679
+ </ul>
680
+ </div>
681
+
682
+ <div class="lens-block">
683
+ <span class="mono-label">Issues to address</span>
684
+ <div class="findings">
685
+
686
+ <!-- FINDING 02 (Critical) -->
687
+ <div class="finding-row" data-sev="crit" id="finding-2">
688
+ <div class="finding-row__prose">
689
+ <div class="find-tags"><span class="severity crit">Critical</span><span class="lens-chip">Jakub</span></div>
690
+ <h4 class="find-title">The add-habit sheet enters, then snaps shut with no exit</h4>
691
+ <div class="find-body">
692
+ <p><span class="label">What</span>The bottom sheet animates up on open (320ms, good), but on dismiss it's removed from the tree instantly — no exit. The component renders conditionally with no <code>AnimatePresence</code> wrapper, so Framer Motion never gets to play the exit.</p>
693
+ <p><span class="label">Why it matters</span>A surface that glides in and <em>vanishes</em> reads as broken — the eye expects symmetry. It's the single most common "half-built motion" tell, and it's on the app's primary create flow.</p>
694
+ <div class="fix"><p><span class="label">Recommended motion</span>Wrap the sheet in <code>AnimatePresence</code> and mirror the enter: slide down + fade over 300ms with the same <code>ease-out-quint</code>. The demo shows the enter; the exit is its reverse.</p></div>
695
+ <p class="find-loc"><code>screens/Habits/AddSheet.tsx:48</code></p>
696
+ </div>
697
+ </div>
698
+ <div class="demo">
699
+ <input class="vh" type="radio" name="st2" id="st2-a" checked>
700
+ <input class="vh" type="radio" name="st2" id="st2-l">
701
+ <input class="vh" type="radio" name="st2" id="st2-d">
702
+ <div class="demo__bar">
703
+ <div class="demo__meta"><span class="demo__title">Sheet enter (mirror for exit)</span><span class="demo__timing">300ms · ease-out-quint</span></div>
704
+ <div class="demo__controls">
705
+ <div class="seg"><label for="st2-a">Auto</label><label for="st2-l">Light</label><label for="st2-d">Dark</label></div>
706
+ <span class="demo__loop">↻</span>
707
+ </div>
708
+ </div>
709
+ <div class="demo__stage" style="align-items:flex-end;">
710
+ <div class="demo-2__mt" style="width:100%;">
711
+ <div class="ui-card" style="min-width:0;width:100%;">
712
+ <div class="ui-label" style="margin-bottom:8px;">New habit</div>
713
+ <div class="ui-row" style="justify-content:space-between;">
714
+ <span>Drink water</span>
715
+ <span class="ui-btn" style="padding:6px 14px;">Add</span>
716
+ </div>
717
+ </div>
718
+ </div>
719
+ </div>
720
+ </div>
721
+ </div>
722
+
723
+ <!-- FINDING 04 (Important) -->
724
+ <div class="finding-row" data-sev="imp" id="finding-4">
725
+ <div class="finding-row__prose">
726
+ <div class="find-tags"><span class="severity imp">Important</span><span class="lens-chip">Jakub</span></div>
727
+ <h4 class="find-title">Streak counter jumps between values with no transition</h4>
728
+ <div class="find-body">
729
+ <p><span class="label">What</span>When a streak increments, the number is replaced in place — a hard swap. There's no transition on the value change, so the most rewarding number in the app updates with the least ceremony.</p>
730
+ <p><span class="label">Why it matters</span>The streak count is the payoff of the whole interaction. A snap makes a hard-won number feel like a re-render, not an achievement.</p>
731
+ <div class="fix"><p><span class="label">Recommended motion</span>Roll the new value in: opacity + 10px translateY + a 5px blur that clears, 220ms. Subtle, but it tells the eye <em>something changed and it's good</em>.</p></div>
732
+ <p class="find-loc"><code>components/StreakBadge.tsx:22</code></p>
733
+ </div>
734
+ </div>
735
+ <div class="demo">
736
+ <input class="vh" type="radio" name="st4" id="st4-a" checked>
737
+ <input class="vh" type="radio" name="st4" id="st4-l">
738
+ <input class="vh" type="radio" name="st4" id="st4-d">
739
+ <div class="demo__bar">
740
+ <div class="demo__meta"><span class="demo__title">Number roll-in</span><span class="demo__timing">220ms · opacity + Y + blur</span></div>
741
+ <div class="demo__controls">
742
+ <div class="seg"><label for="st4-a">Auto</label><label for="st4-l">Light</label><label for="st4-d">Dark</label></div>
743
+ <span class="demo__loop">↻</span>
744
+ </div>
745
+ </div>
746
+ <div class="demo__stage">
747
+ <div class="demo-4__mt" style="text-align:center;">
748
+ <div class="ui-label" style="margin-bottom:6px;">Current streak</div>
749
+ <div><span class="ui-num">7</span> <span class="ui-num" style="font-size:18px;color:var(--st-dim);">days</span></div>
750
+ </div>
751
+ </div>
752
+ </div>
753
+ </div>
754
+
755
+ </div>
756
+ </div>
757
+
758
+ <div class="lens-block">
759
+ <span class="mono-label">Opportunities</span>
760
+ <ul class="lens-list opp">
761
+ <li><span class="mk">💡</span><span>Habit cards lean on a drop shadow that's invisible on the dark theme — a 1px border would carry the elevation on both. <code>components/HabitCard.tsx:9</code></span></li>
762
+ </ul>
763
+ </div>
764
+
765
+ <div class="lens-take">
766
+ <span class="mono-label">Through Jakub's lens</span>
767
+ <p>The vocabulary is right; the sentences are unfinished. Pair every enter with an exit, give the streak its moment, and Tally crosses from "animated" to "polished."</p>
768
+ </div>
769
+ </section>
770
+
771
+ <!-- ═══════════ 04 · PER-LENS — EMIL (secondary) ═══════════ -->
772
+ <section class="lens-sec">
773
+ <div class="sec-index">04</div>
774
+ <div class="lens-sec__head">
775
+ <h3>Emil Kowalski — Restraint &amp; Speed</h3>
776
+ <span class="lens-sec__weight">Secondary lens</span>
777
+ </div>
778
+ <p class="lens-sec__verdict"><span class="verdict v-concern">Concern</span></p>
779
+
780
+ <div class="lens-block">
781
+ <span class="mono-label">What's working well</span>
782
+ <ul class="lens-list good">
783
+ <li><span class="mk">✓</span><span>Card hover lift is 80ms — instant, exactly right for a passive affordance. <code>components/HabitCard.tsx:18</code></span></li>
784
+ <li><span class="mk">✓</span><span>No animation on keyboard-driven navigation — keyboard users aren't taxed with motion they didn't ask for.</span></li>
785
+ </ul>
786
+ </div>
787
+
788
+ <div class="lens-block">
789
+ <span class="mono-label">Issues to address</span>
790
+ <div class="findings">
791
+
792
+ <!-- FINDING 01 (Critical) -->
793
+ <div class="finding-row" data-sev="crit" id="finding-1">
794
+ <div class="finding-row__prose">
795
+ <div class="find-tags"><span class="severity crit">Critical</span><span class="lens-chip">Emil</span></div>
796
+ <h4 class="find-title">Tab switches slide for 600ms — far too slow for the most frequent action</h4>
797
+ <div class="find-body">
798
+ <p><span class="label">What</span>The four bottom tabs cross-slide the full panel width over 600ms with an <code>ease-in-out</code>. Tabs are the app's highest-frequency navigation; a 600ms slide means every switch holds the user behind an animation.</p>
799
+ <p><span class="label">Why it matters</span>Emil's rule: the more often an action fires, the less it should animate. At this duration the motion stops being feedback and becomes a toll. <code>ease-in-out</code> also adds a slow start, compounding the lag.</p>
800
+ <div class="fix"><p><span class="label">Recommended motion</span>Drop to a 180ms opacity crossfade with a 7px slide, <code>ease-out</code>. Better still: consider no slide at all — a fast crossfade is plenty of orientation for a tab.</p></div>
801
+ <p class="find-loc"><code>navigation/TabView.tsx:63</code></p>
802
+ </div>
803
+ </div>
804
+ <div class="demo">
805
+ <input class="vh" type="radio" name="st1" id="st1-a" checked>
806
+ <input class="vh" type="radio" name="st1" id="st1-l">
807
+ <input class="vh" type="radio" name="st1" id="st1-d">
808
+ <div class="demo__bar">
809
+ <div class="demo__meta"><span class="demo__title">Quick tab crossfade</span><span class="demo__timing">180ms · ease-out</span></div>
810
+ <div class="demo__controls">
811
+ <div class="seg"><label for="st1-a">Auto</label><label for="st1-l">Light</label><label for="st1-d">Dark</label></div>
812
+ <span class="demo__loop">↻</span>
813
+ </div>
814
+ </div>
815
+ <div class="demo__stage">
816
+ <div class="demo-1__mt" style="text-align:left;width:100%;">
817
+ <div class="ui-label" style="margin-bottom:10px;">Today</div>
818
+ <div class="ui-row" style="margin-bottom:8px;"><span class="ui-check"><svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3.5"><path d="M5 13l4 4L19 7"/></svg></span><span>Morning walk</span></div>
819
+ <div class="ui-row"><span class="ui-check" style="background:var(--st-line);"></span><span>Read 10 pages</span></div>
820
+ </div>
821
+ </div>
822
+ </div>
823
+ </div>
824
+
825
+ <!-- FINDING 03 (Important) -->
826
+ <div class="finding-row" data-sev="imp" id="finding-3">
827
+ <div class="finding-row__prose">
828
+ <div class="find-tags"><span class="severity imp">Important</span><span class="lens-chip">Emil</span></div>
829
+ <h4 class="find-title">Check-off pops from scale(0) with a spring bounce</h4>
830
+ <div class="find-body">
831
+ <p><span class="label">What</span>Ticking a habit animates the checkmark from <code>scale(0)</code> with a bouncy spring (~450ms to settle). It's the app's core, most-repeated gesture, and it's the showiest animation in the product.</p>
832
+ <p><span class="label">Why it matters</span>Bounce on a high-frequency confirm gets tiring fast — the overshoot draws attention to motion the user has already mentally completed. Starting from <code>scale(0)</code> exaggerates the distance and the time.</p>
833
+ <div class="fix"><p><span class="label">Recommended motion</span>Scale <code>0.9 → 1</code> with opacity, 200ms <code>ease-out</code>, no overshoot. Confident and done before the finger lifts.</p></div>
834
+ <p class="find-loc"><code>components/HabitCheck.tsx:27</code></p>
835
+ </div>
836
+ </div>
837
+ <div class="demo">
838
+ <input class="vh" type="radio" name="st3" id="st3-a" checked>
839
+ <input class="vh" type="radio" name="st3" id="st3-l">
840
+ <input class="vh" type="radio" name="st3" id="st3-d">
841
+ <div class="demo__bar">
842
+ <div class="demo__meta"><span class="demo__title">Calm check, no bounce</span><span class="demo__timing">200ms · ease-out</span></div>
843
+ <div class="demo__controls">
844
+ <div class="seg"><label for="st3-a">Auto</label><label for="st3-l">Light</label><label for="st3-d">Dark</label></div>
845
+ <span class="demo__loop">↻</span>
846
+ </div>
847
+ </div>
848
+ <div class="demo__stage">
849
+ <div class="demo-3__mt">
850
+ <div class="ui-row" style="font-size:15px;"><span class="ui-check" style="width:30px;height:30px;"><svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3.5"><path d="M5 13l4 4L19 7"/></svg></span><span>Drink water</span></div>
851
+ </div>
852
+ </div>
853
+ </div>
854
+ </div>
855
+
856
+ </div>
857
+ </div>
858
+
859
+ <div class="lens-take">
860
+ <span class="mono-label">Through Emil's lens</span>
861
+ <p>Speed up everything the user touches constantly. The tab switch and the check-off should feel instant; their current durations are the difference between an app that feels fast and one that feels fussy.</p>
862
+ </div>
863
+ </section>
864
+
865
+ <!-- ═══════════ 05 · PER-LENS — JHEY (selective) ═══════════ -->
866
+ <section class="lens-sec">
867
+ <div class="sec-index">05</div>
868
+ <div class="lens-sec__head">
869
+ <h3>Jhey Tompkins — Experimentation &amp; Delight</h3>
870
+ <span class="lens-sec__weight">Selective lens</span>
871
+ </div>
872
+ <p class="lens-sec__verdict"><span class="verdict v-strong">Strong</span></p>
873
+
874
+ <div class="lens-block">
875
+ <span class="mono-label">What's working well</span>
876
+ <ul class="lens-list good">
877
+ <li><span class="mk">✓</span><span>Tally resists the urge to animate everything — restraint <em>is</em> the right default for a daily utility. Delight is rationed, which makes room for it to land where it counts.</span></li>
878
+ </ul>
879
+ </div>
880
+
881
+ <div class="lens-block">
882
+ <span class="mono-label">Issues to address</span>
883
+ <div class="findings">
884
+
885
+ <!-- FINDING 05 (Important) -->
886
+ <div class="finding-row" data-sev="imp" id="finding-5">
887
+ <div class="finding-row__prose">
888
+ <div class="find-tags"><span class="severity imp">Important</span><span class="lens-chip">Jhey</span></div>
889
+ <h4 class="find-title">Hitting a milestone streak passes by with no celebration</h4>
890
+ <div class="find-body">
891
+ <p><span class="label">What</span>Crossing a 7-, 30-, or 100-day streak looks identical to any other day — the number just increments. The one moment in the app that has genuinely earned a flourish gets none.</p>
892
+ <p><span class="label">Why it matters</span>This is where delight pays for itself. A milestone is rare, emotionally loaded, and shareable — exactly the place to spend motion the rest of the app withholds. Skipping it leaves the payoff flat.</p>
893
+ <div class="fix"><p><span class="label">Recommended motion</span>On a milestone only: a badge that scales <code>0.8 → 1</code> (260ms <code>ease-out</code>) with a short, one-shot sparkle burst. Fires once on the event — not a looping pulse.</p></div>
894
+ <p class="find-loc"><code>components/StreakBadge.tsx:40</code></p>
895
+ </div>
896
+ </div>
897
+ <div class="demo">
898
+ <input class="vh" type="radio" name="st5" id="st5-a" checked>
899
+ <input class="vh" type="radio" name="st5" id="st5-l">
900
+ <input class="vh" type="radio" name="st5" id="st5-d">
901
+ <div class="demo__bar">
902
+ <div class="demo__meta"><span class="demo__title">Milestone badge enter</span><span class="demo__timing">260ms · ease-out + sparkle</span></div>
903
+ <div class="demo__controls">
904
+ <div class="seg"><label for="st5-a">Auto</label><label for="st5-l">Light</label><label for="st5-d">Dark</label></div>
905
+ <span class="demo__loop">↻</span>
906
+ </div>
907
+ </div>
908
+ <div class="demo__stage">
909
+ <div class="demo-5__mt" style="position:relative;width:96px;height:96px;display:flex;align-items:center;justify-content:center;">
910
+ <div class="badge" style="width:78px;height:78px;border-radius:50%;background:var(--st-fg);display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--st-bg);">
911
+ <span style="font-family:var(--display);font-size:26px;font-weight:700;line-height:1;">7</span>
912
+ <span style="font-family:var(--mono);font-size:8px;letter-spacing:0.12em;margin-top:2px;">DAY</span>
913
+ </div>
914
+ <span class="spark" style="--dx:-30px;--dy:-26px;position:absolute;width:7px;height:7px;border-radius:50%;background:var(--st-fg);"></span>
915
+ <span class="spark" style="--dx:32px;--dy:-20px;position:absolute;width:5px;height:5px;border-radius:50%;background:var(--st-fg);"></span>
916
+ <span class="spark" style="--dx:24px;--dy:30px;position:absolute;width:6px;height:6px;border-radius:50%;background:var(--st-fg);"></span>
917
+ <span class="spark" style="--dx:-28px;--dy:28px;position:absolute;width:5px;height:5px;border-radius:50%;background:var(--st-fg);"></span>
918
+ </div>
919
+ </div>
920
+ </div>
921
+ </div>
922
+
923
+ </div>
924
+ </div>
925
+
926
+ <div class="lens-block">
927
+ <span class="mono-label">Opportunities</span>
928
+ <ul class="lens-list opp">
929
+ <li><span class="mk">💡</span><span>The today-list could stagger its rows in on first paint — 30ms apart, opacity + 6px rise. One orchestrated load beats scattered micro-interactions. <code>screens/Today.tsx:51</code></span></li>
930
+ </ul>
931
+ </div>
932
+
933
+ <div class="lens-take">
934
+ <span class="mono-label">Through Jhey's lens</span>
935
+ <p>Don't add more motion — add it in <em>one</em> right place. The streak milestone is the moment worth engineering; everywhere else, keeping your hands off the controls is the sophisticated move.</p>
936
+ </div>
937
+ </section>
938
+
939
+ <!-- ═══════════ 06 · COMBINED RECOMMENDATIONS ═══════════ -->
940
+ <section>
941
+ <div class="sec-index">06</div>
942
+ <h2 class="section-title">In the order I'd fix them</h2>
943
+ <p class="section-sub">
944
+ Ordered by what users feel: <em>critical</em> (degrades the core loop on every use) → <em>important</em>
945
+ (real friction or a missed payoff) → <em>opportunity</em> (could enhance).
946
+ </p>
947
+
948
+ <div class="rec crit" id="rec-crit">
949
+ <div class="tier-label"><span class="mono-label">Critical · must fix</span><span class="rule"></span><span class="count">2</span></div>
950
+ <table class="rec-table">
951
+ <thead><tr><th>Issue</th><th>File</th><th>Fix</th></tr></thead>
952
+ <tbody>
953
+ <tr><td>Tab switch runs 600ms on the highest-frequency action</td><td><code>TabView.tsx:63</code></td><td>180ms opacity crossfade + 7px slide, ease-out</td></tr>
954
+ <tr><td>Add-habit sheet has no exit — snaps shut</td><td><code>AddSheet.tsx:48</code></td><td>Wrap in AnimatePresence; mirror the 300ms enter on exit</td></tr>
955
+ </tbody>
956
+ </table>
957
+ </div>
958
+
959
+ <div class="rec imp" id="rec-imp">
960
+ <div class="tier-label"><span class="mono-label">Important · should fix</span><span class="rule"></span><span class="count">3</span></div>
961
+ <table class="rec-table">
962
+ <thead><tr><th>Issue</th><th>File</th><th>Fix</th></tr></thead>
963
+ <tbody>
964
+ <tr><td>Check-off pops from scale(0) with a bounce</td><td><code>HabitCheck.tsx:27</code></td><td>scale 0.9→1 + opacity, 200ms ease-out, no overshoot</td></tr>
965
+ <tr><td>Streak counter swaps with no transition</td><td><code>StreakBadge.tsx:22</code></td><td>220ms opacity + translateY + blur roll-in</td></tr>
966
+ <tr><td>Milestone streaks have no celebration moment</td><td><code>StreakBadge.tsx:40</code></td><td>One-shot badge scale-in + sparkle, milestones only</td></tr>
967
+ </tbody>
968
+ </table>
969
+ </div>
970
+
971
+ <div class="rec opp" id="rec-opp">
972
+ <div class="tier-label"><span class="mono-label">Opportunities · could enhance</span><span class="rule"></span><span class="count">2</span></div>
973
+ <table class="rec-table">
974
+ <thead><tr><th>Enhancement</th><th>Where</th><th>Impact</th></tr></thead>
975
+ <tbody>
976
+ <tr><td>Habit-card elevation invisible on dark theme</td><td><code>HabitCard.tsx:9</code></td><td>1px border carries elevation on both themes</td></tr>
977
+ <tr><td>Today-list could stagger in on first paint</td><td><code>Today.tsx:51</code></td><td>30ms stagger, opacity + 6px rise — one orchestrated load</td></tr>
978
+ </tbody>
979
+ </table>
980
+ </div>
981
+ </section>
982
+
983
+ <!-- ═══════════ 07 · LENS REFERENCE SUMMARY ═══════════ -->
984
+ <section style="margin-bottom: var(--s6);">
985
+ <div class="sec-index">07</div>
986
+ <h2 class="section-title">Which lens carried this audit</h2>
987
+ <div class="ref-summary">
988
+ <span class="mono-label">Referenced most</span>
989
+ <p class="big">Jakub Krehel — Production Polish</p>
990
+ <p class="why">Tally's gaps are craft gaps, not taste gaps: enters without exits, state changes that snap. That's Jakub's territory — finishing what's been started — so his lens drove the ordering. Emil set the durations; Jhey marked the one place to spend.</p>
991
+ <ul class="ref-lean">
992
+ <li><span class="nm">Lean Emil</span><span>Treat every duration as a budget. Push tab, check, and toggle timings under 200ms and question any motion on a high-frequency action.</span></li>
993
+ <li><span class="nm">Lean Jakub</span><span>Audit every conditional render for a missing exit. Pair enters and exits, and give meaningful state changes a transition.</span></li>
994
+ <li><span class="nm">Lean Jhey</span><span>Pick the single highest-emotion moment — the milestone — and over-invest there, with @property, springs, or scroll-driven touches.</span></li>
995
+ </ul>
996
+ </div>
997
+ </section>
998
+
999
+ <footer class="doc">
1000
+ <span>Tally motion audit · design-motion-principles</span>
1001
+ <span>7 findings · 2 critical · 3 important · 2 opportunities</span>
1002
+ </footer>
1003
+
1004
+ </div>
1005
+ </body>
1006
+ </html>