@webspire/mcp 0.8.1 → 0.10.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.
- package/README.md +76 -52
- package/css/webspire-components.css +48 -0
- package/data/fonts.json +582 -0
- package/data/registry.json +15501 -3312
- package/dist/registration.d.ts +2 -2
- package/dist/registration.js +439 -304
- package/dist/registry.d.ts +14 -4
- package/dist/registry.js +82 -22
- package/dist/search.d.ts +11 -2
- package/dist/search.js +397 -266
- package/dist/types.d.ts +77 -0
- package/package.json +2 -2
package/dist/search.js
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
const STEM_SUFFIXES = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
'tion',
|
|
3
|
+
'sion',
|
|
4
|
+
'ions',
|
|
5
|
+
'ment',
|
|
6
|
+
'ness',
|
|
7
|
+
'able',
|
|
8
|
+
'ible',
|
|
9
|
+
'ous',
|
|
10
|
+
'ive',
|
|
11
|
+
'ing',
|
|
12
|
+
'ed',
|
|
13
|
+
'es',
|
|
14
|
+
'ly',
|
|
15
|
+
's',
|
|
16
16
|
];
|
|
17
17
|
/** Common synonyms for CSS/UI terms — maps alternative words to canonical terms */
|
|
18
18
|
const SYNONYMS = {
|
|
19
|
-
blur: [
|
|
20
|
-
glass: [
|
|
21
|
-
fade: [
|
|
22
|
-
slide: [
|
|
23
|
-
hover: [
|
|
24
|
-
card: [
|
|
25
|
-
animation: [
|
|
26
|
-
glow: [
|
|
27
|
-
scroll: [
|
|
28
|
-
text: [
|
|
29
|
-
gradient: [
|
|
30
|
-
dark: [
|
|
31
|
-
button: [
|
|
32
|
-
responsive: [
|
|
33
|
-
focus: [
|
|
34
|
-
stagger: [
|
|
35
|
-
reveal: [
|
|
36
|
-
shimmer: [
|
|
37
|
-
ripple: [
|
|
19
|
+
blur: ['glass', 'frosted', 'glassmorphism', 'backdrop'],
|
|
20
|
+
glass: ['blur', 'frosted', 'glassmorphism', 'transparent'],
|
|
21
|
+
fade: ['opacity', 'appear', 'entrance', 'reveal'],
|
|
22
|
+
slide: ['translate', 'move', 'enter', 'entrance'],
|
|
23
|
+
hover: ['mouse', 'interaction', 'lift', 'pointer'],
|
|
24
|
+
card: ['panel', 'surface', 'container', 'box'],
|
|
25
|
+
animation: ['animate', 'motion', 'transition', 'keyframe'],
|
|
26
|
+
glow: ['neon', 'light', 'shine', 'luminous'],
|
|
27
|
+
scroll: ['viewport', 'intersection', 'parallax', 'progress'],
|
|
28
|
+
text: ['font', 'typography', 'heading', 'title'],
|
|
29
|
+
gradient: ['color', 'blend', 'mesh', 'aurora'],
|
|
30
|
+
dark: ['darkmode', 'theme', 'scheme', 'night'],
|
|
31
|
+
button: ['btn', 'cta', 'action', 'click'],
|
|
32
|
+
responsive: ['mobile', 'fluid', 'adaptive', 'clamp'],
|
|
33
|
+
focus: ['keyboard', 'a11y', 'accessibility', 'ring'],
|
|
34
|
+
stagger: ['cascade', 'sequence', 'delay', 'children'],
|
|
35
|
+
reveal: ['show', 'appear', 'unhide', 'visible', 'fade'],
|
|
36
|
+
shimmer: ['skeleton', 'loading', 'placeholder', 'pulse'],
|
|
37
|
+
ripple: ['click', 'material', 'touch', 'feedback'],
|
|
38
38
|
};
|
|
39
39
|
export function stem(word) {
|
|
40
40
|
for (const suffix of STEM_SUFFIXES) {
|
|
@@ -88,22 +88,16 @@ export function scoreSnippet(snippet, queryStems, expandedTerms) {
|
|
|
88
88
|
const desc = snippet.description.toLowerCase();
|
|
89
89
|
score += queryStems.filter((w) => desc.includes(w)).length * 2;
|
|
90
90
|
// Tags
|
|
91
|
-
const tagStr = snippet.tags.join(
|
|
91
|
+
const tagStr = snippet.tags.join(' ').toLowerCase();
|
|
92
92
|
score += queryStems.filter((w) => tagStr.includes(w)).length * 2;
|
|
93
93
|
// Classes
|
|
94
|
-
const classes = snippet.meta.classes.join(
|
|
94
|
+
const classes = snippet.meta.classes.join(' ').toLowerCase();
|
|
95
95
|
score += queryStems.filter((w) => classes.includes(w)).length;
|
|
96
96
|
// Synonym-expanded terms — lower weight to avoid noise
|
|
97
97
|
const synonymOnly = expandedTerms.filter((t) => !queryStems.includes(t));
|
|
98
98
|
if (synonymOnly.length > 0) {
|
|
99
|
-
const allText = [
|
|
100
|
-
|
|
101
|
-
desc,
|
|
102
|
-
tagStr,
|
|
103
|
-
...snippet.meta.useCases,
|
|
104
|
-
...snippet.meta.solves,
|
|
105
|
-
]
|
|
106
|
-
.join(" ")
|
|
99
|
+
const allText = [title, desc, tagStr, ...snippet.meta.useCases, ...snippet.meta.solves]
|
|
100
|
+
.join(' ')
|
|
107
101
|
.toLowerCase();
|
|
108
102
|
score += synonymOnly.filter((w) => allText.includes(w)).length;
|
|
109
103
|
}
|
|
@@ -179,12 +173,10 @@ export function scorePattern(pattern, queryStems, expandedTerms, options) {
|
|
|
179
173
|
// Summary + Description
|
|
180
174
|
const summary = pattern.summary.toLowerCase();
|
|
181
175
|
score += queryStems.filter((w) => summary.includes(w)).length * 2;
|
|
182
|
-
const desc = (pattern.description ??
|
|
176
|
+
const desc = (pattern.description ?? '').toLowerCase();
|
|
183
177
|
score += queryStems.filter((w) => desc.includes(w)).length * 2;
|
|
184
178
|
// Tags + Keywords
|
|
185
|
-
const tagStr = [...pattern.tags, ...pattern.search.keywords]
|
|
186
|
-
.join(" ")
|
|
187
|
-
.toLowerCase();
|
|
179
|
+
const tagStr = [...pattern.tags, ...pattern.search.keywords].join(' ').toLowerCase();
|
|
188
180
|
score += queryStems.filter((w) => tagStr.includes(w)).length * 2;
|
|
189
181
|
// Semantic domain match — strong boost
|
|
190
182
|
if (options.domain && pattern.semantics?.domains.includes(options.domain)) {
|
|
@@ -213,7 +205,7 @@ export function scorePattern(pattern, queryStems, expandedTerms, options) {
|
|
|
213
205
|
...pattern.search.intent,
|
|
214
206
|
...pattern.search.useCases,
|
|
215
207
|
]
|
|
216
|
-
.join(
|
|
208
|
+
.join(' ')
|
|
217
209
|
.toLowerCase();
|
|
218
210
|
score += synonymOnly.filter((w) => allText.includes(w)).length;
|
|
219
211
|
}
|
|
@@ -268,28 +260,28 @@ const ROLE_LIMITS = {
|
|
|
268
260
|
footer: 1,
|
|
269
261
|
};
|
|
270
262
|
const ROLE_FAMILY_BONUSES = {
|
|
271
|
-
navigation: { navbar: 3,
|
|
263
|
+
navigation: { navbar: 3, 'announcement-bar': 1, search: 1 },
|
|
272
264
|
entry: {
|
|
273
265
|
hero: 4,
|
|
274
|
-
|
|
266
|
+
'page-header': 3,
|
|
275
267
|
portfolio: 2,
|
|
276
|
-
|
|
277
|
-
|
|
268
|
+
'product-detail': 2,
|
|
269
|
+
'blog-details': 3,
|
|
278
270
|
},
|
|
279
271
|
supporting: {
|
|
280
272
|
features: 3,
|
|
281
273
|
services: 3,
|
|
282
274
|
about: 2,
|
|
283
275
|
blog: 2,
|
|
284
|
-
|
|
285
|
-
|
|
276
|
+
'blog-timeline': 2,
|
|
277
|
+
'related-articles': 2,
|
|
286
278
|
legal: 2,
|
|
287
279
|
careers: 2,
|
|
288
280
|
portfolio: 2,
|
|
289
|
-
|
|
281
|
+
'case-study': 2,
|
|
290
282
|
integrations: 2,
|
|
291
283
|
faq: 2,
|
|
292
|
-
|
|
284
|
+
'product-grid': 2,
|
|
293
285
|
resume: 2,
|
|
294
286
|
},
|
|
295
287
|
conversion: {
|
|
@@ -297,37 +289,37 @@ const ROLE_FAMILY_BONUSES = {
|
|
|
297
289
|
contact: 3,
|
|
298
290
|
cta: 3,
|
|
299
291
|
appointment: 2,
|
|
300
|
-
|
|
301
|
-
|
|
292
|
+
'cart-progress': 2,
|
|
293
|
+
'deal-of-the-day': 2,
|
|
302
294
|
newsletter: 2,
|
|
303
295
|
},
|
|
304
296
|
trust: {
|
|
305
297
|
testimonials: 3,
|
|
306
|
-
|
|
298
|
+
'logo-cloud': 2,
|
|
307
299
|
team: 2,
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
300
|
+
'trust-strip': 2,
|
|
301
|
+
'awards-list': 2,
|
|
302
|
+
'product-reviews': 3,
|
|
311
303
|
about: 1,
|
|
312
|
-
|
|
304
|
+
'blog-read-next': 2,
|
|
313
305
|
},
|
|
314
306
|
footer: { footer: 4 },
|
|
315
307
|
};
|
|
316
308
|
const DOMAIN_FAMILY_BONUSES = {
|
|
317
309
|
legal: {
|
|
318
310
|
hero: 2,
|
|
319
|
-
|
|
311
|
+
'page-header': 2,
|
|
320
312
|
about: 2,
|
|
321
313
|
legal: 4,
|
|
322
314
|
team: 3,
|
|
323
315
|
testimonials: 3,
|
|
324
316
|
contact: 3,
|
|
325
317
|
faq: 2,
|
|
326
|
-
|
|
318
|
+
'awards-list': 2,
|
|
327
319
|
},
|
|
328
320
|
healthcare: {
|
|
329
321
|
hero: 2,
|
|
330
|
-
|
|
322
|
+
'page-header': 2,
|
|
331
323
|
about: 2,
|
|
332
324
|
appointment: 3,
|
|
333
325
|
team: 3,
|
|
@@ -336,10 +328,10 @@ const DOMAIN_FAMILY_BONUSES = {
|
|
|
336
328
|
},
|
|
337
329
|
education: {
|
|
338
330
|
hero: 2,
|
|
339
|
-
|
|
331
|
+
'page-header': 2,
|
|
340
332
|
blog: 3,
|
|
341
|
-
|
|
342
|
-
|
|
333
|
+
'blog-details': 3,
|
|
334
|
+
'related-articles': 2,
|
|
343
335
|
newsletter: 2,
|
|
344
336
|
features: 2,
|
|
345
337
|
pricing: 2,
|
|
@@ -349,7 +341,7 @@ const DOMAIN_FAMILY_BONUSES = {
|
|
|
349
341
|
},
|
|
350
342
|
finance: {
|
|
351
343
|
hero: 2,
|
|
352
|
-
|
|
344
|
+
'page-header': 2,
|
|
353
345
|
about: 2,
|
|
354
346
|
pricing: 3,
|
|
355
347
|
comparison: 3,
|
|
@@ -358,10 +350,10 @@ const DOMAIN_FAMILY_BONUSES = {
|
|
|
358
350
|
},
|
|
359
351
|
saas: {
|
|
360
352
|
hero: 3,
|
|
361
|
-
|
|
353
|
+
'page-header': 2,
|
|
362
354
|
blog: 2,
|
|
363
|
-
|
|
364
|
-
|
|
355
|
+
'blog-details': 3,
|
|
356
|
+
'related-articles': 2,
|
|
365
357
|
newsletter: 2,
|
|
366
358
|
careers: 2,
|
|
367
359
|
legal: 2,
|
|
@@ -371,58 +363,58 @@ const DOMAIN_FAMILY_BONUSES = {
|
|
|
371
363
|
integrations: 3,
|
|
372
364
|
testimonials: 2,
|
|
373
365
|
faq: 2,
|
|
374
|
-
|
|
366
|
+
'logo-cloud': 2,
|
|
375
367
|
},
|
|
376
368
|
ecommerce: {
|
|
377
369
|
hero: 3,
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
370
|
+
'product-detail': 5,
|
|
371
|
+
'product-reviews': 4,
|
|
372
|
+
'product-grid': 4,
|
|
373
|
+
'deal-of-the-day': 3,
|
|
374
|
+
'cart-progress': 3,
|
|
383
375
|
testimonials: 2,
|
|
384
376
|
faq: 2,
|
|
385
|
-
|
|
377
|
+
'logo-cloud': 1,
|
|
386
378
|
},
|
|
387
379
|
agency: {
|
|
388
380
|
hero: 2,
|
|
389
|
-
|
|
381
|
+
'page-header': 2,
|
|
390
382
|
about: 3,
|
|
391
383
|
blog: 2,
|
|
392
|
-
|
|
393
|
-
|
|
384
|
+
'blog-details': 2,
|
|
385
|
+
'related-articles': 2,
|
|
394
386
|
newsletter: 1,
|
|
395
387
|
careers: 2,
|
|
396
388
|
portfolio: 4,
|
|
397
389
|
services: 3,
|
|
398
|
-
|
|
399
|
-
|
|
390
|
+
'case-study': 3,
|
|
391
|
+
'awards-list': 2,
|
|
400
392
|
team: 2,
|
|
401
393
|
contact: 2,
|
|
402
394
|
},
|
|
403
395
|
consulting: {
|
|
404
396
|
hero: 2,
|
|
405
|
-
|
|
397
|
+
'page-header': 2,
|
|
406
398
|
about: 3,
|
|
407
399
|
blog: 2,
|
|
408
|
-
|
|
409
|
-
|
|
400
|
+
'blog-details': 2,
|
|
401
|
+
'related-articles': 2,
|
|
410
402
|
newsletter: 1,
|
|
411
403
|
careers: 1,
|
|
412
404
|
legal: 1,
|
|
413
405
|
services: 3,
|
|
414
|
-
|
|
406
|
+
'case-study': 3,
|
|
415
407
|
team: 2,
|
|
416
408
|
contact: 2,
|
|
417
409
|
faq: 2,
|
|
418
410
|
},
|
|
419
411
|
nonprofit: {
|
|
420
412
|
hero: 2,
|
|
421
|
-
|
|
413
|
+
'page-header': 2,
|
|
422
414
|
about: 3,
|
|
423
415
|
blog: 2,
|
|
424
|
-
|
|
425
|
-
|
|
416
|
+
'blog-details': 2,
|
|
417
|
+
'related-articles': 2,
|
|
426
418
|
newsletter: 3,
|
|
427
419
|
careers: 1,
|
|
428
420
|
stats: 3,
|
|
@@ -440,7 +432,7 @@ const DOMAIN_FAMILY_BONUSES = {
|
|
|
440
432
|
},
|
|
441
433
|
realestate: {
|
|
442
434
|
hero: 2,
|
|
443
|
-
|
|
435
|
+
'page-header': 2,
|
|
444
436
|
about: 2,
|
|
445
437
|
careers: 1,
|
|
446
438
|
gallery: 3,
|
|
@@ -452,150 +444,141 @@ const DOMAIN_FAMILY_BONUSES = {
|
|
|
452
444
|
hero: 2,
|
|
453
445
|
about: 2,
|
|
454
446
|
blog: 2,
|
|
455
|
-
|
|
456
|
-
|
|
447
|
+
'blog-details': 3,
|
|
448
|
+
'related-articles': 2,
|
|
457
449
|
newsletter: 1,
|
|
458
450
|
portfolio: 3,
|
|
459
451
|
resume: 3,
|
|
460
452
|
testimonials: 1,
|
|
461
453
|
contact: 2,
|
|
462
|
-
|
|
454
|
+
'link-in-bio': 2,
|
|
463
455
|
},
|
|
464
456
|
};
|
|
465
457
|
export const DOMAIN_KEYS = [
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
458
|
+
'legal',
|
|
459
|
+
'healthcare',
|
|
460
|
+
'education',
|
|
461
|
+
'finance',
|
|
462
|
+
'saas',
|
|
463
|
+
'ecommerce',
|
|
464
|
+
'agency',
|
|
465
|
+
'consulting',
|
|
466
|
+
'nonprofit',
|
|
467
|
+
'gastronomy',
|
|
468
|
+
'realestate',
|
|
469
|
+
'personal',
|
|
478
470
|
];
|
|
479
|
-
export const DOMAIN_LABEL = DOMAIN_KEYS.join(
|
|
471
|
+
export const DOMAIN_LABEL = DOMAIN_KEYS.join(', ');
|
|
480
472
|
export const TONE_KEYS = [
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
473
|
+
'serious',
|
|
474
|
+
'premium',
|
|
475
|
+
'modern',
|
|
476
|
+
'friendly',
|
|
477
|
+
'approachable',
|
|
478
|
+
'approachable_professional',
|
|
479
|
+
'technical',
|
|
480
|
+
'editorial',
|
|
481
|
+
'playful',
|
|
482
|
+
'institutional',
|
|
483
|
+
'industrial',
|
|
484
|
+
'casual',
|
|
493
485
|
];
|
|
494
|
-
export const TONE_LABEL = TONE_KEYS.join(
|
|
486
|
+
export const TONE_LABEL = TONE_KEYS.join(', ');
|
|
495
487
|
export const UX_GOAL_KEYS = [
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
488
|
+
'build_trust',
|
|
489
|
+
'drive_contact',
|
|
490
|
+
'drive_signup',
|
|
491
|
+
'drive_purchase',
|
|
492
|
+
'explain_offer',
|
|
493
|
+
'highlight_expertise',
|
|
494
|
+
'showcase_work',
|
|
495
|
+
'reduce_friction',
|
|
496
|
+
'provide_overview',
|
|
497
|
+
'structure_learning',
|
|
498
|
+
'guide_stepwise',
|
|
499
|
+
'tell_story',
|
|
500
|
+
'demonstrate_value',
|
|
509
501
|
];
|
|
510
|
-
export const UX_GOAL_LABEL = UX_GOAL_KEYS.join(
|
|
502
|
+
export const UX_GOAL_LABEL = UX_GOAL_KEYS.join(', ');
|
|
511
503
|
export const CONTENT_NEED_KEYS = [
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
504
|
+
'about',
|
|
505
|
+
'article',
|
|
506
|
+
'blog',
|
|
507
|
+
'careers',
|
|
508
|
+
'case_study',
|
|
509
|
+
'comparison',
|
|
510
|
+
'contact',
|
|
511
|
+
'conversion',
|
|
512
|
+
'faq',
|
|
513
|
+
'gallery',
|
|
514
|
+
'integrations',
|
|
515
|
+
'legal',
|
|
516
|
+
'newsletter',
|
|
517
|
+
'portfolio',
|
|
518
|
+
'pricing',
|
|
519
|
+
'product',
|
|
520
|
+
'related_content',
|
|
521
|
+
'resume',
|
|
522
|
+
'support',
|
|
523
|
+
'team',
|
|
524
|
+
'testimonials',
|
|
525
|
+
'trust',
|
|
534
526
|
];
|
|
535
|
-
export const CONTENT_NEED_LABEL = CONTENT_NEED_KEYS.join(
|
|
527
|
+
export const CONTENT_NEED_LABEL = CONTENT_NEED_KEYS.join(', ');
|
|
536
528
|
const CONTENT_NEED_FAMILIES = {
|
|
537
|
-
about: [
|
|
538
|
-
article: [
|
|
539
|
-
blog: [
|
|
540
|
-
careers: [
|
|
541
|
-
case_study: [
|
|
542
|
-
comparison: [
|
|
543
|
-
contact: [
|
|
544
|
-
conversion: [
|
|
545
|
-
faq: [
|
|
546
|
-
gallery: [
|
|
547
|
-
integrations: [
|
|
548
|
-
legal: [
|
|
549
|
-
newsletter: [
|
|
550
|
-
portfolio: [
|
|
551
|
-
pricing: [
|
|
552
|
-
product: [
|
|
553
|
-
related_content: [
|
|
554
|
-
resume: [
|
|
555
|
-
support: [
|
|
556
|
-
team: [
|
|
557
|
-
testimonials: [
|
|
558
|
-
trust: [
|
|
559
|
-
"testimonials",
|
|
560
|
-
"logo-cloud",
|
|
561
|
-
"trust-strip",
|
|
562
|
-
"awards-list",
|
|
563
|
-
"team",
|
|
564
|
-
"product-reviews",
|
|
565
|
-
],
|
|
529
|
+
about: ['about'],
|
|
530
|
+
article: ['blog-details', 'blog-read-next', 'related-articles'],
|
|
531
|
+
blog: ['blog', 'blog-timeline', 'related-articles', 'newsletter'],
|
|
532
|
+
careers: ['careers', 'team', 'about', 'cta'],
|
|
533
|
+
case_study: ['case-study', 'portfolio'],
|
|
534
|
+
comparison: ['comparison', 'pricing'],
|
|
535
|
+
contact: ['contact', 'appointment', 'cta'],
|
|
536
|
+
conversion: ['pricing', 'cta', 'contact', 'newsletter', 'appointment'],
|
|
537
|
+
faq: ['faq', 'help-center'],
|
|
538
|
+
gallery: ['gallery', 'portfolio'],
|
|
539
|
+
integrations: ['integrations', 'faq'],
|
|
540
|
+
legal: ['legal', 'faq', 'contact'],
|
|
541
|
+
newsletter: ['newsletter'],
|
|
542
|
+
portfolio: ['portfolio', 'case-study', 'gallery'],
|
|
543
|
+
pricing: ['pricing', 'comparison'],
|
|
544
|
+
product: ['product-detail', 'product-reviews', 'product-grid'],
|
|
545
|
+
related_content: ['related-articles', 'blog-read-next', 'newsletter'],
|
|
546
|
+
resume: ['resume', 'about'],
|
|
547
|
+
support: ['help-center', 'faq', 'contact'],
|
|
548
|
+
team: ['team', 'about'],
|
|
549
|
+
testimonials: ['testimonials', 'product-reviews'],
|
|
550
|
+
trust: ['testimonials', 'logo-cloud', 'trust-strip', 'awards-list', 'team', 'product-reviews'],
|
|
566
551
|
};
|
|
567
552
|
const CONTENT_NEED_ALIASES = {
|
|
568
|
-
blog_detail:
|
|
569
|
-
blog_details:
|
|
570
|
-
blog_post:
|
|
571
|
-
blog_posts:
|
|
572
|
-
blog_roll:
|
|
573
|
-
company:
|
|
574
|
-
company_info:
|
|
575
|
-
content:
|
|
576
|
-
cta:
|
|
577
|
-
faq_section:
|
|
578
|
-
hiring:
|
|
579
|
-
lead_capture:
|
|
580
|
-
lead_gen:
|
|
581
|
-
lead_generation:
|
|
582
|
-
page_legal:
|
|
583
|
-
read_next:
|
|
584
|
-
related:
|
|
585
|
-
related_articles:
|
|
586
|
-
reviews:
|
|
587
|
-
social_proof:
|
|
588
|
-
support_center:
|
|
553
|
+
blog_detail: 'article',
|
|
554
|
+
blog_details: 'article',
|
|
555
|
+
blog_post: 'article',
|
|
556
|
+
blog_posts: 'blog',
|
|
557
|
+
blog_roll: 'blog',
|
|
558
|
+
company: 'about',
|
|
559
|
+
company_info: 'about',
|
|
560
|
+
content: 'blog',
|
|
561
|
+
cta: 'conversion',
|
|
562
|
+
faq_section: 'faq',
|
|
563
|
+
hiring: 'careers',
|
|
564
|
+
lead_capture: 'newsletter',
|
|
565
|
+
lead_gen: 'conversion',
|
|
566
|
+
lead_generation: 'conversion',
|
|
567
|
+
page_legal: 'legal',
|
|
568
|
+
read_next: 'related_content',
|
|
569
|
+
related: 'related_content',
|
|
570
|
+
related_articles: 'related_content',
|
|
571
|
+
reviews: 'testimonials',
|
|
572
|
+
social_proof: 'trust',
|
|
573
|
+
support_center: 'support',
|
|
589
574
|
};
|
|
590
575
|
export function normalizeContentNeed(need) {
|
|
591
576
|
const normalized = need
|
|
592
577
|
.trim()
|
|
593
578
|
.toLowerCase()
|
|
594
|
-
.replace(/[\s-]+/g,
|
|
579
|
+
.replace(/[\s-]+/g, '_');
|
|
595
580
|
const canonical = CONTENT_NEED_ALIASES[normalized] ?? normalized;
|
|
596
|
-
return CONTENT_NEED_KEYS.includes(canonical)
|
|
597
|
-
? canonical
|
|
598
|
-
: null;
|
|
581
|
+
return CONTENT_NEED_KEYS.includes(canonical) ? canonical : null;
|
|
599
582
|
}
|
|
600
583
|
const ROLE_SNIPPET_CATEGORY_BONUSES = {
|
|
601
584
|
navigation: { interactions: 2, text: 1 },
|
|
@@ -620,43 +603,43 @@ const TONE_SNIPPET_CATEGORY_BONUSES = {
|
|
|
620
603
|
casual: { interactions: 2, decorative: 1, text: 1 },
|
|
621
604
|
};
|
|
622
605
|
const FAMILY_SNIPPET_TERMS = {
|
|
623
|
-
hero: [
|
|
624
|
-
navbar: [
|
|
625
|
-
features: [
|
|
626
|
-
services: [
|
|
627
|
-
pricing: [
|
|
628
|
-
faq: [
|
|
629
|
-
testimonials: [
|
|
630
|
-
contact: [
|
|
631
|
-
cta: [
|
|
632
|
-
portfolio: [
|
|
633
|
-
gallery: [
|
|
634
|
-
blog: [
|
|
635
|
-
|
|
636
|
-
newsletter: [
|
|
637
|
-
team: [
|
|
638
|
-
footer: [
|
|
606
|
+
hero: ['headline', 'entrance', 'reveal', 'background', 'hero'],
|
|
607
|
+
navbar: ['navigation', 'nav', 'link', 'menu'],
|
|
608
|
+
features: ['cards', 'feature', 'grid', 'content hierarchy'],
|
|
609
|
+
services: ['reveal', 'service', 'list'],
|
|
610
|
+
pricing: ['pricing', 'comparison', 'toggle', 'cta'],
|
|
611
|
+
faq: ['faq', 'accordion', 'readability'],
|
|
612
|
+
testimonials: ['quote', 'social proof', 'testimonial'],
|
|
613
|
+
contact: ['form', 'contact', 'focus', 'cta'],
|
|
614
|
+
cta: ['button', 'cta', 'action', 'conversion'],
|
|
615
|
+
portfolio: ['portfolio', 'gallery', 'showcase'],
|
|
616
|
+
gallery: ['gallery', 'image', 'media'],
|
|
617
|
+
blog: ['editorial', 'article', 'metadata', 'reading'],
|
|
618
|
+
'blog-details': ['article', 'reading', 'progress', 'content'],
|
|
619
|
+
newsletter: ['newsletter', 'signup', 'form'],
|
|
620
|
+
team: ['profile', 'card', 'bio'],
|
|
621
|
+
footer: ['footer', 'link', 'divider'],
|
|
639
622
|
};
|
|
640
623
|
const UX_GOAL_SNIPPET_TERMS = {
|
|
641
|
-
build_trust: [
|
|
642
|
-
drive_contact: [
|
|
643
|
-
drive_signup: [
|
|
644
|
-
drive_purchase: [
|
|
645
|
-
explain_offer: [
|
|
646
|
-
highlight_expertise: [
|
|
647
|
-
showcase_work: [
|
|
648
|
-
reduce_friction: [
|
|
649
|
-
provide_overview: [
|
|
650
|
-
structure_learning: [
|
|
651
|
-
guide_stepwise: [
|
|
652
|
-
tell_story: [
|
|
653
|
-
demonstrate_value: [
|
|
624
|
+
build_trust: ['clarity', 'readability', 'focus', 'social proof'],
|
|
625
|
+
drive_contact: ['form', 'button', 'cta', 'focus'],
|
|
626
|
+
drive_signup: ['signup', 'cta', 'button', 'conversion'],
|
|
627
|
+
drive_purchase: ['pricing', 'cta', 'comparison', 'button'],
|
|
628
|
+
explain_offer: ['feature', 'comparison', 'content hierarchy', 'reading'],
|
|
629
|
+
highlight_expertise: ['editorial', 'case study', 'depth', 'metadata'],
|
|
630
|
+
showcase_work: ['gallery', 'portfolio', 'image', 'showcase'],
|
|
631
|
+
reduce_friction: ['form', 'focus', 'clarity', 'progress'],
|
|
632
|
+
provide_overview: ['overview', 'grid', 'hierarchy', 'cards'],
|
|
633
|
+
structure_learning: ['steps', 'progress', 'reading', 'content'],
|
|
634
|
+
guide_stepwise: ['steps', 'progress', 'sequence', 'reveal'],
|
|
635
|
+
tell_story: ['story', 'editorial', 'scroll', 'timeline'],
|
|
636
|
+
demonstrate_value: ['comparison', 'highlight', 'cta', 'proof'],
|
|
654
637
|
};
|
|
655
638
|
function recommendSnippetsForComposition(snippets, patterns, options, maxSnippets = 5) {
|
|
656
639
|
if (patterns.length === 0)
|
|
657
640
|
return [];
|
|
658
641
|
const families = [...new Set(patterns.map((pattern) => pattern.family))];
|
|
659
|
-
const roles = patterns.map((pattern) => pattern.ai?.compositionRole ??
|
|
642
|
+
const roles = patterns.map((pattern) => pattern.ai?.compositionRole ?? 'supporting');
|
|
660
643
|
const queryTerms = [
|
|
661
644
|
options.domain,
|
|
662
645
|
options.tone,
|
|
@@ -664,9 +647,7 @@ function recommendSnippetsForComposition(snippets, patterns, options, maxSnippet
|
|
|
664
647
|
...families.flatMap((family) => FAMILY_SNIPPET_TERMS[family] ?? [family]),
|
|
665
648
|
...options.uxGoals.flatMap((goal) => UX_GOAL_SNIPPET_TERMS[goal] ?? []),
|
|
666
649
|
];
|
|
667
|
-
const words = [
|
|
668
|
-
...new Set(queryTerms.join(" ").toLowerCase().split(/\s+/).filter(Boolean)),
|
|
669
|
-
];
|
|
650
|
+
const words = [...new Set(queryTerms.join(' ').toLowerCase().split(/\s+/).filter(Boolean))];
|
|
670
651
|
const stems = words.map(stem);
|
|
671
652
|
const expanded = expandWithSynonyms(words);
|
|
672
653
|
const categoryUsage = new Map();
|
|
@@ -676,10 +657,9 @@ function recommendSnippetsForComposition(snippets, patterns, options, maxSnippet
|
|
|
676
657
|
for (const role of roles) {
|
|
677
658
|
score += ROLE_SNIPPET_CATEGORY_BONUSES[role]?.[snippet.category] ?? 0;
|
|
678
659
|
}
|
|
679
|
-
score +=
|
|
680
|
-
TONE_SNIPPET_CATEGORY_BONUSES[options.tone]?.[snippet.category] ?? 0;
|
|
660
|
+
score += TONE_SNIPPET_CATEGORY_BONUSES[options.tone]?.[snippet.category] ?? 0;
|
|
681
661
|
if (snippet.meta.accessibility.prefersReducedMotion &&
|
|
682
|
-
[
|
|
662
|
+
['animations', 'scroll', 'interactions'].includes(snippet.category)) {
|
|
683
663
|
score += 1;
|
|
684
664
|
}
|
|
685
665
|
if (snippet.responsive)
|
|
@@ -690,8 +670,7 @@ function recommendSnippetsForComposition(snippets, patterns, options, maxSnippet
|
|
|
690
670
|
const categoryRoleMatch = roles.some((role) => (ROLE_SNIPPET_CATEGORY_BONUSES[role]?.[snippet.category] ?? 0) > 0);
|
|
691
671
|
if (categoryRoleMatch)
|
|
692
672
|
reasonBits.push(`${snippet.category} fits the selected section roles`);
|
|
693
|
-
if ((TONE_SNIPPET_CATEGORY_BONUSES[options.tone]?.[snippet.category] ?? 0) >
|
|
694
|
-
0) {
|
|
673
|
+
if ((TONE_SNIPPET_CATEGORY_BONUSES[options.tone]?.[snippet.category] ?? 0) > 0) {
|
|
695
674
|
reasonBits.push(`${snippet.category} suits the ${options.tone} tone`);
|
|
696
675
|
}
|
|
697
676
|
const matchedFamilies = families.filter((family) => {
|
|
@@ -702,17 +681,17 @@ function recommendSnippetsForComposition(snippets, patterns, options, maxSnippet
|
|
|
702
681
|
...snippet.meta.useCases,
|
|
703
682
|
...snippet.meta.solves,
|
|
704
683
|
]
|
|
705
|
-
.join(
|
|
684
|
+
.join(' ')
|
|
706
685
|
.toLowerCase();
|
|
707
686
|
return (FAMILY_SNIPPET_TERMS[family] ?? []).some((term) => haystack.includes(term));
|
|
708
687
|
});
|
|
709
688
|
if (matchedFamilies.length > 0) {
|
|
710
|
-
reasonBits.push(`supports ${matchedFamilies.slice(0, 2).join(
|
|
689
|
+
reasonBits.push(`supports ${matchedFamilies.slice(0, 2).join(', ')} sections`);
|
|
711
690
|
}
|
|
712
691
|
return {
|
|
713
692
|
snippet,
|
|
714
693
|
score,
|
|
715
|
-
reason: reasonBits.join(
|
|
694
|
+
reason: reasonBits.join('; '),
|
|
716
695
|
};
|
|
717
696
|
})
|
|
718
697
|
.filter(({ score }) => score > 0)
|
|
@@ -727,8 +706,7 @@ function recommendSnippetsForComposition(snippets, patterns, options, maxSnippet
|
|
|
727
706
|
.slice(0, maxSnippets)
|
|
728
707
|
.map(({ snippet, reason }) => ({
|
|
729
708
|
snippet,
|
|
730
|
-
reason: reason ||
|
|
731
|
-
`Matches ${options.domain}, ${options.tone}, and the selected page goals`,
|
|
709
|
+
reason: reason || `Matches ${options.domain}, ${options.tone}, and the selected page goals`,
|
|
732
710
|
}));
|
|
733
711
|
}
|
|
734
712
|
export function composePage(patterns, snippets, options) {
|
|
@@ -743,7 +721,7 @@ export function composePage(patterns, snippets, options) {
|
|
|
743
721
|
.map((p) => {
|
|
744
722
|
let score = 0;
|
|
745
723
|
let matchedSemantics = 0;
|
|
746
|
-
const role = p.ai?.compositionRole ??
|
|
724
|
+
const role = p.ai?.compositionRole ?? 'supporting';
|
|
747
725
|
// Domain match
|
|
748
726
|
if (p.semantics?.domains.includes(domain)) {
|
|
749
727
|
score += 5;
|
|
@@ -765,10 +743,10 @@ export function composePage(patterns, snippets, options) {
|
|
|
765
743
|
}
|
|
766
744
|
}
|
|
767
745
|
// Prefer base variants
|
|
768
|
-
if (p.tier ===
|
|
746
|
+
if (p.tier === 'base')
|
|
769
747
|
score += 1;
|
|
770
748
|
// compositionRole bonus for entry patterns
|
|
771
|
-
if (p.ai?.compositionRole ===
|
|
749
|
+
if (p.ai?.compositionRole === 'entry')
|
|
772
750
|
score += 2;
|
|
773
751
|
score += ROLE_FAMILY_BONUSES[role]?.[p.family] ?? 0;
|
|
774
752
|
score += DOMAIN_FAMILY_BONUSES[domain]?.[p.family] ?? 0;
|
|
@@ -779,9 +757,7 @@ export function composePage(patterns, snippets, options) {
|
|
|
779
757
|
}
|
|
780
758
|
return { pattern: p, score, matchedSemantics };
|
|
781
759
|
})
|
|
782
|
-
.filter(({ score, matchedSemantics, pattern }) => score > 0 &&
|
|
783
|
-
matchedSemantics > 0 &&
|
|
784
|
-
!pattern.semantics?.avoidForTones.includes(tone))
|
|
760
|
+
.filter(({ score, matchedSemantics, pattern }) => score > 0 && matchedSemantics > 0 && !pattern.semantics?.avoidForTones.includes(tone))
|
|
785
761
|
.sort((a, b) => b.score - a.score || b.matchedSemantics - a.matchedSemantics);
|
|
786
762
|
// Step 2: Select best pattern per composition role
|
|
787
763
|
const selected = new Map();
|
|
@@ -789,7 +765,7 @@ export function composePage(patterns, snippets, options) {
|
|
|
789
765
|
const roleCounts = new Map();
|
|
790
766
|
const coveredContentNeeds = new Set();
|
|
791
767
|
function trySelect(pattern, reasonPrefix, matchedNeed) {
|
|
792
|
-
const role = pattern.ai?.compositionRole ??
|
|
768
|
+
const role = pattern.ai?.compositionRole ?? 'supporting';
|
|
793
769
|
if (usedFamilies.has(pattern.family))
|
|
794
770
|
return false;
|
|
795
771
|
const currentRoleCount = roleCounts.get(role) ?? 0;
|
|
@@ -808,7 +784,7 @@ export function composePage(patterns, snippets, options) {
|
|
|
808
784
|
}
|
|
809
785
|
}
|
|
810
786
|
const matchedUxCount = uxGoals.filter((g) => pattern.semantics?.uxGoals.includes(g)).length;
|
|
811
|
-
reasoning.push(`${reasonPrefix ?? pattern.id}: ${role} (score: domain=${pattern.semantics?.domains.includes(domain) ?
|
|
787
|
+
reasoning.push(`${reasonPrefix ?? pattern.id}: ${role} (score: domain=${pattern.semantics?.domains.includes(domain) ? '✓' : '–'}, tone=${pattern.semantics?.tones.includes(tone) ? '✓' : '–'}, ux=${matchedUxCount}/${uxGoals.length}${matchedNeed ? `, need=${matchedNeed}` : ''})`);
|
|
812
788
|
return true;
|
|
813
789
|
}
|
|
814
790
|
// Step 2a: Satisfy explicit content needs first
|
|
@@ -831,8 +807,8 @@ export function composePage(patterns, snippets, options) {
|
|
|
831
807
|
}
|
|
832
808
|
// Step 3: Sort by composition role
|
|
833
809
|
const result = [...selected.values()].sort((a, b) => {
|
|
834
|
-
const roleA = a.ai?.compositionRole ??
|
|
835
|
-
const roleB = b.ai?.compositionRole ??
|
|
810
|
+
const roleA = a.ai?.compositionRole ?? 'supporting';
|
|
811
|
+
const roleB = b.ai?.compositionRole ?? 'supporting';
|
|
836
812
|
return (ROLE_ORDER[roleA] ?? 2) - (ROLE_ORDER[roleB] ?? 2);
|
|
837
813
|
});
|
|
838
814
|
// Step 4: Check neighbors / warn about conflicts
|
|
@@ -868,3 +844,158 @@ export function composePage(patterns, snippets, options) {
|
|
|
868
844
|
warnings,
|
|
869
845
|
};
|
|
870
846
|
}
|
|
847
|
+
// --- Font Recommendation ---
|
|
848
|
+
export function recommendFonts(fontsData, domain, tone) {
|
|
849
|
+
const key = `${tone}-${domain}`;
|
|
850
|
+
const predefined = fontsData.pairings[key];
|
|
851
|
+
if (predefined) {
|
|
852
|
+
const heading = fontsData.fonts.find((f) => f.id === predefined.heading);
|
|
853
|
+
const body = fontsData.fonts.find((f) => f.id === predefined.body);
|
|
854
|
+
const mono = fontsData.fonts.find((f) => f.id === predefined.mono);
|
|
855
|
+
if (heading && body && mono) {
|
|
856
|
+
const packages = [...new Set([heading.npm, body.npm, mono.npm])];
|
|
857
|
+
return {
|
|
858
|
+
heading: {
|
|
859
|
+
id: heading.id,
|
|
860
|
+
name: heading.name,
|
|
861
|
+
npm: heading.npm,
|
|
862
|
+
css: heading.css,
|
|
863
|
+
reason: `Predefined pairing for ${tone} ${domain}`,
|
|
864
|
+
},
|
|
865
|
+
body: {
|
|
866
|
+
id: body.id,
|
|
867
|
+
name: body.name,
|
|
868
|
+
npm: body.npm,
|
|
869
|
+
css: body.css,
|
|
870
|
+
reason: `Predefined pairing for ${tone} ${domain}`,
|
|
871
|
+
},
|
|
872
|
+
mono: {
|
|
873
|
+
id: mono.id,
|
|
874
|
+
name: mono.name,
|
|
875
|
+
npm: mono.npm,
|
|
876
|
+
css: mono.css,
|
|
877
|
+
reason: `Predefined pairing for ${tone} ${domain}`,
|
|
878
|
+
},
|
|
879
|
+
install: `npm i ${packages.join(' ')}`,
|
|
880
|
+
tailwindTheme: buildTailwindTheme(heading, body, mono),
|
|
881
|
+
isPredefined: true,
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
// Dynamic scoring
|
|
886
|
+
function scoreFont(font, role) {
|
|
887
|
+
if (font.avoidForTones.includes(tone))
|
|
888
|
+
return -1;
|
|
889
|
+
let score = 0;
|
|
890
|
+
if (font.tones.includes(tone))
|
|
891
|
+
score += 5;
|
|
892
|
+
if (font.domains.includes(domain))
|
|
893
|
+
score += 3;
|
|
894
|
+
score += font.roles[role] * 4;
|
|
895
|
+
return score;
|
|
896
|
+
}
|
|
897
|
+
function pickBest(role) {
|
|
898
|
+
const candidates = fontsData.fonts
|
|
899
|
+
.map((f) => ({ font: f, score: scoreFont(f, role) }))
|
|
900
|
+
.filter(({ score }) => score > 0)
|
|
901
|
+
.sort((a, b) => b.score - a.score);
|
|
902
|
+
return candidates[0]?.font ?? fontsData.fonts[0];
|
|
903
|
+
}
|
|
904
|
+
const heading = pickBest('heading');
|
|
905
|
+
const body = pickBest('body');
|
|
906
|
+
const mono = pickBest('mono');
|
|
907
|
+
const packages = [...new Set([heading.npm, body.npm, mono.npm])];
|
|
908
|
+
function reason(font, role) {
|
|
909
|
+
const parts = [];
|
|
910
|
+
if (font.tones.includes(tone))
|
|
911
|
+
parts.push(`tone match (${tone})`);
|
|
912
|
+
if (font.domains.includes(domain))
|
|
913
|
+
parts.push(`domain match (${domain})`);
|
|
914
|
+
parts.push(`${role} fitness: ${font.roles[role]}`);
|
|
915
|
+
return parts.join(', ');
|
|
916
|
+
}
|
|
917
|
+
return {
|
|
918
|
+
heading: {
|
|
919
|
+
id: heading.id,
|
|
920
|
+
name: heading.name,
|
|
921
|
+
npm: heading.npm,
|
|
922
|
+
css: heading.css,
|
|
923
|
+
reason: reason(heading, 'heading'),
|
|
924
|
+
},
|
|
925
|
+
body: {
|
|
926
|
+
id: body.id,
|
|
927
|
+
name: body.name,
|
|
928
|
+
npm: body.npm,
|
|
929
|
+
css: body.css,
|
|
930
|
+
reason: reason(body, 'body'),
|
|
931
|
+
},
|
|
932
|
+
mono: {
|
|
933
|
+
id: mono.id,
|
|
934
|
+
name: mono.name,
|
|
935
|
+
npm: mono.npm,
|
|
936
|
+
css: mono.css,
|
|
937
|
+
reason: reason(mono, 'mono'),
|
|
938
|
+
},
|
|
939
|
+
install: `npm i ${packages.join(' ')}`,
|
|
940
|
+
tailwindTheme: buildTailwindTheme(heading, body, mono),
|
|
941
|
+
isPredefined: false,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
function buildTailwindTheme(heading, body, mono) {
|
|
945
|
+
return `@theme {
|
|
946
|
+
--font-heading: ${heading.css};
|
|
947
|
+
--font-body: ${body.css};
|
|
948
|
+
--font-mono: ${mono.css};
|
|
949
|
+
}`;
|
|
950
|
+
}
|
|
951
|
+
export function searchCanvasEffects(effects, options) {
|
|
952
|
+
const { query, category, interactive, animate, limit = 10 } = options;
|
|
953
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
954
|
+
const stems = words.map(stem);
|
|
955
|
+
const expanded = expandWithSynonyms(words);
|
|
956
|
+
if (stems.length === 0)
|
|
957
|
+
return [];
|
|
958
|
+
const scored = effects
|
|
959
|
+
.filter((effect) => {
|
|
960
|
+
if (category && effect.category !== category)
|
|
961
|
+
return false;
|
|
962
|
+
if (interactive !== undefined && effect.interactive !== interactive)
|
|
963
|
+
return false;
|
|
964
|
+
if (animate !== undefined && effect.animate !== animate)
|
|
965
|
+
return false;
|
|
966
|
+
return true;
|
|
967
|
+
})
|
|
968
|
+
.map((effect) => {
|
|
969
|
+
let score = 0;
|
|
970
|
+
const idLower = effect.id.toLowerCase();
|
|
971
|
+
for (const term of stems) {
|
|
972
|
+
if (idLower.includes(term))
|
|
973
|
+
score += 5;
|
|
974
|
+
}
|
|
975
|
+
const title = effect.title.toLowerCase();
|
|
976
|
+
score += stems.filter((w) => title.includes(w)).length * 3;
|
|
977
|
+
const desc = effect.description.toLowerCase();
|
|
978
|
+
score += stems.filter((w) => desc.includes(w)).length * 2;
|
|
979
|
+
const tagStr = effect.tags.join(' ').toLowerCase();
|
|
980
|
+
score += stems.filter((w) => tagStr.includes(w)).length * 2;
|
|
981
|
+
for (const useCase of effect.useCases) {
|
|
982
|
+
const lower = useCase.toLowerCase();
|
|
983
|
+
score += stems.filter((w) => lower.includes(w)).length * 3;
|
|
984
|
+
}
|
|
985
|
+
for (const solve of effect.solves) {
|
|
986
|
+
const lower = solve.toLowerCase();
|
|
987
|
+
score += stems.filter((w) => lower.includes(w)).length * 4;
|
|
988
|
+
}
|
|
989
|
+
const synonymOnly = expanded.filter((t) => !stems.includes(t));
|
|
990
|
+
if (synonymOnly.length > 0) {
|
|
991
|
+
const allText = [title, desc, tagStr, ...effect.useCases, ...effect.solves]
|
|
992
|
+
.join(' ')
|
|
993
|
+
.toLowerCase();
|
|
994
|
+
score += synonymOnly.filter((w) => allText.includes(w)).length;
|
|
995
|
+
}
|
|
996
|
+
return { effect, score };
|
|
997
|
+
})
|
|
998
|
+
.filter(({ score }) => score > 0)
|
|
999
|
+
.sort((a, b) => b.score - a.score);
|
|
1000
|
+
return scored.slice(0, limit).map(({ effect }) => effect);
|
|
1001
|
+
}
|