← Commit history

readme: packaged applets (pin-1 + datasheet-link), 225 inlined lines down to 16

Ray ·65a96ce0d0 ·2mo ago ·parent e8d55d0
2 files changed +17−225
page.json+4−3
@@ -4,7 +4,7 @@   "slug": "stm32f103c8t6",   "title": "STM32F103C8T6 - Arm Cortex-M3 MCU (Blue Pill)",   "brief": "STMicroelectronics mainstream Arm Cortex-M3 MCU. 72 MHz, 64 KB Flash, 20 KB SRAM, USB, CAN, LQFP-48. The workhorse Blue Pill microcontroller.",-  "version": "0.2.4",+  "version": "0.2.5",   "tags": [     "component",     "microcontroller",@@ -160,9 +160,10 @@     "stm32f103c8t6-board.glb",     "thumbnails/**"   ],-  "changelog": "0.2.4: symbol and footprint viewers above the README (sections array order, git-wiki #280).",+  "changelog": "0.2.5: applets are packaged now (one script tag), datasheet-link vote added, live price and stock from Component Ocean.",   "applet_connect": [-    "https://verify-3i7rt4do98l3.adom.cloud"+    "https://verify-3i7rt4do98l3.adom.cloud",+    "https://component-ocean-jk89eeqplwfl.adom.cloud"   ],   "sections": [     "hero",
readme.html+13−222
@@ -136,228 +136,19 @@ tr[data-pin].hl td,tr[data-pin].hl td.data{color:var(--text)} </div>  <h2 id=pin1>Which pad is pin 1 <span class=s>an applet: your confirmation goes to a real back-end, and the pad is verified once three independent reviewers agree</span></h2>-<style>-.p1wrap{display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:16px;align-items:start;margin:10px 0 6px}-@media(max-width:760px){.p1wrap{grid-template-columns:1fr}}-.p1stage{background:var(--surface);border:1px solid var(--border);border-radius:12px;position:relative;-  padding:14px;display:flex;align-items:center;justify-content:center;min-height:320px}-.p1stage svg{width:100%;max-width:420px;height:auto;display:block}-.p1badge{position:absolute;left:12px;top:10px;font:600 9.5px ui-monospace,monospace;letter-spacing:.12em;-  text-transform:uppercase;color:var(--text-3);border:1px solid var(--border);border-radius:6px;padding:4px 8px}-.p1hint{position:absolute;right:12px;bottom:10px;font-size:10.5px;color:var(--text-3)}-.p1panel{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px;-  display:flex;flex-direction:column;gap:12px}-.p1cons{background:var(--elevated);border:1px solid var(--border);border-radius:10px;padding:12px;-  display:flex;flex-direction:column;gap:9px}-.p1lead{display:flex;justify-content:space-between;align-items:baseline}-.p1lk{font:600 9.5px ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;color:var(--text-3)}-.p1lv{font:600 14px ui-monospace,monospace;color:var(--text)}-.p1dots{display:flex;gap:6px}-.p1dot{height:7px;border-radius:4px;background:#243049;flex:1}-.p1dot.on{background:var(--accent-bright)}-.p1dot.ver{background:var(--green)}-.p1cs{font-size:12px;color:var(--text-2)}-.p1cs.ok{color:var(--green);font-weight:600}-.p1btn{background:var(--accent);color:#04110f;border:none;border-radius:9px;font:600 14px 'Satoshi',system-ui,sans-serif;-  padding:11px;cursor:pointer}-.p1btn:disabled{opacity:.5;cursor:default}-.p1auth{font-size:11.5px;color:var(--text-3);font-family:ui-monospace,monospace;display:flex;gap:6px;align-items:center;flex-wrap:wrap}-.p1auth b{color:var(--text-2)}-.p1auth a{color:var(--accent-bright);cursor:pointer}-.p1in{flex:1;min-width:110px;background:var(--elevated);border:1px solid var(--border);border-radius:8px;color:var(--text);-  font:500 12.5px 'Satoshi',system-ui,sans-serif;padding:8px 10px;outline:none}-.p1in:focus{border-color:var(--accent)}-.p1reason{display:none;flex-direction:column;gap:6px}-.p1reason.show{display:flex}-.p1rl{font:600 9.5px ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;color:var(--text-3)}-.p1rl i{color:var(--accent-bright);font-style:normal}-.p1notes{display:flex;flex-direction:column;gap:6px}-.p1nh{font:600 9.5px ui-monospace,monospace;letter-spacing:.08em;text-transform:uppercase;color:var(--text-3)}-.p1row{display:flex;gap:7px;align-items:baseline;font-size:12px;line-height:1.4}-.p1row .nv{font:600 10.5px ui-monospace,monospace;color:var(--text-3);white-space:nowrap}-.p1row .npad{font:600 10.5px ui-monospace,monospace;color:var(--accent-bright);white-space:nowrap}-.p1row .ntxt{color:var(--text-2)}-.p1row .none{color:var(--text-3);font-style:italic}-.p1row.you .nv{color:var(--accent-bright)}-.p1row.src .nv{color:var(--blue)}-.p1row.src .ntxt{color:var(--text-3)}-.p1foot{font-size:11px;color:var(--text-3);line-height:1.5}-.p1foot code{color:var(--accent-bright);font-family:ui-monospace,monospace}-.p1pad{fill:#59657e;stroke:#8b98af;stroke-width:.6;cursor:pointer}-.p1pad:hover{fill:#7d8aa3}-.p1pad.ver{fill:var(--green);stroke:#04110f}-.p1pad.sel{fill:var(--accent-bright);stroke:#04110f}-.p1num{fill:#8592a8;font:600 8.5px ui-monospace,monospace;pointer-events:none;dominant-baseline:middle}-.p1body{fill:#141a22;stroke:#2f3d57;stroke-width:1.5}-.p1dimple{fill:#0d1117;stroke:#8b98af;stroke-width:1}-</style>-<div class=p1wrap>-  <div class=p1stage>-    <svg id=p1fp viewBox="0 0 560 560" xmlns="http://www.w3.org/2000/svg"></svg>-    <div class=p1badge id=p1badge>confirm or claim pin 1</div>-    <div class=p1hint>manufacturer pin-1 dimple = top-left</div>-  </div>-  <div class=p1panel>-    <div class=p1cons>-      <div class=p1lead><span class=p1lk>current pin 1</span><span class=p1lv id=p1lead>&mdash;</span></div>-      <div class=p1dots id=p1dots><div class=p1dot></div><div class=p1dot></div><div class=p1dot></div></div>-      <div class=p1cs id=p1cs>checking the service&hellip;</div>-    </div>-    <div class=p1reason id=p1reasonwrap>-      <div class=p1rl>Why is this pad pin 1? <i>&middot; required</i></div>-      <input class=p1in id=p1reason type=text maxlength=120 spellcheck=false placeholder="e.g. dimple + bevel are on this corner">-    </div>-    <button class=p1btn id=p1pick disabled>Confirm pin 1</button>-    <div class=p1auth id=p1auth></div>-    <div class=p1notes id=p1notes></div>-    <p class=p1foot>The button calls <code>adom-verify</code>, this page's declared back-end. A vote is tied to a-      signed-in account, so three independent reviewers means three people.</p>-  </div>-</div>-<script>-(function(){-  var MPN='STM32F103C8T6', FACT='pin-1';-  // The starting pin 1 is real provenance, not a seeded vote: the KiCad footprint-  // on this page declares it. Human confirmations start at zero.-  var AUTO_PIN='1', AUTO_SOURCE='footprint', AUTO_BASIS='declared as pin 1 in the KiCad footprint';-  var SVC='https://verify-3i7rt4do98l3.adom.cloud/';   // page.json applet_connect declares this origin-  var $=function(id){return document.getElementById(id);};-  // localStorage throws in the readme sandbox (opaque origin), so keep the session in memory there-  var store=(function(){try{localStorage.setItem('_p1','1');localStorage.removeItem('_p1');return localStorage;}-    catch(e){var m={};return{getItem:function(k){return m[k]||null;},setItem:function(k,v){m[k]=v;},removeItem:function(k){delete m[k];}};}})();-  var TOKEN=store.getItem('verifyToken')||'', ME=null, selected=null, currentPin1=AUTO_PIN, signingIn=false;-  function esc(s){return String(s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];});}-  function hdrs(){return TOKEN?{'Authorization':'Bearer '+TOKEN}:{};}--  // ---- LQFP-48 top view: 12 pads a side, pin 1 top-left, counter-clockwise -----  var NS='http://www.w3.org/2000/svg', N=12, BODY=310, PADL=36, PADW=11, GAP=BODY/N, bl=280-BODY/2;-  var svg=$('p1fp');-  function el(t,a){var e=document.createElementNS(NS,t);for(var k in a)e.setAttribute(k,a[k]);svg.appendChild(e);return e;}-  el('rect',{x:bl,y:bl,width:BODY,height:BODY,rx:10,'class':'p1body'});-  el('circle',{cx:bl+22,cy:bl+22,r:9,'class':'p1dimple'});-  function pad(x,y,w,h,n){var r=el('rect',{x:x,y:y,width:w,height:h,rx:2.5,'class':'p1pad'});r.setAttribute('data-pin',n);return r;}-  function num(x,y,anchor,n){var t=el('text',{x:x,y:y,'text-anchor':anchor,'class':'p1num'});t.textContent=n;}-  var pin=1,i,yc,xc;-  for(i=0;i<N;i++){yc=bl+GAP/2+i*GAP;pad(bl-PADL,yc-PADW/2,PADL,PADW,pin);num(bl-PADL-7,yc,'end',pin);pin++;}-  for(i=0;i<N;i++){xc=bl+GAP/2+i*GAP;pad(xc-PADW/2,bl+BODY,PADW,PADL,pin);num(xc,bl+BODY+PADL+13,'middle',pin);pin++;}-  for(i=0;i<N;i++){yc=bl+BODY-GAP/2-i*GAP;pad(bl+BODY,yc-PADW/2,PADL,PADW,pin);num(bl+BODY+PADL+7,yc,'start',pin);pin++;}-  for(i=0;i<N;i++){xc=bl+BODY-GAP/2-i*GAP;pad(xc-PADW/2,bl-PADL,PADW,PADL,pin);num(xc,bl-PADL-8,'middle',pin);pin++;}--  function targetPin(){return selected||currentPin1;}-  function confirming(){var t=targetPin();return t&&t===currentPin1;}-  function updateBtn(){-    var b=$('p1pick'), t=targetPin();-    if(!ME){$('p1reasonwrap').classList.remove('show');b.disabled=false;b.textContent='Sign in to vote';return;}-    if(!t){b.disabled=true;b.textContent='Pick a pad';return;}-    if(confirming()){$('p1reasonwrap').classList.remove('show');b.disabled=false;b.textContent='Confirm pad '+t+' is pin 1';return;}-    $('p1reasonwrap').classList.add('show');-    if(!$('p1reason').value.trim()){b.disabled=true;b.textContent='Add a reason to claim pad '+t;return;}-    b.disabled=false;b.textContent='Claim pad '+t+' is pin 1';-  }-  svg.addEventListener('click',function(e){-    var p=e.target.closest('.p1pad');if(!p)return;-    [].forEach.call(svg.querySelectorAll('.p1pad.sel'),function(x){x.classList.remove('sel');});-    selected=p.getAttribute('data-pin');-    if(selected!==currentPin1)p.classList.add('sel');       // teal marks a claim on a different pad-    updateBtn();-    if($('p1reasonwrap').classList.contains('show'))$('p1reason').focus();-  });-  $('p1reason').addEventListener('input',updateBtn);--  // ---- identity (no prompt(): modals are blocked in the readme sandbox) -----  function renderAuth(msg){-    var el=$('p1auth');-    if(ME){el.innerHTML='signed in as <b>'+esc(ME.name)+'</b> &middot; <a id=p1out>sign out</a>';-      $('p1out').onclick=function(){TOKEN='';store.removeItem('verifyToken');ME=null;renderAuth();updateBtn();};return;}-    if(signingIn){el.innerHTML='<input class=p1in id=p1handle placeholder="your name or handle" maxlength=40>'-        +'<a id=p1go>sign in</a> <a id=p1cancel style="color:var(--text-3)">cancel</a>';-      var h=$('p1handle');h.focus();-      h.onkeydown=function(e){if(e.key==='Enter')doSignIn();};-      $('p1go').onclick=doSignIn;-      $('p1cancel').onclick=function(){signingIn=false;renderAuth();};return;}-    // signed out: the button already says "Sign in to vote", so this line explains why-    el.innerHTML=msg?'<span style="color:var(--red)">'+esc(msg)+'</span>':'a vote is tied to your account, not this browser';-  }-  function doSignIn(){-    var handle=($('p1handle')&&$('p1handle').value||'').trim();-    if(!handle)return;-    fetch(SVC+'auth/dev',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({handle:handle})})-      .then(function(r){return r.json();})-      .then(function(j){-        if(!j.token)throw new Error(j.error||'sign-in failed');-        TOKEN=j.token;store.setItem('verifyToken',TOKEN);signingIn=false;-        return loadMe().then(function(){renderAuth();updateBtn();load();});-      })-      .catch(function(e){signingIn=false;renderAuth(e.message);});-  }-  function loadMe(){-    if(!TOKEN){ME=null;return Promise.resolve();}-    return fetch(SVC+'me',{headers:hdrs()}).then(function(r){return r.json();})-      .then(function(j){ME=j.authed?j:null;}).catch(function(){ME=null;});-  }--  // ---- consensus -----  function renderNotes(picks){-    var src='<div class="p1row src"><span class=nv>'+esc(AUTO_SOURCE)+'</span><span class=npad>pad '+esc(AUTO_PIN)-      +'</span><span class=ntxt>'+esc(AUTO_BASIS)+'</span></div>';-    var human='';-    if(picks&&picks.length){-      human=picks.slice().sort(function(a,b){return (a.value-b.value)||String(a.name).localeCompare(String(b.name));})-        .map(function(p){-          var you=ME&&p.voter===ME.voter;-          var txt=p.reason?('&ldquo;'+esc(p.reason)+'&rdquo;'):'<span class=none>no reason given</span>';-          return '<div class="p1row'+(you?' you':'')+'"><span class=nv>'+esc(you?'you':p.name)+'</span>'-            +'<span class=npad>pad '+esc(p.value)+'</span><span class=ntxt>'+txt+'</span></div>';-        }).join('');-    }-    $('p1notes').innerHTML='<div class=p1nh>provenance &amp; reviewer picks</div>'+src+human;-  }-  function render(s){-    currentPin1=s.verified_value||s.leader||AUTO_PIN;-    var c=(s.tally&&s.tally[currentPin1])||0;-    $('p1lead').textContent='pad '+currentPin1;-    var dots=$('p1dots').children;-    for(var i=0;i<3;i++)dots[i].className='p1dot'+(i<c?(s.verified?' ver':' on'):'');-    [].forEach.call(svg.querySelectorAll('.p1pad.ver'),function(x){x.classList.remove('ver');});-    var vp=svg.querySelector('.p1pad[data-pin="'+currentPin1+'"]');if(vp)vp.classList.add('ver');-    if(s.verified){-      $('p1cs').textContent='verified ✓ pin 1 = pad '+s.verified_value+' ('+c+'/'+s.needed+' agree)';-      $('p1cs').classList.add('ok');$('p1badge').textContent='pin 1 verified';-    }else{-      $('p1cs').classList.remove('ok');$('p1badge').textContent='confirm or claim pin 1';-      $('p1cs').textContent=c>0-        ? 'pad '+currentPin1+' · '+c+' of '+s.needed+' reviewer confirmations · '+(s.needed-c)+' more needed'-        : 'from the '+AUTO_SOURCE+' · 0 of '+s.needed+' reviewer confirmations';-    }-    renderNotes(s.picks);updateBtn();-  }-  function load(){-    return fetch(SVC+'status?page='+encodeURIComponent(MPN)+'&fact='+FACT)-      .then(function(r){return r.json();}).then(render)-      .catch(function(){$('p1cs').textContent='verification service unreachable';});-  }-  $('p1pick').addEventListener('click',function(){-    if(!ME){signingIn=true;renderAuth();return;}-    var t=targetPin();if(!t)return;-    var conf=confirming(), note=$('p1reason').value.trim();-    if(!conf&&!note)return;-    $('p1pick').disabled=true;$('p1pick').textContent='Submitting…';-    fetch(SVC+'vote?page='+encodeURIComponent(MPN)+'&fact='+FACT+'&value='+t+'&reason='+encodeURIComponent(conf?'':note),-          {method:'POST',headers:hdrs()})-      .then(function(r){-        if(r.status===401){TOKEN='';store.removeItem('verifyToken');ME=null;renderAuth('session expired');updateBtn();return null;}-        return r.json();})-      .then(function(s){-        if(!s)return;-        selected=null;$('p1reason').value='';-        [].forEach.call(svg.querySelectorAll('.p1pad.sel'),function(x){x.classList.remove('sel');});-        render(s);})-      .catch(function(e){$('p1cs').textContent='vote failed: '+e.message;updateBtn();});-  });-  svg.querySelector('.p1pad[data-pin="'+AUTO_PIN+'"]').classList.add('ver');-  loadMe().then(function(){renderAuth();updateBtn();return load();});-})();-</script>+<script src="https://verify-3i7rt4do98l3.adom.cloud/applet.js"></script>+<adom-verify+  page="STM32F103C8T6" fact="pin-1"+  pins="48" package="LQFP-48"+  source="footprint" source-value="1"+  source-basis="declared as pin 1 in the KiCad footprint"></adom-verify>++<h2 id=datasheet-vote>Is this the right datasheet <span class=s>the same back-end, a different fact: confirm the manufacturer document for this exact part, or propose a better one</span></h2>+<adom-verify+  page="STM32F103C8T6" fact="datasheet-link"+  source="page" source-value="https://www.st.com/resource/en/datasheet/stm32f103cb.pdf"+  source-basis="the datasheet declared in page.json"+  values='["https://www.st.com/resource/en/datasheet/stm32f103cb.pdf","https://www.st.com/resource/en/reference_manual/rm0008-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf"]'></adom-verify>  <h2>Datasheet key pages <span class=s>the pages worth looking at first &middot; pick a page, click it to go full screen</span></h2> <div class=dsviewer>