adsa-cli 0.1.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.
package/lib/report.mjs ADDED
@@ -0,0 +1,716 @@
1
+ /**
2
+ * The report: one self-contained HTML file laid out as a dashboard, plus a markdown
3
+ * twin. No external fonts, scripts or stylesheets — it gets committed into somebody
4
+ * else's repository and has to open from disk, offline, years from now.
5
+ *
6
+ * Every figure comes from the scan. Nothing here estimates, rounds up, or invents a
7
+ * metric to fill a panel.
8
+ *
9
+ * The design of this file is edited on its own in the ADSA Dashboard workbench,
10
+ * against frozen real audits, and ported back here as a copy.
11
+ */
12
+ import { RUBRIC } from "./score.mjs";
13
+ import { badgeColor, badgeMarkdown } from "./badge.mjs";
14
+
15
+ /** What the accessibility dimension is called on each platform. */
16
+ const A11Y_STAT_LABEL = {
17
+ web: "keyboard",
18
+ "react-native": "VoiceOver/TalkBack",
19
+ swift: "VoiceOver/Dynamic Type",
20
+ android: "TalkBack",
21
+ };
22
+
23
+ const PLATFORM_LABEL = {
24
+ web: "Web",
25
+ "react-native": "React Native",
26
+ swift: "Swift",
27
+ android: "Android",
28
+ };
29
+
30
+ const MARK = `<svg class="mark" viewBox="0 0 214 135" fill="currentColor" aria-hidden="true"><path d="M211.6 71C211.3 71.1 210.8 71.1 210.5 71.1C210.1 71.1 209.9 71.2 209.8 71.6C208.4 78.1 206.1 82.6 201.9 86.9C196.9 92.3 190.7 95.4 183.8 96.1C181.2 96.4 180.3 96.6 179.1 97.3C176.7 98.6 175.3 100.8 175 103.8C174.5 108.8 173.8 111.7 172.2 114.9C169.5 120.4 165 124.9 159.5 127.7C156.5 129.2 154 130 150 130.9L148.5 131.2L148.6 132.3L148.6 133.5L150 133.4C150.7 133.3 152.1 133.1 153 132.9C163.5 130.5 172 123.3 175.7 113.7C176.9 110.7 177.4 108.3 177.7 104.8C177.9 102.3 178.2 101.6 179.4 100.4C180.5 99.4 181.2 99.1 183.3 98.9C194.4 98 204.5 91.1 209.6 80.8C210.9 78.2 212.2 73.7 212.2 71.7L212.2 70.9L211.6 71Z"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M182.5 58.5C185.2 59.4 187.4 61.6 188.3 64.4C188.9 65.9 188.9 68.8 188.3 70.4C187.8 71.8 186.9 73.2 185.8 74.2C184.1 75.9 182.5 76.5 179.1 76.9C174.8 77.3 171.3 78.4 167.6 80.5C161.8 83.8 157.4 89 155.4 95.1C154.6 97.4 154.3 99.2 154.1 101.9C154 103.1 153.9 104.7 153.7 105.4C152.9 108.8 149.8 112 146.4 112.8C145.3 113 142.7 113 141.5 112.8C138.8 112.2 136.1 110.2 134.8 107.8C134 106.1 133.6 104.7 133.5 102.3C133.1 96.5 130.9 91.1 126.9 86.4C123.8 82.8 119.1 79.8 114.1 78.2C103.7 74.8 92 77.5 85 84.9C81.1 88.9 78.8 94.6 78.8 99.8C78.8 102.8 77.8 105.3 75.9 107.1C74.1 108.9 72.3 109.7 69.6 109.9C66.3 110.1 63.7 109.1 61.7 107.1C60 105.4 59.3 103.8 58.8 100.5C58.1 96.6 56.9 93.6 54.5 90C53.1 87.8 49.4 84.2 47.1 82.7C41.9 79.2 36.7 77.5 30.3 77.1C28.5 77 26.6 76.9 26.2 76.8C23.2 76 20.6 73.4 19.9 70.7C19.5 69.2 19.5 66.8 20 65.3C20.5 63.6 21.7 61.9 23.2 60.8C25.1 59.5 26.3 59.1 30.3 58.9C34.1 58.7 36.2 58.3 39.5 57.1C49.4 53.4 56.6 45.2 58 35.9C58.7 31.9 59.5 30 61.5 28.2C63.4 26.5 65.4 25.8 68.2 25.8C71.2 25.8 73.3 26.6 75.3 28.5C77.2 30.4 78 32.2 78.2 35.7C78.3 37.1 78.5 38.9 78.7 39.7C79.2 41.5 80.3 44.2 81.3 45.9C84.8 51.6 91.2 55.8 99 57.5C100.7 57.8 101.6 57.9 104.5 57.9C108.5 57.9 110.4 57.6 114.1 56.4C122.6 53.6 129.6 47 132.4 39C133.2 36.7 133.5 34.9 133.7 31.8C133.9 30 134 28.7 134.3 27.9C135.7 23.7 139.8 21.1 144.3 21.5C147.1 21.7 149 22.7 150.8 24.6C152.8 26.7 153.4 28.8 153.4 32.8C153.5 35.5 153.8 37.8 154.5 39.8C156.9 46.6 161.5 51.7 168.1 54.9C171.8 56.8 175.1 57.7 179.3 58C180.6 58.1 182 58.3 182.5 58.5ZM184.8 69.4L185.9 70L187 70.5L186.7 71.2C186.1 72.7 184.6 74.1 182.8 75C182 75.4 181.1 75.5 179 75.7C173.6 76.3 169.4 77.9 164.7 81C162.4 82.6 159.2 85.9 157.6 88.1C154.6 92.7 153.4 96.3 152.9 102.9C152.8 104.3 152.6 105.5 152.3 106.2C151.5 108.3 149.3 110.6 147.7 111C147.1 111.2 147.1 111.2 146.6 110.1C146.3 109.6 146.2 109.1 146.3 109C146.4 109 146.9 108.6 147.6 108.1C148.4 107.5 148.8 107.1 149.3 106.1C149.9 104.8 149.9 104.7 150.4 99.8C151.1 93.1 154.2 87 159.5 81.9C164.6 77 171.2 73.9 178.4 73.1C179.8 72.9 181.1 72.7 181.5 72.5C182.6 72.1 183.7 71.2 184.3 70.3L184.8 69.4ZM181.9 59.5C183.9 60.1 185.6 61.5 186.6 63.4C186.9 64 186.8 64.1 185.7 64.7L184.8 65.2L184.1 64.3C182.7 62.5 181.6 62 178.7 61.8C170.5 61.1 163.1 57.7 157.7 52.1C155.5 49.8 153.9 47.7 152.6 45.2C150.4 40.8 149.9 38.7 149.7 33.7C149.4 28.8 149 27.6 146.7 26.1C146.1 25.7 145.7 25.3 145.7 25.2C145.7 25.1 145.9 24.7 146.3 24.2L146.8 23.3L147.5 23.6C148.7 24.1 149.7 24.9 150.4 25.9C152 28 152.3 29 152.4 33.4C152.5 36.8 152.9 38.8 154 41.6C157.9 51.2 168.1 58.4 178.8 59C179.8 59.1 181.2 59.3 181.9 59.5ZM182.5 64.2C182.8 64.5 183.3 65.2 183.5 65.7C184.3 67.2 184 68.8 182.8 70.2C181.8 71.3 180.7 71.8 178.3 72C175.6 72.3 173.9 72.6 171.2 73.6C160 77.3 151.9 86.2 149.7 97.2C149.5 98.2 149.2 100.2 149.1 101.6C148.9 104.5 148.6 105.6 147.5 106.7C145.1 109.2 140.7 108.5 139.2 105.3C138.8 104.6 138.6 103.9 138.5 102C138.1 97.9 137.4 94.9 135.9 91.5C132.1 82.9 125.1 76.6 115.8 73.6C111.6 72.2 110.2 72 105 71.9C100 71.9 98.4 72.2 94.5 73.4C85.9 76.2 79.3 82 75.9 89.8C74.7 92.7 74.1 95.2 73.8 99C73.6 102.1 73.3 102.9 72.1 104C70.1 105.7 66.9 105.5 65.2 103.7C64.3 102.7 64 102 63.7 99.8C62.7 93.4 59.2 86.8 54.2 82.1C47.8 76.1 39.8 72.7 30.7 72.2C28 72.1 27.3 72 26.7 71.7C25.1 70.9 24.2 69.1 24.4 67.5C24.6 66.4 25.5 65.1 26.6 64.5C27.4 64 27.6 64 30.7 63.9C35.5 63.7 39.3 62.8 43.7 60.7C52.1 56.9 58.6 50.1 61.6 42.1C62.3 40 62.7 38.4 63.1 35.8C63.4 33.6 63.8 32.7 64.8 31.8C65.9 30.9 66.7 30.6 68.2 30.6C69.4 30.6 69.8 30.7 70.6 31.2C72.4 32.2 73 33.4 73.3 36.6C73.5 39.4 73.9 41.3 74.8 43.7C76.2 47.6 78.3 50.8 81.3 53.7C91.1 63.3 107.2 65.6 120.2 59.2C126.9 55.9 132.2 50.7 135.5 44.2C137.4 40.5 138.4 37 138.7 32.5C138.8 29.7 139.1 28.7 140.2 27.7C141.1 26.8 142.2 26.3 143.4 26.3C144.9 26.3 145.7 26.6 146.8 27.6C148.2 28.9 148.4 29.4 148.5 33.6C148.7 37.8 149.2 40.2 150.6 43.4C154.8 53.6 165 61.1 176.5 62.6C177.7 62.8 179 62.9 179.5 62.9C180.4 62.9 181.8 63.5 182.5 64.2ZM139.6 107.8C140.2 108.3 140.9 108.8 141.1 108.9C141.5 109 141.5 109 141 110C140.7 110.6 140.4 111.1 140.3 111.1C139.7 111.1 138 110 137.1 109.1C135.4 107.2 134.9 105.9 134.6 102.1C133.7 92.8 129.3 85.5 121.6 80.5C118.6 78.6 114.3 76.8 110.5 76.1C107.9 75.5 101.7 75.6 99.1 76.2C93.1 77.5 88.6 79.8 84.8 83.6C82.6 85.8 81.2 87.7 80 90.3C78.5 93.5 77.9 95.8 77.6 100C77.5 102.4 77.4 102.7 76.8 104C76.1 105.4 74.8 106.9 73.6 107.6C72.8 108 71.7 108.3 71.8 108.1C71.9 108 71.7 107.5 71.5 107L71.1 106.1L72.1 105.3C73.4 104.5 74.3 103.3 74.6 102C74.7 101.5 74.8 100.2 74.9 99.1C75.4 90.5 79.8 83 87.3 77.9C88.8 76.9 92.2 75.3 94.3 74.6C101.3 72.3 108.4 72.3 115.5 74.6C123.8 77.3 130.7 83.2 134.3 90.7C136.2 94.5 136.9 97.3 137.4 102.2C137.7 105.4 138.1 106.3 139.6 107.8ZM140 23.3L140.7 24.3L141.3 25.3L140.7 25.8C138.7 27.3 137.7 29 137.7 30.9C137.7 32.7 137.3 35.7 136.8 37.8C134.8 45.8 129.2 52.9 121.6 57.2C118.1 59.2 114.4 60.6 110 61.3C107.8 61.7 101.9 61.8 99.7 61.4C92.7 60.3 86.3 57.2 81.9 52.8C77.5 48.3 74.9 42.8 74.4 36.8C74.3 35.5 74.1 34.1 74 33.6C73.7 32.5 72.6 31.2 71.4 30.3C70.4 29.7 70.3 29.6 70.5 29.4C70.7 29.2 70.8 29 70.8 28.9C70.8 28.8 71 28.5 71.3 28.1L71.7 27.6L72.8 28.1C74.1 28.8 75.6 30.4 76.3 31.9C76.8 32.9 76.9 33.6 77.1 35.1C77.5 40 78.1 42.2 79.6 45C81.5 48.5 83.5 50.8 86.5 53.1C92.3 57.4 99.7 59.6 106.9 58.9C111.8 58.5 115.7 57.3 120.1 54.9C125.1 52.2 129.6 47.5 132.1 42.4C133.8 38.8 134.6 35.8 134.8 32.1C134.9 29.6 135.1 28.5 135.8 27.2C136.4 25.9 137.9 24.4 139.1 23.8L140 23.3ZM62.7 100.2C63 102.8 63.8 104.3 65.5 105.4C66 105.8 66.4 106.1 66.4 106.1C66.4 106.2 65.5 107.8 65.4 108C65.2 108.3 63.2 107.1 62.4 106.1C60.9 104.6 60.3 103.2 59.8 100.3C59.3 97.3 58.6 95.1 57.1 92.3C55.7 89.5 54.1 87.4 51.5 84.9C48.2 81.7 44.1 79.2 39.5 77.7C36.4 76.7 33.9 76.2 30.3 76C27 75.8 26.1 75.7 24.8 75C23.9 74.5 22.3 73 21.9 72.2C21.5 71.4 21.5 71.3 22.9 70.5L23.6 70.1L24.1 70.9C25.4 72.6 26.5 73.1 29.7 73.3C34.8 73.5 38.5 74.3 42.5 75.9C48.7 78.4 53.9 82.5 57.6 87.7C60.2 91.4 62.1 96.2 62.7 100.2ZM64.1 27.9C64.7 27.7 64.7 27.8 65.2 28.7C65.6 29.2 65.8 29.7 65.8 29.7C65.8 29.7 65.2 30.1 64.6 30.6C63.1 31.7 62.3 33.1 62.1 35.5C61.4 41.3 58.5 47.4 54.1 52C50.2 56.1 45.5 59.1 40.1 61C36.7 62.2 34.6 62.6 30.9 62.7C26.6 62.9 25.5 63.4 24.1 65.3C23.8 65.8 23.5 66.2 23.4 66.2C23.3 66.2 22.9 65.8 22.4 65.4C21.8 64.8 21.7 64.6 21.8 64.2C22 63.4 23.7 61.7 24.9 61.1C26.3 60.3 27.3 60.1 30.5 60C34.9 59.9 38.5 59 42.7 57C49 54 53.8 49.3 56.8 43.4C57.7 41.6 58.7 38.5 59 36.9C59.1 36.2 59.3 35.1 59.4 34.4C59.5 33.7 59.8 32.7 60 32.2C60.7 30.3 62.6 28.3 64.1 27.9Z"/> <path d="M141.7 131.3C141.5 131.2 140.4 131 139.1 130.7C129.3 128.5 121.4 122.8 117.3 114.7C115.5 111.4 114.6 108.4 114.1 104.6C113.8 102.2 113.5 101.3 112.8 100.1C110.1 95.9 104 95.2 100.5 98.8C99.2 100.2 98.7 101.4 98.3 103.9C97.8 108 96.6 112 94.9 115.1C90.4 123.2 82.6 128.8 72.7 130.9L71.2 131.2L71.3 131.9C71.5 133.4 71.5 133.5 72.4 133.4C72.8 133.4 74.1 133.2 75.2 133C80.6 131.8 85.4 129.3 90 125.5C95.9 120.5 100.1 112.7 101 105.1C101.2 102.8 101.6 101.8 102.3 100.9C104.1 98.8 107.1 98.6 109.3 100.3C110.5 101.3 111.1 102.5 111.4 104.9C112 109.9 113.7 114.7 116.7 119.2C118.6 122.1 122.4 125.8 125.4 127.8C129.1 130.3 133.8 132.3 137.7 133.1C140.1 133.6 141.4 133.6 141.6 133.1C141.9 132.1 141.9 131.4 141.7 131.3Z"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M213.6 65.3C213.1 60.4 211.3 55.6 208.5 51.2C206.6 48.3 204.7 46.1 202.1 43.8C196.2 38.7 188.9 35.3 181.8 34.5C179.7 34.2 178.6 33.7 177.9 32.7C177.3 31.9 177.3 31.8 177 28.3C176.4 21.5 174.3 16.1 170.3 11.2C167 7.2 162.1 3.7 156.8 1.9C151.1 -0.2 143.5 -0.6 136.5 0.8C128.2 2.5 120.7 7.4 115.7 14.4C112.5 18.9 110.3 24.9 109.7 30.3C109.6 32 109.4 32.6 108.6 33.5C107 35.4 103.8 35.4 102.1 33.5C101.4 32.7 101.2 32.1 100.9 30C100 22.5 96.6 15.8 91.1 10.6C82.7 2.5 71.1 -0.8 59.4 1.5C55.5 2.3 50.9 4.2 47.6 6.3C39.4 11.7 34 20.7 33.3 30.1C33.2 32.4 33 33 32.2 34C31.2 35 30.6 35.2 28.4 35.6C21.2 36.6 14.9 39.7 9.7 45C6.8 47.9 5.1 50.1 3.4 53.4C2 56.2 1.1 58.8 0.4 61.6C0 63.4 -0 64.1 0 67.9C0 71.5 0.1 72.4 0.4 74C3 85.7 11.8 95.2 23.7 99.4C25.9 100.1 29.2 100.9 31.4 101.2C33.2 101.4 34.4 102 35.1 103.1C35.5 103.8 35.6 104.2 35.8 106C36 108.9 36.2 110.6 36.9 113.1C39.3 122.7 46.4 130.2 56.2 133.5C59.5 134.6 62.2 135 66.3 135C74.5 135 81.2 133.1 87.5 128.8C95.8 123.2 101.1 114.4 102.2 104.3C102.5 101.5 104.1 100.1 106.5 100.3C108.7 100.5 109.9 102 110.2 104.7C111.6 116.4 118.3 125.8 129 131.2C134.2 133.8 138.7 134.8 144.8 134.8C150.9 134.8 155.4 133.8 160.6 131.3C171.3 126.2 177.9 116.5 178.8 104.7C178.9 102.8 179.3 101.9 180.3 101.1C181.1 100.4 181.7 100.2 183.6 100C185.7 99.8 188.7 99.2 190.7 98.6C193.2 97.7 197.1 95.8 199.4 94.2C201.7 92.6 205 89.6 206.7 87.4C210.2 83.1 212.6 77.7 213.3 72.6C213.6 70.7 213.7 67.2 213.6 65.3ZM210.5 71.1C210.8 71.1 211.3 71.1 211.6 71L212.2 70.9L212.2 71.7C212.2 73.7 210.9 78.2 209.6 80.8C204.5 91.1 194.4 98 183.3 98.9C181.2 99.1 180.5 99.4 179.4 100.4C178.2 101.6 177.9 102.3 177.7 104.8C177.4 108.3 176.9 110.7 175.7 113.7C172 123.3 163.5 130.5 153 132.9C152.1 133.1 150.7 133.3 150 133.4L148.6 133.5L148.6 132.3L148.5 131.2L150 130.9C154 130 156.5 129.2 159.5 127.7C165 124.9 169.5 120.4 172.2 114.9C173.8 111.7 174.5 108.8 175 103.8C175.3 100.8 176.7 98.6 179.1 97.3C180.3 96.6 181.2 96.4 183.8 96.1C190.7 95.4 196.9 92.3 201.9 86.9C206.1 82.6 208.4 78.1 209.8 71.6C209.9 71.2 210.1 71.1 210.5 71.1ZM196.9 41.3C204.3 46.1 209.5 53 211.6 60.7C211.8 61.7 212 62.8 212 63.2C212 63.9 212 63.9 211.4 63.9C211 63.9 210.5 64 210.2 64.1C209.8 64.2 209.8 64.2 209.6 63.4C208.3 58.2 206.1 53.9 202.7 49.9C197.7 44.1 190.4 39.9 183.1 38.7C181.9 38.5 180.4 38.2 179.8 38.1C177.4 37.7 175.1 35.9 174 33.6C173.3 32.3 173.3 32.1 173.1 29.5C172.7 22 170.4 16.7 165.5 11.8C161.4 7.7 156.6 5.2 150.8 4.2C149.6 3.9 148.4 3.7 148.2 3.7C147.8 3.6 147.8 3.5 148 3C148.1 2.6 148.1 2.1 148.1 1.8L148.1 1.3L149.9 1.4C154.5 1.7 159.9 3.9 164.5 7.2C166.2 8.4 169.3 11.6 170.6 13.4C172.8 16.5 174.5 20.3 175.2 23.9C175.7 26 175.8 26.9 176 29.8C176.1 31.7 176.2 32.1 176.7 32.9C177.7 34.6 178.7 35.2 181.9 35.6C187.5 36.4 192.2 38.2 196.9 41.3ZM206.9 58.8C207.6 60.4 208.5 63.6 208.7 65.1C208.7 65.4 208.7 67 208.7 68.7C208.7 71.2 208.6 72.1 208.2 73.7C206.2 81.5 200.5 88.5 193.1 92.2C189.8 93.9 186.7 94.8 182.9 95.1C181.9 95.2 180.6 95.5 180 95.7C177.7 96.4 175.4 98.6 174.5 101C174.2 101.7 174 102.8 173.9 104.3C173.6 108.5 172.5 112.1 170.8 115.4C166.8 122.8 159.1 128.1 150.1 129.7C148.2 130 147.4 130.1 144.2 130C140 129.9 138.9 129.7 135.4 128.5C130.3 126.8 125.5 123.7 122.2 120C118.8 116.2 116.2 110.8 115.5 106C115.3 104.9 115.1 103.4 115 102.7C114.4 99.4 111.7 96.5 108.4 95.6C104.2 94.5 100.1 96.4 98.2 100.3C97.7 101.4 97.5 102.1 97.2 103.9C96.7 108 95.6 111.5 93.8 114.8C89.7 122.4 81.3 128.2 72.5 129.7C66.5 130.8 61.8 130.4 56.9 128.5C47.4 124.8 41.6 117 40.8 106.9C40.6 103.5 40.2 102.1 39.3 100.7C37.8 98.4 35.5 96.8 32.9 96.5C29.9 96 28.1 95.6 26.2 95C15.5 91.6 7.2 82.7 5.1 72.3C4.6 69.7 4.6 65.5 5.2 62.9C7.1 54.3 12.8 47 20.7 43C23 41.9 26 40.8 28 40.6C28.9 40.4 30.1 40.3 30.7 40.2C33.4 39.8 36.2 37.6 37.4 35C37.9 33.9 38.1 33.4 38.2 31.4C38.4 28.7 38.9 25.8 39.6 23.8C43.3 13.7 52.9 6.7 64.5 5.6C73.1 4.9 83 8.8 89 15.4C90.7 17.2 92.3 19.6 93.4 21.9C95 25.3 95.6 27.3 96.2 31.7C96.3 32.4 96.6 33.6 96.8 34.1C97.6 36.4 100 38.6 102.4 39.4C104.2 40.1 106.8 40 108.4 39.4C112.2 38 114.3 35.1 114.7 30.7C114.9 27.5 115.8 24.4 117.4 21.1C121.9 11.9 130.8 6 142 4.9C150.8 4 159 6.8 164.7 12.6C167.6 15.5 169.5 18.4 170.7 22.1C171.4 24.1 172 27.6 172 29.3C172 32.4 172.8 34.5 174.7 36.5C176.2 38.1 178 39 180 39.3C180.8 39.4 182.3 39.6 183.3 39.8C193.5 41.7 203.1 49.4 206.9 58.8ZM139.1 130.7C140.4 131 141.5 131.2 141.7 131.3C141.9 131.4 141.9 132.1 141.6 133.1C141.4 133.6 140.1 133.6 137.7 133.1C133.8 132.3 129.1 130.3 125.4 127.8C122.4 125.8 118.6 122.1 116.7 119.2C113.7 114.7 112 109.9 111.4 104.9C111.1 102.5 110.5 101.3 109.3 100.3C107.1 98.6 104.1 98.8 102.3 100.9C101.6 101.8 101.2 102.8 101 105.1C100.1 112.7 95.9 120.5 90 125.5C85.4 129.3 80.6 131.8 75.2 133C74.1 133.2 72.8 133.4 72.4 133.4C71.5 133.5 71.5 133.4 71.3 131.9L71.2 131.2L72.7 130.9C82.6 128.8 90.4 123.2 94.9 115.1C96.6 112 97.8 108 98.3 103.9C98.7 101.4 99.2 100.2 100.5 98.8C104 95.2 110.1 95.9 112.8 100.1C113.5 101.3 113.8 102.2 114.1 104.6C114.6 108.4 115.5 111.4 117.3 114.7C121.4 122.8 129.3 128.5 139.1 130.7ZM139.9 1.5C140.7 1.5 140.7 1.5 140.8 2.2C140.9 3.4 140.9 3.8 140.9 3.8C140.6 3.8 136 4.8 135 5.1C127 7.4 120.2 13.1 116.5 20.6C114.8 23.9 114.1 26.6 113.6 30.3C113.4 32.4 113 33.9 112.5 34.7C110.6 37.9 106.9 39.5 103.5 38.6C101.1 38 99 36.3 98.1 34.3C97.6 33.3 97.4 32.7 96.9 29.2C95.9 22.9 92.6 16.9 87.6 12.5C83.4 8.8 77.9 6.1 71.7 4.8L70.3 4.5L70.5 3.9C71.1 2.1 70.9 2.2 72.2 2.4C75.2 2.7 80.3 4.4 83.4 6.2C92.7 11.5 98.6 20 99.9 30.4C100.2 32.7 100.6 33.5 101.6 34.5C103.2 36.1 105.5 36.5 107.5 35.6C109.4 34.8 110.8 32.7 110.8 30.7C110.8 29.7 111.3 26.8 111.8 25C113.3 19.6 116 15 120.1 11C122.9 8.2 125.2 6.5 128.5 4.8C132.3 2.9 137.2 1.5 139.9 1.5ZM62.3 131.2C63.5 131.4 64.4 131.6 64.5 131.6C64.5 131.6 64.4 131.9 64.3 132.4C64.2 132.8 64.2 133.3 64.2 133.5C64.3 133.9 64.2 133.9 62.8 133.8C59.4 133.5 56.3 132.6 52.7 130.8C49.7 129.4 47.4 127.7 45.1 125.4C40.1 120.4 37.4 114.3 36.9 106.4C36.7 103.5 36.4 102.6 35.2 101.6C34.2 100.7 33.3 100.3 31 100C21.3 98.6 12.7 93.6 7.1 86C4.2 82.1 1.8 76.6 1.5 72.9L1.4 71.7L2.5 71.6C3.6 71.4 3.9 71.6 3.9 72.3C3.9 73 5.3 77.2 6.3 79.2C8.8 84.5 12.3 88.5 17.4 91.9C21.3 94.6 25.5 96.3 29.8 97C31 97.3 32.5 97.5 33 97.6C35.4 98 37.6 99.7 38.7 101.9C39.2 102.8 39.4 103.4 39.5 105C40 110.4 40.9 114 42.3 116.9C46 124.3 53.4 129.6 62.3 131.2ZM61.7 2.3C63.6 2.2 63.5 2.1 63.6 3.7C63.7 4 63.7 4.3 63.6 4.4C63.5 4.4 62.5 4.7 61.3 4.9C53.1 6.5 46 11 41.8 17.3C38.8 21.7 37.7 25.3 37.1 31.7C36.9 33.8 36.4 35 35.1 36.5C33.5 38.3 32 38.9 28.2 39.4C25.3 39.8 20.7 41.5 17.6 43.5C13.4 46.1 8.9 51 6.6 55.5C5.7 57.3 4.7 60.1 4.1 62.4C3.9 63.5 3.6 64.4 3.5 64.5C3.5 64.5 2.9 64.6 2.3 64.7C1.2 64.8 1.2 64.8 1.2 64.2C1.2 63.4 1.9 60 2.4 58.6C3 56.7 5 52.6 6.1 50.9C9.5 46 13.6 42.3 18.8 39.7C22.2 38 25.3 37.1 29 36.6C32.8 36.1 34.2 34.4 34.5 30.1C34.9 21.4 40.1 12.7 47.8 7.5C51.9 4.7 57.6 2.6 61.7 2.3Z"/> <path d="M64.5 131.6C64.4 131.6 63.5 131.4 62.3 131.2C53.4 129.6 46 124.3 42.3 116.9C40.9 114 40 110.4 39.5 105C39.4 103.4 39.2 102.8 38.7 101.9C37.6 99.7 35.4 98 33 97.6C32.5 97.5 31 97.3 29.8 97C25.5 96.3 21.3 94.6 17.4 91.9C12.3 88.5 8.8 84.5 6.3 79.2C5.3 77.2 3.9 73 3.9 72.3C3.9 71.6 3.6 71.4 2.5 71.6L1.4 71.7L1.5 72.9C1.8 76.6 4.2 82.1 7.1 86C12.7 93.6 21.3 98.6 31 100C33.3 100.3 34.2 100.7 35.2 101.6C36.4 102.6 36.7 103.5 36.9 106.4C37.4 114.3 40.1 120.4 45.1 125.4C47.4 127.7 49.7 129.4 52.7 130.8C56.3 132.6 59.4 133.5 62.8 133.8C64.2 133.9 64.3 133.9 64.2 133.5C64.2 133.3 64.2 132.8 64.3 132.4C64.4 131.9 64.5 131.6 64.5 131.6Z"/> <path d="M63.6 3.7C63.5 2.1 63.6 2.2 61.7 2.3C57.6 2.6 51.9 4.7 47.8 7.5C40.1 12.7 34.9 21.4 34.5 30.1C34.2 34.4 32.8 36.1 29 36.6C25.3 37.1 22.2 38 18.8 39.7C13.6 42.3 9.5 46 6.1 50.9C5 52.6 3 56.7 2.4 58.6C1.9 60 1.2 63.4 1.2 64.2C1.2 64.8 1.2 64.8 2.3 64.7C2.9 64.6 3.5 64.5 3.5 64.5C3.6 64.4 3.9 63.5 4.1 62.4C4.7 60.1 5.7 57.3 6.6 55.5C8.9 51 13.4 46.1 17.6 43.5C20.7 41.5 25.3 39.8 28.2 39.4C32 38.9 33.5 38.3 35.1 36.5C36.4 35 36.9 33.8 37.1 31.7C37.7 25.3 38.8 21.7 41.8 17.3C46 11 53.1 6.5 61.3 4.9C62.5 4.7 63.5 4.4 63.6 4.4C63.7 4.3 63.7 4 63.6 3.7Z"/> <path d="M140.8 2.2C140.7 1.5 140.7 1.5 139.9 1.5C137.2 1.5 132.3 2.9 128.5 4.8C125.2 6.5 122.9 8.2 120.1 11C116 15 113.3 19.6 111.8 25C111.3 26.8 110.8 29.7 110.8 30.7C110.8 32.7 109.4 34.8 107.5 35.6C105.5 36.5 103.2 36.1 101.6 34.5C100.6 33.5 100.2 32.7 99.9 30.4C98.6 20 92.7 11.5 83.4 6.2C80.3 4.4 75.2 2.7 72.2 2.4C70.9 2.2 71.1 2.1 70.5 3.9L70.3 4.5L71.7 4.8C77.9 6.1 83.4 8.8 87.6 12.5C92.6 16.9 95.9 22.9 96.9 29.2C97.4 32.7 97.6 33.3 98.1 34.3C99 36.3 101.1 38 103.5 38.6C106.9 39.5 110.6 37.9 112.5 34.7C113 33.9 113.4 32.4 113.6 30.3C114.1 26.6 114.8 23.9 116.5 20.6C120.2 13.1 127 7.4 135 5.1C136 4.8 140.6 3.8 140.9 3.8C140.9 3.8 140.9 3.4 140.8 2.2Z"/> <path d="M140.7 24.3L140 23.3L139.1 23.8C137.9 24.4 136.4 25.9 135.8 27.2C135.1 28.5 134.9 29.6 134.8 32.1C134.6 35.8 133.8 38.8 132.1 42.4C129.6 47.5 125.1 52.2 120.1 54.9C115.7 57.3 111.8 58.5 106.9 58.9C99.7 59.6 92.3 57.4 86.5 53.1C83.5 50.8 81.5 48.5 79.6 45C78.1 42.2 77.5 40 77.1 35.1C76.9 33.6 76.8 32.9 76.3 31.9C75.6 30.4 74.1 28.8 72.8 28.1L71.7 27.6L71.3 28.1C71 28.5 70.8 28.8 70.8 28.9C70.8 29 70.7 29.2 70.5 29.4C70.3 29.6 70.4 29.7 71.4 30.3C72.6 31.2 73.7 32.5 74 33.6C74.1 34.1 74.3 35.5 74.4 36.8C74.9 42.8 77.5 48.3 81.9 52.8C86.3 57.2 92.7 60.3 99.7 61.4C101.9 61.8 107.8 61.7 110 61.3C114.4 60.6 118.1 59.2 121.6 57.2C129.2 52.9 134.8 45.8 136.8 37.8C137.3 35.7 137.7 32.7 137.7 30.9C137.7 29 138.7 27.3 140.7 25.8L141.3 25.3L140.7 24.3Z"/> <path d="M65.2 28.7C64.7 27.8 64.7 27.7 64.1 27.9C62.6 28.3 60.7 30.3 60 32.2C59.8 32.7 59.5 33.7 59.4 34.4C59.3 35.1 59.1 36.2 59 36.9C58.7 38.5 57.7 41.6 56.8 43.4C53.8 49.3 49 54 42.7 57C38.5 59 34.9 59.9 30.5 60C27.3 60.1 26.3 60.3 24.9 61.1C23.7 61.7 22 63.4 21.8 64.2C21.7 64.6 21.8 64.8 22.4 65.4C22.9 65.8 23.3 66.2 23.4 66.2C23.5 66.2 23.8 65.8 24.1 65.3C25.5 63.4 26.6 62.9 30.9 62.7C34.6 62.6 36.7 62.2 40.1 61C45.5 59.1 50.2 56.1 54.1 52C58.5 47.4 61.4 41.3 62.1 35.5C62.3 33.1 63.1 31.7 64.6 30.6C65.2 30.1 65.8 29.7 65.8 29.7C65.8 29.7 65.6 29.2 65.2 28.7Z"/> <path d="M65.5 105.4C63.8 104.3 63 102.8 62.7 100.2C62.1 96.2 60.2 91.4 57.6 87.7C53.9 82.5 48.7 78.4 42.5 75.9C38.5 74.3 34.8 73.5 29.7 73.3C26.5 73.1 25.4 72.6 24.1 70.9L23.6 70.1L22.9 70.5C21.5 71.3 21.5 71.4 21.9 72.2C22.3 73 23.9 74.5 24.8 75C26.1 75.7 27 75.8 30.3 76C33.9 76.2 36.4 76.7 39.5 77.7C44.1 79.2 48.2 81.7 51.5 84.9C54.1 87.4 55.7 89.5 57.1 92.3C58.6 95.1 59.3 97.3 59.8 100.3C60.3 103.2 60.9 104.6 62.4 106.1C63.2 107.1 65.2 108.3 65.4 108C65.5 107.8 66.4 106.2 66.4 106.1C66.4 106.1 66 105.8 65.5 105.4Z"/> <path d="M141.1 108.9C140.9 108.8 140.2 108.3 139.6 107.8C138.1 106.3 137.7 105.4 137.4 102.2C136.9 97.3 136.2 94.5 134.3 90.7C130.7 83.2 123.8 77.3 115.5 74.6C108.4 72.3 101.3 72.3 94.3 74.6C92.2 75.3 88.8 76.9 87.3 77.9C79.8 83 75.4 90.5 74.9 99.1C74.8 100.2 74.7 101.5 74.6 102C74.3 103.3 73.4 104.5 72.1 105.3L71.1 106.1L71.5 107C71.7 107.5 71.9 108 71.8 108.1C71.7 108.3 72.8 108 73.6 107.6C74.8 106.9 76.1 105.4 76.8 104C77.4 102.7 77.5 102.4 77.6 100C77.9 95.8 78.5 93.5 80 90.3C81.2 87.7 82.6 85.8 84.8 83.6C88.6 79.8 93.1 77.5 99.1 76.2C101.7 75.6 107.9 75.5 110.5 76.1C114.3 76.8 118.6 78.6 121.6 80.5C129.3 85.5 133.7 92.8 134.6 102.1C134.9 105.9 135.4 107.2 137.1 109.1C138 110 139.7 111.1 140.3 111.1C140.4 111.1 140.7 110.6 141 110C141.5 109 141.5 109 141.1 108.9Z"/> <path d="M185.9 70L184.8 69.4L184.3 70.3C183.7 71.2 182.6 72.1 181.5 72.5C181.1 72.7 179.8 72.9 178.4 73.1C171.2 73.9 164.6 77 159.5 81.9C154.2 87 151.1 93.1 150.4 99.8C149.9 104.7 149.9 104.8 149.3 106.1C148.8 107.1 148.4 107.5 147.6 108.1C146.9 108.6 146.4 109 146.3 109C146.2 109.1 146.3 109.6 146.6 110.1C147.1 111.2 147.1 111.2 147.7 111C149.3 110.6 151.5 108.3 152.3 106.2C152.6 105.5 152.8 104.3 152.9 102.9C153.4 96.3 154.6 92.7 157.6 88.1C159.2 85.9 162.4 82.6 164.7 81C169.4 77.9 173.6 76.3 179 75.7C181.1 75.5 182 75.4 182.8 75C184.6 74.1 186.1 72.7 186.7 71.2L187 70.5L185.9 70Z"/> <path d="M186.6 63.4C185.6 61.5 183.9 60.1 181.9 59.5C181.2 59.3 179.8 59.1 178.8 59C168.1 58.4 157.9 51.2 154 41.6C152.9 38.8 152.5 36.8 152.4 33.4C152.3 29 152 28 150.4 25.9C149.7 24.9 148.7 24.1 147.5 23.6L146.8 23.3L146.3 24.2C145.9 24.7 145.7 25.1 145.7 25.2C145.7 25.3 146.1 25.7 146.7 26.1C149 27.6 149.4 28.8 149.7 33.7C149.9 38.7 150.4 40.8 152.6 45.2C153.9 47.7 155.5 49.8 157.7 52.1C163.1 57.7 170.5 61.1 178.7 61.8C181.6 62 182.7 62.5 184.1 64.3L184.8 65.2L185.7 64.7C186.8 64.1 186.9 64 186.6 63.4Z"/> <path d="M211.6 60.7C209.5 53 204.3 46.1 196.9 41.3C192.2 38.2 187.5 36.4 181.9 35.6C178.7 35.2 177.7 34.6 176.7 32.9C176.2 32.1 176.1 31.7 176 29.8C175.8 26.9 175.7 26 175.2 23.9C174.5 20.3 172.8 16.5 170.6 13.4C169.3 11.6 166.2 8.4 164.5 7.2C159.9 3.9 154.5 1.7 149.9 1.4L148.1 1.3L148.1 1.8C148.1 2.1 148.1 2.6 148 3C147.8 3.5 147.8 3.6 148.2 3.7C148.4 3.7 149.6 3.9 150.8 4.2C156.6 5.2 161.4 7.7 165.5 11.8C170.4 16.7 172.7 22 173.1 29.5C173.3 32.1 173.3 32.3 174 33.6C175.1 35.9 177.4 37.7 179.8 38.1C180.4 38.2 181.9 38.5 183.1 38.7C190.4 39.9 197.7 44.1 202.7 49.9C206.1 53.9 208.3 58.2 209.6 63.4C209.8 64.2 209.8 64.2 210.2 64.1C210.5 64 211 63.9 211.4 63.9C212 63.9 212 63.9 212 63.2C212 62.8 211.8 61.7 211.6 60.7Z"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M208.7 65.1C208.5 63.6 207.6 60.4 206.9 58.8C203.1 49.4 193.5 41.7 183.3 39.8C182.3 39.6 180.8 39.4 180 39.3C178 39 176.2 38.1 174.7 36.5C172.8 34.5 172 32.4 172 29.3C172 27.6 171.4 24.1 170.7 22.1C169.5 18.4 167.6 15.5 164.7 12.6C159 6.8 150.8 4 142 4.9C130.8 6 121.9 11.9 117.4 21.1C115.8 24.4 114.9 27.5 114.7 30.7C114.3 35.1 112.2 38 108.4 39.4C106.8 40 104.2 40.1 102.4 39.4C100 38.6 97.6 36.4 96.8 34.1C96.6 33.6 96.3 32.4 96.2 31.7C95.6 27.3 95 25.3 93.4 21.9C92.3 19.6 90.7 17.2 89 15.4C83 8.8 73.1 4.9 64.5 5.6C52.9 6.7 43.3 13.7 39.6 23.8C38.9 25.8 38.4 28.7 38.2 31.4C38.1 33.4 37.9 33.9 37.4 35C36.2 37.6 33.4 39.8 30.7 40.2C30.1 40.3 28.9 40.4 28 40.6C26 40.8 23 41.9 20.7 43C12.8 47 7.1 54.3 5.2 62.9C4.6 65.5 4.6 69.7 5.1 72.3C7.2 82.7 15.5 91.6 26.2 95C28.1 95.6 29.9 96 32.9 96.5C35.5 96.8 37.8 98.4 39.3 100.7C40.2 102.1 40.6 103.5 40.8 106.9C41.6 117 47.4 124.8 56.9 128.5C61.8 130.4 66.5 130.8 72.5 129.7C81.3 128.2 89.7 122.4 93.8 114.8C95.6 111.5 96.7 108 97.2 103.9C97.5 102.1 97.7 101.4 98.2 100.3C100.1 96.4 104.2 94.5 108.4 95.6C111.7 96.5 114.4 99.4 115 102.7C115.1 103.4 115.3 104.9 115.5 106C116.2 110.8 118.8 116.2 122.2 120C125.5 123.7 130.3 126.8 135.4 128.5C138.9 129.7 140 129.9 144.2 130C147.4 130.1 148.2 130 150.1 129.7C159.1 128.1 166.8 122.8 170.8 115.4C172.5 112.1 173.6 108.5 173.9 104.3C174 102.8 174.2 101.7 174.5 101C175.4 98.6 177.7 96.4 180 95.7C180.6 95.5 181.9 95.2 182.9 95.1C186.7 94.8 189.8 93.9 193.1 92.2C200.5 88.5 206.2 81.5 208.2 73.7C208.6 72.1 208.7 71.2 208.7 68.7C208.7 67 208.7 65.4 208.7 65.1ZM182.5 58.5C185.2 59.4 187.4 61.6 188.3 64.4C188.9 65.9 188.9 68.8 188.3 70.4C187.8 71.8 186.9 73.2 185.8 74.2C184.1 75.9 182.5 76.5 179.1 76.9C174.8 77.3 171.3 78.4 167.6 80.5C161.8 83.8 157.4 89 155.4 95.1C154.6 97.4 154.3 99.2 154.1 101.9C154 103.1 153.9 104.7 153.7 105.4C152.9 108.8 149.8 112 146.4 112.8C145.3 113 142.7 113 141.5 112.8C138.8 112.2 136.1 110.2 134.8 107.8C134 106.1 133.6 104.7 133.5 102.3C133.1 96.5 130.9 91.1 126.9 86.4C123.8 82.8 119.1 79.8 114.1 78.2C103.7 74.8 92 77.5 85 84.9C81.1 88.9 78.8 94.6 78.8 99.8C78.8 102.8 77.8 105.3 75.9 107.1C74.1 108.9 72.3 109.7 69.6 109.9C66.3 110.1 63.7 109.1 61.7 107.1C60 105.4 59.3 103.8 58.8 100.5C58.1 96.6 56.9 93.6 54.5 90C53.1 87.8 49.4 84.2 47.1 82.7C41.9 79.2 36.7 77.5 30.3 77.1C28.5 77 26.6 76.9 26.2 76.8C23.2 76 20.6 73.4 19.9 70.7C19.5 69.2 19.5 66.8 20 65.3C20.5 63.6 21.7 61.9 23.2 60.8C25.1 59.5 26.3 59.1 30.3 58.9C34.1 58.7 36.2 58.3 39.5 57.1C49.4 53.4 56.6 45.2 58 35.9C58.7 31.9 59.5 30 61.5 28.2C63.4 26.5 65.4 25.8 68.2 25.8C71.2 25.8 73.3 26.6 75.3 28.5C77.2 30.4 78 32.2 78.2 35.7C78.3 37.1 78.5 38.9 78.7 39.7C79.2 41.5 80.3 44.2 81.3 45.9C84.8 51.6 91.2 55.8 99 57.5C100.7 57.8 101.6 57.9 104.5 57.9C108.5 57.9 110.4 57.6 114.1 56.4C122.6 53.6 129.6 47 132.4 39C133.2 36.7 133.5 34.9 133.7 31.8C133.9 30 134 28.7 134.3 27.9C135.7 23.7 139.8 21.1 144.3 21.5C147.1 21.7 149 22.7 150.8 24.6C152.8 26.7 153.4 28.8 153.4 32.8C153.5 35.5 153.8 37.8 154.5 39.8C156.9 46.6 161.5 51.7 168.1 54.9C171.8 56.8 175.1 57.7 179.3 58C180.6 58.1 182 58.3 182.5 58.5Z"/></svg>`;
31
+ const SIGNATURE = "From one designer to all designers, with love &lt;3";
32
+
33
+ const BAND = (n, max) => {
34
+ const r = max ? n / max : 0;
35
+ return r >= 0.85 ? "strong" : r >= 0.6 ? "fair" : r >= 0.35 ? "weak" : "poor";
36
+ };
37
+
38
+ const VERDICT = {
39
+ strong: ["Agent-ready", "Agents can work in this system. Keep the gates that make that true."],
40
+ fair: ["Good foundation", "The bones are right. The gaps left are the ones that let an agent invent things."],
41
+ weak: ["Gaps to address", "An agent will produce plausible, wrong UI here more often than not."],
42
+ poor: ["Not ready", "There is nothing here for an agent to follow, so it will follow its own habits."],
43
+ };
44
+
45
+ /* Mirrors FIXES in lib/fix.mjs. The kind is the honest half of this table: an
46
+ apply is a file this tool writes correctly in any repository, a brief is a task
47
+ that needs the repo's own stack and ships as a spec for an agent to carry out.
48
+ How long a fix takes is not something the scan can observe, so it is not shown. */
49
+ const FIX_LABELS = {
50
+ "agents-md": ["Write agent instructions", "apply"],
51
+ "mcp-config": ["Expose the docs as MCP tools", "apply"],
52
+ "coverage-gate": ["Fail CI on an undocumented export", "brief"],
53
+ "prop-tables": ["Generate prop tables from types", "brief"],
54
+ "examples-check": ["Compile guide examples in CI", "brief"],
55
+ "tokens-doc": ["Document tokens as tables", "apply"],
56
+ "patterns-doc": ["Write page-level patterns", "brief"],
57
+ "a11y-docs": ["Add keyboard and accessibility sections", "brief"],
58
+ "ci-workflow": ["Put the checks in CI", "apply"],
59
+ "gaps-file": ["List what the system does not have", "apply"],
60
+ };
61
+
62
+ const KIND_WORD = { apply: "adsa writes it", brief: "brief for your agent" };
63
+
64
+ export function html(facts, scored, history = []) {
65
+ const rubricVersion = RUBRIC.version;
66
+ const date = new Date().toISOString().slice(0, 10);
67
+ const band = BAND(scored.total, scored.max);
68
+ const [verdictTitle, verdictLine] = VERDICT[band];
69
+ const dims = scored.dimensions;
70
+ const live = dims.filter((d) => !d.skipped);
71
+ const weakest = [...live].sort((a, b) => a.score - b.score).slice(0, 3);
72
+ const todo = buildTodo(scored);
73
+ const trend = history.filter((h) => h && h.total != null).slice(-8);
74
+ const previous = trend.length > 1 ? trend[trend.length - 2] : null;
75
+ const delta = previous ? scored.total - previous.total : null;
76
+ const undocumented = Math.max(facts.coverage.total - facts.coverage.documented, 0);
77
+ const gained = previous ? live.filter((d) => previous.dimensions && previous.dimensions[d.id] != null && d.score > previous.dimensions[d.id]) : [];
78
+
79
+ return `<!doctype html>
80
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
81
+ <title>${esc(facts.name)} — agent readiness</title>
82
+ <meta name="generator" content="adsa">
83
+ <style>
84
+ /* ---------------------------------------------------------------- tokens */
85
+ :root{
86
+ --paper:#f6f3e8;--panel:#fffefa;--sunk:#f1eee2;
87
+ --ink:#16190f;--ink-2:#4b5142;--ink-3:#62685b;
88
+ --line:#e2dfd2;--line-2:#edeadf;
89
+ --green:#166534;--mark-green:#16a34a;--green-tint:#cfe9d4;--green-edge:#a5d2b1;
90
+ --amber:#854d0e;--mark-amber:#ca8a04;--figure-amber:#c68704;--amber-tint:#f6e3b2;--amber-edge:#e0c17c;
91
+ --red:#991b1b;--mark-red:#dc2626;--red-tint:#f8d3cd;--red-edge:#eeaba0;
92
+ --shadow:0 1px 2px rgba(22,25,15,.05);
93
+ --sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",sans-serif;
94
+ --mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,monospace;
95
+ color-scheme:light;
96
+ }
97
+ :root[data-theme="dark"]{
98
+ --paper:#12150d;--panel:#1a1e14;--sunk:#20251a;
99
+ --ink:#e8e7dc;--ink-2:#b1b5a5;--ink-3:#8d9384;
100
+ --line:#2e3327;--line-2:#262b1f;
101
+ --green:#4ade80;--mark-green:#22c55e;--green-tint:#16301f;--green-edge:#2c5c3b;
102
+ --amber:#facc15;--mark-amber:#eab308;--figure-amber:#eab308;--amber-tint:#2e2711;--amber-edge:#5f4d1e;
103
+ --red:#f87171;--mark-red:#ef4444;--red-tint:#331a1a;--red-edge:#6b2f2f;
104
+ --shadow:0 1px 2px rgba(0,0,0,.4);
105
+ color-scheme:dark;
106
+ }
107
+ @media (prefers-color-scheme:dark){
108
+ :root:not([data-theme="light"]){
109
+ --paper:#12150d;--panel:#1a1e14;--sunk:#20251a;
110
+ --ink:#e8e7dc;--ink-2:#b1b5a5;--ink-3:#8d9384;
111
+ --line:#2e3327;--line-2:#262b1f;
112
+ --green:#4ade80;--mark-green:#22c55e;--green-tint:#16301f;--green-edge:#2c5c3b;
113
+ --amber:#facc15;--mark-amber:#eab308;--figure-amber:#eab308;--amber-tint:#2e2711;--amber-edge:#5f4d1e;
114
+ --red:#f87171;--mark-red:#ef4444;--red-tint:#331a1a;--red-edge:#6b2f2f;
115
+ --shadow:0 1px 2px rgba(0,0,0,.4);
116
+ color-scheme:dark;
117
+ }
118
+ }
119
+
120
+ *{box-sizing:border-box}
121
+ html{scroll-behavior:smooth}
122
+ body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans);font-size:15px;line-height:1.6;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
123
+ h1,h2,h3{margin:0;letter-spacing:-0.018em;font-weight:600;line-height:1.25}
124
+ p{margin:0}
125
+ a{color:var(--green)}
126
+ b,strong{font-weight:600}
127
+ code{font-family:var(--mono);font-size:.86em;background:var(--sunk);border:1px solid var(--line-2);border-radius:4px;padding:1px 5px}
128
+ ::selection{background:var(--mark-green);color:#fff}
129
+ :focus-visible{outline:2px solid var(--mark-green);outline-offset:2px;border-radius:4px}
130
+ .num{font-variant-numeric:tabular-nums}
131
+ .sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
132
+
133
+ /* ------------------------------------------------------------------ frame */
134
+ .app{display:grid;grid-template-columns:250px minmax(0,1fr);max-width:1360px;margin:0 auto;min-height:100vh}
135
+
136
+ .rail{border-right:1px solid var(--line);padding:26px 22px 22px;display:flex;flex-direction:column;gap:24px;position:sticky;top:0;align-self:start;height:100vh}
137
+ .brand{display:flex;align-items:center;gap:9px;font-family:var(--mono);font-size:10.5px;letter-spacing:.15em;text-transform:uppercase;color:var(--ink-3)}
138
+ .mark{width:26px;height:auto;color:var(--ink);flex:none}
139
+ .who b{display:block;font-size:16px;letter-spacing:-0.02em;word-break:break-word}
140
+ .who span{font-family:var(--mono);font-size:12px;color:var(--ink-3)}
141
+
142
+ .railscore{display:flex;align-items:baseline;gap:7px}
143
+ .railscore b{font-size:40px;line-height:1;letter-spacing:-0.04em}
144
+ .railscore span{font-family:var(--mono);font-size:12px;color:var(--ink-3)}
145
+ .railscore i{font-style:normal;font-family:var(--mono);font-size:12px;margin-left:auto}
146
+ .pill{display:inline-block;font-size:12px;font-weight:600;padding:4px 10px;border-radius:999px;margin-top:9px;border:1px solid}
147
+ .pill.strong,.pill.fair{background:var(--green-tint);color:var(--green);border-color:var(--green-edge)}
148
+ .railscore i.up{color:var(--green)}.railscore i.down{color:var(--red)}
149
+ .pill.weak{background:var(--amber-tint);color:var(--amber);border-color:var(--amber-edge)}
150
+ .pill.poor{background:var(--red-tint);color:var(--red);border-color:var(--red-edge)}
151
+ .up{color:var(--green)}.down{color:var(--red)}
152
+
153
+ .rail nav{display:flex;flex-direction:column;gap:1px}
154
+ .rail nav a{display:flex;justify-content:space-between;gap:10px;padding:7px 10px;margin:0 -10px;border-radius:7px;text-decoration:none;color:var(--ink-2);font-size:14px}
155
+ .rail nav a:hover{background:var(--sunk);color:var(--ink)}
156
+ .rail nav a[aria-current="true"]{background:var(--green-tint);color:var(--green);font-weight:600;box-shadow:inset 0 0 0 1px var(--green-edge)}
157
+ .rail nav a i{font-style:normal;font-family:var(--mono);font-size:11.5px;color:var(--ink-3)}
158
+ .rail .foot{margin-top:auto;font-size:12px;color:var(--ink-3);line-height:1.55;display:flex;flex-direction:column;gap:10px}
159
+ .rail .foot a{text-decoration:none}
160
+ .theme{align-self:flex-start;display:inline-flex;align-items:center;gap:7px;font-family:var(--mono);font-size:11.5px;color:var(--ink-2);background:none;border:1px solid var(--line);border-radius:999px;padding:5px 10px;cursor:pointer}
161
+ .theme:hover{background:var(--sunk)}
162
+ .theme s{width:11px;height:11px;border-radius:50%;border:1.5px solid currentColor;background:linear-gradient(90deg,currentColor 50%,transparent 50%);text-decoration:none;display:block}
163
+
164
+ main{padding:26px clamp(18px,3vw,38px) 60px;display:flex;flex-direction:column;gap:14px;min-width:0}
165
+
166
+ /* ------------------------------------------------------------------ head */
167
+ .top{display:flex;flex-wrap:wrap;gap:14px 28px;align-items:flex-end;justify-content:space-between;margin-bottom:4px}
168
+ .top h1{font-size:22px}
169
+ .top .meta{font-family:var(--mono);font-size:12px;color:var(--ink-3);margin-top:6px}
170
+ .top .meta s{text-decoration:none;padding:0 7px;color:var(--ink-3);opacity:.5}
171
+ .stamp{font-family:var(--mono);font-size:11.5px;color:var(--ink-3);text-align:right;line-height:1.7}
172
+
173
+ /* ----------------------------------------------------------------- panels */
174
+ .grid{display:grid;gap:14px;align-items:stretch}
175
+ .g-score{grid-template-columns:minmax(0,340px) minmax(0,1fr)}
176
+ .g-3{grid-template-columns:repeat(3,minmax(0,1fr))}
177
+ .g-2{grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);align-items:start}
178
+ .grid.stretch{align-items:stretch}
179
+
180
+ .panel{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:17px 19px;min-width:0;box-shadow:var(--shadow)}
181
+ .panel > header{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:13px}
182
+ .panel h2{font-size:13.5px}
183
+ .panel > header span{font-family:var(--mono);font-size:11.5px;color:var(--ink-3);white-space:nowrap}
184
+
185
+ /* the verdict, said once, in a sentence */
186
+ .verdict{display:flex;gap:16px;align-items:center}
187
+ .verdict .ring{flex:none}
188
+ .verdict-copy b{display:block;font-size:17px;letter-spacing:-0.02em}
189
+ .verdict-copy p{font-size:13.5px;color:var(--ink-2);margin-top:3px}
190
+ .verdict-copy em{font-style:normal;display:block;font-family:var(--mono);font-size:11.5px;color:var(--ink-3);margin-top:8px}
191
+
192
+ .ring{width:112px;height:112px;border-radius:50%;display:grid;place-items:center;print-color-adjust:exact;-webkit-print-color-adjust:exact}
193
+ .ring-hole{width:82px;height:82px;border-radius:50%;background:var(--panel);display:grid;place-items:center;align-content:center;line-height:1}
194
+ .ring-hole b{font-size:27px;letter-spacing:-0.035em}
195
+ .ring-hole span{font-family:var(--mono);font-size:11px;color:var(--ink-3);margin-top:3px}
196
+
197
+ .stat b{display:block;font-size:27px;line-height:1.08;letter-spacing:-0.03em}
198
+ .stat span{font-size:13.5px;color:var(--ink-2);display:block;margin-top:2px}
199
+ .stat em{font-style:normal;display:block;font-size:12px;color:var(--ink-3);margin-top:5px;line-height:1.45}
200
+ /* 27px counts as large text, so these carry the mark colour rather than the
201
+ darker cut the small labels need. The one exception is the amber figure in
202
+ daylight: #ca8a04 lands at 2.91:1 against the panel, two hundredths short of
203
+ the 3:1 a large figure has to clear, so it is nudged to #c68704. */
204
+ .stat.good b{color:var(--mark-green)}
205
+ .stat.warn b{color:var(--figure-amber)}
206
+ .stat.bad b{color:var(--mark-red)}
207
+
208
+ /* --------------------------------------------------------------- coverage */
209
+ .coverage{display:flex;height:9px;border-radius:5px;overflow:hidden;background:var(--sunk);gap:2px}
210
+ .coverage i{display:block}
211
+ .legend{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:11px;font-size:12.5px;color:var(--ink-2)}
212
+ .legend span{display:flex;align-items:center;gap:7px}
213
+ .legend s{width:9px;height:9px;border-radius:3px;text-decoration:none;display:block;flex:none}
214
+ .dim-key{margin:0;padding:14px 19px 17px;border-top:1px solid var(--line-2)}
215
+ .chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:13px}
216
+ .chips b{font-family:var(--mono);font-size:11.5px;font-weight:400;color:var(--ink-2);background:var(--sunk);border:1px solid var(--line-2);border-radius:5px;padding:3px 7px}
217
+ .chips b.more{border-style:dashed;color:var(--ink-3)}
218
+
219
+ .issues{display:flex;flex-direction:column;gap:11px}
220
+ .issue{display:grid;grid-template-columns:8px minmax(0,1fr) auto;gap:10px;align-items:start}
221
+ .issue s{width:8px;height:8px;border-radius:50%;margin-top:6px;text-decoration:none;display:block}
222
+ .issue b{font-size:13.5px;display:block}
223
+ .issue p{font-size:12.5px;color:var(--ink-2);margin-top:1px}
224
+ .issue i{font-style:normal;font-family:var(--mono);font-size:11.5px;color:var(--ink-3)}
225
+
226
+ /* ------------------------------------------------------------- dimensions */
227
+ /* One table, not a bar chart above a card grid saying the same nine names. */
228
+ .dims{padding:0}
229
+ .dims > header{padding:17px 19px 13px}
230
+ .tools{display:flex;gap:10px;align-items:center}
231
+ .tools button{font-family:var(--mono);font-size:11.5px;color:var(--ink-2);background:none;border:1px solid var(--line);border-radius:6px;padding:4px 9px;cursor:pointer}
232
+ .tools button:hover{background:var(--sunk);color:var(--ink)}
233
+
234
+ details.dim{border-top:1px solid var(--line-2)}
235
+ details.dim > summary{list-style:none;cursor:pointer;display:grid;grid-template-columns:minmax(0,1fr) 116px 42px 16px;gap:14px;align-items:center;padding:12px 19px}
236
+ details.dim > summary::-webkit-details-marker{display:none}
237
+ details.dim > summary:hover{background:var(--sunk)}
238
+ details.dim[open] > summary{background:var(--sunk)}
239
+ details.dim[open]{background:var(--sunk)}
240
+ .dim .name{min-width:0}
241
+ .dim .name b{display:block;font-size:14px}
242
+ .dim .name span{display:block;font-size:12.5px;color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
243
+ .meter{display:flex;gap:3px}
244
+ .meter i{flex:1;height:7px;border-radius:2px;background:var(--line);display:block}
245
+ .dim[data-score="5"] .meter i.on{background:var(--mark-green)}
246
+ .dim[data-score="3"] .meter i.on{background:var(--mark-amber)}
247
+ .dim[data-score="1"] .meter i.on{background:var(--mark-red)}
248
+ .dim .v{font-family:var(--mono);font-size:12px;color:var(--ink-2);text-align:right}
249
+ .dim .caret{width:16px;height:16px;color:var(--ink-3);transition:transform .18s ease}
250
+ details.dim[open] .caret{transform:rotate(90deg)}
251
+ .dim.skipped .v,.dim.skipped .name b{color:var(--ink-3)}
252
+
253
+ .dim-body{padding:18px 19px 22px 19px;display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:34px;border-top:1px solid var(--line-2)}
254
+ .dim-body h3{font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);font-weight:400;margin-bottom:10px}
255
+ .ev{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:8px}
256
+ .ev li{font-size:13.5px;line-height:1.55;color:var(--ink-2);padding-left:16px;position:relative}
257
+ .ev li::before{content:"";position:absolute;left:0;top:9px;width:6px;height:1.5px;background:var(--line);}
258
+ .ladder{display:flex;flex-direction:column;gap:10px}
259
+ .ladder p{font-size:13px;line-height:1.55;color:var(--ink-2);display:flex;gap:10px}
260
+ .ladder p em{font-style:normal;font-family:var(--mono);font-size:11.5px;color:var(--ink-3);flex:none;width:12px}
261
+ .ladder p.now{color:var(--ink)}
262
+ .ladder p.now em{color:var(--ink)}
263
+ .ladder p.next em{color:var(--green)}
264
+ .why{font-size:12.5px;color:var(--ink-3);margin-top:14px;padding-top:12px;border-top:1px solid var(--line-2);line-height:1.55}
265
+
266
+ /* ------------------------------------------------------------------- todo */
267
+ ol.todo{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}
268
+ ol.todo li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:start;padding:10px 14px;border:1px solid var(--line);border-radius:10px;background:var(--panel)}
269
+ ol.todo label{display:grid;grid-template-columns:16px minmax(0,1fr);gap:12px;align-items:center;cursor:pointer;padding:4px 0;min-height:24px}
270
+ ol.todo li.done{opacity:.5}
271
+ ol.todo li.done b{text-decoration:line-through}
272
+ ol.todo input{margin:0;width:16px;height:16px;accent-color:var(--mark-green)}
273
+ ol.todo b{font-size:14px;display:block}
274
+ ol.todo .cmd{display:flex;align-items:center;flex-wrap:wrap;gap:8px 10px;margin-top:2px;padding-left:28px}
275
+ ol.todo code{font-size:12px}
276
+ ol.todo .copy{font-family:var(--mono);font-size:11px;color:var(--ink-3);background:none;border:1px solid var(--line);border-radius:5px;padding:5px 9px;min-height:24px;cursor:pointer}
277
+ ol.todo .copy:hover{color:var(--ink);background:var(--sunk)}
278
+ /* Two facts, not one badge: what the fix is worth, and what it costs. They read
279
+ as a right-hand column down the list, the way a price sits in a table. */
280
+ /* The tag says what the fix does to the dimension it belongs to: the score now,
281
+ and the score after. A hairline, like every other rule in this file — a filled
282
+ pill would be the only one on the page. */
283
+ ol.todo .size{display:flex;flex-direction:column;align-items:flex-end;gap:5px;white-space:nowrap;padding-top:3px}
284
+ ol.todo .lift{display:inline-flex;align-items:center;gap:6px;font-family:var(--mono);font-size:13px;line-height:1;font-variant-numeric:tabular-nums;border:1px solid var(--line);border-radius:7px;padding:5px 9px}
285
+ ol.todo .lift b{font-weight:500}
286
+ ol.todo .lift s{text-decoration:none;color:var(--ink-3);font-size:11px}
287
+ ol.todo .lift i{font-style:normal;font-weight:500;color:var(--green)}
288
+ ol.todo .lift.from1 b{color:var(--red)}
289
+ ol.todo .lift.from3 b{color:var(--amber)}
290
+ ol.todo .size em{font-style:normal;font-size:12px;color:var(--ink-3)}
291
+ ol.todo li.brief .lift{border-style:dashed}
292
+ .todo-note{font-size:13.5px;color:var(--ink-2);margin:-4px 0 14px;max-width:64ch}
293
+ ol.todo .dim-of{font-size:12.5px;color:var(--ink-3)}
294
+ .after{margin-top:13px;font-size:13px;color:var(--ink-2);display:flex;gap:9px;align-items:baseline}
295
+ .after s{text-decoration:none;color:var(--ink-3);font-family:var(--mono);font-size:11.5px}
296
+
297
+ /* ------------------------------------------------------------------ keep */
298
+ /* The markdown is meant to be pasted, so the field carries its own copy control. */
299
+ .snippet{display:flex;align-items:center;gap:10px;background:var(--sunk);border:1px solid var(--line-2);border-radius:8px;padding:7px 7px 7px 12px}
300
+ .snippet code{font-family:var(--mono);font-size:11.5px;line-height:26px;color:var(--ink-2);background:none;border:0;padding:0;flex:1;min-width:0;overflow-x:auto;white-space:pre;scrollbar-width:none}
301
+ .snippet code::-webkit-scrollbar{display:none}
302
+ .snippet .copy{flex:none;display:grid;place-items:center;width:26px;height:26px;color:var(--ink-3);background:var(--panel);border:1px solid var(--line);border-radius:6px;cursor:pointer;padding:0}
303
+ .snippet .copy:hover{color:var(--ink);border-color:var(--ink-3)}
304
+ .snippet .copy svg{width:13px;height:13px;display:block}
305
+ .snippet .copy.ok{color:var(--green);border-color:var(--green)}
306
+ .badge{display:inline-flex;font-family:var(--mono);font-size:11px;line-height:1;border-radius:4px;overflow:hidden;margin-bottom:11px}
307
+ .badge b,.badge i{padding:5px 7px;font-style:normal;font-weight:400;color:#fff}
308
+ .badge b{background:#4c4c4c}
309
+ .badge i{background:var(--badge)}
310
+ .badge-note{font-family:var(--mono);font-size:11px;color:var(--ink-3);margin:-4px 0 11px}
311
+ .trend{display:flex;align-items:flex-end;gap:6px;height:64px;margin-bottom:10px}
312
+ .trend i{flex:1;background:var(--green-tint);border-radius:3px 3px 0 0;display:block;min-height:3px}
313
+ .trend i:last-child{background:var(--mark-green)}
314
+ .runs{font-family:var(--mono);font-size:12px;color:var(--ink-3)}
315
+ .empty{font-size:13px;color:var(--ink-3)}
316
+
317
+ .sig{display:flex;flex-wrap:wrap;gap:10px 20px;justify-content:space-between;font-family:var(--mono);font-size:11.5px;color:var(--ink-3);border-top:1px solid var(--line);padding-top:16px;margin-top:6px}
318
+ .sig .mark{width:18px;color:var(--ink-3)}
319
+ .sig span{display:flex;align-items:center;gap:8px}
320
+
321
+ /* ------------------------------------------------------------ responsive */
322
+ @media (max-width:1080px){
323
+ .g-score,.g-2{grid-template-columns:minmax(0,1fr)}
324
+ .dim-body{grid-template-columns:minmax(0,1fr);gap:16px}
325
+ }
326
+ @media (max-width:820px){
327
+ .app{grid-template-columns:minmax(0,1fr)}
328
+ .rail{position:static;height:auto;border-right:0;border-bottom:1px solid var(--line);flex-direction:row;flex-wrap:wrap;align-items:center;gap:18px}
329
+ .rail nav{flex-direction:row;flex-wrap:wrap;gap:2px 4px}
330
+ .rail nav a{margin:0}
331
+ .stamp{text-align:left}
332
+ .rail .foot{margin:0;width:100%;flex-direction:row;flex-wrap:wrap;gap:14px;align-items:center}
333
+ .g-3{grid-template-columns:minmax(0,1fr)}
334
+ details.dim > summary{grid-template-columns:minmax(0,1fr) 74px 40px 14px;gap:10px}
335
+ }
336
+ @media print{
337
+ .app{display:block;max-width:none}
338
+ .rail{position:static;height:auto;border:0;border-bottom:1px solid var(--line);flex-direction:row;gap:24px;align-items:baseline}
339
+ .rail nav,.theme,.tools,ol.todo input{display:none}
340
+ details.dim{break-inside:avoid}
341
+ details.dim > summary{background:none}
342
+ .panel{break-inside:avoid;box-shadow:none}
343
+ body{font-size:11pt}
344
+ }
345
+ @media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}.caret{transition:none}}
346
+ </style></head>
347
+ <body>
348
+ <div class="app">
349
+
350
+ <aside class="rail">
351
+ <div class="brand">${MARK}<span>Agent readiness</span></div>
352
+ <div class="who">
353
+ <b>${esc(facts.name)}</b>
354
+ <span>${facts.version ? "v" + esc(facts.version) : "unversioned"}</span>
355
+ </div>
356
+ <div>
357
+ <div class="railscore"><b class="num">${scored.total}</b><span>/ ${scored.max}</span>${delta === null ? "" : `<i class="${delta >= 0 ? "up" : "down"}">${delta >= 0 ? "+" : ""}${delta}</i>`}</div>
358
+ <span class="pill ${band}">${verdictTitle}</span>
359
+ </div>
360
+ <nav>
361
+ <a href="#overview">Overview</a>
362
+ <a href="#dimensions">Dimensions <i aria-hidden="true">${live.length}</i><span class="sr">, ${live.length} of them</span></a>
363
+ <a href="#fix">What to fix <i aria-hidden="true">${todo.length}</i><span class="sr">, ${todo.length} item${todo.length === 1 ? "" : "s"}</span></a>
364
+ <a href="#keep">Keep the score</a>
365
+ </nav>
366
+ <div class="foot">
367
+ <button class="theme" type="button" data-theme-toggle aria-pressed="false"><s aria-hidden="true"></s><span>Dark</span></button>
368
+ <span>Generated by <a href="https://github.com/nrmk13/ADSA">adsa</a><br>rubric ${esc(rubricVersion)} · ${date}</span>
369
+ </div>
370
+ </aside>
371
+
372
+ <main>
373
+ <div class="top" id="overview">
374
+ <div>
375
+ <h1>Report for ${esc(facts.name)}</h1>
376
+ <div class="meta">${facts.guides.length} guides<s aria-hidden="true">·</s>${facts.coverage.total} importable components<s aria-hidden="true">·</s>${facts.freshness.blocks} code blocks read</div>
377
+ </div>
378
+ <div class="stamp">${esc(platformLine(facts))}<br>${date}</div>
379
+ </div>
380
+
381
+ <div class="grid g-score">
382
+ <div class="panel verdict">
383
+ ${ring(scored)}
384
+ <div class="verdict-copy">
385
+ <b>${verdictTitle}</b>
386
+ <p>${verdictLine}</p>
387
+ <em>${delta === null ? `first run · ${live.length} dimensions` : `${delta >= 0 ? "+" : ""}${delta} since the last run${gained.length ? ` · ${gained.map((d) => esc(d.title.toLowerCase())).join(", ")}` : ""}`}</em>
388
+ </div>
389
+ </div>
390
+ <div class="grid g-3">
391
+ ${stat(ratioClass(facts.coverage.ratio), String(facts.coverage.documented), "Components documented", `${pct(facts.coverage.ratio)} of ${facts.coverage.total}${facts.icons ? `, ${facts.icons} icon exports set aside` : ""}`)}
392
+ ${stat(facts.freshness.unknownCount ? "bad" : "good", String(facts.freshness.unknownCount), "Imports that do not exist", facts.freshness.unknownCount ? "Named in the guides, missing from the source" : "Every symbol in the guides is real")}
393
+ ${stat(facts.a11y.ratio >= 0.9 ? "good" : facts.a11y.ratio >= 0.3 ? "warn" : "bad", String(facts.a11y.withSection), `Guides with ${A11Y_STAT_LABEL[facts.platform?.primary] || "keyboard"} docs`, `${pct(facts.a11y.ratio)} of ${facts.a11y.total} guides`)}
394
+ </div>
395
+ </div>
396
+
397
+ <div class="grid g-2">
398
+ <section class="panel">
399
+ <header><h2>Documentation coverage</h2><span>${facts.coverage.documented} / ${facts.coverage.total}</span></header>
400
+ <div class="coverage" role="img" aria-label="${facts.coverage.documented} documented, ${undocumented} undocumented">
401
+ <i style="flex:${Math.max(facts.coverage.documented, 0.001)};background:var(--mark-green)"></i><i style="flex:${Math.max(undocumented, 0.001)};background:var(--mark-red)"></i>
402
+ </div>
403
+ <div class="legend">
404
+ <span><s style="background:var(--mark-green)"></s>${facts.coverage.documented} with a guide</span>
405
+ <span><s style="background:var(--mark-red)"></s>${undocumented} without</span>
406
+ </div>
407
+ ${chips(facts.coverage.missing, "Every importable component has a guide.")}
408
+ </section>
409
+
410
+ <section class="panel">
411
+ <header><h2>Look at these first</h2><span>lowest three</span></header>
412
+ <div class="issues">${weakest.map(issueRow).join("")}</div>
413
+ </section>
414
+ </div>
415
+
416
+ <section class="panel dims" id="dimensions">
417
+ <header>
418
+ <h2>The nine dimensions</h2>
419
+ <div class="tools"><span class="num">${scored.total} / ${scored.max}</span><button type="button" data-expand>Open all</button></div>
420
+ </header>
421
+ ${dims.map(dimensionRow).join("")}
422
+ <div class="legend dim-key">
423
+ <span><s style="background:var(--mark-red)"></s>1 — nothing to follow</span>
424
+ <span><s style="background:var(--mark-amber)"></s>3 — partly there</span>
425
+ <span><s style="background:var(--mark-green)"></s>5 — done, and enforced</span>
426
+ </div>
427
+ </section>
428
+
429
+ <section class="panel" id="fix">
430
+ <header><h2>What to do, in order</h2><span>${todo.reduce((n, t) => n + t.points, 0)} points on the table</span></header>
431
+ <p class="todo-note">Each row names the dimension it lifts, the score that dimension holds now, and the score it holds once the fix lands. The ones <code>adsa</code> writes itself come first; the rest ship as a brief for an agent to carry out.</p>
432
+ <ol class="todo">${todoRows(todo)}</ol>
433
+ <p class="after"><s>then</s><span>Run <code>npx adsa-cli audit</code> again. The distance between the two runs is the deliverable.</span></p>
434
+ </section>
435
+
436
+ <section class="grid g-2 stretch" id="keep">
437
+ <div class="panel">
438
+ <header><h2>Keep the score</h2><span>.adsa/score.json</span></header>
439
+ <p style="font-size:13.5px;color:var(--ink-2);margin-bottom:12px">Commit the score, gate it in CI, and put the badge in your README so a drop shows up in a diff.</p>
440
+ <div class="badge" style="--badge:#${badgeColor(scored.total, scored.max)}" role="img" aria-label="Badge preview: agent-ready ${scored.total} of ${scored.max}"><b>agent-ready</b><i>${scored.total}/${scored.max}</i></div>
441
+ <p class="badge-note">how it renders in the README</p>
442
+ ${snippet(badgeMarkdown(scored.total, scored.max))}
443
+ </div>
444
+ <div class="panel">
445
+ <header><h2>History</h2><span>${trend.length} run${trend.length === 1 ? "" : "s"}</span></header>
446
+ ${trendBlock(trend)}
447
+ </div>
448
+ </section>
449
+
450
+ <footer class="sig">
451
+ <span>${MARK}adsa · agentic design system audit</span>
452
+ <span>${SIGNATURE}</span>
453
+ </footer>
454
+ </main>
455
+ </div>
456
+
457
+ <script>
458
+ (function () {
459
+ var root = document.documentElement;
460
+
461
+ // Theme: follows the system unless the reader says otherwise.
462
+ var toggle = document.querySelector("[data-theme-toggle]");
463
+ function paint(dark) {
464
+ root.setAttribute("data-theme", dark ? "dark" : "light");
465
+ toggle.setAttribute("aria-pressed", String(dark));
466
+ toggle.querySelector("span").textContent = dark ? "Light" : "Dark";
467
+ }
468
+ try {
469
+ var saved = localStorage.getItem("adsa-report-theme");
470
+ if (saved) paint(saved === "dark");
471
+ else if (matchMedia("(prefers-color-scheme: dark)").matches) paint(true);
472
+ } catch (e) {}
473
+ toggle.addEventListener("click", function () {
474
+ var dark = root.getAttribute("data-theme") !== "dark";
475
+ paint(dark);
476
+ try { localStorage.setItem("adsa-report-theme", dark ? "dark" : "light"); } catch (e) {}
477
+ });
478
+
479
+ // The rail is a table of contents, so it should say where you are.
480
+ var links = [].slice.call(document.querySelectorAll(".rail nav a"));
481
+ var targets = links.map(function (a) { return document.querySelector(a.getAttribute("href")); }).filter(Boolean);
482
+ if (window.IntersectionObserver && targets.length) {
483
+ var seen = {};
484
+ var io = new IntersectionObserver(function (entries) {
485
+ entries.forEach(function (e) { seen[e.target.id] = e.isIntersecting; });
486
+ var current = targets.filter(function (t) { return seen[t.id]; })[0];
487
+ links.forEach(function (a) {
488
+ if (current && a.getAttribute("href") === "#" + current.id) a.setAttribute("aria-current", "true");
489
+ else a.removeAttribute("aria-current");
490
+ });
491
+ }, { rootMargin: "-20% 0px -70% 0px" });
492
+ targets.forEach(function (t) { io.observe(t); });
493
+ }
494
+
495
+ // Open all / close all, for reading the whole thing or printing it.
496
+ var expand = document.querySelector("[data-expand]");
497
+ expand.addEventListener("click", function () {
498
+ var all = [].slice.call(document.querySelectorAll("details.dim"));
499
+ var open = all.every(function (d) { return d.open; });
500
+ all.forEach(function (d) { d.open = !open; });
501
+ expand.textContent = open ? "Open all" : "Close all";
502
+ });
503
+
504
+ // The to-do list is a working list: what is ticked survives a reload.
505
+ var KEY = "adsa-todo:" + document.title;
506
+ var done = {};
507
+ try { done = JSON.parse(localStorage.getItem(KEY) || "{}") || {}; } catch (e) {}
508
+ document.querySelectorAll("ol.todo li").forEach(function (li) {
509
+ var box = li.querySelector("input");
510
+ if (!box) return;
511
+ if (done[box.value]) { box.checked = true; li.classList.add("done"); }
512
+ box.addEventListener("change", function () {
513
+ li.classList.toggle("done", box.checked);
514
+ done[box.value] = box.checked;
515
+ try { localStorage.setItem(KEY, JSON.stringify(done)); } catch (e) {}
516
+ });
517
+ });
518
+
519
+ // Every command is meant to be run, so it is one click from the clipboard.
520
+ var TICK = ${JSON.stringify(TICK_ICON)};
521
+ document.querySelectorAll(".copy").forEach(function (button) {
522
+ var icon = button.classList.contains("icon");
523
+ var original = button.innerHTML;
524
+ button.addEventListener("click", function () {
525
+ var text = button.getAttribute("data-copy");
526
+ var said = function () {
527
+ if (icon) { button.innerHTML = TICK; button.classList.add("ok"); }
528
+ else button.textContent = "copied";
529
+ };
530
+ var reset = function () { setTimeout(function () { button.innerHTML = original; button.classList.remove("ok"); }, 1400); };
531
+ if (navigator.clipboard) {
532
+ navigator.clipboard.writeText(text).then(function () { said(); reset(); });
533
+ } else {
534
+ var field = document.createElement("textarea");
535
+ field.value = text; document.body.appendChild(field); field.select();
536
+ try { document.execCommand("copy"); said(); reset(); } catch (e) {}
537
+ document.body.removeChild(field);
538
+ }
539
+ });
540
+ });
541
+ })();
542
+ </script>
543
+ </body></html>
544
+ `;
545
+ }
546
+
547
+ /* ------------------------------------------------------------------ pieces */
548
+
549
+ function platformLine(facts) {
550
+ const s = facts.stack || {};
551
+ const primary = facts.platform?.primary;
552
+ const parts = [];
553
+ if (primary && PLATFORM_LABEL[primary] && primary !== "web") parts.push(PLATFORM_LABEL[primary]);
554
+ if (s.react) parts.push("React");
555
+ if (s.vue) parts.push("Vue");
556
+ if (s.svelte) parts.push("Svelte");
557
+ if (s.typescript) parts.push("TypeScript");
558
+ if (s.tailwind) parts.push("Tailwind");
559
+ if (s.storybook) parts.push("Storybook");
560
+ if (s.swift) parts.push("Swift");
561
+ if (s.kotlin) parts.push("Kotlin");
562
+ return parts.join(" · ") || "no framework detected";
563
+ }
564
+
565
+ /** Nine wedges, one per dimension, sized by share and coloured by level. */
566
+ function ring(scored) {
567
+ const dims = scored.dimensions;
568
+ const slice = 100 / dims.length;
569
+ const gap = Math.min(0.8, slice * 0.07);
570
+ const arc = slice - gap;
571
+ const stops = [];
572
+ let at = 0;
573
+ for (const d of dims) {
574
+ const filled = d.skipped ? 0 : (d.score / d.max) * arc;
575
+ const colour = d.skipped ? "var(--line-2)" : d.score === 5 ? "var(--mark-green)" : d.score === 3 ? "var(--mark-amber)" : "var(--mark-red)";
576
+ if (filled > 0) stops.push(`${colour} ${round(at)}% ${round(at + filled)}%`);
577
+ if (filled < arc) stops.push(`var(--line-2) ${round(at + filled)}% ${round(at + arc)}%`);
578
+ stops.push(`var(--panel) ${round(at + arc)}% ${round(at + slice)}%`);
579
+ at += slice;
580
+ }
581
+ return `<div class="ring" role="img" aria-label="${scored.total} out of ${scored.max}" style="background:conic-gradient(from -90deg,${stops.join(",")})"><div class="ring-hole"><b class="num">${scored.total}</b><span>/ ${scored.max}</span></div></div>`;
582
+ }
583
+
584
+ const COPY_ICON = `<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><rect x="5.4" y="2.4" width="8.2" height="9.2" rx="2" stroke="currentColor" stroke-width="1.4"/><path d="M10.6 13.6H4.4a2 2 0 0 1-2-2V5.4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>`;
585
+ const TICK_ICON = `<svg viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M3.5 8.4l3 3 6-6.8" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
586
+
587
+ function snippet(text) {
588
+ return `<div class="snippet"><code>${esc(text)}</code><button class="copy icon" type="button" data-copy="${esc(text)}" aria-label="Copy the badge markdown">${COPY_ICON}</button></div>`;
589
+ }
590
+
591
+ function stat(tone, value, label, note) {
592
+ return `<div class="panel stat ${tone}"><b class="num">${esc(value)}</b><span>${esc(label)}</span><em>${esc(note)}</em></div>`;
593
+ }
594
+
595
+ function chips(missing, emptyLine) {
596
+ if (!missing.length) return `<p class="empty" style="margin-top:12px">${emptyLine}</p>`;
597
+ const shown = missing.slice(0, 12);
598
+ const rest = missing.length - shown.length;
599
+ return `<div class="chips">${shown.map((m) => `<b>${esc(m)}</b>`).join("")}${rest ? `<b class="more">+${rest} more</b>` : ""}</div>`;
600
+ }
601
+
602
+ function issueRow(d) {
603
+ const colour = d.score === 1 ? "var(--mark-red)" : d.score === 3 ? "var(--mark-amber)" : "var(--mark-green)";
604
+ return `<div class="issue"><s style="background:${colour}"></s><div><b>${esc(d.title)}</b><p>${esc(d.evidence[0] || "")}</p></div><i class="num">${d.score}/${d.max}</i></div>`;
605
+ }
606
+
607
+ /** One row per dimension: the number, and the evidence that produced it, in the same place. */
608
+ function dimensionRow(d) {
609
+ const meter = [1, 2, 3, 4, 5].map((n) => `<i class="${!d.skipped && n <= d.score ? "on" : ""}"></i>`).join("");
610
+ const caret = `<svg class="caret" viewBox="0 0 16 16" fill="none" aria-hidden="true"><path d="M6 4l4 4-4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
611
+ const ladder = d.skipped
612
+ ? ""
613
+ : `<div class="ladder">
614
+ <p class="now"><em>${d.score}</em><span>${esc(d.level || "")}</span></p>
615
+ ${d.next ? `<p class="next"><em>${d.score === 1 ? 3 : 5}</em><span>${esc(d.next)}</span></p>` : ""}
616
+ </div>${d.why ? `<p class="why">${esc(d.why)}</p>` : ""}`;
617
+ return `<details class="dim${d.skipped ? " skipped" : ""}" data-score="${d.skipped ? "" : d.score}">
618
+ <summary>
619
+ <div class="name"><b>${esc(d.title)}</b><span>${esc(d.question || "Skipped by configuration.")}</span></div>
620
+ <div class="meter" aria-hidden="true">${meter}</div>
621
+ <div class="v num">${d.skipped ? "—" : `${d.score}/${d.max}`}</div>
622
+ ${caret}
623
+ </summary>
624
+ <div class="dim-body">
625
+ <div><h3>Evidence</h3><ul class="ev">${d.evidence.map((e) => `<li>${esc(e)}</li>`).join("")}</ul></div>
626
+ <div>${ladder ? `<h3>Where it sits</h3>${ladder}` : ""}</div>
627
+ </div>
628
+ </details>`;
629
+ }
630
+
631
+ /** The ordered work list. Exported so the CLI names the same three fixes the report does. */
632
+ export function buildTodo(scored) {
633
+ const seen = new Set();
634
+ const items = [];
635
+ for (const d of scored.dimensions) {
636
+ if (d.skipped) continue;
637
+ for (const fix of d.fixes) {
638
+ if (seen.has(fix)) continue;
639
+ seen.add(fix);
640
+ const [label, kind] = FIX_LABELS[fix] || [fix, "brief"];
641
+ items.push({ fix, label, kind, from: d.score, to: d.max, points: d.max - d.score, dimension: d.title });
642
+ }
643
+ }
644
+ // The command that finishes the job comes before the one that writes a spec,
645
+ // and within each, the fix that moves the score furthest comes first.
646
+ const weight = { apply: 0, brief: 1 };
647
+ return items.sort((a, b) => weight[a.kind] - weight[b.kind] || b.points - a.points);
648
+ }
649
+
650
+ function todoRows(items) {
651
+ if (!items.length) return `<li><div><b>Nothing to fix.</b><p style="font-size:13px;color:var(--ink-2);margin-top:2px">Every dimension is at 5. Keep the gates that hold it there.</p></div><div></div></li>`;
652
+ return items
653
+ .map((item) => {
654
+ const command = `npx adsa-cli fix ${item.fix}`;
655
+ return `<li class="${item.kind}">
656
+ <div>
657
+ <label><input type="checkbox" value="${esc(item.fix)}"><b>${esc(item.label)}</b></label>
658
+ <div class="cmd"><code>${esc(command)}</code><button class="copy" type="button" data-copy="${esc(command)}" aria-label="Copy ${esc(command)}">copy</button><span class="dim-of">${esc(item.dimension)}</span></div>
659
+ </div>
660
+ <span class="size">
661
+ <span class="lift from${item.from}" title="${esc(item.dimension)}: ${item.from} out of ${item.to} now, ${item.to} once this is done"><b>${item.from}</b><s aria-hidden="true">→</s><i>${item.to}</i></span>
662
+ <em>${KIND_WORD[item.kind]}</em>
663
+ </span>
664
+ </li>`;
665
+ })
666
+ .join("");
667
+ }
668
+
669
+ function trendBlock(trend) {
670
+ if (trend.length < 2) {
671
+ return `<p class="empty">One run so far. Commit <code>.adsa/score.json</code> and the next audit draws the distance between them.</p>`;
672
+ }
673
+ const top = Math.max(...trend.map((h) => h.max || 45), 1);
674
+ const bars = trend.map((h) => `<i style="height:${Math.max((h.total / top) * 100, 4)}%" title="${h.total} / ${h.max}"></i>`).join("");
675
+ return `<div class="trend" role="img" aria-label="Score history">${bars}</div><p class="runs">${trend.map((h) => h.total).join(" → ")}</p>`;
676
+ }
677
+
678
+ /* ------------------------------------------------------------------ small */
679
+
680
+ const round = (n) => Math.round(n * 100) / 100;
681
+ const pct = (n) => `${Math.round(n * 100)}%`;
682
+ const ratioClass = (r) => (r >= 0.9 ? "good" : r >= 0.6 ? "warn" : "bad");
683
+
684
+ function esc(value) {
685
+ return String(value ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
686
+ }
687
+
688
+ /** The markdown twin: the same facts, for a pull request or a terminal. */
689
+ export function markdown(facts, scored) {
690
+ const band = BAND(scored.total, scored.max);
691
+ const lines = [
692
+ `# ${facts.name} — agent readiness`,
693
+ "",
694
+ `**${scored.total} / ${scored.max}** · ${VERDICT[band][0]} · rubric ${RUBRIC.version} · ${new Date().toISOString().slice(0, 10)}`,
695
+ "",
696
+ VERDICT[band][1],
697
+ "",
698
+ `| Dimension | Score | Evidence |`,
699
+ `| :-- | :-- | :-- |`,
700
+ ];
701
+ for (const d of scored.dimensions) {
702
+ if (d.skipped) {
703
+ lines.push(`| ${d.title} | skipped | — |`);
704
+ continue;
705
+ }
706
+ lines.push(`| ${d.title} | ${d.score} / 5 | ${d.evidence.join(" ").replace(/\|/g, "\\|")} |`);
707
+ }
708
+ lines.push("", "## What to do, in order", "");
709
+ const items = buildTodo(scored);
710
+ if (!items.length) lines.push("- Nothing outstanding. Re-run after the next release.");
711
+ for (const i of items) {
712
+ lines.push(`- [ ] **${i.label}** — ${i.dimension} ${i.from} → ${i.to}, ${KIND_WORD[i.kind]}. \`npx adsa-cli fix ${i.fix}\``);
713
+ }
714
+ lines.push("", "---", "", "*From one designer to all designers, with love <3*", "");
715
+ return lines.join("\n");
716
+ }