@zenky/storefront-vue 0.9.46 → 0.10.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 (93) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +163 -6
  3. package/dist/cart/index.d.ts +559 -0
  4. package/dist/cart/index.js +13 -0
  5. package/dist/cart/index.js.map +1 -0
  6. package/dist/catalog/index.d.ts +73 -0
  7. package/dist/catalog/index.js +4 -0
  8. package/dist/catalog/index.js.map +1 -0
  9. package/dist/chunk-2OD7CLKD.js +102 -0
  10. package/dist/chunk-2OD7CLKD.js.map +1 -0
  11. package/dist/chunk-4MJKN7PL.js +111 -0
  12. package/dist/chunk-4MJKN7PL.js.map +1 -0
  13. package/dist/chunk-5QCUH3FF.js +15 -0
  14. package/dist/chunk-5QCUH3FF.js.map +1 -0
  15. package/dist/chunk-AL5W5JSD.js +41 -0
  16. package/dist/chunk-AL5W5JSD.js.map +1 -0
  17. package/dist/chunk-BIGTKVC6.js +38 -0
  18. package/dist/chunk-BIGTKVC6.js.map +1 -0
  19. package/dist/chunk-BWUKMWHL.js +45 -0
  20. package/dist/chunk-BWUKMWHL.js.map +1 -0
  21. package/dist/chunk-DKWDKVXQ.js +91 -0
  22. package/dist/chunk-DKWDKVXQ.js.map +1 -0
  23. package/dist/chunk-EMGQXBU3.js +3 -0
  24. package/dist/chunk-EMGQXBU3.js.map +1 -0
  25. package/dist/chunk-EYRCAUUP.js +45 -0
  26. package/dist/chunk-EYRCAUUP.js.map +1 -0
  27. package/dist/chunk-GW2QNTAD.js +327 -0
  28. package/dist/chunk-GW2QNTAD.js.map +1 -0
  29. package/dist/chunk-IGDDWGS4.js +557 -0
  30. package/dist/chunk-IGDDWGS4.js.map +1 -0
  31. package/dist/chunk-OQLU7FSR.js +1841 -0
  32. package/dist/chunk-OQLU7FSR.js.map +1 -0
  33. package/dist/chunk-PJ2LEKW6.js +229 -0
  34. package/dist/chunk-PJ2LEKW6.js.map +1 -0
  35. package/dist/chunk-PWHIDGKW.js +97 -0
  36. package/dist/chunk-PWHIDGKW.js.map +1 -0
  37. package/dist/chunk-RO25KNM5.js +36 -0
  38. package/dist/chunk-RO25KNM5.js.map +1 -0
  39. package/dist/chunk-V3VVDQIR.js +27 -0
  40. package/dist/chunk-V3VVDQIR.js.map +1 -0
  41. package/dist/chunk-VL3T36VM.js +386 -0
  42. package/dist/chunk-VL3T36VM.js.map +1 -0
  43. package/dist/chunk-XUGXLJZB.js +14 -0
  44. package/dist/chunk-XUGXLJZB.js.map +1 -0
  45. package/dist/chunk-Z3IW4ODZ.js +36 -0
  46. package/dist/chunk-Z3IW4ODZ.js.map +1 -0
  47. package/dist/index.d.ts +26 -0
  48. package/dist/index.js +21 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/layout/index.d.ts +54 -0
  51. package/dist/layout/index.js +102 -0
  52. package/dist/layout/index.js.map +1 -0
  53. package/dist/money/index.d.ts +26 -0
  54. package/dist/money/index.js +3 -0
  55. package/dist/money/index.js.map +1 -0
  56. package/dist/order/index.d.ts +34 -0
  57. package/dist/order/index.js +209 -0
  58. package/dist/order/index.js.map +1 -0
  59. package/dist/pagination/index.d.ts +87 -0
  60. package/dist/pagination/index.js +97 -0
  61. package/dist/pagination/index.js.map +1 -0
  62. package/dist/ports/index.d.ts +124 -0
  63. package/dist/ports/index.js +3 -0
  64. package/dist/ports/index.js.map +1 -0
  65. package/dist/products/index.d.ts +105 -0
  66. package/dist/products/index.js +7 -0
  67. package/dist/products/index.js.map +1 -0
  68. package/dist/promotions/index.d.ts +10 -0
  69. package/dist/promotions/index.js +7 -0
  70. package/dist/promotions/index.js.map +1 -0
  71. package/dist/schedule-Dd3zwHEb.d.ts +27 -0
  72. package/dist/seo/index.d.ts +91 -0
  73. package/dist/seo/index.js +289 -0
  74. package/dist/seo/index.js.map +1 -0
  75. package/dist/snippets/index.d.ts +17 -0
  76. package/dist/snippets/index.js +3 -0
  77. package/dist/snippets/index.js.map +1 -0
  78. package/dist/snippets-BC5bkSjm.d.ts +23 -0
  79. package/dist/stores/index.d.ts +196 -0
  80. package/dist/stores/index.js +8 -0
  81. package/dist/stores/index.js.map +1 -0
  82. package/dist/theme/index.d.ts +60 -0
  83. package/dist/theme/index.js +3 -0
  84. package/dist/theme/index.js.map +1 -0
  85. package/dist/utils/index.d.ts +66 -0
  86. package/dist/utils/index.js +4 -0
  87. package/dist/utils/index.js.map +1 -0
  88. package/dist/website-settings/index.d.ts +38 -0
  89. package/dist/website-settings/index.js +3 -0
  90. package/dist/website-settings/index.js.map +1 -0
  91. package/package.json +122 -41
  92. package/dist/esm/index.js +0 -3642
  93. package/dist/zenky.d.ts +0 -10377
@@ -0,0 +1,289 @@
1
+ import { resolveVariantPrice } from '../chunk-IGDDWGS4.js';
2
+ import '../chunk-5QCUH3FF.js';
3
+ import '../chunk-PWHIDGKW.js';
4
+ import { getSocialLinks } from '../chunk-PJ2LEKW6.js';
5
+ import '../chunk-XUGXLJZB.js';
6
+ import '../chunk-V3VVDQIR.js';
7
+ import { minorToMajorString } from '../chunk-RO25KNM5.js';
8
+ import { ContactType } from '@zenky/storefront-api';
9
+
10
+ // src/seo/headPriority.ts
11
+ var SEO_META_TAG_PRIORITY = "critical";
12
+ function withSeoMetaPriority(tag) {
13
+ return { ...tag, tagPriority: SEO_META_TAG_PRIORITY };
14
+ }
15
+
16
+ // src/seo/utils.ts
17
+ function resolveMediaUrl(media) {
18
+ return media.hd?.url || media.xlarge?.url || media.large?.url || media.medium?.url || media.placeholder?.url || null;
19
+ }
20
+ function resolveStoreLogoUrl(store) {
21
+ if (!store) return null;
22
+ if (store.square_logo) {
23
+ const url = resolveMediaUrl(store.square_logo);
24
+ if (url) return url;
25
+ }
26
+ if (store.logo) {
27
+ return resolveMediaUrl(store.logo);
28
+ }
29
+ return null;
30
+ }
31
+ function stripHtml(html) {
32
+ return html.replace(/<[^>]*>/g, "").trim();
33
+ }
34
+ function prepareOgDescription(text, maxLength = 160) {
35
+ if (!text) return null;
36
+ const plain = stripHtml(text);
37
+ if (!plain) return null;
38
+ return plain.length > maxLength ? plain.slice(0, maxLength - 3) + "..." : plain;
39
+ }
40
+
41
+ // src/seo/siteOrigin.ts
42
+ function resolveSiteOrigin(storeDomain, fallbackOrigin) {
43
+ const domain = storeDomain?.trim();
44
+ if (domain) {
45
+ const host = domain.replace(/^https?:\/\//, "").replace(/\/+$/, "");
46
+ return `https://${host}`;
47
+ }
48
+ return fallbackOrigin;
49
+ }
50
+
51
+ // src/seo/productSchema.ts
52
+ function isProductInStock(variants) {
53
+ if (!variants.length) return false;
54
+ return variants.some((variant) => {
55
+ const remainders = variant.remainders;
56
+ if (!remainders || !remainders.length) return true;
57
+ return remainders.some((remainder) => remainder.quantity > 0);
58
+ });
59
+ }
60
+ function buildProductSchema(product, currency) {
61
+ const variants = product.variants ?? [];
62
+ const firstVariant = variants[0];
63
+ let offerBlock;
64
+ if (firstVariant) {
65
+ const resolved = resolveVariantPrice(firstVariant);
66
+ offerBlock = {
67
+ "@type": "Offer",
68
+ price: minorToMajorString(resolved.price, currency),
69
+ priceCurrency: currency,
70
+ availability: isProductInStock(variants) ? "https://schema.org/InStock" : "https://schema.org/OutOfStock",
71
+ ...firstVariant.sku ? { sku: firstVariant.sku } : {}
72
+ };
73
+ }
74
+ const images = (product.images ?? []).map(resolveMediaUrl).filter((url) => !!url);
75
+ const schema = {
76
+ "@context": "https://schema.org",
77
+ "@type": "Product",
78
+ name: product.name
79
+ };
80
+ if (product.description) schema.description = product.description;
81
+ if (images.length) schema.image = images;
82
+ if (offerBlock) schema.offers = offerBlock;
83
+ return [schema];
84
+ }
85
+
86
+ // src/seo/categorySchema.ts
87
+ function buildCategorySchemas(category, products, breadcrumbs, origin) {
88
+ const schemas = [];
89
+ if (breadcrumbs.length) {
90
+ schemas.push({
91
+ "@context": "https://schema.org",
92
+ "@type": "BreadcrumbList",
93
+ itemListElement: breadcrumbs.map((crumb, index) => ({
94
+ "@type": "ListItem",
95
+ position: index + 1,
96
+ name: crumb.label,
97
+ ...crumb.to ? { item: `${origin}${crumb.to}` } : {}
98
+ }))
99
+ });
100
+ }
101
+ const items = products.slice(0, 10);
102
+ if (items.length) {
103
+ schemas.push({
104
+ "@context": "https://schema.org",
105
+ "@type": "ItemList",
106
+ name: category.name,
107
+ numberOfItems: items.length,
108
+ itemListElement: items.map((product, index) => {
109
+ const url = `${origin}/products/${product.short_id}/${product.slug || product.short_id}`;
110
+ const firstImage = product.images?.[0];
111
+ const image = firstImage ? resolveMediaUrl(firstImage) : null;
112
+ const item = {
113
+ "@type": "ListItem",
114
+ position: index + 1,
115
+ url,
116
+ name: product.name
117
+ };
118
+ if (image) item.image = image;
119
+ return item;
120
+ })
121
+ });
122
+ }
123
+ return schemas;
124
+ }
125
+
126
+ // src/seo/articleSchema.ts
127
+ function articleSchema(type, opts) {
128
+ const schema = {
129
+ "@context": "https://schema.org",
130
+ "@type": type,
131
+ headline: opts.title ?? "",
132
+ author: {
133
+ "@type": "Organization",
134
+ name: opts.storeName,
135
+ url: opts.origin
136
+ }
137
+ };
138
+ const description = prepareOgDescription(opts.intro);
139
+ if (description) schema.description = description;
140
+ const image = opts.cover ? resolveMediaUrl(opts.cover) : null;
141
+ if (image) schema.image = image;
142
+ return schema;
143
+ }
144
+ function buildOfferArticleSchema(article, storeName, origin) {
145
+ if (!article.title) return [];
146
+ return [
147
+ articleSchema("Article", {
148
+ title: article.title,
149
+ intro: article.intro,
150
+ cover: article.cover,
151
+ storeName,
152
+ origin
153
+ })
154
+ ];
155
+ }
156
+ function buildBlogPostSchema(article, storeName, origin) {
157
+ const schema = articleSchema("BlogPosting", {
158
+ title: article.title,
159
+ intro: article.intro,
160
+ cover: article.cover,
161
+ storeName,
162
+ origin
163
+ });
164
+ if (article.updated_at) {
165
+ schema.dateModified = article.updated_at;
166
+ schema.datePublished = article.updated_at;
167
+ }
168
+ return [schema];
169
+ }
170
+ var SCHEMA_DAY = {
171
+ monday: "Monday",
172
+ tuesday: "Tuesday",
173
+ wednesday: "Wednesday",
174
+ thursday: "Thursday",
175
+ friday: "Friday",
176
+ saturday: "Saturday",
177
+ sunday: "Sunday"
178
+ };
179
+ function buildOpeningHours(schedule) {
180
+ if (!schedule) return [];
181
+ const result = [];
182
+ for (const key of Object.keys(SCHEMA_DAY)) {
183
+ const day = schedule.days[key];
184
+ const match = day?.hours?.match(/(\d{1,2}:\d{2}).*?(\d{1,2}:\d{2})/);
185
+ if (!match) continue;
186
+ result.push({
187
+ "@type": "OpeningHoursSpecification",
188
+ dayOfWeek: SCHEMA_DAY[key],
189
+ opens: match[1],
190
+ closes: match[2]
191
+ });
192
+ }
193
+ return result;
194
+ }
195
+ function buildOrganizationSchema(store, origin) {
196
+ const logoUrl = store.square_logo?.medium?.url || store.square_logo?.large?.url || store.logo?.medium?.url || store.logo?.large?.url || null;
197
+ const phones = (store.phones ?? []).map((p) => p.number).filter((n) => !!n);
198
+ const email = store.links?.find((c) => c.type === ContactType.Email)?.value ?? null;
199
+ const socialLinks = getSocialLinks(store.links ?? []).map((l) => l.url);
200
+ const city = (store.cities ?? []).find((c) => c.is_default) ?? store.cities?.[0] ?? null;
201
+ const stock = city?.stocks?.find((st) => st.address) ?? city?.stocks?.[0] ?? null;
202
+ const streetAddress = stock?.address ?? null;
203
+ const geo = stock?.coordinates ?? city?.coordinates ?? null;
204
+ const openingHours = buildOpeningHours(stock?.schedule ?? city?.schedule);
205
+ const isLocalBusiness = Boolean(streetAddress || geo || openingHours.length);
206
+ const schema = {
207
+ "@context": "https://schema.org",
208
+ "@type": isLocalBusiness ? "LocalBusiness" : "Organization",
209
+ name: store.name,
210
+ url: origin
211
+ };
212
+ if (logoUrl) {
213
+ schema.logo = logoUrl;
214
+ schema.image = logoUrl;
215
+ }
216
+ if (phones.length) schema.telephone = phones.length === 1 ? phones[0] : phones;
217
+ if (email) schema.email = email;
218
+ if (socialLinks.length) schema.sameAs = socialLinks;
219
+ if (streetAddress || city?.name) {
220
+ schema.address = {
221
+ "@type": "PostalAddress",
222
+ ...streetAddress ? { streetAddress } : {},
223
+ ...city?.name ? { addressLocality: city.name } : {},
224
+ addressCountry: "RU"
225
+ };
226
+ }
227
+ if (geo) {
228
+ schema.geo = {
229
+ "@type": "GeoCoordinates",
230
+ latitude: geo.latitude,
231
+ longitude: geo.longitude
232
+ };
233
+ }
234
+ if (openingHours.length) schema.openingHoursSpecification = openingHours;
235
+ return [schema];
236
+ }
237
+
238
+ // src/seo/ogTags.ts
239
+ function buildOgTags(input) {
240
+ const tags = [];
241
+ if (input.title) {
242
+ tags.push(withSeoMetaPriority({ property: "og:title", content: input.title }));
243
+ tags.push(withSeoMetaPriority({ name: "twitter:title", content: input.title }));
244
+ }
245
+ if (input.description) {
246
+ tags.push(withSeoMetaPriority({ property: "og:description", content: input.description }));
247
+ tags.push(withSeoMetaPriority({ name: "twitter:description", content: input.description }));
248
+ }
249
+ if (input.image) {
250
+ tags.push(withSeoMetaPriority({ property: "og:image", content: input.image }));
251
+ tags.push(withSeoMetaPriority({ name: "twitter:image", content: input.image }));
252
+ }
253
+ tags.push(withSeoMetaPriority({ property: "og:type", content: input.type ?? "website" }));
254
+ if (input.url) {
255
+ tags.push(withSeoMetaPriority({ property: "og:url", content: input.url }));
256
+ }
257
+ return tags;
258
+ }
259
+
260
+ // src/seo/entitySeo.ts
261
+ function mapSeoMetaTags(tags) {
262
+ return tags.map((tag) => {
263
+ const attrs = {
264
+ [tag.name.attribute]: tag.name.value,
265
+ [tag.value.attribute]: tag.value.value
266
+ };
267
+ for (const additional of tag.additional) {
268
+ attrs[additional.attribute] = additional.value;
269
+ }
270
+ if (attrs.name === "description" || attrs.name?.startsWith("twitter:") || attrs.property?.startsWith("og:")) {
271
+ return withSeoMetaPriority(attrs);
272
+ }
273
+ return attrs;
274
+ });
275
+ }
276
+ function buildEntitySeoMeta(seo) {
277
+ const tags = [];
278
+ if (seo?.description) {
279
+ tags.push(withSeoMetaPriority({ name: "description", content: seo.description }));
280
+ }
281
+ if (seo?.meta?.length) {
282
+ tags.push(...mapSeoMetaTags(seo.meta));
283
+ }
284
+ return tags;
285
+ }
286
+
287
+ export { buildBlogPostSchema, buildCategorySchemas, buildEntitySeoMeta, buildOfferArticleSchema, buildOgTags, buildOrganizationSchema, buildProductSchema, mapSeoMetaTags, prepareOgDescription, resolveMediaUrl, resolveSiteOrigin, resolveStoreLogoUrl, stripHtml, withSeoMetaPriority };
288
+ //# sourceMappingURL=index.js.map
289
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/seo/headPriority.ts","../../src/seo/utils.ts","../../src/seo/siteOrigin.ts","../../src/seo/productSchema.ts","../../src/seo/categorySchema.ts","../../src/seo/articleSchema.ts","../../src/seo/organizationSchema.ts","../../src/seo/ogTags.ts","../../src/seo/entitySeo.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["const SEO_META_TAG_PRIORITY = 'critical'\n\ntype SeoMetaTag = Record<string, string>\n\nexport function withSeoMetaPriority<T extends SeoMetaTag>(\n tag: T\n): T & { tagPriority: typeof SEO_META_TAG_PRIORITY } {\n return { ...tag, tagPriority: SEO_META_TAG_PRIORITY }\n}\n","import type { Media, Store } from '@zenky/storefront-api'\n\n/**\n * Resolves the best available URL from a Media object.\n */\nexport function resolveMediaUrl(media: Media): string | null {\n return (\n media.hd?.url ||\n media.xlarge?.url ||\n media.large?.url ||\n media.medium?.url ||\n media.placeholder?.url ||\n null\n )\n}\n\n/**\n * Resolves store logo URL, preferring square logo over rectangular.\n * Tries all available size variants so CDN assets with only hd/xlarge are found too.\n */\nexport function resolveStoreLogoUrl(store: Store | null | undefined): string | null {\n if (!store) return null\n if (store.square_logo) {\n const url = resolveMediaUrl(store.square_logo)\n if (url) return url\n }\n if (store.logo) {\n return resolveMediaUrl(store.logo)\n }\n return null\n}\n\n/**\n * Strips HTML tags from a string.\n */\nexport function stripHtml(html: string): string {\n return html.replace(/<[^>]*>/g, '').trim()\n}\n\n/**\n * Prepares a string for use as og:description or meta description:\n * strips HTML tags and truncates to maxLength (default 160).\n */\nexport function prepareOgDescription(\n text: string | null | undefined,\n maxLength = 160\n): string | null {\n if (!text) return null\n const plain = stripHtml(text)\n if (!plain) return null\n return plain.length > maxLength ? plain.slice(0, maxLength - 3) + '...' : plain\n}\n","/**\n * Каноничный публичный origin сайта из `store.domain`. Fallback (хост запроса)\n * приносит потребитель — ядро не знает про useRequestURL.\n */\nexport function resolveSiteOrigin(\n storeDomain: string | null | undefined,\n fallbackOrigin: string\n): string {\n const domain = storeDomain?.trim()\n if (domain) {\n const host = domain.replace(/^https?:\\/\\//, '').replace(/\\/+$/, '')\n return `https://${host}`\n }\n return fallbackOrigin\n}\n","import type { Product, ProductVariant } from '@zenky/storefront-api'\n\nimport { minorToMajorString } from '../money'\nimport { resolveVariantPrice } from '../products'\nimport { resolveMediaUrl } from './utils'\n\nfunction isProductInStock(variants: ProductVariant[]): boolean {\n if (!variants.length) return false\n return variants.some((variant) => {\n const remainders = variant.remainders\n if (!remainders || !remainders.length) return true\n return remainders.some((remainder) => remainder.quantity > 0)\n })\n}\n\n/**\n * JSON-LD schema.org/Product. Google → Rich Results (цена/наличие/рейтинг в выдаче).\n * Возвращает массив (0 или 1 объект) — потребитель оборачивает в `<script ld+json>`.\n * Spec: https://schema.org/Product\n */\nexport function buildProductSchema(product: Product, currency: string): Record<string, unknown>[] {\n const variants = product.variants ?? []\n\n const firstVariant = variants[0]\n let offerBlock: Record<string, unknown> | undefined\n\n if (firstVariant) {\n const resolved = resolveVariantPrice(firstVariant)\n\n offerBlock = {\n '@type': 'Offer',\n price: minorToMajorString(resolved.price, currency),\n priceCurrency: currency,\n availability: isProductInStock(variants)\n ? 'https://schema.org/InStock'\n : 'https://schema.org/OutOfStock',\n ...(firstVariant.sku ? { sku: firstVariant.sku } : {}),\n }\n }\n\n const images = (product.images ?? [])\n .map(resolveMediaUrl)\n .filter((url): url is string => !!url)\n\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': 'Product',\n name: product.name,\n }\n\n if (product.description) schema.description = product.description\n if (images.length) schema.image = images\n if (offerBlock) schema.offers = offerBlock\n\n return [schema]\n}\n","import type { NestedCategory, Product } from '@zenky/storefront-api'\n\nimport { resolveMediaUrl } from './utils'\nimport type { BreadcrumbItem } from './types'\n\n/**\n * JSON-LD для страницы категории: BreadcrumbList (хлебные крошки) + ItemList\n * (первые 10 товаров). Возвращает массив JSON-LD объектов (0–2).\n * Spec: https://schema.org/ItemList · https://schema.org/BreadcrumbList\n */\nexport function buildCategorySchemas(\n category: NestedCategory,\n products: Product[],\n breadcrumbs: BreadcrumbItem[],\n origin: string\n): Record<string, unknown>[] {\n const schemas: Record<string, unknown>[] = []\n\n if (breadcrumbs.length) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: breadcrumbs.map((crumb, index) => ({\n '@type': 'ListItem',\n position: index + 1,\n name: crumb.label,\n ...(crumb.to ? { item: `${origin}${crumb.to}` } : {}),\n })),\n })\n }\n\n const items = products.slice(0, 10)\n if (items.length) {\n schemas.push({\n '@context': 'https://schema.org',\n '@type': 'ItemList',\n name: category.name,\n numberOfItems: items.length,\n itemListElement: items.map((product, index) => {\n const url = `${origin}/products/${product.short_id}/${product.slug || product.short_id}`\n const firstImage = product.images?.[0]\n const image = firstImage ? resolveMediaUrl(firstImage) : null\n\n const item: Record<string, unknown> = {\n '@type': 'ListItem',\n position: index + 1,\n url,\n name: product.name,\n }\n\n if (image) item.image = image\n\n return item\n }),\n })\n }\n\n return schemas\n}\n","import type { Article, Media, OfferArticle } from '@zenky/storefront-api'\n\nimport { prepareOgDescription, resolveMediaUrl } from './utils'\n\nfunction articleSchema(\n type: 'Article' | 'BlogPosting',\n opts: {\n title: string | null | undefined\n intro: string | null | undefined\n cover: Media | null | undefined\n storeName: string\n origin: string\n }\n): Record<string, unknown> {\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': type,\n headline: opts.title ?? '',\n author: {\n '@type': 'Organization',\n name: opts.storeName,\n url: opts.origin,\n },\n }\n\n const description = prepareOgDescription(opts.intro)\n if (description) schema.description = description\n\n const image = opts.cover ? resolveMediaUrl(opts.cover) : null\n if (image) schema.image = image\n\n return schema\n}\n\n/**\n * JSON-LD schema.org/Article для страницы акции (редакционный контент).\n * Spec: https://schema.org/Article\n */\nexport function buildOfferArticleSchema(\n article: OfferArticle,\n storeName: string,\n origin: string\n): Record<string, unknown>[] {\n if (!article.title) return []\n\n return [\n articleSchema('Article', {\n title: article.title,\n intro: article.intro,\n cover: article.cover,\n storeName,\n origin,\n }),\n ]\n}\n\n/**\n * JSON-LD schema.org/BlogPosting для статьи блога (Google Rich Results).\n * Spec: https://schema.org/BlogPosting\n */\nexport function buildBlogPostSchema(\n article: Article,\n storeName: string,\n origin: string\n): Record<string, unknown>[] {\n const schema = articleSchema('BlogPosting', {\n title: article.title,\n intro: article.intro,\n cover: article.cover,\n storeName,\n origin,\n })\n\n if (article.updated_at) {\n schema.dateModified = article.updated_at\n // Нет отдельного datePublished — используем updated_at.\n schema.datePublished = article.updated_at\n }\n\n return [schema]\n}\n","import { ContactType } from '@zenky/storefront-api'\n\nimport type { Schedule, Store } from '@zenky/storefront-api'\n\nimport { getSocialLinks } from '../utils'\n\n/** Ключ дня в Schedule.days → schema.org DayOfWeek. */\nconst SCHEMA_DAY: Record<string, string> = {\n monday: 'Monday',\n tuesday: 'Tuesday',\n wednesday: 'Wednesday',\n thursday: 'Thursday',\n friday: 'Friday',\n saturday: 'Saturday',\n sunday: 'Sunday',\n}\n\n/**\n * Schedule (по дням `hours` — «10:00–22:00») → массив schema.org/OpeningHoursSpecification.\n * Дни без распознаваемого интервала (выходной / круглосуточно / пусто) пропускаем.\n */\nfunction buildOpeningHours(schedule: Schedule | null | undefined): Record<string, unknown>[] {\n if (!schedule) return []\n\n const result: Record<string, unknown>[] = []\n for (const key of Object.keys(SCHEMA_DAY)) {\n const day = schedule.days[key as keyof typeof schedule.days]\n const match = day?.hours?.match(/(\\d{1,2}:\\d{2}).*?(\\d{1,2}:\\d{2})/)\n if (!match) continue\n\n result.push({\n '@type': 'OpeningHoursSpecification',\n dayOfWeek: SCHEMA_DAY[key],\n opens: match[1],\n closes: match[2],\n })\n }\n\n return result\n}\n\n/**\n * JSON-LD schema.org/Organization (или LocalBusiness при наличии физической точки)\n * для главной. Google → Knowledge Panel. Все данные из уже загруженного профиля.\n * Spec: https://schema.org/Organization · https://schema.org/LocalBusiness\n */\nexport function buildOrganizationSchema(store: Store, origin: string): Record<string, unknown>[] {\n const logoUrl =\n store.square_logo?.medium?.url ||\n store.square_logo?.large?.url ||\n store.logo?.medium?.url ||\n store.logo?.large?.url ||\n null\n\n const phones = (store.phones ?? []).map((p) => p.number).filter((n): n is string => !!n)\n const email = store.links?.find((c) => c.type === ContactType.Email)?.value ?? null\n const socialLinks = getSocialLinks(store.links ?? []).map((l) => l.url)\n\n const city = (store.cities ?? []).find((c) => c.is_default) ?? store.cities?.[0] ?? null\n const stock = city?.stocks?.find((st) => st.address) ?? city?.stocks?.[0] ?? null\n\n const streetAddress = stock?.address ?? null\n const geo = stock?.coordinates ?? city?.coordinates ?? null\n const openingHours = buildOpeningHours(stock?.schedule ?? city?.schedule)\n\n const isLocalBusiness = Boolean(streetAddress || geo || openingHours.length)\n\n const schema: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': isLocalBusiness ? 'LocalBusiness' : 'Organization',\n name: store.name,\n url: origin,\n }\n\n if (logoUrl) {\n schema.logo = logoUrl\n schema.image = logoUrl\n }\n\n if (phones.length) schema.telephone = phones.length === 1 ? phones[0] : phones\n if (email) schema.email = email\n if (socialLinks.length) schema.sameAs = socialLinks\n\n if (streetAddress || city?.name) {\n schema.address = {\n '@type': 'PostalAddress',\n ...(streetAddress ? { streetAddress } : {}),\n ...(city?.name ? { addressLocality: city.name } : {}),\n addressCountry: 'RU',\n }\n }\n\n if (geo) {\n schema.geo = {\n '@type': 'GeoCoordinates',\n latitude: geo.latitude,\n longitude: geo.longitude,\n }\n }\n\n if (openingHours.length) schema.openingHoursSpecification = openingHours\n\n return [schema]\n}\n","import { withSeoMetaPriority } from './headPriority'\n\n/** Уже разрешённые (с учётом дефолтов потребителя) значения OG/Twitter-тегов. */\nexport interface OgTagsInput {\n title: string | null\n description?: string | null\n image?: string | null\n type?: string\n url?: string | null\n}\n\n/** OG + Twitter meta-теги (critical-приоритет). Telegram читает twitter:image. */\nexport function buildOgTags(input: OgTagsInput): Record<string, string>[] {\n const tags: Record<string, string>[] = []\n\n if (input.title) {\n tags.push(withSeoMetaPriority({ property: 'og:title', content: input.title }))\n tags.push(withSeoMetaPriority({ name: 'twitter:title', content: input.title }))\n }\n\n if (input.description) {\n tags.push(withSeoMetaPriority({ property: 'og:description', content: input.description }))\n tags.push(withSeoMetaPriority({ name: 'twitter:description', content: input.description }))\n }\n\n if (input.image) {\n tags.push(withSeoMetaPriority({ property: 'og:image', content: input.image }))\n tags.push(withSeoMetaPriority({ name: 'twitter:image', content: input.image }))\n }\n\n tags.push(withSeoMetaPriority({ property: 'og:type', content: input.type ?? 'website' }))\n\n if (input.url) {\n tags.push(withSeoMetaPriority({ property: 'og:url', content: input.url }))\n }\n\n return tags\n}\n","import type { Seo, SeoMetaTag } from '@zenky/storefront-api'\n\nimport { withSeoMetaPriority } from './headPriority'\n\n/** SDK-теги `Seo.meta` → head-meta records (с critical-приоритетом для og/twitter/description). */\nexport function mapSeoMetaTags(tags: SeoMetaTag[]): Record<string, string>[] {\n return tags.map((tag) => {\n const attrs: Record<string, string> = {\n [tag.name.attribute]: tag.name.value,\n [tag.value.attribute]: tag.value.value,\n }\n\n for (const additional of tag.additional) {\n attrs[additional.attribute] = additional.value\n }\n\n if (\n attrs.name === 'description' ||\n attrs.name?.startsWith('twitter:') ||\n attrs.property?.startsWith('og:')\n ) {\n return withSeoMetaPriority(attrs)\n }\n\n return attrs\n })\n}\n\n/** meta-теги из `Seo`-сущности (description + маппинг `Seo.meta`). Title резолвит потребитель. */\nexport function buildEntitySeoMeta(seo: Seo | null | undefined): Record<string, string>[] {\n const tags: Record<string, string>[] = []\n\n if (seo?.description) {\n tags.push(withSeoMetaPriority({ name: 'description', content: seo.description }))\n }\n\n if (seo?.meta?.length) {\n tags.push(...mapSeoMetaTags(seo.meta))\n }\n\n return tags\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { S as Snippet } from '../snippets-BC5bkSjm.js';
2
+ export { R as RawSnippetItem, a as SnippetGroup, b as SnippetLanguage, c as SnippetPosition, n as normalizeSnippets } from '../snippets-BC5bkSjm.js';
3
+
4
+ /** Head-нагрузка для инъекции сниппетов (потребитель отдаёт её в useHead). */
5
+ interface SnippetHead {
6
+ meta: Record<string, string | boolean>[];
7
+ link: Record<string, string | boolean>[];
8
+ script: Record<string, unknown>[];
9
+ style: Record<string, string | boolean>[];
10
+ }
11
+ /**
12
+ * Раскладывает сниппеты магазина в head-нагрузку: JS (обёрнут в try/catch),
13
+ * CSS (style), head-HTML (распарсенные meta/link/script). Инъекция — у потребителя.
14
+ */
15
+ declare function buildSnippetHead(snippets: Snippet[]): SnippetHead;
16
+
17
+ export { Snippet, type SnippetHead, buildSnippetHead };
@@ -0,0 +1,3 @@
1
+ export { buildSnippetHead, normalizeSnippets } from '../chunk-DKWDKVXQ.js';
2
+ //# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
@@ -0,0 +1,23 @@
1
+ type SnippetPosition = 'head_start' | 'head_end' | 'body_start' | 'body_end';
2
+ type SnippetLanguage = 'html' | 'css' | 'javascript';
3
+ interface Snippet {
4
+ id: string;
5
+ name?: string;
6
+ position: SnippetPosition;
7
+ language: SnippetLanguage;
8
+ code: string;
9
+ enabled: boolean;
10
+ }
11
+ interface RawSnippetItem {
12
+ language: SnippetLanguage;
13
+ content: string;
14
+ enabled?: boolean;
15
+ name?: string;
16
+ }
17
+ interface SnippetGroup {
18
+ region: SnippetPosition;
19
+ snippets: RawSnippetItem[];
20
+ }
21
+ declare function normalizeSnippets(groups: SnippetGroup[] | null | undefined): Snippet[];
22
+
23
+ export { type RawSnippetItem as R, type Snippet as S, type SnippetGroup as a, type SnippetLanguage as b, type SnippetPosition as c, normalizeSnippets as n };
@@ -0,0 +1,196 @@
1
+ import * as pinia from 'pinia';
2
+ import * as vue from 'vue';
3
+ import { Ref, ComputedRef } from 'vue';
4
+ import * as _zenky_storefront_api from '@zenky/storefront-api';
5
+ import { CatalogItem, Product, Store, City, Stock, NestedCategory, DeliveryMethod, Order, OrderDeliveryMethod, OrderSettings, OrderCheckoutTotal, PaymentMethod, OrderPaymentTransaction, OrderProductVariant, OrderPaymentMethod } from '@zenky/storefront-api';
6
+ import { Website } from '../website-settings/index.js';
7
+ import '@zenky/integrations-sdk';
8
+ import '../snippets-BC5bkSjm.js';
9
+
10
+ interface CatalogStore {
11
+ catalog: Ref<CatalogItem[]>;
12
+ currentHash: Ref<string | null>;
13
+ reloading: Ref<boolean>;
14
+ loadError: Ref<boolean>;
15
+ initCatalog: (items: CatalogItem[], hash?: string | null) => void;
16
+ setLoadError: () => void;
17
+ setReloading: (value: boolean) => void;
18
+ hasCatalog: ComputedRef<boolean>;
19
+ }
20
+ declare const useCatalogStore: pinia.StoreDefinition<"zenky-catalog", Pick<CatalogStore, "catalog" | "currentHash" | "reloading" | "loadError">, Pick<CatalogStore, "hasCatalog">, Pick<CatalogStore, "initCatalog" | "setLoadError" | "setReloading">>;
21
+
22
+ interface ProductsStore {
23
+ products: Ref<Product[]>;
24
+ setProducts: (list: Product[]) => void;
25
+ hasProducts: ComputedRef<boolean>;
26
+ }
27
+ declare const useProductsStore: pinia.StoreDefinition<"zenky-products", Pick<ProductsStore, "products">, Pick<ProductsStore, "hasProducts">, Pick<ProductsStore, "setProducts">>;
28
+
29
+ interface StoreProfileStore {
30
+ store: Ref<Store | null>;
31
+ city: Ref<City | null>;
32
+ stock: Ref<Stock | null>;
33
+ categories: Ref<NestedCategory[]>;
34
+ website: Ref<Website | null>;
35
+ initStore: (store: Store, city: City, stock: Stock | null) => void;
36
+ setStock: (stock: Stock | null) => void;
37
+ setCategories: (categories: NestedCategory[]) => void;
38
+ initWebsite: (website: Website | null) => void;
39
+ isReady: ComputedRef<boolean>;
40
+ hasStock: ComputedRef<boolean>;
41
+ currency: ComputedRef<string>;
42
+ squareLogoUrl: ComputedRef<string | null>;
43
+ }
44
+ declare const useStoreProfileStore: pinia.StoreDefinition<"zenky-store-profile", Pick<StoreProfileStore, "store" | "city" | "stock" | "categories" | "website">, Pick<StoreProfileStore, "currency" | "isReady" | "hasStock" | "squareLogoUrl">, Pick<StoreProfileStore, "initStore" | "setStock" | "setCategories" | "initWebsite">>;
45
+
46
+ interface AuthStore {
47
+ token: Ref<string | null>;
48
+ phone: Ref<string | null>;
49
+ countryCode: Ref<string>;
50
+ isAuthenticated: ComputedRef<boolean>;
51
+ initials: ComputedRef<string>;
52
+ setToken: (token: string, phone?: string, countryCode?: string) => void;
53
+ clearToken: () => void;
54
+ initFromCookie: () => void;
55
+ }
56
+ declare const useAuthStore: pinia.StoreDefinition<"zenky-auth", Pick<AuthStore, "token" | "phone" | "countryCode">, Pick<AuthStore, "isAuthenticated" | "initials">, Pick<AuthStore, "setToken" | "clearToken" | "initFromCookie">>;
57
+
58
+ interface OrderSettingsStore {
59
+ deliveryMethod: Ref<DeliveryMethod>;
60
+ onPremiseTable: Ref<string | null>;
61
+ selectedStock: Ref<Stock | null>;
62
+ selectedAddress: Ref<string | null>;
63
+ selectedAddressId: Ref<string | null>;
64
+ selectedAddressName: Ref<string | null>;
65
+ setDeliveryMethod: (method: DeliveryMethod) => void;
66
+ setOnPremiseTable: (table: string | null) => void;
67
+ setSelectedStock: (stock: Stock | null) => void;
68
+ setSelectedAddress: (address: string | null, addressId?: string | null, addressName?: string | null) => void;
69
+ validateDeliveryMethod: (availableMethods: DeliveryMethod[]) => void;
70
+ restoreFromCookies: (city: City) => void;
71
+ isDelivery: ComputedRef<boolean>;
72
+ isPickup: ComputedRef<boolean>;
73
+ isOnPremise: ComputedRef<boolean>;
74
+ needsAddress: ComputedRef<boolean>;
75
+ needsStock: ComputedRef<boolean>;
76
+ needsTable: ComputedRef<boolean>;
77
+ }
78
+ declare const useOrderSettingsStore: pinia.StoreDefinition<"zenky-order-settings", Pick<OrderSettingsStore, "deliveryMethod" | "onPremiseTable" | "selectedStock" | "selectedAddress" | "selectedAddressId" | "selectedAddressName">, Pick<OrderSettingsStore, "isDelivery" | "isPickup" | "isOnPremise" | "needsAddress" | "needsStock" | "needsTable">, Pick<OrderSettingsStore, "setDeliveryMethod" | "setOnPremiseTable" | "setSelectedStock" | "setSelectedAddress" | "validateDeliveryMethod" | "restoreFromCookies">>;
79
+
80
+ interface CartCredentials {
81
+ id: string;
82
+ token: string;
83
+ }
84
+ /** Намерение по бонусам (business-state) — для headless-reconciliation. */
85
+ interface AppliedBonusesIntent {
86
+ enabled: boolean;
87
+ /** null = «списать максимум». */
88
+ desiredAmountMinor: number | null;
89
+ /** PIN-flow: бонусы сняты, нужно подтвердить заново. */
90
+ requiresConfirmation: boolean;
91
+ }
92
+ interface MinPriceViolation {
93
+ remaining: number;
94
+ progress: number;
95
+ minPrice: number;
96
+ }
97
+ interface CartStore {
98
+ order: Ref<Order | null>;
99
+ credentials: Ref<CartCredentials | null>;
100
+ loading: Ref<boolean>;
101
+ initializing: Ref<boolean>;
102
+ optimisticAdjustments: Ref<Record<string, number>>;
103
+ lineOptimistic: Ref<Record<string, number>>;
104
+ deliveryMethods: Ref<OrderDeliveryMethod[]>;
105
+ orderSettings: Ref<OrderSettings | null>;
106
+ comment: Ref<string>;
107
+ personsCount: Ref<number>;
108
+ checkoutTotal: Ref<OrderCheckoutTotal | null>;
109
+ selectedPaymentMethod: Ref<PaymentMethod | null>;
110
+ selectedPaymentBill: Ref<number | null>;
111
+ selectedCardTokenId: Ref<string | null>;
112
+ selectedSaveCard: Ref<boolean>;
113
+ bonusesIntent: Ref<AppliedBonusesIntent>;
114
+ deliveryTimeIntent: Ref<string | null>;
115
+ minPriceViolation: Ref<MinPriceViolation | null>;
116
+ setOrder: (order: Order) => void;
117
+ patchOrder: (partial: Order) => void;
118
+ setCredentials: (credentials: CartCredentials) => void;
119
+ setLoading: (value: boolean) => void;
120
+ setInitializing: (value: boolean) => void;
121
+ adjustOptimistic: (productId: string, delta: number) => void;
122
+ clearOptimistic: (productId: string) => void;
123
+ adjustLineOptimistic: (lineKey: string, delta: number) => void;
124
+ setDeliveryMethods: (methods: OrderDeliveryMethod[]) => void;
125
+ setOrderSettings: (settings: OrderSettings) => void;
126
+ setComment: (value: string) => void;
127
+ setPersonsCount: (value: number) => void;
128
+ setSelectedPaymentMethod: (method: PaymentMethod | null) => void;
129
+ setSelectedPaymentBill: (bill: number | null) => void;
130
+ setSelectedCardTokenId: (id: string | null) => void;
131
+ setSelectedSaveCard: (value: boolean) => void;
132
+ setBonusesIntentEnabled: (value: boolean) => void;
133
+ setDesiredBonusesAmount: (value: number | null) => void;
134
+ setBonusesRequiresConfirmation: (value: boolean) => void;
135
+ clearBonusesIntent: () => void;
136
+ setDeliveryTimeIntent: (value: string | null) => void;
137
+ restorePaymentSelection: (payments: OrderPaymentTransaction[]) => void;
138
+ validatePaymentSelection: () => void;
139
+ setOrderPaymentsState: (payments: OrderPaymentTransaction[]) => void;
140
+ setCheckoutTotal: (value: OrderCheckoutTotal) => void;
141
+ clearCart: () => void;
142
+ hasOrder: ComputedRef<boolean>;
143
+ hasCredentials: ComputedRef<boolean>;
144
+ variants: ComputedRef<OrderProductVariant[]>;
145
+ totalPrice: ComputedRef<number>;
146
+ itemCount: ComputedRef<number>;
147
+ availableDeliveryMethodIds: ComputedRef<DeliveryMethod[]>;
148
+ paymentMethods: ComputedRef<OrderPaymentMethod[]>;
149
+ hasBonusesApplied: ComputedRef<boolean>;
150
+ productQuantities: ComputedRef<Record<string, number>>;
151
+ }
152
+ declare const useCartStore: pinia.StoreDefinition<"zenky-cart", Pick<CartStore, "order" | "credentials" | "loading" | "initializing" | "optimisticAdjustments" | "lineOptimistic" | "deliveryMethods" | "orderSettings" | "comment" | "personsCount" | "checkoutTotal" | "selectedPaymentMethod" | "selectedPaymentBill" | "selectedCardTokenId" | "selectedSaveCard" | "bonusesIntent" | "deliveryTimeIntent" | "minPriceViolation">, Pick<CartStore, "hasOrder" | "hasCredentials" | "variants" | "totalPrice" | "itemCount" | "availableDeliveryMethodIds" | "paymentMethods" | "hasBonusesApplied" | "productQuantities">, Pick<CartStore, "setOrder" | "patchOrder" | "setCredentials" | "setLoading" | "setInitializing" | "adjustOptimistic" | "clearOptimistic" | "adjustLineOptimistic" | "setDeliveryMethods" | "setOrderSettings" | "setComment" | "setPersonsCount" | "setSelectedPaymentMethod" | "setSelectedPaymentBill" | "setSelectedCardTokenId" | "setSelectedSaveCard" | "setBonusesIntentEnabled" | "setDesiredBonusesAmount" | "setBonusesRequiresConfirmation" | "clearBonusesIntent" | "setDeliveryTimeIntent" | "restorePaymentSelection" | "validatePaymentSelection" | "setOrderPaymentsState" | "setCheckoutTotal" | "clearCart">>;
153
+
154
+ interface RewardSelectionContext {
155
+ type: string;
156
+ id: string;
157
+ }
158
+ interface RewardSelection {
159
+ offerId: string;
160
+ promotionId: string;
161
+ rewardId: string;
162
+ context: RewardSelectionContext | null;
163
+ limit: number;
164
+ }
165
+ interface PromotionsStore {
166
+ rewards: Ref<RewardSelection[]>;
167
+ checking: Ref<boolean>;
168
+ addOrUpdateReward: (selection: RewardSelection) => void;
169
+ removeRewardByPromotion: (promotionId: string) => void;
170
+ clearRewards: () => void;
171
+ setChecking: (value: boolean) => void;
172
+ hasRewards: ComputedRef<boolean>;
173
+ totalEmptySlots: ComputedRef<number>;
174
+ rewardSelection: ComputedRef<RewardSelection | null>;
175
+ needsSelection: ComputedRef<boolean>;
176
+ }
177
+ declare const usePromotionsStore: pinia.StoreDefinition<"zenky-promotions", Pick<PromotionsStore, "rewards" | "checking">, Pick<PromotionsStore, "hasRewards" | "totalEmptySlots" | "rewardSelection" | "needsSelection">, Pick<PromotionsStore, "addOrUpdateReward" | "removeRewardByPromotion" | "clearRewards" | "setChecking">>;
178
+
179
+ /** Аксессор загруженного стора/города/склада (store/city — гарантированно загружены). */
180
+ declare function useStore(): {
181
+ store: ComputedRef<Store>;
182
+ city: ComputedRef<City>;
183
+ stock: ComputedRef<Stock | null>;
184
+ };
185
+
186
+ /** Реактивный доступ к профилю (store/city/stock/categories + флаги готовности). */
187
+ declare function useStorefrontProfile(): {
188
+ store: vue.Ref<_zenky_storefront_api.Store | null, _zenky_storefront_api.Store | null>;
189
+ city: vue.Ref<_zenky_storefront_api.City | null, _zenky_storefront_api.City | null>;
190
+ stock: vue.Ref<_zenky_storefront_api.Stock | null, _zenky_storefront_api.Stock | null>;
191
+ categories: vue.Ref<_zenky_storefront_api.NestedCategory[], _zenky_storefront_api.NestedCategory[]>;
192
+ isReady: vue.ComputedRef<boolean>;
193
+ hasStock: vue.ComputedRef<boolean>;
194
+ };
195
+
196
+ export { type AppliedBonusesIntent, type AuthStore, type CartStore, type MinPriceViolation, type OrderSettingsStore, type PromotionsStore, type RewardSelection, type RewardSelectionContext, type StoreProfileStore, useAuthStore, useCartStore, useCatalogStore, useOrderSettingsStore, useProductsStore, usePromotionsStore, useStore, useStoreProfileStore, useStorefrontProfile };
@@ -0,0 +1,8 @@
1
+ export { useProductsStore, useStore, useStorefrontProfile } from '../chunk-BIGTKVC6.js';
2
+ export { useCatalogStore } from '../chunk-Z3IW4ODZ.js';
3
+ export { usePromotionsStore } from '../chunk-BWUKMWHL.js';
4
+ export { useAuthStore, useStoreProfileStore } from '../chunk-PWHIDGKW.js';
5
+ export { useCartStore, useOrderSettingsStore } from '../chunk-VL3T36VM.js';
6
+ import '../chunk-V3VVDQIR.js';
7
+ //# sourceMappingURL=index.js.map
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}