@yoamigo.com/core 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Copyright (c) 2025 YoAmigo. All Rights Reserved.
2
+
3
+ This software and associated documentation files (the "Software") are the
4
+ proprietary property of YoAmigo. Unauthorized copying, modification,
5
+ distribution, or use of this Software, via any medium, is strictly prohibited.
6
+
7
+ The Software is provided for use solely in connection with the YoAmigo platform
8
+ and template development program. Any other use requires explicit written
9
+ permission from YoAmigo.
10
+
11
+ For licensing inquiries, contact: support@yoamigo.com
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @yoamigo/core
2
+
3
+ Core components, Vite plugin, and utilities for building YoAmigo templates.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @yoamigo/core
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Vite Plugin
14
+
15
+ Add the YoAmigo plugin to your `vite.config.ts`:
16
+
17
+ ```typescript
18
+ import { defineConfig } from 'vite'
19
+ import { yoamigoPlugin } from '@yoamigo/core/plugin'
20
+
21
+ export default defineConfig({
22
+ plugins: [yoamigoPlugin()],
23
+ })
24
+ ```
25
+
26
+ The plugin automatically handles:
27
+ - React/Preact swap in production builds
28
+ - Path aliases (`@/` maps to `src/`)
29
+ - Environment variables (`YA_*` prefix)
30
+ - Optimized build output
31
+
32
+ ### Components
33
+
34
+ Use the editable components in your templates:
35
+
36
+ ```tsx
37
+ import { YaText, YaImage, YaLink, ContentStoreProvider } from '@yoamigo/core'
38
+
39
+ function App() {
40
+ return (
41
+ <ContentStoreProvider>
42
+ <h1>
43
+ <YaText fieldId="hero.title" />
44
+ </h1>
45
+ <YaImage fieldId="hero.image" className="hero-img" />
46
+ <YaLink fieldId="hero.cta" className="btn">
47
+ <YaText fieldId="hero.cta.text" />
48
+ </YaLink>
49
+ </ContentStoreProvider>
50
+ )
51
+ }
52
+ ```
53
+
54
+ ### Router
55
+
56
+ A lightweight router for multi-page templates:
57
+
58
+ ```tsx
59
+ import { Router, Route, Link, useLocation } from '@yoamigo/core/router'
60
+
61
+ function App() {
62
+ return (
63
+ <Router>
64
+ <nav>
65
+ <Link href="/">Home</Link>
66
+ <Link href="/about">About</Link>
67
+ </nav>
68
+ <Route path="/" component={Home} />
69
+ <Route path="/about" component={About} />
70
+ </Router>
71
+ )
72
+ }
73
+ ```
74
+
75
+ ## Documentation
76
+
77
+ For full documentation, visit [yoamigo.com/developers](https://yoamigo.com/developers).
78
+
79
+ ## License
80
+
81
+ Proprietary - All Rights Reserved. See LICENSE file for details.
@@ -0,0 +1,106 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React$1, { ReactNode } from 'react';
3
+
4
+ type EditMode = 'read-only' | 'inline-edit';
5
+ interface ContentStore {
6
+ getValue: (fieldId: string) => string;
7
+ setValue: (fieldId: string, value: string) => void;
8
+ mode: EditMode;
9
+ setMode: (mode: EditMode) => void;
10
+ subscribe: (listener: () => void) => () => void;
11
+ saveToWorker?: (fieldId: string, value: string) => Promise<void>;
12
+ }
13
+ declare function useContentStore(): ContentStore;
14
+ interface ContentStoreProviderProps {
15
+ children: ReactNode;
16
+ initialContent?: Record<string, string>;
17
+ initialMode?: EditMode;
18
+ }
19
+ declare function ContentStoreProvider({ children }: ContentStoreProviderProps): react_jsx_runtime.JSX.Element;
20
+
21
+ interface PageInfo {
22
+ path: string;
23
+ label: string;
24
+ }
25
+ interface YaLinkProps {
26
+ fieldId: string;
27
+ /** Default href if not set in content store */
28
+ href?: string;
29
+ className?: string;
30
+ as?: 'a' | 'span';
31
+ children?: React$1.ReactNode;
32
+ /** Available pages for href dropdown (injected by template) */
33
+ availablePages?: PageInfo[];
34
+ /** Optional click handler called after navigation */
35
+ onClick?: () => void;
36
+ }
37
+ declare module '@tiptap/core' {
38
+ interface Commands<ReturnType> {
39
+ fontSize: {
40
+ setFontSize: (fontSize: string) => ReturnType;
41
+ unsetFontSize: () => ReturnType;
42
+ };
43
+ fontWeight: {
44
+ setFontWeight: (fontWeight: string) => ReturnType;
45
+ unsetFontWeight: () => ReturnType;
46
+ };
47
+ }
48
+ }
49
+ declare function YaLink({ fieldId, href: defaultHref, className, as: Component, children, availablePages, onClick }: YaLinkProps): react_jsx_runtime.JSX.Element;
50
+
51
+ /**
52
+ * StaticText Component - Production-only static text renderer
53
+ *
54
+ * This component replaces MpText in production builds via Vite alias.
55
+ * No editing capabilities, no Tiptap, no DOMPurify - minimal bundle size.
56
+ *
57
+ * Content from content.json is trusted because:
58
+ * 1. It's created in the builder using Tiptap (which controls allowed markup)
59
+ * 2. It's sanitized by SafeHtml when displayed in the builder
60
+ * 3. Users can't inject arbitrary HTML
61
+ */
62
+ /** Common HTML elements that MpText can render as */
63
+ type MpTextElement = 'span' | 'div' | 'p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'strong' | 'em';
64
+ interface MpTextProps {
65
+ fieldId: string;
66
+ className?: string;
67
+ as?: MpTextElement;
68
+ /** Optional fallback content (used if fieldId not in store) */
69
+ children?: React.ReactNode;
70
+ }
71
+ declare function MpText({ fieldId, className, as: Component, children }: MpTextProps): react_jsx_runtime.JSX.Element;
72
+
73
+ type StaticTextProps = MpTextProps;
74
+
75
+ /**
76
+ * StaticImage Component - Production-only static image renderer
77
+ *
78
+ * This component replaces MpImage in production builds via Vite alias.
79
+ * No editing overlay, no click handlers, no postMessage - minimal bundle size.
80
+ */
81
+ interface MpImageProps {
82
+ fieldId: string;
83
+ className?: string;
84
+ alt?: string;
85
+ objectFit?: 'cover' | 'contain' | 'fill';
86
+ objectPosition?: string;
87
+ loading?: 'lazy' | 'eager';
88
+ /** Fallback for backward compatibility with config.ts images */
89
+ fallbackSrc?: string;
90
+ /** Fallback alt text */
91
+ fallbackAlt?: string;
92
+ }
93
+ declare function MpImage({ fieldId, className, alt, objectFit: propObjectFit, objectPosition: propObjectPosition, loading, fallbackSrc, fallbackAlt, }: MpImageProps): react_jsx_runtime.JSX.Element;
94
+
95
+ type StaticImageProps = MpImageProps;
96
+
97
+ interface MarkdownTextProps {
98
+ content: string;
99
+ className?: string;
100
+ }
101
+ /**
102
+ * MarkdownText component - renders markdown content safely
103
+ */
104
+ declare function MarkdownText({ content, className }: MarkdownTextProps): react_jsx_runtime.JSX.Element;
105
+
106
+ export { ContentStoreProvider as C, type EditMode as E, MpText as M, type PageInfo as P, type StaticTextProps as S, YaLink as Y, type ContentStore as a, MpImage as b, type StaticImageProps as c, MarkdownText as d, type MarkdownTextProps as e, type YaLinkProps as f, useContentStore as u };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * API Client
3
+ *
4
+ * Handles communication with the backend API for:
5
+ * - Email subscriptions
6
+ * - Contact form submissions
7
+ * - Product/cart operations (future)
8
+ */
9
+ /**
10
+ * Subscribe to newsletter
11
+ */
12
+ declare function subscribeToNewsletter(email: string): Promise<{
13
+ success: boolean;
14
+ }>;
15
+ /**
16
+ * Submit contact form
17
+ */
18
+ interface ContactFormData {
19
+ name: string;
20
+ email: string;
21
+ phone?: string;
22
+ eventType?: string;
23
+ eventDate?: string;
24
+ location?: string;
25
+ message: string;
26
+ }
27
+ declare function submitContactForm(data: ContactFormData): Promise<{
28
+ success: boolean;
29
+ }>;
30
+ /**
31
+ * Get site metadata (used by worker for runtime config injection)
32
+ */
33
+ interface SiteMetadata {
34
+ siteId: string;
35
+ apiUrl: string;
36
+ version: number;
37
+ }
38
+ declare function getSiteMetadata(): Promise<SiteMetadata | null>;
39
+
40
+ export { type ContactFormData as C, type SiteMetadata as S, submitContactForm as a, getSiteMetadata as g, subscribeToNewsletter as s };
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Content Registry - Global content store for production builds
3
+ *
4
+ * Templates register their content.json at startup, and StaticText/StaticImage
5
+ * components read from this registry instead of importing directly.
6
+ *
7
+ * @example
8
+ * // In template's main.tsx:
9
+ * import content from './content.json'
10
+ * import { registerContent } from '@yoamigo/template-base'
11
+ * registerContent(content)
12
+ */
13
+ /**
14
+ * Register content from template's content.json
15
+ * Call this at app startup before rendering
16
+ */
17
+ declare function registerContent(content: Record<string, string>): void;
18
+ /**
19
+ * Get content value by fieldId
20
+ * @param fieldId - Dot-notation path (e.g., 'hero.title', 'artist.bio')
21
+ * @returns The content string, or empty string if not found
22
+ */
23
+ declare function getContent(fieldId: string): string;
24
+ /**
25
+ * Get all content as a Record
26
+ * Useful for initializing ContentStoreProvider
27
+ */
28
+ declare function getAllContent(): Record<string, string>;
29
+ /**
30
+ * Check if a fieldId exists in content
31
+ */
32
+ declare function hasContent(fieldId: string): boolean;
33
+ /**
34
+ * Content registry interface for type exports
35
+ */
36
+ interface ContentRegistry {
37
+ registerContent: typeof registerContent;
38
+ getContent: typeof getContent;
39
+ getAllContent: typeof getAllContent;
40
+ hasContent: typeof hasContent;
41
+ }
42
+ /**
43
+ * Content registry object for convenience
44
+ */
45
+ declare const contentRegistry: ContentRegistry;
46
+
47
+ /**
48
+ * Asset Resolver - Configurable asset URL resolution
49
+ *
50
+ * Templates register their asset resolver at startup. This allows
51
+ * MpImage/StaticImage to resolve asset paths correctly based on
52
+ * Vite's base path configuration.
53
+ *
54
+ * @example
55
+ * // In template's main.tsx:
56
+ * import { setAssetResolver } from '@yoamigo/template-base'
57
+ * setAssetResolver((path) => `${import.meta.env.BASE_URL}${path}`)
58
+ */
59
+ type AssetResolverFn = (path: string) => string;
60
+ type AssetResolver = AssetResolverFn;
61
+ /**
62
+ * Set the asset resolver function
63
+ * Call this at app startup before rendering
64
+ */
65
+ declare function setAssetResolver(resolver: AssetResolver): void;
66
+ /**
67
+ * Resolve an asset path to a full URL
68
+ * Uses the registered resolver function
69
+ */
70
+ declare function resolveAssetUrl(path: string): string;
71
+
72
+ export { type AssetResolverFn as A, type ContentRegistry as C, getAllContent as a, resolveAssetUrl as b, contentRegistry as c, getContent as g, hasContent as h, registerContent as r, setAssetResolver as s };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Initialize builder selection when running in iframe
3
+ */
4
+ declare function initBuilderSelection(): void;
5
+
6
+ export { initBuilderSelection as i };
package/dist/index.css ADDED
@@ -0,0 +1,372 @@
1
+ /* src/components/ya-tooltip.css */
2
+ .ya-tooltip {
3
+ position: fixed;
4
+ z-index: 9999;
5
+ display: flex;
6
+ align-items: center;
7
+ gap: 6px;
8
+ padding: 8px 12px;
9
+ background: #1a1a1a;
10
+ color: white;
11
+ border-radius: 6px;
12
+ font-size: 13px;
13
+ font-weight: 500;
14
+ font-family:
15
+ system-ui,
16
+ -apple-system,
17
+ BlinkMacSystemFont,
18
+ "Segoe UI",
19
+ sans-serif;
20
+ white-space: nowrap;
21
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
22
+ animation: ya-tooltip-fade-in 0.15s ease;
23
+ pointer-events: none;
24
+ }
25
+ @keyframes ya-tooltip-fade-in {
26
+ from {
27
+ opacity: 0;
28
+ transform: scale(0.95);
29
+ }
30
+ to {
31
+ opacity: 1;
32
+ transform: scale(1);
33
+ }
34
+ }
35
+ .ya-tooltip svg {
36
+ width: 16px;
37
+ height: 16px;
38
+ flex-shrink: 0;
39
+ }
40
+ .ya-tooltip-bottom {
41
+ transform: translateX(-50%);
42
+ }
43
+ .ya-tooltip-bottom::before {
44
+ content: "";
45
+ position: absolute;
46
+ bottom: 100%;
47
+ left: 50%;
48
+ transform: translateX(-50%);
49
+ border: 6px solid transparent;
50
+ border-bottom-color: #1a1a1a;
51
+ }
52
+ .ya-tooltip-top {
53
+ transform: translateX(-50%);
54
+ }
55
+ .ya-tooltip-top::before {
56
+ content: "";
57
+ position: absolute;
58
+ top: 100%;
59
+ left: 50%;
60
+ transform: translateX(-50%);
61
+ border: 6px solid transparent;
62
+ border-top-color: #1a1a1a;
63
+ }
64
+ .ya-tooltip-right {
65
+ transform: translateY(-50%);
66
+ }
67
+ .ya-tooltip-right::before {
68
+ content: "";
69
+ position: absolute;
70
+ right: 100%;
71
+ top: 50%;
72
+ transform: translateY(-50%);
73
+ border: 6px solid transparent;
74
+ border-right-color: #1a1a1a;
75
+ }
76
+ .ya-tooltip-left {
77
+ transform: translateY(-50%);
78
+ }
79
+ .ya-tooltip-left::before {
80
+ content: "";
81
+ position: absolute;
82
+ left: 100%;
83
+ top: 50%;
84
+ transform: translateY(-50%);
85
+ border: 6px solid transparent;
86
+ border-left-color: #1a1a1a;
87
+ }
88
+
89
+ /* src/components/ya-link.css */
90
+ .ya-link-wrapper {
91
+ position: relative;
92
+ display: inline;
93
+ }
94
+ .ya-link-editable {
95
+ cursor: pointer;
96
+ transition: outline 0.15s ease;
97
+ }
98
+ .ya-link-editable:hover {
99
+ outline: 2px dashed var(--color-primary, #D4A574);
100
+ outline-offset: 4px;
101
+ border-radius: 4px;
102
+ }
103
+ body.builder-selector-active .ya-link-editable:hover {
104
+ outline: none;
105
+ cursor: inherit;
106
+ }
107
+ .ya-link-editing {
108
+ outline: 2px solid var(--color-primary, #D4A574);
109
+ outline-offset: 4px;
110
+ border-radius: 4px;
111
+ position: relative;
112
+ }
113
+ .ya-link-actions {
114
+ display: flex;
115
+ gap: 8px;
116
+ position: absolute;
117
+ bottom: -60px;
118
+ right: 0;
119
+ z-index: 10;
120
+ background: rgba(26, 26, 26, 0.95);
121
+ padding: 8px 10px;
122
+ border-radius: 8px;
123
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
124
+ font-family:
125
+ system-ui,
126
+ -apple-system,
127
+ BlinkMacSystemFont,
128
+ "Segoe UI",
129
+ sans-serif;
130
+ }
131
+ .ya-link-btn {
132
+ padding: 6px 14px;
133
+ font-size: 12px;
134
+ font-weight: 500;
135
+ border-radius: 6px;
136
+ cursor: pointer;
137
+ transition: all 0.15s ease;
138
+ border: none;
139
+ }
140
+ .ya-link-btn-cancel {
141
+ background: #333333;
142
+ color: #ffffff;
143
+ border: 1px solid #555555;
144
+ }
145
+ .ya-link-btn-cancel:hover {
146
+ background: #444444;
147
+ color: #ffffff;
148
+ border-color: #666666;
149
+ }
150
+ .ya-link-btn-save {
151
+ background: #D4A574;
152
+ color: #1a1a1a;
153
+ }
154
+ .ya-link-btn-save:hover {
155
+ background: #c4956a;
156
+ }
157
+ .ya-href-popover {
158
+ position: absolute;
159
+ top: 100%;
160
+ left: 50%;
161
+ margin-top: 8px;
162
+ z-index: 10;
163
+ min-width: 280px;
164
+ max-width: 320px;
165
+ background: #1a1a1a;
166
+ border-radius: 12px;
167
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
168
+ transform: translateX(-50%);
169
+ animation: ya-href-popover-fade-in 0.15s ease;
170
+ overflow: hidden;
171
+ font-family:
172
+ system-ui,
173
+ -apple-system,
174
+ BlinkMacSystemFont,
175
+ "Segoe UI",
176
+ sans-serif;
177
+ }
178
+ @keyframes ya-href-popover-fade-in {
179
+ from {
180
+ opacity: 0;
181
+ transform: translateX(-50%) translateY(-8px);
182
+ }
183
+ to {
184
+ opacity: 1;
185
+ transform: translateX(-50%) translateY(0);
186
+ }
187
+ }
188
+ .ya-href-popover::before {
189
+ content: "";
190
+ position: absolute;
191
+ top: -6px;
192
+ left: 50%;
193
+ transform: translateX(-50%);
194
+ border-left: 8px solid transparent;
195
+ border-right: 8px solid transparent;
196
+ border-bottom: 8px solid #1a1a1a;
197
+ }
198
+ .ya-href-popover-header {
199
+ padding: 12px 16px;
200
+ font-size: 13px;
201
+ font-weight: 600;
202
+ color: #ffffff;
203
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
204
+ }
205
+ .ya-href-popover-section {
206
+ padding: 12px 16px;
207
+ }
208
+ .ya-href-popover-label {
209
+ display: block;
210
+ font-size: 11px;
211
+ font-weight: 500;
212
+ color: rgba(255, 255, 255, 0.6);
213
+ text-transform: uppercase;
214
+ letter-spacing: 0.5px;
215
+ margin-bottom: 8px;
216
+ }
217
+ .ya-href-collapsible-header {
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 6px;
221
+ width: 100%;
222
+ padding: 0;
223
+ background: transparent;
224
+ border: none;
225
+ cursor: pointer;
226
+ transition: color 0.15s ease;
227
+ }
228
+ .ya-href-collapsible-header:hover {
229
+ color: rgba(255, 255, 255, 0.8);
230
+ }
231
+ .ya-href-chevron {
232
+ font-size: 8px;
233
+ color: rgba(255, 255, 255, 0.4);
234
+ }
235
+ .ya-href-popover-pages {
236
+ display: flex;
237
+ flex-direction: column;
238
+ gap: 4px;
239
+ max-height: 200px;
240
+ overflow-y: auto;
241
+ }
242
+ .ya-href-page-btn {
243
+ display: flex;
244
+ flex-direction: column;
245
+ align-items: flex-start;
246
+ gap: 4px;
247
+ width: 100%;
248
+ padding: 10px 12px;
249
+ background: rgba(255, 255, 255, 0.05);
250
+ border: 1px solid transparent;
251
+ border-radius: 8px;
252
+ color: #e0e0e0;
253
+ font-size: 13px;
254
+ font-weight: 500;
255
+ text-align: left;
256
+ cursor: pointer;
257
+ transition: all 0.15s ease;
258
+ }
259
+ .ya-href-page-btn:hover {
260
+ background: rgba(255, 255, 255, 0.1);
261
+ border-color: rgba(255, 255, 255, 0.2);
262
+ }
263
+ .ya-href-page-btn.is-selected {
264
+ background: #D4A574;
265
+ color: #1a1a1a;
266
+ }
267
+ .ya-href-page-btn.is-selected .ya-href-page-path {
268
+ color: rgba(26, 26, 26, 0.6);
269
+ }
270
+ .ya-href-page-path {
271
+ font-size: 11px;
272
+ color: rgba(255, 255, 255, 0.4);
273
+ font-family: monospace;
274
+ word-break: break-all;
275
+ }
276
+ .ya-href-external-toggle {
277
+ display: block;
278
+ width: 100%;
279
+ padding: 10px 16px;
280
+ background: transparent;
281
+ border: none;
282
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
283
+ color: #D4A574;
284
+ font-size: 12px;
285
+ font-weight: 500;
286
+ text-align: center;
287
+ cursor: pointer;
288
+ transition: background 0.15s ease;
289
+ }
290
+ .ya-href-external-toggle:hover {
291
+ background: rgba(255, 255, 255, 0.05);
292
+ }
293
+ .ya-href-url-input {
294
+ width: 100%;
295
+ padding: 10px 12px;
296
+ background: rgba(255, 255, 255, 0.05);
297
+ border: 1px solid rgba(255, 255, 255, 0.2);
298
+ border-radius: 8px;
299
+ color: #ffffff;
300
+ font-size: 13px;
301
+ outline: none;
302
+ transition: border-color 0.15s ease;
303
+ }
304
+ .ya-href-url-input::placeholder {
305
+ color: rgba(255, 255, 255, 0.4);
306
+ }
307
+ .ya-href-url-input:focus {
308
+ border-color: var(--color-primary, #D4A574);
309
+ }
310
+ .ya-href-popover-actions {
311
+ display: flex;
312
+ justify-content: flex-end;
313
+ gap: 8px;
314
+ padding: 12px 16px;
315
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
316
+ }
317
+ .ya-link-edit-popover {
318
+ position: absolute;
319
+ top: 100%;
320
+ left: 50%;
321
+ margin-top: 8px;
322
+ z-index: 10;
323
+ background: #2a2a2a;
324
+ border-radius: 6px;
325
+ padding: 4px;
326
+ display: flex;
327
+ gap: 4px;
328
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
329
+ transform: translateX(-50%);
330
+ animation: ya-edit-popover-fade-in 0.1s ease;
331
+ font-family:
332
+ system-ui,
333
+ -apple-system,
334
+ BlinkMacSystemFont,
335
+ "Segoe UI",
336
+ sans-serif;
337
+ white-space: nowrap;
338
+ }
339
+ @keyframes ya-edit-popover-fade-in {
340
+ from {
341
+ opacity: 0;
342
+ transform: translateX(-50%) translateY(-4px);
343
+ }
344
+ to {
345
+ opacity: 1;
346
+ transform: translateX(-50%) translateY(0);
347
+ }
348
+ }
349
+ .ya-link-edit-popover::before {
350
+ content: "";
351
+ position: absolute;
352
+ top: -5px;
353
+ left: 50%;
354
+ transform: translateX(-50%);
355
+ border-left: 6px solid transparent;
356
+ border-right: 6px solid transparent;
357
+ border-bottom: 6px solid #2a2a2a;
358
+ }
359
+ .ya-link-edit-popover button {
360
+ background: #3a3a3a;
361
+ border: none;
362
+ color: #fff;
363
+ padding: 6px 12px;
364
+ border-radius: 4px;
365
+ cursor: pointer;
366
+ font-size: 13px;
367
+ font-weight: 500;
368
+ transition: background 0.15s ease;
369
+ }
370
+ .ya-link-edit-popover button:hover {
371
+ background: #4a4a4a;
372
+ }