@lacspace/og 1.0.0 → 1.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.
package/README.md CHANGED
@@ -77,6 +77,53 @@ Also exports `fitFontSize(title)` if you want the sizing math for your own layou
77
77
 
78
78
  Pairs with [`@lacspace/seo`](https://www.npmjs.com/package/@lacspace/seo) — feed the generated URL to `defineSite().meta({ image })`.
79
79
 
80
+ ## More card layouts (new)
81
+
82
+ Beyond `ogCard`, the package ships four more ready-made layouts — all return the
83
+ same element tree, work in the edge runtime, and degrade gracefully when
84
+ optional fields are missing.
85
+
86
+ ```ts
87
+ import {
88
+ ogCardSplit, // title/subtitle on the left, gradient accent panel + logo on the right
89
+ ogCardMinimal, // big centered title on a full-bleed gradient, minimal chrome
90
+ ogArticle, // category · title · footer row with author · date · reading time
91
+ ogProduct, // title · subtitle/badge · large price with currency
92
+ ogThemes, // named gradient presets: lacspace, ocean, sunset, forest, grape, slate
93
+ } from "@lacspace/og";
94
+
95
+ // Two-column split (logo falls back to the title's first letter)
96
+ ogCardSplit({ title: "Ship faster", subtitle: "docs.example.com", logo: "L", theme: "dark" });
97
+
98
+ // Minimal centered — pick a named gradient instead of from/to
99
+ ogCardMinimal({ title: "Launch day", gradient: "sunset", pattern: "glow" });
100
+
101
+ // Article card
102
+ ogArticle({
103
+ title: "How we cut cold starts in half",
104
+ eyebrow: "Engineering",
105
+ author: "Ada Lovelace",
106
+ date: "Sep 5, 2026",
107
+ readingTime: "6 min read",
108
+ pattern: "dots",
109
+ });
110
+
111
+ // Product / pricing card
112
+ ogProduct({ title: "Pro plan", subtitle: "Everything you need", badge: "POPULAR", price: 49, currency: "$" });
113
+ ```
114
+
115
+ Extra options accepted by all four (on top of the existing `OgOptions`):
116
+
117
+ | Option | Meaning |
118
+ | --- | --- |
119
+ | `gradient` | A named preset from `ogThemes` (`lacspace` · `ocean` · `sunset` · `forest` · `grape` · `slate`) — shorthand for `from`/`to`; explicit `from`/`to` still win |
120
+ | `pattern` | Faint background texture: `"dots"` · `"glow"` · `"none"` (default) |
121
+ | `author` / `date` / `readingTime` | Footer meta for `ogArticle` |
122
+ | `price` / `currency` | Large price for `ogProduct` (e.g. `price: 49, currency: "$"` → `$49`) |
123
+
124
+ `ogCard`, `ogSvg`, `ogSvgDataUri` and `fitFontSize` are unchanged and fully
125
+ backward compatible.
126
+
80
127
  ## Licensing
81
128
 
82
129
  Free under the **[Lacspace Free Licence](https://lacspace.com/licenses/lacspace-free-1.0)** — MIT-equivalent freedoms. Use it in personal and commercial projects at no cost; just keep the notice. See the **[Lacspace Licence Centre](https://lacspace.com/licenses)**.
package/dist/index.cjs CHANGED
@@ -187,7 +187,7 @@ function ogCard(options) {
187
187
  );
188
188
  }
189
189
  function esc(s) {
190
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
190
+ return s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
191
191
  }
192
192
  function wrap(text, fontSize, maxWidth, maxLines) {
193
193
  const charW = fontSize * 0.56;
@@ -300,10 +300,523 @@ function ogSvg(options) {
300
300
  function ogSvgDataUri(options) {
301
301
  return `data:image/svg+xml;utf8,${encodeURIComponent(ogSvg(options))}`;
302
302
  }
303
+ var ogThemes = {
304
+ lacspace: { from: "#22d3ee", to: "#6366f1" },
305
+ ocean: { from: "#0ea5e9", to: "#2563eb" },
306
+ sunset: { from: "#f43f5e", to: "#f59e0b" },
307
+ forest: { from: "#10b981", to: "#065f46" },
308
+ grape: { from: "#a855f7", to: "#6366f1" },
309
+ slate: { from: "#94a3b8", to: "#1e293b" }
310
+ };
311
+ function withGradient(o) {
312
+ if (o.gradient && ogThemes[o.gradient]) {
313
+ const g = ogThemes[o.gradient];
314
+ return { ...o, from: o.from ?? g.from, to: o.to ?? g.to };
315
+ }
316
+ return o;
317
+ }
318
+ function glowLayer(r, p) {
319
+ return el("div", {
320
+ position: "absolute",
321
+ top: `-${Math.round(r.height * 0.35)}px`,
322
+ right: `-${Math.round(r.width * 0.18)}px`,
323
+ width: `${Math.round(r.width * 0.55)}px`,
324
+ height: `${Math.round(r.width * 0.55)}px`,
325
+ borderRadius: "9999px",
326
+ background: `linear-gradient(135deg, ${p.from}, ${p.to})`,
327
+ opacity: r.theme === "dark" ? "0.22" : "0.16",
328
+ filter: "blur(20px)"
329
+ });
330
+ }
331
+ function patternLayer(kind, r, p, over = "ground") {
332
+ const k = kind ?? "none";
333
+ if (k === "none") return null;
334
+ if (k === "glow") {
335
+ if (over === "gradient") {
336
+ return el("div", {
337
+ position: "absolute",
338
+ top: "0",
339
+ left: "0",
340
+ width: "100%",
341
+ height: "100%",
342
+ background: "radial-gradient(circle at 82% 12%, rgba(255,255,255,0.28), transparent 55%)"
343
+ });
344
+ }
345
+ return glowLayer(r, p);
346
+ }
347
+ const dot = over === "gradient" ? "rgba(255,255,255,0.16)" : r.theme === "dark" ? "rgba(255,255,255,0.10)" : "rgba(10,10,15,0.08)";
348
+ return el("div", {
349
+ position: "absolute",
350
+ top: "0",
351
+ left: "0",
352
+ width: "100%",
353
+ height: "100%",
354
+ backgroundImage: `radial-gradient(${dot} 2px, transparent 2px)`,
355
+ backgroundSize: "44px 44px"
356
+ });
357
+ }
358
+ function eyebrowNode(text, color) {
359
+ return el(
360
+ "div",
361
+ {
362
+ display: "flex",
363
+ fontSize: "26px",
364
+ fontWeight: 600,
365
+ letterSpacing: "0.12em",
366
+ textTransform: "uppercase",
367
+ color
368
+ },
369
+ text.toUpperCase()
370
+ );
371
+ }
372
+ function badgeNode(text, p) {
373
+ return el(
374
+ "div",
375
+ {
376
+ display: "flex",
377
+ alignItems: "center",
378
+ padding: "8px 20px",
379
+ borderRadius: "9999px",
380
+ fontSize: "24px",
381
+ fontWeight: 700,
382
+ color: p.fg,
383
+ background: `linear-gradient(90deg, ${p.from}33, ${p.to}33)`,
384
+ border: `1px solid ${p.border}`
385
+ },
386
+ text
387
+ );
388
+ }
389
+ function ogCardSplit(options) {
390
+ const r = resolve(withGradient(options));
391
+ const p = palette(r);
392
+ const pad = Math.round(r.width * 0.065);
393
+ const titleSize = fitFontSize(r.title, { max: 68, min: 40 });
394
+ const leftChildren = [];
395
+ if (r.eyebrow) leftChildren.push(eyebrowNode(r.eyebrow, p.from));
396
+ leftChildren.push(
397
+ el(
398
+ "div",
399
+ {
400
+ display: "flex",
401
+ fontSize: `${titleSize}px`,
402
+ fontWeight: 800,
403
+ lineHeight: 1.05,
404
+ letterSpacing: "-0.02em",
405
+ color: p.fg
406
+ },
407
+ r.title
408
+ )
409
+ );
410
+ if (r.subtitle) {
411
+ leftChildren.push(
412
+ el(
413
+ "div",
414
+ { display: "flex", fontSize: "32px", fontWeight: 500, color: p.muted },
415
+ r.subtitle
416
+ )
417
+ );
418
+ }
419
+ if (r.badge) leftChildren.push(badgeNode(r.badge, p));
420
+ const left = el(
421
+ "div",
422
+ {
423
+ display: "flex",
424
+ flexDirection: "column",
425
+ justifyContent: "center",
426
+ gap: "24px",
427
+ width: "62%",
428
+ height: "100%",
429
+ padding: `${pad}px`
430
+ },
431
+ leftChildren
432
+ );
433
+ const mark = r.logo ?? (r.title.trim().charAt(0).toUpperCase() || "\u2022");
434
+ const right = el(
435
+ "div",
436
+ {
437
+ display: "flex",
438
+ alignItems: "center",
439
+ justifyContent: "center",
440
+ width: "38%",
441
+ height: "100%",
442
+ background: `linear-gradient(135deg, ${p.from}, ${p.to})`
443
+ },
444
+ [
445
+ el(
446
+ "div",
447
+ { display: "flex", fontSize: "220px", fontWeight: 800, color: "#0a0a0f" },
448
+ mark
449
+ )
450
+ ]
451
+ );
452
+ const children = [];
453
+ const pat = patternLayer(options.pattern, r, p);
454
+ if (pat) children.push(pat);
455
+ children.push(left, right);
456
+ if (r.footer) {
457
+ children.push(
458
+ el(
459
+ "div",
460
+ {
461
+ position: "absolute",
462
+ bottom: `${Math.round(pad * 0.7)}px`,
463
+ left: `${pad}px`,
464
+ display: "flex",
465
+ fontSize: "26px",
466
+ fontWeight: 500,
467
+ color: p.muted
468
+ },
469
+ r.footer
470
+ )
471
+ );
472
+ }
473
+ return el(
474
+ "div",
475
+ {
476
+ position: "relative",
477
+ display: "flex",
478
+ flexDirection: "row",
479
+ width: "100%",
480
+ height: "100%",
481
+ background: p.bg,
482
+ color: p.fg,
483
+ fontFamily: "sans-serif"
484
+ },
485
+ children
486
+ );
487
+ }
488
+ function ogCardMinimal(options) {
489
+ const r = resolve(withGradient(options));
490
+ const p = palette(r);
491
+ const pad = Math.round(r.width * 0.09);
492
+ const titleSize = fitFontSize(r.title, { max: 96, min: 52 });
493
+ const center = [];
494
+ if (r.eyebrow) {
495
+ center.push(
496
+ el(
497
+ "div",
498
+ {
499
+ display: "flex",
500
+ fontSize: "28px",
501
+ fontWeight: 600,
502
+ letterSpacing: "0.16em",
503
+ textTransform: "uppercase",
504
+ color: "rgba(255,255,255,0.85)"
505
+ },
506
+ r.eyebrow.toUpperCase()
507
+ )
508
+ );
509
+ }
510
+ center.push(
511
+ el(
512
+ "div",
513
+ {
514
+ display: "flex",
515
+ fontSize: `${titleSize}px`,
516
+ fontWeight: 800,
517
+ lineHeight: 1.04,
518
+ letterSpacing: "-0.02em",
519
+ color: "#ffffff",
520
+ textAlign: "center"
521
+ },
522
+ r.title
523
+ )
524
+ );
525
+ if (r.subtitle) {
526
+ center.push(
527
+ el(
528
+ "div",
529
+ {
530
+ display: "flex",
531
+ fontSize: "34px",
532
+ fontWeight: 500,
533
+ color: "rgba(255,255,255,0.82)",
534
+ textAlign: "center"
535
+ },
536
+ r.subtitle
537
+ )
538
+ );
539
+ }
540
+ const children = [];
541
+ const pat = patternLayer(options.pattern, r, p, "gradient");
542
+ if (pat) children.push(pat);
543
+ children.push(
544
+ el(
545
+ "div",
546
+ {
547
+ display: "flex",
548
+ flexDirection: "column",
549
+ alignItems: "center",
550
+ justifyContent: "center",
551
+ gap: "28px",
552
+ width: "100%",
553
+ height: "100%",
554
+ padding: `${pad}px`
555
+ },
556
+ center
557
+ )
558
+ );
559
+ if (r.footer) {
560
+ children.push(
561
+ el(
562
+ "div",
563
+ {
564
+ position: "absolute",
565
+ bottom: `${Math.round(r.height * 0.06)}px`,
566
+ left: "0",
567
+ width: "100%",
568
+ display: "flex",
569
+ justifyContent: "center",
570
+ fontSize: "26px",
571
+ fontWeight: 500,
572
+ color: "rgba(255,255,255,0.78)"
573
+ },
574
+ r.footer
575
+ )
576
+ );
577
+ }
578
+ return el(
579
+ "div",
580
+ {
581
+ position: "relative",
582
+ display: "flex",
583
+ width: "100%",
584
+ height: "100%",
585
+ background: `linear-gradient(135deg, ${p.from}, ${p.to})`,
586
+ color: "#ffffff",
587
+ fontFamily: "sans-serif"
588
+ },
589
+ children
590
+ );
591
+ }
592
+ function metaDot(color) {
593
+ return el(
594
+ "div",
595
+ { display: "flex", fontSize: "26px", color, opacity: "0.6" },
596
+ "\xB7"
597
+ );
598
+ }
599
+ function ogArticle(options) {
600
+ const r = resolve(withGradient(options));
601
+ const p = palette(r);
602
+ const pad = Math.round(r.width * 0.075);
603
+ const titleSize = fitFontSize(r.title, { max: 74, min: 40 });
604
+ const accentBar = el("div", {
605
+ position: "absolute",
606
+ top: "0",
607
+ left: "0",
608
+ width: "100%",
609
+ height: `${Math.round(r.height * 0.02)}px`,
610
+ background: `linear-gradient(90deg, ${p.from}, ${p.to})`
611
+ });
612
+ const header = el(
613
+ "div",
614
+ {
615
+ display: "flex",
616
+ alignItems: "center",
617
+ justifyContent: "space-between",
618
+ width: "100%"
619
+ },
620
+ [
621
+ r.eyebrow ? eyebrowNode(r.eyebrow, p.from) : el("div", { display: "flex" }, ""),
622
+ r.logo ? el(
623
+ "div",
624
+ {
625
+ display: "flex",
626
+ alignItems: "center",
627
+ justifyContent: "center",
628
+ width: "72px",
629
+ height: "72px",
630
+ borderRadius: "18px",
631
+ fontSize: "38px",
632
+ fontWeight: 800,
633
+ color: "#0a0a0f",
634
+ background: `linear-gradient(135deg, ${p.from}, ${p.to})`
635
+ },
636
+ r.logo
637
+ ) : el("div", { display: "flex" }, "")
638
+ ]
639
+ );
640
+ const title = el(
641
+ "div",
642
+ {
643
+ display: "flex",
644
+ fontSize: `${titleSize}px`,
645
+ fontWeight: 800,
646
+ lineHeight: 1.06,
647
+ letterSpacing: "-0.02em",
648
+ color: p.fg
649
+ },
650
+ r.title
651
+ );
652
+ const metaItems = [];
653
+ if (options.author) {
654
+ metaItems.push(
655
+ el(
656
+ "div",
657
+ { display: "flex", fontSize: "28px", fontWeight: 700, color: p.fg },
658
+ options.author
659
+ )
660
+ );
661
+ }
662
+ const muted = (t) => el(
663
+ "div",
664
+ { display: "flex", fontSize: "26px", fontWeight: 500, color: p.muted },
665
+ t
666
+ );
667
+ if (options.date) {
668
+ if (metaItems.length) metaItems.push(metaDot(p.muted));
669
+ metaItems.push(muted(options.date));
670
+ }
671
+ if (options.readingTime) {
672
+ if (metaItems.length) metaItems.push(metaDot(p.muted));
673
+ metaItems.push(muted(options.readingTime));
674
+ }
675
+ const footerRow = metaItems.length ? el(
676
+ "div",
677
+ { display: "flex", alignItems: "center", gap: "16px" },
678
+ metaItems
679
+ ) : r.footer ? muted(r.footer) : el("div", { display: "flex" }, "");
680
+ const children = [accentBar];
681
+ const pat = patternLayer(options.pattern, r, p);
682
+ if (pat) children.push(pat);
683
+ children.push(
684
+ header,
685
+ el("div", { display: "flex", flexDirection: "column" }, [title]),
686
+ footerRow
687
+ );
688
+ return el(
689
+ "div",
690
+ {
691
+ position: "relative",
692
+ display: "flex",
693
+ flexDirection: "column",
694
+ justifyContent: "space-between",
695
+ width: "100%",
696
+ height: "100%",
697
+ padding: `${pad}px`,
698
+ background: p.bg,
699
+ color: p.fg,
700
+ fontFamily: "sans-serif"
701
+ },
702
+ children
703
+ );
704
+ }
705
+ function ogProduct(options) {
706
+ const r = resolve(withGradient(options));
707
+ const p = palette(r);
708
+ const pad = Math.round(r.width * 0.075);
709
+ const titleSize = fitFontSize(r.title, { max: 76, min: 42 });
710
+ const header = el(
711
+ "div",
712
+ {
713
+ display: "flex",
714
+ alignItems: "center",
715
+ justifyContent: "space-between",
716
+ width: "100%"
717
+ },
718
+ [
719
+ r.badge ? badgeNode(r.badge, p) : el("div", { display: "flex" }, ""),
720
+ r.logo ? el(
721
+ "div",
722
+ {
723
+ display: "flex",
724
+ alignItems: "center",
725
+ justifyContent: "center",
726
+ width: "76px",
727
+ height: "76px",
728
+ borderRadius: "20px",
729
+ fontSize: "40px",
730
+ fontWeight: 800,
731
+ color: "#0a0a0f",
732
+ background: `linear-gradient(135deg, ${p.from}, ${p.to})`
733
+ },
734
+ r.logo
735
+ ) : el("div", { display: "flex" }, "")
736
+ ]
737
+ );
738
+ const body = [
739
+ el(
740
+ "div",
741
+ {
742
+ display: "flex",
743
+ fontSize: `${titleSize}px`,
744
+ fontWeight: 800,
745
+ lineHeight: 1.05,
746
+ letterSpacing: "-0.02em",
747
+ color: p.fg
748
+ },
749
+ r.title
750
+ )
751
+ ];
752
+ if (r.subtitle) {
753
+ body.push(
754
+ el(
755
+ "div",
756
+ {
757
+ display: "flex",
758
+ marginTop: "22px",
759
+ fontSize: "32px",
760
+ fontWeight: 500,
761
+ color: p.muted
762
+ },
763
+ r.subtitle
764
+ )
765
+ );
766
+ }
767
+ const priceStr = options.price === void 0 || options.price === null ? void 0 : `${options.currency ?? ""}${options.price}`;
768
+ const priceNode = priceStr ? el(
769
+ "div",
770
+ {
771
+ display: "flex",
772
+ alignItems: "center",
773
+ fontSize: "88px",
774
+ fontWeight: 800,
775
+ letterSpacing: "-0.02em",
776
+ background: `linear-gradient(90deg, ${p.from}, ${p.to})`,
777
+ backgroundClip: "text",
778
+ color: "transparent"
779
+ },
780
+ priceStr
781
+ ) : r.footer ? el(
782
+ "div",
783
+ { display: "flex", fontSize: "26px", fontWeight: 500, color: p.muted },
784
+ r.footer
785
+ ) : el("div", { display: "flex" }, "");
786
+ const children = [];
787
+ const pat = patternLayer(options.pattern, r, p);
788
+ if (pat) children.push(pat);
789
+ children.push(
790
+ header,
791
+ el("div", { display: "flex", flexDirection: "column" }, body),
792
+ priceNode
793
+ );
794
+ return el(
795
+ "div",
796
+ {
797
+ position: "relative",
798
+ display: "flex",
799
+ flexDirection: "column",
800
+ justifyContent: "space-between",
801
+ width: "100%",
802
+ height: "100%",
803
+ padding: `${pad}px`,
804
+ background: p.bg,
805
+ color: p.fg,
806
+ fontFamily: "sans-serif"
807
+ },
808
+ children
809
+ );
810
+ }
303
811
 
304
812
  exports.fitFontSize = fitFontSize;
813
+ exports.ogArticle = ogArticle;
305
814
  exports.ogCard = ogCard;
815
+ exports.ogCardMinimal = ogCardMinimal;
816
+ exports.ogCardSplit = ogCardSplit;
817
+ exports.ogProduct = ogProduct;
306
818
  exports.ogSvg = ogSvg;
307
819
  exports.ogSvgDataUri = ogSvgDataUri;
820
+ exports.ogThemes = ogThemes;
308
821
  //# sourceMappingURL=index.cjs.map
309
822
  //# sourceMappingURL=index.cjs.map