@yoamigo.com/core 0.3.0 → 0.3.3

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/dist/prod.js CHANGED
@@ -298,12 +298,588 @@ function MarkdownText({ content, className }) {
298
298
  return /* @__PURE__ */ jsx6("span", { className, children: elements });
299
299
  }
300
300
 
301
+ // src/components/StaticContainer.tsx
302
+ import { jsx as jsx7 } from "react/jsx-runtime";
303
+ function parseBackgroundConfig(value) {
304
+ if (!value) {
305
+ return { type: "none" };
306
+ }
307
+ try {
308
+ const parsed = JSON.parse(value);
309
+ if (typeof parsed === "object" && parsed.type) {
310
+ return parsed;
311
+ }
312
+ } catch {
313
+ }
314
+ return { type: "none" };
315
+ }
316
+ function serializeBackgroundConfig(config) {
317
+ return JSON.stringify(config);
318
+ }
319
+ function getObjectPosition2(imageConfig) {
320
+ if (imageConfig.focalPoint) {
321
+ return `${imageConfig.focalPoint.x}% ${imageConfig.focalPoint.y}%`;
322
+ }
323
+ return imageConfig.objectPosition || "50% 50%";
324
+ }
325
+ function StaticContainer({
326
+ fieldId,
327
+ className,
328
+ style,
329
+ as: Tag = "section",
330
+ children,
331
+ defaultBackground
332
+ }) {
333
+ const { getValue } = useContentStore();
334
+ const rawValue = getValue(fieldId);
335
+ const backgroundConfig = rawValue ? parseBackgroundConfig(rawValue) : defaultBackground || { type: "none" };
336
+ const hasBackground = backgroundConfig.type !== "none";
337
+ const backgroundStyles = {};
338
+ if (backgroundConfig.type === "color" && backgroundConfig.backgroundColor) {
339
+ backgroundStyles.backgroundColor = backgroundConfig.backgroundColor;
340
+ }
341
+ if (backgroundConfig.type === "image" && backgroundConfig.backgroundImage) {
342
+ const img = backgroundConfig.backgroundImage;
343
+ const resolvedSrc = resolveAssetUrl(img.src);
344
+ backgroundStyles.backgroundImage = `url(${resolvedSrc})`;
345
+ backgroundStyles.backgroundSize = img.objectFit || "cover";
346
+ backgroundStyles.backgroundPosition = getObjectPosition2(img);
347
+ backgroundStyles.backgroundRepeat = "no-repeat";
348
+ }
349
+ const overlayCustomProps = {};
350
+ if (backgroundConfig.overlay) {
351
+ overlayCustomProps["--ya-overlay-color"] = backgroundConfig.overlay.color;
352
+ overlayCustomProps["--ya-overlay-opacity"] = backgroundConfig.overlay.opacity;
353
+ }
354
+ return /* @__PURE__ */ jsx7(
355
+ Tag,
356
+ {
357
+ className: `ya-container ${hasBackground ? "ya-container-has-overlay" : ""} ${className || ""}`,
358
+ style: {
359
+ ...backgroundStyles,
360
+ ...overlayCustomProps,
361
+ ...style
362
+ },
363
+ "data-field-id": fieldId,
364
+ children
365
+ }
366
+ );
367
+ }
368
+
369
+ // src/components/StaticVideo.tsx
370
+ import { useCallback, useEffect, useRef, useState } from "react";
371
+ import { jsx as jsx8 } from "react/jsx-runtime";
372
+ function parseVideoValue(value) {
373
+ if (!value) {
374
+ return { type: "upload", src: "" };
375
+ }
376
+ try {
377
+ const parsed = JSON.parse(value);
378
+ if (typeof parsed === "object" && parsed.src) {
379
+ return {
380
+ type: parsed.type || "upload",
381
+ ...parsed
382
+ };
383
+ }
384
+ } catch {
385
+ }
386
+ return { type: "upload", src: value };
387
+ }
388
+ function serializeVideoValue(value) {
389
+ return JSON.stringify(value);
390
+ }
391
+ function buildYouTubeEmbedUrl(videoId, value) {
392
+ const params = new URLSearchParams({
393
+ rel: "0",
394
+ modestbranding: "1"
395
+ });
396
+ if (value.autoplay) params.set("autoplay", "1");
397
+ if (value.muted) params.set("mute", "1");
398
+ if (value.loop) {
399
+ params.set("loop", "1");
400
+ params.set("playlist", videoId);
401
+ }
402
+ if (value.controls === false) params.set("controls", "0");
403
+ if (value.startTime) params.set("start", String(Math.floor(value.startTime)));
404
+ if (value.endTime) params.set("end", String(Math.floor(value.endTime)));
405
+ return `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
406
+ }
407
+ function buildVimeoEmbedUrl(videoId, value) {
408
+ const params = new URLSearchParams({
409
+ title: "0",
410
+ byline: "0",
411
+ portrait: "0"
412
+ });
413
+ if (value.autoplay) params.set("autoplay", "1");
414
+ if (value.muted) params.set("muted", "1");
415
+ if (value.loop) params.set("loop", "1");
416
+ if (value.controls === false) params.set("controls", "0");
417
+ let url = `https://player.vimeo.com/video/${videoId}?${params.toString()}`;
418
+ if (value.startTime) {
419
+ url += `#t=${Math.floor(value.startTime)}s`;
420
+ }
421
+ return url;
422
+ }
423
+ function StaticVideo({
424
+ fieldId,
425
+ className,
426
+ aspectRatio: propAspectRatio,
427
+ objectFit: propObjectFit,
428
+ loading = "lazy",
429
+ defaultValue,
430
+ fallbackSrc,
431
+ fallbackPoster
432
+ }) {
433
+ const { getValue } = useContentStore();
434
+ const containerRef = useRef(null);
435
+ const videoRef = useRef(null);
436
+ const [isInView, setIsInView] = useState(loading === "eager");
437
+ const rawValue = getValue(fieldId);
438
+ const parsedValue = parseVideoValue(rawValue);
439
+ const videoData = parsedValue.src ? parsedValue : defaultValue || parsedValue;
440
+ const src = videoData.src || fallbackSrc || "";
441
+ const poster = videoData.poster || fallbackPoster || "";
442
+ const objectFit = videoData.objectFit || propObjectFit || "cover";
443
+ const aspectRatio = videoData.aspectRatio || propAspectRatio || "16/9";
444
+ const autoplay = videoData.autoplay ?? false;
445
+ const muted = videoData.muted ?? false;
446
+ const loop = videoData.loop ?? false;
447
+ const controls = videoData.controls ?? true;
448
+ const playsinline = videoData.playsinline ?? true;
449
+ const preload = videoData.preload ?? "metadata";
450
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
451
+ useEffect(() => {
452
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
453
+ setPrefersReducedMotion(mediaQuery.matches);
454
+ const handleChange = (e) => {
455
+ setPrefersReducedMotion(e.matches);
456
+ };
457
+ mediaQuery.addEventListener("change", handleChange);
458
+ return () => mediaQuery.removeEventListener("change", handleChange);
459
+ }, []);
460
+ const effectiveAutoplay = autoplay && !prefersReducedMotion;
461
+ useEffect(() => {
462
+ if (loading === "eager" || isInView) return;
463
+ const observer = new IntersectionObserver(
464
+ (entries) => {
465
+ if (entries[0]?.isIntersecting) {
466
+ setIsInView(true);
467
+ observer.disconnect();
468
+ }
469
+ },
470
+ { rootMargin: "200px" }
471
+ );
472
+ if (containerRef.current) {
473
+ observer.observe(containerRef.current);
474
+ }
475
+ return () => observer.disconnect();
476
+ }, [loading, isInView]);
477
+ const handleKeyDown = useCallback(
478
+ (e) => {
479
+ if ((e.key === " " || e.key === "Enter") && videoData.type === "upload" && controls) {
480
+ e.preventDefault();
481
+ const video = videoRef.current;
482
+ if (video) {
483
+ if (video.paused) {
484
+ video.play();
485
+ } else {
486
+ video.pause();
487
+ }
488
+ }
489
+ }
490
+ },
491
+ [videoData.type, controls]
492
+ );
493
+ const renderVideo = () => {
494
+ if (!src) return null;
495
+ if (!isInView && loading === "lazy") {
496
+ return /* @__PURE__ */ jsx8("div", { className: "ya-video-placeholder", style: { aspectRatio } });
497
+ }
498
+ if (videoData.type === "youtube" && src) {
499
+ const embedUrl = buildYouTubeEmbedUrl(src, videoData);
500
+ return /* @__PURE__ */ jsx8(
501
+ "iframe",
502
+ {
503
+ src: embedUrl,
504
+ className,
505
+ style: {
506
+ width: "100%",
507
+ height: "100%",
508
+ border: "none",
509
+ aspectRatio,
510
+ objectFit
511
+ },
512
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
513
+ allowFullScreen: true,
514
+ loading
515
+ }
516
+ );
517
+ }
518
+ if (videoData.type === "vimeo" && src) {
519
+ const embedUrl = buildVimeoEmbedUrl(src, videoData);
520
+ return /* @__PURE__ */ jsx8(
521
+ "iframe",
522
+ {
523
+ src: embedUrl,
524
+ className,
525
+ style: {
526
+ width: "100%",
527
+ height: "100%",
528
+ border: "none",
529
+ aspectRatio,
530
+ objectFit
531
+ },
532
+ allow: "autoplay; fullscreen; picture-in-picture",
533
+ allowFullScreen: true,
534
+ loading
535
+ }
536
+ );
537
+ }
538
+ const resolvedSrc = resolveAssetUrl(src);
539
+ const resolvedPoster = poster ? resolveAssetUrl(poster) : void 0;
540
+ return /* @__PURE__ */ jsx8(
541
+ "video",
542
+ {
543
+ ref: videoRef,
544
+ src: resolvedSrc,
545
+ poster: resolvedPoster,
546
+ className,
547
+ style: {
548
+ objectFit,
549
+ aspectRatio
550
+ },
551
+ autoPlay: effectiveAutoplay,
552
+ muted,
553
+ loop,
554
+ controls,
555
+ playsInline: playsinline,
556
+ preload,
557
+ onLoadedMetadata: (e) => {
558
+ if (videoData.startTime) {
559
+ e.currentTarget.currentTime = videoData.startTime;
560
+ }
561
+ },
562
+ onTimeUpdate: (e) => {
563
+ if (videoData.endTime && e.currentTarget.currentTime >= videoData.endTime) {
564
+ if (loop) {
565
+ e.currentTarget.currentTime = videoData.startTime || 0;
566
+ } else {
567
+ e.currentTarget.pause();
568
+ }
569
+ }
570
+ }
571
+ }
572
+ );
573
+ };
574
+ return /* @__PURE__ */ jsx8(
575
+ "div",
576
+ {
577
+ ref: containerRef,
578
+ className: `ya-video-wrapper ${className || ""}`,
579
+ style: { aspectRatio },
580
+ "data-field-id": fieldId,
581
+ tabIndex: videoData.type === "upload" && controls ? 0 : void 0,
582
+ role: videoData.type === "upload" && controls ? "application" : void 0,
583
+ "aria-label": videoData.type === "upload" && controls ? "Video player. Press Space or Enter to play/pause." : void 0,
584
+ onKeyDown: handleKeyDown,
585
+ children: renderVideo()
586
+ }
587
+ );
588
+ }
589
+
590
+ // src/components/StaticEmbed.tsx
591
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
592
+ import { jsx as jsx9, jsxs } from "react/jsx-runtime";
593
+ function parseEmbedValue(value) {
594
+ if (!value) {
595
+ return { type: "custom", src: "" };
596
+ }
597
+ try {
598
+ const parsed = JSON.parse(value);
599
+ if (typeof parsed === "object" && parsed.src) {
600
+ return {
601
+ type: parsed.type || "custom",
602
+ ...parsed
603
+ };
604
+ }
605
+ } catch {
606
+ }
607
+ return { type: "custom", src: value, originalUrl: value };
608
+ }
609
+ function serializeEmbedValue(value) {
610
+ return JSON.stringify(value);
611
+ }
612
+ function buildSpotifyEmbedUrl(src) {
613
+ return `https://open.spotify.com/embed/${src}?utm_source=generator&theme=0`;
614
+ }
615
+ function buildSoundCloudEmbedUrl(src) {
616
+ return `https://w.soundcloud.com/player/?url=${encodeURIComponent(src)}&color=%23ff5500&auto_play=false&hide_related=true&show_comments=false&show_user=true&show_reposts=false&show_teaser=false`;
617
+ }
618
+ function buildInstagramEmbedUrl(shortcode) {
619
+ return `https://www.instagram.com/p/${shortcode}/embed`;
620
+ }
621
+ function StaticEmbed({
622
+ fieldId,
623
+ className,
624
+ aspectRatio: propAspectRatio,
625
+ maxWidth,
626
+ loading = "lazy",
627
+ defaultValue
628
+ }) {
629
+ const { getValue } = useContentStore();
630
+ const containerRef = useRef2(null);
631
+ const [isInView, setIsInView] = useState2(loading === "eager");
632
+ const rawValue = getValue(fieldId);
633
+ const parsedValue = parseEmbedValue(rawValue);
634
+ const embedData = parsedValue.src ? parsedValue : defaultValue || parsedValue;
635
+ const src = embedData.src || "";
636
+ const embedType = embedData.type || "custom";
637
+ const height = embedData.height;
638
+ const aspectRatio = embedData.aspectRatio || propAspectRatio || "16/9";
639
+ useEffect2(() => {
640
+ if (loading === "eager" || isInView) return;
641
+ const observer = new IntersectionObserver(
642
+ (entries) => {
643
+ if (entries[0]?.isIntersecting) {
644
+ setIsInView(true);
645
+ observer.disconnect();
646
+ }
647
+ },
648
+ { rootMargin: "200px" }
649
+ );
650
+ if (containerRef.current) {
651
+ observer.observe(containerRef.current);
652
+ }
653
+ return () => observer.disconnect();
654
+ }, [loading, isInView]);
655
+ const renderEmbed = () => {
656
+ if (!src) return null;
657
+ if (!isInView && loading === "lazy") {
658
+ return /* @__PURE__ */ jsx9("div", { className: "ya-embed-placeholder", style: { aspectRatio } });
659
+ }
660
+ if (embedType === "spotify" && src) {
661
+ const embedUrl = buildSpotifyEmbedUrl(src);
662
+ return /* @__PURE__ */ jsx9(
663
+ "iframe",
664
+ {
665
+ src: embedUrl,
666
+ style: {
667
+ width: "100%",
668
+ height: height ? `${height}px` : "100%",
669
+ border: "none",
670
+ borderRadius: "12px"
671
+ },
672
+ allow: "autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture",
673
+ loading,
674
+ title: "Spotify embed"
675
+ }
676
+ );
677
+ }
678
+ if (embedType === "soundcloud" && src) {
679
+ const embedUrl = buildSoundCloudEmbedUrl(src);
680
+ return /* @__PURE__ */ jsx9(
681
+ "iframe",
682
+ {
683
+ src: embedUrl,
684
+ style: {
685
+ width: "100%",
686
+ height: height ? `${height}px` : "166px",
687
+ border: "none"
688
+ },
689
+ allow: "autoplay",
690
+ loading,
691
+ title: "SoundCloud embed"
692
+ }
693
+ );
694
+ }
695
+ if (embedType === "twitter" && src) {
696
+ return /* @__PURE__ */ jsxs("div", { className: "ya-embed-twitter", children: [
697
+ /* @__PURE__ */ jsx9("blockquote", { className: "twitter-tweet", "data-dnt": "true", children: /* @__PURE__ */ jsx9("a", { href: embedData.originalUrl || `https://twitter.com/i/status/${src}`, children: "Loading tweet..." }) }),
698
+ /* @__PURE__ */ jsx9(TwitterWidgetLoader, {})
699
+ ] });
700
+ }
701
+ if (embedType === "instagram" && src) {
702
+ const embedUrl = buildInstagramEmbedUrl(src);
703
+ return /* @__PURE__ */ jsx9(
704
+ "iframe",
705
+ {
706
+ src: embedUrl,
707
+ style: {
708
+ width: "100%",
709
+ height: "100%",
710
+ aspectRatio,
711
+ border: "none",
712
+ minHeight: "400px"
713
+ },
714
+ allow: "encrypted-media",
715
+ loading,
716
+ title: "Instagram embed"
717
+ }
718
+ );
719
+ }
720
+ if (embedType === "custom" && src) {
721
+ return /* @__PURE__ */ jsx9(
722
+ "iframe",
723
+ {
724
+ src,
725
+ style: {
726
+ width: "100%",
727
+ height: "100%",
728
+ border: "none",
729
+ aspectRatio
730
+ },
731
+ sandbox: "allow-scripts allow-same-origin allow-popups allow-forms",
732
+ loading,
733
+ title: "Embedded content"
734
+ }
735
+ );
736
+ }
737
+ return null;
738
+ };
739
+ const wrapperStyle = {
740
+ aspectRatio: height ? void 0 : aspectRatio,
741
+ height: height ? `${height}px` : void 0,
742
+ maxWidth: maxWidth ? `${maxWidth}px` : void 0
743
+ };
744
+ return /* @__PURE__ */ jsx9(
745
+ "div",
746
+ {
747
+ ref: containerRef,
748
+ className: `ya-embed-wrapper ${className || ""}`,
749
+ style: wrapperStyle,
750
+ "data-field-id": fieldId,
751
+ "data-embed-type": embedType,
752
+ children: renderEmbed()
753
+ }
754
+ );
755
+ }
756
+ function TwitterWidgetLoader() {
757
+ useEffect2(() => {
758
+ if (window.twttr?.widgets) {
759
+ ;
760
+ window.twttr.widgets.load();
761
+ return;
762
+ }
763
+ if (document.getElementById("twitter-wjs")) {
764
+ return;
765
+ }
766
+ const script = document.createElement("script");
767
+ script.id = "twitter-wjs";
768
+ script.src = "https://platform.twitter.com/widgets.js";
769
+ script.async = true;
770
+ document.body.appendChild(script);
771
+ }, []);
772
+ return null;
773
+ }
774
+
775
+ // src/components/YaEmbed.tsx
776
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
777
+
778
+ // #style-inject:#style-inject
779
+ function styleInject(css, { insertAt } = {}) {
780
+ if (!css || typeof document === "undefined") return;
781
+ const head = document.head || document.getElementsByTagName("head")[0];
782
+ const style = document.createElement("style");
783
+ style.type = "text/css";
784
+ if (insertAt === "top") {
785
+ if (head.firstChild) {
786
+ head.insertBefore(style, head.firstChild);
787
+ } else {
788
+ head.appendChild(style);
789
+ }
790
+ } else {
791
+ head.appendChild(style);
792
+ }
793
+ if (style.styleSheet) {
794
+ style.styleSheet.cssText = css;
795
+ } else {
796
+ style.appendChild(document.createTextNode(css));
797
+ }
798
+ }
799
+
800
+ // src/components/ya-embed.css
801
+ styleInject('.ya-embed-wrapper {\n position: relative;\n display: block;\n width: 100%;\n}\n.ya-embed-wrapper iframe {\n display: block;\n width: 100%;\n height: 100%;\n}\n.ya-embed-container {\n position: relative;\n display: block;\n width: 100%;\n min-width: 80px;\n min-height: 80px;\n cursor: pointer;\n transition: outline 0.15s ease;\n}\n.ya-embed-container iframe {\n display: block;\n width: 100%;\n height: 100%;\n pointer-events: none;\n}\n.ya-embed-editable {\n cursor: pointer;\n}\n.ya-embed-editable:hover {\n outline: 2px dashed var(--color-primary, #d4a574);\n outline-offset: 4px;\n}\n.ya-embed-selected {\n outline: 3px solid var(--color-primary, #d4a574);\n outline-offset: 4px;\n}\n.ya-embed-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n background: rgba(0, 0, 0, 0.5);\n opacity: 0;\n transition: opacity 0.2s ease;\n pointer-events: none;\n border-radius: inherit;\n}\n.ya-embed-editable:hover .ya-embed-overlay {\n opacity: 1;\n}\n.ya-embed-selected .ya-embed-overlay {\n opacity: 0;\n}\n.ya-embed-edit-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 48px;\n height: 48px;\n background: white;\n border-radius: 50%;\n color: #1a1a1a;\n box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);\n}\n.ya-embed-edit-icon svg {\n width: 24px;\n height: 24px;\n}\n.ya-embed-edit-label {\n color: white;\n font-size: 14px;\n font-weight: 500;\n text-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);\n}\n.ya-embed-placeholder {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n height: 100%;\n min-height: 120px;\n background: #f3f4f6;\n border: 2px dashed #d1d5db;\n border-radius: 8px;\n color: #6b7280;\n font-size: 14px;\n}\n.ya-embed-placeholder img {\n width: 64px;\n height: auto;\n opacity: 0.5;\n}\n@keyframes ya-embed-success {\n 0% {\n outline-color: var(--color-primary, #d4a574);\n }\n 50% {\n outline-color: #22c55e;\n outline-width: 4px;\n }\n 100% {\n outline-color: var(--color-primary, #d4a574);\n outline-width: 2px;\n }\n}\n.ya-embed-success {\n animation: ya-embed-success 0.4s ease;\n}\n.ya-embed-loading::after {\n content: "";\n position: absolute;\n inset: 0;\n background:\n linear-gradient(\n 90deg,\n rgba(255, 255, 255, 0) 0%,\n rgba(255, 255, 255, 0.3) 50%,\n rgba(255, 255, 255, 0) 100%);\n background-size: 200% 100%;\n animation: ya-embed-shimmer 1.5s infinite;\n}\n@keyframes ya-embed-shimmer {\n 0% {\n background-position: -200% 0;\n }\n 100% {\n background-position: 200% 0;\n }\n}\n.ya-embed-container:focus {\n outline: 3px solid var(--color-primary, #d4a574);\n outline-offset: 4px;\n}\n.ya-embed-container:focus:not(:focus-visible) {\n outline: none;\n}\n.ya-embed-container:focus-visible {\n outline: 3px solid var(--color-primary, #d4a574);\n outline-offset: 4px;\n}\n.ya-embed-small .ya-embed-overlay {\n display: none;\n}\n.ya-embed-twitter {\n min-height: 200px;\n}\n.ya-embed-twitter .twitter-tweet {\n margin: 0 auto !important;\n}\n.ya-embed-wrapper[data-embed-type=spotify],\n.ya-embed-container[data-embed-type=spotify] {\n min-height: 80px;\n}\n.ya-embed-wrapper[data-embed-type=soundcloud],\n.ya-embed-container[data-embed-type=soundcloud] {\n min-height: 166px;\n}\n.ya-embed-wrapper[data-embed-type=instagram] iframe,\n.ya-embed-container[data-embed-type=instagram] iframe {\n min-height: 400px;\n}\n');
802
+
803
+ // src/components/YaEmbed.tsx
804
+ import { jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
805
+ function parseEmbedUrl(url) {
806
+ if (!url) return null;
807
+ const trimmedUrl = url.trim();
808
+ const spotifyMatch = trimmedUrl.match(
809
+ /open\.spotify\.com\/(track|album|playlist|episode|show)\/([a-zA-Z0-9]+)/
810
+ );
811
+ if (spotifyMatch) {
812
+ const spotifyType = spotifyMatch[1];
813
+ const spotifyId = spotifyMatch[2];
814
+ const height = spotifyType === "track" ? 152 : 352;
815
+ return {
816
+ type: "spotify",
817
+ src: `${spotifyType}/${spotifyId}`,
818
+ originalUrl: trimmedUrl,
819
+ height,
820
+ spotifyType
821
+ };
822
+ }
823
+ const soundcloudMatch = trimmedUrl.match(/soundcloud\.com\/([^/]+)\/([^/?]+)/);
824
+ if (soundcloudMatch) {
825
+ return {
826
+ type: "soundcloud",
827
+ src: trimmedUrl,
828
+ // SoundCloud embeds use full URL
829
+ originalUrl: trimmedUrl,
830
+ height: 166
831
+ };
832
+ }
833
+ const twitterMatch = trimmedUrl.match(/(?:twitter\.com|x\.com)\/\w+\/status\/(\d+)/);
834
+ if (twitterMatch) {
835
+ return {
836
+ type: "twitter",
837
+ src: twitterMatch[1],
838
+ // Tweet ID
839
+ originalUrl: trimmedUrl,
840
+ aspectRatio: "1/1"
841
+ // Twitter embeds are roughly square
842
+ };
843
+ }
844
+ const instagramMatch = trimmedUrl.match(/instagram\.com\/(?:p|reel)\/([a-zA-Z0-9_-]+)/);
845
+ if (instagramMatch) {
846
+ return {
847
+ type: "instagram",
848
+ src: instagramMatch[1],
849
+ // Post shortcode
850
+ originalUrl: trimmedUrl,
851
+ aspectRatio: "1/1"
852
+ };
853
+ }
854
+ if (trimmedUrl.startsWith("https://")) {
855
+ if (trimmedUrl.startsWith("javascript:") || trimmedUrl.startsWith("data:") || trimmedUrl.includes("<script")) {
856
+ return null;
857
+ }
858
+ return {
859
+ type: "custom",
860
+ src: trimmedUrl,
861
+ originalUrl: trimmedUrl,
862
+ aspectRatio: "16/9"
863
+ };
864
+ }
865
+ return null;
866
+ }
867
+ var PLACEHOLDER_SVG = `data:image/svg+xml,${encodeURIComponent(`
868
+ <svg xmlns="http://www.w3.org/2000/svg" width="400" height="225" viewBox="0 0 400 225">
869
+ <rect fill="#e5e7eb" width="400" height="225"/>
870
+ <g fill="#9ca3af" transform="translate(175, 87)">
871
+ <rect x="5" y="5" width="40" height="30" rx="4" stroke="currentColor" stroke-width="2" fill="none"/>
872
+ <path d="M15 15 L25 22 L15 29 Z" fill="currentColor"/>
873
+ </g>
874
+ </svg>
875
+ `)}`;
876
+
301
877
  // src/router/Link.tsx
302
878
  import { Link as WouterLink } from "wouter";
303
- import { jsx as jsx7 } from "react/jsx-runtime";
879
+ import { jsx as jsx11 } from "react/jsx-runtime";
304
880
  function Link({ to, href, children, className, onClick, replace, ...props }) {
305
881
  const target = href ?? to ?? "/";
306
- return /* @__PURE__ */ jsx7(WouterLink, { href: target, className, onClick, replace, ...props, children });
882
+ return /* @__PURE__ */ jsx11(WouterLink, { href: target, className, onClick, replace, ...props, children });
307
883
  }
308
884
 
309
885
  // src/router/useNavigate.ts
@@ -322,7 +898,7 @@ function useNavigate() {
322
898
 
323
899
  // src/router/Router.tsx
324
900
  import { Router as WouterRouter } from "wouter";
325
- import { jsx as jsx8 } from "react/jsx-runtime";
901
+ import { jsx as jsx12 } from "react/jsx-runtime";
326
902
  function detectBasename() {
327
903
  if (typeof window === "undefined") return "";
328
904
  const sessionMatch = window.location.pathname.match(/^\/session\/[^/]+/);
@@ -337,7 +913,7 @@ function detectBasename() {
337
913
  }
338
914
  function Router({ children, base }) {
339
915
  const basename = base ?? detectBasename();
340
- return /* @__PURE__ */ jsx8(WouterRouter, { base: basename, children });
916
+ return /* @__PURE__ */ jsx12(WouterRouter, { base: basename, children });
341
917
  }
342
918
 
343
919
  // src/router/index.ts
@@ -349,19 +925,30 @@ export {
349
925
  Route,
350
926
  Router,
351
927
  SafeHtml,
928
+ StaticContainer,
929
+ StaticEmbed,
352
930
  MpImage as StaticImage,
353
931
  StaticLink,
354
932
  MpText as StaticText,
933
+ StaticVideo,
355
934
  Switch,
935
+ StaticContainer as YaContainer,
936
+ StaticEmbed as YaEmbed,
356
937
  MpImage as YaImage,
357
938
  StaticLink as YaLink,
358
939
  MpText as YaText,
940
+ StaticVideo as YaVideo,
359
941
  contentRegistry,
360
942
  getAllContent,
361
943
  getContent,
362
944
  hasContent,
945
+ parseBackgroundConfig,
946
+ parseEmbedUrl,
363
947
  registerContent,
364
948
  resolveAssetUrl,
949
+ serializeBackgroundConfig,
950
+ serializeEmbedValue,
951
+ serializeVideoValue,
365
952
  setAssetResolver,
366
953
  useContentStore,
367
954
  useNavigate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoamigo.com/core",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "description": "Core components, router, and utilities for YoAmigo templates",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",