Commit 9d96712a69
Verified · cmc
internal/web/static/style.css +2 −2
| @@ -1225,7 +1225,7 @@ table.difftable td.ln a.cmt:focus { color: var(--link); text-decoration: underli | ||
| 1225 | 1225 | line-height: 1.5; |
| 1226 | 1226 | color: var(--chip); |
| 1227 | 1227 | border: 1px solid color-mix(in srgb, var(--chip) 40%, transparent); |
| 1228 | background: color-mix(in srgb, var(--chip) 10%, transparent); | |
| 1228 | background: color-mix(in srgb, var(--chip) 10%, var(--canvas)); | |
| 1229 | 1229 | white-space: nowrap; |
| 1230 | 1230 | vertical-align: middle; |
| 1231 | 1231 | } |
| @@ -1380,7 +1380,7 @@ details.refmenu .refdrop a.allrefs { | ||
| 1380 | 1380 | .lede { font-size: var(--fs-4); color: var(--muted); line-height: 1.5; max-width: 40rem; margin: 0 0 var(--sp-5); } |
| 1381 | 1381 | pre.quickstart { margin: 0 0 var(--sp-4); } |
| 1382 | 1382 | .shot { border: 1px solid var(--line); border-radius: var(--r-card); overflow: hidden; margin: var(--sp-5) 0; } |
| 1383 | .shot img { display: block; width: 100%; } | |
| 1383 | .shot img { display: block; width: 100%; height: auto; } | |
| 1384 | 1384 | .facets { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-5); margin: var(--sp-5) 0; } |
| 1385 | 1385 | .facets h2 { font-size: var(--fs-3); margin: 0 0 var(--sp-1); } |
| 1386 | 1386 | .facets p { color: var(--muted); font-size: var(--fs-2); margin: 0; } |
internal/web/tokens_test.go +23 −5
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | package web |
| 2 | 2 | |
| 3 | 3 | import ( |
| 4 | "fmt" | |
| 4 | 5 | "math" |
| 5 | 6 | "regexp" |
| 6 | 7 | "strconv" |
| @@ -45,11 +46,8 @@ func parseTokens(css []byte) (light, dark map[string]string) { | ||
| 45 | 46 | } |
| 46 | 47 | light = parse(lm[1]) |
| 47 | 48 | dark = parse(dm[1]) |
| 48 | for k, v := range light { | |
| 49 | if _, ok := dark[k]; !ok && strings.HasPrefix(v, "#") { | |
| 50 | dark[k] = v // a light-only colour is a bug; keep it visible below | |
| 51 | } | |
| 52 | } | |
| 49 | // A token missing from the dark block is a bug, not a fallback to the | |
| 50 | // light value: the missing-token loop in TestTokenContrast reports it. | |
| 53 | 51 | return light, dark |
| 54 | 52 | } |
| 55 | 53 | |
| @@ -78,6 +76,18 @@ func contrastHex(a, b string) float64 { | ||
| 78 | 76 | return (la + 0.05) / (lb + 0.05) |
| 79 | 77 | } |
| 80 | 78 | |
| 79 | // mixHex blends two hex colours per channel, t of a and 1-t of b, matching | |
| 80 | // CSS's color-mix(in srgb, a <t*100>%, b). | |
| 81 | func mixHex(a, b string, t float64) string { | |
| 82 | a, b = strings.TrimPrefix(a, "#"), strings.TrimPrefix(b, "#") | |
| 83 | channel := func(ca, cb string) string { | |
| 84 | na, _ := strconv.ParseUint(ca, 16, 8) | |
| 85 | nb, _ := strconv.ParseUint(cb, 16, 8) | |
| 86 | return fmt.Sprintf("%02x", int(math.Round(t*float64(na)+(1-t)*float64(nb)))) | |
| 87 | } | |
| 88 | return "#" + channel(a[0:2], b[0:2]) + channel(a[2:4], b[2:4]) + channel(a[4:6], b[4:6]) | |
| 89 | } | |
| 90 | ||
| 81 | 91 | // TestTokenContrast is the contract for the colour tokens in style.css: |
| 82 | 92 | // every text colour clears WCAG AA on every ground it lands on, in both |
| 83 | 93 | // schemes. The hex values in the stylesheet are free to move as long as |
| @@ -120,6 +130,14 @@ func TestTokenContrast(t *testing.T) { | ||
| 120 | 130 | t.Errorf("%s: --%s (%s) on --%s (%s) is %.2f:1, want >= %.1f", name, c.fg, scheme[c.fg], c.bg, scheme[c.bg], got, c.floor) |
| 121 | 131 | } |
| 122 | 132 | } |
| 133 | // The chip/badge ground is opaque (color-mix against canvas, not | |
| 134 | // transparent), so its ratio does not depend on the row behind it. | |
| 135 | for _, chip := range []string{"ok", "bad", "warn", "done", "neutral", "link"} { | |
| 136 | ground := mixHex(scheme[chip], scheme["canvas"], 0.1) | |
| 137 | if got := contrastHex(scheme[chip], ground); got < 4.5 { | |
| 138 | t.Errorf("%s: chip --%s (%s) on its ground %s is %.2f:1, want >= 4.5", name, chip, scheme[chip], ground, got) | |
| 139 | } | |
| 140 | } | |
| 123 | 141 | // The surface ladder must be visible: canvas, surface and inset |
| 124 | 142 | // are three grounds, not one. |
| 125 | 143 | lc, ls, _ := luminanceHex(scheme["canvas"]), luminanceHex(scheme["surface"]), luminanceHex(scheme["inset"]) |
internal/web/web_test.go +3 −3
| @@ -84,9 +84,9 @@ func TestWhenNamesTheZone(t *testing.T) { | ||
| 84 | 84 | } |
| 85 | 85 | } |
| 86 | 86 | |
| 87 | // TestMainWidthClass renders every page against an empty struct and | |
| 88 | // checks main carries exactly one width class, and that the pages the | |
| 89 | // spec calls wide or bounded say so. | |
| 87 | // TestMainWidthClass checks each template's source for the width define | |
| 88 | // the spec assigns it (wide or bounded), and that a reading page defines | |
| 89 | // none. | |
| 90 | 90 | func TestMainWidthClass(t *testing.T) { |
| 91 | 91 | wide := map[string]bool{"tree.html": true, "blob.html": true, "blame.html": true, "log.html": true, "commit.html": true, "compare.html": true, "builds.html": true, "build.html": true, "search.html": true, "globalsearch.html": true} |
| 92 | 92 | bounded := map[string]bool{"landing.html": true, "login.html": true, "register.html": true, "registered.html": true, "new.html": true, "issuenew.html": true, "mrnew.html": true, "settings.html": true, "account.html": true, "admin.html": true, "edit.html": true, "snippetnew.html": true, "privacy.html": true, "404.html": true} |