Files
AOS/skills/design/design-templates/social-card-swiss/assets/magazine-bg-webgl.js
T
Z User e45b68bb8b feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)
Real IMAP connection via imapflow library:
- Connects to IMAP server (IMAPFlow, TLS on port 993)
- Fetches recent messages (configurable limit, default 20)
- Extracts: from, fromEmail, subject, date, snippet (200 chars)
- Thread-to-contact matching (PRD §6.1):
  1. Match sender email against persons table
  2. If no person match, extract domain and match against company.website
  3. Returns matchedCompany + matchedPerson for each email
- Audit-logged (action: crm.mailsync:imap)
- Zod validated (host, port, user, password, folder, limit)
- 502 on IMAP connection failure with detail
- 400 on validation failure

Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
2026-10-27 22:50:50 +00:00

161 lines
6.1 KiB
JavaScript
Executable File

/* Reusable Guizang-style electronic magazine background.
Copy or inline into generated HTML. It renders a deterministic WebGL ink-flow
canvas and falls back to 2D if WebGL is unavailable. */
(function () {
function rgb(input, fallback) {
return Array.isArray(input) && input.length === 3 ? input.map((v) => v / 255) : fallback;
}
function fallback2d(canvas, opts) {
const ctx = canvas.getContext("2d");
if (!ctx) return;
const dpr = Math.min(2, window.devicePixelRatio || 1);
const rect = canvas.getBoundingClientRect();
canvas.width = Math.max(1, Math.floor(rect.width * dpr));
canvas.height = Math.max(1, Math.floor(rect.height * dpr));
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
const w = rect.width;
const h = rect.height;
const ink = opts.inkCss || "rgba(26,46,31,.22)";
const accent = opts.accentCss || "rgba(46,107,79,.18)";
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < 5; i++) {
const x = w * (0.14 + ((i * 0.19 + 0.17) % 0.72));
const y = h * (0.10 + ((i * 0.29 + 0.23) % 0.78));
const r = Math.max(w, h) * (0.22 + i * 0.045);
const g = ctx.createRadialGradient(x, y, 0, x, y, r);
g.addColorStop(0, i % 2 ? accent : ink);
g.addColorStop(1, "rgba(0,0,0,0)");
ctx.fillStyle = g;
ctx.fillRect(0, 0, w, h);
}
ctx.globalAlpha = 0.09;
ctx.strokeStyle = opts.inkCss || "rgba(26,46,31,.22)";
for (let i = 0; i < 18; i++) {
ctx.beginPath();
ctx.ellipse(w * (0.5 + Math.sin(i) * 0.34), h * (0.5 + Math.cos(i * 1.7) * 0.28), 120 + i * 18, 42 + i * 9, i * 0.37, 0, Math.PI * 2);
ctx.stroke();
}
ctx.globalAlpha = 1;
}
function mount(canvas, options) {
const opts = options || {};
const gl = canvas.getContext("webgl", { alpha: true, antialias: true, preserveDrawingBuffer: true });
if (!gl) {
fallback2d(canvas, opts);
return { mode: "2d" };
}
const vert = `
attribute vec2 aPos;
void main(){ gl_Position = vec4(aPos, 0.0, 1.0); }
`;
const frag = `
precision mediump float;
uniform vec2 uRes;
uniform float uTime;
uniform vec3 uInk;
uniform vec3 uPaper;
uniform vec3 uAccent;
uniform float uStrength;
float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }
float noise(vec2 p){
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(hash(i + vec2(0.0,0.0)), hash(i + vec2(1.0,0.0)), u.x),
mix(hash(i + vec2(0.0,1.0)), hash(i + vec2(1.0,1.0)), u.x),
u.y
);
}
float fbm(vec2 p){
float v = 0.0;
float a = 0.5;
mat2 m = mat2(1.6, 1.2, -1.2, 1.6);
for(int i=0; i<5; i++){
v += a * noise(p);
p = m * p * 1.18;
a *= 0.52;
}
return v;
}
void main(){
vec2 uv = gl_FragCoord.xy / uRes.xy;
vec2 p = (gl_FragCoord.xy - 0.5 * uRes.xy) / min(uRes.x, uRes.y);
float t = uTime * 0.045;
float n = fbm(p * 2.2 + vec2(t, -t * 0.7));
float n2 = fbm(p * 5.4 - vec2(t * 1.7, t));
float contour = smoothstep(0.015, 0.0, abs(fract((n + n2 * 0.35) * 7.0) - 0.5));
float wash = smoothstep(0.22, 0.88, n);
float vignette = 1.0 - smoothstep(0.28, 0.92, length(p));
vec3 col = mix(uPaper, uInk, wash * 0.42 + contour * 0.18);
col = mix(col, uAccent, (0.18 + 0.25 * n2) * contour);
float alpha = uStrength * (0.18 + wash * 0.42 + contour * 0.30) * (0.55 + 0.45 * vignette);
gl_FragColor = vec4(col, alpha);
}
`;
function shader(type, source) {
const sh = gl.createShader(type);
gl.shaderSource(sh, source);
gl.compileShader(sh);
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(sh));
return sh;
}
const program = gl.createProgram();
gl.attachShader(program, shader(gl.VERTEX_SHADER, vert));
gl.attachShader(program, shader(gl.FRAGMENT_SHADER, frag));
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program));
gl.useProgram(program);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]), gl.STATIC_DRAW);
const loc = gl.getAttribLocation(program, "aPos");
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
const uRes = gl.getUniformLocation(program, "uRes");
const uTime = gl.getUniformLocation(program, "uTime");
const uInk = gl.getUniformLocation(program, "uInk");
const uPaper = gl.getUniformLocation(program, "uPaper");
const uAccent = gl.getUniformLocation(program, "uAccent");
const uStrength = gl.getUniformLocation(program, "uStrength");
const ink = rgb(opts.ink, [0.10, 0.18, 0.12]);
const paper = rgb(opts.paper, [0.96, 0.94, 0.90]);
const accent = rgb(opts.accent, [0.18, 0.42, 0.31]);
function resize() {
const dpr = Math.min(2, window.devicePixelRatio || 1);
const rect = canvas.getBoundingClientRect();
canvas.width = Math.max(1, Math.floor(rect.width * dpr));
canvas.height = Math.max(1, Math.floor(rect.height * dpr));
gl.viewport(0, 0, canvas.width, canvas.height);
}
function draw(time) {
resize();
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.uniform2f(uRes, canvas.width, canvas.height);
gl.uniform1f(uTime, opts.frozenTime == null ? time * 0.001 : opts.frozenTime);
gl.uniform3f(uInk, ink[0], ink[1], ink[2]);
gl.uniform3f(uPaper, paper[0], paper[1], paper[2]);
gl.uniform3f(uAccent, accent[0], accent[1], accent[2]);
gl.uniform1f(uStrength, opts.strength == null ? 0.34 : opts.strength);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
draw(0);
if (opts.frozenTime == null) requestAnimationFrame(draw);
return { mode: "webgl", redraw: () => draw(0) };
}
window.MagazineBg = { mount };
})();