@focus-reactive/payload-plugin-seo 1.6.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/README.md +12 -1
  2. package/dist/admin.css +232 -17
  3. package/dist/components/SeoButton/SeoButtonInner.d.ts.map +1 -1
  4. package/dist/components/SeoButton/SeoButtonInner.js +46 -16
  5. package/dist/components/SeoButton/SeoButtonInner.js.map +1 -1
  6. package/dist/components/SeoDrawer/build-analysis-input.d.ts +2 -2
  7. package/dist/components/SeoDrawer/build-analysis-input.d.ts.map +1 -1
  8. package/dist/components/SeoDrawer/build-analysis-input.js +1 -1
  9. package/dist/components/SeoDrawer/build-analysis-input.js.map +1 -1
  10. package/dist/components/SeoDrawer/buildInput.d.ts +3 -3
  11. package/dist/components/SeoDrawer/buildInput.d.ts.map +1 -1
  12. package/dist/components/SeoDrawer/buildInput.js +3 -2
  13. package/dist/components/SeoDrawer/buildInput.js.map +1 -1
  14. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssueBanner.d.ts +7 -0
  15. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssueBanner.d.ts.map +1 -0
  16. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssueBanner.js +26 -0
  17. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssueBanner.js.map +1 -0
  18. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssuePills.d.ts +8 -0
  19. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssuePills.d.ts.map +1 -0
  20. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssuePills.js +17 -0
  21. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssuePills.js.map +1 -0
  22. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.d.ts.map +1 -1
  23. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.js +2 -0
  24. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.js.map +1 -1
  25. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.d.ts +1 -0
  26. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.d.ts.map +1 -1
  27. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.js +14 -1
  28. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.js.map +1 -1
  29. package/dist/components/SeoDrawer/components/HeadingsSection/headingIssueCopy.d.ts +7 -0
  30. package/dist/components/SeoDrawer/components/HeadingsSection/headingIssueCopy.d.ts.map +1 -0
  31. package/dist/components/SeoDrawer/components/HeadingsSection/headingIssueCopy.js +26 -0
  32. package/dist/components/SeoDrawer/components/HeadingsSection/headingIssueCopy.js.map +1 -0
  33. package/dist/components/SeoDrawer/components/HeadingsSection/index.d.ts.map +1 -1
  34. package/dist/components/SeoDrawer/components/HeadingsSection/index.js +17 -6
  35. package/dist/components/SeoDrawer/components/HeadingsSection/index.js.map +1 -1
  36. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.d.ts +1 -1
  37. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.d.ts.map +1 -1
  38. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.js +10 -3
  39. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.js.map +1 -1
  40. package/dist/components/SeoDrawer/components/SerpPreview/index.d.ts +2 -1
  41. package/dist/components/SeoDrawer/components/SerpPreview/index.d.ts.map +1 -1
  42. package/dist/components/SeoDrawer/components/SerpPreview/index.js +8 -2
  43. package/dist/components/SeoDrawer/components/SerpPreview/index.js.map +1 -1
  44. package/dist/components/SeoDrawer/components/SerpPreview/variants.d.ts +6 -6
  45. package/dist/components/SeoDrawer/components/TabWrapper.d.ts +6 -0
  46. package/dist/components/SeoDrawer/components/TabWrapper.d.ts.map +1 -0
  47. package/dist/components/SeoDrawer/components/TabWrapper.js +8 -0
  48. package/dist/components/SeoDrawer/components/TabWrapper.js.map +1 -0
  49. package/dist/components/SeoDrawer/index.d.ts +11 -4
  50. package/dist/components/SeoDrawer/index.d.ts.map +1 -1
  51. package/dist/components/SeoDrawer/index.js +38 -16
  52. package/dist/components/SeoDrawer/index.js.map +1 -1
  53. package/dist/components/SeoDrawer/keyphraseState.d.ts +16 -0
  54. package/dist/components/SeoDrawer/keyphraseState.d.ts.map +1 -0
  55. package/dist/components/SeoDrawer/keyphraseState.js +73 -0
  56. package/dist/components/SeoDrawer/keyphraseState.js.map +1 -0
  57. package/dist/components/SeoDrawer/keyphraseStorage.d.ts +5 -0
  58. package/dist/components/SeoDrawer/keyphraseStorage.d.ts.map +1 -0
  59. package/dist/components/SeoDrawer/keyphraseStorage.js +60 -0
  60. package/dist/components/SeoDrawer/keyphraseStorage.js.map +1 -0
  61. package/dist/components/SeoDrawer/tabs/InclusiveTab.d.ts.map +1 -1
  62. package/dist/components/SeoDrawer/tabs/InclusiveTab.js +3 -2
  63. package/dist/components/SeoDrawer/tabs/InclusiveTab.js.map +1 -1
  64. package/dist/components/SeoDrawer/tabs/KeyphraseTab.d.ts +14 -7
  65. package/dist/components/SeoDrawer/tabs/KeyphraseTab.d.ts.map +1 -1
  66. package/dist/components/SeoDrawer/tabs/KeyphraseTab.js +110 -69
  67. package/dist/components/SeoDrawer/tabs/KeyphraseTab.js.map +1 -1
  68. package/dist/components/SeoDrawer/tabs/OnPageTab.d.ts.map +1 -1
  69. package/dist/components/SeoDrawer/tabs/OnPageTab.js +3 -2
  70. package/dist/components/SeoDrawer/tabs/OnPageTab.js.map +1 -1
  71. package/dist/components/SeoDrawer/tabs/ReadabilityTab.d.ts.map +1 -1
  72. package/dist/components/SeoDrawer/tabs/ReadabilityTab.js +3 -2
  73. package/dist/components/SeoDrawer/tabs/ReadabilityTab.js.map +1 -1
  74. package/dist/components/SeoDrawer/tabs/SerpTab.d.ts +2 -1
  75. package/dist/components/SeoDrawer/tabs/SerpTab.d.ts.map +1 -1
  76. package/dist/components/SeoDrawer/tabs/SerpTab.js +14 -3
  77. package/dist/components/SeoDrawer/tabs/SerpTab.js.map +1 -1
  78. package/dist/components/SeoDrawer/tabs/VitalsTab.d.ts.map +1 -1
  79. package/dist/components/SeoDrawer/tabs/VitalsTab.js +7 -3
  80. package/dist/components/SeoDrawer/tabs/VitalsTab.js.map +1 -1
  81. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.d.ts +21 -0
  82. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.d.ts.map +1 -0
  83. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.js +38 -0
  84. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.js.map +1 -0
  85. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.d.ts +25 -0
  86. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.d.ts.map +1 -0
  87. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.js +127 -0
  88. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.js.map +1 -0
  89. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.d.ts +12 -0
  90. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.d.ts.map +1 -0
  91. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.js +54 -0
  92. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.js.map +1 -0
  93. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.d.ts +7 -0
  94. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.d.ts.map +1 -0
  95. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.js +93 -0
  96. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.js.map +1 -0
  97. package/dist/components/SeoDrawer/tabs/keyphrase/icons.d.ts +2 -0
  98. package/dist/components/SeoDrawer/tabs/keyphrase/icons.d.ts.map +1 -0
  99. package/dist/components/SeoDrawer/tabs/keyphrase/icons.js +7 -0
  100. package/dist/components/SeoDrawer/tabs/keyphrase/icons.js.map +1 -0
  101. package/dist/components/SeoDrawer/useAnalysis.d.ts +0 -1
  102. package/dist/components/SeoDrawer/useAnalysis.d.ts.map +1 -1
  103. package/dist/components/SeoDrawer/useAnalysis.js +0 -3
  104. package/dist/components/SeoDrawer/useAnalysis.js.map +1 -1
  105. package/dist/components/SeoDrawer/useKeyphrases.d.ts +18 -0
  106. package/dist/components/SeoDrawer/useKeyphrases.d.ts.map +1 -0
  107. package/dist/components/SeoDrawer/useKeyphrases.js +78 -0
  108. package/dist/components/SeoDrawer/useKeyphrases.js.map +1 -0
  109. package/dist/components/SeoDrawer/useLiveDocument.d.ts +4 -3
  110. package/dist/components/SeoDrawer/useLiveDocument.d.ts.map +1 -1
  111. package/dist/components/SeoDrawer/useLiveDocument.js +17 -9
  112. package/dist/components/SeoDrawer/useLiveDocument.js.map +1 -1
  113. package/dist/components/SeoDrawer/variants.d.ts +1 -1
  114. package/dist/components/SeoDrawer/variants.d.ts.map +1 -1
  115. package/dist/components/SeoDrawer/variants.js +2 -1
  116. package/dist/components/SeoDrawer/variants.js.map +1 -1
  117. package/dist/constants/index.d.ts +1 -0
  118. package/dist/constants/index.d.ts.map +1 -1
  119. package/dist/constants/index.js +2 -0
  120. package/dist/constants/index.js.map +1 -1
  121. package/dist/engine/buildPaper.d.ts +2 -2
  122. package/dist/engine/buildPaper.d.ts.map +1 -1
  123. package/dist/engine/buildPaper.js +4 -2
  124. package/dist/engine/buildPaper.js.map +1 -1
  125. package/dist/engine/runAnalysis/index.d.ts.map +1 -1
  126. package/dist/engine/runAnalysis/index.js +7 -0
  127. package/dist/engine/runAnalysis/index.js.map +1 -1
  128. package/dist/engine/runAnalysis/services/derive-related.d.ts +4 -0
  129. package/dist/engine/runAnalysis/services/derive-related.d.ts.map +1 -0
  130. package/dist/engine/runAnalysis/services/derive-related.js +16 -0
  131. package/dist/engine/runAnalysis/services/derive-related.js.map +1 -0
  132. package/dist/engine/runAnalysis/services/derive-vitals/heading-issues.d.ts +8 -0
  133. package/dist/engine/runAnalysis/services/derive-vitals/heading-issues.d.ts.map +1 -0
  134. package/dist/engine/runAnalysis/services/derive-vitals/heading-issues.js +37 -0
  135. package/dist/engine/runAnalysis/services/derive-vitals/heading-issues.js.map +1 -0
  136. package/dist/engine/runAnalysis/services/derive-vitals/heading-tree.d.ts.map +1 -1
  137. package/dist/engine/runAnalysis/services/derive-vitals/heading-tree.js +7 -3
  138. package/dist/engine/runAnalysis/services/derive-vitals/heading-tree.js.map +1 -1
  139. package/dist/engine/types/analysis.d.ts +23 -0
  140. package/dist/engine/types/analysis.d.ts.map +1 -1
  141. package/dist/ui/Button.d.ts +9 -0
  142. package/dist/ui/Button.d.ts.map +1 -0
  143. package/dist/ui/Button.js +34 -0
  144. package/dist/ui/Button.js.map +1 -0
  145. package/dist/ui/ScoreRing.d.ts +17 -4
  146. package/dist/ui/ScoreRing.d.ts.map +1 -1
  147. package/dist/ui/ScoreRing.js +38 -14
  148. package/dist/ui/ScoreRing.js.map +1 -1
  149. package/package.json +1 -1
  150. package/dist/components/SeoDrawer/keyphrasePending.d.ts +0 -2
  151. package/dist/components/SeoDrawer/keyphrasePending.d.ts.map +0 -1
  152. package/dist/components/SeoDrawer/keyphrasePending.js +0 -9
  153. package/dist/components/SeoDrawer/keyphrasePending.js.map +0 -1
@@ -1,76 +1,117 @@
1
1
  "use client";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { Loader2 } from "lucide-react";
4
- import { useState } from "react";
5
- import { hasKeyphrase } from "../../../engine/helpers/has-keyphrase";
6
- import { TabHeader } from "../../../ui/TabHeader";
7
- import { SectionCard } from "../../../ui/SectionCard";
8
- import { Pill } from "../../../ui/Pill";
9
- import { FilterPills } from "../../../ui/FilterPills";
10
- import { CheckRow } from "../../../ui/CheckRow";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useRef } from "react";
4
+ import { KeyphraseDetail } from "./keyphrase/KeyphraseDetail";
5
+ import { KeyphraseRail } from "./keyphrase/KeyphraseRail";
6
+ function resultForEntry(entry, isFocus, result) {
7
+ if (!result)
8
+ return void 0;
9
+ if (isFocus)
10
+ return result.keyphraseText === entry.text ? result.keyphrase : void 0;
11
+ return result.relatedKeyphrases.find((r) => r.text === entry.text)?.result;
12
+ }
11
13
  function KeyphraseTab({
12
- data,
13
- keyphrase,
14
- setKeyphrase,
14
+ keyphrases,
15
+ selectedId: selectedIdProp,
16
+ onSelect,
17
+ result,
15
18
  analyzing,
16
- keyphrasePending,
17
- analyzeNow
19
+ onAddRelated,
20
+ onTextChange,
21
+ onAddSynonym,
22
+ onRemoveSynonym,
23
+ onRemove,
24
+ onSetFocus,
25
+ isDuplicate
18
26
  }) {
19
- const [filter, setFilter] = useState("all");
20
- const visible = data.checks.filter((c) => filter === "all" || c.status === filter);
21
- const passing = data.checks.filter((c) => c.status === "good").length;
22
- const keyphrasePresent = hasKeyphrase(keyphrase);
23
- const checksReady = keyphrasePresent && !keyphrasePending && !analyzing && data.checks.length > 0;
24
- return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
25
- /* @__PURE__ */ jsxs("div", { className: "flex gap-[8px]", children: [
26
- /* @__PURE__ */ jsx("label", { className: "flex-1 flex items-center gap-[8px] px-[12px] py-[9px] border border-neutral-200 rounded-rs bg-neutral-0", children: /* @__PURE__ */ jsx(
27
- "input",
28
- {
29
- type: "text",
30
- className: "border-0 outline-none flex-1 text-[13px] text-neutral-800 bg-transparent",
31
- value: keyphrase,
32
- onChange: (e) => setKeyphrase(e.target.value),
33
- placeholder: "Focus keyphrase",
34
- "aria-label": "Focus keyphrase"
35
- }
36
- ) }),
37
- /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", role: "status", children: keyphrasePending ? "Analyzing keyphrase\u2026" : "" }),
38
- /* @__PURE__ */ jsx(
39
- "button",
40
- {
41
- type: "button",
42
- className: "px-[18px] py-[9px] bg-neutral-1000 text-neutral-0 border-0 rounded-rs font-medium text-[13px] cursor-pointer disabled:opacity-50",
43
- disabled: !keyphrasePresent,
44
- onClick: () => analyzeNow(),
45
- children: analyzing ? "Analyzing\u2026" : "Analyze"
46
- }
47
- )
48
- ] }),
49
- !keyphrasePresent && /* @__PURE__ */ jsx("p", { className: "text-neutral-500 text-[13px]", children: "Enter a focus keyphrase to analyze how well your content targets it." }),
50
- keyphrasePresent && !checksReady && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[8px] text-neutral-500 text-[13px]", children: [
51
- /* @__PURE__ */ jsx(Loader2, { "aria-hidden": "true", className: "w-[14px] h-[14px] animate-spin" }),
52
- "Analyzing keyphrase\u2026"
53
- ] }),
54
- checksReady && /* @__PURE__ */ jsxs(Fragment, { children: [
55
- /* @__PURE__ */ jsx(
56
- TabHeader,
57
- {
58
- title: "Keyphrase optimization",
59
- score: data.ringScore,
60
- status: data.status,
61
- subtitle: /* @__PURE__ */ jsxs(Fragment, { children: [
62
- passing,
63
- " / ",
64
- data.checks.length,
65
- " checks passing"
66
- ] })
67
- }
68
- ),
69
- /* @__PURE__ */ jsxs(SectionCard, { title: "Checks", widget: /* @__PURE__ */ jsx(Pill, { variant: "neutral", children: data.checks.length }), children: [
70
- /* @__PURE__ */ jsx(FilterPills, { checks: data.checks, value: filter, onChange: setFilter }),
71
- visible.map((c) => /* @__PURE__ */ jsx(CheckRow, { check: c }, c.id))
72
- ] })
73
- ] })
27
+ const selectedId = selectedIdProp ?? keyphrases[0]?.id ?? null;
28
+ const selected = keyphrases.find((k) => k.id === selectedId) ?? keyphrases[0] ?? null;
29
+ const selectedIsFocus = selected ? keyphrases.indexOf(selected) === 0 : true;
30
+ const lastMetrics = useRef(/* @__PURE__ */ new Map());
31
+ const liveIds = new Set(keyphrases.map((k) => k.id));
32
+ for (const id of [...lastMetrics.current.keys()]) {
33
+ if (!liveIds.has(id))
34
+ lastMetrics.current.delete(id);
35
+ }
36
+ keyphrases.forEach((entry, index) => {
37
+ if (entry.text.trim() === "") {
38
+ lastMetrics.current.delete(entry.id);
39
+ return;
40
+ }
41
+ const fresh = resultForEntry(entry, index === 0, result);
42
+ if (fresh)
43
+ lastMetrics.current.set(entry.id, fresh);
44
+ });
45
+ const stateFor = (entry) => {
46
+ if (!entry.text.trim())
47
+ return { kind: "idle" };
48
+ const isFocus = keyphrases.indexOf(entry) === 0;
49
+ if (!isFocus && isDuplicate(entry.id, entry.text))
50
+ return { kind: "idle" };
51
+ const category = resultForEntry(entry, isFocus, result);
52
+ if (category)
53
+ return { kind: "score", score: category.ringScore, status: category.status };
54
+ return { kind: "analyzing" };
55
+ };
56
+ const detailAnalysis = () => {
57
+ if (!selected)
58
+ return { kind: "hint", message: "" };
59
+ if (isDuplicate(selected.id, selected.text)) {
60
+ return {
61
+ kind: "hint",
62
+ message: "Resolve the duplicate to analyse this keyphrase."
63
+ };
64
+ }
65
+ if (!selected.text.trim()) {
66
+ return {
67
+ kind: "hint",
68
+ message: "Start typing a keyphrase to see its analysis."
69
+ };
70
+ }
71
+ const category = resultForEntry(selected, selectedIsFocus, result);
72
+ if (category) {
73
+ return {
74
+ kind: "metrics",
75
+ result: category,
76
+ dim: analyzing
77
+ };
78
+ }
79
+ const previous = lastMetrics.current.get(selected.id);
80
+ if (previous) {
81
+ return {
82
+ kind: "metrics",
83
+ result: previous,
84
+ dim: true
85
+ };
86
+ }
87
+ return { kind: "analyzing" };
88
+ };
89
+ const analysis = detailAnalysis();
90
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-[420px]", children: [
91
+ /* @__PURE__ */ jsx(
92
+ KeyphraseRail,
93
+ {
94
+ entries: keyphrases,
95
+ onAdd: onAddRelated,
96
+ onSelect,
97
+ selectedId,
98
+ stateFor
99
+ }
100
+ ),
101
+ selected && /* @__PURE__ */ jsx(
102
+ KeyphraseDetail,
103
+ {
104
+ analysis,
105
+ duplicate: isDuplicate(selected.id, selected.text),
106
+ entry: selected,
107
+ isFocus: selectedIsFocus,
108
+ onAddSynonym: (syn) => onAddSynonym(selected.id, syn),
109
+ onRemove: () => onRemove(selected.id),
110
+ onRemoveSynonym: (index) => onRemoveSynonym(selected.id, index),
111
+ onSetFocus: () => onSetFocus(selected.id),
112
+ onTextChange: (text) => onTextChange(selected.id, text)
113
+ }
114
+ )
74
115
  ] });
75
116
  }
76
117
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/KeyphraseTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { Loader2 } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { hasKeyphrase } from \"../../../engine/helpers/has-keyphrase\";\nimport type { CategoryResult } from \"../../../engine/types/analysis\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { FilterPills } from \"../../../ui/FilterPills\";\nimport type { Filter } from \"../../../ui/FilterPills\";\nimport { CheckRow } from \"../../../ui/CheckRow\";\n\nexport interface KeyphraseTabProps {\n data: CategoryResult;\n keyphrase: string;\n setKeyphrase: (keyphrase: string) => void;\n analyzing: boolean;\n keyphrasePending: boolean;\n analyzeNow: () => void;\n}\n\nexport function KeyphraseTab({\n data,\n keyphrase,\n setKeyphrase,\n analyzing,\n keyphrasePending,\n analyzeNow,\n}: KeyphraseTabProps) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n const visible = data.checks.filter((c) => filter === \"all\" || c.status === filter);\n const passing = data.checks.filter((c) => c.status === \"good\").length;\n const keyphrasePresent = hasKeyphrase(keyphrase);\n const checksReady = keyphrasePresent && !keyphrasePending && !analyzing && data.checks.length > 0;\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n <div className=\"flex gap-[8px]\">\n <label className=\"flex-1 flex items-center gap-[8px] px-[12px] py-[9px] border border-neutral-200 rounded-rs bg-neutral-0\">\n <input\n type=\"text\"\n className=\"border-0 outline-none flex-1 text-[13px] text-neutral-800 bg-transparent\"\n value={keyphrase}\n onChange={(e) => setKeyphrase(e.target.value)}\n placeholder=\"Focus keyphrase\"\n aria-label=\"Focus keyphrase\"\n />\n </label>\n <span aria-live=\"polite\" className=\"sr-only\" role=\"status\">\n {keyphrasePending ? \"Analyzing keyphrase…\" : \"\"}\n </span>\n <button\n type=\"button\"\n className=\"px-[18px] py-[9px] bg-neutral-1000 text-neutral-0 border-0 rounded-rs font-medium text-[13px] cursor-pointer disabled:opacity-50\"\n disabled={!keyphrasePresent}\n onClick={() => analyzeNow()}\n >\n {analyzing ? \"Analyzing…\" : \"Analyze\"}\n </button>\n </div>\n\n {!keyphrasePresent && (\n <p className=\"text-neutral-500 text-[13px]\">\n Enter a focus keyphrase to analyze how well your content targets it.\n </p>\n )}\n\n {keyphrasePresent && !checksReady && (\n <div className=\"flex items-center gap-[8px] text-neutral-500 text-[13px]\">\n <Loader2 aria-hidden=\"true\" className=\"w-[14px] h-[14px] animate-spin\" />\n Analyzing keyphrase…\n </div>\n )}\n\n {checksReady && (\n <>\n <TabHeader\n title=\"Keyphrase optimization\"\n score={data.ringScore}\n status={data.status}\n subtitle={\n <>\n {passing} / {data.checks.length} checks passing\n </>\n }\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{data.checks.length}</Pill>}>\n <FilterPills checks={data.checks} value={filter} onChange={setFilter} />\n {visible.map((c) => (\n <CheckRow key={c.id} check={c} />\n ))}\n </SectionCard>\n </>\n )}\n </section>\n );\n}\n"],"mappings":";AAsCM,SA4CQ,UA1CJ,KAFJ;AApCN,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAE7B,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AAWlB,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAClD,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,WAAW,MAAM;AACjF,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAC/D,QAAM,mBAAmB,aAAa,SAAS;AAC/C,QAAM,cAAc,oBAAoB,CAAC,oBAAoB,CAAC,aAAa,KAAK,OAAO,SAAS;AAEhG,SACE,qBAAC,aAAQ,WAAU,4BACjB;AAAA,yBAAC,SAAI,WAAU,kBACb;AAAA,0BAAC,WAAM,WAAU,2GACf;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK;AAAA,UAC5C,aAAY;AAAA,UACZ,cAAW;AAAA;AAAA,MACb,GACF;AAAA,MACA,oBAAC,UAAK,aAAU,UAAS,WAAU,WAAU,MAAK,UAC/C,6BAAmB,8BAAyB,IAC/C;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,UAAU,CAAC;AAAA,UACX,SAAS,MAAM,WAAW;AAAA,UAEzB,sBAAY,oBAAe;AAAA;AAAA,MAC9B;AAAA,OACF;AAAA,IAEC,CAAC,oBACA,oBAAC,OAAE,WAAU,gCAA+B,kFAE5C;AAAA,IAGD,oBAAoB,CAAC,eACpB,qBAAC,SAAI,WAAU,4DACb;AAAA,0BAAC,WAAQ,eAAY,QAAO,WAAU,kCAAiC;AAAA,MAAE;AAAA,OAE3E;AAAA,IAGD,eACC,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb,UACE,iCACG;AAAA;AAAA,YAAQ;AAAA,YAAI,KAAK,OAAO;AAAA,YAAO;AAAA,aAClC;AAAA;AAAA,MAEJ;AAAA,MAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAO,QAAO,GAC9E;AAAA,4BAAC,eAAY,QAAQ,KAAK,QAAQ,OAAO,QAAQ,UAAU,WAAW;AAAA,QACrE,QAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAChC;AAAA,SACH;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/KeyphraseTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useRef } from \"react\";\nimport type { AnalysisResult, CategoryResult } from \"../../../engine/types/analysis\";\nimport type { KeyphraseEntry } from \"../keyphraseState\";\nimport type { CardState } from \"./keyphrase/KeyphraseCard\";\nimport { KeyphraseDetail } from \"./keyphrase/KeyphraseDetail\";\nimport type { DetailAnalysis } from \"./keyphrase/KeyphraseDetail\";\nimport { KeyphraseRail } from \"./keyphrase/KeyphraseRail\";\n\nexport interface KeyphraseTabProps {\n keyphrases: KeyphraseEntry[];\n selectedId: string | null;\n onSelect: (id: string) => void;\n result: AnalysisResult | null;\n analyzing: boolean;\n onAddRelated: () => void;\n onTextChange: (id: string, text: string) => void;\n onAddSynonym: (id: string, syn: string) => void;\n onRemoveSynonym: (id: string, index: number) => void;\n onRemove: (id: string) => void;\n onSetFocus: (id: string) => void;\n isDuplicate: (id: string, text: string) => boolean;\n}\n\nfunction resultForEntry(\n entry: KeyphraseEntry,\n isFocus: boolean,\n result: AnalysisResult | null\n): CategoryResult | undefined {\n if (!result) return undefined;\n if (isFocus) return result.keyphraseText === entry.text ? result.keyphrase : undefined;\n return result.relatedKeyphrases.find((r) => r.text === entry.text)?.result;\n}\n\nexport function KeyphraseTab({\n keyphrases,\n selectedId: selectedIdProp,\n onSelect,\n result,\n analyzing,\n onAddRelated,\n onTextChange,\n onAddSynonym,\n onRemoveSynonym,\n onRemove,\n onSetFocus,\n isDuplicate,\n}: KeyphraseTabProps) {\n const selectedId = selectedIdProp ?? keyphrases[0]?.id ?? null;\n const selected = keyphrases.find((k) => k.id === selectedId) ?? keyphrases[0] ?? null;\n const selectedIsFocus = selected ? keyphrases.indexOf(selected) === 0 : true;\n\n const lastMetrics = useRef<Map<string, CategoryResult>>(new Map());\n const liveIds = new Set(keyphrases.map((k) => k.id));\n\n for (const id of [...lastMetrics.current.keys()]) {\n if (!liveIds.has(id)) lastMetrics.current.delete(id);\n }\n\n keyphrases.forEach((entry, index) => {\n if (entry.text.trim() === \"\") {\n lastMetrics.current.delete(entry.id);\n return;\n }\n const fresh = resultForEntry(entry, index === 0, result);\n if (fresh) lastMetrics.current.set(entry.id, fresh);\n });\n\n const stateFor = (entry: KeyphraseEntry): CardState => {\n if (!entry.text.trim()) return { kind: \"idle\" };\n\n const isFocus = keyphrases.indexOf(entry) === 0;\n if (!isFocus && isDuplicate(entry.id, entry.text)) return { kind: \"idle\" };\n\n const category = resultForEntry(entry, isFocus, result);\n if (category) return { kind: \"score\", score: category.ringScore, status: category.status };\n\n return { kind: \"analyzing\" };\n };\n\n const detailAnalysis = (): DetailAnalysis => {\n if (!selected) return { kind: \"hint\", message: \"\" };\n\n if (isDuplicate(selected.id, selected.text)) {\n return {\n kind: \"hint\",\n message: \"Resolve the duplicate to analyse this keyphrase.\",\n };\n }\n\n if (!selected.text.trim()) {\n return {\n kind: \"hint\",\n message: \"Start typing a keyphrase to see its analysis.\",\n };\n }\n\n const category = resultForEntry(selected, selectedIsFocus, result);\n if (category) {\n return {\n kind: \"metrics\",\n result: category,\n dim: analyzing,\n };\n }\n\n const previous = lastMetrics.current.get(selected.id);\n if (previous) {\n return {\n kind: \"metrics\",\n result: previous,\n dim: true,\n };\n }\n\n return { kind: \"analyzing\" };\n };\n\n const analysis = detailAnalysis();\n\n return (\n <div className=\"flex min-h-[420px]\">\n <KeyphraseRail\n entries={keyphrases}\n onAdd={onAddRelated}\n onSelect={onSelect}\n selectedId={selectedId}\n stateFor={stateFor}\n />\n\n {selected && (\n <KeyphraseDetail\n analysis={analysis}\n duplicate={isDuplicate(selected.id, selected.text)}\n entry={selected}\n isFocus={selectedIsFocus}\n onAddSynonym={(syn) => onAddSynonym(selected.id, syn)}\n onRemove={() => onRemove(selected.id)}\n onRemoveSynonym={(index) => onRemoveSynonym(selected.id, index)}\n onSetFocus={() => onSetFocus(selected.id)}\n onTextChange={(text) => onTextChange(selected.id, text)}\n />\n )}\n </div>\n );\n}\n"],"mappings":";AA0HI,SACE,KADF;AAxHJ,SAAS,cAAc;AAIvB,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB;AAiB9B,SAAS,eACP,OACA,SACA,QAC4B;AAC5B,MAAI,CAAC;AAAQ,WAAO;AACpB,MAAI;AAAS,WAAO,OAAO,kBAAkB,MAAM,OAAO,OAAO,YAAY;AAC7E,SAAO,OAAO,kBAAkB,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,IAAI,GAAG;AACtE;AAEO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,aAAa,kBAAkB,WAAW,CAAC,GAAG,MAAM;AAC1D,QAAM,WAAW,WAAW,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU,KAAK,WAAW,CAAC,KAAK;AACjF,QAAM,kBAAkB,WAAW,WAAW,QAAQ,QAAQ,MAAM,IAAI;AAExE,QAAM,cAAc,OAAoC,oBAAI,IAAI,CAAC;AACjE,QAAM,UAAU,IAAI,IAAI,WAAW,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAEnD,aAAW,MAAM,CAAC,GAAG,YAAY,QAAQ,KAAK,CAAC,GAAG;AAChD,QAAI,CAAC,QAAQ,IAAI,EAAE;AAAG,kBAAY,QAAQ,OAAO,EAAE;AAAA,EACrD;AAEA,aAAW,QAAQ,CAAC,OAAO,UAAU;AACnC,QAAI,MAAM,KAAK,KAAK,MAAM,IAAI;AAC5B,kBAAY,QAAQ,OAAO,MAAM,EAAE;AACnC;AAAA,IACF;AACA,UAAM,QAAQ,eAAe,OAAO,UAAU,GAAG,MAAM;AACvD,QAAI;AAAO,kBAAY,QAAQ,IAAI,MAAM,IAAI,KAAK;AAAA,EACpD,CAAC;AAED,QAAM,WAAW,CAAC,UAAqC;AACrD,QAAI,CAAC,MAAM,KAAK,KAAK;AAAG,aAAO,EAAE,MAAM,OAAO;AAE9C,UAAM,UAAU,WAAW,QAAQ,KAAK,MAAM;AAC9C,QAAI,CAAC,WAAW,YAAY,MAAM,IAAI,MAAM,IAAI;AAAG,aAAO,EAAE,MAAM,OAAO;AAEzE,UAAM,WAAW,eAAe,OAAO,SAAS,MAAM;AACtD,QAAI;AAAU,aAAO,EAAE,MAAM,SAAS,OAAO,SAAS,WAAW,QAAQ,SAAS,OAAO;AAEzF,WAAO,EAAE,MAAM,YAAY;AAAA,EAC7B;AAEA,QAAM,iBAAiB,MAAsB;AAC3C,QAAI,CAAC;AAAU,aAAO,EAAE,MAAM,QAAQ,SAAS,GAAG;AAElD,QAAI,YAAY,SAAS,IAAI,SAAS,IAAI,GAAG;AAC3C,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,KAAK,KAAK,GAAG;AACzB,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF;AAEA,UAAM,WAAW,eAAe,UAAU,iBAAiB,MAAM;AACjE,QAAI,UAAU;AACZ,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,KAAK;AAAA,MACP;AAAA,IACF;AAEA,UAAM,WAAW,YAAY,QAAQ,IAAI,SAAS,EAAE;AACpD,QAAI,UAAU;AACZ,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,KAAK;AAAA,MACP;AAAA,IACF;AAEA,WAAO,EAAE,MAAM,YAAY;AAAA,EAC7B;AAEA,QAAM,WAAW,eAAe;AAEhC,SACE,qBAAC,SAAI,WAAU,sBACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAEC,YACC;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW,YAAY,SAAS,IAAI,SAAS,IAAI;AAAA,QACjD,OAAO;AAAA,QACP,SAAS;AAAA,QACT,cAAc,CAAC,QAAQ,aAAa,SAAS,IAAI,GAAG;AAAA,QACpD,UAAU,MAAM,SAAS,SAAS,EAAE;AAAA,QACpC,iBAAiB,CAAC,UAAU,gBAAgB,SAAS,IAAI,KAAK;AAAA,QAC9D,YAAY,MAAM,WAAW,SAAS,EAAE;AAAA,QACxC,cAAc,CAAC,SAAS,aAAa,SAAS,IAAI,IAAI;AAAA;AAAA,IACxD;AAAA,KAEJ;AAEJ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"OnPageTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/OnPageTab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAQrE,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,2CA0B3D"}
1
+ {"version":3,"file":"OnPageTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/OnPageTab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AASrE,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,2CA4B3D"}
@@ -6,11 +6,12 @@ import { SectionCard } from "../../../ui/SectionCard";
6
6
  import { Pill } from "../../../ui/Pill";
7
7
  import { FilterPills } from "../../../ui/FilterPills";
8
8
  import { CheckRow } from "../../../ui/CheckRow";
9
+ import { TabWrapper } from "../components/TabWrapper";
9
10
  function OnPageTab({ data }) {
10
11
  const [filter, setFilter] = useState("all");
11
12
  const visible = data.checks.filter((c) => filter === "all" || c.status === filter);
12
13
  const passing = data.checks.filter((c) => c.status === "good").length;
13
- return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
14
+ return /* @__PURE__ */ jsx(TabWrapper, { children: /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
14
15
  /* @__PURE__ */ jsx(
15
16
  TabHeader,
16
17
  {
@@ -29,7 +30,7 @@ function OnPageTab({ data }) {
29
30
  /* @__PURE__ */ jsx(FilterPills, { checks: data.checks, value: filter, onChange: setFilter }),
30
31
  visible.map((c) => /* @__PURE__ */ jsx(CheckRow, { check: c }, c.id))
31
32
  ] })
32
- ] });
33
+ ] }) });
33
34
  }
34
35
  export {
35
36
  OnPageTab
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/OnPageTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { CategoryResult } from \"../../../engine/types/analysis\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { FilterPills } from \"../../../ui/FilterPills\";\nimport type { Filter } from \"../../../ui/FilterPills\";\nimport { CheckRow } from \"../../../ui/CheckRow\";\n\nexport function OnPageTab({ data }: { data: CategoryResult }) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n const visible = data.checks.filter((c) => filter === \"all\" || c.status === filter);\n const passing = data.checks.filter((c) => c.status === \"good\").length;\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n <TabHeader\n title=\"On-page structure\"\n score={data.ringScore}\n status={data.status}\n subtitle={\n <>\n {passing} / {data.checks.length} checks passing\n </>\n }\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{data.checks.length}</Pill>}>\n <FilterPills checks={data.checks} value={filter} onChange={setFilter} />\n {visible.map((c) => (\n <CheckRow key={c.id} check={c} />\n ))}\n </SectionCard>\n </section>\n );\n}\n"],"mappings":";AAkBM,SAKI,UALJ,KAKI,YALJ;AAhBN,SAAS,gBAAgB;AAEzB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AAElB,SAAS,UAAU,EAAE,KAAK,GAA6B;AAC5D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAClD,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,WAAW,MAAM;AACjF,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAE/D,SACE,qBAAC,aAAQ,WAAU,4BACjB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,UACE,iCACG;AAAA;AAAA,UAAQ;AAAA,UAAI,KAAK,OAAO;AAAA,UAAO;AAAA,WAClC;AAAA;AAAA,IAEJ;AAAA,IAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAO,QAAO,GAC9E;AAAA,0BAAC,eAAY,QAAQ,KAAK,QAAQ,OAAO,QAAQ,UAAU,WAAW;AAAA,MACrE,QAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAChC;AAAA,OACH;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/OnPageTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { CategoryResult } from \"../../../engine/types/analysis\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { FilterPills } from \"../../../ui/FilterPills\";\nimport type { Filter } from \"../../../ui/FilterPills\";\nimport { CheckRow } from \"../../../ui/CheckRow\";\nimport { TabWrapper } from \"../components/TabWrapper\";\n\nexport function OnPageTab({ data }: { data: CategoryResult }) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n const visible = data.checks.filter((c) => filter === \"all\" || c.status === filter);\n const passing = data.checks.filter((c) => c.status === \"good\").length;\n\n return (\n <TabWrapper>\n <section className=\"flex flex-col gap-[13px]\">\n <TabHeader\n title=\"On-page structure\"\n score={data.ringScore}\n status={data.status}\n subtitle={\n <>\n {passing} / {data.checks.length} checks passing\n </>\n }\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{data.checks.length}</Pill>}>\n <FilterPills checks={data.checks} value={filter} onChange={setFilter} />\n {visible.map((c) => (\n <CheckRow key={c.id} check={c} />\n ))}\n </SectionCard>\n </section>\n </TabWrapper>\n );\n}\n"],"mappings":";AAoBQ,SAKI,UALJ,KAKI,YALJ;AAlBR,SAAS,gBAAgB;AAEzB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAEpB,SAAS,UAAU,EAAE,KAAK,GAA6B;AAC5D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAClD,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,WAAW,MAAM;AACjF,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAE/D,SACE,oBAAC,cACC,+BAAC,aAAQ,WAAU,4BACjB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,UACE,iCACG;AAAA;AAAA,UAAQ;AAAA,UAAI,KAAK,OAAO;AAAA,UAAO;AAAA,WAClC;AAAA;AAAA,IAEJ;AAAA,IAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAO,QAAO,GAC9E;AAAA,0BAAC,eAAY,QAAQ,KAAK,QAAQ,OAAO,QAAQ,UAAU,WAAW;AAAA,MACrE,QAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAChC;AAAA,OACH;AAAA,KACF,GACF;AAEJ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"ReadabilityTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/ReadabilityTab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAQrE,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,2CA0BhE"}
1
+ {"version":3,"file":"ReadabilityTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/ReadabilityTab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AASrE,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,2CA4BhE"}
@@ -6,11 +6,12 @@ import { SectionCard } from "../../../ui/SectionCard";
6
6
  import { Pill } from "../../../ui/Pill";
7
7
  import { FilterPills } from "../../../ui/FilterPills";
8
8
  import { CheckRow } from "../../../ui/CheckRow";
9
+ import { TabWrapper } from "../components/TabWrapper";
9
10
  function ReadabilityTab({ data }) {
10
11
  const [filter, setFilter] = useState("all");
11
12
  const visible = data.checks.filter((c) => filter === "all" || c.status === filter);
12
13
  const passing = data.checks.filter((c) => c.status === "good").length;
13
- return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
14
+ return /* @__PURE__ */ jsx(TabWrapper, { children: /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
14
15
  /* @__PURE__ */ jsx(
15
16
  TabHeader,
16
17
  {
@@ -29,7 +30,7 @@ function ReadabilityTab({ data }) {
29
30
  /* @__PURE__ */ jsx(FilterPills, { checks: data.checks, value: filter, onChange: setFilter }),
30
31
  visible.map((c) => /* @__PURE__ */ jsx(CheckRow, { check: c }, c.id))
31
32
  ] })
32
- ] });
33
+ ] }) });
33
34
  }
34
35
  export {
35
36
  ReadabilityTab
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/ReadabilityTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { CategoryResult } from \"../../../engine/types/analysis\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { FilterPills } from \"../../../ui/FilterPills\";\nimport type { Filter } from \"../../../ui/FilterPills\";\nimport { CheckRow } from \"../../../ui/CheckRow\";\n\nexport function ReadabilityTab({ data }: { data: CategoryResult }) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n const visible = data.checks.filter((c) => filter === \"all\" || c.status === filter);\n const passing = data.checks.filter((c) => c.status === \"good\").length;\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n <TabHeader\n title=\"Readability\"\n score={data.ringScore}\n status={data.status}\n subtitle={\n <>\n {passing} / {data.checks.length} checks passing\n </>\n }\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{data.checks.length}</Pill>}>\n <FilterPills checks={data.checks} value={filter} onChange={setFilter} />\n {visible.map((c) => (\n <CheckRow key={c.id} check={c} />\n ))}\n </SectionCard>\n </section>\n );\n}\n"],"mappings":";AAkBM,SAKI,UALJ,KAKI,YALJ;AAhBN,SAAS,gBAAgB;AAEzB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AAElB,SAAS,eAAe,EAAE,KAAK,GAA6B;AACjE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAClD,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,WAAW,MAAM;AACjF,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAE/D,SACE,qBAAC,aAAQ,WAAU,4BACjB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,UACE,iCACG;AAAA;AAAA,UAAQ;AAAA,UAAI,KAAK,OAAO;AAAA,UAAO;AAAA,WAClC;AAAA;AAAA,IAEJ;AAAA,IAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAO,QAAO,GAC9E;AAAA,0BAAC,eAAY,QAAQ,KAAK,QAAQ,OAAO,QAAQ,UAAU,WAAW;AAAA,MACrE,QAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAChC;AAAA,OACH;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/ReadabilityTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { CategoryResult } from \"../../../engine/types/analysis\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { FilterPills } from \"../../../ui/FilterPills\";\nimport type { Filter } from \"../../../ui/FilterPills\";\nimport { CheckRow } from \"../../../ui/CheckRow\";\nimport { TabWrapper } from \"../components/TabWrapper\";\n\nexport function ReadabilityTab({ data }: { data: CategoryResult }) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n const visible = data.checks.filter((c) => filter === \"all\" || c.status === filter);\n const passing = data.checks.filter((c) => c.status === \"good\").length;\n\n return (\n <TabWrapper>\n <section className=\"flex flex-col gap-[13px]\">\n <TabHeader\n title=\"Readability\"\n score={data.ringScore}\n status={data.status}\n subtitle={\n <>\n {passing} / {data.checks.length} checks passing\n </>\n }\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{data.checks.length}</Pill>}>\n <FilterPills checks={data.checks} value={filter} onChange={setFilter} />\n {visible.map((c) => (\n <CheckRow key={c.id} check={c} />\n ))}\n </SectionCard>\n </section>\n </TabWrapper>\n );\n}\n"],"mappings":";AAoBQ,SAKI,UALJ,KAKI,YALJ;AAlBR,SAAS,gBAAgB;AAEzB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAEpB,SAAS,eAAe,EAAE,KAAK,GAA6B;AACjE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAClD,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,WAAW,MAAM;AACjF,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAE/D,SACE,oBAAC,cACC,+BAAC,aAAQ,WAAU,4BACjB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,UACE,iCACG;AAAA;AAAA,UAAQ;AAAA,UAAI,KAAK,OAAO;AAAA,UAAO;AAAA,WAClC;AAAA;AAAA,IAEJ;AAAA,IAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAO,QAAO,GAC9E;AAAA,0BAAC,eAAY,QAAQ,KAAK,QAAQ,OAAO,QAAQ,UAAU,WAAW;AAAA,MACrE,QAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAChC;AAAA,OACH;AAAA,KACF,GACF;AAEJ;","names":[]}
@@ -1,7 +1,8 @@
1
1
  import type { SerpResult } from "../../../engine/types/analysis";
2
- export declare function SerpTab({ data, keyphrase, faviconUrl, }: {
2
+ export declare function SerpTab({ data, keyphrase, synonyms, faviconUrl, }: {
3
3
  data: SerpResult;
4
4
  keyphrase: string;
5
+ synonyms?: string[];
5
6
  faviconUrl: string;
6
7
  }): import("react/jsx-runtime").JSX.Element;
7
8
  //# sourceMappingURL=SerpTab.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SerpTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/SerpTab.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAMjE,wBAAgB,OAAO,CAAC,EACtB,IAAI,EACJ,SAAS,EACT,UAAU,GACX,EAAE;IACD,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,2CAiCA"}
1
+ {"version":3,"file":"SerpTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/SerpTab.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAOjE,wBAAgB,OAAO,CAAC,EACtB,IAAI,EACJ,SAAS,EACT,QAAa,EACb,UAAU,GACX,EAAE;IACD,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,2CAyCA"}
@@ -5,13 +5,15 @@ import { useState } from "react";
5
5
  import { SectionCard } from "../../../ui/SectionCard";
6
6
  import { SegmentedControl } from "../../../ui/SegmentedControl";
7
7
  import { SerpPreview } from "../components/SerpPreview";
8
+ import { TabWrapper } from "../components/TabWrapper";
8
9
  function SerpTab({
9
10
  data,
10
11
  keyphrase,
12
+ synonyms = [],
11
13
  faviconUrl
12
14
  }) {
13
15
  const [mode, setMode] = useState("mobile");
14
- return /* @__PURE__ */ jsx("section", { className: "flex flex-col gap-[13px]", children: /* @__PURE__ */ jsx(
16
+ return /* @__PURE__ */ jsx(TabWrapper, { children: /* @__PURE__ */ jsx("section", { className: "flex flex-col gap-[13px]", children: /* @__PURE__ */ jsx(
15
17
  SectionCard,
16
18
  {
17
19
  title: "Search result preview",
@@ -35,9 +37,18 @@ function SerpTab({
35
37
  label: "Preview device"
36
38
  }
37
39
  ),
38
- children: /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx(SerpPreview, { data, keyphrase, faviconUrl, mode }) })
40
+ children: /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx(
41
+ SerpPreview,
42
+ {
43
+ data,
44
+ keyphrase,
45
+ synonyms,
46
+ faviconUrl,
47
+ mode
48
+ }
49
+ ) })
39
50
  }
40
- ) });
51
+ ) }) });
41
52
  }
42
53
  export {
43
54
  SerpTab
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/SerpTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { Monitor, Smartphone } from \"lucide-react\";\nimport { useState } from \"react\";\n\nimport type { SerpResult } from \"../../../engine/types/analysis\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { SegmentedControl } from \"../../../ui/SegmentedControl\";\nimport { SerpPreview } from \"../components/SerpPreview\";\nimport type { SerpMode } from \"../components/SerpPreview\";\n\nexport function SerpTab({\n data,\n keyphrase,\n faviconUrl,\n}: {\n data: SerpResult;\n keyphrase: string;\n faviconUrl: string;\n}) {\n const [mode, setMode] = useState<SerpMode>(\"mobile\");\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n <SectionCard\n title=\"Search result preview\"\n widget={\n <SegmentedControl\n options={[\n {\n value: \"mobile\",\n label: \"Mobile\",\n icon: <Smartphone aria-hidden=\"true\" className=\"w-[13px] h-[13px]\" />,\n },\n {\n value: \"desktop\",\n label: \"Desktop\",\n icon: <Monitor aria-hidden=\"true\" className=\"w-[13px] h-[13px]\" />,\n },\n ]}\n value={mode}\n onChange={setMode}\n label=\"Preview device\"\n />\n }\n >\n <div className=\"p-4\">\n <SerpPreview data={data} keyphrase={keyphrase} faviconUrl={faviconUrl} mode={mode} />\n </div>\n </SectionCard>\n </section>\n );\n}\n"],"mappings":";AAgCsB;AA9BtB,SAAS,SAAS,kBAAkB;AACpC,SAAS,gBAAgB;AAGzB,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB;AACjC,SAAS,mBAAmB;AAGrB,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAmB,QAAQ;AAEnD,SACE,oBAAC,aAAQ,WAAU,4BACjB;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,QACE;AAAA,QAAC;AAAA;AAAA,UACC,SAAS;AAAA,YACP;AAAA,cACE,OAAO;AAAA,cACP,OAAO;AAAA,cACP,MAAM,oBAAC,cAAW,eAAY,QAAO,WAAU,qBAAoB;AAAA,YACrE;AAAA,YACA;AAAA,cACE,OAAO;AAAA,cACP,OAAO;AAAA,cACP,MAAM,oBAAC,WAAQ,eAAY,QAAO,WAAU,qBAAoB;AAAA,YAClE;AAAA,UACF;AAAA,UACA,OAAO;AAAA,UACP,UAAU;AAAA,UACV,OAAM;AAAA;AAAA,MACR;AAAA,MAGF,8BAAC,SAAI,WAAU,OACb,8BAAC,eAAY,MAAY,WAAsB,YAAwB,MAAY,GACrF;AAAA;AAAA,EACF,GACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/SerpTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { Monitor, Smartphone } from \"lucide-react\";\nimport { useState } from \"react\";\n\nimport type { SerpResult } from \"../../../engine/types/analysis\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { SegmentedControl } from \"../../../ui/SegmentedControl\";\nimport { SerpPreview } from \"../components/SerpPreview\";\nimport type { SerpMode } from \"../components/SerpPreview\";\nimport { TabWrapper } from \"../components/TabWrapper\";\n\nexport function SerpTab({\n data,\n keyphrase,\n synonyms = [],\n faviconUrl,\n}: {\n data: SerpResult;\n keyphrase: string;\n synonyms?: string[];\n faviconUrl: string;\n}) {\n const [mode, setMode] = useState<SerpMode>(\"mobile\");\n\n return (\n <TabWrapper>\n <section className=\"flex flex-col gap-[13px]\">\n <SectionCard\n title=\"Search result preview\"\n widget={\n <SegmentedControl\n options={[\n {\n value: \"mobile\",\n label: \"Mobile\",\n icon: <Smartphone aria-hidden=\"true\" className=\"w-[13px] h-[13px]\" />,\n },\n {\n value: \"desktop\",\n label: \"Desktop\",\n icon: <Monitor aria-hidden=\"true\" className=\"w-[13px] h-[13px]\" />,\n },\n ]}\n value={mode}\n onChange={setMode}\n label=\"Preview device\"\n />\n }\n >\n <div className=\"p-4\">\n <SerpPreview\n data={data}\n keyphrase={keyphrase}\n synonyms={synonyms}\n faviconUrl={faviconUrl}\n mode={mode}\n />\n </div>\n </SectionCard>\n </section>\n </TabWrapper>\n );\n}\n"],"mappings":";AAoCwB;AAlCxB,SAAS,SAAS,kBAAkB;AACpC,SAAS,gBAAgB;AAGzB,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB;AACjC,SAAS,mBAAmB;AAE5B,SAAS,kBAAkB;AAEpB,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA,WAAW,CAAC;AAAA,EACZ;AACF,GAKG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAmB,QAAQ;AAEnD,SACE,oBAAC,cACC,8BAAC,aAAQ,WAAU,4BACjB;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,QACE;AAAA,QAAC;AAAA;AAAA,UACC,SAAS;AAAA,YACP;AAAA,cACE,OAAO;AAAA,cACP,OAAO;AAAA,cACP,MAAM,oBAAC,cAAW,eAAY,QAAO,WAAU,qBAAoB;AAAA,YACrE;AAAA,YACA;AAAA,cACE,OAAO;AAAA,cACP,OAAO;AAAA,cACP,MAAM,oBAAC,WAAQ,eAAY,QAAO,WAAU,qBAAoB;AAAA,YAClE;AAAA,UACF;AAAA,UACA,OAAO;AAAA,UACP,UAAU;AAAA,UACV,OAAM;AAAA;AAAA,MACR;AAAA,MAGF,8BAAC,SAAI,WAAU,OACb;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF,GACF;AAAA;AAAA,EACF,GACF,GACF;AAEJ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"VitalsTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/VitalsTab.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAOnE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,kBAAkB,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,cAAc,2CA6DrE"}
1
+ {"version":3,"file":"VitalsTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/VitalsTab.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAQnE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,kBAAkB,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,cAAc,2CAkErE"}
@@ -5,10 +5,11 @@ import { KpiCard } from "../../../ui/KpiCard";
5
5
  import { SectionCard } from "../../../ui/SectionCard";
6
6
  import { Pill } from "../../../ui/Pill";
7
7
  import { HeadingsSection } from "../components/HeadingsSection";
8
+ import { TabWrapper } from "../components/TabWrapper";
8
9
  function VitalsTab({ data, onRequestKeyphrase }) {
9
10
  const max = Math.max(1, ...data.prominentWords.map((w) => w.count));
10
11
  const noKeyphraseMatch = data.prominentWords.every((w) => !w.isKeyphrase);
11
- return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
12
+ return /* @__PURE__ */ jsx(TabWrapper, { children: /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
12
13
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-[9px]", children: [
13
14
  /* @__PURE__ */ jsx(KpiCard, { label: "Words", value: data.words.toLocaleString() }),
14
15
  /* @__PURE__ */ jsx(KpiCard, { label: "Sentences", value: data.sentences }),
@@ -39,7 +40,10 @@ function VitalsTab({ data, onRequestKeyphrase }) {
39
40
  /* @__PURE__ */ jsx("div", { className: "flex-1 h-[6px] rounded-[3px] bg-neutral-100 overflow-hidden", children: /* @__PURE__ */ jsx(
40
41
  "i",
41
42
  {
42
- className: cn("block h-full", w.isKeyphrase ? "bg-neutral-1000" : "bg-neutral-400"),
43
+ className: cn(
44
+ "block h-full",
45
+ w.isKeyphrase ? "bg-neutral-1000" : "bg-neutral-400"
46
+ ),
43
47
  style: { width: `${w.count / max * 100}%` }
44
48
  }
45
49
  ) }),
@@ -59,7 +63,7 @@ function VitalsTab({ data, onRequestKeyphrase }) {
59
63
  children: "Set a focus keyphrase to see which prominent words match it"
60
64
  }
61
65
  )
62
- ] });
66
+ ] }) });
63
67
  }
64
68
  export {
65
69
  VitalsTab
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/VitalsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport type { VitalsResult } from \"../../../engine/types/analysis\";\nimport { cn, ROW_SEPARATOR } from \"../../../utils/style\";\nimport { KpiCard } from \"../../../ui/KpiCard\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { HeadingsSection } from \"../components/HeadingsSection\";\n\nexport interface VitalsTabProps {\n data: VitalsResult;\n onRequestKeyphrase: () => void;\n}\n\nexport function VitalsTab({ data, onRequestKeyphrase }: VitalsTabProps) {\n const max = Math.max(1, ...data.prominentWords.map((w) => w.count));\n const noKeyphraseMatch = data.prominentWords.every((w) => !w.isKeyphrase);\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n <div className=\"grid grid-cols-3 gap-[9px]\">\n <KpiCard label=\"Words\" value={data.words.toLocaleString()} />\n <KpiCard label=\"Sentences\" value={data.sentences} />\n <KpiCard label=\"Paragraphs\" value={data.paragraphs} />\n <KpiCard label=\"Images\" value={data.images} />\n <KpiCard label=\"Videos\" value={data.videos} />\n <KpiCard label=\"Reading time\" value={data.readingTimeMinutes} suffix=\"min\" />\n </div>\n\n <HeadingsSection data={data.headings} />\n\n <SectionCard\n title=\"Prominent words\"\n widget={<Pill variant=\"neutral\">{data.prominentWords.length}</Pill>}\n >\n {data.prominentWords.map((w) => (\n <div\n className={cn(\n \"relative flex items-center gap-[12px] px-[15px] py-[9px]\",\n ROW_SEPARATOR\n )}\n key={w.word}\n >\n <div className=\"w-[120px] flex-none text-[12px] font-medium flex items-center gap-[6px]\">\n {w.word}{\" \"}\n {w.isKeyphrase && (\n <span className=\"text-[9px] font-bold uppercase tracking-[0.04em] text-neutral-1000 bg-neutral-150 rounded-[3px] px-[5px] py-[1px]\">\n Key\n </span>\n )}\n </div>\n <div className=\"flex-1 h-[6px] rounded-[3px] bg-neutral-100 overflow-hidden\">\n <i\n className={cn(\"block h-full\", w.isKeyphrase ? \"bg-neutral-1000\" : \"bg-neutral-400\")}\n style={{ width: `${(w.count / max) * 100}%` }}\n />\n </div>\n <div className=\"w-[30px] text-right font-mono text-[11px] font-semibold text-neutral-700\">\n {w.count}\n </div>\n </div>\n ))}\n </SectionCard>\n\n {noKeyphraseMatch && (\n <button\n type=\"button\"\n onClick={onRequestKeyphrase}\n className=\"self-start text-[12px] text-neutral-600 underline underline-offset-2 hover:text-neutral-800 cursor-pointer bg-transparent border-0 p-0\"\n >\n Set a focus keyphrase to see which prominent words match it\n </button>\n )}\n </section>\n );\n}\n"],"mappings":";AAoBM,SACE,KADF;AAjBN,SAAS,IAAI,qBAAqB;AAClC,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,uBAAuB;AAOzB,SAAS,UAAU,EAAE,MAAM,mBAAmB,GAAmB;AACtE,QAAM,MAAM,KAAK,IAAI,GAAG,GAAG,KAAK,eAAe,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAClE,QAAM,mBAAmB,KAAK,eAAe,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW;AAExE,SACE,qBAAC,aAAQ,WAAU,4BACjB;AAAA,yBAAC,SAAI,WAAU,8BACb;AAAA,0BAAC,WAAQ,OAAM,SAAQ,OAAO,KAAK,MAAM,eAAe,GAAG;AAAA,MAC3D,oBAAC,WAAQ,OAAM,aAAY,OAAO,KAAK,WAAW;AAAA,MAClD,oBAAC,WAAQ,OAAM,cAAa,OAAO,KAAK,YAAY;AAAA,MACpD,oBAAC,WAAQ,OAAM,UAAS,OAAO,KAAK,QAAQ;AAAA,MAC5C,oBAAC,WAAQ,OAAM,UAAS,OAAO,KAAK,QAAQ;AAAA,MAC5C,oBAAC,WAAQ,OAAM,gBAAe,OAAO,KAAK,oBAAoB,QAAO,OAAM;AAAA,OAC7E;AAAA,IAEA,oBAAC,mBAAgB,MAAM,KAAK,UAAU;AAAA,IAEtC;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,eAAe,QAAO;AAAA,QAE3D,eAAK,eAAe,IAAI,CAAC,MACxB;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA;AAAA,YACF;AAAA,YAGA;AAAA,mCAAC,SAAI,WAAU,2EACZ;AAAA,kBAAE;AAAA,gBAAM;AAAA,gBACR,EAAE,eACD,oBAAC,UAAK,WAAU,qHAAoH,iBAEpI;AAAA,iBAEJ;AAAA,cACA,oBAAC,SAAI,WAAU,+DACb;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW,GAAG,gBAAgB,EAAE,cAAc,oBAAoB,gBAAgB;AAAA,kBAClF,OAAO,EAAE,OAAO,GAAI,EAAE,QAAQ,MAAO,GAAG,IAAI;AAAA;AAAA,cAC9C,GACF;AAAA,cACA,oBAAC,SAAI,WAAU,4EACZ,YAAE,OACL;AAAA;AAAA;AAAA,UAlBK,EAAE;AAAA,QAmBT,CACD;AAAA;AAAA,IACH;AAAA,IAEC,oBACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QACX;AAAA;AAAA,IAED;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/VitalsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport type { VitalsResult } from \"../../../engine/types/analysis\";\nimport { cn, ROW_SEPARATOR } from \"../../../utils/style\";\nimport { KpiCard } from \"../../../ui/KpiCard\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { HeadingsSection } from \"../components/HeadingsSection\";\nimport { TabWrapper } from \"../components/TabWrapper\";\n\nexport interface VitalsTabProps {\n data: VitalsResult;\n onRequestKeyphrase: () => void;\n}\n\nexport function VitalsTab({ data, onRequestKeyphrase }: VitalsTabProps) {\n const max = Math.max(1, ...data.prominentWords.map((w) => w.count));\n const noKeyphraseMatch = data.prominentWords.every((w) => !w.isKeyphrase);\n\n return (\n <TabWrapper>\n <section className=\"flex flex-col gap-[13px]\">\n <div className=\"grid grid-cols-3 gap-[9px]\">\n <KpiCard label=\"Words\" value={data.words.toLocaleString()} />\n <KpiCard label=\"Sentences\" value={data.sentences} />\n <KpiCard label=\"Paragraphs\" value={data.paragraphs} />\n <KpiCard label=\"Images\" value={data.images} />\n <KpiCard label=\"Videos\" value={data.videos} />\n <KpiCard label=\"Reading time\" value={data.readingTimeMinutes} suffix=\"min\" />\n </div>\n\n <HeadingsSection data={data.headings} />\n\n <SectionCard\n title=\"Prominent words\"\n widget={<Pill variant=\"neutral\">{data.prominentWords.length}</Pill>}\n >\n {data.prominentWords.map((w) => (\n <div\n className={cn(\n \"relative flex items-center gap-[12px] px-[15px] py-[9px]\",\n ROW_SEPARATOR\n )}\n key={w.word}\n >\n <div className=\"w-[120px] flex-none text-[12px] font-medium flex items-center gap-[6px]\">\n {w.word}{\" \"}\n {w.isKeyphrase && (\n <span className=\"text-[9px] font-bold uppercase tracking-[0.04em] text-neutral-1000 bg-neutral-150 rounded-[3px] px-[5px] py-[1px]\">\n Key\n </span>\n )}\n </div>\n <div className=\"flex-1 h-[6px] rounded-[3px] bg-neutral-100 overflow-hidden\">\n <i\n className={cn(\n \"block h-full\",\n w.isKeyphrase ? \"bg-neutral-1000\" : \"bg-neutral-400\"\n )}\n style={{ width: `${(w.count / max) * 100}%` }}\n />\n </div>\n <div className=\"w-[30px] text-right font-mono text-[11px] font-semibold text-neutral-700\">\n {w.count}\n </div>\n </div>\n ))}\n </SectionCard>\n\n {noKeyphraseMatch && (\n <button\n type=\"button\"\n onClick={onRequestKeyphrase}\n className=\"self-start text-[12px] text-neutral-600 underline underline-offset-2 hover:text-neutral-800 cursor-pointer bg-transparent border-0 p-0\"\n >\n Set a focus keyphrase to see which prominent words match it\n </button>\n )}\n </section>\n </TabWrapper>\n );\n}\n"],"mappings":";AAsBQ,SACE,KADF;AAnBR,SAAS,IAAI,qBAAqB;AAClC,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAOpB,SAAS,UAAU,EAAE,MAAM,mBAAmB,GAAmB;AACtE,QAAM,MAAM,KAAK,IAAI,GAAG,GAAG,KAAK,eAAe,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAClE,QAAM,mBAAmB,KAAK,eAAe,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW;AAExE,SACE,oBAAC,cACC,+BAAC,aAAQ,WAAU,4BACjB;AAAA,yBAAC,SAAI,WAAU,8BACb;AAAA,0BAAC,WAAQ,OAAM,SAAQ,OAAO,KAAK,MAAM,eAAe,GAAG;AAAA,MAC3D,oBAAC,WAAQ,OAAM,aAAY,OAAO,KAAK,WAAW;AAAA,MAClD,oBAAC,WAAQ,OAAM,cAAa,OAAO,KAAK,YAAY;AAAA,MACpD,oBAAC,WAAQ,OAAM,UAAS,OAAO,KAAK,QAAQ;AAAA,MAC5C,oBAAC,WAAQ,OAAM,UAAS,OAAO,KAAK,QAAQ;AAAA,MAC5C,oBAAC,WAAQ,OAAM,gBAAe,OAAO,KAAK,oBAAoB,QAAO,OAAM;AAAA,OAC7E;AAAA,IAEA,oBAAC,mBAAgB,MAAM,KAAK,UAAU;AAAA,IAEtC;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,eAAe,QAAO;AAAA,QAE3D,eAAK,eAAe,IAAI,CAAC,MACxB;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA;AAAA,YACF;AAAA,YAGA;AAAA,mCAAC,SAAI,WAAU,2EACZ;AAAA,kBAAE;AAAA,gBAAM;AAAA,gBACR,EAAE,eACD,oBAAC,UAAK,WAAU,qHAAoH,iBAEpI;AAAA,iBAEJ;AAAA,cACA,oBAAC,SAAI,WAAU,+DACb;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW;AAAA,oBACT;AAAA,oBACA,EAAE,cAAc,oBAAoB;AAAA,kBACtC;AAAA,kBACA,OAAO,EAAE,OAAO,GAAI,EAAE,QAAQ,MAAO,GAAG,IAAI;AAAA;AAAA,cAC9C,GACF;AAAA,cACA,oBAAC,SAAI,WAAU,4EACZ,YAAE,OACL;AAAA;AAAA;AAAA,UArBK,EAAE;AAAA,QAsBT,CACD;AAAA;AAAA,IACH;AAAA,IAEC,oBACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QACX;AAAA;AAAA,IAED;AAAA,KAEJ,GACF;AAEJ;","names":[]}
@@ -0,0 +1,21 @@
1
+ import type { Status } from "../../../../engine/types/analysis";
2
+ import type { KeyphraseEntry } from "../../keyphraseState";
3
+ export type CardState = {
4
+ kind: "score";
5
+ score: number;
6
+ status: Status;
7
+ } | {
8
+ kind: "analyzing";
9
+ } | {
10
+ kind: "idle";
11
+ };
12
+ interface KeyphraseCardProps {
13
+ entry: KeyphraseEntry;
14
+ isFocus: boolean;
15
+ selected: boolean;
16
+ state: CardState;
17
+ onSelect: (id: string) => void;
18
+ }
19
+ export declare function KeyphraseCard({ entry, isFocus, selected, state, onSelect }: KeyphraseCardProps): import("react/jsx-runtime").JSX.Element;
20
+ export {};
21
+ //# sourceMappingURL=KeyphraseCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeyphraseCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAGhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,UAAU,kBAAkB;IAC1B,KAAK,EAAE,cAAc,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAcD,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,kBAAkB,2CAgC9F"}
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { ScoreRing } from "../../../../ui/ScoreRing";
4
+ import { cn } from "../../../../utils/style";
5
+ function MiniRing({ state }) {
6
+ if (state.kind === "analyzing") {
7
+ return /* @__PURE__ */ jsx(ScoreRing, { size: "small", status: "loading" });
8
+ }
9
+ if (state.kind === "score") {
10
+ return /* @__PURE__ */ jsx(ScoreRing, { score: state.score, size: "small", status: state.status });
11
+ }
12
+ return /* @__PURE__ */ jsx(ScoreRing, { size: "small", status: "idle" });
13
+ }
14
+ function KeyphraseCard({ entry, isFocus, selected, state, onSelect }) {
15
+ return /* @__PURE__ */ jsxs(
16
+ "button",
17
+ {
18
+ type: "button",
19
+ "aria-pressed": selected,
20
+ onClick: () => onSelect(entry.id),
21
+ className: cn(
22
+ "flex items-center gap-[10px] p-[8px] rounded-rs w-full text-left bg-neutral-0 border",
23
+ selected ? "border-neutral-300 shadow-[0_1px_5px_rgba(30,25,20,0.09)]" : "border-transparent hover:border-neutral-150"
24
+ ),
25
+ children: [
26
+ /* @__PURE__ */ jsx(MiniRing, { state }),
27
+ /* @__PURE__ */ jsxs("span", { className: "flex-1 min-w-0 flex flex-col gap-[2px]", children: [
28
+ isFocus && /* @__PURE__ */ jsx("span", { className: "self-start text-[7px] leading-none uppercase bg-neutral-1000 text-neutral-0 px-[6px] py-[2px] rounded-[7px] tracking-[0.06em] font-bold", children: "Focus" }),
29
+ entry.text ? /* @__PURE__ */ jsx("span", { className: "text-[12px] font-semibold text-neutral-800 truncate", children: entry.text }) : /* @__PURE__ */ jsx("span", { className: "text-[12px] font-medium italic text-neutral-300 truncate", children: "New keyphrase" })
30
+ ] })
31
+ ]
32
+ }
33
+ );
34
+ }
35
+ export {
36
+ KeyphraseCard
37
+ };
38
+ //# sourceMappingURL=KeyphraseCard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.tsx"],"sourcesContent":["\"use client\";\n\nimport type { Status } from \"../../../../engine/types/analysis\";\nimport { ScoreRing } from \"../../../../ui/ScoreRing\";\nimport { cn } from \"../../../../utils/style\";\nimport type { KeyphraseEntry } from \"../../keyphraseState\";\n\nexport type CardState =\n | { kind: \"score\"; score: number; status: Status }\n | { kind: \"analyzing\" }\n | { kind: \"idle\" };\n\ninterface KeyphraseCardProps {\n entry: KeyphraseEntry;\n isFocus: boolean;\n selected: boolean;\n state: CardState;\n onSelect: (id: string) => void;\n}\n\nfunction MiniRing({ state }: { state: CardState }) {\n if (state.kind === \"analyzing\") {\n return <ScoreRing size=\"small\" status=\"loading\" />;\n }\n\n if (state.kind === \"score\") {\n return <ScoreRing score={state.score} size=\"small\" status={state.status} />;\n }\n\n return <ScoreRing size=\"small\" status=\"idle\" />;\n}\n\nexport function KeyphraseCard({ entry, isFocus, selected, state, onSelect }: KeyphraseCardProps) {\n return (\n <button\n type=\"button\"\n aria-pressed={selected}\n onClick={() => onSelect(entry.id)}\n className={cn(\n \"flex items-center gap-[10px] p-[8px] rounded-rs w-full text-left bg-neutral-0 border\",\n selected\n ? \"border-neutral-300 shadow-[0_1px_5px_rgba(30,25,20,0.09)]\"\n : \"border-transparent hover:border-neutral-150\"\n )}\n >\n <MiniRing state={state} />\n\n <span className=\"flex-1 min-w-0 flex flex-col gap-[2px]\">\n {isFocus && (\n <span className=\"self-start text-[7px] leading-none uppercase bg-neutral-1000 text-neutral-0 px-[6px] py-[2px] rounded-[7px] tracking-[0.06em] font-bold\">\n Focus\n </span>\n )}\n\n {entry.text ? (\n <span className=\"text-[12px] font-semibold text-neutral-800 truncate\">{entry.text}</span>\n ) : (\n <span className=\"text-[12px] font-medium italic text-neutral-300 truncate\">\n New keyphrase\n </span>\n )}\n </span>\n </button>\n );\n}\n"],"mappings":";AAsBW,cAyBL,YAzBK;AAnBX,SAAS,iBAAiB;AAC1B,SAAS,UAAU;AAgBnB,SAAS,SAAS,EAAE,MAAM,GAAyB;AACjD,MAAI,MAAM,SAAS,aAAa;AAC9B,WAAO,oBAAC,aAAU,MAAK,SAAQ,QAAO,WAAU;AAAA,EAClD;AAEA,MAAI,MAAM,SAAS,SAAS;AAC1B,WAAO,oBAAC,aAAU,OAAO,MAAM,OAAO,MAAK,SAAQ,QAAQ,MAAM,QAAQ;AAAA,EAC3E;AAEA,SAAO,oBAAC,aAAU,MAAK,SAAQ,QAAO,QAAO;AAC/C;AAEO,SAAS,cAAc,EAAE,OAAO,SAAS,UAAU,OAAO,SAAS,GAAuB;AAC/F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,gBAAc;AAAA,MACd,SAAS,MAAM,SAAS,MAAM,EAAE;AAAA,MAChC,WAAW;AAAA,QACT;AAAA,QACA,WACI,8DACA;AAAA,MACN;AAAA,MAEA;AAAA,4BAAC,YAAS,OAAc;AAAA,QAExB,qBAAC,UAAK,WAAU,0CACb;AAAA,qBACC,oBAAC,UAAK,WAAU,2IAA0I,mBAE1J;AAAA,UAGD,MAAM,OACL,oBAAC,UAAK,WAAU,uDAAuD,gBAAM,MAAK,IAElF,oBAAC,UAAK,WAAU,4DAA2D,2BAE3E;AAAA,WAEJ;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,25 @@
1
+ import type { CategoryResult } from "../../../../engine/types/analysis";
2
+ import type { KeyphraseEntry } from "../../keyphraseState";
3
+ export type DetailAnalysis = {
4
+ kind: "metrics";
5
+ result: CategoryResult;
6
+ dim: boolean;
7
+ } | {
8
+ kind: "analyzing";
9
+ } | {
10
+ kind: "hint";
11
+ message: string;
12
+ };
13
+ export interface KeyphraseDetailProps {
14
+ entry: KeyphraseEntry;
15
+ isFocus: boolean;
16
+ duplicate: boolean;
17
+ analysis: DetailAnalysis;
18
+ onTextChange: (text: string) => void;
19
+ onAddSynonym: (syn: string) => void;
20
+ onRemoveSynonym: (index: number) => void;
21
+ onSetFocus: () => void;
22
+ onRemove: () => void;
23
+ }
24
+ export declare function KeyphraseDetail({ entry, isFocus, duplicate, analysis, onTextChange, onAddSynonym, onRemoveSynonym, onSetFocus, onRemove, }: KeyphraseDetailProps): import("react/jsx-runtime").JSX.Element;
25
+ //# sourceMappingURL=KeyphraseDetail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeyphraseDetail.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AASxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,cAAc,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,cAAc,CAAC;IACzB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AA0DD,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,OAAO,EACP,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,UAAU,EACV,QAAQ,GACT,EAAE,oBAAoB,2CAqDtB"}