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:
1 parent
053a6a6723
commit
d9445b5888
126 files changed
+10345
-1401
No files matched your search
@@ -0,0 +1,31 @@
|
||||
// Content values are plain text. A line break is content, not injected markup.
|
||||
export function contentValue(content, path) {
|
||||
return path.split('.').reduce((value,key)=>value!=null && Object.hasOwn(value,key)?value[key]:undefined,content);
|
||||
}
|
||||
function url(value, image=false) {
|
||||
if(typeof value!=='string' || !value.trim())return null;
|
||||
try {
|
||||
const protocol=new URL(value,document.baseURI).protocol;
|
||||
if(['http:','https:','file:'].includes(protocol) || (!image && ['mailto:','tel:'].includes(protocol)) || (image && (protocol==='blob:' || /^data:image\/(png|jpeg|webp|gif|svg\+xml)[;,]/i.test(value))))return value;
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
export function applyContent(element, content={}) {
|
||||
if(!content || typeof content!=='object')return;
|
||||
for(const node of element.querySelectorAll('[data-text]')) {
|
||||
const value=contentValue(content,node.dataset.text);
|
||||
if(value!==undefined && value!==null)node.textContent=Array.isArray(value)?value.join('\n'):String(value);
|
||||
}
|
||||
for(const node of [element,...element.querySelectorAll('[data-href]')]) {
|
||||
if(!node.hasAttribute('data-href'))continue;
|
||||
const value=contentValue(content,node.dataset.href);
|
||||
if(value===undefined)continue;
|
||||
const href=url(value);if(href)node.setAttribute('href',href);else node.removeAttribute('href');
|
||||
}
|
||||
for(const node of element.querySelectorAll('[data-image]')) {
|
||||
const value=contentValue(content,node.dataset.image);
|
||||
if(!value)continue;
|
||||
const src=url(typeof value==='string'?value:value.src,true);
|
||||
if(src){node.src=src;if(typeof value==='object' && value.alt!==undefined)node.alt=String(value.alt);}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/* Light, edge and contact shadow are authored together for each material. */
|
||||
:where(.card,.showcase-card) {
|
||||
--_card-shadow-contact:0 1px 2px #18253808,0 3px 6px -3px #18253812;
|
||||
--_card-shadow-lifted:0 2px 5px -3px #18253812,0 10px 20px -12px #18253820;
|
||||
--_card-shadow-glass:0 1px 3px #18253805,0 7px 16px -10px #18253814;
|
||||
--_card-shadow-solid:0 1px 1px #18253809,0 4px 8px -5px #18253816;
|
||||
--_card-material-shadow:0 0 #0000;
|
||||
--_card-metal-base:color-mix(in oklab,var(--card-surface) 95%,var(--card-muted));
|
||||
--_card-metal-band:linear-gradient(180deg,#ffffff66,transparent 36%,#17263b05);
|
||||
--_card-metal-rim:inset 0 1px 0 #ffffff70,inset 0 -1px 0 #17223514;
|
||||
--_card-glass-rim:inset 0 1px 0 #ffffff80,inset 1px 0 0 #ffffff29,inset 0 -1px 0 #35496d0d;
|
||||
--_card-solid-rim:inset 0 1px 0 #ffffff80,inset 0 -1px 0 color-mix(in oklab,var(--card-ink) 11%,var(--card-surface));
|
||||
--_card-finish-noise:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='.025'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cpath filter='url(%23n)' d='M0 0h160v160H0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
:where([data-theme="dark"]) :where(.card,.showcase-card),:where(.card,.showcase-card)[data-theme="dark"] {
|
||||
--_card-shadow-contact:0 1px 2px #0003,0 3px 6px -3px #0004;
|
||||
--_card-shadow-lifted:0 2px 5px -3px #0005,0 10px 20px -12px #0008;
|
||||
--_card-shadow-glass:0 1px 3px #0002,0 7px 16px -10px #0006;
|
||||
--_card-shadow-solid:0 1px 1px #0004,0 4px 8px -5px #0006;
|
||||
--_card-metal-base:color-mix(in oklab,var(--card-surface) 93%,var(--card-muted));
|
||||
--_card-metal-band:linear-gradient(180deg,#ffffff07,transparent 36%,#0000000a);
|
||||
--_card-metal-rim:inset 0 1px 0 #ffffff1c,inset 0 -1px 0 #0003;
|
||||
--_card-glass-rim:inset 0 1px 0 #ffffff22,inset 1px 0 0 #ffffff0b,inset 0 -1px 0 #0002;
|
||||
--_card-solid-rim:inset 0 1px 0 #ffffff16,inset 0 -1px 0 #0005;
|
||||
}
|
||||
:is(.card,.showcase-card)[data-shadow="auto"] {--_card-outer-shadow:var(--_card-material-shadow);}
|
||||
:is(.card,.showcase-card)[data-shadow="none"] {--_card-outer-shadow:0 0 #0000;}
|
||||
:is(.card,.showcase-card)[data-shadow="contact"] {--_card-outer-shadow:var(--_card-shadow-contact);}
|
||||
:is(.card,.showcase-card)[data-shadow="lifted"] {--_card-outer-shadow:var(--_card-shadow-lifted);}
|
||||
.card,.showcase-card > .showcase-window {box-shadow:var(--_card-outer-shadow,var(--_card-material-shadow));}
|
||||
.card-finish {display:none;position:absolute;inset:0;z-index:0;border-radius:inherit;pointer-events:none;user-select:none;box-shadow:var(--_card-material-rim,none);}
|
||||
.card-finish::before {content:"";position:absolute;inset:0;border-radius:inherit;background-image:var(--_card-finish-art,none);background-size:var(--_card-finish-size,auto);opacity:var(--_card-material-reflection,1);}
|
||||
:is(.card,.showcase-card)[data-material="metal"] {
|
||||
--_card-material-rim:var(--_card-metal-rim);
|
||||
--_card-material-shadow:var(--_card-shadow-contact);
|
||||
& .card-finish {display:block;background-color:var(--_card-finish-base,var(--_card-metal-base));--_card-finish-art:var(--_card-metal-band);}
|
||||
}
|
||||
:is(.card,.showcase-card)[data-material="glass"] {
|
||||
--_card-material-rim:var(--_card-glass-rim);
|
||||
--_card-material-shadow:var(--_card-shadow-glass);
|
||||
& .card-finish {display:block;--_card-finish-art:linear-gradient(126deg,#ffffff24,transparent 34% 65%,#ffffff0b);}
|
||||
}
|
||||
/* Keep a readable fallback when a host cannot blur the backdrop. */
|
||||
.card[data-material="glass"],.showcase-card[data-material="glass"] > .showcase-window {background:color-mix(in oklab,var(--card-surface) 90%,transparent);}
|
||||
@supports (backdrop-filter:blur(1px)) {
|
||||
.card[data-material="glass"],.showcase-card[data-material="glass"] > .showcase-window {background:color-mix(in oklab,var(--card-surface) 62%,transparent);backdrop-filter:blur(18px) saturate(1.12);}
|
||||
}
|
||||
:is(.card,.showcase-card)[data-material="solid"] {
|
||||
--_card-material-rim:var(--_card-solid-rim);
|
||||
--_card-material-shadow:var(--_card-shadow-solid);
|
||||
& .card-finish {display:block;background-color:var(--_card-finish-base,color-mix(in oklab,var(--card-surface) 98%,var(--card-accent)));--_card-finish-art:var(--_card-finish-noise),linear-gradient(160deg,#ffffff18,transparent 35%,#15203406);--_card-finish-size:160px 160px,100% 100%;}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Generated from Natural Earth 110m land (public domain).
|
||||
export const LAND_WIDTH = 360;
|
||||
export const LAND_HEIGHT = 180;
|
||||
export const LAND_BITS = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADg/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADA/v8/AOD//z8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+///D/////5//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAH/j/+//////8PAAAAgB0AAHgAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAAOD//wP8//////8BAACAfx4AAAAAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAA4+I73//n///////8AAAAAfwAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAAOAQACDwP4D///////8BAAAAPAQAAAAAAAAAAAAYAAAAAAAAAAAAAAAAAAAAADzAgPP5H8D//////38AAAAAAAAAAAAA4AEAAAD+DwAAAAAAAAAAAAAAAAAAAIC/g7PfBwAA/v////8AAAAAAAAAAADgBwAAwP//PwAA4B8AAAAAAAAAAAAAAAAMAAD8AgAA+P////8AAAAAAAAAAABwAAAA/P//AwAAAAAAAAAAAAAAAAAAAP8Ahvc5OwAA8P///38AAAAAAAAAAAAcAADg////34cHAAcAAAAAAAAAAAAAgN9/xzf8RwAA4P///z8AAAAAAAAAAAAPAB7g//////8fgB8AAAAAAQAAAAAAAO//B3f8/wsA4P///z8AAAAAAAAAAAAPAG////////8f2f8HAAAAAAD4HwAAAAD8H/D4/38A4P///xMAAAAAAPAHAAAAgN//////////////DwAAAAD+///h/4//H+/Bg/8BwP7//w8AAAAAwP8fAAAAj9///////////////88/AeD///////8DB8TPB/4AAP///wAAAAAA8P//AwDjf77/////////////////DwD////////////vh/kPwP//BwAAAAAA/P//H/P//5//////////////////34H8////////////Afw/gP//AQAAAAAA/v+fH////+///////////////////vD///////////9fAPwYgP8PAOA/AAAA/+N/8P//////////////////////QID4///////////Pw/0DAP8HAMAfAACA//F//v////////////////////8/AAP4///////////BBPAHAP4HAAAAAADg//z///////////////////////9/AID///////////8AQ8ABAPwBAAAAAAD8P/7///////////////////////9fAMD//////////z8AwA8AAPgBAAAAAAD+H/7/////////////////////Of8BAID///z//////z8AwD8AAMABAAAAAAD+P/z///////////////////9/wH8AAAD8MwD//////x8AwH8MAAAAAAAAAAD+P2D///////////////////95cAAAAACAAwD8/////38AwP8eAAAAAAAAgAGcH/D//////////////////wEAOAAAAADADgDA/////38AgP8/AAAAAAAAwANAH/T//////////////////wAAfgAAAAAwAAAA//////8PgP8/AAAAAAAAwAFwD/7/////////////////PwAAfwAAAAAGAAAA//////8/wP//AAAAAAAAwAOwA/7/////////////////DwCAPwAAAIAAAAAA/v//////8///BwAAAAAAOAcgcP//////////////////HwAAHwAAAAAAAACA+P//////4///DwAAAAAAOA74/////////////////////wUADwAAAAAAAAAA8P//////4///DwAAAAAAGD///////////////////////wUAAwAAAAAAAAAA8P//////7///FwAAAAAAAB///////////////////////wUAAgAAAAAAAAAA0P//////////DAAAAAAAgOH//////////////////////w0AAAAAAAAAAAAAYP////////8xLAAAAAAAAPz//////////////////////wwAAAAAAAAAAAAAAP7//////38HfgAAAAAAgP///////////////////////wQAAAAAAAAAAAAAAP3///////8HUAAAAAAAAP7/////////////////////fwQAAAAAAAAAAAAAAP////////+XAAAAAAAAAPz///93/P//////////////PwwAAAAAAAAAAAAAAP////////9/AAAAAAAAAPj//f9j/v//////////////HwAAAAAAAAAAAAAAAP////////8MAAAAAAAAAPj/+P8B/P//////////////DwQAAAAAAAAAAAAAAP///////z8AAAAAAAAAcPzH8/8A8P//////////////Bx4AAAAAAAAAAAAAAP///////x8AAAAAAAAA+H+Aw/8AwP////////////9/AA8AAAAAAAAAAAAAAP///////x8AAAAAAAAA+D8Aj//w4f////////////8/AAAAAAAAAAAAAAAAAP///////wMAAAAAAAAA+B8wuE/+/////////////98fAAMAAAAAAAAAAAAAAP///////wMAAAAAAAAA+A8wEMf//////////////0cOAAMAAAAAAAAAAAAAAP7//////wEAAAAAAAAA+A8AAI7//////////////wMOAAEAAAAAAAAAAAAAAP7//////wAAAAAAAAAA+AcABob//////////////zccwAEAAAAAAAAAAAAAAPz//////wAAAAAAAAAAQOB/AAQ2/////////////x8c8AEAAAAAAAAAAAAAAPj//////wAAAAAAAAAAQPx/AAAA/////////////w8Y/gEAAAAAAAAAAAAAAPD/////fwAAAAAAAAAA4P8/AAAA/////////////w+EGwAAAAAAAAAAAAAAAMD/////HwAAAAAAAAAA8P9/AACA/////////////x/AAwAAAAAAAAAAAAAAAID/////DwAAAAAAAAAA+P//BwaA/////////////x/AAAAAAAAAAAAAAAAAAID9////BwAAAAAAAAAA/P//D3+E/////////////z9AAAAAAAAAAAAAAAAAAAD5////BwAAAAAAAAAA/P//f////////////////x8AAAAAAAAAAAAAAAAAAADy/x8GBgAAAAAAAAAA/P/////v/8///////////z8AAAAAAAAAAAAAAAAAAAD0/w8ABgAAAAAAAAAA/v////+//4///////////z8AAAAAAAAAAAAAAAAAAADu/wcADgAAAAAAAACA//////8f/x/+/////////x8AAAAAAAAAAAAAAAAAAACI/wcALAAAAAAAAADA//////8//z/g/////////w8AAAAAAAAAAAAAAAAAAACQ/wcACAAAAAAAAADg//////9//r+A/////////wcAAAAAAAAAAAAAAAAAAAAQ/wMAAAAAAAAAAADg//////9//n8cgP///////ycAAAAAAAAAAAAAAAAAAAAA/gMAAAAAAAAAAADw////////+P9/AP///////xEAAAAAAAAAAAAAAAAAAAAA/AMAHQAAAAAAAADw////////+P//APz/f///fxAAAAAAAAAAAAAAAAAAAAAA+AcAYAAAAAAAAAD4////////+f9/APz/B///BgAAAAAAAAAAAAAAAAAAAAAA/AccgAEAAAAAAADw////////8f9/AOD/B/5/AAAAAAAAAAAAAAAAAQAAAAAA+A8eADgAAAAAAADw////////4f8/AOD/Afw/BgAAAAAAAAAAAAAAAAAAAAAA8J8PAPwCAAAAAADw////////4/8fAOD/APw/AgAAAAAAAAAAAAAAAAAAAAAAgP8PAAAAAAAAAADw////////x/8HAOB/APx/ADAAAAAAAAAAAAAAAAAAAAAAAP4PAAAAAAAAAAD4////////h/8BAOA/APz/ADAAAAAAAAAAAAAAAAAAAAAAAID/AAAAAAAAAAD4////////j/8AAMAPAMD/ATAAAAAAAAAAAAAAAAAAAAAAAAD/AQAAAAAAAAD4////////nx8AAMAPAMD/ATAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAAAD4////////vwcAAIAPAMD/AcAAAAAAAAAAAAAAAAAAAAAAAADgAQAAAAAAAAD4////////fwAAAIAPAMD8AQABAAAAAAAAAAAAAAAAAAAAAADAAAgAAAAAAADw////////f2AAAAAPAID4AUACAAAAAAAAAAAAAAAAAAAAAADAAe9TAAAAAADg/////////34AAAAPAEBwAAgAAAAAAAAAAAAAAAAAAAAAAAAAE+9/AAAAAADA/////////38AAAAXAEAgAAQCAAAAAAAAAAAAAAAAAAAAAAAAz///AAAAAACA/////////z8AAAASAMAAAIACAAAAAAAAAAAAAAAAAAAAAAAAyP//AQAAAACA/////////z8AAAAwAIAAAEAHA
Line truncated
|
||||
Binary file not shown.
@@ -0,0 +1,252 @@
|
||||
import {applyContent} from './content.js';
|
||||
export const TAU = Math.PI * 2;
|
||||
export const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
||||
export const mix = (a, b, t) => a + (b - a) * t;
|
||||
export const smooth = t => { t=clamp(t);return t*t*t*(t*(t*6-15)+10); };
|
||||
export function staticMotion(element, options={}) {
|
||||
const media=matchMedia('(prefers-reduced-motion: reduce)');
|
||||
return ()=>Number(options.speed)===0 || media.matches || element.closest('[data-motion]')?.dataset.motion==='off';
|
||||
}
|
||||
export function random(seed = 17) {
|
||||
return () => { seed |= 0; seed = seed + 0x6D2B79F5 | 0; let t = Math.imul(seed ^ seed >>> 15, 1 | seed); t ^= t + Math.imul(t ^ t >>> 7, 61 | t); return ((t ^ t >>> 14) >>> 0) / 4294967296; };
|
||||
}
|
||||
/* Original triangular-lattice 2D simplex implementation, deterministic gradients. */
|
||||
export function simplex(seed = 43) {
|
||||
const rng = random(seed), p = Array.from({length: 256}, (_, i) => i);
|
||||
for (let i = 255; i; i--) { const j = Math.floor(rng() * (i + 1)); [p[i], p[j]] = [p[j], p[i]]; }
|
||||
const perm = [...p, ...p], grads = Array.from({length: 8}, (_, i) => [Math.cos(i * TAU / 8), Math.sin(i * TAU / 8)]);
|
||||
return (x, y) => {
|
||||
const s = (x + y) * .3660254038, i = Math.floor(x + s), j = Math.floor(y + s), u = (i + j) * .2113248654;
|
||||
const x0 = x - i + u, y0 = y - j + u, a = x0 > y0 ? 1 : 0, b = 1 - a;
|
||||
const xs = [x0, x0 - a + .2113248654, x0 - .5773502692], ys = [y0, y0 - b + .2113248654, y0 - .5773502692];
|
||||
let n = 0;
|
||||
for (let k = 0; k < 3; k++) {
|
||||
const ii = i + (k === 1 ? a : k), jj = j + (k === 1 ? b : k), g = grads[perm[(ii & 255) + perm[jj & 255]] & 7];
|
||||
const h = Math.max(0, .5 - xs[k] * xs[k] - ys[k] * ys[k]);
|
||||
n += h ** 4 * (xs[k] * g[0] + ys[k] * g[1]);
|
||||
}
|
||||
return n * 70;
|
||||
};
|
||||
}
|
||||
export function colors(element) {
|
||||
const cs = getComputedStyle(element), c = document.createElement('canvas'); c.width = c.height = 1;
|
||||
const ctx = c.getContext('2d', {willReadFrequently: true});
|
||||
const read = name => {
|
||||
ctx.clearRect(0, 0, 1, 1); ctx.fillStyle = cs.getPropertyValue(name).trim(); ctx.fillRect(0, 0, 1, 1);
|
||||
return [...ctx.getImageData(0, 0, 1, 1).data].slice(0, 3).map(n => n / 255);
|
||||
};
|
||||
return {a: read('--card-accent'), b: read('--_card-accent-2'), c: read('--_card-accent-3'), surface: read('--card-surface'), dark: cs.getPropertyValue('--card-surface').trim() === '#14171e'};
|
||||
}
|
||||
export const rgb = (c, alpha = 1) => `rgba(${c.map(n => Math.round(n * 255)).join(',')},${alpha})`;
|
||||
export function mixColor(a, b, t) {
|
||||
const linear = c => c.map(n => n <= .04045 ? n / 12.92 : ((n + .055) / 1.055) ** 2.4);
|
||||
const lab = c => {const [r,g,b]=linear(c),l=Math.cbrt(.412221*r+.536333*g+.051446*b),m=Math.cbrt(.211903*r+.680700*g+.107397*b),s=Math.cbrt(.088302*r+.281719*g+.629979*b);return [.210454*l+.793618*m-.004072*s,1.977998*l-2.428592*m+.450594*s,.025904*l+.782772*m-.808676*s];};
|
||||
const A=lab(a),B=lab(b),[L,x,y]=A.map((n,i)=>mix(n,B[i],t)),l=(L+.396338*x+.215804*y)**3,m=(L-.105561*x-.063854*y)**3,s=(L-.089484*x-1.291486*y)**3;
|
||||
return [4.076742*l-3.307711*m+.230970*s,-1.268438*l+2.609757*m-.341319*s,-.004196*l-.703419*m+1.707615*s].map(n=>clamp(n<=.0031308?12.92*n:1.055*Math.max(0,n)**(1/2.4)-.055));
|
||||
}
|
||||
/* SVG has no OKLab interpolation mode. Dense OKLab stops preserve the palette. */
|
||||
export function svgStops(stops) {
|
||||
return stops.flatMap(([x,c,a=1],i)=>{
|
||||
if(i===stops.length-1)return [`<stop offset="${x}" stop-color="${c}" stop-opacity="${a}"/>`];
|
||||
const [nx,nc,na=1]=stops[i+1];
|
||||
return Array.from({length:8},(_,j)=>{const t=j/8,color=j?`color-mix(in oklab,${c} ${(1-t)*100}%,${nc})`:c;return `<stop offset="${x+(nx-x)*t}" stop-color="${color}" stop-opacity="${a+(na-a)*t}"/>`;});
|
||||
}).join('');
|
||||
}
|
||||
export const glColor = `
|
||||
vec3 lab(vec3 c){c=mix(c/12.92,pow((c+.055)/1.055,vec3(2.4)),step(vec3(.04045),c));vec3 lms=vec3(dot(c,vec3(.412221,.536333,.051446)),dot(c,vec3(.211903,.680700,.107397)),dot(c,vec3(.088302,.281719,.629979)));lms=pow(max(lms,0.),vec3(1./3.));return vec3(dot(lms,vec3(.210454,.793618,-.004072)),dot(lms,vec3(1.977998,-2.428592,.450594)),dot(lms,vec3(.025904,.782772,-.808676)));}
|
||||
vec3 srgb(vec3 c){float l=c.x+.396338*c.y+.215804*c.z,m=c.x-.105561*c.y-.063854*c.z,s=c.x-.089484*c.y-1.291486*c.z;l=l*l*l;m=m*m*m;s=s*s*s;vec3 v=max(vec3(4.076742*l-3.307711*m+.230970*s,-1.268438*l+2.609757*m-.341319*s,-.004196*l-.703419*m+1.707615*s),0.);return clamp(mix(v*12.92,1.055*pow(v,vec3(1./2.4))-.055,step(vec3(.0031308),v)),0.,1.);}
|
||||
vec3 pigment(vec3 a,vec3 b,float t){return srgb(mix(lab(a),lab(b),t));}
|
||||
`;
|
||||
export function watchColors(element, fn) {
|
||||
let signature = '';
|
||||
const update = () => {
|
||||
const cs = getComputedStyle(element), next = ['--card-accent','--_card-accent-2','--_card-accent-3','--card-surface'].map(n => cs.getPropertyValue(n)).join('|');
|
||||
if (next !== signature) { signature = next; fn(colors(element)); }
|
||||
};
|
||||
const observer = new MutationObserver(update);
|
||||
for (let p = element; p; p = p.parentElement) observer.observe(p, {attributes: true, attributeFilter: ['style','data-theme','class']});
|
||||
element.addEventListener('card-theme-change', update); update();
|
||||
return () => { observer.disconnect(); element.removeEventListener('card-theme-change', update); };
|
||||
}
|
||||
/* Timeline motion eases to rest; pointer damping keeps real dt until settled. */
|
||||
export function loop(element, draw, {continuous = true, speed = 1, duration = Infinity, settled = () => true} = {}) {
|
||||
let visible=false,hovered=element.matches(':hover'),id=0,elapsed=0,previous=0,destroyed=false,started=false;
|
||||
const rate=Number.isFinite(Number(speed)) && Number(speed)>=0?Number(speed):1;
|
||||
const media=matchMedia('(prefers-reduced-motion: reduce)');
|
||||
const finite=Number.isFinite(duration),mode=()=>element.closest('[data-motion]')?.dataset.motion || 'auto';
|
||||
let activity=mode()==='hover'?0:1;
|
||||
const allowed=()=>continuous && rate>0 && visible && !document.hidden && !media.matches && mode()!=='off';
|
||||
const running=()=>allowed() && elapsed<duration && (mode()!=='hover' || hovered || (finite && started) || (!finite && (activity>0 || !settled())));
|
||||
const stop=()=>{cancelAnimationFrame(id);id=0;previous=0;};
|
||||
const tick=now=>{
|
||||
id=0;
|
||||
if(destroyed || !running()){stop();element.dataset.paused='true';return;}
|
||||
const dt=Math.min(now-previous,64)/1000*rate;previous=now;started=true;
|
||||
const target=mode()!=='hover' || hovered?1:0,before=activity;
|
||||
activity=mix(activity,target,1-Math.exp(-dt*(target?18:10)));
|
||||
if(!target && activity<.002)activity=0;
|
||||
// A short demonstration finishes once started; continuous art slows to a stop.
|
||||
elapsed=Math.min(duration,elapsed+dt*(finite?1:(before+activity)*.5));
|
||||
draw(elapsed,dt);
|
||||
if(running())id=requestAnimationFrame(tick);else{stop();element.dataset.paused='true';}
|
||||
};
|
||||
const sync=()=>{
|
||||
if(destroyed)return;
|
||||
if(finite && (!rate || media.matches || mode()==='off'))elapsed=duration;
|
||||
const active=running();element.dataset.paused=String(!active);
|
||||
if(active){if(!id){previous=performance.now();id=requestAnimationFrame(tick);}}
|
||||
else{stop();draw(elapsed,0);}
|
||||
};
|
||||
const enter=()=>{hovered=true;sync();},leave=()=>{hovered=false;sync();};
|
||||
const io=new IntersectionObserver(entries=>{visible=entries[0].isIntersecting;sync();});io.observe(element);
|
||||
const observer=new MutationObserver(sync);
|
||||
for(let p=element;p;p=p.parentElement)observer.observe(p,{attributes:true,attributeFilter:['data-motion']});
|
||||
element.addEventListener('pointerenter',enter);element.addEventListener('pointerleave',leave);element.addEventListener('pointercancel',leave);
|
||||
document.addEventListener('visibilitychange',sync);media.addEventListener('change',sync);
|
||||
draw(0,0);
|
||||
return {invalidate(){if(!destroyed)draw(elapsed,0);},restart(){if(!destroyed){elapsed=0;started=false;sync();}},destroy(){destroyed=true;stop();io.disconnect();observer.disconnect();document.removeEventListener('visibilitychange',sync);media.removeEventListener('change',sync);element.removeEventListener('pointerenter',enter);element.removeEventListener('pointerleave',leave);element.removeEventListener('pointercancel',leave);delete element.dataset.paused;}};
|
||||
}
|
||||
export function fitCanvas(canvas, scale = 1) {
|
||||
const width=canvas.clientWidth,height=canvas.clientHeight,dpr = Math.min(devicePixelRatio || 1, 2.5) * scale;
|
||||
const w = Math.max(1, Math.round(width * dpr)), h = Math.max(1, Math.round(height * dpr));
|
||||
if (canvas.width !== w || canvas.height !== h) { canvas.width = w; canvas.height = h; }
|
||||
return {w: width, h: height, dpr};
|
||||
}
|
||||
export function glProgram(canvas, vertex, fragment) {
|
||||
const gl = canvas.getContext('webgl', {alpha: true, antialias: false, premultipliedAlpha: true});
|
||||
if (!gl) return null;
|
||||
gl.getExtension('OES_standard_derivatives');
|
||||
const program = gl.createProgram(), shaders = [];
|
||||
try {
|
||||
for (const [type, source] of [[gl.VERTEX_SHADER,vertex], [gl.FRAGMENT_SHADER,fragment]]) {
|
||||
const s = gl.createShader(type); shaders.push(s); gl.shaderSource(s, source); gl.compileShader(s);
|
||||
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
|
||||
gl.attachShader(program, s);
|
||||
}
|
||||
gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program));
|
||||
gl.useProgram(program);
|
||||
} catch (_) { shaders.forEach(s => gl.deleteShader(s)); gl.deleteProgram(program);gl.getExtension('WEBGL_lose_context')?.loseContext();canvas.width=canvas.height=1;return null; }
|
||||
const buffers = [];let disposed=false;
|
||||
return {gl, program, uniform(name) { return gl.getUniformLocation(program, name); }, attribute(name, data, size) {
|
||||
const b = gl.createBuffer(); buffers.push(b); gl.bindBuffer(gl.ARRAY_BUFFER, b); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
|
||||
const p = gl.getAttribLocation(program, name); gl.enableVertexAttribArray(p); gl.vertexAttribPointer(p, size, gl.FLOAT, false, 0, 0);
|
||||
}, destroy() {
|
||||
if(disposed)return;disposed=true;
|
||||
buffers.forEach(b => gl.deleteBuffer(b)); shaders.forEach(s => gl.deleteShader(s)); gl.deleteProgram(program);
|
||||
gl.getExtension('WEBGL_lose_context')?.loseContext();canvas.width=canvas.height=1;
|
||||
}};
|
||||
}
|
||||
export const glNoise = `
|
||||
float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}
|
||||
vec2 grad(vec2 p){float a=hash(p)*6.2831853;return vec2(cos(a),sin(a));}
|
||||
float simplex(vec2 p){
|
||||
vec2 i=floor(p+(p.x+p.y)*.3660254); vec2 x=p-i+(i.x+i.y)*.2113249;
|
||||
vec2 j=x.x>x.y?vec2(1,0):vec2(0,1);vec2 y=x-j+.2113249,z=x-.5773503;
|
||||
vec3 h=max(.5-vec3(dot(x,x),dot(y,y),dot(z,z)),0.);h=h*h*h*h;
|
||||
return 70.*dot(h,vec3(dot(grad(i),x),dot(grad(i+j),y),dot(grad(i+1.),z)));
|
||||
}`;
|
||||
export function shell(element, options = {}) {
|
||||
// Apply the public contract before a component measures, reads colors or draws.
|
||||
const attributes=new Map();
|
||||
for(const key of ['theme','motion','variant','effects','shadow','material']){
|
||||
const value=options[key];
|
||||
if(value===undefined || (key==='theme' && !['light','dark'].includes(value)) || (key==='motion' && !['auto','hover','off'].includes(value)))continue;
|
||||
attributes.set(key,element.getAttribute('data-'+key));element.dataset[key]=String(value);
|
||||
}
|
||||
applyContent(element,options.content);
|
||||
const button = options.trigger ?? null;
|
||||
const rate=Number.isFinite(Number(options.speed ?? 1)) && Number(options.speed ?? 1)>=0?Number(options.speed ?? 1):1;
|
||||
const media=matchMedia('(prefers-reduced-motion: reduce)');
|
||||
const still=()=>!rate || element.closest('[data-motion]')?.dataset.motion==='off' || media.matches;
|
||||
let x=0,y=0,tx=0,ty=0,rx=0,ry=0,trx=0,try_=0,on=false,frame=0,previous=0,destroyed=false,entryRect=null;
|
||||
let dialog=null,placeholder=null,animation=null,lightX=0,lightY=0;
|
||||
const surface=element.querySelector('.showcase-window') || element;
|
||||
// A separate background layer leaves artwork pseudo-elements and text intact.
|
||||
const finish=document.createElement('span');finish.className='card-finish';finish.setAttribute('aria-hidden','true');surface.prepend(finish);
|
||||
const effects=()=>new Set((element.dataset.effects || '').split(/\s+/));
|
||||
const enabled=()=>{const e=effects();return !dialog && !still() && (e.has('glow') || e.has('tilt'));};
|
||||
const paint = () => {
|
||||
element.style.setProperty('--pointer-x',`${x-lightX}px`);element.style.setProperty('--pointer-y',`${y-lightY}px`);
|
||||
element.style.setProperty('--_card-rotate-x',`${rx}deg`);element.style.setProperty('--_card-rotate-y',`${ry}deg`);
|
||||
};
|
||||
const stop = () => {cancelAnimationFrame(frame);frame=0;previous=0;};
|
||||
const reset = () => {on=false;stop();rx=ry=trx=try_=0;entryRect=null;element.style.setProperty('--pointer-on',0);paint();};
|
||||
const schedule = () => {if(!frame && !destroyed){previous=performance.now();frame=requestAnimationFrame(follow);}};
|
||||
const leave = () => {
|
||||
on=false;entryRect=null;trx=try_=0;element.style.setProperty('--pointer-on',0);
|
||||
if(document.hidden || !enabled()){reset();return;}
|
||||
if(Math.abs(rx)+Math.abs(ry)>.005)schedule();else stop();
|
||||
};
|
||||
const follow = now => {
|
||||
frame=0;
|
||||
if(destroyed || document.hidden || !enabled()){reset();return;}
|
||||
const dt=Math.min(now-previous,64)/1000*rate;previous=now;
|
||||
const ease=1-Math.exp(-dt*(on?18:10));
|
||||
if(on){x=mix(x,tx,ease);y=mix(y,ty,ease);}
|
||||
rx=mix(rx,trx,ease);ry=mix(ry,try_,ease);
|
||||
const positionDone=!on || Math.abs(x-tx)+Math.abs(y-ty)<.1;
|
||||
const rotationDone=Math.abs(rx-trx)+Math.abs(ry-try_)<.005;
|
||||
if(positionDone && on){x=tx;y=ty;}if(rotationDone){rx=trx;ry=try_;}
|
||||
paint();
|
||||
if(!positionDone || !rotationDone)frame=requestAnimationFrame(follow);else previous=0;
|
||||
};
|
||||
const move = e => {
|
||||
if(e.pointerType==='touch' || document.hidden || !enabled()){reset();return;}
|
||||
// A fixed entry rectangle avoids feeding the tilted bounds back into tilt.
|
||||
const r=entryRect || (entryRect=element.getBoundingClientRect());
|
||||
tx=e.clientX-r.left-element.clientLeft;ty=e.clientY-r.top-element.clientTop;
|
||||
const active=effects();
|
||||
trx=active.has('tilt')?-clamp((e.clientY-r.top)/r.height*2-1,-1,1)*2.4:0;
|
||||
try_=active.has('tilt')?clamp((e.clientX-r.left)/r.width*2-1,-1,1)*2.4:0;
|
||||
if(!on){x=tx;y=ty;on=true;paint();}
|
||||
element.style.setProperty('--pointer-on',active.has('glow')?1:0);
|
||||
if(Math.abs(x-tx)+Math.abs(y-ty)>=.1 || Math.abs(rx-trx)+Math.abs(ry-try_)>=.005)schedule();
|
||||
};
|
||||
const sync = () => {element.toggleAttribute('data-static-motion',still());if(document.hidden || !enabled())reset();else if(on){const active=effects();if(!active.has('tilt'))trx=try_=rx=ry=0;element.style.setProperty('--pointer-on',active.has('glow')?1:0);paint();}};
|
||||
const observer=new MutationObserver(()=>{measure();sync();});
|
||||
for(let p=element;p;p=p.parentElement)observer.observe(p,{attributes:true,attributeFilter:['data-motion','data-effects','data-title']});
|
||||
const io=new IntersectionObserver(entries=>{if(!entries[0].isIntersecting)reset();});io.observe(element);
|
||||
const heading=element.querySelector(':scope > .card-heading, :scope > .card-caption, :scope > .metric-copy');
|
||||
const measure=()=>{if(heading)element.style.setProperty('--_card-heading-height',`${heading.offsetHeight}px`);if(surface!==element){lightX=surface.offsetLeft;lightY=surface.offsetTop;}};
|
||||
const ro=new ResizeObserver(()=>{measure();if(on)leave();});ro.observe(element);if(heading)ro.observe(heading);measure();
|
||||
element.addEventListener('pointerenter',move);element.addEventListener('pointermove',move);element.addEventListener('pointerleave',leave);element.addEventListener('pointercancel',leave);
|
||||
document.addEventListener('visibilitychange',sync);media.addEventListener('change',sync);
|
||||
sync();
|
||||
window.addEventListener('scroll',leave,{passive:true,capture:true});
|
||||
const restore = () => {
|
||||
if (!dialog) return; reset(); animation?.cancel(); placeholder.replaceWith(element); dialog.remove(); dialog=null; placeholder=null;
|
||||
element.dispatchEvent(new Event('card-theme-change')); button?.focus({preventScroll:true});
|
||||
};
|
||||
const close = () => {
|
||||
if (!dialog) return;
|
||||
if (still()) { restore(); return; }
|
||||
const from=element.getBoundingClientRect(), to=placeholder.getBoundingClientRect();
|
||||
animation=element.animate([{transform:'none'},{transform:`translate(${to.left-from.left}px,${to.top-from.top}px) scale(${to.width/from.width},${to.height/from.height})`}],{duration:450/rate,easing:'cubic-bezier(.165,.84,.44,1)',fill:'forwards'});
|
||||
animation.finished.then(restore).catch(()=>{});
|
||||
};
|
||||
const open = event => {
|
||||
if(button?.tagName === 'A') { if(!button.getAttribute('href')?.startsWith('#'))return; event?.preventDefault(); }
|
||||
if (dialog) return; reset(); const from=element.getBoundingClientRect();
|
||||
placeholder=document.createElement('div');placeholder.style.cssText=`height:${from.height}px;width:100%`;element.before(placeholder);
|
||||
dialog=document.createElement('dialog');dialog.className=`card-dialog ${element.classList.contains('showcase-card')?'showcase-dialog':element.classList.contains('banner') || element.classList.contains('strip')?'wide':''}`;
|
||||
dialog.setAttribute('aria-label',element.querySelector('h2, .showcase-wordmark')?.textContent || '查看卡片');
|
||||
const cs=getComputedStyle(element);
|
||||
// The element changes parent; carry its inherited host typography with it.
|
||||
dialog.style.font=cs.font;
|
||||
for(const n of ['--card-accent','--card-accent-2','--card-accent-3','--card-radius']) {const value=cs.getPropertyValue(n).trim();if(value)dialog.style.setProperty(n,value);}
|
||||
dialog.dataset.theme=element.closest('[data-theme]')?.dataset.theme || 'light';
|
||||
dialog.dataset.motion=element.closest('[data-motion]')?.dataset.motion || 'auto';
|
||||
const exit=document.createElement('button');exit.className='dialog-close';exit.textContent='×';exit.setAttribute('aria-label','关闭');exit.addEventListener('click',close);
|
||||
dialog.append(element,exit);document.body.append(dialog);dialog.showModal();
|
||||
dialog.addEventListener('cancel',e=>{e.preventDefault();close();});dialog.addEventListener('click',e=>{if(e.target===dialog)close();});
|
||||
const to=element.getBoundingClientRect();
|
||||
if(!still()) animation=element.animate([{transform:`translate(${from.left-to.left}px,${from.top-to.top}px) scale(${from.width/to.width},${from.height/to.height})`},{transform:'none'}],{duration:700/rate,easing:'cubic-bezier(.165,.84,.44,1)'});
|
||||
element.dispatchEvent(new Event('card-theme-change'));
|
||||
};
|
||||
button?.addEventListener('click',open);
|
||||
return () => { if(destroyed)return;destroyed=true;restore();stop();finish.remove();observer.disconnect();io.disconnect();ro.disconnect();window.removeEventListener('scroll',leave,true);media.removeEventListener('change',sync);document.removeEventListener('visibilitychange',sync);element.removeEventListener('pointerenter',move);element.removeEventListener('pointermove',move);element.removeEventListener('pointerleave',leave);element.removeEventListener('pointercancel',leave);button?.removeEventListener('click',open);element.removeAttribute('data-static-motion');element.style.removeProperty('--pointer-on');element.style.removeProperty('--pointer-x');element.style.removeProperty('--pointer-y');element.style.removeProperty('--_card-rotate-x');element.style.removeProperty('--_card-rotate-y');element.style.removeProperty('--_card-heading-height');
|
||||
// Keep template canvases for remounting, but release their backing stores.
|
||||
for(const canvas of element.querySelectorAll('canvas'))canvas.width=canvas.height=1;
|
||||
for(const [key,value] of attributes){if(value===null)element.removeAttribute('data-'+key);else element.setAttribute('data-'+key,value);}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/* The host supplies the accent and font. Geometry is intentionally fixed. */
|
||||
:where(.card, .showcase-card, .card-dialog) {
|
||||
--_card-accent-2: var(--card-accent-2, oklch(from var(--card-accent) calc(l + .06) c calc(h + 50)));
|
||||
--_card-accent-3: var(--card-accent-3, oklch(from var(--card-accent) calc(l + .10) calc(c * .8) calc(h + 100)));
|
||||
--_card-accent-ink: oklch(from var(--card-accent) min(l,.5) c h);
|
||||
--_card-accent-2-ink: oklch(from var(--_card-accent-2) min(l,.5) c h);
|
||||
--_card-accent-3-ink: oklch(from var(--_card-accent-3) min(l,.5) c h);
|
||||
/* A checkmark sits on the chosen color itself, independently of page theme. */
|
||||
--_card-on-accent: oklch(from var(--card-accent) clamp(0, round(up, calc(.65 - l), 1), 1) 0 h);
|
||||
--_card-radius: var(--card-radius, 8px);
|
||||
color-scheme:light;
|
||||
--card-surface: #fff;
|
||||
--card-panel: #fff;
|
||||
--card-ink: #233044;
|
||||
--card-muted: #7b8798;
|
||||
--card-line: #e5edf5;
|
||||
--card-wash: #f5f7fa;
|
||||
--card-shadow: 0 12px 32px -12px #23364c26, 0 2px 8px #23364c06;
|
||||
--_card-edge-core: color-mix(in oklab, var(--card-accent) 56%, var(--card-surface));
|
||||
color: var(--card-ink);
|
||||
}
|
||||
:where([data-theme="dark"]) :where(.card, .showcase-card, .card-dialog), :where(.card, .showcase-card, .card-dialog)[data-theme="dark"] {
|
||||
--_card-accent-ink: oklch(from var(--card-accent) max(l,.8) c h);
|
||||
--_card-accent-2-ink: oklch(from var(--_card-accent-2) max(l,.8) c h);
|
||||
--_card-accent-3-ink: oklch(from var(--_card-accent-3) max(l,.8) c h);
|
||||
color-scheme:dark;
|
||||
--card-surface: #14171e;
|
||||
--card-panel: #20242d;
|
||||
--card-ink: #edf0f5;
|
||||
--card-muted: #979fad;
|
||||
--card-line: #ffffff12;
|
||||
--card-wash: #191e27;
|
||||
--card-shadow: 0 14px 40px -12px #0008, 0 0 0 1px #ffffff08;
|
||||
--_card-edge-core: color-mix(in oklab, var(--card-accent) 48%, #fff);
|
||||
}
|
||||
/* A local light theme also works inside a dark host. */
|
||||
:is(.card, .showcase-card, .card-dialog)[data-theme="light"] {
|
||||
--_card-accent-ink: oklch(from var(--card-accent) min(l,.5) c h);
|
||||
--_card-accent-2-ink: oklch(from var(--_card-accent-2) min(l,.5) c h);
|
||||
--_card-accent-3-ink: oklch(from var(--_card-accent-3) min(l,.5) c h);
|
||||
color-scheme:light;
|
||||
--card-surface: #fff;
|
||||
--card-panel: #fff;
|
||||
--card-ink: #233044;
|
||||
--card-muted: #7b8798;
|
||||
--card-line: #e5edf5;
|
||||
--card-wash: #f5f7fa;
|
||||
--card-shadow: 0 12px 32px -12px #23364c26, 0 2px 8px #23364c06;
|
||||
--_card-edge-core: color-mix(in oklab, var(--card-accent) 56%, var(--card-surface));
|
||||
}
|
||||
|
||||
.card {
|
||||
width:100%; min-width:0;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
overflow: hidden;
|
||||
/* Clip without an internal scroll box when focus/scrollIntoView targets art. */
|
||||
overflow: clip;
|
||||
border: 1px solid var(--card-line);
|
||||
border-radius: var(--_card-radius);
|
||||
background: var(--card-surface);
|
||||
aspect-ratio: 1.2;
|
||||
&.portrait { aspect-ratio: .6; }
|
||||
&.banner { aspect-ratio: 2.7; }
|
||||
&.strip { aspect-ratio: 4.7; }
|
||||
& .card-heading {
|
||||
position: absolute; z-index: 4; top: 24px; left: 24px; right: 24px;
|
||||
margin: 0; font-size: clamp(22px, 1.8vw, 26px); font-weight: 400;
|
||||
line-height: 1.12; letter-spacing: -.045em;
|
||||
}
|
||||
& .card-stage { position: absolute; inset: 78px 0 0; overflow: hidden; overflow:clip; }
|
||||
&.bottom-title {
|
||||
& .card-stage { inset: 0 0 94px; }
|
||||
& .card-caption { position: absolute; bottom: 23px; left: 24px; right: 24px; z-index: 4; }
|
||||
& h2 { margin: 0 0 8px; font-size: 18px; font-weight: 550; letter-spacing: -.035em; }
|
||||
& .card-caption p { margin: 0; font-size: 13px; line-height: 1.55; color: var(--card-muted); }
|
||||
}
|
||||
& canvas { display: block; width: 100%; height: 100%; }
|
||||
}
|
||||
.card::after, .showcase-window::before {
|
||||
content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 9;
|
||||
padding: 1px; border-radius: inherit;
|
||||
background: radial-gradient(150px circle at var(--pointer-x, -300px) var(--pointer-y, -300px) in oklab,
|
||||
var(--_card-edge-core) 0%,
|
||||
color-mix(in oklab, var(--card-accent) 45%, transparent) 24%,
|
||||
color-mix(in oklab, var(--card-accent) 16%, transparent) 52%,
|
||||
transparent 76%);
|
||||
mask: linear-gradient(in oklab,#fff 0 0) content-box, linear-gradient(in oklab,#fff 0 0);
|
||||
mask-composite: exclude; opacity: 0;
|
||||
transition: opacity .45s cubic-bezier(.16,1,.3,1);
|
||||
}
|
||||
|
||||
.card:hover::after, .showcase-card:hover .showcase-window::before { transition-duration:.2s; }
|
||||
.card[data-effects~="glow"]::after, .showcase-card[data-effects~="glow"] .showcase-window::before { opacity:calc(var(--pointer-on,0) * .85); }
|
||||
:is(.card,.showcase-card)[data-effects~="tilt"] { transform:perspective(1000px) rotateX(var(--_card-rotate-x,0deg)) rotateY(var(--_card-rotate-y,0deg)); }
|
||||
.card-dialog {
|
||||
border: 0; padding: 0; background: transparent; overflow: visible;
|
||||
width: min(560px, calc((100vh - 48px) * .6), calc(100vw - 40px)); max-height: calc(100vh - 48px);
|
||||
&.wide { width: min(1100px, calc(100vw - 40px)); }
|
||||
&::backdrop { background: #10172682; backdrop-filter: blur(8px); }
|
||||
& .card { width: 100%; max-height: calc(100vh - 48px); }
|
||||
& :is(.card,.showcase-card)[data-effects~="tilt"] { transform:none; }
|
||||
& .dialog-close { position: absolute; right: -12px; top: -12px; width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--card-line); background: var(--card-panel); color: var(--card-ink); z-index: 20; cursor: pointer; font-size: 20px; }
|
||||
}
|
||||
@media (max-width: 650px) {
|
||||
.card {
|
||||
& .card-heading { font-size: 24px; }
|
||||
&.banner { aspect-ratio: 1.2; }
|
||||
&.strip { aspect-ratio: 1.2; }
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.card *, .showcase-card * { transition-duration: 0s !important; animation: none !important; }
|
||||
.card[data-effects]::after, .showcase-card[data-effects] .showcase-window::before { opacity:0; transition:none; }
|
||||
:is(.card,.showcase-card)[data-effects~="tilt"] { transform:none; }
|
||||
}
|
||||
|
||||
/* Text occupies a real row; the art keeps its own crop and minimum stage. */
|
||||
.card:has(> .card-stage):not(.line-ribbons) {
|
||||
--_card-stage-min:240px;
|
||||
display:grid;
|
||||
grid-template-columns:minmax(0,1fr);
|
||||
grid-template-rows:auto minmax(var(--_card-stage-track-min,var(--_card-stage-min)),1fr);
|
||||
row-gap:var(--_card-stage-gap,26px);
|
||||
min-height:min-content;
|
||||
& > .card-heading, & > .card-caption {
|
||||
position:relative; inset:auto; min-width:0; order:0;
|
||||
margin:24px 24px 0; flex:none;
|
||||
}
|
||||
& > .card-caption h2 { font-size:22px; line-height:1.15; font-weight:450; margin:0 0 8px; }
|
||||
& > .card-stage {
|
||||
position:relative; inset:auto; order:1; min-width:0; min-height:var(--_card-stage-min);
|
||||
margin:0;
|
||||
}
|
||||
&[data-title="bottom"] {
|
||||
grid-template-rows:minmax(var(--_card-stage-track-min,var(--_card-stage-min)),1fr) auto; row-gap:28px;
|
||||
&:has(> .card-caption) { row-gap:25px; }
|
||||
& > .card-stage { order:0; }
|
||||
& > .card-heading { order:1; margin:0 24px 28px; }
|
||||
& > .card-caption { order:1; margin:0 24px 23px; }
|
||||
}
|
||||
}
|
||||
.card:is(.ui-slice,.terminal,.beam-graph,.keycaps) {
|
||||
height:420px; aspect-ratio:auto; --_card-stage-gap:20px;
|
||||
}
|
||||
/* Runtime resolves the closest motion mode, speed=0 and reduced motion together. */
|
||||
:is(.card,.showcase-card)[data-static-motion] *, .card[data-motion="off"] * { transition-duration:0s!important; animation:none!important; }
|
||||
.card[data-motion="off"]::after, .showcase-card[data-motion="off"] .showcase-window::before { opacity:0; transition:none; }
|
||||
:is(.card,.showcase-card)[data-motion="off"][data-effects~="tilt"] { transform:none; }
|
||||
[data-text] { white-space:pre-line; overflow-wrap:anywhere; }
|
||||
Reference in new issue
Block a user