mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
feat(registry): hw write-on wave — hw-write-title block + control surfaces for the handwritten family (#3557)
* feat(registry): hw write-on wave — hw-write-title block + control surfaces for four hw components Adds hw-write-title (true glyph write-on: pen-traced Caveat via baked centerline masks, curvature-adaptive pen velocity, pen lifts, underline) and grows the handwritten family's four components with declared control surfaces (controls per the #3227 convention), the completed stroke-texture matrix (sharp + deterministic seeded spray — no feTurbulence), boil poses, and physically-derived arrival deformation (travel-aligned squash with volume preserved and spring recovery). Shipped single-path callers keep working unchanged (legacy helper bodies preserved; proven in a legacy wiring harness). Validated in one reference build: check clean, double-render framemd5 910/910 bit-identical, seek-shuffle 8/8, WCAG 2.3.1 flash-scan zero violations, physics burn-ins hand-recomputed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(catalog): index hw-write-title for meaning search --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
This commit is contained in:
co-authored by
Claude Fable 5
Miguel Ángel
parent
61ba800a5d
commit
5b45bcc16d
@@ -0,0 +1,552 @@
|
||||
---
|
||||
title: "Handwritten Write-On Title"
|
||||
description: "True glyph write-on: filled Caveat letterforms revealed by baked per-glyph centerline strokes traced in real stroke order with a curvature-adaptive pen-velocity ease (fast on straights, slow into curves, inter-stroke pen lifts); optional squiggle underline; boils"
|
||||
---
|
||||
|
||||
import { InstallCommand } from "/snippets/install-command.jsx";
|
||||
|
||||
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/hw-write-title.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/hw-write-title.png" autoPlay muted loop playsInline />
|
||||
|
||||
## Install
|
||||
|
||||
<InstallCommand command="npx hyperframes add hw-write-title" item="hw-write-title" />
|
||||
|
||||
That writes `compositions/hw-write-title.html`, plus 2 supporting files under `assets/baked/` and `assets/fonts/`.
|
||||
|
||||
## Add it to your video
|
||||
|
||||
It runs for 6 seconds at 1920×1080. Paste this into your composition:
|
||||
|
||||
```html index.html
|
||||
<div
|
||||
data-composition-id="hw-write-title"
|
||||
data-composition-src="compositions/hw-write-title.html"
|
||||
data-start="0"
|
||||
data-duration="6"
|
||||
data-track-index="1"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
></div>
|
||||
```
|
||||
|
||||
Move it in time with `data-start`. Put it on a different timeline row with
|
||||
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
|
||||
|
||||
## Source
|
||||
|
||||
<Accordion title={`hw-write-title.html`}>
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script src="assets/baked/caveat-pen.js"></script>
|
||||
<style>
|
||||
/* Caveat is NOT auto-resolved by the renderer — bundle it. Asset paths
|
||||
are ROOT-relative. The glyph write-on itself renders BAKED outlines
|
||||
(no live font dependency); the bundled face serves the fallback path
|
||||
for characters outside the baked table. */
|
||||
@font-face {
|
||||
font-family: "Caveat";
|
||||
src: url("assets/fonts/Caveat-700-latin.woff2") format("woff2");
|
||||
font-weight: 700;
|
||||
font-display: block;
|
||||
}
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* ---- hw-write-title -------------------------------------------------
|
||||
TRUE glyph write-on: filled Caveat letterforms revealed by animated
|
||||
per-glyph centerline strokes (the AE mask-reveal classic — "a mask
|
||||
barely covering the line"), traced in real stroke order with a
|
||||
curvature-adaptive pen-velocity ease (fast on straights, slow into
|
||||
curves; inter-stroke pen lifts). Centerlines + outlines + stem widths
|
||||
ship baked (assets/baked/caveat-pen.js, generated deterministically
|
||||
from Caveat-700 — regeneration script in the PR).
|
||||
RULES: hwBoil owns x/y/rotation of the boil target; entrances/exits
|
||||
ride the outer wrapper. One timeline has ONE onUpdate (hwOnUpdate). */
|
||||
#hw-wt-root {
|
||||
--hw-font-print: "Caveat", cursive;
|
||||
--hw-ink: #f4f2ec;
|
||||
--hw-ink-dark: #26241f;
|
||||
--hw-accent-warm: #ffb020;
|
||||
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: transparent; /* overlays footage or a board */
|
||||
font-family: var(--hw-font-print, "Caveat", cursive);
|
||||
}
|
||||
#hw-wt-wrap {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
#hw-wt-svg {
|
||||
overflow: visible;
|
||||
display: block;
|
||||
}
|
||||
#hw-wt-under {
|
||||
overflow: visible;
|
||||
display: block;
|
||||
margin-top: -8px;
|
||||
}
|
||||
#hw-wt-under path {
|
||||
fill: none;
|
||||
stroke: var(--hw-accent-warm);
|
||||
stroke-width: 7;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
.hw-wt-fallback {
|
||||
font-weight: 700;
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="hw-wt-root"
|
||||
data-composition-id="hw-write-title"
|
||||
data-start="0"
|
||||
data-duration="6"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div id="hw-wt-move">
|
||||
<div id="hw-wt-boil">
|
||||
<div id="hw-wt-wrap">
|
||||
<svg id="hw-wt-svg"></svg>
|
||||
<svg id="hw-wt-under" width="10" height="40"><path id="hw-wt-under-path"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="hw-wt-drv"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="6"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
var CONFIG = {
|
||||
// ── Content (freely editable — not counted) ─────────────────────
|
||||
text: "write it on",
|
||||
fontSize: 170, // px; the word block auto-centers when x/y are null
|
||||
x: null,
|
||||
y: null,
|
||||
|
||||
// ── Controls (family base + 2 item-specific; comment grammar is the published-parameter list) ──
|
||||
scheme: "chalk", // variant: "chalk" | "ink" — selects the authored token set (family default "chalk")
|
||||
animationIn: true, // toggle: entrance on/off — write-on IS the entrance; false = hard-on at t=0 (pairs with animationOut; the pair counts as one control)
|
||||
animationOut: true, // (exit fade; duration derived from DUR, never exposed)
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3; seed locked
|
||||
writeOn: "trace", // variant: "trace" | "reveal" | "off" — trace = pen-traced glyph write-on (curvature-adaptive velocity, pen lifts); reveal = per-glyph staggered fade; off = hard-on; durations derived from stroke count, never exposed
|
||||
underline: true, // toggle: seeded squiggle underline drawn after the write completes
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// seed, pen-velocity constants (kCurve 10, floor 0.22, lift 60ms),
|
||||
// trace-duration derivation, boil pose tables, all eases/durations
|
||||
};
|
||||
var SEED = 8; // locked — determinism
|
||||
var DUR = 6;
|
||||
|
||||
function clamp(v, lo, hi) {
|
||||
return Math.min(hi, Math.max(lo, v));
|
||||
}
|
||||
var OUT = clamp(DUR * 0.06, 0.25, 0.6);
|
||||
|
||||
/* ---- hw family runtime (copy identical across hw items) ---- */
|
||||
window.hwOnUpdate = function (tl, fn) {
|
||||
if (!tl.__hwRenders) {
|
||||
tl.__hwRenders = [];
|
||||
tl.eventCallback("onUpdate", function () {
|
||||
for (var i = 0; i < tl.__hwRenders.length; i++) tl.__hwRenders[i]();
|
||||
});
|
||||
}
|
||||
tl.__hwRenders.push(fn);
|
||||
fn();
|
||||
};
|
||||
window.hwHash = function (n, seed) {
|
||||
var x = Math.sin(n * 127.1 + (seed || 1) * 311.7) * 43758.5453;
|
||||
return (x - Math.floor(x)) * 2 - 1;
|
||||
};
|
||||
window.hwBoil = function (tl, target, opts) {
|
||||
opts = opts || {};
|
||||
var amp = opts.amp !== undefined ? opts.amp : 1.6;
|
||||
var rot = opts.rot !== undefined ? opts.rot : 0.5;
|
||||
var fps = opts.fps || 30;
|
||||
var drop = opts.frameDrop || 3;
|
||||
var seed = opts.seed || 1;
|
||||
var els = gsap.utils.toArray(target);
|
||||
window.hwOnUpdate(tl, function () {
|
||||
var step = Math.floor((tl.time() * fps) / drop);
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
gsap.set(els[i], {
|
||||
x: window.hwHash(step * 3 + i * 97, seed) * amp,
|
||||
y: window.hwHash(step * 3 + 1 + i * 97, seed) * amp,
|
||||
rotation: (opts.baseRot || 0) + window.hwHash(step * 3 + 2 + i * 97, seed) * rot,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
/* ---- pen-velocity ease: pure function of the path geometry ----
|
||||
Sample 25–75 curvature-adaptive points, speed ∝ 1/(1+k·curvature)
|
||||
floored at 0.22 (a pen slows into curves, never stalls), integrate
|
||||
ds/speed, invert to a normalized time→distance ease. Seek-safe:
|
||||
no per-frame state. */
|
||||
window.hwPenEase = function (pathEl, opts) {
|
||||
opts = opts || {};
|
||||
var kCurve = opts.kCurve !== undefined ? opts.kCurve : 10;
|
||||
var L = pathEl.getTotalLength();
|
||||
if (L <= 0)
|
||||
return {
|
||||
ease: function (t) {
|
||||
return t;
|
||||
},
|
||||
samples: 2,
|
||||
inkTime: 1,
|
||||
len: 0,
|
||||
};
|
||||
var PRE = 48;
|
||||
var pts = [];
|
||||
for (var i = 0; i <= PRE; i++) pts.push(pathEl.getPointAtLength((L * i) / PRE));
|
||||
var curv = [0];
|
||||
for (i = 1; i < PRE; i++) {
|
||||
var ax = pts[i].x - pts[i - 1].x,
|
||||
ay = pts[i].y - pts[i - 1].y;
|
||||
var bx = pts[i + 1].x - pts[i].x,
|
||||
by = pts[i + 1].y - pts[i].y;
|
||||
var la = Math.hypot(ax, ay) || 1e-6,
|
||||
lb = Math.hypot(bx, by) || 1e-6;
|
||||
var dot = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
|
||||
curv.push(Math.acos(dot) / ((la + lb) / 2));
|
||||
}
|
||||
curv.push(0);
|
||||
var mass = 0;
|
||||
for (i = 0; i < curv.length; i++) mass += curv[i];
|
||||
var N = Math.max(25, Math.min(75, Math.round(25 + mass * 18)));
|
||||
var seg = L / N;
|
||||
var times = [0];
|
||||
var t = 0;
|
||||
for (i = 1; i <= N; i++) {
|
||||
var u = ((i - 0.5) / N) * PRE;
|
||||
var i0 = Math.floor(u),
|
||||
f = u - i0;
|
||||
var c = curv[Math.min(i0, PRE)] * (1 - f) + curv[Math.min(i0 + 1, PRE)] * f;
|
||||
var speed = Math.max(1 / (1 + kCurve * c * 14), 0.22);
|
||||
t += seg / speed;
|
||||
times.push(t);
|
||||
}
|
||||
var total = times[N];
|
||||
for (i = 0; i <= N; i++) times[i] /= total;
|
||||
var easeFn = function (p) {
|
||||
if (p <= 0) return 0;
|
||||
if (p >= 1) return 1;
|
||||
var lo = 0,
|
||||
hi = N;
|
||||
while (hi - lo > 1) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
if (times[mid] <= p) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
var span = times[hi] - times[lo] || 1e-9;
|
||||
return (lo + (p - times[lo]) / span) / N;
|
||||
};
|
||||
return { ease: easeFn, samples: N, inkTime: total, len: L };
|
||||
};
|
||||
|
||||
/* ---- word assembly from the baked table ----
|
||||
Each glyph: <mask> of centerline strokes (stroke-width = the glyph's
|
||||
baked stem × 1.35) over the filled outline. Characters missing from
|
||||
the table fall back to live Caveat text revealed by fade (+ console
|
||||
warning) — the bundled face exists for exactly this path. */
|
||||
function buildWord(svg, text) {
|
||||
// everything in GLYPH UNITS (upem 1000, y-down, baseline 0) — the
|
||||
// svg viewBox maps units → px, no scale transform needed
|
||||
var table = window.__hwPenCaveat;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
// scheme control: bounded lookup — unknown → default + console error
|
||||
var schemes = { chalk: "var(--hw-ink)", ink: "var(--hw-ink-dark)" };
|
||||
if (!(CONFIG.scheme in schemes)) {
|
||||
console.error('hw: unknown scheme "' + CONFIG.scheme + '" — falling back to "chalk"');
|
||||
}
|
||||
var ink = schemes[CONFIG.scheme] || schemes.chalk;
|
||||
var g = document.createElementNS(ns, "g");
|
||||
var x = 0;
|
||||
var strokes = [];
|
||||
var fallbacks = [];
|
||||
var gi = 0;
|
||||
for (var ci = 0; ci < text.length; ci++) {
|
||||
var ch = text[ci];
|
||||
var gl = table.glyphs[ch];
|
||||
if (ch === " ") {
|
||||
x += 340;
|
||||
continue;
|
||||
}
|
||||
if (!gl) {
|
||||
console.warn(
|
||||
'hw-write-title: "' + ch + '" not in the baked table — Caveat fallback fade',
|
||||
);
|
||||
var ft = document.createElementNS(ns, "text");
|
||||
ft.setAttribute("x", x);
|
||||
ft.setAttribute("y", 0);
|
||||
ft.setAttribute("class", "hw-wt-fallback");
|
||||
ft.setAttribute("font-size", "1000");
|
||||
ft.setAttribute("fill", ink);
|
||||
ft.textContent = ch;
|
||||
g.appendChild(ft);
|
||||
fallbacks.push(ft);
|
||||
x += 450;
|
||||
continue;
|
||||
}
|
||||
var gg = document.createElementNS(ns, "g");
|
||||
gg.setAttribute("transform", "translate(" + x + ",0)");
|
||||
var maskId = "hw-wt-m" + gi;
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", maskId);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
mask.setAttribute("x", "-200");
|
||||
mask.setAttribute("y", "-900");
|
||||
mask.setAttribute("width", gl.adv + 400);
|
||||
mask.setAttribute("height", "1400");
|
||||
for (var si = 0; si < gl.strokes.length; si++) {
|
||||
var mp = document.createElementNS(ns, "path");
|
||||
mp.setAttribute("d", gl.strokes[si]);
|
||||
mp.setAttribute("fill", "none");
|
||||
mp.setAttribute("stroke", "#fff");
|
||||
mp.setAttribute("stroke-width", Math.max(30, Math.round(gl.stem * 1.35)));
|
||||
mp.setAttribute("stroke-linecap", "round");
|
||||
mp.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(mp);
|
||||
strokes.push({ path: mp, glyph: gi });
|
||||
}
|
||||
gg.appendChild(mask);
|
||||
var op = document.createElementNS(ns, "path");
|
||||
op.setAttribute("d", gl.outline);
|
||||
op.setAttribute("fill", ink);
|
||||
op.setAttribute("mask", "url(#" + maskId + ")");
|
||||
gg.appendChild(op);
|
||||
g.appendChild(gg);
|
||||
x += gl.adv;
|
||||
gi++;
|
||||
}
|
||||
svg.appendChild(g);
|
||||
return { el: g, widthUnits: x, strokes: strokes, fallbacks: fallbacks };
|
||||
}
|
||||
|
||||
/* ---- trace choreography: per-stroke reveals, pen-velocity eases,
|
||||
60ms pen lifts; duration DERIVED from stroke count (writeOn owns
|
||||
it — never exposed). One dispatcher-registered render applies the
|
||||
dashoffsets. */
|
||||
function writeOnTrace(tl, built, at) {
|
||||
var strokes = built.strokes;
|
||||
if (!strokes.length) return at;
|
||||
var lift = 0.06;
|
||||
var metas = [];
|
||||
var inkSum = 0;
|
||||
for (var i = 0; i < strokes.length; i++) {
|
||||
var pe = window.hwPenEase(strokes[i].path);
|
||||
var len = strokes[i].path.getTotalLength();
|
||||
metas.push({ pe: pe, len: len, st: { p: 0 } });
|
||||
inkSum += pe.inkTime;
|
||||
strokes[i].path.setAttribute("stroke-dasharray", len + " " + len);
|
||||
strokes[i].path.setAttribute("stroke-dashoffset", len);
|
||||
strokes[i].path.style.opacity = "0"; // no round-cap nub pre-draw
|
||||
}
|
||||
var dur = clamp(0.55 + 0.22 * strokes.length, 1.2, 4);
|
||||
var drawTotal = Math.max(0.1, dur - lift * (strokes.length - 1));
|
||||
var t = at;
|
||||
for (i = 0; i < metas.length; i++) {
|
||||
var d = (metas[i].pe.inkTime / inkSum) * drawTotal;
|
||||
metas[i].d = d;
|
||||
tl.to(metas[i].st, { p: 1, duration: d, ease: metas[i].pe.ease }, t);
|
||||
t += d + lift;
|
||||
}
|
||||
window.hwOnUpdate(tl, function () {
|
||||
for (var i = 0; i < metas.length; i++) {
|
||||
var m = metas[i];
|
||||
strokes[i].path.style.strokeDashoffset = m.len * (1 - m.st.p);
|
||||
strokes[i].path.style.opacity = m.st.p > 0 ? "1" : "0";
|
||||
}
|
||||
});
|
||||
for (i = 0; i < built.fallbacks.length; i++) {
|
||||
gsap.set(built.fallbacks[i], { opacity: 0 });
|
||||
tl.to(
|
||||
built.fallbacks[i],
|
||||
{ opacity: 1, duration: 0.4, ease: "power2.out" },
|
||||
at + dur * 0.5,
|
||||
);
|
||||
}
|
||||
return t - lift;
|
||||
}
|
||||
|
||||
/* writeOn pose control — unknown → "trace" + console error */
|
||||
function writeOnPose(tl, built, at, pose) {
|
||||
if (pose !== "trace" && pose !== "reveal" && pose !== "off") {
|
||||
console.error('hw: unknown writeOn pose "' + pose + '" — falling back to "trace"');
|
||||
pose = "trace";
|
||||
}
|
||||
var all = built.strokes
|
||||
.map(function (s) {
|
||||
return s.path;
|
||||
})
|
||||
.concat(built.fallbacks);
|
||||
if (pose === "off") {
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
all[i].style.opacity = "0";
|
||||
tl.set(all[i], { opacity: 1 }, at);
|
||||
}
|
||||
return at;
|
||||
}
|
||||
if (pose === "reveal") {
|
||||
// per-glyph staggered fade (the family sweep grammar); derived timing
|
||||
var byGlyph = {};
|
||||
for (var j = 0; j < built.strokes.length; j++) {
|
||||
(byGlyph[built.strokes[j].glyph] = byGlyph[built.strokes[j].glyph] || []).push(
|
||||
built.strokes[j].path,
|
||||
);
|
||||
}
|
||||
var keys = Object.keys(byGlyph);
|
||||
for (var k = 0; k < keys.length; k++) {
|
||||
var paths = byGlyph[keys[k]];
|
||||
for (var m = 0; m < paths.length; m++) {
|
||||
gsap.set(paths[m], { opacity: 0 });
|
||||
tl.to(paths[m], { opacity: 1, duration: 0.3, ease: "power2.out" }, at + k * 0.12);
|
||||
}
|
||||
}
|
||||
for (var f = 0; f < built.fallbacks.length; f++) {
|
||||
gsap.set(built.fallbacks[f], { opacity: 0 });
|
||||
tl.to(
|
||||
built.fallbacks[f],
|
||||
{ opacity: 1, duration: 0.3, ease: "power2.out" },
|
||||
at + keys.length * 0.06,
|
||||
);
|
||||
}
|
||||
return at + keys.length * 0.12 + 0.3;
|
||||
}
|
||||
return writeOnTrace(tl, built, at);
|
||||
}
|
||||
|
||||
/* ---- build ---- */
|
||||
var table = window.__hwPenCaveat;
|
||||
var scalePx = CONFIG.fontSize / 1000;
|
||||
var svg = document.getElementById("hw-wt-svg");
|
||||
var built = buildWord(svg, CONFIG.text);
|
||||
var wUnits = built.widthUnits;
|
||||
var hUnits = table.meta.ascent - table.meta.descent;
|
||||
var wPx = wUnits * scalePx;
|
||||
var hPx = hUnits * scalePx;
|
||||
svg.setAttribute("width", Math.ceil(wPx));
|
||||
svg.setAttribute("height", Math.ceil(hPx));
|
||||
svg.setAttribute("viewBox", "0 " + -table.meta.ascent + " " + wUnits + " " + hUnits);
|
||||
|
||||
var wrap = document.getElementById("hw-wt-wrap");
|
||||
var move = document.getElementById("hw-wt-move");
|
||||
move.style.position = "absolute";
|
||||
move.style.left = (CONFIG.x === null ? Math.round((1920 - wPx) / 2) : CONFIG.x) + "px";
|
||||
move.style.top = (CONFIG.y === null ? Math.round((1080 - hPx) / 2) : CONFIG.y) + "px";
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
var startAt = CONFIG.animationIn ? 0.35 : 0;
|
||||
var writeEnd = startAt;
|
||||
if (CONFIG.animationIn) {
|
||||
writeEnd = writeOnPose(tl, built, startAt, CONFIG.writeOn);
|
||||
} else {
|
||||
// hard-on pose regardless of writeOn (animationIn=false = instant-on)
|
||||
writeEnd = writeOnPose(tl, built, 0, "off");
|
||||
}
|
||||
|
||||
/* squiggle underline (accent), drawn as the write lands */
|
||||
var underPath = document.getElementById("hw-wt-under-path");
|
||||
if (CONFIG.underline) {
|
||||
var uw = Math.round(wPx);
|
||||
var usvg = document.getElementById("hw-wt-under");
|
||||
usvg.setAttribute("width", uw);
|
||||
usvg.setAttribute("viewBox", "0 0 " + uw + " 40");
|
||||
var segs = Math.max(6, Math.round(uw / 90));
|
||||
var d = "M 2 " + (18 + window.hwHash(0, SEED) * 4).toFixed(1);
|
||||
for (var i = 1; i <= segs; i++) {
|
||||
var x2 = (uw / segs) * i;
|
||||
var y2 = 18 + window.hwHash(i * 5 + 1, SEED) * 6;
|
||||
var cxp = x2 - uw / segs / 2 + window.hwHash(i * 5 + 2, SEED) * 10;
|
||||
var cyp = 18 + (i % 2 ? -1 : 1) * (9 + window.hwHash(i * 5 + 3, SEED) * 4);
|
||||
d +=
|
||||
" Q " +
|
||||
cxp.toFixed(1) +
|
||||
" " +
|
||||
cyp.toFixed(1) +
|
||||
" " +
|
||||
x2.toFixed(1) +
|
||||
" " +
|
||||
y2.toFixed(1);
|
||||
}
|
||||
underPath.setAttribute("d", d);
|
||||
var ulen = underPath.getTotalLength();
|
||||
gsap.set(underPath, {
|
||||
strokeDasharray: ulen + " " + ulen,
|
||||
strokeDashoffset: ulen,
|
||||
opacity: 0,
|
||||
});
|
||||
var uAt = Math.min(writeEnd + 0.1, DUR - 1.2);
|
||||
tl.set(underPath, { opacity: 1 }, uAt);
|
||||
tl.to(underPath, { strokeDashoffset: 0, duration: 0.7, ease: "power2.inOut" }, uAt);
|
||||
} else {
|
||||
document.getElementById("hw-wt-under").style.display = "none";
|
||||
}
|
||||
|
||||
/* the boil rides its own wrapper; entrances/exits ride #hw-wt-move */
|
||||
window.hwBoilPose(tl, "#hw-wt-boil", CONFIG.boil, { seed: SEED });
|
||||
|
||||
if (CONFIG.animationOut) {
|
||||
tl.to(move, { opacity: 0, duration: OUT, ease: "power2.in" }, DUR - OUT - 0.05);
|
||||
}
|
||||
tl.set("#hw-wt-root", { visibility: "hidden" }, DUR - 0.02);
|
||||
|
||||
window.__timelines["hw-write-title"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
{/* hf:generated-footer */}
|
||||
|
||||
Tagged `handwritten` `typography` `title-card` `write-on` `overlay`.
|
||||
|
||||
## Related topics
|
||||
|
||||
- [Browse the complete Catalog](/catalog)
|
||||
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
|
||||
- [Build a richer composition](/go-further)
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Hand-Drawn Arrow"
|
||||
description: "Wobbled draw-on arrow with straight, gentle, and swoop curve presets; boils while on screen"
|
||||
description: "Wobbled draw-on arrow with straight, gentle, and swoop curve poses, a four-way stroke texture matrix (plain/soft/sharp/spray), and a travel-aligned head-arrival stretch pulse with spring recovery; boils while on screen"
|
||||
---
|
||||
|
||||
import { InstallCommand } from "/snippets/install-command.jsx";
|
||||
@@ -54,9 +54,12 @@ you paste it into.
|
||||
overflow: hidden;
|
||||
}
|
||||
/* ---- hw-arrow ------------------------------------------------------
|
||||
Hand-wobbled draw-on arrow, boiling. Position the wrapper, pick a
|
||||
curve preset, call the helpers. Requires the hw-boil helpers
|
||||
(hwOnUpdate / hwHash / hwBoil) in the host script. */
|
||||
Hand-wobbled draw-on arrow, boiling, with a travel-aligned
|
||||
arrival stretch on the head. Position the wrapper, build with a
|
||||
CONFIG (arrowStyle / strokeType / boil), wire with a position
|
||||
only: hwArrowBuild + hwArrowOn / hwArrowOff. Requires the hw
|
||||
family helpers below (hwOnUpdate / hwHash / hwBoil / hwBoilPose /
|
||||
springEase / hwStrokeApply / hwDrawOn) in the host script. */
|
||||
.hw-arrow {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
@@ -92,9 +95,25 @@ you paste it into.
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Self-preview. WIRING: copy the .hw-arrow CSS, the markup pattern,
|
||||
and the hwArrowPath / hwArrowOn / hwArrowOff helpers (plus the
|
||||
hw-boil helpers) into your host. Then:
|
||||
<!-- Self-preview. WIRING (g09): copy the .hw-arrow CSS, the markup
|
||||
pattern (positioned wrapper + empty svg with a viewBox), and the
|
||||
helpers into your host. Build with a CONFIG, wire with a position
|
||||
only — the helper owns its durations, eases, and seeds:
|
||||
|
||||
hwArrowBuild("#hw-my-arrow", {
|
||||
arrowStyle: "swoop", // "straight" | "gentle" | "swoop"
|
||||
strokeType: "spray", // "plain" | "soft" | "sharp" | "spray"
|
||||
boil: "calm", // "off" | "calm" | "lively"
|
||||
});
|
||||
hwArrowOn(tl, "#hw-my-arrow", 2.0);
|
||||
hwArrowOff(tl, "#hw-my-arrow", 8.0);
|
||||
|
||||
Each built target needs a UNIQUE id — the wobble seed and the
|
||||
sharp/spray mask id derive from it (an id-less target
|
||||
console.errors and falls back to a shared "hw-arrow" slot).
|
||||
|
||||
Legacy single-path wiring (a <path> in the svg + hwArrowPath)
|
||||
keeps working exactly as before:
|
||||
|
||||
document.querySelector("#hw-my-arrow path")
|
||||
.setAttribute("d", hwArrowPath(360, 160, "swoop", 3));
|
||||
@@ -116,7 +135,7 @@ you paste it into.
|
||||
id="hw-ar-demo"
|
||||
style="left: 560px; top: 320px; width: 380px; height: 180px"
|
||||
>
|
||||
<svg viewBox="0 0 380 180"><path id="hw-ar-demo-path"></path></svg>
|
||||
<svg viewBox="0 0 380 180"></svg>
|
||||
</div>
|
||||
<div
|
||||
id="hw-ar-drv"
|
||||
@@ -165,6 +184,248 @@ you paste it into.
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
/* ---- spring library (snappy register; factory duration consumed verbatim) ---- */
|
||||
var LN1000 = Math.log(1000);
|
||||
var FEELS = {
|
||||
snappy: { zeta: 0.9, response: 0.22 },
|
||||
};
|
||||
window.springEase = function (opts) {
|
||||
var f = opts.feel ? FEELS[opts.feel] : opts;
|
||||
var zeta = f.zeta,
|
||||
response = f.response;
|
||||
var w0 = (2 * Math.PI) / response;
|
||||
var T = LN1000 / (zeta * w0);
|
||||
var x;
|
||||
if (zeta < 1) {
|
||||
var wd = w0 * Math.sqrt(1 - zeta * zeta);
|
||||
x = function (t) {
|
||||
return (
|
||||
1 -
|
||||
Math.exp(-zeta * w0 * t) *
|
||||
(Math.cos(wd * t) + ((zeta * w0) / wd) * Math.sin(wd * t))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
x = function (t) {
|
||||
return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
||||
};
|
||||
}
|
||||
var xT = x(T);
|
||||
return {
|
||||
ease: function (p) {
|
||||
return p >= 1 ? 1 : x(p * T) / xT;
|
||||
},
|
||||
duration: T,
|
||||
zeta: zeta,
|
||||
response: response,
|
||||
};
|
||||
};
|
||||
|
||||
/* ---- pen-velocity ease: pure function of the path geometry ----
|
||||
Sample 25–75 curvature-adaptive points, speed ∝ 1/(1+k·curvature)
|
||||
floored at 0.22 (a pen slows into curves, never stalls), integrate
|
||||
ds/speed, invert to a normalized time→distance ease. Seek-safe:
|
||||
no per-frame state. */
|
||||
window.hwPenEase = function (pathEl, opts) {
|
||||
opts = opts || {};
|
||||
var kCurve = opts.kCurve !== undefined ? opts.kCurve : 10;
|
||||
var L = pathEl.getTotalLength();
|
||||
if (L <= 0)
|
||||
return {
|
||||
ease: function (t) {
|
||||
return t;
|
||||
},
|
||||
samples: 2,
|
||||
inkTime: 1,
|
||||
len: 0,
|
||||
};
|
||||
var PRE = 48;
|
||||
var pts = [];
|
||||
for (var i = 0; i <= PRE; i++) pts.push(pathEl.getPointAtLength((L * i) / PRE));
|
||||
var curv = [0];
|
||||
for (i = 1; i < PRE; i++) {
|
||||
var ax = pts[i].x - pts[i - 1].x,
|
||||
ay = pts[i].y - pts[i - 1].y;
|
||||
var bx = pts[i + 1].x - pts[i].x,
|
||||
by = pts[i + 1].y - pts[i].y;
|
||||
var la = Math.hypot(ax, ay) || 1e-6,
|
||||
lb = Math.hypot(bx, by) || 1e-6;
|
||||
var dot = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
|
||||
curv.push(Math.acos(dot) / ((la + lb) / 2));
|
||||
}
|
||||
curv.push(0);
|
||||
var mass = 0;
|
||||
for (i = 0; i < curv.length; i++) mass += curv[i];
|
||||
var N = Math.max(25, Math.min(75, Math.round(25 + mass * 18)));
|
||||
var seg = L / N;
|
||||
var times = [0];
|
||||
var t = 0;
|
||||
for (i = 1; i <= N; i++) {
|
||||
var u = ((i - 0.5) / N) * PRE;
|
||||
var i0 = Math.floor(u),
|
||||
f = u - i0;
|
||||
var c = curv[Math.min(i0, PRE)] * (1 - f) + curv[Math.min(i0 + 1, PRE)] * f;
|
||||
var speed = Math.max(1 / (1 + kCurve * c * 14), 0.22);
|
||||
t += seg / speed;
|
||||
times.push(t);
|
||||
}
|
||||
var total = times[N];
|
||||
for (i = 0; i <= N; i++) times[i] /= total;
|
||||
var easeFn = function (p) {
|
||||
if (p <= 0) return 0;
|
||||
if (p >= 1) return 1;
|
||||
var lo = 0,
|
||||
hi = N;
|
||||
while (hi - lo > 1) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
if (times[mid] <= p) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
var span = times[hi] - times[lo] || 1e-9;
|
||||
return (lo + (p - times[lo]) / span) / N;
|
||||
};
|
||||
return { ease: easeFn, samples: N, inkTime: total, len: L };
|
||||
};
|
||||
|
||||
/* ---- stroke matrix: plain | soft | sharp | spray -------------------
|
||||
strokeType is a g09 variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
therefore rides a MASK clone (the visible dasharray never animates).
|
||||
spray = index-seeded deterministic dots along the path via hwHash;
|
||||
NO feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever hwStrokeApply
|
||||
says. The draw target is always solid (plain/soft: the path itself;
|
||||
sharp/spray: the mask clone — the visible path keeps its texture
|
||||
dasharray untouched). Optionally pen-velocity eased (opts.pen). */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.pen ? window.hwPenEase(p, opts).ease : opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
// ---- hw-arrow helpers (copy these too) ----
|
||||
// Wobbled arrow path in a w x h box, tail at left, head at right.
|
||||
@@ -220,27 +481,219 @@ you paste it into.
|
||||
head(-0.5, wob(34, 10, 6))
|
||||
);
|
||||
};
|
||||
|
||||
// ---- hwArrowBuild: CONFIG-reading component builder (g09) ----
|
||||
// Builds the arrow as TWO paths (curve + head group) inside the
|
||||
// target's svg with the same wobble math and seed slots as
|
||||
// hwArrowPath, applies CONFIG.strokeType to the curve (the head
|
||||
// stays plain — texture on a ~30px head reads as noise), and rigs
|
||||
// the travel-aligned arrival wrapper: an outer g rotated to the
|
||||
// travel direction + an inner g counter-rotated. The pair cancels,
|
||||
// so geometry stays put while scaleX on the outer acts along
|
||||
// travel. NO transform-box styles on these g's — they fight GSAP's
|
||||
// baked SVG origins (the outer's fill-box would derive from its
|
||||
// rotated child and the cancellation breaks).
|
||||
// `seed` is internal — defaults per target from the element id
|
||||
// (a pure hash of the id string: deterministic across page loads);
|
||||
// it is NOT a control. Targets must carry unique ids: id-less
|
||||
// targets fall back to a shared "hw-arrow" slot (identical wobble;
|
||||
// duplicate mask ids cross-wire sharp/spray reveals), so that case
|
||||
// fails loudly (bounded-controls law).
|
||||
window.hwArrowBuild = function (target, CONFIG, seed) {
|
||||
CONFIG = CONFIG || {};
|
||||
var el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
var svg = el.querySelector("svg");
|
||||
if (!el.id)
|
||||
console.error(
|
||||
'hw: hwArrowBuild target has no id — falling back to the shared "hw-arrow" seed/mask slot; give each built target a unique id',
|
||||
);
|
||||
if (seed === undefined) {
|
||||
var idStr = el.id || "hw-arrow";
|
||||
seed = 0;
|
||||
for (var si = 0; si < idStr.length; si++)
|
||||
seed = (seed * 31 + idStr.charCodeAt(si)) % 997;
|
||||
seed = (seed % 96) + 1;
|
||||
}
|
||||
var style = CONFIG.arrowStyle === undefined ? "gentle" : CONFIG.arrowStyle;
|
||||
if (style !== "straight" && style !== "gentle" && style !== "swoop") {
|
||||
console.error('hw: unknown arrowStyle "' + style + '" — falling back to "gentle"');
|
||||
style = "gentle";
|
||||
}
|
||||
var vb = svg.viewBox.baseVal;
|
||||
var w = vb && vb.width ? vb.width : el.clientWidth;
|
||||
var h = vb && vb.height ? vb.height : el.clientHeight;
|
||||
// same wobble math + hash slots as hwArrowPath (1–8 body, 9/10 head)
|
||||
var bend = style === "swoop" ? 0.55 : style === "gentle" ? 0.28 : 0.06;
|
||||
function wob(v, n, range) {
|
||||
return v + window.hwHash(n, seed) * range;
|
||||
}
|
||||
var x0 = wob(4, 1, 3),
|
||||
y0 = wob(h * 0.72, 2, 4),
|
||||
x1 = wob(w - 10, 3, 4),
|
||||
y1 = wob(h * 0.3, 4, 4);
|
||||
var cx1 = wob(w * 0.35, 5, 10),
|
||||
cy1 = wob(h * (0.72 + bend * 0.5), 6, 8);
|
||||
var cx2 = wob(w * 0.7, 7, 10),
|
||||
cy2 = wob(h * (0.3 + bend), 8, 8);
|
||||
var ang = Math.atan2(y1 - cy2, x1 - cx2);
|
||||
function headSeg(da, len) {
|
||||
var a = ang + Math.PI + da;
|
||||
return (
|
||||
"M " +
|
||||
x1.toFixed(1) +
|
||||
" " +
|
||||
y1.toFixed(1) +
|
||||
" l " +
|
||||
(Math.cos(a) * len).toFixed(1) +
|
||||
" " +
|
||||
(Math.sin(a) * len).toFixed(1)
|
||||
);
|
||||
}
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var curve = document.createElementNS(ns, "path");
|
||||
curve.setAttribute(
|
||||
"d",
|
||||
"M " +
|
||||
x0.toFixed(1) +
|
||||
" " +
|
||||
y0.toFixed(1) +
|
||||
" C " +
|
||||
cx1.toFixed(1) +
|
||||
" " +
|
||||
cy1.toFixed(1) +
|
||||
", " +
|
||||
cx2.toFixed(1) +
|
||||
" " +
|
||||
cy2.toFixed(1) +
|
||||
", " +
|
||||
x1.toFixed(1) +
|
||||
" " +
|
||||
y1.toFixed(1),
|
||||
);
|
||||
svg.appendChild(curve);
|
||||
var headG = document.createElementNS(ns, "g");
|
||||
var headInner = document.createElementNS(ns, "g");
|
||||
var head = document.createElementNS(ns, "path");
|
||||
head.setAttribute("d", headSeg(0.5, wob(34, 9, 6)) + " " + headSeg(-0.5, wob(34, 10, 6)));
|
||||
headInner.appendChild(head);
|
||||
headG.appendChild(headInner);
|
||||
svg.appendChild(headG);
|
||||
// curve texture; mask id derived from the element id — deterministic
|
||||
// and collision-free across id-bearing instances (id-less targets
|
||||
// errored above and share the fallback)
|
||||
var applied = window.hwStrokeApply(
|
||||
curve,
|
||||
CONFIG.strokeType === undefined ? "plain" : CONFIG.strokeType,
|
||||
{ seed: seed, id: (el.id || "hw-arrow") + "-sm" },
|
||||
);
|
||||
if (applied.group) {
|
||||
// the item CSS (.hw-arrow path) outranks the mask clone's
|
||||
// presentation attributes — pin them as inline styles so the
|
||||
// reveal mask keeps its authored width and white stroke
|
||||
applied.draw.style.stroke = "#fff";
|
||||
applied.draw.style.strokeWidth = applied.draw.getAttribute("stroke-width");
|
||||
}
|
||||
// arrival rig: rotate the WRAPPER to travel, counter-rotate the
|
||||
// content — wrapper scaleX then acts along travel
|
||||
var angDeg = (ang * 180) / Math.PI;
|
||||
gsap.set(headG, { transformOrigin: "50% 50%", rotation: angDeg, opacity: 0 });
|
||||
gsap.set(headInner, { transformOrigin: "50% 50%", rotation: -angDeg });
|
||||
el.__hwArrowBuild = {
|
||||
el: el,
|
||||
applied: applied,
|
||||
headG: headG,
|
||||
headInner: headInner,
|
||||
seed: seed,
|
||||
boil: CONFIG.boil === undefined ? "calm" : CONFIG.boil,
|
||||
boiled: false,
|
||||
};
|
||||
return el.__hwArrowBuild;
|
||||
};
|
||||
|
||||
// Draw-on. Built targets (hwArrowBuild): accelerating draw (causal
|
||||
// arrival), then the head pops with the g21 travel-aligned stretch —
|
||||
// v measured numerically from the draw ease end-slope, S = min(0.06
|
||||
// × v/2000, 0.25), volume preserved (scaleX × scaleY ≈ 1, asserted),
|
||||
// 2-frame apply + snappy spring recovery (factory duration verbatim).
|
||||
// Legacy single-path targets keep the shipped dashoffset behavior.
|
||||
window.hwArrowOn = function (tl, target, at, dur) {
|
||||
var el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
var b = el && el.__hwArrowBuild;
|
||||
if (!b) {
|
||||
var p = document.querySelector(target + " path");
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: "power2.inOut" },
|
||||
{
|
||||
strokeDashoffset: 0,
|
||||
duration: dur === undefined ? 0.7 : dur,
|
||||
ease: "power2.inOut",
|
||||
},
|
||||
at,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// built targets (g09): durations are item-locked — a
|
||||
// legacy-signature `dur` is ignored + console.error (edit the item
|
||||
// source to change); the legacy branch above keeps honoring it
|
||||
if (dur !== undefined)
|
||||
console.error(
|
||||
"hw: hwArrowOn draw duration is locked for built targets — ignoring dur=" + dur,
|
||||
);
|
||||
dur = 0.7;
|
||||
// the pen ACCELERATES into the head (power2.in) so the measured
|
||||
// arrival velocity is real — a settling ease ends at ~zero slope
|
||||
// and would make the stretch invisible
|
||||
var drawEase = gsap.parseEase("power2.in");
|
||||
window.hwDrawOn(tl, b.applied, at, dur, { ease: drawEase });
|
||||
var dlen = b.applied.draw.getTotalLength();
|
||||
var eps = 1e-4;
|
||||
var v = ((drawEase(1) - drawEase(1 - eps)) / eps) * (dlen / dur);
|
||||
var S = Math.min(0.06 * (v / 2000), 0.25);
|
||||
var rec = window.springEase({ feel: "snappy" });
|
||||
var FPS = 30;
|
||||
var st = { s: 0 };
|
||||
var hg = b.headG;
|
||||
window.hwOnUpdate(tl, function () {
|
||||
// stretch ALONG travel (the wrapper's local x after rotation)
|
||||
var along = 1 + st.s;
|
||||
gsap.set(hg, { scaleX: along, scaleY: 1 / along });
|
||||
var ax = parseFloat(gsap.getProperty(hg, "scaleX"));
|
||||
var ay = parseFloat(gsap.getProperty(hg, "scaleY"));
|
||||
if (Math.abs(ax * ay - 1) > 0.01)
|
||||
console.error("hw volume law violated (arrowhead): " + (ax * ay).toFixed(4));
|
||||
});
|
||||
tl.set(hg, { opacity: 1 }, at + dur);
|
||||
tl.to(st, { s: S, duration: 2 / FPS, ease: "power3.out" }, at + dur);
|
||||
tl.to(st, { s: 0, duration: rec.duration, ease: rec.ease }, at + dur + 3 / FPS);
|
||||
// boil pose (recorded from CONFIG at build; hwBoil needs the
|
||||
// timeline, so it registers on first wiring). Boil owns x/y/rotation
|
||||
// of the WRAPPER element — the arrival stretch lives on the nested
|
||||
// head rig, never on the boiled element.
|
||||
if (!b.boiled) {
|
||||
b.boiled = true;
|
||||
window.hwBoilPose(tl, el, b.boil, { seed: b.seed });
|
||||
}
|
||||
};
|
||||
window.hwArrowOff = function (tl, target, at) {
|
||||
tl.to(target, { opacity: 0, duration: 0.3, ease: "power2.in" }, at);
|
||||
};
|
||||
|
||||
// ---- self-preview ----
|
||||
document
|
||||
.getElementById("hw-ar-demo-path")
|
||||
.setAttribute("d", window.hwArrowPath(380, 180, "swoop", 7));
|
||||
var CONFIG = {
|
||||
// ── Controls (family base + 2 item-specific; comment grammar is the published-parameter list) ──
|
||||
arrowStyle: "gentle", // variant: "straight" | "gentle" | "swoop" — curve bend pose over the seeded wobble math (bend + both control points)
|
||||
strokeType: "plain", // variant: "plain" | "soft" | "sharp" | "spray" — curve texture via hwStrokeApply (sharp/spray draw on a mask clone); head stays plain
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3; seed locked
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// seed (derived per target), draw ease (power2.in), arrival
|
||||
// softness 0.06, snappy spring recovery, durations
|
||||
};
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.hwArrowBuild("#hw-ar-demo", CONFIG);
|
||||
window.hwArrowOn(tl, "#hw-ar-demo", 0.4);
|
||||
window.hwBoil(tl, "#hw-ar-demo", { amp: 2, rot: 0.6, frameDrop: 3, seed: 7 });
|
||||
window.hwArrowOff(tl, "#hw-ar-demo", 3.4);
|
||||
tl.set("#hw-ar-root", { visibility: "hidden" }, 3.98);
|
||||
window.__timelines["hw-arrow"] = tl;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Hand-Drawn Boil"
|
||||
description: "Hand-drawn jitter that re-poses every N frames, as seek-safe helpers: pose derives from quantized timeline time through a seeded hash; includes the shared onUpdate dispatcher and a wobbled-ellipse path generator"
|
||||
description: "Hand-drawn jitter that re-poses every N frames, as seek-safe helpers: pose derives from quantized timeline time through a seeded hash; includes the shared onUpdate dispatcher, a wobbled-ellipse path generator, the boil pose control (off/calm/lively over the family-locked frameDrop), a curvature-adaptive pen-velocity ease, and the completed stroke matrix (plain/soft/sharp/spray — spray is index-seeded deterministic dots, no turbulence) with texture-safe draw-on"
|
||||
---
|
||||
|
||||
import { InstallCommand } from "/snippets/install-command.jsx";
|
||||
@@ -61,14 +61,28 @@ you paste it into.
|
||||
The hand-drawn "boil": jitter that re-poses only every N frames
|
||||
(quake + frame-drop style). Pose is a pure function of quantized
|
||||
timeline time through a seeded hash — fully seek-safe, renders
|
||||
identically on every pass over the same frame.
|
||||
identically on every pass over the same frame. Ships the control
|
||||
layer on top of the raw mechanic: hwBoilPose (the "boil" control —
|
||||
authored amp/rot poses over the family-locked frameDrop), the
|
||||
stroke matrix (hwStrokeTypes / hwStrokeApply / hwDrawOn — the
|
||||
"strokeType" control: plain | soft | sharp | spray; spray is
|
||||
index-seeded deterministic dots, NO feTurbulence anywhere) and the
|
||||
curvature-adaptive pen-velocity ease (hwPenEase).
|
||||
|
||||
RULES:
|
||||
- hwBoil owns x/y/rotation of the boiled element. Entrance/exit
|
||||
tweens go on a WRAPPER around it, never the boiled element.
|
||||
- hwBoil owns x/y/rotation of the boiled element.
|
||||
Transform-touching entrance/exit tweens go on a WRAPPER
|
||||
around it, never the boiled element (non-transform tweens
|
||||
like opacity don't contend and may ride it).
|
||||
- One timeline has ONE onUpdate — hwOnUpdate is the dispatcher
|
||||
that lets the boil coexist with sweep scalars etc. Register
|
||||
every onUpdate-style render through it. */
|
||||
every onUpdate-style render through it.
|
||||
- Never boil an element carrying an SVG transform attribute —
|
||||
nest a positioning <g> (or div) and boil the clean inner group.
|
||||
- A textured path (sharp/spray) lives in its OWN svg inside the
|
||||
boiled wrapper, so the stroke-matrix mask in that svg's defs
|
||||
rides the boil with it (a mask left in a shared static svg
|
||||
would clip the texture at jitter extremes). */
|
||||
#hw-boil-root {
|
||||
--hw-font-print: "Caveat", cursive;
|
||||
--hw-font-script: "Caveat", cursive;
|
||||
@@ -112,7 +126,7 @@ you paste it into.
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
letter-spacing: 0.12em;
|
||||
color: rgba(244, 242, 236, 0.45);
|
||||
color: rgba(244, 242, 236, 0.62);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -122,7 +136,26 @@ you paste it into.
|
||||
|
||||
hwBoil(tl, "#hw-my-annotation", { amp: 1.6, rot: 0.5, frameDrop: 3, seed: 2 });
|
||||
|
||||
frameDrop 3-4 = classic boil; 1 = frantic; boil: off = don't call it. -->
|
||||
frameDrop 3-4 = classic boil; 1 = frantic; boil: off = don't call it.
|
||||
|
||||
Control layer (copy alongside): hwBoilPose maps the "boil" control
|
||||
("off" | "calm" | "lively") to authored amp/rot pairs over the
|
||||
family-locked frameDrop 3 —
|
||||
|
||||
hwBoilPose(tl, "#hw-my-annotation", CONFIG.boil, { seed: 2 });
|
||||
|
||||
The stroke matrix (hwStrokeTypes / hwStrokeApply / hwDrawOn, plus
|
||||
hwPenEase) maps the "strokeType" control ("plain" | "soft" |
|
||||
"sharp" | "spray") to authored textures. sharp/spray return a mask
|
||||
clone in applied.draw and hwDrawOn animates THAT (the visible
|
||||
texture never animates); { pen: true } rides the curvature-adaptive
|
||||
pen-velocity ease —
|
||||
|
||||
var applied = hwStrokeApply(pathEl, CONFIG.strokeType, { seed: 4, id: "my-mask" });
|
||||
hwDrawOn(tl, applied, 0.3, 0.8, { pen: true });
|
||||
|
||||
Unknown control values fall back to the declared default with a
|
||||
console error (bounded-controls law). -->
|
||||
<div
|
||||
id="hw-boil-root"
|
||||
data-composition-id="hw-boil"
|
||||
@@ -132,7 +165,6 @@ you paste it into.
|
||||
data-height="1080"
|
||||
>
|
||||
<svg viewBox="0 0 1920 1080">
|
||||
<g id="hw-b-circle-g"><path id="hw-b-circle"></path></g>
|
||||
<g id="hw-b-arrow-g">
|
||||
<path
|
||||
id="hw-b-arrow"
|
||||
@@ -140,9 +172,23 @@ you paste it into.
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
<!-- the CONFIG element: own svg inside the boiled div — the
|
||||
stroke-matrix mask (in this svg's defs) rides the boil with it -->
|
||||
<div
|
||||
id="hw-b-circle-w"
|
||||
style="position: absolute; left: 250px; top: 470px; width: 420px; height: 300px"
|
||||
>
|
||||
<div id="hw-b-circle-g" style="position: absolute; inset: 0">
|
||||
<svg viewBox="0 0 420 300" style="overflow: visible">
|
||||
<path id="hw-b-circle"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hw-b-label" id="hw-b-word" style="left: 1230px; top: 560px">the boil</div>
|
||||
<div class="hw-b-tag" style="left: 350px; top: 782px">FRAME DROP 3</div>
|
||||
<div class="hw-b-tag" style="left: 700px; top: 782px">FRAME DROP 1</div>
|
||||
<div class="hw-b-tag" id="hw-b-tag-config" style="left: 350px; top: 782px">
|
||||
BOIL "CALM" · STROKE "PLAIN"
|
||||
</div>
|
||||
<div class="hw-b-tag" style="left: 940px; top: 782px">FRAME DROP 1</div>
|
||||
<div class="hw-b-tag" style="left: 1230px; top: 700px">TEXT, AMP 1.2</div>
|
||||
<div
|
||||
id="hw-b-drv"
|
||||
@@ -157,6 +203,16 @@ you paste it into.
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
var CONFIG = {
|
||||
// ── Controls (family base + 1 item-specific; comment grammar is the published-parameter list) ──
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3, applied to the demo circle; seed locked (family base)
|
||||
strokeType: "plain", // variant: "plain" | "soft" | "sharp" | "spray" — authored stroke texture on the demo circle (soft = blur; sharp = micro-dash dry marker; spray = seeded deterministic dots); draw-on rides the mask clone for textured types (4 params)
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// seeds, draw durations/eases, pen-velocity constants (kCurve 10,
|
||||
// floor 0.22), boil pose tables, frameDrop values, demo geometry
|
||||
};
|
||||
|
||||
// ---- copy these helpers into the host script ----
|
||||
|
||||
// Dispatcher: a GSAP timeline has ONE onUpdate. Register every
|
||||
@@ -201,6 +257,21 @@ you paste it into.
|
||||
});
|
||||
};
|
||||
|
||||
// The boil CONTROL: authored amp/rot poses over the family-locked
|
||||
// frameDrop 3; seed stays locked. Unknown pose → "calm" + console
|
||||
// error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
// Seeded hand-wobbled ellipse path (smooth quadratic chain).
|
||||
window.hwWobbleEllipse = function (cx, cy, rx, ry, seed, wobblePct) {
|
||||
wobblePct = wobblePct === undefined ? 3 : wobblePct;
|
||||
@@ -230,23 +301,230 @@ you paste it into.
|
||||
return d;
|
||||
};
|
||||
|
||||
/* ---- pen-velocity ease: pure function of the path geometry ----
|
||||
Sample 25–75 curvature-adaptive points, speed ∝ 1/(1+k·curvature)
|
||||
floored at 0.22 (a pen slows into curves, never stalls), integrate
|
||||
ds/speed, invert to a normalized time→distance ease. Seek-safe:
|
||||
no per-frame state. */
|
||||
window.hwPenEase = function (pathEl, opts) {
|
||||
opts = opts || {};
|
||||
var kCurve = opts.kCurve !== undefined ? opts.kCurve : 10;
|
||||
var L = pathEl.getTotalLength();
|
||||
if (L <= 0)
|
||||
return {
|
||||
ease: function (t) {
|
||||
return t;
|
||||
},
|
||||
samples: 2,
|
||||
inkTime: 1,
|
||||
len: 0,
|
||||
};
|
||||
var PRE = 48;
|
||||
var pts = [];
|
||||
for (var i = 0; i <= PRE; i++) pts.push(pathEl.getPointAtLength((L * i) / PRE));
|
||||
var curv = [0];
|
||||
for (i = 1; i < PRE; i++) {
|
||||
var ax = pts[i].x - pts[i - 1].x,
|
||||
ay = pts[i].y - pts[i - 1].y;
|
||||
var bx = pts[i + 1].x - pts[i].x,
|
||||
by = pts[i + 1].y - pts[i].y;
|
||||
var la = Math.hypot(ax, ay) || 1e-6,
|
||||
lb = Math.hypot(bx, by) || 1e-6;
|
||||
var dot = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
|
||||
curv.push(Math.acos(dot) / ((la + lb) / 2));
|
||||
}
|
||||
curv.push(0);
|
||||
var mass = 0;
|
||||
for (i = 0; i < curv.length; i++) mass += curv[i];
|
||||
var N = Math.max(25, Math.min(75, Math.round(25 + mass * 18)));
|
||||
var seg = L / N;
|
||||
var times = [0];
|
||||
var t = 0;
|
||||
for (i = 1; i <= N; i++) {
|
||||
var u = ((i - 0.5) / N) * PRE;
|
||||
var i0 = Math.floor(u),
|
||||
f = u - i0;
|
||||
var c = curv[Math.min(i0, PRE)] * (1 - f) + curv[Math.min(i0 + 1, PRE)] * f;
|
||||
var speed = Math.max(1 / (1 + kCurve * c * 14), 0.22);
|
||||
t += seg / speed;
|
||||
times.push(t);
|
||||
}
|
||||
var total = times[N];
|
||||
for (i = 0; i <= N; i++) times[i] /= total;
|
||||
var easeFn = function (p) {
|
||||
if (p <= 0) return 0;
|
||||
if (p >= 1) return 1;
|
||||
var lo = 0,
|
||||
hi = N;
|
||||
while (hi - lo > 1) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
if (times[mid] <= p) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
var span = times[hi] - times[lo] || 1e-9;
|
||||
return (lo + (p - times[lo]) / span) / N;
|
||||
};
|
||||
return { ease: easeFn, samples: N, inkTime: total, len: L };
|
||||
};
|
||||
|
||||
/* ---- stroke matrix: plain | soft | sharp | spray ------------------
|
||||
strokeType is a variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
must therefore ride a MASK clone (the visible dasharray never
|
||||
animates). spray = index-seeded deterministic dots along the path
|
||||
via hwHash: position a pure function of path parameter + seed; NO
|
||||
feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever
|
||||
hwStrokeApply says. Optionally pen-velocity eased (opts.pen: true). */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.pen ? window.hwPenEase(p, opts).ease : opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
// ---- self-preview (not part of the snippet you copy) ----
|
||||
var circle = document.getElementById("hw-b-circle");
|
||||
circle.setAttribute("d", window.hwWobbleEllipse(460, 620, 190, 130, 4, 3.2));
|
||||
circle.setAttribute("d", window.hwWobbleEllipse(210, 150, 190, 130, 4, 3.2));
|
||||
|
||||
document.getElementById("hw-b-tag-config").textContent = (
|
||||
'boil "' +
|
||||
CONFIG.boil +
|
||||
'" · stroke "' +
|
||||
CONFIG.strokeType +
|
||||
'"'
|
||||
).toUpperCase();
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
/* draw-on entrances (strokeDashoffset — never x/y, the boil owns those) */
|
||||
[circle, document.getElementById("hw-b-arrow")].forEach(function (p, i) {
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
tl.to(p, { strokeDashoffset: 0, duration: 0.8, ease: "power2.inOut" }, 0.3 + i * 0.35);
|
||||
});
|
||||
/* the CONFIG circle rides the stroke matrix: texture from
|
||||
CONFIG.strokeType, draw-on through the mask-safe hwDrawOn with the
|
||||
pen-velocity ease (strokeDashoffset — never x/y, the boil owns those) */
|
||||
var applied = window.hwStrokeApply(circle, CONFIG.strokeType, { seed: 4, id: "hw-b-mask" });
|
||||
window.hwDrawOn(tl, applied, 0.3, 0.8, { pen: true });
|
||||
|
||||
/* raw draw-on entrance for the arrow (not routed through the matrix) */
|
||||
var arrow = document.getElementById("hw-b-arrow");
|
||||
var alen = arrow.getTotalLength();
|
||||
gsap.set(arrow, { strokeDasharray: alen, strokeDashoffset: alen });
|
||||
tl.to(arrow, { strokeDashoffset: 0, duration: 0.8, ease: "power2.inOut" }, 0.65);
|
||||
|
||||
gsap.set("#hw-b-word", { opacity: 0 });
|
||||
tl.to("#hw-b-word", { opacity: 1, duration: 0.5, ease: "power2.out" }, 1.1);
|
||||
|
||||
/* three boils with different characters, one dispatcher */
|
||||
window.hwBoil(tl, "#hw-b-circle-g", { amp: 2.2, rot: 0.4, frameDrop: 3, seed: 2 });
|
||||
/* three boils with different characters, one dispatcher: the circle
|
||||
rides the boil CONTROL (CONFIG.boil → authored pose); arrow + word
|
||||
keep raw hwBoil calls — the multi-character demo */
|
||||
window.hwBoilPose(tl, "#hw-b-circle-g", CONFIG.boil, { seed: 2 });
|
||||
window.hwBoil(tl, "#hw-b-arrow-g", { amp: 2.6, rot: 0.7, frameDrop: 1, seed: 5 });
|
||||
window.hwBoil(tl, "#hw-b-word", { amp: 1.2, rot: 0.35, frameDrop: 3, seed: 9 });
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Hand-Drawn Box Label"
|
||||
description: "Wobbled rounded rectangle draws around a region with a handwritten label above or centered"
|
||||
description: "Wobbled rounded rectangle draws on — across the authored stroke matrix (plain/soft/sharp/spray) — or drops in and lands with a volume-true contact squash and bouncy spring recovery, with a handwritten label above or centered; boils"
|
||||
---
|
||||
|
||||
import { InstallCommand } from "/snippets/install-command.jsx";
|
||||
@@ -55,17 +55,30 @@ you paste it into.
|
||||
}
|
||||
/* ---- hw-box-label ----------------------------------------------------
|
||||
Rounded hand-drawn box + marker label (map-label style annotations).
|
||||
Requires the hw-boil helpers in the host. */
|
||||
Requires the hw-boil helpers in the host.
|
||||
Wrapper anatomy (outer to inner): .hw-box = position/move wrapper
|
||||
(entrances/exits target THIS, never the boil element) > .hw-bx-deform
|
||||
= deform wrapper (g21 squash scale, transform-origin 50% 100%) >
|
||||
.hw-bx-boil = boil target (hwBoilPose owns its x/y/rotation) >
|
||||
content (svg + label). */
|
||||
.hw-box {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hw-box .hw-bx-deform,
|
||||
.hw-box .hw-bx-boil {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
.hw-box svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
.hw-box path {
|
||||
/* :not([stroke]) keeps texture machinery out of the ink rule: the
|
||||
stroke-matrix mask clone carries a stroke="#fff" attribute and must
|
||||
not inherit the ink color/width (CSS would beat its attributes). */
|
||||
.hw-box path:not([stroke]) {
|
||||
fill: none;
|
||||
stroke: var(--hw-box-ink, var(--hw-ink, #f4f2ec));
|
||||
stroke-width: var(--hw-box-w, 6);
|
||||
@@ -93,14 +106,20 @@ you paste it into.
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Self-preview. WIRING: copy the .hw-box CSS + markup pattern, the
|
||||
hwWobbleRect / hwBoxBuild / hwBoxOn / hwBoxOff helpers, AND the
|
||||
hw-boil helpers. Size the wrapper over your target, then:
|
||||
<!-- Self-preview. WIRING: copy the .hw-box CSS + markup pattern (position
|
||||
wrapper > .hw-bx-deform > .hw-bx-boil > svg + label), the CONFIG
|
||||
block, the hw family runtime, the spring library, the stroke matrix,
|
||||
AND the hwWobbleRect / hwBoxBuild / hwBoxOn / hwBoxOff helpers. Size
|
||||
the wrapper over your target, then wire with positions only:
|
||||
|
||||
hwBoxBuild("#hw-my-box", { seed: 3, label: "Europe", labelPos: "above" });
|
||||
hwBoxOn(tl, "#hw-my-box", 2.0);
|
||||
hwBoil(tl, "#hw-my-box", { frameDrop: 3, seed: 3 });
|
||||
hwBoxBuild("#hw-my-box", { seed: 3, label: "Europe" });
|
||||
hwBoxOn(tl, "#hw-my-box", 2.0); // entrance + boil per CONFIG
|
||||
hwBoxOff(tl, "#hw-my-box", 8.0);
|
||||
|
||||
The levers live in CONFIG (entrance / strokeType / labelPos / boil).
|
||||
Multi-instance hosts may override per instance via hwBoxBuild opts
|
||||
using the SAME declared values (bounded identically). The helpers own
|
||||
all durations/eases internally — no raw tween args in host code.
|
||||
-->
|
||||
<div
|
||||
id="hw-bx-root"
|
||||
@@ -116,9 +135,13 @@ you paste it into.
|
||||
id="hw-bx-demo"
|
||||
style="left: 700px; top: 420px; width: 420px; height: 230px"
|
||||
>
|
||||
<div class="hw-bx-deform">
|
||||
<div class="hw-bx-boil">
|
||||
<svg viewBox="0 0 420 230"><path id="hw-bx-demo-path"></path></svg>
|
||||
<div class="hw-bx-label" id="hw-bx-demo-label"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="hw-bx-drv"
|
||||
class="clip"
|
||||
@@ -132,6 +155,25 @@ you paste it into.
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
var CONFIG = {
|
||||
// ── Content (freely editable — not counted) ─────────────────────
|
||||
// per-instance content rides hwBoxBuild(target, opts):
|
||||
// opts.label (the label text) · opts.seed (which wobble pose the
|
||||
// box gets — an identity pick, not a feel lever)
|
||||
|
||||
// ── Controls (family base + 3 item-specific; comment grammar is the published-parameter list) ──
|
||||
entrance: "draw", // variant: "draw" | "drop" — draw = stroke draw-on + label fade (the shipped entrance); drop = landing squash: box+label drop in, contact squash on the deform wrapper, bouncy spring recovery; travel/duration derived, never exposed
|
||||
strokeType: "plain", // variant: "plain" | "soft" | "sharp" | "spray" — authored stroke texture on the box path (constants per type; draw-on composes via the mask clone)
|
||||
labelPos: "above", // variant: "above" | "inside" — label above the box or vertically centered inside it
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3; seed locked
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// draw duration/ease, drop travel/duration derivation (≈1.9× box
|
||||
// height into ~0.5s, power2.in), softness tier 0.11 (UI card),
|
||||
// spring registers, stroke-type constants, boil pose tables,
|
||||
// label fade timing, wobble amplitude
|
||||
};
|
||||
|
||||
/* ---- hw-boil helpers (shared family runtime — copy once per host) ---- */
|
||||
window.hwOnUpdate = function (tl, fn) {
|
||||
if (!tl.__hwRenders) {
|
||||
@@ -166,6 +208,252 @@ you paste it into.
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
/* ---- spring library (authored feel registers) -------------------
|
||||
The factory owns the recovery duration — consume rec.duration
|
||||
verbatim, never scale or multiply it. */
|
||||
var LN1000 = Math.log(1000);
|
||||
var FEELS = {
|
||||
snappy: { zeta: 0.9, response: 0.22 },
|
||||
bouncy: { zeta: 0.5, response: 0.4 },
|
||||
};
|
||||
window.springEase = function (opts) {
|
||||
var f = opts.feel ? FEELS[opts.feel] : opts;
|
||||
var zeta = f.zeta,
|
||||
response = f.response;
|
||||
var w0 = (2 * Math.PI) / response;
|
||||
var T = LN1000 / (zeta * w0);
|
||||
var x;
|
||||
if (zeta < 1) {
|
||||
var wd = w0 * Math.sqrt(1 - zeta * zeta);
|
||||
x = function (t) {
|
||||
return (
|
||||
1 -
|
||||
Math.exp(-zeta * w0 * t) *
|
||||
(Math.cos(wd * t) + ((zeta * w0) / wd) * Math.sin(wd * t))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
x = function (t) {
|
||||
return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
||||
};
|
||||
}
|
||||
var xT = x(T);
|
||||
return {
|
||||
ease: function (p) {
|
||||
return p >= 1 ? 1 : x(p * T) / xT;
|
||||
},
|
||||
duration: T,
|
||||
zeta: zeta,
|
||||
response: response,
|
||||
};
|
||||
};
|
||||
|
||||
/* ---- stroke matrix: plain | soft | sharp | spray ----------------
|
||||
strokeType is a g09 variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
must therefore ride a MASK clone (the visible dasharray never
|
||||
animates). spray = index-seeded deterministic dots along the path
|
||||
via hwHash: position a pure function of path parameter + seed; NO
|
||||
feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever
|
||||
hwStrokeApply says. (opts.pen requires the hwPenEase helper from
|
||||
the write-on items — not bundled here, never passed here.) */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.pen ? window.hwPenEase(p, opts).ease : opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
/* ---- g21 volume squash-stretch on hw impacts ----------------------
|
||||
S = min(SOFTNESS × v/V_REF, 0.25), v measured numerically from the
|
||||
entrance ease end-slope; scaleAlong × scalePerp ≈ 1 every frame
|
||||
(one proxy drives both); in-flight stretch E = 0.55 × SOFTNESS,
|
||||
velocity term clamped; contact squash ≤2 frames + 1-frame hold;
|
||||
recovery on a spring register with the factory duration consumed
|
||||
verbatim. Deformation lives on a WRAPPER (boil owns x/y/rotation of
|
||||
its own target; type counter-scaled to net identity inside). All
|
||||
per-frame application runs through hwOnUpdate.
|
||||
Volume assertion: |along×perp − 1| > 0.01 → console.error (fails check). */
|
||||
window.hwImpactSquash = function (tl, opts) {
|
||||
var wrap = opts.wrap; // deform wrapper (transform-origin 50% 100%)
|
||||
var label = opts.label || null; // counter-scaled type inside
|
||||
var travel = opts.travel;
|
||||
var at = opts.at;
|
||||
var dur = opts.dur;
|
||||
var softness = opts.softness !== undefined ? opts.softness : 0.11;
|
||||
var V_REF = 2000;
|
||||
var E = 0.55 * softness;
|
||||
var FPS = opts.fps || 30;
|
||||
var dropEase = opts.dropEase || gsap.parseEase("power2.in");
|
||||
// measured impact velocity (px/s) from the entrance ease end-slope
|
||||
var eps = 1e-4;
|
||||
var v = ((dropEase(1) - dropEase(1 - eps)) / eps) * (travel / dur);
|
||||
var S = Math.min(softness * (v / V_REF), 0.25);
|
||||
var rec = window.springEase({ feel: opts.feel || "bouncy" });
|
||||
var st = { ph: 0, s: 0 }; // ph: flight progress (linear), s: contact squash
|
||||
gsap.set(wrap, { transformOrigin: "50% 100%" });
|
||||
var setBoth = function (along) {
|
||||
gsap.set(wrap, { scaleY: along, scaleX: 1 / along });
|
||||
if (label) gsap.set(label, { scaleY: 1 / along, scaleX: along });
|
||||
// assert on the APPLIED transforms — catches any competing tween that
|
||||
// breaks the one-proxy-drives-both-axes law
|
||||
var ay = parseFloat(gsap.getProperty(wrap, "scaleY"));
|
||||
var ax = parseFloat(gsap.getProperty(wrap, "scaleX"));
|
||||
if (Math.abs(ax * ay - 1) > 0.01)
|
||||
console.error("hw volume law violated: scaleX*scaleY=" + (ax * ay).toFixed(4));
|
||||
};
|
||||
window.hwOnUpdate(tl, function () {
|
||||
if (st.ph > 0 && st.ph < 1) {
|
||||
var p = st.ph;
|
||||
var vp = ((dropEase(Math.min(p + eps, 1)) - dropEase(p)) / eps) * (travel / dur);
|
||||
setBoth(1 + E * Math.min(Math.abs(vp) / V_REF, 1));
|
||||
} else {
|
||||
setBoth(1 - st.s);
|
||||
}
|
||||
});
|
||||
// flight: wrapper position tween + linear proxy (same window)
|
||||
gsap.set(opts.moveTarget || wrap, { y: -travel }); // off-stage until entrance
|
||||
tl.fromTo(
|
||||
opts.moveTarget || wrap,
|
||||
{ y: -travel },
|
||||
{ y: 0, duration: dur, ease: dropEase, immediateRender: false },
|
||||
at,
|
||||
);
|
||||
tl.fromTo(st, { ph: 0 }, { ph: 1, duration: dur, ease: "none" }, at);
|
||||
// contact: squash in 2 frames, 1-frame hold, spring home (factory duration verbatim)
|
||||
tl.to(st, { s: S, duration: 2 / FPS, ease: "power3.out" }, at + dur);
|
||||
tl.to(st, { s: 0, duration: rec.duration, ease: rec.ease }, at + dur + 3 / FPS);
|
||||
return {
|
||||
v: Math.round(v),
|
||||
S: +S.toFixed(3),
|
||||
T: +rec.duration.toFixed(3),
|
||||
feel: opts.feel || "bouncy",
|
||||
};
|
||||
};
|
||||
|
||||
// ---- hw-box helpers (copy these too) ----
|
||||
// Wobbled rounded-rect path in a w x h box.
|
||||
@@ -197,6 +485,13 @@ you paste it into.
|
||||
d += " Q " + wob(31) + " " + wob(32) + " " + (r + wob(33)) + " " + wob(34);
|
||||
return d;
|
||||
};
|
||||
// bounded variant resolution (g09): unknown value → declared default
|
||||
// + console error. strokeType/boil bound inside their own helpers.
|
||||
function hwBxPick(name, val, values, dflt) {
|
||||
if (values.indexOf(val) !== -1) return val;
|
||||
console.error("hw: unknown " + name + ' "' + val + '" — falling back to "' + dflt + '"');
|
||||
return dflt;
|
||||
}
|
||||
window.hwBoxBuild = function (target, opts) {
|
||||
opts = opts || {};
|
||||
var el = document.querySelector(target);
|
||||
@@ -204,13 +499,35 @@ you paste it into.
|
||||
var vb = svg.getAttribute("viewBox").split(" ");
|
||||
var W = parseFloat(vb[2]),
|
||||
H = parseFloat(vb[3]);
|
||||
el.querySelector("path").setAttribute(
|
||||
"d",
|
||||
window.hwWobbleRect(W, H, Math.min(26, W * 0.1), opts.seed || 1),
|
||||
);
|
||||
var path = el.querySelector("path");
|
||||
path.setAttribute("d", window.hwWobbleRect(W, H, Math.min(26, W * 0.1), opts.seed || 1));
|
||||
// resolve the control set: CONFIG defaults; per-instance opts
|
||||
// overrides accepted (declared enum values only — bounded the same)
|
||||
var cfg = {
|
||||
seed: opts.seed || 1,
|
||||
entrance: hwBxPick(
|
||||
"entrance",
|
||||
opts.entrance !== undefined ? opts.entrance : CONFIG.entrance,
|
||||
["draw", "drop"],
|
||||
"draw",
|
||||
),
|
||||
labelPos: hwBxPick(
|
||||
"labelPos",
|
||||
opts.labelPos !== undefined ? opts.labelPos : CONFIG.labelPos,
|
||||
["above", "inside"],
|
||||
"above",
|
||||
),
|
||||
strokeType: opts.strokeType !== undefined ? opts.strokeType : CONFIG.strokeType,
|
||||
boil: opts.boil !== undefined ? opts.boil : CONFIG.boil,
|
||||
};
|
||||
el.__hwCfg = cfg;
|
||||
el.__hwApplied = window.hwStrokeApply(path, cfg.strokeType, {
|
||||
seed: cfg.seed,
|
||||
id: "hw-bx-m-" + target.replace(/[^a-zA-Z0-9_-]/g, ""),
|
||||
});
|
||||
var label = el.querySelector(".hw-bx-label");
|
||||
label.textContent = opts.label || "";
|
||||
if ((opts.labelPos || "above") === "above") {
|
||||
if (cfg.labelPos === "above") {
|
||||
label.style.top = "-64px";
|
||||
} else {
|
||||
label.style.top = "50%";
|
||||
@@ -218,25 +535,45 @@ you paste it into.
|
||||
}
|
||||
};
|
||||
window.hwBoxOn = function (tl, target, at) {
|
||||
var p = document.querySelector(target + " path");
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
tl.to(p, { strokeDashoffset: 0, duration: 0.75, ease: "power2.inOut" }, at);
|
||||
tl.to(
|
||||
document.querySelector(target + " .hw-bx-label"),
|
||||
{ opacity: 1, duration: 0.4, ease: "power2.out" },
|
||||
at + 0.55,
|
||||
);
|
||||
var el = document.querySelector(target);
|
||||
var cfg = el.__hwCfg;
|
||||
var label = el.querySelector(".hw-bx-label");
|
||||
if (cfg.entrance === "drop") {
|
||||
// landing squash: the whole box+label drops in and lands with a
|
||||
// contact squash on the deform wrapper; the label is counter-
|
||||
// scaled to net identity inside the shared apply (type NEVER
|
||||
// takes a non-uniform scale). Travel/duration derivation LOCKED:
|
||||
// ≈1.9× box height into ~0.5s of power2.in.
|
||||
gsap.set(label, { opacity: 1 }); // the label rides the drop
|
||||
gsap.set(el, { opacity: 0 }); // hidden until the entrance
|
||||
tl.set(el, { opacity: 1 }, at);
|
||||
window.hwImpactSquash(tl, {
|
||||
wrap: el.querySelector(".hw-bx-deform"),
|
||||
moveTarget: el,
|
||||
label: label,
|
||||
travel: Math.round(el.offsetHeight * 1.9),
|
||||
at: at,
|
||||
dur: 0.5,
|
||||
softness: 0.11, // UI-card tier
|
||||
feel: "bouncy",
|
||||
});
|
||||
} else {
|
||||
// "draw" — the shipped entrance, routed through hwDrawOn so
|
||||
// sharp/spray textures compose (the mask clone animates; the
|
||||
// visible texture dasharray never does)
|
||||
window.hwDrawOn(tl, el.__hwApplied, at, 0.75);
|
||||
tl.to(label, { opacity: 1, duration: 0.4, ease: "power2.out" }, at + 0.55);
|
||||
}
|
||||
window.hwBoilPose(tl, target + " .hw-bx-boil", cfg.boil, { seed: cfg.seed });
|
||||
};
|
||||
window.hwBoxOff = function (tl, target, at) {
|
||||
tl.to(target, { opacity: 0, duration: 0.35, ease: "power2.in" }, at);
|
||||
};
|
||||
|
||||
// ---- self-preview ----
|
||||
window.hwBoxBuild("#hw-bx-demo", { seed: 3, label: "Europe", labelPos: "above" });
|
||||
window.hwBoxBuild("#hw-bx-demo", { seed: 3, label: "Europe" });
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.hwBoxOn(tl, "#hw-bx-demo", 0.4);
|
||||
window.hwBoil(tl, "#hw-bx-demo", { amp: 1.8, rot: 0.4, frameDrop: 3, seed: 3 });
|
||||
window.hwBoxOff(tl, "#hw-bx-demo", 3.4);
|
||||
tl.set("#hw-bx-root", { visibility: "hidden" }, 3.98);
|
||||
window.__timelines["hw-box-label"] = tl;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Scribble Callout Circle"
|
||||
description: "Wobbled ellipse draws around a target with optional scribble fill, curved connector, and handwritten label"
|
||||
description: "Wobbled ellipse draws around a target with optional scribble fill and curved connector; the handwritten label pops in WITH momentum and the drawn shapes absorb the arrival with a volume-preserving contact squash; outline and connector take the family stroke matrix (plain/soft/sharp/spray); boils"
|
||||
---
|
||||
|
||||
import { InstallCommand } from "/snippets/install-command.jsx";
|
||||
@@ -56,11 +56,29 @@ you paste it into.
|
||||
/* ---- hw-callout-circle ----------------------------------------------
|
||||
The full callout anatomy: wobbled ellipse outline draws around a
|
||||
target, optional scribble fill sketches in, a curved connector leads
|
||||
to a handwritten label. Requires the hw-boil helpers in the host. */
|
||||
to a handwritten label that POPS in with momentum — the drawn shapes
|
||||
absorb the arrival with a volume-preserving contact squash (g21).
|
||||
Ships the full family runtime it needs (hw-boil + boil poses +
|
||||
stroke matrix + springEase) — copy the helpers once per host.
|
||||
WRAPPER RULES: boil owns x/y/rotation of .hw-co-boil; the squash
|
||||
deforms .hw-co-deform (outline + scribble only); the label rides
|
||||
the uniform-scale .hw-co-pop OUTSIDE the deform — non-uniform
|
||||
scale never reaches glyphs. One timeline has ONE onUpdate
|
||||
(everything registers through hwOnUpdate). */
|
||||
.hw-callout {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hw-callout .hw-co-boil,
|
||||
.hw-callout .hw-co-deform,
|
||||
.hw-callout .hw-co-conn-layer,
|
||||
.hw-callout .hw-co-pop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
.hw-callout .hw-co-pop {
|
||||
opacity: 0;
|
||||
}
|
||||
.hw-callout svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -94,7 +112,6 @@ you paste it into.
|
||||
font-size: 54px;
|
||||
color: var(--hw-co-ink, var(--hw-ink, #f4f2ec));
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
text-shadow: 0 2px 14px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
/* self-preview scene */
|
||||
@@ -122,21 +139,26 @@ you paste it into.
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Self-preview. WIRING: copy the .hw-callout CSS + markup pattern, the
|
||||
hwCalloutBuild / hwCalloutOn / hwCalloutOff helpers, AND the hw-boil
|
||||
helpers. Size the wrapper around your target, then:
|
||||
<!-- Self-preview. WIRING: copy the .hw-callout CSS + markup pattern (the
|
||||
boil / deform / conn-layer / pop wrapper stack is REQUIRED — boil
|
||||
owns x/y/rotation, the squash owns the deform, the label pops on
|
||||
.hw-co-pop) and ALL helpers below. Size the wrapper around your
|
||||
target, then:
|
||||
|
||||
hwCalloutBuild("#hw-my-callout", { scribble: true, seed: 4,
|
||||
label: "look here!", labelAt: "right" });
|
||||
label: "look here!", labelAt: "right",
|
||||
strokeType: "plain", boil: "calm" });
|
||||
hwCalloutOn(tl, "#hw-my-callout", 2.0);
|
||||
hwBoil(tl, "#hw-my-callout", { frameDrop: 3, seed: 4 });
|
||||
hwCalloutOff(tl, "#hw-my-callout", 8.0);
|
||||
|
||||
hwCalloutOn applies the boil pose internally — do NOT add a host
|
||||
hwBoil call on the callout (it would double-boil).
|
||||
-->
|
||||
<div
|
||||
id="hw-co-root"
|
||||
data-composition-id="hw-callout-circle"
|
||||
data-start="0"
|
||||
data-duration="5"
|
||||
data-duration="4"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
@@ -147,18 +169,27 @@ you paste it into.
|
||||
id="hw-co-demo"
|
||||
style="left: 640px; top: 370px; width: 340px; height: 250px"
|
||||
>
|
||||
<div class="hw-co-boil">
|
||||
<div class="hw-co-deform">
|
||||
<svg viewBox="0 0 340 250">
|
||||
<path class="hw-co-scribble" id="hw-co-demo-scribble"></path>
|
||||
<path class="hw-co-outline" id="hw-co-demo-outline"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<svg class="hw-co-conn-layer" viewBox="0 0 340 250">
|
||||
<path class="hw-co-connector" id="hw-co-demo-connector"></path>
|
||||
</svg>
|
||||
<div class="hw-co-label" id="hw-co-demo-label"></div>
|
||||
<div class="hw-co-pop">
|
||||
<!-- labels intentionally hang outside the wrapper box -->
|
||||
<div class="hw-co-label" id="hw-co-demo-label" data-layout-allow-overflow></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="hw-co-drv"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="5"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
@@ -167,6 +198,24 @@ you paste it into.
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
var CONFIG = {
|
||||
// ── Content (freely editable — not counted) ─────────────────────
|
||||
label: "look here!",
|
||||
scribble: true, // which anatomy pieces exist (content, not a feel lever)
|
||||
connector: true, // (connector: false also suppresses the label anchor)
|
||||
|
||||
// ── Controls (family base + 2 item-specific; comment grammar is the published-parameter list) ──
|
||||
labelAt: "right", // variant: "left" | "right" | "below" — connector start point, sweep direction + label anchor pose (6 geometry params)
|
||||
strokeType: "plain", // variant: "plain" | "soft" | "sharp" | "spray" — outline + connector stroke texture (authored constants per type); the scribble fill stays plain — texture-on-texture reads as mud
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3; seed locked
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// draw durations/eases, pop ease + contact-squash constants
|
||||
// (softness 0.08 light-drawn-line tier, snappy recovery),
|
||||
// stroke-type constants, boil pose tables, seeds
|
||||
};
|
||||
var SEED = 4; // locked — determinism (hosts pass a distinct seed per callout instance)
|
||||
|
||||
/* ---- hw-boil helpers (shared family runtime — copy once per host) ---- */
|
||||
window.hwOnUpdate = function (tl, fn) {
|
||||
if (!tl.__hwRenders) {
|
||||
@@ -201,6 +250,58 @@ you paste it into.
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
/* ---- spring library (spring-lab, validated 2026-08-04 — verbatim) ---- */
|
||||
var LN1000 = Math.log(1000);
|
||||
var FEELS = {
|
||||
snappy: { zeta: 0.9, response: 0.22 },
|
||||
"heavy-settle": { zeta: 1.0, response: 0.8 },
|
||||
bouncy: { zeta: 0.5, response: 0.4 },
|
||||
wobbly: { zeta: 0.28, response: 0.5 },
|
||||
};
|
||||
window.springEase = function (opts) {
|
||||
var f = opts.feel ? FEELS[opts.feel] : opts;
|
||||
var zeta = f.zeta,
|
||||
response = f.response;
|
||||
var w0 = (2 * Math.PI) / response;
|
||||
var T = LN1000 / (zeta * w0);
|
||||
var x;
|
||||
if (zeta < 1) {
|
||||
var wd = w0 * Math.sqrt(1 - zeta * zeta);
|
||||
x = function (t) {
|
||||
return (
|
||||
1 -
|
||||
Math.exp(-zeta * w0 * t) *
|
||||
(Math.cos(wd * t) + ((zeta * w0) / wd) * Math.sin(wd * t))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
x = function (t) {
|
||||
return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
||||
};
|
||||
}
|
||||
var xT = x(T);
|
||||
return {
|
||||
ease: function (p) {
|
||||
return p >= 1 ? 1 : x(p * T) / xT;
|
||||
},
|
||||
duration: T,
|
||||
zeta: zeta,
|
||||
response: response,
|
||||
};
|
||||
};
|
||||
window.hwWobbleEllipse = function (cx, cy, rx, ry, seed, wobblePct) {
|
||||
wobblePct = wobblePct === undefined ? 3 : wobblePct;
|
||||
var N = 14,
|
||||
@@ -229,10 +330,138 @@ you paste it into.
|
||||
return d;
|
||||
};
|
||||
|
||||
/* ---- stroke matrix (family runtime): plain | soft | sharp | spray ----
|
||||
strokeType is a g09 variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
must therefore ride a MASK clone (the visible dasharray never
|
||||
animates). spray = index-seeded deterministic dots along the path
|
||||
via hwHash: position a pure function of path parameter + seed; NO
|
||||
feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever hwStrokeApply
|
||||
says. (The pen-velocity ease variant rides the write-on item, not
|
||||
this one.) */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
// ---- hw-callout helpers (copy these too) ----
|
||||
// Builds outline + scribble + connector + label inside the wrapper.
|
||||
// opts: scribble (bool), seed, label, labelAt ("right"|"left"|"below"),
|
||||
// connector (bool)
|
||||
// Builds outline + scribble + connector + label inside the wrapper and
|
||||
// stores the draw records + control poses hwCalloutOn consumes.
|
||||
// opts — content (uncounted): scribble (bool), seed, label, connector (bool)
|
||||
// controls (g09 — see the CONFIG block, the published-parameter
|
||||
// list): labelAt, strokeType, boil
|
||||
window.hwCalloutBuild = function (target, opts) {
|
||||
opts = opts || {};
|
||||
var seed = opts.seed || 1;
|
||||
@@ -246,10 +475,8 @@ you paste it into.
|
||||
rx = W * 0.42,
|
||||
ry = H * 0.36;
|
||||
|
||||
el.querySelector(".hw-co-outline").setAttribute(
|
||||
"d",
|
||||
window.hwWobbleEllipse(cx, cy, rx, ry, seed, 4),
|
||||
);
|
||||
var outline = el.querySelector(".hw-co-outline");
|
||||
outline.setAttribute("d", window.hwWobbleEllipse(cx, cy, rx, ry, seed, 4));
|
||||
|
||||
var scr = el.querySelector(".hw-co-scribble");
|
||||
if (opts.scribble === false) {
|
||||
@@ -278,14 +505,24 @@ you paste it into.
|
||||
|
||||
var conn = el.querySelector(".hw-co-connector");
|
||||
var label = el.querySelector(".hw-co-label");
|
||||
// labelAt is a declared variant control — bounded: unknown value
|
||||
// falls back to the default WITH a console error. Validated BEFORE
|
||||
// the connector branch so a malformed value always logs, even when
|
||||
// connector: false leaves the control inert.
|
||||
var at = opts.labelAt || "right";
|
||||
if (at !== "left" && at !== "right" && at !== "below") {
|
||||
console.error('hw: unknown labelAt "' + at + '" — falling back to "right"');
|
||||
at = "right";
|
||||
}
|
||||
var ex = null,
|
||||
ey = null;
|
||||
if (opts.connector === false) {
|
||||
conn.style.display = "none";
|
||||
} else {
|
||||
var at = opts.labelAt || "right";
|
||||
var sx = at === "left" ? cx - rx : at === "below" ? cx : cx + rx;
|
||||
var sy = at === "below" ? cy + ry : cy + ry * 0.35;
|
||||
var ex = at === "left" ? sx - 70 : at === "below" ? sx + 40 : sx + 70;
|
||||
var ey = sy + 55;
|
||||
ex = at === "left" ? sx - 70 : at === "below" ? sx + 40 : sx + 70;
|
||||
ey = sy + 55;
|
||||
conn.setAttribute(
|
||||
"d",
|
||||
"M " +
|
||||
@@ -305,40 +542,112 @@ you paste it into.
|
||||
label.style.top = ey - 24 + "px";
|
||||
}
|
||||
label.textContent = opts.label || "";
|
||||
|
||||
// strokeType control: OUTLINE + CONNECTOR take the texture (the
|
||||
// scribble stays plain). Bounding lives inside hwStrokeApply.
|
||||
var strokeType = opts.strokeType || "plain";
|
||||
var idBase = (el.id || "hw-co") + "-sm";
|
||||
el.__hwCo = {
|
||||
rx: rx,
|
||||
seed: seed,
|
||||
boil: opts.boil || "calm",
|
||||
// the label pops out of the connector tip (falls back to center
|
||||
// when the connector anatomy is off)
|
||||
popOrigin: ex === null ? "50% 50%" : ex.toFixed(1) + "px " + ey.toFixed(1) + "px",
|
||||
draw: {
|
||||
outline: window.hwStrokeApply(outline, strokeType, {
|
||||
seed: seed,
|
||||
id: idBase + "-outline",
|
||||
}),
|
||||
scribble:
|
||||
opts.scribble === false ? null : window.hwStrokeApply(scr, "plain", { seed: seed }),
|
||||
connector:
|
||||
opts.connector === false
|
||||
? null
|
||||
: window.hwStrokeApply(conn, strokeType, {
|
||||
seed: seed,
|
||||
id: idBase + "-connector",
|
||||
}),
|
||||
},
|
||||
};
|
||||
// Draw sequence: outline -> scribble -> connector -> label pop.
|
||||
};
|
||||
// Draw sequence: outline -> scribble -> connector -> label pop, with
|
||||
// the g21 contact squash on the shapes group at pop arrival. The boil
|
||||
// pose registers here too — the host wires a position only.
|
||||
window.hwCalloutOn = function (tl, target, at) {
|
||||
var el = document.querySelector(target);
|
||||
var co = el.__hwCo;
|
||||
var t = at;
|
||||
["outline", "scribble", "connector"].forEach(function (k, idx) {
|
||||
var p = el.querySelector(".hw-co-" + k);
|
||||
if (!p || p.style.display === "none") return;
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
["outline", "scribble", "connector"].forEach(function (k) {
|
||||
var rec = co.draw[k];
|
||||
if (!rec) return;
|
||||
var dur = k === "scribble" ? 0.55 : k === "connector" ? 0.35 : 0.7;
|
||||
tl.to(p, { strokeDashoffset: 0, duration: dur, ease: "power2.inOut" }, t);
|
||||
window.hwDrawOn(tl, rec, t, dur);
|
||||
t += dur * 0.85;
|
||||
});
|
||||
var label = el.querySelector(".hw-co-label");
|
||||
gsap.set(label, { scale: 0.8, transformOrigin: "0% 50%" });
|
||||
tl.to(label, { opacity: 1, scale: 1, duration: 0.4, ease: "back.out(1.6)" }, t);
|
||||
// label pop: the pop ARRIVES WITH MOMENTUM (accelerating into
|
||||
// contact) — the squash absorbs it; a settling back.out crosses
|
||||
// scale 1.0 at ~zero speed and the computed squash is invisible
|
||||
// (measured, not hand-picked)
|
||||
var pop = el.querySelector(".hw-co-pop");
|
||||
var popEase = gsap.parseEase("power2.in");
|
||||
gsap.set(pop, { scale: 0.001, opacity: 0, transformOrigin: co.popOrigin });
|
||||
tl.to(pop, { opacity: 1, duration: 0.08, ease: "none" }, t);
|
||||
tl.to(pop, { scale: 1, duration: 0.3, ease: popEase, immediateRender: false }, t);
|
||||
// arrival velocity in px of radial (rx) travel, measured at the
|
||||
// FIRST crossing of 1.0 — a settling ease's end-slope is ~0 and is
|
||||
// NOT the arrival velocity
|
||||
var eps = 1e-4;
|
||||
var pc = 1;
|
||||
for (var k2 = 1; k2 <= 1000; k2++) {
|
||||
if (popEase(k2 / 1000) >= 1) {
|
||||
pc = k2 / 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var vPop =
|
||||
((popEase(Math.min(pc + eps, 1)) - popEase(pc - eps)) / (2 * eps)) * (co.rx / 0.3);
|
||||
var S = Math.min(0.08 * (Math.abs(vPop) / 2000), 0.25); // 0.08 = light drawn line tier
|
||||
var rec2 = window.springEase({ feel: "snappy" });
|
||||
var st = { s: 0 };
|
||||
var deform = el.querySelector(".hw-co-deform");
|
||||
gsap.set(deform, { transformOrigin: "50% 100%" });
|
||||
// volume-preserving squash on the SHAPES wrapper only — the label
|
||||
// rides the uniform-scale pop OUTSIDE the deform, so non-uniform
|
||||
// scale never reaches glyphs. Read-back assertion enforces the law.
|
||||
window.hwOnUpdate(tl, function () {
|
||||
var along = 1 - st.s;
|
||||
gsap.set(deform, { scaleY: along, scaleX: 1 / along });
|
||||
var ax = parseFloat(gsap.getProperty(deform, "scaleX"));
|
||||
var ay = parseFloat(gsap.getProperty(deform, "scaleY"));
|
||||
if (Math.abs(ax * ay - 1) > 0.01)
|
||||
console.error("hw volume law violated (callout): " + (ax * ay).toFixed(4));
|
||||
});
|
||||
// contact: squash in 2 frames, 1-frame hold, spring home (factory
|
||||
// duration consumed verbatim)
|
||||
tl.to(st, { s: S, duration: 2 / 30, ease: "power3.out" }, t + 0.3);
|
||||
tl.to(st, { s: 0, duration: rec2.duration, ease: rec2.ease }, t + 0.3 + 3 / 30);
|
||||
// boil control (family base) — x/y/rotation on the boil wrapper only
|
||||
window.hwBoilPose(tl, el.querySelector(".hw-co-boil"), co.boil, { seed: co.seed });
|
||||
};
|
||||
window.hwCalloutOff = function (tl, target, at) {
|
||||
tl.to(target, { opacity: 0, duration: 0.35, ease: "power2.in" }, at);
|
||||
};
|
||||
|
||||
// ---- self-preview ----
|
||||
// ---- self-preview (shipped-default CONFIG) ----
|
||||
window.hwCalloutBuild("#hw-co-demo", {
|
||||
scribble: true,
|
||||
seed: 4,
|
||||
label: "look here!",
|
||||
labelAt: "right",
|
||||
scribble: CONFIG.scribble,
|
||||
seed: SEED,
|
||||
label: CONFIG.label,
|
||||
connector: CONFIG.connector,
|
||||
labelAt: CONFIG.labelAt,
|
||||
strokeType: CONFIG.strokeType,
|
||||
boil: CONFIG.boil,
|
||||
});
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.hwCalloutOn(tl, "#hw-co-demo", 0.4);
|
||||
window.hwBoil(tl, "#hw-co-demo", { amp: 1.8, rot: 0.4, frameDrop: 3, seed: 4 });
|
||||
window.hwCalloutOff(tl, "#hw-co-demo", 4.3);
|
||||
tl.set("#hw-co-root", { visibility: "hidden" }, 4.98);
|
||||
window.hwCalloutOff(tl, "#hw-co-demo", 3.3);
|
||||
tl.set("#hw-co-root", { visibility: "hidden" }, 3.98);
|
||||
window.__timelines["hw-callout-circle"] = tl;
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -295,6 +295,7 @@
|
||||
"catalog/components/headline-slam",
|
||||
"catalog/blocks/hw-path-text",
|
||||
"catalog/blocks/hw-title",
|
||||
"catalog/blocks/hw-write-title",
|
||||
"catalog/components/inline-highlight",
|
||||
"catalog/components/kinetic-type-swap",
|
||||
"catalog/components/line-by-line-slide",
|
||||
|
||||
@@ -1946,7 +1946,7 @@
|
||||
"name": "hw-arrow",
|
||||
"type": "component",
|
||||
"title": "Hand-Drawn Arrow",
|
||||
"description": "Wobbled draw-on arrow with straight, gentle, and swoop curve presets; boils while on screen",
|
||||
"description": "Wobbled draw-on arrow with straight, gentle, and swoop curve poses, a four-way stroke texture matrix (plain/soft/sharp/spray), and a travel-aligned head-arrival stretch pulse with spring recovery; boils while on screen",
|
||||
"tags": [
|
||||
"handwritten",
|
||||
"annotation",
|
||||
@@ -1960,7 +1960,7 @@
|
||||
"name": "hw-boil",
|
||||
"type": "component",
|
||||
"title": "Hand-Drawn Boil",
|
||||
"description": "Hand-drawn jitter that re-poses every N frames, as seek-safe helpers: pose derives from quantized timeline time through a seeded hash; includes the shared onUpdate dispatcher and a wobbled-ellipse path generator",
|
||||
"description": "Hand-drawn jitter that re-poses every N frames, as seek-safe helpers: pose derives from quantized timeline time through a seeded hash; includes the shared onUpdate dispatcher, a wobbled-ellipse path generator, the boil pose control (off/calm/lively over the family-locked frameDrop), a curvature-adaptive pen-velocity ease, and the completed stroke matrix (plain/soft/sharp/spray — spray is index-seeded deterministic dots, no turbulence) with texture-safe draw-on",
|
||||
"tags": [
|
||||
"handwritten",
|
||||
"effect",
|
||||
@@ -1973,7 +1973,7 @@
|
||||
"name": "hw-box-label",
|
||||
"type": "component",
|
||||
"title": "Hand-Drawn Box Label",
|
||||
"description": "Wobbled rounded rectangle draws around a region with a handwritten label above or centered",
|
||||
"description": "Wobbled rounded rectangle draws on — across the authored stroke matrix (plain/soft/sharp/spray) — or drops in and lands with a volume-true contact squash and bouncy spring recovery, with a handwritten label above or centered; boils",
|
||||
"tags": [
|
||||
"handwritten",
|
||||
"annotation",
|
||||
@@ -1986,7 +1986,7 @@
|
||||
"name": "hw-callout-circle",
|
||||
"type": "component",
|
||||
"title": "Scribble Callout Circle",
|
||||
"description": "Wobbled ellipse draws around a target with optional scribble fill, curved connector, and handwritten label",
|
||||
"description": "Wobbled ellipse draws around a target with optional scribble fill and curved connector; the handwritten label pops in WITH momentum and the drawn shapes absorb the arrival with a volume-preserving contact squash; outline and connector take the family stroke matrix (plain/soft/sharp/spray); boils",
|
||||
"tags": [
|
||||
"handwritten",
|
||||
"annotation",
|
||||
@@ -2090,6 +2090,21 @@
|
||||
"href": "/catalog/components/hw-underline",
|
||||
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/hw-underline.png"
|
||||
},
|
||||
{
|
||||
"name": "hw-write-title",
|
||||
"type": "block",
|
||||
"title": "Handwritten Write-On Title",
|
||||
"description": "True glyph write-on: filled Caveat letterforms revealed by baked per-glyph centerline strokes traced in real stroke order with a curvature-adaptive pen-velocity ease (fast on straights, slow into curves, inter-stroke pen lifts); optional squiggle underline; boils",
|
||||
"tags": [
|
||||
"handwritten",
|
||||
"typography",
|
||||
"title-card",
|
||||
"write-on",
|
||||
"overlay"
|
||||
],
|
||||
"href": "/catalog/blocks/hw-write-title",
|
||||
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/hw-write-title.png"
|
||||
},
|
||||
{
|
||||
"name": "icon-morph-beat",
|
||||
"type": "component",
|
||||
|
||||
@@ -0,0 +1,732 @@
|
||||
// baked by bake-centerlines.mjs (generator attached to the PR, not shipped) — Caveat per-glyph centerline strokes
|
||||
// (y-down font units, baseline origin, upem 1000) + outlines + stem widths.
|
||||
window.__hwPenCaveat = {
|
||||
meta: { face: "Caveat-700.ttf", upem: 1000, ascent: 960, descent: -300 },
|
||||
glyphs: {
|
||||
0: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M280-21L280-21Q273-21 258.5-27.5Q244-34 228-43.5Q212-53 202-62L202-62Q180-83 165-113Q150-143 144-177.5Q138-212 143-244L143-244Q154-308 191-370.5Q228-433 276-474L276-474Q302-497 323-509.5Q344-522 374-529L374-529Q385-532 398-532.5Q411-533 418-533L418-533Q438-533 458-525Q478-517 492-505.5Q506-494 506-484L506-484Q506-477 505-473.5Q504-470 496-474L496-474L486-474L496-445Q500-437 503-423.5Q506-410 506-399L506-399Q506-390 503-370.5Q500-351 495-329Q490-307 485-288Q480-269 476-260L476-260Q473-254 472.5-248.5Q472-243 472-239L472-239Q468-232 459.5-212Q451-192 447-184L447-184Q443-180 440.5-177Q438-174 442-170L442-170Q442-168 437-157.5Q432-147 426.5-138Q421-129 418-129L418-129Q418-129 415.5-128.5Q413-128 413-125L413-125Q413-121 400-104.5Q387-88 369-70L369-70Q359-60 341.5-48.5Q324-37 307-29Q290-21 280-21ZM277-84L277-84Q288-84 298-87.5Q308-91 315-98L315-98Q333-110 357-137.5Q381-165 392-191L392-191Q403-206 409.5-222.5Q416-239 430-279L430-279Q438-290 441-306Q444-322 444-333L444-333Q444-344 444.5-363Q445-382 449-395L449-395Q449-421 448-429.5Q447-438 440-446L440-446Q436-449 429.5-452Q423-455 416-455L416-455Q409-455 390-447.5Q371-440 363-435L363-435Q356-431 347.5-434Q339-437 339-442L339-442Q339-456 321-444.5Q303-433 281-406L281-406Q263-388 250.5-369.5Q238-351 238-347L238-347Q238-343 235-337Q232-331 229-323L229-323Q224-316 217-300Q210-284 203.5-268.5Q197-253 195-245L195-245Q195-241 195-223.5Q195-206 195-191L195-191Q195-174 196.5-162Q198-150 201-141.5Q204-133 209-123L209-123Q213-116 221.5-106.5Q230-97 233-93L233-93Q244-83 251-81.5Q258-80 277-84Z",
|
||||
stem: 52,
|
||||
strokes: [
|
||||
"M 457.1 -485.7 C 460.1 -472 474.4 -435.7 475 -403.6 C 475.6 -371.5 473.2 -335.2 460.7 -292.9 C 448.2 -250.6 420.2 -185.1 400 -150 C 379.8 -114.9 356.6 -97 339.3 -82.1 C 322 -67.2 306.5 -64.9 296.4 -60.7 C 286.3 -56.5 289.9 -55.9 278.6 -57.1 C 267.3 -58.3 240.5 -62.5 228.6 -67.9 C 216.7 -73.3 216 -77.4 207.1 -89.3 C 198.2 -101.2 181.5 -113.7 175 -139.3 C 168.5 -164.9 167.3 -217.3 167.9 -242.9 C 168.5 -268.5 169.1 -269.1 178.6 -292.9 C 188.1 -316.7 205.4 -357.1 225 -385.7 C 244.6 -414.3 280.9 -449.4 296.4 -464.3 C 311.9 -479.2 302.4 -470.2 317.9 -475 C 333.4 -479.8 366.1 -491.1 389.3 -492.9 C 412.5 -494.7 445.8 -486.9 457.1 -485.7",
|
||||
],
|
||||
},
|
||||
1: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M321 10L321 10Q318 12 313 10.5Q308 9 302 4.5Q296 0 289-6L289-6Q285-10 284-15.5Q283-21 286-35Q289-49 295-80L295-80Q299-100 304-120Q309-140 313-153.5Q317-167 317-167L317-167Q322-169 331.5-197Q341-225 353-265L353-265Q357-279 367-312Q377-345 387-376L387-376Q396-405 402-428.5Q408-452 408-452L408-452Q408-452 393.5-443.5Q379-435 357-421.5Q335-408 314-392L314-392Q297-382 273.5-364Q250-346 232-335L232-335Q205-314 199-311.5Q193-309 184-314L184-314Q171-325 166.5-337Q162-349 168-359L168-359Q170-360 180.5-367Q191-374 204-383Q217-392 228.5-400Q240-408 245-411L245-411Q248-411 256-418Q264-425 273-431L273-431Q278-437 308-458Q338-479 373-505L373-505Q410-528 435.5-542Q461-556 465-555L465-555Q468-555 474.5-551Q481-547 487-541Q493-535 494-529L494-529Q500-522 488-497L488-497Q484-488 475-464.5Q466-441 455.5-410.5Q445-380 435.5-350.5Q426-321 420-300L420-300Q412-269 405.5-252Q399-235 393.5-219.5Q388-204 381-177L381-177Q374-161 366.5-136.5Q359-112 353-88.5Q347-65 344-50L344-50Q336-28 331-11Q326 6 321 10Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 196.4 -346.4 C 226.8 -367.8 338.7 -451.2 378.6 -475 C 418.5 -498.8 426.2 -486.9 435.7 -489.3",
|
||||
"M 435.7 -489.3 C 434.5 -478.6 445.9 -484.5 428.6 -425 C 411.3 -365.5 352.4 -198.2 332.1 -132.1 C 311.9 -66 311.3 -45.8 307.1 -28.6",
|
||||
],
|
||||
},
|
||||
2: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M159-23L159-23Q155-25 147.5-30.5Q140-36 132.5-43.5Q125-51 122-57L122-57Q119-62 122.5-75Q126-88 130-88L130-88Q133-86 140-95Q147-104 154-114.5Q161-125 161-127L161-127Q162-131 166.5-133.5Q171-136 175-139L175-139Q180-147 188.5-155.5Q197-164 202-170L202-170Q210-180 231-200Q252-220 272-241L272-241Q293-264 309-281.5Q325-299 326-306L326-306Q326-306 329.5-309.5Q333-313 338-316L338-316Q343-319 351.5-326Q360-333 366-339L366-339Q368-347 377.5-361.5Q387-376 397-386L397-386Q405-398 412-412.5Q419-427 421-434L421-434Q424-445 421-447.5Q418-450 396-457L396-457Q372-465 343.5-463.5Q315-462 280-437L280-437Q266-434 254.5-429Q243-424 235-425L235-425Q231-427 226-434Q221-441 215-447L215-447L205-468L236-485Q277-511 310.5-521Q344-531 372-522L372-522Q383-522 392-519.5Q401-517 401-517L401-517Q409-518 421-508.5Q433-499 444.5-485Q456-471 462-457L462-457Q478-433 472.5-406.5Q467-380 442-343L442-343Q437-337 420-318Q403-299 381.5-275.5Q360-252 342.5-234Q325-216 320-211L320-211Q313-208 296.5-190.5Q280-173 261.5-152.5Q243-132 230.5-116.5Q218-101 220-101L220-101Q219-97 223-97.5Q227-98 236-101L236-101Q251-106 275.5-111Q300-116 326-119.5Q352-123 371.5-124Q391-125 394-122L394-122Q396-117 397.5-118.5Q399-120 399-120L399-120Q401-127 424.5-124Q448-121 467-115L467-115Q478-112 484.5-110Q491-108 491-104L491-104Q491-104 493.5-99.5Q496-95 502-88L502-88Q518-75 506-71Q494-67 454-74L454-74Q428-74 411-71L411-71Q407-69 394-69Q381-69 369-68L369-68Q351-70 333-66.5Q315-63 290-55L290-55Q281-52 262-46Q243-40 221-34Q199-28 181.5-25Q164-22 159-23Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 246.4 -460.7 C 257.1 -466.1 287.5 -487.5 310.7 -492.9 C 333.9 -498.3 365.5 -497.1 385.7 -492.9 C 405.9 -488.7 422.6 -481.6 432.1 -467.9 C 441.6 -454.2 450.6 -435.1 442.9 -410.7 C 435.2 -386.3 402.4 -342.2 385.7 -321.4 C 369 -300.6 354.8 -298.2 342.9 -285.7 C 331 -273.2 336.9 -270.8 314.3 -246.4 C 291.7 -222 226.8 -159.5 207.1 -139.3 C 187.5 -119.1 200.6 -136.3 196.4 -125 C 192.2 -113.7 176.7 -80.3 182.1 -71.4 C 187.5 -62.5 204.2 -67.2 228.6 -71.4 C 253 -75.6 298.9 -91.6 328.6 -96.4 C 358.4 -101.2 384.5 -100 407.1 -100 C 429.7 -100 450.6 -99.4 464.3 -96.4 C 478 -93.4 485.1 -84.5 489.3 -82.1",
|
||||
],
|
||||
},
|
||||
3: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M243-16L243-16Q218-16 194.5-21.5Q171-27 157-36.5Q143-46 144-58L144-58Q145-74 151-75.5Q157-77 179-74L179-74Q213-67 252-73Q291-79 325-94Q359-109 378-128L378-128Q405-154 402.5-180Q400-206 371-225L371-225Q358-234 335-242Q312-250 289.5-254.5Q267-259 253-258L253-258Q244-257 238.5-258Q233-259 220-266L220-266Q214-273 209-281.5Q204-290 202-294L202-294Q201-299 207.5-304Q214-309 236-321.5Q258-334 304-359L304-359Q346-382 370.5-396.5Q395-411 409.5-420.5Q424-430 433-438L433-438Q451-462 451-469Q451-476 425-481L425-481Q400-483 377-484Q354-485 354-481L354-481Q354-481 353.5-481Q353-481 350-481L350-481Q345-483 328-480Q311-477 295.5-473Q280-469 279-466L279-466Q278-464 267.5-459.5Q257-455 246.5-452Q236-449 233-449L233-449Q228-451 221.5-459.5Q215-468 211-477.5Q207-487 209-492L209-492Q211-496 221.5-502.5Q232-509 246-516.5Q260-524 271-529L271-529Q285-533 310.5-535Q336-537 362-537.5Q388-538 400-537L400-537Q446-532 471-513Q496-494 502-462L502-462Q505-448 504-439Q503-430 495-420.5Q487-411 467-393L467-393Q442-377 426-366Q410-355 378-338L378-338Q371-333 359-325Q347-317 337-310.5Q327-304 327-302L327-302Q327-302 331-299.5Q335-297 341-293Q347-289 352-284L352-284Q381-274 405-252Q429-230 439-216L439-216Q453-185 450.5-156Q448-127 430-101.5Q412-76 377-55L377-55Q349-39 312-28.5Q275-18 243-16Z",
|
||||
stem: 55,
|
||||
strokes: [
|
||||
"M 289.3 -296.4 C 292.3 -293.4 292.8 -286.3 307.1 -278.6 C 321.4 -270.9 357.7 -260.1 375 -250 C 392.3 -239.9 402.4 -231.6 410.7 -217.9 C 419 -204.2 425 -183.4 425 -167.9 C 425 -152.4 422 -139.9 410.7 -125 C 399.4 -110.1 376.7 -90.5 357.1 -78.6 C 337.5 -66.7 314.3 -59 292.9 -53.6 C 271.5 -48.2 250 -45.8 228.6 -46.4 C 207.2 -47 175 -55.3 164.3 -57.1",
|
||||
"M 242.9 -482.1 C 253 -486.9 278.6 -505.3 303.6 -510.7 C 328.6 -516.1 369.7 -515.5 392.9 -514.3 C 416.1 -513.1 432.2 -506.6 442.9 -503.6 C 453.6 -500.6 452.9 -500.6 457.1 -496.4 C 461.3 -492.2 465.5 -486.9 467.9 -478.6 C 470.3 -470.3 472 -454.1 471.4 -446.4 C 470.8 -438.6 474.4 -441.6 464.3 -432.1 C 454.2 -422.6 436.9 -406 410.7 -389.3 C 384.5 -372.6 327.3 -347.6 307.1 -332.1 C 286.9 -316.6 292.3 -302.3 289.3 -296.4",
|
||||
],
|
||||
},
|
||||
4: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M317-7L317-7Q310-16 310-28Q310-40 319-77L319-77Q324-103 331-133.5Q338-164 342-182L342-182Q350-201 354.5-217Q359-233 359-233L359-233Q359-233 346-232Q333-231 315-223L315-223Q265-211 237.5-216Q210-221 194-248L194-248Q189-253 185.5-265.5Q182-278 182-290Q182-302 187-307L187-307Q193-320 199.5-333Q206-346 210.5-356Q215-366 215-368L215-368Q215-371 217.5-374.5Q220-378 220-382L220-382Q224-391 239.5-415.5Q255-440 274.5-469Q294-498 311.5-522Q329-546 337-553L337-553Q345-557 354-559.5Q363-562 366-558L366-558Q369-553 372-543Q375-533 376.5-525Q378-517 375-517L375-517Q375-517 372.5-516.5Q370-516 370-512L370-512Q370-508 360-495Q350-482 338-468L338-468Q324-449 312-431.5Q300-414 289-397Q278-380 267.5-363Q257-346 247-328L247-328Q236-294 234.5-285Q233-276 237-272L237-272Q243-269 262.5-270.5Q282-272 317-282L317-282Q336-286 352.5-289.5Q369-293 376-297L376-297Q384-297 390.5-300Q397-303 397-307L397-307Q397-311 403.5-324Q410-337 414-348L414-348Q422-363 433.5-388.5Q445-414 457-433L457-433Q482-489 488.5-505.5Q495-522 499-522L499-522Q503-522 510-518.5Q517-515 521-507L521-507Q528-500 530-493.5Q532-487 524-473L524-473Q522-461 518.5-452.5Q515-444 508.5-432Q502-420 490-397L490-397Q478-373 462.5-340Q447-307 432-272Q417-237 404.5-206.5Q392-176 386-158L386-158Q372-118 365-95.5Q358-73 357.5-60.5Q357-48 362-38L362-38Q364-20 354-7.5Q344 5 332.5 6.5Q321 8 317-7Z",
|
||||
stem: 50,
|
||||
strokes: [
|
||||
"M 350 -532.1 C 335.7 -512.5 286.3 -448.2 264.3 -414.3 C 242.3 -380.4 226.8 -347.7 217.9 -328.6 C 209 -309.5 211.3 -311.9 210.7 -300 C 210.1 -288.1 207.8 -266 214.3 -257.1 C 220.9 -248.2 224.4 -245.8 250 -246.4 C 275.6 -247 342.9 -255.9 367.9 -260.7 C 392.9 -265.5 379.8 -243.4 400 -275 C 420.2 -306.5 472 -413.1 489.3 -450 C 506.6 -486.9 501.2 -488.7 503.6 -496.4",
|
||||
"M 382.1 -264.3 C 380.9 -254.2 381.5 -228.6 375 -203.6 C 368.5 -178.6 350 -144.7 342.9 -114.3 C 335.8 -83.9 333.9 -36.9 332.1 -21.4",
|
||||
],
|
||||
},
|
||||
5: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M167-24L167-24Q149-37 149-46L149-46Q145-52 140-52L140-52Q137-51 134.5-58Q132-65 130.5-74.5Q129-84 130-88L130-88Q134-96 142-97.5Q150-99 157.5-95.5Q165-92 166-84L166-84Q168-76 181.5-71Q195-66 217-67L217-67Q245-68 275.5-81Q306-94 332.5-114Q359-134 375-155L375-155Q382-169 388-192Q394-215 396-233Q398-251 393-251L393-251Q393-251 391.5-258Q390-265 387-273L387-273Q382-288 364-303.5Q346-319 329-326L329-326Q322-328 303.5-329.5Q285-331 270-329L270-329Q254-328 241.5-329Q229-330 229-330L229-330Q224-331 214.5-338Q205-345 197-353Q189-361 189-366L189-366Q191-373 199-388.5Q207-404 218.5-423.5Q230-443 243-460.5Q256-478 268-490L268-490Q298-523 336.5-539.5Q375-556 416-554L416-554Q445-553 457.5-549.5Q470-546 482-530L482-530Q490-516 489-505Q488-494 481-490Q474-486 462-492L462-492Q448-501 423.5-499Q399-497 372-489L372-489Q351-481 327.5-463.5Q304-446 299-435L299-435Q295-432 293.5-429Q292-426 288-427L288-427Q288-427 285-423.5Q282-420 282-416L282-416Q277-410 275.5-407.5Q274-405 270-409L270-409Q270-409 270-406Q270-403 269-399L269-399Q268-392 270.5-387.5Q273-383 292-381L292-381Q317-377 330-373.5Q343-370 368-355L368-355Q396-339 413-312.5Q430-286 436.5-253Q443-220 438-186L438-186Q435-164 432-152Q429-140 415-119L415-119Q392-87 359-62.5Q326-38 290-24.5Q254-11 221.5-10Q189-9 167-24Z",
|
||||
stem: 55,
|
||||
strokes: [
|
||||
"M 464.3 -514.3 C 458.9 -516.7 444.6 -526.2 432.1 -528.6 C 419.6 -531 407.2 -532.8 389.3 -528.6 C 371.5 -524.4 339.3 -510.1 325 -503.6 C 310.7 -497 316.7 -502.4 303.6 -489.3 C 290.5 -476.2 257.7 -442.3 246.4 -425 C 235.1 -407.7 236.9 -395.8 235.7 -385.7 C 234.5 -375.6 220.3 -371.4 239.3 -364.3 C 258.4 -357.2 323.8 -353.6 350 -342.9 C 376.2 -332.2 386.3 -312.5 396.4 -300 C 406.5 -287.5 407.7 -284 410.7 -267.9 C 413.7 -251.8 417.3 -225.6 414.3 -203.6 C 411.3 -181.6 403.6 -154.1 392.9 -135.7 C 382.2 -117.2 372 -107.8 350 -92.9 C 328 -78 287.5 -54.1 260.7 -46.4 C 233.9 -38.6 204.8 -45.2 189.3 -46.4 C 173.8 -47.6 175.1 -48.2 167.9 -53.6 C 160.8 -59 150 -74.4 146.4 -78.6",
|
||||
],
|
||||
},
|
||||
6: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M253-31L253-31Q220-22 198-24.5Q176-27 155-47L155-47Q141-58 134-68Q127-78 124-98L124-98Q119-113 118-122Q117-131 119.5-144Q122-157 127-184L127-184Q135-220 141-240.5Q147-261 165-286L165-286Q173-307 183.5-328.5Q194-350 199-356L199-356Q204-363 214-377Q224-391 235-403L235-403Q245-416 253.5-427.5Q262-439 262-439L262-439Q267-442 271.5-445Q276-448 281-455L281-455Q289-464 303.5-476Q318-488 336.5-499Q355-510 373.5-518Q392-526 408-528L408-528Q440-539 438-527L438-527Q438-527 441-524Q444-521 452-519L452-519Q456-518 458.5-512.5Q461-507 464-502L464-502Q466-494 463.5-488Q461-482 447-469L447-469Q433-459 427.5-458Q422-457 418-461L418-461Q419-465 414-466.5Q409-468 409-468L409-468Q403-470 383-459Q363-448 340.5-431Q318-414 303-398L303-398Q294-390 287-382Q280-374 278-370L278-370Q265-357 255-344.5Q245-332 245-332L245-332Q248-326 259.5-329Q271-332 272-336L272-336Q272-340 294.5-346.5Q317-353 340-355L340-355Q349-359 364.5-356Q380-353 390-351L390-351Q412-347 420-343Q428-339 440-324L440-324Q460-305 467.5-286.5Q475-268 474-242L474-242Q474-226 471-214Q468-202 457-189L457-189Q449-176 436-158.5Q423-141 410.5-127Q398-113 389-107L389-107Q385-105 381.5-102.5Q378-100 378-100L378-100Q377-94 363.5-84.5Q350-75 330-64Q310-53 289.5-44Q269-35 253-31ZM186-80L186-80Q204-76 214-76.5Q224-77 249-87L249-87Q257-89 264-91Q271-93 275-92L275-92Q275-92 289-101.5Q303-111 322-122L322-122Q375-156 404-197.5Q433-239 428-266L428-266Q429-274 425.5-281.5Q422-289 423-293L423-293Q420-293 413-295Q406-297 395-299L395-299Q383-302 371.5-301Q360-300 337.5-295.5Q315-291 269-280L269-280Q262-278 253-273.5Q244-269 235-267L235-267Q231-264 223-259.5Q215-255 208-253L208-253Q196-252 188-234Q180-216 169-166L169-166Q162-132 161-120Q160-108 165-100L165-100Q167-88 173-85Q179-82 186-80Z",
|
||||
stem: 52,
|
||||
strokes: [
|
||||
"M 421.4 -500 C 413.7 -498.8 388.7 -497.7 375 -492.9 C 361.3 -488.1 351.2 -479.7 339.3 -471.4 C 327.4 -463.1 321.5 -460.7 303.6 -442.9 C 285.7 -425 247 -387.5 232.1 -364.3 C 217.2 -341.1 223.2 -319.1 214.3 -303.6 C 205.4 -288.1 187.5 -282.1 178.6 -271.4 C 169.7 -260.7 167.3 -259.5 160.7 -239.3 C 154.1 -219.1 142.9 -172 139.3 -150 C 135.7 -128 137.5 -119 139.3 -107.1 C 141.1 -95.2 139.9 -87.5 150 -78.6 C 160.1 -69.7 187.5 -57.8 200 -53.6 C 212.5 -49.4 209.5 -50 225 -53.6 C 240.5 -57.2 273.3 -66.7 292.9 -75 C 312.5 -83.3 327.4 -92.9 342.9 -103.6 C 358.4 -114.3 368.5 -119.1 385.7 -139.3 C 403 -159.5 436.3 -203 446.4 -225 C 456.5 -247 448.8 -257.7 446.4 -271.4 C 444 -285.1 440.4 -298.2 432.1 -307.1 C 423.8 -316 413.7 -321.4 396.4 -325 C 379.2 -328.6 347.7 -331 328.6 -328.6 C 309.6 -326.2 301.2 -314.9 282.1 -310.7 C 263.1 -306.5 225.6 -304.8 214.3 -303.6",
|
||||
],
|
||||
},
|
||||
7: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M221 18L221 18Q209 16 200 5Q191-6 185-20L185-20Q181-39 201-86.5Q221-134 255-198L255-198Q277-234 276-240Q275-246 257-246L257-246Q248-248 236-250.5Q224-253 221-257L221-257Q205-265 202.5-274.5Q200-284 209-285L209-285Q213-287 212-290.5Q211-294 212-297L212-297Q215-298 219.5-297Q224-296 229-295L229-295Q243-295 250-295Q257-295 268-295L268-295Q273-295 280.5-295Q288-295 294-294L294-294Q308-292 313-296.5Q318-301 332-319L332-319Q340-333 357-356.5Q374-380 387-405L387-405Q435-473 440.5-496Q446-519 417-517L417-517Q401-516 381.5-510Q362-504 324-490L324-490Q287-474 272.5-470.5Q258-467 247-473L247-473Q241-474 237.5-479.5Q234-485 233.5-492.5Q233-500 234-507L234-507Q236-518 275-531Q314-544 365-557L365-557Q388-561 409-562Q430-563 445.5-560Q461-557 465-549L465-549Q465-549 467.5-546.5Q470-544 471-548L471-548Q475-547 477-540.5Q479-534 485-524L485-524Q495-507 497.5-499Q500-491 497-476L497-476Q494-465 487-454Q480-443 476-440L476-440Q471-436 468.5-431Q466-426 466-426L466-426Q465-422 460-418.5Q455-415 450-409L450-409Q449-405 445.5-400Q442-395 441-392L441-392Q440-391 433-380Q426-369 416-354Q406-339 397.5-326.5Q389-314 386-309L386-309Q372-296 372-290Q372-284 385-278L385-278Q402-271 413-261.5Q424-252 428-238L428-238Q429-218 423-215L423-215Q418-208 407.5-209Q397-210 394-214L394-214Q391-220 380.5-225.5Q370-231 360-234Q350-237 347-237L347-237Q343-235 339-229.5Q335-224 330-218L330-218Q323-208 311.5-185.5Q300-163 288.5-141.5Q277-120 271-110L271-110Q270-105 265.5-100Q261-95 259-88L259-88Q255-81 252-67.5Q249-54 249-43.5Q249-33 252-32L252-32Q260-34 263.5-30.5Q267-27 269-19L269-19Q274-12 266-2.5Q258 7 245 13.5Q232 20 221 18Z",
|
||||
stem: 52,
|
||||
strokes: [
|
||||
"M 260.7 -496.4 C 266.7 -499.4 273.2 -506.5 296.4 -514.3 C 319.6 -522 375.6 -538.7 400 -542.9 C 424.4 -547.1 433.4 -541.7 442.9 -539.3 C 452.4 -536.9 453.5 -538.7 457.1 -528.6 C 460.7 -518.5 482.2 -514.9 464.3 -478.6 C 446.5 -442.3 370.8 -345.8 350 -310.7 C 329.2 -275.6 341.1 -275 339.3 -267.9",
|
||||
"M 303.6 -264.3 C 301.8 -256 306 -244.7 292.9 -214.3 C 279.8 -183.9 237.5 -111.9 225 -82.1 C 212.5 -52.3 217.9 -47 217.9 -35.7 C 217.9 -24.4 221.4 -17.3 225 -14.3 C 228.6 -11.3 236.9 -17.3 239.3 -17.9",
|
||||
"M 232.1 -282.1 C 242.8 -280.3 284.5 -274.4 296.4 -271.4 C 308.3 -268.4 296.5 -264.9 303.6 -264.3 C 310.8 -263.7 323.2 -271.5 339.3 -267.9 C 355.4 -264.3 389.9 -247.1 400 -242.9",
|
||||
],
|
||||
},
|
||||
8: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M176 32L176 32Q169 31 162 27Q155 23 148.5 17Q142 11 136 6L136 6Q129-11 126-20Q123-29 126-50L126-50Q126-62 128.5-78Q131-94 138-104L138-104Q145-124 160.5-151Q176-178 194.5-205Q213-232 229.5-254.5Q246-277 254-287L254-287Q265-301 264.5-310.5Q264-320 244-347L244-347Q240-358 235-363.5Q230-369 234-369L234-369Q234-369 231.5-372.5Q229-376 226-383L226-383Q211-397 211.5-426Q212-455 226-483L226-483Q242-519 261.5-537.5Q281-556 311-560L311-560Q322-559 348-554Q374-549 402-543Q430-537 447-529L447-529Q459-524 474.5-513Q490-502 502-491.5Q514-481 515-475L515-475Q515-472 518.5-468.5Q522-465 523-460L523-460Q529-449 529.5-434Q530-419 527-404.5Q524-390 518-381L518-381Q493-353 483-349L483-349Q478-345 471.5-339Q465-333 457-331L457-331Q452-324 447.5-319Q443-314 439-314L439-314Q435-317 431-317.5Q427-318 427-314L427-314Q427-312 413.5-305Q400-298 386-293Q372-288 368-290L368-290Q368-290 365-290Q362-290 356-286L356-286Q343-288 371-254L371-254Q394-211 405.5-171.5Q417-132 402-111L402-111Q383-68 361-40.5Q339-13 326-14L326-14Q326-14 321.5-11Q317-8 312-5L312-5Q310 2 303.5 4.5Q297 7 292 6L292 6Q283 2 261 16L261 16Q248 25 230.5 29.5Q213 34 198 34.5Q183 35 176 32ZM192-19L192-19Q202-18 220-25Q238-32 258.5-43.5Q279-55 294-65L294-65Q332-96 345.5-125Q359-154 347-185L347-185Q341-197 334-211Q327-225 322-233L322-233Q316-242 311.5-246Q307-250 302-253L302-253Q302-253 294.5-247.5Q287-242 282-231L282-231Q271-217 264.5-207Q258-197 254-190L254-190Q247-183 241-177Q235-171 232-168L232-168Q232-165 226-150.5Q220-136 212-122L212-122Q190-84 184.5-52.5Q179-21 192-19ZM347-349L347-349Q368-368 383.5-370.5Q399-373 405-362L405-362Q407-358 428.5-371Q450-384 466-401L466-401Q472-412 473-418Q474-424 472-440L472-440Q473-450 470-456.5Q467-463 457.5-469Q448-475 427-483L427-483Q365-505 325-497Q285-489 271-442L271-442Q265-423 265-408Q265-393 273-376L273-376Q282-372 285-365.5Q288-359 288-359L288-359Q288-350 296-350L296-350Q298-345 310-339Q322-333 347-349Z",
|
||||
stem: 55,
|
||||
strokes: [
|
||||
"M 303.6 -296.4 C 300.6 -299.4 291.7 -304.8 285.7 -314.3 C 279.8 -323.8 273.3 -345.3 267.9 -353.6 C 262.5 -361.9 259 -352.4 253.6 -364.3 C 248.2 -376.2 236.9 -407.1 235.7 -425 C 234.5 -442.8 242.2 -459.5 246.4 -471.4 C 250.6 -483.3 254.8 -488.6 260.7 -496.4 C 266.7 -504.1 274.4 -512.5 282.1 -517.9 C 289.8 -523.3 287.5 -528 307.1 -528.6 C 326.8 -529.2 372.6 -527.9 400 -521.4 C 427.4 -514.8 457.1 -497 471.4 -489.3 C 485.7 -481.6 481.5 -482.1 485.7 -475 C 489.9 -467.8 494.6 -457.7 496.4 -446.4 C 498.2 -435.1 500 -419 496.4 -407.1 C 492.8 -395.2 486.3 -385.7 475 -375 C 463.7 -364.3 439.9 -348.2 428.6 -342.9 C 417.3 -337.5 417.8 -346.5 407.1 -342.9 C 396.4 -339.3 378.6 -326.8 364.3 -321.4 C 350 -316 333.3 -317.2 321.4 -310.7 C 309.5 -304.1 303 -291 292.9 -282.1 C 282.8 -273.2 279.2 -280.9 260.7 -257.1 C 242.2 -233.3 200 -170.2 182.1 -139.3 C 164.3 -108.3 158.4 -89.9 153.6 -71.4 C 148.9 -52.9 151.2 -40.5 153.6 -28.6 C 156 -16.7 157.8 -5.4 167.9 0 C 178 5.4 196.5 6.6 214.3 3.6 C 232.2 0.6 257.2 -9.6 275 -17.9 C 292.9 -26.2 307.7 -34.5 321.4 -46.4 C 335.1 -58.3 347.6 -72.6 357.1 -89.3 C 366.6 -106 376.8 -127.3 378.6 -146.4 C 380.4 -165.5 379.2 -178.6 367.9 -203.6 C 356.6 -228.6 320.2 -280.9 310.7 -296.4",
|
||||
],
|
||||
},
|
||||
9: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M313 27L313 27Q308 26 302 22Q296 18 292 13Q288 8 288 6L288 6Q289 1 284 1L284 1Q284 1 282-5.5Q280-12 281-19L281-19Q281-22 282-34Q283-46 283-53L283-53Q285-73 293-106Q301-139 312-175.5Q323-212 333-241L333-241Q347-277 345.5-285Q344-293 320-291L320-291Q289-286 263.5-299Q238-312 223-337.5Q208-363 210-392L210-392Q211-407 216.5-419.5Q222-432 235-454L235-454Q256-485 281-509.5Q306-534 333.5-550Q361-566 387-571L387-571Q403-574 425-573Q447-572 459-567L459-567Q474-561 487.5-544.5Q501-528 509-510Q517-492 514-480L514-480Q507-447 498-430Q489-413 476-399L476-399Q460-385 438.5-348Q417-311 395.5-263.5Q374-216 358-170L358-170Q344-128 340.5-94Q337-60 344-59L344-59Q348-59 354.5-56Q361-53 365-53L365-53Q368-49 368-44Q368-39 363-28L363-28Q360-21 353.5-11.5Q347-2 339 7Q331 16 324 21.5Q317 27 313 27ZM268-354L268-354Q283-338 309-344Q335-350 363.5-372Q392-394 413-427L413-427Q425-441 433.5-453.5Q442-466 446-469L446-469Q459-483 460.5-492Q462-501 455-508L455-508Q448-519 435-520Q422-521 398-512L398-512Q382-509 368.5-502.5Q355-496 334-482L334-482Q330-478 326-477.5Q322-477 318-478L318-478Q318-478 316-477.5Q314-477 314-473L314-473Q319-464 302-450L302-450Q276-422 267-393.5Q258-365 268-354Z",
|
||||
stem: 55,
|
||||
strokes: [
|
||||
"M 371.4 -325 C 357.1 -324.4 304.2 -320.8 285.7 -321.4 C 267.3 -322 267.8 -324.4 260.7 -328.6 C 253.6 -332.8 247.1 -338.1 242.9 -346.4 C 238.7 -354.7 236.3 -367.9 235.7 -378.6 C 235.1 -389.3 235.1 -398.8 239.3 -410.7 C 243.5 -422.6 251.8 -438.1 260.7 -450 C 269.6 -461.9 287 -474.4 292.9 -482.1 C 298.8 -489.8 281.5 -486.3 296.4 -496.4 C 311.3 -506.5 363.7 -534 382.1 -542.9 C 400.6 -551.8 395.2 -550 407.1 -550 C 419 -550 442.3 -547.7 453.6 -542.9 C 464.9 -538.1 470.3 -529.1 475 -521.4 C 479.8 -513.6 480.9 -504.7 482.1 -496.4 C 483.3 -488.1 483.9 -479.1 482.1 -471.4 C 480.3 -463.7 489.9 -474.4 471.4 -450 C 453 -425.6 389.3 -357.1 371.4 -325 C 353.5 -292.8 372.6 -288 364.3 -257.1 C 356 -226.2 330.3 -176.8 321.4 -139.3 C 312.5 -101.8 312.5 -50 310.7 -32.1",
|
||||
],
|
||||
},
|
||||
a: {
|
||||
adv: 437,
|
||||
outline:
|
||||
"M107-36L107-36Q103-44 97-48.5Q91-53 91-57L91-57Q91-61 90.5-65.5Q90-70 86-74L86-74Q81-80 87-101.5Q93-123 107-154.5Q121-186 140-221L140-221Q148-229 156-242Q164-255 168-259L168-259Q168-263 171-266.5Q174-270 178-270L178-270L178-276Q178-281 187.5-293Q197-305 211-319.5Q225-334 240-347.5Q255-361 265-367L265-367Q286-381 308.5-384Q331-387 347-374L347-374Q350-371 357-364Q364-357 373.5-348.5Q383-340 393-331L393-331L430-298L412-248Q399-208 405.5-178Q412-148 425-126L425-126Q430-115 445.5-105Q461-95 475-93L475-93Q483-92 490-87Q497-82 497-71L497-71Q497-61 493-55Q489-49 479-47L479-47Q446-43 417.5-63Q389-83 365-129L365-129Q361-135 354.5-151.5Q348-168 347-173L347-173Q343-168 332-154.5Q321-141 315-135L315-135Q244-63 192.5-38.5Q141-14 107-36ZM143-92L143-92Q143-85 164.5-92.5Q186-100 211-120L211-120Q245-149 279.5-184Q314-219 335-249L335-249L357-285Q347-308 335.5-316.5Q324-325 316-325L316-325Q303-325 277.5-301Q252-277 223-237Q194-197 168-149L168-149Q158-129 150.5-110.5Q143-92 143-92Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 364.3 -207.1 C 357.8 -204.7 342.3 -206.6 325 -192.9 C 307.7 -179.2 280.3 -143.5 260.7 -125 C 241.1 -106.5 223.8 -92.2 207.1 -82.1 C 190.4 -72 175.6 -66.1 160.7 -64.3 C 145.8 -62.5 125 -62.5 117.9 -71.4 C 110.8 -80.3 111.4 -96.5 117.9 -117.9 C 124.4 -139.3 139.3 -170.2 157.1 -200 C 175 -229.7 204.8 -272 225 -296.4 C 245.3 -320.8 266.1 -336.9 278.6 -346.4 C 291.1 -355.9 289.9 -353.6 300 -353.6 C 310.1 -353.6 326.8 -353.5 339.3 -346.4 C 351.8 -339.2 367.9 -323.8 375 -310.7 C 382.1 -297.6 383.9 -285.2 382.1 -267.9 C 380.3 -250.6 365.5 -219 364.3 -207.1 C 363.1 -195.2 372.6 -204.1 375 -196.4 C 377.4 -188.7 372.7 -176.8 378.6 -160.7 C 384.6 -144.6 399.4 -114.3 410.7 -100 C 422 -85.7 436.9 -79.8 446.4 -75 C 455.9 -70.2 464.3 -72 467.9 -71.4",
|
||||
],
|
||||
},
|
||||
b: {
|
||||
adv: 435,
|
||||
outline:
|
||||
"M211-17L211-17Q191-19 186.5-22Q182-25 176-38L176-38Q168-47 166.5-52Q165-57 166-61L166-61Q171-61 175-61.5Q179-62 180-65L180-65Q180-68 183.5-69Q187-70 191-69L191-69Q210-68 239.5-81.5Q269-95 303.5-119.5Q338-144 371-174L371-174Q397-208 409-227.5Q421-247 423-259.5Q425-272 420-281L420-281Q416-289 391-287.5Q366-286 335-266L335-266Q292-239 266-216Q240-193 218-164L218-164Q210-154 202.5-140Q195-126 190-116Q185-106 185-106L185-106Q185-102 173.5-96Q162-90 154-90L154-90Q150-92 143.5-97.5Q137-103 135-112L135-112Q128-117 125-120Q122-123 121-127L121-127Q120-133 124-146Q128-159 135-187L135-187Q141-213 148.5-241Q156-269 163-289Q170-309 173-311L173-311Q173-311 177-320Q181-329 181-341L181-341Q183-353 187.5-362Q192-371 192-375L192-375Q196-378 204-399Q212-420 224-454L224-454Q224-457 231-473.5Q238-490 248-512.5Q258-535 268.5-559.5Q279-584 286-603L286-603Q298-620 304.5-634.5Q311-649 318-652L318-652Q323-653 330.5-651.5Q338-650 345.5-646Q353-642 358-634L358-634Q361-629 362-625Q363-621 359-610.5Q355-600 343.5-575.5Q332-551 310-505L310-505Q282-445 264-398Q246-351 234-315Q222-279 212-253L212-253L207-231L232-255Q247-266 265-282Q283-298 297-310L297-310Q305-316 318.5-322.5Q332-329 345-334Q358-339 364-340L364-340Q367-341 379-343Q391-345 408-345L408-345Q430-345 443.5-335Q457-325 470-303L470-303Q481-282 479-255.5Q477-229 463-206L463-206Q458-202 453-195.5Q448-189 448-185L448-185Q447-178 430-156.5Q413-135 396-116L396-116Q378-97 352-78.5Q326-60 300.5-45.5Q275-31 257-25L257-25Q249-24 234-21.5Q219-19 211-17Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 328.6 -621.4 C 317.9 -598.8 284 -530.9 264.3 -485.7 C 244.7 -440.5 225 -388.7 210.7 -350 C 196.4 -311.3 184 -279.8 178.6 -253.6 C 173.3 -227.4 180.4 -205.4 178.6 -192.9 C 176.8 -180.4 171.5 -188.1 167.9 -178.6 C 164.3 -169.1 158.9 -142.8 157.1 -135.7",
|
||||
"M 178.6 -200 C 186.9 -203 209 -204.8 228.6 -217.9 C 248.2 -231 276.2 -263.1 296.4 -278.6 C 316.6 -294.1 331 -304.1 350 -310.7 C 369.1 -317.2 396.4 -318.5 410.7 -317.9 C 425 -317.3 429.8 -313.1 435.7 -307.1 C 441.7 -301.1 444.6 -292.8 446.4 -282.1 C 448.2 -271.4 448.8 -254.2 446.4 -242.9 C 444 -231.6 445.8 -233.3 432.1 -214.3 C 418.4 -195.2 395.3 -155.4 364.3 -128.6 C 333.4 -101.8 273.2 -66.7 246.4 -53.6 C 219.6 -40.5 210.7 -50.6 203.6 -50",
|
||||
],
|
||||
},
|
||||
c: {
|
||||
adv: 355,
|
||||
outline:
|
||||
"M209-11L209-11Q180-6 156-14.5Q132-23 117.5-43.5Q103-64 103-96L103-96Q100-126 110-165.5Q120-205 139.5-245Q159-285 182-314L182-314Q202-338 222-358Q242-378 258-385L258-385Q272-391 284.5-392.5Q297-394 305-394L305-394Q321-390 339.5-379.5Q358-369 358-357L358-357Q358-357 360.5-354.5Q363-352 363-352L363-352Q369-352 373.5-339Q378-326 379-309.5Q380-293 374-282L374-282Q370-263 360-249Q350-235 342-235L342-235Q338-235 334.5-234.5Q331-234 331-229L331-229Q331-224 325.5-225Q320-226 314-231.5Q308-237 305-245L305-245Q305-250 309-262Q313-274 317-293L317-293Q323-321 316-329.5Q309-338 289-330L289-330Q264-316 241.5-291Q219-266 200-226.5Q181-187 166-128L166-128Q160-105 161.5-88Q163-71 166-70L166-70Q180-66 204-68.5Q228-71 239-77L239-77Q255-85 275-98.5Q295-112 310-123L310-123Q315-126 322-133Q329-140 337-144L337-144Q345-156 351-158Q357-160 369-160L369-160Q377-158 384-154Q391-150 393.5-146Q396-142 390-139L390-139Q386-139 385.5-136.5Q385-134 385-134L385-134Q387-130 376.5-117.5Q366-105 350-90Q334-75 319.5-64.5Q305-54 299-54L299-54Q289-54 289-48L289-48Q289-46 274.5-38Q260-30 241.5-22Q223-14 209-11Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 325 -257.1 C 326.8 -258.9 332.1 -259.6 335.7 -267.9 C 339.3 -276.2 345.8 -295.2 346.4 -307.1 C 347 -319 342.9 -331 339.3 -339.3 C 335.7 -347.6 332.7 -352.9 325 -357.1 C 317.3 -361.3 303 -364.3 292.9 -364.3 C 282.8 -364.3 279.2 -367.8 264.3 -357.1 C 249.4 -346.4 223.3 -327.4 203.6 -300 C 184 -272.6 158.9 -223.8 146.4 -192.9 C 133.9 -162 130.4 -135.7 128.6 -114.3 C 126.8 -92.9 132.7 -75 135.7 -64.3 C 138.7 -53.6 139.3 -53.6 146.4 -50 C 153.6 -46.4 161.9 -42.3 178.6 -42.9 C 195.3 -43.5 226.8 -47.1 246.4 -53.6 C 266 -60.1 277.3 -67.8 296.4 -82.1 C 315.5 -96.4 350 -129.8 360.7 -139.3",
|
||||
],
|
||||
},
|
||||
d: {
|
||||
adv: 393,
|
||||
outline:
|
||||
"M362 19L362 19Q349 22 340.5 15.5Q332 9 328-3L328-3Q320-26 316.5-53.5Q313-81 315-102L315-102L317-123L278-92Q244-62 212.5-49Q181-36 157-36L157-36Q146-38 135-44Q124-50 115.5-59Q107-68 102-78L102-78Q96-92 98.5-116Q101-140 110.5-168.5Q120-197 134.5-224.5Q149-252 168-273L168-273Q201-323 239-334Q277-345 314-323L314-323Q340-312 346.5-313Q353-314 358-336L358-336Q362-352 366-368.5Q370-385 379-413L379-413Q404-489 422.5-532Q441-575 456-607L456-607Q467-630 474.5-635.5Q482-641 490-640L490-640Q501-640 510-632Q519-624 524-610L524-610Q528-601 527.5-594.5Q527-588 521-577L521-577Q508-553 490.5-513.5Q473-474 455.5-427Q438-380 423-334Q408-288 398-253L398-253Q392-231 387.5-197Q383-163 380-127.5Q377-92 376.5-64.5Q376-37 378-29L378-29Q384-9 383.5 1Q383 11 378 14.5Q373 18 362 19ZM193-92L193-92Q207-95 220.5-103Q234-111 249-122L249-122Q279-145 301-164Q323-183 330-222L330-222Q332-241 326.5-252.5Q321-264 309-274L309-274Q297-282 276.5-280.5Q256-279 234-259L234-259Q210-237 187.5-200Q165-163 154-109L154-109Q152-99 160-93Q168-87 193-92Z",
|
||||
stem: 60,
|
||||
strokes: [
|
||||
"M 360.7 -285.7 C 345.8 -289.9 289.3 -306.5 271.4 -310.7 C 253.6 -314.9 261.3 -313.1 253.6 -310.7 C 245.9 -308.3 240.5 -311.3 225 -296.4 C 209.5 -281.5 177.4 -250 160.7 -221.4 C 144 -192.8 129.8 -148.8 125 -125 C 120.2 -101.2 127.3 -88.1 132.1 -78.6 C 136.9 -69.1 146.5 -69.7 153.6 -67.9 C 160.8 -66.1 161.9 -64.9 175 -67.9 C 188.1 -70.9 212.5 -74.4 232.1 -85.7 C 251.8 -97 273.8 -125 292.9 -135.7 C 312 -146.4 337.5 -147.6 346.4 -150",
|
||||
"M 489.3 -600 C 479.8 -579.2 451.2 -526.2 432.1 -475 C 413.1 -423.8 386.9 -324.4 375 -292.9 C 363.1 -261.3 366.1 -317.2 360.7 -285.7 C 355.3 -254.1 344.1 -149.4 342.9 -103.6 C 341.7 -57.8 351.8 -26.2 353.6 -10.7",
|
||||
],
|
||||
},
|
||||
e: {
|
||||
adv: 325,
|
||||
outline:
|
||||
"M276-35L276-35Q255-17 227.5-14Q200-11 174-22.5Q148-34 131-58L131-58Q114-80 109-108.5Q104-137 108-183L108-183Q111-222 133-261.5Q155-301 186.5-332Q218-363 249-375L249-375Q264-385 274.5-384.5Q285-384 307-382L307-382Q330-379 341.5-373Q353-367 360-357L360-357Q375-342 380-327.5Q385-313 385-299L385-299Q381-277 355.5-245.5Q330-214 289-194L289-194Q267-187 241-188Q215-189 198-201L198-201Q179-209 173-198.5Q167-188 162-148L162-148Q158-110 168-92.5Q178-75 195-72L195-72Q215-68 226.5-66.5Q238-65 255-75L255-75Q271-85 275-86.5Q279-88 281.5-90.5Q284-93 294-103L294-103Q301-110 305-113.5Q309-117 314-117L314-117Q319-117 323-112.5Q327-108 331-106L331-106Q341-106 335.5-94Q330-82 314-65.5Q298-49 276-35ZM275-247L275-247Q286-252 298-261.5Q310-271 318.5-282.5Q327-294 327-302L327-302Q327-316 314-325.5Q301-335 275-328L275-328Q264-325 250-313.5Q236-302 223.5-288Q211-274 203.5-263Q196-252 198-250L198-250Q201-245 215-242.5Q229-240 246.5-241Q264-242 275-247Z",
|
||||
stem: 52,
|
||||
strokes: [
|
||||
"M 167.9 -235.7 C 169.7 -244 167.3 -267.8 178.6 -285.7 C 189.9 -303.6 218.5 -330.4 235.7 -342.9 C 253 -355.4 269 -358.3 282.1 -360.7 C 295.2 -363.1 305.4 -359.5 314.3 -357.1 C 323.2 -354.7 329.8 -352.9 335.7 -346.4 C 341.7 -339.9 347.6 -326.2 350 -317.9 C 352.4 -309.6 351.2 -303.5 350 -296.4 C 348.8 -289.2 348.8 -283.9 342.9 -275 C 337 -266.1 327.4 -252.4 314.3 -242.9 C 301.2 -233.4 285.7 -220.9 264.3 -217.9 C 242.9 -214.9 203.6 -222 185.7 -225 C 167.8 -228 164.2 -236.9 157.1 -235.7 C 150 -234.5 147.1 -227.4 142.9 -217.9 C 138.7 -208.4 133.3 -198.2 132.1 -178.6 C 130.9 -158.9 133.3 -117.3 135.7 -100 C 138.1 -82.7 140.5 -82.7 146.4 -75 C 152.4 -67.3 163.7 -58.4 171.4 -53.6 C 179.2 -48.8 179.8 -47 192.9 -46.4 C 206 -45.8 235.7 -47 250 -50 C 264.3 -53 267.3 -56 278.6 -64.3 C 289.9 -72.6 311.3 -94 317.9 -100",
|
||||
],
|
||||
},
|
||||
f: {
|
||||
adv: 291,
|
||||
outline:
|
||||
"M123 106L123 106Q117 110 109.5 104Q102 98 88 82L88 82Q81 74 79 65Q77 56 80 31L80 31Q84 20 84.5 10Q85 0 85-4L85-4Q85-10 88.5-32Q92-54 96-78.5Q100-103 103-116L103-116Q103-117 104-121Q105-125 106-128L106-128Q108-137 111.5-152Q115-167 118-189L118-189Q126-214 131.5-238.5Q137-263 137-272L137-272Q137-290 151-298L151-298Q152-303 157-313.5Q162-324 166-331L166-331Q186-388 199-421.5Q212-455 226-480L226-480Q232-497 239.5-515Q247-533 255-543L255-543Q260-556 278-576.5Q296-597 316-616.5Q336-636 348-646L348-646Q370-657 383-661Q396-665 417-665L417-665Q436-665 442.5-660.5Q449-656 462-644L462-644Q476-629 478-624Q480-619 480-605L480-605Q478-592 469.5-571.5Q461-551 451.5-534.5Q442-518 436-517L436-517Q424-513 418.5-519.5Q413-526 413-541L413-541Q413-548 409.5-554.5Q406-561 406-561L406-561Q406-564 407.5-571.5Q409-579 413-586L413-586Q418-596 415.5-601Q413-606 402-603L402-603Q399-601 386-591Q373-581 358.5-568.5Q344-556 333-545Q322-534 322-531L322-531Q322-531 318-522.5Q314-514 303-501L303-501Q296-491 290.5-481.5Q285-472 285-470L285-470Q285-466 284.5-461.5Q284-457 279-457L279-457Q279-457 276-448Q273-439 265-425L265-425Q261-415 255-404.5Q249-394 245-390L245-390L246-390Q246-390 241.5-377.5Q237-365 232-351.5Q227-338 227-336L227-336Q227-332 241.5-332Q256-332 276-335.5Q296-339 312-343L312-343Q326-347 331-347Q336-347 351-343L351-343Q359-338 364.5-328.5Q370-319 372.5-310.5Q375-302 372-298L372-298Q371-294 350-287Q329-280 308-276L308-276Q290-272 264-267Q238-262 218.5-258Q199-254 199-251L199-251Q195-251 192-246.5Q189-242 189-239L189-239Q189-225 185-218L185-218Q183-212 178-194Q173-176 167.5-154Q162-132 158.5-114Q155-96 155-90L155-90Q149-83 148-72.5Q147-62 147-54L147-54Q143-46 142-39.5Q141-33 141-29L141-29Q141-28 141-25Q141-22 140-19L140-19Q139-9 137.5 7.5Q136 24 136 40L136 40L133 69Q132 82 130 91.5Q128 101 123 106Z",
|
||||
stem: 60,
|
||||
strokes: [
|
||||
"M 332.1 -314.3 C 309.5 -311.9 224.4 -307.1 196.4 -300 C 168.4 -292.8 178 -312.5 164.3 -271.4 C 150.6 -230.3 124.4 -105.4 114.3 -53.6 C 104.2 -1.8 103.6 16.7 103.6 39.3 C 103.6 61.9 112.5 75 114.3 82.1",
|
||||
"M 428.6 -546.4 C 431 -557.1 442.3 -597 442.9 -610.7 C 443.5 -624.4 436.9 -624.4 432.1 -628.6 C 427.3 -632.8 423.8 -635.7 414.3 -635.7 C 404.8 -635.7 394.1 -641.1 375 -628.6 C 356 -616.1 317.3 -579.2 300 -560.7 C 282.7 -542.2 286.3 -546.5 271.4 -517.9 C 256.5 -489.3 223.2 -425.6 210.7 -389.3 C 198.2 -353 198.8 -314.9 196.4 -300",
|
||||
],
|
||||
},
|
||||
g: {
|
||||
adv: 355,
|
||||
outline:
|
||||
"M205 191L205 191Q177 202 146.5 187.5Q116 173 94 142L94 142Q90 136 84.5 123.5Q79 111 74.5 98Q70 85 69 75Q68 65 71 63L71 63Q74 60 81 61Q88 62 94.5 66Q101 70 103 76L103 76Q106 83 112.5 94Q119 105 127 114Q135 123 139 124L139 124Q158 133 167 132Q176 131 183 126L183 126Q200 113 211 92Q222 71 236 27L236 27Q247-10 258.5-51.5Q270-93 279-116L279-116Q282-128 286.5-136.5Q291-145 295-149L295-149L301-149Q279-135 262-117Q245-99 219-88L219-88Q213-81 189.5-79.5Q166-78 144-85L144-85Q130-93 123.5-97Q117-101 113.5-108Q110-115 104-130L104-130Q97-145 102-179Q107-213 120-247L120-247Q132-272 155-301Q178-330 204-353.5Q230-377 251-383L251-383Q259-387 268.5-388.5Q278-390 296-385L296-385Q313-383 322-377.5Q331-372 351-355L351-355Q364-342 372.5-329Q381-316 386-302L386-302Q393-287 391.5-274.5Q390-262 380-249L380-249Q371-236 361-223Q351-210 346-187L346-187Q341-157 330-119.5Q319-82 311-50L311-50L295 12Q270 97 247.5 138.5Q225 180 205 191ZM169-129L169-129Q189-130 207-137Q225-144 242-160.5Q259-177 276-203L276-203Q293-227 300-253.5Q307-280 305-305L305-305Q304-321 300-326Q296-331 283-329L283-329Q264-326 241-308.5Q218-291 198-265L198-265Q183-245 173-217L173-217Q163-187 161-160Q159-133 169-129Z",
|
||||
stem: 55,
|
||||
strokes: [
|
||||
"M 303.6 -167.9 C 296.5 -165.5 272.6 -160.7 260.7 -153.6 C 248.8 -146.4 244 -132.7 232.1 -125 C 220.2 -117.2 204.8 -108.3 189.3 -107.1 C 173.8 -105.9 149.4 -109.6 139.3 -117.9 C 129.2 -126.2 129.8 -143.4 128.6 -157.1 C 127.4 -170.8 128.5 -184.5 132.1 -200 C 135.7 -215.5 142.3 -234.5 150 -250 C 157.8 -265.5 161.9 -275.6 178.6 -292.9 C 195.3 -310.2 231 -342.9 250 -353.6 C 269.1 -364.3 281.6 -357.7 292.9 -357.1 C 304.2 -356.5 310.8 -357.7 317.9 -350 C 325 -342.3 332.1 -323.2 335.7 -310.7 C 339.3 -298.2 344.7 -297.6 339.3 -275 C 334 -252.4 309.6 -201.2 303.6 -175 C 297.7 -148.8 313.7 -160.7 303.6 -117.9 C 293.5 -75.1 255.4 43.4 242.9 82.1 C 230.4 120.8 236.4 102.4 228.6 114.3 C 220.9 126.2 207.1 146.5 196.4 153.6 C 185.7 160.7 177.4 159.5 164.3 157.1 C 151.2 154.7 129.2 147 117.9 139.3 C 106.6 131.6 101.8 120.8 96.4 110.7 C 91 100.6 87.5 83.9 85.7 78.6",
|
||||
],
|
||||
},
|
||||
h: {
|
||||
adv: 464,
|
||||
outline:
|
||||
"M136-54L136-54Q129-53 121.5-54.5Q114-56 107-61L107-61Q99-69 96-73.5Q93-78 94.5-88Q96-98 101-119L101-119Q106-135 113-163.5Q120-192 128-220L128-220Q131-236 136.5-253Q142-270 148-284.5Q154-299 157-307L157-307Q165-335 172.5-356Q180-377 191-404.5Q202-432 218-478L218-478Q240-538 251.5-569.5Q263-601 268.5-615.5Q274-630 276-638L276-638Q276-646 280.5-650Q285-654 292-654L292-654Q306-654 314.5-640.5Q323-627 325-608.5Q327-590 319-574L319-574Q303-538 285-488.5Q267-439 250-391.5Q233-344 218-311L218-311Q218-307 215-297Q212-287 208-279L208-279Q204-271 200.5-264Q197-257 197-257L197-257Q197-253 194-242Q191-231 186-215L186-215Q182-203 176.5-184Q171-165 169-152L169-152Q174-161 190-179Q206-197 220-210L220-210Q236-224 256-241Q276-258 288-266L288-266Q300-274 309.5-280.5Q319-287 319-287L319-287Q319-291 322.5-291.5Q326-292 330-292L330-292Q334-292 334.5-293Q335-294 335-298L335-298Q335-303 346.5-307Q358-311 373.5-313.5Q389-316 399-316L399-316Q411-316 418-311Q425-306 436-289L436-289Q448-273 451.5-250.5Q455-228 447-204L447-204Q447-196 444.5-181.5Q442-167 442-151L442-151L436-119L452-119Q460-119 467-116.5Q474-114 474-114L474-114Q478-110 482-106.5Q486-103 490-103L490-103Q502-103 499-93.5Q496-84 484-76L484-76Q460-60 440.5-60Q421-60 405-76L405-76Q389-93 385-114Q381-135 389-167L389-167Q393-211 393.5-231.5Q394-252 383-252L383-252Q373-252 348-238.5Q323-225 292-202Q261-179 231-151Q201-123 181-93L181-93Q169-78 158-66.5Q147-55 136-54Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 292.9 -621.4 C 292.9 -617.2 310.2 -647.6 292.9 -596.4 C 275.6 -545.2 209.6 -366.7 189.3 -314.3 C 169.1 -261.9 179.1 -304.7 171.4 -282.1 C 163.7 -259.5 148.3 -201.8 142.9 -178.6 C 137.6 -155.4 138.7 -150 139.3 -142.9 C 139.9 -135.7 145.2 -136.9 146.4 -135.7",
|
||||
"M 157.1 -135.7 C 162.5 -137.5 176.8 -136.9 189.3 -146.4 C 201.8 -155.9 211.9 -175 232.1 -192.9 C 252.3 -210.8 289.3 -238.7 310.7 -253.6 C 332.1 -268.5 347.6 -276.7 360.7 -282.1 C 373.8 -287.5 381 -287.5 389.3 -285.7 C 397.6 -283.9 405.9 -277.3 410.7 -271.4 C 415.5 -265.4 418.5 -272.6 417.9 -250 C 417.3 -227.4 407.7 -160.1 407.1 -135.7 C 406.5 -111.3 408.9 -110.7 414.3 -103.6 C 419.7 -96.5 428.6 -94.1 439.3 -92.9 C 450 -91.7 472.1 -95.8 478.6 -96.4",
|
||||
"M 164.3 -153.6 C 164.3 -154.8 163.1 -160.7 164.3 -160.7 C 165.5 -160.7 171.4 -154.8 171.4 -153.6 C 171.4 -152.4 166.7 -156.6 164.3 -153.6 C 161.9 -150.6 158.3 -138.7 157.1 -135.7",
|
||||
],
|
||||
},
|
||||
i: {
|
||||
adv: 187,
|
||||
outline:
|
||||
"M111-32L111-32Q103-40 99.5-52Q96-64 97-76Q98-88 102-96L102-96Q102-104 104.5-108.5Q107-113 106-117L106-117Q105-121 108-131Q111-141 114-150L114-150Q121-166 129-188.5Q137-211 144-233Q151-255 156-271Q161-287 162-291L162-291Q162-303 178-304L178-304Q190-308 196-304.5Q202-301 212-285L212-285Q220-277 218-261Q216-245 205-221L205-221Q201-213 195.5-198Q190-183 188-171L188-171Q184-163 178.5-148.5Q173-134 169-121L169-121Q167-116 165-106.5Q163-97 160-86.5Q157-76 152-68L152-68Q143-27 135.5-20Q128-13 111-32ZM208-438L208-438Q203-443 199-452.5Q195-462 194-470.5Q193-479 196-479L196-479Q204-480 215-496Q226-512 224-524L224-524Q224-528 227-531.5Q230-535 234-536L234-536Q246-544 257.5-538.5Q269-533 279-517L279-517Q288-498 284-485.5Q280-473 262-451L262-451Q247-431 234.5-429Q222-427 208-438Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 182.1 -275 C 182.1 -272 191 -285.1 182.1 -257.1 C 173.2 -229.1 138.7 -137.4 128.6 -107.1 C 118.5 -76.7 122 -85.7 121.4 -75 C 120.8 -64.3 124.4 -48.2 125 -42.9",
|
||||
"M 246.4 -500 L 239.3 -475",
|
||||
],
|
||||
},
|
||||
j: {
|
||||
adv: 204,
|
||||
outline:
|
||||
"M60 194L60 194Q38 216 3.5 215.5Q-31 215-55 190L-55 190Q-67 171-73.5 149Q-80 127-79.5 110.5Q-79 94-69 94L-69 94Q-64 94-60 97Q-56 100-53 105.5Q-50 111-50 119L-50 119Q-50 134-43 146Q-36 158-23 158L-23 158Q-12 162 6.5 152Q25 142 36 124L36 124Q46 111 60.5 83Q75 55 89.5 24.5Q104-6 111-25L111-25Q123-66 132.5-109Q142-152 152-201L152-201Q156-228 161.5-247.5Q167-267 167-271L167-271Q167-275 168-287.5Q169-300 174-310L174-310Q174-325 176.5-336.5Q179-348 179-352L179-352Q182-355 187.5-354Q193-353 199.5-350Q206-347 211-342L211-342Q219-334 221.5-326Q224-318 222.5-301Q221-284 216-247L216-247Q216-241 213-222.5Q210-204 205-181Q200-158 195-136.5Q190-115 187-101L187-101Q183-86 178-68Q173-50 172-43L172-43Q171-37 165-19.5Q159-2 152.5 15Q146 32 145 39L145 39Q144 43 139.5 58Q135 73 121 104L121 104Q116 116 104.5 135Q93 154 81 170.5Q69 187 60 194ZM218-485L218-485Q207-498 210.5-519Q214-540 230-561L230-561Q243-576 258-578Q273-580 286-569L286-569Q297-552 297-541.5Q297-531 286-515L286-515Q261-487 246-480.5Q231-474 218-485Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 189.3 -332.1 C 190.5 -328.5 196.4 -325.6 196.4 -310.7 C 196.4 -295.8 197.6 -285.2 189.3 -242.9 C 181 -200.6 159.5 -105.9 146.4 -57.1 C 133.3 -8.3 122.6 20.3 110.7 50 C 98.8 79.8 87.5 101.2 75 121.4 C 62.5 141.6 46.4 161.3 35.7 171.4 C 25 181.5 19 180.3 10.7 182.1 C 2.4 183.9 -5.4 183.9 -14.3 182.1 C -23.2 180.3 -35.8 175.6 -42.9 171.4 C -50 167.2 -52.9 167.2 -57.1 157.1 C -61.3 147 -66.1 118.4 -67.9 110.7",
|
||||
"M 253.6 -539.3 L 239.3 -517.9",
|
||||
],
|
||||
},
|
||||
k: {
|
||||
adv: 365,
|
||||
outline:
|
||||
"M112-5L112-5Q104-8 95-17.5Q86-27 80-40Q74-53 74-64L74-64Q79-75 87-102.5Q95-130 107-166L107-166Q115-177 119.5-192Q124-207 128-219L128-219Q136-235 147-260.5Q158-286 168.5-312.5Q179-339 187-357L187-357Q200-384 209.5-406.5Q219-429 228.5-452Q238-475 249-502L249-502Q261-530 273.5-559Q286-588 295.5-611.5Q305-635 307-645L307-645Q307-653 310.5-657Q314-661 318-661L318-661Q322-665 326-665.5Q330-666 334-666L334-666Q338-666 349.5-651Q361-636 361-624L361-624Q361-608 345.5-564Q330-520 302-464L302-464Q268-390 247-342.5Q226-295 218-271.5Q210-248 212-245L212-245Q212-245 226-251.5Q240-258 256-270L256-270Q267-276 280-285Q293-294 305-302.5Q317-311 325-313L325-313Q337-321 350-328.5Q363-336 367-340L367-340Q384-348 392.5-345Q401-342 410-330L410-330Q421-317 414-307.5Q407-298 381-282L381-282Q360-269 330.5-251Q301-233 278.5-219Q256-205 253-202L253-202Q253-202 261-187Q269-172 277-160L277-160Q285-144 294.5-130.5Q304-117 304-112L304-112Q315-81 337-67Q359-53 383-64L383-64Q403-72 412.5-69Q422-66 426-54L426-54Q430-46 428.5-42.5Q427-39 415-27L415-27Q396-8 376.5-3Q357 2 341-6L341-6Q326-5 304-25.5Q282-46 241-99L241-99Q241-103 237.5-110.5Q234-118 230-125L230-125Q226-133 220-142.5Q214-152 214-152L214-152Q214-156 211-161Q208-166 203-173L203-173Q187-178 180-167Q173-156 165-144L165-144Q160-131 152.5-107.5Q145-84 140.5-62Q136-40 138-32L138-32Q138-16 133-10.5Q128-5 112-5Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 325 -628.6 C 325.6 -626.8 331.6 -629.8 328.6 -617.9 C 325.6 -606 330.9 -613.1 307.1 -557.1 C 283.3 -501.1 207.7 -339.2 185.7 -282.1 C 163.7 -225 176.8 -225.6 175 -214.3",
|
||||
"M 378.6 -314.3 C 353 -299.4 251.8 -241.7 225 -225 C 198.2 -208.3 226.2 -216.1 217.9 -214.3 C 209.6 -212.5 185.7 -216.7 175 -214.3 C 164.3 -211.9 164.3 -220.8 153.6 -200 C 142.9 -179.2 118.5 -113.7 110.7 -89.3 C 103 -64.9 106.5 -61.9 107.1 -53.6 C 107.7 -45.3 113.1 -41.7 114.3 -39.3",
|
||||
"M 217.9 -214.3 C 224.4 -200 241.6 -155.4 257.1 -128.6 C 272.6 -101.8 294.6 -69.1 310.7 -53.6 C 326.8 -38.1 339.3 -36.9 353.6 -35.7 C 367.9 -34.5 389.3 -44.6 396.4 -46.4",
|
||||
],
|
||||
},
|
||||
l: {
|
||||
adv: 169,
|
||||
outline:
|
||||
"M130 0L130 0Q111 5 99.5-8Q88-21 86-48Q84-75 92-112L92-112Q97-124 100-135.5Q103-147 103-154L103-154Q105-164 111-183.5Q117-203 124-224Q131-245 137.5-261.5Q144-278 146-282L146-282Q150-282 153-289Q156-296 156-303L156-303Q156-311 159.5-321Q163-331 167-335L167-335Q167-340 169.5-346.5Q172-353 172-357L172-357Q172-361 174.5-365Q177-369 177-373L177-373Q184-386 195.5-414.5Q207-443 220.5-478Q234-513 246-547Q258-581 265.5-604.5Q273-628 273-633L273-633Q273-641 277-645Q281-649 289-649L289-649Q292-652 296.5-653Q301-654 305-654L305-654Q305-654 309-650Q313-646 321-638L321-638Q329-631 330.5-622Q332-613 332-601L332-601Q332-588 329-578Q326-568 317-549Q308-530 289-490L289-490Q281-470 272-452.5Q263-435 263-431L263-431Q263-427 259.5-419Q256-411 252-399L252-399Q248-391 241-375.5Q234-360 226.5-342.5Q219-325 214-312.5Q209-300 209-298L209-298Q209-294 208.5-290Q208-286 204-282L204-282Q200-282 199.5-276.5Q199-271 199-271L199-271Q199-261 183-218L183-218Q167-163 153.5-117Q140-71 140-43L140-43Q140-19 139-11.5Q138-4 130 0Z",
|
||||
stem: 60,
|
||||
strokes: [
|
||||
"M 292.9 -621.4 C 292.9 -614.8 310.2 -629.1 292.9 -582.1 C 275.6 -535.1 209 -389.9 189.3 -339.3 C 169.7 -288.7 184.5 -307.8 175 -278.6 C 165.5 -249.4 142.8 -197 132.1 -164.3 C 121.4 -131.5 113.1 -104.1 110.7 -82.1 C 108.3 -60.1 116.7 -40.4 117.9 -32.1",
|
||||
],
|
||||
},
|
||||
m: {
|
||||
adv: 560,
|
||||
outline:
|
||||
"M551-42L551-42Q546-44 536-52Q526-60 519-79.5Q512-99 512-134L512-134Q513-164 513.5-181Q514-198 506-198L506-198Q498-198 475.5-182.5Q453-167 424-141Q395-115 365-83L365-83Q355-72 345.5-62.5Q336-53 327-50.5Q318-48 306-57L306-57L295-66Q290-71 289-76.5Q288-82 290-89L290-89L313-155Q329-191 335-207.5Q341-224 341-228.5Q341-233 338-232L338-232Q330-231 314-222.5Q298-214 293-206L293-206Q289-206 281-197Q273-188 261-180L261-180Q254-168 233.5-149Q213-130 193-110L193-110Q180-99 167.5-83Q155-67 146.5-55Q138-43 135-43L135-43Q122-43 111.5-50Q101-57 96-73L96-73Q88-90 96-120.5Q104-151 114-200L114-200Q118-215 119-222Q120-229 122.5-236.5Q125-244 131.5-260Q138-276 152-308L152-308Q159-327 160.5-336.5Q162-346 162-350L162-350Q162-360 167.5-367Q173-374 181-377Q189-380 196-378L196-378Q207-376 215.5-367Q224-358 225-342L225-342Q226-330 223-320.5Q220-311 217-303L217-303L206-276Q201-265 193-244Q185-223 179-205.5Q173-188 173-185L173-185Q174-182 180-186.5Q186-191 195.5-199.5Q205-208 215-216L215-216Q236-236 262.5-254.5Q289-273 313-286Q337-299 350-303L350-303Q367-303 373.5-297Q380-291 396-275L396-275Q403-268 404-257Q405-246 403-236L403-236Q402-232 397-216.5Q392-201 387.5-187Q383-173 383-173L383-173Q383-173 394.5-183Q406-193 420-204.5Q434-216 441-222L441-222Q480-252 509-260Q538-268 556-239L556-239Q568-228 568.5-215Q569-202 569-170L569-170Q570-157 571-139Q572-121 573.5-106.5Q575-92 577-89L577-89Q580-85 588.5-82.5Q597-80 611-78L611-78Q626-76 629-65Q632-54 616-44L616-44Q601-35 583-36Q565-37 551-42Z",
|
||||
stem: 60,
|
||||
strokes: [
|
||||
"M 189.3 -339.3 C 186.9 -330.4 182.2 -305.3 175 -285.7 C 167.9 -266 152.4 -242.2 146.4 -221.4 C 140.5 -200.6 139.3 -174.4 139.3 -160.7 C 139.3 -147 148.2 -147.6 146.4 -139.3 C 144.6 -131 132.2 -119 128.6 -110.7 C 125 -102.4 125.6 -92.9 125 -89.3",
|
||||
"M 146.4 -150 C 155.3 -152.4 176.8 -148.8 200 -164.3 C 223.2 -179.8 267.3 -227.4 285.7 -242.9 C 304.2 -258.4 300 -253.5 310.7 -257.1 C 321.4 -260.7 341.1 -264.9 350 -264.3 C 358.9 -263.7 363.7 -273.8 364.3 -253.6 C 364.9 -233.4 360.8 -169.7 353.6 -142.9 C 346.5 -116.1 326.8 -101.2 321.4 -92.9",
|
||||
"M 353.6 -146.4 C 363.1 -149.4 390.5 -152.4 410.7 -164.3 C 430.9 -176.2 456 -207.8 475 -217.9 C 494.1 -228 514.3 -228 525 -225 C 535.7 -222 536.9 -217.3 539.3 -200 C 541.7 -182.7 537.5 -141.6 539.3 -121.4 C 541.1 -101.2 544.1 -88.7 550 -78.6 C 556 -68.5 566.7 -63.1 575 -60.7 C 583.3 -58.3 595.8 -63.7 600 -64.3",
|
||||
],
|
||||
},
|
||||
n: {
|
||||
adv: 450,
|
||||
outline:
|
||||
"M454-46L454-46Q434-46 421-54.5Q408-63 399-83L399-83Q395-97 392.5-114.5Q390-132 390-147.5Q390-163 393-168L393-168Q397-172 396.5-182.5Q396-193 396-205L396-205Q395-225 386.5-227.5Q378-230 362-221L362-221Q344-210 315-191.5Q286-173 254.5-151Q223-129 195-105L195-105Q173-81 168.5-68.5Q164-56 156-56L156-56Q148-52 142.5-55.5Q137-59 127-67L127-67Q120-75 113-82.5Q106-90 104-94L104-94Q104-99 107-115Q110-131 114.5-147Q119-163 121-168L121-168Q126-168 129.5-178Q133-188 141-200L141-200Q143-212 151.5-232.5Q160-253 167-269L167-269Q181-293 186.5-309Q192-325 194-349L194-349Q193-353 196-356.5Q199-360 203-360L203-360L203-360L203-360Q211-368 222-364.5Q233-361 242-349L242-349Q260-325 256.5-299.5Q253-274 224-227L224-227Q217-211 213.5-202.5Q210-194 210-189L210-189Q210-189 220.5-196Q231-203 244.5-212.5Q258-222 266-227L266-227Q271-232 289-245Q307-258 327.5-270.5Q348-283 358-285L358-285Q366-289 375-292.5Q384-296 388-296L388-296Q396-296 408.5-287.5Q421-279 432-267.5Q443-256 445-248L445-248Q445-244 445.5-240Q446-236 447-232L447-232Q451-219 451.5-197Q452-175 450-157Q448-139 443-136L443-136Q443-136 443.5-133.5Q444-131 448-131L448-131Q448-127 448-126.5Q448-126 448-126L448-126Q443-126 444-117.5Q445-109 450.5-99Q456-89 462-83L462-83Q474-71 472-60.5Q470-50 454-46Z",
|
||||
stem: 60,
|
||||
strokes: [
|
||||
"M 214.3 -325 C 214.3 -318.4 219.7 -302.4 214.3 -285.7 C 208.9 -269 189.3 -246.4 182.1 -225 C 175 -203.6 177.9 -174.4 171.4 -157.1 C 164.9 -139.8 147.1 -131.5 142.9 -121.4 C 138.7 -111.3 145.8 -100.6 146.4 -96.4",
|
||||
"M 171.4 -157.1 C 181.5 -159.5 205.3 -157.1 232.1 -171.4 C 258.9 -185.7 307.1 -228 332.1 -242.9 C 357.1 -257.8 370.2 -258.9 382.1 -260.7 C 394 -262.5 397.6 -257.8 403.6 -253.6 C 409.6 -249.4 415.5 -260.7 417.9 -235.7 C 420.3 -210.7 413.7 -131.6 417.9 -103.6 C 422.1 -75.6 438.7 -73.9 442.9 -67.9",
|
||||
],
|
||||
},
|
||||
o: {
|
||||
adv: 353,
|
||||
outline:
|
||||
"M169-15L169-15Q156-15 148.5-20Q141-25 130-34L130-34Q111-54 103-72.5Q95-91 98-108L98-108Q101-116 100.5-123.5Q100-131 100-131L100-131Q95-136 97-137.5Q99-139 101-143L101-143Q107-143 105-154L105-154Q103-162 113-189Q123-216 138-240L138-240Q144-250 156-266.5Q168-283 182.5-299.5Q197-316 209-326L209-326Q234-347 253-357Q272-367 294-372L294-372Q316-377 324.5-377Q333-377 348-361L348-361Q371-351 381.5-337Q392-323 395-296L395-296Q398-266 390.5-227.5Q383-189 343-132L343-132Q318-96 297-78.5Q276-61 247-44L247-44Q226-30 209-23.5Q192-17 169-15ZM265-121L265-121Q289-148 303.5-172Q318-196 331-233L331-233Q335-244 336-260.5Q337-277 335.5-291.5Q334-306 328-308L328-308Q316-315 299-312.5Q282-310 262-293L262-293Q231-267 208-228.5Q185-190 163-141L163-141Q148-105 156.5-83.5Q165-62 193.5-68.5Q222-75 265-121Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 289.3 -342.9 C 285.1 -341.7 276.2 -342.8 264.3 -335.7 C 252.4 -328.5 235.2 -319 217.9 -300 C 200.6 -280.9 175.6 -245.8 160.7 -221.4 C 145.8 -197 134.5 -174.4 128.6 -153.6 C 122.7 -132.8 123.8 -111.3 125 -96.4 C 126.2 -81.5 129.1 -72.6 135.7 -64.3 C 142.3 -56 153.6 -48.8 164.3 -46.4 C 175 -44 187.5 -46.4 200 -50 C 212.5 -53.6 222.6 -56 239.3 -67.9 C 256 -79.8 280.4 -95.8 300 -121.4 C 319.6 -147 347 -191.6 357.1 -221.4 C 367.2 -251.2 361.3 -282.7 360.7 -300 C 360.1 -317.3 358.4 -318.4 353.6 -325 C 348.8 -331.5 342.8 -336.3 332.1 -339.3 C 321.4 -342.3 296.4 -342.3 289.3 -342.9",
|
||||
],
|
||||
},
|
||||
p: {
|
||||
adv: 378,
|
||||
outline:
|
||||
"M61 224L61 224Q57 224 51 224.5Q45 225 40 216L40 216Q36 211 35.5 201Q35 191 40 165L40 165Q45 148 48 131.5Q51 115 51 107L51 107Q51 103 53.5 91.5Q56 80 56 75L56 75Q61 67 64 53Q67 39 67 28L67 28Q67 16 79 6L79 6Q79-2 82.5-14Q86-26 86-30L86-30Q89-44 99.5-79.5Q110-115 125.5-160Q141-205 156-248Q171-291 183-321L183-321Q195-349 199.5-369.5Q204-390 204-400L204-400Q204-404 206-406.5Q208-409 215-417L215-417Q225-427 232.5-423Q240-419 251-411L251-411Q259-398 262.5-389Q266-380 262-358L262-358Q262-343 257-332Q252-321 248-315.5Q244-310 246-311L246-311Q287-330 318.5-331.5Q350-333 371.5-320.5Q393-308 404-286Q415-264 415-235L415-235Q415-223 410-206.5Q405-190 397.5-176.5Q390-163 384-157L384-157Q380-157 376-150.5Q372-144 368-136L368-136Q360-126 342.5-113.5Q325-101 308-93.5Q291-86 283-89L283-89Q283-89 280.5-88.5Q278-88 278-83L278-83Q278-81 262-74.5Q246-68 225-62.5Q204-57 188-57L188-57Q167-57 158-65L158-65Q156-63 153.5-59Q151-55 151-52L151-52Q151-46 146.5-29.5Q142-13 136 2Q130 17 125 17L125 17Q125 17 123 20Q121 23 125 28L125 28Q125 28 124.5 39.5Q124 51 119 59L119 59Q119 67 116 83Q113 99 109 107L109 107Q109 119 105.5 130.5Q102 142 98 142L98 142Q94 143 93 154.5Q92 166 92 171L92 171Q91 196 82 210Q73 224 61 224ZM182-110L182-110Q190-110 215-117Q240-124 266.5-134.5Q293-145 306-156L306-156Q332-179 346.5-208.5Q361-238 344-265L344-265Q336-279 315-276.5Q294-274 269-260Q244-246 222-225L222-225Q210-212 200-193Q190-174 183.5-155Q177-136 176-123Q175-110 182-110Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 228.6 -292.9 C 231.6 -292.3 232.1 -286.9 246.4 -289.3 C 260.7 -291.7 296.4 -305.3 314.3 -307.1 C 332.2 -308.9 342.9 -308.3 353.6 -300 C 364.3 -291.7 375 -273.2 378.6 -257.1 C 382.2 -241 381 -220.8 375 -203.6 C 369.1 -186.3 356.6 -167.9 342.9 -153.6 C 329.2 -139.3 314.3 -128.6 292.9 -117.9 C 271.5 -107.2 240.5 -93.5 214.3 -89.3 C 188.1 -85.1 150 -93.5 135.7 -92.9 C 121.4 -92.3 134.5 -101.8 128.6 -85.7 C 122.7 -69.6 104.8 -14.8 100 3.6 C 95.2 22.1 101.8 18.5 100 25 C 98.2 31.6 92.9 29.2 89.3 42.9 C 85.7 56.6 82.2 93.4 78.6 107.1 C 75 120.8 71.5 110.1 67.9 125 C 64.3 139.9 58.9 184.5 57.1 196.4",
|
||||
"M 228.6 -382.1 C 226.8 -366.6 225.7 -313.1 217.9 -289.3 C 210.2 -265.5 192.2 -257.7 182.1 -239.3 C 172 -220.8 163.6 -203 157.1 -178.6 C 150.6 -154.2 145.3 -107.2 142.9 -92.9",
|
||||
"M 242.9 -310.7 C 244.1 -310.7 250 -309.5 250 -310.7 C 250 -311.9 244.1 -318.5 242.9 -317.9 C 241.7 -317.3 245.3 -311.3 242.9 -307.1 C 240.5 -302.9 231 -295.3 228.6 -292.9",
|
||||
],
|
||||
},
|
||||
q: {
|
||||
adv: 377,
|
||||
outline:
|
||||
"M246 220L246 220Q234 212 229 204.5Q224 197 219 177L219 177Q214 150 218.5 105.5Q223 61 236 7.5Q249-46 267-99L267-99Q272-107 273-115.5Q274-124 273-128L273-128Q226-106 202-96.5Q178-87 161-87L161-87Q148-87 134-98Q120-109 111-125.5Q102-142 102-158L102-158Q102-174 119-205Q136-236 156-264L156-264Q164-278 183-296Q202-314 223-330Q244-346 257-354L257-354Q265-359 273-362.5Q281-366 289-370L289-370Q297-375 304-378Q311-381 315-381L315-381Q331-385 351.5-385Q372-385 384-381L384-381Q396-373 407-355Q418-337 422-317L422-317Q422-307 421-298.5Q420-290 414.5-276Q409-262 395-232L395-232Q379-208 368.5-185Q358-162 358-158L358-158Q353-145 343.5-124Q334-103 326-85.5Q318-68 315-62L315-62Q313-52 306-26.5Q299-1 291.5 29.5Q284 60 278 87L278 87Q270 131 270 164Q270 197 278 209L278 209Q282 221 272 224.5Q262 228 246 220ZM174-178L163-149Q196-139 229.5-159Q263-179 294-209L294-209Q311-225 326.5-249Q342-273 350.5-294.5Q359-316 356-324L356-324Q352-332 327-322.5Q302-313 270-289L270-289Q234-261 210-233.5Q186-206 174-178L174-178Z",
|
||||
stem: 60,
|
||||
strokes: [
|
||||
"M 307.1 -157.1 C 283.3 -151.1 192.3 -125.6 164.3 -121.4 C 136.3 -117.2 144.1 -123.2 139.3 -132.1 C 134.5 -141 129.8 -155.9 135.7 -175 C 141.6 -194 156 -222.6 175 -246.4 C 194.1 -270.2 225 -299.4 250 -317.9 C 275 -336.3 306.6 -350.6 325 -357.1 C 343.5 -363.6 351.8 -359.5 360.7 -357.1 C 369.6 -354.7 375 -353.6 378.6 -342.9 C 382.2 -332.2 384.5 -308.4 382.1 -292.9 C 379.7 -277.4 376.8 -272 364.3 -250 C 351.8 -228 318.4 -186.3 307.1 -160.7 C 295.8 -135.1 305.9 -132.7 296.4 -96.4 C 286.9 -60.1 258.9 13.6 250 57.1 C 241.1 100.6 241.7 139.3 242.9 164.3 C 244.1 189.3 254.7 200 257.1 207.1",
|
||||
],
|
||||
},
|
||||
r: {
|
||||
adv: 360,
|
||||
outline:
|
||||
"M119-35L119-35Q110-42 106.5-49Q103-56 106-76L106-76Q109-104 114-125.5Q119-147 126-175L126-175Q128-187 137.5-216Q147-245 161-279Q175-313 189-343.5Q203-374 213-389L213-389Q221-395 232.5-394Q244-393 252-385L252-385Q263-376 255.5-353.5Q248-331 225-283L225-283Q213-256 207-242.5Q201-229 202-226.5Q203-224 211-229L211-229Q217-240 233-254.5Q249-269 267-283Q285-297 301-308Q317-319 323-322L323-322Q331-327 341.5-330.5Q352-334 356-336L356-336Q379-347 403-344Q427-341 433-326L433-326Q434-318 439.5-314Q445-310 445-310L445-310Q449-310 449.5-307Q450-304 450-304L450-304Q450-297 439.5-295.5Q429-294 413-293L413-293Q400-292 390-288Q380-284 366-278L366-278Q328-256 293.5-235Q259-214 227-179L227-179Q220-171 213-160Q206-149 198-136L198-136Q193-131 188-114Q183-97 179.5-81.5Q176-66 176-63L176-63Q176-63 173-53.5Q170-44 163-35L163-35Q151-19 143-19Q135-19 119-35Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 228.6 -367.9 C 220.3 -350 188.1 -291.7 178.6 -260.7 C 169.1 -229.7 175 -198.8 171.4 -182.1 C 167.8 -165.4 163.1 -178.5 157.1 -160.7 C 151.1 -142.8 139.3 -89.3 135.7 -75",
|
||||
"M 171.4 -192.9 C 179.7 -195.9 204.7 -200 221.4 -210.7 C 238.1 -221.4 248.8 -240.4 271.4 -257.1 C 294 -273.8 335.1 -300.6 357.1 -310.7 C 379.1 -320.8 392.9 -318.5 403.6 -317.9 C 414.3 -317.3 415.5 -308.9 421.4 -307.1 C 427.4 -305.3 436.3 -307.1 439.3 -307.1",
|
||||
],
|
||||
},
|
||||
s: {
|
||||
adv: 341,
|
||||
outline:
|
||||
"M161 4L161 4Q142-4 125-17.5Q108-31 103-40L103-40Q103-49 106-52.5Q109-56 123.5-55.5Q138-55 171-51L171-51Q183-51 201-53Q219-55 234.5-59Q250-63 252-69L252-69Q257-69 254-72.5Q251-76 247-80L247-80Q243-80 238-83.5Q233-87 229-87L229-87Q229-92 219.5-98.5Q210-105 200-109L200-109Q178-127 162-141.5Q146-156 150-161L150-161Q150-165 150-165.5Q150-166 150-166L150-166Q143-166 134.5-186Q126-206 126-222L126-222Q126-234 133-252.5Q140-271 152-291Q164-311 177-324L177-324Q195-341 222-360.5Q249-380 275.5-395.5Q302-411 320-416L320-416Q338-423 353.5-423Q369-423 381-416L381-416Q391-411 399.5-401.5Q408-392 414.5-382.5Q421-373 423-366L423-366Q423-354 421-339.5Q419-325 415.5-314Q412-303 405-303L405-303Q405-303 402.5-302.5Q400-302 400-297L400-297Q400-287 385-274L385-274Q366-261 352-258.5Q338-256 335-269L335-269Q332-273 331-276Q330-279 326-279L326-279Q326-279 323-282Q320-285 320-292L320-292Q320-296 323-299.5Q326-303 326-303L326-303Q331-303 339-314.5Q347-326 353.5-339.5Q360-353 360-358L360-358Q360-364 358.5-365Q357-366 349-366L349-366Q335-360 315-350Q295-340 277.5-330.5Q260-321 252-313L252-313Q248-306 247-304.5Q246-303 242-303L242-303Q242-307 235.5-299.5Q229-292 219.5-279Q210-266 201.5-253.5Q193-241 189-235L189-235Q187-233 184.5-223.5Q182-214 182-206L182-206Q178-194 183-189.5Q188-185 205-171L205-171Q240-145 260-128Q280-111 290-100Q300-89 302-80L302-80Q320-50 313-34Q306-18 279-6L279-6Q269-1 246 2Q223 5 199 5.5Q175 6 161 4Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 335.7 -292.9 C 339.3 -292.9 348.8 -285.8 357.1 -292.9 C 365.4 -300 382.1 -320.2 385.7 -335.7 C 389.3 -351.2 383.4 -376.2 378.6 -385.7 C 373.8 -395.2 366.6 -392.3 357.1 -392.9 C 347.6 -393.5 333.9 -392.9 321.4 -389.3 C 308.9 -385.7 302.9 -385.1 282.1 -371.4 C 261.3 -357.7 216.6 -326.7 196.4 -307.1 C 176.2 -287.5 167.8 -266.7 160.7 -253.6 C 153.6 -240.5 154.2 -238.1 153.6 -228.6 C 153 -219.1 153.5 -207.7 157.1 -196.4 C 160.7 -185.1 156.5 -177.9 175 -160.7 C 193.5 -143.4 251.2 -111.9 267.9 -92.9 C 284.6 -73.9 276.2 -55.9 275 -46.4 C 273.8 -36.9 270.8 -39.3 260.7 -35.7 C 250.6 -32.1 230.4 -26.2 214.3 -25 C 198.2 -23.8 178.6 -25.6 164.3 -28.6 C 150 -31.6 134.6 -40.5 128.6 -42.9",
|
||||
],
|
||||
},
|
||||
t: {
|
||||
adv: 331,
|
||||
outline:
|
||||
"M179-7L179-7Q168-9 156.5-18.5Q145-28 136.5-40.5Q128-53 125-63L125-63Q125-65 122.5-68Q120-71 120-71L120-71Q116-74 115.5-97.5Q115-121 118-150Q121-179 125-196L125-196Q130-212 140-239.5Q150-267 157-289L157-289L184-347L144-358Q137-360 132.5-366Q128-372 128-376L128-376Q128-384 137-395.5Q146-407 150-407L150-407Q155-406 168.5-406Q182-406 195-407Q208-408 211-409L211-409Q215-411 219-414.5Q223-418 225-425L225-425Q230-436 234-445.5Q238-455 245-467L245-467Q252-480 256.5-489Q261-498 271-514L271-514Q276-521 282-531Q288-541 297-548L297-548Q306-556 315.5-555.5Q325-555 340-542L340-542Q348-532 349-527Q350-522 341-508L341-508Q333-491 322.5-474Q312-457 303-440L303-440Q287-418 297-415Q307-412 353-419L353-419Q382-424 395-416.5Q408-409 410-392L410-392Q413-368 405-363Q397-358 359-358L359-358Q335-354 318-353.5Q301-353 293-353L293-353Q280-352 276-352Q272-352 264-350L264-350Q256-348 252.5-343.5Q249-339 245-331L245-331Q241-327 233-310Q225-293 213-273L213-273Q199-231 189-189.5Q179-148 173-109L173-109Q169-84 174-74.5Q179-65 200-63L200-63Q211-62 235.5-63.5Q260-65 274-69L274-69Q285-74 297-79.5Q309-85 319-91L319-91Q328-96 337-101.5Q346-107 346-107L346-107Q351-115 357-112Q363-109 367-97L367-97Q367-85 362-72Q357-59 325-41L325-41Q280-13 242.5-7.5Q205-2 179-7Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 367.9 -389.3 C 348.3 -389.3 275.6 -391.7 250 -389.3 C 224.4 -386.9 222 -386.3 214.3 -375 C 206.6 -363.7 208.4 -335.1 203.6 -321.4 C 198.8 -307.7 193.5 -311.9 185.7 -292.9 C 178 -273.8 164.2 -232.7 157.1 -207.1 C 150 -181.5 144.1 -163.7 142.9 -139.3 C 141.7 -114.9 146.4 -76.2 150 -60.7 C 153.6 -45.2 157.8 -50 164.3 -46.4 C 170.9 -42.8 170.3 -39.3 189.3 -39.3 C 208.4 -39.3 255.4 -41 278.6 -46.4 C 301.8 -51.7 317.3 -64.2 328.6 -71.4 C 339.9 -78.6 343.4 -86.3 346.4 -89.3",
|
||||
"M 307.1 -517.9 C 300 -505.4 272.6 -464.3 264.3 -442.9 C 256 -421.5 258.3 -398.2 257.1 -389.3",
|
||||
],
|
||||
},
|
||||
u: {
|
||||
adv: 370,
|
||||
outline:
|
||||
"M339-2L339-2Q329-12 319-29.5Q309-47 303-70L303-70Q301-78 301.5-92Q302-106 301-112L301-112Q298-105 291.5-97.5Q285-90 279-85L279-85Q275-81 261-69.5Q247-58 233-51L233-51Q200-30 178.5-28Q157-26 138-50L138-50Q122-63 115.5-79Q109-95 115-123L115-123Q117-158 125-191.5Q133-225 158-281L158-281Q179-322 190-341.5Q201-361 204-365L204-365Q219-370 229-364.5Q239-359 241-345L241-345Q247-335 239.5-311Q232-287 214-252L214-252Q200-218 188.5-184Q177-150 175-126Q173-102 187-97L187-97Q196-92 218.5-100Q241-108 269.5-140Q298-172 325-237L325-237Q333-257 337-272.5Q341-288 342-307L342-307Q340-319 343.5-328.5Q347-338 359-345L359-345Q363-350 370-354.5Q377-359 379-354L379-354L395-355Q399-356 400.5-350.5Q402-345 407-341L407-341Q418-332 413-302Q408-272 385-218L385-218Q366-177 359.5-156Q353-135 352-115L352-115Q352-97 359-81Q366-65 381-52L381-52Q392-45 393.5-36Q395-27 394-15L394-15Q392-6 382.5-0.5Q373 5 361 5Q349 5 339-2Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 214.3 -335.7 C 203.6 -310.1 161.9 -215.4 150 -182.1 C 138.1 -148.8 143.5 -151.2 142.9 -135.7 C 142.3 -120.2 144.6 -98.8 146.4 -89.3 C 148.2 -79.8 148.2 -82.2 153.6 -78.6 C 159 -75 166.7 -67.9 178.6 -67.9 C 190.5 -67.9 211.3 -73.2 225 -78.6 C 238.7 -83.9 251.8 -92.9 260.7 -100 C 269.6 -107.1 268.5 -115.4 278.6 -121.4 C 288.7 -127.3 314.3 -133.3 321.4 -135.7",
|
||||
"M 375 -310.7 C 371.4 -297 362.5 -254.8 353.6 -228.6 C 344.7 -202.4 325.6 -179.8 321.4 -153.6 C 317.2 -127.4 323.8 -91.1 328.6 -71.4 C 333.4 -51.8 346.4 -41.7 350 -35.7",
|
||||
],
|
||||
},
|
||||
v: {
|
||||
adv: 331,
|
||||
outline:
|
||||
"M183-61L183-61Q176-67 165.5-97Q155-127 145.5-167.5Q136-208 130-242L130-242Q126-262 121.5-276Q117-290 114-299L114-299Q109-317 111-329Q113-341 125-348L125-348Q130-351 137.5-352.5Q145-354 149-353L149-353Q161-349 171-316.5Q181-284 189-231L189-231Q193-200 199-178Q205-156 205-152L205-152Q209-144 214.5-136.5Q220-129 228-137L228-137Q232-141 244.5-155Q257-169 269-185L269-185Q290-213 302.5-231Q315-249 331-269L331-269Q338-279 347-291Q356-303 365.5-313.5Q375-324 383-326L383-326Q407-330 409-316Q411-302 391-274L391-274Q383-262 378.5-251.5Q374-241 370-237L370-237Q362-221 345-196.5Q328-172 312-151.5Q296-131 290-125L290-125Q286-121 272.5-110Q259-99 247-82L247-82Q223-59 209-54Q195-49 183-61Z",
|
||||
stem: 55,
|
||||
strokes: [
|
||||
"M 139.3 -317.9 C 145.9 -286.9 167.9 -167.8 178.6 -132.1 C 189.3 -96.4 192.9 -106 203.6 -103.6 C 214.3 -101.2 231 -110.2 242.9 -117.9 C 254.8 -125.6 251.2 -117.9 275 -150 C 298.8 -182.1 367.3 -283.9 385.7 -310.7",
|
||||
],
|
||||
},
|
||||
w: {
|
||||
adv: 517,
|
||||
outline:
|
||||
"M177-45L177-45Q162-45 149.5-63.5Q137-82 131.5-111.5Q126-141 128-173L128-173Q128-205 131.5-224.5Q135-244 139-263L139-263Q142-269 142.5-273Q143-277 144.5-286Q146-295 151-316L151-316Q155-333 159-344Q163-355 167-359L167-359Q172-365 180-366.5Q188-368 196-365Q204-362 209-354L209-354Q215-349 215.5-331.5Q216-314 213-295.5Q210-277 204-269L204-269Q202-264 199.5-243Q197-222 194.5-199Q192-176 189-162L189-162Q186-144 192.5-133Q199-122 217.5-132.5Q236-143 267-188L267-188Q288-218 299-237Q310-256 316.5-272.5Q323-289 330-308L330-308Q335-324 339.5-334.5Q344-345 348-348L348-348Q355-353 364.5-352Q374-351 386-343L386-343Q398-331 398-314.5Q398-298 386-274L386-274Q378-258 370.5-233Q363-208 358.5-182.5Q354-157 354-141L354-141Q354-121 359.5-117.5Q365-114 373-114L373-114Q385-114 402-123Q419-132 440-154L440-154Q449-163 462-180.5Q475-198 487.5-217.5Q500-237 508.5-252Q517-267 517-271L517-271Q517-277 526-288L526-288Q532-296 535.5-300.5Q539-305 549-310L549-310Q557-314 563-311.5Q569-309 576-301L576-301Q581-297 581.5-289Q582-281 582-269L582-269Q579-258 572.5-240.5Q566-223 558.5-207.5Q551-192 546-186L546-186Q542-182 532-171.5Q522-161 514-149L514-149Q489-119 478-110Q467-101 459-93L459-93Q438-72 415-61.5Q392-51 371.5-51Q351-51 337-61L337-61Q306-77 306-115L306-115Q306-123 303.5-129.5Q301-136 301-136L301-136Q297-136 290.5-134Q284-132 277-120L277-120Q268-103 248.5-85.5Q229-68 209-56.5Q189-45 177-45Z",
|
||||
stem: 64,
|
||||
strokes: [
|
||||
"M 185.7 -325 C 183.3 -318.4 176.2 -306.5 171.4 -285.7 C 166.6 -264.9 158.9 -228.6 157.1 -200 C 155.3 -171.4 156.5 -131.6 160.7 -114.3 C 164.9 -97 170.2 -97 182.1 -96.4 C 194 -95.8 217.2 -100.6 232.1 -110.7 C 247 -120.8 257.7 -146.4 271.4 -157.1 C 285.1 -167.8 298.8 -148.8 314.3 -175 C 329.8 -201.2 356 -291.1 364.3 -314.3",
|
||||
"M 314.3 -175 C 316.1 -173.2 322.6 -175 325 -164.3 C 327.4 -153.6 326.2 -122 328.6 -110.7 C 331 -99.4 334 -100 339.3 -96.4 C 344.7 -92.8 350 -89.9 360.7 -89.3 C 371.4 -88.7 388.7 -86.9 403.6 -92.9 C 418.5 -98.9 435.7 -112.5 450 -125 C 464.3 -137.5 476.8 -152.4 489.3 -167.9 C 501.8 -183.4 514.3 -199.4 525 -217.9 C 535.7 -236.3 548.8 -268.5 553.6 -278.6",
|
||||
],
|
||||
},
|
||||
x: {
|
||||
adv: 343,
|
||||
outline:
|
||||
"M391-3L391-3Q387-3 380.5-4Q374-5 370-9L370-9Q366-9 356-12Q346-15 338-19L338-19Q328-23 311-35.5Q294-48 277-63Q260-78 248.5-91Q237-104 237-110L237-110Q237-115 232-115L232-115Q232-115 229-118.5Q226-122 226-126L226-126Q226-136 216-136L216-136Q216-136 199-122Q182-108 162-88L162-88Q134-60 120.5-47.5Q107-35 99-35L99-35Q91-35 81-41Q71-47 63.5-55.5Q56-64 56-72L56-72Q56-72 76.5-95Q97-118 125-142L125-142L189-205L178-243Q171-259 163.5-276.5Q156-294 152-306L152-306Q140-323 139.5-327.5Q139-332 147-344L147-344L147-344L147-344Q155-352 160.5-354.5Q166-357 178-349L178-349Q184-349 194-332.5Q204-316 212.5-296.5Q221-277 221-269L221-269Q221-253 233-256Q245-259 269-275L269-275Q281-286 288-293.5Q295-301 295-301L295-301Q298-304 313-314.5Q328-325 344-336Q360-347 365-349L365-349Q373-355 383-349.5Q393-344 400-333.5Q407-323 407-312L407-312Q407-300 401-293Q395-286 375-275L375-275Q357-262 343.5-252Q330-242 315.5-230.5Q301-219 279-200L279-200L253-179L269-152Q273-145 280.5-132Q288-119 295-115L295-115Q301-107 318-93Q335-79 351.5-67.5Q368-56 370-56L370-56Q374-56 379-53Q384-50 391-46L391-46Q399-38 401.5-28Q404-18 401.5-10.5Q399-3 391-3Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 167.9 -328.6 C 172.7 -318.5 191.1 -282.2 196.4 -267.9 C 201.8 -253.6 195.8 -250 200 -242.9 C 204.2 -235.7 209.5 -225.6 221.4 -225 C 233.3 -224.4 253.5 -228.6 271.4 -239.3 C 289.3 -250 311.9 -276.8 328.6 -289.3 C 345.3 -301.8 364.3 -310.1 371.4 -314.3",
|
||||
"M 135.7 -114.3 C 145.2 -122.6 179.2 -154.8 192.9 -164.3 C 206.6 -173.8 213.7 -170.2 217.9 -171.4",
|
||||
"M 221.4 -225 C 220.8 -216.1 214.9 -183.3 217.9 -171.4 C 220.9 -159.5 231.6 -164.3 239.3 -153.6 C 247 -142.9 250.6 -123.2 264.3 -107.1 C 278 -91 301.8 -70.8 321.4 -57.1 C 341 -43.4 372 -30.3 382.1 -25",
|
||||
],
|
||||
},
|
||||
y: {
|
||||
adv: 341,
|
||||
outline:
|
||||
"M79 199L79 199Q55 206 26.5 201.5Q-2 197-23.5 184Q-45 171-48 155L-48 155Q-49 149-44.5 142Q-40 135-33.5 132.5Q-27 130-22 136L-22 136Q-14 141 7 148.5Q28 156 54 151L54 151Q77 146 99.5 124Q122 102 136 83L136 83Q144 71 163 40Q182 9 202.5-28.5Q223-66 237-98L237-98Q240-106 244-117Q248-128 254-135L254-135Q260-147 264.5-157.5Q269-168 270-172L270-172Q271-177 279-192Q287-207 294-222L294-222Q299-237 309-266Q319-295 329-313L329-313Q341-336 348.5-350.5Q356-365 365-368L365-368L365-368L365-368Q374-375 377.5-375Q381-375 392-369L392-369Q404-357 407-345.5Q410-334 404.5-317.5Q399-301 384-273L384-273Q376-249 366.5-229.5Q357-210 352-204L352-204Q347-192 340.5-177.5Q334-163 329-151L329-151Q311-111 298-86Q285-61 275-40Q265-19 254 9L254 9Q231 46 209.5 79.5Q188 113 164 142L164 142Q154 151 139 163Q124 175 108.5 185Q93 195 79 199ZM214-41L214-41Q199-43 184.5-59Q170-75 162-93Q154-111 156-119L156-119Q156-124 156-127Q156-130 156-130L156-130Q150-130 145.5-145Q141-160 139-184Q137-208 136.5-235Q136-262 138-286L138-286Q142-303 142-323.5Q142-344 143-356L143-356Q142-372 146-377.5Q150-383 154-386L154-386L154-386L154-386Q182-398 191.5-371.5Q201-345 196-273L196-273Q194-233 196-193Q198-153 207.5-123Q217-93 234-82L234-82Q241-78 241-71Q241-64 237-56.5Q233-49 226.5-44.5Q220-40 214-41Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 235.7 -60.7 C 234.5 -52.4 241.1 -36.9 228.6 -10.7 C 216.1 15.5 180.4 69.6 160.7 96.4 C 141 123.2 128 136.9 110.7 150 C 93.4 163.1 74.4 171.4 57.1 175 C 39.8 178.6 21.4 175 7.1 171.4 C -7.2 167.8 -22.7 156.6 -28.6 153.6",
|
||||
"M 164.3 -360.7 C 164.3 -333.3 162.5 -235.1 164.3 -196.4 C 166.1 -157.7 169.7 -147.6 175 -128.6 C 180.4 -109.5 186.3 -93.4 196.4 -82.1 C 206.5 -70.8 226.2 -63.1 235.7 -60.7 C 245.2 -58.3 230.4 -22.1 253.6 -67.9 C 276.8 -113.7 354.8 -291.1 375 -335.7",
|
||||
],
|
||||
},
|
||||
z: {
|
||||
adv: 315,
|
||||
outline:
|
||||
"M140-28L140-28Q102-23 83-21.5Q64-20 54.5-24Q45-28 34-38L34-38Q25-46 27.5-57Q30-68 61-100L61-100Q80-123 100-143.5Q120-164 136-177L136-177Q143-183 152-189.5Q161-196 181-210Q201-224 240-250L240-250Q259-265 270-273Q281-281 288.5-286.5Q296-292 304-298L304-298Q297-299 273.5-301Q250-303 230-301L230-301Q202-301 194-301.5Q186-302 178-304L178-304L163-304Q158-304 145-308.5Q132-313 121-320Q110-327 109-334L109-334Q109-334 115-337.5Q121-341 125-345L125-345Q133-350 137.5-353Q142-356 146-352L146-352Q146-352 165-352.5Q184-353 208-354L208-354Q266-353 293.5-350.5Q321-348 334.5-342Q348-336 361-324L361-324Q377-308 374-293Q371-278 360-266L360-266Q356-262 352.5-257.5Q349-253 346-253L346-253Q346-253 332-244Q318-235 298-222L298-222Q279-209 260.5-198Q242-187 238-182L238-182Q231-178 221-171.5Q211-165 207-161L207-161Q204-157 193.5-147Q183-137 171-129L171-129Q162-121 149.5-109Q137-97 128-88Q119-79 119-79L119-79Q119-79 139-81.5Q159-84 189-86.5Q219-89 248-87L248-87Q295-88 322.5-89.5Q350-91 364.5-90.5Q379-90 387-86L387-86Q394-84 399.5-78Q405-72 407.5-65.5Q410-59 408-56L408-56Q404-56 377-53.5Q350-51 315-49L315-49Q269-45 226-39Q183-33 140-28Z",
|
||||
stem: 55,
|
||||
strokes: [
|
||||
"M 142.9 -332.1 C 170.9 -330.9 281 -326.8 310.7 -325 C 340.5 -323.2 318.4 -327.3 321.4 -321.4 C 324.4 -315.4 329.2 -297.6 328.6 -289.3 C 328 -281 341.7 -288.7 317.9 -271.4 C 294.1 -254.1 215.5 -206.5 185.7 -185.7 C 155.9 -164.9 153.6 -160.1 139.3 -146.4 C 125 -132.7 108.3 -117.9 100 -103.6 C 91.7 -89.3 91.1 -67.8 89.3 -60.7",
|
||||
"M 89.3 -60.7 C 91.7 -59.5 61.9 -51.2 103.6 -53.6 C 145.3 -56 292.9 -72.6 339.3 -75 C 385.7 -77.4 375 -69.1 382.1 -67.9",
|
||||
],
|
||||
},
|
||||
A: {
|
||||
adv: 502,
|
||||
outline:
|
||||
"M85 45L69 33Q51 23 49.5 11Q48-1 63-31Q78-61 110-124L110-124Q134-169 146.5-194.5Q159-220 163.5-231Q168-242 168-245L168-245Q166-254 170-259.5Q174-265 183-269L183-269Q188-274 199.5-287Q211-300 221-318L221-318Q230-339 242-361Q254-383 274-416.5Q294-450 326-504L326-504Q346-539 370-575.5Q394-612 421-648L421-648Q432-662 441-670.5Q450-679 459-678L459-678Q471-678 486.5-670Q502-662 515-649Q528-636 534-621L534-621Q539-613 538-587Q537-561 534-524Q531-487 527-445L527-445Q524-404 518-348Q512-292 506.5-231Q501-170 498-113.5Q495-57 498-15L498-15Q499-6 498 2Q497 10 497 15L497 15Q491 21 477.5 20.5Q464 20 451.5 14Q439 8 437-1L437-1Q434-4 433.5-9.5Q433-15 434-33Q435-51 436-90L436-90L441-252L428-251Q419-251 394-249Q369-247 340-243.5Q311-240 286.5-236.5Q262-233 253-230L253-230Q244-230 231.5-218Q219-206 208-187L208-187Q204-173 193.5-157.5Q183-142 183-137L183-137Q176-132 171.5-125Q167-118 167-118L167-118Q167-112 157.5-88.5Q148-65 134-36.5Q120-8 106 13L106 13L85 45ZM276-289L276-289Q276-289 286.5-291Q297-293 311.5-295.5Q326-298 338-298L338-298Q380-308 399-311.5Q418-315 425.5-315Q433-315 439-312L439-312Q443-307 446.5-306Q450-305 450-305L450-305Q451-307 453-328Q455-349 458.5-380Q462-411 465.5-444.5Q469-478 471-506.5Q473-535 474-550L474-550Q475-569 476-584.5Q477-600 477-600L477-600Q471-600 451.5-578Q432-556 408-520Q384-484 363-443L363-443Q353-424 344-409Q335-394 330-394L330-394Q328-392 318.5-376.5Q309-361 298-341.5Q287-322 280-306.5Q273-291 276-289Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 471.4 -278.6 C 459.5 -279.2 427.4 -283.9 400 -282.1 C 372.6 -280.3 334.5 -270.3 307.1 -267.9 C 279.7 -265.5 252.4 -270.9 235.7 -267.9 C 219 -264.9 214.2 -255.4 207.1 -250 C 200 -244.6 214.9 -277.4 192.9 -235.7 C 170.9 -194 94.7 -39.3 75 0",
|
||||
"M 239.3 -267.9 C 241.1 -276.8 244.7 -305.3 250 -321.4 C 255.4 -337.5 263.7 -350 271.4 -364.3 C 279.1 -378.6 288.7 -396.4 296.4 -407.1 C 304.2 -417.8 297.7 -397 317.9 -428.6 C 338.2 -460.1 395.3 -562.5 417.9 -596.4 C 440.5 -630.3 445.9 -625.5 453.6 -632.1 C 461.3 -638.7 457.2 -636.9 464.3 -635.7 C 471.4 -634.5 489.9 -630.3 496.4 -625 C 503 -619.6 502.4 -615.5 503.6 -603.6 C 504.8 -591.7 507.8 -606 503.6 -553.6 C 499.4 -501.2 484.6 -339.9 478.6 -289.3 C 472.7 -238.7 470.3 -294 467.9 -250 C 465.5 -205.9 464.9 -62.5 464.3 -25",
|
||||
],
|
||||
},
|
||||
B: {
|
||||
adv: 520,
|
||||
outline:
|
||||
"M205 57L205 57Q162 29 147 8.5Q132-12 159-29L159-29Q165-33 178.5-22.5Q192-12 201-3L201-3Q221 15 250.5 19.5Q280 24 317 14L317 14Q359 1 390.5-22.5Q422-46 450-80L450-80Q471-107 479-138.5Q487-170 485.5-197Q484-224 474-235L474-235Q449-270 404.5-283.5Q360-297 300-281L300-281Q293-280 279-277Q265-274 254-271.5Q243-269 242-269L242-269Q235-267 231-264.5Q227-262 221.5-249.5Q216-237 205-205.5Q194-174 172-113L172-113Q151-52 138.5-26.5Q126-1 116.5-3.5Q107-6 93-25L93-25Q85-39 83-52.5Q81-66 91-84L91-84Q96-93 99-100.5Q102-108 107-127L107-127Q112-142 126.5-179.5Q141-217 161-267.5Q181-318 203-372Q225-426 245.5-474Q266-522 280-555L280-555Q294-586 297-600Q300-614 295-615L295-615Q295-620 292-624.5Q289-629 290-633L290-633Q290-637 303.5-644Q317-651 333.5-657Q350-663 359-661L359-661Q364-661 371-661.5Q378-662 378-666L378-666Q381-666 397-668Q413-670 432-668L432-668Q499-662 536-629.5Q573-597 572-541L572-541Q571-524 554.5-495Q538-466 514.5-437Q491-408 469-388L469-388Q456-377 441.5-365.5Q427-354 416.5-346.5Q406-339 406-339L406-339Q406-339 414-336Q422-333 431-330L431-330Q477-313 507-287Q537-261 548-215L548-215Q552-200 548.5-178Q545-156 539-134.5Q533-113 526-98L526-98Q519-91 515-83.5Q511-76 511-76L511-76Q511-74 503-60.5Q495-47 484-31Q473-15 463-3L463-3Q453 5 439.5 16Q426 27 409.5 37Q393 47 374 54L374 54Q346 66 313.5 70.5Q281 75 252 71.5Q223 68 205 57ZM254-328L254-328Q254-328 272.5-331.5Q291-335 313-339.5Q335-344 345-348L345-348Q350-351 359.5-356Q369-361 379-371L379-371Q393-384 414.5-408.5Q436-433 457-460.5Q478-488 489-510L489-510Q512-546 514-563.5Q516-581 503-594L503-594Q494-605 472.5-608.5Q451-612 427-610.5Q403-609 386-604L386-604Q373-600 366-594.5Q359-589 359-578L359-578Q359-569 353.5-560.5Q348-552 343-547L343-547Q342-540 338-534.5Q334-529 334-529L334-529Q334-525 320-496Q306-467 292-429L292-429Q274-386 264-359.5Q254-333 254-328Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 378.6 -321.4 C 381 -327.3 378.6 -339.8 392.9 -357.1 C 407.2 -374.4 443.5 -401.8 464.3 -425 C 485.1 -448.2 505.4 -473.8 517.9 -496.4 C 530.4 -519 538.1 -542.2 539.3 -560.7 C 540.5 -579.2 531.6 -595.8 525 -607.1 C 518.5 -618.4 508.3 -623.8 500 -628.6 C 491.7 -633.4 490.5 -633.3 475 -635.7 C 459.5 -638.1 425.6 -642.9 407.1 -642.9 C 388.7 -642.9 377.4 -639.9 364.3 -635.7 C 351.2 -631.5 336.9 -630.4 328.6 -617.9 C 320.3 -605.4 331.6 -604.7 314.3 -560.7 C 297 -516.7 241.1 -396.5 225 -353.6 C 208.9 -310.7 222.1 -314.3 217.9 -303.6 C 213.7 -292.9 209.5 -307.7 200 -289.3 C 190.5 -270.8 175 -230.4 160.7 -192.9 C 146.4 -155.4 122 -88.7 114.3 -64.3 C 106.6 -39.9 114.3 -49.4 114.3 -46.4",
|
||||
"M 217.9 -303.6 C 242.3 -306.6 328.6 -321.4 364.3 -321.4 C 400 -321.4 416 -309 432.1 -303.6 C 448.2 -298.2 450 -297 460.7 -289.3 C 471.4 -281.5 487.5 -272 496.4 -257.1 C 505.3 -242.2 511.9 -216.7 514.3 -200 C 516.7 -183.3 514.9 -174.9 510.7 -157.1 C 506.5 -139.2 499.4 -113.1 489.3 -92.9 C 479.2 -72.7 462.5 -50.6 450 -35.7 C 437.5 -20.8 428.6 -13.7 414.3 -3.6 C 400 6.5 381.6 17.3 364.3 25 C 347 32.8 330.3 39.9 310.7 42.9 C 291.1 45.9 266 46.5 246.4 42.9 C 226.8 39.3 205.4 28 192.9 21.4 C 180.4 14.8 175 6.6 171.4 3.6",
|
||||
],
|
||||
},
|
||||
C: {
|
||||
adv: 472,
|
||||
outline:
|
||||
"M214 56L214 56Q199 50 183.5 43Q168 36 154.5 22.5Q141 9 131-15L131-15Q121-44 117.5-72Q114-100 115-137L115-137Q115-189 122.5-227.5Q130-266 146-306L146-306Q148-309 153.5-323.5Q159-338 166.5-355Q174-372 178-381L178-381Q186-398 202.5-426.5Q219-455 238-482L238-482Q267-525 286-550Q305-575 322-592Q339-609 360.5-624.5Q382-640 416-663L416-663Q438-679 464.5-680Q491-681 514-668.5Q537-656 548-632L548-632Q559-611 559-581.5Q559-552 550-519.5Q541-487 525-458Q509-429 488-410L488-410Q475-398 464.5-401Q454-404 440-417L440-417Q431-434 430.5-437Q430-440 434-441L434-441Q440-448 450-465Q460-482 470-501.5Q480-521 484-533L484-533Q492-575 495-592.5Q498-610 489-619L489-619Q485-623 477.5-622Q470-621 453-612L453-612Q430-602 393-567.5Q356-533 325-491L325-491Q285-438 258-397Q231-356 217-313L217-313Q215-309 210.5-298Q206-287 206-287L206-287Q207-283 203-267.5Q199-252 194-241L194-241Q183-195 181-145.5Q179-96 186.5-58Q194-20 209-5L209-5Q222 4 244.5 1Q267-2 296-13Q325-24 355-37L355-37Q379-47 399-59Q419-71 429-80L429-80Q437-86 449.5-92.5Q462-99 471-99L471-99Q485-104 487-103Q489-102 489-94L489-94Q489-80 473.5-63Q458-46 434-32L434-32Q426-24 413-15Q400-6 386-1L386-1Q365 15 334 30Q303 45 271.5 53Q240 61 214 56Z",
|
||||
stem: 64,
|
||||
strokes: [
|
||||
"M 467.9 -439.3 C 475 -451.2 501.2 -488.1 510.7 -510.7 C 520.2 -533.3 523.8 -556.5 525 -575 C 526.2 -593.4 520.9 -610.1 517.9 -621.4 C 514.9 -632.7 513.1 -638.1 507.1 -642.9 C 501.1 -647.7 491.6 -648.8 482.1 -650 C 472.6 -651.2 457.7 -651.2 450 -650 C 442.3 -648.8 447.6 -650.6 435.7 -642.9 C 423.8 -635.2 398.2 -620.3 378.6 -603.6 C 359 -586.9 345.3 -578 317.9 -542.9 C 290.5 -507.8 238.1 -432.8 214.3 -392.9 C 190.5 -353 185.7 -334 175 -303.6 C 164.3 -273.2 154.8 -245.8 150 -210.7 C 145.2 -175.6 145.2 -122.7 146.4 -92.9 C 147.6 -63.1 150.6 -50 157.1 -32.1 C 163.6 -14.2 170.8 4.8 185.7 14.3 C 200.6 23.8 231.5 23.8 246.4 25 C 261.3 26.2 258.3 26.8 275 21.4 C 291.7 16 324.4 3 346.4 -7.1 C 368.4 -17.2 385.1 -24.4 407.1 -39.3 C 429.1 -54.2 466.7 -86.9 478.6 -96.4",
|
||||
],
|
||||
},
|
||||
D: {
|
||||
adv: 578,
|
||||
outline:
|
||||
"M225 95L225 95Q206 98 190 98.5Q174 99 143 91L143 91Q127 86 120.5 79Q114 72 99 53L99 53Q88 37 83.5 23.5Q79 10 82 2.5Q85-5 95-2L95-2Q98-1 104.5 4.5Q111 10 117 18L117 18Q131 33 151 38Q171 43 204 39L204 39Q218 36 238 30.5Q258 25 277 17Q296 9 307 1L307 1Q360-35 392-56Q424-77 424-77L424-77Q426-81 431.5-83.5Q437-86 445-89L445-89Q451-95 464.5-108Q478-121 490.5-134Q503-147 506-149L506-149Q508-158 510-163.5Q512-169 512-175L512-175Q520-183 533.5-208.5Q547-234 561-265.5Q575-297 581-323L581-323Q595-393 588-441Q581-489 559.5-523.5Q538-558 506-585L506-585Q495-594 475.5-604.5Q456-615 435-623Q414-631 398.5-636Q383-641 380-640L380-640Q376-639 363.5-608.5Q351-578 334-531Q317-484 297.5-431Q278-378 261-331L261-331Q255-312 248-291Q241-270 236-254.5Q231-239 231-234L231-234Q220-217 213.5-189Q207-161 199-96L199-96Q190-62 186-44.5Q182-27 178-28L178-28Q178-28 177.5-27.5Q177-27 174-24L174-24Q174-18 166-18Q158-18 147.5-22.5Q137-27 131-33L131-33Q128-44 127.5-54Q127-64 135-93L135-93Q140-112 147.5-139.5Q155-167 164-196.5Q173-226 181-251Q189-276 193-289L193-289Q200-306 213-353L213-353Q221-379 229.5-405Q238-431 246-451.5Q254-472 257-478L257-478Q262-491 272-522Q282-553 298-591L298-591L305-624L294-629Q282-625 275.5-626.5Q269-628 267-629L267-629Q261-627 250-632Q239-637 230.5-645Q222-653 220-659L220-659Q218-663 217.5-669.5Q217-676 217-676L217-676Q218-682 232-687.5Q246-693 268.5-697Q291-701 317.5-702.5Q344-704 369-702L369-702Q395-700 418-691Q441-682 468-672L468-672Q500-660 535.5-630.5Q571-601 600.5-561Q630-521 640-477L640-477Q643-465 646-443Q649-421 649.5-400.5Q650-380 645-374L645-374Q643-370 642.5-364.5Q642-359 646-357L646-357Q645-354 639-334.5Q633-315 628-301L628-301Q626-275 616-247Q606-219 594-194.5Q582-170 572-153L572-153Q568-153 564.5-151.5Q561-150 565-149L565-149Q565-144 554-130L554-130Q550-127 541.5-115.5Q533-104 528-100L528-100Q527-93 522-88.5Q517-84 513-84L513-84Q513-84 511.5-84Q510-84 510-80L510-80Q502-68 485-51.5Q468-35 446.5-17.5Q425 0 402.5 15Q380 30 360 38L360 38Q350 45 339 49.5Q328 54 318 59L318 59Q313 63 296 70.5Q279 78 259.5 85Q240 92 225 95Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 260.7 -667.9 C 283.9 -667.9 364.9 -672.1 400 -667.9 C 435.1 -663.7 446.4 -657.2 471.4 -642.9 C 496.4 -628.6 532.1 -598.2 550 -582.1 C 567.9 -566 568.5 -565.4 578.6 -546.4 C 588.7 -527.4 604.8 -497.1 610.7 -467.9 C 616.7 -438.7 616.1 -400 614.3 -371.4 C 612.5 -342.8 605.4 -317.8 600 -296.4 C 594.6 -275 595.2 -270.3 582.1 -242.9 C 569 -215.5 541.6 -161.3 521.4 -132.1 C 501.2 -102.9 486.9 -89.9 460.7 -67.9 C 434.5 -45.9 392.3 -17.9 364.3 0 C 336.3 17.9 318.5 28.6 292.9 39.3 C 267.3 50 236.9 61.3 210.7 64.3 C 184.5 67.3 153.5 62.5 135.7 57.1 C 117.9 51.7 110.7 39.8 103.6 32.1 C 96.5 24.4 94.7 14.3 92.9 10.7",
|
||||
"M 335.7 -667.9 C 333.9 -655.4 338.7 -636.4 325 -592.9 C 311.3 -549.4 275.6 -469 253.6 -407.1 C 231.6 -345.2 209 -278.5 192.9 -221.4 C 176.8 -164.3 163.1 -90.5 157.1 -64.3",
|
||||
],
|
||||
},
|
||||
E: {
|
||||
adv: 529,
|
||||
outline:
|
||||
"M231 24L231 24Q200 22 176.5 5Q153-12 127-30L127-30Q110-47 100-66.5Q90-86 84-118L84-118Q80-137 84-166Q88-195 97-224.5Q106-254 114-272L114-272Q126-300 144.5-332.5Q163-365 181.5-394.5Q200-424 211-442L211-442Q218-449 217.5-449.5Q217-450 217-450L217-450Q217-455 227.5-475.5Q238-496 246-512L246-512Q261-528 263-538Q265-548 260-554L260-554Q258-558 267-568Q276-578 294-591Q312-604 335-617L335-617Q359-636 381.5-637.5Q404-639 408-628L408-628Q408-625 419.5-626.5Q431-628 446.5-631.5Q462-635 474-637L474-637Q479-643 497.5-646.5Q516-650 541-653L541-653Q554-652 566.5-652.5Q579-653 587.5-654Q596-655 596-655L596-655Q603-657 612.5-653Q622-649 630-631L630-631Q637-613 625.5-604Q614-595 573-590L573-590Q543-586 521-583.5Q499-581 475-577.5Q451-574 416-566L416-566Q391-561 375-554.5Q359-548 347-535.5Q335-523 320-500L320-500Q313-495 302-474Q291-453 280-432L280-432Q252-393 231.5-364.5Q211-336 215-331L215-331Q215-331 226-328Q237-325 241-325L241-325Q251-330 265.5-332Q280-334 289-336L289-336Q351-352 386.5-361Q422-370 440.5-374.5Q459-379 469-380L469-380Q483-391 496.5-384Q510-377 518-362L518-362Q523-351 518.5-343Q514-335 493.5-327Q473-319 433-308Q393-297 328-279L328-279Q273-263 234-260Q195-257 197-277L197-277Q190-281 181.5-267.5Q173-254 165-231Q157-208 151.5-182Q146-156 144.5-133.5Q143-111 147-100L147-100Q155-77 168-63.5Q181-50 215-40L215-40Q235-39 254-37Q273-35 297-45L297-45Q356-55 390-66Q424-77 429-81L429-81Q437-85 439.5-85.5Q442-86 447-86L447-86Q444-81 449.5-85.5Q455-90 465-100L465-100Q476-110 494-119Q512-128 526.5-133Q541-138 542-133L542-133Q548-123 540-105.5Q532-88 511-67.5Q490-47 457-28L457-28Q404 0 342 11.5Q280 23 231 24Z",
|
||||
stem: 64,
|
||||
strokes: [
|
||||
"M 585.7 -625 C 583.3 -625.6 586.3 -631 571.4 -628.6 C 556.5 -626.2 526.8 -615.5 496.4 -610.7 C 466.1 -605.9 417.3 -605.9 389.3 -600 C 361.3 -594 344.7 -585.1 328.6 -575 C 312.5 -564.9 308.4 -564.3 292.9 -539.3 C 277.4 -514.3 253 -454.8 235.7 -425 C 218.4 -395.2 198.8 -379.7 189.3 -360.7 C 179.8 -341.6 180.4 -319 178.6 -310.7",
|
||||
"M 482.1 -357.1 C 447 -347.6 313.7 -310.1 271.4 -300 C 229.2 -289.9 239.9 -295.2 228.6 -296.4 C 217.3 -297.6 214.3 -305.3 203.6 -307.1 C 192.9 -308.9 177.4 -320.8 164.3 -307.1 C 151.2 -293.4 133.9 -250 125 -225 C 116.1 -200 112.5 -176.7 110.7 -157.1 C 108.9 -137.4 109.5 -124.3 114.3 -107.1 C 119.1 -89.8 128.6 -67.3 139.3 -53.6 C 150 -39.9 164.9 -32.1 178.6 -25 C 192.3 -17.8 203.6 -13.1 221.4 -10.7 C 239.3 -8.3 252.4 -4.7 285.7 -10.7 C 319 -16.6 391 -36.9 421.4 -46.4 C 451.8 -55.9 450 -55.4 467.9 -67.9 C 485.8 -80.4 518.5 -112.5 528.6 -121.4",
|
||||
],
|
||||
},
|
||||
F: {
|
||||
adv: 454,
|
||||
outline:
|
||||
"M511-316L511-316Q508-309 488-305Q468-301 424-296L424-296Q411-295 385.5-294Q360-293 330.5-292Q301-291 277-291.5Q253-292 244-294L244-294Q244-294 239.5-289Q235-284 235-276L235-276Q228-264 217.5-239Q207-214 194.5-184Q182-154 171.5-124Q161-94 154-72Q147-50 147-42L147-42Q138-18 136-1Q134 16 129 19L129 19Q128 23 116 21Q104 19 96 10L96 10Q84 1 82.5-6.5Q81-14 88-36L88-36Q91-49 98.5-74.5Q106-100 115.5-131Q125-162 135.5-191.5Q146-221 156-240L156-240Q167-259 170-274.5Q173-290 178-303L178-303Q177-314 178-318Q179-322 186-330L186-330Q191-334 198.5-344.5Q206-355 216-377L216-377Q219-387 225.5-404.5Q232-422 238-440.5Q244-459 249-472Q254-485 254-486L254-486Q256-490 260-501.5Q264-513 266-520L266-520Q270-534 272.5-543Q275-552 271-552L271-552Q263-550 239-549L239-549Q227-547 221.5-546.5Q216-546 212-545L212-545Q202-544 194-547Q186-550 186-559L186-559Q188-575 197.5-585.5Q207-596 222-597L222-597Q229-599 237.5-600.5Q246-602 261-606L261-606Q280-610 290.5-612.5Q301-615 307-613L307-613Q312-616 316-619.5Q320-623 320-627L320-627Q320-632 324.5-634.5Q329-637 335-637.5Q341-638 347.5-636Q354-634 357-629L357-629Q359-626 366-624Q373-622 391.5-624.5Q410-627 444-634L444-634Q462-636 489.5-638Q517-640 533-635L533-635Q550-638 563.5-637.5Q577-637 582-641L582-641Q603-637 612.5-635.5Q622-634 628-629L628-629Q632-625 630.5-621.5Q629-618 629-618L629-618Q625-620 630-617.5Q635-615 637-613L637-613Q644-610 646-605.5Q648-601 648-597Q648-593 647-590L647-590Q646-581 637-579.5Q628-578 592-579L592-579Q549-585 507.5-583.5Q466-582 411-574L411-574Q391-573 375.5-568.5Q360-564 358-564L358-564Q354-564 344-550.5Q334-537 325-511L325-511Q321-504 311-479Q301-454 289.5-425Q278-396 269.5-374Q261-352 259-350L259-350Q273-346 304.5-346Q336-346 374.5-348.5Q413-351 445-354L445-354Q460-357 476.5-359.5Q493-362 498-360L498-360Q503-357 507-348.5Q511-340 512.5-331Q514-322 511-316Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 303.6 -582.1 C 302.4 -572.6 307.1 -558.9 296.4 -525 C 285.7 -491.1 250 -410.7 239.3 -378.6 C 228.6 -346.5 238.7 -346.4 232.1 -332.1 C 225.6 -317.8 206 -303.6 200 -292.9 C 194.1 -282.2 208.9 -302.4 196.4 -267.9 C 183.9 -233.4 139.3 -127.4 125 -85.7 C 110.7 -44 113.1 -29.2 110.7 -17.9",
|
||||
"M 214.3 -567.9 C 216.1 -569.1 208.3 -572.6 225 -575 C 241.7 -577.4 298.8 -579.7 314.3 -582.1 C 329.8 -584.5 311.3 -586.3 317.9 -589.3 C 324.5 -592.3 339.9 -598.2 353.6 -600 C 367.3 -601.8 383.9 -598.2 400 -600 C 416.1 -601.8 417.3 -608.9 450 -610.7 C 482.7 -612.5 568.4 -612.5 596.4 -610.7 C 624.4 -608.9 614.3 -601.8 617.9 -600",
|
||||
"M 228.6 -325 C 252.4 -324.4 330.3 -320.2 371.4 -321.4 C 412.5 -322.6 457.7 -330.3 475 -332.1",
|
||||
],
|
||||
},
|
||||
G: {
|
||||
adv: 487,
|
||||
outline:
|
||||
"M171 31L171 31Q144 14 129-17.5Q114-49 115-81L115-81Q114-143 125.5-206.5Q137-270 156-330L156-330Q169-366 184.5-404.5Q200-443 216-477Q232-511 244-531L244-531Q275-582 309.5-615.5Q344-649 361-659L361-659Q375-667 409.5-675Q444-683 481-674L481-674Q507-667 529.5-657Q552-647 559-640L559-640Q567-628 573.5-619.5Q580-611 580-599L580-599Q581-591 574.5-579.5Q568-568 558.5-563Q549-558 540-568L540-568Q527-588 513-595.5Q499-603 483-611L483-611Q472-618 453-618.5Q434-619 414-614Q394-609 379-597L379-597Q369-588 353-570.5Q337-553 321-532Q305-511 295-491L295-491Q268-444 246.5-396.5Q225-349 210-293L210-293Q206-279 199.5-252Q193-225 187-198Q181-171 179-154L179-154Q172-105 174.5-68Q177-31 200-17Q223-3 276-21L276-21Q299-24 325.5-41Q352-58 377.5-83.5Q403-109 421-138L421-138Q438-170 451.5-201Q465-232 466-252Q467-272 447-271L447-271Q410-275 387-273.5Q364-272 346-280L346-280Q339-291 340-302Q341-313 352-324L352-324Q363-328 389-330Q415-332 445-328.5Q475-325 498-314L498-314Q515-306 522-290.5Q529-275 529-255Q529-235 525-214L525-214Q520-192 503.5-161.5Q487-131 464-97L464-97Q449-76 422.5-51Q396-26 368.5-6Q341 14 322 21L322 21Q282 39 240 44.5Q198 50 171 31Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 550 -600 C 548.2 -603 548.8 -610.7 539.3 -617.9 C 529.8 -625 507.8 -637.5 492.9 -642.9 C 478 -648.2 464.9 -649.4 450 -650 C 435.1 -650.6 419.7 -651.2 403.6 -646.4 C 387.5 -641.6 370.3 -634.5 353.6 -621.4 C 336.9 -608.3 318.5 -586.3 303.6 -567.9 C 288.7 -549.4 281 -541.7 264.3 -510.7 C 247.6 -479.7 221.5 -429.7 203.6 -382.1 C 185.7 -334.5 167.2 -265.5 157.1 -225 C 147 -184.5 145.3 -164.3 142.9 -139.3 C 140.5 -114.3 139.9 -95.8 142.9 -75 C 145.9 -54.2 151.8 -28.6 160.7 -14.3 C 169.6 0 184.5 5.9 196.4 10.7 C 208.3 15.5 219 14.9 232.1 14.3 C 245.2 13.7 256.5 13.1 275 7.1 C 293.5 1.1 321.5 -8.3 342.9 -21.4 C 364.3 -34.5 388.1 -56.5 403.6 -71.4 C 419.1 -86.3 421.4 -86.9 435.7 -110.7 C 450 -134.5 480.4 -186.9 489.3 -214.3 C 498.2 -241.7 491.7 -261.3 489.3 -275 C 486.9 -288.7 482.7 -291.6 475 -296.4 C 467.3 -301.2 459.6 -302.4 442.9 -303.6 C 426.2 -304.8 386.3 -303.6 375 -303.6",
|
||||
],
|
||||
},
|
||||
H: {
|
||||
adv: 563,
|
||||
outline:
|
||||
"M130 83L130 83Q130 83 127 81.5Q124 80 119.5 79Q115 78 109 78L109 78Q93 74 86 60.5Q79 47 83 31L83 31Q87 27 89 14Q91 1 91-7L91-7Q95-15 96-18.5Q97-22 101-38L101-38Q104-55 111.5-85.5Q119-116 128.5-151Q138-186 147-218Q156-250 163.5-272Q171-294 174-297L174-297Q174-297 174-300.5Q174-304 174-308L174-308Q174-308 175.5-317.5Q177-327 185-339L185-339Q189-350 192-363Q195-376 195-380L195-380Q199-385 206.5-404.5Q214-424 221-443L221-443Q227-459 234.5-477Q242-495 247.5-508Q253-521 253-521L253-521Q253-525 253-525.5Q253-526 253-526L253-526Q257-530 272.5-566.5Q288-603 315-661L315-661Q319-669 325-679Q331-689 331-693L331-693Q336-701 345-700.5Q354-700 363.5-694Q373-688 378-677L378-677L388-661L352-589Q336-561 325.5-538Q315-515 315-511L315-511Q315-511 309-495Q303-479 295-457Q287-435 281-417L281-417Q270-386 260.5-366Q251-346 251-342L251-342Q251-344 273-345Q295-346 329-346Q363-346 398.5-346.5Q434-347 461.5-347.5Q489-348 499-350L499-350L551-355L585-458Q593-486 606-526Q619-566 629-594L629-594Q645-640 654-651Q663-662 675-662L675-662Q678-662 683.5-660.5Q689-659 694.5-655.5Q700-652 702-646L702-646Q714-635 713.5-621Q713-607 697-584L697-584Q681-557 670.5-525.5Q660-494 652-474L652-474Q652-462 649-454.5Q646-447 642-443L642-443Q640-441 634-428Q628-415 623-402Q618-389 618-386L618-386Q618-379 615-369Q612-359 608-355L608-355Q606-349 599-325Q592-301 583-268.5Q574-236 564-205.5Q554-175 546-155L546-155Q546-148 541-137.5Q536-127 536-119L536-119Q532-107 526-89Q520-71 520-66L520-66Q515-43 509.5-19Q504 5 500.5 22.5Q497 40 497 43L497 43Q493 47 485 44Q477 41 466 37L466 37Q447 26 443.5 13.5Q440 1 451-22L451-22Q456-30 463.5-61.5Q471-93 479-124L479-124Q485-139 492-164.5Q499-190 506-216.5Q513-243 518-261.5Q523-280 525-282L525-282Q525-286 525-289Q525-292 525-292L525-292Q523-294 500-295.5Q477-297 443-296.5Q409-296 372.5-294.5Q336-293 307-289L307-289Q296-289 282-289Q268-289 260-289L260-289Q249-289 241.5-289Q234-289 234-289L234-289Q233-286 227-268Q221-250 213-224.5Q205-199 196.5-172.5Q188-146 182.5-124.5Q177-103 175-94L175-94Q171-76 164.5-49Q158-22 150.5 6.5Q143 35 137.5 56Q132 77 130 83Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 350 -660.7 C 334.5 -627.4 277.9 -508.3 257.1 -460.7 C 236.3 -413.1 231.5 -398.2 225 -375 C 218.5 -351.8 220.9 -330.9 217.9 -321.4 C 214.9 -311.9 210.1 -320.9 207.1 -317.9 C 204.1 -314.9 210.1 -336.3 200 -303.6 C 189.9 -270.8 161.3 -175 146.4 -121.4 C 131.5 -67.8 116.7 -9.5 110.7 17.9 C 104.8 45.3 110.7 38.7 110.7 42.9",
|
||||
"M 217.9 -321.4 C 230.4 -320.8 270.9 -317.3 292.9 -317.9 C 314.9 -318.5 304.8 -323.2 350 -325 C 395.2 -326.8 510.1 -277.4 564.3 -328.6 C 618.5 -379.8 656.6 -581.5 675 -632.1",
|
||||
"M 553.6 -325 C 552.4 -313.7 555.3 -292.2 546.4 -257.1 C 537.5 -222 511.9 -157.1 500 -114.3 C 488.1 -71.4 479.2 -19 475 0",
|
||||
],
|
||||
},
|
||||
I: {
|
||||
adv: 400,
|
||||
outline:
|
||||
"M192 31L192 31Q185 20 160.5 17Q136 14 101 16L101 16Q87 18 74 14.5Q61 11 53.5 3.5Q46-4 48-12L48-12Q49-24 68.5-31Q88-38 131-39L131-39L184-40L194-60Q198-71 204-91Q210-111 217-134L217-134Q221-148 229-172Q237-196 246-223Q255-250 262-271.5Q269-293 271-302L271-302Q275-311 282-332.5Q289-354 295.5-376.5Q302-399 304-409L304-409Q330-488 338-521.5Q346-555 342-556L342-556Q338-556 322.5-555Q307-554 288.5-552.5Q270-551 255.5-549Q241-547 238-545L238-545Q233-538 221-542Q209-546 206-556L206-556Q205-565 205.5-570Q206-575 211-578L211-578Q218-584 238-591.5Q258-599 283.5-605Q309-611 332-614L332-614Q349-616 366-621.5Q383-627 393-635L393-635Q402-638 410.5-640Q419-642 428-641L428-641Q436-636 455.5-636Q475-636 497-637L497-637Q553-642 572.5-640Q592-638 599-624L599-624Q604-605 601-594.5Q598-584 581-585L581-585Q572-583 548.5-581.5Q525-580 499-579L499-579Q428-573 423-565L423-565Q419-561 416.5-557Q414-553 410-548L410-548Q411-544 405.5-522.5Q400-501 392-479L392-479Q388-466 380-442Q372-418 363.5-392.5Q355-367 348-346.5Q341-326 338-319L338-319Q334-310 331.5-299.5Q329-289 328-284L328-284Q314-235 303.5-201Q293-167 284.5-141.5Q276-116 269-92.5Q262-69 254-41L254-41Q254-41 269.5-42.5Q285-44 311-46L311-46Q358-51 378.5-52Q399-53 406-49.5Q413-46 416-37L416-37Q436-10 421 0Q406 10 350 9L350 9Q298 11 270.5 12.5Q243 14 232.5 17Q222 20 220 27L220 27Q220 35 212 35Q204 35 192 31Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 564.3 -610.7 C 544.7 -610.7 472.6 -611.9 446.4 -610.7 C 420.2 -609.5 418.4 -607.2 407.1 -603.6 C 395.8 -600 385.1 -604.2 378.6 -589.3 C 372.1 -574.4 391.7 -594.7 367.9 -514.3 C 344.1 -433.9 260.1 -189.2 235.7 -107.1 C 211.3 -25 223.8 -35.7 221.4 -21.4",
|
||||
"M 78.6 -10.7 C 100 -11.3 183.9 -12.5 207.1 -14.3 C 230.3 -16.1 199.4 -20.8 217.9 -21.4 C 236.4 -22 294.1 -17.3 317.9 -17.9 C 341.7 -18.5 348.2 -25 360.7 -25 C 373.2 -25 387.5 -19.1 392.9 -17.9",
|
||||
"M 228.6 -564.3 C 242.3 -567.9 287.5 -581.5 310.7 -585.7 C 333.9 -589.9 357.2 -589.9 367.9 -589.3 C 378.6 -588.7 373.8 -583.3 375 -582.1",
|
||||
],
|
||||
},
|
||||
J: {
|
||||
adv: 306,
|
||||
outline:
|
||||
"M89 181L89 181Q72 189 50.5 187.5Q29 186 8 175.5Q-13 165-29 148L-29 148Q-36 140-38 132.5Q-40 125-36 112L-36 112Q-31 94-23 94L-23 94Q-12 94 2 113L2 113Q30 138 57.5 127Q85 116 116 68L116 68Q122 53 135 25Q148-3 162.5-35Q177-67 189-94.5Q201-122 205-134L205-134Q205-134 205.5-137.5Q206-141 210-145L210-145Q211-149 215-161Q219-173 223-189L223-189Q227-200 234.5-222Q242-244 250.5-268Q259-292 265-308L265-308Q270-329 277.5-353.5Q285-378 290-391L290-391Q294-399 302-423.5Q310-448 320-477L320-477Q332-516 343-549.5Q354-583 360.5-604.5Q367-626 365-629L365-629Q365-629 347.5-626Q330-623 309-619L309-619Q292-612 275.5-614.5Q259-617 260-621L260-621Q260-625 260-625.5Q260-626 260-626L260-626Q257-624 252-627.5Q247-631 243-637.5Q239-644 239-649L239-649Q239-657 259-663.5Q279-670 310-674L310-674Q330-677 349.5-679.5Q369-682 382-684L382-684Q386-687 396.5-684Q407-681 418.5-676Q430-671 435-665L435-665Q441-657 442.5-648Q444-639 440-623Q436-607 425.5-577.5Q415-548 396-497L396-497Q374-435 357-384.5Q340-334 326-301L326-301Q323-290 315.5-269Q308-248 301-226.5Q294-205 290-191L290-191Q282-172 275-150Q268-128 264-115L264-115Q264-107 260-97Q256-87 251-82L251-82Q251-82 244-64.5Q237-47 227-23L227-23Q219-3 209.5 21.5Q200 46 192 59L192 59Q187 71 180 81.5Q173 92 173 96L173 96Q173 96 172.5 99Q172 102 168 102L168 102Q168 102 165 104.5Q162 107 162 112L162 112Q160 118 151.5 128Q143 138 131.5 149Q120 160 108.5 168.5Q97 177 89 181Z",
|
||||
stem: 64,
|
||||
strokes: [
|
||||
"M 267.9 -646.4 C 270.9 -645.8 266.1 -641.1 285.7 -642.9 C 305.3 -644.7 367.3 -659.5 385.7 -657.1 C 404.2 -654.7 397.6 -646.4 396.4 -628.6 C 395.2 -610.7 394.1 -597 378.6 -550 C 363.1 -503 328 -418.4 303.6 -346.4 C 279.2 -274.4 260.7 -192.3 232.1 -117.9 C 203.5 -43.5 155.3 57.1 132.1 100 C 108.9 142.9 101.8 131 92.9 139.3 C 84 147.6 86.9 147.6 78.6 150 C 70.3 152.4 54.2 154.2 42.9 153.6 C 31.6 153 21.4 151.8 10.7 146.4 C 0 141 -16 125.6 -21.4 121.4",
|
||||
],
|
||||
},
|
||||
K: {
|
||||
adv: 498,
|
||||
outline:
|
||||
"M96 37L96 37Q83 29 82 21.5Q81 14 87-7L87-7Q86-20 88.5-31Q91-42 92-46L92-46Q97-46 98-52Q99-58 99-62L99-62Q99-70 105.5-89Q112-108 117-128L117-128Q125-148 132.5-165.5Q140-183 140-187L140-187Q140-195 144-203.5Q148-212 153-220L153-220Q158-232 160.5-246.5Q163-261 163-269L163-269Q169-282 176-297Q183-312 187-328L187-328Q193-345 204.5-377.5Q216-410 229-444L229-444Q238-463 247-484Q256-505 263-520Q270-535 273-535L273-535Q273-535 273-538.5Q273-542 273-546L273-546Q273-551 281-565.5Q289-580 296-596L296-596Q306-611 310-624.5Q314-638 314-638L314-638Q314-638 317.5-644Q321-650 325-654L325-654Q338-663 349.5-661Q361-659 369-642L369-642Q377-626 376-615.5Q375-605 367-593L367-593Q359-575 346-547.5Q333-520 318.5-488.5Q304-457 291.5-428.5Q279-400 271.5-379.5Q264-359 264-354L264-354L223-253Q223-251 219.5-243Q216-235 210.5-219Q205-203 197-177Q189-151 178-114L178-114Q158-50 147-14.5Q136 21 129.5 34.5Q123 48 116 48L116 48Q112 48 107.5 44.5Q103 41 96 37ZM449 45L449 45Q440 46 424.5 36.5Q409 27 391.5 13Q374-1 358.5-16Q343-31 334-42L334-42Q318-65 294-102.5Q270-140 258-183L258-183Q251-215 244-233.5Q237-252 235-264L235-264L236-334L287-318Q292-264 302.5-228.5Q313-193 328-166L328-166Q340-140 355-114.5Q370-89 387-72L387-72Q410-47 428-35Q446-23 455-23L455-23Q460-22 469.5-16Q479-10 487.5-3.5Q496 3 497 9L497 9Q498 12 498 18.5Q498 25 496.5 30.5Q495 36 493 39L493 39Q490 46 482.5 51.5Q475 57 449 45ZM253-271L225-326L298-379Q301-382 319.5-399Q338-416 381-448L381-448L504-548Q529-561 555.5-581Q582-601 596-610L596-610Q613-622 626-631Q639-640 643-639L643-639Q651-643 659.5-636.5Q668-630 673.5-619.5Q679-609 678-601L678-601Q680-591 674-584Q668-577 645-564L645-564Q626-555 611.5-546Q597-537 594-531L594-531Q594-531 575.5-516.5Q557-502 528-482L528-482Q488-456 459.5-433Q431-410 423-398L423-398Q419-398 415.5-395Q412-392 408-393L408-393Q408-393 400.5-388Q393-383 389-378L389-378Q387-373 380.5-364Q374-355 364-356L364-356Q357-352 339-339.5Q321-327 301.5-312Q282-297 268-285Q254-273 253-271L253-271Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 642.9 -603.6 C 617.3 -586.3 548.2 -544 489.3 -500 C 430.4 -455.9 328 -367.3 289.3 -339.3 C 250.6 -311.3 264.3 -335.7 257.1 -332.1 C 250 -328.5 250.6 -320.3 246.4 -317.9 C 242.2 -315.5 238.7 -320.3 232.1 -317.9 C 225.6 -315.5 227.9 -356.6 207.1 -303.6 C 186.3 -250.6 123.8 -50.6 107.1 0",
|
||||
"M 339.3 -625 C 322 -585.1 253.6 -436.9 235.7 -385.7 C 217.8 -334.5 232.7 -329.2 232.1 -317.9",
|
||||
"M 246.4 -317.9 C 248.8 -315.5 258.3 -311.3 260.7 -303.6 C 263.1 -295.8 257.1 -290.4 260.7 -271.4 C 264.3 -252.3 272.6 -215.5 282.1 -189.3 C 291.6 -163.1 301.2 -140.5 317.9 -114.3 C 334.6 -88.1 357.7 -54.1 382.1 -32.1 C 406.5 -10.1 450.6 9.6 464.3 17.9",
|
||||
],
|
||||
},
|
||||
L: {
|
||||
adv: 426,
|
||||
outline:
|
||||
"M217 38L217 38Q177 28 145.5-2Q114-32 105-64L105-64Q104-77 102.5-98Q101-119 101.5-137Q102-155 104-158L104-158Q107-160 107-170L107-170Q106-174 108.5-183.5Q111-193 114-203L114-203Q119-215 119-222.5Q119-230 118-234L118-234Q118-234 121-240.5Q124-247 128-251L128-251Q134-257 133.5-263Q133-269 133-269L133-269Q132-273 135-280Q138-287 141-291L141-291Q143-293 146.5-299.5Q150-306 150-306L150-306Q150-309 154.5-322Q159-335 165-350.5Q171-366 176.5-379Q182-392 185-396L185-396L185-398Q189-408 196-419L196-419Q197-424 201-435.5Q205-447 210-458L210-458Q221-482 237.5-512.5Q254-543 273-573.5Q292-604 309.5-628.5Q327-653 340-665L340-665Q357-684 367-685Q377-686 392-671L392-671Q399-664 402-654Q405-644 404.5-636.5Q404-629 400-628L400-628Q393-624 379-605.5Q365-587 347.5-562Q330-537 313.5-510.5Q297-484 285-463L285-463Q272-434 261-412.5Q250-391 250-391L250-391Q249-388 248-386Q247-384 246-382L246-382Q231-351 221-325.5Q211-300 201-272L201-272Q195-265 194-258Q193-251 193-251L193-251Q191-252 190-246.5Q189-241 186-234L186-234Q181-230 180-221.5Q179-213 179-205L179-205Q172-187 166-161.5Q160-136 157.5-112.5Q155-89 156-76L156-76Q158-59 173.5-45.5Q189-32 211-23L211-23Q222-19 230-16.5Q238-14 245-14L245-14Q253-13 265.5-14.5Q278-16 300-20L300-20L299-20Q312-22 326.5-25.5Q341-29 354-32L354-32Q366-34 377-36.5Q388-39 394-41L394-41Q416-47 426-49.5Q436-52 441-48L441-48Q446-44 448-38.5Q450-33 447-24L447-24Q445-19 431-9.5Q417 0 397.5 9Q378 18 359 24L359 24Q339 33 311.5 38Q284 43 258.5 43Q233 43 217 38Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 364.3 -646.4 C 348.8 -624.4 299.4 -562.5 271.4 -514.3 C 243.4 -466.1 213.7 -397 196.4 -357.1 C 179.2 -317.2 175 -292.2 167.9 -275 C 160.8 -257.7 156 -260.7 153.6 -253.6 C 151.2 -246.4 157.2 -245.8 153.6 -232.1 C 150 -218.4 136.3 -196.4 132.1 -171.4 C 127.9 -146.4 127.4 -103.5 128.6 -82.1 C 129.8 -60.7 131.6 -54.8 139.3 -42.9 C 147 -31 162.5 -19 175 -10.7 C 187.5 -2.4 195.9 3.5 214.3 7.1 C 232.8 10.7 257.7 14.3 285.7 10.7 C 313.7 7.1 359.5 -7.2 382.1 -14.3 C 404.7 -21.4 414.9 -29.1 421.4 -32.1",
|
||||
],
|
||||
},
|
||||
M: {
|
||||
adv: 720,
|
||||
outline:
|
||||
"M661 48L661 48Q660 54 649.5 51.5Q639 49 631 37L631 37Q623 18 623 2Q623-14 624-46L624-46Q630-81 640.5-137.5Q651-194 665.5-264.5Q680-335 697-412L697-412Q709-452 716.5-485.5Q724-519 728-540Q732-561 732-561L732-561Q732-564 726.5-558.5Q721-553 716-546Q711-539 712-535L712-535Q712-535 705-525Q698-515 687-498L687-498Q677-488 663-458Q649-428 632-402L632-402Q616-372 598-337.5Q580-303 566-280L566-280Q557-267 548-250Q539-233 537-228L537-228Q533-217 523-201.5Q513-186 501-171L501-171Q495-162 489-152.5Q483-143 479.5-136.5Q476-130 476-130L476-130Q476-127 466.5-112Q457-97 443.5-79Q430-61 418-48L418-48Q383-11 362-4.5Q341 2 315-22L315-22Q302-38 296-58.5Q290-79 291-109.5Q292-140 298-186L298-186Q305-221 311-256.5Q317-292 322.5-323.5Q328-355 332.5-377Q337-399 340-406L340-406Q342-426 339-428Q336-430 327-416L327-416Q323-409 306-381.5Q289-354 266.5-314Q244-274 222-227L222-227Q181-151 154.5-101.5Q128-52 112.5-23Q97 6 90 20Q83 34 80 39Q77 44 75 46L75 46Q66 46 56.5 38.5Q47 31 37 21L37 21Q34 14 34.5 7Q35 0 41-13.5Q47-27 59-55L59-55Q72-81 83-100.5Q94-120 100-124L100-124Q105-132 109-140Q113-148 114-159L114-159Q119-165 129.5-183.5Q140-202 151-224L151-224Q159-237 171.5-258Q184-279 198-304.5Q212-330 223-353L223-353Q269-432 301-486.5Q333-541 349-556L349-556Q354-561 359.5-570Q365-579 365-586L365-586Q365-607 373.5-624.5Q382-642 391-640L391-640Q401-640 409-638Q417-636 426-625L426-625Q434-613 440-601Q446-589 439-553L439-553Q439-539 436-521.5Q433-504 430.5-489.5Q428-475 427-467L427-467Q414-428 403-379.5Q392-331 383-281.5Q374-232 368-189.5Q362-147 360.5-119.5Q359-92 362-88L362-88Q369-84 374-87Q379-90 391-113L391-113Q398-125 410.5-142.5Q423-160 435-175L435-175Q443-189 460-217Q477-245 503.5-289Q530-333 567.5-396.5Q605-460 653-544L653-544Q676-579 696.5-612.5Q717-646 730-656L730-656Q744-669 759-662.5Q774-656 785.5-636Q797-616 798-586L798-586Q799-572 794.5-541Q790-510 781-470.5Q772-431 758-389L758-389Q755-372 748.5-346.5Q742-321 734-294Q726-267 718-243L718-243Q713-210 706.5-178Q700-146 702-135L702-135Q693-119 690-89Q687-59 687-27L687-27Q683 12 681 28Q679 44 674 43L674 43Q661 39 661 48Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 396.4 -600 C 396.4 -590.5 400 -561.3 396.4 -542.9 C 392.8 -524.4 381 -501.2 375 -489.3 C 369.1 -477.4 372.6 -479.1 360.7 -471.4 C 348.8 -463.7 342.9 -503 303.6 -442.9 C 264.3 -382.8 158.3 -170.8 125 -110.7 C 91.7 -50.6 113.7 -101.1 103.6 -82.1 C 93.5 -63 70.9 -10.7 64.3 3.6",
|
||||
"M 364.3 -475 C 366.1 -473.2 374.4 -478.6 375 -464.3 C 375.6 -450 376.2 -441.1 367.9 -389.3 C 359.6 -337.5 331.6 -207.2 325 -153.6 C 318.5 -100 323.8 -84.6 328.6 -67.9 C 333.4 -51.2 342.9 -53.6 353.6 -53.6 C 364.3 -53.6 376.8 -52.4 392.9 -67.9 C 409 -83.4 398.8 -61.9 450 -146.4 C 501.2 -230.9 651.8 -500 700 -575 C 748.2 -650 729.2 -594 739.3 -596.4 C 749.4 -598.8 758.3 -604.2 760.7 -589.3 C 763.1 -574.4 764.3 -556.5 753.6 -507.1 C 742.9 -457.7 711.3 -354.8 696.4 -292.9 C 681.5 -231 672 -180.3 664.3 -135.7 C 656.6 -91 651.8 -48.8 650 -25 C 648.2 -1.2 653 1.7 653.6 7.1",
|
||||
],
|
||||
},
|
||||
N: {
|
||||
adv: 599,
|
||||
outline:
|
||||
"M97 62L97 62Q87 51 84 43.5Q81 36 83.5 25.5Q86 15 91-4L91-4Q99-36 108.5-57.5Q118-79 127.5-102.5Q137-126 144-163L144-163Q160-200 172.5-233Q185-266 192-287Q199-308 199-308L199-308Q200-312 208-329Q216-346 228.5-370.5Q241-395 252-420L252-420Q269-468 279.5-502.5Q290-537 301-565Q312-593 330-620L330-620Q342-641 358-647Q374-653 388.5-646.5Q403-640 409-620L409-620Q414-610 417.5-596Q421-582 424-555.5Q427-529 428-481L428-481Q430-424 429-368.5Q428-313 429.5-266Q431-219 440-186L440-186Q443-168 448.5-142.5Q454-117 460.5-92Q467-67 473.5-50Q480-33 484-33L484-33Q489-33 497.5-46Q506-59 519.5-93Q533-127 552-189.5Q571-252 595-350L595-350Q600-367 608.5-405Q617-443 621-482L621-482Q628-507 635-537Q642-567 647.5-590Q653-613 653-616L653-616Q653-623 653.5-628Q654-633 653-638L653-638Q658-638 662-651.5Q666-665 666-675L666-675Q666-692 670.5-702.5Q675-713 680-718L680-718Q683-726 695-725.5Q707-725 719-719Q731-713 734-703L734-703Q736-692 728.5-653.5Q721-615 706-550L706-550Q706-539 700-514Q694-489 687.5-461Q681-433 678-413L678-413Q674-387 669-364.5Q664-342 659-343L659-343Q633-230 604.5-150.5Q576-71 554-21L554-21Q530 34 501.5 45.5Q473 57 441 16L441 16Q422-10 411-35.5Q400-61 393-95.5Q386-130 376-181L376-181Q361-222 359.5-264Q358-306 362-350.5Q366-395 368-443L368-443Q367-491 364-507Q361-523 352.5-505.5Q344-488 325-435L325-435Q318-416 306.5-389Q295-362 284-335.5Q273-309 265-289.5Q257-270 255-265L255-265Q249-253 241-233.5Q233-214 228-192L228-192Q192-98 174.5-42Q157 14 149 36L149 36Q139 64 128 73.5Q117 83 97 62Z",
|
||||
stem: 67,
|
||||
strokes: [
|
||||
"M 700 -689.3 C 684.5 -619.6 631.5 -369.6 607.1 -271.4 C 582.7 -173.2 567.9 -141.1 553.6 -100 C 539.3 -58.9 531.5 -41.1 521.4 -25 C 511.3 -8.9 503 -6 492.9 -3.6 C 482.8 -1.2 471.4 1.2 460.7 -10.7 C 450 -22.6 439.9 -35.1 428.6 -75 C 417.3 -114.9 398.8 -173.8 392.9 -250 C 387 -326.2 393.5 -483.3 392.9 -532.1 C 392.3 -580.9 394.1 -538.1 389.3 -542.9 C 384.5 -547.7 374.4 -560.7 364.3 -560.7 C 354.2 -560.7 335.8 -547.7 328.6 -542.9 C 321.5 -538.1 330.3 -556.5 321.4 -532.1 C 312.5 -507.7 294.1 -443.4 275 -396.4 C 256 -349.4 233.9 -322 207.1 -250 C 180.3 -178 129.8 -11.9 114.3 35.7",
|
||||
],
|
||||
},
|
||||
O: {
|
||||
adv: 496,
|
||||
outline:
|
||||
"M175 20L175 20Q138-8 128-23.5Q118-39 116-61L116-61Q114-79 116.5-110Q119-141 123-171Q127-201 130-216L130-216Q135-221 134.5-229.5Q134-238 134-238L134-238Q133-243 141-264.5Q149-286 156-320L156-320Q176-380 194.5-420.5Q213-461 231-490Q249-519 267-543L267-543Q308-585 333-606.5Q358-628 389-642L389-642Q409-654 426-661Q443-668 454-663L454-663Q510-651 538-617.5Q566-584 579-514L579-514Q586-470 587.5-434.5Q589-399 582-364.5Q575-330 556.5-289Q538-248 505-193L505-193Q470-124 426.5-73Q383-22 337.5 7.5Q292 37 249.5 41Q207 45 175 20ZM239-9L239-9Q262-12 283-23Q304-34 327-55.5Q350-77 379-110L379-110Q405-140 421.5-160.5Q438-181 449.5-202Q461-223 471.5-253Q482-283 496-331L496-331Q504-361 508-381Q512-401 513-418.5Q514-436 513.5-459Q513-482 513-518L513-518Q512-535 506-553.5Q500-572 492-581L492-581Q482-592 464-596.5Q446-601 424-591L424-591Q412-586 393-568.5Q374-551 353-528Q332-505 313.5-481Q295-457 283.5-438.5Q272-420 272-413L272-413Q272-413 271.5-408Q271-403 267-398L267-398Q254-384 238.5-348Q223-312 209-266.5Q195-221 185.5-176.5Q176-132 174-99L174-99Q172-72 172-62Q172-52 176-46.5Q180-41 189-26L189-26Q195-16 209.5-11Q224-6 239-9Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 435.7 -628.6 C 430.9 -627.4 418.4 -626.8 407.1 -621.4 C 395.8 -616 389.3 -616 367.9 -596.4 C 346.5 -576.8 304.2 -538.1 278.6 -503.6 C 253 -469.1 232.2 -428.6 214.3 -389.3 C 196.4 -350 182.7 -307.2 171.4 -267.9 C 160.1 -228.6 150.6 -190.5 146.4 -153.6 C 142.2 -116.7 144.6 -67.8 146.4 -46.4 C 148.2 -25 150 -33.3 157.1 -25 C 164.3 -16.7 177.4 -2.9 189.3 3.6 C 201.2 10.2 216.7 13.7 228.6 14.3 C 240.5 14.9 251.2 8.9 260.7 7.1 C 270.2 5.3 275 7.8 285.7 3.6 C 296.4 -0.6 303 1.2 325 -17.9 C 347 -36.9 394.7 -84.5 417.9 -110.7 C 441.1 -136.9 444.7 -136.3 464.3 -175 C 483.9 -213.7 522 -306.6 535.7 -342.9 C 549.4 -379.2 544.6 -371.5 546.4 -392.9 C 548.2 -414.3 548.8 -442.2 546.4 -471.4 C 544 -500.6 536.9 -547.1 532.1 -567.9 C 527.4 -588.7 523.9 -588.1 517.9 -596.4 C 512 -604.7 510.1 -612.5 496.4 -617.9 C 482.7 -623.3 445.8 -626.8 435.7 -628.6",
|
||||
],
|
||||
},
|
||||
P: {
|
||||
adv: 464,
|
||||
outline:
|
||||
"M110 48L110 48Q98 48 90 35L90 35Q84 29 82.5 18.5Q81 8 89-19.5Q97-47 116-104L116-104Q126-142 132-159.5Q138-177 138-185L138-185Q138-196 140.5-203.5Q143-211 143-218L143-218Q149-218 155-229Q161-240 165-259L165-259Q169-276 172.5-286.5Q176-297 176-301L176-301Q181-309 186.5-326Q192-343 204-367L204-367Q212-397 222-429.5Q232-462 242.5-490.5Q253-519 260-535L260-535Q271-566 277-578.5Q283-591 277-595L277-595Q270-600 274-612Q278-624 296-639L296-639Q307-652 315-657.5Q323-663 335.5-663Q348-663 372-661L372-661Q390-660 410.5-660Q431-660 436-660L436-660Q445-656 458-651.5Q471-647 481.5-643Q492-639 492-636L492-636Q492-630 496.5-628.5Q501-627 506-626L506-626Q513-626 525-615.5Q537-605 550-589.5Q563-574 570-557L570-557Q578-540 582.5-525Q587-510 582-482L582-482Q581-467 577.5-452Q574-437 570-432L570-432Q564-428 559-422Q554-416 554-411L554-411Q554-407 547-395.5Q540-384 529-369Q518-354 505-339Q492-324 480-313L480-313Q467-297 449.5-283Q432-269 418-260L418-260Q392-248 360.5-236Q329-224 300.5-216Q272-208 253-209L253-209Q224-205 215.5-201.5Q207-198 203-184L203-184Q198-171 193.5-157.5Q189-144 189-139L189-139Q187-135 179.5-114.5Q172-94 163.5-67Q155-40 147-14Q139 12 135 29L135 29Q131 43 125 45.5Q119 48 110 48ZM234-262L234-262Q238-258 265-261Q292-264 323-276L323-276Q343-283 363.5-293Q384-303 403-315L403-315Q413-322 433-340Q453-358 473-383L473-383Q476-388 483.5-402.5Q491-417 497-430L497-430Q506-444 515-471.5Q524-499 519-517L519-517Q514-543 493-565.5Q472-588 443-596L443-596Q421-600 406.5-600Q392-600 375-597L375-597Q359-592 351.5-583Q344-574 332-545L332-545Q325-531 312-502.5Q299-474 290-449L290-449Q272-383 254.5-340.5Q237-298 233-287L233-287Q234-276 232-271.5Q230-267 234-262Z",
|
||||
stem: 60,
|
||||
strokes: [
|
||||
"M 196.4 -235.7 C 215.5 -237.5 276.8 -238.7 310.7 -246.4 C 344.6 -254.1 378.6 -271.4 400 -282.1 C 421.4 -292.8 423.8 -296.4 439.3 -310.7 C 454.8 -325 475.1 -341.7 492.9 -367.9 C 510.8 -394.1 536.9 -445.3 546.4 -467.9 C 555.9 -490.5 551.2 -489.9 550 -503.6 C 548.8 -517.3 548.2 -533.3 539.3 -550 C 530.4 -566.7 508.9 -591.7 496.4 -603.6 C 483.9 -615.5 478 -616.6 464.3 -621.4 C 450.6 -626.1 433.4 -630.3 414.3 -632.1 C 395.3 -633.9 366.7 -636.3 350 -632.1 C 333.3 -627.9 328 -631.5 314.3 -607.1 C 300.6 -582.7 281 -524.4 267.9 -485.7 C 254.8 -447 245.8 -404.2 235.7 -375 C 225.6 -345.8 213.7 -333.9 207.1 -310.7 C 200.6 -287.5 200.6 -250 196.4 -235.7 C 192.2 -221.4 188.1 -235.1 182.1 -225 C 176.1 -214.9 164.9 -188.7 160.7 -175 C 156.5 -161.3 165.4 -173.2 157.1 -142.9 C 148.8 -112.5 118.4 -17.9 110.7 7.1",
|
||||
],
|
||||
},
|
||||
Q: {
|
||||
adv: 502,
|
||||
outline:
|
||||
"M231 71L231 71Q216 68 198 59Q180 50 165 36.5Q150 23 143 9L143 9Q118-42 115.5-99.5Q113-157 130-239L130-239Q135-256 143.5-285Q152-314 162-343Q172-372 180-392Q188-412 190-412L190-412Q193-415 193-429L193-429Q193-434 201.5-450.5Q210-467 223.5-488Q237-509 251.5-528.5Q266-548 279-559L279-559Q299-585 324.5-603.5Q350-622 375-635L375-635Q381-639 392.5-644Q404-649 418-653Q432-657 443-658L443-658Q493-661 518-646Q543-631 562-592L562-592Q582-570 584-550Q586-530 590-501L590-501Q590-474 584.5-437.5Q579-401 570-365.5Q561-330 550-306.5Q539-283 529-283L529-283Q529-283 526-282.5Q523-282 523-277L523-277Q525-270 519-245.5Q513-221 498-192L498-192Q466-102 422.5-41.5Q379 19 330 48Q281 77 231 71ZM437 126L437 126Q421 127 405 114.5Q389 102 385 89L385 89Q378 83 378 77.5Q378 72 376 70L376 70Q372 68 365 59.5Q358 51 355 40L355 40Q348 26 334.5-0.5Q321-27 306-55.5Q291-84 279.5-105.5Q268-127 266-130L266-130Q258-138 268-150L268-150Q273-162 279.5-164Q286-166 305-161L305-161Q328-161 347-113L347-113Q348-108 356-89Q364-70 374-51.5Q384-33 390-28L390-28Q395-17 401-3Q407 11 414 20L414 20Q417 31 427 44Q437 57 441 71L441 71Q464 107 463.5 117Q463 127 437 126ZM239 13L239 13Q281 8 322.5-34.5Q364-77 401.5-146.5Q439-216 469-301L469-301Q478-326 491-356.5Q504-387 514-424Q524-461 524-502L524-502Q530-546 521-567Q512-588 488-599L488-599Q456-611 417.5-594.5Q379-578 342-535Q305-492 276-422L276-422Q235-335 211-266Q187-197 180-142Q173-87 183-42L183-42Q185-11 199.5 4.5Q214 20 239 13Z",
|
||||
stem: 64,
|
||||
strokes: [
|
||||
"M 296.4 -135.7 C 302.3 -123.8 322.6 -79.2 332.1 -64.3 C 341.6 -49.4 344.7 -47.6 353.6 -46.4 C 362.5 -45.2 373.2 -44.6 385.7 -57.1 C 398.2 -69.6 412.5 -90.4 428.6 -121.4 C 444.7 -152.4 470.2 -211.9 482.1 -242.9 C 494 -273.8 494.6 -294 500 -307.1 C 505.4 -320.2 508.4 -309.5 514.3 -321.4 C 520.3 -333.3 529.2 -354.8 535.7 -378.6 C 542.3 -402.4 550.6 -440.5 553.6 -464.3 C 556.6 -488.1 555.4 -502.9 553.6 -521.4 C 551.8 -539.8 548.3 -560.1 542.9 -575 C 537.5 -589.9 532.1 -601.2 521.4 -610.7 C 510.7 -620.2 497.7 -629.7 478.6 -632.1 C 459.6 -634.5 429.7 -632.7 407.1 -625 C 384.5 -617.3 364.3 -604.2 342.9 -585.7 C 321.5 -567.2 294.7 -535.1 278.6 -514.3 C 262.5 -493.5 263.1 -497 246.4 -460.7 C 229.7 -424.4 195.3 -344.6 178.6 -296.4 C 161.9 -248.2 151.8 -207.7 146.4 -171.4 C 141 -135.1 142.8 -108.3 146.4 -78.6 C 150 -48.8 160.2 -10.8 167.9 7.1 C 175.7 25 177.4 23.2 192.9 28.6 C 208.4 34 233.3 45.3 260.7 39.3 C 288.1 33.3 341 0.6 357.1 -7.1",
|
||||
"M 357.1 -46.4 C 357.1 -39.8 354.1 -16.6 357.1 -7.1 C 360.1 2.4 364.9 -5.4 375 10.7 C 385.1 26.8 407.8 73.8 417.9 89.3 C 428 104.8 432.7 101.2 435.7 103.6",
|
||||
],
|
||||
},
|
||||
R: {
|
||||
adv: 537,
|
||||
outline:
|
||||
"M140 60L140 60Q136 70 125.5 71Q115 72 98 59L98 59Q86 47 82.5 40.5Q79 34 86 18.5Q93 3 110-36L110-36Q114-60 124-84.5Q134-109 139-119L139-119Q143-133 155.5-165.5Q168-198 184.5-238Q201-278 216-313.5Q231-349 238-367L238-367Q255-407 273-451Q291-495 322-562L322-562Q326-572 326.5-583Q327-594 310-594L310-594Q300-605 299-616.5Q298-628 309-633L309-633L361-654Q408-665 457.5-660.5Q507-656 551-633L551-633Q599-605 619-576.5Q639-548 639.5-519Q640-490 629-462L629-462Q621-441 598.5-416Q576-391 546.5-366Q517-341 485.5-320.5Q454-300 427-289L427-289Q402-277 375-268Q348-259 319-250L319-250Q303-246 281-246Q259-246 248-250L248-250L231-203Q221-167 196.5-96Q172-25 140 60ZM537 71L537 71Q529 74 518.5 73Q508 72 485 57L485 57Q441 31 397-6Q353-43 298-106L298-106Q277-131 262.5-152Q248-173 232-211L232-211L218-265L245-280L281-244Q297-209 322.5-177Q348-145 370-120L370-120Q388-99 419-69.5Q450-40 485-13Q520 14 548 28L548 28Q554 33 552 43Q550 53 545.5 61.5Q541 70 537 71ZM279-301L279-301Q302-303 333-312.5Q364-322 395-335L395-335Q424-349 456.5-368.5Q489-388 517-412Q545-436 562.5-462Q580-488 578-514L578-514Q578-527 568-541Q558-555 544-566Q530-577 517-581L517-581Q477-591 451-594Q425-597 411-590.5Q397-584 392-564L392-564Q392-561 384-544Q376-527 364.5-502Q353-477 342-449L342-449Q313-390 298-359.5Q283-329 279-317Q275-305 279-301Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 242.9 -275 C 256 -276.2 287.5 -272.6 321.4 -282.1 C 355.3 -291.6 409.5 -312.5 446.4 -332.1 C 483.3 -351.7 522.1 -383.9 542.9 -400 C 563.7 -416.1 561.9 -416.1 571.4 -428.6 C 580.9 -441.1 595.2 -455.4 600 -475 C 604.8 -494.6 603.6 -529.1 600 -546.4 C 596.4 -563.7 587.5 -569.1 578.6 -578.6 C 569.7 -588.1 557.7 -597.1 546.4 -603.6 C 535.1 -610.1 529.2 -613.1 510.7 -617.9 C 492.3 -622.6 457.1 -630.9 435.7 -632.1 C 414.3 -633.3 395.2 -629.2 382.1 -625 C 369 -620.8 363.1 -620.8 357.1 -607.1 C 351.2 -593.4 363.7 -586.9 346.4 -542.9 C 329.2 -498.9 271.5 -392.9 253.6 -342.9 C 235.8 -292.9 245.3 -260.8 239.3 -242.9 C 233.4 -225 223.9 -239.3 217.9 -235.7 C 212 -232.1 220.9 -265.4 203.6 -221.4 C 186.3 -177.3 129.2 -13.1 114.3 28.6",
|
||||
"M 239.3 -242.9 C 242.3 -240.5 248.2 -242.3 257.1 -228.6 C 266 -214.9 277.4 -183.3 292.9 -160.7 C 308.4 -138.1 326.8 -117.3 350 -92.9 C 373.2 -68.5 403.5 -36.9 432.1 -14.3 C 460.7 8.3 506.5 33.4 521.4 42.9",
|
||||
],
|
||||
},
|
||||
S: {
|
||||
adv: 486,
|
||||
outline:
|
||||
"M254 50L254 50Q236 45 225 41.5Q214 38 214 38L214 38Q216 33 212.5 29Q209 25 198 24L198 24Q183 15 170.5 1.5Q158-12 150-25Q142-38 138-47L138-47Q131-62 131.5-65.5Q132-69 143-74L143-74Q154-82 159.5-79Q165-76 173-59L173-59Q186-36 217.5-24Q249-12 288.5-13.5Q328-15 364-31L364-31Q401-46 410.5-62.5Q420-79 403-105L403-105Q400-113 396-118Q392-123 383.5-131Q375-139 356-157L356-157Q341-164 328.5-170Q316-176 302-183L302-183Q286-191 263.5-206.5Q241-222 221-241Q201-260 192-276L192-276Q176-291 168-317.5Q160-344 162-374Q164-404 177-428L177-428Q183-442 197.5-462.5Q212-483 230-504Q248-525 265-542Q282-559 293-566L293-566Q301-574 310-582.5Q319-591 319-591L319-591Q320-596 322.5-596Q325-596 325-596L325-596Q330-599 347-606Q364-613 384-632L384-632Q451-664 493-679Q535-694 562-694L562-694L562-694L562-694Q587-693 607.5-682.5Q628-672 641.5-655Q655-638 658-614L658-614Q665-590 654-558.5Q643-527 621.5-499.5Q600-472 575-458L575-458Q547-446 537.5-447.5Q528-449 529-468L529-468Q529-476 534.5-487Q540-498 565-526L565-526Q597-573 604-601Q611-629 591-632L591-632Q585-634 569.5-632Q554-630 535.5-626.5Q517-623 504-619.5Q491-616 491-614L491-614Q491-614 491-611Q491-608 487-609L487-609Q485-609 470-601Q455-593 434-580Q413-567 392-554Q371-541 356.5-531.5Q342-522 341-520L341-520Q339-509 318-500L318-500Q298-488 281.5-465Q265-442 245-408L245-408Q228-385 221-358.5Q214-332 221-316L221-316Q226-300 252-275Q278-250 317-227L317-227Q351-208 378-189.5Q405-171 422.5-154Q440-137 446-124L446-124Q458-107 463.5-89.5Q469-72 469.5-56Q470-40 466-27L466-27Q459-11 441.5 6Q424 23 412 22L412 22Q401 25 395.5 29Q390 33 383 36L383 36Q376 40 353.5 44Q331 48 304 49.5Q277 51 254 50Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 550 -471.4 C 558.3 -480.3 586.9 -505.9 600 -525 C 613.1 -544 623.8 -570.2 628.6 -585.7 C 633.4 -601.2 630.4 -607.8 628.6 -617.9 C 626.8 -628 628.6 -638.7 617.9 -646.4 C 607.2 -654.1 582.2 -662.5 564.3 -664.3 C 546.4 -666.1 535.7 -665.4 510.7 -657.1 C 485.7 -648.8 442.9 -629.2 414.3 -614.3 C 385.7 -599.4 365.5 -588.1 339.3 -567.9 C 313.1 -547.7 278.5 -516.1 257.1 -492.9 C 235.7 -469.7 222 -447.6 210.7 -428.6 C 199.4 -409.5 191.7 -398.8 189.3 -378.6 C 186.9 -358.3 186.9 -329.1 196.4 -307.1 C 205.9 -285.1 224.4 -265.4 246.4 -246.4 C 268.4 -227.4 309 -204.2 328.6 -192.9 C 348.3 -181.6 350 -188.7 364.3 -178.6 C 378.6 -168.5 403 -145.8 414.3 -132.1 C 425.6 -118.4 428.5 -110.7 432.1 -96.4 C 435.7 -82.1 438.1 -58.9 435.7 -46.4 C 433.3 -33.9 431 -30.3 417.9 -21.4 C 404.8 -12.5 382.7 1.1 357.1 7.1 C 331.5 13.1 283.9 13.7 264.3 14.3 C 244.7 14.9 252.4 15.5 239.3 10.7 C 226.2 5.9 200.6 -2.4 185.7 -14.3 C 170.8 -26.2 156 -53 150 -60.7",
|
||||
],
|
||||
},
|
||||
T: {
|
||||
adv: 458,
|
||||
outline:
|
||||
"M215 44L215 44Q207 44 197 31Q187 18 188 6L188 6Q189-10 197-40Q205-70 215-101Q225-132 233-150L233-150Q237-166 240.5-178.5Q244-191 245-195L245-195Q248-199 254.5-216Q261-233 265-249L265-249Q267-257 273.5-279.5Q280-302 289.5-332Q299-362 308.5-391Q318-420 325-442Q332-464 334-471L334-471Q342-483 346-494Q350-505 351-509L351-509Q357-513 358-519.5Q359-526 359-530L359-530Q359-535 362.5-538Q366-541 366-545L366-545Q370-548 371-554.5Q372-561 377-566L377-566L382-583L441-584Q439-572 435-554.5Q431-537 416-508L416-508Q411-488 404.5-472Q398-456 399-451L399-451Q397-449 390.5-431Q384-413 375.5-387Q367-361 358.5-335Q350-309 343.5-289.5Q337-270 335-265L335-265Q327-249 317-218Q307-187 287-134L287-134Q279-118 267.5-86Q256-54 249-28L249-28Q238 11 233.5 26.5Q229 42 226 43.5Q223 45 215 44ZM223-552L223-552Q195-551 181.5-555Q168-559 172-575L172-575Q175-587 185-593Q195-599 214-602L214-602Q225-604 242.5-607Q260-610 276-613L276-613Q309-617 330.5-620Q352-623 375-625Q398-627 433-629L433-629Q462-631 482.5-632.5Q503-634 503-634L503-634Q506-634 524-632Q542-630 563-627Q584-624 594-620L594-620Q617-615 627.5-606Q638-597 642-580L642-580Q645-568 642-565.5Q639-563 631-559L631-559Q608-552 597-552Q586-552 572-557L572-557Q567-562 560.5-566Q554-570 538.5-572.5Q523-575 490-577L490-577Q462-574 441-574Q420-574 420-569L420-569L361-566L312-561Q302-560 284-558.5Q266-557 249-555.5Q232-554 223-552Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 200 -575 C 216.7 -578 253 -587.5 300 -592.9 C 347 -598.2 439.8 -605.3 482.1 -607.1 C 524.4 -608.9 533.4 -607.8 553.6 -603.6 C 573.9 -599.4 595.3 -585.7 603.6 -582.1",
|
||||
"M 407.1 -600 C 405.9 -591.1 407.7 -570.2 400 -546.4 C 392.3 -522.6 380.3 -513.6 360.7 -457.1 C 341.1 -400.5 304.7 -273.2 282.1 -207.1 C 259.5 -141 236.9 -94 225 -60.7 C 213.1 -27.4 212.5 -19.6 210.7 -7.1 C 208.9 5.4 213.7 10.7 214.3 14.3",
|
||||
],
|
||||
},
|
||||
U: {
|
||||
adv: 482,
|
||||
outline:
|
||||
"M220 52L220 52Q174 35 149.5-1.5Q125-38 123.5-104.5Q122-171 144-275L144-275Q151-310 164-355Q177-400 194.5-447.5Q212-495 231.5-539.5Q251-584 270-618L270-618Q293-659 305.5-669.5Q318-680 328-678L328-678Q347-673 355.5-659.5Q364-646 361.5-630.5Q359-615 342-604L342-604Q331-591 313.5-556.5Q296-522 278-490L278-490Q258-432 243-387.5Q228-343 218-304Q208-265 200-222L200-222Q191-180 186.5-153Q182-126 180.5-108.5Q179-91 179-78Q179-65 179-51L179-51Q187-38 192-28Q197-18 205-16L205-16Q222-8 243.5-12Q265-16 286-28L286-28Q291-38 299-42Q307-46 307-46L307-46Q309-41 311.5-43.5Q314-46 315-50L315-50Q314-54 317-57.5Q320-61 324-65L324-65Q332-68 347.5-85.5Q363-103 379.5-125Q396-147 407.5-165Q419-183 419-187L419-187Q419-192 423-196Q427-200 432-206L432-206Q437-211 452.5-238.5Q468-266 486.5-303Q505-340 520-371L520-371Q523-382 531-406.5Q539-431 548.5-463Q558-495 567-527.5Q576-560 583.5-585.5Q591-611 593-623L593-623Q595-636 599-644.5Q603-653 604-657L604-657Q609-659 616-656Q623-653 630.5-649Q638-645 644-642L644-642Q650-634 649-622Q648-610 642.5-585.5Q637-561 627-515L627-515Q606-433 576.5-364Q547-295 513-218L513-218Q513-218 513-217.5Q513-217 513-212L513-212Q501-205 494-191L494-191Q489-182 485.5-175.5Q482-169 477-166L477-166Q474-157 471-150Q468-143 468-143L468-143Q467-138 461-134Q455-130 449-126L449-126Q444-124 443-118Q442-112 439-108L439-108Q417-75 389-43Q361-11 331.5 12.5Q302 36 275 46L275 46Q257 53 245.5 54Q234 55 220 52Z",
|
||||
stem: 62,
|
||||
strokes: [
|
||||
"M 325 -639.3 C 319.7 -632.7 309 -629.8 292.9 -600 C 276.8 -570.2 246.5 -506.5 228.6 -460.7 C 210.7 -414.9 198.2 -373.2 185.7 -325 C 173.2 -276.8 159.5 -213.1 153.6 -171.4 C 147.7 -129.7 149.4 -97.6 150 -75 C 150.6 -52.4 151.8 -48.2 157.1 -35.7 C 162.5 -23.2 171.4 -8.3 182.1 0 C 192.8 8.3 204.7 13.1 221.4 14.3 C 238.1 15.5 264.2 13.7 282.1 7.1 C 300 0.5 311.9 -10.1 328.6 -25 C 345.3 -39.9 363.7 -59.5 382.1 -82.1 C 400.6 -104.7 422 -135.1 439.3 -160.7 C 456.6 -186.3 466.1 -196.4 485.7 -235.7 C 505.3 -275 535.1 -332.1 557.1 -396.4 C 579.1 -460.7 607.8 -583.9 617.9 -621.4",
|
||||
],
|
||||
},
|
||||
V: {
|
||||
adv: 490,
|
||||
outline:
|
||||
"M260 58L260 58Q255 56 253 52Q251 48 249 36L249 36Q249 25 243 20Q237 15 220 11L220 11Q209 0 204-14Q199-28 196.5-60Q194-92 190-156L190-156Q192-191 193.5-215Q195-239 197-260Q199-281 201.5-305Q204-329 209-365L209-365Q207-388 211.5-415Q216-442 213-458L213-458Q220-480 222-508.5Q224-537 231-559L231-559Q233-576 235.5-591Q238-606 238-616L238-616Q236-636 240-653Q244-670 256-675L256-675Q262-677 273-678Q284-679 289-675L289-675Q297-671 300-662Q303-653 302-642L302-642Q304-631 302.5-604Q301-577 297-544.5Q293-512 286-481L286-481Q278-440 273.5-397.5Q269-355 267-314L267-314Q263-232 259-178Q255-124 258-100L258-100Q258-97 260-84Q262-71 265-59.5Q268-48 271-48L271-48Q279-49 286.5-62Q294-75 300.5-92.5Q307-110 313-123L313-123Q326-148 339.5-177.5Q353-207 363-234L363-234Q381-276 399-306Q417-336 433-373L433-373Q439-389 454.5-419.5Q470-450 484-476L484-476Q501-502 516.5-526Q532-550 553-590L553-590Q582-638 598-656Q614-674 630-674L630-674Q641-676 643.5-674.5Q646-673 653-663L653-663Q661-652 661.5-640.5Q662-629 651.5-609Q641-589 615-552L615-552Q595-522 570.5-482Q546-442 525.5-406.5Q505-371 496-354L496-354Q491-343 487-331Q483-319 475-309L475-309Q465-299 448-266Q431-233 415-201L415-201Q397-159 386.5-135.5Q376-112 369.5-97Q363-82 357-68Q351-54 342-33L342-33Q337-16 328.5 2.5Q320 21 319 27L319 27Q312 38 300.5 46.5Q289 55 278 58Q267 61 260 58Z",
|
||||
stem: 67,
|
||||
strokes: [
|
||||
"M 271.4 -639.3 C 266 -603.6 247.6 -498.2 239.3 -425 C 231 -351.8 223.2 -263.1 221.4 -200 C 219.6 -136.9 225.6 -75.6 228.6 -46.4 C 231.6 -17.2 232.8 -29.7 239.3 -25 C 245.9 -20.2 260.2 -19.1 267.9 -17.9 C 275.6 -16.7 279.8 -16.1 285.7 -17.9 C 291.7 -19.7 286.3 6.5 303.6 -28.6 C 320.9 -63.7 366.1 -179.8 389.3 -228.6 C 412.5 -277.4 428 -292.2 442.9 -321.4 C 457.8 -350.6 448.9 -351.2 478.6 -403.6 C 508.4 -456 597.6 -597 621.4 -635.7",
|
||||
],
|
||||
},
|
||||
W: {
|
||||
adv: 720,
|
||||
outline:
|
||||
"M498 53L498 53Q485 54 474.5 47.5Q464 41 460 31L460 31Q456 26 448 14Q440 2 438-10L438-10Q435-42 434.5-72Q434-102 437-156L437-156Q440-200 448.5-241.5Q457-283 465.5-316Q474-349 479-368.5Q484-388 481-388L481-388Q477-388 469-373.5Q461-359 451-338Q441-317 431-296.5Q421-276 414-263L414-263Q400-232 381.5-197.5Q363-163 344-131Q325-99 311-75L311-75Q302-65 290-46.5Q278-28 264-8Q250 12 235 25L235 25Q213 46 191.5 48Q170 50 155 37L155 37Q152 33 148.5 27.5Q145 22 143 13Q141 4 144-8L144-8Q143-22 144.5-51.5Q146-81 150-116.5Q154-152 158.5-185Q163-218 167-240L167-240Q176-304 191.5-371Q207-438 220-506L220-506Q228-526 234.5-551.5Q241-577 248-599L248-599Q249-621 258-628.5Q267-636 278.5-633.5Q290-631 297-621L297-621Q307-611 312.5-590Q318-569 304-537L304-537Q284-488 265.5-417.5Q247-347 232-247L232-247Q228-223 223-190Q218-157 214-126Q210-95 208.5-74Q207-53 209-52L209-52Q217-50 231-64.5Q245-79 256-103L256-103Q297-171 337.5-245.5Q378-320 415-393L415-393Q432-422 449.5-456.5Q467-491 483-520L483-520Q500-550 514-574.5Q528-599 538-615Q548-631 554-633L554-633Q570-632 581.5-625.5Q593-619 601-599L601-599Q603-594 600-582Q597-570 593-561L593-561Q592-554 584.5-532Q577-510 571-483L571-483Q561-456 553.5-429.5Q546-403 543-386L543-386Q535-343 523-291.5Q511-240 502.5-189.5Q494-139 493-99L493-99Q492-71 493.5-55Q495-39 499-30L499-30Q501-22 511.5-20Q522-18 529-27L529-27Q549-52 563.5-72.5Q578-93 591-114.5Q604-136 618-163L618-163Q643-209 669.5-265.5Q696-322 720.5-378Q745-434 763-476L763-476Q782-530 793.5-562Q805-594 814-610Q823-626 833-632L833-632Q838-636 847-636Q856-636 865-628L865-628Q869-624 872.5-615.5Q876-607 875-595L875-595Q870-579 857.5-544Q845-509 829-467Q813-425 798-387Q783-349 772-326L772-326Q761-305 751-290Q741-275 741-267L741-267Q741-263 731-242.5Q721-222 705.5-193Q690-164 672-133Q654-102 638-76Q622-50 611-36L611-36Q586-5 562.5 15Q539 35 522 44Q505 53 498 53Z",
|
||||
stem: 64,
|
||||
strokes: [
|
||||
"M 275 -592.9 C 275 -589.3 280.4 -591 275 -571.4 C 269.7 -551.7 252.4 -508.9 242.9 -475 C 233.4 -441.1 227.4 -416.1 217.9 -367.9 C 208.4 -319.7 192.9 -229.7 185.7 -185.7 C 178.5 -141.6 176.8 -131 175 -103.6 C 173.2 -76.2 173.2 -36.3 175 -21.4 C 176.8 -6.5 175 -12.5 185.7 -14.3 C 196.4 -16.1 224.4 -21.4 239.3 -32.1 C 254.2 -42.8 254.2 -45.3 275 -78.6 C 295.8 -111.9 335.7 -178.5 364.3 -232.1 C 392.9 -285.7 431.5 -370.2 446.4 -400 C 461.3 -429.8 445.3 -405.9 453.6 -410.7 C 461.9 -415.5 478 -398.8 496.4 -428.6 C 514.8 -458.4 553 -562.5 564.3 -589.3",
|
||||
"M 496.4 -428.6 C 498.8 -426.8 508.9 -428 510.7 -417.9 C 512.5 -407.8 514.2 -405.4 507.1 -367.9 C 500 -330.4 475.6 -246.5 467.9 -192.9 C 460.2 -139.3 460.7 -76.2 460.7 -46.4 C 460.7 -16.6 464.9 -22 467.9 -14.3 C 470.9 -6.6 473.3 -3.6 478.6 0 C 484 3.6 489.9 7.7 500 7.1 C 510.1 6.5 525 6.5 539.3 -3.6 C 553.6 -13.7 566.1 -25.6 585.7 -53.6 C 605.3 -81.6 629.1 -118.4 657.1 -171.4 C 685.1 -224.4 722.6 -299.4 753.6 -371.4 C 784.6 -443.4 828 -564.9 842.9 -603.6",
|
||||
],
|
||||
},
|
||||
X: {
|
||||
adv: 524,
|
||||
outline:
|
||||
"M633-643L633-643Q653-642 659-626.5Q665-611 657-584Q649-557 625-523L625-523Q602-500 599-484L599-484Q592-475 577.5-458.5Q563-442 546.5-423.5Q530-405 517.5-391Q505-377 501-374L501-374Q487-362 469.5-346Q452-330 438.5-316.5Q425-303 421-298L421-298Q414-291 408-283.5Q402-276 397-274L397-274Q396-270 391-264.5Q386-259 384-254L384-254Q382-253 371-243Q360-233 346.5-220.5Q333-208 322.5-198Q312-188 311-186L311-186Q300-176 277-151.5Q254-127 226-97Q198-67 172-38.5Q146-10 128.5 10.5Q111 31 109 36L109 36Q105 44 96.5 46.5Q88 49 80 47L80 47Q76 49 71.5 48.5Q67 48 65 47L65 47Q65 47 63 42Q61 37 59 26L59 26Q53 16 56 7Q59-2 63-13L63-13Q70-26 77-35Q84-44 101-59.5Q118-75 153-108L153-108Q169-124 185.5-138.5Q202-153 203-156L203-156Q204-160 211-167Q218-174 225-184L225-184Q232-189 248.5-204.5Q265-220 285-239.5Q305-259 324.5-278Q344-297 357-309L357-309Q363-317 371.5-325.5Q380-334 390-346L390-346Q391-348 403.5-360.5Q416-373 433-390.5Q450-408 465.5-424.5Q481-441 489-450L489-450Q529-497 561.5-537.5Q594-578 607-606L607-606Q617-627 621-634.5Q625-642 633-643ZM492 27L492 27Q473 25 454 7.5Q435-10 412-51L412-51Q375-102 347-174.5Q319-247 308-331L308-331Q303-366 297-391.5Q291-417 289-419L289-419Q288-423 290.5-424.5Q293-426 293-426L293-426Q296-429 294-432.5Q292-436 290-441L290-441Q290-441 285-464Q280-487 277-521L277-521Q273-557 270.5-583.5Q268-610 266-618L266-618Q263-620 263-624Q263-628 263-628L263-628Q265-634 269-638.5Q273-643 276-646L276-646Q291-657 305.5-650.5Q320-644 329.5-625.5Q339-607 339-582L339-582Q339-556 345-550L345-550Q344-548 344.5-535Q345-522 346.5-505.5Q348-489 350-475Q352-461 353-456L353-456Q349-444 353-439L353-439Q354-431 356-417.5Q358-404 357-399L357-399Q356-394 359.5-381.5Q363-369 366-356L366-356L392-266Q400-234 407-209.5Q414-185 424-161.5Q434-138 448-109L448-109Q459-85 474-64.5Q489-44 501-37L501-37Q503-35 511.5-34.5Q520-34 522-37L522-37Q533-40 538.5-33.5Q544-27 546.5-17.5Q549-8 549 2L549 2Q551 9 542.5 15.5Q534 22 520.5 25.5Q507 29 492 27Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 353.6 -246.4 C 345.9 -243.4 345.2 -262.5 307.1 -228.6 C 269 -194.7 161.9 -82.8 125 -42.9 C 88.1 -3 92.3 1.8 85.7 10.7",
|
||||
"M 292.9 -614.3 C 294.7 -609.5 300 -609.5 303.6 -585.7 C 307.2 -561.9 311.3 -492.2 314.3 -471.4 C 317.3 -450.6 319 -479.1 321.4 -460.7 C 323.8 -442.2 325.6 -385.1 328.6 -360.7 C 331.6 -336.3 335.1 -324.4 339.3 -314.3 C 343.5 -304.2 351.2 -311.3 353.6 -300 C 356 -288.7 351.8 -257.7 353.6 -246.4 C 355.4 -235.1 358.4 -248.2 364.3 -232.1 C 370.3 -216 376.8 -179.2 389.3 -150 C 401.8 -120.8 425.6 -79.1 439.3 -57.1 C 453 -35.1 460.1 -25.6 471.4 -17.9 C 482.7 -10.2 501.2 -11.9 507.1 -10.7",
|
||||
"M 635.7 -614.3 C 630.4 -603 625.6 -578.5 603.6 -546.4 C 581.6 -514.2 538.1 -460.1 503.6 -421.4 C 469.1 -382.7 421.4 -334.5 396.4 -314.3 C 371.4 -294.1 360.7 -302.4 353.6 -300",
|
||||
],
|
||||
},
|
||||
Y: {
|
||||
adv: 504,
|
||||
outline:
|
||||
"M357-254L330-267L295-311Q275-341 263-367Q251-393 246-431L246-431Q239-455 237-476Q235-497 235.5-524.5Q236-552 237-593L237-593Q238-631 245.5-648Q253-665 257-673L257-673Q267-685 275.5-676Q284-667 289.5-645.5Q295-624 294-595L294-595Q297-488 309.5-425Q322-362 344-348L344-348Q356-345 365.5-349.5Q375-354 393.5-373.5Q412-393 449-433L449-433Q476-462 504.5-495Q533-528 558.5-558Q584-588 604.5-609.5Q625-631 637-638L637-638Q641-644 645.5-647Q650-650 654-650.5Q658-651 658-651L658-651Q666-649 667-638.5Q668-628 675-618L675-618Q677-614 678-609.5Q679-605 678-600L678-600Q678-597 674-592.5Q670-588 665.5-583.5Q661-579 656-574L656-574Q643-563 615.5-535.5Q588-508 559-476.5Q530-445 510-420L510-420Q466-369 431.5-332Q397-295 389-288L389-288Q388-286 375-274Q362-262 357-254L357-254ZM259 68L259 68Q255 67 247 61.5Q239 56 229 49L229 49Q222 41 220.5 34Q219 27 223 14Q227 1 234-26L234-26Q235-49 243.5-82Q252-115 264-137L264-137Q264-148 269-167Q274-186 281.5-206.5Q289-227 295-242L295-242L313-294L353-328L381-297Q377-291 374-283Q371-275 366-256Q361-237 351-197L351-197Q343-170 333.5-139Q324-108 316-81Q308-54 302-37L302-37Q290 8 284 32Q278 56 273.5 63.5Q269 71 259 68Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 264.3 -657.1 C 263.7 -639.2 258.3 -591.7 260.7 -550 C 263.1 -508.3 270.3 -442.8 278.6 -407.1 C 286.9 -371.4 300 -351.2 310.7 -335.7 C 321.4 -320.2 329.8 -314.9 342.9 -314.3 C 356 -313.7 372.6 -320.2 389.3 -332.1 C 406 -344 414.9 -354.1 442.9 -385.7 C 470.9 -417.2 523.2 -483.9 557.1 -521.4 C 591 -558.9 630.3 -592.2 646.4 -610.7 C 662.5 -629.2 652.4 -628.5 653.6 -632.1",
|
||||
"M 342.9 -314.3 C 339.9 -300 338.1 -273.8 325 -228.6 C 311.9 -183.4 276.2 -82.2 264.3 -42.9 C 252.4 -3.6 254.8 -5.4 253.6 7.1 C 252.4 19.6 256.5 27.9 257.1 32.1",
|
||||
],
|
||||
},
|
||||
Z: {
|
||||
adv: 522,
|
||||
outline:
|
||||
"M122 36L122 36Q101 34 84 30.5Q67 27 63 7L63 7Q61-7 66.5-21.5Q72-36 82-46L82-46Q99-64 119.5-86Q140-108 168.5-136Q197-164 237.5-202.5Q278-241 336-292L336-292Q376-326 400-348.5Q424-371 443-390Q462-409 485-433L485-433Q520-469 546.5-503Q573-537 578-557L578-557Q584-577 575.5-585.5Q567-594 545.5-597.5Q524-601 489-603L489-603Q427-608 379.5-602.5Q332-597 288-589L288-589Q281-587 271.5-592.5Q262-598 262-602L262-602Q266-607 266-612Q266-617 258-623L258-623Q254-633 258-639Q262-645 266-649L266-649Q274-653 292-654.5Q310-656 333-660L333-660Q348-663 377.5-664Q407-665 437.5-665Q468-665 486-663L486-663Q522-660 555-656.5Q588-653 612-644Q636-635 645-613L645-613Q656-590 649.5-572Q643-554 632-524L632-524Q620-502 591.5-465.5Q563-429 526.5-389.5Q490-350 452-315L452-315Q396-263 351.5-222.5Q307-182 263-144L263-144Q235-120 207-94.5Q179-69 151-43L151-43Q141-30 147.5-23.5Q154-17 195-19L195-19Q218-20 231-23Q244-26 259-29Q274-32 301-33L301-33Q353-36 399-39Q445-42 473-42L473-42Q481-42 486.5-40Q492-38 502-24L502-24Q511-21 513.5-15.5Q516-10 513-4Q510 2 501 9L501 9Q494 9 476.5 12.5Q459 16 438.5 18.5Q418 21 402 20L402 20Q389 19 352.5 21.5Q316 24 274 29L274 29Q237 35 193 36Q149 37 122 36Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 292.9 -625 C 310.8 -627.4 372 -636.9 400 -639.3 C 428 -641.7 430.9 -641.7 460.7 -639.3 C 490.5 -636.9 554.2 -630.9 578.6 -625 C 603 -619 602.4 -614.9 607.1 -603.6 C 611.9 -592.3 611.3 -573.8 607.1 -557.1 C 602.9 -540.4 597.6 -526.2 582.1 -503.6 C 566.6 -481 590.5 -495.8 514.3 -421.4 C 438.1 -347 192.3 -126.7 125 -57.1 C 57.7 12.5 110.1 -14.3 110.7 -3.6 C 111.3 7.1 113.1 5.3 128.6 7.1 C 144.1 8.9 175.6 9.5 203.6 7.1 C 231.6 4.7 255.3 -2.9 296.4 -7.1 C 337.5 -11.3 420.2 -17.3 450 -17.9 C 479.8 -18.5 470.8 -11.9 475 -10.7",
|
||||
],
|
||||
},
|
||||
"!": {
|
||||
adv: 210,
|
||||
outline:
|
||||
"M197-170L197-170Q169-165 160-197Q151-229 165-285L165-285Q171-297 175-308.5Q179-320 179-327L179-327Q183-340 189.5-357.5Q196-375 203-390.5Q210-406 213-411L213-411Q217-411 221.5-418Q226-425 226-432L226-432Q226-440 229.5-450Q233-460 237-464L237-464Q237-469 241-475Q245-481 245-485L245-485Q245-489 247.5-492Q250-495 250-499L250-499Q259-515 273.5-546.5Q288-578 302.5-613Q317-648 327-675Q337-702 337-708L337-708Q339-716 343-720Q347-724 355-722L355-722Q359-725 363-725.5Q367-726 371-726L371-726Q371-726 374.5-722Q378-718 385-710L385-710Q393-703 394-694Q395-685 395-673L395-673Q394-654 381-630Q368-606 352-577L352-577Q342-558 332.5-541Q323-524 323-520L323-520Q323-516 319-508Q315-500 310-488L310-488Q304-478 297-464Q290-450 284.5-438.5Q279-427 279-424L279-424Q278-420 276.5-416Q275-412 271-408L271-408Q267-408 266.5-402.5Q266-397 266-397L266-397Q266-393 261.5-378.5Q257-364 247-344L247-344Q228-291 220-259.5Q212-228 210-206L210-206Q208-182 206.5-178Q205-174 197-170ZM143-10L143-10Q124-3 111.5-3.5Q99-4 84-15L84-15Q65-33 71.5-51.5Q78-70 104-88L104-88Q122-99 130.5-100Q139-101 157-93L157-93Q187-74 183.5-51Q180-28 143-10Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 357.1 -685.7 C 357.7 -683.3 373.2 -702.3 360.7 -671.4 C 348.2 -640.4 297.6 -531.5 282.1 -500 C 266.6 -468.4 273.8 -493.4 267.9 -482.1 C 262 -470.8 250.6 -445.2 246.4 -432.1 C 242.2 -419 249.5 -420.3 242.9 -403.6 C 236.4 -386.9 217.2 -357.7 207.1 -332.1 C 197 -306.5 185.7 -271.4 182.1 -250 C 178.5 -228.6 185.1 -211.3 185.7 -203.6",
|
||||
"M 132.1 -57.1 L 117.9 -46.4",
|
||||
],
|
||||
},
|
||||
"?": {
|
||||
adv: 374,
|
||||
outline:
|
||||
"M250-191L250-191Q234-193 227-204.5Q220-216 220-228L220-228Q220-230 220.5-236Q221-242 223-250L223-250Q223-256 229-269Q235-282 242-300L242-300Q253-320 276-345Q299-370 315-382L315-382Q324-389 328-391.5Q332-394 332-394L332-394Q332-398 346-409.5Q360-421 370-425L370-425Q370-425 383-434Q396-443 403-450L403-450Q426-473 437-492.5Q448-512 454-537Q460-562 467-599L467-599Q468-634 467.5-646.5Q467-659 455-668L455-668Q434-679 403.5-670.5Q373-662 344.5-638.5Q316-615 301-581L301-581Q295-564 294.5-558Q294-552 297-542L297-542Q297-534 299.5-527Q302-520 302-515L302-515Q298-514 287.5-514Q277-514 266-515Q255-516 248-519L248-519Q248-519 244-525Q240-531 238-535L238-535Q232-543 232.5-550.5Q233-558 233-573L233-573Q240-593 252-616.5Q264-640 283-661Q302-682 327-695L327-695Q348-704 362.5-711.5Q377-719 377-719L377-719Q377-719 384.5-719.5Q392-720 410-722L410-722Q416-721 423.5-722Q431-723 431-718L431-718Q434-718 443-718Q452-718 460-718L460-718Q472-715 485-708.5Q498-702 509.5-694.5Q521-687 528-679L528-679Q535-657 534-624.5Q533-592 524-556Q515-520 500.5-487.5Q486-455 466-433L466-433Q460-427 444-413Q428-399 410.5-382.5Q393-366 382-354L382-354Q348-335 322-297Q296-259 283-223L283-223Q272-199 268-195Q264-191 250-191ZM222-10L222-10Q203-3 190.5-3.5Q178-4 163-15L163-15Q144-33 150.5-51.5Q157-70 183-88L183-88Q201-99 209.5-100Q218-101 236-93L236-93Q266-74 262.5-51Q259-28 222-10Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 296.4 -517.9 C 291 -523.8 269.7 -544.1 264.3 -553.6 C 259 -563.1 260.7 -563.1 264.3 -575 C 267.9 -586.9 275 -609.5 285.7 -625 C 296.4 -640.5 310.7 -655.4 328.6 -667.9 C 346.5 -680.4 373.8 -694.6 392.9 -700 C 412 -705.3 427.4 -703.6 442.9 -700 C 458.4 -696.4 476.8 -686.9 485.7 -678.6 C 494.6 -670.3 495.2 -668.4 496.4 -650 C 497.6 -631.5 499.5 -597.7 492.9 -567.9 C 486.4 -538.1 471.4 -496.4 457.1 -471.4 C 442.8 -446.4 419.6 -429.2 407.1 -417.9 C 394.6 -406.6 400.6 -419.1 382.1 -403.6 C 363.7 -388.1 319 -353 296.4 -325 C 273.8 -297 254.7 -250.6 246.4 -235.7",
|
||||
"M 210.7 -57.1 L 196.4 -46.4",
|
||||
],
|
||||
},
|
||||
".": {
|
||||
adv: 198,
|
||||
outline:
|
||||
"M153-52L153-52Q134-45 121.5-45.5Q109-46 94-57L94-57Q75-75 81.5-93.5Q88-112 114-130L114-130Q132-141 140.5-142Q149-143 167-135L167-135Q197-116 193.5-93Q190-70 153-52Z",
|
||||
stem: 86,
|
||||
strokes: ["M 142.9 -100 L 128.6 -89.3"],
|
||||
},
|
||||
",": {
|
||||
adv: 198,
|
||||
outline:
|
||||
"M52 79L52 79Q42 79 39.5 77.5Q37 76 33 69L33 69Q33 62 36.5 57Q40 52 47 45L47 45Q57 35 70 20.5Q83 6 94.5-9Q106-24 111-33L111-33Q115-44 118.5-59Q122-74 126-92L126-92Q126-100 126.5-106.5Q127-113 131-117L131-117Q138-124 143.5-123Q149-122 153.5-111Q158-100 160-78L160-78Q164-56 160-38Q156-20 145 6L145 6Q143 13 132 27Q121 41 110 53Q99 65 96 65L96 65Q92 65 89 67.5Q86 70 82 74L82 74Q78 78 70.5 78.5Q63 79 52 79Z",
|
||||
stem: 48,
|
||||
strokes: [
|
||||
"M 139.3 -103.6 C 139.3 -95.3 141.7 -69.1 139.3 -53.6 C 136.9 -38.1 132.7 -25 125 -10.7 C 117.3 3.6 102.4 22 92.9 32.1 C 83.4 42.2 72.1 47 67.9 50",
|
||||
],
|
||||
},
|
||||
"'": {
|
||||
adv: 117,
|
||||
outline:
|
||||
"M189-440L189-440Q179-439 176-440Q173-441 166-447L166-447Q165-454 168-460Q171-466 176-473L176-473Q188-493 203.5-520.5Q219-548 224-563L224-563Q226-574 230.5-591.5Q235-609 235-628L235-628Q235-633 235.5-640Q236-647 239-652.5Q242-658 247-655L247-655Q256-651 266.5-645.5Q277-640 283.5-633Q290-626 288-615L288-615Q286-591 278-570.5Q270-550 264-530L264-530Q258-508 244-489Q230-470 213-450L213-450Q214-446 207-445Q200-444 189-440Z",
|
||||
stem: 50,
|
||||
strokes: [
|
||||
"M 253.6 -617.9 C 254.2 -615.5 259.5 -617.3 257.1 -603.6 C 254.7 -589.9 250.6 -558.9 239.3 -535.7 C 228 -512.5 197.6 -476.2 189.3 -464.3",
|
||||
],
|
||||
},
|
||||
"-": {
|
||||
adv: 326,
|
||||
outline:
|
||||
"M178-178L178-178Q172-178 163-178.5Q154-179 143-183L143-183Q128-183 123-186.5Q118-190 114-197L114-197Q103-212 112-217.5Q121-223 158-227L158-227Q192-235 221-238Q250-241 290-241L290-241Q316-241 325-239Q334-237 345-226L345-226Q360-211 356-197.5Q352-184 329-188L329-188Q322-187 303-186Q284-185 266-183L266-183Q255-182 231-180Q207-178 178-178Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 135.7 -207.1 C 142.9 -206.5 160.1 -202.4 178.6 -203.6 C 197.1 -204.8 223.8 -212.5 246.4 -214.3 C 269 -216.1 300.6 -215.5 314.3 -214.3 C 328 -213.1 326.2 -208.3 328.6 -207.1",
|
||||
],
|
||||
},
|
||||
"&": {
|
||||
adv: 564,
|
||||
outline:
|
||||
"M657 54L657 54Q658 66 646.5 67.5Q635 69 616.5 63.5Q598 58 578 48.5Q558 39 543 30L543 30Q524 25 490-7Q456-39 417-85L417-85Q377-63 352-49.5Q327-36 306.5-27.5Q286-19 258-11L258-11Q192-5 137-24L137-24Q129-27 116.5-36Q104-45 92-55Q80-65 75-70L75-70Q70-73 70-83.5Q70-94 72-105Q74-116 75-119L75-119Q78-128 92.5-150.5Q107-173 129.5-203Q152-233 178.5-264Q205-295 231-321L231-321Q207-356 198-387.5Q189-419 198.5-456Q208-493 236-543L236-543Q246-559 261-576.5Q276-594 291.5-609.5Q307-625 318.5-633.5Q330-642 331-640L331-640Q335-633 339-635Q343-637 347-637L347-637Q351-639 362-638.5Q373-638 387.5-636Q402-634 415-629L415-629Q451-625 465.5-621Q480-617 492-603L492-603Q507-592 515-581Q523-570 526.5-559.5Q530-549 530-540L530-540Q530-527 509.5-500Q489-473 455-443L455-443Q442-427 428.5-414.5Q415-402 415-402L415-402Q415-398 410-391Q405-384 397-381L397-381Q386-371 366-353Q346-335 330-320Q314-305 313-302L313-302Q312-300 322-284.5Q332-269 348-248Q364-227 380.5-206.5Q397-186 410-172.5Q423-159 427-159L427-159Q460-191 495.5-221.5Q531-252 568-286L568-286Q593-312 603.5-322.5Q614-333 619.5-332Q625-331 632-324L632-324Q645-322 646-311.5Q647-301 643-291.5Q639-282 638-281L638-281Q625-264 602-242Q579-220 553-196.5Q527-173 504-152Q481-131 466-116L466-116Q466-116 478.5-101.5Q491-87 503-76L503-76Q536-42 554-28.5Q572-15 595-7L595-7Q626 3 641 17.5Q656 32 657 54ZM166-67L166-67Q196-58 225-60.5Q254-63 286.5-77Q319-91 357-117L357-117L376-131L351-160Q344-168 330-187Q316-206 302-223L302-223Q287-237 279-250Q271-263 271-263L271-263Q268-263 257.5-253Q247-243 235.5-229.5Q224-216 218-206L218-206Q195-188 183-170.5Q171-153 156-130L156-130Q149-114 148-102Q147-90 151.5-82Q156-74 166-67ZM280-357L280-357Q291-369 313.5-387.5Q336-406 362.5-428.5Q389-451 412-474.5Q435-498 448.5-519.5Q462-541 457-558L457-558Q455-562 440.5-569Q426-576 408.5-582Q391-588 379-587L379-587Q359-589 333.5-566Q308-543 285-510L285-510Q260-473 255-432.5Q250-392 280-357Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 267.9 -303.6 C 263.7 -301.2 257.2 -302.4 242.9 -289.3 C 228.6 -276.2 201.2 -247.6 182.1 -225 C 163 -202.4 139.9 -170.9 128.6 -153.6 C 117.3 -136.3 117.3 -130.9 114.3 -121.4 C 111.3 -111.9 109.5 -104.7 110.7 -96.4 C 111.9 -88.1 117.2 -78.5 121.4 -71.4 C 125.6 -64.3 128.5 -58.3 135.7 -53.6 C 142.9 -48.8 145.3 -45.3 164.3 -42.9 C 183.4 -40.5 228 -37.5 250 -39.3 C 272 -41.1 273.8 -42.3 296.4 -53.6 C 319 -64.9 369 -98.2 385.7 -107.1 C 402.4 -116 392.2 -104.1 396.4 -107.1 C 400.6 -110.1 405.3 -122 410.7 -125 C 416.1 -128 404.8 -106.5 428.6 -125 C 452.4 -143.4 532.8 -217.2 553.6 -235.7",
|
||||
"M 267.9 -317.9 C 270.3 -320.3 276.2 -328.5 282.1 -332.1 C 288.1 -335.7 286.9 -326.8 303.6 -339.3 C 320.3 -351.8 355.9 -382.1 382.1 -407.1 C 408.3 -432.1 444.6 -471.4 460.7 -489.3 C 476.8 -507.2 474.4 -505.4 478.6 -514.3 C 482.8 -523.2 486.3 -531.6 485.7 -542.9 C 485.1 -554.2 483.9 -571.4 475 -582.1 C 466.1 -592.8 449.4 -601.7 432.1 -607.1 C 414.8 -612.5 387.5 -613.7 371.4 -614.3 C 355.3 -614.9 345.8 -614.3 335.7 -610.7 C 325.6 -607.1 323.2 -605.4 310.7 -592.9 C 298.2 -580.4 275 -558.9 260.7 -535.7 C 246.4 -512.5 231 -476.8 225 -453.6 C 219.1 -430.4 220.8 -416 225 -396.4 C 229.2 -376.7 242.9 -348.8 250 -335.7 C 257.2 -322.6 264.9 -323.2 267.9 -317.9 C 270.9 -312.5 263.7 -309.6 267.9 -303.6 C 272.1 -297.6 287.5 -288.7 292.9 -282.1 C 298.3 -275.5 280.4 -290.5 300 -264.3 C 319.6 -238.1 392.3 -148.2 410.7 -125",
|
||||
"M 428.6 -125 C 431.6 -119.6 429.7 -111.9 446.4 -92.9 C 463.1 -73.9 501.2 -30.9 528.6 -10.7 C 556 9.6 593.5 19.1 610.7 28.6 C 628 38.1 628.5 43.4 632.1 46.4",
|
||||
],
|
||||
},
|
||||
":": {
|
||||
adv: 198,
|
||||
outline:
|
||||
"M229-262L229-262Q210-255 197.5-255.5Q185-256 170-267L170-267Q151-285 157.5-303.5Q164-322 190-340L190-340Q208-351 216.5-352Q225-353 243-345L243-345Q273-326 269.5-303Q266-280 229-262ZM159-52L159-52Q140-45 127.5-45.5Q115-46 100-57L100-57Q81-75 87.5-93.5Q94-112 120-130L120-130Q138-141 146.5-142Q155-143 173-135L173-135Q203-116 199.5-93Q196-70 159-52Z",
|
||||
stem: 86,
|
||||
strokes: ["M 132.1 -89.3 L 146.4 -96.4", "M 217.9 -310.7 L 203.6 -300"],
|
||||
},
|
||||
";": {
|
||||
adv: 198,
|
||||
outline:
|
||||
"M59 79L59 79Q49 79 46.5 77.5Q44 76 40 69L40 69Q40 62 43.5 57Q47 52 54 45L54 45Q64 35 77 20.5Q90 6 101.5-9Q113-24 118-33L118-33Q122-44 125.5-59Q129-74 133-92L133-92Q133-100 133.5-106.5Q134-113 138-117L138-117Q145-124 150.5-123Q156-122 160.5-111Q165-100 167-78L167-78Q171-56 167-38Q163-20 152 6L152 6Q150 13 139 27Q128 41 117 53Q106 65 103 65L103 65Q99 65 96 67.5Q93 70 89 74L89 74Q85 78 77.5 78.5Q70 79 59 79ZM229-262L229-262Q210-255 197.5-255.5Q185-256 170-267L170-267Q151-285 157.5-303.5Q164-322 190-340L190-340Q208-351 216.5-352Q225-353 243-345L243-345Q273-326 269.5-303Q266-280 229-262Z",
|
||||
stem: 48,
|
||||
strokes: [
|
||||
"M 146.4 -107.1 C 146.4 -98.8 148.8 -73.2 146.4 -57.1 C 144 -41 139.8 -25.6 132.1 -10.7 C 124.4 4.2 108.9 22.6 100 32.1 C 91.1 41.6 82.2 44 78.6 46.4",
|
||||
"M 217.9 -310.7 L 203.6 -300",
|
||||
],
|
||||
},
|
||||
"(": {
|
||||
adv: 330,
|
||||
outline:
|
||||
"M287 128L287 128Q246 120 213 90Q180 60 166 17L166 17Q147-25 148-92.5Q149-160 168-230L168-230Q173-249 177-269Q181-289 182-296L182-296Q185-306 195-329Q205-352 218-381.5Q231-411 243.5-440.5Q256-470 265-493L265-493Q276-518 297.5-552Q319-586 342.5-620Q366-654 385.5-679Q405-704 413-710L413-710Q418-715 427-723Q436-731 443-732L443-732Q447-743 459-750Q471-757 480-757L480-757Q503-769 516.5-764Q530-759 538-729L538-729Q541-715 538.5-711Q536-707 516-698L516-698Q506-694 491-686.5Q476-679 469-671L469-671Q460-660 449-649Q438-638 432-634L432-634Q414-627 414-618L414-618Q414-618 405.5-601Q397-584 378-560L378-560Q366-541 350-513Q334-485 325-471L325-471Q316-455 310.5-444.5Q305-434 295-423L295-423Q295-420 292-409Q289-398 280-384L280-384Q274-362 256-325L256-325Q249-309 239.5-283Q230-257 223.5-233Q217-209 217-199L217-199Q215-199 213.5-193.5Q212-188 209.5-169.5Q207-151 201-110L201-110Q200-65 203.5-36Q207-7 218 13.5Q229 34 247 54L247 54Q258 64 269 69Q280 74 295 75L295 75L329 96Q323 119 313.5 124.5Q304 130 287 128Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 496.4 -728.6 C 489.3 -725 471.5 -721.4 453.6 -707.1 C 435.8 -692.8 409 -667.3 389.3 -642.9 C 369.7 -618.5 355.9 -594.6 335.7 -560.7 C 315.5 -526.8 288.7 -482.7 267.9 -439.3 C 247.1 -395.8 224.4 -338.1 210.7 -300 C 197 -261.9 192.3 -240.5 185.7 -210.7 C 179.1 -180.9 173.8 -146.4 171.4 -121.4 C 169 -96.4 169 -82.1 171.4 -60.7 C 173.8 -39.3 179.1 -12 185.7 7.1 C 192.3 26.2 202.4 41.7 210.7 53.6 C 219 65.5 222.6 70.3 235.7 78.6 C 248.8 86.9 278 100 289.3 103.6 C 300.6 107.2 301.2 100.6 303.6 100",
|
||||
],
|
||||
},
|
||||
")": {
|
||||
adv: 330,
|
||||
outline:
|
||||
"M72 120L72 120Q53 115 42.5 106Q32 97 31 82L31 82Q32 79 32 73.5Q32 68 36 61L36 61Q36 61 42.5 64Q49 67 65 63L65 63Q85 64 93 59Q101 54 116 37L116 37Q127 22 141.5 2Q156-18 161-31L161-31Q177-42 184-51.5Q191-61 197-71L197-71Q197-76 200-89.5Q203-103 215-122L215-122Q226-147 236-164.5Q246-182 246-187L246-187Q246-187 249-194Q252-201 251-211L251-211Q257-214 261.5-224.5Q266-235 265-246L265-246Q265-249 274-269.5Q283-290 284-311L284-311Q295-347 300.5-375.5Q306-404 309-438Q312-472 315-525L315-525Q317-570 318-592.5Q319-615 317.5-627Q316-639 308-651L308-651Q298-683 291-683L291-683Q288-683 284.5-691.5Q281-700 285-714L285-714Q284-718 288-723Q292-728 301-727L301-727Q308-727 316.5-721.5Q325-716 328-712L328-712Q332-716 340.5-702.5Q349-689 359-660L359-660Q365-646 368-636.5Q371-627 372-615.5Q373-604 372-583.5Q371-563 369-527L369-527Q369-492 364.5-450.5Q360-409 354-369.5Q348-330 341-298L341-298Q334-272 322-235.5Q310-199 295.5-162Q281-125 268.5-96Q256-67 248-55L248-55Q250-55 242.5-43Q235-31 222.5-12.5Q210 6 197 25.5Q184 45 175 59L175 59Q160 80 141 95Q122 110 104 117Q86 124 72 120Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 300 -703.6 C 302.4 -702.4 309.5 -701.8 314.3 -696.4 C 319.1 -691 323.8 -686.3 328.6 -671.4 C 333.4 -656.5 342.9 -655.3 342.9 -607.1 C 342.9 -558.9 335.2 -437.4 328.6 -382.1 C 322.1 -326.7 314.9 -313.1 303.6 -275 C 292.3 -236.9 278 -194.7 260.7 -153.6 C 243.4 -112.5 213.1 -51.8 200 -28.6 C 186.9 -5.4 188.7 -23.2 182.1 -14.3 C 175.5 -5.4 170.8 10.7 160.7 25 C 150.6 39.3 132.1 61.3 121.4 71.4 C 110.7 81.5 105.9 83.9 96.4 85.7 C 86.9 87.5 69.7 82.7 64.3 82.1",
|
||||
],
|
||||
},
|
||||
'"': {
|
||||
adv: 237,
|
||||
outline:
|
||||
"M199-440L199-440Q189-439 186-440Q183-441 176-447L176-447Q175-454 178-460Q181-466 186-473L186-473Q198-493 213.5-520.5Q229-548 234-563L234-563Q236-574 240.5-591.5Q245-609 245-628L245-628Q245-633 245.5-640Q246-647 249-652.5Q252-658 257-655L257-655Q266-651 276.5-645.5Q287-640 293.5-633Q300-626 298-615L298-615Q296-591 288-570.5Q280-550 274-530L274-530Q268-508 254-489Q240-470 223-450L223-450Q224-446 217-445Q210-444 199-440ZM319-440L319-440Q309-439 306-440Q303-441 296-447L296-447Q295-454 298-460Q301-466 306-473L306-473Q318-493 333.5-520.5Q349-548 354-563L354-563Q356-574 360.5-591.5Q365-609 365-628L365-628Q365-633 365.5-640Q366-647 369-652.5Q372-658 377-655L377-655Q386-651 396.5-645.5Q407-640 413.5-633Q420-626 418-615L418-615Q416-591 408-570.5Q400-550 394-530L394-530Q388-508 374-489Q360-470 343-450L343-450Q344-446 337-445Q330-444 319-440Z",
|
||||
stem: 50,
|
||||
strokes: [
|
||||
"M 264.3 -617.9 C 264.9 -615.5 270.9 -618.5 267.9 -603.6 C 264.9 -588.7 256.5 -551.2 246.4 -528.6 C 236.3 -506 213.7 -478 207.1 -467.9",
|
||||
"M 382.1 -617.9 C 382.7 -614.3 388.1 -610.7 385.7 -596.4 C 383.3 -582.1 379.8 -555.3 367.9 -532.1 C 356 -508.9 323.2 -469.6 314.3 -457.1",
|
||||
],
|
||||
},
|
||||
"+": {
|
||||
adv: 446,
|
||||
outline:
|
||||
"M178-178L178-178Q172-178 163-178.5Q154-179 143-183L143-183Q128-183 123-186.5Q118-190 114-197L114-197Q103-212 112-217.5Q121-223 158-227L158-227Q175-231 208.5-233.5Q242-236 281-237.5Q320-239 355-240Q390-241 410-241L410-241Q436-241 445-239Q454-237 465-226L465-226Q480-211 476-197.5Q472-184 449-188L449-188Q444-188 433.5-187Q423-186 410.5-185Q398-184 386-183L386-183Q380-183 353-182Q326-181 290.5-180Q255-179 223.5-178.5Q192-178 178-178ZM304-31L304-31Q289-20 283.5-29Q278-38 274-75L274-75Q270-92 267.5-125.5Q265-159 263.5-198Q262-237 261-272Q260-307 260-327L260-327Q260-353 262.5-362Q265-371 275-382L275-382Q290-397 303.5-393Q317-389 313-366L313-366Q314-361 314.5-350.5Q315-340 316-327.5Q317-315 318-303L318-303Q318-297 319-270Q320-243 321-207.5Q322-172 322.5-140.5Q323-109 323-95L323-95Q323-89 322.5-80Q322-71 318-60L318-60Q318-45 314.5-40Q311-35 304-31Z",
|
||||
stem: 57,
|
||||
strokes: [
|
||||
"M 135.7 -207.1 C 142.9 -206.5 132.2 -201.8 178.6 -203.6 C 225 -205.4 370.9 -216.7 414.3 -217.9 C 457.8 -219.1 435.1 -211.9 439.3 -210.7",
|
||||
"M 292.9 -360.7 C 291.7 -358.3 285.7 -396.4 285.7 -346.4 C 285.7 -296.4 291.7 -108.3 292.9 -60.7",
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
Binary file not shown.
+500
@@ -0,0 +1,500 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script src="assets/baked/caveat-pen.js"></script>
|
||||
<style>
|
||||
/* Caveat is NOT auto-resolved by the renderer — bundle it. Asset paths
|
||||
are ROOT-relative. The glyph write-on itself renders BAKED outlines
|
||||
(no live font dependency); the bundled face serves the fallback path
|
||||
for characters outside the baked table. */
|
||||
@font-face {
|
||||
font-family: "Caveat";
|
||||
src: url("assets/fonts/Caveat-700-latin.woff2") format("woff2");
|
||||
font-weight: 700;
|
||||
font-display: block;
|
||||
}
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
background: transparent;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* ---- hw-write-title -------------------------------------------------
|
||||
TRUE glyph write-on: filled Caveat letterforms revealed by animated
|
||||
per-glyph centerline strokes (the AE mask-reveal classic — "a mask
|
||||
barely covering the line"), traced in real stroke order with a
|
||||
curvature-adaptive pen-velocity ease (fast on straights, slow into
|
||||
curves; inter-stroke pen lifts). Centerlines + outlines + stem widths
|
||||
ship baked (assets/baked/caveat-pen.js, generated deterministically
|
||||
from Caveat-700 — regeneration script in the PR).
|
||||
RULES: hwBoil owns x/y/rotation of the boil target; entrances/exits
|
||||
ride the outer wrapper. One timeline has ONE onUpdate (hwOnUpdate). */
|
||||
#hw-wt-root {
|
||||
--hw-font-print: "Caveat", cursive;
|
||||
--hw-ink: #f4f2ec;
|
||||
--hw-ink-dark: #26241f;
|
||||
--hw-accent-warm: #ffb020;
|
||||
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
background: transparent; /* overlays footage or a board */
|
||||
font-family: var(--hw-font-print, "Caveat", cursive);
|
||||
}
|
||||
#hw-wt-wrap {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
#hw-wt-svg {
|
||||
overflow: visible;
|
||||
display: block;
|
||||
}
|
||||
#hw-wt-under {
|
||||
overflow: visible;
|
||||
display: block;
|
||||
margin-top: -8px;
|
||||
}
|
||||
#hw-wt-under path {
|
||||
fill: none;
|
||||
stroke: var(--hw-accent-warm);
|
||||
stroke-width: 7;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
.hw-wt-fallback {
|
||||
font-weight: 700;
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="hw-wt-root"
|
||||
data-composition-id="hw-write-title"
|
||||
data-start="0"
|
||||
data-duration="6"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div id="hw-wt-move">
|
||||
<div id="hw-wt-boil">
|
||||
<div id="hw-wt-wrap">
|
||||
<svg id="hw-wt-svg"></svg>
|
||||
<svg id="hw-wt-under" width="10" height="40"><path id="hw-wt-under-path"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="hw-wt-drv"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="6"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
var CONFIG = {
|
||||
// ── Content (freely editable — not counted) ─────────────────────
|
||||
text: "write it on",
|
||||
fontSize: 170, // px; the word block auto-centers when x/y are null
|
||||
x: null,
|
||||
y: null,
|
||||
|
||||
// ── Controls (family base + 2 item-specific; comment grammar is the published-parameter list) ──
|
||||
scheme: "chalk", // variant: "chalk" | "ink" — selects the authored token set (family default "chalk")
|
||||
animationIn: true, // toggle: entrance on/off — write-on IS the entrance; false = hard-on at t=0 (pairs with animationOut; the pair counts as one control)
|
||||
animationOut: true, // (exit fade; duration derived from DUR, never exposed)
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3; seed locked
|
||||
writeOn: "trace", // variant: "trace" | "reveal" | "off" — trace = pen-traced glyph write-on (curvature-adaptive velocity, pen lifts); reveal = per-glyph staggered fade; off = hard-on; durations derived from stroke count, never exposed
|
||||
underline: true, // toggle: seeded squiggle underline drawn after the write completes
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// seed, pen-velocity constants (kCurve 10, floor 0.22, lift 60ms),
|
||||
// trace-duration derivation, boil pose tables, all eases/durations
|
||||
};
|
||||
var SEED = 8; // locked — determinism
|
||||
var DUR = 6;
|
||||
|
||||
function clamp(v, lo, hi) {
|
||||
return Math.min(hi, Math.max(lo, v));
|
||||
}
|
||||
var OUT = clamp(DUR * 0.06, 0.25, 0.6);
|
||||
|
||||
/* ---- hw family runtime (copy identical across hw items) ---- */
|
||||
window.hwOnUpdate = function (tl, fn) {
|
||||
if (!tl.__hwRenders) {
|
||||
tl.__hwRenders = [];
|
||||
tl.eventCallback("onUpdate", function () {
|
||||
for (var i = 0; i < tl.__hwRenders.length; i++) tl.__hwRenders[i]();
|
||||
});
|
||||
}
|
||||
tl.__hwRenders.push(fn);
|
||||
fn();
|
||||
};
|
||||
window.hwHash = function (n, seed) {
|
||||
var x = Math.sin(n * 127.1 + (seed || 1) * 311.7) * 43758.5453;
|
||||
return (x - Math.floor(x)) * 2 - 1;
|
||||
};
|
||||
window.hwBoil = function (tl, target, opts) {
|
||||
opts = opts || {};
|
||||
var amp = opts.amp !== undefined ? opts.amp : 1.6;
|
||||
var rot = opts.rot !== undefined ? opts.rot : 0.5;
|
||||
var fps = opts.fps || 30;
|
||||
var drop = opts.frameDrop || 3;
|
||||
var seed = opts.seed || 1;
|
||||
var els = gsap.utils.toArray(target);
|
||||
window.hwOnUpdate(tl, function () {
|
||||
var step = Math.floor((tl.time() * fps) / drop);
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
gsap.set(els[i], {
|
||||
x: window.hwHash(step * 3 + i * 97, seed) * amp,
|
||||
y: window.hwHash(step * 3 + 1 + i * 97, seed) * amp,
|
||||
rotation: (opts.baseRot || 0) + window.hwHash(step * 3 + 2 + i * 97, seed) * rot,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
/* ---- pen-velocity ease: pure function of the path geometry ----
|
||||
Sample 25–75 curvature-adaptive points, speed ∝ 1/(1+k·curvature)
|
||||
floored at 0.22 (a pen slows into curves, never stalls), integrate
|
||||
ds/speed, invert to a normalized time→distance ease. Seek-safe:
|
||||
no per-frame state. */
|
||||
window.hwPenEase = function (pathEl, opts) {
|
||||
opts = opts || {};
|
||||
var kCurve = opts.kCurve !== undefined ? opts.kCurve : 10;
|
||||
var L = pathEl.getTotalLength();
|
||||
if (L <= 0)
|
||||
return {
|
||||
ease: function (t) {
|
||||
return t;
|
||||
},
|
||||
samples: 2,
|
||||
inkTime: 1,
|
||||
len: 0,
|
||||
};
|
||||
var PRE = 48;
|
||||
var pts = [];
|
||||
for (var i = 0; i <= PRE; i++) pts.push(pathEl.getPointAtLength((L * i) / PRE));
|
||||
var curv = [0];
|
||||
for (i = 1; i < PRE; i++) {
|
||||
var ax = pts[i].x - pts[i - 1].x,
|
||||
ay = pts[i].y - pts[i - 1].y;
|
||||
var bx = pts[i + 1].x - pts[i].x,
|
||||
by = pts[i + 1].y - pts[i].y;
|
||||
var la = Math.hypot(ax, ay) || 1e-6,
|
||||
lb = Math.hypot(bx, by) || 1e-6;
|
||||
var dot = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
|
||||
curv.push(Math.acos(dot) / ((la + lb) / 2));
|
||||
}
|
||||
curv.push(0);
|
||||
var mass = 0;
|
||||
for (i = 0; i < curv.length; i++) mass += curv[i];
|
||||
var N = Math.max(25, Math.min(75, Math.round(25 + mass * 18)));
|
||||
var seg = L / N;
|
||||
var times = [0];
|
||||
var t = 0;
|
||||
for (i = 1; i <= N; i++) {
|
||||
var u = ((i - 0.5) / N) * PRE;
|
||||
var i0 = Math.floor(u),
|
||||
f = u - i0;
|
||||
var c = curv[Math.min(i0, PRE)] * (1 - f) + curv[Math.min(i0 + 1, PRE)] * f;
|
||||
var speed = Math.max(1 / (1 + kCurve * c * 14), 0.22);
|
||||
t += seg / speed;
|
||||
times.push(t);
|
||||
}
|
||||
var total = times[N];
|
||||
for (i = 0; i <= N; i++) times[i] /= total;
|
||||
var easeFn = function (p) {
|
||||
if (p <= 0) return 0;
|
||||
if (p >= 1) return 1;
|
||||
var lo = 0,
|
||||
hi = N;
|
||||
while (hi - lo > 1) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
if (times[mid] <= p) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
var span = times[hi] - times[lo] || 1e-9;
|
||||
return (lo + (p - times[lo]) / span) / N;
|
||||
};
|
||||
return { ease: easeFn, samples: N, inkTime: total, len: L };
|
||||
};
|
||||
|
||||
/* ---- word assembly from the baked table ----
|
||||
Each glyph: <mask> of centerline strokes (stroke-width = the glyph's
|
||||
baked stem × 1.35) over the filled outline. Characters missing from
|
||||
the table fall back to live Caveat text revealed by fade (+ console
|
||||
warning) — the bundled face exists for exactly this path. */
|
||||
function buildWord(svg, text) {
|
||||
// everything in GLYPH UNITS (upem 1000, y-down, baseline 0) — the
|
||||
// svg viewBox maps units → px, no scale transform needed
|
||||
var table = window.__hwPenCaveat;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
// scheme control: bounded lookup — unknown → default + console error
|
||||
var schemes = { chalk: "var(--hw-ink)", ink: "var(--hw-ink-dark)" };
|
||||
if (!(CONFIG.scheme in schemes)) {
|
||||
console.error('hw: unknown scheme "' + CONFIG.scheme + '" — falling back to "chalk"');
|
||||
}
|
||||
var ink = schemes[CONFIG.scheme] || schemes.chalk;
|
||||
var g = document.createElementNS(ns, "g");
|
||||
var x = 0;
|
||||
var strokes = [];
|
||||
var fallbacks = [];
|
||||
var gi = 0;
|
||||
for (var ci = 0; ci < text.length; ci++) {
|
||||
var ch = text[ci];
|
||||
var gl = table.glyphs[ch];
|
||||
if (ch === " ") {
|
||||
x += 340;
|
||||
continue;
|
||||
}
|
||||
if (!gl) {
|
||||
console.warn(
|
||||
'hw-write-title: "' + ch + '" not in the baked table — Caveat fallback fade',
|
||||
);
|
||||
var ft = document.createElementNS(ns, "text");
|
||||
ft.setAttribute("x", x);
|
||||
ft.setAttribute("y", 0);
|
||||
ft.setAttribute("class", "hw-wt-fallback");
|
||||
ft.setAttribute("font-size", "1000");
|
||||
ft.setAttribute("fill", ink);
|
||||
ft.textContent = ch;
|
||||
g.appendChild(ft);
|
||||
fallbacks.push(ft);
|
||||
x += 450;
|
||||
continue;
|
||||
}
|
||||
var gg = document.createElementNS(ns, "g");
|
||||
gg.setAttribute("transform", "translate(" + x + ",0)");
|
||||
var maskId = "hw-wt-m" + gi;
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", maskId);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
mask.setAttribute("x", "-200");
|
||||
mask.setAttribute("y", "-900");
|
||||
mask.setAttribute("width", gl.adv + 400);
|
||||
mask.setAttribute("height", "1400");
|
||||
for (var si = 0; si < gl.strokes.length; si++) {
|
||||
var mp = document.createElementNS(ns, "path");
|
||||
mp.setAttribute("d", gl.strokes[si]);
|
||||
mp.setAttribute("fill", "none");
|
||||
mp.setAttribute("stroke", "#fff");
|
||||
mp.setAttribute("stroke-width", Math.max(30, Math.round(gl.stem * 1.35)));
|
||||
mp.setAttribute("stroke-linecap", "round");
|
||||
mp.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(mp);
|
||||
strokes.push({ path: mp, glyph: gi });
|
||||
}
|
||||
gg.appendChild(mask);
|
||||
var op = document.createElementNS(ns, "path");
|
||||
op.setAttribute("d", gl.outline);
|
||||
op.setAttribute("fill", ink);
|
||||
op.setAttribute("mask", "url(#" + maskId + ")");
|
||||
gg.appendChild(op);
|
||||
g.appendChild(gg);
|
||||
x += gl.adv;
|
||||
gi++;
|
||||
}
|
||||
svg.appendChild(g);
|
||||
return { el: g, widthUnits: x, strokes: strokes, fallbacks: fallbacks };
|
||||
}
|
||||
|
||||
/* ---- trace choreography: per-stroke reveals, pen-velocity eases,
|
||||
60ms pen lifts; duration DERIVED from stroke count (writeOn owns
|
||||
it — never exposed). One dispatcher-registered render applies the
|
||||
dashoffsets. */
|
||||
function writeOnTrace(tl, built, at) {
|
||||
var strokes = built.strokes;
|
||||
if (!strokes.length) return at;
|
||||
var lift = 0.06;
|
||||
var metas = [];
|
||||
var inkSum = 0;
|
||||
for (var i = 0; i < strokes.length; i++) {
|
||||
var pe = window.hwPenEase(strokes[i].path);
|
||||
var len = strokes[i].path.getTotalLength();
|
||||
metas.push({ pe: pe, len: len, st: { p: 0 } });
|
||||
inkSum += pe.inkTime;
|
||||
strokes[i].path.setAttribute("stroke-dasharray", len + " " + len);
|
||||
strokes[i].path.setAttribute("stroke-dashoffset", len);
|
||||
strokes[i].path.style.opacity = "0"; // no round-cap nub pre-draw
|
||||
}
|
||||
var dur = clamp(0.55 + 0.22 * strokes.length, 1.2, 4);
|
||||
var drawTotal = Math.max(0.1, dur - lift * (strokes.length - 1));
|
||||
var t = at;
|
||||
for (i = 0; i < metas.length; i++) {
|
||||
var d = (metas[i].pe.inkTime / inkSum) * drawTotal;
|
||||
metas[i].d = d;
|
||||
tl.to(metas[i].st, { p: 1, duration: d, ease: metas[i].pe.ease }, t);
|
||||
t += d + lift;
|
||||
}
|
||||
window.hwOnUpdate(tl, function () {
|
||||
for (var i = 0; i < metas.length; i++) {
|
||||
var m = metas[i];
|
||||
strokes[i].path.style.strokeDashoffset = m.len * (1 - m.st.p);
|
||||
strokes[i].path.style.opacity = m.st.p > 0 ? "1" : "0";
|
||||
}
|
||||
});
|
||||
for (i = 0; i < built.fallbacks.length; i++) {
|
||||
gsap.set(built.fallbacks[i], { opacity: 0 });
|
||||
tl.to(
|
||||
built.fallbacks[i],
|
||||
{ opacity: 1, duration: 0.4, ease: "power2.out" },
|
||||
at + dur * 0.5,
|
||||
);
|
||||
}
|
||||
return t - lift;
|
||||
}
|
||||
|
||||
/* writeOn pose control — unknown → "trace" + console error */
|
||||
function writeOnPose(tl, built, at, pose) {
|
||||
if (pose !== "trace" && pose !== "reveal" && pose !== "off") {
|
||||
console.error('hw: unknown writeOn pose "' + pose + '" — falling back to "trace"');
|
||||
pose = "trace";
|
||||
}
|
||||
var all = built.strokes
|
||||
.map(function (s) {
|
||||
return s.path;
|
||||
})
|
||||
.concat(built.fallbacks);
|
||||
if (pose === "off") {
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
all[i].style.opacity = "0";
|
||||
tl.set(all[i], { opacity: 1 }, at);
|
||||
}
|
||||
return at;
|
||||
}
|
||||
if (pose === "reveal") {
|
||||
// per-glyph staggered fade (the family sweep grammar); derived timing
|
||||
var byGlyph = {};
|
||||
for (var j = 0; j < built.strokes.length; j++) {
|
||||
(byGlyph[built.strokes[j].glyph] = byGlyph[built.strokes[j].glyph] || []).push(
|
||||
built.strokes[j].path,
|
||||
);
|
||||
}
|
||||
var keys = Object.keys(byGlyph);
|
||||
for (var k = 0; k < keys.length; k++) {
|
||||
var paths = byGlyph[keys[k]];
|
||||
for (var m = 0; m < paths.length; m++) {
|
||||
gsap.set(paths[m], { opacity: 0 });
|
||||
tl.to(paths[m], { opacity: 1, duration: 0.3, ease: "power2.out" }, at + k * 0.12);
|
||||
}
|
||||
}
|
||||
for (var f = 0; f < built.fallbacks.length; f++) {
|
||||
gsap.set(built.fallbacks[f], { opacity: 0 });
|
||||
tl.to(
|
||||
built.fallbacks[f],
|
||||
{ opacity: 1, duration: 0.3, ease: "power2.out" },
|
||||
at + keys.length * 0.06,
|
||||
);
|
||||
}
|
||||
return at + keys.length * 0.12 + 0.3;
|
||||
}
|
||||
return writeOnTrace(tl, built, at);
|
||||
}
|
||||
|
||||
/* ---- build ---- */
|
||||
var table = window.__hwPenCaveat;
|
||||
var scalePx = CONFIG.fontSize / 1000;
|
||||
var svg = document.getElementById("hw-wt-svg");
|
||||
var built = buildWord(svg, CONFIG.text);
|
||||
var wUnits = built.widthUnits;
|
||||
var hUnits = table.meta.ascent - table.meta.descent;
|
||||
var wPx = wUnits * scalePx;
|
||||
var hPx = hUnits * scalePx;
|
||||
svg.setAttribute("width", Math.ceil(wPx));
|
||||
svg.setAttribute("height", Math.ceil(hPx));
|
||||
svg.setAttribute("viewBox", "0 " + -table.meta.ascent + " " + wUnits + " " + hUnits);
|
||||
|
||||
var wrap = document.getElementById("hw-wt-wrap");
|
||||
var move = document.getElementById("hw-wt-move");
|
||||
move.style.position = "absolute";
|
||||
move.style.left = (CONFIG.x === null ? Math.round((1920 - wPx) / 2) : CONFIG.x) + "px";
|
||||
move.style.top = (CONFIG.y === null ? Math.round((1080 - hPx) / 2) : CONFIG.y) + "px";
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
var startAt = CONFIG.animationIn ? 0.35 : 0;
|
||||
var writeEnd = startAt;
|
||||
if (CONFIG.animationIn) {
|
||||
writeEnd = writeOnPose(tl, built, startAt, CONFIG.writeOn);
|
||||
} else {
|
||||
// hard-on pose regardless of writeOn (animationIn=false = instant-on)
|
||||
writeEnd = writeOnPose(tl, built, 0, "off");
|
||||
}
|
||||
|
||||
/* squiggle underline (accent), drawn as the write lands */
|
||||
var underPath = document.getElementById("hw-wt-under-path");
|
||||
if (CONFIG.underline) {
|
||||
var uw = Math.round(wPx);
|
||||
var usvg = document.getElementById("hw-wt-under");
|
||||
usvg.setAttribute("width", uw);
|
||||
usvg.setAttribute("viewBox", "0 0 " + uw + " 40");
|
||||
var segs = Math.max(6, Math.round(uw / 90));
|
||||
var d = "M 2 " + (18 + window.hwHash(0, SEED) * 4).toFixed(1);
|
||||
for (var i = 1; i <= segs; i++) {
|
||||
var x2 = (uw / segs) * i;
|
||||
var y2 = 18 + window.hwHash(i * 5 + 1, SEED) * 6;
|
||||
var cxp = x2 - uw / segs / 2 + window.hwHash(i * 5 + 2, SEED) * 10;
|
||||
var cyp = 18 + (i % 2 ? -1 : 1) * (9 + window.hwHash(i * 5 + 3, SEED) * 4);
|
||||
d +=
|
||||
" Q " +
|
||||
cxp.toFixed(1) +
|
||||
" " +
|
||||
cyp.toFixed(1) +
|
||||
" " +
|
||||
x2.toFixed(1) +
|
||||
" " +
|
||||
y2.toFixed(1);
|
||||
}
|
||||
underPath.setAttribute("d", d);
|
||||
var ulen = underPath.getTotalLength();
|
||||
gsap.set(underPath, {
|
||||
strokeDasharray: ulen + " " + ulen,
|
||||
strokeDashoffset: ulen,
|
||||
opacity: 0,
|
||||
});
|
||||
var uAt = Math.min(writeEnd + 0.1, DUR - 1.2);
|
||||
tl.set(underPath, { opacity: 1 }, uAt);
|
||||
tl.to(underPath, { strokeDashoffset: 0, duration: 0.7, ease: "power2.inOut" }, uAt);
|
||||
} else {
|
||||
document.getElementById("hw-wt-under").style.display = "none";
|
||||
}
|
||||
|
||||
/* the boil rides its own wrapper; entrances/exits ride #hw-wt-move */
|
||||
window.hwBoilPose(tl, "#hw-wt-boil", CONFIG.boil, { seed: SEED });
|
||||
|
||||
if (CONFIG.animationOut) {
|
||||
tl.to(move, { opacity: 0, duration: OUT, ease: "power2.in" }, DUR - OUT - 0.05);
|
||||
}
|
||||
tl.set("#hw-wt-root", { visibility: "hidden" }, DUR - 0.02);
|
||||
|
||||
window.__timelines["hw-write-title"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "hw-write-title",
|
||||
"type": "hyperframes:block",
|
||||
"title": "Handwritten Write-On Title",
|
||||
"description": "True glyph write-on: filled Caveat letterforms revealed by baked per-glyph centerline strokes traced in real stroke order with a curvature-adaptive pen-velocity ease (fast on straights, slow into curves, inter-stroke pen lifts); optional squiggle underline; boils",
|
||||
"tags": ["handwritten", "typography", "title-card", "write-on", "overlay"],
|
||||
"dimensions": {
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
},
|
||||
"duration": 6,
|
||||
"files": [
|
||||
{
|
||||
"path": "hw-write-title.html",
|
||||
"target": "compositions/hw-write-title.html",
|
||||
"type": "hyperframes:composition"
|
||||
},
|
||||
{
|
||||
"path": "assets/baked/caveat-pen.js",
|
||||
"target": "assets/baked/caveat-pen.js",
|
||||
"type": "hyperframes:asset"
|
||||
},
|
||||
{
|
||||
"path": "assets/fonts/Caveat-700-latin.woff2",
|
||||
"target": "assets/fonts/Caveat-700-latin.woff2",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"name": "scheme",
|
||||
"type": "variant",
|
||||
"values": ["chalk", "ink"],
|
||||
"default": "chalk",
|
||||
"drives": "which authored token set inks the letterforms (chalk-white over footage / marker-ink on boards)"
|
||||
},
|
||||
{
|
||||
"name": "animation",
|
||||
"type": "toggle",
|
||||
"fields": ["animationIn", "animationOut"],
|
||||
"default": { "animationIn": true, "animationOut": true },
|
||||
"drives": "entrance (the write-on itself; false = hard-on) and exit fade — durations derived, never exposed"
|
||||
},
|
||||
{
|
||||
"name": "boil",
|
||||
"type": "variant",
|
||||
"values": ["off", "calm", "lively"],
|
||||
"default": "calm",
|
||||
"drives": "boil amp/rot as authored pairs over the family-locked frameDrop 3; seed locked"
|
||||
},
|
||||
{
|
||||
"name": "writeOn",
|
||||
"type": "variant",
|
||||
"values": ["trace", "reveal", "off"],
|
||||
"default": "trace",
|
||||
"drives": "reveal mechanic: pen-traced stroke order with velocity physics / per-glyph staggered fade / hard-on — trace duration derived from stroke count"
|
||||
},
|
||||
{
|
||||
"name": "underline",
|
||||
"type": "toggle",
|
||||
"default": true,
|
||||
"drives": "seeded accent squiggle underline drawn after the write lands"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -2,7 +2,7 @@
|
||||
"model": "bge-small-en-v1.5",
|
||||
"modelRevision": "ea104dacec62c0de699686887e3f920caeb4f3e3",
|
||||
"dimensions": 384,
|
||||
"revision": "fb089c68adca459318a6d28b88c09a3ea9ead77d58ef8459bcf90ec79d0b61d9",
|
||||
"revision": "2611d04335aacf5a70649019f948e5ba2f474c38bc45cd59e179d71294cd8cb8",
|
||||
"names": [
|
||||
"ai-chat-reveal",
|
||||
"animated-bar-chart",
|
||||
@@ -146,6 +146,7 @@
|
||||
"hw-text-cloud",
|
||||
"hw-title",
|
||||
"hw-underline",
|
||||
"hw-write-title",
|
||||
"icon-morph-beat",
|
||||
"icon-swap",
|
||||
"ink-bleed-reveal",
|
||||
|
||||
+512
-22
@@ -1,6 +1,9 @@
|
||||
<!doctype html>
|
||||
<!-- Demo plate for the catalog preview. Identical to hw-arrow.html except the
|
||||
root background, which is opaque here so the transparent overlay reads.
|
||||
<!-- Demo plate for the catalog preview. Same helpers as hw-arrow.html, with an
|
||||
opaque root background so the transparent overlay reads, and NON-DEFAULT
|
||||
control states shown side by side per the control-surface convention:
|
||||
swoop+spray+lively / gentle+sharp / straight+soft+boil-off — every
|
||||
declared pose of every control renders here (state contact sheet).
|
||||
Not installed by `hyperframes add` - registry-item.json lists hw-arrow.html only. -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -25,9 +28,12 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
/* ---- hw-arrow ------------------------------------------------------
|
||||
Hand-wobbled draw-on arrow, boiling. Position the wrapper, pick a
|
||||
curve preset, call the helpers. Requires the hw-boil helpers
|
||||
(hwOnUpdate / hwHash / hwBoil) in the host script. */
|
||||
Hand-wobbled draw-on arrow, boiling, with a travel-aligned
|
||||
arrival stretch on the head. Position the wrapper, build with a
|
||||
CONFIG (arrowStyle / strokeType / boil), wire with a position
|
||||
only: hwArrowBuild + hwArrowOn / hwArrowOff. Requires the hw
|
||||
family helpers below (hwOnUpdate / hwHash / hwBoil / hwBoilPose /
|
||||
springEase / hwStrokeApply / hwDrawOn) in the host script. */
|
||||
.hw-arrow {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
@@ -60,12 +66,36 @@
|
||||
font-size: 80px;
|
||||
color: rgba(244, 242, 236, 0.92);
|
||||
}
|
||||
#hw-ar-scene span {
|
||||
position: absolute;
|
||||
top: 650px;
|
||||
width: 420px;
|
||||
text-align: center;
|
||||
font-size: 42px;
|
||||
color: rgba(244, 242, 236, 0.75);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Self-preview. WIRING: copy the .hw-arrow CSS, the markup pattern,
|
||||
and the hwArrowPath / hwArrowOn / hwArrowOff helpers (plus the
|
||||
hw-boil helpers) into your host. Then:
|
||||
<!-- Self-preview. WIRING (g09): copy the .hw-arrow CSS, the markup
|
||||
pattern (positioned wrapper + empty svg with a viewBox), and the
|
||||
helpers into your host. Build with a CONFIG, wire with a position
|
||||
only — the helper owns its durations, eases, and seeds:
|
||||
|
||||
hwArrowBuild("#hw-my-arrow", {
|
||||
arrowStyle: "swoop", // "straight" | "gentle" | "swoop"
|
||||
strokeType: "spray", // "plain" | "soft" | "sharp" | "spray"
|
||||
boil: "calm", // "off" | "calm" | "lively"
|
||||
});
|
||||
hwArrowOn(tl, "#hw-my-arrow", 2.0);
|
||||
hwArrowOff(tl, "#hw-my-arrow", 8.0);
|
||||
|
||||
Each built target needs a UNIQUE id — the wobble seed and the
|
||||
sharp/spray mask id derive from it (an id-less target
|
||||
console.errors and falls back to a shared "hw-arrow" slot).
|
||||
|
||||
Legacy single-path wiring (a <path> in the svg + hwArrowPath)
|
||||
keeps working exactly as before:
|
||||
|
||||
document.querySelector("#hw-my-arrow path")
|
||||
.setAttribute("d", hwArrowPath(360, 160, "swoop", 3));
|
||||
@@ -75,19 +105,37 @@
|
||||
-->
|
||||
<div
|
||||
id="hw-ar-root"
|
||||
data-composition-id="hw-arrow"
|
||||
data-composition-id="hw-arrow-demo"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div id="hw-ar-scene"><span>point at things</span></div>
|
||||
<div id="hw-ar-scene">
|
||||
<span style="left: 140px">swoop · spray · lively</span>
|
||||
<span style="left: 750px">gentle · sharp</span>
|
||||
<span style="left: 1360px">straight · soft · boil off</span>
|
||||
</div>
|
||||
<div
|
||||
class="hw-arrow"
|
||||
id="hw-ar-demo"
|
||||
style="left: 560px; top: 320px; width: 380px; height: 180px"
|
||||
id="hw-ar-a"
|
||||
style="left: 140px; top: 360px; width: 420px; height: 220px"
|
||||
>
|
||||
<svg viewBox="0 0 380 180"><path id="hw-ar-demo-path"></path></svg>
|
||||
<svg viewBox="0 0 420 220"></svg>
|
||||
</div>
|
||||
<div
|
||||
class="hw-arrow"
|
||||
id="hw-ar-b"
|
||||
style="left: 750px; top: 360px; width: 420px; height: 220px"
|
||||
>
|
||||
<svg viewBox="0 0 420 220"></svg>
|
||||
</div>
|
||||
<div
|
||||
class="hw-arrow"
|
||||
id="hw-ar-c"
|
||||
style="left: 1360px; top: 360px; width: 420px; height: 220px"
|
||||
>
|
||||
<svg viewBox="0 0 420 220"></svg>
|
||||
</div>
|
||||
<div
|
||||
id="hw-ar-drv"
|
||||
@@ -136,6 +184,248 @@
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
/* ---- spring library (snappy register; factory duration consumed verbatim) ---- */
|
||||
var LN1000 = Math.log(1000);
|
||||
var FEELS = {
|
||||
snappy: { zeta: 0.9, response: 0.22 },
|
||||
};
|
||||
window.springEase = function (opts) {
|
||||
var f = opts.feel ? FEELS[opts.feel] : opts;
|
||||
var zeta = f.zeta,
|
||||
response = f.response;
|
||||
var w0 = (2 * Math.PI) / response;
|
||||
var T = LN1000 / (zeta * w0);
|
||||
var x;
|
||||
if (zeta < 1) {
|
||||
var wd = w0 * Math.sqrt(1 - zeta * zeta);
|
||||
x = function (t) {
|
||||
return (
|
||||
1 -
|
||||
Math.exp(-zeta * w0 * t) *
|
||||
(Math.cos(wd * t) + ((zeta * w0) / wd) * Math.sin(wd * t))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
x = function (t) {
|
||||
return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
||||
};
|
||||
}
|
||||
var xT = x(T);
|
||||
return {
|
||||
ease: function (p) {
|
||||
return p >= 1 ? 1 : x(p * T) / xT;
|
||||
},
|
||||
duration: T,
|
||||
zeta: zeta,
|
||||
response: response,
|
||||
};
|
||||
};
|
||||
|
||||
/* ---- pen-velocity ease: pure function of the path geometry ----
|
||||
Sample 25–75 curvature-adaptive points, speed ∝ 1/(1+k·curvature)
|
||||
floored at 0.22 (a pen slows into curves, never stalls), integrate
|
||||
ds/speed, invert to a normalized time→distance ease. Seek-safe:
|
||||
no per-frame state. */
|
||||
window.hwPenEase = function (pathEl, opts) {
|
||||
opts = opts || {};
|
||||
var kCurve = opts.kCurve !== undefined ? opts.kCurve : 10;
|
||||
var L = pathEl.getTotalLength();
|
||||
if (L <= 0)
|
||||
return {
|
||||
ease: function (t) {
|
||||
return t;
|
||||
},
|
||||
samples: 2,
|
||||
inkTime: 1,
|
||||
len: 0,
|
||||
};
|
||||
var PRE = 48;
|
||||
var pts = [];
|
||||
for (var i = 0; i <= PRE; i++) pts.push(pathEl.getPointAtLength((L * i) / PRE));
|
||||
var curv = [0];
|
||||
for (i = 1; i < PRE; i++) {
|
||||
var ax = pts[i].x - pts[i - 1].x,
|
||||
ay = pts[i].y - pts[i - 1].y;
|
||||
var bx = pts[i + 1].x - pts[i].x,
|
||||
by = pts[i + 1].y - pts[i].y;
|
||||
var la = Math.hypot(ax, ay) || 1e-6,
|
||||
lb = Math.hypot(bx, by) || 1e-6;
|
||||
var dot = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
|
||||
curv.push(Math.acos(dot) / ((la + lb) / 2));
|
||||
}
|
||||
curv.push(0);
|
||||
var mass = 0;
|
||||
for (i = 0; i < curv.length; i++) mass += curv[i];
|
||||
var N = Math.max(25, Math.min(75, Math.round(25 + mass * 18)));
|
||||
var seg = L / N;
|
||||
var times = [0];
|
||||
var t = 0;
|
||||
for (i = 1; i <= N; i++) {
|
||||
var u = ((i - 0.5) / N) * PRE;
|
||||
var i0 = Math.floor(u),
|
||||
f = u - i0;
|
||||
var c = curv[Math.min(i0, PRE)] * (1 - f) + curv[Math.min(i0 + 1, PRE)] * f;
|
||||
var speed = Math.max(1 / (1 + kCurve * c * 14), 0.22);
|
||||
t += seg / speed;
|
||||
times.push(t);
|
||||
}
|
||||
var total = times[N];
|
||||
for (i = 0; i <= N; i++) times[i] /= total;
|
||||
var easeFn = function (p) {
|
||||
if (p <= 0) return 0;
|
||||
if (p >= 1) return 1;
|
||||
var lo = 0,
|
||||
hi = N;
|
||||
while (hi - lo > 1) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
if (times[mid] <= p) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
var span = times[hi] - times[lo] || 1e-9;
|
||||
return (lo + (p - times[lo]) / span) / N;
|
||||
};
|
||||
return { ease: easeFn, samples: N, inkTime: total, len: L };
|
||||
};
|
||||
|
||||
/* ---- stroke matrix: plain | soft | sharp | spray -------------------
|
||||
strokeType is a g09 variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
therefore rides a MASK clone (the visible dasharray never animates).
|
||||
spray = index-seeded deterministic dots along the path via hwHash;
|
||||
NO feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever hwStrokeApply
|
||||
says. The draw target is always solid (plain/soft: the path itself;
|
||||
sharp/spray: the mask clone — the visible path keeps its texture
|
||||
dasharray untouched). Optionally pen-velocity eased (opts.pen). */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.pen ? window.hwPenEase(p, opts).ease : opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
// ---- hw-arrow helpers (copy these too) ----
|
||||
// Wobbled arrow path in a w x h box, tail at left, head at right.
|
||||
@@ -191,30 +481,230 @@
|
||||
head(-0.5, wob(34, 10, 6))
|
||||
);
|
||||
};
|
||||
|
||||
// ---- hwArrowBuild: CONFIG-reading component builder (g09) ----
|
||||
// Builds the arrow as TWO paths (curve + head group) inside the
|
||||
// target's svg with the same wobble math and seed slots as
|
||||
// hwArrowPath, applies CONFIG.strokeType to the curve (the head
|
||||
// stays plain — texture on a ~30px head reads as noise), and rigs
|
||||
// the travel-aligned arrival wrapper: an outer g rotated to the
|
||||
// travel direction + an inner g counter-rotated. The pair cancels,
|
||||
// so geometry stays put while scaleX on the outer acts along
|
||||
// travel. NO transform-box styles on these g's — they fight GSAP's
|
||||
// baked SVG origins (the outer's fill-box would derive from its
|
||||
// rotated child and the cancellation breaks).
|
||||
// `seed` is internal — defaults per target from the element id
|
||||
// (a pure hash of the id string: deterministic across page loads);
|
||||
// it is NOT a control. Targets must carry unique ids: id-less
|
||||
// targets fall back to a shared "hw-arrow" slot (identical wobble;
|
||||
// duplicate mask ids cross-wire sharp/spray reveals), so that case
|
||||
// fails loudly (bounded-controls law).
|
||||
window.hwArrowBuild = function (target, CONFIG, seed) {
|
||||
CONFIG = CONFIG || {};
|
||||
var el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
var svg = el.querySelector("svg");
|
||||
if (!el.id)
|
||||
console.error(
|
||||
'hw: hwArrowBuild target has no id — falling back to the shared "hw-arrow" seed/mask slot; give each built target a unique id',
|
||||
);
|
||||
if (seed === undefined) {
|
||||
var idStr = el.id || "hw-arrow";
|
||||
seed = 0;
|
||||
for (var si = 0; si < idStr.length; si++)
|
||||
seed = (seed * 31 + idStr.charCodeAt(si)) % 997;
|
||||
seed = (seed % 96) + 1;
|
||||
}
|
||||
var style = CONFIG.arrowStyle === undefined ? "gentle" : CONFIG.arrowStyle;
|
||||
if (style !== "straight" && style !== "gentle" && style !== "swoop") {
|
||||
console.error('hw: unknown arrowStyle "' + style + '" — falling back to "gentle"');
|
||||
style = "gentle";
|
||||
}
|
||||
var vb = svg.viewBox.baseVal;
|
||||
var w = vb && vb.width ? vb.width : el.clientWidth;
|
||||
var h = vb && vb.height ? vb.height : el.clientHeight;
|
||||
// same wobble math + hash slots as hwArrowPath (1–8 body, 9/10 head)
|
||||
var bend = style === "swoop" ? 0.55 : style === "gentle" ? 0.28 : 0.06;
|
||||
function wob(v, n, range) {
|
||||
return v + window.hwHash(n, seed) * range;
|
||||
}
|
||||
var x0 = wob(4, 1, 3),
|
||||
y0 = wob(h * 0.72, 2, 4),
|
||||
x1 = wob(w - 10, 3, 4),
|
||||
y1 = wob(h * 0.3, 4, 4);
|
||||
var cx1 = wob(w * 0.35, 5, 10),
|
||||
cy1 = wob(h * (0.72 + bend * 0.5), 6, 8);
|
||||
var cx2 = wob(w * 0.7, 7, 10),
|
||||
cy2 = wob(h * (0.3 + bend), 8, 8);
|
||||
var ang = Math.atan2(y1 - cy2, x1 - cx2);
|
||||
function headSeg(da, len) {
|
||||
var a = ang + Math.PI + da;
|
||||
return (
|
||||
"M " +
|
||||
x1.toFixed(1) +
|
||||
" " +
|
||||
y1.toFixed(1) +
|
||||
" l " +
|
||||
(Math.cos(a) * len).toFixed(1) +
|
||||
" " +
|
||||
(Math.sin(a) * len).toFixed(1)
|
||||
);
|
||||
}
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var curve = document.createElementNS(ns, "path");
|
||||
curve.setAttribute(
|
||||
"d",
|
||||
"M " +
|
||||
x0.toFixed(1) +
|
||||
" " +
|
||||
y0.toFixed(1) +
|
||||
" C " +
|
||||
cx1.toFixed(1) +
|
||||
" " +
|
||||
cy1.toFixed(1) +
|
||||
", " +
|
||||
cx2.toFixed(1) +
|
||||
" " +
|
||||
cy2.toFixed(1) +
|
||||
", " +
|
||||
x1.toFixed(1) +
|
||||
" " +
|
||||
y1.toFixed(1),
|
||||
);
|
||||
svg.appendChild(curve);
|
||||
var headG = document.createElementNS(ns, "g");
|
||||
var headInner = document.createElementNS(ns, "g");
|
||||
var head = document.createElementNS(ns, "path");
|
||||
head.setAttribute("d", headSeg(0.5, wob(34, 9, 6)) + " " + headSeg(-0.5, wob(34, 10, 6)));
|
||||
headInner.appendChild(head);
|
||||
headG.appendChild(headInner);
|
||||
svg.appendChild(headG);
|
||||
// curve texture; mask id derived from the element id — deterministic
|
||||
// and collision-free across id-bearing instances (id-less targets
|
||||
// errored above and share the fallback)
|
||||
var applied = window.hwStrokeApply(
|
||||
curve,
|
||||
CONFIG.strokeType === undefined ? "plain" : CONFIG.strokeType,
|
||||
{ seed: seed, id: (el.id || "hw-arrow") + "-sm" },
|
||||
);
|
||||
if (applied.group) {
|
||||
// the item CSS (.hw-arrow path) outranks the mask clone's
|
||||
// presentation attributes — pin them as inline styles so the
|
||||
// reveal mask keeps its authored width and white stroke
|
||||
applied.draw.style.stroke = "#fff";
|
||||
applied.draw.style.strokeWidth = applied.draw.getAttribute("stroke-width");
|
||||
}
|
||||
// arrival rig: rotate the WRAPPER to travel, counter-rotate the
|
||||
// content — wrapper scaleX then acts along travel
|
||||
var angDeg = (ang * 180) / Math.PI;
|
||||
gsap.set(headG, { transformOrigin: "50% 50%", rotation: angDeg, opacity: 0 });
|
||||
gsap.set(headInner, { transformOrigin: "50% 50%", rotation: -angDeg });
|
||||
el.__hwArrowBuild = {
|
||||
el: el,
|
||||
applied: applied,
|
||||
headG: headG,
|
||||
headInner: headInner,
|
||||
seed: seed,
|
||||
boil: CONFIG.boil === undefined ? "calm" : CONFIG.boil,
|
||||
boiled: false,
|
||||
};
|
||||
return el.__hwArrowBuild;
|
||||
};
|
||||
|
||||
// Draw-on. Built targets (hwArrowBuild): accelerating draw (causal
|
||||
// arrival), then the head pops with the g21 travel-aligned stretch —
|
||||
// v measured numerically from the draw ease end-slope, S = min(0.06
|
||||
// × v/2000, 0.25), volume preserved (scaleX × scaleY ≈ 1, asserted),
|
||||
// 2-frame apply + snappy spring recovery (factory duration verbatim).
|
||||
// Legacy single-path targets keep the shipped dashoffset behavior.
|
||||
window.hwArrowOn = function (tl, target, at, dur) {
|
||||
var el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
var b = el && el.__hwArrowBuild;
|
||||
if (!b) {
|
||||
var p = document.querySelector(target + " path");
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: "power2.inOut" },
|
||||
{
|
||||
strokeDashoffset: 0,
|
||||
duration: dur === undefined ? 0.7 : dur,
|
||||
ease: "power2.inOut",
|
||||
},
|
||||
at,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// built targets (g09): durations are item-locked — a
|
||||
// legacy-signature `dur` is ignored + console.error (edit the item
|
||||
// source to change); the legacy branch above keeps honoring it
|
||||
if (dur !== undefined)
|
||||
console.error(
|
||||
"hw: hwArrowOn draw duration is locked for built targets — ignoring dur=" + dur,
|
||||
);
|
||||
dur = 0.7;
|
||||
// the pen ACCELERATES into the head (power2.in) so the measured
|
||||
// arrival velocity is real — a settling ease ends at ~zero slope
|
||||
// and would make the stretch invisible
|
||||
var drawEase = gsap.parseEase("power2.in");
|
||||
window.hwDrawOn(tl, b.applied, at, dur, { ease: drawEase });
|
||||
var dlen = b.applied.draw.getTotalLength();
|
||||
var eps = 1e-4;
|
||||
var v = ((drawEase(1) - drawEase(1 - eps)) / eps) * (dlen / dur);
|
||||
var S = Math.min(0.06 * (v / 2000), 0.25);
|
||||
var rec = window.springEase({ feel: "snappy" });
|
||||
var FPS = 30;
|
||||
var st = { s: 0 };
|
||||
var hg = b.headG;
|
||||
window.hwOnUpdate(tl, function () {
|
||||
// stretch ALONG travel (the wrapper's local x after rotation)
|
||||
var along = 1 + st.s;
|
||||
gsap.set(hg, { scaleX: along, scaleY: 1 / along });
|
||||
var ax = parseFloat(gsap.getProperty(hg, "scaleX"));
|
||||
var ay = parseFloat(gsap.getProperty(hg, "scaleY"));
|
||||
if (Math.abs(ax * ay - 1) > 0.01)
|
||||
console.error("hw volume law violated (arrowhead): " + (ax * ay).toFixed(4));
|
||||
});
|
||||
tl.set(hg, { opacity: 1 }, at + dur);
|
||||
tl.to(st, { s: S, duration: 2 / FPS, ease: "power3.out" }, at + dur);
|
||||
tl.to(st, { s: 0, duration: rec.duration, ease: rec.ease }, at + dur + 3 / FPS);
|
||||
// boil pose (recorded from CONFIG at build; hwBoil needs the
|
||||
// timeline, so it registers on first wiring). Boil owns x/y/rotation
|
||||
// of the WRAPPER element — the arrival stretch lives on the nested
|
||||
// head rig, never on the boiled element.
|
||||
if (!b.boiled) {
|
||||
b.boiled = true;
|
||||
window.hwBoilPose(tl, el, b.boil, { seed: b.seed });
|
||||
}
|
||||
};
|
||||
window.hwArrowOff = function (tl, target, at) {
|
||||
tl.to(target, { opacity: 0, duration: 0.3, ease: "power2.in" }, at);
|
||||
};
|
||||
|
||||
// ---- self-preview ----
|
||||
document
|
||||
.getElementById("hw-ar-demo-path")
|
||||
.setAttribute("d", window.hwArrowPath(380, 180, "swoop", 7));
|
||||
// ---- demo: non-default control states, side by side ----
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.hwArrowOn(tl, "#hw-ar-demo", 0.4);
|
||||
window.hwBoil(tl, "#hw-ar-demo", { amp: 2, rot: 0.6, frameDrop: 3, seed: 7 });
|
||||
window.hwArrowOff(tl, "#hw-ar-demo", 3.4);
|
||||
window.hwArrowBuild("#hw-ar-a", {
|
||||
arrowStyle: "swoop",
|
||||
strokeType: "spray",
|
||||
boil: "lively",
|
||||
});
|
||||
window.hwArrowBuild("#hw-ar-b", {
|
||||
arrowStyle: "gentle",
|
||||
strokeType: "sharp",
|
||||
boil: "calm",
|
||||
});
|
||||
window.hwArrowBuild("#hw-ar-c", {
|
||||
arrowStyle: "straight",
|
||||
strokeType: "soft",
|
||||
boil: "off",
|
||||
});
|
||||
window.hwArrowOn(tl, "#hw-ar-a", 0.4);
|
||||
window.hwArrowOn(tl, "#hw-ar-b", 0.9);
|
||||
window.hwArrowOn(tl, "#hw-ar-c", 1.4);
|
||||
window.hwArrowOff(tl, "#hw-ar-a", 3.4);
|
||||
window.hwArrowOff(tl, "#hw-ar-b", 3.4);
|
||||
window.hwArrowOff(tl, "#hw-ar-c", 3.4);
|
||||
tl.set("#hw-ar-root", { visibility: "hidden" }, 3.98);
|
||||
window.__timelines["hw-arrow"] = tl;
|
||||
window.__timelines["hw-arrow-demo"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
+465
-12
@@ -22,9 +22,12 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
/* ---- hw-arrow ------------------------------------------------------
|
||||
Hand-wobbled draw-on arrow, boiling. Position the wrapper, pick a
|
||||
curve preset, call the helpers. Requires the hw-boil helpers
|
||||
(hwOnUpdate / hwHash / hwBoil) in the host script. */
|
||||
Hand-wobbled draw-on arrow, boiling, with a travel-aligned
|
||||
arrival stretch on the head. Position the wrapper, build with a
|
||||
CONFIG (arrowStyle / strokeType / boil), wire with a position
|
||||
only: hwArrowBuild + hwArrowOn / hwArrowOff. Requires the hw
|
||||
family helpers below (hwOnUpdate / hwHash / hwBoil / hwBoilPose /
|
||||
springEase / hwStrokeApply / hwDrawOn) in the host script. */
|
||||
.hw-arrow {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
@@ -60,9 +63,25 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Self-preview. WIRING: copy the .hw-arrow CSS, the markup pattern,
|
||||
and the hwArrowPath / hwArrowOn / hwArrowOff helpers (plus the
|
||||
hw-boil helpers) into your host. Then:
|
||||
<!-- Self-preview. WIRING (g09): copy the .hw-arrow CSS, the markup
|
||||
pattern (positioned wrapper + empty svg with a viewBox), and the
|
||||
helpers into your host. Build with a CONFIG, wire with a position
|
||||
only — the helper owns its durations, eases, and seeds:
|
||||
|
||||
hwArrowBuild("#hw-my-arrow", {
|
||||
arrowStyle: "swoop", // "straight" | "gentle" | "swoop"
|
||||
strokeType: "spray", // "plain" | "soft" | "sharp" | "spray"
|
||||
boil: "calm", // "off" | "calm" | "lively"
|
||||
});
|
||||
hwArrowOn(tl, "#hw-my-arrow", 2.0);
|
||||
hwArrowOff(tl, "#hw-my-arrow", 8.0);
|
||||
|
||||
Each built target needs a UNIQUE id — the wobble seed and the
|
||||
sharp/spray mask id derive from it (an id-less target
|
||||
console.errors and falls back to a shared "hw-arrow" slot).
|
||||
|
||||
Legacy single-path wiring (a <path> in the svg + hwArrowPath)
|
||||
keeps working exactly as before:
|
||||
|
||||
document.querySelector("#hw-my-arrow path")
|
||||
.setAttribute("d", hwArrowPath(360, 160, "swoop", 3));
|
||||
@@ -84,7 +103,7 @@
|
||||
id="hw-ar-demo"
|
||||
style="left: 560px; top: 320px; width: 380px; height: 180px"
|
||||
>
|
||||
<svg viewBox="0 0 380 180"><path id="hw-ar-demo-path"></path></svg>
|
||||
<svg viewBox="0 0 380 180"></svg>
|
||||
</div>
|
||||
<div
|
||||
id="hw-ar-drv"
|
||||
@@ -133,6 +152,248 @@
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
/* ---- spring library (snappy register; factory duration consumed verbatim) ---- */
|
||||
var LN1000 = Math.log(1000);
|
||||
var FEELS = {
|
||||
snappy: { zeta: 0.9, response: 0.22 },
|
||||
};
|
||||
window.springEase = function (opts) {
|
||||
var f = opts.feel ? FEELS[opts.feel] : opts;
|
||||
var zeta = f.zeta,
|
||||
response = f.response;
|
||||
var w0 = (2 * Math.PI) / response;
|
||||
var T = LN1000 / (zeta * w0);
|
||||
var x;
|
||||
if (zeta < 1) {
|
||||
var wd = w0 * Math.sqrt(1 - zeta * zeta);
|
||||
x = function (t) {
|
||||
return (
|
||||
1 -
|
||||
Math.exp(-zeta * w0 * t) *
|
||||
(Math.cos(wd * t) + ((zeta * w0) / wd) * Math.sin(wd * t))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
x = function (t) {
|
||||
return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
||||
};
|
||||
}
|
||||
var xT = x(T);
|
||||
return {
|
||||
ease: function (p) {
|
||||
return p >= 1 ? 1 : x(p * T) / xT;
|
||||
},
|
||||
duration: T,
|
||||
zeta: zeta,
|
||||
response: response,
|
||||
};
|
||||
};
|
||||
|
||||
/* ---- pen-velocity ease: pure function of the path geometry ----
|
||||
Sample 25–75 curvature-adaptive points, speed ∝ 1/(1+k·curvature)
|
||||
floored at 0.22 (a pen slows into curves, never stalls), integrate
|
||||
ds/speed, invert to a normalized time→distance ease. Seek-safe:
|
||||
no per-frame state. */
|
||||
window.hwPenEase = function (pathEl, opts) {
|
||||
opts = opts || {};
|
||||
var kCurve = opts.kCurve !== undefined ? opts.kCurve : 10;
|
||||
var L = pathEl.getTotalLength();
|
||||
if (L <= 0)
|
||||
return {
|
||||
ease: function (t) {
|
||||
return t;
|
||||
},
|
||||
samples: 2,
|
||||
inkTime: 1,
|
||||
len: 0,
|
||||
};
|
||||
var PRE = 48;
|
||||
var pts = [];
|
||||
for (var i = 0; i <= PRE; i++) pts.push(pathEl.getPointAtLength((L * i) / PRE));
|
||||
var curv = [0];
|
||||
for (i = 1; i < PRE; i++) {
|
||||
var ax = pts[i].x - pts[i - 1].x,
|
||||
ay = pts[i].y - pts[i - 1].y;
|
||||
var bx = pts[i + 1].x - pts[i].x,
|
||||
by = pts[i + 1].y - pts[i].y;
|
||||
var la = Math.hypot(ax, ay) || 1e-6,
|
||||
lb = Math.hypot(bx, by) || 1e-6;
|
||||
var dot = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
|
||||
curv.push(Math.acos(dot) / ((la + lb) / 2));
|
||||
}
|
||||
curv.push(0);
|
||||
var mass = 0;
|
||||
for (i = 0; i < curv.length; i++) mass += curv[i];
|
||||
var N = Math.max(25, Math.min(75, Math.round(25 + mass * 18)));
|
||||
var seg = L / N;
|
||||
var times = [0];
|
||||
var t = 0;
|
||||
for (i = 1; i <= N; i++) {
|
||||
var u = ((i - 0.5) / N) * PRE;
|
||||
var i0 = Math.floor(u),
|
||||
f = u - i0;
|
||||
var c = curv[Math.min(i0, PRE)] * (1 - f) + curv[Math.min(i0 + 1, PRE)] * f;
|
||||
var speed = Math.max(1 / (1 + kCurve * c * 14), 0.22);
|
||||
t += seg / speed;
|
||||
times.push(t);
|
||||
}
|
||||
var total = times[N];
|
||||
for (i = 0; i <= N; i++) times[i] /= total;
|
||||
var easeFn = function (p) {
|
||||
if (p <= 0) return 0;
|
||||
if (p >= 1) return 1;
|
||||
var lo = 0,
|
||||
hi = N;
|
||||
while (hi - lo > 1) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
if (times[mid] <= p) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
var span = times[hi] - times[lo] || 1e-9;
|
||||
return (lo + (p - times[lo]) / span) / N;
|
||||
};
|
||||
return { ease: easeFn, samples: N, inkTime: total, len: L };
|
||||
};
|
||||
|
||||
/* ---- stroke matrix: plain | soft | sharp | spray -------------------
|
||||
strokeType is a g09 variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
therefore rides a MASK clone (the visible dasharray never animates).
|
||||
spray = index-seeded deterministic dots along the path via hwHash;
|
||||
NO feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever hwStrokeApply
|
||||
says. The draw target is always solid (plain/soft: the path itself;
|
||||
sharp/spray: the mask clone — the visible path keeps its texture
|
||||
dasharray untouched). Optionally pen-velocity eased (opts.pen). */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.pen ? window.hwPenEase(p, opts).ease : opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
// ---- hw-arrow helpers (copy these too) ----
|
||||
// Wobbled arrow path in a w x h box, tail at left, head at right.
|
||||
@@ -188,27 +449,219 @@
|
||||
head(-0.5, wob(34, 10, 6))
|
||||
);
|
||||
};
|
||||
|
||||
// ---- hwArrowBuild: CONFIG-reading component builder (g09) ----
|
||||
// Builds the arrow as TWO paths (curve + head group) inside the
|
||||
// target's svg with the same wobble math and seed slots as
|
||||
// hwArrowPath, applies CONFIG.strokeType to the curve (the head
|
||||
// stays plain — texture on a ~30px head reads as noise), and rigs
|
||||
// the travel-aligned arrival wrapper: an outer g rotated to the
|
||||
// travel direction + an inner g counter-rotated. The pair cancels,
|
||||
// so geometry stays put while scaleX on the outer acts along
|
||||
// travel. NO transform-box styles on these g's — they fight GSAP's
|
||||
// baked SVG origins (the outer's fill-box would derive from its
|
||||
// rotated child and the cancellation breaks).
|
||||
// `seed` is internal — defaults per target from the element id
|
||||
// (a pure hash of the id string: deterministic across page loads);
|
||||
// it is NOT a control. Targets must carry unique ids: id-less
|
||||
// targets fall back to a shared "hw-arrow" slot (identical wobble;
|
||||
// duplicate mask ids cross-wire sharp/spray reveals), so that case
|
||||
// fails loudly (bounded-controls law).
|
||||
window.hwArrowBuild = function (target, CONFIG, seed) {
|
||||
CONFIG = CONFIG || {};
|
||||
var el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
var svg = el.querySelector("svg");
|
||||
if (!el.id)
|
||||
console.error(
|
||||
'hw: hwArrowBuild target has no id — falling back to the shared "hw-arrow" seed/mask slot; give each built target a unique id',
|
||||
);
|
||||
if (seed === undefined) {
|
||||
var idStr = el.id || "hw-arrow";
|
||||
seed = 0;
|
||||
for (var si = 0; si < idStr.length; si++)
|
||||
seed = (seed * 31 + idStr.charCodeAt(si)) % 997;
|
||||
seed = (seed % 96) + 1;
|
||||
}
|
||||
var style = CONFIG.arrowStyle === undefined ? "gentle" : CONFIG.arrowStyle;
|
||||
if (style !== "straight" && style !== "gentle" && style !== "swoop") {
|
||||
console.error('hw: unknown arrowStyle "' + style + '" — falling back to "gentle"');
|
||||
style = "gentle";
|
||||
}
|
||||
var vb = svg.viewBox.baseVal;
|
||||
var w = vb && vb.width ? vb.width : el.clientWidth;
|
||||
var h = vb && vb.height ? vb.height : el.clientHeight;
|
||||
// same wobble math + hash slots as hwArrowPath (1–8 body, 9/10 head)
|
||||
var bend = style === "swoop" ? 0.55 : style === "gentle" ? 0.28 : 0.06;
|
||||
function wob(v, n, range) {
|
||||
return v + window.hwHash(n, seed) * range;
|
||||
}
|
||||
var x0 = wob(4, 1, 3),
|
||||
y0 = wob(h * 0.72, 2, 4),
|
||||
x1 = wob(w - 10, 3, 4),
|
||||
y1 = wob(h * 0.3, 4, 4);
|
||||
var cx1 = wob(w * 0.35, 5, 10),
|
||||
cy1 = wob(h * (0.72 + bend * 0.5), 6, 8);
|
||||
var cx2 = wob(w * 0.7, 7, 10),
|
||||
cy2 = wob(h * (0.3 + bend), 8, 8);
|
||||
var ang = Math.atan2(y1 - cy2, x1 - cx2);
|
||||
function headSeg(da, len) {
|
||||
var a = ang + Math.PI + da;
|
||||
return (
|
||||
"M " +
|
||||
x1.toFixed(1) +
|
||||
" " +
|
||||
y1.toFixed(1) +
|
||||
" l " +
|
||||
(Math.cos(a) * len).toFixed(1) +
|
||||
" " +
|
||||
(Math.sin(a) * len).toFixed(1)
|
||||
);
|
||||
}
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var curve = document.createElementNS(ns, "path");
|
||||
curve.setAttribute(
|
||||
"d",
|
||||
"M " +
|
||||
x0.toFixed(1) +
|
||||
" " +
|
||||
y0.toFixed(1) +
|
||||
" C " +
|
||||
cx1.toFixed(1) +
|
||||
" " +
|
||||
cy1.toFixed(1) +
|
||||
", " +
|
||||
cx2.toFixed(1) +
|
||||
" " +
|
||||
cy2.toFixed(1) +
|
||||
", " +
|
||||
x1.toFixed(1) +
|
||||
" " +
|
||||
y1.toFixed(1),
|
||||
);
|
||||
svg.appendChild(curve);
|
||||
var headG = document.createElementNS(ns, "g");
|
||||
var headInner = document.createElementNS(ns, "g");
|
||||
var head = document.createElementNS(ns, "path");
|
||||
head.setAttribute("d", headSeg(0.5, wob(34, 9, 6)) + " " + headSeg(-0.5, wob(34, 10, 6)));
|
||||
headInner.appendChild(head);
|
||||
headG.appendChild(headInner);
|
||||
svg.appendChild(headG);
|
||||
// curve texture; mask id derived from the element id — deterministic
|
||||
// and collision-free across id-bearing instances (id-less targets
|
||||
// errored above and share the fallback)
|
||||
var applied = window.hwStrokeApply(
|
||||
curve,
|
||||
CONFIG.strokeType === undefined ? "plain" : CONFIG.strokeType,
|
||||
{ seed: seed, id: (el.id || "hw-arrow") + "-sm" },
|
||||
);
|
||||
if (applied.group) {
|
||||
// the item CSS (.hw-arrow path) outranks the mask clone's
|
||||
// presentation attributes — pin them as inline styles so the
|
||||
// reveal mask keeps its authored width and white stroke
|
||||
applied.draw.style.stroke = "#fff";
|
||||
applied.draw.style.strokeWidth = applied.draw.getAttribute("stroke-width");
|
||||
}
|
||||
// arrival rig: rotate the WRAPPER to travel, counter-rotate the
|
||||
// content — wrapper scaleX then acts along travel
|
||||
var angDeg = (ang * 180) / Math.PI;
|
||||
gsap.set(headG, { transformOrigin: "50% 50%", rotation: angDeg, opacity: 0 });
|
||||
gsap.set(headInner, { transformOrigin: "50% 50%", rotation: -angDeg });
|
||||
el.__hwArrowBuild = {
|
||||
el: el,
|
||||
applied: applied,
|
||||
headG: headG,
|
||||
headInner: headInner,
|
||||
seed: seed,
|
||||
boil: CONFIG.boil === undefined ? "calm" : CONFIG.boil,
|
||||
boiled: false,
|
||||
};
|
||||
return el.__hwArrowBuild;
|
||||
};
|
||||
|
||||
// Draw-on. Built targets (hwArrowBuild): accelerating draw (causal
|
||||
// arrival), then the head pops with the g21 travel-aligned stretch —
|
||||
// v measured numerically from the draw ease end-slope, S = min(0.06
|
||||
// × v/2000, 0.25), volume preserved (scaleX × scaleY ≈ 1, asserted),
|
||||
// 2-frame apply + snappy spring recovery (factory duration verbatim).
|
||||
// Legacy single-path targets keep the shipped dashoffset behavior.
|
||||
window.hwArrowOn = function (tl, target, at, dur) {
|
||||
var el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
var b = el && el.__hwArrowBuild;
|
||||
if (!b) {
|
||||
var p = document.querySelector(target + " path");
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: "power2.inOut" },
|
||||
{
|
||||
strokeDashoffset: 0,
|
||||
duration: dur === undefined ? 0.7 : dur,
|
||||
ease: "power2.inOut",
|
||||
},
|
||||
at,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// built targets (g09): durations are item-locked — a
|
||||
// legacy-signature `dur` is ignored + console.error (edit the item
|
||||
// source to change); the legacy branch above keeps honoring it
|
||||
if (dur !== undefined)
|
||||
console.error(
|
||||
"hw: hwArrowOn draw duration is locked for built targets — ignoring dur=" + dur,
|
||||
);
|
||||
dur = 0.7;
|
||||
// the pen ACCELERATES into the head (power2.in) so the measured
|
||||
// arrival velocity is real — a settling ease ends at ~zero slope
|
||||
// and would make the stretch invisible
|
||||
var drawEase = gsap.parseEase("power2.in");
|
||||
window.hwDrawOn(tl, b.applied, at, dur, { ease: drawEase });
|
||||
var dlen = b.applied.draw.getTotalLength();
|
||||
var eps = 1e-4;
|
||||
var v = ((drawEase(1) - drawEase(1 - eps)) / eps) * (dlen / dur);
|
||||
var S = Math.min(0.06 * (v / 2000), 0.25);
|
||||
var rec = window.springEase({ feel: "snappy" });
|
||||
var FPS = 30;
|
||||
var st = { s: 0 };
|
||||
var hg = b.headG;
|
||||
window.hwOnUpdate(tl, function () {
|
||||
// stretch ALONG travel (the wrapper's local x after rotation)
|
||||
var along = 1 + st.s;
|
||||
gsap.set(hg, { scaleX: along, scaleY: 1 / along });
|
||||
var ax = parseFloat(gsap.getProperty(hg, "scaleX"));
|
||||
var ay = parseFloat(gsap.getProperty(hg, "scaleY"));
|
||||
if (Math.abs(ax * ay - 1) > 0.01)
|
||||
console.error("hw volume law violated (arrowhead): " + (ax * ay).toFixed(4));
|
||||
});
|
||||
tl.set(hg, { opacity: 1 }, at + dur);
|
||||
tl.to(st, { s: S, duration: 2 / FPS, ease: "power3.out" }, at + dur);
|
||||
tl.to(st, { s: 0, duration: rec.duration, ease: rec.ease }, at + dur + 3 / FPS);
|
||||
// boil pose (recorded from CONFIG at build; hwBoil needs the
|
||||
// timeline, so it registers on first wiring). Boil owns x/y/rotation
|
||||
// of the WRAPPER element — the arrival stretch lives on the nested
|
||||
// head rig, never on the boiled element.
|
||||
if (!b.boiled) {
|
||||
b.boiled = true;
|
||||
window.hwBoilPose(tl, el, b.boil, { seed: b.seed });
|
||||
}
|
||||
};
|
||||
window.hwArrowOff = function (tl, target, at) {
|
||||
tl.to(target, { opacity: 0, duration: 0.3, ease: "power2.in" }, at);
|
||||
};
|
||||
|
||||
// ---- self-preview ----
|
||||
document
|
||||
.getElementById("hw-ar-demo-path")
|
||||
.setAttribute("d", window.hwArrowPath(380, 180, "swoop", 7));
|
||||
var CONFIG = {
|
||||
// ── Controls (family base + 2 item-specific; comment grammar is the published-parameter list) ──
|
||||
arrowStyle: "gentle", // variant: "straight" | "gentle" | "swoop" — curve bend pose over the seeded wobble math (bend + both control points)
|
||||
strokeType: "plain", // variant: "plain" | "soft" | "sharp" | "spray" — curve texture via hwStrokeApply (sharp/spray draw on a mask clone); head stays plain
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3; seed locked
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// seed (derived per target), draw ease (power2.in), arrival
|
||||
// softness 0.06, snappy spring recovery, durations
|
||||
};
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.hwArrowBuild("#hw-ar-demo", CONFIG);
|
||||
window.hwArrowOn(tl, "#hw-ar-demo", 0.4);
|
||||
window.hwBoil(tl, "#hw-ar-demo", { amp: 2, rot: 0.6, frameDrop: 3, seed: 7 });
|
||||
window.hwArrowOff(tl, "#hw-ar-demo", 3.4);
|
||||
tl.set("#hw-ar-root", { visibility: "hidden" }, 3.98);
|
||||
window.__timelines["hw-arrow"] = tl;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "hw-arrow",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Hand-Drawn Arrow",
|
||||
"description": "Wobbled draw-on arrow with straight, gentle, and swoop curve presets; boils while on screen",
|
||||
"description": "Wobbled draw-on arrow with straight, gentle, and swoop curve poses, a four-way stroke texture matrix (plain/soft/sharp/spray), and a travel-aligned head-arrival stretch pulse with spring recovery; boils while on screen",
|
||||
"tags": ["handwritten", "annotation", "arrow", "overlay"],
|
||||
"files": [
|
||||
{
|
||||
@@ -16,5 +16,28 @@
|
||||
"target": "assets/fonts/Caveat-700-latin.woff2",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"name": "arrowStyle",
|
||||
"type": "variant",
|
||||
"values": ["straight", "gentle", "swoop"],
|
||||
"default": "gentle",
|
||||
"drives": "curve bend pose over the seeded wobble math (bend constant + both cubic control points)"
|
||||
},
|
||||
{
|
||||
"name": "strokeType",
|
||||
"type": "variant",
|
||||
"values": ["plain", "soft", "sharp", "spray"],
|
||||
"default": "plain",
|
||||
"drives": "curve texture via hwStrokeApply (soft blur / dry-marker dasharray on a mask clone / seeded deterministic spray dots); the head stays plain"
|
||||
},
|
||||
{
|
||||
"name": "boil",
|
||||
"type": "variant",
|
||||
"values": ["off", "calm", "lively"],
|
||||
"default": "calm",
|
||||
"drives": "boil amp/rot as authored pairs over the family-locked frameDrop 3; seed locked"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+293
-36
@@ -1,6 +1,9 @@
|
||||
<!doctype html>
|
||||
<!-- Demo plate for the catalog preview. Identical to hw-boil.html except the
|
||||
root background, which is opaque here so the transparent overlay reads.
|
||||
<!-- Demo plate for the catalog preview. Same helper set as hw-boil.html with
|
||||
an opaque root background, exercising the control surface in NON-DEFAULT
|
||||
states (the demo law: at least one non-default control state): stroke
|
||||
matrix "sharp" and "spray" side by side, the "lively" boil pose on the
|
||||
text, and the raw frantic arrow.
|
||||
Not installed by `hyperframes add` - registry-item.json lists hw-boil.html only. -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -32,14 +35,28 @@
|
||||
The hand-drawn "boil": jitter that re-poses only every N frames
|
||||
(quake + frame-drop style). Pose is a pure function of quantized
|
||||
timeline time through a seeded hash — fully seek-safe, renders
|
||||
identically on every pass over the same frame.
|
||||
identically on every pass over the same frame. Ships the control
|
||||
layer on top of the raw mechanic: hwBoilPose (the "boil" control —
|
||||
authored amp/rot poses over the family-locked frameDrop), the
|
||||
stroke matrix (hwStrokeTypes / hwStrokeApply / hwDrawOn — the
|
||||
"strokeType" control: plain | soft | sharp | spray; spray is
|
||||
index-seeded deterministic dots, NO feTurbulence anywhere) and the
|
||||
curvature-adaptive pen-velocity ease (hwPenEase).
|
||||
|
||||
RULES:
|
||||
- hwBoil owns x/y/rotation of the boiled element. Entrance/exit
|
||||
tweens go on a WRAPPER around it, never the boiled element.
|
||||
- hwBoil owns x/y/rotation of the boiled element.
|
||||
Transform-touching entrance/exit tweens go on a WRAPPER
|
||||
around it, never the boiled element (non-transform tweens
|
||||
like opacity don't contend and may ride it).
|
||||
- One timeline has ONE onUpdate — hwOnUpdate is the dispatcher
|
||||
that lets the boil coexist with sweep scalars etc. Register
|
||||
every onUpdate-style render through it. */
|
||||
every onUpdate-style render through it.
|
||||
- Never boil an element carrying an SVG transform attribute —
|
||||
nest a positioning <g> (or div) and boil the clean inner group.
|
||||
- A textured path (sharp/spray) lives in its OWN svg inside the
|
||||
boiled wrapper, so the stroke-matrix mask in that svg's defs
|
||||
rides the boil with it (a mask left in a shared static svg
|
||||
would clip the texture at jitter extremes). */
|
||||
#hw-boil-root {
|
||||
--hw-font-print: "Caveat", cursive;
|
||||
--hw-font-script: "Caveat", cursive;
|
||||
@@ -83,43 +100,59 @@
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
letter-spacing: 0.12em;
|
||||
color: rgba(244, 242, 236, 0.45);
|
||||
color: rgba(244, 242, 236, 0.62);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Self-preview. WIRING: copy the hwOnUpdate / hwHash / hwBoil /
|
||||
hwWobbleEllipse helpers into your host script, then:
|
||||
|
||||
hwBoil(tl, "#hw-my-annotation", { amp: 1.6, rot: 0.5, frameDrop: 3, seed: 2 });
|
||||
|
||||
frameDrop 3-4 = classic boil; 1 = frantic; boil: off = don't call it. -->
|
||||
<div
|
||||
id="hw-boil-root"
|
||||
data-composition-id="hw-boil"
|
||||
data-composition-id="hw-boil-demo"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-duration="6"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<svg viewBox="0 0 1920 1080">
|
||||
<g id="hw-b-circle-g"><path id="hw-b-circle"></path></g>
|
||||
<g id="hw-b-arrow-g">
|
||||
<path
|
||||
id="hw-b-arrow"
|
||||
d="M 640 640 C 760 700, 980 720, 1130 620 M 1130 620 l -64 -6 M 1130 620 l -18 58"
|
||||
d="M 910 780 C 1030 840, 1250 860, 1400 760 M 1400 760 l -64 -6 M 1400 760 l -18 58"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="hw-b-label" id="hw-b-word" style="left: 1230px; top: 560px">the boil</div>
|
||||
<div class="hw-b-tag" style="left: 350px; top: 782px">FRAME DROP 3</div>
|
||||
<div class="hw-b-tag" style="left: 700px; top: 782px">FRAME DROP 1</div>
|
||||
<div class="hw-b-tag" style="left: 1230px; top: 700px">TEXT, AMP 1.2</div>
|
||||
<!-- textured paths: own svg inside the boiled div — the stroke-matrix
|
||||
mask (in each svg's defs) rides the boil with its path -->
|
||||
<div
|
||||
id="hw-b-sharp-w"
|
||||
style="position: absolute; left: 185px; top: 320px; width: 390px; height: 280px"
|
||||
>
|
||||
<div id="hw-b-sharp-g" style="position: absolute; inset: 0">
|
||||
<svg viewBox="0 0 390 280" style="overflow: visible">
|
||||
<path id="hw-b-sharp"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="hw-b-spray-w"
|
||||
style="position: absolute; left: 755px; top: 320px; width: 390px; height: 280px"
|
||||
>
|
||||
<div id="hw-b-spray-g" style="position: absolute; inset: 0">
|
||||
<svg viewBox="0 0 390 280" style="overflow: visible">
|
||||
<path id="hw-b-spray"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hw-b-label" id="hw-b-word" style="left: 1430px; top: 700px">the boil</div>
|
||||
<div class="hw-b-tag" style="left: 260px; top: 660px">STROKE "SHARP" · BOIL "CALM"</div>
|
||||
<div class="hw-b-tag" style="left: 830px; top: 660px">STROKE "SPRAY" · BOIL "CALM"</div>
|
||||
<div class="hw-b-tag" style="left: 1430px; top: 850px">BOIL "LIVELY"</div>
|
||||
<div class="hw-b-tag" style="left: 910px; top: 930px">RAW hwBoil · FRAME DROP 1</div>
|
||||
<div
|
||||
id="hw-b-drv"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-duration="6"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
@@ -172,6 +205,21 @@
|
||||
});
|
||||
};
|
||||
|
||||
// The boil CONTROL: authored amp/rot poses over the family-locked
|
||||
// frameDrop 3; seed stays locked. Unknown pose → "calm" + console
|
||||
// error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
// Seeded hand-wobbled ellipse path (smooth quadratic chain).
|
||||
window.hwWobbleEllipse = function (cx, cy, rx, ry, seed, wobblePct) {
|
||||
wobblePct = wobblePct === undefined ? 3 : wobblePct;
|
||||
@@ -201,28 +249,237 @@
|
||||
return d;
|
||||
};
|
||||
|
||||
// ---- self-preview (not part of the snippet you copy) ----
|
||||
var circle = document.getElementById("hw-b-circle");
|
||||
circle.setAttribute("d", window.hwWobbleEllipse(460, 620, 190, 130, 4, 3.2));
|
||||
/* ---- pen-velocity ease: pure function of the path geometry ----
|
||||
Sample 25–75 curvature-adaptive points, speed ∝ 1/(1+k·curvature)
|
||||
floored at 0.22 (a pen slows into curves, never stalls), integrate
|
||||
ds/speed, invert to a normalized time→distance ease. Seek-safe:
|
||||
no per-frame state. */
|
||||
window.hwPenEase = function (pathEl, opts) {
|
||||
opts = opts || {};
|
||||
var kCurve = opts.kCurve !== undefined ? opts.kCurve : 10;
|
||||
var L = pathEl.getTotalLength();
|
||||
if (L <= 0)
|
||||
return {
|
||||
ease: function (t) {
|
||||
return t;
|
||||
},
|
||||
samples: 2,
|
||||
inkTime: 1,
|
||||
len: 0,
|
||||
};
|
||||
var PRE = 48;
|
||||
var pts = [];
|
||||
for (var i = 0; i <= PRE; i++) pts.push(pathEl.getPointAtLength((L * i) / PRE));
|
||||
var curv = [0];
|
||||
for (i = 1; i < PRE; i++) {
|
||||
var ax = pts[i].x - pts[i - 1].x,
|
||||
ay = pts[i].y - pts[i - 1].y;
|
||||
var bx = pts[i + 1].x - pts[i].x,
|
||||
by = pts[i + 1].y - pts[i].y;
|
||||
var la = Math.hypot(ax, ay) || 1e-6,
|
||||
lb = Math.hypot(bx, by) || 1e-6;
|
||||
var dot = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
|
||||
curv.push(Math.acos(dot) / ((la + lb) / 2));
|
||||
}
|
||||
curv.push(0);
|
||||
var mass = 0;
|
||||
for (i = 0; i < curv.length; i++) mass += curv[i];
|
||||
var N = Math.max(25, Math.min(75, Math.round(25 + mass * 18)));
|
||||
var seg = L / N;
|
||||
var times = [0];
|
||||
var t = 0;
|
||||
for (i = 1; i <= N; i++) {
|
||||
var u = ((i - 0.5) / N) * PRE;
|
||||
var i0 = Math.floor(u),
|
||||
f = u - i0;
|
||||
var c = curv[Math.min(i0, PRE)] * (1 - f) + curv[Math.min(i0 + 1, PRE)] * f;
|
||||
var speed = Math.max(1 / (1 + kCurve * c * 14), 0.22);
|
||||
t += seg / speed;
|
||||
times.push(t);
|
||||
}
|
||||
var total = times[N];
|
||||
for (i = 0; i <= N; i++) times[i] /= total;
|
||||
var easeFn = function (p) {
|
||||
if (p <= 0) return 0;
|
||||
if (p >= 1) return 1;
|
||||
var lo = 0,
|
||||
hi = N;
|
||||
while (hi - lo > 1) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
if (times[mid] <= p) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
var span = times[hi] - times[lo] || 1e-9;
|
||||
return (lo + (p - times[lo]) / span) / N;
|
||||
};
|
||||
return { ease: easeFn, samples: N, inkTime: total, len: L };
|
||||
};
|
||||
|
||||
/* ---- stroke matrix: plain | soft | sharp | spray ------------------
|
||||
strokeType is a variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
must therefore ride a MASK clone (the visible dasharray never
|
||||
animates). spray = index-seeded deterministic dots along the path
|
||||
via hwHash: position a pure function of path parameter + seed; NO
|
||||
feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever
|
||||
hwStrokeApply says. Optionally pen-velocity eased (opts.pen: true). */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.pen ? window.hwPenEase(p, opts).ease : opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
// ---- demo scene: non-default control states, labeled ----
|
||||
var sharpPath = document.getElementById("hw-b-sharp");
|
||||
sharpPath.setAttribute("d", window.hwWobbleEllipse(195, 140, 175, 120, 4, 3.2));
|
||||
var sprayPath = document.getElementById("hw-b-spray");
|
||||
sprayPath.setAttribute("d", window.hwWobbleEllipse(195, 140, 175, 120, 7, 3.2));
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
/* draw-on entrances (strokeDashoffset — never x/y, the boil owns those) */
|
||||
[circle, document.getElementById("hw-b-arrow")].forEach(function (p, i) {
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
tl.to(p, { strokeDashoffset: 0, duration: 0.8, ease: "power2.inOut" }, 0.3 + i * 0.35);
|
||||
/* stroke matrix, non-default states side by side: sharp (pen-eased
|
||||
draw-on) and spray (index-seeded deterministic dots) — both drawn
|
||||
through the mask-safe hwDrawOn */
|
||||
var sharpApplied = window.hwStrokeApply(sharpPath, "sharp", {
|
||||
seed: 4,
|
||||
id: "hw-b-mask-sharp",
|
||||
});
|
||||
window.hwDrawOn(tl, sharpApplied, 0.3, 0.9, { pen: true });
|
||||
var sprayApplied = window.hwStrokeApply(sprayPath, "spray", {
|
||||
seed: 6,
|
||||
id: "hw-b-mask-spray",
|
||||
});
|
||||
window.hwDrawOn(tl, sprayApplied, 0.7, 0.9, { pen: true });
|
||||
|
||||
/* raw draw-on entrance for the arrow (not routed through the matrix) */
|
||||
var arrow = document.getElementById("hw-b-arrow");
|
||||
var alen = arrow.getTotalLength();
|
||||
gsap.set(arrow, { strokeDasharray: alen, strokeDashoffset: alen });
|
||||
tl.to(arrow, { strokeDashoffset: 0, duration: 0.8, ease: "power2.inOut" }, 1.1);
|
||||
|
||||
gsap.set("#hw-b-word", { opacity: 0 });
|
||||
tl.to("#hw-b-word", { opacity: 1, duration: 0.5, ease: "power2.out" }, 1.1);
|
||||
tl.to("#hw-b-word", { opacity: 1, duration: 0.5, ease: "power2.out" }, 1.6);
|
||||
|
||||
/* three boils with different characters, one dispatcher */
|
||||
window.hwBoil(tl, "#hw-b-circle-g", { amp: 2.2, rot: 0.4, frameDrop: 3, seed: 2 });
|
||||
/* boil control states (calm / calm / lively) + one raw frantic
|
||||
character, one dispatcher */
|
||||
window.hwBoilPose(tl, "#hw-b-sharp-g", "calm", { seed: 2 });
|
||||
window.hwBoilPose(tl, "#hw-b-spray-g", "calm", { seed: 6 });
|
||||
window.hwBoilPose(tl, "#hw-b-word", "lively", { seed: 9 });
|
||||
window.hwBoil(tl, "#hw-b-arrow-g", { amp: 2.6, rot: 0.7, frameDrop: 1, seed: 5 });
|
||||
window.hwBoil(tl, "#hw-b-word", { amp: 1.2, rot: 0.35, frameDrop: 3, seed: 9 });
|
||||
|
||||
tl.set("#hw-boil-root", { visibility: "hidden" }, 3.98);
|
||||
window.__timelines["hw-boil"] = tl;
|
||||
tl.set("#hw-boil-root", { visibility: "hidden" }, 5.98);
|
||||
window.__timelines["hw-boil-demo"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
+296
-18
@@ -29,14 +29,28 @@
|
||||
The hand-drawn "boil": jitter that re-poses only every N frames
|
||||
(quake + frame-drop style). Pose is a pure function of quantized
|
||||
timeline time through a seeded hash — fully seek-safe, renders
|
||||
identically on every pass over the same frame.
|
||||
identically on every pass over the same frame. Ships the control
|
||||
layer on top of the raw mechanic: hwBoilPose (the "boil" control —
|
||||
authored amp/rot poses over the family-locked frameDrop), the
|
||||
stroke matrix (hwStrokeTypes / hwStrokeApply / hwDrawOn — the
|
||||
"strokeType" control: plain | soft | sharp | spray; spray is
|
||||
index-seeded deterministic dots, NO feTurbulence anywhere) and the
|
||||
curvature-adaptive pen-velocity ease (hwPenEase).
|
||||
|
||||
RULES:
|
||||
- hwBoil owns x/y/rotation of the boiled element. Entrance/exit
|
||||
tweens go on a WRAPPER around it, never the boiled element.
|
||||
- hwBoil owns x/y/rotation of the boiled element.
|
||||
Transform-touching entrance/exit tweens go on a WRAPPER
|
||||
around it, never the boiled element (non-transform tweens
|
||||
like opacity don't contend and may ride it).
|
||||
- One timeline has ONE onUpdate — hwOnUpdate is the dispatcher
|
||||
that lets the boil coexist with sweep scalars etc. Register
|
||||
every onUpdate-style render through it. */
|
||||
every onUpdate-style render through it.
|
||||
- Never boil an element carrying an SVG transform attribute —
|
||||
nest a positioning <g> (or div) and boil the clean inner group.
|
||||
- A textured path (sharp/spray) lives in its OWN svg inside the
|
||||
boiled wrapper, so the stroke-matrix mask in that svg's defs
|
||||
rides the boil with it (a mask left in a shared static svg
|
||||
would clip the texture at jitter extremes). */
|
||||
#hw-boil-root {
|
||||
--hw-font-print: "Caveat", cursive;
|
||||
--hw-font-script: "Caveat", cursive;
|
||||
@@ -80,7 +94,7 @@
|
||||
font-weight: 500;
|
||||
font-size: 22px;
|
||||
letter-spacing: 0.12em;
|
||||
color: rgba(244, 242, 236, 0.45);
|
||||
color: rgba(244, 242, 236, 0.62);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -90,7 +104,26 @@
|
||||
|
||||
hwBoil(tl, "#hw-my-annotation", { amp: 1.6, rot: 0.5, frameDrop: 3, seed: 2 });
|
||||
|
||||
frameDrop 3-4 = classic boil; 1 = frantic; boil: off = don't call it. -->
|
||||
frameDrop 3-4 = classic boil; 1 = frantic; boil: off = don't call it.
|
||||
|
||||
Control layer (copy alongside): hwBoilPose maps the "boil" control
|
||||
("off" | "calm" | "lively") to authored amp/rot pairs over the
|
||||
family-locked frameDrop 3 —
|
||||
|
||||
hwBoilPose(tl, "#hw-my-annotation", CONFIG.boil, { seed: 2 });
|
||||
|
||||
The stroke matrix (hwStrokeTypes / hwStrokeApply / hwDrawOn, plus
|
||||
hwPenEase) maps the "strokeType" control ("plain" | "soft" |
|
||||
"sharp" | "spray") to authored textures. sharp/spray return a mask
|
||||
clone in applied.draw and hwDrawOn animates THAT (the visible
|
||||
texture never animates); { pen: true } rides the curvature-adaptive
|
||||
pen-velocity ease —
|
||||
|
||||
var applied = hwStrokeApply(pathEl, CONFIG.strokeType, { seed: 4, id: "my-mask" });
|
||||
hwDrawOn(tl, applied, 0.3, 0.8, { pen: true });
|
||||
|
||||
Unknown control values fall back to the declared default with a
|
||||
console error (bounded-controls law). -->
|
||||
<div
|
||||
id="hw-boil-root"
|
||||
data-composition-id="hw-boil"
|
||||
@@ -100,7 +133,6 @@
|
||||
data-height="1080"
|
||||
>
|
||||
<svg viewBox="0 0 1920 1080">
|
||||
<g id="hw-b-circle-g"><path id="hw-b-circle"></path></g>
|
||||
<g id="hw-b-arrow-g">
|
||||
<path
|
||||
id="hw-b-arrow"
|
||||
@@ -108,9 +140,23 @@
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
<!-- the CONFIG element: own svg inside the boiled div — the
|
||||
stroke-matrix mask (in this svg's defs) rides the boil with it -->
|
||||
<div
|
||||
id="hw-b-circle-w"
|
||||
style="position: absolute; left: 250px; top: 470px; width: 420px; height: 300px"
|
||||
>
|
||||
<div id="hw-b-circle-g" style="position: absolute; inset: 0">
|
||||
<svg viewBox="0 0 420 300" style="overflow: visible">
|
||||
<path id="hw-b-circle"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hw-b-label" id="hw-b-word" style="left: 1230px; top: 560px">the boil</div>
|
||||
<div class="hw-b-tag" style="left: 350px; top: 782px">FRAME DROP 3</div>
|
||||
<div class="hw-b-tag" style="left: 700px; top: 782px">FRAME DROP 1</div>
|
||||
<div class="hw-b-tag" id="hw-b-tag-config" style="left: 350px; top: 782px">
|
||||
BOIL "CALM" · STROKE "PLAIN"
|
||||
</div>
|
||||
<div class="hw-b-tag" style="left: 940px; top: 782px">FRAME DROP 1</div>
|
||||
<div class="hw-b-tag" style="left: 1230px; top: 700px">TEXT, AMP 1.2</div>
|
||||
<div
|
||||
id="hw-b-drv"
|
||||
@@ -125,6 +171,16 @@
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
var CONFIG = {
|
||||
// ── Controls (family base + 1 item-specific; comment grammar is the published-parameter list) ──
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3, applied to the demo circle; seed locked (family base)
|
||||
strokeType: "plain", // variant: "plain" | "soft" | "sharp" | "spray" — authored stroke texture on the demo circle (soft = blur; sharp = micro-dash dry marker; spray = seeded deterministic dots); draw-on rides the mask clone for textured types (4 params)
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// seeds, draw durations/eases, pen-velocity constants (kCurve 10,
|
||||
// floor 0.22), boil pose tables, frameDrop values, demo geometry
|
||||
};
|
||||
|
||||
// ---- copy these helpers into the host script ----
|
||||
|
||||
// Dispatcher: a GSAP timeline has ONE onUpdate. Register every
|
||||
@@ -169,6 +225,21 @@
|
||||
});
|
||||
};
|
||||
|
||||
// The boil CONTROL: authored amp/rot poses over the family-locked
|
||||
// frameDrop 3; seed stays locked. Unknown pose → "calm" + console
|
||||
// error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
// Seeded hand-wobbled ellipse path (smooth quadratic chain).
|
||||
window.hwWobbleEllipse = function (cx, cy, rx, ry, seed, wobblePct) {
|
||||
wobblePct = wobblePct === undefined ? 3 : wobblePct;
|
||||
@@ -198,23 +269,230 @@
|
||||
return d;
|
||||
};
|
||||
|
||||
/* ---- pen-velocity ease: pure function of the path geometry ----
|
||||
Sample 25–75 curvature-adaptive points, speed ∝ 1/(1+k·curvature)
|
||||
floored at 0.22 (a pen slows into curves, never stalls), integrate
|
||||
ds/speed, invert to a normalized time→distance ease. Seek-safe:
|
||||
no per-frame state. */
|
||||
window.hwPenEase = function (pathEl, opts) {
|
||||
opts = opts || {};
|
||||
var kCurve = opts.kCurve !== undefined ? opts.kCurve : 10;
|
||||
var L = pathEl.getTotalLength();
|
||||
if (L <= 0)
|
||||
return {
|
||||
ease: function (t) {
|
||||
return t;
|
||||
},
|
||||
samples: 2,
|
||||
inkTime: 1,
|
||||
len: 0,
|
||||
};
|
||||
var PRE = 48;
|
||||
var pts = [];
|
||||
for (var i = 0; i <= PRE; i++) pts.push(pathEl.getPointAtLength((L * i) / PRE));
|
||||
var curv = [0];
|
||||
for (i = 1; i < PRE; i++) {
|
||||
var ax = pts[i].x - pts[i - 1].x,
|
||||
ay = pts[i].y - pts[i - 1].y;
|
||||
var bx = pts[i + 1].x - pts[i].x,
|
||||
by = pts[i + 1].y - pts[i].y;
|
||||
var la = Math.hypot(ax, ay) || 1e-6,
|
||||
lb = Math.hypot(bx, by) || 1e-6;
|
||||
var dot = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
|
||||
curv.push(Math.acos(dot) / ((la + lb) / 2));
|
||||
}
|
||||
curv.push(0);
|
||||
var mass = 0;
|
||||
for (i = 0; i < curv.length; i++) mass += curv[i];
|
||||
var N = Math.max(25, Math.min(75, Math.round(25 + mass * 18)));
|
||||
var seg = L / N;
|
||||
var times = [0];
|
||||
var t = 0;
|
||||
for (i = 1; i <= N; i++) {
|
||||
var u = ((i - 0.5) / N) * PRE;
|
||||
var i0 = Math.floor(u),
|
||||
f = u - i0;
|
||||
var c = curv[Math.min(i0, PRE)] * (1 - f) + curv[Math.min(i0 + 1, PRE)] * f;
|
||||
var speed = Math.max(1 / (1 + kCurve * c * 14), 0.22);
|
||||
t += seg / speed;
|
||||
times.push(t);
|
||||
}
|
||||
var total = times[N];
|
||||
for (i = 0; i <= N; i++) times[i] /= total;
|
||||
var easeFn = function (p) {
|
||||
if (p <= 0) return 0;
|
||||
if (p >= 1) return 1;
|
||||
var lo = 0,
|
||||
hi = N;
|
||||
while (hi - lo > 1) {
|
||||
var mid = (lo + hi) >> 1;
|
||||
if (times[mid] <= p) lo = mid;
|
||||
else hi = mid;
|
||||
}
|
||||
var span = times[hi] - times[lo] || 1e-9;
|
||||
return (lo + (p - times[lo]) / span) / N;
|
||||
};
|
||||
return { ease: easeFn, samples: N, inkTime: total, len: L };
|
||||
};
|
||||
|
||||
/* ---- stroke matrix: plain | soft | sharp | spray ------------------
|
||||
strokeType is a variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
must therefore ride a MASK clone (the visible dasharray never
|
||||
animates). spray = index-seeded deterministic dots along the path
|
||||
via hwHash: position a pure function of path parameter + seed; NO
|
||||
feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever
|
||||
hwStrokeApply says. Optionally pen-velocity eased (opts.pen: true). */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.pen ? window.hwPenEase(p, opts).ease : opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
// ---- self-preview (not part of the snippet you copy) ----
|
||||
var circle = document.getElementById("hw-b-circle");
|
||||
circle.setAttribute("d", window.hwWobbleEllipse(460, 620, 190, 130, 4, 3.2));
|
||||
circle.setAttribute("d", window.hwWobbleEllipse(210, 150, 190, 130, 4, 3.2));
|
||||
|
||||
document.getElementById("hw-b-tag-config").textContent = (
|
||||
'boil "' +
|
||||
CONFIG.boil +
|
||||
'" · stroke "' +
|
||||
CONFIG.strokeType +
|
||||
'"'
|
||||
).toUpperCase();
|
||||
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
/* draw-on entrances (strokeDashoffset — never x/y, the boil owns those) */
|
||||
[circle, document.getElementById("hw-b-arrow")].forEach(function (p, i) {
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
tl.to(p, { strokeDashoffset: 0, duration: 0.8, ease: "power2.inOut" }, 0.3 + i * 0.35);
|
||||
});
|
||||
/* the CONFIG circle rides the stroke matrix: texture from
|
||||
CONFIG.strokeType, draw-on through the mask-safe hwDrawOn with the
|
||||
pen-velocity ease (strokeDashoffset — never x/y, the boil owns those) */
|
||||
var applied = window.hwStrokeApply(circle, CONFIG.strokeType, { seed: 4, id: "hw-b-mask" });
|
||||
window.hwDrawOn(tl, applied, 0.3, 0.8, { pen: true });
|
||||
|
||||
/* raw draw-on entrance for the arrow (not routed through the matrix) */
|
||||
var arrow = document.getElementById("hw-b-arrow");
|
||||
var alen = arrow.getTotalLength();
|
||||
gsap.set(arrow, { strokeDasharray: alen, strokeDashoffset: alen });
|
||||
tl.to(arrow, { strokeDashoffset: 0, duration: 0.8, ease: "power2.inOut" }, 0.65);
|
||||
|
||||
gsap.set("#hw-b-word", { opacity: 0 });
|
||||
tl.to("#hw-b-word", { opacity: 1, duration: 0.5, ease: "power2.out" }, 1.1);
|
||||
|
||||
/* three boils with different characters, one dispatcher */
|
||||
window.hwBoil(tl, "#hw-b-circle-g", { amp: 2.2, rot: 0.4, frameDrop: 3, seed: 2 });
|
||||
/* three boils with different characters, one dispatcher: the circle
|
||||
rides the boil CONTROL (CONFIG.boil → authored pose); arrow + word
|
||||
keep raw hwBoil calls — the multi-character demo */
|
||||
window.hwBoilPose(tl, "#hw-b-circle-g", CONFIG.boil, { seed: 2 });
|
||||
window.hwBoil(tl, "#hw-b-arrow-g", { amp: 2.6, rot: 0.7, frameDrop: 1, seed: 5 });
|
||||
window.hwBoil(tl, "#hw-b-word", { amp: 1.2, rot: 0.35, frameDrop: 3, seed: 9 });
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "hw-boil",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Hand-Drawn Boil",
|
||||
"description": "Hand-drawn jitter that re-poses every N frames, as seek-safe helpers: pose derives from quantized timeline time through a seeded hash; includes the shared onUpdate dispatcher and a wobbled-ellipse path generator",
|
||||
"description": "Hand-drawn jitter that re-poses every N frames, as seek-safe helpers: pose derives from quantized timeline time through a seeded hash; includes the shared onUpdate dispatcher, a wobbled-ellipse path generator, the boil pose control (off/calm/lively over the family-locked frameDrop), a curvature-adaptive pen-velocity ease, and the completed stroke matrix (plain/soft/sharp/spray — spray is index-seeded deterministic dots, no turbulence) with texture-safe draw-on",
|
||||
"tags": ["handwritten", "effect", "annotation"],
|
||||
"files": [
|
||||
{
|
||||
@@ -16,5 +16,21 @@
|
||||
"target": "assets/fonts/Caveat-700-latin.woff2",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"name": "boil",
|
||||
"type": "variant",
|
||||
"values": ["off", "calm", "lively"],
|
||||
"default": "calm",
|
||||
"drives": "boil amp/rot as authored pairs over the family-locked frameDrop 3; seed locked"
|
||||
},
|
||||
{
|
||||
"name": "strokeType",
|
||||
"type": "variant",
|
||||
"values": ["plain", "soft", "sharp", "spray"],
|
||||
"default": "plain",
|
||||
"drives": "stroke texture of the drawn path (soft = slight blur; sharp = micro-dash dry marker; spray = seeded deterministic dot scatter) — textured types draw on via a mask clone so the texture itself never animates"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+425
-46
@@ -1,10 +1,18 @@
|
||||
<!doctype html>
|
||||
<!-- Demo plate for the catalog preview. Identical to hw-box-label.html except the
|
||||
root background, which is opaque here so the transparent overlay reads.
|
||||
<!-- Demo plate for the catalog preview. Same helpers as hw-box-label.html;
|
||||
the root background is opaque here so the transparent overlay reads, and
|
||||
the plate exercises the control surface with NON-DEFAULT states side by
|
||||
side (per the control-surface convention's demo requirement):
|
||||
- entrance "drop" (landing squash, label counter-scaled)
|
||||
- strokeType "spray" (seeded deterministic dots)
|
||||
- labelPos "inside"
|
||||
- boil "lively"
|
||||
Not installed by `hyperframes add` - registry-item.json lists hw-box-label.html only. -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Hand-Drawn Box Label — Demo</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<style>
|
||||
@font-face {
|
||||
@@ -26,17 +34,30 @@
|
||||
}
|
||||
/* ---- hw-box-label ----------------------------------------------------
|
||||
Rounded hand-drawn box + marker label (map-label style annotations).
|
||||
Requires the hw-boil helpers in the host. */
|
||||
Requires the hw-boil helpers in the host.
|
||||
Wrapper anatomy (outer to inner): .hw-box = position/move wrapper
|
||||
(entrances/exits target THIS, never the boil element) > .hw-bx-deform
|
||||
= deform wrapper (g21 squash scale, transform-origin 50% 100%) >
|
||||
.hw-bx-boil = boil target (hwBoilPose owns its x/y/rotation) >
|
||||
content (svg + label). */
|
||||
.hw-box {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hw-box .hw-bx-deform,
|
||||
.hw-box .hw-bx-boil {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
.hw-box svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
.hw-box path {
|
||||
/* :not([stroke]) keeps texture machinery out of the ink rule: the
|
||||
stroke-matrix mask clone carries a stroke="#fff" attribute and must
|
||||
not inherit the ink color/width (CSS would beat its attributes). */
|
||||
.hw-box path:not([stroke]) {
|
||||
fill: none;
|
||||
stroke: var(--hw-box-ink, var(--hw-ink, #f4f2ec));
|
||||
stroke-width: var(--hw-box-w, 6);
|
||||
@@ -55,8 +76,8 @@
|
||||
opacity: 0;
|
||||
text-shadow: 0 2px 14px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
/* self-preview scene */
|
||||
#hw-bx-scene {
|
||||
/* demo scene */
|
||||
#hw-bxd-scene {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(140deg, #4a3560 0%, #1c1423 100%);
|
||||
@@ -64,37 +85,72 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Self-preview. WIRING: copy the .hw-box CSS + markup pattern, the
|
||||
hwWobbleRect / hwBoxBuild / hwBoxOn / hwBoxOff helpers, AND the
|
||||
hw-boil helpers. Size the wrapper over your target, then:
|
||||
|
||||
hwBoxBuild("#hw-my-box", { seed: 3, label: "Europe", labelPos: "above" });
|
||||
hwBoxOn(tl, "#hw-my-box", 2.0);
|
||||
hwBoil(tl, "#hw-my-box", { frameDrop: 3, seed: 3 });
|
||||
hwBoxOff(tl, "#hw-my-box", 8.0);
|
||||
-->
|
||||
<div
|
||||
id="hw-bx-root"
|
||||
data-composition-id="hw-box-label"
|
||||
id="hw-bxd-root"
|
||||
data-composition-id="hw-box-label-demo"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-duration="6"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div id="hw-bx-scene"></div>
|
||||
<div id="hw-bxd-scene"></div>
|
||||
<!-- entrance: "drop" (non-default) -->
|
||||
<div
|
||||
class="hw-box"
|
||||
id="hw-bx-demo"
|
||||
style="left: 700px; top: 420px; width: 420px; height: 230px"
|
||||
id="hw-bxd-drop"
|
||||
style="left: 170px; top: 260px; width: 380px; height: 210px"
|
||||
>
|
||||
<svg viewBox="0 0 420 230"><path id="hw-bx-demo-path"></path></svg>
|
||||
<div class="hw-bx-label" id="hw-bx-demo-label"></div>
|
||||
<div class="hw-bx-deform">
|
||||
<div class="hw-bx-boil">
|
||||
<svg viewBox="0 0 380 210"><path id="hw-bxd-drop-path"></path></svg>
|
||||
<div class="hw-bx-label" id="hw-bxd-drop-label"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- strokeType: "spray" (non-default) -->
|
||||
<div
|
||||
class="hw-box"
|
||||
id="hw-bxd-spray"
|
||||
style="left: 1220px; top: 260px; width: 480px; height: 240px"
|
||||
>
|
||||
<div class="hw-bx-deform">
|
||||
<div class="hw-bx-boil">
|
||||
<svg viewBox="0 0 480 240"><path id="hw-bxd-spray-path"></path></svg>
|
||||
<div class="hw-bx-label" id="hw-bxd-spray-label"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- labelPos: "inside" (non-default) -->
|
||||
<div
|
||||
class="hw-box"
|
||||
id="hw-bxd-inside"
|
||||
style="left: 170px; top: 680px; width: 420px; height: 230px"
|
||||
>
|
||||
<div class="hw-bx-deform">
|
||||
<div class="hw-bx-boil">
|
||||
<svg viewBox="0 0 420 230"><path id="hw-bxd-inside-path"></path></svg>
|
||||
<div class="hw-bx-label" id="hw-bxd-inside-label"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- boil: "lively" (non-default) -->
|
||||
<div
|
||||
class="hw-box"
|
||||
id="hw-bxd-lively"
|
||||
style="left: 1250px; top: 680px; width: 430px; height: 240px"
|
||||
>
|
||||
<div class="hw-bx-deform">
|
||||
<div class="hw-bx-boil">
|
||||
<svg viewBox="0 0 430 240"><path id="hw-bxd-lively-path"></path></svg>
|
||||
<div class="hw-bx-label" id="hw-bxd-lively-label"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="hw-bx-drv"
|
||||
id="hw-bxd-drv"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="4"
|
||||
data-duration="6"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
@@ -103,6 +159,25 @@
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
var CONFIG = {
|
||||
// ── Content (freely editable — not counted) ─────────────────────
|
||||
// per-instance content rides hwBoxBuild(target, opts):
|
||||
// opts.label (the label text) · opts.seed (which wobble pose the
|
||||
// box gets — an identity pick, not a feel lever)
|
||||
|
||||
// ── Controls (family base + 3 item-specific; comment grammar is the published-parameter list) ──
|
||||
entrance: "draw", // variant: "draw" | "drop" — draw = stroke draw-on + label fade (the shipped entrance); drop = landing squash: box+label drop in, contact squash on the deform wrapper, bouncy spring recovery; travel/duration derived, never exposed
|
||||
strokeType: "plain", // variant: "plain" | "soft" | "sharp" | "spray" — authored stroke texture on the box path (constants per type; draw-on composes via the mask clone)
|
||||
labelPos: "above", // variant: "above" | "inside" — label above the box or vertically centered inside it
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3; seed locked
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// draw duration/ease, drop travel/duration derivation (≈1.9× box
|
||||
// height into ~0.5s, power2.in), softness tier 0.11 (UI card),
|
||||
// spring registers, stroke-type constants, boil pose tables,
|
||||
// label fade timing, wobble amplitude
|
||||
};
|
||||
|
||||
/* ---- hw-boil helpers (shared family runtime — copy once per host) ---- */
|
||||
window.hwOnUpdate = function (tl, fn) {
|
||||
if (!tl.__hwRenders) {
|
||||
@@ -137,6 +212,252 @@
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
/* ---- spring library (authored feel registers) -------------------
|
||||
The factory owns the recovery duration — consume rec.duration
|
||||
verbatim, never scale or multiply it. */
|
||||
var LN1000 = Math.log(1000);
|
||||
var FEELS = {
|
||||
snappy: { zeta: 0.9, response: 0.22 },
|
||||
bouncy: { zeta: 0.5, response: 0.4 },
|
||||
};
|
||||
window.springEase = function (opts) {
|
||||
var f = opts.feel ? FEELS[opts.feel] : opts;
|
||||
var zeta = f.zeta,
|
||||
response = f.response;
|
||||
var w0 = (2 * Math.PI) / response;
|
||||
var T = LN1000 / (zeta * w0);
|
||||
var x;
|
||||
if (zeta < 1) {
|
||||
var wd = w0 * Math.sqrt(1 - zeta * zeta);
|
||||
x = function (t) {
|
||||
return (
|
||||
1 -
|
||||
Math.exp(-zeta * w0 * t) *
|
||||
(Math.cos(wd * t) + ((zeta * w0) / wd) * Math.sin(wd * t))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
x = function (t) {
|
||||
return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
||||
};
|
||||
}
|
||||
var xT = x(T);
|
||||
return {
|
||||
ease: function (p) {
|
||||
return p >= 1 ? 1 : x(p * T) / xT;
|
||||
},
|
||||
duration: T,
|
||||
zeta: zeta,
|
||||
response: response,
|
||||
};
|
||||
};
|
||||
|
||||
/* ---- stroke matrix: plain | soft | sharp | spray ----------------
|
||||
strokeType is a g09 variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
must therefore ride a MASK clone (the visible dasharray never
|
||||
animates). spray = index-seeded deterministic dots along the path
|
||||
via hwHash: position a pure function of path parameter + seed; NO
|
||||
feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever
|
||||
hwStrokeApply says. (opts.pen requires the hwPenEase helper from
|
||||
the write-on items — not bundled here, never passed here.) */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.pen ? window.hwPenEase(p, opts).ease : opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
/* ---- g21 volume squash-stretch on hw impacts ----------------------
|
||||
S = min(SOFTNESS × v/V_REF, 0.25), v measured numerically from the
|
||||
entrance ease end-slope; scaleAlong × scalePerp ≈ 1 every frame
|
||||
(one proxy drives both); in-flight stretch E = 0.55 × SOFTNESS,
|
||||
velocity term clamped; contact squash ≤2 frames + 1-frame hold;
|
||||
recovery on a spring register with the factory duration consumed
|
||||
verbatim. Deformation lives on a WRAPPER (boil owns x/y/rotation of
|
||||
its own target; type counter-scaled to net identity inside). All
|
||||
per-frame application runs through hwOnUpdate.
|
||||
Volume assertion: |along×perp − 1| > 0.01 → console.error (fails check). */
|
||||
window.hwImpactSquash = function (tl, opts) {
|
||||
var wrap = opts.wrap; // deform wrapper (transform-origin 50% 100%)
|
||||
var label = opts.label || null; // counter-scaled type inside
|
||||
var travel = opts.travel;
|
||||
var at = opts.at;
|
||||
var dur = opts.dur;
|
||||
var softness = opts.softness !== undefined ? opts.softness : 0.11;
|
||||
var V_REF = 2000;
|
||||
var E = 0.55 * softness;
|
||||
var FPS = opts.fps || 30;
|
||||
var dropEase = opts.dropEase || gsap.parseEase("power2.in");
|
||||
// measured impact velocity (px/s) from the entrance ease end-slope
|
||||
var eps = 1e-4;
|
||||
var v = ((dropEase(1) - dropEase(1 - eps)) / eps) * (travel / dur);
|
||||
var S = Math.min(softness * (v / V_REF), 0.25);
|
||||
var rec = window.springEase({ feel: opts.feel || "bouncy" });
|
||||
var st = { ph: 0, s: 0 }; // ph: flight progress (linear), s: contact squash
|
||||
gsap.set(wrap, { transformOrigin: "50% 100%" });
|
||||
var setBoth = function (along) {
|
||||
gsap.set(wrap, { scaleY: along, scaleX: 1 / along });
|
||||
if (label) gsap.set(label, { scaleY: 1 / along, scaleX: along });
|
||||
// assert on the APPLIED transforms — catches any competing tween that
|
||||
// breaks the one-proxy-drives-both-axes law
|
||||
var ay = parseFloat(gsap.getProperty(wrap, "scaleY"));
|
||||
var ax = parseFloat(gsap.getProperty(wrap, "scaleX"));
|
||||
if (Math.abs(ax * ay - 1) > 0.01)
|
||||
console.error("hw volume law violated: scaleX*scaleY=" + (ax * ay).toFixed(4));
|
||||
};
|
||||
window.hwOnUpdate(tl, function () {
|
||||
if (st.ph > 0 && st.ph < 1) {
|
||||
var p = st.ph;
|
||||
var vp = ((dropEase(Math.min(p + eps, 1)) - dropEase(p)) / eps) * (travel / dur);
|
||||
setBoth(1 + E * Math.min(Math.abs(vp) / V_REF, 1));
|
||||
} else {
|
||||
setBoth(1 - st.s);
|
||||
}
|
||||
});
|
||||
// flight: wrapper position tween + linear proxy (same window)
|
||||
gsap.set(opts.moveTarget || wrap, { y: -travel }); // off-stage until entrance
|
||||
tl.fromTo(
|
||||
opts.moveTarget || wrap,
|
||||
{ y: -travel },
|
||||
{ y: 0, duration: dur, ease: dropEase, immediateRender: false },
|
||||
at,
|
||||
);
|
||||
tl.fromTo(st, { ph: 0 }, { ph: 1, duration: dur, ease: "none" }, at);
|
||||
// contact: squash in 2 frames, 1-frame hold, spring home (factory duration verbatim)
|
||||
tl.to(st, { s: S, duration: 2 / FPS, ease: "power3.out" }, at + dur);
|
||||
tl.to(st, { s: 0, duration: rec.duration, ease: rec.ease }, at + dur + 3 / FPS);
|
||||
return {
|
||||
v: Math.round(v),
|
||||
S: +S.toFixed(3),
|
||||
T: +rec.duration.toFixed(3),
|
||||
feel: opts.feel || "bouncy",
|
||||
};
|
||||
};
|
||||
|
||||
// ---- hw-box helpers (copy these too) ----
|
||||
// Wobbled rounded-rect path in a w x h box.
|
||||
@@ -168,6 +489,13 @@
|
||||
d += " Q " + wob(31) + " " + wob(32) + " " + (r + wob(33)) + " " + wob(34);
|
||||
return d;
|
||||
};
|
||||
// bounded variant resolution (g09): unknown value → declared default
|
||||
// + console error. strokeType/boil bound inside their own helpers.
|
||||
function hwBxPick(name, val, values, dflt) {
|
||||
if (values.indexOf(val) !== -1) return val;
|
||||
console.error("hw: unknown " + name + ' "' + val + '" — falling back to "' + dflt + '"');
|
||||
return dflt;
|
||||
}
|
||||
window.hwBoxBuild = function (target, opts) {
|
||||
opts = opts || {};
|
||||
var el = document.querySelector(target);
|
||||
@@ -175,13 +503,35 @@
|
||||
var vb = svg.getAttribute("viewBox").split(" ");
|
||||
var W = parseFloat(vb[2]),
|
||||
H = parseFloat(vb[3]);
|
||||
el.querySelector("path").setAttribute(
|
||||
"d",
|
||||
window.hwWobbleRect(W, H, Math.min(26, W * 0.1), opts.seed || 1),
|
||||
);
|
||||
var path = el.querySelector("path");
|
||||
path.setAttribute("d", window.hwWobbleRect(W, H, Math.min(26, W * 0.1), opts.seed || 1));
|
||||
// resolve the control set: CONFIG defaults; per-instance opts
|
||||
// overrides accepted (declared enum values only — bounded the same)
|
||||
var cfg = {
|
||||
seed: opts.seed || 1,
|
||||
entrance: hwBxPick(
|
||||
"entrance",
|
||||
opts.entrance !== undefined ? opts.entrance : CONFIG.entrance,
|
||||
["draw", "drop"],
|
||||
"draw",
|
||||
),
|
||||
labelPos: hwBxPick(
|
||||
"labelPos",
|
||||
opts.labelPos !== undefined ? opts.labelPos : CONFIG.labelPos,
|
||||
["above", "inside"],
|
||||
"above",
|
||||
),
|
||||
strokeType: opts.strokeType !== undefined ? opts.strokeType : CONFIG.strokeType,
|
||||
boil: opts.boil !== undefined ? opts.boil : CONFIG.boil,
|
||||
};
|
||||
el.__hwCfg = cfg;
|
||||
el.__hwApplied = window.hwStrokeApply(path, cfg.strokeType, {
|
||||
seed: cfg.seed,
|
||||
id: "hw-bx-m-" + target.replace(/[^a-zA-Z0-9_-]/g, ""),
|
||||
});
|
||||
var label = el.querySelector(".hw-bx-label");
|
||||
label.textContent = opts.label || "";
|
||||
if ((opts.labelPos || "above") === "above") {
|
||||
if (cfg.labelPos === "above") {
|
||||
label.style.top = "-64px";
|
||||
} else {
|
||||
label.style.top = "50%";
|
||||
@@ -189,28 +539,57 @@
|
||||
}
|
||||
};
|
||||
window.hwBoxOn = function (tl, target, at) {
|
||||
var p = document.querySelector(target + " path");
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
tl.to(p, { strokeDashoffset: 0, duration: 0.75, ease: "power2.inOut" }, at);
|
||||
tl.to(
|
||||
document.querySelector(target + " .hw-bx-label"),
|
||||
{ opacity: 1, duration: 0.4, ease: "power2.out" },
|
||||
at + 0.55,
|
||||
);
|
||||
var el = document.querySelector(target);
|
||||
var cfg = el.__hwCfg;
|
||||
var label = el.querySelector(".hw-bx-label");
|
||||
if (cfg.entrance === "drop") {
|
||||
// landing squash: the whole box+label drops in and lands with a
|
||||
// contact squash on the deform wrapper; the label is counter-
|
||||
// scaled to net identity inside the shared apply (type NEVER
|
||||
// takes a non-uniform scale). Travel/duration derivation LOCKED:
|
||||
// ≈1.9× box height into ~0.5s of power2.in.
|
||||
gsap.set(label, { opacity: 1 }); // the label rides the drop
|
||||
gsap.set(el, { opacity: 0 }); // hidden until the entrance
|
||||
tl.set(el, { opacity: 1 }, at);
|
||||
window.hwImpactSquash(tl, {
|
||||
wrap: el.querySelector(".hw-bx-deform"),
|
||||
moveTarget: el,
|
||||
label: label,
|
||||
travel: Math.round(el.offsetHeight * 1.9),
|
||||
at: at,
|
||||
dur: 0.5,
|
||||
softness: 0.11, // UI-card tier
|
||||
feel: "bouncy",
|
||||
});
|
||||
} else {
|
||||
// "draw" — the shipped entrance, routed through hwDrawOn so
|
||||
// sharp/spray textures compose (the mask clone animates; the
|
||||
// visible texture dasharray never does)
|
||||
window.hwDrawOn(tl, el.__hwApplied, at, 0.75);
|
||||
tl.to(label, { opacity: 1, duration: 0.4, ease: "power2.out" }, at + 0.55);
|
||||
}
|
||||
window.hwBoilPose(tl, target + " .hw-bx-boil", cfg.boil, { seed: cfg.seed });
|
||||
};
|
||||
window.hwBoxOff = function (tl, target, at) {
|
||||
tl.to(target, { opacity: 0, duration: 0.35, ease: "power2.in" }, at);
|
||||
};
|
||||
|
||||
// ---- self-preview ----
|
||||
window.hwBoxBuild("#hw-bx-demo", { seed: 3, label: "Europe", labelPos: "above" });
|
||||
// ---- demo choreography: four instances, one non-default state each ----
|
||||
window.hwBoxBuild("#hw-bxd-drop", { seed: 3, label: "drop entrance", entrance: "drop" });
|
||||
window.hwBoxBuild("#hw-bxd-spray", { seed: 5, label: "spray stroke", strokeType: "spray" });
|
||||
window.hwBoxBuild("#hw-bxd-inside", { seed: 7, label: "label inside", labelPos: "inside" });
|
||||
window.hwBoxBuild("#hw-bxd-lively", { seed: 9, label: "boil lively", boil: "lively" });
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.hwBoxOn(tl, "#hw-bx-demo", 0.4);
|
||||
window.hwBoil(tl, "#hw-bx-demo", { amp: 1.8, rot: 0.4, frameDrop: 3, seed: 3 });
|
||||
window.hwBoxOff(tl, "#hw-bx-demo", 3.4);
|
||||
tl.set("#hw-bx-root", { visibility: "hidden" }, 3.98);
|
||||
window.__timelines["hw-box-label"] = tl;
|
||||
window.hwBoxOn(tl, "#hw-bxd-drop", 0.5);
|
||||
window.hwBoxOn(tl, "#hw-bxd-spray", 0.7);
|
||||
window.hwBoxOn(tl, "#hw-bxd-inside", 1.0);
|
||||
window.hwBoxOn(tl, "#hw-bxd-lively", 1.3);
|
||||
window.hwBoxOff(tl, "#hw-bxd-drop", 5.2);
|
||||
window.hwBoxOff(tl, "#hw-bxd-spray", 5.2);
|
||||
window.hwBoxOff(tl, "#hw-bxd-inside", 5.2);
|
||||
window.hwBoxOff(tl, "#hw-bxd-lively", 5.2);
|
||||
tl.set("#hw-bxd-root", { visibility: "hidden" }, 5.98);
|
||||
window.__timelines["hw-box-label-demo"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
+361
-24
@@ -23,17 +23,30 @@
|
||||
}
|
||||
/* ---- hw-box-label ----------------------------------------------------
|
||||
Rounded hand-drawn box + marker label (map-label style annotations).
|
||||
Requires the hw-boil helpers in the host. */
|
||||
Requires the hw-boil helpers in the host.
|
||||
Wrapper anatomy (outer to inner): .hw-box = position/move wrapper
|
||||
(entrances/exits target THIS, never the boil element) > .hw-bx-deform
|
||||
= deform wrapper (g21 squash scale, transform-origin 50% 100%) >
|
||||
.hw-bx-boil = boil target (hwBoilPose owns its x/y/rotation) >
|
||||
content (svg + label). */
|
||||
.hw-box {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hw-box .hw-bx-deform,
|
||||
.hw-box .hw-bx-boil {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
.hw-box svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
.hw-box path {
|
||||
/* :not([stroke]) keeps texture machinery out of the ink rule: the
|
||||
stroke-matrix mask clone carries a stroke="#fff" attribute and must
|
||||
not inherit the ink color/width (CSS would beat its attributes). */
|
||||
.hw-box path:not([stroke]) {
|
||||
fill: none;
|
||||
stroke: var(--hw-box-ink, var(--hw-ink, #f4f2ec));
|
||||
stroke-width: var(--hw-box-w, 6);
|
||||
@@ -61,14 +74,20 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Self-preview. WIRING: copy the .hw-box CSS + markup pattern, the
|
||||
hwWobbleRect / hwBoxBuild / hwBoxOn / hwBoxOff helpers, AND the
|
||||
hw-boil helpers. Size the wrapper over your target, then:
|
||||
<!-- Self-preview. WIRING: copy the .hw-box CSS + markup pattern (position
|
||||
wrapper > .hw-bx-deform > .hw-bx-boil > svg + label), the CONFIG
|
||||
block, the hw family runtime, the spring library, the stroke matrix,
|
||||
AND the hwWobbleRect / hwBoxBuild / hwBoxOn / hwBoxOff helpers. Size
|
||||
the wrapper over your target, then wire with positions only:
|
||||
|
||||
hwBoxBuild("#hw-my-box", { seed: 3, label: "Europe", labelPos: "above" });
|
||||
hwBoxOn(tl, "#hw-my-box", 2.0);
|
||||
hwBoil(tl, "#hw-my-box", { frameDrop: 3, seed: 3 });
|
||||
hwBoxBuild("#hw-my-box", { seed: 3, label: "Europe" });
|
||||
hwBoxOn(tl, "#hw-my-box", 2.0); // entrance + boil per CONFIG
|
||||
hwBoxOff(tl, "#hw-my-box", 8.0);
|
||||
|
||||
The levers live in CONFIG (entrance / strokeType / labelPos / boil).
|
||||
Multi-instance hosts may override per instance via hwBoxBuild opts
|
||||
using the SAME declared values (bounded identically). The helpers own
|
||||
all durations/eases internally — no raw tween args in host code.
|
||||
-->
|
||||
<div
|
||||
id="hw-bx-root"
|
||||
@@ -84,9 +103,13 @@
|
||||
id="hw-bx-demo"
|
||||
style="left: 700px; top: 420px; width: 420px; height: 230px"
|
||||
>
|
||||
<div class="hw-bx-deform">
|
||||
<div class="hw-bx-boil">
|
||||
<svg viewBox="0 0 420 230"><path id="hw-bx-demo-path"></path></svg>
|
||||
<div class="hw-bx-label" id="hw-bx-demo-label"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="hw-bx-drv"
|
||||
class="clip"
|
||||
@@ -100,6 +123,25 @@
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
var CONFIG = {
|
||||
// ── Content (freely editable — not counted) ─────────────────────
|
||||
// per-instance content rides hwBoxBuild(target, opts):
|
||||
// opts.label (the label text) · opts.seed (which wobble pose the
|
||||
// box gets — an identity pick, not a feel lever)
|
||||
|
||||
// ── Controls (family base + 3 item-specific; comment grammar is the published-parameter list) ──
|
||||
entrance: "draw", // variant: "draw" | "drop" — draw = stroke draw-on + label fade (the shipped entrance); drop = landing squash: box+label drop in, contact squash on the deform wrapper, bouncy spring recovery; travel/duration derived, never exposed
|
||||
strokeType: "plain", // variant: "plain" | "soft" | "sharp" | "spray" — authored stroke texture on the box path (constants per type; draw-on composes via the mask clone)
|
||||
labelPos: "above", // variant: "above" | "inside" — label above the box or vertically centered inside it
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3; seed locked
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// draw duration/ease, drop travel/duration derivation (≈1.9× box
|
||||
// height into ~0.5s, power2.in), softness tier 0.11 (UI card),
|
||||
// spring registers, stroke-type constants, boil pose tables,
|
||||
// label fade timing, wobble amplitude
|
||||
};
|
||||
|
||||
/* ---- hw-boil helpers (shared family runtime — copy once per host) ---- */
|
||||
window.hwOnUpdate = function (tl, fn) {
|
||||
if (!tl.__hwRenders) {
|
||||
@@ -134,6 +176,252 @@
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
|
||||
/* ---- spring library (authored feel registers) -------------------
|
||||
The factory owns the recovery duration — consume rec.duration
|
||||
verbatim, never scale or multiply it. */
|
||||
var LN1000 = Math.log(1000);
|
||||
var FEELS = {
|
||||
snappy: { zeta: 0.9, response: 0.22 },
|
||||
bouncy: { zeta: 0.5, response: 0.4 },
|
||||
};
|
||||
window.springEase = function (opts) {
|
||||
var f = opts.feel ? FEELS[opts.feel] : opts;
|
||||
var zeta = f.zeta,
|
||||
response = f.response;
|
||||
var w0 = (2 * Math.PI) / response;
|
||||
var T = LN1000 / (zeta * w0);
|
||||
var x;
|
||||
if (zeta < 1) {
|
||||
var wd = w0 * Math.sqrt(1 - zeta * zeta);
|
||||
x = function (t) {
|
||||
return (
|
||||
1 -
|
||||
Math.exp(-zeta * w0 * t) *
|
||||
(Math.cos(wd * t) + ((zeta * w0) / wd) * Math.sin(wd * t))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
x = function (t) {
|
||||
return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
||||
};
|
||||
}
|
||||
var xT = x(T);
|
||||
return {
|
||||
ease: function (p) {
|
||||
return p >= 1 ? 1 : x(p * T) / xT;
|
||||
},
|
||||
duration: T,
|
||||
zeta: zeta,
|
||||
response: response,
|
||||
};
|
||||
};
|
||||
|
||||
/* ---- stroke matrix: plain | soft | sharp | spray ----------------
|
||||
strokeType is a g09 variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
must therefore ride a MASK clone (the visible dasharray never
|
||||
animates). spray = index-seeded deterministic dots along the path
|
||||
via hwHash: position a pure function of path parameter + seed; NO
|
||||
feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever
|
||||
hwStrokeApply says. (opts.pen requires the hwPenEase helper from
|
||||
the write-on items — not bundled here, never passed here.) */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.pen ? window.hwPenEase(p, opts).ease : opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
/* ---- g21 volume squash-stretch on hw impacts ----------------------
|
||||
S = min(SOFTNESS × v/V_REF, 0.25), v measured numerically from the
|
||||
entrance ease end-slope; scaleAlong × scalePerp ≈ 1 every frame
|
||||
(one proxy drives both); in-flight stretch E = 0.55 × SOFTNESS,
|
||||
velocity term clamped; contact squash ≤2 frames + 1-frame hold;
|
||||
recovery on a spring register with the factory duration consumed
|
||||
verbatim. Deformation lives on a WRAPPER (boil owns x/y/rotation of
|
||||
its own target; type counter-scaled to net identity inside). All
|
||||
per-frame application runs through hwOnUpdate.
|
||||
Volume assertion: |along×perp − 1| > 0.01 → console.error (fails check). */
|
||||
window.hwImpactSquash = function (tl, opts) {
|
||||
var wrap = opts.wrap; // deform wrapper (transform-origin 50% 100%)
|
||||
var label = opts.label || null; // counter-scaled type inside
|
||||
var travel = opts.travel;
|
||||
var at = opts.at;
|
||||
var dur = opts.dur;
|
||||
var softness = opts.softness !== undefined ? opts.softness : 0.11;
|
||||
var V_REF = 2000;
|
||||
var E = 0.55 * softness;
|
||||
var FPS = opts.fps || 30;
|
||||
var dropEase = opts.dropEase || gsap.parseEase("power2.in");
|
||||
// measured impact velocity (px/s) from the entrance ease end-slope
|
||||
var eps = 1e-4;
|
||||
var v = ((dropEase(1) - dropEase(1 - eps)) / eps) * (travel / dur);
|
||||
var S = Math.min(softness * (v / V_REF), 0.25);
|
||||
var rec = window.springEase({ feel: opts.feel || "bouncy" });
|
||||
var st = { ph: 0, s: 0 }; // ph: flight progress (linear), s: contact squash
|
||||
gsap.set(wrap, { transformOrigin: "50% 100%" });
|
||||
var setBoth = function (along) {
|
||||
gsap.set(wrap, { scaleY: along, scaleX: 1 / along });
|
||||
if (label) gsap.set(label, { scaleY: 1 / along, scaleX: along });
|
||||
// assert on the APPLIED transforms — catches any competing tween that
|
||||
// breaks the one-proxy-drives-both-axes law
|
||||
var ay = parseFloat(gsap.getProperty(wrap, "scaleY"));
|
||||
var ax = parseFloat(gsap.getProperty(wrap, "scaleX"));
|
||||
if (Math.abs(ax * ay - 1) > 0.01)
|
||||
console.error("hw volume law violated: scaleX*scaleY=" + (ax * ay).toFixed(4));
|
||||
};
|
||||
window.hwOnUpdate(tl, function () {
|
||||
if (st.ph > 0 && st.ph < 1) {
|
||||
var p = st.ph;
|
||||
var vp = ((dropEase(Math.min(p + eps, 1)) - dropEase(p)) / eps) * (travel / dur);
|
||||
setBoth(1 + E * Math.min(Math.abs(vp) / V_REF, 1));
|
||||
} else {
|
||||
setBoth(1 - st.s);
|
||||
}
|
||||
});
|
||||
// flight: wrapper position tween + linear proxy (same window)
|
||||
gsap.set(opts.moveTarget || wrap, { y: -travel }); // off-stage until entrance
|
||||
tl.fromTo(
|
||||
opts.moveTarget || wrap,
|
||||
{ y: -travel },
|
||||
{ y: 0, duration: dur, ease: dropEase, immediateRender: false },
|
||||
at,
|
||||
);
|
||||
tl.fromTo(st, { ph: 0 }, { ph: 1, duration: dur, ease: "none" }, at);
|
||||
// contact: squash in 2 frames, 1-frame hold, spring home (factory duration verbatim)
|
||||
tl.to(st, { s: S, duration: 2 / FPS, ease: "power3.out" }, at + dur);
|
||||
tl.to(st, { s: 0, duration: rec.duration, ease: rec.ease }, at + dur + 3 / FPS);
|
||||
return {
|
||||
v: Math.round(v),
|
||||
S: +S.toFixed(3),
|
||||
T: +rec.duration.toFixed(3),
|
||||
feel: opts.feel || "bouncy",
|
||||
};
|
||||
};
|
||||
|
||||
// ---- hw-box helpers (copy these too) ----
|
||||
// Wobbled rounded-rect path in a w x h box.
|
||||
@@ -165,6 +453,13 @@
|
||||
d += " Q " + wob(31) + " " + wob(32) + " " + (r + wob(33)) + " " + wob(34);
|
||||
return d;
|
||||
};
|
||||
// bounded variant resolution (g09): unknown value → declared default
|
||||
// + console error. strokeType/boil bound inside their own helpers.
|
||||
function hwBxPick(name, val, values, dflt) {
|
||||
if (values.indexOf(val) !== -1) return val;
|
||||
console.error("hw: unknown " + name + ' "' + val + '" — falling back to "' + dflt + '"');
|
||||
return dflt;
|
||||
}
|
||||
window.hwBoxBuild = function (target, opts) {
|
||||
opts = opts || {};
|
||||
var el = document.querySelector(target);
|
||||
@@ -172,13 +467,35 @@
|
||||
var vb = svg.getAttribute("viewBox").split(" ");
|
||||
var W = parseFloat(vb[2]),
|
||||
H = parseFloat(vb[3]);
|
||||
el.querySelector("path").setAttribute(
|
||||
"d",
|
||||
window.hwWobbleRect(W, H, Math.min(26, W * 0.1), opts.seed || 1),
|
||||
);
|
||||
var path = el.querySelector("path");
|
||||
path.setAttribute("d", window.hwWobbleRect(W, H, Math.min(26, W * 0.1), opts.seed || 1));
|
||||
// resolve the control set: CONFIG defaults; per-instance opts
|
||||
// overrides accepted (declared enum values only — bounded the same)
|
||||
var cfg = {
|
||||
seed: opts.seed || 1,
|
||||
entrance: hwBxPick(
|
||||
"entrance",
|
||||
opts.entrance !== undefined ? opts.entrance : CONFIG.entrance,
|
||||
["draw", "drop"],
|
||||
"draw",
|
||||
),
|
||||
labelPos: hwBxPick(
|
||||
"labelPos",
|
||||
opts.labelPos !== undefined ? opts.labelPos : CONFIG.labelPos,
|
||||
["above", "inside"],
|
||||
"above",
|
||||
),
|
||||
strokeType: opts.strokeType !== undefined ? opts.strokeType : CONFIG.strokeType,
|
||||
boil: opts.boil !== undefined ? opts.boil : CONFIG.boil,
|
||||
};
|
||||
el.__hwCfg = cfg;
|
||||
el.__hwApplied = window.hwStrokeApply(path, cfg.strokeType, {
|
||||
seed: cfg.seed,
|
||||
id: "hw-bx-m-" + target.replace(/[^a-zA-Z0-9_-]/g, ""),
|
||||
});
|
||||
var label = el.querySelector(".hw-bx-label");
|
||||
label.textContent = opts.label || "";
|
||||
if ((opts.labelPos || "above") === "above") {
|
||||
if (cfg.labelPos === "above") {
|
||||
label.style.top = "-64px";
|
||||
} else {
|
||||
label.style.top = "50%";
|
||||
@@ -186,25 +503,45 @@
|
||||
}
|
||||
};
|
||||
window.hwBoxOn = function (tl, target, at) {
|
||||
var p = document.querySelector(target + " path");
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
tl.to(p, { strokeDashoffset: 0, duration: 0.75, ease: "power2.inOut" }, at);
|
||||
tl.to(
|
||||
document.querySelector(target + " .hw-bx-label"),
|
||||
{ opacity: 1, duration: 0.4, ease: "power2.out" },
|
||||
at + 0.55,
|
||||
);
|
||||
var el = document.querySelector(target);
|
||||
var cfg = el.__hwCfg;
|
||||
var label = el.querySelector(".hw-bx-label");
|
||||
if (cfg.entrance === "drop") {
|
||||
// landing squash: the whole box+label drops in and lands with a
|
||||
// contact squash on the deform wrapper; the label is counter-
|
||||
// scaled to net identity inside the shared apply (type NEVER
|
||||
// takes a non-uniform scale). Travel/duration derivation LOCKED:
|
||||
// ≈1.9× box height into ~0.5s of power2.in.
|
||||
gsap.set(label, { opacity: 1 }); // the label rides the drop
|
||||
gsap.set(el, { opacity: 0 }); // hidden until the entrance
|
||||
tl.set(el, { opacity: 1 }, at);
|
||||
window.hwImpactSquash(tl, {
|
||||
wrap: el.querySelector(".hw-bx-deform"),
|
||||
moveTarget: el,
|
||||
label: label,
|
||||
travel: Math.round(el.offsetHeight * 1.9),
|
||||
at: at,
|
||||
dur: 0.5,
|
||||
softness: 0.11, // UI-card tier
|
||||
feel: "bouncy",
|
||||
});
|
||||
} else {
|
||||
// "draw" — the shipped entrance, routed through hwDrawOn so
|
||||
// sharp/spray textures compose (the mask clone animates; the
|
||||
// visible texture dasharray never does)
|
||||
window.hwDrawOn(tl, el.__hwApplied, at, 0.75);
|
||||
tl.to(label, { opacity: 1, duration: 0.4, ease: "power2.out" }, at + 0.55);
|
||||
}
|
||||
window.hwBoilPose(tl, target + " .hw-bx-boil", cfg.boil, { seed: cfg.seed });
|
||||
};
|
||||
window.hwBoxOff = function (tl, target, at) {
|
||||
tl.to(target, { opacity: 0, duration: 0.35, ease: "power2.in" }, at);
|
||||
};
|
||||
|
||||
// ---- self-preview ----
|
||||
window.hwBoxBuild("#hw-bx-demo", { seed: 3, label: "Europe", labelPos: "above" });
|
||||
window.hwBoxBuild("#hw-bx-demo", { seed: 3, label: "Europe" });
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.hwBoxOn(tl, "#hw-bx-demo", 0.4);
|
||||
window.hwBoil(tl, "#hw-bx-demo", { amp: 1.8, rot: 0.4, frameDrop: 3, seed: 3 });
|
||||
window.hwBoxOff(tl, "#hw-bx-demo", 3.4);
|
||||
tl.set("#hw-bx-root", { visibility: "hidden" }, 3.98);
|
||||
window.__timelines["hw-box-label"] = tl;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "hw-box-label",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Hand-Drawn Box Label",
|
||||
"description": "Wobbled rounded rectangle draws around a region with a handwritten label above or centered",
|
||||
"description": "Wobbled rounded rectangle draws on — across the authored stroke matrix (plain/soft/sharp/spray) — or drops in and lands with a volume-true contact squash and bouncy spring recovery, with a handwritten label above or centered; boils",
|
||||
"tags": ["handwritten", "annotation", "overlay"],
|
||||
"files": [
|
||||
{
|
||||
@@ -16,5 +16,35 @@
|
||||
"target": "assets/fonts/Caveat-700-latin.woff2",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"name": "entrance",
|
||||
"type": "variant",
|
||||
"values": ["draw", "drop"],
|
||||
"default": "draw",
|
||||
"drives": "how the box arrives: stroke draw-on + label fade, or the landing squash (drop-in, contact squash on the deform wrapper, label counter-scaled to net identity, bouncy spring recovery) — travel/duration derived, never exposed"
|
||||
},
|
||||
{
|
||||
"name": "strokeType",
|
||||
"type": "variant",
|
||||
"values": ["plain", "soft", "sharp", "spray"],
|
||||
"default": "plain",
|
||||
"drives": "authored stroke texture on the box path (per-type constants: blur, dry-marker dash, seeded spray dots); draw-on composes via the mask clone"
|
||||
},
|
||||
{
|
||||
"name": "labelPos",
|
||||
"type": "variant",
|
||||
"values": ["above", "inside"],
|
||||
"default": "above",
|
||||
"drives": "label placement: above the box or vertically centered inside it"
|
||||
},
|
||||
{
|
||||
"name": "boil",
|
||||
"type": "variant",
|
||||
"values": ["off", "calm", "lively"],
|
||||
"default": "calm",
|
||||
"drives": "boil amp/rot as authored pairs over the family-locked frameDrop 3; seed locked"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+421
-63
@@ -1,7 +1,11 @@
|
||||
<!doctype html>
|
||||
<!-- Demo plate for the catalog preview. Identical to hw-callout-circle.html except the
|
||||
root background, which is opaque here so the transparent overlay reads.
|
||||
Not installed by `hyperframes add` - registry-item.json lists hw-callout-circle.html only. -->
|
||||
<!-- Demo plate for the catalog preview. Same CSS + helpers as
|
||||
hw-callout-circle.html, with an opaque root background so the transparent
|
||||
overlay reads, and THREE instances exercising the control surface in
|
||||
non-default states (g09 verification hook): labelAt "below" / "left",
|
||||
strokeType "sharp" / "spray" on outline + connector, and one callout at
|
||||
boil "lively". Not installed by `hyperframes add` - registry-item.json
|
||||
lists hw-callout-circle.html only. -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -27,11 +31,29 @@
|
||||
/* ---- hw-callout-circle ----------------------------------------------
|
||||
The full callout anatomy: wobbled ellipse outline draws around a
|
||||
target, optional scribble fill sketches in, a curved connector leads
|
||||
to a handwritten label. Requires the hw-boil helpers in the host. */
|
||||
to a handwritten label that POPS in with momentum — the drawn shapes
|
||||
absorb the arrival with a volume-preserving contact squash (g21).
|
||||
Ships the full family runtime it needs (hw-boil + boil poses +
|
||||
stroke matrix + springEase) — copy the helpers once per host.
|
||||
WRAPPER RULES: boil owns x/y/rotation of .hw-co-boil; the squash
|
||||
deforms .hw-co-deform (outline + scribble only); the label rides
|
||||
the uniform-scale .hw-co-pop OUTSIDE the deform — non-uniform
|
||||
scale never reaches glyphs. One timeline has ONE onUpdate
|
||||
(everything registers through hwOnUpdate). */
|
||||
.hw-callout {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hw-callout .hw-co-boil,
|
||||
.hw-callout .hw-co-deform,
|
||||
.hw-callout .hw-co-conn-layer,
|
||||
.hw-callout .hw-co-pop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
.hw-callout .hw-co-pop {
|
||||
opacity: 0;
|
||||
}
|
||||
.hw-callout svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -65,19 +87,16 @@
|
||||
font-size: 54px;
|
||||
color: var(--hw-co-ink, var(--hw-ink, #f4f2ec));
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
text-shadow: 0 2px 14px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
/* self-preview scene */
|
||||
#hw-co-scene {
|
||||
/* demo scene */
|
||||
#hw-cod-scene {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(150deg, #35604a 0%, #14231c 100%);
|
||||
}
|
||||
#hw-co-target {
|
||||
.hw-cod-target {
|
||||
position: absolute;
|
||||
left: 700px;
|
||||
top: 420px;
|
||||
width: 220px;
|
||||
height: 150px;
|
||||
border-radius: 18px;
|
||||
@@ -93,43 +112,88 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Self-preview. WIRING: copy the .hw-callout CSS + markup pattern, the
|
||||
hwCalloutBuild / hwCalloutOn / hwCalloutOff helpers, AND the hw-boil
|
||||
helpers. Size the wrapper around your target, then:
|
||||
|
||||
hwCalloutBuild("#hw-my-callout", { scribble: true, seed: 4,
|
||||
label: "look here!", labelAt: "right" });
|
||||
hwCalloutOn(tl, "#hw-my-callout", 2.0);
|
||||
hwBoil(tl, "#hw-my-callout", { frameDrop: 3, seed: 4 });
|
||||
hwCalloutOff(tl, "#hw-my-callout", 8.0);
|
||||
-->
|
||||
<!-- Three instances, staggered: A labelAt "below" (plain, calm) ·
|
||||
B strokeType "sharp" (right, calm) · C strokeType "spray" +
|
||||
labelAt "left" + boil "lively". Every labelAt pose and two
|
||||
non-default stroke textures are on screen within the 6s demo. -->
|
||||
<div
|
||||
id="hw-co-root"
|
||||
data-composition-id="hw-callout-circle"
|
||||
id="hw-cod-root"
|
||||
data-composition-id="hw-callout-circle-demo"
|
||||
data-start="0"
|
||||
data-duration="5"
|
||||
data-duration="6"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<div id="hw-co-scene"></div>
|
||||
<div id="hw-co-target">subject</div>
|
||||
<div id="hw-cod-scene"></div>
|
||||
<div class="hw-cod-target" style="left: 210px; top: 430px">subject</div>
|
||||
<div class="hw-cod-target" style="left: 850px; top: 350px">subject</div>
|
||||
<div class="hw-cod-target" style="left: 1490px; top: 480px">subject</div>
|
||||
<div
|
||||
class="hw-callout"
|
||||
id="hw-co-demo"
|
||||
style="left: 640px; top: 370px; width: 340px; height: 250px"
|
||||
id="hw-cod-a"
|
||||
style="left: 150px; top: 380px; width: 340px; height: 250px"
|
||||
>
|
||||
<div class="hw-co-boil">
|
||||
<div class="hw-co-deform">
|
||||
<svg viewBox="0 0 340 250">
|
||||
<path class="hw-co-scribble" id="hw-co-demo-scribble"></path>
|
||||
<path class="hw-co-outline" id="hw-co-demo-outline"></path>
|
||||
<path class="hw-co-connector" id="hw-co-demo-connector"></path>
|
||||
<path class="hw-co-scribble"></path>
|
||||
<path class="hw-co-outline"></path>
|
||||
</svg>
|
||||
<div class="hw-co-label" id="hw-co-demo-label"></div>
|
||||
</div>
|
||||
<svg class="hw-co-conn-layer" viewBox="0 0 340 250">
|
||||
<path class="hw-co-connector"></path>
|
||||
</svg>
|
||||
<div class="hw-co-pop">
|
||||
<!-- labels intentionally hang outside the wrapper box -->
|
||||
<div class="hw-co-label" data-layout-allow-overflow></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="hw-co-drv"
|
||||
class="hw-callout"
|
||||
id="hw-cod-b"
|
||||
style="left: 790px; top: 300px; width: 340px; height: 250px"
|
||||
>
|
||||
<div class="hw-co-boil">
|
||||
<div class="hw-co-deform">
|
||||
<svg viewBox="0 0 340 250">
|
||||
<path class="hw-co-scribble"></path>
|
||||
<path class="hw-co-outline"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<svg class="hw-co-conn-layer" viewBox="0 0 340 250">
|
||||
<path class="hw-co-connector"></path>
|
||||
</svg>
|
||||
<div class="hw-co-pop">
|
||||
<div class="hw-co-label" data-layout-allow-overflow></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="hw-callout"
|
||||
id="hw-cod-c"
|
||||
style="left: 1430px; top: 430px; width: 340px; height: 250px"
|
||||
>
|
||||
<div class="hw-co-boil">
|
||||
<div class="hw-co-deform">
|
||||
<svg viewBox="0 0 340 250">
|
||||
<path class="hw-co-scribble"></path>
|
||||
<path class="hw-co-outline"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<svg class="hw-co-conn-layer" viewBox="0 0 340 250">
|
||||
<path class="hw-co-connector"></path>
|
||||
</svg>
|
||||
<div class="hw-co-pop">
|
||||
<div class="hw-co-label" data-layout-allow-overflow></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="hw-cod-drv"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="5"
|
||||
data-duration="6"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
@@ -172,6 +236,58 @@
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
/* ---- spring library (spring-lab, validated 2026-08-04 — verbatim) ---- */
|
||||
var LN1000 = Math.log(1000);
|
||||
var FEELS = {
|
||||
snappy: { zeta: 0.9, response: 0.22 },
|
||||
"heavy-settle": { zeta: 1.0, response: 0.8 },
|
||||
bouncy: { zeta: 0.5, response: 0.4 },
|
||||
wobbly: { zeta: 0.28, response: 0.5 },
|
||||
};
|
||||
window.springEase = function (opts) {
|
||||
var f = opts.feel ? FEELS[opts.feel] : opts;
|
||||
var zeta = f.zeta,
|
||||
response = f.response;
|
||||
var w0 = (2 * Math.PI) / response;
|
||||
var T = LN1000 / (zeta * w0);
|
||||
var x;
|
||||
if (zeta < 1) {
|
||||
var wd = w0 * Math.sqrt(1 - zeta * zeta);
|
||||
x = function (t) {
|
||||
return (
|
||||
1 -
|
||||
Math.exp(-zeta * w0 * t) *
|
||||
(Math.cos(wd * t) + ((zeta * w0) / wd) * Math.sin(wd * t))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
x = function (t) {
|
||||
return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
||||
};
|
||||
}
|
||||
var xT = x(T);
|
||||
return {
|
||||
ease: function (p) {
|
||||
return p >= 1 ? 1 : x(p * T) / xT;
|
||||
},
|
||||
duration: T,
|
||||
zeta: zeta,
|
||||
response: response,
|
||||
};
|
||||
};
|
||||
window.hwWobbleEllipse = function (cx, cy, rx, ry, seed, wobblePct) {
|
||||
wobblePct = wobblePct === undefined ? 3 : wobblePct;
|
||||
var N = 14,
|
||||
@@ -200,10 +316,138 @@
|
||||
return d;
|
||||
};
|
||||
|
||||
/* ---- stroke matrix (family runtime): plain | soft | sharp | spray ----
|
||||
strokeType is a g09 variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
must therefore ride a MASK clone (the visible dasharray never
|
||||
animates). spray = index-seeded deterministic dots along the path
|
||||
via hwHash: position a pure function of path parameter + seed; NO
|
||||
feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever hwStrokeApply
|
||||
says. (The pen-velocity ease variant rides the write-on item, not
|
||||
this one.) */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
// ---- hw-callout helpers (copy these too) ----
|
||||
// Builds outline + scribble + connector + label inside the wrapper.
|
||||
// opts: scribble (bool), seed, label, labelAt ("right"|"left"|"below"),
|
||||
// connector (bool)
|
||||
// Builds outline + scribble + connector + label inside the wrapper and
|
||||
// stores the draw records + control poses hwCalloutOn consumes.
|
||||
// opts — content (uncounted): scribble (bool), seed, label, connector (bool)
|
||||
// controls (g09 — the item's CONFIG block is the published-
|
||||
// parameter list): labelAt, strokeType, boil
|
||||
window.hwCalloutBuild = function (target, opts) {
|
||||
opts = opts || {};
|
||||
var seed = opts.seed || 1;
|
||||
@@ -217,10 +461,8 @@
|
||||
rx = W * 0.42,
|
||||
ry = H * 0.36;
|
||||
|
||||
el.querySelector(".hw-co-outline").setAttribute(
|
||||
"d",
|
||||
window.hwWobbleEllipse(cx, cy, rx, ry, seed, 4),
|
||||
);
|
||||
var outline = el.querySelector(".hw-co-outline");
|
||||
outline.setAttribute("d", window.hwWobbleEllipse(cx, cy, rx, ry, seed, 4));
|
||||
|
||||
var scr = el.querySelector(".hw-co-scribble");
|
||||
if (opts.scribble === false) {
|
||||
@@ -249,14 +491,24 @@
|
||||
|
||||
var conn = el.querySelector(".hw-co-connector");
|
||||
var label = el.querySelector(".hw-co-label");
|
||||
// labelAt is a declared variant control — bounded: unknown value
|
||||
// falls back to the default WITH a console error. Validated BEFORE
|
||||
// the connector branch so a malformed value always logs, even when
|
||||
// connector: false leaves the control inert.
|
||||
var at = opts.labelAt || "right";
|
||||
if (at !== "left" && at !== "right" && at !== "below") {
|
||||
console.error('hw: unknown labelAt "' + at + '" — falling back to "right"');
|
||||
at = "right";
|
||||
}
|
||||
var ex = null,
|
||||
ey = null;
|
||||
if (opts.connector === false) {
|
||||
conn.style.display = "none";
|
||||
} else {
|
||||
var at = opts.labelAt || "right";
|
||||
var sx = at === "left" ? cx - rx : at === "below" ? cx : cx + rx;
|
||||
var sy = at === "below" ? cy + ry : cy + ry * 0.35;
|
||||
var ex = at === "left" ? sx - 70 : at === "below" ? sx + 40 : sx + 70;
|
||||
var ey = sy + 55;
|
||||
ex = at === "left" ? sx - 70 : at === "below" ? sx + 40 : sx + 70;
|
||||
ey = sy + 55;
|
||||
conn.setAttribute(
|
||||
"d",
|
||||
"M " +
|
||||
@@ -276,41 +528,147 @@
|
||||
label.style.top = ey - 24 + "px";
|
||||
}
|
||||
label.textContent = opts.label || "";
|
||||
|
||||
// strokeType control: OUTLINE + CONNECTOR take the texture (the
|
||||
// scribble stays plain). Bounding lives inside hwStrokeApply.
|
||||
var strokeType = opts.strokeType || "plain";
|
||||
var idBase = (el.id || "hw-co") + "-sm";
|
||||
el.__hwCo = {
|
||||
rx: rx,
|
||||
seed: seed,
|
||||
boil: opts.boil || "calm",
|
||||
// the label pops out of the connector tip (falls back to center
|
||||
// when the connector anatomy is off)
|
||||
popOrigin: ex === null ? "50% 50%" : ex.toFixed(1) + "px " + ey.toFixed(1) + "px",
|
||||
draw: {
|
||||
outline: window.hwStrokeApply(outline, strokeType, {
|
||||
seed: seed,
|
||||
id: idBase + "-outline",
|
||||
}),
|
||||
scribble:
|
||||
opts.scribble === false ? null : window.hwStrokeApply(scr, "plain", { seed: seed }),
|
||||
connector:
|
||||
opts.connector === false
|
||||
? null
|
||||
: window.hwStrokeApply(conn, strokeType, {
|
||||
seed: seed,
|
||||
id: idBase + "-connector",
|
||||
}),
|
||||
},
|
||||
};
|
||||
// Draw sequence: outline -> scribble -> connector -> label pop.
|
||||
};
|
||||
// Draw sequence: outline -> scribble -> connector -> label pop, with
|
||||
// the g21 contact squash on the shapes group at pop arrival. The boil
|
||||
// pose registers here too — the host wires a position only.
|
||||
window.hwCalloutOn = function (tl, target, at) {
|
||||
var el = document.querySelector(target);
|
||||
var co = el.__hwCo;
|
||||
var t = at;
|
||||
["outline", "scribble", "connector"].forEach(function (k, idx) {
|
||||
var p = el.querySelector(".hw-co-" + k);
|
||||
if (!p || p.style.display === "none") return;
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
["outline", "scribble", "connector"].forEach(function (k) {
|
||||
var rec = co.draw[k];
|
||||
if (!rec) return;
|
||||
var dur = k === "scribble" ? 0.55 : k === "connector" ? 0.35 : 0.7;
|
||||
tl.to(p, { strokeDashoffset: 0, duration: dur, ease: "power2.inOut" }, t);
|
||||
window.hwDrawOn(tl, rec, t, dur);
|
||||
t += dur * 0.85;
|
||||
});
|
||||
var label = el.querySelector(".hw-co-label");
|
||||
gsap.set(label, { scale: 0.8, transformOrigin: "0% 50%" });
|
||||
tl.to(label, { opacity: 1, scale: 1, duration: 0.4, ease: "back.out(1.6)" }, t);
|
||||
// label pop: the pop ARRIVES WITH MOMENTUM (accelerating into
|
||||
// contact) — the squash absorbs it; a settling back.out crosses
|
||||
// scale 1.0 at ~zero speed and the computed squash is invisible
|
||||
// (measured, not hand-picked)
|
||||
var pop = el.querySelector(".hw-co-pop");
|
||||
var popEase = gsap.parseEase("power2.in");
|
||||
gsap.set(pop, { scale: 0.001, opacity: 0, transformOrigin: co.popOrigin });
|
||||
tl.to(pop, { opacity: 1, duration: 0.08, ease: "none" }, t);
|
||||
tl.to(pop, { scale: 1, duration: 0.3, ease: popEase, immediateRender: false }, t);
|
||||
// arrival velocity in px of radial (rx) travel, measured at the
|
||||
// FIRST crossing of 1.0 — a settling ease's end-slope is ~0 and is
|
||||
// NOT the arrival velocity
|
||||
var eps = 1e-4;
|
||||
var pc = 1;
|
||||
for (var k2 = 1; k2 <= 1000; k2++) {
|
||||
if (popEase(k2 / 1000) >= 1) {
|
||||
pc = k2 / 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var vPop =
|
||||
((popEase(Math.min(pc + eps, 1)) - popEase(pc - eps)) / (2 * eps)) * (co.rx / 0.3);
|
||||
var S = Math.min(0.08 * (Math.abs(vPop) / 2000), 0.25); // 0.08 = light drawn line tier
|
||||
var rec2 = window.springEase({ feel: "snappy" });
|
||||
var st = { s: 0 };
|
||||
var deform = el.querySelector(".hw-co-deform");
|
||||
gsap.set(deform, { transformOrigin: "50% 100%" });
|
||||
// volume-preserving squash on the SHAPES wrapper only — the label
|
||||
// rides the uniform-scale pop OUTSIDE the deform, so non-uniform
|
||||
// scale never reaches glyphs. Read-back assertion enforces the law.
|
||||
window.hwOnUpdate(tl, function () {
|
||||
var along = 1 - st.s;
|
||||
gsap.set(deform, { scaleY: along, scaleX: 1 / along });
|
||||
var ax = parseFloat(gsap.getProperty(deform, "scaleX"));
|
||||
var ay = parseFloat(gsap.getProperty(deform, "scaleY"));
|
||||
if (Math.abs(ax * ay - 1) > 0.01)
|
||||
console.error("hw volume law violated (callout): " + (ax * ay).toFixed(4));
|
||||
});
|
||||
// contact: squash in 2 frames, 1-frame hold, spring home (factory
|
||||
// duration consumed verbatim)
|
||||
tl.to(st, { s: S, duration: 2 / 30, ease: "power3.out" }, t + 0.3);
|
||||
tl.to(st, { s: 0, duration: rec2.duration, ease: rec2.ease }, t + 0.3 + 3 / 30);
|
||||
// boil control (family base) — x/y/rotation on the boil wrapper only
|
||||
window.hwBoilPose(tl, el.querySelector(".hw-co-boil"), co.boil, { seed: co.seed });
|
||||
};
|
||||
window.hwCalloutOff = function (tl, target, at) {
|
||||
tl.to(target, { opacity: 0, duration: 0.35, ease: "power2.in" }, at);
|
||||
};
|
||||
|
||||
// ---- self-preview ----
|
||||
window.hwCalloutBuild("#hw-co-demo", {
|
||||
scribble: true,
|
||||
// ---- demo: three instances in NON-DEFAULT control states ----
|
||||
var CALLOUTS = [
|
||||
// A: labelAt "below" (non-default pose), plain, calm
|
||||
{
|
||||
target: "#hw-cod-a",
|
||||
seed: 4,
|
||||
label: "look here!",
|
||||
label: "label below",
|
||||
labelAt: "below",
|
||||
strokeType: "plain",
|
||||
boil: "calm",
|
||||
at: 0.4,
|
||||
},
|
||||
// B: strokeType "sharp" (dry marker) on outline + connector
|
||||
{
|
||||
target: "#hw-cod-b",
|
||||
seed: 7,
|
||||
label: "dry marker",
|
||||
labelAt: "right",
|
||||
});
|
||||
strokeType: "sharp",
|
||||
boil: "calm",
|
||||
at: 0.9,
|
||||
},
|
||||
// C: strokeType "spray" + labelAt "left" + boil "lively"
|
||||
{
|
||||
target: "#hw-cod-c",
|
||||
seed: 11,
|
||||
label: "lively!",
|
||||
labelAt: "left",
|
||||
strokeType: "spray",
|
||||
boil: "lively",
|
||||
at: 1.4,
|
||||
},
|
||||
];
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.hwCalloutOn(tl, "#hw-co-demo", 0.4);
|
||||
window.hwBoil(tl, "#hw-co-demo", { amp: 1.8, rot: 0.4, frameDrop: 3, seed: 4 });
|
||||
window.hwCalloutOff(tl, "#hw-co-demo", 4.3);
|
||||
tl.set("#hw-co-root", { visibility: "hidden" }, 4.98);
|
||||
window.__timelines["hw-callout-circle"] = tl;
|
||||
for (var i = 0; i < CALLOUTS.length; i++) {
|
||||
var c = CALLOUTS[i];
|
||||
window.hwCalloutBuild(c.target, {
|
||||
scribble: true,
|
||||
seed: c.seed,
|
||||
label: c.label,
|
||||
labelAt: c.labelAt,
|
||||
strokeType: c.strokeType,
|
||||
boil: c.boil,
|
||||
});
|
||||
window.hwCalloutOn(tl, c.target, c.at);
|
||||
window.hwCalloutOff(tl, c.target, 5.2);
|
||||
}
|
||||
tl.set("#hw-cod-root", { visibility: "hidden" }, 5.98);
|
||||
window.__timelines["hw-callout-circle-demo"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
+347
-38
@@ -24,11 +24,29 @@
|
||||
/* ---- hw-callout-circle ----------------------------------------------
|
||||
The full callout anatomy: wobbled ellipse outline draws around a
|
||||
target, optional scribble fill sketches in, a curved connector leads
|
||||
to a handwritten label. Requires the hw-boil helpers in the host. */
|
||||
to a handwritten label that POPS in with momentum — the drawn shapes
|
||||
absorb the arrival with a volume-preserving contact squash (g21).
|
||||
Ships the full family runtime it needs (hw-boil + boil poses +
|
||||
stroke matrix + springEase) — copy the helpers once per host.
|
||||
WRAPPER RULES: boil owns x/y/rotation of .hw-co-boil; the squash
|
||||
deforms .hw-co-deform (outline + scribble only); the label rides
|
||||
the uniform-scale .hw-co-pop OUTSIDE the deform — non-uniform
|
||||
scale never reaches glyphs. One timeline has ONE onUpdate
|
||||
(everything registers through hwOnUpdate). */
|
||||
.hw-callout {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hw-callout .hw-co-boil,
|
||||
.hw-callout .hw-co-deform,
|
||||
.hw-callout .hw-co-conn-layer,
|
||||
.hw-callout .hw-co-pop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
.hw-callout .hw-co-pop {
|
||||
opacity: 0;
|
||||
}
|
||||
.hw-callout svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -62,7 +80,6 @@
|
||||
font-size: 54px;
|
||||
color: var(--hw-co-ink, var(--hw-ink, #f4f2ec));
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
text-shadow: 0 2px 14px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
/* self-preview scene */
|
||||
@@ -90,21 +107,26 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Self-preview. WIRING: copy the .hw-callout CSS + markup pattern, the
|
||||
hwCalloutBuild / hwCalloutOn / hwCalloutOff helpers, AND the hw-boil
|
||||
helpers. Size the wrapper around your target, then:
|
||||
<!-- Self-preview. WIRING: copy the .hw-callout CSS + markup pattern (the
|
||||
boil / deform / conn-layer / pop wrapper stack is REQUIRED — boil
|
||||
owns x/y/rotation, the squash owns the deform, the label pops on
|
||||
.hw-co-pop) and ALL helpers below. Size the wrapper around your
|
||||
target, then:
|
||||
|
||||
hwCalloutBuild("#hw-my-callout", { scribble: true, seed: 4,
|
||||
label: "look here!", labelAt: "right" });
|
||||
label: "look here!", labelAt: "right",
|
||||
strokeType: "plain", boil: "calm" });
|
||||
hwCalloutOn(tl, "#hw-my-callout", 2.0);
|
||||
hwBoil(tl, "#hw-my-callout", { frameDrop: 3, seed: 4 });
|
||||
hwCalloutOff(tl, "#hw-my-callout", 8.0);
|
||||
|
||||
hwCalloutOn applies the boil pose internally — do NOT add a host
|
||||
hwBoil call on the callout (it would double-boil).
|
||||
-->
|
||||
<div
|
||||
id="hw-co-root"
|
||||
data-composition-id="hw-callout-circle"
|
||||
data-start="0"
|
||||
data-duration="5"
|
||||
data-duration="4"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
@@ -115,18 +137,27 @@
|
||||
id="hw-co-demo"
|
||||
style="left: 640px; top: 370px; width: 340px; height: 250px"
|
||||
>
|
||||
<div class="hw-co-boil">
|
||||
<div class="hw-co-deform">
|
||||
<svg viewBox="0 0 340 250">
|
||||
<path class="hw-co-scribble" id="hw-co-demo-scribble"></path>
|
||||
<path class="hw-co-outline" id="hw-co-demo-outline"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<svg class="hw-co-conn-layer" viewBox="0 0 340 250">
|
||||
<path class="hw-co-connector" id="hw-co-demo-connector"></path>
|
||||
</svg>
|
||||
<div class="hw-co-label" id="hw-co-demo-label"></div>
|
||||
<div class="hw-co-pop">
|
||||
<!-- labels intentionally hang outside the wrapper box -->
|
||||
<div class="hw-co-label" id="hw-co-demo-label" data-layout-allow-overflow></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="hw-co-drv"
|
||||
class="clip"
|
||||
data-start="0"
|
||||
data-duration="5"
|
||||
data-duration="4"
|
||||
data-track-index="0"
|
||||
style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
|
||||
></div>
|
||||
@@ -135,6 +166,24 @@
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
var CONFIG = {
|
||||
// ── Content (freely editable — not counted) ─────────────────────
|
||||
label: "look here!",
|
||||
scribble: true, // which anatomy pieces exist (content, not a feel lever)
|
||||
connector: true, // (connector: false also suppresses the label anchor)
|
||||
|
||||
// ── Controls (family base + 2 item-specific; comment grammar is the published-parameter list) ──
|
||||
labelAt: "right", // variant: "left" | "right" | "below" — connector start point, sweep direction + label anchor pose (6 geometry params)
|
||||
strokeType: "plain", // variant: "plain" | "soft" | "sharp" | "spray" — outline + connector stroke texture (authored constants per type); the scribble fill stays plain — texture-on-texture reads as mud
|
||||
boil: "calm", // variant: "off" | "calm" | "lively" — authored amp/rot pairs over the family-locked frameDrop 3; seed locked
|
||||
|
||||
// ── Locked (edit the item source to change) ─────────────────────
|
||||
// draw durations/eases, pop ease + contact-squash constants
|
||||
// (softness 0.08 light-drawn-line tier, snappy recovery),
|
||||
// stroke-type constants, boil pose tables, seeds
|
||||
};
|
||||
var SEED = 4; // locked — determinism (hosts pass a distinct seed per callout instance)
|
||||
|
||||
/* ---- hw-boil helpers (shared family runtime — copy once per host) ---- */
|
||||
window.hwOnUpdate = function (tl, fn) {
|
||||
if (!tl.__hwRenders) {
|
||||
@@ -169,6 +218,58 @@
|
||||
}
|
||||
});
|
||||
};
|
||||
// boil control: authored poses over the family-locked frameDrop.
|
||||
// Unknown pose → default + console error (bounded-controls law).
|
||||
window.hwBoilPose = function (tl, target, pose, opts) {
|
||||
opts = opts || {};
|
||||
var poses = { calm: { amp: 1.6, rot: 0.5 }, lively: { amp: 2.6, rot: 0.9 } };
|
||||
if (pose === "off") return;
|
||||
var p = poses[pose];
|
||||
if (!p) {
|
||||
console.error('hw: unknown boil pose "' + pose + '" — falling back to "calm"');
|
||||
p = poses.calm;
|
||||
}
|
||||
window.hwBoil(tl, target, { amp: p.amp, rot: p.rot, frameDrop: 3, seed: opts.seed || 1 });
|
||||
};
|
||||
/* ---- spring library (spring-lab, validated 2026-08-04 — verbatim) ---- */
|
||||
var LN1000 = Math.log(1000);
|
||||
var FEELS = {
|
||||
snappy: { zeta: 0.9, response: 0.22 },
|
||||
"heavy-settle": { zeta: 1.0, response: 0.8 },
|
||||
bouncy: { zeta: 0.5, response: 0.4 },
|
||||
wobbly: { zeta: 0.28, response: 0.5 },
|
||||
};
|
||||
window.springEase = function (opts) {
|
||||
var f = opts.feel ? FEELS[opts.feel] : opts;
|
||||
var zeta = f.zeta,
|
||||
response = f.response;
|
||||
var w0 = (2 * Math.PI) / response;
|
||||
var T = LN1000 / (zeta * w0);
|
||||
var x;
|
||||
if (zeta < 1) {
|
||||
var wd = w0 * Math.sqrt(1 - zeta * zeta);
|
||||
x = function (t) {
|
||||
return (
|
||||
1 -
|
||||
Math.exp(-zeta * w0 * t) *
|
||||
(Math.cos(wd * t) + ((zeta * w0) / wd) * Math.sin(wd * t))
|
||||
);
|
||||
};
|
||||
} else {
|
||||
x = function (t) {
|
||||
return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
|
||||
};
|
||||
}
|
||||
var xT = x(T);
|
||||
return {
|
||||
ease: function (p) {
|
||||
return p >= 1 ? 1 : x(p * T) / xT;
|
||||
},
|
||||
duration: T,
|
||||
zeta: zeta,
|
||||
response: response,
|
||||
};
|
||||
};
|
||||
window.hwWobbleEllipse = function (cx, cy, rx, ry, seed, wobblePct) {
|
||||
wobblePct = wobblePct === undefined ? 3 : wobblePct;
|
||||
var N = 14,
|
||||
@@ -197,10 +298,138 @@
|
||||
return d;
|
||||
};
|
||||
|
||||
/* ---- stroke matrix (family runtime): plain | soft | sharp | spray ----
|
||||
strokeType is a g09 variant control. Authored constants per type;
|
||||
unknown value → default ("plain") + console error (bounded-controls
|
||||
law). sharp = micro stroke-dasharray gaps (dry marker) — draw-on
|
||||
must therefore ride a MASK clone (the visible dasharray never
|
||||
animates). spray = index-seeded deterministic dots along the path
|
||||
via hwHash: position a pure function of path parameter + seed; NO
|
||||
feTurbulence anywhere.
|
||||
Returns { draw: pathToDashAnimate, group: maskedGroupOrNull } */
|
||||
window.hwStrokeTypes = {
|
||||
plain: {},
|
||||
soft: { blur: 0.6 },
|
||||
sharp: { dash: [3.0, 1.55] }, // × stroke-width
|
||||
spray: {
|
||||
coreOpacity: 0.5,
|
||||
coreBlur: 0.4,
|
||||
density: 0.16,
|
||||
scatter: 2.6,
|
||||
rMin: 1.1,
|
||||
rMax: 2.6,
|
||||
},
|
||||
};
|
||||
window.hwStrokeApply = function (pathEl, type, opts) {
|
||||
opts = opts || {};
|
||||
var cfg = window.hwStrokeTypes[type];
|
||||
if (!cfg) {
|
||||
console.error('hw: unknown strokeType "' + type + '" — falling back to "plain"');
|
||||
type = "plain";
|
||||
cfg = window.hwStrokeTypes.plain;
|
||||
}
|
||||
var svg = pathEl.ownerSVGElement;
|
||||
var ns = "http://www.w3.org/2000/svg";
|
||||
var w = parseFloat(getComputedStyle(pathEl).strokeWidth) || 6;
|
||||
if (type === "plain") return { draw: pathEl, group: null, type: type };
|
||||
if (type === "soft") {
|
||||
pathEl.style.filter = "blur(" + cfg.blur + "px)";
|
||||
return { draw: pathEl, group: null, type: type };
|
||||
}
|
||||
// sharp + spray: wrap in a masked group, draw-on animates the mask clone
|
||||
var id =
|
||||
opts.id ||
|
||||
"hwsm-" + Math.abs(Math.round(window.hwHash((opts.seed || 1) * 13.7, 5) * 1e6));
|
||||
var defs = svg.querySelector("defs");
|
||||
if (!defs) {
|
||||
defs = document.createElementNS(ns, "defs");
|
||||
svg.insertBefore(defs, svg.firstChild);
|
||||
}
|
||||
var mask = document.createElementNS(ns, "mask");
|
||||
mask.setAttribute("id", id);
|
||||
mask.setAttribute("maskUnits", "userSpaceOnUse");
|
||||
var clone = document.createElementNS(ns, "path");
|
||||
clone.setAttribute("d", pathEl.getAttribute("d"));
|
||||
clone.setAttribute("fill", "none");
|
||||
clone.setAttribute("stroke", "#fff");
|
||||
clone.setAttribute(
|
||||
"stroke-width",
|
||||
w * (type === "spray" ? 2 + (cfg.scatter * 2) / w : 1.7),
|
||||
);
|
||||
clone.setAttribute("stroke-linecap", "round");
|
||||
clone.setAttribute("stroke-linejoin", "round");
|
||||
mask.appendChild(clone);
|
||||
defs.appendChild(mask);
|
||||
var group = document.createElementNS(ns, "g");
|
||||
group.setAttribute("mask", "url(#" + id + ")");
|
||||
pathEl.parentNode.insertBefore(group, pathEl);
|
||||
group.appendChild(pathEl);
|
||||
if (type === "sharp") {
|
||||
pathEl.setAttribute(
|
||||
"stroke-dasharray",
|
||||
(cfg.dash[0] * w).toFixed(1) + " " + (cfg.dash[1] * w).toFixed(1),
|
||||
);
|
||||
} else {
|
||||
// spray: faint core + seeded dots
|
||||
pathEl.setAttribute("stroke-opacity", cfg.coreOpacity);
|
||||
pathEl.style.filter = "blur(" + cfg.coreBlur + "px)";
|
||||
var len = pathEl.getTotalLength();
|
||||
var n = Math.round(len * cfg.density * (opts.densityScale || 1));
|
||||
var seed = opts.seed || 1;
|
||||
var dots = document.createElementNS(ns, "g");
|
||||
for (var i = 0; i < n; i++) {
|
||||
var t = Math.min(
|
||||
1,
|
||||
Math.max(0, (i + 0.5) / n + (window.hwHash(i * 7, seed) * 0.35) / n),
|
||||
);
|
||||
var pt = pathEl.getPointAtLength(t * len);
|
||||
var pt2 = pathEl.getPointAtLength(Math.min(len, t * len + 0.5));
|
||||
var tx = pt2.x - pt.x,
|
||||
ty = pt2.y - pt.y;
|
||||
var tl2 = Math.hypot(tx, ty) || 1;
|
||||
var nx = -ty / tl2,
|
||||
nyv = tx / tl2;
|
||||
var off = window.hwHash(i * 7 + 1, seed) * (cfg.scatter + w * 0.55);
|
||||
var r = cfg.rMin + Math.abs(window.hwHash(i * 7 + 2, seed)) * (cfg.rMax - cfg.rMin);
|
||||
var c = document.createElementNS(ns, "circle");
|
||||
c.setAttribute("cx", (pt.x + nx * off).toFixed(1));
|
||||
c.setAttribute("cy", (pt.y + nyv * off).toFixed(1));
|
||||
c.setAttribute("r", r.toFixed(2));
|
||||
c.setAttribute("fill", getComputedStyle(pathEl).stroke);
|
||||
dots.appendChild(c);
|
||||
}
|
||||
group.appendChild(dots);
|
||||
}
|
||||
return { draw: clone, group: group, type: type };
|
||||
};
|
||||
|
||||
/* Draw-on across the stroke matrix: dash-animate whatever hwStrokeApply
|
||||
says. (The pen-velocity ease variant rides the write-on item, not
|
||||
this one.) */
|
||||
window.hwDrawOn = function (tl, applied, at, dur, opts) {
|
||||
opts = opts || {};
|
||||
var p = applied.draw;
|
||||
var len = p.getTotalLength();
|
||||
// the draw target is always solid (plain/soft: the path itself; sharp/spray:
|
||||
// the mask clone — the visible path keeps its texture dasharray untouched)
|
||||
p.setAttribute("stroke-dasharray", len + " " + len);
|
||||
p.setAttribute("stroke-dashoffset", len);
|
||||
gsap.set(p, { opacity: 0 }); // kill the round-cap start nub pre-draw
|
||||
var ease = opts.ease || "power2.inOut";
|
||||
tl.set(p, { opacity: 1 }, at);
|
||||
tl.to(
|
||||
p,
|
||||
{ strokeDashoffset: 0, duration: dur === undefined ? 0.7 : dur, ease: ease },
|
||||
at,
|
||||
);
|
||||
};
|
||||
|
||||
// ---- hw-callout helpers (copy these too) ----
|
||||
// Builds outline + scribble + connector + label inside the wrapper.
|
||||
// opts: scribble (bool), seed, label, labelAt ("right"|"left"|"below"),
|
||||
// connector (bool)
|
||||
// Builds outline + scribble + connector + label inside the wrapper and
|
||||
// stores the draw records + control poses hwCalloutOn consumes.
|
||||
// opts — content (uncounted): scribble (bool), seed, label, connector (bool)
|
||||
// controls (g09 — see the CONFIG block, the published-parameter
|
||||
// list): labelAt, strokeType, boil
|
||||
window.hwCalloutBuild = function (target, opts) {
|
||||
opts = opts || {};
|
||||
var seed = opts.seed || 1;
|
||||
@@ -214,10 +443,8 @@
|
||||
rx = W * 0.42,
|
||||
ry = H * 0.36;
|
||||
|
||||
el.querySelector(".hw-co-outline").setAttribute(
|
||||
"d",
|
||||
window.hwWobbleEllipse(cx, cy, rx, ry, seed, 4),
|
||||
);
|
||||
var outline = el.querySelector(".hw-co-outline");
|
||||
outline.setAttribute("d", window.hwWobbleEllipse(cx, cy, rx, ry, seed, 4));
|
||||
|
||||
var scr = el.querySelector(".hw-co-scribble");
|
||||
if (opts.scribble === false) {
|
||||
@@ -246,14 +473,24 @@
|
||||
|
||||
var conn = el.querySelector(".hw-co-connector");
|
||||
var label = el.querySelector(".hw-co-label");
|
||||
// labelAt is a declared variant control — bounded: unknown value
|
||||
// falls back to the default WITH a console error. Validated BEFORE
|
||||
// the connector branch so a malformed value always logs, even when
|
||||
// connector: false leaves the control inert.
|
||||
var at = opts.labelAt || "right";
|
||||
if (at !== "left" && at !== "right" && at !== "below") {
|
||||
console.error('hw: unknown labelAt "' + at + '" — falling back to "right"');
|
||||
at = "right";
|
||||
}
|
||||
var ex = null,
|
||||
ey = null;
|
||||
if (opts.connector === false) {
|
||||
conn.style.display = "none";
|
||||
} else {
|
||||
var at = opts.labelAt || "right";
|
||||
var sx = at === "left" ? cx - rx : at === "below" ? cx : cx + rx;
|
||||
var sy = at === "below" ? cy + ry : cy + ry * 0.35;
|
||||
var ex = at === "left" ? sx - 70 : at === "below" ? sx + 40 : sx + 70;
|
||||
var ey = sy + 55;
|
||||
ex = at === "left" ? sx - 70 : at === "below" ? sx + 40 : sx + 70;
|
||||
ey = sy + 55;
|
||||
conn.setAttribute(
|
||||
"d",
|
||||
"M " +
|
||||
@@ -273,40 +510,112 @@
|
||||
label.style.top = ey - 24 + "px";
|
||||
}
|
||||
label.textContent = opts.label || "";
|
||||
|
||||
// strokeType control: OUTLINE + CONNECTOR take the texture (the
|
||||
// scribble stays plain). Bounding lives inside hwStrokeApply.
|
||||
var strokeType = opts.strokeType || "plain";
|
||||
var idBase = (el.id || "hw-co") + "-sm";
|
||||
el.__hwCo = {
|
||||
rx: rx,
|
||||
seed: seed,
|
||||
boil: opts.boil || "calm",
|
||||
// the label pops out of the connector tip (falls back to center
|
||||
// when the connector anatomy is off)
|
||||
popOrigin: ex === null ? "50% 50%" : ex.toFixed(1) + "px " + ey.toFixed(1) + "px",
|
||||
draw: {
|
||||
outline: window.hwStrokeApply(outline, strokeType, {
|
||||
seed: seed,
|
||||
id: idBase + "-outline",
|
||||
}),
|
||||
scribble:
|
||||
opts.scribble === false ? null : window.hwStrokeApply(scr, "plain", { seed: seed }),
|
||||
connector:
|
||||
opts.connector === false
|
||||
? null
|
||||
: window.hwStrokeApply(conn, strokeType, {
|
||||
seed: seed,
|
||||
id: idBase + "-connector",
|
||||
}),
|
||||
},
|
||||
};
|
||||
// Draw sequence: outline -> scribble -> connector -> label pop.
|
||||
};
|
||||
// Draw sequence: outline -> scribble -> connector -> label pop, with
|
||||
// the g21 contact squash on the shapes group at pop arrival. The boil
|
||||
// pose registers here too — the host wires a position only.
|
||||
window.hwCalloutOn = function (tl, target, at) {
|
||||
var el = document.querySelector(target);
|
||||
var co = el.__hwCo;
|
||||
var t = at;
|
||||
["outline", "scribble", "connector"].forEach(function (k, idx) {
|
||||
var p = el.querySelector(".hw-co-" + k);
|
||||
if (!p || p.style.display === "none") return;
|
||||
var len = p.getTotalLength();
|
||||
gsap.set(p, { strokeDasharray: len, strokeDashoffset: len });
|
||||
["outline", "scribble", "connector"].forEach(function (k) {
|
||||
var rec = co.draw[k];
|
||||
if (!rec) return;
|
||||
var dur = k === "scribble" ? 0.55 : k === "connector" ? 0.35 : 0.7;
|
||||
tl.to(p, { strokeDashoffset: 0, duration: dur, ease: "power2.inOut" }, t);
|
||||
window.hwDrawOn(tl, rec, t, dur);
|
||||
t += dur * 0.85;
|
||||
});
|
||||
var label = el.querySelector(".hw-co-label");
|
||||
gsap.set(label, { scale: 0.8, transformOrigin: "0% 50%" });
|
||||
tl.to(label, { opacity: 1, scale: 1, duration: 0.4, ease: "back.out(1.6)" }, t);
|
||||
// label pop: the pop ARRIVES WITH MOMENTUM (accelerating into
|
||||
// contact) — the squash absorbs it; a settling back.out crosses
|
||||
// scale 1.0 at ~zero speed and the computed squash is invisible
|
||||
// (measured, not hand-picked)
|
||||
var pop = el.querySelector(".hw-co-pop");
|
||||
var popEase = gsap.parseEase("power2.in");
|
||||
gsap.set(pop, { scale: 0.001, opacity: 0, transformOrigin: co.popOrigin });
|
||||
tl.to(pop, { opacity: 1, duration: 0.08, ease: "none" }, t);
|
||||
tl.to(pop, { scale: 1, duration: 0.3, ease: popEase, immediateRender: false }, t);
|
||||
// arrival velocity in px of radial (rx) travel, measured at the
|
||||
// FIRST crossing of 1.0 — a settling ease's end-slope is ~0 and is
|
||||
// NOT the arrival velocity
|
||||
var eps = 1e-4;
|
||||
var pc = 1;
|
||||
for (var k2 = 1; k2 <= 1000; k2++) {
|
||||
if (popEase(k2 / 1000) >= 1) {
|
||||
pc = k2 / 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var vPop =
|
||||
((popEase(Math.min(pc + eps, 1)) - popEase(pc - eps)) / (2 * eps)) * (co.rx / 0.3);
|
||||
var S = Math.min(0.08 * (Math.abs(vPop) / 2000), 0.25); // 0.08 = light drawn line tier
|
||||
var rec2 = window.springEase({ feel: "snappy" });
|
||||
var st = { s: 0 };
|
||||
var deform = el.querySelector(".hw-co-deform");
|
||||
gsap.set(deform, { transformOrigin: "50% 100%" });
|
||||
// volume-preserving squash on the SHAPES wrapper only — the label
|
||||
// rides the uniform-scale pop OUTSIDE the deform, so non-uniform
|
||||
// scale never reaches glyphs. Read-back assertion enforces the law.
|
||||
window.hwOnUpdate(tl, function () {
|
||||
var along = 1 - st.s;
|
||||
gsap.set(deform, { scaleY: along, scaleX: 1 / along });
|
||||
var ax = parseFloat(gsap.getProperty(deform, "scaleX"));
|
||||
var ay = parseFloat(gsap.getProperty(deform, "scaleY"));
|
||||
if (Math.abs(ax * ay - 1) > 0.01)
|
||||
console.error("hw volume law violated (callout): " + (ax * ay).toFixed(4));
|
||||
});
|
||||
// contact: squash in 2 frames, 1-frame hold, spring home (factory
|
||||
// duration consumed verbatim)
|
||||
tl.to(st, { s: S, duration: 2 / 30, ease: "power3.out" }, t + 0.3);
|
||||
tl.to(st, { s: 0, duration: rec2.duration, ease: rec2.ease }, t + 0.3 + 3 / 30);
|
||||
// boil control (family base) — x/y/rotation on the boil wrapper only
|
||||
window.hwBoilPose(tl, el.querySelector(".hw-co-boil"), co.boil, { seed: co.seed });
|
||||
};
|
||||
window.hwCalloutOff = function (tl, target, at) {
|
||||
tl.to(target, { opacity: 0, duration: 0.35, ease: "power2.in" }, at);
|
||||
};
|
||||
|
||||
// ---- self-preview ----
|
||||
// ---- self-preview (shipped-default CONFIG) ----
|
||||
window.hwCalloutBuild("#hw-co-demo", {
|
||||
scribble: true,
|
||||
seed: 4,
|
||||
label: "look here!",
|
||||
labelAt: "right",
|
||||
scribble: CONFIG.scribble,
|
||||
seed: SEED,
|
||||
label: CONFIG.label,
|
||||
connector: CONFIG.connector,
|
||||
labelAt: CONFIG.labelAt,
|
||||
strokeType: CONFIG.strokeType,
|
||||
boil: CONFIG.boil,
|
||||
});
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
window.hwCalloutOn(tl, "#hw-co-demo", 0.4);
|
||||
window.hwBoil(tl, "#hw-co-demo", { amp: 1.8, rot: 0.4, frameDrop: 3, seed: 4 });
|
||||
window.hwCalloutOff(tl, "#hw-co-demo", 4.3);
|
||||
tl.set("#hw-co-root", { visibility: "hidden" }, 4.98);
|
||||
window.hwCalloutOff(tl, "#hw-co-demo", 3.3);
|
||||
tl.set("#hw-co-root", { visibility: "hidden" }, 3.98);
|
||||
window.__timelines["hw-callout-circle"] = tl;
|
||||
})();
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "hw-callout-circle",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Scribble Callout Circle",
|
||||
"description": "Wobbled ellipse draws around a target with optional scribble fill, curved connector, and handwritten label",
|
||||
"description": "Wobbled ellipse draws around a target with optional scribble fill and curved connector; the handwritten label pops in WITH momentum and the drawn shapes absorb the arrival with a volume-preserving contact squash; outline and connector take the family stroke matrix (plain/soft/sharp/spray); boils",
|
||||
"tags": ["handwritten", "annotation", "callout", "overlay"],
|
||||
"files": [
|
||||
{
|
||||
@@ -16,5 +16,28 @@
|
||||
"target": "assets/fonts/Caveat-700-latin.woff2",
|
||||
"type": "hyperframes:asset"
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"name": "labelAt",
|
||||
"type": "variant",
|
||||
"values": ["left", "right", "below"],
|
||||
"default": "right",
|
||||
"drives": "connector start point, sweep direction, and label anchor pose (6 geometry params — promoted from the former raw build opt)"
|
||||
},
|
||||
{
|
||||
"name": "strokeType",
|
||||
"type": "variant",
|
||||
"values": ["plain", "soft", "sharp", "spray"],
|
||||
"default": "plain",
|
||||
"drives": "outline + connector stroke texture as authored constants per type (dash geometry, blur, seeded spray dots, mask-clone draw routing); the scribble fill stays plain — texture-on-texture reads as mud"
|
||||
},
|
||||
{
|
||||
"name": "boil",
|
||||
"type": "variant",
|
||||
"values": ["off", "calm", "lively"],
|
||||
"default": "calm",
|
||||
"drives": "boil amp/rot as authored pairs over the family-locked frameDrop 3; seed locked"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -299,6 +299,10 @@
|
||||
"name": "hw-title",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "hw-write-title",
|
||||
"type": "hyperframes:block"
|
||||
},
|
||||
{
|
||||
"name": "instagram-follow",
|
||||
"type": "hyperframes:block"
|
||||
@@ -1529,6 +1533,6 @@
|
||||
}
|
||||
],
|
||||
"catalogArtifact": {
|
||||
"revision": "fb089c68adca459318a6d28b88c09a3ea9ead77d58ef8459bcf90ec79d0b61d9"
|
||||
"revision": "2611d04335aacf5a70649019f948e5ba2f474c38bc45cd59e179d71294cd8cb8"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user