component
STM32F103C8T6 - Arm Cortex-M3 MCU (Blue Pill)
Public Made by Adomby adom
STMicroelectronics mainstream Arm Cortex-M3 MCU. 72 MHz, 64 KB Flash, 20 KB SRAM, USB, CAN, LQFP-48. The workhorse Blue Pill microcontroller.
← Commit history
pinout section: load the registered widget instead of an inlined copy
This file carried its own ~20KB copy of pinout-widget.js, which is why the
corner-overhang fix shipped in adom-widget on Aug 24 never reached the page and
the end pins of every side were still crossing outside the body a week later.
It now loads https://wiki.adom.inc/widget-script/adom-pinout.js, the registry
route, so the next widget fix arrives here without an edit. 24913 bytes down to
1500. Data moved out to render/pinout.json and passed as src.
Kept as a section rather than a README markdown tag on purpose: the tag form
would move the pinout inside the readme block, and the pinout was deliberately
made its own section.
Verified against the registered script: body spans 76..324, pin run 95.5..304.5,
48 lines, no overhang on any side.
1 file changed
+20−567
sections/pinout.html+20−567@@ -4,574 +4,27 @@ :root{color-scheme:dark} body{margin:0;padding:4px 0 10px;background:transparent; font-family:'Satoshi',system-ui,-apple-system,'Segoe UI',sans-serif;color:#e6edf3}-</style></head><body>-<adom-pinout><script type="application/json">{- "mpn": "STM32F103C8T6",- "package": "LQFP-48",- "pins": [- {- "number": 1,- "name": "VBAT",- "type": "power"- },- {- "number": 2,- "name": "PC13-TAMPER-RTC",- "type": "io"- },- {- "number": 3,- "name": "PC14-OSC32_IN",- "type": "io"- },- {- "number": 4,- "name": "PC15-OSC32_OUT",- "type": "io"- },- {- "number": 5,- "name": "PD0-OSC_IN",- "type": "io"- },- {- "number": 6,- "name": "PD1-OSC_OUT",- "type": "io"- },- {- "number": 7,- "name": "NRST",- "type": "io"- },- {- "number": 8,- "name": "VSSA",- "type": "power"- },- {- "number": 9,- "name": "VDDA",- "type": "power"- },- {- "number": 10,- "name": "PA0-WKUP",- "type": "io"- },- {- "number": 11,- "name": "PA1",- "type": "io"- },- {- "number": 12,- "name": "PA2",- "type": "io"- },- {- "number": 13,- "name": "PA3",- "type": "io"- },- {- "number": 14,- "name": "PA4",- "type": "io"- },- {- "number": 15,- "name": "PA5",- "type": "io"- },- {- "number": 16,- "name": "PA6",- "type": "io"- },- {- "number": 17,- "name": "PA7",- "type": "io"- },- {- "number": 18,- "name": "PB0",- "type": "io"- },- {- "number": 19,- "name": "PB1",- "type": "io"- },- {- "number": 20,- "name": "PB2",- "type": "io"- },- {- "number": 21,- "name": "PB10",- "type": "io"- },- {- "number": 22,- "name": "PB11",- "type": "io"- },- {- "number": 23,- "name": "VSS_1",- "type": "power"- },- {- "number": 24,- "name": "VDD_1",- "type": "power"- },- {- "number": 25,- "name": "PB12",- "type": "io"- },- {- "number": 26,- "name": "PB13",- "type": "io"- },- {- "number": 27,- "name": "PB14",- "type": "io"- },- {- "number": 28,- "name": "PB15",- "type": "io"- },- {- "number": 29,- "name": "PA8",- "type": "io"- },- {- "number": 30,- "name": "PA9",- "type": "io"- },- {- "number": 31,- "name": "PA10",- "type": "io"- },- {- "number": 32,- "name": "PA11",- "type": "io"- },- {- "number": 33,- "name": "PA12",- "type": "io"- },- {- "number": 34,- "name": "PA13",- "type": "io"- },- {- "number": 35,- "name": "VSS_2",- "type": "power"- },- {- "number": 36,- "name": "VDD_2",- "type": "power"- },- {- "number": 37,- "name": "PA14",- "type": "io"- },- {- "number": 38,- "name": "PA15",- "type": "io"- },- {- "number": 39,- "name": "PB3",- "type": "io"- },- {- "number": 40,- "name": "PB4",- "type": "io"- },- {- "number": 41,- "name": "PB5",- "type": "io"- },- {- "number": 42,- "name": "PB6",- "type": "io"- },- {- "number": 43,- "name": "PB7",- "type": "io"- },- {- "number": 44,- "name": "BOOT0",- "type": "io"- },- {- "number": 45,- "name": "PB8",- "type": "io"- },- {- "number": 46,- "name": "PB9",- "type": "io"- },- {- "number": 47,- "name": "VSS_3",- "type": "power"- },- {- "number": 48,- "name": "VDD_3",- "type": "power"- }- ]-}</script></adom-pinout>-<script>/*!- * <adom-pinout> — a reusable, dependency-free pinout widget.- *- * Drop-in:- * <script src="pinout-widget.js"><\/script>- * <adom-pinout src="part.json"></adom-pinout>- * <adom-pinout><script type="application/json">{ ...pin data... }<\/script></adom-pinout>- * // or: document.querySelector('adom-pinout').data = { ... }- *- * Data contract (everything except pins[].name is optional):- * {- * "mpn": "STM32F103C8T6", // label shown in the chip body- * "package": "LQFP-48", // label + hints the inferred layout (quad vs dual)- * "pins": [- * { "number": 1, "name": "VBAT", // number+name are the essentials- * "type": "power", // power|io|clock|ctrl|other (auto-inferred from name if omitted)- * "xy": [-2.75, 4.25] } // mm pad centre (footprint frame, +y up) — best placement.- * // omit xy → placed by inferred quad/dual layout.- * ]- * }- *- * Attributes: src (fetch JSON), layout ("auto"|"quad"|"dual"), heading (override title).- * Self-contained (Shadow DOM), themes to the host via prefers-color-scheme.- */-(() => {- const TYPE_COLORS = {- power: '#ff5c5c', io: '#00d3c8', clock: '#4c9dff', ctrl: '#b57cff', control: '#b57cff', other: '#8b97ac',- };- const TYPE_LABEL = { power: 'power', io: 'I/O', clock: 'clock', ctrl: 'control', control: 'control', other: 'other' };-- function inferType(name) {- const n = String(name || '').toUpperCase();- if (n === 'NC' || n === '') return 'other';- if (/^(VDD|VSS|VCC|GND|VBAT|VDDA|VSSA|AVDD|AVSS|VREF|VEE|VBUS|V\+|V-|AGND|DGND)/.test(n)) return 'power';- if (/(OSC|XTAL|CLKIN|CLKOUT|\bCLK\b|SCLK|MCLK)/.test(n)) return 'clock';- if (/^(NRST|~?RESET|RST|BOOT|EN\b|~?CE|~?CS|MODE|TEST|WAKE|SHDN|~?SHDN|PDN)/.test(n)) return 'ctrl';- return 'io';- }-- // Exposed / thermal pad (QFN/DFN/SON/PowerPAD) — belongs in the chip centre, not on an edge.- const isCenterPad = p => {- const n = String(p.name || '').trim().toUpperCase();- return /^(EP|EPAD|PAD|TAB|THERMAL|E[-_]?PAD|EXP|EXPPAD)$/.test(n) || /EXPOSED\s*PAD|THERMAL\s*PAD/.test(n);- };-- // Return each pin as { ...pin, x, y, side } in a S×S box. Center pads get side:'center'.- function layout(pins, pkg, forced) {- const S = 400, pad = 60, mid = S / 2;- const centers = pins.filter(isCenterPad);- const edge = pins.filter(p => !isCenterPad(p));- let laid;- const hasXY = edge.length > 0 && edge.every(p => Array.isArray(p.xy) && p.xy.length === 2);- if (hasXY && forced !== 'quad' && forced !== 'dual') {- const xs = edge.map(p => p.xy[0]), ys = edge.map(p => p.xy[1]);- const minx = Math.min(...xs), maxx = Math.max(...xs), miny = Math.min(...ys), maxy = Math.max(...ys);- const span = Math.max(maxx - minx, maxy - miny) || 1, sc = (S - 2 * pad) / span;- const cx = (maxx + minx) / 2, cy = (maxy + miny) / 2, off = (v, c) => mid + (v - c) * sc;- laid = edge.map(p => {- const x = off(p.xy[0], cx), y = S - off(p.xy[1], cy); // flip y (mm +y up → screen down)- const side = p.xy[1] === maxy ? 'top' : p.xy[1] === miny ? 'bottom' : p.xy[0] === maxx ? 'right' : 'left';- return { ...p, x, y, side };- });- } else {- // The body is drawn inset from the outermost pins, so a run distributed- // across the whole inner..outer span puts the FIRST and LAST pin of each- // side OUTSIDE the body: at 12 per side they land 4.3px past each corner- // and cross the end pin of the neighbouring side (1 into 48, 12 into 13,- // 24 into 25, 36 into 37). Real quad packages have a corner keep-out- // anyway, which is why the explicit-xy path never showed this. Inset the- // run to match the body. Ported from adom-widget 28e23c3.- const N = edge.length, inner = pad, outer = S - pad, at = (a, b, t) => a + (b - a) * t;- const CORNER = 26, runLo = inner + CORNER, runHi = outer - CORNER;- const quad = forced === 'quad' || (forced !== 'dual' && /QF|BGA|LGA|LCC|QFN|QFP/i.test(pkg || ''));- if (quad) {- const per = Math.ceil(N / 4);- laid = edge.map((p, i) => {- const s = Math.floor(i / per), t = ((i % per) + 0.5) / per;- if (s === 0) return { ...p, x: inner, y: at(runLo, runHi, t), side: 'left' };- if (s === 1) return { ...p, x: at(runLo, runHi, t), y: outer, side: 'bottom' };- if (s === 2) return { ...p, x: outer, y: at(runHi, runLo, t), side: 'right' };- return { ...p, x: at(runHi, runLo, t), y: inner, side: 'top' };- });- } else {- const half = Math.ceil(N / 2); // DIP-style: left top→bottom, right bottom→top- laid = edge.map((p, i) => i < half- ? { ...p, x: inner, y: at(runLo, runHi, (i + 0.5) / half), side: 'left' }- : { ...p, x: outer, y: at(runHi, runLo, (i - half + 0.5) / (N - half)), side: 'right' });- }- }- centers.forEach(p => laid.push({ ...p, x: mid, y: mid, side: 'center' }));- return laid;- }-- const esc = s => String(s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));-- class AdomPinout extends HTMLElement {- set data(d) { this._data = d; if (this.shadowRoot) this._render(); }- get data() { return this._data; }-- async connectedCallback() {- if (this._init) return; this._init = true;- this.attachShadow({ mode: 'open' });- this._busInit();- if (!this._data) {- const inline = this.querySelector('script[type="application/json"]');- if (inline) { try { this._data = JSON.parse(inline.textContent); } catch (e) { /* ignore */ } }- const src = this.getAttribute('src');- if (!this._data && src) { try { this._data = await (await fetch(src)).json(); } catch (e) { this._err = 'could not load ' + src; } }- }- this._render();- }-- _render() {- const d = this._data;- const root = this.shadowRoot;- if (this._err || !d || !Array.isArray(d.pins)) {- root.innerHTML = `<style>${this._css()}</style><div class="card"><div class="empty">${esc(this._err || 'no pin data')}</div></div>`;- return;- }- const pins = d.pins.map(p => ({ ...p, type: (p.type || inferType(p.name)).toLowerCase() }));- const laid = layout(pins, d.package, this.getAttribute('layout'));- const typesPresent = [...new Set(pins.map(p => p.type))];- const heading = this.getAttribute('heading') || d.mpn || '';- root.innerHTML = `<style>${this._css()}</style>- <div class="card">- <div class="hd">${heading ? `<span class="mpn">${esc(heading)}</span>` : ''}${d.package ? `<span class="pkg">${esc(d.package)}</span>` : ''}<span class="count">${pins.length} pins</span><span class="tools"><button class="tbtn" data-act="copy" title="Copy the pin map as JSON">copy</button><button class="tbtn" data-act="dl" title="Download the pin map as JSON">JSON ↓</button></span></div>- <div class="body">- <div class="diagram">- <div class="readout" data-readout>hover a pin →</div>- ${this._svg(laid, d)}- <div class="legend">${typesPresent.map(t => `<span><i style="background:${TYPE_COLORS[t] || TYPE_COLORS.other}"></i>${TYPE_LABEL[t] || t}</span>`).join('')}</div>- </div>- <div class="tablewrap"><table>- <thead><tr><th>#</th><th>Name</th><th>Type</th></tr></thead>- <tbody>${pins.slice().sort((a, b) => a.number - b.number).map(p => `- <tr data-pin="${esc(p.number)}"><td class="num">${esc(p.number)}</td><td class="nm">${esc(p.name)}</td>- <td><span class="tp" style="color:${TYPE_COLORS[p.type] || TYPE_COLORS.other}">${TYPE_LABEL[p.type] || p.type}</span></td></tr>`).join('')}</tbody>- </table></div>- </div>- </div>`;- this._wire(pins);- }-- _svg(laid, d) {- const S = 400;- const edge = laid.filter(p => p.side !== 'center');- const cen = laid.filter(p => p.side === 'center');- const xs = edge.map(p => p.x), ys = edge.map(p => p.y);- const pminX = Math.min(...xs), pmaxX = Math.max(...xs), pminY = Math.min(...ys), pmaxY = Math.max(...ys);- const sides = new Set(edge.map(p => p.side)), hasTB = sides.has('top') || sides.has('bottom');- const bL = pminX + 16, bR = pmaxX - 16, bT = hasTB ? pminY + 16 : pminY - 12, bB = hasTB ? pmaxY - 16 : pmaxY + 12;- const cx = (bL + bR) / 2, cyB = (bT + bB) / 2;- let s = `<svg viewBox="0 0 ${S} ${S}" class="chip">`;- s += `<rect x="${bL}" y="${bT}" width="${bR - bL}" height="${bB - bT}" rx="10" class="pkgbody"/>`;- s += `<circle cx="${bL + 15}" cy="${bT + 15}" r="4" class="pin1"/>`;- const lY = cen.length ? bT + 32 : cyB - 3;- s += `<text x="${cx}" y="${lY}" text-anchor="middle" class="lbl">${esc(d.mpn || '')}</text>`;- if (d.package) s += `<text x="${cx}" y="${lY + 13}" text-anchor="middle" class="lbl2">${esc(d.package)}</text>`;- // center pad(s) — exposed/thermal pad, drawn in the body centre- cen.forEach((p, i) => {- const col = TYPE_COLORS[p.type] || TYPE_COLORS.other;- const ex = cx + (i - (cen.length - 1) / 2) * 40, ey = cyB + (cen.length ? 22 : 0);- s += `<g class="pin" data-pin="${esc(p.number)}">`- + `<rect class="ep" x="${ex - 19}" y="${ey - 13}" width="38" height="26" rx="4" fill="${col}" fill-opacity="0.15" stroke="${col}"/>`- + `<text class="ept" x="${ex}" y="${ey + 3}" text-anchor="middle">${esc(p.name)}·${esc(p.number)}</text></g>`;- });- for (const p of edge) {- const col = TYPE_COLORS[p.type] || TYPE_COLORS.other;- let ex = p.x, ey = p.y, lx = p.x, ly = p.y, anc = 'middle';- if (p.side === 'left') { ex = bL; lx = p.x - 11; anc = 'end'; ly = p.y + 3; }- else if (p.side === 'right') { ex = bR; lx = p.x + 11; anc = 'start'; ly = p.y + 3; }- else if (p.side === 'top') { ey = bT; ly = p.y - 9; }- else { ey = bB; ly = p.y + 14; }- s += `<g class="pin" data-pin="${esc(p.number)}">`- + `<line class="stub" x1="${p.x}" y1="${p.y}" x2="${ex}" y2="${ey}" stroke="${col}"/>`- + `<circle class="dot" cx="${p.x}" cy="${p.y}" r="3.1" fill="${col}"/>`- + `<text class="pn" x="${lx}" y="${ly}" text-anchor="${anc}">${esc(p.number)}</text></g>`;- }- return s + `</svg>`;- }-- // Set the highlighted pin. `emit` fires a `pinhover` event (internal hover);- // external callers use highlight() which does NOT emit (avoids feedback loops).- _set(n, emit) {- const root = this.shadowRoot; if (!root) return;- const s = (n == null ? '' : String(n));- root.querySelectorAll('tr[data-pin]').forEach(r => r.classList.toggle('hot', r.dataset.pin === s));- root.querySelectorAll('svg .pin[data-pin]').forEach(g => g.classList.toggle('hot', g.dataset.pin === s));- const p = (this._pinsRendered || []).find(x => String(x.number) === s);- const ro = root.querySelector('[data-readout]');- if (ro) ro.innerHTML = p- ? `<b>PIN ${esc(p.number)}</b> · ${esc(p.name)}<span class="rt" style="color:${TYPE_COLORS[p.type] || TYPE_COLORS.other}">${TYPE_LABEL[p.type] || p.type}</span>`- : 'hover a pin →';- if (emit) { this.dispatchEvent(new CustomEvent('pinhover', { detail: p || null, bubbles: true })); this._emitBus(s); }- }-- /** Public: drive the highlight from outside (e.g. a linked footprint/symbol widget). */- highlight(n) { this._set(n, false); }-- /** Public: the canonical pin map as a plain object — the structured handle for code/AI.- * Types are resolved (inferred filled in) so consumers get a complete map. No fabricated fields. */- toJSON() {- const d = this._data || {};- const pins = (Array.isArray(d.pins) ? d.pins : [])- .map(p => ({ number: p.number, name: p.name, type: (p.type || inferType(p.name)).toLowerCase(), ...(p.xy ? { xy: p.xy } : {}) }))- .sort((a, b) => (Number(a.number) - Number(b.number)) || String(a.number).localeCompare(String(b.number)));- const out = {};- if (d.mpn) out.mpn = d.mpn;- if (d.package) out.package = d.package;- out.pins = pins;- return out;- }-- _exportJson() { return JSON.stringify(this.toJSON(), null, 2); }- _exportName() { const d = this._data || {}; return ((d.mpn || 'pinout') + (d.package ? '-' + d.package : '')).replace(/[^\w.-]+/g, '_') + '.pins.json'; }- _download() {- // data:, not blob:. This renders inside the readme's sandboxed frame,- // whose CSP carries no blob:, so createObjectURL was refused and the- // button did nothing at all without saying so.- const a = document.createElement('a');- a.href = 'data:application/json;charset=utf-8,' + encodeURIComponent(this._exportJson());- a.download = this._exportName(); document.body.appendChild(a); a.click(); a.remove();- }- async _copy(btn) {- const txt = this._exportJson(), done = () => { if (!btn) return; const o = btn.textContent; btn.textContent = 'copied'; btn.classList.add('ok'); setTimeout(() => { btn.textContent = o; btn.classList.remove('ok'); }, 1100); };- try { await navigator.clipboard.writeText(txt); done(); return; } catch (e) { /* sandboxed / denied — fall back */ }- try { const ta = document.createElement('textarea'); ta.value = txt; ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); done(); }- catch (e2) { this._download(); } // last resort: hand them the file- }-- // adom-lbr shared cross-highlight bus: BroadcastChannel('lbr-hover') + parent relay.- _busInit() {- this._self = 'w' + Math.random().toString(36).slice(2);- this._bus = ('BroadcastChannel' in window) ? new BroadcastChannel('lbr-hover') : null;- const on = e => { const d = e.data || {}; if (d.type === 'lbr-hover' && d.src !== this._self) this._set(d.pin == null ? '' : d.pin, false); };- if (this._bus) this._bus.onmessage = on;- this._winmsg = on; window.addEventListener('message', on);- }- disconnectedCallback() { try { this._bus && this._bus.close(); } catch (_) { /*noop*/ } if (this._winmsg) window.removeEventListener('message', this._winmsg); }- _emitBus(n) {- const m = { type: 'lbr-hover', pin: (n === '' || n == null) ? null : String(n), mpn: (this._data && this._data.mpn) || '', src: this._self };- try { this._bus && this._bus.postMessage(m); } catch (_) { /*noop*/ }- try { if (window.parent && window.parent !== window) window.parent.postMessage(m, '*'); } catch (_) { /*noop*/ }- }-- _wire(pins) {- this._pinsRendered = pins;- const root = this.shadowRoot;- const dl = root.querySelector('.tbtn[data-act="dl"]'); if (dl) dl.onclick = () => this._download();- const cp = root.querySelector('.tbtn[data-act="copy"]'); if (cp) cp.onclick = () => this._copy(cp);- root.querySelectorAll('tr[data-pin]').forEach(r => { r.onmouseenter = () => this._set(r.dataset.pin, true); r.onmouseleave = () => this._set('', true); });- root.querySelectorAll('svg .pin[data-pin]').forEach(g => { g.onmouseenter = () => this._set(g.dataset.pin, true); g.onmouseleave = () => this._set('', true); });- // Match the pin table's height to the chip diagram column so it fills the- // space and scrolls, instead of a fixed short box next to a taller chip.- const dia = root.querySelector('.diagram'), tw = root.querySelector('.tablewrap');- if (dia && tw) {- const fit = () => { const h = dia.getBoundingClientRect().height; if (h > 40) tw.style.maxHeight = Math.round(h) + 'px'; };- requestAnimationFrame(fit); setTimeout(fit, 60);- try { if (this._ro) this._ro.disconnect(); this._ro = new ResizeObserver(fit); this._ro.observe(dia); } catch (e) { /* */ }- }- }-- _css() {- return `- :host{display:block;- --bg:#0d1420;--panel:#121a26;--line:#26324a;--line2:#33425f;--text:#dbe2ec;--dim:#8b97ac;--dim2:#66738c;--accent:#00e6dc;- --hot:rgba(0,230,220,.13)}- @media (prefers-color-scheme:light){:host{--bg:#f6f8fc;--panel:#fff;--line:#dbe2ee;--line2:#c3cede;--text:#1a2230;--dim:#5a6678;--dim2:#8592a6;--accent:#0d8f88;--hot:rgba(13,143,136,.10)}}- :host([theme="dark"]){--bg:#0d1420;--panel:#121a26;--line:#26324a;--line2:#33425f;--text:#dbe2ec;--dim:#8b97ac;--dim2:#66738c;--accent:#00e6dc;--hot:rgba(0,230,220,.13)}- :host([theme="light"]){--bg:#f6f8fc;--panel:#fff;--line:#dbe2ee;--line2:#c3cede;--text:#1a2230;--dim:#5a6678;--dim2:#8592a6;--accent:#0d8f88;--hot:rgba(13,143,136,.10)}- *{box-sizing:border-box}- .card{background:var(--bg);border:1px solid var(--line);border-radius:14px;padding:16px;color:var(--text);- font:14px/1.5 -apple-system,'Segoe UI',Roboto,Arial,sans-serif;container-type:inline-size}- .hd{display:flex;align-items:baseline;gap:10px;margin-bottom:12px}- .hd .mpn{font:700 17px ui-monospace,'SF Mono',Menlo,monospace;letter-spacing:-.2px}- .hd .pkg{font:600 11px ui-monospace,monospace;color:var(--accent);background:color-mix(in srgb,var(--accent) 12%,transparent);border:1px solid color-mix(in srgb,var(--accent) 30%,transparent);padding:2px 8px;border-radius:6px}- .hd .count{margin-left:auto;font:12px ui-monospace,monospace;color:var(--dim2)}- .hd .tools{display:flex;gap:6px;align-self:center}- .tbtn{font:600 10.5px ui-monospace,monospace;letter-spacing:.03em;color:var(--dim);background:var(--panel);- border:1px solid var(--line);border-radius:6px;padding:3px 8px;cursor:pointer;transition:color .12s,border-color .12s,background .12s;white-space:nowrap}- .tbtn:hover{color:var(--text);border-color:var(--line2)}- .tbtn.ok{color:var(--accent);border-color:color-mix(in srgb,var(--accent) 45%,transparent)}- .body{display:grid;grid-template-columns:minmax(300px,1fr) minmax(220px,1fr);gap:20px;align-items:start}- /* stack diagram over table when the WIDGET (not the viewport) is narrow —- e.g. side-by-side in a cross-link row — so the table never clips */- @container (max-width:580px){.body{grid-template-columns:1fr}}- .readout{height:24px;display:flex;align-items:center;gap:9px;font:600 13px ui-monospace,monospace;color:var(--dim);padding:0 4px}- .readout b{color:var(--accent)} .readout .rt{margin-left:auto;font-size:11px;text-transform:uppercase;letter-spacing:.08em}- .diagram{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:8px}- svg.chip{width:100%;height:auto;display:block;max-height:440px}- .pkgbody{fill:color-mix(in srgb,var(--text) 6%,var(--panel));stroke:var(--line2);stroke-width:1}- .pin1{fill:var(--accent)}- .lbl{fill:var(--dim);font:600 11px ui-monospace,monospace} .lbl2{fill:var(--dim2);font:9px ui-monospace,monospace}- .pin .stub{stroke-width:2} .pin .pn{fill:var(--dim2);font:8px ui-monospace,monospace}- .pin:hover .stub,.pin.hot .stub{stroke-width:3.6} .pin:hover .pn,.pin.hot .pn{fill:var(--text);font-weight:700}- .pin .ep{stroke-width:1.2} .pin .ept{fill:var(--dim);font:8px ui-monospace,monospace}- .pin:hover .ep,.pin.hot .ep{fill-opacity:.34;stroke-width:2} .pin:hover .ept,.pin.hot .ept{fill:var(--text);font-weight:700}- .pin{cursor:default}- .legend{display:flex;flex-wrap:wrap;gap:13px;margin:8px 2px 2px;font-size:11.5px;color:var(--dim)}- .legend i{display:inline-block;width:9px;height:9px;border-radius:2px;margin-right:5px;vertical-align:middle}- .tablewrap{max-height:440px;overflow:auto;border:1px solid var(--line);border-radius:10px;scrollbar-width:thin;scrollbar-color:var(--line2) transparent}- .tablewrap::-webkit-scrollbar{width:9px;height:9px}- .tablewrap::-webkit-scrollbar-track{background:transparent}- .tablewrap::-webkit-scrollbar-thumb{background:var(--line2);border-radius:9px;border:2px solid transparent;background-clip:padding-box}- .tablewrap::-webkit-scrollbar-thumb:hover{background:var(--dim2);border:2px solid transparent;background-clip:padding-box}- .tablewrap::-webkit-scrollbar-button{display:none;width:0;height:0}- .tablewrap::-webkit-scrollbar-corner{background:transparent}- table{width:100%;border-collapse:collapse;font-size:12.5px}- thead th{position:sticky;top:0;background:var(--panel);text-align:left;color:var(--dim2);font:600 10px ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;padding:7px 9px;border-bottom:1px solid var(--line)}- td{padding:4px 9px;border-bottom:1px solid color-mix(in srgb,var(--line) 55%,transparent);font-family:ui-monospace,monospace}- td.num{color:var(--dim2)} td.nm{color:var(--text)}- tr[data-pin]{cursor:default} tr.hot td{background:var(--hot)}- .tp{font-size:10px;text-transform:uppercase;letter-spacing:.05em}- .empty{color:var(--dim2);padding:30px;text-align:center;font-family:ui-monospace,monospace}`;- }- }- if (!customElements.get('adom-pinout')) customElements.define('adom-pinout', AdomPinout);-})();-</script>+</style>+<!-- The REGISTERED widget, not a copy of it.++ This file used to inline ~20KB of pinout-widget.js. That meant fixes shipped+ in adom-widget never arrived here: the corner-overhang fix (28e23c3,+ 2026-08-24) sat unapplied on this page for a week while the registry served+ the corrected script the whole time. Loading /widget-script/adom-pinout.js+ means the next fix lands without anyone editing this page.++ The frame's CSP is `script-src 'unsafe-inline' https:` and `connect-src+ 'self'`, so both the script and the same-origin data fetch resolve.++ The data src is the /api/pages/.../files/ path, NOT the /render/ shortcut the+ registry README suggests: /adom/stm32f103c8t6/render/pinout.json 404s on a+ component page, while the same shape works on an app page+ (/ray/widget-order-lab/render/pinout-small.json is 200). -->+<script src="https://wiki.adom.inc/widget-script/adom-pinout.js"></script>+</head><body>+<adom-pinout src="/api/pages/adom/stm32f103c8t6/files/render/pinout.json"></adom-pinout> <script> function fit(){parent.postMessage({type:'readme-height',height:document.documentElement.scrollHeight},'*')} addEventListener('load',fit);new ResizeObserver(fit).observe(document.documentElement); </script>-</body></html>+</body></html>