From deefc282b4bc4a8ad2c045f5b6daa8b77f9ebfda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=89=E5=AF=92?= Date: Mon, 15 Jun 2026 00:16:37 +0800 Subject: [PATCH] Add 6 dynamic components: rotary knob, dot loader, OTP, live counter, activity stream, copy field --- README.md | 2 +- css/nothing-ui.css | 34 +++++++++++++++++++++++++++++++++ index.html | 40 +++++++++++++++++++++++++++++++++++++++ js/nothing-ui.js | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f35267f..e45db92 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ No package manager, no compiler — copy the files into any static site, or take ## Components -Foundations, forms, navigation, feedback, data display, and an applied dashboard — 40+ components, all in two themes. +Foundations, forms, navigation, feedback, data display, dynamic controls, and an applied app screen — 46 components, all in two themes. ![Foundations — color, dot-matrix type, iconography](docs/components.png) diff --git a/css/nothing-ui.css b/css/nothing-ui.css index 6d51e9a..6af9a50 100644 --- a/css/nothing-ui.css +++ b/css/nothing-ui.css @@ -459,4 +459,38 @@ td{font-family:var(--f-mono);font-size:11px;padding:9px 8px;border-bottom:1px so @keyframes deployscan{0%{width:10%}85%{width:90%}100%{width:90%}} .pill.sig{background:var(--accent);color:var(--accent-ink)} @media(max-width:720px){.appscreen{flex-direction:column}.appscreen .rail{flex-direction:row;width:auto;border-right:0;border-bottom:1px solid var(--line)}.appscreen .rail .sp{display:none}} + +/* ===== additional dynamic components (41–46) — net-new types, Nothing-flavored ===== */ +@keyframes llin{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}} +/* 41 rotary knob (drag) — Teenage-Engineering / hardware feel */ +.knobwrap{text-align:center} +.knob{width:78px;height:78px;border-radius:50%;border:1px solid var(--line-2);background:var(--surface);position:relative;margin:0 auto;cursor:grab;touch-action:none} +.knob:active{cursor:grabbing} +.knob::after{content:"";position:absolute;inset:11px;border-radius:50%;border:1px solid var(--line)} +.knob .ind{position:absolute;left:50%;top:50%;width:2px;height:30px;background:var(--display);transform-origin:50% 100%;transform:translate(-50%,-100%) rotate(var(--a,-135deg))} +.knobval{font-family:var(--f-display);font-weight:500;font-size:22px;color:var(--display);margin-top:14px} +/* 42 dot-matrix spinner */ +.dspin{position:relative;width:50px;height:50px;margin:14px auto} +.dspin i{position:absolute;left:50%;top:50%;width:6px;height:6px;margin:-3px;border-radius:50%;background:var(--display);opacity:.15;animation:dfade 1s linear infinite} +@keyframes dfade{0%{opacity:1}70%,100%{opacity:.15}} +/* 43 verification (OTP) input */ +.otp{display:flex;gap:8px} +.otp input{width:40px;height:50px;text-align:center;background:transparent;border:1px solid var(--line-2);border-radius:var(--r-sm);color:var(--display);font-family:var(--f-display);font-weight:500;font-size:22px;outline:0;transition:border-color .2s var(--ease)} +.otp input:focus{border-color:var(--primary)} +.otp.done input{border-color:var(--success);color:var(--success)} +/* 44 live counter (count-up) */ +.ticker .tval{font-family:var(--f-display);font-weight:500;font-size:40px;color:var(--display);line-height:1} +.ticker .tdelta{font-family:var(--f-mono);font-size:11px;margin-top:8px;letter-spacing:.04em} +.ticker .tdelta.up{color:var(--success)} .ticker .tdelta.dn{color:var(--error)} +/* 45 activity stream */ +.stream{display:flex;flex-direction:column;gap:7px;height:150px;overflow:hidden;font-family:var(--f-mono);font-size:10px} +.stream .s-l{display:flex;gap:8px;color:var(--secondary);animation:llin .3s var(--ease)} +.stream .s-l .t{color:var(--muted);flex-shrink:0} +.stream .s-l.sig{color:var(--accent-text)} +/* 46 copy field */ +.copyfield{display:flex;align-items:center;gap:8px;border:1px solid var(--line-2);border-radius:var(--r-sm);padding:7px 7px 7px 12px} +.copyfield code{flex:1;min-width:0;font-family:var(--f-mono);font-size:11px;color:var(--primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.copyfield .cbtn{flex-shrink:0;font-family:var(--f-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;border:1px solid var(--line-2);border-radius:5px;padding:6px 10px;background:transparent;color:var(--secondary);cursor:pointer;transition:opacity .2s var(--ease)} +.copyfield .cbtn:hover{opacity:.7} +.copyfield .cbtn.ok{background:var(--display);color:var(--bg);border-color:var(--display)} footer{padding:28px 24px;display:flex;justify-content:space-between;color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:.08em;border-top:1px solid var(--line)} diff --git a/index.html b/index.html index f42d58d..759f7d6 100644 --- a/index.html +++ b/index.html @@ -468,6 +468,46 @@
+ +
+
41Rotary Knobdrag
+
50
GAIN
+
+ + +
+
42Dot Loader
+
+
LOADING
+
+ + +
+
43Verification Code
+
+
ENTER 4-DIGIT CODE
+
+ + +
+
44Live Counter
+
0
+
↑ 12% THIS WEEK
+
+ + +
+
45Activity Streamlive
+
+
+ + +
+
46Copy Field
+
git clone github.com/wangbh030722/vibe-nothing-ui-design
+
CLICK TO COPY · CONFIRMS INLINE
+
+ diff --git a/js/nothing-ui.js b/js/nothing-ui.js index 60a0d47..5f7b305 100644 --- a/js/nothing-ui.js +++ b/js/nothing-ui.js @@ -127,3 +127,50 @@ const t_dark=document.getElementById('t-dark'),t_light=document.getElementById(' /* Applied · Workspace screen: the deploying-row progress + live dot are pure CSS; rail nav / tabs / pagination are handled by the delegated click handler above. */ + + /* ===== additional dynamic components (41–46) ===== */ + // 41 rotary knob — vertical drag, value 0–100, indicator rotates -135°…135° + const knob=document.getElementById('knob'),knobval=document.getElementById('knobval'); + if(knob&&knobval){let v=50,drag=false,sy=0,sv=50; + const set=x=>{v=Math.max(0,Math.min(100,x));knob.style.setProperty('--a',(-135+v/100*270)+'deg');knobval.textContent=Math.round(v);}; + set(50); + knob.addEventListener('pointerdown',e=>{drag=true;sy=e.clientY;sv=v;knob.setPointerCapture(e.pointerId);}); + knob.addEventListener('pointermove',e=>{if(drag)set(sv+(sy-e.clientY)*0.7);}); + const stop=()=>drag=false;knob.addEventListener('pointerup',stop);knob.addEventListener('pointercancel',stop);} + + // 42 dot-matrix spinner — 8 dots on a ring, staggered fade + const dspin=document.getElementById('dspin'); + if(dspin){for(let i=0;i<8;i++){const d=document.createElement('i'); + d.style.transform='rotate('+(i*45)+'deg) translateY(-19px)';d.style.animationDelay=(i/8)+'s';dspin.appendChild(d);}} + + // 43 verification (OTP) input — auto-advance, backspace-back, success when full + const otp=document.getElementById('otp'); + if(otp){const ins=[...otp.querySelectorAll('input')]; + ins.forEach((inp,i)=>{ + inp.addEventListener('input',()=>{inp.value=inp.value.replace(/\D/g,'').slice(0,1); + if(inp.value&&ix.value));}); + inp.addEventListener('keydown',e=>{if(e.key==='Backspace'&&!inp.value&&i>0)ins[i-1].focus();}); + });} + + // 44 live counter — count up with easing when scrolled into view + document.querySelectorAll('.ticker .tval[data-to]').forEach(el=>{const to=+el.getAttribute('data-to'); + const io=new IntersectionObserver(es=>es.forEach(en=>{if(en.isIntersecting){let s=null;const D=1200; + const step=t=>{if(!s)s=t;const k=Math.min(1,(t-s)/D);const e=k<.5?4*k*k*k:1-Math.pow(-2*k+2,3)/2; + el.textContent=Math.round(to*e);if(k<1)requestAnimationFrame(step);else el.textContent=to;}; + requestAnimationFrame(step);io.disconnect();}}),{threshold:.6});io.observe(el);}); + + // 45 activity stream — mono log lines appended live, newest on top + const stream=document.getElementById('stream'); + if(stream){const L=[['user.auth · session opened',0],['build #482 · passed',0],['agent-core · 3 files changed',0],['rate limit · 12% used',0],['deploy · review required',1],['cache · purged',0],['webhook · delivered',0],['db · migration applied',0]]; + let i=0;const pad=n=>String(n).padStart(2,'0'); + const push=()=>{const d=new Date();const t=pad(d.getHours())+':'+pad(d.getMinutes())+':'+pad(d.getSeconds());const[m,sig]=L[i++%L.length]; + const row=document.createElement('div');row.className='s-l'+(sig?' sig':'');row.innerHTML=''+t+''+m; + stream.prepend(row);while(stream.children.length>7)stream.removeChild(stream.lastChild);}; + for(let k=0;k<6;k++)push();setInterval(push,1900);} + + // 46 copy field — click → "Copied" inline confirm + document.querySelectorAll('.cbtn[data-copy]').forEach(b=>b.addEventListener('click',()=>{ + const t=b.getAttribute('data-copy');try{navigator.clipboard&&navigator.clipboard.writeText(t);}catch(_){} + const o=b.textContent;b.textContent='Copied';b.classList.add('ok'); + setTimeout(()=>{b.textContent=o;b.classList.remove('ok');},1200);}));