opencode-skills-collection 4.1.2 → 4.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +9 -1
- package/bundled-skills/antigravity-maintainer-batch-release/SKILL.md +15 -1
- package/bundled-skills/deep-research-framework/SKILL.md +81 -0
- package/bundled-skills/five-axis-code-review/SKILL.md +78 -0
- package/bundled-skills/git-commit-message/SKILL.md +76 -0
- package/bundled-skills/idea-to-blueprint/LICENSE +21 -0
- package/bundled-skills/idea-to-blueprint/SKILL.md +260 -0
- package/bundled-skills/idea-to-blueprint/assets/AGENTS.md.template +38 -0
- package/bundled-skills/idea-to-blueprint/assets/CLAUDE.md.template +38 -0
- package/bundled-skills/idea-to-blueprint/assets/DECISIONS.md.template +14 -0
- package/bundled-skills/idea-to-blueprint/assets/PROGRESS.md.template +47 -0
- package/bundled-skills/idea-to-blueprint/assets/session-prompt.md +23 -0
- package/bundled-skills/idea-to-blueprint/references/architecture-decisions.md +202 -0
- package/bundled-skills/idea-to-blueprint/references/blueprint-template.md +259 -0
- package/bundled-skills/idea-to-blueprint/references/code-quality-and-review.md +216 -0
- package/bundled-skills/idea-to-blueprint/references/design-minimal.md +96 -0
- package/bundled-skills/idea-to-blueprint/references/edge-case-catalog.md +239 -0
- package/bundled-skills/idea-to-blueprint/references/intake-questions.md +105 -0
- package/bundled-skills/idea-to-blueprint/references/research-protocol.md +196 -0
- package/bundled-skills/idea-to-blueprint/references/session-protocol.md +133 -0
- package/bundled-skills/idea-to-blueprint/references/stories-ac-tests.md +194 -0
- package/bundled-skills/idea-to-blueprint/references/ux-writing-guide.md +128 -0
- package/bundled-skills/idea-to-blueprint/scripts/lint_blueprint.py +460 -0
- package/bundled-skills/liuguang-banlan-ui/SKILL.md +22 -18
- package/bundled-skills/liuguang-banlan-ui/assets/starter/launcher.css +2 -2
- package/bundled-skills/liuguang-banlan-ui/assets/starter/obsidian/index.html +5 -4
- package/bundled-skills/liuguang-banlan-ui/assets/starter/obsidian/theme-config.js +3 -10
- package/bundled-skills/liuguang-banlan-ui/assets/starter/obsidian/theme.css +48 -62
- package/bundled-skills/liuguang-banlan-ui/assets/starter/opal/index.html +5 -4
- package/bundled-skills/liuguang-banlan-ui/assets/starter/opal/theme-config.js +27 -35
- package/bundled-skills/liuguang-banlan-ui/assets/starter/opal/theme.css +48 -62
- package/bundled-skills/liuguang-banlan-ui/assets/starter/shared/spectral-field.js +135 -148
- package/bundled-skills/liuguang-banlan-ui/references/style-contract.md +26 -7
- package/bundled-skills/liuguang-banlan-ui/references/verification.md +19 -7
- package/bundled-skills/liuguang-banlan-ui/scripts/measure_preview.py +78 -47
- package/bundled-skills/liuguang-banlan-ui/scripts/serve_preview.py +35 -0
- package/bundled-skills/liuguang-banlan-ui/scripts/validate_manifest.py +34 -3
- package/bundled-skills/liuguang-banlan-ui/tests/test_measure_preview.py +119 -0
- package/bundled-skills/liuguang-banlan-ui/tests/test_serve_preview.py +38 -0
- package/bundled-skills/liuguang-banlan-ui/tests/test_validate_manifest.py +73 -0
- package/bundled-skills/loki-mode/examples/todo-app-generated/frontend/package-lock.json +12 -12
- package/bundled-skills/loki-mode/examples/todo-app-generated/frontend/package.json +1 -1
- package/bundled-skills/meeting-notes/SKILL.md +80 -0
- package/bundled-skills/repo-foundation/SKILL.md +166 -0
- package/bundled-skills/repo-foundation/references/LICENSE.md +21 -0
- package/bundled-skills/repo-foundation/references/bootstrap.md +63 -0
- package/bundled-skills/repo-foundation/references/companion.md +9 -0
- package/bundled-skills/repo-foundation/references/continuity.md +50 -0
- package/bundled-skills/repo-foundation/references/evolution.md +60 -0
- package/bundled-skills/repo-foundation/references/migration-examples.md +53 -0
- package/bundled-skills/repo-foundation/references/shared-contracts.md +48 -0
- package/bundled-skills/repo-foundation/references/verification.md +50 -0
- package/bundled-skills/repo-native-refactor/SKILL.md +225 -0
- package/bundled-skills/repo-native-refactor/references/LICENSE.md +21 -0
- package/bundled-skills/repo-native-refactor/references/deterministic-tooling.md +101 -0
- package/bundled-skills/repo-native-refactor/references/error-reliability.md +117 -0
- package/bundled-skills/repo-native-refactor/references/finding-taxonomy.md +181 -0
- package/bundled-skills/repo-native-refactor/references/refactor-examples.md +77 -0
- package/bundled-skills/repo-native-refactor/references/repository-prose.md +157 -0
- package/bundled-skills/repo-native-refactor/references/repository-rehabilitation.md +94 -0
- package/bundled-skills/repo-native-refactor/references/semantic-risk.md +187 -0
- package/bundled-skills/repo-native-refactor/references/shared-contracts.md +48 -0
- package/bundled-skills/repo-native-refactor/references/testing-integrity.md +103 -0
- package/bundled-skills/tech-writing-proofread/SKILL.md +78 -0
- package/bundled-skills/typescript-expert/scripts/ts_diagnostic.py +40 -19
- package/bundled-skills/youtube-summarizer/SKILL.md +3 -3
- package/package.json +1 -1
- package/skills_index.json +254 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
(function () {
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// The field drifts slowly. Spacing frames so each advances field time by at most
|
|
5
|
+
// MAX_FIELD_STEP keeps the change per frame below a visible step at less GPU cost.
|
|
6
|
+
const MIN_FRAME_INTERVAL = 50;
|
|
7
|
+
const MAX_FRAME_INTERVAL = 250;
|
|
8
|
+
const MAX_FIELD_STEP = 0.003;
|
|
5
9
|
|
|
6
10
|
const VERTEX_SHADER = `
|
|
7
11
|
attribute vec2 aPosition;
|
|
@@ -23,12 +27,12 @@
|
|
|
23
27
|
uniform float uWarp;
|
|
24
28
|
uniform float uDither;
|
|
25
29
|
uniform float uLuminanceCap;
|
|
30
|
+
uniform float uLevels;
|
|
26
31
|
uniform vec3 uBase;
|
|
27
|
-
uniform vec3 uColors[
|
|
28
|
-
uniform float uStrengths[
|
|
29
|
-
uniform float uFieldScales[
|
|
30
|
-
uniform vec2 uPhases[
|
|
31
|
-
uniform float uColorCount;
|
|
32
|
+
uniform vec3 uColors[6];
|
|
33
|
+
uniform float uStrengths[6];
|
|
34
|
+
uniform float uFieldScales[6];
|
|
35
|
+
uniform vec2 uPhases[6];
|
|
32
36
|
|
|
33
37
|
float hash21(vec2 p) {
|
|
34
38
|
p = fract(p * vec2(123.34, 456.21));
|
|
@@ -52,8 +56,8 @@
|
|
|
52
56
|
float amplitude = 0.52;
|
|
53
57
|
mat2 rotation = mat2(0.80, 0.60, -0.60, 0.80);
|
|
54
58
|
for (int i = 0; i < 5; i++) {
|
|
55
|
-
|
|
56
|
-
value += amplitude * noise(p)
|
|
59
|
+
if (float(i) + 0.5 >= uOctaves) break;
|
|
60
|
+
value += amplitude * noise(p);
|
|
57
61
|
p = rotation * p * 2.03 + vec2(13.1, 7.7);
|
|
58
62
|
amplitude *= 0.48;
|
|
59
63
|
}
|
|
@@ -77,6 +81,19 @@
|
|
|
77
81
|
return c / (1.0 + max(c - 1.0, 0.0));
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
vec3 linearToSrgb(vec3 c) {
|
|
85
|
+
vec3 high = 1.055 * pow(max(c, vec3(0.0031308)), vec3(1.0 / 2.4)) - 0.055;
|
|
86
|
+
return mix(c * 12.92, high, step(vec3(0.0031308), c));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Not hash21: offsetting pixel coordinates by the seed pushes them past
|
|
90
|
+
// float precision, and the dither turns into visible stripes.
|
|
91
|
+
float ditherHash(vec2 p) {
|
|
92
|
+
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
|
|
93
|
+
p3 += dot(p3, p3.yzx + 33.33);
|
|
94
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
95
|
+
}
|
|
96
|
+
|
|
80
97
|
void main() {
|
|
81
98
|
vec2 uv = gl_FragCoord.xy / uResolution.xy;
|
|
82
99
|
vec2 p = (uv - 0.5) * vec2(uResolution.x / uResolution.y, 1.0);
|
|
@@ -104,7 +121,12 @@
|
|
|
104
121
|
+ dot(warped, vec2(0.72, -0.49))
|
|
105
122
|
+ uSeed * 0.000017
|
|
106
123
|
);
|
|
107
|
-
for (int i = 0; i <
|
|
124
|
+
for (int i = 0; i < 6; i++) {
|
|
125
|
+
float hueStop = float(i) / 6.0;
|
|
126
|
+
float hueDistance = abs(spectralFlow - hueStop);
|
|
127
|
+
hueDistance = min(hueDistance, 1.0 - hueDistance);
|
|
128
|
+
float hueBand = 1.0 - smoothstep(0.035, 0.205, hueDistance);
|
|
129
|
+
if (hueBand <= 0.0) continue;
|
|
108
130
|
float field = colorField(
|
|
109
131
|
warped + q * (0.21 + float(i) * 0.025),
|
|
110
132
|
uPhases[i],
|
|
@@ -112,16 +134,11 @@
|
|
|
112
134
|
uFieldScales[i]
|
|
113
135
|
);
|
|
114
136
|
float softBand = smoothstep(0.22, 0.78, field);
|
|
115
|
-
float active = step(float(i) + 0.5, uColorCount);
|
|
116
|
-
float hueStop = float(i) / max(uColorCount, 1.0);
|
|
117
|
-
float hueDistance = abs(spectralFlow - hueStop);
|
|
118
|
-
hueDistance = min(hueDistance, 1.0 - hueDistance);
|
|
119
|
-
float hueBand = 1.0 - smoothstep(0.035, 0.205, hueDistance);
|
|
120
137
|
float shapedBand = pow(hueBand, 2.2) * (0.64 + softBand * 0.36);
|
|
121
|
-
float weight = shapedBand * sqrt(max(uStrengths[i], 0.0))
|
|
138
|
+
float weight = shapedBand * sqrt(max(uStrengths[i], 0.0));
|
|
122
139
|
colorSum += uColors[i] * weight;
|
|
123
140
|
weightSum += weight;
|
|
124
|
-
energySum += shapedBand * uStrengths[i]
|
|
141
|
+
energySum += shapedBand * uStrengths[i];
|
|
125
142
|
strongestBand = max(strongestBand, shapedBand);
|
|
126
143
|
}
|
|
127
144
|
|
|
@@ -145,11 +162,13 @@
|
|
|
145
162
|
}
|
|
146
163
|
}
|
|
147
164
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
165
|
+
vec2 pixel = floor(gl_FragCoord.xy);
|
|
166
|
+
float triangle = ditherHash(pixel) + ditherHash(pixel + vec2(47.0, 113.0)) - 1.0;
|
|
167
|
+
vec3 encoded = linearToSrgb(toneMap(max(color, 0.0)));
|
|
168
|
+
// Quantize here so the result does not depend on whether the GPU rounds or
|
|
169
|
+
// truncates when it stores floats; +0.25 lands inside the chosen code either way.
|
|
170
|
+
vec3 code = clamp(floor(encoded * uLevels + 0.5 + triangle * uDither), 0.0, uLevels);
|
|
171
|
+
gl_FragColor = vec4((code + 0.25) / uLevels, 1.0);
|
|
153
172
|
}
|
|
154
173
|
`;
|
|
155
174
|
|
|
@@ -170,6 +189,8 @@
|
|
|
170
189
|
];
|
|
171
190
|
}
|
|
172
191
|
|
|
192
|
+
const MAX_COLORS = 12;
|
|
193
|
+
|
|
173
194
|
function clamp01(value) {
|
|
174
195
|
const number = Number(value);
|
|
175
196
|
return Number.isFinite(number) ? Math.min(1, Math.max(0, number)) : 0;
|
|
@@ -243,89 +264,72 @@
|
|
|
243
264
|
this.applyFallbackConfig(config);
|
|
244
265
|
this.available = false;
|
|
245
266
|
this.error = null;
|
|
246
|
-
this.
|
|
247
|
-
this.motionEnabled = !this.motionPreference.matches;
|
|
267
|
+
this.motionEnabled = !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
248
268
|
this.visible = !document.hidden;
|
|
249
|
-
this.
|
|
250
|
-
this.
|
|
269
|
+
this.fieldTime = config.field.staticTime;
|
|
270
|
+
this.lastTick = null;
|
|
251
271
|
this.lastFrame = 0;
|
|
252
272
|
this.frameHandle = 0;
|
|
253
273
|
try {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
)
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const speed = this.config.field.motionSpeed;
|
|
314
|
-
this.startTime = speed > 0 && this.phaseTime !== null
|
|
315
|
-
? now - (this.phaseTime / speed) * 1000
|
|
316
|
-
: now;
|
|
317
|
-
}
|
|
318
|
-
this.render(now, true);
|
|
319
|
-
this.schedule();
|
|
320
|
-
};
|
|
321
|
-
this.onMotionPreference = (event) => this.setMotion(!event.matches);
|
|
322
|
-
window.addEventListener("resize", this.onResize, { passive: true });
|
|
323
|
-
document.addEventListener("visibilitychange", this.onVisibility);
|
|
324
|
-
this.motionPreference.addEventListener("change", this.onMotionPreference);
|
|
325
|
-
this.available = true;
|
|
326
|
-
this.resize();
|
|
327
|
-
this.uploadConfig();
|
|
328
|
-
this.schedule();
|
|
274
|
+
this.gl = canvas.getContext("webgl", {
|
|
275
|
+
alpha: false,
|
|
276
|
+
antialias: false,
|
|
277
|
+
depth: false,
|
|
278
|
+
stencil: false,
|
|
279
|
+
powerPreference: "low-power",
|
|
280
|
+
preserveDrawingBuffer: true
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (!this.gl) throw new Error("WebGL context unavailable");
|
|
284
|
+
|
|
285
|
+
this.available = true;
|
|
286
|
+
this.levels = Math.pow(2, this.gl.getParameter(this.gl.RED_BITS) || 8) - 1;
|
|
287
|
+
this.program = createProgram(this.gl);
|
|
288
|
+
this.gl.useProgram(this.program);
|
|
289
|
+
const buffer = this.gl.createBuffer();
|
|
290
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
|
|
291
|
+
this.gl.bufferData(
|
|
292
|
+
this.gl.ARRAY_BUFFER,
|
|
293
|
+
new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
|
|
294
|
+
this.gl.STATIC_DRAW
|
|
295
|
+
);
|
|
296
|
+
const position = this.gl.getAttribLocation(this.program, "aPosition");
|
|
297
|
+
this.gl.enableVertexAttribArray(position);
|
|
298
|
+
this.gl.vertexAttribPointer(position, 2, this.gl.FLOAT, false, 0, 0);
|
|
299
|
+
|
|
300
|
+
this.locations = {
|
|
301
|
+
resolution: uniform(this.gl, this.program, "uResolution"),
|
|
302
|
+
time: uniform(this.gl, this.program, "uTime"),
|
|
303
|
+
seed: uniform(this.gl, this.program, "uSeed"),
|
|
304
|
+
mode: uniform(this.gl, this.program, "uMode"),
|
|
305
|
+
overall: uniform(this.gl, this.program, "uOverall"),
|
|
306
|
+
scale: uniform(this.gl, this.program, "uScale"),
|
|
307
|
+
octaves: uniform(this.gl, this.program, "uOctaves"),
|
|
308
|
+
warp: uniform(this.gl, this.program, "uWarp"),
|
|
309
|
+
dither: uniform(this.gl, this.program, "uDither"),
|
|
310
|
+
luminanceCap: uniform(this.gl, this.program, "uLuminanceCap"),
|
|
311
|
+
levels: uniform(this.gl, this.program, "uLevels"),
|
|
312
|
+
base: uniform(this.gl, this.program, "uBase"),
|
|
313
|
+
colors: uniform(this.gl, this.program, "uColors[0]"),
|
|
314
|
+
strengths: uniform(this.gl, this.program, "uStrengths[0]"),
|
|
315
|
+
fieldScales: uniform(this.gl, this.program, "uFieldScales[0]"),
|
|
316
|
+
phases: uniform(this.gl, this.program, "uPhases[0]")
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// Resizing clears the drawing buffer; a paused field has no next frame to repaint it.
|
|
320
|
+
this.onResize = () => {
|
|
321
|
+
if (this.resize()) this.render(performance.now(), true);
|
|
322
|
+
};
|
|
323
|
+
this.onVisibility = () => {
|
|
324
|
+
this.visible = !document.hidden;
|
|
325
|
+
this.lastTick = null;
|
|
326
|
+
if (this.visible) this.schedule();
|
|
327
|
+
};
|
|
328
|
+
window.addEventListener("resize", this.onResize, { passive: true });
|
|
329
|
+
document.addEventListener("visibilitychange", this.onVisibility);
|
|
330
|
+
this.resize();
|
|
331
|
+
this.uploadConfig();
|
|
332
|
+
this.schedule();
|
|
329
333
|
} catch (error) {
|
|
330
334
|
this.error = error instanceof Error ? error.message : String(error);
|
|
331
335
|
this.available = false;
|
|
@@ -334,36 +338,28 @@
|
|
|
334
338
|
}
|
|
335
339
|
}
|
|
336
340
|
|
|
341
|
+
applyFallbackConfig(config) {
|
|
342
|
+
document.documentElement.style.setProperty("--field-fallback-background", fallbackBackground(config));
|
|
343
|
+
}
|
|
344
|
+
|
|
337
345
|
resize() {
|
|
338
|
-
if (!this.available) return;
|
|
346
|
+
if (!this.available) return false;
|
|
339
347
|
const pixelRatio = Math.min(window.devicePixelRatio || 1, 1.5);
|
|
340
348
|
const width = Math.max(1, Math.round(this.canvas.clientWidth * pixelRatio));
|
|
341
349
|
const height = Math.max(1, Math.round(this.canvas.clientHeight * pixelRatio));
|
|
342
|
-
if (this.canvas.width
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
applyFallbackConfig(config) {
|
|
350
|
-
document.documentElement.style.setProperty("--field-fallback-background", fallbackBackground(config));
|
|
350
|
+
if (this.canvas.width === width && this.canvas.height === height) return false;
|
|
351
|
+
this.canvas.width = width;
|
|
352
|
+
this.canvas.height = height;
|
|
353
|
+
this.gl.viewport(0, 0, width, height);
|
|
354
|
+
return true;
|
|
351
355
|
}
|
|
352
356
|
|
|
353
357
|
uploadConfig() {
|
|
354
358
|
if (!this.available) return;
|
|
355
359
|
const gl = this.gl;
|
|
356
360
|
const config = this.config;
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
oklch: config.base.oklch,
|
|
360
|
-
intensity: 0,
|
|
361
|
-
peakOpacity: 0,
|
|
362
|
-
fieldScale: config.field.scale,
|
|
363
|
-
phase: [0, 0]
|
|
364
|
-
};
|
|
365
|
-
const colors = sourceColors.slice();
|
|
366
|
-
while (colors.length < MAX_COLORS) colors.push(fallbackColor);
|
|
361
|
+
const colors = config.colors.slice(0, 6);
|
|
362
|
+
while (colors.length < 6) colors.push(colors[colors.length - 1]);
|
|
367
363
|
const colorValues = colors.flatMap((entry) => oklchToLinearRgb(entry.oklch));
|
|
368
364
|
const strengthValues = colors.map((entry) => entry.intensity * entry.peakOpacity * 3.0);
|
|
369
365
|
const fieldScaleValues = colors.map((entry) => entry.fieldScale);
|
|
@@ -372,12 +368,12 @@
|
|
|
372
368
|
gl.uniform1f(this.locations.seed, config.seed);
|
|
373
369
|
gl.uniform1f(this.locations.mode, config.mode === "obsidian" ? 1 : 0);
|
|
374
370
|
gl.uniform1f(this.locations.overall, config.overallColorIntensity);
|
|
375
|
-
gl.uniform1f(this.locations.colorCount, sourceColors.length);
|
|
376
371
|
gl.uniform1f(this.locations.scale, config.field.scale);
|
|
377
372
|
gl.uniform1f(this.locations.octaves, config.field.octaves);
|
|
378
373
|
gl.uniform1f(this.locations.warp, config.field.warpStrength);
|
|
379
374
|
gl.uniform1f(this.locations.dither, config.field.ditherStrength);
|
|
380
|
-
gl.uniform1f(this.locations.luminanceCap, config.field.luminanceCap);
|
|
375
|
+
gl.uniform1f(this.locations.luminanceCap, config.field.luminanceCap ?? 1);
|
|
376
|
+
gl.uniform1f(this.locations.levels, this.levels);
|
|
381
377
|
gl.uniform3fv(this.locations.base, new Float32Array(oklchToLinearRgb(config.base.oklch)));
|
|
382
378
|
gl.uniform3fv(this.locations.colors, new Float32Array(colorValues));
|
|
383
379
|
gl.uniform1fv(this.locations.strengths, new Float32Array(strengthValues));
|
|
@@ -385,40 +381,20 @@
|
|
|
385
381
|
gl.uniform2fv(this.locations.phases, new Float32Array(phaseValues));
|
|
386
382
|
}
|
|
387
383
|
|
|
388
|
-
capturePhase(now) {
|
|
389
|
-
if (this.motionEnabled) {
|
|
390
|
-
this.phaseTime = ((now - this.startTime) / 1000) * this.config.field.motionSpeed;
|
|
391
|
-
} else if (this.phaseTime === null) {
|
|
392
|
-
this.phaseTime = this.config.field.staticTime;
|
|
393
|
-
}
|
|
394
|
-
return this.phaseTime;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
384
|
updateConfig(config) {
|
|
398
|
-
const now = performance.now();
|
|
399
|
-
this.capturePhase(now);
|
|
400
385
|
this.config = config;
|
|
401
386
|
this.applyFallbackConfig(config);
|
|
402
387
|
this.uploadConfig();
|
|
403
|
-
this.render(now, true);
|
|
388
|
+
this.render(performance.now(), true);
|
|
404
389
|
}
|
|
405
390
|
|
|
406
391
|
setMotion(enabled) {
|
|
407
|
-
if (enabled === this.motionEnabled) {
|
|
408
|
-
const now = performance.now();
|
|
409
|
-
if (!enabled) this.capturePhase(now);
|
|
410
|
-
this.render(now, true);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
const now = performance.now();
|
|
414
|
-
const phase = this.capturePhase(now);
|
|
415
392
|
this.motionEnabled = enabled;
|
|
393
|
+
this.lastTick = null;
|
|
416
394
|
if (enabled) {
|
|
417
|
-
const speed = this.config.field.motionSpeed;
|
|
418
|
-
this.startTime = speed > 0 ? now - (phase / speed) * 1000 : now;
|
|
419
395
|
this.schedule();
|
|
420
396
|
} else {
|
|
421
|
-
this.render(now, true);
|
|
397
|
+
this.render(performance.now(), true);
|
|
422
398
|
}
|
|
423
399
|
}
|
|
424
400
|
|
|
@@ -431,17 +407,28 @@
|
|
|
431
407
|
});
|
|
432
408
|
}
|
|
433
409
|
|
|
410
|
+
frameInterval() {
|
|
411
|
+
const speed = this.config.field.motionSpeed;
|
|
412
|
+
if (!(speed > 0)) return MAX_FRAME_INTERVAL;
|
|
413
|
+
return Math.min(MAX_FRAME_INTERVAL, Math.max(MIN_FRAME_INTERVAL, (MAX_FIELD_STEP / speed) * 1000));
|
|
414
|
+
}
|
|
415
|
+
|
|
434
416
|
render(now, force) {
|
|
435
417
|
if (!this.available) return;
|
|
436
|
-
if (!force && now - this.lastFrame <
|
|
418
|
+
if (!force && now - this.lastFrame < this.frameInterval()) {
|
|
437
419
|
this.schedule();
|
|
438
420
|
return;
|
|
439
421
|
}
|
|
440
422
|
this.lastFrame = now;
|
|
441
423
|
this.resize();
|
|
442
|
-
|
|
424
|
+
if (this.motionEnabled) {
|
|
425
|
+
if (this.lastTick !== null) {
|
|
426
|
+
this.fieldTime += (Math.max(0, now - this.lastTick) / 1000) * this.config.field.motionSpeed;
|
|
427
|
+
}
|
|
428
|
+
this.lastTick = now;
|
|
429
|
+
}
|
|
443
430
|
this.gl.uniform2f(this.locations.resolution, this.canvas.width, this.canvas.height);
|
|
444
|
-
this.gl.uniform1f(this.locations.time,
|
|
431
|
+
this.gl.uniform1f(this.locations.time, this.fieldTime);
|
|
445
432
|
this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
|
|
446
433
|
}
|
|
447
434
|
}
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## 共通原则
|
|
4
4
|
|
|
5
|
-
- 把色场当作环境现象,不把它当成组件皮肤或装饰贴纸。
|
|
5
|
+
- 把色场当作环境现象,不把它当成组件皮肤或装饰贴纸。logo、头像、分隔线等界面部件保持中性,不用调色板渐变上色。
|
|
6
6
|
- 先建立稳定的信息层级:导航、列表/队列、详情、元数据、状态带。
|
|
7
7
|
- 让颜色跨越整个场景,但保持文字、边界、按钮和选择态可读。
|
|
8
8
|
- 用 OKLCH 编写颜色;为降级路径保留 sRGB 值。
|
|
9
|
+
- 颜色数组按色相顺序排列。渲染器把颜色依次排在一条首尾相接的色相轴上,相邻两色在交界处混合;近互补色相邻时,交界处色度互相抵消,只剩变暗。
|
|
9
10
|
- 使用连续的 fBm/domain-warp、流带或方向性流域。禁止明显的圆形光斑、中心聚光和硬彩虹条。
|
|
10
11
|
- 维持静、薄、净、密、活的工作台气质:表面轻、信息密、状态清楚、背景有生命但不抢语义。
|
|
11
12
|
|
|
@@ -13,6 +14,8 @@
|
|
|
13
14
|
|
|
14
15
|
定位:白色是第一视觉,色彩像被光照亮的珠光薄层,能看到变化但不破坏“白”。
|
|
15
16
|
|
|
17
|
+
下表按 starter 工作台标定:顶栏、侧栏和工作区等面板以 66%–92% 的不透明度盖在色场上。色场直接露出时,按下一节调整。
|
|
18
|
+
|
|
16
19
|
| 参数 | 建议范围 | 作用 |
|
|
17
20
|
|---|---:|---|
|
|
18
21
|
| `overallColorIntensity` | 0.60–0.90 | 控制全局彩色预算,默认约 0.82 |
|
|
@@ -22,9 +25,23 @@
|
|
|
22
25
|
| 颜色 `oklch.c` | 0.030–0.065 | 足够识别,但避免糖果霓虹 |
|
|
23
26
|
| `peakOpacity` | 0.055–0.110 | 决定局部峰值,先调它再调总体 |
|
|
24
27
|
| `field.warpStrength` | 0.22–0.42 | 让颜色互相流动而非分块 |
|
|
25
|
-
| `field.luminanceCap` | 0.94–1.00 | 白场通常不需强行压暗 |
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
白版不使用 `field.luminanceCap`。建议使用玫瑰、海玻璃青、丁香、珠杏、薄荷和远空蓝等低饱和色相。至少让三种色相在同一截图中可辨,不要让整张图退化为普通暖白、灰雾或单一冷白。
|
|
30
|
+
|
|
31
|
+
### 色场直接露出
|
|
32
|
+
|
|
33
|
+
个人主页、文章页这类页面没有面板盖住色场,按上表的强度会显得花,也会把整页压暗。下表是一个真实站点在一位用户屏幕上的判断。`tint.p50` 是 `measure_preview.py` 测得的相对底色色偏中位数(OKLab),用用户当时看到的那一版渲染器重新测得;换站点或屏幕需要重新确认。
|
|
34
|
+
|
|
35
|
+
| 用户判断 | `tint.p50` | 配置 |
|
|
36
|
+
|---|---:|---|
|
|
37
|
+
| 看不出色场 | 约 0.004 | overall 0.30 |
|
|
38
|
+
| 合适:仔细看能看出,不发灰 | 约 0.0056 | overall 0.45 |
|
|
39
|
+
| 显得花且乱 | 约 0.009 | 上表默认参数,色相交错排列 |
|
|
40
|
+
| 阅读页,接近全白 | 约 0.0017 | overall 0.15 |
|
|
41
|
+
|
|
42
|
+
那个站点的其他参数:底色 `l` 0.988,颜色按色相排序,`field.scale` 0.8,`octaves` 3,`warpStrength` 0.26。当前渲染器下,这组参数在 overall 0.45 时测得约 0.0052,0.55 时约 0.0063。
|
|
43
|
+
|
|
44
|
+
白版的颜色都比底色暗,着色的同时一定变暗。同一组颜色下,变暗和色偏按同一比例随强度变化,`shiftToTint` 基本不变,所以降低强度只会让色场更淡,不会让它更干净。要减少发灰,先按色相排序并检查 `grayRatio`。把颜色亮度调得更接近底色也能减少变暗,但越接近白色,sRGB 能容纳的色度越小;这一点还没有在当前渲染器上实测。
|
|
28
45
|
|
|
29
46
|
## 五彩斑斓黑 / `obsidian`
|
|
30
47
|
|
|
@@ -45,9 +62,11 @@
|
|
|
45
62
|
|
|
46
63
|
## 参数解释
|
|
47
64
|
|
|
48
|
-
- `intensity
|
|
49
|
-
- `peakOpacity
|
|
65
|
+
- `intensity`:该色相参与场的权重,必须可被滑杆实时改变。渲染器把它和 `peakOpacity` 相乘,作为该色相的强度。
|
|
66
|
+
- `peakOpacity`:`intensity` 为 1 时该色相的强度,是校准时定下的值;不要把它误写成整个页面透明度。
|
|
50
67
|
- `fieldScale`:该色相的空间频率;不同值能避免所有颜色同相叠加。
|
|
51
68
|
- `phase`:确定性相位,长度为 2 的数组;同一 seed 下保持稳定。
|
|
52
|
-
- `
|
|
53
|
-
- `
|
|
69
|
+
- `ditherStrength`:输出码值上的三角抖动幅度,以一个色阶为单位。1 能消除量化偏差;小于 1 会留下一部分偏差,强度越低越明显。
|
|
70
|
+
- `luminanceCap`:只在黑版生效,限制混色后的线性亮度。
|
|
71
|
+
- `measuredCoverage`:相对底色的色偏超过可见阈值、并按偏离方向归属该色相的像素块占比。
|
|
72
|
+
- `effectiveShare`:可见色偏能量中该色相的相对份额;这是近似归属,不是 shader 内部精确贡献。
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
## 浏览器检查
|
|
15
15
|
|
|
16
|
-
用真实浏览器生成至少两种视口:桌面(建议 1440×960)和窄屏(建议 390×844
|
|
16
|
+
用真实浏览器生成至少两种视口:桌面(建议 1440×960)和窄屏(建议 390×844)。预览用 `scripts/serve_preview.py`:`python -m http.server` 不发 `Cache-Control`,改完 manifest 后浏览器可能继续用旧文件,截图测到的就是上一版。至少检查:
|
|
17
17
|
|
|
18
18
|
1. 页面初始渲染与 WebGL/CSS fallback 状态;
|
|
19
19
|
2. 参数面板打开、关闭、Escape 返回焦点;
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
4. 恢复预设、导出 JSON、复制失败时的可用提示;
|
|
22
22
|
5. 导航、记录选择、标签切换、移动端导航收起;
|
|
23
23
|
6. 控制台错误/警告和本地静态资源请求;
|
|
24
|
-
7. reduced-motion
|
|
24
|
+
7. reduced-motion 与页面隐藏时暂停/冻结行为;
|
|
25
|
+
8. 暂停后改变窗口尺寸,色场不能变黑;暂停和继续时画面不能跳。
|
|
25
26
|
|
|
26
27
|
截图必须直接看过才能产生审美结论。重点检查:白版是否仍以白为主、黑版是否仍以黑为底、六色是否形成连续流域、文字是否可读,以及是否出现圆形大光斑、RGB 霓虹、灰雾、黑压死或全页玻璃。
|
|
27
28
|
|
|
@@ -30,16 +31,27 @@
|
|
|
30
31
|
用 `scripts/measure_preview.py` 测量纯色场截图,而不是只测被面板遮挡的完整页面。脚本会:
|
|
31
32
|
|
|
32
33
|
- 读取 JS manifest 中的 base 与 palette;
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
34
|
+
- 在线性光里按像素块(默认 8×8)取平均,抵消抖动,再转换到 OKLab;
|
|
35
|
+
- 计算每个像素块相对 manifest 底色的偏离:亮度偏移,以及 a、b 上的色偏;
|
|
36
|
+
- 把色偏超过可见阈值的像素块算作可见色,按偏离方向归属到最近的配置色相;
|
|
37
|
+
- 输出可见色比例、色偏分位数、亮度偏移分位数、亮度偏移与色偏之比(`shiftToTint`)、灰块比例(`grayRatio`)、每色覆盖率和有效份额。
|
|
38
|
+
|
|
39
|
+
偏离相对底色计算,底色自身的色度不影响结果。当前渲染器按 sRGB 曲线编码并在量化前抖动,overall 为 0 时截图应与 manifest 底色一致。换用别的渲染器时,先用 overall 0 的截图验证这一点,否则测到的偏离里混有渲染器自身的偏差。
|
|
37
40
|
|
|
38
41
|
低色度白场的色相归属只能作为近似统计,不能替代视觉检查。黑版必须同时检查最大亮度不超过 manifest 的 `luminanceCap` 意图;不要把截图中的 sRGB 值直接当成线性亮度。
|
|
39
42
|
|
|
43
|
+
## 变暗检查
|
|
44
|
+
|
|
45
|
+
白版的颜色都比底色暗,着色一定伴随变暗。看两项:
|
|
46
|
+
|
|
47
|
+
- `lightnessShift.mean`:整页平均比底色暗多少。
|
|
48
|
+
- `grayRatio`:亮度明显改变、色偏却很小的像素块占比,只在白版计算。它应接近 0;偏高时先检查颜色是否按色相排序,`validate_manifest.py` 会对乱序给出警告。
|
|
49
|
+
|
|
50
|
+
同一组颜色下,`shiftToTint` 基本不随强度变化,调低强度不会让它变小。色场直接露出的页面,按 `style-contract.md` 里“色场直接露出”一节确认强度。
|
|
51
|
+
|
|
40
52
|
## 报告结构
|
|
41
53
|
|
|
42
|
-
|
|
54
|
+
最终报告至少输出下面这些字段,另外写出 `lightnessShift.mean` 和 `grayRatio`(黑版为 `null`):
|
|
43
55
|
|
|
44
56
|
```json
|
|
45
57
|
{
|