feat(bimi): logo studio, certificate upload and a setup check in the console

The console took pasted SVG text and a URL. It now:

- takes a PNG or SVG and makes the logo BIMI accepts (SVG Tiny PS, square,
  at most 32 KB). An SVG has what the format forbids removed and is told what;
  a PNG is traced into vectors in the browser (imagetracerjs, public domain),
  walking down quality presets until it fits. The person positions it as in the
  photo editor: drag, zoom, background colour, preview in a circle or rounded
  square, and downloads the exact SVG to give a certificate authority
- holds the .pem the authority returns, validated against the logo and domain,
  and serves it at /.well-known/bimi/vmc.pem (routed by the webmail)
- shows the DNS record and runs the setup check (logo, DMARC, record, public
  URLs, certificate)
- a golden-file contract: the SVGs the studio produces are validated by the
  server's own validator (Go) and pinned by the studio's tests (TS)
- found by those tests: an element inside a link kept its onclick when the
  link was unwrapped; links are now unwrapped before cleaning
- the BIMI and photo texts are in Brazilian Portuguese with accents (they had
  been written without), and in Spanish with accents

A VMC cannot be generated from an image: it is issued by a certificate
authority. The README and the panel say so.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-10-16 17:04:35 -03:00
1 parent 109b8360dd
commit dabd1568d1
15 files changed
+1583 -164

No files matched your search

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny-ps" viewBox="0 0 512 512"><title>Example</title><rect width="512" height="512" fill="#ffffff"/><g transform="matrix(2.048 0 0 2.048 51.2 153.6)"><defs><linearGradient id="g"><stop offset="0" stop-color="#ff0000" stop-opacity="1"/><stop offset="1" stop-color="#0000ff"/></linearGradient></defs>
<rect x="10" y="10" width="80" height="80" fill="url(#g)" stroke="none"/>
<circle cx="150" cy="50" r="40" fill="#123456"/></g></svg>

After

Width:  |  Height:  |  Size: 542 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny-ps" viewBox="0 0 512 512"><title>Example brand</title><rect width="512" height="512" fill="#0b5c55"/><g transform="matrix(2.662 0 0 2.662 -51.2 148.48)"><defs><linearGradient id="g"><stop offset="0" stop-color="#ff0000" stop-opacity="1"/><stop offset="1" stop-color="#0000ff"/></linearGradient></defs>
<rect x="10" y="10" width="80" height="80" fill="url(#g)" stroke="none"/>
<circle cx="150" cy="50" r="40" fill="#123456"/></g></svg>

After

Width:  |  Height:  |  Size: 550 B

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" baseProfile="tiny-ps" viewBox="0 0 512 512"><title>Traced</title><rect width="512" height="512" fill="#ffffff"/><g transform="matrix(6.4 0 0 6.4 51.2 51.2)"><path fill="#dc1e1e" d="M 17.5 19 L 27.5 19 L 35 25.5 Q 37.7 29.3 36 37.5 L 29.5 45 Q 25.7 47.7 17.5 46 L 10 39.5 Q 7.3 35.7 9 27.5 L 15.5 20 L 17.5 19 Z "/><path fill="#143cc8" d="M 40 24 L 58 24 L 58 42 L 40 42 L 40 24 Z "/></g></svg>

After

Width:  |  Height:  |  Size: 487 B