kmcom-nuxt-layers 1.2.0 → 1.3.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/layers/layout/app/components/Layout/Grid/Item.vue +43 -19
- package/layers/layout/app/components/Layout/Section/Grid.vue +49 -0
- package/layers/layout/app/components/Layout/Section/Sidebar.vue +72 -0
- package/layers/layout/app/components/Layout/Section/Stack.vue +63 -0
- package/layers/layout/app/types/layouts.ts +7 -9
- package/layers/layout/app.config.ts +49 -20
- package/layers/motion/app/plugins/locomotive-scroll.client.ts +5 -5
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @prop {string} as - HTML element to render (default: 'div')
|
|
9
9
|
* @prop {number | ResponsiveValue} colStart - Starting column (1-18)
|
|
10
|
-
* @prop {
|
|
10
|
+
* @prop {ColSpanValue | ResponsiveValue} colSpan - Columns to span (default: 'full')
|
|
11
11
|
* @prop {number | ResponsiveValue} rowStart - Starting row (1-12)
|
|
12
12
|
* @prop {number | ResponsiveValue} rowSpan - Number of rows to span (default: 1)
|
|
13
13
|
* @prop {Alignment} align - Vertical alignment (align-self): start, center, end, stretch
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* </BaseGridItem>
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
+
type ColSpanValue = number | 'full'
|
|
32
33
|
type Alignment = 'start' | 'center' | 'end' | 'stretch'
|
|
33
34
|
type LayerName = 'back' | 'mid' | 'front' | 'top'
|
|
34
35
|
type BleedDirection = 'left' | 'right' | 'both'
|
|
@@ -44,7 +45,7 @@ interface Props {
|
|
|
44
45
|
preset?: string
|
|
45
46
|
as?: string
|
|
46
47
|
colStart?: number | ResponsiveValue<number>
|
|
47
|
-
colSpan?:
|
|
48
|
+
colSpan?: ColSpanValue | ResponsiveValue<number>
|
|
48
49
|
rowStart?: number | ResponsiveValue<number>
|
|
49
50
|
rowSpan?: number | ResponsiveValue<number>
|
|
50
51
|
align?: Alignment
|
|
@@ -57,18 +58,22 @@ interface Props {
|
|
|
57
58
|
|
|
58
59
|
const props = defineProps<Props>()
|
|
59
60
|
|
|
60
|
-
const { as = 'div'
|
|
61
|
+
const { as = 'div' } = props
|
|
61
62
|
|
|
62
63
|
// Get preset configuration if preset prop is provided
|
|
63
64
|
const { getPreset } = useGridConfig()
|
|
64
|
-
const presetConfig = computed(() => (preset ? getPreset(preset) : undefined))
|
|
65
|
+
const presetConfig = computed(() => (props.preset ? getPreset(props.preset) : undefined))
|
|
65
66
|
|
|
66
67
|
// Merge preset values with explicit props (explicit props take precedence)
|
|
67
68
|
const colStart = computed(() => props.colStart ?? presetConfig.value?.colStart)
|
|
68
|
-
const colSpan = computed(() => props.colSpan ?? presetConfig.value?.colSpan ??
|
|
69
|
+
const colSpan = computed(() => props.colSpan ?? presetConfig.value?.colSpan ?? 'full')
|
|
69
70
|
const rowStart = computed(() => props.rowStart ?? presetConfig.value?.rowStart)
|
|
70
71
|
const rowSpan = computed(() => props.rowSpan ?? presetConfig.value?.rowSpan ?? 1)
|
|
71
72
|
|
|
73
|
+
// Preset-aware alignment computed refs
|
|
74
|
+
const align = computed(() => props.align ?? presetConfig.value?.align)
|
|
75
|
+
const justify = computed(() => props.justify ?? presetConfig.value?.justify)
|
|
76
|
+
|
|
72
77
|
const layerZIndex: Record<LayerName, number> = {
|
|
73
78
|
back: 0,
|
|
74
79
|
mid: 10,
|
|
@@ -109,20 +114,39 @@ const style = computed(() => {
|
|
|
109
114
|
const styles: Record<string, string> = {}
|
|
110
115
|
|
|
111
116
|
const colStartVal = getDefaultValue(colStart.value, undefined)
|
|
112
|
-
const colSpanVal = getDefaultValue(colSpan.value,
|
|
117
|
+
const colSpanVal = getDefaultValue(colSpan.value as ColSpanValue | ResponsiveValue<number> | undefined, 'full' as ColSpanValue)
|
|
113
118
|
const rowStartVal = getDefaultValue(rowStart.value, undefined)
|
|
114
119
|
const rowSpanVal = getDefaultValue(rowSpan.value, 1)
|
|
115
120
|
|
|
116
|
-
if (bleed) {
|
|
117
|
-
|
|
118
|
-
if (bleed === 'both') {
|
|
121
|
+
if (props.bleed) {
|
|
122
|
+
if (props.bleed === 'both') {
|
|
119
123
|
styles.gridColumn = '1 / -1'
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
styles.marginInline = 'calc(-1 * var(--grid-padding))'
|
|
125
|
+
} else if (props.bleed === 'left') {
|
|
126
|
+
const spanNum = typeof colSpanVal === 'number' ? colSpanVal : undefined
|
|
127
|
+
styles.gridColumn = spanNum ? `1 / span ${spanNum}` : '1 / -1'
|
|
128
|
+
styles.marginInlineStart = 'calc(-1 * var(--grid-padding))'
|
|
129
|
+
} else if (props.bleed === 'right') {
|
|
123
130
|
styles.gridColumn = `${colStartVal ?? 'auto'} / -1`
|
|
131
|
+
styles.marginInlineEnd = 'calc(-1 * var(--grid-padding))'
|
|
124
132
|
}
|
|
125
133
|
styles.gridRow = `${rowStartVal ?? 'auto'} / span ${rowSpanVal}`
|
|
134
|
+
} else if (colSpanVal === 'full') {
|
|
135
|
+
// 'full' span: use inline gridColumn directly (no CSS var approach needed)
|
|
136
|
+
styles.gridColumn = `${colStartVal ?? 1} / -1`
|
|
137
|
+
// Still set row vars for responsive row support
|
|
138
|
+
styles['--_rs'] = String(rowStartVal ?? 'auto')
|
|
139
|
+
styles['--_re'] = String(rowSpanVal)
|
|
140
|
+
|
|
141
|
+
const mdRowStart = getResponsiveValue(rowStart.value, 'md')
|
|
142
|
+
const lgRowStart = getResponsiveValue(rowStart.value, 'lg')
|
|
143
|
+
const mdRowSpan = getResponsiveValue(rowSpan.value, 'md')
|
|
144
|
+
const lgRowSpan = getResponsiveValue(rowSpan.value, 'lg')
|
|
145
|
+
|
|
146
|
+
if (mdRowStart !== undefined) styles['--_md-rs'] = String(mdRowStart)
|
|
147
|
+
if (lgRowStart !== undefined) styles['--_lg-rs'] = String(lgRowStart)
|
|
148
|
+
if (mdRowSpan !== undefined) styles['--_md-re'] = String(mdRowSpan)
|
|
149
|
+
if (lgRowSpan !== undefined) styles['--_lg-re'] = String(lgRowSpan)
|
|
126
150
|
} else {
|
|
127
151
|
// Set CSS custom properties instead of grid-column/grid-row directly.
|
|
128
152
|
// The <style> block below reads these vars and applies them at each breakpoint,
|
|
@@ -134,8 +158,8 @@ const style = computed(() => {
|
|
|
134
158
|
|
|
135
159
|
const mdColStart = getResponsiveValue(colStart.value, 'md')
|
|
136
160
|
const lgColStart = getResponsiveValue(colStart.value, 'lg')
|
|
137
|
-
const mdColSpan = getResponsiveValue(colSpan.value, 'md')
|
|
138
|
-
const lgColSpan = getResponsiveValue(colSpan.value, 'lg')
|
|
161
|
+
const mdColSpan = getResponsiveValue(colSpan.value as ResponsiveValue<number> | undefined, 'md')
|
|
162
|
+
const lgColSpan = getResponsiveValue(colSpan.value as ResponsiveValue<number> | undefined, 'lg')
|
|
139
163
|
const mdRowStart = getResponsiveValue(rowStart.value, 'md')
|
|
140
164
|
const lgRowStart = getResponsiveValue(rowStart.value, 'lg')
|
|
141
165
|
const mdRowSpan = getResponsiveValue(rowSpan.value, 'md')
|
|
@@ -152,15 +176,15 @@ const style = computed(() => {
|
|
|
152
176
|
}
|
|
153
177
|
|
|
154
178
|
// Content alignment
|
|
155
|
-
if (align || justify) {
|
|
179
|
+
if (align.value || justify.value) {
|
|
156
180
|
styles.display = 'grid'
|
|
157
181
|
styles.width = '100%'
|
|
158
182
|
styles.height = '100%'
|
|
159
|
-
styles.placeItems = `${align ?? 'stretch'} ${justify ?? 'stretch'}`
|
|
183
|
+
styles.placeItems = `${align.value ?? 'stretch'} ${justify.value ?? 'stretch'}`
|
|
160
184
|
}
|
|
161
185
|
|
|
162
186
|
// Z-index
|
|
163
|
-
const zIndex = z ?? (layer ? layerZIndex[layer] : undefined)
|
|
187
|
+
const zIndex = props.z ?? (props.layer ? layerZIndex[props.layer] : undefined)
|
|
164
188
|
if (zIndex !== undefined) styles.zIndex = String(zIndex)
|
|
165
189
|
|
|
166
190
|
return styles
|
|
@@ -169,8 +193,8 @@ const style = computed(() => {
|
|
|
169
193
|
const classes = computed(() => {
|
|
170
194
|
const classList: string[] = ['gi-placed', '@container', '@container/item']
|
|
171
195
|
|
|
172
|
-
if (aspect) {
|
|
173
|
-
classList.push(aspectClasses[aspect])
|
|
196
|
+
if (props.aspect) {
|
|
197
|
+
classList.push(aspectClasses[props.aspect])
|
|
174
198
|
}
|
|
175
199
|
|
|
176
200
|
return classList.join(' ')
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LayoutSectionGrid — RAM (Repeat Auto Minmax) pattern
|
|
4
|
+
*
|
|
5
|
+
* Auto-wrapping grid that fits as many columns as possible at `minItemWidth`
|
|
6
|
+
* without media queries. Uses `repeat(auto-fit, minmax(..., 1fr))`.
|
|
7
|
+
*
|
|
8
|
+
* @prop {string} minItemWidth - Minimum column width before wrapping (default: '200px')
|
|
9
|
+
* @prop {boolean} fullHeight - Force 100svh on the section (default: false)
|
|
10
|
+
*
|
|
11
|
+
* @slot default - Grid items
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <LayoutSectionGrid min-item-width="250px">
|
|
15
|
+
* <div>Card 1</div>
|
|
16
|
+
* <div>Card 2</div>
|
|
17
|
+
* <div>Card 3</div>
|
|
18
|
+
* </LayoutSectionGrid>
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
minItemWidth?: string
|
|
23
|
+
fullHeight?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { minItemWidth = '200px', fullHeight = false } = defineProps<Props>()
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<LayoutSection :full-height>
|
|
31
|
+
<LayoutGridItem>
|
|
32
|
+
<div
|
|
33
|
+
class="ram-grid"
|
|
34
|
+
:style="{ '--min-item-width': minItemWidth }"
|
|
35
|
+
>
|
|
36
|
+
<slot />
|
|
37
|
+
</div>
|
|
38
|
+
</LayoutGridItem>
|
|
39
|
+
</LayoutSection>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<style scoped>
|
|
43
|
+
.ram-grid {
|
|
44
|
+
display: grid;
|
|
45
|
+
grid-template-columns: repeat(auto-fit, minmax(var(--min-item-width, 200px), 1fr));
|
|
46
|
+
gap: var(--grid-gap, clamp(0.75rem, 1.5vw, 1.5rem));
|
|
47
|
+
width: 100%;
|
|
48
|
+
}
|
|
49
|
+
</style>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LayoutSectionSidebar — Sidebar Says layout
|
|
4
|
+
*
|
|
5
|
+
* Two-column layout where the sidebar takes a clamped width and main content
|
|
6
|
+
* fills the rest. Uses `minmax(min, max) 1fr` (or reversed for right sidebar).
|
|
7
|
+
*
|
|
8
|
+
* @prop {string} sidebarMin - Minimum sidebar width (default: '150px')
|
|
9
|
+
* @prop {string} sidebarMax - Maximum sidebar width (default: '25%')
|
|
10
|
+
* @prop {boolean} reverse - Put sidebar on the right (default: false)
|
|
11
|
+
* @prop {boolean} fullHeight - Force 100svh on the section (default: false)
|
|
12
|
+
*
|
|
13
|
+
* @slot sidebar - Sidebar content
|
|
14
|
+
* @slot default - Main content (fills remaining space)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* <LayoutSectionSidebar sidebar-max="20rem">
|
|
18
|
+
* <template #sidebar><nav>...</nav></template>
|
|
19
|
+
* <article>Main content</article>
|
|
20
|
+
* </LayoutSectionSidebar>
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
sidebarMin?: string
|
|
25
|
+
sidebarMax?: string
|
|
26
|
+
reverse?: boolean
|
|
27
|
+
fullHeight?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { sidebarMin = '150px', sidebarMax = '25%', reverse = false, fullHeight = false } = defineProps<Props>()
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<LayoutSection :full-height>
|
|
35
|
+
<LayoutGridItem>
|
|
36
|
+
<div
|
|
37
|
+
class="sidebar-inner"
|
|
38
|
+
:class="{ 'sidebar-reverse': reverse }"
|
|
39
|
+
:style="{ '--sidebar-min': sidebarMin, '--sidebar-max': sidebarMax }"
|
|
40
|
+
>
|
|
41
|
+
<div class="sidebar-aside">
|
|
42
|
+
<slot name="sidebar" />
|
|
43
|
+
</div>
|
|
44
|
+
<div class="sidebar-main">
|
|
45
|
+
<slot />
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</LayoutGridItem>
|
|
49
|
+
</LayoutSection>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<style scoped>
|
|
53
|
+
.sidebar-inner {
|
|
54
|
+
display: grid;
|
|
55
|
+
grid-template-columns: minmax(var(--sidebar-min, 150px), var(--sidebar-max, 25%)) 1fr;
|
|
56
|
+
gap: var(--grid-gap, clamp(0.75rem, 1.5vw, 1.5rem));
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 100%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.sidebar-inner.sidebar-reverse {
|
|
62
|
+
grid-template-columns: 1fr minmax(var(--sidebar-min, 150px), var(--sidebar-max, 25%));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.sidebar-inner.sidebar-reverse .sidebar-aside {
|
|
66
|
+
order: 2;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.sidebar-inner.sidebar-reverse .sidebar-main {
|
|
70
|
+
order: 1;
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LayoutSectionStack — Pancake Stack layout
|
|
4
|
+
*
|
|
5
|
+
* Full-section wrapper using `auto 1fr auto` row template. Header and footer
|
|
6
|
+
* take natural height; the default slot fills the remaining space.
|
|
7
|
+
*
|
|
8
|
+
* @prop {boolean} fullHeight - Force 100svh on the section (default: true)
|
|
9
|
+
*
|
|
10
|
+
* @slot header - Top-pinned content (auto height)
|
|
11
|
+
* @slot default - Main content area (fills remaining space)
|
|
12
|
+
* @slot footer - Bottom-pinned content (auto height)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <LayoutSectionStack>
|
|
16
|
+
* <template #header><nav>...</nav></template>
|
|
17
|
+
* <main>Content</main>
|
|
18
|
+
* <template #footer><footer>...</footer></template>
|
|
19
|
+
* </LayoutSectionStack>
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
interface Props {
|
|
23
|
+
fullHeight?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const { fullHeight = true } = defineProps<Props>()
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<LayoutSection :full-height>
|
|
31
|
+
<LayoutGridItem>
|
|
32
|
+
<div class="stack-inner">
|
|
33
|
+
<div v-if="$slots.header" class="stack-header">
|
|
34
|
+
<slot name="header" />
|
|
35
|
+
</div>
|
|
36
|
+
<div class="stack-main">
|
|
37
|
+
<slot />
|
|
38
|
+
</div>
|
|
39
|
+
<div v-if="$slots.footer" class="stack-footer">
|
|
40
|
+
<slot name="footer" />
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</LayoutGridItem>
|
|
44
|
+
</LayoutSection>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<style scoped>
|
|
48
|
+
.stack-inner {
|
|
49
|
+
display: grid;
|
|
50
|
+
grid-template-rows: auto 1fr auto;
|
|
51
|
+
height: 100%;
|
|
52
|
+
width: 100%;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.stack-header,
|
|
56
|
+
.stack-footer {
|
|
57
|
+
/* auto — natural height */
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.stack-main {
|
|
61
|
+
/* 1fr — grows to fill remaining space */
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -32,8 +32,8 @@ export type GridDensity = 'compact' | 'normal' | 'relaxed'
|
|
|
32
32
|
export type GridMode = 'swiss' | 'fluid' | 'disabled'
|
|
33
33
|
|
|
34
34
|
export interface GridPresetsItem {
|
|
35
|
-
colStart
|
|
36
|
-
colSpan: number | ResponsiveValue<number>
|
|
35
|
+
colStart?: number | ResponsiveValue<number>
|
|
36
|
+
colSpan: number | 'full' | ResponsiveValue<number>
|
|
37
37
|
rowStart?: number | ResponsiveValue<number>
|
|
38
38
|
rowSpan?: number
|
|
39
39
|
/** Container size applied to the item's content */
|
|
@@ -42,15 +42,13 @@ export interface GridPresetsItem {
|
|
|
42
42
|
gap?: string
|
|
43
43
|
/** Vertical rhythm density for this preset */
|
|
44
44
|
density?: GridDensity
|
|
45
|
+
/** Vertical alignment (align-self) */
|
|
46
|
+
align?: 'start' | 'center' | 'end' | 'stretch'
|
|
47
|
+
/** Horizontal alignment (justify-self) */
|
|
48
|
+
justify?: 'start' | 'center' | 'end' | 'stretch'
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
export
|
|
48
|
-
hero: GridPresetsItem
|
|
49
|
-
centered: GridPresetsItem
|
|
50
|
-
fullWidth: GridPresetsItem
|
|
51
|
-
sidebar: GridPresetsItem
|
|
52
|
-
content: GridPresetsItem
|
|
53
|
-
}
|
|
51
|
+
export type GridPresets = Record<string, GridPresetsItem>
|
|
54
52
|
|
|
55
53
|
export interface GridConfig {
|
|
56
54
|
/**
|
|
@@ -83,20 +83,36 @@ export default defineAppConfig({
|
|
|
83
83
|
|
|
84
84
|
// Preset layouts for common patterns
|
|
85
85
|
presets: {
|
|
86
|
-
hero:
|
|
86
|
+
// Full-viewport hero: full width, all 12 rows
|
|
87
|
+
hero: { colSpan: 'full', rowSpan: 12 },
|
|
88
|
+
|
|
89
|
+
// Centered content column (equal margins both sides)
|
|
90
|
+
// lg: 3 + 12 + 3 = 18 ✓ | md: 1 + 10 + 1 = 12 ✓
|
|
91
|
+
centered: {
|
|
87
92
|
colStart: { default: 1, md: 2, lg: 4 },
|
|
88
93
|
colSpan: { default: 6, md: 10, lg: 12 },
|
|
89
94
|
rowSpan: 12,
|
|
90
95
|
},
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
96
|
+
|
|
97
|
+
// Full width (all columns, no bleed)
|
|
98
|
+
fullWidth: { colSpan: 'full' },
|
|
99
|
+
|
|
100
|
+
// Narrow prose column (tighter reading width)
|
|
101
|
+
// lg: 4 + 10 + 4 = 18 ✓ | md: 2 + 8 + 2 = 12 ✓
|
|
102
|
+
prose: {
|
|
103
|
+
colStart: { default: 1, md: 3, lg: 5 },
|
|
104
|
+
colSpan: { default: 6, md: 8, lg: 10 },
|
|
95
105
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
|
|
107
|
+
// Wide content (generous width, minimal margins)
|
|
108
|
+
// lg: 1 + 16 + 1 = 18 ✓ | md: 1 + 10 + 1 = 12 ✓
|
|
109
|
+
wide: {
|
|
110
|
+
colStart: { default: 1, md: 2, lg: 2 },
|
|
111
|
+
colSpan: { default: 6, md: 10, lg: 16 },
|
|
99
112
|
},
|
|
113
|
+
|
|
114
|
+
// Sidebar + content pair (use together)
|
|
115
|
+
// sidebar: cols 1-4 (lg) | content: cols 5-18 (14 cols) → 4+14=18 ✓
|
|
100
116
|
sidebar: {
|
|
101
117
|
colStart: { default: 1, md: 1, lg: 1 },
|
|
102
118
|
colSpan: { default: 6, md: 4, lg: 4 },
|
|
@@ -106,7 +122,8 @@ export default defineAppConfig({
|
|
|
106
122
|
colSpan: { default: 6, md: 8, lg: 14 },
|
|
107
123
|
},
|
|
108
124
|
|
|
109
|
-
// 50/50 vertical
|
|
125
|
+
// 50/50 vertical splits (stack on mobile)
|
|
126
|
+
// lg: 9+9=18 ✓ | md: 6+6=12 ✓
|
|
110
127
|
splitLeft: {
|
|
111
128
|
colStart: 1,
|
|
112
129
|
colSpan: { default: 6, md: 6, lg: 9 },
|
|
@@ -118,7 +135,8 @@ export default defineAppConfig({
|
|
|
118
135
|
rowSpan: 12,
|
|
119
136
|
},
|
|
120
137
|
|
|
121
|
-
// 25/75
|
|
138
|
+
// 25/75 vertical splits (stack on mobile)
|
|
139
|
+
// lg: 5+13=18 ✓ | md: 3+9=12 ✓
|
|
122
140
|
quarterLeft: {
|
|
123
141
|
colStart: 1,
|
|
124
142
|
colSpan: { default: 6, md: 3, lg: 5 },
|
|
@@ -130,18 +148,29 @@ export default defineAppConfig({
|
|
|
130
148
|
rowSpan: 12,
|
|
131
149
|
},
|
|
132
150
|
|
|
133
|
-
//
|
|
134
|
-
|
|
151
|
+
// 75/25 vertical splits (stack on mobile)
|
|
152
|
+
// lg: 13+5=18 ✓ | md: 9+3=12 ✓
|
|
153
|
+
threeQuarterLeft: {
|
|
135
154
|
colStart: 1,
|
|
136
|
-
colSpan: { default: 6, md:
|
|
137
|
-
|
|
138
|
-
rowSpan: 6,
|
|
155
|
+
colSpan: { default: 6, md: 9, lg: 13 },
|
|
156
|
+
rowSpan: 12,
|
|
139
157
|
},
|
|
140
|
-
|
|
141
|
-
colStart: 1,
|
|
142
|
-
colSpan: { default: 6, md:
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
quarterRight: {
|
|
159
|
+
colStart: { default: 1, md: 10, lg: 14 },
|
|
160
|
+
colSpan: { default: 6, md: 3, lg: 5 },
|
|
161
|
+
rowSpan: 12,
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
// Horizontal 50/50 stacks (100vw × 50vh each)
|
|
165
|
+
halfTop: { colSpan: 'full', rowStart: 1, rowSpan: 6 },
|
|
166
|
+
halfBottom: { colSpan: 'full', rowStart: 7, rowSpan: 6 },
|
|
167
|
+
|
|
168
|
+
// Super centered: full-width, full-height, content perfectly centered
|
|
169
|
+
superCentered: {
|
|
170
|
+
colSpan: 'full',
|
|
171
|
+
rowSpan: 12,
|
|
172
|
+
align: 'center',
|
|
173
|
+
justify: 'center',
|
|
145
174
|
},
|
|
146
175
|
},
|
|
147
176
|
},
|
|
@@ -46,17 +46,17 @@ export default defineNuxtPlugin(() => {
|
|
|
46
46
|
|
|
47
47
|
const router = useRouter()
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
const SMOOTH_SCROLL_ROUTES = ['/locomotive-scroll', '/layout-stacking', '/layout-blind-reveal']
|
|
50
|
+
|
|
50
51
|
addRouteMiddleware((to, from) => {
|
|
51
|
-
if (to.path
|
|
52
|
+
if (SMOOTH_SCROLL_ROUTES.includes(to.path)) {
|
|
52
53
|
nextTick(init)
|
|
53
|
-
} else if (from?.path
|
|
54
|
+
} else if (from?.path && SMOOTH_SCROLL_ROUTES.includes(from.path)) {
|
|
54
55
|
destroy()
|
|
55
56
|
}
|
|
56
57
|
})
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
if (router.currentRoute.value.path === '/locomotive-scroll') {
|
|
59
|
+
if (SMOOTH_SCROLL_ROUTES.includes(router.currentRoute.value.path)) {
|
|
60
60
|
init()
|
|
61
61
|
}
|
|
62
62
|
|