oil-ui-pro 0.15.4 → 0.22.2(上游 ui.oiloil.org 正式包)+ 记 P0-98 跨盘 rename 坑

- oil-ui-pro 升到 0.22.2:references 13→28 份(新增 16、删 imagery.md)、assets 增 cards/(22 组)与 fonts/、SKILL.md 顶层五步结构未改名。
- 升级路径:官方 `npx github:oil-oil/oil-cli update` 稳定失败(error=replace、报「检查目录权限」)。
  真身为 EXDEV —— 解压在 os.tmpdir()(C 盘)、技能目录在 E 盘,Windows 上 rename 不能跨盘;
  CLI 的 catch 把非 CliError 原生异常一律兜底成 replaceFailed,真错误一个字不吐。
  做法=借其 prepare() 只做下载+校验,再 cp 到同盘 stage、rename 交换;官方命令复验 up_to_date。
- 回归:product-planning 按名引用的 visual-review.md/tools.md/design-direction.md/style-explorer.md 全部健在;命名闸门 2061|0|27。
- session-mechanism/references/pitfalls.md 新增 P0-98(含通则:更新器把原生错误兜底成指错方向的人话)。
This commit is contained in:
admin committed 2026-10-14 10:21:46 +08:00
1 parent 053a6a6723
commit d9445b5888
126 files changed
+10345 -1401

No files matched your search

@@ -0,0 +1,23 @@
.flow-object {
& .card-stage { overflow: visible; perspective: 1100px; }
& .flow-aura { position:absolute; inset:0 -10% -10%; background:radial-gradient(ellipse at 70% 66% in oklab,color-mix(in oklab,var(--card-accent) 17%,transparent),transparent 64%),radial-gradient(ellipse at 24% 82% in oklab,color-mix(in oklab,var(--_card-accent-3) 14%,transparent),transparent 59%); }
& .physical-card { position: absolute; top:21%; left:17.5%; width:65%; aspect-ratio: 685/1078; overflow: hidden; overflow:clip; border-radius: calc(var(--_card-radius) * 1.9); background: linear-gradient(150deg in oklab,var(--_card-accent-2),var(--card-accent),var(--_card-accent-3)); box-shadow: 0 5px 12px -4px #35164536,0 26px 54px -17px color-mix(in oklab,var(--_card-accent-2) 35%,transparent),inset 0 0 0 1px #ffffff50; transform-style: preserve-3d; }
& .flow-surface { position: absolute; inset: 0; }
& .card-edition { position: absolute; top: 7%; left: 10%; font: 9px ui-monospace, monospace; letter-spacing: .18em; color: #fff9; }
& .card-white { position: absolute; inset: 0; width: 100%; height: 100%; }
& .physical-wordmark { position: absolute; right: 10%; bottom: 6%; font-size: 31px; color: #34314c; font-weight: 650; letter-spacing: -.08em; & span { font-size: 9px; position: absolute; right: -8px; top: 5px; letter-spacing: 0; } }
& .physical-number { position: absolute; bottom: 8%; left: 10%; font: 9px ui-monospace, monospace; color: #97929d; letter-spacing: .14em; }
& .token-engraving { position:absolute; inset:17% 10% 27%; color:#fff; text-shadow:0 1px 1px #17214460,0 -1px 0 #ffffff30; font:9px ui-monospace,monospace; }
& .token-status { display:flex; align-items:center; gap:7px; font-size:8px; letter-spacing:.16em; }
& .token-led { flex:none; width:5px; height:5px; border-radius:50%; background:#d4ffec; box-shadow:0 0 3px 1px #e4fffccc,0 0 13px #b3ffdf70; opacity:var(--token-pulse,.8); }
& .token-hash-label { display:block; margin-top:20%; font-size:7px; opacity:.7; letter-spacing:.24em; }
& .token-hash { display:block; margin-top:7px; font-size:clamp(13px,1.6vw,20px); font-weight:500; letter-spacing:.08em; overflow-wrap:anywhere; }
& .token-specs { display:grid; gap:9px; margin-top:18%; padding-top:12px; border-top:1px solid #ffffff38; }
& .token-specs > div { display:flex; flex-wrap:wrap; justify-content:space-between; gap:4px; }
& .token-specs span { opacity:.66; font-size:7px; letter-spacing:.12em; }
& .token-specs b { font-weight:400; font-size:8px; letter-spacing:.04em; }
& .token-registration { display:flex; gap:4px; margin-top:14%; height:13px; opacity:.45; }
& .token-registration i { width:1px; height:100%; background:currentColor; }
& .token-registration i:nth-child(3n) { height:55%; }
&[data-variant="card"] .token-engraving { display:none; }
}
@@ -0,0 +1,21 @@
<article class="card portrait flow-object" data-card="flow-object" data-motion="auto">
<h2 class="card-heading" data-text="title">让数字身份,<br>拥有可以触碰的形态</h2>
<div class="card-stage">
<div class="flow-aura" aria-hidden="true"></div>
<div class="physical-card">
<canvas class="flow-surface" aria-hidden="true"></canvas>
<span class="card-edition" data-text="edition">IDENTITY / 01</span>
<div class="token-engraving">
<div class="token-status"><i class="token-led" aria-hidden="true"></i><span data-text="status">VERIFIED</span></div>
<span class="token-hash-label" data-text="hashLabel">SIGNATURE</span>
<strong class="token-hash" data-text="hash">7F2A:9C04</strong>
<div class="token-specs"><div><span data-text="specs.0.label">PROTOCOL</span><b data-text="specs.0.value">OPEN / 01</b></div><div><span data-text="specs.1.label">SCOPE</span><b data-text="specs.1.value">WORKSPACE</b></div><div><span data-text="specs.2.label">REVISION</span><b data-text="specs.2.value">2026.10</b></div></div>
<div class="token-registration" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
</div>
<svg class="card-white" viewBox="0 0 300 470" preserveAspectRatio="none" aria-hidden="true"><path d="M0 344Q160 386 300 324V470H0Z" fill="white"/></svg>
<span class="physical-wordmark" data-text="brand">identity</span>
<span class="physical-number" data-text="identifier">ID / 0842</span>
</div>
</div>
</article>
@@ -0,0 +1,27 @@
import {loop, watchColors, shell, mix, clamp, staticMotion} from '../shared/runtime.js';
import {coatedSurface} from './material.js';
export function mount(element, options = {}) {
const variant=options.variant ?? element.dataset.variant ?? 'token';
const cleanShell=shell(element,{...options,variant:variant==='card'?'card':'token'});
const surface=coatedSurface(element.querySelector('.flow-surface'));
const card=element.querySelector('.physical-card');
let palette,x=0,y=0,tx=0,ty=0;
const still=staticMotion(element,options);
const draw=(time,dt)=>{
if(still())x=y=tx=ty=0;
if(dt || still()){
const ease=1-Math.exp(-dt*7);x=mix(x,tx,ease);y=mix(y,ty,ease);
card.style.transform=`rotateX(${-y*5}deg) rotateY(${x*7}deg)`;
}
surface.draw(time,palette,[x,y]);
element.style.setProperty('--token-pulse',still()?.8:.65+.35*(.5+.5*Math.sin(time*2.2)));
};
const clock=loop(element,draw,{...options,settled:()=>Math.abs(x-tx)+Math.abs(y-ty)<.001});
const off=watchColors(element,p=>{palette=p;clock.invalidate();});
const ro=new ResizeObserver(()=>{surface.resize();clock.invalidate();});ro.observe(card);
const move=e=>{if(e.pointerType==='touch' || still())return;const r=element.getBoundingClientRect();tx=clamp((e.clientX-r.left)/r.width*2-1,-1,1);ty=clamp((e.clientY-r.top)/r.height*2-1,-1,1);};
const leave=()=>{tx=ty=0;};
element.addEventListener('pointermove',move);element.addEventListener('pointerleave',leave);element.addEventListener('pointercancel',leave);
return {destroy(){clock.destroy();off();ro.disconnect();surface.destroy();cleanShell();element.removeEventListener('pointermove',move);element.removeEventListener('pointerleave',leave);element.removeEventListener('pointercancel',leave);card.style.removeProperty('transform');element.style.removeProperty('--token-pulse');}};
}
@@ -0,0 +1,77 @@
import {glProgram, glNoise, glColor} from '../shared/runtime.js';
// One coated surface, not a stack of ribbons. Paint stays still; light travels.
const vertex = `attribute vec2 a_position;varying vec2 v_uv;
void main(){v_uv=a_position*.5+.5;gl_Position=vec4(a_position,0.,1.);}`;
const fragment = `precision highp float;
varying vec2 v_uv;uniform vec2 u_resolution,u_pointer;
uniform float u_time;uniform vec3 u_a,u_b,u_c;
${glNoise}
${glColor}
float bell(float d,float w){return exp(-d*d/(w*w));}
void main(){
// Fit the field to the short side: a wider host reveals more of the material.
vec2 uv=vec2(v_uv.x,1.-v_uv.y);
vec2 p=(uv-.5)*u_resolution/min(u_resolution.x,u_resolution.y);
float grain=hash(gl_FragCoord.xy)-.5;
float dye=p.y+.42*p.x+.07*sin(p.x*2.1);
vec3 color=mix(lab(u_b),lab(u_a),smoothstep(-.48,.30,dye));
// Warm pigment is a broad off-centre pool, not another repeated band.
float warm=smoothstep(-.01,.43,p.y+.18*p.x-.12*sin(p.x*2.));
color=mix(color,lab(u_c),warm);
color=mix(color,lab(vec3(1.)),.18+.09*bell(dye+.52,.40));
float drift=.055*sin(u_time*.32)+u_pointer.x*.065-u_pointer.y*.035;
float d=p.y+.43*p.x-.065*p.x*p.x+.10-drift;
// One narrow glint nested inside an asymmetric, much wider bloom.
float width=max(1.6/min(u_resolution.x,u_resolution.y),.005);
float glint=bell(d,width*1.5);
float shoulder=bell(d-.012,.035)*.20;
float bloom=bell(d-.055,.14)*.29;
float envelope=.66+.34*bell(p.x-.10-.22*sin(u_time*.19),.85);
float brushed=simplex(vec2(p.x*.9,d*220.));
float echo=bell(d+.038+.008*sin(p.x*4.),.014)*.07;
float grazing=(glint*.29+shoulder+bloom+echo)*envelope;
color=mix(color,lab(vec3(1.)),clamp(grazing,0.,.78));
// A broad reflected softbox moves independently of the pigment boundary.
float softbox=bell(p.x*.70-p.y*.22+.27*sin(u_time*.25)+u_pointer.x*.14,.52);
color.x+=softbox*.035;
color.x+=brushed*.009*bell(d,.14);
vec3 outcolor=srgb(color)+grain*.020;
gl_FragColor=vec4(outcolor,1.);
}`;
export function coatedSurface(canvas) {
let gpu, uniforms, lastFrame=null, disposed=false;
// Use the untransformed plane, so tilting the host cannot stretch its grain.
const resize=()=>{
const dpr=Math.min(devicePixelRatio || 1,2.5);
const w=Math.max(1,Math.round(canvas.clientWidth*dpr)),h=Math.max(1,Math.round(canvas.clientHeight*dpr));
if(canvas.width!==w || canvas.height!==h){canvas.width=w;canvas.height=h;}
};
const initialize=()=>{
gpu=glProgram(canvas,vertex,fragment);
canvas.dataset.renderer=gpu?'webgl':'css';
if(!gpu)return;
gpu.attribute('a_position',[-1,-1,1,-1,-1,1,-1,1,1,-1,1,1],2);
uniforms=Object.fromEntries(['resolution','pointer','time','a','b','c'].map(n=>[n,gpu.uniform('u_'+n)]));
resize();
};
const lost=e=>{if(disposed)return;e.preventDefault();gpu=null;canvas.dataset.renderer='css';};
const restored=()=>{if(!disposed){initialize();if(lastFrame)draw(...lastFrame);}};
canvas.addEventListener('webglcontextlost',lost);
canvas.addEventListener('webglcontextrestored',restored);
initialize();
function draw(time,palette,pointer=[0,0]){
lastFrame=[time,palette,pointer];
if(!gpu || !palette || disposed)return;
const {gl}=gpu;gl.useProgram(gpu.program);gl.viewport(0,0,canvas.width,canvas.height);
gl.uniform2f(uniforms.resolution,canvas.width,canvas.height);
gl.uniform2f(uniforms.pointer,...pointer);gl.uniform1f(uniforms.time,time);
for(const n of ['a','b','c'])gl.uniform3fv(uniforms[n],palette[n]);
gl.drawArrays(gl.TRIANGLES,0,6);
}
return {
resize,draw,
destroy(){if(disposed)return;disposed=true;canvas.removeEventListener('webglcontextlost',lost);canvas.removeEventListener('webglcontextrestored',restored);gpu?.destroy();gpu=null;lastFrame=null;canvas.width=canvas.height=1;delete canvas.dataset.renderer;}
};
}