@astro-live-cms/core 0.2.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 (163) hide show
  1. package/dist/index.d.ts +15 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +122 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/runtime/auth/session.d.ts +14 -0
  6. package/dist/runtime/auth/session.d.ts.map +1 -0
  7. package/dist/runtime/auth/session.js +77 -0
  8. package/dist/runtime/auth/session.js.map +1 -0
  9. package/dist/runtime/bind.d.ts +14 -0
  10. package/dist/runtime/bind.d.ts.map +1 -0
  11. package/dist/runtime/bind.js +11 -0
  12. package/dist/runtime/bind.js.map +1 -0
  13. package/dist/runtime/config.d.ts +6 -0
  14. package/dist/runtime/config.d.ts.map +1 -0
  15. package/dist/runtime/config.js +18 -0
  16. package/dist/runtime/config.js.map +1 -0
  17. package/dist/runtime/content.d.ts +30 -0
  18. package/dist/runtime/content.d.ts.map +1 -0
  19. package/dist/runtime/content.js +48 -0
  20. package/dist/runtime/content.js.map +1 -0
  21. package/dist/runtime/index.d.ts +10 -0
  22. package/dist/runtime/index.d.ts.map +1 -0
  23. package/dist/runtime/index.js +12 -0
  24. package/dist/runtime/index.js.map +1 -0
  25. package/dist/runtime/markers.d.ts +17 -0
  26. package/dist/runtime/markers.d.ts.map +1 -0
  27. package/dist/runtime/markers.js +17 -0
  28. package/dist/runtime/markers.js.map +1 -0
  29. package/dist/runtime/middleware.d.ts +12 -0
  30. package/dist/runtime/middleware.d.ts.map +1 -0
  31. package/dist/runtime/middleware.js +37 -0
  32. package/dist/runtime/middleware.js.map +1 -0
  33. package/dist/runtime/mutations/contracts.d.ts +57 -0
  34. package/dist/runtime/mutations/contracts.d.ts.map +1 -0
  35. package/dist/runtime/mutations/contracts.js +242 -0
  36. package/dist/runtime/mutations/contracts.js.map +1 -0
  37. package/dist/runtime/mutations/engine.d.ts +23 -0
  38. package/dist/runtime/mutations/engine.d.ts.map +1 -0
  39. package/dist/runtime/mutations/engine.js +161 -0
  40. package/dist/runtime/mutations/engine.js.map +1 -0
  41. package/dist/runtime/routes/_helpers.d.ts +6 -0
  42. package/dist/runtime/routes/_helpers.d.ts.map +1 -0
  43. package/dist/runtime/routes/_helpers.js +23 -0
  44. package/dist/runtime/routes/_helpers.js.map +1 -0
  45. package/dist/runtime/routes/admin.d.ts +3 -0
  46. package/dist/runtime/routes/admin.d.ts.map +1 -0
  47. package/dist/runtime/routes/admin.js +110 -0
  48. package/dist/runtime/routes/admin.js.map +1 -0
  49. package/dist/runtime/routes/auth-login.d.ts +4 -0
  50. package/dist/runtime/routes/auth-login.d.ts.map +1 -0
  51. package/dist/runtime/routes/auth-login.js +66 -0
  52. package/dist/runtime/routes/auth-login.js.map +1 -0
  53. package/dist/runtime/routes/auth-logout.d.ts +4 -0
  54. package/dist/runtime/routes/auth-logout.d.ts.map +1 -0
  55. package/dist/runtime/routes/auth-logout.js +51 -0
  56. package/dist/runtime/routes/auth-logout.js.map +1 -0
  57. package/dist/runtime/routes/editor-assets.d.ts +3 -0
  58. package/dist/runtime/routes/editor-assets.d.ts.map +1 -0
  59. package/dist/runtime/routes/editor-assets.js +47 -0
  60. package/dist/runtime/routes/editor-assets.js.map +1 -0
  61. package/dist/runtime/routes/entries.d.ts +3 -0
  62. package/dist/runtime/routes/entries.d.ts.map +1 -0
  63. package/dist/runtime/routes/entries.js +89 -0
  64. package/dist/runtime/routes/entries.js.map +1 -0
  65. package/dist/runtime/routes/history.d.ts +4 -0
  66. package/dist/runtime/routes/history.d.ts.map +1 -0
  67. package/dist/runtime/routes/history.js +56 -0
  68. package/dist/runtime/routes/history.js.map +1 -0
  69. package/dist/runtime/routes/mutate.d.ts +4 -0
  70. package/dist/runtime/routes/mutate.d.ts.map +1 -0
  71. package/dist/runtime/routes/mutate.js +35 -0
  72. package/dist/runtime/routes/mutate.js.map +1 -0
  73. package/dist/runtime/routes/schema.d.ts +3 -0
  74. package/dist/runtime/routes/schema.d.ts.map +1 -0
  75. package/dist/runtime/routes/schema.js +27 -0
  76. package/dist/runtime/routes/schema.js.map +1 -0
  77. package/dist/runtime/routes/studio-home.d.ts +3 -0
  78. package/dist/runtime/routes/studio-home.d.ts.map +1 -0
  79. package/dist/runtime/routes/studio-home.js +174 -0
  80. package/dist/runtime/routes/studio-home.js.map +1 -0
  81. package/dist/runtime/routes/theme-bootstrap.d.ts +4 -0
  82. package/dist/runtime/routes/theme-bootstrap.d.ts.map +1 -0
  83. package/dist/runtime/routes/theme-bootstrap.js +65 -0
  84. package/dist/runtime/routes/theme-bootstrap.js.map +1 -0
  85. package/dist/runtime/routes/theme-factory.d.ts +3 -0
  86. package/dist/runtime/routes/theme-factory.d.ts.map +1 -0
  87. package/dist/runtime/routes/theme-factory.js +142 -0
  88. package/dist/runtime/routes/theme-factory.js.map +1 -0
  89. package/dist/runtime/routes/upload.d.ts +3 -0
  90. package/dist/runtime/routes/upload.d.ts.map +1 -0
  91. package/dist/runtime/routes/upload.js +29 -0
  92. package/dist/runtime/routes/upload.js.map +1 -0
  93. package/dist/runtime/schema/infer-json-schema.d.ts +12 -0
  94. package/dist/runtime/schema/infer-json-schema.d.ts.map +1 -0
  95. package/dist/runtime/schema/infer-json-schema.js +75 -0
  96. package/dist/runtime/schema/infer-json-schema.js.map +1 -0
  97. package/dist/runtime/storage/filesystem-adapter.d.ts +29 -0
  98. package/dist/runtime/storage/filesystem-adapter.d.ts.map +1 -0
  99. package/dist/runtime/storage/filesystem-adapter.js +182 -0
  100. package/dist/runtime/storage/filesystem-adapter.js.map +1 -0
  101. package/dist/runtime/storage/filesystem-upload-handler.d.ts +11 -0
  102. package/dist/runtime/storage/filesystem-upload-handler.d.ts.map +1 -0
  103. package/dist/runtime/storage/filesystem-upload-handler.js +37 -0
  104. package/dist/runtime/storage/filesystem-upload-handler.js.map +1 -0
  105. package/dist/runtime/storage/in-memory-adapter.d.ts +24 -0
  106. package/dist/runtime/storage/in-memory-adapter.d.ts.map +1 -0
  107. package/dist/runtime/storage/in-memory-adapter.js +78 -0
  108. package/dist/runtime/storage/in-memory-adapter.js.map +1 -0
  109. package/dist/runtime/themes/preset-catalog.d.ts +9 -0
  110. package/dist/runtime/themes/preset-catalog.d.ts.map +1 -0
  111. package/dist/runtime/themes/preset-catalog.js +47 -0
  112. package/dist/runtime/themes/preset-catalog.js.map +1 -0
  113. package/dist/runtime/utils.d.ts +6 -0
  114. package/dist/runtime/utils.d.ts.map +1 -0
  115. package/dist/runtime/utils.js +29 -0
  116. package/dist/runtime/utils.js.map +1 -0
  117. package/dist/types.d.ts +28 -0
  118. package/dist/types.d.ts.map +1 -0
  119. package/dist/types.js +2 -0
  120. package/dist/types.js.map +1 -0
  121. package/dist/vite/virtual-config-plugin.d.ts +7 -0
  122. package/dist/vite/virtual-config-plugin.d.ts.map +1 -0
  123. package/dist/vite/virtual-config-plugin.js +25 -0
  124. package/dist/vite/virtual-config-plugin.js.map +1 -0
  125. package/package.json +35 -0
  126. package/static/cms/editor/config.js +6 -0
  127. package/static/cms/editor/guards.js +68 -0
  128. package/static/cms/editor/helpers.js +16 -0
  129. package/static/cms/editor/image-edit.js +148 -0
  130. package/static/cms/editor/image-utils.js +84 -0
  131. package/static/cms/editor/image.css +133 -0
  132. package/static/cms/editor/link-ui.css +143 -0
  133. package/static/cms/editor/linkify.js +55 -0
  134. package/static/cms/editor/panel.css +91 -0
  135. package/static/cms/editor/panel.js +64 -0
  136. package/static/cms/editor/save-queue.js +167 -0
  137. package/static/cms/editor/section-controls/activation.js +10 -0
  138. package/static/cms/editor/section-controls/api.js +88 -0
  139. package/static/cms/editor/section-controls/constants.js +24 -0
  140. package/static/cms/editor/section-controls/index.js +622 -0
  141. package/static/cms/editor/section-controls/model.js +76 -0
  142. package/static/cms/editor/section-controls/mutations.js +112 -0
  143. package/static/cms/editor/section-controls/page-context.js +34 -0
  144. package/static/cms/editor/section-controls/pickers.js +196 -0
  145. package/static/cms/editor/section-controls/reorder.js +92 -0
  146. package/static/cms/editor/section-controls/selection.js +54 -0
  147. package/static/cms/editor/section-controls/spacing-drag.js +83 -0
  148. package/static/cms/editor/section-controls/ui-elements.js +54 -0
  149. package/static/cms/editor/section-controls/utils.js +35 -0
  150. package/static/cms/editor/section-controls.css +349 -0
  151. package/static/cms/editor/section-controls.js +1 -0
  152. package/static/cms/editor/security.js +23 -0
  153. package/static/cms/editor/sync.js +64 -0
  154. package/static/cms/editor/text-edit.js +129 -0
  155. package/static/cms/editor/text-link-interactions.js +191 -0
  156. package/static/cms/editor/toast.css +50 -0
  157. package/static/cms/editor/toast.js +29 -0
  158. package/static/cms/editor/tokens-and-text.css +94 -0
  159. package/static/cms/editor/toolbar.css +261 -0
  160. package/static/cms/editor/toolbar.js +110 -0
  161. package/static/cms/editor.css +10 -0
  162. package/static/cms/editor.js +101 -0
  163. package/static/cms/studio.css +312 -0
@@ -0,0 +1,110 @@
1
+ function getToolbarNavLinks(pagePath) {
2
+ const links = [];
3
+ const headerNav = document.querySelector('#main-header nav');
4
+ if (!headerNav) return links;
5
+
6
+ headerNav.querySelectorAll('a[href]').forEach((a) => {
7
+ const href = a.getAttribute('href');
8
+ const label = a.textContent?.trim();
9
+ if (href && label && href.startsWith('/')) {
10
+ const isActive = pagePath === href || (href !== '/' && pagePath.startsWith(href));
11
+ links.push({ href, label, isActive });
12
+ }
13
+ });
14
+ return links;
15
+ }
16
+
17
+ function renderToolbar(navLinks) {
18
+ const navLinksHtml = navLinks
19
+ .map(
20
+ ({ href, label, isActive }) =>
21
+ `<a href="${href}" class="cms-nav-link ${isActive ? 'active' : ''}">${label}</a>`,
22
+ )
23
+ .join('');
24
+
25
+ const toolbar = document.createElement('div');
26
+ toolbar.className = 'cms-toolbar';
27
+ toolbar.innerHTML = `
28
+ <div class="cms-toolbar-inner">
29
+ <div class="cms-toolbar-left">
30
+ <div class="cms-toolbar-badge">
31
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 9l10 13 10-13L12 2zm0 3.84L18.26 9 12 17.65 5.74 9 12 5.84z"/></svg>
32
+ <span class="cms-toolbar-badge-text">Editor</span>
33
+ </div>
34
+ <div class="cms-toolbar-divider"></div>
35
+ <div class="cms-status-group">
36
+ <span class="cms-status-dot cms-status-idle"></span>
37
+ <span class="cms-status-text">Ready</span>
38
+ </div>
39
+ </div>
40
+ ${navLinksHtml ? `<div class="cms-toolbar-nav">${navLinksHtml}</div>` : ''}
41
+ <div class="cms-toolbar-right">
42
+ <button type="button" class="cms-studio-btn" title="Toggle Content Studio panel">
43
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="9" y1="9" x2="21" y2="9"/></svg>
44
+ Studio
45
+ </button>
46
+ <div class="cms-toolbar-divider"></div>
47
+ <button class="cms-highlight-btn" type="button" title="Show all editable regions">
48
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
49
+ Show All
50
+ </button>
51
+ <div class="cms-toolbar-divider"></div>
52
+ <button class="cms-exit-btn" type="button">
53
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
54
+ Log Out
55
+ </button>
56
+ </div>
57
+ </div>
58
+ `;
59
+ document.body.appendChild(toolbar);
60
+ return toolbar;
61
+ }
62
+
63
+ export function mountToolbar({ showToast, clearDirty, onLogout }) {
64
+ const navLinks = getToolbarNavLinks(window.location.pathname);
65
+ const toolbar = renderToolbar(navLinks);
66
+
67
+ const statusDot = toolbar.querySelector('.cms-status-dot');
68
+ const statusText = toolbar.querySelector('.cms-status-text');
69
+
70
+ function setStatus(state, text) {
71
+ if (statusDot) {
72
+ statusDot.className = `cms-status-dot cms-status-${state}`;
73
+ }
74
+ if (statusText && text) {
75
+ statusText.textContent = text;
76
+ }
77
+ }
78
+
79
+ let highlightActive = false;
80
+ const highlightBtn = toolbar.querySelector('.cms-highlight-btn');
81
+
82
+ highlightBtn?.addEventListener('click', () => {
83
+ highlightActive = !highlightActive;
84
+ document.body.classList.toggle('cms-highlight-all', highlightActive);
85
+ highlightBtn.classList.toggle('cms-highlight-btn-active', highlightActive);
86
+
87
+ const textNode = Array.from(highlightBtn.childNodes).find(
88
+ (n) => n.nodeType === 3 && n.textContent?.trim(),
89
+ );
90
+
91
+ if (highlightActive) {
92
+ if (textNode) textNode.textContent = ' Hide All';
93
+ showToast('Showing all editable regions', 'success');
94
+ } else {
95
+ if (textNode) textNode.textContent = ' Show All';
96
+ }
97
+ });
98
+
99
+ toolbar.querySelector('.cms-exit-btn')?.addEventListener('click', async () => {
100
+ clearDirty();
101
+ await onLogout();
102
+ });
103
+
104
+ const studioButton = toolbar.querySelector('.cms-studio-btn');
105
+ return {
106
+ toolbar,
107
+ studioButton,
108
+ setStatus,
109
+ };
110
+ }
@@ -0,0 +1,10 @@
1
+ /* ========================================================================
2
+ CMS Editor Styles — only loaded when isEditor = true
3
+ ======================================================================== */
4
+ @import "./editor/tokens-and-text.css";
5
+ @import "./editor/image.css";
6
+ @import "./editor/section-controls.css";
7
+ @import "./editor/toolbar.css";
8
+ @import "./editor/toast.css";
9
+ @import "./editor/panel.css";
10
+ @import "./editor/link-ui.css";
@@ -0,0 +1,101 @@
1
+ /**
2
+ * CMS Inline Editor runtime entry.
3
+ *
4
+ * This file is intentionally thin and wires independent controllers:
5
+ * - guards
6
+ * - toolbar
7
+ * - studio panel + iframe sync
8
+ * - text editing
9
+ * - image editing
10
+ */
11
+ import { MOUNT_PATH, API_BASE } from './editor/config.js';
12
+ import { parseCmsAttr, flash } from './editor/helpers.js';
13
+ import { clientLinkify } from './editor/linkify.js';
14
+ import { createSaveField } from './editor/save-queue.js';
15
+ import { compressImage, updateEmblaCarousel } from './editor/image-utils.js';
16
+ import { createToast } from './editor/toast.js';
17
+ import { mountEditorGuards } from './editor/guards.js';
18
+ import { mountToolbar } from './editor/toolbar.js';
19
+ import { mountStudioPanel } from './editor/panel.js';
20
+ import { mountStudioSync } from './editor/sync.js';
21
+ import { mountTextEditors } from './editor/text-edit.js';
22
+ import { mountImageEditors } from './editor/image-edit.js';
23
+ import { mountSectionControls } from './editor/section-controls.js';
24
+
25
+ function redirectToEditorLogin() {
26
+ window.location.href = `${MOUNT_PATH}?redirect=${encodeURIComponent(window.location.pathname)}`;
27
+ }
28
+
29
+ function boot() {
30
+ const state = {
31
+ dirtyEl: null,
32
+ linkPopupEl: null,
33
+ };
34
+
35
+ mountEditorGuards(state);
36
+
37
+ const showToast = createToast();
38
+
39
+ const { studioButton, setStatus } = mountToolbar({
40
+ showToast,
41
+ clearDirty: () => {
42
+ state.dirtyEl = null;
43
+ },
44
+ onLogout: async () => {
45
+ await fetch(`${API_BASE}/auth/logout`, { method: 'POST' });
46
+ window.location.href = '/';
47
+ },
48
+ });
49
+
50
+ const saveField = createSaveField({
51
+ setStatus,
52
+ onUnauthorized: redirectToEditorLogin,
53
+ });
54
+
55
+ const { iframe: studioIframe } = mountStudioPanel({ studioButton });
56
+
57
+ mountStudioSync({
58
+ studioIframe,
59
+ state,
60
+ showToast,
61
+ clientLinkify,
62
+ });
63
+
64
+ const mountInlineEditors = () => {
65
+ mountTextEditors({
66
+ state,
67
+ parseCmsAttr,
68
+ clientLinkify,
69
+ saveField,
70
+ flash,
71
+ showToast,
72
+ });
73
+
74
+ mountImageEditors({
75
+ parseCmsAttr,
76
+ flash,
77
+ compressImage,
78
+ updateEmblaCarousel,
79
+ saveField,
80
+ setStatus,
81
+ showToast,
82
+ onUnauthorized: redirectToEditorLogin,
83
+ });
84
+ };
85
+
86
+ mountSectionControls({
87
+ parseCmsAttr,
88
+ setStatus,
89
+ showToast,
90
+ onUnauthorized: redirectToEditorLogin,
91
+ onCanvasStructureChanged: mountInlineEditors,
92
+ });
93
+
94
+ mountInlineEditors();
95
+ }
96
+
97
+ if (document.readyState === 'loading') {
98
+ document.addEventListener('DOMContentLoaded', boot);
99
+ } else {
100
+ boot();
101
+ }
@@ -0,0 +1,312 @@
1
+ /* ========================================================================
2
+ Content Studio Styles
3
+ ======================================================================== */
4
+
5
+ :root {
6
+ --studio-bg: #0a1128;
7
+ --studio-surface: #0f1a33;
8
+ --studio-surface-hover: #152040;
9
+ --studio-border: rgba(255, 255, 255, 0.06);
10
+ --studio-border-hover: rgba(255, 255, 255, 0.12);
11
+ --studio-gold: #D4AF37;
12
+ --studio-gold-dim: rgba(212, 175, 55, 0.15);
13
+ --studio-gold-hover: rgba(212, 175, 55, 0.25);
14
+ --studio-text: rgba(255, 255, 255, 0.9);
15
+ --studio-text-muted: rgba(255, 255, 255, 0.5);
16
+ --studio-text-dim: rgba(255, 255, 255, 0.3);
17
+ --studio-green: #22c55e;
18
+ --studio-red: #ef4444;
19
+ --studio-blue: #3b82f6;
20
+ --studio-font: 'Poppins', system-ui, -apple-system, sans-serif;
21
+ --studio-font-heading: 'Playfair Display', Georgia, serif;
22
+ }
23
+
24
+ /* ---------------------------------------------------------------------------
25
+ Scrollbar
26
+ --------------------------------------------------------------------------- */
27
+
28
+ .studio-scroll::-webkit-scrollbar {
29
+ width: 6px;
30
+ }
31
+
32
+ .studio-scroll::-webkit-scrollbar-track {
33
+ background: transparent;
34
+ }
35
+
36
+ .studio-scroll::-webkit-scrollbar-thumb {
37
+ background: rgba(255, 255, 255, 0.1);
38
+ border-radius: 3px;
39
+ }
40
+
41
+ .studio-scroll::-webkit-scrollbar-thumb:hover {
42
+ background: rgba(255, 255, 255, 0.2);
43
+ }
44
+
45
+ /* ---------------------------------------------------------------------------
46
+ Drop zone
47
+ --------------------------------------------------------------------------- */
48
+
49
+ .studio-dropzone {
50
+ border: 2px dashed rgba(255, 255, 255, 0.15);
51
+ border-radius: 4px;
52
+ transition: all 0.2s ease;
53
+ }
54
+
55
+ .studio-dropzone:hover,
56
+ .studio-dropzone.drag-over {
57
+ border-color: var(--studio-gold);
58
+ background: var(--studio-gold-dim);
59
+ }
60
+
61
+ /* ---------------------------------------------------------------------------
62
+ Image gallery drag
63
+ --------------------------------------------------------------------------- */
64
+
65
+ .studio-gallery-item {
66
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
67
+ }
68
+
69
+ .studio-gallery-item.dragging {
70
+ opacity: 0.5;
71
+ transform: scale(0.95);
72
+ }
73
+
74
+ .studio-gallery-item.drag-over-item {
75
+ outline: 2px solid var(--studio-gold);
76
+ outline-offset: 2px;
77
+ }
78
+
79
+ /* ---------------------------------------------------------------------------
80
+ Animations
81
+ --------------------------------------------------------------------------- */
82
+
83
+ @keyframes studio-fade-in {
84
+ from { opacity: 0; transform: translateY(8px); }
85
+ to { opacity: 1; transform: translateY(0); }
86
+ }
87
+
88
+ @keyframes studio-spin {
89
+ to { transform: rotate(360deg); }
90
+ }
91
+
92
+ .studio-fade-in {
93
+ animation: studio-fade-in 0.3s ease forwards;
94
+ }
95
+
96
+ .studio-spinner {
97
+ animation: studio-spin 0.8s linear infinite;
98
+ border-color: var(--studio-gold);
99
+ border-top-color: transparent;
100
+ }
101
+
102
+ /* ---------------------------------------------------------------------------
103
+ Upload progress
104
+ --------------------------------------------------------------------------- */
105
+
106
+ .studio-upload-progress {
107
+ height: 3px;
108
+ background: rgba(255, 255, 255, 0.1);
109
+ border-radius: 2px;
110
+ overflow: hidden;
111
+ }
112
+
113
+ .studio-upload-progress-bar {
114
+ height: 100%;
115
+ background: var(--studio-gold);
116
+ transition: width 0.3s ease;
117
+ }
118
+
119
+ /* ---------------------------------------------------------------------------
120
+ Form inputs
121
+ --------------------------------------------------------------------------- */
122
+
123
+ .studio-input {
124
+ width: 100%;
125
+ padding: 10px 14px;
126
+ background: rgba(255, 255, 255, 0.04);
127
+ border: 1px solid var(--studio-border);
128
+ color: var(--studio-text);
129
+ font-family: var(--studio-font);
130
+ font-size: 13px;
131
+ transition: border-color 0.2s, background 0.2s;
132
+ outline: none;
133
+ }
134
+
135
+ .studio-input:focus {
136
+ border-color: var(--studio-gold);
137
+ background: rgba(255, 255, 255, 0.06);
138
+ }
139
+
140
+ .studio-input::placeholder {
141
+ color: var(--studio-text-dim);
142
+ }
143
+
144
+ textarea.studio-input {
145
+ resize: vertical;
146
+ min-height: 80px;
147
+ }
148
+
149
+ /* ---------------------------------------------------------------------------
150
+ Toggle switch
151
+ --------------------------------------------------------------------------- */
152
+
153
+ .studio-toggle {
154
+ position: relative;
155
+ width: 40px;
156
+ height: 22px;
157
+ background: rgba(255, 255, 255, 0.1);
158
+ border-radius: 11px;
159
+ cursor: pointer;
160
+ transition: background 0.2s;
161
+ border: none;
162
+ padding: 0;
163
+ }
164
+
165
+ .studio-toggle.active {
166
+ background: var(--studio-gold);
167
+ }
168
+
169
+ .studio-toggle::after {
170
+ content: '';
171
+ position: absolute;
172
+ top: 2px;
173
+ left: 2px;
174
+ width: 18px;
175
+ height: 18px;
176
+ background: white;
177
+ border-radius: 50%;
178
+ transition: transform 0.2s;
179
+ }
180
+
181
+ .studio-toggle.active::after {
182
+ transform: translateX(18px);
183
+ }
184
+
185
+ /* ---------------------------------------------------------------------------
186
+ Tag list editor
187
+ --------------------------------------------------------------------------- */
188
+
189
+ .studio-tag {
190
+ display: inline-flex;
191
+ align-items: center;
192
+ gap: 6px;
193
+ padding: 4px 10px;
194
+ background: rgba(255, 255, 255, 0.06);
195
+ border: 1px solid var(--studio-border);
196
+ font-size: 12px;
197
+ color: var(--studio-text-muted);
198
+ }
199
+
200
+ .studio-tag button {
201
+ background: none;
202
+ border: none;
203
+ color: var(--studio-text-dim);
204
+ cursor: pointer;
205
+ padding: 0;
206
+ font-size: 14px;
207
+ line-height: 1;
208
+ }
209
+
210
+ .studio-tag button:hover {
211
+ color: var(--studio-red);
212
+ }
213
+
214
+ /* ---------------------------------------------------------------------------
215
+ Header nav links
216
+ --------------------------------------------------------------------------- */
217
+
218
+ .studio-nav-link {
219
+ padding: 4px 12px;
220
+ font-size: 10px;
221
+ letter-spacing: 0.1em;
222
+ text-transform: uppercase;
223
+ text-decoration: none;
224
+ color: var(--studio-text-dim);
225
+ transition: color 0.15s, background 0.15s;
226
+ border-radius: 2px;
227
+ }
228
+
229
+ .studio-nav-link:hover {
230
+ color: var(--studio-text-muted);
231
+ background: rgba(255, 255, 255, 0.04);
232
+ }
233
+
234
+ .studio-nav-link.active {
235
+ color: var(--studio-gold);
236
+ background: rgba(212, 175, 55, 0.1);
237
+ }
238
+
239
+ /* ---------------------------------------------------------------------------
240
+ Component classes — reusable patterns consumed via class names
241
+ --------------------------------------------------------------------------- */
242
+
243
+ /* Buttons */
244
+ .studio-btn-gold {
245
+ padding: 8px 16px;
246
+ font-size: 12px;
247
+ font-weight: 600;
248
+ text-transform: uppercase;
249
+ letter-spacing: 0.1em;
250
+ transition: all 0.2s;
251
+ background: var(--studio-gold);
252
+ color: #0a1128;
253
+ border: none;
254
+ }
255
+
256
+ .studio-btn-ghost {
257
+ padding: 8px 16px;
258
+ font-size: 12px;
259
+ border: 1px solid var(--studio-border);
260
+ transition: color 0.15s, border-color 0.15s;
261
+ color: var(--studio-text-muted);
262
+ background: transparent;
263
+ cursor: pointer;
264
+ }
265
+
266
+ /* Cards */
267
+ .studio-card {
268
+ background: var(--studio-surface);
269
+ border: 1px solid var(--studio-border);
270
+ border-radius: 2px;
271
+ }
272
+
273
+ /* Labels */
274
+ .studio-label {
275
+ display: block;
276
+ font-size: 10px;
277
+ font-weight: 500;
278
+ text-transform: uppercase;
279
+ letter-spacing: 0.1em;
280
+ color: var(--studio-text-muted);
281
+ }
282
+
283
+ /* Save button states (JS class toggling) */
284
+ .studio-save-dirty {
285
+ background: var(--studio-gold);
286
+ color: #0a1128;
287
+ cursor: pointer;
288
+ }
289
+
290
+ .studio-save-clean {
291
+ background: rgba(255, 255, 255, 0.06);
292
+ color: var(--studio-text-dim);
293
+ cursor: default;
294
+ }
295
+
296
+ .studio-save-saving {
297
+ background: rgba(255, 255, 255, 0.06);
298
+ color: var(--studio-text-dim);
299
+ cursor: default;
300
+ opacity: 0.7;
301
+ }
302
+
303
+ /* Field error state */
304
+ .studio-field-error {
305
+ border-color: var(--studio-red) !important;
306
+ }
307
+
308
+ /* Corner accent decorations */
309
+ .studio-corner-accent {
310
+ background: var(--studio-gold);
311
+ opacity: 0.4;
312
+ }