@astuteo/breakout-grid 5.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 +21 -0
- package/README.md +223 -0
- package/breakout-grid-visualizer-lite.js +659 -0
- package/breakout-grid-visualizer.js +2523 -0
- package/craft-integration.twig +58 -0
- package/dist/_objects.breakout-grid.css +656 -0
- package/package.json +65 -0
|
@@ -0,0 +1,2523 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
"use strict";
|
|
3
|
+
const VERSION = `v${"5.1.0"}`;
|
|
4
|
+
const LOREM_CONTENT = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.
|
|
5
|
+
|
|
6
|
+
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet.`;
|
|
7
|
+
const GRID_AREAS = [
|
|
8
|
+
{ name: "full", label: "Full", className: ".col-full", color: "rgba(239, 68, 68, 0.25)", borderColor: "rgb(239, 68, 68)" },
|
|
9
|
+
{ name: "full-limit", label: "Full Limit", className: ".col-full-limit", color: "rgba(220, 38, 38, 0.25)", borderColor: "rgb(220, 38, 38)" },
|
|
10
|
+
{ name: "feature", label: "Feature", className: ".col-feature", color: "rgba(6, 182, 212, 0.25)", borderColor: "rgb(6, 182, 212)" },
|
|
11
|
+
{ name: "popout", label: "Popout", className: ".col-popout", color: "rgba(34, 197, 94, 0.25)", borderColor: "rgb(34, 197, 94)" },
|
|
12
|
+
{ name: "content", label: "Content", className: ".col-content", color: "rgba(168, 85, 247, 0.25)", borderColor: "rgb(168, 85, 247)" }
|
|
13
|
+
];
|
|
14
|
+
const CONFIG_OPTIONS = {
|
|
15
|
+
// Base measurements
|
|
16
|
+
baseGap: { value: "1rem", desc: "Minimum gap between columns. Use rem.", cssVar: "--config-base-gap", liveVar: "--base-gap" },
|
|
17
|
+
maxGap: { value: "15rem", desc: "Maximum gap cap for ultra-wide. Use rem.", cssVar: "--config-max-gap", liveVar: "--max-gap" },
|
|
18
|
+
contentMin: { value: "53rem", desc: "Min width for content column (~848px). Use rem.", cssVar: "--config-content-min", liveVar: "--content-min" },
|
|
19
|
+
contentMax: { value: "61rem", desc: "Max width for content column (~976px). Use rem.", cssVar: "--config-content-max", liveVar: "--content-max" },
|
|
20
|
+
contentBase: { value: "75vw", desc: "Preferred width for content (fluid). Use vw.", cssVar: "--config-content-base", liveVar: "--content-base" },
|
|
21
|
+
// Track widths
|
|
22
|
+
popoutWidth: { value: "5rem", desc: "Popout extends beyond content. Use rem.", cssVar: "--config-popout", liveVar: null },
|
|
23
|
+
featureMin: { value: "0rem", desc: "Minimum feature track width (floor)", cssVar: "--config-feature-min", liveVar: null },
|
|
24
|
+
featureScale: { value: "12vw", desc: "Fluid feature track scaling", cssVar: "--config-feature-scale", liveVar: null },
|
|
25
|
+
featureMax: { value: "12rem", desc: "Maximum feature track width (ceiling)", cssVar: "--config-feature-max", liveVar: null },
|
|
26
|
+
fullLimit: { value: "115rem", desc: "Max width for col-full-limit. Use rem.", cssVar: "--config-full-limit", liveVar: "--full-limit" },
|
|
27
|
+
// Default column
|
|
28
|
+
defaultCol: { value: "content", desc: "Default column when no col-* class", type: "select", options: ["content", "popout", "feature", "full"], cssVar: "--config-default-col" }
|
|
29
|
+
};
|
|
30
|
+
const GAP_SCALE_OPTIONS = {
|
|
31
|
+
default: { value: "4vw", desc: "Mobile/default gap scaling. Use vw.", cssVar: "--config-gap-scale-default" },
|
|
32
|
+
lg: { value: "5vw", desc: "Large screens (1024px+). Use vw.", cssVar: "--config-gap-scale-lg" },
|
|
33
|
+
xl: { value: "6vw", desc: "Extra large (1280px+). Use vw.", cssVar: "--config-gap-scale-xl" }
|
|
34
|
+
};
|
|
35
|
+
const BREAKOUT_OPTIONS = {
|
|
36
|
+
min: { value: "1rem", desc: "Minimum breakout padding (floor)", cssVar: "--config-breakout-min" },
|
|
37
|
+
scale: { value: "5vw", desc: "Fluid breakout scaling", cssVar: "--config-breakout-scale" }
|
|
38
|
+
// max is popoutWidth
|
|
39
|
+
};
|
|
40
|
+
const BREAKPOINT_OPTIONS = {
|
|
41
|
+
lg: { value: "1024", desc: "Large breakpoint (px)", cssVar: "--config-breakpoint-lg" },
|
|
42
|
+
xl: { value: "1280", desc: "Extra large breakpoint (px)", cssVar: "--config-breakpoint-xl" }
|
|
43
|
+
};
|
|
44
|
+
function createInitialState() {
|
|
45
|
+
return {
|
|
46
|
+
// UI State
|
|
47
|
+
isVisible: false,
|
|
48
|
+
showLabels: true,
|
|
49
|
+
showClassNames: true,
|
|
50
|
+
showMeasurements: true,
|
|
51
|
+
showPixelWidths: false,
|
|
52
|
+
showGapPadding: false,
|
|
53
|
+
showBreakoutPadding: false,
|
|
54
|
+
showAdvanced: false,
|
|
55
|
+
showLoremIpsum: false,
|
|
56
|
+
showEditor: false,
|
|
57
|
+
showDiagram: false,
|
|
58
|
+
editMode: false,
|
|
59
|
+
viewportWidth: window.innerWidth,
|
|
60
|
+
selectedArea: null,
|
|
61
|
+
hoveredArea: null,
|
|
62
|
+
editValues: {},
|
|
63
|
+
copySuccess: false,
|
|
64
|
+
configCopied: false,
|
|
65
|
+
editorPos: { x: 20, y: 100 },
|
|
66
|
+
isDragging: false,
|
|
67
|
+
dragOffset: { x: 0, y: 0 },
|
|
68
|
+
// Column resize drag state
|
|
69
|
+
resizingColumn: null,
|
|
70
|
+
resizeStartX: 0,
|
|
71
|
+
resizeStartValue: 0,
|
|
72
|
+
// Panel collapse state
|
|
73
|
+
controlPanelCollapsed: false,
|
|
74
|
+
configEditorCollapsed: false,
|
|
75
|
+
// Computed column widths in pixels (pre-initialized for reactivity)
|
|
76
|
+
columnWidths: {
|
|
77
|
+
full: 0,
|
|
78
|
+
"full-limit": 0,
|
|
79
|
+
feature: 0,
|
|
80
|
+
popout: 0,
|
|
81
|
+
content: 0,
|
|
82
|
+
center: 0
|
|
83
|
+
},
|
|
84
|
+
// Current breakpoint for gap scale (mobile, lg, xl)
|
|
85
|
+
currentBreakpoint: "mobile",
|
|
86
|
+
// Spacing panel state
|
|
87
|
+
spacingPanelCollapsed: false,
|
|
88
|
+
spacingPanelPos: { x: 16, y: 16 },
|
|
89
|
+
isDraggingSpacing: false,
|
|
90
|
+
dragOffsetSpacing: { x: 0, y: 0 },
|
|
91
|
+
// Restore config modal
|
|
92
|
+
showRestoreModal: false,
|
|
93
|
+
restoreInput: "",
|
|
94
|
+
restoreError: null,
|
|
95
|
+
// Section copy feedback
|
|
96
|
+
sectionCopied: null
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const BUILD_VERSION = "5.1.0";
|
|
100
|
+
function generateCSSExport(c, version = BUILD_VERSION) {
|
|
101
|
+
var _a, _b, _c, _d, _e;
|
|
102
|
+
const VERSION2 = version;
|
|
103
|
+
const breakoutMin = c.breakoutMin || "1rem";
|
|
104
|
+
const breakoutScale = c.breakoutScale || "5vw";
|
|
105
|
+
const breakpointLg = ((_a = c.breakpoints) == null ? void 0 : _a.lg) || "1024";
|
|
106
|
+
const breakpointXl = ((_b = c.breakpoints) == null ? void 0 : _b.xl) || "1280";
|
|
107
|
+
return `/**
|
|
108
|
+
* Breakout Grid - Objects Layer (ITCSS)
|
|
109
|
+
* Version: ${VERSION2}
|
|
110
|
+
*
|
|
111
|
+
* Documentation: https://github.com/astuteo-llc/breakout-grid
|
|
112
|
+
*
|
|
113
|
+
* ============================================================================
|
|
114
|
+
* TABLE OF CONTENTS
|
|
115
|
+
* ============================================================================
|
|
116
|
+
*
|
|
117
|
+
* CONFIGURATION
|
|
118
|
+
* - Configuration Variables ........... Customizable :root variables
|
|
119
|
+
* - Computed Values ................... Auto-calculated (do not edit)
|
|
120
|
+
*
|
|
121
|
+
* GRID CONTAINERS
|
|
122
|
+
* - Grid Container - Main ............. .grid-cols-breakout
|
|
123
|
+
* - Subgrid ........................... .grid-cols-breakout-subgrid
|
|
124
|
+
* - Left/Right Aligned Variants ....... .grid-cols-{area}-{left|right}
|
|
125
|
+
* - Breakout Modifiers ................ .breakout-to-{content|popout|feature}
|
|
126
|
+
* - Breakout None ..................... .breakout-none, .breakout-none-flex
|
|
127
|
+
*
|
|
128
|
+
* COLUMN UTILITIES
|
|
129
|
+
* - Basic ............................. .col-{full|feature|popout|content|center}
|
|
130
|
+
* - Start/End ......................... .col-start-*, .col-end-*
|
|
131
|
+
* - Left/Right Spans .................. .col-*-left, .col-*-right
|
|
132
|
+
* - Advanced Spans .................... .col-*-to-*
|
|
133
|
+
* - Full Limit ........................ .col-full-limit
|
|
134
|
+
*
|
|
135
|
+
* SPACING UTILITIES
|
|
136
|
+
* - Padding ........................... .p-breakout, .p-gap, .p-*-to-content
|
|
137
|
+
* - Margins ........................... .m-breakout, .m-gap, .-m-*
|
|
138
|
+
*
|
|
139
|
+
* ============================================================================
|
|
140
|
+
* INTEGRATION (ITCSS + Tailwind v4)
|
|
141
|
+
* ============================================================================
|
|
142
|
+
*
|
|
143
|
+
* Add this file to your Objects layer. In your main CSS file:
|
|
144
|
+
*
|
|
145
|
+
* @import 'tailwindcss';
|
|
146
|
+
*
|
|
147
|
+
* @import './_settings.fonts.css';
|
|
148
|
+
* @import './_objects.breakout-grid.css'; <-- Add here (Objects layer)
|
|
149
|
+
* @import './_utilities.global.css';
|
|
150
|
+
*
|
|
151
|
+
* @layer components {
|
|
152
|
+
* @import './_components.hero.css';
|
|
153
|
+
* ...
|
|
154
|
+
* }
|
|
155
|
+
*
|
|
156
|
+
* ============================================================================
|
|
157
|
+
* QUICK START
|
|
158
|
+
* ============================================================================
|
|
159
|
+
*
|
|
160
|
+
* <main class="grid-cols-breakout">
|
|
161
|
+
* <article class="col-content">Reading width</article>
|
|
162
|
+
* <figure class="col-feature">Wider for images</figure>
|
|
163
|
+
* <div class="col-full">Edge to edge</div>
|
|
164
|
+
* </main>
|
|
165
|
+
*
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
/* ============================================================================
|
|
169
|
+
CONFIGURATION VARIABLES
|
|
170
|
+
============================================================================
|
|
171
|
+
To restore this grid in the visualizer, copy from here to END CONFIGURATION.
|
|
172
|
+
Paste into the "Restore" dialog at:
|
|
173
|
+
https://github.com/astuteo-llc/breakout-grid
|
|
174
|
+
============================================================================ */
|
|
175
|
+
|
|
176
|
+
:root {
|
|
177
|
+
/* Content (text width) */
|
|
178
|
+
--content-min: ${c.contentMin};
|
|
179
|
+
--content-base: ${c.contentBase};
|
|
180
|
+
--content-max: ${c.contentMax};
|
|
181
|
+
|
|
182
|
+
/* Default column for children without col-* class */
|
|
183
|
+
--default-col: ${c.defaultCol || "content"};
|
|
184
|
+
|
|
185
|
+
/* Track widths */
|
|
186
|
+
--popout-width: ${c.popoutWidth};
|
|
187
|
+
--full-limit: ${c.fullLimit};
|
|
188
|
+
|
|
189
|
+
/* Feature track */
|
|
190
|
+
--feature-min: ${c.featureMin};
|
|
191
|
+
--feature-scale: ${c.featureScale};
|
|
192
|
+
--feature-max: ${c.featureMax};
|
|
193
|
+
|
|
194
|
+
/* Outer margins */
|
|
195
|
+
--base-gap: ${c.baseGap};
|
|
196
|
+
--max-gap: ${c.maxGap};
|
|
197
|
+
|
|
198
|
+
/* Responsive scale */
|
|
199
|
+
--gap-scale-default: ${((_c = c.gapScale) == null ? void 0 : _c.default) || "4vw"};
|
|
200
|
+
--gap-scale-lg: ${((_d = c.gapScale) == null ? void 0 : _d.lg) || "5vw"};
|
|
201
|
+
--gap-scale-xl: ${((_e = c.gapScale) == null ? void 0 : _e.xl) || "6vw"};
|
|
202
|
+
|
|
203
|
+
/* Breakout padding */
|
|
204
|
+
--breakout-min: ${breakoutMin};
|
|
205
|
+
--breakout-scale: ${breakoutScale};
|
|
206
|
+
|
|
207
|
+
/* Breakpoints (used in media queries below) */
|
|
208
|
+
/* --breakpoint-lg: ${breakpointLg}px; */
|
|
209
|
+
/* --breakpoint-xl: ${breakpointXl}px; */
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ============================================================================
|
|
213
|
+
END CONFIGURATION
|
|
214
|
+
============================================================================ */
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
/* ============================================================================
|
|
218
|
+
COMPUTED VALUES - DO NOT EDIT
|
|
219
|
+
============================================================================
|
|
220
|
+
These are calculated from the customizable variables above.
|
|
221
|
+
Editing these directly will break the grid calculations.
|
|
222
|
+
============================================================================ */
|
|
223
|
+
|
|
224
|
+
:root {
|
|
225
|
+
/* Responsive gap: scales between base and max based on viewport */
|
|
226
|
+
--gap: clamp(var(--base-gap), var(--gap-scale-default), var(--max-gap));
|
|
227
|
+
|
|
228
|
+
/* Computed gap: larger value for full-width spacing */
|
|
229
|
+
--computed-gap: max(var(--gap), calc((100vw - var(--content)) / 10));
|
|
230
|
+
|
|
231
|
+
/* Content width: fluid between min/max, respects gap on both sides */
|
|
232
|
+
--content: min(clamp(var(--content-min), var(--content-base), var(--content-max)), 100% - var(--gap) * 2);
|
|
233
|
+
|
|
234
|
+
/* Content inset: for left/right aligned grids (single gap) */
|
|
235
|
+
--content-inset: min(clamp(var(--content-min), var(--content-base), var(--content-max)), calc(100% - var(--gap)));
|
|
236
|
+
|
|
237
|
+
/* Half content: used for center alignment */
|
|
238
|
+
--content-half: calc(var(--content) / 2);
|
|
239
|
+
|
|
240
|
+
/* Track definitions for grid-template-columns */
|
|
241
|
+
--full: minmax(var(--gap), 1fr);
|
|
242
|
+
--feature: minmax(0, clamp(var(--feature-min), var(--feature-scale), var(--feature-max)));
|
|
243
|
+
--popout: minmax(0, var(--popout-width));
|
|
244
|
+
|
|
245
|
+
/* Alignment padding: for aligning content inside wider columns */
|
|
246
|
+
--breakout-padding: clamp(var(--breakout-min), var(--breakout-scale), var(--popout-width));
|
|
247
|
+
--popout-to-content: clamp(var(--breakout-min), var(--breakout-scale), var(--popout-width));
|
|
248
|
+
--feature-to-content: calc(clamp(var(--feature-min), var(--feature-scale), var(--feature-max)) + var(--popout-width));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* Responsive gap scaling */
|
|
252
|
+
@media (min-width: ${breakpointLg}px) {
|
|
253
|
+
:root {
|
|
254
|
+
--gap: clamp(var(--base-gap), var(--gap-scale-lg), var(--max-gap));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@media (min-width: ${breakpointXl}px) {
|
|
259
|
+
:root {
|
|
260
|
+
--gap: clamp(var(--base-gap), var(--gap-scale-xl), var(--max-gap));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* ========================================
|
|
265
|
+
Grid Container - Main
|
|
266
|
+
========================================
|
|
267
|
+
|
|
268
|
+
The primary grid container. Apply to any element that should use
|
|
269
|
+
the breakout grid system. All direct children default to the
|
|
270
|
+
content column unless given a col-* class.
|
|
271
|
+
|
|
272
|
+
Basic usage:
|
|
273
|
+
<main class="grid-cols-breakout">
|
|
274
|
+
<article>Default content width</article>
|
|
275
|
+
<figure class="col-feature">Wider for images</figure>
|
|
276
|
+
<div class="col-full">Edge to edge</div>
|
|
277
|
+
</main>
|
|
278
|
+
*/
|
|
279
|
+
.grid-cols-breakout {
|
|
280
|
+
display: grid;
|
|
281
|
+
grid-template-columns:
|
|
282
|
+
[full-start] var(--full)
|
|
283
|
+
[feature-start] var(--feature)
|
|
284
|
+
[popout-start] var(--popout)
|
|
285
|
+
[content-start] var(--content-half) [center-start center-end] var(--content-half) [content-end]
|
|
286
|
+
var(--popout) [popout-end]
|
|
287
|
+
var(--feature) [feature-end]
|
|
288
|
+
var(--full) [full-end];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* Default column for direct children without explicit col-* class */
|
|
292
|
+
[class*='grid-cols-breakout'] > *:not([class*='col-']),
|
|
293
|
+
[class*='grid-cols-feature'] > *:not([class*='col-']),
|
|
294
|
+
[class*='grid-cols-popout'] > *:not([class*='col-']),
|
|
295
|
+
[class*='grid-cols-content'] > *:not([class*='col-']) {
|
|
296
|
+
grid-column: var(--default-col, content);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* ----------------------------------------
|
|
300
|
+
Subgrid - Nested Alignment
|
|
301
|
+
----------------------------------------
|
|
302
|
+
|
|
303
|
+
Use subgrid when you need children of a spanning element to align
|
|
304
|
+
with the parent grid's tracks. The child inherits the parent's
|
|
305
|
+
column lines.
|
|
306
|
+
|
|
307
|
+
Example - Card grid inside a feature-width container:
|
|
308
|
+
<div class="col-feature grid-cols-breakout-subgrid">
|
|
309
|
+
<h2 class="col-content">Title aligns with content</h2>
|
|
310
|
+
<div class="col-feature">Full width of parent</div>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
Browser support: ~93% (check caniuse.com/css-subgrid)
|
|
314
|
+
*/
|
|
315
|
+
.grid-cols-breakout-subgrid {
|
|
316
|
+
display: grid;
|
|
317
|
+
grid-template-columns: subgrid;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* ========================================
|
|
321
|
+
Grid Container - Left/Right Aligned Variants
|
|
322
|
+
========================================
|
|
323
|
+
|
|
324
|
+
Use these when content should anchor to one side instead of centering.
|
|
325
|
+
Common for asymmetric layouts, sidebars, or split-screen designs.
|
|
326
|
+
|
|
327
|
+
Left variants: Content anchors to left edge, right side has outer tracks
|
|
328
|
+
Right variants: Content anchors to right edge, left side has outer tracks
|
|
329
|
+
|
|
330
|
+
Example - Image left, text right:
|
|
331
|
+
<section class="grid-cols-feature-left">
|
|
332
|
+
<figure class="col-feature">Image anchored left</figure>
|
|
333
|
+
<div class="col-content">Text in content area</div>
|
|
334
|
+
</section>
|
|
335
|
+
|
|
336
|
+
Example - Sidebar layout:
|
|
337
|
+
<div class="grid-cols-content-right">
|
|
338
|
+
<aside class="col-full">Sidebar fills left</aside>
|
|
339
|
+
<main class="col-content">Main content right-aligned</main>
|
|
340
|
+
</div>
|
|
341
|
+
*/
|
|
342
|
+
.grid-cols-feature-left {
|
|
343
|
+
display: grid;
|
|
344
|
+
grid-template-columns:
|
|
345
|
+
[full-start] var(--full)
|
|
346
|
+
[feature-start] var(--feature)
|
|
347
|
+
[popout-start] var(--popout)
|
|
348
|
+
[content-start] var(--content-inset) [content-end]
|
|
349
|
+
var(--popout) [popout-end]
|
|
350
|
+
var(--feature) [feature-end full-end];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.grid-cols-popout-left {
|
|
354
|
+
display: grid;
|
|
355
|
+
grid-template-columns:
|
|
356
|
+
[full-start] var(--full)
|
|
357
|
+
[feature-start] var(--feature)
|
|
358
|
+
[popout-start] var(--popout)
|
|
359
|
+
[content-start] var(--content-inset) [content-end]
|
|
360
|
+
var(--popout) [popout-end full-end];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.grid-cols-content-left {
|
|
364
|
+
display: grid;
|
|
365
|
+
grid-template-columns:
|
|
366
|
+
[full-start] var(--full)
|
|
367
|
+
[feature-start] var(--feature)
|
|
368
|
+
[popout-start] var(--popout)
|
|
369
|
+
[content-start] var(--content-inset) [content-end full-end];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* ========================================
|
|
373
|
+
Grid Container - Right Aligned Variants
|
|
374
|
+
======================================== */
|
|
375
|
+
.grid-cols-feature-right {
|
|
376
|
+
display: grid;
|
|
377
|
+
grid-template-columns:
|
|
378
|
+
[full-start feature-start] var(--feature)
|
|
379
|
+
[popout-start] var(--popout)
|
|
380
|
+
[content-start] var(--content-inset) [content-end]
|
|
381
|
+
var(--popout) [popout-end]
|
|
382
|
+
var(--feature) [feature-end]
|
|
383
|
+
var(--full) [full-end];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.grid-cols-popout-right {
|
|
387
|
+
display: grid;
|
|
388
|
+
grid-template-columns:
|
|
389
|
+
[full-start popout-start] var(--popout)
|
|
390
|
+
[content-start] var(--content-inset) [content-end]
|
|
391
|
+
var(--popout) [popout-end]
|
|
392
|
+
var(--feature) [feature-end]
|
|
393
|
+
var(--full) [full-end];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.grid-cols-content-right {
|
|
397
|
+
display: grid;
|
|
398
|
+
grid-template-columns:
|
|
399
|
+
[full-start content-start] var(--content-inset) [content-end]
|
|
400
|
+
var(--popout) [popout-end]
|
|
401
|
+
var(--feature) [feature-end]
|
|
402
|
+
var(--full) [full-end];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* ========================================
|
|
406
|
+
Breakout Modifiers (for nested grids)
|
|
407
|
+
========================================
|
|
408
|
+
|
|
409
|
+
When you nest a grid inside another element (like inside col-feature),
|
|
410
|
+
the nested grid doesn't know about the parent's constraints. Use these
|
|
411
|
+
modifiers to "reset" the grid to fit its container.
|
|
412
|
+
|
|
413
|
+
breakout-to-content: Collapses all tracks - nested grid fills container
|
|
414
|
+
breakout-to-popout: Keeps popout tracks, collapses feature/full
|
|
415
|
+
breakout-to-feature: Keeps feature+popout tracks, collapses full
|
|
416
|
+
|
|
417
|
+
Example - Full-width hero with nested content grid:
|
|
418
|
+
<div class="col-full bg-blue-500">
|
|
419
|
+
<div class="grid-cols-breakout breakout-to-content">
|
|
420
|
+
<h1>This h1 fills the blue container</h1>
|
|
421
|
+
<p class="col-content">But content still works!</p>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
424
|
+
|
|
425
|
+
Example - Feature-width card with internal grid:
|
|
426
|
+
<article class="col-feature">
|
|
427
|
+
<div class="grid-cols-breakout breakout-to-feature">
|
|
428
|
+
<img class="col-feature">Full width of card</img>
|
|
429
|
+
<p class="col-content">Padded text inside</p>
|
|
430
|
+
</div>
|
|
431
|
+
</article>
|
|
432
|
+
*/
|
|
433
|
+
.grid-cols-breakout.breakout-to-content {
|
|
434
|
+
grid-template-columns: [full-start feature-start popout-start content-start center-start] minmax(0, 1fr) [center-end content-end popout-end feature-end full-end];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.grid-cols-breakout.breakout-to-popout {
|
|
438
|
+
grid-template-columns: [full-start feature-start popout-start] var(--popout) [content-start center-start] minmax(0, 1fr) [center-end content-end] var(--popout) [popout-end feature-end full-end];
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.grid-cols-breakout.breakout-to-feature {
|
|
442
|
+
grid-template-columns: [full-start feature-start] var(--feature) [popout-start] var(--popout) [content-start center-start] minmax(0, 1fr) [center-end content-end] var(--popout) [popout-end] var(--feature) [feature-end full-end];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* ----------------------------------------
|
|
446
|
+
Breakout None - Disable Grid
|
|
447
|
+
----------------------------------------
|
|
448
|
+
|
|
449
|
+
Use when you need to escape the grid entirely. Useful for:
|
|
450
|
+
- Sidebar layouts where one column shouldn't use grid
|
|
451
|
+
- Components that manage their own layout
|
|
452
|
+
- CMS blocks that shouldn't inherit grid behavior
|
|
453
|
+
|
|
454
|
+
Example - Two-column layout with sidebar:
|
|
455
|
+
<div class="grid grid-cols-[300px_1fr]">
|
|
456
|
+
<aside class="breakout-none">Sidebar - no grid here</aside>
|
|
457
|
+
<main class="grid-cols-breakout">Main content uses grid</main>
|
|
458
|
+
</div>
|
|
459
|
+
*/
|
|
460
|
+
.breakout-none { display: block; }
|
|
461
|
+
.breakout-none-flex { display: flex; }
|
|
462
|
+
.breakout-none-grid { display: grid; }
|
|
463
|
+
|
|
464
|
+
/* ========================================
|
|
465
|
+
Column Utilities - Basic
|
|
466
|
+
========================================
|
|
467
|
+
|
|
468
|
+
Place elements in specific grid tracks. These are the core utilities
|
|
469
|
+
you'll use most often.
|
|
470
|
+
|
|
471
|
+
col-full: Edge to edge (viewport width minus gap)
|
|
472
|
+
col-feature: Wide content (images, videos, heroes)
|
|
473
|
+
col-popout: Slightly wider than content (pull quotes, callouts)
|
|
474
|
+
col-content: Standard reading width (articles, text)
|
|
475
|
+
col-center: Centered within content (rare, for precise centering)
|
|
476
|
+
*/
|
|
477
|
+
.col-full { grid-column: full; }
|
|
478
|
+
.col-feature { grid-column: feature; }
|
|
479
|
+
.col-popout { grid-column: popout; }
|
|
480
|
+
.col-content { grid-column: content; }
|
|
481
|
+
.col-center { grid-column: center; }
|
|
482
|
+
|
|
483
|
+
/* Backward compatibility: col-narrow maps to content */
|
|
484
|
+
.col-narrow { grid-column: content; }
|
|
485
|
+
|
|
486
|
+
/* ========================================
|
|
487
|
+
Column Utilities - Start/End
|
|
488
|
+
========================================
|
|
489
|
+
|
|
490
|
+
Fine-grained control for custom spans. Combine start and end
|
|
491
|
+
utilities to create any span you need.
|
|
492
|
+
|
|
493
|
+
Example - Span from popout to feature on right:
|
|
494
|
+
<div class="col-start-popout col-end-feature">
|
|
495
|
+
Custom span
|
|
496
|
+
</div>
|
|
497
|
+
*/
|
|
498
|
+
.col-start-full { grid-column-start: full-start; }
|
|
499
|
+
.col-start-feature { grid-column-start: feature-start; }
|
|
500
|
+
.col-start-popout { grid-column-start: popout-start; }
|
|
501
|
+
.col-start-content { grid-column-start: content-start; }
|
|
502
|
+
.col-start-center { grid-column-start: center-start; }
|
|
503
|
+
|
|
504
|
+
/* Backward compatibility */
|
|
505
|
+
.col-start-narrow { grid-column-start: content-start; }
|
|
506
|
+
|
|
507
|
+
.col-end-full { grid-column-end: full-end; }
|
|
508
|
+
.col-end-feature { grid-column-end: feature-end; }
|
|
509
|
+
.col-end-popout { grid-column-end: popout-end; }
|
|
510
|
+
.col-end-content { grid-column-end: content-end; }
|
|
511
|
+
.col-end-center { grid-column-end: center-end; }
|
|
512
|
+
|
|
513
|
+
/* Backward compatibility */
|
|
514
|
+
.col-end-narrow { grid-column-end: content-end; }
|
|
515
|
+
|
|
516
|
+
/* ========================================
|
|
517
|
+
Column Utilities - Left/Right Spans
|
|
518
|
+
========================================
|
|
519
|
+
|
|
520
|
+
Asymmetric spans that anchor to one edge. Perfect for:
|
|
521
|
+
- Split layouts (image left, text right)
|
|
522
|
+
- Overlapping elements
|
|
523
|
+
- Pull quotes that bleed to one edge
|
|
524
|
+
|
|
525
|
+
Pattern: col-{track}-left = full-start → {track}-end
|
|
526
|
+
col-{track}-right = {track}-start → full-end
|
|
527
|
+
|
|
528
|
+
Example - Image bleeds left, caption stays in content:
|
|
529
|
+
<figure class="col-content-left">
|
|
530
|
+
<img class="w-full">Spans from left edge to content</img>
|
|
531
|
+
</figure>
|
|
532
|
+
|
|
533
|
+
Example - Quote pulls right:
|
|
534
|
+
<blockquote class="col-popout-right">
|
|
535
|
+
Spans from popout through to right edge
|
|
536
|
+
</blockquote>
|
|
537
|
+
*/
|
|
538
|
+
.col-feature-left { grid-column: full-start / feature-end; }
|
|
539
|
+
.col-feature-right { grid-column: feature-start / full-end; }
|
|
540
|
+
.col-popout-left { grid-column: full-start / popout-end; }
|
|
541
|
+
.col-popout-right { grid-column: popout-start / full-end; }
|
|
542
|
+
.col-content-left { grid-column: full-start / content-end; }
|
|
543
|
+
.col-content-right { grid-column: content-start / full-end; }
|
|
544
|
+
.col-center-left { grid-column: full-start / center-end; }
|
|
545
|
+
.col-center-right { grid-column: center-start / full-end; }
|
|
546
|
+
|
|
547
|
+
/* Backward compatibility */
|
|
548
|
+
.col-narrow-left { grid-column: full-start / content-end; }
|
|
549
|
+
.col-narrow-right { grid-column: content-start / full-end; }
|
|
550
|
+
|
|
551
|
+
/* ========================================
|
|
552
|
+
Column Utilities - Advanced Spans
|
|
553
|
+
========================================
|
|
554
|
+
|
|
555
|
+
Partial spans between non-adjacent tracks. Use when you need
|
|
556
|
+
elements that span from an inner track outward but not all
|
|
557
|
+
the way to the edge.
|
|
558
|
+
|
|
559
|
+
Example - Card that spans feature to content (not to edge):
|
|
560
|
+
<div class="col-feature-to-content">
|
|
561
|
+
Wide but doesn't bleed to viewport edge
|
|
562
|
+
</div>
|
|
563
|
+
*/
|
|
564
|
+
/* Feature to other columns */
|
|
565
|
+
.col-feature-to-popout { grid-column: feature-start / popout-end; }
|
|
566
|
+
.col-feature-to-content { grid-column: feature-start / content-end; }
|
|
567
|
+
.col-feature-to-center { grid-column: feature-start / center-end; }
|
|
568
|
+
|
|
569
|
+
/* Popout to other columns */
|
|
570
|
+
.col-popout-to-content { grid-column: popout-start / content-end; }
|
|
571
|
+
.col-popout-to-center { grid-column: popout-start / center-end; }
|
|
572
|
+
.col-popout-to-feature { grid-column: popout-start / feature-end; }
|
|
573
|
+
|
|
574
|
+
/* Content to other columns */
|
|
575
|
+
.col-content-to-center { grid-column: content-start / center-end; }
|
|
576
|
+
.col-content-to-popout { grid-column: content-start / popout-end; }
|
|
577
|
+
.col-content-to-feature { grid-column: content-start / feature-end; }
|
|
578
|
+
|
|
579
|
+
/* ----------------------------------------
|
|
580
|
+
Full Limit - Capped Full Width
|
|
581
|
+
----------------------------------------
|
|
582
|
+
|
|
583
|
+
Goes edge-to-edge like col-full, but caps at --full-limit on
|
|
584
|
+
ultra-wide screens. Prevents content from becoming too wide
|
|
585
|
+
on large monitors while still being full-width on normal screens.
|
|
586
|
+
|
|
587
|
+
Example - Hero that doesn't get absurdly wide:
|
|
588
|
+
<section class="col-full-limit">
|
|
589
|
+
Full width up to ${c.fullLimit}, then centered
|
|
590
|
+
</section>
|
|
591
|
+
*/
|
|
592
|
+
.col-full-limit {
|
|
593
|
+
grid-column: full;
|
|
594
|
+
width: 100%;
|
|
595
|
+
max-width: var(--full-limit);
|
|
596
|
+
margin-left: auto;
|
|
597
|
+
margin-right: auto;
|
|
598
|
+
box-sizing: border-box;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/* ========================================
|
|
602
|
+
Padding Utilities
|
|
603
|
+
========================================
|
|
604
|
+
|
|
605
|
+
Match padding to grid measurements for alignment. These utilities
|
|
606
|
+
help content inside non-grid elements align with the grid.
|
|
607
|
+
|
|
608
|
+
--breakout-padding: Fluid padding that matches popout track behavior
|
|
609
|
+
--gap: Matches the outer grid gap
|
|
610
|
+
--computed-gap: Larger gap for full-width elements
|
|
611
|
+
--popout-to-content: Align edges with content track from popout
|
|
612
|
+
--feature-to-content: Align edges with content track from feature
|
|
613
|
+
|
|
614
|
+
Example - Full-width section with content-aligned padding:
|
|
615
|
+
<section class="col-full bg-gray-100 px-feature-to-content">
|
|
616
|
+
<p>This text aligns with content column above/below</p>
|
|
617
|
+
</section>
|
|
618
|
+
|
|
619
|
+
Example - Card with consistent internal spacing:
|
|
620
|
+
<div class="col-popout p-breakout">
|
|
621
|
+
Padding scales with the grid
|
|
622
|
+
</div>
|
|
623
|
+
*/
|
|
624
|
+
.p-breakout { padding: var(--breakout-padding); }
|
|
625
|
+
.px-breakout { padding-left: var(--breakout-padding); padding-right: var(--breakout-padding); }
|
|
626
|
+
.py-breakout { padding-top: var(--breakout-padding); padding-bottom: var(--breakout-padding); }
|
|
627
|
+
.pl-breakout { padding-left: var(--breakout-padding); }
|
|
628
|
+
.pr-breakout { padding-right: var(--breakout-padding); }
|
|
629
|
+
.pt-breakout { padding-top: var(--breakout-padding); }
|
|
630
|
+
.pb-breakout { padding-bottom: var(--breakout-padding); }
|
|
631
|
+
|
|
632
|
+
/* Gap-based padding */
|
|
633
|
+
.p-gap { padding: var(--gap); }
|
|
634
|
+
.px-gap { padding-left: var(--gap); padding-right: var(--gap); }
|
|
635
|
+
.py-gap { padding-top: var(--gap); padding-bottom: var(--gap); }
|
|
636
|
+
.pl-gap { padding-left: var(--gap); }
|
|
637
|
+
.pr-gap { padding-right: var(--gap); }
|
|
638
|
+
.pt-gap { padding-top: var(--gap); }
|
|
639
|
+
.pb-gap { padding-bottom: var(--gap); }
|
|
640
|
+
|
|
641
|
+
/* Full-gap padding (computed, for full-width elements) */
|
|
642
|
+
.p-full-gap { padding: var(--computed-gap); }
|
|
643
|
+
.px-full-gap { padding-left: var(--computed-gap); padding-right: var(--computed-gap); }
|
|
644
|
+
.py-full-gap { padding-top: var(--computed-gap); padding-bottom: var(--computed-gap); }
|
|
645
|
+
.pl-full-gap { padding-left: var(--computed-gap); }
|
|
646
|
+
.pr-full-gap { padding-right: var(--computed-gap); }
|
|
647
|
+
.pt-full-gap { padding-top: var(--computed-gap); }
|
|
648
|
+
.pb-full-gap { padding-bottom: var(--computed-gap); }
|
|
649
|
+
|
|
650
|
+
/* Popout-width padding */
|
|
651
|
+
.p-popout { padding: var(--popout); }
|
|
652
|
+
.px-popout { padding-left: var(--popout); padding-right: var(--popout); }
|
|
653
|
+
.py-popout { padding-top: var(--popout); padding-bottom: var(--popout); }
|
|
654
|
+
.pl-popout { padding-left: var(--popout); }
|
|
655
|
+
.pr-popout { padding-right: var(--popout); }
|
|
656
|
+
.pt-popout { padding-top: var(--popout); }
|
|
657
|
+
.pb-popout { padding-bottom: var(--popout); }
|
|
658
|
+
|
|
659
|
+
/* Alignment padding - align content inside wider columns */
|
|
660
|
+
.p-popout-to-content { padding: var(--popout-to-content); }
|
|
661
|
+
.px-popout-to-content { padding-left: var(--popout-to-content); padding-right: var(--popout-to-content); }
|
|
662
|
+
.py-popout-to-content { padding-top: var(--popout-to-content); padding-bottom: var(--popout-to-content); }
|
|
663
|
+
.pt-popout-to-content { padding-top: var(--popout-to-content); }
|
|
664
|
+
.pr-popout-to-content { padding-right: var(--popout-to-content); }
|
|
665
|
+
.pb-popout-to-content { padding-bottom: var(--popout-to-content); }
|
|
666
|
+
.pl-popout-to-content { padding-left: var(--popout-to-content); }
|
|
667
|
+
|
|
668
|
+
.p-feature-to-content { padding: var(--feature-to-content); }
|
|
669
|
+
.px-feature-to-content { padding-left: var(--feature-to-content); padding-right: var(--feature-to-content); }
|
|
670
|
+
.py-feature-to-content { padding-top: var(--feature-to-content); padding-bottom: var(--feature-to-content); }
|
|
671
|
+
.pt-feature-to-content { padding-top: var(--feature-to-content); }
|
|
672
|
+
.pr-feature-to-content { padding-right: var(--feature-to-content); }
|
|
673
|
+
.pb-feature-to-content { padding-bottom: var(--feature-to-content); }
|
|
674
|
+
.pl-feature-to-content { padding-left: var(--feature-to-content); }
|
|
675
|
+
|
|
676
|
+
/* ========================================
|
|
677
|
+
Margin Utilities
|
|
678
|
+
========================================
|
|
679
|
+
|
|
680
|
+
Same values as padding utilities, but for margins. Includes
|
|
681
|
+
negative variants for pulling elements outside their container.
|
|
682
|
+
|
|
683
|
+
Example - Pull image outside its container:
|
|
684
|
+
<div class="col-content">
|
|
685
|
+
<img class="-mx-breakout">Bleeds into popout area</img>
|
|
686
|
+
</div>
|
|
687
|
+
|
|
688
|
+
Example - Overlap previous section:
|
|
689
|
+
<section class="-mt-gap">
|
|
690
|
+
Pulls up into the section above
|
|
691
|
+
</section>
|
|
692
|
+
*/
|
|
693
|
+
.m-breakout { margin: var(--breakout-padding); }
|
|
694
|
+
.mx-breakout { margin-left: var(--breakout-padding); margin-right: var(--breakout-padding); }
|
|
695
|
+
.my-breakout { margin-top: var(--breakout-padding); margin-bottom: var(--breakout-padding); }
|
|
696
|
+
.ml-breakout { margin-left: var(--breakout-padding); }
|
|
697
|
+
.mr-breakout { margin-right: var(--breakout-padding); }
|
|
698
|
+
.mt-breakout { margin-top: var(--breakout-padding); }
|
|
699
|
+
.mb-breakout { margin-bottom: var(--breakout-padding); }
|
|
700
|
+
|
|
701
|
+
/* Negative margins */
|
|
702
|
+
.-m-breakout { margin: calc(var(--breakout-padding) * -1); }
|
|
703
|
+
.-mx-breakout { margin-left: calc(var(--breakout-padding) * -1); margin-right: calc(var(--breakout-padding) * -1); }
|
|
704
|
+
.-my-breakout { margin-top: calc(var(--breakout-padding) * -1); margin-bottom: calc(var(--breakout-padding) * -1); }
|
|
705
|
+
.-ml-breakout { margin-left: calc(var(--breakout-padding) * -1); }
|
|
706
|
+
.-mr-breakout { margin-right: calc(var(--breakout-padding) * -1); }
|
|
707
|
+
.-mt-breakout { margin-top: calc(var(--breakout-padding) * -1); }
|
|
708
|
+
.-mb-breakout { margin-bottom: calc(var(--breakout-padding) * -1); }
|
|
709
|
+
|
|
710
|
+
/* Gap-based margins */
|
|
711
|
+
.m-gap { margin: var(--gap); }
|
|
712
|
+
.mx-gap { margin-left: var(--gap); margin-right: var(--gap); }
|
|
713
|
+
.my-gap { margin-top: var(--gap); margin-bottom: var(--gap); }
|
|
714
|
+
.ml-gap { margin-left: var(--gap); }
|
|
715
|
+
.mr-gap { margin-right: var(--gap); }
|
|
716
|
+
.mt-gap { margin-top: var(--gap); }
|
|
717
|
+
.mb-gap { margin-bottom: var(--gap); }
|
|
718
|
+
|
|
719
|
+
/* Negative margins */
|
|
720
|
+
.-m-gap { margin: calc(var(--gap) * -1); }
|
|
721
|
+
.-mx-gap { margin-left: calc(var(--gap) * -1); margin-right: calc(var(--gap) * -1); }
|
|
722
|
+
.-my-gap { margin-top: calc(var(--gap) * -1); margin-bottom: calc(var(--gap) * -1); }
|
|
723
|
+
.-ml-gap { margin-left: calc(var(--gap) * -1); }
|
|
724
|
+
.-mr-gap { margin-right: calc(var(--gap) * -1); }
|
|
725
|
+
.-mt-gap { margin-top: calc(var(--gap) * -1); }
|
|
726
|
+
.-mb-gap { margin-bottom: calc(var(--gap) * -1); }
|
|
727
|
+
|
|
728
|
+
/* Full-gap margins */
|
|
729
|
+
.m-full-gap { margin: var(--computed-gap); }
|
|
730
|
+
.mx-full-gap { margin-left: var(--computed-gap); margin-right: var(--computed-gap); }
|
|
731
|
+
.my-full-gap { margin-top: var(--computed-gap); margin-bottom: var(--computed-gap); }
|
|
732
|
+
.ml-full-gap { margin-left: var(--computed-gap); }
|
|
733
|
+
.mr-full-gap { margin-right: var(--computed-gap); }
|
|
734
|
+
.mt-full-gap { margin-top: var(--computed-gap); }
|
|
735
|
+
.mb-full-gap { margin-bottom: var(--computed-gap); }
|
|
736
|
+
|
|
737
|
+
/* Negative margins */
|
|
738
|
+
.-m-full-gap { margin: calc(var(--computed-gap) * -1); }
|
|
739
|
+
.-mx-full-gap { margin-left: calc(var(--computed-gap) * -1); margin-right: calc(var(--computed-gap) * -1); }
|
|
740
|
+
.-my-full-gap { margin-top: calc(var(--computed-gap) * -1); margin-bottom: calc(var(--computed-gap) * -1); }
|
|
741
|
+
.-ml-full-gap { margin-left: calc(var(--computed-gap) * -1); }
|
|
742
|
+
.-mr-full-gap { margin-right: calc(var(--computed-gap) * -1); }
|
|
743
|
+
.-mt-full-gap { margin-top: calc(var(--computed-gap) * -1); }
|
|
744
|
+
.-mb-full-gap { margin-bottom: calc(var(--computed-gap) * -1); }
|
|
745
|
+
|
|
746
|
+
/* Popout-width margins */
|
|
747
|
+
.m-popout { margin: var(--popout); }
|
|
748
|
+
.mx-popout { margin-left: var(--popout); margin-right: var(--popout); }
|
|
749
|
+
.my-popout { margin-top: var(--popout); margin-bottom: var(--popout); }
|
|
750
|
+
.ml-popout { margin-left: var(--popout); }
|
|
751
|
+
.mr-popout { margin-right: var(--popout); }
|
|
752
|
+
.mt-popout { margin-top: var(--popout); }
|
|
753
|
+
.mb-popout { margin-bottom: var(--popout); }
|
|
754
|
+
|
|
755
|
+
/* Negative margins */
|
|
756
|
+
.-m-popout { margin: calc(var(--popout) * -1); }
|
|
757
|
+
.-mx-popout { margin-left: calc(var(--popout) * -1); margin-right: calc(var(--popout) * -1); }
|
|
758
|
+
.-my-popout { margin-top: calc(var(--popout) * -1); margin-bottom: calc(var(--popout) * -1); }
|
|
759
|
+
.-ml-popout { margin-left: calc(var(--popout) * -1); }
|
|
760
|
+
.-mr-popout { margin-right: calc(var(--popout) * -1); }
|
|
761
|
+
.-mt-popout { margin-top: calc(var(--popout) * -1); }
|
|
762
|
+
.-mb-popout { margin-bottom: calc(var(--popout) * -1); }
|
|
763
|
+
`;
|
|
764
|
+
}
|
|
765
|
+
const methods = {
|
|
766
|
+
// Initialize
|
|
767
|
+
init() {
|
|
768
|
+
const saved = localStorage.getItem("breakoutGridVisualizerVisible");
|
|
769
|
+
if (saved !== null) {
|
|
770
|
+
this.isVisible = saved === "true";
|
|
771
|
+
}
|
|
772
|
+
const editorOpen = localStorage.getItem("breakoutGridEditorOpen");
|
|
773
|
+
if (editorOpen === "true") {
|
|
774
|
+
this.showEditor = true;
|
|
775
|
+
this.editMode = true;
|
|
776
|
+
this.$nextTick(() => this.loadCurrentValues());
|
|
777
|
+
}
|
|
778
|
+
const editorPos = localStorage.getItem("breakoutGridEditorPos");
|
|
779
|
+
if (editorPos) {
|
|
780
|
+
try {
|
|
781
|
+
this.editorPos = JSON.parse(editorPos);
|
|
782
|
+
} catch (e) {
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
const spacingPos = localStorage.getItem("breakoutGridSpacingPos");
|
|
786
|
+
if (spacingPos) {
|
|
787
|
+
try {
|
|
788
|
+
this.spacingPanelPos = JSON.parse(spacingPos);
|
|
789
|
+
} catch (e) {
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const spacingCollapsed = localStorage.getItem("breakoutGridSpacingCollapsed");
|
|
793
|
+
if (spacingCollapsed !== null) {
|
|
794
|
+
this.spacingPanelCollapsed = spacingCollapsed === "true";
|
|
795
|
+
}
|
|
796
|
+
window.addEventListener("keydown", (e) => {
|
|
797
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "g") {
|
|
798
|
+
e.preventDefault();
|
|
799
|
+
this.toggle();
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
window.addEventListener("resize", () => {
|
|
803
|
+
this.viewportWidth = window.innerWidth;
|
|
804
|
+
this.updateColumnWidths();
|
|
805
|
+
this.updateCurrentBreakpoint();
|
|
806
|
+
if (this.editMode) {
|
|
807
|
+
this.updateGapLive();
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
this.updateCurrentBreakpoint();
|
|
811
|
+
console.log("Breakout Grid Visualizer loaded. Press Ctrl/Cmd + G to toggle.");
|
|
812
|
+
},
|
|
813
|
+
// Toggle visibility
|
|
814
|
+
toggle() {
|
|
815
|
+
this.isVisible = !this.isVisible;
|
|
816
|
+
localStorage.setItem("breakoutGridVisualizerVisible", this.isVisible);
|
|
817
|
+
},
|
|
818
|
+
// Update column widths by querying DOM elements
|
|
819
|
+
updateColumnWidths() {
|
|
820
|
+
this.$nextTick(() => {
|
|
821
|
+
this.gridAreas.forEach((area) => {
|
|
822
|
+
const el = document.querySelector(`.breakout-visualizer-grid .col-${area.name}`);
|
|
823
|
+
if (el) {
|
|
824
|
+
this.columnWidths[area.name] = Math.round(el.getBoundingClientRect().width);
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
},
|
|
829
|
+
// Detect current breakpoint based on viewport width
|
|
830
|
+
updateCurrentBreakpoint() {
|
|
831
|
+
const width = window.innerWidth;
|
|
832
|
+
if (width >= 1280) {
|
|
833
|
+
this.currentBreakpoint = "xl";
|
|
834
|
+
} else if (width >= 1024) {
|
|
835
|
+
this.currentBreakpoint = "lg";
|
|
836
|
+
} else {
|
|
837
|
+
this.currentBreakpoint = "mobile";
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
// Update --gap live based on current breakpoint and edit values
|
|
841
|
+
updateGapLive() {
|
|
842
|
+
const scaleKey = this.currentBreakpoint === "mobile" ? "default" : this.currentBreakpoint;
|
|
843
|
+
const base = this.editValues.baseGap || this.configOptions.baseGap.value;
|
|
844
|
+
const max = this.editValues.maxGap || this.configOptions.maxGap.value;
|
|
845
|
+
const scale = this.editValues[`gapScale_${scaleKey}`] || this.gapScaleOptions[scaleKey].value;
|
|
846
|
+
document.documentElement.style.setProperty("--gap", `clamp(${base}, ${scale}, ${max})`);
|
|
847
|
+
this.updateColumnWidths();
|
|
848
|
+
},
|
|
849
|
+
// Check if content width exceeds comfortable reading width (55rem)
|
|
850
|
+
getContentReadabilityWarning() {
|
|
851
|
+
const contentMax = parseFloat(this.editValues.contentMax || this.configOptions.contentMax.value);
|
|
852
|
+
if (contentMax > 55) {
|
|
853
|
+
return `Content max (${contentMax}rem) exceeds 55rem—may be wide for reading. Ideal for prose: 45–55rem.`;
|
|
854
|
+
}
|
|
855
|
+
return null;
|
|
856
|
+
},
|
|
857
|
+
// Check if configured track widths would exceed viewport
|
|
858
|
+
getTrackOverflowWarning() {
|
|
859
|
+
const contentMax = parseFloat(this.editValues.contentMax || this.configOptions.contentMax.value) * 16;
|
|
860
|
+
const featureMax = parseFloat(this.editValues.featureMax || this.configOptions.featureMax.value) * 16;
|
|
861
|
+
const popoutWidth = parseFloat(this.editValues.popoutWidth || this.configOptions.popoutWidth.value) * 16;
|
|
862
|
+
const featurePx = featureMax * 2;
|
|
863
|
+
const popoutPx = popoutWidth * 2;
|
|
864
|
+
const totalFixed = contentMax + featurePx + popoutPx;
|
|
865
|
+
if (totalFixed > this.viewportWidth) {
|
|
866
|
+
return `Tracks exceed viewport by ~${Math.round(totalFixed - this.viewportWidth)}px — outer columns will compress`;
|
|
867
|
+
}
|
|
868
|
+
return null;
|
|
869
|
+
},
|
|
870
|
+
// Get computed CSS variable value
|
|
871
|
+
getCSSVariable(varName) {
|
|
872
|
+
const value = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
|
|
873
|
+
return value || "Not set";
|
|
874
|
+
},
|
|
875
|
+
// Helper to load options from CSS variables
|
|
876
|
+
loadOptionsFromCSS(options, prefix = "") {
|
|
877
|
+
Object.keys(options).forEach((key) => {
|
|
878
|
+
const opt = options[key];
|
|
879
|
+
const editKey = prefix ? `${prefix}_${key}` : key;
|
|
880
|
+
if (opt.cssVar) {
|
|
881
|
+
const computed = this.getCSSVariable(opt.cssVar);
|
|
882
|
+
this.editValues[editKey] = computed && computed !== "Not set" && computed !== "" ? computed : opt.value;
|
|
883
|
+
} else {
|
|
884
|
+
this.editValues[editKey] = opt.value;
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
},
|
|
888
|
+
// Load current values from CSS variables where available
|
|
889
|
+
loadCurrentValues() {
|
|
890
|
+
this.loadOptionsFromCSS(this.configOptions);
|
|
891
|
+
this.loadOptionsFromCSS(this.gapScaleOptions, "gapScale");
|
|
892
|
+
this.loadOptionsFromCSS(this.breakoutOptions, "breakout");
|
|
893
|
+
},
|
|
894
|
+
// Generate export config object
|
|
895
|
+
generateConfigExport() {
|
|
896
|
+
var _a, _b, _c, _d;
|
|
897
|
+
const config = {};
|
|
898
|
+
Object.keys(this.configOptions).forEach((key) => {
|
|
899
|
+
config[key] = this.editValues[key] || this.configOptions[key].value;
|
|
900
|
+
});
|
|
901
|
+
config.gapScale = {};
|
|
902
|
+
Object.keys(this.gapScaleOptions).forEach((key) => {
|
|
903
|
+
config.gapScale[key] = this.editValues[`gapScale_${key}`] || this.gapScaleOptions[key].value;
|
|
904
|
+
});
|
|
905
|
+
config.breakoutMin = this.editValues.breakout_min || this.breakoutOptions.min.value;
|
|
906
|
+
config.breakoutScale = this.editValues.breakout_scale || this.breakoutOptions.scale.value;
|
|
907
|
+
config.breakpoints = {
|
|
908
|
+
lg: this.editValues.breakpoint_lg || ((_b = (_a = this.breakpointOptions) == null ? void 0 : _a.lg) == null ? void 0 : _b.value) || "1024",
|
|
909
|
+
xl: this.editValues.breakpoint_xl || ((_d = (_c = this.breakpointOptions) == null ? void 0 : _c.xl) == null ? void 0 : _d.value) || "1280"
|
|
910
|
+
};
|
|
911
|
+
return config;
|
|
912
|
+
},
|
|
913
|
+
// Format config object with single quotes for values, no quotes for keys
|
|
914
|
+
formatConfig(obj, indent = 2) {
|
|
915
|
+
const pad = " ".repeat(indent);
|
|
916
|
+
const lines = ["{"];
|
|
917
|
+
const entries = Object.entries(obj);
|
|
918
|
+
entries.forEach(([key, value], i) => {
|
|
919
|
+
const comma = i < entries.length - 1 ? "," : "";
|
|
920
|
+
if (typeof value === "object" && value !== null) {
|
|
921
|
+
lines.push(`${pad}${key}: ${this.formatConfig(value, indent + 2).replace(/\n/g, "\n" + pad)}${comma}`);
|
|
922
|
+
} else {
|
|
923
|
+
lines.push(`${pad}${key}: '${value}'${comma}`);
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
lines.push("}");
|
|
927
|
+
return lines.join("\n");
|
|
928
|
+
},
|
|
929
|
+
// Section definitions for partial copying
|
|
930
|
+
configSections: {
|
|
931
|
+
content: {
|
|
932
|
+
keys: ["contentMin", "contentBase", "contentMax"],
|
|
933
|
+
label: "Content"
|
|
934
|
+
},
|
|
935
|
+
defaultCol: {
|
|
936
|
+
keys: ["defaultCol"],
|
|
937
|
+
label: "Default Column"
|
|
938
|
+
},
|
|
939
|
+
tracks: {
|
|
940
|
+
keys: ["popoutWidth", "fullLimit"],
|
|
941
|
+
label: "Track Widths"
|
|
942
|
+
},
|
|
943
|
+
feature: {
|
|
944
|
+
keys: ["featureMin", "featureScale", "featureMax"],
|
|
945
|
+
label: "Feature"
|
|
946
|
+
},
|
|
947
|
+
gap: {
|
|
948
|
+
keys: ["baseGap", "maxGap"],
|
|
949
|
+
nested: { gapScale: ["default", "lg", "xl"] },
|
|
950
|
+
label: "Gap"
|
|
951
|
+
},
|
|
952
|
+
breakout: {
|
|
953
|
+
keys: ["breakoutMin", "breakoutScale"],
|
|
954
|
+
label: "Breakout"
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
// Copy a specific section to clipboard
|
|
958
|
+
copySection(sectionName) {
|
|
959
|
+
const section = this.configSections[sectionName];
|
|
960
|
+
if (!section) return;
|
|
961
|
+
const config = {};
|
|
962
|
+
section.keys.forEach((key) => {
|
|
963
|
+
if (this.configOptions[key]) {
|
|
964
|
+
config[key] = this.editValues[key] || this.configOptions[key].value;
|
|
965
|
+
} else if (key === "breakoutMin") {
|
|
966
|
+
config[key] = this.editValues.breakout_min || this.breakoutOptions.min.value;
|
|
967
|
+
} else if (key === "breakoutScale") {
|
|
968
|
+
config[key] = this.editValues.breakout_scale || this.breakoutOptions.scale.value;
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
if (section.nested) {
|
|
972
|
+
Object.keys(section.nested).forEach((nestedKey) => {
|
|
973
|
+
config[nestedKey] = {};
|
|
974
|
+
section.nested[nestedKey].forEach((subKey) => {
|
|
975
|
+
config[nestedKey][subKey] = this.editValues[`gapScale_${subKey}`] || this.gapScaleOptions[subKey].value;
|
|
976
|
+
});
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
const configStr = this.formatConfigFlat(config);
|
|
980
|
+
navigator.clipboard.writeText(configStr).then(() => {
|
|
981
|
+
this.sectionCopied = sectionName;
|
|
982
|
+
setTimeout(() => this.sectionCopied = null, 1500);
|
|
983
|
+
});
|
|
984
|
+
},
|
|
985
|
+
// Format config as flat key-value pairs (no wrapping braces)
|
|
986
|
+
formatConfigFlat(obj) {
|
|
987
|
+
const lines = [];
|
|
988
|
+
const entries = Object.entries(obj);
|
|
989
|
+
entries.forEach(([key, value], i) => {
|
|
990
|
+
const comma = i < entries.length - 1 ? "," : ",";
|
|
991
|
+
if (typeof value === "object" && value !== null) {
|
|
992
|
+
lines.push(`${key}: ${this.formatConfig(value)}${comma}`);
|
|
993
|
+
} else {
|
|
994
|
+
lines.push(`${key}: '${value}'${comma}`);
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
return lines.join("\n");
|
|
998
|
+
},
|
|
999
|
+
// Copy config to clipboard as CSS variables
|
|
1000
|
+
copyConfig() {
|
|
1001
|
+
var _a, _b, _c, _d, _e;
|
|
1002
|
+
const config = this.generateConfigExport();
|
|
1003
|
+
const lines = [
|
|
1004
|
+
":root {",
|
|
1005
|
+
` /* Content (text width) */`,
|
|
1006
|
+
` --content-min: ${config.contentMin};`,
|
|
1007
|
+
` --content-base: ${config.contentBase};`,
|
|
1008
|
+
` --content-max: ${config.contentMax};`,
|
|
1009
|
+
` /* Default column */`,
|
|
1010
|
+
` --default-col: ${config.defaultCol || "content"};`,
|
|
1011
|
+
` /* Track widths */`,
|
|
1012
|
+
` --popout-width: ${config.popoutWidth};`,
|
|
1013
|
+
` --full-limit: ${config.fullLimit};`,
|
|
1014
|
+
` /* Feature track */`,
|
|
1015
|
+
` --feature-min: ${config.featureMin};`,
|
|
1016
|
+
` --feature-scale: ${config.featureScale};`,
|
|
1017
|
+
` --feature-max: ${config.featureMax};`,
|
|
1018
|
+
` /* Outer margins */`,
|
|
1019
|
+
` --base-gap: ${config.baseGap};`,
|
|
1020
|
+
` --max-gap: ${config.maxGap};`,
|
|
1021
|
+
` /* Responsive scale */`,
|
|
1022
|
+
` --gap-scale-default: ${((_a = config.gapScale) == null ? void 0 : _a.default) || "4vw"};`,
|
|
1023
|
+
` --gap-scale-lg: ${((_b = config.gapScale) == null ? void 0 : _b.lg) || "5vw"};`,
|
|
1024
|
+
` --gap-scale-xl: ${((_c = config.gapScale) == null ? void 0 : _c.xl) || "6vw"};`,
|
|
1025
|
+
` /* Breakout padding */`,
|
|
1026
|
+
` --breakout-min: ${config.breakoutMin || "1rem"};`,
|
|
1027
|
+
` --breakout-scale: ${config.breakoutScale || "5vw"};`,
|
|
1028
|
+
` /* Breakpoints */`,
|
|
1029
|
+
` /* --breakpoint-lg: ${((_d = config.breakpoints) == null ? void 0 : _d.lg) || "1024"}px; */`,
|
|
1030
|
+
` /* --breakpoint-xl: ${((_e = config.breakpoints) == null ? void 0 : _e.xl) || "1280"}px; */`,
|
|
1031
|
+
"}"
|
|
1032
|
+
];
|
|
1033
|
+
const configStr = lines.join("\n");
|
|
1034
|
+
navigator.clipboard.writeText(configStr).then(() => {
|
|
1035
|
+
this.copySuccess = true;
|
|
1036
|
+
this.configCopied = true;
|
|
1037
|
+
setTimeout(() => this.copySuccess = false, 2e3);
|
|
1038
|
+
});
|
|
1039
|
+
},
|
|
1040
|
+
// Generate and download standalone CSS file
|
|
1041
|
+
downloadCSS() {
|
|
1042
|
+
const css = this.generateCSSExport(this.generateConfigExport());
|
|
1043
|
+
const blob = new Blob([css], { type: "text/css" });
|
|
1044
|
+
const url = URL.createObjectURL(blob);
|
|
1045
|
+
const a = document.createElement("a");
|
|
1046
|
+
a.href = url;
|
|
1047
|
+
a.download = `_objects.breakout-grid.css`;
|
|
1048
|
+
a.click();
|
|
1049
|
+
URL.revokeObjectURL(url);
|
|
1050
|
+
},
|
|
1051
|
+
// Parse CSS value into number and unit (e.g., "4rem" -> { num: 4, unit: "rem" })
|
|
1052
|
+
parseValue(val) {
|
|
1053
|
+
const match = String(val).match(/^([\d.]+)(.*)$/);
|
|
1054
|
+
if (match) {
|
|
1055
|
+
return { num: parseFloat(match[1]), unit: match[2] || "rem" };
|
|
1056
|
+
}
|
|
1057
|
+
return { num: 0, unit: "rem" };
|
|
1058
|
+
},
|
|
1059
|
+
// Get the numeric part of a config value
|
|
1060
|
+
getNumericValue(key) {
|
|
1061
|
+
const val = this.editValues[key] || this.configOptions[key].value;
|
|
1062
|
+
return this.parseValue(val).num;
|
|
1063
|
+
},
|
|
1064
|
+
// Get the unit part of a config value
|
|
1065
|
+
getUnit(key) {
|
|
1066
|
+
const val = this.editValues[key] || this.configOptions[key].value;
|
|
1067
|
+
return this.parseValue(val).unit;
|
|
1068
|
+
},
|
|
1069
|
+
// Check if a field should have unit selection (rem-based fields only)
|
|
1070
|
+
hasUnitSelector(key) {
|
|
1071
|
+
const unit = this.getUnit(key);
|
|
1072
|
+
return unit === "rem" || unit === "ch" || unit === "px";
|
|
1073
|
+
},
|
|
1074
|
+
// Available units for selection
|
|
1075
|
+
unitOptions: ["rem", "ch", "px"],
|
|
1076
|
+
// Update just the unit, keeping the numeric value
|
|
1077
|
+
updateUnit(key, newUnit) {
|
|
1078
|
+
const num = this.getNumericValue(key);
|
|
1079
|
+
this.updateConfigValue(key, num + newUnit);
|
|
1080
|
+
},
|
|
1081
|
+
// Update just the numeric part, keeping the unit
|
|
1082
|
+
updateNumericValue(key, num) {
|
|
1083
|
+
if (key === "content" && num < 1) num = 1;
|
|
1084
|
+
if (key === "baseGap" && num < 0) num = 0;
|
|
1085
|
+
if (key === "popoutWidth" && num < 0) num = 0;
|
|
1086
|
+
if ((key === "featureMin" || key === "featureScale" || key === "featureMax") && num < 0) num = 0;
|
|
1087
|
+
const unit = this.getUnit(key);
|
|
1088
|
+
this.updateConfigValue(key, num + unit);
|
|
1089
|
+
},
|
|
1090
|
+
// Generic getter for prefixed options (gapScale, breakout)
|
|
1091
|
+
getPrefixedNumeric(prefix, options, key) {
|
|
1092
|
+
const val = this.editValues[`${prefix}_${key}`] || options[key].value;
|
|
1093
|
+
return this.parseValue(val).num;
|
|
1094
|
+
},
|
|
1095
|
+
getPrefixedUnit(prefix, options, key) {
|
|
1096
|
+
const val = this.editValues[`${prefix}_${key}`] || options[key].value;
|
|
1097
|
+
return this.parseValue(val).unit;
|
|
1098
|
+
},
|
|
1099
|
+
// Gap scale helpers (use generic)
|
|
1100
|
+
getGapScaleNumeric(key) {
|
|
1101
|
+
return this.getPrefixedNumeric("gapScale", this.gapScaleOptions, key);
|
|
1102
|
+
},
|
|
1103
|
+
getGapScaleUnit(key) {
|
|
1104
|
+
return this.getPrefixedUnit("gapScale", this.gapScaleOptions, key);
|
|
1105
|
+
},
|
|
1106
|
+
updateGapScaleNumeric(key, num) {
|
|
1107
|
+
this.editValues[`gapScale_${key}`] = num + this.getGapScaleUnit(key);
|
|
1108
|
+
this.configCopied = false;
|
|
1109
|
+
this.updateGapLive();
|
|
1110
|
+
},
|
|
1111
|
+
// Breakout helpers (use generic)
|
|
1112
|
+
getBreakoutNumeric(key) {
|
|
1113
|
+
return this.getPrefixedNumeric("breakout", this.breakoutOptions, key);
|
|
1114
|
+
},
|
|
1115
|
+
getBreakoutUnit(key) {
|
|
1116
|
+
return this.getPrefixedUnit("breakout", this.breakoutOptions, key);
|
|
1117
|
+
},
|
|
1118
|
+
updateBreakoutNumeric(key, num) {
|
|
1119
|
+
this.editValues[`breakout_${key}`] = num + this.getBreakoutUnit(key);
|
|
1120
|
+
this.configCopied = false;
|
|
1121
|
+
this.updateBreakoutLive();
|
|
1122
|
+
},
|
|
1123
|
+
// Update --breakout-padding live
|
|
1124
|
+
updateBreakoutLive() {
|
|
1125
|
+
const min = this.editValues.breakout_min || this.breakoutOptions.min.value;
|
|
1126
|
+
const scale = this.editValues.breakout_scale || this.breakoutOptions.scale.value;
|
|
1127
|
+
const max = this.editValues.popoutWidth || this.configOptions.popoutWidth.value;
|
|
1128
|
+
document.documentElement.style.setProperty("--breakout-padding", `clamp(${min}, ${scale}, ${max})`);
|
|
1129
|
+
},
|
|
1130
|
+
// Update a config value (and live CSS var if applicable)
|
|
1131
|
+
updateConfigValue(key, value) {
|
|
1132
|
+
this.editValues[key] = value;
|
|
1133
|
+
this.configCopied = false;
|
|
1134
|
+
const opt = this.configOptions[key];
|
|
1135
|
+
if (opt && opt.liveVar) {
|
|
1136
|
+
document.documentElement.style.setProperty(opt.liveVar, value);
|
|
1137
|
+
}
|
|
1138
|
+
if (key === "popoutWidth") {
|
|
1139
|
+
document.documentElement.style.setProperty("--popout", `minmax(0, ${value})`);
|
|
1140
|
+
this.updateBreakoutLive();
|
|
1141
|
+
}
|
|
1142
|
+
if (key === "featureMin" || key === "featureScale" || key === "featureMax") {
|
|
1143
|
+
const featureMin = this.editValues.featureMin || this.configOptions.featureMin.value;
|
|
1144
|
+
const featureScale = this.editValues.featureScale || this.configOptions.featureScale.value;
|
|
1145
|
+
const featureMax = this.editValues.featureMax || this.configOptions.featureMax.value;
|
|
1146
|
+
document.documentElement.style.setProperty("--feature", `minmax(0, clamp(${featureMin}, ${featureScale}, ${featureMax}))`);
|
|
1147
|
+
}
|
|
1148
|
+
if (key === "content") {
|
|
1149
|
+
document.documentElement.style.setProperty("--content", `minmax(0, ${value})`);
|
|
1150
|
+
}
|
|
1151
|
+
if (key === "baseGap" || key === "maxGap") {
|
|
1152
|
+
this.updateGapLive();
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
// Select a grid area
|
|
1156
|
+
selectArea(areaName) {
|
|
1157
|
+
this.selectedArea = this.selectedArea === areaName ? null : areaName;
|
|
1158
|
+
},
|
|
1159
|
+
// Check if area is selected
|
|
1160
|
+
isSelected(areaName) {
|
|
1161
|
+
return this.selectedArea === areaName;
|
|
1162
|
+
},
|
|
1163
|
+
// Restore all CSS variable overrides to original values
|
|
1164
|
+
restoreCSSVariables() {
|
|
1165
|
+
Object.keys(this.configOptions).forEach((key) => {
|
|
1166
|
+
const opt = this.configOptions[key];
|
|
1167
|
+
if (opt.liveVar) {
|
|
1168
|
+
document.documentElement.style.removeProperty(opt.liveVar);
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
document.documentElement.style.removeProperty("--popout");
|
|
1172
|
+
document.documentElement.style.removeProperty("--feature");
|
|
1173
|
+
document.documentElement.style.removeProperty("--content");
|
|
1174
|
+
document.documentElement.style.removeProperty("--breakout-padding");
|
|
1175
|
+
document.documentElement.style.removeProperty("--popout-to-content");
|
|
1176
|
+
this.editValues = {};
|
|
1177
|
+
this.configCopied = false;
|
|
1178
|
+
},
|
|
1179
|
+
// Toggle edit mode
|
|
1180
|
+
toggleEditMode() {
|
|
1181
|
+
this.editMode = !this.editMode;
|
|
1182
|
+
if (this.editMode) {
|
|
1183
|
+
this.loadCurrentValues();
|
|
1184
|
+
} else {
|
|
1185
|
+
this.restoreCSSVariables();
|
|
1186
|
+
}
|
|
1187
|
+
},
|
|
1188
|
+
// Check if any values have been edited and not yet copied
|
|
1189
|
+
hasUnsavedEdits() {
|
|
1190
|
+
return Object.keys(this.editValues).length > 0 && !this.configCopied;
|
|
1191
|
+
},
|
|
1192
|
+
// Open floating editor
|
|
1193
|
+
openEditor() {
|
|
1194
|
+
this.showEditor = true;
|
|
1195
|
+
this.editMode = true;
|
|
1196
|
+
this.loadCurrentValues();
|
|
1197
|
+
localStorage.setItem("breakoutGridEditorOpen", "true");
|
|
1198
|
+
},
|
|
1199
|
+
// Close floating editor
|
|
1200
|
+
closeEditor(force = false) {
|
|
1201
|
+
if (!force && this.hasUnsavedEdits()) {
|
|
1202
|
+
if (!confirm("You have unsaved config changes. Close without copying?")) {
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
this.showEditor = false;
|
|
1207
|
+
this.editMode = false;
|
|
1208
|
+
this.restoreCSSVariables();
|
|
1209
|
+
localStorage.setItem("breakoutGridEditorOpen", "false");
|
|
1210
|
+
},
|
|
1211
|
+
// Generic drag handling for panels
|
|
1212
|
+
_dragConfigs: {
|
|
1213
|
+
editor: { pos: "editorPos", dragging: "isDragging", offset: "dragOffset", storage: "breakoutGridEditorPos" },
|
|
1214
|
+
spacing: { pos: "spacingPanelPos", dragging: "isDraggingSpacing", offset: "dragOffsetSpacing", storage: "breakoutGridSpacingPos" }
|
|
1215
|
+
},
|
|
1216
|
+
startPanelDrag(e, panel) {
|
|
1217
|
+
const cfg = this._dragConfigs[panel];
|
|
1218
|
+
this[cfg.dragging] = true;
|
|
1219
|
+
this[cfg.offset] = { x: e.clientX - this[cfg.pos].x, y: e.clientY - this[cfg.pos].y };
|
|
1220
|
+
},
|
|
1221
|
+
onPanelDrag(e, panel) {
|
|
1222
|
+
const cfg = this._dragConfigs[panel];
|
|
1223
|
+
if (this[cfg.dragging]) {
|
|
1224
|
+
this[cfg.pos] = { x: e.clientX - this[cfg.offset].x, y: e.clientY - this[cfg.offset].y };
|
|
1225
|
+
}
|
|
1226
|
+
},
|
|
1227
|
+
stopPanelDrag(panel) {
|
|
1228
|
+
const cfg = this._dragConfigs[panel];
|
|
1229
|
+
if (this[cfg.dragging]) localStorage.setItem(cfg.storage, JSON.stringify(this[cfg.pos]));
|
|
1230
|
+
this[cfg.dragging] = false;
|
|
1231
|
+
},
|
|
1232
|
+
// Editor drag (shorthand)
|
|
1233
|
+
startDrag(e) {
|
|
1234
|
+
this.startPanelDrag(e, "editor");
|
|
1235
|
+
},
|
|
1236
|
+
onDrag(e) {
|
|
1237
|
+
this.onPanelDrag(e, "editor");
|
|
1238
|
+
},
|
|
1239
|
+
stopDrag() {
|
|
1240
|
+
this.stopPanelDrag("editor");
|
|
1241
|
+
},
|
|
1242
|
+
// Spacing drag (shorthand)
|
|
1243
|
+
startDragSpacing(e) {
|
|
1244
|
+
this.startPanelDrag(e, "spacing");
|
|
1245
|
+
},
|
|
1246
|
+
onDragSpacing(e) {
|
|
1247
|
+
this.onPanelDrag(e, "spacing");
|
|
1248
|
+
},
|
|
1249
|
+
stopDragSpacing() {
|
|
1250
|
+
this.stopPanelDrag("spacing");
|
|
1251
|
+
},
|
|
1252
|
+
// Column resize drag handling
|
|
1253
|
+
startColumnResize(e, columnType) {
|
|
1254
|
+
if (!this.editMode) return;
|
|
1255
|
+
e.preventDefault();
|
|
1256
|
+
e.stopPropagation();
|
|
1257
|
+
this.resizingColumn = columnType;
|
|
1258
|
+
this.resizeStartX = e.clientX;
|
|
1259
|
+
const currentVal = this.editValues[columnType] || this.configOptions[columnType].value;
|
|
1260
|
+
this.resizeStartValue = this.parseValue(currentVal).num;
|
|
1261
|
+
},
|
|
1262
|
+
onColumnResize(e) {
|
|
1263
|
+
if (!this.resizingColumn) return;
|
|
1264
|
+
const deltaX = e.clientX - this.resizeStartX;
|
|
1265
|
+
const col = this.resizingColumn;
|
|
1266
|
+
const unit = this.getUnit(col);
|
|
1267
|
+
let pxPerUnit;
|
|
1268
|
+
if (unit === "vw") {
|
|
1269
|
+
pxPerUnit = window.innerWidth / 100;
|
|
1270
|
+
} else if (unit === "rem") {
|
|
1271
|
+
pxPerUnit = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
1272
|
+
} else {
|
|
1273
|
+
pxPerUnit = 1;
|
|
1274
|
+
}
|
|
1275
|
+
const isRightHandle = col === "contentMax" || col === "contentBase";
|
|
1276
|
+
const delta = isRightHandle ? deltaX / pxPerUnit : -deltaX / pxPerUnit;
|
|
1277
|
+
let newValue = this.resizeStartValue + delta;
|
|
1278
|
+
if (newValue < 0) newValue = 0;
|
|
1279
|
+
newValue = Math.round(newValue * 10) / 10;
|
|
1280
|
+
this.updateConfigValue(col, newValue + unit);
|
|
1281
|
+
this.updateColumnWidths();
|
|
1282
|
+
},
|
|
1283
|
+
stopColumnResize() {
|
|
1284
|
+
this.resizingColumn = null;
|
|
1285
|
+
},
|
|
1286
|
+
// Map column names to their config keys for resizing
|
|
1287
|
+
getResizeConfig(colName) {
|
|
1288
|
+
const map = {
|
|
1289
|
+
"full-limit": "fullLimit",
|
|
1290
|
+
"feature": "featureScale",
|
|
1291
|
+
"popout": "popoutWidth"
|
|
1292
|
+
// content has its own integrated handles for min/max/base
|
|
1293
|
+
// feature has its own integrated handles for min/scale/max
|
|
1294
|
+
};
|
|
1295
|
+
return map[colName] || null;
|
|
1296
|
+
},
|
|
1297
|
+
// Parse a CSS variables string into a config object
|
|
1298
|
+
parseConfigString(input) {
|
|
1299
|
+
const str = input.trim();
|
|
1300
|
+
const config = { gapScale: {}, breakpoints: {} };
|
|
1301
|
+
const varMap = {
|
|
1302
|
+
"--base-gap": "baseGap",
|
|
1303
|
+
"--max-gap": "maxGap",
|
|
1304
|
+
"--content-min": "contentMin",
|
|
1305
|
+
"--content-max": "contentMax",
|
|
1306
|
+
"--content-base": "contentBase",
|
|
1307
|
+
"--popout-width": "popoutWidth",
|
|
1308
|
+
"--feature-min": "featureMin",
|
|
1309
|
+
"--feature-scale": "featureScale",
|
|
1310
|
+
"--feature-max": "featureMax",
|
|
1311
|
+
"--full-limit": "fullLimit",
|
|
1312
|
+
"--breakout-min": "breakoutMin",
|
|
1313
|
+
"--breakout-scale": "breakoutScale",
|
|
1314
|
+
"--default-col": "defaultCol"
|
|
1315
|
+
};
|
|
1316
|
+
const gapScaleMap = {
|
|
1317
|
+
"--gap-scale-default": "default",
|
|
1318
|
+
"--gap-scale-lg": "lg",
|
|
1319
|
+
"--gap-scale-xl": "xl"
|
|
1320
|
+
};
|
|
1321
|
+
const breakpointMap = {
|
|
1322
|
+
"--breakpoint-lg": "lg",
|
|
1323
|
+
"--breakpoint-xl": "xl"
|
|
1324
|
+
};
|
|
1325
|
+
const varRegex = /(?:\/\*\s*)?(--[\w-]+)\s*:\s*([^;*]+);?\s*(?:\*\/)?/g;
|
|
1326
|
+
let match;
|
|
1327
|
+
let foundAny = false;
|
|
1328
|
+
while ((match = varRegex.exec(str)) !== null) {
|
|
1329
|
+
const [, varName, value] = match;
|
|
1330
|
+
let trimmedValue = value.trim();
|
|
1331
|
+
foundAny = true;
|
|
1332
|
+
if (varMap[varName]) {
|
|
1333
|
+
config[varMap[varName]] = trimmedValue;
|
|
1334
|
+
} else if (gapScaleMap[varName]) {
|
|
1335
|
+
config.gapScale[gapScaleMap[varName]] = trimmedValue;
|
|
1336
|
+
} else if (breakpointMap[varName]) {
|
|
1337
|
+
config.breakpoints[breakpointMap[varName]] = trimmedValue.replace(/px$/, "");
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
if (!foundAny) {
|
|
1341
|
+
throw new Error('Invalid format. Paste CSS variables from "Copy Variables".');
|
|
1342
|
+
}
|
|
1343
|
+
return config;
|
|
1344
|
+
},
|
|
1345
|
+
// Open restore modal
|
|
1346
|
+
openRestoreModal() {
|
|
1347
|
+
this.showRestoreModal = true;
|
|
1348
|
+
this.restoreInput = "";
|
|
1349
|
+
this.restoreError = null;
|
|
1350
|
+
},
|
|
1351
|
+
// Close restore modal
|
|
1352
|
+
closeRestoreModal() {
|
|
1353
|
+
this.showRestoreModal = false;
|
|
1354
|
+
this.restoreInput = "";
|
|
1355
|
+
this.restoreError = null;
|
|
1356
|
+
},
|
|
1357
|
+
// Apply a parsed config to the editor
|
|
1358
|
+
restoreConfig() {
|
|
1359
|
+
this.restoreError = null;
|
|
1360
|
+
try {
|
|
1361
|
+
const config = this.parseConfigString(this.restoreInput);
|
|
1362
|
+
Object.keys(this.configOptions).forEach((key) => {
|
|
1363
|
+
if (config[key] !== void 0) {
|
|
1364
|
+
this.editValues[key] = config[key];
|
|
1365
|
+
this.updateConfigValue(key, config[key]);
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
if (config.gapScale) {
|
|
1369
|
+
Object.keys(this.gapScaleOptions).forEach((key) => {
|
|
1370
|
+
if (config.gapScale[key] !== void 0) {
|
|
1371
|
+
this.editValues[`gapScale_${key}`] = config.gapScale[key];
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
this.updateGapLive();
|
|
1375
|
+
}
|
|
1376
|
+
if (config.breakoutMin !== void 0) {
|
|
1377
|
+
this.editValues.breakout_min = config.breakoutMin;
|
|
1378
|
+
}
|
|
1379
|
+
if (config.breakoutScale !== void 0) {
|
|
1380
|
+
this.editValues.breakout_scale = config.breakoutScale;
|
|
1381
|
+
}
|
|
1382
|
+
this.updateBreakoutLive();
|
|
1383
|
+
if (config.breakpoints) {
|
|
1384
|
+
if (config.breakpoints.lg !== void 0) {
|
|
1385
|
+
this.editValues.breakpoint_lg = config.breakpoints.lg;
|
|
1386
|
+
}
|
|
1387
|
+
if (config.breakpoints.xl !== void 0) {
|
|
1388
|
+
this.editValues.breakpoint_xl = config.breakpoints.xl;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
this.updateColumnWidths();
|
|
1392
|
+
this.closeRestoreModal();
|
|
1393
|
+
this.configCopied = false;
|
|
1394
|
+
} catch (e) {
|
|
1395
|
+
this.restoreError = e.message;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
const template = `
|
|
1400
|
+
<div x-show="isVisible"
|
|
1401
|
+
x-transition
|
|
1402
|
+
class="breakout-grid-visualizer"
|
|
1403
|
+
@mousemove.window="onColumnResize($event)"
|
|
1404
|
+
@mouseup.window="stopColumnResize()"
|
|
1405
|
+
style="position: fixed; inset: 0; pointer-events: none; z-index: 9999;">
|
|
1406
|
+
|
|
1407
|
+
<!-- Edit Mode Backdrop - fades page content -->
|
|
1408
|
+
<div x-show="editMode"
|
|
1409
|
+
x-transition:enter="transition ease-out duration-200"
|
|
1410
|
+
x-transition:enter-start="opacity-0"
|
|
1411
|
+
x-transition:enter-end="opacity-100"
|
|
1412
|
+
x-transition:leave="transition ease-in duration-150"
|
|
1413
|
+
x-transition:leave-start="opacity-100"
|
|
1414
|
+
x-transition:leave-end="opacity-0"
|
|
1415
|
+
style="position: absolute; inset: 0; background: rgba(255, 255, 255, 0.85); z-index: 1;"></div>
|
|
1416
|
+
|
|
1417
|
+
<!-- Advanced Span Examples Overlay -->
|
|
1418
|
+
<div x-show="showAdvanced"
|
|
1419
|
+
class="grid-cols-breakout"
|
|
1420
|
+
style="position: absolute; inset: 0; height: 100%; pointer-events: auto; z-index: 5;">
|
|
1421
|
+
|
|
1422
|
+
<!-- Left-anchored: full-start to feature-end -->
|
|
1423
|
+
<div x-data="{ hovered: false }"
|
|
1424
|
+
@mouseenter="hovered = true"
|
|
1425
|
+
@mouseleave="hovered = false"
|
|
1426
|
+
:style="{
|
|
1427
|
+
gridColumn: 'full-start / feature-end',
|
|
1428
|
+
background: hovered ? 'linear-gradient(135deg, rgba(236, 72, 153, 0.6) 0%, rgba(139, 92, 246, 0.6) 100%)' : 'linear-gradient(135deg, rgba(236, 72, 153, 0.25) 0%, rgba(139, 92, 246, 0.25) 100%)',
|
|
1429
|
+
border: '3px solid rgb(168, 85, 247)',
|
|
1430
|
+
margin: '1rem 0',
|
|
1431
|
+
padding: '1rem',
|
|
1432
|
+
display: 'flex',
|
|
1433
|
+
alignItems: 'center',
|
|
1434
|
+
justifyContent: 'flex-start',
|
|
1435
|
+
transition: 'background 0.2s ease'
|
|
1436
|
+
}">
|
|
1437
|
+
<div style="background: rgb(139, 92, 246);
|
|
1438
|
+
color: white;
|
|
1439
|
+
padding: 0.75rem 1rem;
|
|
1440
|
+
font-size: 0.75rem;
|
|
1441
|
+
font-weight: 700;
|
|
1442
|
+
text-align: left;
|
|
1443
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
|
|
1444
|
+
<div style="font-family: monospace; margin-bottom: 0.25rem;">.col-feature-left</div>
|
|
1445
|
+
<div style="font-size: 0.625rem; opacity: 0.9; font-weight: 500;">Left edge → feature boundary</div>
|
|
1446
|
+
</div>
|
|
1447
|
+
</div>
|
|
1448
|
+
|
|
1449
|
+
<!-- Right-anchored: feature-start to full-end -->
|
|
1450
|
+
<div x-data="{ hovered: false }"
|
|
1451
|
+
@mouseenter="hovered = true"
|
|
1452
|
+
@mouseleave="hovered = false"
|
|
1453
|
+
:style="{
|
|
1454
|
+
gridColumn: 'feature-start / full-end',
|
|
1455
|
+
background: hovered ? 'linear-gradient(135deg, rgba(34, 197, 94, 0.6) 0%, rgba(59, 130, 246, 0.6) 100%)' : 'linear-gradient(135deg, rgba(34, 197, 94, 0.25) 0%, rgba(59, 130, 246, 0.25) 100%)',
|
|
1456
|
+
border: '3px solid rgb(34, 197, 94)',
|
|
1457
|
+
margin: '1rem 0',
|
|
1458
|
+
padding: '1rem',
|
|
1459
|
+
display: 'flex',
|
|
1460
|
+
alignItems: 'center',
|
|
1461
|
+
justifyContent: 'flex-end',
|
|
1462
|
+
transition: 'background 0.2s ease'
|
|
1463
|
+
}">
|
|
1464
|
+
<div style="background: rgb(34, 197, 94);
|
|
1465
|
+
color: white;
|
|
1466
|
+
padding: 0.75rem 1rem;
|
|
1467
|
+
font-size: 0.75rem;
|
|
1468
|
+
font-weight: 700;
|
|
1469
|
+
text-align: right;
|
|
1470
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
|
|
1471
|
+
<div style="font-family: monospace; margin-bottom: 0.25rem;">.col-feature-right</div>
|
|
1472
|
+
<div style="font-size: 0.625rem; opacity: 0.9; font-weight: 500;">Feature boundary → right edge</div>
|
|
1473
|
+
</div>
|
|
1474
|
+
</div>
|
|
1475
|
+
|
|
1476
|
+
<!-- To center point: full-start to center-end -->
|
|
1477
|
+
<div x-data="{ hovered: false }"
|
|
1478
|
+
@mouseenter="hovered = true"
|
|
1479
|
+
@mouseleave="hovered = false"
|
|
1480
|
+
:style="{
|
|
1481
|
+
gridColumn: 'full-start / center-end',
|
|
1482
|
+
background: hovered ? 'linear-gradient(135deg, rgba(251, 146, 60, 0.6) 0%, rgba(234, 179, 8, 0.6) 100%)' : 'linear-gradient(135deg, rgba(251, 146, 60, 0.25) 0%, rgba(234, 179, 8, 0.25) 100%)',
|
|
1483
|
+
border: '3px solid rgb(234, 179, 8)',
|
|
1484
|
+
margin: '1rem 0',
|
|
1485
|
+
padding: '1rem',
|
|
1486
|
+
display: 'flex',
|
|
1487
|
+
alignItems: 'center',
|
|
1488
|
+
justifyContent: 'flex-end',
|
|
1489
|
+
transition: 'background 0.2s ease'
|
|
1490
|
+
}">
|
|
1491
|
+
<div style="background: rgb(234, 179, 8);
|
|
1492
|
+
color: white;
|
|
1493
|
+
padding: 0.75rem 1rem;
|
|
1494
|
+
font-size: 0.75rem;
|
|
1495
|
+
font-weight: 700;
|
|
1496
|
+
text-align: right;
|
|
1497
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
|
|
1498
|
+
<div style="font-family: monospace; margin-bottom: 0.25rem;">.col-center-left</div>
|
|
1499
|
+
<div style="font-size: 0.625rem; opacity: 0.9; font-weight: 500;">Left edge → center point</div>
|
|
1500
|
+
</div>
|
|
1501
|
+
</div>
|
|
1502
|
+
|
|
1503
|
+
<!-- Nested grid example: breakout-to-feature inside col-feature -->
|
|
1504
|
+
<div x-data="{ hovered: false }"
|
|
1505
|
+
@mouseenter="hovered = true"
|
|
1506
|
+
@mouseleave="hovered = false"
|
|
1507
|
+
:style="{
|
|
1508
|
+
gridColumn: 'feature',
|
|
1509
|
+
border: '3px dashed rgb(59, 130, 246)',
|
|
1510
|
+
margin: '1rem 0',
|
|
1511
|
+
background: hovered ? 'rgba(59, 130, 246, 0.3)' : 'rgba(59, 130, 246, 0.05)',
|
|
1512
|
+
transition: 'background 0.2s ease',
|
|
1513
|
+
padding: '0.5rem'
|
|
1514
|
+
}">
|
|
1515
|
+
<div style="font-size: 0.625rem; font-family: monospace; color: rgb(30, 64, 175); margin-bottom: 0.5rem; padding: 0.25rem;">
|
|
1516
|
+
Parent: .col-feature container
|
|
1517
|
+
</div>
|
|
1518
|
+
<div class="grid-cols-breakout breakout-to-feature"
|
|
1519
|
+
style="background: rgba(59, 130, 246, 0.1);">
|
|
1520
|
+
<div style="grid-column: feature;
|
|
1521
|
+
background: rgba(59, 130, 246, 0.3);
|
|
1522
|
+
padding: 0.5rem;
|
|
1523
|
+
font-size: 0.625rem;
|
|
1524
|
+
font-family: monospace;
|
|
1525
|
+
color: rgb(30, 64, 175);">
|
|
1526
|
+
.col-feature → fills container
|
|
1527
|
+
</div>
|
|
1528
|
+
<div style="grid-column: content;
|
|
1529
|
+
background: rgb(59, 130, 246);
|
|
1530
|
+
color: white;
|
|
1531
|
+
padding: 0.75rem 1rem;
|
|
1532
|
+
font-size: 0.75rem;
|
|
1533
|
+
font-weight: 700;
|
|
1534
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
|
|
1535
|
+
<div style="font-family: monospace; margin-bottom: 0.5rem;">.col-content → has margins</div>
|
|
1536
|
+
<div style="font-size: 0.625rem; opacity: 0.9; font-weight: 500; margin-bottom: 0.75rem;">breakout-to-feature collapses outer tracks</div>
|
|
1537
|
+
<pre style="font-size: 0.5rem; background: rgba(0,0,0,0.2); padding: 0.5rem; margin: 0; white-space: pre-wrap; text-align: left;"><div class="col-feature">
|
|
1538
|
+
<div class="grid-cols-breakout breakout-to-feature">
|
|
1539
|
+
<div class="col-feature">Fills container</div>
|
|
1540
|
+
<p class="col-content">Has margins</p>
|
|
1541
|
+
</div>
|
|
1542
|
+
</div></pre>
|
|
1543
|
+
</div>
|
|
1544
|
+
</div>
|
|
1545
|
+
</div>
|
|
1546
|
+
|
|
1547
|
+
<!-- Subgrid example: child aligns to parent grid tracks -->
|
|
1548
|
+
<div x-data="{ hovered: false }"
|
|
1549
|
+
@mouseenter="hovered = true"
|
|
1550
|
+
@mouseleave="hovered = false"
|
|
1551
|
+
:style="{
|
|
1552
|
+
gridColumn: 'feature-start / full-end',
|
|
1553
|
+
display: 'grid',
|
|
1554
|
+
gridTemplateColumns: 'subgrid',
|
|
1555
|
+
border: '3px solid rgb(236, 72, 153)',
|
|
1556
|
+
margin: '1rem 0',
|
|
1557
|
+
background: hovered ? 'rgba(236, 72, 153, 0.15)' : 'rgba(236, 72, 153, 0.05)',
|
|
1558
|
+
transition: 'background 0.2s ease'
|
|
1559
|
+
}">
|
|
1560
|
+
<!-- Parent label -->
|
|
1561
|
+
<div style="grid-column: 1 / -1;
|
|
1562
|
+
font-size: 0.625rem;
|
|
1563
|
+
font-family: monospace;
|
|
1564
|
+
color: rgb(157, 23, 77);
|
|
1565
|
+
padding: 0.5rem;
|
|
1566
|
+
background: rgba(236, 72, 153, 0.1);">
|
|
1567
|
+
Parent: .col-feature-right .grid-cols-breakout-subgrid
|
|
1568
|
+
</div>
|
|
1569
|
+
<!-- Child spanning feature (wider, lighter) -->
|
|
1570
|
+
<div style="grid-column: feature;
|
|
1571
|
+
background: rgba(236, 72, 153, 0.3);
|
|
1572
|
+
padding: 0.5rem;
|
|
1573
|
+
margin: 0.5rem 0;
|
|
1574
|
+
font-size: 0.625rem;
|
|
1575
|
+
font-family: monospace;
|
|
1576
|
+
color: rgb(157, 23, 77);">
|
|
1577
|
+
Child: .col-feature (aligns to feature area)
|
|
1578
|
+
</div>
|
|
1579
|
+
<!-- Child using subgrid to align to content (darker) -->
|
|
1580
|
+
<div style="grid-column: content;
|
|
1581
|
+
background: rgb(236, 72, 153);
|
|
1582
|
+
color: white;
|
|
1583
|
+
padding: 0.75rem 1rem;
|
|
1584
|
+
margin: 0.5rem 0;
|
|
1585
|
+
font-size: 0.75rem;
|
|
1586
|
+
font-weight: 700;
|
|
1587
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);">
|
|
1588
|
+
<div style="font-family: monospace; margin-bottom: 0.5rem;">Child: .col-content</div>
|
|
1589
|
+
<div style="font-size: 0.625rem; opacity: 0.9; font-weight: 500; margin-bottom: 0.75rem;">Subgrid lets children align to parent's named lines</div>
|
|
1590
|
+
<pre style="font-size: 0.5rem; background: rgba(0,0,0,0.2); padding: 0.5rem; margin: 0; white-space: pre-wrap; text-align: left;"><div class="col-feature-right grid-cols-breakout-subgrid">
|
|
1591
|
+
<div class="col-feature">Aligns to feature!</div>
|
|
1592
|
+
<div class="col-content">Aligns to content!</div>
|
|
1593
|
+
</div></pre>
|
|
1594
|
+
<div style="margin-top: 0.75rem;">
|
|
1595
|
+
<a href="https://caniuse.com/css-subgrid" target="_blank" rel="noopener" style="display: inline-block; background: rgba(255,255,255,0.2); color: white; text-decoration: none; padding: 0.375rem 0.75rem; border-radius: 0.25rem; font-size: 0.625rem; font-weight: 600; border: 1px solid rgba(255,255,255,0.3);">Check browser support</a>
|
|
1596
|
+
<span style="font-size: 0.5rem; opacity: 0.7; margin-left: 0.5rem;">(~90% as of Jan 2025)</span>
|
|
1597
|
+
</div>
|
|
1598
|
+
</div>
|
|
1599
|
+
</div>
|
|
1600
|
+
|
|
1601
|
+
</div>
|
|
1602
|
+
|
|
1603
|
+
<!-- Spacing Panel -->
|
|
1604
|
+
<div x-show="!showAdvanced"
|
|
1605
|
+
x-init="updateCurrentBreakpoint()"
|
|
1606
|
+
@mousemove.window="onDragSpacing($event)"
|
|
1607
|
+
@mouseup.window="stopDragSpacing()"
|
|
1608
|
+
:style="{
|
|
1609
|
+
position: 'fixed',
|
|
1610
|
+
left: spacingPanelPos.x + 'px',
|
|
1611
|
+
top: spacingPanelPos.y + 'px',
|
|
1612
|
+
zIndex: 30,
|
|
1613
|
+
pointerEvents: 'auto',
|
|
1614
|
+
background: '#f7f7f7',
|
|
1615
|
+
borderRadius: '8px',
|
|
1616
|
+
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.15)',
|
|
1617
|
+
width: '220px',
|
|
1618
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
1619
|
+
overflow: 'hidden'
|
|
1620
|
+
}">
|
|
1621
|
+
<!-- Header -->
|
|
1622
|
+
<div @mousedown="startDragSpacing($event)"
|
|
1623
|
+
@dblclick="spacingPanelCollapsed = !spacingPanelCollapsed; localStorage.setItem('breakoutGridSpacingCollapsed', spacingPanelCollapsed)"
|
|
1624
|
+
style="padding: 8px 12px; background: #1a1a2e; color: white; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none;">
|
|
1625
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
1626
|
+
<span style="font-weight: 600; font-size: 12px;">Spacing</span>
|
|
1627
|
+
<span style="font-size: 10px; font-weight: 600; color: white; background: transparent; border: 1.5px solid rgba(255,255,255,0.5); padding: 1px 6px; border-radius: 3px;" x-text="'@' + currentBreakpoint"></span>
|
|
1628
|
+
</div>
|
|
1629
|
+
<button @click.stop="spacingPanelCollapsed = !spacingPanelCollapsed; localStorage.setItem('breakoutGridSpacingCollapsed', spacingPanelCollapsed)" style="background: transparent; border: none; color: rgba(255,255,255,0.6); cursor: pointer; font-size: 14px; line-height: 1; padding: 0;" x-text="spacingPanelCollapsed ? '+' : '−'"></button>
|
|
1630
|
+
</div>
|
|
1631
|
+
<!-- Content -->
|
|
1632
|
+
<div x-show="!spacingPanelCollapsed" style="padding: 12px;">
|
|
1633
|
+
<!-- Gap -->
|
|
1634
|
+
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
1635
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
1636
|
+
<span style="font-size: 10px; font-weight: 600; color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px;">Gap</span>
|
|
1637
|
+
<span style="font-size: 9px; color: #9ca3af;">outer margins</span>
|
|
1638
|
+
</div>
|
|
1639
|
+
<div style="display: flex; align-items: flex-end; gap: 8px;">
|
|
1640
|
+
<div style="width: var(--gap); height: 24px; background: #f97316; min-width: 20px;"></div>
|
|
1641
|
+
<div style="width: 24px; height: var(--gap); background: #f97316; min-height: 20px;"></div>
|
|
1642
|
+
</div>
|
|
1643
|
+
<div style="font-size: 9px; font-family: 'SF Mono', Monaco, monospace; color: #6b7280;">
|
|
1644
|
+
clamp(<span style="color: #10b981; font-weight: 600;" x-text="editValues.baseGap || configOptions.baseGap.value"></span>, <span style="color: #6366f1; font-weight: 600;" x-text="editValues['gapScale_' + (currentBreakpoint === 'mobile' ? 'default' : currentBreakpoint)] || gapScaleOptions[currentBreakpoint === 'mobile' ? 'default' : currentBreakpoint].value"></span>, <span style="color: #10b981; font-weight: 600;" x-text="editValues.maxGap || configOptions.maxGap.value"></span>)
|
|
1645
|
+
</div>
|
|
1646
|
+
</div>
|
|
1647
|
+
<!-- Breakout Padding -->
|
|
1648
|
+
<div style="display: flex; flex-direction: column; gap: 8px; padding-top: 12px; margin-top: 12px; border-top: 1px solid #e5e5e5;">
|
|
1649
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
1650
|
+
<span style="font-size: 10px; font-weight: 600; color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px;">Breakout</span>
|
|
1651
|
+
<span style="font-size: 9px; color: #9ca3af;">p-breakout / m-breakout</span>
|
|
1652
|
+
</div>
|
|
1653
|
+
<div style="display: flex; align-items: flex-end; gap: 8px;">
|
|
1654
|
+
<div style="width: var(--breakout-padding); height: 24px; background: #8b5cf6; min-width: 20px;"></div>
|
|
1655
|
+
<div style="width: 24px; height: var(--breakout-padding); background: #8b5cf6; min-height: 20px;"></div>
|
|
1656
|
+
</div>
|
|
1657
|
+
<div style="font-size: 9px; font-family: 'SF Mono', Monaco, monospace; color: #6b7280;">
|
|
1658
|
+
clamp(<span style="color: #8b5cf6; font-weight: 600;" x-text="editValues.breakout_min || breakoutOptions.min.value"></span>, <span style="color: #8b5cf6; font-weight: 600;" x-text="editValues.breakout_scale || breakoutOptions.scale.value"></span>, <span style="color: #10b981; font-weight: 600;" x-text="editValues.popoutWidth || configOptions.popoutWidth.value"></span>)
|
|
1659
|
+
</div>
|
|
1660
|
+
<!-- Editable breakout values -->
|
|
1661
|
+
<div style="display: flex; gap: 8px; margin-top: 4px;">
|
|
1662
|
+
<div style="flex: 1;">
|
|
1663
|
+
<div style="font-size: 8px; color: #9ca3af; margin-bottom: 2px;">min</div>
|
|
1664
|
+
<div style="display: flex; align-items: center; gap: 2px;">
|
|
1665
|
+
<input type="number" :value="getBreakoutNumeric('min')" @input="updateBreakoutNumeric('min', $event.target.value)" step="0.5"
|
|
1666
|
+
style="width: 100%; padding: 4px 6px; font-size: 10px; font-family: 'SF Mono', Monaco, monospace; border: 1px solid #e5e5e5; border-radius: 3px; background: white; text-align: right;">
|
|
1667
|
+
<span style="font-size: 9px; color: #9ca3af;" x-text="getBreakoutUnit('min')"></span>
|
|
1668
|
+
</div>
|
|
1669
|
+
</div>
|
|
1670
|
+
<div style="flex: 1;">
|
|
1671
|
+
<div style="font-size: 8px; color: #9ca3af; margin-bottom: 2px;">scale</div>
|
|
1672
|
+
<div style="display: flex; align-items: center; gap: 2px;">
|
|
1673
|
+
<input type="number" :value="getBreakoutNumeric('scale')" @input="updateBreakoutNumeric('scale', $event.target.value)" step="1"
|
|
1674
|
+
style="width: 100%; padding: 4px 6px; font-size: 10px; font-family: 'SF Mono', Monaco, monospace; border: 1px solid #e5e5e5; border-radius: 3px; background: white; text-align: right;">
|
|
1675
|
+
<span style="font-size: 9px; color: #9ca3af;" x-text="getBreakoutUnit('scale')"></span>
|
|
1676
|
+
</div>
|
|
1677
|
+
</div>
|
|
1678
|
+
</div>
|
|
1679
|
+
<div style="font-size: 8px; color: #9ca3af; font-style: italic;">max = popout width</div>
|
|
1680
|
+
</div>
|
|
1681
|
+
</div>
|
|
1682
|
+
</div>
|
|
1683
|
+
|
|
1684
|
+
<!-- Grid Overlay (hidden in Advanced mode) -->
|
|
1685
|
+
<div x-show="!showAdvanced" x-init="$watch('isVisible', v => v && setTimeout(() => updateColumnWidths(), 50)); setTimeout(() => updateColumnWidths(), 100)" class="grid-cols-breakout breakout-visualizer-grid" style="height: 100%; position: relative; z-index: 2;">
|
|
1686
|
+
<template x-for="area in gridAreas" :key="area.name">
|
|
1687
|
+
<div :class="'col-' + area.name"
|
|
1688
|
+
@click="selectArea(area.name)"
|
|
1689
|
+
@mouseenter="hoveredArea = area.name"
|
|
1690
|
+
@mouseleave="hoveredArea = null"
|
|
1691
|
+
:style="{
|
|
1692
|
+
backgroundColor: (hoveredArea === area.name || isSelected(area.name)) ? area.color.replace('0.25', '0.6') : area.color,
|
|
1693
|
+
borderLeft: '1px solid ' + area.borderColor,
|
|
1694
|
+
borderRight: '1px solid ' + area.borderColor,
|
|
1695
|
+
position: 'relative',
|
|
1696
|
+
height: '100%',
|
|
1697
|
+
pointerEvents: 'auto',
|
|
1698
|
+
cursor: 'pointer',
|
|
1699
|
+
transition: 'background-color 0.2s'
|
|
1700
|
+
}">
|
|
1701
|
+
|
|
1702
|
+
<!-- Label (centered) -->
|
|
1703
|
+
<div x-show="showLabels"
|
|
1704
|
+
:style="{
|
|
1705
|
+
position: 'absolute',
|
|
1706
|
+
top: '50%',
|
|
1707
|
+
left: '50%',
|
|
1708
|
+
transform: 'translate(-50%, -50%)',
|
|
1709
|
+
backgroundColor: area.borderColor,
|
|
1710
|
+
color: 'white',
|
|
1711
|
+
padding: '0.75rem 1rem',
|
|
1712
|
+
borderRadius: '0.375rem',
|
|
1713
|
+
fontSize: '0.75rem',
|
|
1714
|
+
fontWeight: '600',
|
|
1715
|
+
textTransform: 'uppercase',
|
|
1716
|
+
letterSpacing: '0.05em',
|
|
1717
|
+
whiteSpace: 'nowrap',
|
|
1718
|
+
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
|
|
1719
|
+
opacity: isSelected(area.name) ? '1' : '0.8',
|
|
1720
|
+
textAlign: 'center',
|
|
1721
|
+
zIndex: '10'
|
|
1722
|
+
}">
|
|
1723
|
+
<div x-text="area.label"></div>
|
|
1724
|
+
<div x-show="showClassNames"
|
|
1725
|
+
:style="{
|
|
1726
|
+
fontSize: '0.625rem',
|
|
1727
|
+
fontWeight: '500',
|
|
1728
|
+
textTransform: 'none',
|
|
1729
|
+
marginTop: '0.25rem',
|
|
1730
|
+
opacity: '0.9',
|
|
1731
|
+
fontFamily: 'monospace'
|
|
1732
|
+
}" x-text="area.className"></div>
|
|
1733
|
+
<div x-show="showPixelWidths && columnWidths[area.name] > 0"
|
|
1734
|
+
:style="{
|
|
1735
|
+
fontSize: '0.625rem',
|
|
1736
|
+
fontWeight: '600',
|
|
1737
|
+
textTransform: 'none',
|
|
1738
|
+
marginTop: '0.25rem',
|
|
1739
|
+
opacity: '0.75',
|
|
1740
|
+
fontFamily: 'monospace',
|
|
1741
|
+
backgroundColor: 'rgba(0,0,0,0.2)',
|
|
1742
|
+
padding: '0.125rem 0.375rem',
|
|
1743
|
+
borderRadius: '0.25rem'
|
|
1744
|
+
}" x-text="columnWidths[area.name] + 'px'"></div>
|
|
1745
|
+
</div>
|
|
1746
|
+
|
|
1747
|
+
<!-- Lorem Ipsum Content (behind label) -->
|
|
1748
|
+
<div x-show="showLoremIpsum"
|
|
1749
|
+
:style="{
|
|
1750
|
+
position: 'absolute',
|
|
1751
|
+
inset: '0',
|
|
1752
|
+
padding: showGapPadding ? 'var(--gap)' : (showBreakoutPadding ? 'var(--breakout-padding)' : '1.5rem 0'),
|
|
1753
|
+
boxSizing: 'border-box',
|
|
1754
|
+
overflow: 'hidden',
|
|
1755
|
+
whiteSpace: 'pre-line',
|
|
1756
|
+
fontSize: '1.125rem',
|
|
1757
|
+
lineHeight: '1.75',
|
|
1758
|
+
color: 'white',
|
|
1759
|
+
textShadow: '0 1px 2px rgba(0,0,0,0.3)',
|
|
1760
|
+
zIndex: '1'
|
|
1761
|
+
}" x-text="loremContent"></div>
|
|
1762
|
+
|
|
1763
|
+
<!-- p-gap / px-gap Padding Overlay -->
|
|
1764
|
+
<div x-show="showGapPadding"
|
|
1765
|
+
:style="{
|
|
1766
|
+
position: 'absolute',
|
|
1767
|
+
inset: 'var(--gap)',
|
|
1768
|
+
border: '2px dotted ' + area.borderColor,
|
|
1769
|
+
backgroundColor: area.color.replace('0.1', '0.2'),
|
|
1770
|
+
pointerEvents: 'none',
|
|
1771
|
+
zIndex: '10'
|
|
1772
|
+
}">
|
|
1773
|
+
<div :style="{
|
|
1774
|
+
position: 'absolute',
|
|
1775
|
+
top: '0.5rem',
|
|
1776
|
+
left: '0.5rem',
|
|
1777
|
+
fontSize: '0.625rem',
|
|
1778
|
+
fontWeight: '700',
|
|
1779
|
+
color: area.borderColor,
|
|
1780
|
+
textTransform: 'uppercase',
|
|
1781
|
+
letterSpacing: '0.05em',
|
|
1782
|
+
backgroundColor: 'white',
|
|
1783
|
+
padding: '0.25rem 0.5rem',
|
|
1784
|
+
borderRadius: '0.25rem',
|
|
1785
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
|
|
1786
|
+
}">p-gap</div>
|
|
1787
|
+
</div>
|
|
1788
|
+
|
|
1789
|
+
<!-- p-breakout / px-breakout Padding Overlay -->
|
|
1790
|
+
<div x-show="showBreakoutPadding"
|
|
1791
|
+
:style="{
|
|
1792
|
+
position: 'absolute',
|
|
1793
|
+
inset: 'var(--breakout-padding)',
|
|
1794
|
+
border: '3px dashed ' + area.borderColor,
|
|
1795
|
+
backgroundColor: area.color.replace('0.1', '0.25'),
|
|
1796
|
+
pointerEvents: 'none',
|
|
1797
|
+
zIndex: '10'
|
|
1798
|
+
}">
|
|
1799
|
+
<div :style="{
|
|
1800
|
+
position: 'absolute',
|
|
1801
|
+
top: '0.5rem',
|
|
1802
|
+
left: '0.5rem',
|
|
1803
|
+
fontSize: '0.625rem',
|
|
1804
|
+
fontWeight: '700',
|
|
1805
|
+
color: area.borderColor,
|
|
1806
|
+
textTransform: 'uppercase',
|
|
1807
|
+
letterSpacing: '0.05em',
|
|
1808
|
+
backgroundColor: 'white',
|
|
1809
|
+
padding: '0.25rem 0.5rem',
|
|
1810
|
+
borderRadius: '0.25rem',
|
|
1811
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
|
|
1812
|
+
}">p-breakout</div>
|
|
1813
|
+
</div>
|
|
1814
|
+
|
|
1815
|
+
<!-- Drag Handle - Left (edit mode only, for resizable columns) -->
|
|
1816
|
+
<div x-show="editMode && hoveredArea === area.name && getResizeConfig(area.name)"
|
|
1817
|
+
@mousedown.stop="startColumnResize($event, getResizeConfig(area.name))"
|
|
1818
|
+
:style="{
|
|
1819
|
+
position: 'absolute',
|
|
1820
|
+
left: '-4px',
|
|
1821
|
+
top: '0',
|
|
1822
|
+
width: '16px',
|
|
1823
|
+
height: '100%',
|
|
1824
|
+
cursor: 'ew-resize',
|
|
1825
|
+
pointerEvents: 'auto',
|
|
1826
|
+
zIndex: '100',
|
|
1827
|
+
display: 'flex',
|
|
1828
|
+
alignItems: 'center',
|
|
1829
|
+
justifyContent: 'center'
|
|
1830
|
+
}">
|
|
1831
|
+
<div style="display: flex; flex-direction: column; align-items: center; gap: 4px;">
|
|
1832
|
+
<div :style="{
|
|
1833
|
+
width: '10px',
|
|
1834
|
+
height: '100px',
|
|
1835
|
+
background: area.borderColor,
|
|
1836
|
+
borderRadius: '5px',
|
|
1837
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.4)',
|
|
1838
|
+
border: '2px solid white'
|
|
1839
|
+
}"></div>
|
|
1840
|
+
<div :style="{
|
|
1841
|
+
background: area.borderColor,
|
|
1842
|
+
color: 'white',
|
|
1843
|
+
padding: '4px 8px',
|
|
1844
|
+
borderRadius: '4px',
|
|
1845
|
+
fontSize: '10px',
|
|
1846
|
+
fontWeight: '700',
|
|
1847
|
+
whiteSpace: 'nowrap',
|
|
1848
|
+
boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
|
|
1849
|
+
}" x-text="area.name === 'content' ? '← min' : '↔'"></div>
|
|
1850
|
+
</div>
|
|
1851
|
+
</div>
|
|
1852
|
+
|
|
1853
|
+
<!-- Content Min/Base/Max Visual Guides with integrated handles (edit mode only) -->
|
|
1854
|
+
<template x-if="editMode && area.name === 'content'">
|
|
1855
|
+
<div style="position: absolute; inset: 0; pointer-events: none; z-index: 50; overflow: visible;">
|
|
1856
|
+
<!-- Max boundary (outer, dotted) with drag handle - can overflow to show full width -->
|
|
1857
|
+
<div :style="{
|
|
1858
|
+
position: 'absolute',
|
|
1859
|
+
top: '0',
|
|
1860
|
+
bottom: '0',
|
|
1861
|
+
left: '50%',
|
|
1862
|
+
transform: 'translateX(-50%)',
|
|
1863
|
+
width: editValues.contentMax || configOptions.contentMax.value,
|
|
1864
|
+
border: '3px dotted rgba(139, 92, 246, 0.9)',
|
|
1865
|
+
boxSizing: 'border-box',
|
|
1866
|
+
background: 'rgba(139, 92, 246, 0.05)'
|
|
1867
|
+
}">
|
|
1868
|
+
<div style="position: absolute; top: 8px; right: 8px; background: rgba(139, 92, 246, 0.95); color: white; padding: 3px 8px; border-radius: 3px; font-size: 10px; font-weight: 700;">
|
|
1869
|
+
max: <span x-text="editValues.contentMax || configOptions.contentMax.value"></span>
|
|
1870
|
+
</div>
|
|
1871
|
+
<!-- Max drag handle on right edge, at top - show on hover or when selected -->
|
|
1872
|
+
<div x-show="hoveredArea === 'content' || selectedArea === 'content'"
|
|
1873
|
+
@mousedown.stop="startColumnResize($event, 'contentMax')"
|
|
1874
|
+
style="position: absolute; right: -8px; top: 8px; width: 16px; height: 60px; cursor: ew-resize; pointer-events: auto; display: flex; align-items: center; justify-content: center;">
|
|
1875
|
+
<div style="width: 8px; height: 100%; background: rgb(139, 92, 246); border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.4); border: 2px solid white;"></div>
|
|
1876
|
+
</div>
|
|
1877
|
+
</div>
|
|
1878
|
+
<!-- Base boundary (middle, solid) - half height, inset, can overflow -->
|
|
1879
|
+
<div :style="{
|
|
1880
|
+
position: 'absolute',
|
|
1881
|
+
top: '25%',
|
|
1882
|
+
bottom: '25%',
|
|
1883
|
+
left: '50%',
|
|
1884
|
+
transform: 'translateX(-50%)',
|
|
1885
|
+
width: editValues.contentBase || configOptions.contentBase.value,
|
|
1886
|
+
border: '3px solid rgba(236, 72, 153, 1)',
|
|
1887
|
+
background: 'rgba(236, 72, 153, 0.5)',
|
|
1888
|
+
boxSizing: 'border-box',
|
|
1889
|
+
borderRadius: '4px'
|
|
1890
|
+
}">
|
|
1891
|
+
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(236, 72, 153, 0.95); color: white; padding: 3px 8px; border-radius: 3px; font-size: 10px; font-weight: 700; white-space: nowrap;">
|
|
1892
|
+
base: <span x-text="editValues.contentBase || configOptions.contentBase.value"></span>
|
|
1893
|
+
</div>
|
|
1894
|
+
<!-- Base drag handle on right edge - show on hover or when selected -->
|
|
1895
|
+
<div x-show="hoveredArea === 'content' || selectedArea === 'content'"
|
|
1896
|
+
@mousedown.stop="startColumnResize($event, 'contentBase')"
|
|
1897
|
+
style="position: absolute; right: -8px; top: 50%; transform: translateY(-50%); width: 16px; height: 40px; cursor: ew-resize; pointer-events: auto; display: flex; align-items: center; justify-content: center;">
|
|
1898
|
+
<div style="width: 8px; height: 100%; background: rgb(236, 72, 153); border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.4); border: 2px solid white;"></div>
|
|
1899
|
+
</div>
|
|
1900
|
+
</div>
|
|
1901
|
+
<!-- Min boundary (inner, dashed) with drag handle - can overflow to show full width -->
|
|
1902
|
+
<div :style="{
|
|
1903
|
+
position: 'absolute',
|
|
1904
|
+
top: '0',
|
|
1905
|
+
bottom: '0',
|
|
1906
|
+
left: '50%',
|
|
1907
|
+
transform: 'translateX(-50%)',
|
|
1908
|
+
width: editValues.contentMin || configOptions.contentMin.value,
|
|
1909
|
+
border: '3px dashed rgba(168, 85, 247, 0.9)',
|
|
1910
|
+
background: 'rgba(168, 85, 247, 0.15)',
|
|
1911
|
+
boxSizing: 'border-box'
|
|
1912
|
+
}">
|
|
1913
|
+
<div style="position: absolute; top: 8px; left: 8px; background: rgba(168, 85, 247, 0.95); color: white; padding: 3px 8px; border-radius: 3px; font-size: 10px; font-weight: 700;">
|
|
1914
|
+
min: <span x-text="editValues.contentMin || configOptions.contentMin.value"></span>
|
|
1915
|
+
</div>
|
|
1916
|
+
<!-- Min drag handle on left edge, at top - show on hover or when selected -->
|
|
1917
|
+
<div x-show="hoveredArea === 'content' || selectedArea === 'content'"
|
|
1918
|
+
@mousedown.stop="startColumnResize($event, 'contentMin')"
|
|
1919
|
+
style="position: absolute; left: -8px; top: 8px; width: 16px; height: 60px; cursor: ew-resize; pointer-events: auto; display: flex; align-items: center; justify-content: center;">
|
|
1920
|
+
<div style="width: 8px; height: 100%; background: rgb(168, 85, 247); border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.4); border: 2px solid white;"></div>
|
|
1921
|
+
</div>
|
|
1922
|
+
</div>
|
|
1923
|
+
</div>
|
|
1924
|
+
</template>
|
|
1925
|
+
|
|
1926
|
+
<!-- Feature Min/Scale/Max Visual Guides with integrated handles (edit mode only) -->
|
|
1927
|
+
<template x-if="editMode && area.name === 'feature'">
|
|
1928
|
+
<div style="position: absolute; inset: 0; pointer-events: none; z-index: 50; overflow: visible;">
|
|
1929
|
+
<!-- Max boundary (outer, dotted) - anchored from right edge (content side) -->
|
|
1930
|
+
<div :style="{
|
|
1931
|
+
position: 'absolute',
|
|
1932
|
+
top: '0',
|
|
1933
|
+
bottom: '0',
|
|
1934
|
+
right: '0',
|
|
1935
|
+
width: editValues.featureMax || configOptions.featureMax.value,
|
|
1936
|
+
border: '3px dotted rgba(6, 182, 212, 0.9)',
|
|
1937
|
+
boxSizing: 'border-box',
|
|
1938
|
+
background: 'rgba(6, 182, 212, 0.05)'
|
|
1939
|
+
}">
|
|
1940
|
+
<div style="position: absolute; top: 8px; left: 8px; background: rgba(6, 182, 212, 0.95); color: white; padding: 3px 8px; border-radius: 3px; font-size: 10px; font-weight: 700;">
|
|
1941
|
+
max: <span x-text="editValues.featureMax || configOptions.featureMax.value"></span>
|
|
1942
|
+
</div>
|
|
1943
|
+
<!-- Max drag handle on left edge -->
|
|
1944
|
+
<div x-show="hoveredArea === 'feature' || selectedArea === 'feature'"
|
|
1945
|
+
@mousedown.stop="startColumnResize($event, 'featureMax')"
|
|
1946
|
+
style="position: absolute; left: -8px; top: 8px; width: 16px; height: 60px; cursor: ew-resize; pointer-events: auto; display: flex; align-items: center; justify-content: center;">
|
|
1947
|
+
<div style="width: 8px; height: 100%; background: rgb(6, 182, 212); border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.4); border: 2px solid white;"></div>
|
|
1948
|
+
</div>
|
|
1949
|
+
</div>
|
|
1950
|
+
<!-- Scale boundary (middle, solid) - half height, inset -->
|
|
1951
|
+
<div :style="{
|
|
1952
|
+
position: 'absolute',
|
|
1953
|
+
top: '25%',
|
|
1954
|
+
bottom: '25%',
|
|
1955
|
+
right: '0',
|
|
1956
|
+
width: editValues.featureScale || configOptions.featureScale.value,
|
|
1957
|
+
border: '3px solid rgba(14, 165, 233, 1)',
|
|
1958
|
+
background: 'rgba(14, 165, 233, 0.5)',
|
|
1959
|
+
boxSizing: 'border-box',
|
|
1960
|
+
borderRadius: '4px'
|
|
1961
|
+
}">
|
|
1962
|
+
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(14, 165, 233, 0.95); color: white; padding: 3px 8px; border-radius: 3px; font-size: 10px; font-weight: 700; white-space: nowrap;">
|
|
1963
|
+
scale: <span x-text="editValues.featureScale || configOptions.featureScale.value"></span>
|
|
1964
|
+
</div>
|
|
1965
|
+
<!-- Scale drag handle on left edge -->
|
|
1966
|
+
<div x-show="hoveredArea === 'feature' || selectedArea === 'feature'"
|
|
1967
|
+
@mousedown.stop="startColumnResize($event, 'featureScale')"
|
|
1968
|
+
style="position: absolute; left: -8px; top: 50%; transform: translateY(-50%); width: 16px; height: 40px; cursor: ew-resize; pointer-events: auto; display: flex; align-items: center; justify-content: center;">
|
|
1969
|
+
<div style="width: 8px; height: 100%; background: rgb(14, 165, 233); border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.4); border: 2px solid white;"></div>
|
|
1970
|
+
</div>
|
|
1971
|
+
</div>
|
|
1972
|
+
<!-- Min boundary (inner, dashed) -->
|
|
1973
|
+
<div :style="{
|
|
1974
|
+
position: 'absolute',
|
|
1975
|
+
top: '0',
|
|
1976
|
+
bottom: '0',
|
|
1977
|
+
right: '0',
|
|
1978
|
+
width: editValues.featureMin || configOptions.featureMin.value,
|
|
1979
|
+
border: '3px dashed rgba(56, 189, 248, 0.9)',
|
|
1980
|
+
background: 'rgba(56, 189, 248, 0.15)',
|
|
1981
|
+
boxSizing: 'border-box'
|
|
1982
|
+
}">
|
|
1983
|
+
<div style="position: absolute; bottom: 8px; left: 8px; background: rgba(56, 189, 248, 0.95); color: white; padding: 3px 8px; border-radius: 3px; font-size: 10px; font-weight: 700;">
|
|
1984
|
+
min: <span x-text="editValues.featureMin || configOptions.featureMin.value"></span>
|
|
1985
|
+
</div>
|
|
1986
|
+
<!-- Min drag handle on left edge, at bottom -->
|
|
1987
|
+
<div x-show="hoveredArea === 'feature' || selectedArea === 'feature'"
|
|
1988
|
+
@mousedown.stop="startColumnResize($event, 'featureMin')"
|
|
1989
|
+
style="position: absolute; left: -8px; bottom: 8px; width: 16px; height: 60px; cursor: ew-resize; pointer-events: auto; display: flex; align-items: center; justify-content: center;">
|
|
1990
|
+
<div style="width: 8px; height: 100%; background: rgb(56, 189, 248); border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.4); border: 2px solid white;"></div>
|
|
1991
|
+
</div>
|
|
1992
|
+
</div>
|
|
1993
|
+
</div>
|
|
1994
|
+
</template>
|
|
1995
|
+
</div>
|
|
1996
|
+
</template>
|
|
1997
|
+
</div>
|
|
1998
|
+
|
|
1999
|
+
|
|
2000
|
+
<!-- Control Panel - Ubiquiti-style -->
|
|
2001
|
+
<div :style="{
|
|
2002
|
+
position: 'fixed',
|
|
2003
|
+
bottom: '12px',
|
|
2004
|
+
right: '12px',
|
|
2005
|
+
pointerEvents: 'auto',
|
|
2006
|
+
background: '#f7f7f7',
|
|
2007
|
+
borderRadius: '8px',
|
|
2008
|
+
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.15)',
|
|
2009
|
+
width: '200px',
|
|
2010
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
2011
|
+
zIndex: '10000',
|
|
2012
|
+
overflow: 'hidden'
|
|
2013
|
+
}">
|
|
2014
|
+
|
|
2015
|
+
<!-- Header -->
|
|
2016
|
+
<div @dblclick="controlPanelCollapsed = !controlPanelCollapsed"
|
|
2017
|
+
style="padding: 8px 12px; background: #1a1a2e; color: white; display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none;">
|
|
2018
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
2019
|
+
<span style="font-weight: 600; font-size: 12px;">Grid</span>
|
|
2020
|
+
<span style="font-size: 10px; color: rgba(255,255,255,0.5);" x-text="version"></span>
|
|
2021
|
+
<span x-show="controlPanelCollapsed" style="font-size: 10px; color: rgba(255,255,255,0.4);">...</span>
|
|
2022
|
+
</div>
|
|
2023
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
2024
|
+
<span style="font-size: 11px; font-variant-numeric: tabular-nums; color: rgba(255,255,255,0.7);" x-text="viewportWidth + 'px'"></span>
|
|
2025
|
+
<button @click.stop="toggle()" style="background: transparent; border: none; color: rgba(255,255,255,0.6); cursor: pointer; font-size: 16px; line-height: 1; padding: 0;">×</button>
|
|
2026
|
+
</div>
|
|
2027
|
+
</div>
|
|
2028
|
+
|
|
2029
|
+
<!-- Collapsible Content -->
|
|
2030
|
+
<div x-show="!controlPanelCollapsed">
|
|
2031
|
+
<!-- Action Buttons -->
|
|
2032
|
+
<div style="padding: 8px; background: white; border-bottom: 1px solid #e5e5e5; display: flex; gap: 6px;">
|
|
2033
|
+
<button @click="openEditor()"
|
|
2034
|
+
:style="{
|
|
2035
|
+
flex: 1,
|
|
2036
|
+
padding: '6px 8px',
|
|
2037
|
+
fontSize: '10px',
|
|
2038
|
+
fontWeight: '600',
|
|
2039
|
+
border: 'none',
|
|
2040
|
+
borderRadius: '4px',
|
|
2041
|
+
cursor: 'pointer',
|
|
2042
|
+
background: showEditor ? '#1a1a2e' : '#e5e5e5',
|
|
2043
|
+
color: showEditor ? 'white' : '#374151'
|
|
2044
|
+
}">
|
|
2045
|
+
Config
|
|
2046
|
+
</button>
|
|
2047
|
+
<button @click="showDiagram = !showDiagram; if(showDiagram && Object.keys(editValues).length === 0) loadCurrentValues()"
|
|
2048
|
+
:style="{
|
|
2049
|
+
flex: 1,
|
|
2050
|
+
padding: '6px 8px',
|
|
2051
|
+
fontSize: '10px',
|
|
2052
|
+
fontWeight: '600',
|
|
2053
|
+
border: 'none',
|
|
2054
|
+
borderRadius: '4px',
|
|
2055
|
+
cursor: 'pointer',
|
|
2056
|
+
background: showDiagram ? '#1a1a2e' : '#e5e5e5',
|
|
2057
|
+
color: showDiagram ? 'white' : '#374151'
|
|
2058
|
+
}">
|
|
2059
|
+
Diagram
|
|
2060
|
+
</button>
|
|
2061
|
+
</div>
|
|
2062
|
+
|
|
2063
|
+
<!-- Display Options -->
|
|
2064
|
+
<div style="padding: 8px 12px; background: white; border-bottom: 1px solid #e5e5e5;">
|
|
2065
|
+
<div style="font-size: 9px; font-weight: 600; color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px;">Display</div>
|
|
2066
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px;">
|
|
2067
|
+
<label style="display: flex; align-items: center; cursor: pointer; font-size: 11px; color: #374151;">
|
|
2068
|
+
<input type="checkbox" x-model="showLabels" style="margin-right: 6px; cursor: pointer; accent-color: #1a1a2e;">
|
|
2069
|
+
Labels
|
|
2070
|
+
</label>
|
|
2071
|
+
<label style="display: flex; align-items: center; cursor: pointer; font-size: 11px; color: #374151;">
|
|
2072
|
+
<input type="checkbox" x-model="showClassNames" style="margin-right: 6px; cursor: pointer; accent-color: #1a1a2e;">
|
|
2073
|
+
Classes
|
|
2074
|
+
</label>
|
|
2075
|
+
<label style="display: flex; align-items: center; cursor: pointer; font-size: 11px; color: #374151;">
|
|
2076
|
+
<input type="checkbox" x-model="showMeasurements" style="margin-right: 6px; cursor: pointer; accent-color: #1a1a2e;">
|
|
2077
|
+
Values
|
|
2078
|
+
</label>
|
|
2079
|
+
<label style="display: flex; align-items: center; cursor: pointer; font-size: 11px; color: #374151;">
|
|
2080
|
+
<input type="checkbox" x-model="showLoremIpsum" style="margin-right: 6px; cursor: pointer; accent-color: #1a1a2e;">
|
|
2081
|
+
Lorem
|
|
2082
|
+
</label>
|
|
2083
|
+
<label style="display: flex; align-items: center; cursor: pointer; font-size: 11px; color: #374151;">
|
|
2084
|
+
<input type="checkbox" x-model="showPixelWidths" style="margin-right: 6px; cursor: pointer; accent-color: #1a1a2e;">
|
|
2085
|
+
Pixels
|
|
2086
|
+
</label>
|
|
2087
|
+
</div>
|
|
2088
|
+
</div>
|
|
2089
|
+
|
|
2090
|
+
<!-- Padding Options -->
|
|
2091
|
+
<div style="padding: 8px 12px; background: white; border-bottom: 1px solid #e5e5e5;">
|
|
2092
|
+
<div style="font-size: 9px; font-weight: 600; color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px;">Padding</div>
|
|
2093
|
+
<div style="display: flex; gap: 12px;">
|
|
2094
|
+
<label style="display: flex; align-items: center; cursor: pointer; font-size: 11px; color: #374151;">
|
|
2095
|
+
<input type="checkbox" x-model="showGapPadding" style="margin-right: 6px; cursor: pointer; accent-color: #1a1a2e;">
|
|
2096
|
+
p-gap
|
|
2097
|
+
</label>
|
|
2098
|
+
<label style="display: flex; align-items: center; cursor: pointer; font-size: 11px; color: #374151;">
|
|
2099
|
+
<input type="checkbox" x-model="showBreakoutPadding" style="margin-right: 6px; cursor: pointer; accent-color: #1a1a2e;">
|
|
2100
|
+
p-breakout
|
|
2101
|
+
</label>
|
|
2102
|
+
</div>
|
|
2103
|
+
</div>
|
|
2104
|
+
|
|
2105
|
+
<!-- Advanced -->
|
|
2106
|
+
<div style="padding: 8px 12px; background: white;">
|
|
2107
|
+
<label style="display: flex; align-items: center; cursor: pointer; font-size: 11px; color: #374151;">
|
|
2108
|
+
<input type="checkbox" x-model="showAdvanced" style="margin-right: 6px; cursor: pointer; accent-color: #1a1a2e;">
|
|
2109
|
+
Advanced Spans
|
|
2110
|
+
</label>
|
|
2111
|
+
</div>
|
|
2112
|
+
|
|
2113
|
+
<!-- Footer -->
|
|
2114
|
+
<div style="padding: 6px 12px; background: #f7f7f7; border-top: 1px solid #e5e5e5;">
|
|
2115
|
+
<div style="font-size: 9px; color: #9ca3af; text-align: center;">
|
|
2116
|
+
<kbd style="background: #e5e5e5; padding: 1px 4px; border-radius: 2px; font-size: 9px; font-weight: 600; color: #374151;">⌘G</kbd> toggle
|
|
2117
|
+
</div>
|
|
2118
|
+
</div>
|
|
2119
|
+
|
|
2120
|
+
<!-- Selected Area Info -->
|
|
2121
|
+
<div x-show="selectedArea" style="padding: 8px 12px; background: #f0f9ff; border-top: 1px solid #e5e5e5;">
|
|
2122
|
+
<div style="font-size: 11px; color: #1a1a2e; font-weight: 600; font-family: monospace;" x-text="gridAreas.find(a => a.name === selectedArea)?.className || ''"></div>
|
|
2123
|
+
</div>
|
|
2124
|
+
</div><!-- End Collapsible Content -->
|
|
2125
|
+
|
|
2126
|
+
</div>
|
|
2127
|
+
|
|
2128
|
+
<!-- Floating Editor Window - Ubiquiti-style -->
|
|
2129
|
+
<div x-show="showEditor"
|
|
2130
|
+
@mousedown.self="startDrag($event)"
|
|
2131
|
+
@mousemove.window="onDrag($event)"
|
|
2132
|
+
@mouseup.window="stopDrag()"
|
|
2133
|
+
:style="{
|
|
2134
|
+
position: 'fixed',
|
|
2135
|
+
left: editorPos.x + 'px',
|
|
2136
|
+
top: editorPos.y + 'px',
|
|
2137
|
+
width: '280px',
|
|
2138
|
+
maxHeight: '85vh',
|
|
2139
|
+
background: '#f7f7f7',
|
|
2140
|
+
borderRadius: '8px',
|
|
2141
|
+
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.15)',
|
|
2142
|
+
pointerEvents: 'auto',
|
|
2143
|
+
zIndex: '10001',
|
|
2144
|
+
overflow: 'hidden',
|
|
2145
|
+
fontFamily: 'system-ui, -apple-system, sans-serif'
|
|
2146
|
+
}">
|
|
2147
|
+
<!-- Editor Header (draggable) -->
|
|
2148
|
+
<div @mousedown="startDrag($event)"
|
|
2149
|
+
@dblclick="configEditorCollapsed = !configEditorCollapsed"
|
|
2150
|
+
style="padding: 10px 12px; background: #1a1a2e; color: white; cursor: move; display: flex; justify-content: space-between; align-items: center; user-select: none;">
|
|
2151
|
+
<div style="display: flex; align-items: center; gap: 8px;">
|
|
2152
|
+
<span style="font-weight: 600; font-size: 12px; letter-spacing: 0.3px;">Grid Config</span>
|
|
2153
|
+
<span x-show="configEditorCollapsed" style="font-size: 10px; color: rgba(255,255,255,0.4);">...</span>
|
|
2154
|
+
</div>
|
|
2155
|
+
<button @click.stop="closeEditor()" style="background: transparent; border: none; color: rgba(255,255,255,0.6); padding: 2px 6px; cursor: pointer; font-size: 16px; line-height: 1;">×</button>
|
|
2156
|
+
</div>
|
|
2157
|
+
<!-- Editor Content -->
|
|
2158
|
+
<div x-show="!configEditorCollapsed" style="max-height: calc(85vh - 40px); overflow-y: auto;">
|
|
2159
|
+
<!-- Workflow tip -->
|
|
2160
|
+
<div style="background: #e8f4f8; padding: 8px 12px; font-size: 10px; color: #1a1a2e; line-height: 1.4; border-bottom: 1px solid #e5e5e5;">
|
|
2161
|
+
Start with <strong>content</strong>, then build outward: popout → feature → full
|
|
2162
|
+
</div>
|
|
2163
|
+
|
|
2164
|
+
<!-- Track overflow warning -->
|
|
2165
|
+
<div x-show="getTrackOverflowWarning()"
|
|
2166
|
+
style="background: #fef3c7; padding: 8px 12px; font-size: 10px; color: #92400e; line-height: 1.4; border-bottom: 1px solid #fcd34d;">
|
|
2167
|
+
<span style="font-weight: 600;">⚠️</span> <span x-text="getTrackOverflowWarning()"></span>
|
|
2168
|
+
</div>
|
|
2169
|
+
|
|
2170
|
+
<!-- Content Section -->
|
|
2171
|
+
<div style="padding: 8px 12px; background: white; border-bottom: 1px solid #e5e5e5;">
|
|
2172
|
+
<div @click="copySection('content')" style="font-size: 9px; font-weight: 600; color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; cursor: pointer; display: flex; align-items: center; gap: 6px;" :style="{ color: sectionCopied === 'content' ? '#10b981' : '#6b7280' }">
|
|
2173
|
+
<span x-text="sectionCopied === 'content' ? '✓ Copied' : 'Content (Text Width)'"></span>
|
|
2174
|
+
</div>
|
|
2175
|
+
<template x-for="key in ['contentMin', 'contentBase', 'contentMax']" :key="'ed_'+key">
|
|
2176
|
+
<div style="display: flex; align-items: center; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid #f3f4f6;">
|
|
2177
|
+
<span style="font-size: 11px; color: #374151;" x-text="key.replace('content', '').toLowerCase()"></span>
|
|
2178
|
+
<div style="display: flex; align-items: center; gap: 4px;">
|
|
2179
|
+
<input type="number" :value="getNumericValue(key)" @input="updateNumericValue(key, $event.target.value)" step="1"
|
|
2180
|
+
style="width: 72px; padding: 6px 8px; font-size: 11px; font-family: 'SF Mono', Monaco, monospace; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; text-align: right;">
|
|
2181
|
+
<select x-show="hasUnitSelector(key)" @change="updateUnit(key, $event.target.value)" :value="getUnit(key)"
|
|
2182
|
+
style="padding: 6px 4px; font-size: 10px; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; color: #6b7280; cursor: pointer; width: 50px; text-align: center;">
|
|
2183
|
+
<template x-for="u in unitOptions" :key="u">
|
|
2184
|
+
<option :value="u" :selected="getUnit(key) === u" x-text="u"></option>
|
|
2185
|
+
</template>
|
|
2186
|
+
</select>
|
|
2187
|
+
<span x-show="!hasUnitSelector(key)" style="font-size: 10px; color: #9ca3af; width: 50px; text-align: center; display: inline-block;" x-text="getUnit(key)"></span>
|
|
2188
|
+
</div>
|
|
2189
|
+
</div>
|
|
2190
|
+
</template>
|
|
2191
|
+
<!-- Readability warning -->
|
|
2192
|
+
<div x-show="getContentReadabilityWarning()"
|
|
2193
|
+
x-data="{ expanded: false }"
|
|
2194
|
+
style="margin-top: 6px; padding: 6px 8px; background: #fef3c7; border-radius: 4px; border: 1px solid #fcd34d;">
|
|
2195
|
+
<div @click="expanded = !expanded" style="display: flex; align-items: flex-start; gap: 6px; cursor: pointer;">
|
|
2196
|
+
<svg style="width: 14px; height: 14px; color: #b45309; flex-shrink: 0; margin-top: 1px;" fill="currentColor" viewBox="0 0 20 20">
|
|
2197
|
+
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
|
|
2198
|
+
</svg>
|
|
2199
|
+
<div style="flex: 1;">
|
|
2200
|
+
<div style="font-size: 10px; font-weight: 600; color: #92400e;">Wide for reading</div>
|
|
2201
|
+
<div x-show="!expanded" style="font-size: 9px; color: #b45309; margin-top: 2px;">Ideal: 45–55rem for prose. Click for details.</div>
|
|
2202
|
+
<div x-show="expanded" x-transition style="font-size: 9px; color: #78350f; margin-top: 4px; line-height: 1.4;">
|
|
2203
|
+
At 16px base, 55rem+ can hit 100+ characters/line—too wide for comfortable reading.<br><br>
|
|
2204
|
+
<strong>Guidelines (at 1rem/16px):</strong><br>
|
|
2205
|
+
• 45ch ≈ 35–40rem (min)<br>
|
|
2206
|
+
• 66ch ≈ 45–50rem (ideal)<br>
|
|
2207
|
+
• 75ch ≈ 50–55rem (max)<br><br>
|
|
2208
|
+
Fine for mixed layouts; consider tightening for prose-heavy pages.
|
|
2209
|
+
</div>
|
|
2210
|
+
</div>
|
|
2211
|
+
</div>
|
|
2212
|
+
</div>
|
|
2213
|
+
</div>
|
|
2214
|
+
|
|
2215
|
+
<!-- Default Column Section -->
|
|
2216
|
+
<div style="padding: 8px 12px; background: white; border-bottom: 1px solid #e5e5e5;">
|
|
2217
|
+
<div style="display: flex; align-items: center; justify-content: space-between;">
|
|
2218
|
+
<div @click="copySection('defaultCol')" style="cursor: pointer;">
|
|
2219
|
+
<div style="font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;" :style="{ color: sectionCopied === 'defaultCol' ? '#10b981' : '#6b7280' }" x-text="sectionCopied === 'defaultCol' ? '✓ Copied' : 'Default Column'"></div>
|
|
2220
|
+
<div style="font-size: 9px; color: #9ca3af; margin-top: 2px;">For children without col-* class</div>
|
|
2221
|
+
</div>
|
|
2222
|
+
<select @change="editValues.defaultCol = $event.target.value; configCopied = false"
|
|
2223
|
+
:value="editValues.defaultCol || configOptions.defaultCol.value"
|
|
2224
|
+
style="padding: 6px 8px; font-size: 11px; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; cursor: pointer;">
|
|
2225
|
+
<template x-for="opt in configOptions.defaultCol.options" :key="opt">
|
|
2226
|
+
<option :value="opt" :selected="(editValues.defaultCol || configOptions.defaultCol.value) === opt" x-text="opt"></option>
|
|
2227
|
+
</template>
|
|
2228
|
+
</select>
|
|
2229
|
+
</div>
|
|
2230
|
+
</div>
|
|
2231
|
+
|
|
2232
|
+
<!-- Track Widths Section -->
|
|
2233
|
+
<div style="padding: 8px 12px; background: white; border-bottom: 1px solid #e5e5e5;">
|
|
2234
|
+
<div @click="copySection('tracks')" style="font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; cursor: pointer;" :style="{ color: sectionCopied === 'tracks' ? '#10b981' : '#6b7280' }" x-text="sectionCopied === 'tracks' ? '✓ Copied' : 'Track Widths'"></div>
|
|
2235
|
+
<template x-for="key in ['popoutWidth', 'fullLimit']" :key="'ed_'+key">
|
|
2236
|
+
<div style="display: flex; align-items: center; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid #f3f4f6;">
|
|
2237
|
+
<span style="font-size: 11px; color: #374151;" x-text="key.replace('Width', '')"></span>
|
|
2238
|
+
<div style="display: flex; align-items: center; gap: 4px;">
|
|
2239
|
+
<input type="number" :value="getNumericValue(key)" @input="updateNumericValue(key, $event.target.value)" step="1"
|
|
2240
|
+
style="width: 72px; padding: 6px 8px; font-size: 11px; font-family: 'SF Mono', Monaco, monospace; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; text-align: right;">
|
|
2241
|
+
<select x-show="hasUnitSelector(key)" @change="updateUnit(key, $event.target.value)" :value="getUnit(key)"
|
|
2242
|
+
style="padding: 6px 4px; font-size: 10px; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; color: #6b7280; cursor: pointer; width: 50px; text-align: center;">
|
|
2243
|
+
<template x-for="u in unitOptions" :key="u">
|
|
2244
|
+
<option :value="u" :selected="getUnit(key) === u" x-text="u"></option>
|
|
2245
|
+
</template>
|
|
2246
|
+
</select>
|
|
2247
|
+
<span x-show="!hasUnitSelector(key)" style="font-size: 10px; color: #9ca3af; width: 50px; text-align: center; display: inline-block;" x-text="getUnit(key)"></span>
|
|
2248
|
+
</div>
|
|
2249
|
+
</div>
|
|
2250
|
+
</template>
|
|
2251
|
+
</div>
|
|
2252
|
+
|
|
2253
|
+
<!-- Feature Section (Track Width) -->
|
|
2254
|
+
<div style="padding: 8px 12px; background: white; border-bottom: 1px solid #e5e5e5;">
|
|
2255
|
+
<div @click="copySection('feature')" style="font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; cursor: pointer;" :style="{ color: sectionCopied === 'feature' ? '#10b981' : '#6b7280' }" x-text="sectionCopied === 'feature' ? '✓ Copied' : 'Feature (Track Width)'"></div>
|
|
2256
|
+
<!-- featureMin (locked) -->
|
|
2257
|
+
<div style="display: flex; align-items: center; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid #f3f4f6;">
|
|
2258
|
+
<span style="font-size: 11px; color: #9ca3af;">min <span style="font-size: 8px;" title="Must be 0 for track to collapse">(locked)</span></span>
|
|
2259
|
+
<div style="display: flex; align-items: center; gap: 4px;">
|
|
2260
|
+
<input type="number" :value="getNumericValue('featureMin')" disabled
|
|
2261
|
+
style="width: 72px; padding: 6px 8px; font-size: 11px; font-family: 'SF Mono', Monaco, monospace; border: 1px solid #e5e5e5; border-radius: 4px; background: #f3f4f6; color: #9ca3af; text-align: right; cursor: not-allowed;">
|
|
2262
|
+
<select disabled style="padding: 6px 4px; font-size: 10px; border: 1px solid #e5e5e5; border-radius: 4px; background: #f3f4f6; color: #9ca3af; width: 50px; text-align: center; cursor: not-allowed;">
|
|
2263
|
+
<option selected>rem</option>
|
|
2264
|
+
</select>
|
|
2265
|
+
</div>
|
|
2266
|
+
</div>
|
|
2267
|
+
<!-- featureScale and featureMax -->
|
|
2268
|
+
<template x-for="key in ['featureScale', 'featureMax']" :key="'ed_'+key">
|
|
2269
|
+
<div style="display: flex; align-items: center; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid #f3f4f6;">
|
|
2270
|
+
<span style="font-size: 11px; color: #374151;" x-text="key.replace('feature', '').toLowerCase()"></span>
|
|
2271
|
+
<div style="display: flex; align-items: center; gap: 4px;">
|
|
2272
|
+
<input type="number" :value="getNumericValue(key)" @input="updateNumericValue(key, $event.target.value)" step="1"
|
|
2273
|
+
style="width: 72px; padding: 6px 8px; font-size: 11px; font-family: 'SF Mono', Monaco, monospace; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; text-align: right;">
|
|
2274
|
+
<select x-show="hasUnitSelector(key)" @change="updateUnit(key, $event.target.value)" :value="getUnit(key)"
|
|
2275
|
+
style="padding: 6px 4px; font-size: 10px; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; color: #6b7280; cursor: pointer; width: 50px; text-align: center;">
|
|
2276
|
+
<template x-for="u in unitOptions" :key="u">
|
|
2277
|
+
<option :value="u" :selected="getUnit(key) === u" x-text="u"></option>
|
|
2278
|
+
</template>
|
|
2279
|
+
</select>
|
|
2280
|
+
<span x-show="!hasUnitSelector(key)" style="font-size: 10px; color: #9ca3af; width: 50px; text-align: center; display: inline-block;" x-text="getUnit(key)"></span>
|
|
2281
|
+
</div>
|
|
2282
|
+
</div>
|
|
2283
|
+
</template>
|
|
2284
|
+
</div>
|
|
2285
|
+
|
|
2286
|
+
<!-- Gap Section (Outer Margins) -->
|
|
2287
|
+
<div style="padding: 8px 12px; background: white; border-bottom: 1px solid #e5e5e5;">
|
|
2288
|
+
<div @click="copySection('gap')" style="font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px; cursor: pointer;" :style="{ color: sectionCopied === 'gap' ? '#10b981' : '#6b7280' }" x-text="sectionCopied === 'gap' ? '✓ Copied' : 'Outer Margins'"></div>
|
|
2289
|
+
<div style="font-size: 9px; color: #9ca3af; margin-bottom: 8px; line-height: 1.4;">Space between viewport edge and content. Auto-centers your layout.</div>
|
|
2290
|
+
|
|
2291
|
+
<div style="display: flex; align-items: center; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid #f3f4f6;">
|
|
2292
|
+
<div>
|
|
2293
|
+
<span style="font-size: 11px; color: #374151;">min</span>
|
|
2294
|
+
<span style="font-size: 9px; color: #9ca3af; margin-left: 4px;">floor</span>
|
|
2295
|
+
<span style="font-size: 8px; color: #10b981; margin-left: 4px; font-weight: 500;">live</span>
|
|
2296
|
+
</div>
|
|
2297
|
+
<div style="display: flex; align-items: center; gap: 4px;">
|
|
2298
|
+
<input type="number" :value="getNumericValue('baseGap')" @input="updateNumericValue('baseGap', $event.target.value)" step="0.5"
|
|
2299
|
+
style="width: 72px; padding: 6px 8px; font-size: 11px; font-family: 'SF Mono', Monaco, monospace; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; text-align: right;">
|
|
2300
|
+
<select @change="updateUnit('baseGap', $event.target.value)" :value="getUnit('baseGap')"
|
|
2301
|
+
style="padding: 6px 4px; font-size: 10px; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; color: #6b7280; cursor: pointer; width: 50px; text-align: center;">
|
|
2302
|
+
<template x-for="u in unitOptions" :key="u">
|
|
2303
|
+
<option :value="u" :selected="getUnit('baseGap') === u" x-text="u"></option>
|
|
2304
|
+
</template>
|
|
2305
|
+
</select>
|
|
2306
|
+
</div>
|
|
2307
|
+
</div>
|
|
2308
|
+
<div style="display: flex; align-items: center; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid #f3f4f6;">
|
|
2309
|
+
<div>
|
|
2310
|
+
<span style="font-size: 11px; color: #374151;">max</span>
|
|
2311
|
+
<span style="font-size: 9px; color: #9ca3af; margin-left: 4px;">ceiling</span>
|
|
2312
|
+
<span style="font-size: 8px; color: #10b981; margin-left: 4px; font-weight: 500;">live</span>
|
|
2313
|
+
</div>
|
|
2314
|
+
<div style="display: flex; align-items: center; gap: 4px;">
|
|
2315
|
+
<input type="number" :value="getNumericValue('maxGap')" @input="updateNumericValue('maxGap', $event.target.value)" step="1"
|
|
2316
|
+
style="width: 72px; padding: 6px 8px; font-size: 11px; font-family: 'SF Mono', Monaco, monospace; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; text-align: right;">
|
|
2317
|
+
<select @change="updateUnit('maxGap', $event.target.value)" :value="getUnit('maxGap')"
|
|
2318
|
+
style="padding: 6px 4px; font-size: 10px; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; color: #6b7280; cursor: pointer; width: 50px; text-align: center;">
|
|
2319
|
+
<template x-for="u in unitOptions" :key="u">
|
|
2320
|
+
<option :value="u" :selected="getUnit('maxGap') === u" x-text="u"></option>
|
|
2321
|
+
</template>
|
|
2322
|
+
</select>
|
|
2323
|
+
</div>
|
|
2324
|
+
</div>
|
|
2325
|
+
|
|
2326
|
+
<div style="font-size: 9px; font-weight: 600; color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px; margin: 10px 0 2px;">Responsive Scale <span style="font-size: 8px; color: #10b981; font-weight: 500; text-transform: none;">live preview</span></div>
|
|
2327
|
+
<div style="font-size: 9px; color: #9ca3af; margin-bottom: 6px; line-height: 1.4;">Fluid value (vw) that grows with viewport. Active breakpoint previews live.</div>
|
|
2328
|
+
<template x-for="key in Object.keys(gapScaleOptions)" :key="'ed_gs_'+key">
|
|
2329
|
+
<div :style="{
|
|
2330
|
+
display: 'flex',
|
|
2331
|
+
alignItems: 'center',
|
|
2332
|
+
justifyContent: 'space-between',
|
|
2333
|
+
padding: '4px 6px',
|
|
2334
|
+
margin: '0 -6px',
|
|
2335
|
+
borderBottom: '1px solid #f3f4f6',
|
|
2336
|
+
borderRadius: '4px',
|
|
2337
|
+
background: (key === 'default' && currentBreakpoint === 'mobile') || key === currentBreakpoint ? 'rgba(249, 115, 22, 0.1)' : 'transparent',
|
|
2338
|
+
border: (key === 'default' && currentBreakpoint === 'mobile') || key === currentBreakpoint ? '1px solid rgba(249, 115, 22, 0.3)' : '1px solid transparent'
|
|
2339
|
+
}">
|
|
2340
|
+
<div style="display: flex; align-items: center; gap: 6px;">
|
|
2341
|
+
<span style="font-size: 11px; color: #374151;" x-text="key === 'default' ? 'mobile' : key"></span>
|
|
2342
|
+
<span x-show="(key === 'default' && currentBreakpoint === 'mobile') || key === currentBreakpoint"
|
|
2343
|
+
style="font-size: 8px; font-weight: 600; color: #f97316;">ACTIVE</span>
|
|
2344
|
+
</div>
|
|
2345
|
+
<div style="display: flex; align-items: center; gap: 4px;">
|
|
2346
|
+
<input type="number" :value="getGapScaleNumeric(key)" @input="updateGapScaleNumeric(key, $event.target.value)" step="1"
|
|
2347
|
+
style="width: 72px; padding: 6px 8px; font-size: 11px; font-family: 'SF Mono', Monaco, monospace; border: 1px solid #e5e5e5; border-radius: 4px; background: #f9fafb; text-align: right;">
|
|
2348
|
+
<span style="font-size: 10px; color: #9ca3af; width: 50px; text-align: center; display: inline-block;" x-text="getGapScaleUnit(key)"></span>
|
|
2349
|
+
</div>
|
|
2350
|
+
</div>
|
|
2351
|
+
</template>
|
|
2352
|
+
|
|
2353
|
+
<!-- Live formula preview -->
|
|
2354
|
+
<div style="margin-top: 8px; padding: 8px; background: #f3f4f6; border-radius: 4px; font-family: 'SF Mono', Monaco, monospace; font-size: 9px; line-height: 1.6;">
|
|
2355
|
+
<div style="color: #6b7280; margin-bottom: 4px;">Generated CSS:</div>
|
|
2356
|
+
<div style="color: #374151;"><span style="color: #9ca3af;">mobile:</span> clamp(<span x-text="editValues.baseGap || configOptions.baseGap.value"></span>, <span x-text="editValues.gapScale_default || gapScaleOptions.default.value"></span>, <span x-text="editValues.maxGap || configOptions.maxGap.value"></span>)</div>
|
|
2357
|
+
<div style="color: #374151;"><span style="color: #9ca3af;">lg:</span> clamp(<span x-text="editValues.baseGap || configOptions.baseGap.value"></span>, <span x-text="editValues.gapScale_lg || gapScaleOptions.lg.value"></span>, <span x-text="editValues.maxGap || configOptions.maxGap.value"></span>)</div>
|
|
2358
|
+
<div style="color: #374151;"><span style="color: #9ca3af;">xl:</span> clamp(<span x-text="editValues.baseGap || configOptions.baseGap.value"></span>, <span x-text="editValues.gapScale_xl || gapScaleOptions.xl.value"></span>, <span x-text="editValues.maxGap || configOptions.maxGap.value"></span>)</div>
|
|
2359
|
+
</div>
|
|
2360
|
+
</div>
|
|
2361
|
+
|
|
2362
|
+
<!-- Action Buttons -->
|
|
2363
|
+
<div style="padding: 10px 12px; background: #f7f7f7; display: flex; gap: 8px;">
|
|
2364
|
+
<button @click="copyConfig()" :style="{ flex: 1, padding: '8px', fontSize: '11px', fontWeight: '600', border: 'none', borderRadius: '4px', cursor: 'pointer', background: copySuccess ? '#10b981' : '#1a1a2e', color: 'white', transition: 'background 0.2s' }">
|
|
2365
|
+
<span x-text="copySuccess ? '✓ Copied' : 'Copy Variables'"></span>
|
|
2366
|
+
</button>
|
|
2367
|
+
<button @click="openRestoreModal()" style="padding: 8px 12px; font-size: 11px; font-weight: 600; border: 1px solid #e5e5e5; border-radius: 4px; cursor: pointer; background: white; color: #374151;" title="Restore from CSS variables">
|
|
2368
|
+
Restore
|
|
2369
|
+
</button>
|
|
2370
|
+
<button @click="downloadCSS()" style="padding: 8px 12px; font-size: 11px; font-weight: 600; border: 1px solid #e5e5e5; border-radius: 4px; cursor: pointer; background: white; color: #374151;">
|
|
2371
|
+
CSS
|
|
2372
|
+
</button>
|
|
2373
|
+
</div>
|
|
2374
|
+
</div>
|
|
2375
|
+
</div>
|
|
2376
|
+
|
|
2377
|
+
<!-- Restore Config Modal -->
|
|
2378
|
+
<div x-show="showRestoreModal"
|
|
2379
|
+
x-transition:enter="transition ease-out duration-200"
|
|
2380
|
+
x-transition:enter-start="opacity-0"
|
|
2381
|
+
x-transition:enter-end="opacity-100"
|
|
2382
|
+
x-transition:leave="transition ease-in duration-150"
|
|
2383
|
+
x-transition:leave-start="opacity-100"
|
|
2384
|
+
x-transition:leave-end="opacity-0"
|
|
2385
|
+
style="position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 10002; pointer-events: auto;">
|
|
2386
|
+
<div @click.stop style="background: white; border-radius: 8px; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.25); width: 400px; max-width: 90vw; font-family: system-ui, -apple-system, sans-serif;">
|
|
2387
|
+
<!-- Modal Header -->
|
|
2388
|
+
<div style="padding: 12px 16px; background: #1a1a2e; color: white; border-radius: 8px 8px 0 0; display: flex; justify-content: space-between; align-items: center;">
|
|
2389
|
+
<span style="font-weight: 600; font-size: 13px;">Restore Config</span>
|
|
2390
|
+
<button @click="closeRestoreModal()" style="background: transparent; border: none; color: rgba(255,255,255,0.6); cursor: pointer; font-size: 18px; line-height: 1;">×</button>
|
|
2391
|
+
</div>
|
|
2392
|
+
<!-- Modal Body -->
|
|
2393
|
+
<div style="padding: 16px;">
|
|
2394
|
+
<p style="font-size: 12px; color: #6b7280; margin: 0 0 8px 0; line-height: 1.5;">Paste the <code style="background: #f3f4f6; padding: 1px 4px; border-radius: 3px;">:root { }</code> block from your exported CSS file:</p>
|
|
2395
|
+
<p style="font-size: 10px; color: #9ca3af; margin: 0 0 12px 0;">Look for "CONFIGURATION VARIABLES" section in _objects.breakout-grid.css</p>
|
|
2396
|
+
<textarea x-model="restoreInput"
|
|
2397
|
+
@keydown.meta.enter="restoreConfig()"
|
|
2398
|
+
@keydown.ctrl.enter="restoreConfig()"
|
|
2399
|
+
placeholder=":root {
|
|
2400
|
+
--base-gap: 1rem;
|
|
2401
|
+
--max-gap: 15rem;
|
|
2402
|
+
--content-min: 53rem;
|
|
2403
|
+
...
|
|
2404
|
+
}"
|
|
2405
|
+
style="width: 100%; height: 200px; padding: 12px; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; border: 1px solid #e5e5e5; border-radius: 4px; resize: vertical; box-sizing: border-box;"></textarea>
|
|
2406
|
+
<!-- Error message -->
|
|
2407
|
+
<div x-show="restoreError" x-text="restoreError" style="margin-top: 8px; padding: 8px 12px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 4px; color: #dc2626; font-size: 11px;"></div>
|
|
2408
|
+
<p style="font-size: 10px; color: #9ca3af; margin: 8px 0 0 0;">Press ⌘/Ctrl + Enter to apply</p>
|
|
2409
|
+
</div>
|
|
2410
|
+
<!-- Modal Footer -->
|
|
2411
|
+
<div style="padding: 12px 16px; background: #f7f7f7; border-radius: 0 0 8px 8px; display: flex; justify-content: flex-end; gap: 8px;">
|
|
2412
|
+
<button @click="closeRestoreModal()" style="padding: 8px 16px; font-size: 11px; font-weight: 600; border: 1px solid #e5e5e5; border-radius: 4px; cursor: pointer; background: white; color: #374151;">Cancel</button>
|
|
2413
|
+
<button @click="restoreConfig()" style="padding: 8px 16px; font-size: 11px; font-weight: 600; border: none; border-radius: 4px; cursor: pointer; background: #1a1a2e; color: white;">Apply</button>
|
|
2414
|
+
</div>
|
|
2415
|
+
</div>
|
|
2416
|
+
</div>
|
|
2417
|
+
|
|
2418
|
+
<!-- Grid Diagram -->
|
|
2419
|
+
<div x-show="showDiagram"
|
|
2420
|
+
style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; border-radius: 0.5rem; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); pointer-events: auto; z-index: 10001; padding: 1.5rem; max-width: 90vw;">
|
|
2421
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
|
2422
|
+
<span style="font-weight: 700; font-size: 0.875rem; color: #111827;">Breakout Grid Structure</span>
|
|
2423
|
+
<button @click="showDiagram = false" style="background: #ef4444; border: none; color: white; padding: 0.25rem 0.5rem; border-radius: 0.25rem; cursor: pointer; font-size: 0.625rem; font-weight: 600;">Close</button>
|
|
2424
|
+
</div>
|
|
2425
|
+
<!-- Visual Diagram -->
|
|
2426
|
+
<div style="font-family: Monaco, monospace; font-size: 0.625rem; line-height: 1.8;">
|
|
2427
|
+
<!-- Column structure visualization -->
|
|
2428
|
+
<div style="display: flex; align-items: stretch; border: 2px solid #e5e7eb; border-radius: 0.25rem; overflow: hidden; min-height: 120px;">
|
|
2429
|
+
<!-- Full left -->
|
|
2430
|
+
<div style="background: rgba(239, 68, 68, 0.2); padding: 0.5rem 0.25rem; display: flex; flex-direction: column; justify-content: center; align-items: center; border-right: 1px dashed #e5e7eb; min-width: 40px;">
|
|
2431
|
+
<div style="writing-mode: vertical-rl; transform: rotate(180deg); color: #dc2626; font-weight: 600;">full</div>
|
|
2432
|
+
<div style="color: #9ca3af; font-size: 0.5rem;">1fr</div>
|
|
2433
|
+
</div>
|
|
2434
|
+
<!-- Feature left -->
|
|
2435
|
+
<div style="background: rgba(6, 182, 212, 0.2); padding: 0.5rem 0.25rem; display: flex; flex-direction: column; justify-content: center; align-items: center; border-right: 1px dashed #e5e7eb; min-width: 50px;">
|
|
2436
|
+
<div style="color: #0891b2; font-weight: 600;">feature</div>
|
|
2437
|
+
<div style="color: #9ca3af; font-size: 0.5rem;" x-text="(editValues.featureMin || configOptions.featureMin.value) + ' - ' + (editValues.featureMax || configOptions.featureMax.value)"></div>
|
|
2438
|
+
</div>
|
|
2439
|
+
<!-- Popout left -->
|
|
2440
|
+
<div style="background: rgba(34, 197, 94, 0.2); padding: 0.5rem 0.25rem; display: flex; flex-direction: column; justify-content: center; align-items: center; border-right: 1px dashed #e5e7eb; min-width: 40px;">
|
|
2441
|
+
<div style="color: #15803d; font-weight: 600;">popout</div>
|
|
2442
|
+
<div style="color: #9ca3af; font-size: 0.5rem;" x-text="editValues.popoutWidth || configOptions.popoutWidth.value"></div>
|
|
2443
|
+
</div>
|
|
2444
|
+
<!-- Content -->
|
|
2445
|
+
<div style="background: rgba(168, 85, 247, 0.2); padding: 0.5rem; display: flex; flex-direction: column; justify-content: center; align-items: center; flex: 1; min-width: 80px;">
|
|
2446
|
+
<div style="color: #7c3aed; font-weight: 700;">content</div>
|
|
2447
|
+
<div style="color: #9ca3af; font-size: 0.5rem;" x-text="'(' + (editValues.contentMin || configOptions.contentMin.value) + ' - ' + (editValues.contentMax || configOptions.contentMax.value) + ')'"></div>
|
|
2448
|
+
</div>
|
|
2449
|
+
<!-- Popout right -->
|
|
2450
|
+
<div style="background: rgba(34, 197, 94, 0.2); padding: 0.5rem 0.25rem; display: flex; flex-direction: column; justify-content: center; align-items: center; border-left: 1px dashed #e5e7eb; min-width: 40px;">
|
|
2451
|
+
<div style="color: #15803d; font-weight: 600;">popout</div>
|
|
2452
|
+
<div style="color: #9ca3af; font-size: 0.5rem;" x-text="editValues.popoutWidth || configOptions.popoutWidth.value"></div>
|
|
2453
|
+
</div>
|
|
2454
|
+
<!-- Feature right -->
|
|
2455
|
+
<div style="background: rgba(6, 182, 212, 0.2); padding: 0.5rem 0.25rem; display: flex; flex-direction: column; justify-content: center; align-items: center; border-left: 1px dashed #e5e7eb; min-width: 50px;">
|
|
2456
|
+
<div style="color: #0891b2; font-weight: 600;">feature</div>
|
|
2457
|
+
<div style="color: #9ca3af; font-size: 0.5rem;" x-text="(editValues.featureMin || configOptions.featureMin.value) + ' - ' + (editValues.featureMax || configOptions.featureMax.value)"></div>
|
|
2458
|
+
</div>
|
|
2459
|
+
<!-- Full right -->
|
|
2460
|
+
<div style="background: rgba(239, 68, 68, 0.2); padding: 0.5rem 0.25rem; display: flex; flex-direction: column; justify-content: center; align-items: center; border-left: 1px dashed #e5e7eb; min-width: 40px;">
|
|
2461
|
+
<div style="writing-mode: vertical-rl; transform: rotate(180deg); color: #dc2626; font-weight: 600;">full</div>
|
|
2462
|
+
<div style="color: #9ca3af; font-size: 0.5rem;">1fr</div>
|
|
2463
|
+
</div>
|
|
2464
|
+
</div>
|
|
2465
|
+
<!-- Legend -->
|
|
2466
|
+
<div style="margin-top: 1rem; display: flex; flex-wrap: wrap; gap: 0.75rem; font-size: 0.5625rem;">
|
|
2467
|
+
<div><span style="display: inline-block; width: 12px; height: 12px; background: rgba(239, 68, 68, 0.3); border-radius: 2px; vertical-align: middle; margin-right: 0.25rem;"></span>.col-full</div>
|
|
2468
|
+
<div><span style="display: inline-block; width: 12px; height: 12px; background: rgba(6, 182, 212, 0.3); border-radius: 2px; vertical-align: middle; margin-right: 0.25rem;"></span>.col-feature</div>
|
|
2469
|
+
<div><span style="display: inline-block; width: 12px; height: 12px; background: rgba(34, 197, 94, 0.3); border-radius: 2px; vertical-align: middle; margin-right: 0.25rem;"></span>.col-popout</div>
|
|
2470
|
+
<div><span style="display: inline-block; width: 12px; height: 12px; background: rgba(168, 85, 247, 0.3); border-radius: 2px; vertical-align: middle; margin-right: 0.25rem;"></span>.col-content</div>
|
|
2471
|
+
</div>
|
|
2472
|
+
<!-- Padding explanation -->
|
|
2473
|
+
<div style="margin-top: 1rem; padding: 0.75rem; background: #f9fafb; border-radius: 0.25rem; font-size: 0.5625rem; color: #4b5563;">
|
|
2474
|
+
<div style="font-weight: 700; margin-bottom: 0.25rem;">px-breakout aligns full-width content:</div>
|
|
2475
|
+
<div>Uses <span style="color: #3b82f6;" x-text="editValues.popoutWidth || configOptions.popoutWidth.value"></span> padding so content aligns with .col-content edge</div>
|
|
2476
|
+
</div>
|
|
2477
|
+
</div>
|
|
2478
|
+
</div>
|
|
2479
|
+
|
|
2480
|
+
</div>
|
|
2481
|
+
`;
|
|
2482
|
+
(function() {
|
|
2483
|
+
document.addEventListener("alpine:init", () => {
|
|
2484
|
+
Alpine.data("breakoutGridVisualizer", () => ({
|
|
2485
|
+
// Constants
|
|
2486
|
+
version: VERSION,
|
|
2487
|
+
loremContent: LOREM_CONTENT,
|
|
2488
|
+
// Configuration
|
|
2489
|
+
gridAreas: GRID_AREAS,
|
|
2490
|
+
configOptions: CONFIG_OPTIONS,
|
|
2491
|
+
gapScaleOptions: GAP_SCALE_OPTIONS,
|
|
2492
|
+
breakoutOptions: BREAKOUT_OPTIONS,
|
|
2493
|
+
breakpointOptions: BREAKPOINT_OPTIONS,
|
|
2494
|
+
// State
|
|
2495
|
+
...createInitialState(),
|
|
2496
|
+
// Methods
|
|
2497
|
+
...methods,
|
|
2498
|
+
// CSS export
|
|
2499
|
+
generateCSSExport,
|
|
2500
|
+
cssExportVersion: BUILD_VERSION,
|
|
2501
|
+
// Template
|
|
2502
|
+
template
|
|
2503
|
+
}));
|
|
2504
|
+
});
|
|
2505
|
+
function injectVisualizer() {
|
|
2506
|
+
if (document.getElementById("breakout-grid-visualizer-root")) return;
|
|
2507
|
+
const container = document.createElement("div");
|
|
2508
|
+
container.id = "breakout-grid-visualizer-root";
|
|
2509
|
+
container.setAttribute("x-data", "breakoutGridVisualizer");
|
|
2510
|
+
container.setAttribute("x-html", "template");
|
|
2511
|
+
document.body.appendChild(container);
|
|
2512
|
+
console.log("Breakout Grid Visualizer injected. Press Ctrl/Cmd + G to toggle.");
|
|
2513
|
+
}
|
|
2514
|
+
if (document.readyState === "loading") {
|
|
2515
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
2516
|
+
setTimeout(injectVisualizer, 10);
|
|
2517
|
+
});
|
|
2518
|
+
} else {
|
|
2519
|
+
document.addEventListener("alpine:initialized", injectVisualizer);
|
|
2520
|
+
setTimeout(injectVisualizer, 100);
|
|
2521
|
+
}
|
|
2522
|
+
})();
|
|
2523
|
+
})();
|