85453da49f
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
307 lines
10 KiB
HTML
307 lines
10 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>asset-fusion · circle-highlight TEMPLATE (drop-in)</title>
|
||
<!-- ============================================================
|
||
DROP-IN circle-highlight (editorial amber annotation style). To reuse:
|
||
1) set CFG below (asset path, EVEN stage dims, BOX from the locate protocol, LABEL)
|
||
2) render with even width/height (ODD width => ffmpeg encode fails!)
|
||
BOX comes from grounding/PROTOCOL.md (locate.mjs loop) — NEVER eyeball coords.
|
||
Everything (wash pool, double ring, connector, callout, brackets) is
|
||
computed from BOX. Set MODE='full' (ring+label+brackets) or 'circle'
|
||
(ring only). Gotchas are flagged inline.
|
||
============================================================ -->
|
||
<style>
|
||
:root {
|
||
--ink: #0a0c10;
|
||
--amber: #f2c078;
|
||
--paper: #f5efe6;
|
||
}
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
html,
|
||
body {
|
||
overflow: hidden;
|
||
background: var(--ink);
|
||
}
|
||
#stage {
|
||
position: relative;
|
||
overflow: hidden;
|
||
background: #fff;
|
||
font-family:
|
||
"Inter",
|
||
-apple-system,
|
||
system-ui,
|
||
sans-serif;
|
||
}
|
||
.scene {
|
||
position: absolute;
|
||
inset: 0;
|
||
}
|
||
#shot {
|
||
position: absolute;
|
||
inset: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
z-index: 0;
|
||
}
|
||
#wash {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 1;
|
||
}
|
||
#ann {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 3;
|
||
pointer-events: none;
|
||
overflow: visible;
|
||
}
|
||
#ring-a,
|
||
#ring-b,
|
||
#conn {
|
||
stroke: var(--amber);
|
||
fill: none;
|
||
stroke-linecap: round;
|
||
}
|
||
#ring-a {
|
||
stroke-width: 8;
|
||
filter: drop-shadow(0 0 12px rgba(242, 192, 120, 0.5));
|
||
}
|
||
#ring-b {
|
||
stroke-width: 5;
|
||
opacity: 0.85;
|
||
}
|
||
#conn {
|
||
stroke-width: 5;
|
||
}
|
||
#callout {
|
||
position: absolute;
|
||
z-index: 4;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
padding: 15px 28px;
|
||
background: var(--ink);
|
||
border: 2px solid var(--amber);
|
||
border-radius: 999px;
|
||
box-shadow: 0 14px 40px rgba(8, 9, 12, 0.6);
|
||
transform-origin: left center;
|
||
}
|
||
#callout .dot {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: var(--amber);
|
||
}
|
||
#callout .label {
|
||
font-size: 33px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.14em;
|
||
color: var(--paper);
|
||
text-transform: uppercase;
|
||
white-space: nowrap;
|
||
}
|
||
.tick {
|
||
position: absolute;
|
||
z-index: 5;
|
||
width: 56px;
|
||
height: 56px;
|
||
border: 3px solid var(--paper);
|
||
opacity: 0.55;
|
||
}
|
||
.tick.tl {
|
||
top: 44px;
|
||
left: 44px;
|
||
border-right: none;
|
||
border-bottom: none;
|
||
}
|
||
.tick.tr {
|
||
top: 44px;
|
||
right: 44px;
|
||
border-left: none;
|
||
border-bottom: none;
|
||
}
|
||
.tick.bl {
|
||
bottom: 44px;
|
||
left: 44px;
|
||
border-right: none;
|
||
border-top: none;
|
||
}
|
||
.tick.br {
|
||
bottom: 44px;
|
||
right: 44px;
|
||
border-left: none;
|
||
border-top: none;
|
||
}
|
||
#scan {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 5;
|
||
pointer-events: none;
|
||
mix-blend-mode: overlay;
|
||
background: repeating-linear-gradient(
|
||
to bottom,
|
||
rgba(245, 239, 230, 0.05) 0 1px,
|
||
transparent 1px 4px
|
||
);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- ⚠ data-width/height + the #stage width/height MUST be set STATICALLY here
|
||
(the renderer's StaticGuard reads them at compile time, BEFORE JS runs — JS
|
||
setAttribute is too late and the render falls back to portrait 1080×1920).
|
||
Keep these EQUAL to CFG.W/CFG.H below, and EVEN. -->
|
||
<div
|
||
id="stage"
|
||
style="width: 1998px; height: 1368px"
|
||
data-composition-id="circle-highlight"
|
||
data-start="0"
|
||
data-duration="6"
|
||
data-fps="30"
|
||
data-width="1998"
|
||
data-height="1368"
|
||
>
|
||
<div class="scene clip" data-start="0" data-duration="6" data-track-index="0">
|
||
<img id="shot" alt="asset" />
|
||
<div id="wash"></div>
|
||
<svg id="ann">
|
||
<ellipse id="ring-a" />
|
||
<ellipse id="ring-b" />
|
||
<path id="conn" />
|
||
</svg>
|
||
<div id="callout"><span class="dot"></span><span class="label"></span></div>
|
||
<div id="scan"></div>
|
||
<span class="tick tl"></span><span class="tick tr"></span><span class="tick bl"></span
|
||
><span class="tick br"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||
<script>
|
||
// ====== CONFIG — set these (BOX from the locate protocol) ======
|
||
const CFG = {
|
||
asset: "assets/shot.png",
|
||
W: 1998,
|
||
H: 1368, // EVEN dims (odd width => encode fails)
|
||
box: [0.244, 0.277, 0.607, 0.425], // [x0,y0,x1,y1] normalized, from locate.mjs
|
||
label: "HyperFrames",
|
||
mode: "full", // 'full' = ring+connector+label+brackets+scan ; 'circle' = ring only
|
||
};
|
||
// =====================================================
|
||
const S = document.getElementById("stage"); // dims are STATIC in the HTML above (must equal CFG.W/H)
|
||
document.getElementById("shot").src = CFG.asset;
|
||
const ann = document.getElementById("ann");
|
||
ann.setAttribute("viewBox", `0 0 ${CFG.W} ${CFG.H}`);
|
||
ann.setAttribute("width", CFG.W);
|
||
ann.setAttribute("height", CFG.H);
|
||
|
||
// geometry from BOX
|
||
const [x0, y0, x1, y1] = CFG.box;
|
||
const cx = ((x0 + x1) / 2) * CFG.W,
|
||
cy = ((y0 + y1) / 2) * CFG.H;
|
||
const bw = (x1 - x0) * CFG.W,
|
||
bh = (y1 - y0) * CFG.H;
|
||
const rxA = bw / 2 + 40,
|
||
ryA = bh / 2 + 42;
|
||
// wash: a lit radial pool centered on the box, dark elsewhere
|
||
const px = (((x0 + x1) / 2) * 100).toFixed(1),
|
||
py = (((y0 + y1) / 2) * 100).toFixed(1);
|
||
const ex = (((x1 - x0) / 2) * 1.15 * 100).toFixed(1),
|
||
ey = (((y1 - y0) / 2) * 1.4 * 100).toFixed(1);
|
||
document.getElementById("wash").style.background =
|
||
`radial-gradient(ellipse ${ex}% ${ey}% at ${px}% ${py}%, rgba(8,9,12,0) 0%, rgba(8,9,12,.1) 42%, rgba(8,9,12,.66) 70%, rgba(8,9,12,.9) 100%)`;
|
||
const ra = document.getElementById("ring-a"),
|
||
rb = document.getElementById("ring-b");
|
||
ra.setAttribute("cx", cx);
|
||
ra.setAttribute("cy", cy);
|
||
ra.setAttribute("rx", rxA);
|
||
ra.setAttribute("ry", ryA);
|
||
ra.setAttribute("transform", `rotate(-4 ${cx} ${cy})`);
|
||
rb.setAttribute("cx", cx);
|
||
rb.setAttribute("cy", cy);
|
||
rb.setAttribute("rx", rxA - 20);
|
||
rb.setAttribute("ry", ryA - 16);
|
||
rb.setAttribute("transform", `rotate(3 ${cx} ${cy})`);
|
||
// callout: above-right of the box (flip left if box is on the right edge)
|
||
const right = cx < CFG.W * 0.62;
|
||
const co = document.getElementById("callout");
|
||
co.querySelector(".label").textContent = CFG.label;
|
||
const coX = right ? Math.min(cx + rxA + 10, CFG.W - 360) : Math.max(cx - rxA - 360, 30);
|
||
const coY = Math.max(cy - ryA - 60, 40);
|
||
co.style.left = coX + "px";
|
||
co.style.top = coY + "px";
|
||
const conn = document.getElementById("conn");
|
||
const ex0 = cx + (right ? rxA * 0.75 : -rxA * 0.75),
|
||
ey0 = cy - ryA * 0.7;
|
||
conn.setAttribute(
|
||
"d",
|
||
`M ${ex0} ${ey0} C ${(ex0 + coX) / 2} ${ey0 - 50}, ${coX - 20} ${coY + 70}, ${coX + 2} ${coY + 26}`,
|
||
);
|
||
|
||
// ====== seek-safe timeline ======
|
||
window.__timelines = window.__timelines || {};
|
||
const tl = gsap.timeline({ paused: true });
|
||
// GOTCHA: getTotalLength can read 0 before layout -> dash fails -> a solid
|
||
// line shows at t=0. Fall back to a constant AND gate with autoAlpha:0.
|
||
const lenA = ra.getTotalLength() || 1900,
|
||
lenB = rb.getTotalLength() || 1750,
|
||
lenC = conn.getTotalLength() || 220;
|
||
const full = CFG.mode === "full";
|
||
gsap.set("#wash", { autoAlpha: 0 });
|
||
gsap.set(ra, { strokeDasharray: lenA, strokeDashoffset: lenA, autoAlpha: 0 });
|
||
gsap.set(rb, { strokeDasharray: lenB, strokeDashoffset: lenB, autoAlpha: 0 });
|
||
gsap.set(conn, { strokeDasharray: lenC, strokeDashoffset: lenC, autoAlpha: 0 });
|
||
gsap.set("#callout", { autoAlpha: 0, scale: 0.6 });
|
||
gsap.set("#scan", { autoAlpha: 0 });
|
||
gsap.set(".tick", { autoAlpha: 0 });
|
||
if (!full) {
|
||
document.getElementById("conn").remove();
|
||
document.getElementById("callout").remove();
|
||
document.getElementById("scan").remove();
|
||
document.querySelectorAll(".tick").forEach((t) => t.remove());
|
||
}
|
||
|
||
tl.to("#wash", { autoAlpha: 1, duration: 0.9, ease: "power2.out" }, 0.6);
|
||
if (full) {
|
||
tl.to("#scan", { autoAlpha: 1, duration: 0.8 }, 0.7);
|
||
tl.to(".tick", { autoAlpha: 0.55, duration: 0.5, stagger: 0.06 }, 0.8);
|
||
}
|
||
tl.to(ra, { autoAlpha: 1, duration: 0.08 }, 1.3);
|
||
tl.to(ra, { strokeDashoffset: 0, duration: 0.6, ease: "power2.inOut" }, 1.3);
|
||
tl.to(rb, { autoAlpha: 1, duration: 0.08 }, 1.68);
|
||
tl.to(rb, { strokeDashoffset: 0, duration: 0.5, ease: "power2.inOut" }, 1.68);
|
||
if (full) {
|
||
tl.to(conn, { autoAlpha: 1, duration: 0.05 }, 2.55);
|
||
tl.to(conn, { strokeDashoffset: 0, duration: 0.35, ease: "power3.out" }, 2.6);
|
||
tl.to("#callout", { autoAlpha: 1, scale: 1, duration: 0.45, ease: "back.out(2.4)" }, 2.95);
|
||
}
|
||
window.__timelines["circle-highlight"] = tl;
|
||
tl.seek(0);
|
||
if (!window.__HYPERFRAMES_HOST__) {
|
||
const fit = () => {
|
||
const s = Math.min(innerWidth / CFG.W, innerHeight / CFG.H) * 0.96;
|
||
S.style.transform = `scale(${s})`;
|
||
S.style.transformOrigin = "center center";
|
||
S.style.position = "absolute";
|
||
S.style.left = "50%";
|
||
S.style.top = "50%";
|
||
S.style.marginLeft = -(CFG.W / 2) + "px";
|
||
S.style.marginTop = -(CFG.H / 2) + "px";
|
||
};
|
||
addEventListener("resize", fit);
|
||
fit();
|
||
tl.eventCallback("onComplete", () => setTimeout(() => tl.restart(), 1000));
|
||
tl.play();
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|