strokes-js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +211 -0
  2. package/dist/glyphs/digits/digit-0.d.ts +3 -0
  3. package/dist/glyphs/digits/digit-0.js +9 -0
  4. package/dist/glyphs/digits/digit-1.d.ts +3 -0
  5. package/dist/glyphs/digits/digit-1.js +9 -0
  6. package/dist/glyphs/digits/digit-2.d.ts +3 -0
  7. package/dist/glyphs/digits/digit-2.js +9 -0
  8. package/dist/glyphs/digits/digit-3.d.ts +3 -0
  9. package/dist/glyphs/digits/digit-3.js +9 -0
  10. package/dist/glyphs/digits/digit-4.d.ts +3 -0
  11. package/dist/glyphs/digits/digit-4.js +9 -0
  12. package/dist/glyphs/digits/digit-5.d.ts +3 -0
  13. package/dist/glyphs/digits/digit-5.js +9 -0
  14. package/dist/glyphs/digits/digit-6.d.ts +3 -0
  15. package/dist/glyphs/digits/digit-6.js +9 -0
  16. package/dist/glyphs/digits/digit-7.d.ts +3 -0
  17. package/dist/glyphs/digits/digit-7.js +9 -0
  18. package/dist/glyphs/digits/digit-8.d.ts +3 -0
  19. package/dist/glyphs/digits/digit-8.js +9 -0
  20. package/dist/glyphs/digits/digit-9.d.ts +3 -0
  21. package/dist/glyphs/digits/digit-9.js +9 -0
  22. package/dist/glyphs/glyphs.d.ts +3 -0
  23. package/dist/glyphs/glyphs.js +138 -0
  24. package/dist/glyphs/letters/letter-a-upper.d.ts +3 -0
  25. package/dist/glyphs/letters/letter-a-upper.js +9 -0
  26. package/dist/glyphs/letters/letter-a.d.ts +3 -0
  27. package/dist/glyphs/letters/letter-a.js +9 -0
  28. package/dist/glyphs/letters/letter-b-upper.d.ts +3 -0
  29. package/dist/glyphs/letters/letter-b-upper.js +9 -0
  30. package/dist/glyphs/letters/letter-b.d.ts +3 -0
  31. package/dist/glyphs/letters/letter-b.js +9 -0
  32. package/dist/glyphs/letters/letter-c-upper.d.ts +3 -0
  33. package/dist/glyphs/letters/letter-c-upper.js +9 -0
  34. package/dist/glyphs/letters/letter-c.d.ts +3 -0
  35. package/dist/glyphs/letters/letter-c.js +9 -0
  36. package/dist/glyphs/letters/letter-d-upper.d.ts +3 -0
  37. package/dist/glyphs/letters/letter-d-upper.js +9 -0
  38. package/dist/glyphs/letters/letter-d.d.ts +3 -0
  39. package/dist/glyphs/letters/letter-d.js +9 -0
  40. package/dist/glyphs/letters/letter-e-upper.d.ts +3 -0
  41. package/dist/glyphs/letters/letter-e-upper.js +9 -0
  42. package/dist/glyphs/letters/letter-e.d.ts +3 -0
  43. package/dist/glyphs/letters/letter-e.js +9 -0
  44. package/dist/glyphs/letters/letter-f-upper.d.ts +3 -0
  45. package/dist/glyphs/letters/letter-f-upper.js +9 -0
  46. package/dist/glyphs/letters/letter-f.d.ts +3 -0
  47. package/dist/glyphs/letters/letter-f.js +9 -0
  48. package/dist/glyphs/letters/letter-g-upper.d.ts +3 -0
  49. package/dist/glyphs/letters/letter-g-upper.js +9 -0
  50. package/dist/glyphs/letters/letter-g.d.ts +3 -0
  51. package/dist/glyphs/letters/letter-g.js +9 -0
  52. package/dist/glyphs/letters/letter-h-upper.d.ts +3 -0
  53. package/dist/glyphs/letters/letter-h-upper.js +9 -0
  54. package/dist/glyphs/letters/letter-h.d.ts +3 -0
  55. package/dist/glyphs/letters/letter-h.js +9 -0
  56. package/dist/glyphs/letters/letter-i-upper.d.ts +3 -0
  57. package/dist/glyphs/letters/letter-i-upper.js +9 -0
  58. package/dist/glyphs/letters/letter-i.d.ts +3 -0
  59. package/dist/glyphs/letters/letter-i.js +9 -0
  60. package/dist/glyphs/letters/letter-j-upper.d.ts +3 -0
  61. package/dist/glyphs/letters/letter-j-upper.js +9 -0
  62. package/dist/glyphs/letters/letter-j.d.ts +3 -0
  63. package/dist/glyphs/letters/letter-j.js +9 -0
  64. package/dist/glyphs/letters/letter-k-upper.d.ts +3 -0
  65. package/dist/glyphs/letters/letter-k-upper.js +9 -0
  66. package/dist/glyphs/letters/letter-k.d.ts +3 -0
  67. package/dist/glyphs/letters/letter-k.js +9 -0
  68. package/dist/glyphs/letters/letter-l-upper.d.ts +3 -0
  69. package/dist/glyphs/letters/letter-l-upper.js +9 -0
  70. package/dist/glyphs/letters/letter-l.d.ts +3 -0
  71. package/dist/glyphs/letters/letter-l.js +9 -0
  72. package/dist/glyphs/letters/letter-m-upper.d.ts +3 -0
  73. package/dist/glyphs/letters/letter-m-upper.js +9 -0
  74. package/dist/glyphs/letters/letter-m.d.ts +3 -0
  75. package/dist/glyphs/letters/letter-m.js +9 -0
  76. package/dist/glyphs/letters/letter-n-upper.d.ts +3 -0
  77. package/dist/glyphs/letters/letter-n-upper.js +9 -0
  78. package/dist/glyphs/letters/letter-n.d.ts +3 -0
  79. package/dist/glyphs/letters/letter-n.js +9 -0
  80. package/dist/glyphs/letters/letter-o-upper.d.ts +3 -0
  81. package/dist/glyphs/letters/letter-o-upper.js +9 -0
  82. package/dist/glyphs/letters/letter-o.d.ts +3 -0
  83. package/dist/glyphs/letters/letter-o.js +9 -0
  84. package/dist/glyphs/letters/letter-p-upper.d.ts +3 -0
  85. package/dist/glyphs/letters/letter-p-upper.js +9 -0
  86. package/dist/glyphs/letters/letter-p.d.ts +3 -0
  87. package/dist/glyphs/letters/letter-p.js +9 -0
  88. package/dist/glyphs/letters/letter-q-upper.d.ts +3 -0
  89. package/dist/glyphs/letters/letter-q-upper.js +9 -0
  90. package/dist/glyphs/letters/letter-q.d.ts +3 -0
  91. package/dist/glyphs/letters/letter-q.js +9 -0
  92. package/dist/glyphs/letters/letter-r-upper.d.ts +3 -0
  93. package/dist/glyphs/letters/letter-r-upper.js +9 -0
  94. package/dist/glyphs/letters/letter-r.d.ts +3 -0
  95. package/dist/glyphs/letters/letter-r.js +9 -0
  96. package/dist/glyphs/letters/letter-s-upper.d.ts +3 -0
  97. package/dist/glyphs/letters/letter-s-upper.js +9 -0
  98. package/dist/glyphs/letters/letter-s.d.ts +3 -0
  99. package/dist/glyphs/letters/letter-s.js +9 -0
  100. package/dist/glyphs/letters/letter-t-upper.d.ts +3 -0
  101. package/dist/glyphs/letters/letter-t-upper.js +9 -0
  102. package/dist/glyphs/letters/letter-t.d.ts +3 -0
  103. package/dist/glyphs/letters/letter-t.js +9 -0
  104. package/dist/glyphs/letters/letter-u-upper.d.ts +3 -0
  105. package/dist/glyphs/letters/letter-u-upper.js +9 -0
  106. package/dist/glyphs/letters/letter-u.d.ts +3 -0
  107. package/dist/glyphs/letters/letter-u.js +9 -0
  108. package/dist/glyphs/letters/letter-v-upper.d.ts +3 -0
  109. package/dist/glyphs/letters/letter-v-upper.js +9 -0
  110. package/dist/glyphs/letters/letter-v.d.ts +3 -0
  111. package/dist/glyphs/letters/letter-v.js +9 -0
  112. package/dist/glyphs/letters/letter-w-upper.d.ts +3 -0
  113. package/dist/glyphs/letters/letter-w-upper.js +9 -0
  114. package/dist/glyphs/letters/letter-w.d.ts +3 -0
  115. package/dist/glyphs/letters/letter-w.js +9 -0
  116. package/dist/glyphs/letters/letter-x-upper.d.ts +3 -0
  117. package/dist/glyphs/letters/letter-x-upper.js +9 -0
  118. package/dist/glyphs/letters/letter-x.d.ts +3 -0
  119. package/dist/glyphs/letters/letter-x.js +9 -0
  120. package/dist/glyphs/letters/letter-y-upper.d.ts +3 -0
  121. package/dist/glyphs/letters/letter-y-upper.js +9 -0
  122. package/dist/glyphs/letters/letter-y.d.ts +3 -0
  123. package/dist/glyphs/letters/letter-y.js +9 -0
  124. package/dist/glyphs/letters/letter-z-upper.d.ts +3 -0
  125. package/dist/glyphs/letters/letter-z-upper.js +9 -0
  126. package/dist/glyphs/letters/letter-z.d.ts +3 -0
  127. package/dist/glyphs/letters/letter-z.js +9 -0
  128. package/dist/glyphs/punctuation/punct-apostrophe.d.ts +3 -0
  129. package/dist/glyphs/punctuation/punct-apostrophe.js +9 -0
  130. package/dist/glyphs/punctuation/punct-comma.d.ts +3 -0
  131. package/dist/glyphs/punctuation/punct-comma.js +9 -0
  132. package/dist/glyphs/punctuation/punct-exclamation.d.ts +3 -0
  133. package/dist/glyphs/punctuation/punct-exclamation.js +9 -0
  134. package/dist/glyphs/punctuation/punct-hyphen.d.ts +3 -0
  135. package/dist/glyphs/punctuation/punct-hyphen.js +9 -0
  136. package/dist/glyphs/punctuation/punct-period.d.ts +3 -0
  137. package/dist/glyphs/punctuation/punct-period.js +9 -0
  138. package/dist/glyphs/punctuation/punct-question.d.ts +3 -0
  139. package/dist/glyphs/punctuation/punct-question.js +9 -0
  140. package/dist/glyphs/types.d.ts +5 -0
  141. package/dist/glyphs/types.js +1 -0
  142. package/dist/index.d.ts +11 -0
  143. package/dist/index.js +16 -0
  144. package/dist/observer.d.ts +8 -0
  145. package/dist/observer.js +17 -0
  146. package/dist/renderer.d.ts +12 -0
  147. package/dist/renderer.js +98 -0
  148. package/package.json +41 -0
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: monoline "w", 3 wobble variants. */
2
+ export const letterW = {
3
+ width: 13,
4
+ variants: [
5
+ "M1 9 L3.5 20 L6 12 L8.5 20 L11 9",
6
+ "M1.2 8.9 L3.4 20.2 L6.2 12.1 L8.6 19.9 L10.8 8.8",
7
+ "M1.1 9.0 L3.5 20.1 L6.2 12.0 L8.7 20.2 L11.0 8.9",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: monoline "X" (uppercase), 3 wobble variants. */
3
+ export declare const letterXUpper: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: monoline "X" (uppercase), 3 wobble variants. */
2
+ export const letterXUpper = {
3
+ width: 13,
4
+ variants: [
5
+ "M1 4 L11 20 M11 4 L1 20",
6
+ "M0.9 3.9 L11.1 19.8 M11.2 4.0 L1.2 20.2",
7
+ "M0.9 3.8 L11.2 20.1 M11.1 4.1 L1.0 19.9",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: monoline "x", 3 wobble variants. */
3
+ export declare const letterX: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: monoline "x", 3 wobble variants. */
2
+ export const letterX = {
3
+ width: 11,
4
+ variants: [
5
+ "M1 9 L9 20 M9 9 L1 20",
6
+ "M1.2 8.9 L9.0 20.0 M9.2 9.1 L1.1 20.0",
7
+ "M0.8 9.1 L8.9 19.8 M9.1 9.0 L0.9 19.9",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: monoline "Y" (uppercase), 3 wobble variants. */
3
+ export declare const letterYUpper: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: monoline "Y" (uppercase), 3 wobble variants. */
2
+ export const letterYUpper = {
3
+ width: 14,
4
+ variants: [
5
+ "M1 4 L6.5 12.5 L12 4 M6.5 12.5 L6.5 20",
6
+ "M1.0 4.0 L6.4 12.6 L12.1 4.0 M6.6 12.6 L6.7 20.2",
7
+ "M1.0 4.2 L6.7 12.6 L11.8 4.1 M6.7 12.7 L6.4 20.0",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: monoline "y", 3 wobble variants. */
3
+ export declare const letterY: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: monoline "y", 3 wobble variants. */
2
+ export const letterY = {
3
+ width: 11,
4
+ variants: [
5
+ "M1 9 L5 15 L9 9 M5 15 L4.2 21.5 C 4 22.8 2.6 23.2 1.5 22.6",
6
+ "M0.9 8.8 L4.9 15.0 L9.0 9.1 M5.1 14.9 L4.0 21.4 C 3.8 22.8 2.5 23.0 1.7 22.6",
7
+ "M1.0 9.1 L5.1 14.9 L9.1 9.2 M5.2 15.1 L4.4 21.7 C 3.9 22.7 2.7 23.2 1.3 22.7",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: monoline "Z" (uppercase), 3 wobble variants. */
3
+ export declare const letterZUpper: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: monoline "Z" (uppercase), 3 wobble variants. */
2
+ export const letterZUpper = {
3
+ width: 13,
4
+ variants: [
5
+ "M1 4 L11 4 L1 20 L11 20",
6
+ "M1.1 4.1 L10.9 4.0 L0.9 20.1 L11.0 19.8",
7
+ "M1.1 4.0 L11.0 4.0 L0.9 20.0 L11.0 20.0",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: monoline "z", 3 wobble variants. */
3
+ export declare const letterZ: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: monoline "z", 3 wobble variants. */
2
+ export const letterZ = {
3
+ width: 11,
4
+ variants: [
5
+ "M1 9 L9 9 L1 20 L9 20",
6
+ "M1.2 9.2 L8.8 8.8 L1.2 20.1 L9.0 19.9",
7
+ "M0.8 8.9 L9.0 8.9 L0.9 19.8 L9.2 19.9",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: "'", 3 wobble variants. */
3
+ export declare const punctApostrophe: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: "'", 3 wobble variants. */
2
+ export const punctApostrophe = {
3
+ width: 6,
4
+ variants: [
5
+ "M4.5 4 L4 7",
6
+ "M4.4 3.9 L4.1 7.1",
7
+ "M4.4 3.9 L3.9 6.9",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: ",", 3 wobble variants. */
3
+ export declare const punctComma: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: ",", 3 wobble variants. */
2
+ export const punctComma = {
3
+ width: 6,
4
+ variants: [
5
+ "M4.5 18.5 C 4.5 20 3 21.5 2 22",
6
+ "M4.5 18.5 C 4.5 20.0 3.1 21.5 2.0 22.0",
7
+ "M4.5 18.5 C 4.5 20.0 3.0 21.4 1.9 22.1",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: "!", 3 wobble variants. */
3
+ export declare const punctExclamation: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: "!", 3 wobble variants. */
2
+ export const punctExclamation = {
3
+ width: 7,
4
+ variants: [
5
+ "M5 4 L5 15 M5 18.5 L5.2 18.7",
6
+ "M5.0 4.0 L5.0 14.9 M5.0 18.5 L5.3 18.8",
7
+ "M4.9 4.0 L5.1 14.9 M5.0 18.4 L5.3 18.6",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: "-", 3 wobble variants. */
3
+ export declare const punctHyphen: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: "-", 3 wobble variants. */
2
+ export const punctHyphen = {
3
+ width: 8,
4
+ variants: [
5
+ "M1 12 L7 12",
6
+ "M0.9 12.0 L7.0 11.9",
7
+ "M1.0 12.0 L7.0 12.0",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: ".", 3 wobble variants. */
3
+ export declare const punctPeriod: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: ".", 3 wobble variants. */
2
+ export const punctPeriod = {
3
+ width: 6,
4
+ variants: [
5
+ "M4 19.5 L4.2 19.7",
6
+ "M3.9 19.6 L4.2 19.8",
7
+ "M3.9 19.6 L4.1 19.6",
8
+ ],
9
+ };
@@ -0,0 +1,3 @@
1
+ import type { GlyphEntry } from "../types.js";
2
+ /** Real hand-drawn glyph: "?", 3 wobble variants. */
3
+ export declare const punctQuestion: GlyphEntry;
@@ -0,0 +1,9 @@
1
+ /** Real hand-drawn glyph: "?", 3 wobble variants. */
2
+ export const punctQuestion = {
3
+ width: 9,
4
+ variants: [
5
+ "M2 7.5 C 2 4.5 4.5 3.5 6.5 4.5 C 8.5 5.5 8.3 8 6.5 9.5 C 5.3 10.5 5 11.5 5 13.5 M5 17.5 L5.2 17.7",
6
+ "M2.0 7.4 C 2.0 4.6 4.6 3.5 6.4 4.4 C 8.5 5.5 8.3 8.1 6.4 9.6 C 5.4 10.5 5.1 11.4 4.9 13.6 M4.9 17.5 L5.3 17.8",
7
+ "M1.9 7.5 C 2.0 4.5 4.4 3.5 6.4 4.6 C 8.5 5.4 8.3 7.9 6.5 9.5 C 5.2 10.5 5.1 11.5 4.9 13.4 M5.0 17.4 L5.1 17.7",
8
+ ],
9
+ };
@@ -0,0 +1,5 @@
1
+ export interface GlyphEntry {
2
+ width: number;
3
+ variants: string[];
4
+ }
5
+ export type GlyphDatabase = Record<string, GlyphEntry>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ export { renderText, animateWriting, type RenderTextOptions } from "./renderer.js";
2
+ export { attach, type AttachOptions } from "./observer.js";
3
+ import { type RenderTextOptions } from "./renderer.js";
4
+ import { type AttachOptions } from "./observer.js";
5
+ export interface WriteOptions extends RenderTextOptions, AttachOptions {
6
+ }
7
+ /**
8
+ * One-call entry point: renders `text` into `target` and wires up the scroll-triggered
9
+ * draw-in animation. Pass `{ animate: false }` for static, fully-drawn output (no observer).
10
+ */
11
+ export declare function write(target: HTMLElement | string, text: string, options?: WriteOptions): void;
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ export { renderText, animateWriting } from "./renderer.js";
2
+ export { attach } from "./observer.js";
3
+ import { renderText } from "./renderer.js";
4
+ import { attach } from "./observer.js";
5
+ /**
6
+ * One-call entry point: renders `text` into `target` and wires up the scroll-triggered
7
+ * draw-in animation. Pass `{ animate: false }` for static, fully-drawn output (no observer).
8
+ */
9
+ export function write(target, text, options = {}) {
10
+ const el = typeof target === "string" ? document.querySelector(target) : target;
11
+ if (!el)
12
+ return;
13
+ el.innerHTML = renderText(text, options);
14
+ if (options.animate !== false)
15
+ attach(el, options);
16
+ }
@@ -0,0 +1,8 @@
1
+ export interface AttachOptions {
2
+ root?: Element | null;
3
+ rootMargin?: string;
4
+ threshold?: number | number[];
5
+ once?: boolean;
6
+ }
7
+ /** Wires an IntersectionObserver to a container so its hw-word svgs animate in once scrolled into view. */
8
+ export declare function attach(el: HTMLElement, options?: AttachOptions): void;
@@ -0,0 +1,17 @@
1
+ import { animateWriting } from "./renderer.js";
2
+ /** Wires an IntersectionObserver to a container so its hw-word svgs animate in once scrolled into view. */
3
+ export function attach(el, options = {}) {
4
+ const { once = true, ...observerOptions } = options;
5
+ const observer = new IntersectionObserver((entries) => {
6
+ for (const entry of entries) {
7
+ if (!entry.isIntersecting)
8
+ continue;
9
+ for (const word of entry.target.querySelectorAll(".hw-word")) {
10
+ animateWriting(word);
11
+ }
12
+ if (once)
13
+ observer.unobserve(entry.target);
14
+ }
15
+ }, observerOptions);
16
+ observer.observe(el);
17
+ }
@@ -0,0 +1,12 @@
1
+ export interface RenderTextOptions {
2
+ /** Draw letters in via stroke-dashoffset when the word scrolls into view (default true). Set false for static, fully-drawn output. */
3
+ animate?: boolean;
4
+ }
5
+ /**
6
+ * Renders text as a sentence wrapper containing one <svg> per word.
7
+ * When animate (default), each word's letters draw in via stroke-dashoffset once `.hw-visible`
8
+ * is toggled on it — pass the result to observer.ts's `attach()` to trigger that on scroll.
9
+ */
10
+ export declare function renderText(text: string, options?: RenderTextOptions): string;
11
+ /** Toggles the class that starts the draw-in transition for a rendered word's strokes. */
12
+ export declare function animateWriting(el: Element): void;
@@ -0,0 +1,98 @@
1
+ import { glyphs } from "./glyphs/glyphs.js";
2
+ const PUNCTUATION = new Set([".", ",", "!", "?", "'", "-"]);
3
+ const LETTER_HEIGHT = 24;
4
+ const STAGGER_MS = 60;
5
+ const STROKE_MS = 400;
6
+ /** Tracks the last variant index picked per character so the same glyph never repeats back-to-back. */
7
+ const lastVariant = new Map();
8
+ function pickVariant(char, entry) {
9
+ const prev = lastVariant.get(char);
10
+ if (entry.variants.length === 1)
11
+ return 0;
12
+ let index = Math.floor(Math.random() * entry.variants.length);
13
+ while (index === prev) {
14
+ index = Math.floor(Math.random() * entry.variants.length);
15
+ }
16
+ lastVariant.set(char, index);
17
+ return index;
18
+ }
19
+ function splitWords(text) {
20
+ // Attach trailing punctuation to the preceding word (e.g. "word," stays one unit).
21
+ return text.split(/\s+/).filter((w) => w.length > 0);
22
+ }
23
+ const JITTER_ROTATE_DEG = 7;
24
+ const JITTER_BASELINE = 1;
25
+ const JITTER_SCALE = 0.1;
26
+ /** Small per-instance wobble (rotation, baseline drift, scale) layered on top of the picked variant, so no two renders of the same letter sit identically even when they share a path. */
27
+ function randomJitter() {
28
+ return {
29
+ rotate: (Math.random() * 2 - 1) * JITTER_ROTATE_DEG,
30
+ dy: (Math.random() * 2 - 1) * JITTER_BASELINE,
31
+ scale: 1 + (Math.random() * 2 - 1) * JITTER_SCALE,
32
+ };
33
+ }
34
+ function renderWord(word, delayOffset, animate) {
35
+ let x = 0;
36
+ const paths = [];
37
+ let letterIndex = 0;
38
+ for (const rawChar of word) {
39
+ const char = PUNCTUATION.has(rawChar)
40
+ ? rawChar
41
+ : glyphs[rawChar]
42
+ ? rawChar
43
+ : rawChar.toLowerCase();
44
+ const entry = glyphs[char];
45
+ if (!entry)
46
+ continue;
47
+ const variantIndex = pickVariant(char, entry);
48
+ const d = entry.variants[variantIndex];
49
+ const style = animate
50
+ ? `stroke-dasharray:1;stroke-dashoffset:1;transition:stroke-dashoffset ${STROKE_MS}ms ease ${(delayOffset + letterIndex) * STAGGER_MS}ms;`
51
+ : "";
52
+ const { rotate, dy, scale } = randomJitter();
53
+ const cx = entry.width / 2;
54
+ const cy = LETTER_HEIGHT / 2;
55
+ // Position first, then rotate/scale around the letter's own center (not the SVG origin)
56
+ // so the jitter can't nudge later letters in long words out of position.
57
+ const transform = `translate(${x} ${dy.toFixed(2)}) translate(${cx} ${cy}) ` +
58
+ `rotate(${rotate.toFixed(1)}) scale(${scale.toFixed(3)}) translate(${-cx} ${-cy})`;
59
+ paths.push(`<path d="${d}" transform="${transform}" stroke="var(--hw-color, #000)" fill="none" ` +
60
+ `stroke-width="2" stroke-linecap="round" pathLength="1" style="${style}" class="hw-letter" />`);
61
+ x += entry.width;
62
+ letterIndex++;
63
+ }
64
+ const svg = `<svg class="hw-word" xmlns="http://www.w3.org/2000/svg" width="${x}" height="${LETTER_HEIGHT}" ` +
65
+ `viewBox="0 0 ${x} ${LETTER_HEIGHT}" aria-hidden="true">${paths.join("")}</svg>`;
66
+ return { svg, letterCount: letterIndex };
67
+ }
68
+ /**
69
+ * Renders text as a sentence wrapper containing one <svg> per word.
70
+ * When animate (default), each word's letters draw in via stroke-dashoffset once `.hw-visible`
71
+ * is toggled on it — pass the result to observer.ts's `attach()` to trigger that on scroll.
72
+ */
73
+ export function renderText(text, options = {}) {
74
+ const { animate = true } = options;
75
+ const words = splitWords(text);
76
+ let delayOffset = 0;
77
+ const wordSvgs = [];
78
+ for (const word of words) {
79
+ const { svg, letterCount } = renderWord(word, delayOffset, animate);
80
+ wordSvgs.push(svg);
81
+ delayOffset += letterCount;
82
+ }
83
+ return `<span class="hw-sentence" aria-label="${escapeAttr(text)}">${wordSvgs.join(" ")}</span>`;
84
+ }
85
+ function escapeAttr(value) {
86
+ return value
87
+ .replace(/&/g, "&amp;")
88
+ .replace(/"/g, "&quot;")
89
+ .replace(/</g, "&lt;")
90
+ .replace(/>/g, "&gt;");
91
+ }
92
+ /** Toggles the class that starts the draw-in transition for a rendered word's strokes. */
93
+ export function animateWriting(el) {
94
+ el.classList.add("hw-visible");
95
+ for (const path of el.querySelectorAll(".hw-letter")) {
96
+ path.style.strokeDashoffset = "0";
97
+ }
98
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "strokes-js",
3
+ "version": "0.1.0",
4
+ "description": "Renders text as animated hand-drawn SVG",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "sideEffects": false,
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "typecheck": "tsc --noEmit",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/a-elhaag/strokes.js.git"
26
+ },
27
+ "homepage": "https://github.com/a-elhaag/strokes.js#readme",
28
+ "bugs": "https://github.com/a-elhaag/strokes.js/issues",
29
+ "keywords": [
30
+ "handwriting",
31
+ "svg",
32
+ "animation",
33
+ "text",
34
+ "typography"
35
+ ],
36
+ "author": "",
37
+ "license": "ISC",
38
+ "devDependencies": {
39
+ "typescript": "^7.0.2"
40
+ }
41
+ }