@jjlmoya/utils-developer 1.14.0 → 1.16.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 (200) hide show
  1. package/package.json +1 -1
  2. package/src/category/i18n/fr.ts +1 -1
  3. package/src/category/i18n/ru.ts +1 -1
  4. package/src/category/index.ts +2 -1
  5. package/src/entries.ts +4 -1
  6. package/src/index.ts +2 -0
  7. package/src/layouts/PreviewLayout.astro +1 -0
  8. package/src/pages/[locale]/[slug].astro +29 -12
  9. package/src/tests/diacritics_density.test.ts +118 -0
  10. package/src/tests/inverted_punctuation.test.ts +113 -0
  11. package/src/tests/locale_completeness.test.ts +2 -3
  12. package/src/tests/no_en_dash.test.ts +73 -0
  13. package/src/tests/pagespeed_best_practices.test.ts +198 -0
  14. package/src/tests/script_density.test.ts +94 -0
  15. package/src/tests/shared-test-helpers.ts +56 -0
  16. package/src/tests/tool_exports.test.ts +34 -0
  17. package/src/tests/tool_validation.test.ts +2 -2
  18. package/src/tool/aspectRatio/component.astro +3 -1
  19. package/src/tool/aspectRatio/i18n/de.ts +1 -1
  20. package/src/tool/aspectRatio/i18n/fr.ts +1 -1
  21. package/src/tool/aspectRatio/i18n/pl.ts +2 -2
  22. package/src/tool/aspectRatio/i18n/ru.ts +4 -4
  23. package/src/tool/aspectRatio/i18n/zh.ts +1 -1
  24. package/src/tool/aspectRatio/seo.astro +2 -1
  25. package/src/tool/calculadoraTiempoDatos/i18n/de.ts +1 -1
  26. package/src/tool/calculadoraTiempoDatos/i18n/pl.ts +1 -1
  27. package/src/tool/calculadoraTiempoDatos/i18n/ru.ts +5 -5
  28. package/src/tool/calculadoraTiempoDatos/i18n/sv.ts +5 -5
  29. package/src/tool/calculadoraTiempoDatos/i18n/zh.ts +1 -1
  30. package/src/tool/calculadoraTiempoDatos/seo.astro +2 -1
  31. package/src/tool/colorConverter/component.astro +3 -3
  32. package/src/tool/colorConverter/i18n/de.ts +1 -1
  33. package/src/tool/colorConverter/i18n/ru.ts +2 -2
  34. package/src/tool/colorConverter/seo.astro +2 -1
  35. package/src/tool/conversorExcelCsvHtml/i18n/es.ts +1 -1
  36. package/src/tool/conversorExcelCsvHtml/i18n/pl.ts +1 -1
  37. package/src/tool/conversorExcelCsvHtml/i18n/ru.ts +2 -2
  38. package/src/tool/conversorExcelCsvHtml/i18n/zh.ts +2 -2
  39. package/src/tool/conversorExcelCsvHtml/seo.astro +2 -1
  40. package/src/tool/cronGenerator/i18n/de.ts +4 -4
  41. package/src/tool/cronGenerator/i18n/en.ts +1 -1
  42. package/src/tool/cronGenerator/i18n/es.ts +2 -2
  43. package/src/tool/cronGenerator/i18n/fr.ts +5 -5
  44. package/src/tool/cronGenerator/i18n/id.ts +1 -1
  45. package/src/tool/cronGenerator/i18n/nl.ts +1 -1
  46. package/src/tool/cronGenerator/i18n/pl.ts +8 -8
  47. package/src/tool/cronGenerator/i18n/pt.ts +1 -1
  48. package/src/tool/cronGenerator/i18n/ru.ts +9 -9
  49. package/src/tool/cronGenerator/i18n/sv.ts +1 -1
  50. package/src/tool/cronGenerator/i18n/zh.ts +3 -3
  51. package/src/tool/cronGenerator/seo.astro +2 -1
  52. package/src/tool/cssSpecificityCalculator/i18n/de.ts +1 -1
  53. package/src/tool/cssSpecificityCalculator/i18n/fr.ts +3 -3
  54. package/src/tool/cssSpecificityCalculator/i18n/pl.ts +2 -2
  55. package/src/tool/cssSpecificityCalculator/i18n/ru.ts +2 -2
  56. package/src/tool/cssSpecificityCalculator/seo.astro +2 -1
  57. package/src/tool/cssToInlineConverter/i18n/de.ts +6 -6
  58. package/src/tool/cssToInlineConverter/i18n/es.ts +1 -1
  59. package/src/tool/cssToInlineConverter/i18n/fr.ts +3 -3
  60. package/src/tool/cssToInlineConverter/seo.astro +2 -1
  61. package/src/tool/duplicateCssRemover/i18n/de.ts +7 -7
  62. package/src/tool/duplicateCssRemover/i18n/fr.ts +1 -1
  63. package/src/tool/duplicateCssRemover/i18n/id.ts +1 -1
  64. package/src/tool/duplicateCssRemover/i18n/nl.ts +1 -1
  65. package/src/tool/duplicateCssRemover/i18n/pl.ts +1 -1
  66. package/src/tool/duplicateCssRemover/i18n/pt.ts +1 -1
  67. package/src/tool/duplicateCssRemover/i18n/ru.ts +3 -3
  68. package/src/tool/duplicateCssRemover/i18n/sv.ts +1 -1
  69. package/src/tool/duplicateCssRemover/seo.astro +2 -1
  70. package/src/tool/generadorSecurityTxt/i18n/de.ts +5 -5
  71. package/src/tool/generadorSecurityTxt/i18n/ru.ts +2 -2
  72. package/src/tool/generadorSecurityTxt/seo.astro +2 -1
  73. package/src/tool/hashGenerator/i18n/ru.ts +5 -5
  74. package/src/tool/hashGenerator/i18n/zh.ts +2 -2
  75. package/src/tool/hashGenerator/seo.astro +2 -1
  76. package/src/tool/inspectorCertificadosSsl/i18n/de.ts +6 -6
  77. package/src/tool/inspectorCertificadosSsl/i18n/ru.ts +5 -5
  78. package/src/tool/inspectorCertificadosSsl/seo.astro +2 -1
  79. package/src/tool/jsonFormatter/i18n/de.ts +4 -4
  80. package/src/tool/jsonFormatter/i18n/en.ts +4 -4
  81. package/src/tool/jsonFormatter/i18n/es.ts +4 -4
  82. package/src/tool/jsonFormatter/i18n/fr.ts +4 -4
  83. package/src/tool/jsonFormatter/i18n/id.ts +4 -4
  84. package/src/tool/jsonFormatter/i18n/it.ts +4 -4
  85. package/src/tool/jsonFormatter/i18n/ja.ts +4 -4
  86. package/src/tool/jsonFormatter/i18n/ko.ts +4 -4
  87. package/src/tool/jsonFormatter/i18n/nl.ts +4 -4
  88. package/src/tool/jsonFormatter/i18n/pl.ts +4 -4
  89. package/src/tool/jsonFormatter/i18n/pt.ts +4 -4
  90. package/src/tool/jsonFormatter/i18n/ru.ts +5 -5
  91. package/src/tool/jsonFormatter/i18n/sv.ts +4 -4
  92. package/src/tool/jsonFormatter/i18n/tr.ts +4 -4
  93. package/src/tool/jsonFormatter/i18n/zh.ts +5 -5
  94. package/src/tool/jsonFormatter/seo.astro +2 -1
  95. package/src/tool/keycode/i18n/de.ts +7 -7
  96. package/src/tool/keycode/i18n/en.ts +5 -5
  97. package/src/tool/keycode/i18n/es.ts +5 -5
  98. package/src/tool/keycode/i18n/fr.ts +7 -7
  99. package/src/tool/keycode/i18n/id.ts +5 -5
  100. package/src/tool/keycode/i18n/it.ts +5 -5
  101. package/src/tool/keycode/i18n/ja.ts +5 -5
  102. package/src/tool/keycode/i18n/ko.ts +5 -5
  103. package/src/tool/keycode/i18n/nl.ts +5 -5
  104. package/src/tool/keycode/i18n/pl.ts +7 -7
  105. package/src/tool/keycode/i18n/pt.ts +5 -5
  106. package/src/tool/keycode/i18n/ru.ts +6 -6
  107. package/src/tool/keycode/i18n/sv.ts +5 -5
  108. package/src/tool/keycode/i18n/tr.ts +5 -5
  109. package/src/tool/keycode/i18n/zh.ts +5 -5
  110. package/src/tool/keycode/seo.astro +2 -1
  111. package/src/tool/llmCostCalculator/component.astro +1 -2
  112. package/src/tool/llmCostCalculator/i18n/de.ts +4 -4
  113. package/src/tool/llmCostCalculator/i18n/en.ts +3 -3
  114. package/src/tool/llmCostCalculator/i18n/fr.ts +3 -3
  115. package/src/tool/llmCostCalculator/i18n/id.ts +3 -3
  116. package/src/tool/llmCostCalculator/i18n/pt.ts +3 -3
  117. package/src/tool/llmCostCalculator/i18n/ru.ts +3 -3
  118. package/src/tool/llmCostCalculator/i18n/sv.ts +3 -3
  119. package/src/tool/llmCostCalculator/i18n/tr.ts +3 -3
  120. package/src/tool/llmCostCalculator/i18n/zh.ts +3 -3
  121. package/src/tool/llmCostCalculator/seo.astro +2 -1
  122. package/src/tool/mobileMockupGenerator/component.astro +4 -5
  123. package/src/tool/mobileMockupGenerator/i18n/es.ts +1 -1
  124. package/src/tool/mobileMockupGenerator/seo.astro +2 -1
  125. package/src/tool/musicalTypography/i18n/de.ts +1 -1
  126. package/src/tool/musicalTypography/i18n/fr.ts +1 -1
  127. package/src/tool/musicalTypography/i18n/id.ts +1 -1
  128. package/src/tool/musicalTypography/i18n/it.ts +1 -1
  129. package/src/tool/musicalTypography/i18n/ko.ts +1 -1
  130. package/src/tool/musicalTypography/i18n/ru.ts +3 -3
  131. package/src/tool/musicalTypography/seo.astro +2 -1
  132. package/src/tool/placeholderGenerator/component.astro +2 -2
  133. package/src/tool/placeholderGenerator/i18n/de.ts +4 -4
  134. package/src/tool/placeholderGenerator/i18n/ru.ts +1 -1
  135. package/src/tool/placeholderGenerator/seo.astro +2 -1
  136. package/src/tool/promptLibrary/i18n/de.ts +1 -1
  137. package/src/tool/promptLibrary/seo.astro +2 -1
  138. package/src/tool/readabilityCalculator/component.astro +4 -4
  139. package/src/tool/readabilityCalculator/i18n/de.ts +2 -2
  140. package/src/tool/readabilityCalculator/seo.astro +2 -1
  141. package/src/tool/readabilityCalculator/visual-readability-calculator-wcag-apca.css +87 -3
  142. package/src/tool/serpPixelSimulator/bibliography.astro +7 -0
  143. package/src/tool/serpPixelSimulator/bibliography.ts +16 -0
  144. package/src/tool/serpPixelSimulator/component.astro +84 -0
  145. package/src/tool/serpPixelSimulator/controller.ts +323 -0
  146. package/src/tool/serpPixelSimulator/entry.ts +30 -0
  147. package/src/tool/serpPixelSimulator/i18n/de.ts +214 -0
  148. package/src/tool/serpPixelSimulator/i18n/en.ts +213 -0
  149. package/src/tool/serpPixelSimulator/i18n/es.ts +214 -0
  150. package/src/tool/serpPixelSimulator/i18n/fr.ts +214 -0
  151. package/src/tool/serpPixelSimulator/i18n/id.ts +214 -0
  152. package/src/tool/serpPixelSimulator/i18n/it.ts +214 -0
  153. package/src/tool/serpPixelSimulator/i18n/ja.ts +214 -0
  154. package/src/tool/serpPixelSimulator/i18n/ko.ts +214 -0
  155. package/src/tool/serpPixelSimulator/i18n/nl.ts +214 -0
  156. package/src/tool/serpPixelSimulator/i18n/pl.ts +214 -0
  157. package/src/tool/serpPixelSimulator/i18n/pt.ts +214 -0
  158. package/src/tool/serpPixelSimulator/i18n/ru.ts +214 -0
  159. package/src/tool/serpPixelSimulator/i18n/sv.ts +214 -0
  160. package/src/tool/serpPixelSimulator/i18n/tr.ts +214 -0
  161. package/src/tool/serpPixelSimulator/i18n/zh.ts +214 -0
  162. package/src/tool/serpPixelSimulator/index.ts +12 -0
  163. package/src/tool/serpPixelSimulator/logic.ts +18 -0
  164. package/src/tool/serpPixelSimulator/seo.astro +12 -0
  165. package/src/tool/serpPixelSimulator/serp-pixel-simulator.css +335 -0
  166. package/src/tool/serpPixelSimulator/ui.ts +33 -0
  167. package/src/tool/svgSanitizer/i18n/de.ts +2 -2
  168. package/src/tool/svgSanitizer/i18n/id.ts +1 -1
  169. package/src/tool/svgSanitizer/i18n/pl.ts +1 -1
  170. package/src/tool/svgSanitizer/i18n/ru.ts +7 -7
  171. package/src/tool/svgSanitizer/i18n/sv.ts +3 -3
  172. package/src/tool/svgSanitizer/i18n/tr.ts +3 -3
  173. package/src/tool/svgSanitizer/i18n/zh.ts +3 -3
  174. package/src/tool/svgSanitizer/seo.astro +2 -1
  175. package/src/tool/svgToCss/i18n/de.ts +2 -2
  176. package/src/tool/svgToCss/i18n/es.ts +1 -1
  177. package/src/tool/svgToCss/i18n/fr.ts +1 -1
  178. package/src/tool/svgToCss/i18n/pl.ts +1 -1
  179. package/src/tool/svgToCss/i18n/ru.ts +2 -2
  180. package/src/tool/svgToCss/i18n/zh.ts +2 -2
  181. package/src/tool/svgToCss/seo.astro +2 -1
  182. package/src/tool/urlCleaner/component.astro +23 -5
  183. package/src/tool/urlCleaner/i18n/de.ts +3 -3
  184. package/src/tool/urlCleaner/i18n/es.ts +1 -1
  185. package/src/tool/urlCleaner/i18n/id.ts +1 -1
  186. package/src/tool/urlCleaner/i18n/nl.ts +1 -1
  187. package/src/tool/urlCleaner/seo.astro +2 -1
  188. package/src/tool/urlCleaner/url-tracking-cleaner.css +92 -2
  189. package/src/tool/urlEncoderDecoder/i18n/de.ts +5 -5
  190. package/src/tool/urlEncoderDecoder/i18n/fr.ts +2 -2
  191. package/src/tool/urlEncoderDecoder/i18n/pl.ts +5 -5
  192. package/src/tool/urlEncoderDecoder/i18n/ru.ts +8 -8
  193. package/src/tool/urlEncoderDecoder/i18n/sv.ts +5 -5
  194. package/src/tool/urlEncoderDecoder/i18n/zh.ts +5 -5
  195. package/src/tool/urlEncoderDecoder/seo.astro +2 -1
  196. package/src/tool/utmGenerator/i18n/de.ts +2 -2
  197. package/src/tool/utmGenerator/i18n/pl.ts +5 -5
  198. package/src/tool/utmGenerator/i18n/ru.ts +6 -6
  199. package/src/tool/utmGenerator/seo.astro +2 -1
  200. package/src/tools.ts +2 -2
@@ -0,0 +1,214 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { SerpPixelSimulatorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'serp-pixel-simulator-raknare';
7
+ const title = 'SERP Simulator och SEO Pixelräknare';
8
+ const description = 'Förhandsgranska Google-liknande söksnuttar i realtid, mät bredden på titel och metabeskrivning i pixlar och se exakt var din text kommer att kapas.';
9
+
10
+ const howTo = [
11
+ {
12
+ name: 'Ange title-taggen',
13
+ text: 'Skriv eller klistra in sidtiteln du vill testa. SERP-förhandsgranskningen och pixelmätaren uppdateras vid varje tangenttryckning.',
14
+ },
15
+ {
16
+ name: 'Lägg till den synliga URL:en',
17
+ text: 'Använd en realistisk domän och sökväg så att snutten liknar resultatet en sökare skulle skanna.',
18
+ },
19
+ {
20
+ name: 'Skriv metabeskrivningen',
21
+ text: 'Lägg till beskrivningstexten och titta på pixelfältet. När den överskrider den rekommenderade visuella bredden kapar förhandsgranskningen den med ellipser.',
22
+ },
23
+ {
24
+ name: 'Växla mellan dator och mobil',
25
+ text: 'Jämför titelrenderingen med kortbredden för dator eller mobil innan du publicerar metadata.',
26
+ },
27
+ ];
28
+
29
+ const faq = [
30
+ {
31
+ question: 'Varför räkna pixlar istället för tecken för SEO-titlar?',
32
+ answer: 'Googles sökresultatkort begränsas av visuell bredd. En titel med många smala bokstäver kan rymma fler tecken än en titel med breda bokstäver, versaler eller fetstilta glyfer. Pixelmätning ger en mer exakt förhandsvisning av det synliga resultatet.',
33
+ },
34
+ {
35
+ question: 'Garanterar detta exakt hur Google kommer att kapa min snutt?',
36
+ answer: 'Nej. Google kan skriva om titellänkar och snuttar, och renderingen kan variera beroende på sökfråga, enhet, språk och experiment. Verktyget är utformat som en praktisk visuell vägledning för att skriva metadata som är mindre troligt att kapas.',
37
+ },
38
+ {
39
+ question: 'Vilka pixelgränser använder simulatorn?',
40
+ answer: 'Standardgränsen för titel på dator är 580 px, på mobil 600 px och gränsen för metabeskrivning är 920 px. Dessa är skrivmål, inte officiella Google-gränser.',
41
+ },
42
+ {
43
+ question: 'Varför lägger förhandsgranskningen till ellipser?',
44
+ answer: 'När den uppmätta texten överskrider den tillgängliga pixelbredden kapar simulatorn strängen vid det sista passande tecknet och lägger till tre punkter, vilket matchar det praktiska beteende SEO-team behöver för att upptäcka meningsförlust.',
45
+ },
46
+ ];
47
+
48
+ const ui: SerpPixelSimulatorUI = {
49
+ titleLabel: 'Title-tagg',
50
+ titlePlaceholder: 'GameBob | Oberoende Utvecklingsstudio',
51
+ urlLabel: 'Synlig URL',
52
+ urlPlaceholder: 'https://www.gamebob.dev/sv/',
53
+ descriptionLabel: 'Metabeskrivning',
54
+ descriptionPlaceholder: 'Upptäck vår samling av verktyg och spel som är utformade för att lyfta ditt digitala arbetsflöde och underhållning.',
55
+ deviceLabel: 'Förhandsgranskningsläge',
56
+ desktopLabel: 'Dator',
57
+ mobileLabel: 'Mobil',
58
+ titlePixelsLabel: 'Titelbredd',
59
+ descriptionPixelsLabel: 'Beskrivningsbredd',
60
+ charactersLabel: 'tecken',
61
+ previewLabel: 'Live-förhandsgranskning i Google-stil',
62
+ tooLongLabel: 'För bred',
63
+ goodLabel: 'Passar',
64
+ emptyTitle: 'Din titel visas här',
65
+ emptyDescription: 'Förhandsgranskningen av din metabeskrivning visas här medan du skriver.',
66
+ defaultTitle: 'GameBob | Oberoende Utvecklingsstudio',
67
+ defaultUrl: 'https://www.gamebob.dev/sv/',
68
+ defaultDescription: 'Upptäck vår samling av verktyg och spel som är utformade för att lyfta ditt digitala arbetsflöde och underhållning.',
69
+ fallbackUrl: 'exempel.se',
70
+ fallbackFaviconText: 'G',
71
+ pixelUnit: 'px',
72
+ ellipsis: '...',
73
+ fetchButtonLabel: 'Hämta',
74
+ fetchLoadingLabel: 'Hämtar...',
75
+ fetchSuccessLabel: 'Metadata laddades från URL:en.',
76
+ fetchCorsError: 'Webbläsaren kunde inte läsa denna sida. Den kan vara blockerad av CORS, en omdirigering, blandat innehåll eller en nätverksregel. Du kan fortfarande klistra in eller redigera metadata manuellt.',
77
+ fetchInvalidUrlError: 'Ange en giltig URL innan du hämtar metadata.',
78
+ fetchNoMetadataError: 'Sidan hämtades, men ingen titel eller metabeskrivning hittades.',
79
+ fetchGenericError: 'Metadata kunde inte hämtas från denna URL. Kontrollera adressen eller fyll i fälten manuellt.',
80
+ };
81
+
82
+ const faqSchema: WithContext<FAQPage> = {
83
+ '@context': 'https://schema.org',
84
+ '@type': 'FAQPage',
85
+ mainEntity: faq.map((item) => ({
86
+ '@type': 'Question',
87
+ name: item.question,
88
+ acceptedAnswer: {
89
+ '@type': 'Answer',
90
+ text: item.answer,
91
+ },
92
+ })),
93
+ };
94
+
95
+ const howToSchema: WithContext<HowTo> = {
96
+ '@context': 'https://schema.org',
97
+ '@type': 'HowTo',
98
+ name: title,
99
+ description,
100
+ step: howTo.map((step, index) => ({
101
+ '@type': 'HowToStep',
102
+ position: index + 1,
103
+ name: step.name,
104
+ text: step.text,
105
+ })),
106
+ };
107
+
108
+ const appSchema: WithContext<SoftwareApplication> = {
109
+ '@context': 'https://schema.org',
110
+ '@type': 'SoftwareApplication',
111
+ name: title,
112
+ description,
113
+ applicationCategory: 'BusinessApplication',
114
+ operatingSystem: 'Any',
115
+ offers: {
116
+ '@type': 'Offer',
117
+ price: '0',
118
+ priceCurrency: 'EUR',
119
+ },
120
+ inLanguage: 'sv',
121
+ };
122
+
123
+ export const content: ToolLocaleContent<SerpPixelSimulatorUI> = {
124
+ slug,
125
+ title,
126
+ description,
127
+ ui,
128
+ faqTitle: 'Vanliga frågor om SERP simulatorn',
129
+ faq,
130
+ bibliographyTitle: 'Dokumentation om sökresultat',
131
+ bibliography,
132
+ howTo,
133
+ schemas: [appSchema, faqSchema, howToSchema],
134
+ seo: [
135
+ {
136
+ type: 'title',
137
+ text: 'Sluta gissa hur ditt Google-resultat kommer att se ut',
138
+ level: 2,
139
+ },
140
+ {
141
+ type: 'paragraph',
142
+ html: 'En title-tagg kan se perfekt ut i ett kalkylblad och ändå misslyckas i sökresultatet. Google reserverar inte utrymme efter teckenantal; det renderar text inuti ett visuellt kort. Det betyder att <strong>GameBob | Oberoende Utvecklingsstudio</strong> och en annan titel med samma antal tecken kan uppta mycket olika bredder beroende på bokstäverna, versaler, interpunktion och mellanrum.',
143
+ },
144
+ {
145
+ type: 'tip',
146
+ title: 'Regeln som verkligen hjälper',
147
+ html: 'Skriv snutten så att det viktiga löftet överlever ellipsen. Placera sidtypen, sökintentionen och den starkaste anledningen att klicka före pixelgränsen. Varumärkesnamn är användbara, men de ska inte trycka ut huvudfördelen ur sikte.',
148
+ },
149
+ {
150
+ type: 'title',
151
+ text: 'Vad pixelräknaren mäter',
152
+ level: 3,
153
+ },
154
+ {
155
+ type: 'table',
156
+ headers: ['Element', 'Vad som spelar roll', 'Hur du använder resultatet'],
157
+ rows: [
158
+ ['Title-tagg', 'Renderad bredd i pixlar, inte antal tecken', 'Håll det primära nyckelordet och klicklöftet synliga före kapningen.'],
159
+ ['Synlig URL', 'Visuellt förtroende och ämnestydehet', 'Använd en läsbar sökväg som förstärker vart resultatet leder.'],
160
+ ['Metabeskrivning', 'Ett bredare snuttområde med frågeberoende beteende', 'Placera fördelen först eftersom Google kan förkorta eller skriva om den.'],
161
+ ['Enhetsläge', 'Dator- och mobilkort kan kännas olika', 'Kontrollera båda innan du publicerar metadata för viktiga sidor.'],
162
+ ],
163
+ },
164
+ {
165
+ type: 'title',
166
+ text: 'Varför teckengränser är en svag SEO-vana',
167
+ level: 3,
168
+ },
169
+ {
170
+ type: 'paragraph',
171
+ html: 'Traditionella råd som "håll titlar under 60 tecken" är bekväma, men döljer det verkliga problemet. Breda bokstäver som W och M, versaler, avgränsare, siffror och långa varumärkesnamn tar alla olika utrymme. Pixelmätning gör avvägningen omedelbart synlig: du kan se om en fras förtjänar sin plats eller stjäl utrymme från ett starkare budskap.',
172
+ },
173
+ {
174
+ type: 'title',
175
+ text: 'Ett praktiskt arbetsflöde för bättre snuttar',
176
+ level: 3,
177
+ },
178
+ {
179
+ type: 'list',
180
+ items: [
181
+ '<strong>Börja med intentionen:</strong> beskriv vad användaren får, inte bara vad sidan heter.',
182
+ '<strong>Testa hela titeln:</strong> klistra in den i simulatorn och titta på fältet innan publicering.',
183
+ '<strong>Ta bort svaga ord:</strong> om fältet blir rött, ta bort utfyllnadsord innan du kapar värdefulla termer.',
184
+ '<strong>Kontrollera ellipsen:</strong> om den kapade förhandsgranskningen tappar mening, skriv om titeln istället för att acceptera kapningen.',
185
+ '<strong>Upprepa för beskrivningen:</strong> se till att den första meningen bär värdeerbjudandet på egen hand.',
186
+ ],
187
+ },
188
+ {
189
+ type: 'diagnostic',
190
+ variant: 'info',
191
+ title: 'När fältet blir rött',
192
+ html: 'Ett rött fält är inte en straffvarning. Det betyder att den aktuella texten är bredare än det valda visuella målet, så simulatorn kapar den med punkter. Behandla det som en redaktionell signal: avgör om de dolda orden är onödiga eller om snutten behöver en skarpare struktur.',
193
+ },
194
+ {
195
+ type: 'title',
196
+ text: 'Begränsningar, omskrivningar och realistiska förväntningar',
197
+ level: 3,
198
+ },
199
+ {
200
+ type: 'paragraph',
201
+ html: 'Ingen simulator kan garantera den exakta snutt Google kommer att visa. Google kan skriva om titellänkar, fetstilta söktermer, välja sidtext istället för metabeskrivningen eller visa olika snuttar för olika sökningar. Detta verktyg är bäst lämpat som ett snabbt skriv- och QA-steg: det fångar uppenbar visuell överskridning innan sidan når produktion.',
202
+ },
203
+ {
204
+ type: 'summary',
205
+ title: 'Bästa användning av denna SERP simulator',
206
+ items: [
207
+ 'Använd pixelfältet för att fånga visuell överskridning innan du publicerar metadata.',
208
+ 'Håll den huvudsakliga sökintentionen och klicklöftet synliga före någon ellips.',
209
+ 'Hämta metadata från URL:er som tillåter CORS och redigera sedan resultatet manuellt vid behov.',
210
+ 'Betrakta förhandsgranskningen som en skrivguide, eftersom Google fortfarande kan skriva om snuttar per sökfråga.',
211
+ ],
212
+ },
213
+ ],
214
+ };
@@ -0,0 +1,214 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { SerpPixelSimulatorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'serp-pixel-simulatoru';
7
+ const title = 'SERP Simülatörü ve SEO Piksel Sayacı';
8
+ const description = 'Google tarzı arama snippet\'lerini gerçek zamanlı olarak önizleyin, başlık ve meta açıklamasının piksel cinsinden genişliğini ölçün ve metninizin tam olarak nerede kesileceğini görün.';
9
+
10
+ const howTo = [
11
+ {
12
+ name: 'Title etiketini girin',
13
+ text: 'Test etmek istediğiniz sayfa başlığını yazın veya yapıştırın. SERP önizlemesi ve piksel ölçer her tuş vuruşunda güncellenir.',
14
+ },
15
+ {
16
+ name: 'Görünen URL\'yi ekleyin',
17
+ text: 'Snippet\'in bir arayıcının tarayacağı sonuca benzemesi için gerçekçi bir alan adı ve yol kullanın.',
18
+ },
19
+ {
20
+ name: 'Meta açıklamasını yazın',
21
+ text: 'Açıklama metnini ekleyin ve piksel çubuğunu izleyin. Önerilen görsel genişliği aştığında, önizleme üç nokta ile keser.',
22
+ },
23
+ {
24
+ name: 'Masaüstü ve mobil arasında geçiş yapın',
25
+ text: 'Meta verilerini yayınlamadan önce başlık görüntülemesini masaüstü veya mobil kart genişliğiyle karşılaştırın.',
26
+ },
27
+ ];
28
+
29
+ const faq = [
30
+ {
31
+ question: 'SEO başlıkları için neden karakter yerine piksel sayılmalı?',
32
+ answer: 'Google arama sonucu kartları görsel genişlikle sınırlıdır. Çok sayıda dar harf içeren bir başlık, geniş harfler, büyük harfler veya kalın görünen glifler içeren bir başlıktan daha fazla karakter alabilir. Piksel ölçümü, görünen sonucun daha gerçekçi bir önizlemesini sunar.',
33
+ },
34
+ {
35
+ question: 'Bu, Google\'ın snippet\'imi tam olarak nasıl keseceğini garanti eder mi?',
36
+ answer: 'Hayır. Google başlık bağlantılarını ve snippet\'leri yeniden yazabilir ve görüntüleme sorguya, cihaza, dile ve deneylere göre değişebilir. Araç, kesilme olasılığı daha düşük meta verileri yazmak için pratik bir görsel kılavuz olarak tasarlanmıştır.',
37
+ },
38
+ {
39
+ question: 'Simülatör hangi piksel sınırlarını kullanıyor?',
40
+ answer: 'Masaüstü için varsayılan başlık sınırı 580 px, mobil için 600 px ve meta açıklama sınırı 920 px\'dir. Bunlar yazım hedefleridir, resmi Google sınırları değildir.',
41
+ },
42
+ {
43
+ question: 'Önizleme neden üç nokta ekliyor?',
44
+ answer: 'Ölçülen metin mevcut piksel genişliğini aştığında, simülatör dizeyi sığan son karakterde keser ve üç nokta ekler; bu, SEO ekiplerinin anlam kaybını tespit etmek için ihtiyaç duyduğu pratik davranışla örtüşür.',
45
+ },
46
+ ];
47
+
48
+ const ui: SerpPixelSimulatorUI = {
49
+ titleLabel: 'Title etiketi',
50
+ titlePlaceholder: 'GameBob | Bağımsız Geliştirme Stüdyosu',
51
+ urlLabel: 'Görünen URL',
52
+ urlPlaceholder: 'https://www.gamebob.dev/tr/',
53
+ descriptionLabel: 'Meta açıklaması',
54
+ descriptionPlaceholder: 'Dijital iş akışınızı ve eğlencenizi geliştirmek için tasarlanmış araç ve oyun koleksiyonumuzu keşfedin.',
55
+ deviceLabel: 'Önizleme modu',
56
+ desktopLabel: 'Masaüstü',
57
+ mobileLabel: 'Mobil',
58
+ titlePixelsLabel: 'Başlık genişliği',
59
+ descriptionPixelsLabel: 'Açıklama genişliği',
60
+ charactersLabel: 'karakter',
61
+ previewLabel: 'Canlı Google tarzı önizleme',
62
+ tooLongLabel: 'Çok geniş',
63
+ goodLabel: 'Uygun',
64
+ emptyTitle: 'Başlığınız burada görünecek',
65
+ emptyDescription: 'Meta açıklamanızın önizlemesi siz yazdıkça burada görünecek.',
66
+ defaultTitle: 'GameBob | Bağımsız Geliştirme Stüdyosu',
67
+ defaultUrl: 'https://www.gamebob.dev/tr/',
68
+ defaultDescription: 'Dijital iş akışınızı ve eğlencenizi geliştirmek için tasarlanmış araç ve oyun koleksiyonumuzu keşfedin.',
69
+ fallbackUrl: 'ornek.com.tr',
70
+ fallbackFaviconText: 'G',
71
+ pixelUnit: 'px',
72
+ ellipsis: '...',
73
+ fetchButtonLabel: 'Getir',
74
+ fetchLoadingLabel: 'Getiriliyor...',
75
+ fetchSuccessLabel: 'Meta verileri URL\'den yüklendi.',
76
+ fetchCorsError: 'Tarayıcı bu sayfayı okuyamadı. CORS, bir yönlendirme, karışık içerik veya bir ağ kuralı tarafından engellenmiş olabilir. Meta verileri yine de manuel olarak yapıştırabilir veya düzenleyebilirsiniz.',
77
+ fetchInvalidUrlError: 'Meta verileri getirmeden önce geçerli bir URL girin.',
78
+ fetchNoMetadataError: 'Sayfa getirildi, ancak başlık veya meta açıklaması bulunamadı.',
79
+ fetchGenericError: 'Bu URL\'den meta verileri getirilemedi. Adresi kontrol edin veya alanları manuel olarak doldurun.',
80
+ };
81
+
82
+ const faqSchema: WithContext<FAQPage> = {
83
+ '@context': 'https://schema.org',
84
+ '@type': 'FAQPage',
85
+ mainEntity: faq.map((item) => ({
86
+ '@type': 'Question',
87
+ name: item.question,
88
+ acceptedAnswer: {
89
+ '@type': 'Answer',
90
+ text: item.answer,
91
+ },
92
+ })),
93
+ };
94
+
95
+ const howToSchema: WithContext<HowTo> = {
96
+ '@context': 'https://schema.org',
97
+ '@type': 'HowTo',
98
+ name: title,
99
+ description,
100
+ step: howTo.map((step, index) => ({
101
+ '@type': 'HowToStep',
102
+ position: index + 1,
103
+ name: step.name,
104
+ text: step.text,
105
+ })),
106
+ };
107
+
108
+ const appSchema: WithContext<SoftwareApplication> = {
109
+ '@context': 'https://schema.org',
110
+ '@type': 'SoftwareApplication',
111
+ name: title,
112
+ description,
113
+ applicationCategory: 'BusinessApplication',
114
+ operatingSystem: 'Any',
115
+ offers: {
116
+ '@type': 'Offer',
117
+ price: '0',
118
+ priceCurrency: 'EUR',
119
+ },
120
+ inLanguage: 'tr',
121
+ };
122
+
123
+ export const content: ToolLocaleContent<SerpPixelSimulatorUI> = {
124
+ slug,
125
+ title,
126
+ description,
127
+ ui,
128
+ faqTitle: 'SERP simülatörü hakkında sık sorulan sorular',
129
+ faq,
130
+ bibliographyTitle: 'Arama sonuçları dokümantasyonu',
131
+ bibliography,
132
+ howTo,
133
+ schemas: [appSchema, faqSchema, howToSchema],
134
+ seo: [
135
+ {
136
+ type: 'title',
137
+ text: 'Google sonucunuzun nasıl görüneceğini tahmin etmeyi bırakın',
138
+ level: 2,
139
+ },
140
+ {
141
+ type: 'paragraph',
142
+ html: 'Bir title etiketi bir elektronik tabloda mükemmel görünebilir ve yine de arama sonucunda başarısız olabilir. Google, karakter sayısına göre alan ayırmaz; metni görsel bir kart içinde işler. Bu, <strong>GameBob | Bağımsız Geliştirme Stüdyosu</strong> ile aynı sayıda karaktere sahip başka bir başlığın, harflere, büyük/küçük harfe, noktalamaya ve boşluklara bağlı olarak çok farklı genişlikler kaplayabileceği anlamına gelir.',
143
+ },
144
+ {
145
+ type: 'tip',
146
+ title: 'Gerçekten işe yarayan kural',
147
+ html: 'Snippet\'i, önemli vaat üç noktadan sağ çıkacak şekilde yazın. Sayfa türünü, arama niyetini ve tıklamak için en güçlü nedeni piksel sınırından önce yerleştirin. Marka adları faydalıdır, ancak ana faydayı görüş alanından çıkarmamalıdır.',
148
+ },
149
+ {
150
+ type: 'title',
151
+ text: 'Piksel sayacı neyi ölçüyor',
152
+ level: 3,
153
+ },
154
+ {
155
+ type: 'table',
156
+ headers: ['Öğe', 'Önemli olan', 'Sonucu nasıl kullanmalısınız'],
157
+ rows: [
158
+ ['Title etiketi', 'Ham karakter sayısı değil, piksel cinsinden işlenmiş genişlik', 'Birincil anahtar kelimeyi ve tıklama vaadini kesilmeden önce görünür tutun.'],
159
+ ['Görünen URL', 'Görsel güven ve konu netliği', 'Sonucun nereye yönlendirdiğini pekiştiren okunabilir bir yol kullanın.'],
160
+ ['Meta açıklaması', 'Sorguya bağlı davranışa sahip daha geniş bir snippet alanı', 'Google kısaltabileceği veya yeniden yazabileceği için faydayı öne koyun.'],
161
+ ['Cihaz modu', 'Masaüstü ve mobil kartlar farklı hissedilebilir', 'Önemli sayfalar için meta verileri yayınlamadan önce her ikisini de kontrol edin.'],
162
+ ],
163
+ },
164
+ {
165
+ type: 'title',
166
+ text: 'Karakter sınırları neden zayıf bir SEO alışkanlığıdır',
167
+ level: 3,
168
+ },
169
+ {
170
+ type: 'paragraph',
171
+ html: '"Başlıkları 60 karakterin altında tutun" gibi geleneksel tavsiyeler kullanışlıdır, ancak gerçek sorunu gizler. W ve M gibi geniş harfler, büyük harfli kelimeler, ayırıcılar, sayılar ve uzun marka adları farklı alanlar kaplar. Piksel ölçümü, ödünleşimi hemen görünür kılar: bir ifadenin yerini hak edip etmediğini veya daha güçlü bir mesajdan alan çalıp çalmadığını görebilirsiniz.',
172
+ },
173
+ {
174
+ type: 'title',
175
+ text: 'Daha iyi snippet\'ler için pratik bir iş akışı',
176
+ level: 3,
177
+ },
178
+ {
179
+ type: 'list',
180
+ items: [
181
+ '<strong>Niyetle başlayın:</strong> kullanıcının ne elde ettiğini tanımlayın, sadece sayfanın adını değil.',
182
+ '<strong>Tam başlığı test edin:</strong> simülatöre yapıştırın ve yayınlamadan önce çubuğu izleyin.',
183
+ '<strong>Zayıf kelimeleri çıkarın:</strong> çubuk kırmızıya dönerse, değerli terimleri kesmeden önce dolgu kelimelerini kaldırın.',
184
+ '<strong>Üç noktayı kontrol edin:</strong> kesilmiş önizleme anlamını kaybederse, kesmeyi kabul etmek yerine başlığı yeniden yazın.',
185
+ '<strong>Açıklama için tekrarlayın:</strong> ilk cümlenin değer önerisini tek başına taşıdığından emin olun.',
186
+ ],
187
+ },
188
+ {
189
+ type: 'diagnostic',
190
+ variant: 'info',
191
+ title: 'Çubuk kırmızıya döndüğünde',
192
+ html: 'Kırmızı çubuk bir ceza uyarısı değildir. Mevcut metnin seçilen görsel hedeften daha geniş olduğu anlamına gelir, bu yüzden simülatör onu noktalarla keser. Bunu bir editoryal sinyal olarak değerlendirin: gizli kelimelerin vazgeçilebilir olup olmadığına veya snippet\'in daha keskin bir yapıya ihtiyacı olup olmadığına karar verin.',
193
+ },
194
+ {
195
+ type: 'title',
196
+ text: 'Sınırlar, yeniden yazmalar ve gerçekçi beklentiler',
197
+ level: 3,
198
+ },
199
+ {
200
+ type: 'paragraph',
201
+ html: 'Hiçbir simülatör Google\'ın göstereceği tam snippet\'i garanti edemez. Google başlık bağlantılarını yeniden yazabilir, sorgu terimlerini kalınlaştırabilir, meta açıklaması yerine sayfa metnini seçebilir veya farklı aramalar için farklı snippet\'ler gösterebilir. Bu araç en iyi şekilde hızlı bir yazım ve QA adımı olarak kullanılır: sayfa üretime geçmeden önce bariz görsel taşmaları yakalar.',
202
+ },
203
+ {
204
+ type: 'summary',
205
+ title: 'Bu SERP simülatörünün en iyi kullanımı',
206
+ items: [
207
+ 'Meta verileri yayınlamadan önce görsel taşmaları yakalamak için piksel çubuğunu kullanın.',
208
+ 'Ana arama niyetini ve tıklama vaadini herhangi bir üç noktadan önce görünür tutun.',
209
+ 'CORS\'a izin veren URL\'lerden meta verileri getirin, ardından sonucu gerektiğinde manuel olarak düzenleyin.',
210
+ 'Önizlemeyi bir yazım kılavuzu olarak değerlendirin, çünkü Google sorgu başına snippet\'leri yeniden yazabilir.',
211
+ ],
212
+ },
213
+ ],
214
+ };
@@ -0,0 +1,214 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { SerpPixelSimulatorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'serp-pixel-simulator';
7
+ const title = 'SERP模拟器和SEO像素计数器';
8
+ const description = '实时预览Google风格的搜索结果摘要,以像素为单位测量标题和元描述的宽度,并准确查看文本将在何处被截断。';
9
+
10
+ const howTo = [
11
+ {
12
+ name: '输入标题标签',
13
+ text: '输入或粘贴您要测试的页面标题。SERP预览和像素计量器会在每次按键时更新。',
14
+ },
15
+ {
16
+ name: '添加显示网址',
17
+ text: '使用逼真的域名和路径,让摘要看起来像搜索者会扫描的结果。',
18
+ },
19
+ {
20
+ name: '编写元描述',
21
+ text: '添加描述文本并观察像素条。当超过推荐的视觉宽度时,预览会用省略号截断。',
22
+ },
23
+ {
24
+ name: '在桌面端和移动端之间切换',
25
+ text: '在发布元数据之前,将标题的渲染效果与桌面端或移动端的卡片宽度进行比较。',
26
+ },
27
+ ];
28
+
29
+ const faq = [
30
+ {
31
+ question: '为什么要用像素而不是字符数来计算SEO标题?',
32
+ answer: 'Google搜索结果卡片受视觉宽度限制。包含许多窄字母的标题比包含宽字母、大写字母或粗体字形的标题能容纳更多字符。像素测量可以更准确地预览可见结果。',
33
+ },
34
+ {
35
+ question: '这能保证Google会精确按照这种方式截断我的摘要吗?',
36
+ answer: '不能。Google可能会重写标题链接和摘要,而且渲染效果可能因查询、设备、语言和实验而异。该工具旨在作为编写不太可能被截断的元数据的实用视觉指南。',
37
+ },
38
+ {
39
+ question: '模拟器使用什么像素限制?',
40
+ answer: '桌面端默认标题限制为580像素,移动端标题限制为600像素,元描述限制为920像素。这些是编写目标,而不是Google的官方限制。',
41
+ },
42
+ {
43
+ question: '为什么预览会添加省略号?',
44
+ answer: '当测量的文本超出可用的像素宽度时,模拟器会在最后一个能容纳的字符处截断字符串,并添加三个点,这与SEO团队检测含义丢失所需的实际行为一致。',
45
+ },
46
+ ];
47
+
48
+ const ui: SerpPixelSimulatorUI = {
49
+ titleLabel: '标题标签',
50
+ titlePlaceholder: 'GameBob | 独立开发工作室',
51
+ urlLabel: '显示网址',
52
+ urlPlaceholder: 'https://www.gamebob.dev/zh/',
53
+ descriptionLabel: '元描述',
54
+ descriptionPlaceholder: '探索我们为提升您的数字工作流程和娱乐体验而设计的工具和游戏集合。',
55
+ deviceLabel: '预览模式',
56
+ desktopLabel: '桌面端',
57
+ mobileLabel: '移动端',
58
+ titlePixelsLabel: '标题宽度',
59
+ descriptionPixelsLabel: '描述宽度',
60
+ charactersLabel: '字符',
61
+ previewLabel: 'Google风格实时预览',
62
+ tooLongLabel: '过宽',
63
+ goodLabel: '适合',
64
+ emptyTitle: '您的标题将显示在此处',
65
+ emptyDescription: '您的元描述预览将在您输入时显示在此处。',
66
+ defaultTitle: 'GameBob | 独立开发工作室',
67
+ defaultUrl: 'https://www.gamebob.dev/zh/',
68
+ defaultDescription: '探索我们为提升您的数字工作流程和娱乐体验而设计的工具和游戏集合。',
69
+ fallbackUrl: 'example.cn',
70
+ fallbackFaviconText: 'G',
71
+ pixelUnit: 'px',
72
+ ellipsis: '...',
73
+ fetchButtonLabel: '获取',
74
+ fetchLoadingLabel: '获取中...',
75
+ fetchSuccessLabel: '已从URL加载元数据。',
76
+ fetchCorsError: '浏览器无法读取此页面。可能被CORS、重定向、混合内容或网络规则阻止。您仍然可以手动粘贴或编辑元数据。',
77
+ fetchInvalidUrlError: '在获取元数据之前,请输入有效的URL。',
78
+ fetchNoMetadataError: '页面已获取,但未找到标题或元描述。',
79
+ fetchGenericError: '无法从此URL获取元数据。请检查地址或手动填写字段。',
80
+ };
81
+
82
+ const faqSchema: WithContext<FAQPage> = {
83
+ '@context': 'https://schema.org',
84
+ '@type': 'FAQPage',
85
+ mainEntity: faq.map((item) => ({
86
+ '@type': 'Question',
87
+ name: item.question,
88
+ acceptedAnswer: {
89
+ '@type': 'Answer',
90
+ text: item.answer,
91
+ },
92
+ })),
93
+ };
94
+
95
+ const howToSchema: WithContext<HowTo> = {
96
+ '@context': 'https://schema.org',
97
+ '@type': 'HowTo',
98
+ name: title,
99
+ description,
100
+ step: howTo.map((step, index) => ({
101
+ '@type': 'HowToStep',
102
+ position: index + 1,
103
+ name: step.name,
104
+ text: step.text,
105
+ })),
106
+ };
107
+
108
+ const appSchema: WithContext<SoftwareApplication> = {
109
+ '@context': 'https://schema.org',
110
+ '@type': 'SoftwareApplication',
111
+ name: title,
112
+ description,
113
+ applicationCategory: 'BusinessApplication',
114
+ operatingSystem: 'Any',
115
+ offers: {
116
+ '@type': 'Offer',
117
+ price: '0',
118
+ priceCurrency: 'EUR',
119
+ },
120
+ inLanguage: 'zh',
121
+ };
122
+
123
+ export const content: ToolLocaleContent<SerpPixelSimulatorUI> = {
124
+ slug,
125
+ title,
126
+ description,
127
+ ui,
128
+ faqTitle: 'SERP模拟器常见问题',
129
+ faq,
130
+ bibliographyTitle: '搜索结果文档',
131
+ bibliography,
132
+ howTo,
133
+ schemas: [appSchema, faqSchema, howToSchema],
134
+ seo: [
135
+ {
136
+ type: 'title',
137
+ text: '不要再猜测您的Google搜索结果会是什么样子',
138
+ level: 2,
139
+ },
140
+ {
141
+ type: 'paragraph',
142
+ html: '标题标签在电子表格中看起来很完美,但在搜索结果中仍可能失败。Google不是按字符数预留空间,而是在视觉卡片内渲染文本。这意味着<strong>GameBob | 独立开发工作室</strong>和另一个字符数相同的标题,根据字母、大小写、标点符号和间距的不同,可能占据截然不同的宽度。',
143
+ },
144
+ {
145
+ type: 'tip',
146
+ title: '真正有用的规则',
147
+ html: '编写摘要时,让重要的承诺在省略号之后仍然存在。将页面类型、搜索意图和最强烈的点击理由放在像素限制之前。品牌名称有用,但不应将主要优势挤出视线。',
148
+ },
149
+ {
150
+ type: 'title',
151
+ text: '像素计数器测量什么',
152
+ level: 3,
153
+ },
154
+ {
155
+ type: 'table',
156
+ headers: ['元素', '重要因素', '如何使用结果'],
157
+ rows: [
158
+ ['标题标签', '以像素为单位的渲染宽度,而非原始字符数', '在截断之前保持主要关键词和点击承诺可见。'],
159
+ ['显示网址', '视觉信任和主题清晰度', '使用可读的路径,强化结果的去向。'],
160
+ ['元描述', '较宽的摘要区域,具有查询依赖行为', '将优势放在前面,因为Google可能会缩短或重写它。'],
161
+ ['设备模式', '桌面端和移动端卡片可能有不同的感觉', '在发布重要页面的元数据之前,检查两种模式。'],
162
+ ],
163
+ },
164
+ {
165
+ type: 'title',
166
+ text: '为什么字符限制是薄弱的SEO习惯',
167
+ level: 3,
168
+ },
169
+ {
170
+ type: 'paragraph',
171
+ html: '像"保持标题在60个字符以下"这样的传统建议很方便,但隐藏了真正的问题。宽字母如W和M、大写单词、分隔符、数字和长品牌名称都占用不同的空间。像素测量使权衡立即可见:您可以看到一个短语是否值得占据其位置,或者是否从更强的信息中窃取了空间。',
172
+ },
173
+ {
174
+ type: 'title',
175
+ text: '制作更好摘要的实用工作流程',
176
+ level: 3,
177
+ },
178
+ {
179
+ type: 'list',
180
+ items: [
181
+ '<strong>从意图开始:</strong>描述用户得到什么,而不仅仅是页面叫什么。',
182
+ '<strong>测试完整标题:</strong>在发布前将其粘贴到模拟器中并观察像素条。',
183
+ '<strong>移除无力的词语:</strong>如果像素条变红,在剪掉有价值的术语之前删除填充词。',
184
+ '<strong>检查省略号:</strong>如果截断的预览失去意义,请重写标题而不是接受截断。',
185
+ '<strong>对描述重复操作:</strong>确保第一句话本身就承载了价值主张。',
186
+ ],
187
+ },
188
+ {
189
+ type: 'diagnostic',
190
+ variant: 'info',
191
+ title: '当像素条变红时',
192
+ html: '红色条不是惩罚警告。这意味着当前文本比所选的视觉目标宽,因此模拟器用点截断它。将其视为编辑信号:决定隐藏的词语是否可有可无,或者摘要是否需要更清晰的结构。',
193
+ },
194
+ {
195
+ type: 'title',
196
+ text: '限制、重写和现实预期',
197
+ level: 3,
198
+ },
199
+ {
200
+ type: 'paragraph',
201
+ html: '任何模拟器都无法保证Google显示的摘要完全一致。Google可能会重写标题链接,将查询词加粗,选择页面文本而不是元描述,或为不同的搜索显示不同的摘要。此工具最适合作为快速的编写和QA步骤:在页面投入生产之前,它能捕捉到明显的视觉溢出。',
202
+ },
203
+ {
204
+ type: 'summary',
205
+ title: '此SERP模拟器的最佳用途',
206
+ items: [
207
+ '在发布元数据之前,使用像素条检测视觉溢出。',
208
+ '在任何省略号之前保持主要搜索意图和点击承诺可见。',
209
+ '从允许CORS的URL获取元数据,然后在需要时手动编辑结果。',
210
+ '将预览视为编写指南,因为Google仍可能根据查询重写摘要。',
211
+ ],
212
+ },
213
+ ],
214
+ };
@@ -0,0 +1,12 @@
1
+ import type { ToolDefinition } from '../../types';
2
+ import type { ToolDefinition } from '../../types';
3
+ import { serpPixelSimulator } from './entry';
4
+
5
+ export * from './entry';
6
+
7
+ export const SERP_PIXEL_SIMULATOR_TOOL: ToolDefinition = {
8
+ entry: serpPixelSimulator,
9
+ Component: () => import('./component.astro'),
10
+ SEOComponent: () => import('./seo.astro'),
11
+ BibliographyComponent: () => import('./bibliography.astro'),
12
+ };
@@ -0,0 +1,18 @@
1
+ export const DESKTOP_TITLE_LIMIT = 580;
2
+ export const MOBILE_TITLE_LIMIT = 600;
3
+ export const DESCRIPTION_LIMIT = 920;
4
+
5
+ export type SerpDevice = 'desktop' | 'mobile';
6
+
7
+ export function getTitleLimit(device: SerpDevice): number {
8
+ return device === 'mobile' ? MOBILE_TITLE_LIMIT : DESKTOP_TITLE_LIMIT;
9
+ }
10
+
11
+ export function ratioToPercent(width: number, limit: number): number {
12
+ if (!Number.isFinite(width) || !Number.isFinite(limit) || limit <= 0) return 0;
13
+ return Math.min(100, Math.max(0, (width / limit) * 100));
14
+ }
15
+
16
+ export function needsPixelTrim(width: number, limit: number): boolean {
17
+ return width > limit;
18
+ }