@skhema/embed 0.1.9 → 0.1.10

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 (40) hide show
  1. package/README.md +55 -0
  2. package/dist/components/SkhemaComponent.d.ts +0 -1
  3. package/dist/components/SkhemaComponent.d.ts.map +1 -1
  4. package/dist/components/SkhemaElement.d.ts.map +1 -1
  5. package/dist/embed.min.js +1 -1
  6. package/dist/embed.min.js.map +1 -1
  7. package/dist/index-DBeyneZT.cjs +215 -0
  8. package/dist/index-DBeyneZT.cjs.map +1 -0
  9. package/dist/index-jwx5CXLB.js +216 -0
  10. package/dist/index-jwx5CXLB.js.map +1 -0
  11. package/dist/index.cjs +79 -440
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.es.js +81 -442
  14. package/dist/index.es.js.map +1 -1
  15. package/dist/render/index.d.ts +55 -0
  16. package/dist/render/index.d.ts.map +1 -0
  17. package/dist/render.cjs +7 -0
  18. package/dist/render.cjs.map +1 -0
  19. package/dist/render.d.ts +2 -0
  20. package/dist/render.es.js +7 -0
  21. package/dist/render.es.js.map +1 -0
  22. package/dist/styles/design-tokens.d.ts +32 -4
  23. package/dist/styles/design-tokens.d.ts.map +1 -1
  24. package/dist/tokens.cjs +1 -1
  25. package/dist/tokens.d.ts +1 -1
  26. package/dist/tokens.d.ts.map +1 -1
  27. package/dist/tokens.es.js +1 -1
  28. package/dist/utils/color.d.ts +25 -0
  29. package/dist/utils/color.d.ts.map +1 -0
  30. package/dist/utils/sanitization.d.ts +7 -1
  31. package/dist/utils/sanitization.d.ts.map +1 -1
  32. package/dist/{validation-CNWaQfh_.cjs → validation-DCw9rQeH.cjs} +40 -95
  33. package/dist/validation-DCw9rQeH.cjs.map +1 -0
  34. package/dist/{validation-MSiRF5Wt.js → validation-Dt4L_df8.js} +42 -97
  35. package/dist/validation-Dt4L_df8.js.map +1 -0
  36. package/package.json +11 -4
  37. package/dist/utils/author-attribution.d.ts +0 -16
  38. package/dist/utils/author-attribution.d.ts.map +0 -1
  39. package/dist/validation-CNWaQfh_.cjs.map +0 -1
  40. package/dist/validation-MSiRF5Wt.js.map +0 -1
package/dist/index.es.js CHANGED
@@ -1,39 +1,6 @@
1
- import { d as getElementTypeLabel, b as getComponentTypeLabel, i as isValidComponentType, v as validateElementBelongsToComponent, e as getElementTypesForComponent, g as getComponentTypeAcronym, a as COMPONENT_COLORS, S as SHARED_CARD_STYLES, U as USER_ICON_SVG, h as validateAttributes, r as resolveComponentType } from "./validation-MSiRF5Wt.js";
2
- import { c, f } from "./validation-MSiRF5Wt.js";
3
- function readAuthorAttribution(element) {
4
- const authorName = element.getAttribute("author-name") || element.getAttribute("contributor-name");
5
- if (!authorName?.trim()) {
6
- return null;
7
- }
8
- const authorSlug = element.getAttribute("author-slug")?.trim();
9
- return {
10
- authorName: authorName.trim(),
11
- authorSlug: authorSlug || void 0
12
- };
13
- }
14
- function escapeHtml(text) {
15
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
16
- }
17
- function formatAuthorAttributionHtml(authorName, authorSlug) {
18
- const label = `By ${escapeHtml(authorName)}`;
19
- if (authorSlug) {
20
- return `<a href="https://skhema.com/contributors/${encodeURIComponent(authorSlug)}" class="skhema-author-link" target="_blank" rel="noopener noreferrer">${label}</a>`;
21
- }
22
- return `<span class="skhema-author-text">${label}</span>`;
23
- }
24
- function formatContributorIdFallback(contributorId) {
25
- return contributorId.split(/[_-]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
26
- }
27
- function resolveAuthorFooterHtml(element, contributorId) {
28
- const attribution = readAuthorAttribution(element);
29
- if (attribution) {
30
- return formatAuthorAttributionHtml(
31
- attribution.authorName,
32
- attribution.authorSlug
33
- );
34
- }
35
- return `<span class="skhema-author-text">By ${escapeHtml(formatContributorIdFallback(contributorId))}</span>`;
36
- }
1
+ import { r as renderComponentCardHtml, a as renderElementCardHtml, v as validateContentSecurity } from "./index-jwx5CXLB.js";
2
+ import { d as getElementTypeLabel, b as getComponentTypeLabel, i as isValidComponentType, v as validateElementBelongsToComponent, e as getElementTypesForComponent, l as CARD_SHADOW_LG, m as PRIMARY_HOVER_HEX, n as PRIMARY_PRESSED_HEX, o as validateAttributes, P as PRIMARY_HEX } from "./validation-Dt4L_df8.js";
3
+ import { g, c, f, r } from "./validation-Dt4L_df8.js";
37
4
  const ANALYTICS_ENDPOINT = "https://analytics.skhema.com/functions/v1/embed-manage";
38
5
  const TRACKING_COOKIE_NAME = "_sk";
39
6
  const TRACKING_EXPIRY_HOURS = 24;
@@ -426,75 +393,6 @@ function generateComponentHash(elements) {
426
393
  const combined = sorted.map((e) => `${e.elementType}:${e.content}`).join("|");
427
394
  return generateContentHash(combined);
428
395
  }
429
- function sanitizeContent(content) {
430
- const htmlEncode = (str) => {
431
- const div = document.createElement("div");
432
- div.textContent = str;
433
- return div.innerHTML;
434
- };
435
- let sanitized = stripUrls(content);
436
- sanitized = htmlEncode(sanitized);
437
- sanitized = sanitized.replace(/\n/g, "<br>");
438
- sanitized = applyTextWrapping(sanitized);
439
- return sanitized;
440
- }
441
- function stripUrls(text) {
442
- const patterns = [
443
- // Standard URLs with protocols
444
- /https?:\/\/[^\s<>"{}|\\^`[\]]+/gi,
445
- // FTP URLs
446
- /ftp:\/\/[^\s<>"{}|\\^`[\]]+/gi,
447
- // URLs without protocol but with www
448
- /www\.[^\s<>"{}|\\^`[\]]+/gi,
449
- // Email-like patterns
450
- /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/gi,
451
- // Common domain patterns (anything.com, anything.org, etc.)
452
- /(?:^|\s)([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?/gi
453
- ];
454
- let stripped = text;
455
- patterns.forEach((pattern) => {
456
- stripped = stripped.replace(pattern, "");
457
- });
458
- stripped = stripped.replace(/\s+/g, " ").trim();
459
- return stripped;
460
- }
461
- function applyTextWrapping(text) {
462
- const words = text.split(/(\s+)/);
463
- return words.map((word) => {
464
- if (/^\s+$/.test(word) || word.includes("<")) {
465
- return word;
466
- }
467
- if (word.length > 30) {
468
- return word.replace(/(.{10})/g, "$1​");
469
- }
470
- return word;
471
- }).join("");
472
- }
473
- function validateContentSecurity(content) {
474
- const issues = [];
475
- if (/<script[\s>]/i.test(content)) {
476
- issues.push("Script tags detected");
477
- }
478
- if (/on\w+\s*=/i.test(content)) {
479
- issues.push("Event handlers detected");
480
- }
481
- if (/javascript:/i.test(content)) {
482
- issues.push("JavaScript protocol detected");
483
- }
484
- if (/data:[^,]*script/i.test(content)) {
485
- issues.push("Data URL with script detected");
486
- }
487
- if (/<iframe[\s>]/i.test(content)) {
488
- issues.push("Iframe tags detected");
489
- }
490
- if (/https?:\/\//i.test(content) || /www\./i.test(content)) {
491
- issues.push("URLs detected in content");
492
- }
493
- return {
494
- isSecure: issues.length === 0,
495
- issues
496
- };
497
- }
498
396
  function generateStructuredData(content, elementType, contributorId, sourceUrl) {
499
397
  return {
500
398
  "@context": "https://schema.org",
@@ -626,7 +524,7 @@ const styles$1 = `
626
524
  color: var(--skhema-text);
627
525
  }
628
526
 
629
- /* Dark mode */
527
+ /* Dark mode (skeleton/error only) */
630
528
  .skhema-component-card[data-theme="dark"],
631
529
  .skhema-skeleton[data-theme="dark"] {
632
530
  --skhema-bg: hsl(222.2 84% 4.9%);
@@ -638,106 +536,40 @@ const styles$1 = `
638
536
  --skhema-surface-subtle: hsl(217.2 32.6% 12%);
639
537
  }
640
538
 
641
- /* Main card */
642
- .skhema-component-card {
643
- position: relative;
644
- background: var(--skhema-card);
645
- border: 1px solid var(--skhema-border);
646
- border-radius: calc(var(--skhema-radius) + 2px);
647
- padding: 0;
648
- box-shadow: var(--skhema-shadow);
649
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
650
- max-width: 600px;
651
- margin: 8px 0;
652
- overflow: hidden;
539
+ /* Progressive enhancement over the inline-styled, rendered card. Inline styles
540
+ win on specificity, so the changed properties use !important to layer on. */
541
+ .skhema-card {
542
+ transition: box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1), transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
653
543
  }
654
544
 
655
- .skhema-component-card:hover {
656
- box-shadow: var(--skhema-shadow-lg);
545
+ .skhema-card:hover {
546
+ box-shadow: ${CARD_SHADOW_LG} !important;
657
547
  transform: translateY(-1px);
658
548
  }
659
549
 
660
- /* Colored top border */
661
- .skhema-top-border {
662
- height: 2px;
663
- width: 100%;
664
- }
665
-
666
- /* Header */
667
- .skhema-header {
668
- display: flex;
669
- align-items: center;
670
- gap: 8px;
671
- padding: 12px 16px;
672
- border-bottom: 1px solid var(--skhema-border);
550
+ .skhema-save-btn {
551
+ transition: background 0.15s ease;
673
552
  }
674
553
 
675
- .skhema-type-label {
676
- font-size: 13px;
677
- font-weight: 500;
678
- color: var(--skhema-text-muted);
554
+ .skhema-save-btn:hover {
555
+ background: ${PRIMARY_HOVER_HEX} !important;
679
556
  }
680
557
 
681
- .skhema-title-separator {
682
- color: var(--skhema-text-muted);
683
- font-size: 13px;
558
+ .skhema-save-btn:active {
559
+ background: ${PRIMARY_PRESSED_HEX} !important;
684
560
  }
685
561
 
686
- .skhema-title {
687
- font-size: 13px;
688
- font-weight: 600;
689
- color: var(--skhema-text);
690
- }
691
-
692
- /* Elements section */
693
- .skhema-elements {
694
- padding: 16px;
695
- display: flex;
696
- flex-direction: column;
697
- gap: 16px;
698
- }
699
-
700
- /* Element group */
701
- .skhema-element-group {
702
- display: flex;
703
- flex-direction: column;
704
- gap: 4px;
705
- }
706
-
707
- .skhema-element-label {
708
- text-transform: uppercase;
709
- letter-spacing: 0.05em;
710
- font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
711
- font-size: 10px;
712
- font-weight: 600;
713
- color: var(--skhema-text-muted);
714
- }
715
-
716
- .skhema-element-content {
717
- font-size: 14px;
718
- line-height: 1.6;
719
- color: var(--skhema-text);
720
- padding-left: 8px;
721
- border-left: 2px solid var(--skhema-border);
722
- word-wrap: break-word;
723
- overflow-wrap: break-word;
724
- }
725
-
726
- /* Footer */
727
- .skhema-footer {
728
- padding: 12px 16px;
729
- border-top: 1px solid var(--skhema-border);
730
- }
731
-
732
- .skhema-footer-row {
733
- display: flex;
734
- align-items: center;
735
- justify-content: space-between;
736
- gap: 12px;
562
+ /* Error card wrapper (browser-only state) */
563
+ .skhema-component-card {
564
+ background: var(--skhema-card);
565
+ border: 1px solid var(--skhema-border);
566
+ border-radius: calc(var(--skhema-radius) + 2px);
567
+ box-shadow: var(--skhema-shadow);
568
+ max-width: 600px;
569
+ margin: 8px 0;
570
+ overflow: hidden;
737
571
  }
738
572
 
739
- ${SHARED_CARD_STYLES}
740
-
741
573
  /* Error state */
742
574
  .skhema-error {
743
575
  background: hsl(0 93% 94%);
@@ -822,39 +654,9 @@ ${SHARED_CARD_STYLES}
822
654
  100% { background-position: 200% 0; }
823
655
  }
824
656
 
825
- /* Responsive */
826
- @media (max-width: 640px) {
827
- .skhema-component-card {
828
- margin: 4px 0;
829
- }
830
-
831
- .skhema-header {
832
- padding: 10px 12px;
833
- flex-wrap: wrap;
834
- }
835
-
836
- .skhema-elements {
837
- padding: 12px;
838
- }
839
-
840
- .skhema-footer {
841
- padding: 10px 12px;
842
- }
843
-
844
- .skhema-footer-row {
845
- flex-direction: column;
846
- align-items: stretch;
847
- gap: 8px;
848
- }
849
-
850
- .skhema-save-btn {
851
- justify-content: center;
852
- }
853
- }
854
-
855
657
  /* Accessibility */
856
658
  @media (prefers-reduced-motion: reduce) {
857
- .skhema-component-card,
659
+ .skhema-card,
858
660
  .skhema-save-btn {
859
661
  transition: none;
860
662
  }
@@ -1020,9 +822,6 @@ class SkhemaComponent extends HTMLElement {
1020
822
  if (!this.contentData) return;
1021
823
  const { component_type, contributor_id, title } = this.contentData;
1022
824
  const componentLabel = getComponentTypeLabel(component_type);
1023
- const acronym = getComponentTypeAcronym(component_type);
1024
- const colors = COMPONENT_COLORS[component_type];
1025
- const authorFooterHtml = resolveAuthorFooterHtml(this, contributor_id);
1026
825
  const themeAttribute = this.getAttribute("theme") || "auto";
1027
826
  const actualTheme = this.getActualTheme(themeAttribute);
1028
827
  const redirectUrl = generateComponentRedirectUrl(
@@ -1030,65 +829,25 @@ class SkhemaComponent extends HTMLElement {
1030
829
  component_type,
1031
830
  contributor_id
1032
831
  );
1033
- const badgeStyle = colors ? `background: ${colors.bg}; color: ${colors.text}; border: 1px solid ${colors.border};` : "";
1034
- const topBorderColor = colors ? colors.text : "var(--skhema-primary)";
1035
- const groupedElements = this.groupElementsByType(elements);
1036
- const elementSectionsHtml = groupedElements.map(
1037
- (group) => `
1038
- <div class="skhema-element-group">
1039
- <div class="skhema-element-label">${getElementTypeLabel(group.elementType)}</div>
1040
- ${group.items.map(
1041
- (item) => `<div class="skhema-element-content">${sanitizeContent(item.content)}</div>`
1042
- ).join("")}
1043
- </div>
1044
- `
1045
- ).join("");
1046
832
  this.setAttribute("role", "article");
1047
833
  this.setAttribute(
1048
834
  "aria-label",
1049
835
  `${componentLabel} component${title ? ` — ${title}` : ""}`
1050
836
  );
1051
- const titleHtml = title ? `<span class="skhema-title-separator">&mdash;</span><span class="skhema-title">${title}</span>` : "";
1052
- this.shadow.innerHTML = `
1053
- <style>${styles$1}</style>
1054
-
1055
- <div class="skhema-component-card" data-theme="${actualTheme}">
1056
- <div class="skhema-top-border" style="background: ${topBorderColor};"></div>
1057
-
1058
- <div class="skhema-header">
1059
- <span class="skhema-acronym-badge" style="${badgeStyle}" title="${componentLabel}">
1060
- ${acronym}
1061
- </span>
1062
- <span class="skhema-type-label">${componentLabel}</span>
1063
- ${titleHtml}
1064
- </div>
1065
-
1066
- <div class="skhema-elements">
1067
- ${elementSectionsHtml}
1068
- </div>
1069
-
1070
- <div class="skhema-footer">
1071
- <div class="skhema-footer-row">
1072
- <span class="skhema-contributor-line">
1073
- ${USER_ICON_SVG}
1074
- ${authorFooterHtml}
1075
- </span>
1076
- <a href="${redirectUrl}"
1077
- class="skhema-save-btn"
1078
- target="_blank"
1079
- rel="noopener noreferrer"
1080
- title="Save this component to Skhema">
1081
- Save to Skhema
1082
- </a>
1083
- </div>
1084
- <div class="skhema-footer-attribution">
1085
- Strategy powered by <a href="https://skhema.com" target="_blank" rel="noopener noreferrer">Skhema</a>
1086
- </div>
1087
- </div>
1088
- </div>
1089
-
1090
- <slot style="display:none;"></slot>
1091
- `;
837
+ const cardHtml = renderComponentCardHtml({
838
+ componentType: component_type,
839
+ title,
840
+ elements: elements.map((el) => ({
841
+ elementType: el.elementType,
842
+ content: el.content
843
+ })),
844
+ saveUrl: redirectUrl,
845
+ authorName: this.getAttribute("author-name") || this.getAttribute("contributor-name"),
846
+ authorSlug: this.getAttribute("author-slug"),
847
+ contributorId: contributor_id,
848
+ theme: actualTheme
849
+ });
850
+ this.shadow.innerHTML = `<style>${styles$1}</style>${cardHtml}<slot style="display:none;"></slot>`;
1092
851
  const saveBtn = this.shadow.querySelector(
1093
852
  ".skhema-save-btn"
1094
853
  );
@@ -1098,21 +857,6 @@ class SkhemaComponent extends HTMLElement {
1098
857
  });
1099
858
  }
1100
859
  }
1101
- groupElementsByType(elements) {
1102
- const groups = /* @__PURE__ */ new Map();
1103
- for (const el of elements) {
1104
- const existing = groups.get(el.elementType);
1105
- if (existing) {
1106
- existing.push(el);
1107
- } else {
1108
- groups.set(el.elementType, [el]);
1109
- }
1110
- }
1111
- return Array.from(groups.entries()).map(([elementType, items]) => ({
1112
- elementType,
1113
- items
1114
- }));
1115
- }
1116
860
  getActualTheme(themeAttribute) {
1117
861
  if (themeAttribute === "light" || themeAttribute === "dark") {
1118
862
  return themeAttribute;
@@ -1300,12 +1044,8 @@ class SkhemaComponent extends HTMLElement {
1300
1044
  }
1301
1045
  updateTheme() {
1302
1046
  const themeAttribute = this.getAttribute("theme") || "auto";
1303
- if (themeAttribute === "auto") {
1304
- const card = this.shadow.querySelector(".skhema-component-card");
1305
- if (card) {
1306
- const newTheme = this.getActualTheme("auto");
1307
- card.setAttribute("data-theme", newTheme);
1308
- }
1047
+ if (themeAttribute === "auto" && this.contentData) {
1048
+ this.scheduleRender();
1309
1049
  }
1310
1050
  }
1311
1051
  }
@@ -1337,7 +1077,7 @@ const styles = `
1337
1077
  color: var(--skhema-text);
1338
1078
  }
1339
1079
 
1340
- /* Dark mode styles - applied via data-theme attribute */
1080
+ /* Dark mode styles - applied via data-theme attribute (skeleton/error only) */
1341
1081
  .skhema-element-card[data-theme="dark"],
1342
1082
  .skhema-skeleton[data-theme="dark"] {
1343
1083
  --skhema-bg: hsl(222.2 84% 4.9%);
@@ -1348,71 +1088,42 @@ const styles = `
1348
1088
  --skhema-accent: hsl(217.2 32.6% 17.5%);
1349
1089
  }
1350
1090
 
1351
- /* Main component card */
1352
- .skhema-element-card {
1353
- position: relative;
1354
- background: var(--skhema-card);
1355
- border: 1px solid var(--skhema-border);
1356
- border-radius: calc(var(--skhema-radius) + 2px);
1357
- padding: 0;
1358
- box-shadow: var(--skhema-shadow);
1359
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
1360
- max-width: 600px;
1361
- margin: 8px 0;
1362
- overflow: hidden;
1091
+ /* Progressive enhancement over the inline-styled, rendered card. Inline styles
1092
+ win on specificity, so the changed properties use !important to layer on. */
1093
+ .skhema-card {
1094
+ transition: box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1), transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
1363
1095
  }
1364
1096
 
1365
- .skhema-element-card:hover {
1366
- box-shadow: var(--skhema-shadow-lg);
1367
- border-color: var(--skhema-primary);
1097
+ .skhema-card:hover {
1098
+ box-shadow: ${CARD_SHADOW_LG} !important;
1368
1099
  transform: translateY(-1px);
1369
1100
  }
1370
1101
 
1371
- /* Header section with acronym badge + element type label */
1372
- .skhema-header {
1373
- display: flex;
1374
- align-items: center;
1375
- gap: 8px;
1376
- padding: 12px 16px;
1377
- border-bottom: 1px solid var(--skhema-border);
1378
- }
1379
-
1380
- /* Content section */
1381
- .skhema-content {
1382
- padding: 16px;
1102
+ .skhema-card[data-skhema-kind="element"]:hover {
1103
+ border-color: ${PRIMARY_HEX} !important;
1383
1104
  }
1384
1105
 
1385
- .skhema-content-text {
1386
- font-size: 15px;
1387
- line-height: 1.6;
1388
- color: var(--skhema-text);
1389
- margin: 0;
1390
- word-wrap: break-word;
1391
- overflow-wrap: break-word;
1392
- hyphens: auto;
1393
- max-width: 100%;
1106
+ .skhema-save-btn {
1107
+ transition: background 0.15s ease;
1394
1108
  }
1395
1109
 
1396
- /* Footer section */
1397
- .skhema-footer {
1398
- padding: 12px 16px;
1399
- border-top: 1px solid var(--skhema-border);
1110
+ .skhema-save-btn:hover {
1111
+ background: ${PRIMARY_HOVER_HEX} !important;
1400
1112
  }
1401
1113
 
1402
- .skhema-footer-row {
1403
- display: flex;
1404
- align-items: center;
1405
- justify-content: space-between;
1406
- gap: 12px;
1114
+ .skhema-save-btn:active {
1115
+ background: ${PRIMARY_PRESSED_HEX} !important;
1407
1116
  }
1408
1117
 
1409
- ${SHARED_CARD_STYLES}
1410
-
1411
- /* Element type label next to badge */
1412
- .skhema-type-label {
1413
- font-size: 13px;
1414
- font-weight: 500;
1415
- color: var(--skhema-text);
1118
+ /* Error card wrapper (browser-only state) */
1119
+ .skhema-element-card {
1120
+ background: var(--skhema-card);
1121
+ border: 1px solid var(--skhema-border);
1122
+ border-radius: calc(var(--skhema-radius) + 2px);
1123
+ box-shadow: var(--skhema-shadow);
1124
+ max-width: 600px;
1125
+ margin: 8px 0;
1126
+ overflow: hidden;
1416
1127
  }
1417
1128
 
1418
1129
  /* Error state */
@@ -1512,49 +1223,12 @@ ${SHARED_CARD_STYLES}
1512
1223
  }
1513
1224
  }
1514
1225
 
1515
- /* Responsive design */
1516
- @media (max-width: 640px) {
1517
- .skhema-element-card {
1518
- margin: 4px 0;
1519
- }
1520
-
1521
- .skhema-header {
1522
- padding: 10px 12px;
1523
- }
1524
-
1525
- .skhema-content {
1526
- padding: 12px;
1527
- }
1528
-
1529
- .skhema-footer {
1530
- padding: 10px 12px;
1531
- }
1532
-
1533
- .skhema-footer-row {
1534
- flex-direction: column;
1535
- align-items: stretch;
1536
- gap: 8px;
1537
- }
1538
-
1539
- .skhema-save-btn {
1540
- justify-content: center;
1541
- }
1542
- }
1543
-
1544
1226
  /* Accessibility */
1545
1227
  @media (prefers-reduced-motion: reduce) {
1546
- .skhema-element-card,
1228
+ .skhema-card,
1547
1229
  .skhema-save-btn {
1548
1230
  transition: none;
1549
1231
  }
1550
-
1551
- .skhema-save-btn::after {
1552
- transition: none;
1553
- }
1554
-
1555
- .skhema-save-btn:hover::after {
1556
- transform: none;
1557
- }
1558
1232
  }
1559
1233
 
1560
1234
  .skhema-structured-data {
@@ -1692,7 +1366,6 @@ class SkhemaElement extends HTMLElement {
1692
1366
  renderContent() {
1693
1367
  if (!this.contentData) return;
1694
1368
  const { element_type, contributor_id, content } = this.contentData;
1695
- const label = getElementTypeLabel(element_type);
1696
1369
  const redirectUrl = generateRedirectUrl(
1697
1370
  content,
1698
1371
  element_type,
@@ -1700,50 +1373,20 @@ class SkhemaElement extends HTMLElement {
1700
1373
  );
1701
1374
  const themeAttribute = this.getAttribute("theme") || "auto";
1702
1375
  const actualTheme = this.getActualTheme(themeAttribute);
1703
- const authorFooterHtml = resolveAuthorFooterHtml(this, contributor_id);
1704
- const componentType = resolveComponentType(element_type);
1705
- const acronym = getComponentTypeAcronym(componentType);
1706
- const colors = COMPONENT_COLORS[componentType];
1707
1376
  const ariaAttrs = createAriaAttributes(element_type);
1708
1377
  Object.entries(ariaAttrs).forEach(([key, value]) => {
1709
1378
  this.setAttribute(key, value);
1710
1379
  });
1711
- const badgeStyle = colors ? `background: ${colors.bg}; color: ${colors.text}; border: 1px solid ${colors.border};` : "";
1712
- this.shadow.innerHTML = `
1713
- <style>${styles}</style>
1714
-
1715
- <div class="skhema-element-card" data-theme="${actualTheme}">
1716
- <div class="skhema-header">
1717
- <span class="skhema-acronym-badge" style="${badgeStyle}" title="${label}">
1718
- ${acronym}
1719
- </span>
1720
- <span class="skhema-type-label">${label}</span>
1721
- </div>
1722
-
1723
- <div class="skhema-content">
1724
- <div class="skhema-content-text">${sanitizeContent(content)}</div>
1725
- </div>
1726
-
1727
- <div class="skhema-footer">
1728
- <div class="skhema-footer-row">
1729
- <span class="skhema-contributor-line">
1730
- ${USER_ICON_SVG}
1731
- ${authorFooterHtml}
1732
- </span>
1733
- <a href="${redirectUrl}"
1734
- class="skhema-save-btn"
1735
- target="_blank"
1736
- rel="noopener noreferrer"
1737
- title="Save this element to Skhema">
1738
- Save to Skhema
1739
- </a>
1740
- </div>
1741
- <div class="skhema-footer-attribution">
1742
- Strategy powered by <a href="https://skhema.com" target="_blank" rel="noopener noreferrer">Skhema</a>
1743
- </div>
1744
- </div>
1745
- </div>
1746
- `;
1380
+ const cardHtml = renderElementCardHtml({
1381
+ elementType: element_type,
1382
+ content,
1383
+ saveUrl: redirectUrl,
1384
+ authorName: this.getAttribute("author-name") || this.getAttribute("contributor-name"),
1385
+ authorSlug: this.getAttribute("author-slug"),
1386
+ contributorId: contributor_id,
1387
+ theme: actualTheme
1388
+ });
1389
+ this.shadow.innerHTML = `<style>${styles}</style>${cardHtml}`;
1747
1390
  const saveBtn = this.shadow.querySelector(
1748
1391
  ".skhema-save-btn"
1749
1392
  );
@@ -1931,12 +1574,8 @@ class SkhemaElement extends HTMLElement {
1931
1574
  }
1932
1575
  updateTheme() {
1933
1576
  const themeAttribute = this.getAttribute("theme") || "auto";
1934
- if (themeAttribute === "auto") {
1935
- const card = this.shadow.querySelector(".skhema-element-card");
1936
- if (card) {
1937
- const newTheme = this.getActualTheme("auto");
1938
- card.setAttribute("data-theme", newTheme);
1939
- }
1577
+ if (themeAttribute === "auto" && this.contentData && !this.nestedMode) {
1578
+ this.renderContent();
1940
1579
  }
1941
1580
  }
1942
1581
  }
@@ -1979,7 +1618,7 @@ export {
1979
1618
  generateComponentStructuredData,
1980
1619
  generateRedirectUrl,
1981
1620
  generateStructuredData,
1982
- getComponentTypeAcronym,
1621
+ g as getComponentTypeAcronym,
1983
1622
  getComponentTypeLabel,
1984
1623
  c as getElementTypeAcronym,
1985
1624
  getElementTypeLabel,
@@ -1987,7 +1626,7 @@ export {
1987
1626
  f as isValidElementType,
1988
1627
  registerSkhemaComponent,
1989
1628
  registerSkhemaElement,
1990
- resolveComponentType,
1629
+ r as resolveComponentType,
1991
1630
  shouldTrackAnalytics,
1992
1631
  trackComponentClick,
1993
1632
  trackComponentEmbedLoad,