@xenterprises/nuxt-x-marketing 1.7.0 → 1.9.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/CHANGELOG.md +37 -0
- package/app/app.config.ts +6 -0
- package/app/assets/css/x-marketing.css +42 -0
- package/app/components/X/Mark/Media/index.vue +120 -0
- package/app/components/X/X/Footer/index.vue +21 -3
- package/app/components/X/X/Legal/index.vue +76 -3
- package/app/components/X/X/SkipLink/index.vue +32 -0
- package/app/plugins/x-container.ts +10 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -30,6 +30,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
30
30
|
section and its sample product data were dropped with them. Every other section of the
|
|
31
31
|
demo page is unchanged.
|
|
32
32
|
|
|
33
|
+
## [1.9.0] - 2026-10-12
|
|
34
|
+
|
|
35
|
+
All additions are opt-in; with none of them used, rendered HTML and pixels are identical to 1.8.0.
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **`XXLegal` affiliate disclosure** (`disclosure` prop, or `appConfig.xMarketing.footer.disclosure`),
|
|
40
|
+
shaped `{ before, link: { label, to }, after? }`. Renders one `<p>` above the copyright row with a
|
|
41
|
+
same-tab `NuxtLink` inline. Follows the 1.8.0 `container` prop and `xMarketing.container: '7xl'`.
|
|
42
|
+
`disclosure: false` suppresses an appConfig value on one instance.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **`XXSkipLink`** now moves focus to the target on activation (adds `tabindex="-1"` when the
|
|
47
|
+
target is not focusable), so the next Tab continues inside main content. No style changes.
|
|
48
|
+
|
|
49
|
+
## [1.8.0] - 2026-10-12
|
|
50
|
+
|
|
51
|
+
All additions are opt-in; with none of them used, rendered HTML and pixels are identical to 1.7.0.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- **`XMarkMedia`**: full-width image block (e.g. under an H1-only thin hero). Props `img`
|
|
56
|
+
(`{ src, alt?, sources? }`, `sources` renders `<picture>` like XMarkHero), `aspectRatio`
|
|
57
|
+
(CSS ratio, default `16/9`), `container` (same values as XMarkSection, default `lg`),
|
|
58
|
+
`rounded` (`none` default, `sm` to `xl`), `caption` (`<figure>`/`<figcaption>`),
|
|
59
|
+
`imagePosition`, `loading` (default `eager`).
|
|
60
|
+
- **`XXFooter` / `XXLegal` `container`**: `"lg"` uses `max-w-7xl px-4 sm:px-6 lg:px-8`,
|
|
61
|
+
matching XMarkHero/XMarkSection. Default output unchanged.
|
|
62
|
+
- **`appConfig.xMarketing.container: "7xl"`**: adds `html.x-container-7xl`, which sets
|
|
63
|
+
`--ui-container` to `80rem` so the header lines up with max-w-7xl, and makes XXFooter/XXLegal
|
|
64
|
+
default to `container="lg"`. Unset keeps `--ui-container: 1440px`.
|
|
65
|
+
- **`cta` color palette** (`--color-cta-*`, shade 500 = `#c2410c`): opt in with
|
|
66
|
+
`ui: { colors: { primary: 'cta' } }`; solid primary buttons get white text at 5.18:1.
|
|
67
|
+
- **`XXSkipLink`**: skip-to-content link (`href="#main"` by default), visually hidden until
|
|
68
|
+
focused. Opt-in; place it first inside `<UApp>`.
|
|
69
|
+
|
|
33
70
|
## [1.7.0] - 2026-10-12
|
|
34
71
|
|
|
35
72
|
### Added
|
package/app/app.config.ts
CHANGED
|
@@ -27,6 +27,9 @@ export default defineAppConfig({
|
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
29
|
xMarketing: {
|
|
30
|
+
/** 1.8.0 opt-in. "7xl" aligns --ui-container (header) and XXFooter/XXLegal with
|
|
31
|
+
* the max-w-7xl column used by XMarkHero/XMarkSection. Unset keeps 1440px. */
|
|
32
|
+
container: undefined as undefined | "7xl",
|
|
30
33
|
/** Site name — used by the shipped shell for the SEO title template
|
|
31
34
|
* ("%s | name") and og:site_name. Leave undefined to keep bare titles. */
|
|
32
35
|
name: undefined,
|
|
@@ -54,6 +57,9 @@ export default defineAppConfig({
|
|
|
54
57
|
footer: {
|
|
55
58
|
/** Set false to opt out of the default footer in the shipped shell. */
|
|
56
59
|
active: true as boolean,
|
|
60
|
+
/** 1.9.0 opt-in affiliate disclosure rendered by XXLegal:
|
|
61
|
+
* { before: string, link: { label, to }, after?: string } */
|
|
62
|
+
disclosure: undefined as undefined | { before: string; link: { label: string; to: string }; after?: string },
|
|
57
63
|
logo: {
|
|
58
64
|
src: undefined,
|
|
59
65
|
alt: undefined,
|
|
@@ -459,6 +459,21 @@
|
|
|
459
459
|
--color-brand-950: #000a1a;
|
|
460
460
|
|
|
461
461
|
/* Deep blacks with warm undertone */
|
|
462
|
+
/* cta (1.8.0, opt-in): AA orange palette whose shade 500 is orange-700, so
|
|
463
|
+
* solid buttons with white text pass AA (5.18:1). Opt in from app.config:
|
|
464
|
+
* ui: { colors: { primary: 'cta' } }. */
|
|
465
|
+
--color-cta-50: #fff7ed;
|
|
466
|
+
--color-cta-100: #ffedd5;
|
|
467
|
+
--color-cta-200: #fed7aa;
|
|
468
|
+
--color-cta-300: #fdba74;
|
|
469
|
+
--color-cta-400: #ea580c;
|
|
470
|
+
--color-cta-500: #c2410c;
|
|
471
|
+
--color-cta-600: #9a3412;
|
|
472
|
+
--color-cta-700: #7c2d12;
|
|
473
|
+
--color-cta-800: #6c2710;
|
|
474
|
+
--color-cta-900: #431407;
|
|
475
|
+
--color-cta-950: #2a0c04;
|
|
476
|
+
|
|
462
477
|
--color-deep-50: #f5f5f4;
|
|
463
478
|
--color-deep-100: #e8e8e3;
|
|
464
479
|
--color-deep-200: #c5c5bd;
|
|
@@ -682,3 +697,30 @@ body {
|
|
|
682
697
|
-moz-osx-font-smoothing: grayscale;
|
|
683
698
|
text-rendering: optimizeLegibility;
|
|
684
699
|
}
|
|
700
|
+
|
|
701
|
+
/* Opt-in (1.8.0): appConfig.xMarketing.container = "7xl" adds html.x-container-7xl,
|
|
702
|
+
* aligning UContainer / UHeader (--ui-container) with max-w-7xl (80rem), the
|
|
703
|
+
* XMarkHero and XMarkSection column. Default stays 1440px. */
|
|
704
|
+
html.x-container-7xl {
|
|
705
|
+
--ui-container: 80rem;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/* XXSkipLink (1.8.0) */
|
|
709
|
+
.x-skip-link {
|
|
710
|
+
position: absolute;
|
|
711
|
+
left: 0.5rem;
|
|
712
|
+
top: 0.5rem;
|
|
713
|
+
z-index: 100;
|
|
714
|
+
padding: 0.5rem 0.75rem;
|
|
715
|
+
background: #fff;
|
|
716
|
+
color: #111;
|
|
717
|
+
border-radius: 0.25rem;
|
|
718
|
+
font-weight: 600;
|
|
719
|
+
transform: translateY(-200%);
|
|
720
|
+
}
|
|
721
|
+
.x-skip-link:focus,
|
|
722
|
+
.x-skip-link:focus-visible {
|
|
723
|
+
transform: none;
|
|
724
|
+
outline: 2px solid #111;
|
|
725
|
+
outline-offset: 2px;
|
|
726
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="containerClasses">
|
|
3
|
+
<figure class="m-0">
|
|
4
|
+
<div
|
|
5
|
+
class="relative w-full overflow-hidden"
|
|
6
|
+
:class="roundedClass"
|
|
7
|
+
:style="aspectStyle"
|
|
8
|
+
>
|
|
9
|
+
<picture v-if="img?.sources?.length">
|
|
10
|
+
<source
|
|
11
|
+
v-for="(source, i) in img.sources"
|
|
12
|
+
:key="i"
|
|
13
|
+
:srcset="source.srcset"
|
|
14
|
+
:type="source.type"
|
|
15
|
+
:media="source.media"
|
|
16
|
+
:sizes="source.sizes"
|
|
17
|
+
>
|
|
18
|
+
<img
|
|
19
|
+
:src="img.src"
|
|
20
|
+
:alt="img.alt || ''"
|
|
21
|
+
:loading="loading"
|
|
22
|
+
class="absolute inset-0 w-full h-full object-cover"
|
|
23
|
+
:style="positionStyle"
|
|
24
|
+
>
|
|
25
|
+
</picture>
|
|
26
|
+
<img
|
|
27
|
+
v-else
|
|
28
|
+
:src="img.src"
|
|
29
|
+
:alt="img.alt || ''"
|
|
30
|
+
:loading="loading"
|
|
31
|
+
class="absolute inset-0 w-full h-full object-cover"
|
|
32
|
+
:style="positionStyle"
|
|
33
|
+
>
|
|
34
|
+
</div>
|
|
35
|
+
<figcaption
|
|
36
|
+
v-if="caption"
|
|
37
|
+
class="mt-3 text-sm text-neutral-600"
|
|
38
|
+
>
|
|
39
|
+
{{ caption }}
|
|
40
|
+
</figcaption>
|
|
41
|
+
</figure>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<script setup>
|
|
46
|
+
/**
|
|
47
|
+
* XMarkMedia (1.8.0) - full-width image block, e.g. directly under a thin H1 hero.
|
|
48
|
+
* img.sources renders <picture> (same shape as XMarkHero img.sources).
|
|
49
|
+
*/
|
|
50
|
+
const props = defineProps({
|
|
51
|
+
/** { src, alt?, sources?: [{ srcset, type?, media?, sizes? }] } */
|
|
52
|
+
img: {
|
|
53
|
+
type: Object,
|
|
54
|
+
required: true,
|
|
55
|
+
validator: (v) =>
|
|
56
|
+
!!v &&
|
|
57
|
+
typeof v.src === "string" &&
|
|
58
|
+
(v.sources === undefined ||
|
|
59
|
+
(Array.isArray(v.sources) &&
|
|
60
|
+
v.sources.every((x) => x && typeof x.srcset === "string"))),
|
|
61
|
+
},
|
|
62
|
+
/** CSS aspect-ratio, e.g. "16/9", "21/9", "4/3" */
|
|
63
|
+
aspectRatio: {
|
|
64
|
+
type: String,
|
|
65
|
+
default: "16/9",
|
|
66
|
+
validator: (v) => /^\d+(\.\d+)?\s*\/\s*\d+(\.\d+)?$/.test(v),
|
|
67
|
+
},
|
|
68
|
+
/** Same values and classes as XMarkSection container */
|
|
69
|
+
container: {
|
|
70
|
+
type: String,
|
|
71
|
+
default: "lg",
|
|
72
|
+
validator: (v) => ["sm", "md", "lg", "xl", "full", "none"].includes(v),
|
|
73
|
+
},
|
|
74
|
+
/** Corner radius: none, sm, md, lg, xl */
|
|
75
|
+
rounded: {
|
|
76
|
+
type: String,
|
|
77
|
+
default: "none",
|
|
78
|
+
validator: (v) => ["none", "sm", "md", "lg", "xl"].includes(v),
|
|
79
|
+
},
|
|
80
|
+
/** Optional caption, rendered as <figcaption> */
|
|
81
|
+
caption: {
|
|
82
|
+
type: String,
|
|
83
|
+
default: "",
|
|
84
|
+
},
|
|
85
|
+
/** CSS object-position, e.g. "center 30%" */
|
|
86
|
+
imagePosition: {
|
|
87
|
+
type: String,
|
|
88
|
+
default: "",
|
|
89
|
+
},
|
|
90
|
+
/** Native loading hint; eager by default since it sits above the fold */
|
|
91
|
+
loading: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: "eager",
|
|
94
|
+
validator: (v) => ["eager", "lazy"].includes(v),
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const containerClasses = computed(() => {
|
|
99
|
+
const containers = {
|
|
100
|
+
sm: "max-w-[42rem] mx-auto px-4 sm:px-6",
|
|
101
|
+
md: "max-w-[64rem] mx-auto px-4 sm:px-6 lg:px-8",
|
|
102
|
+
lg: "max-w-[80rem] mx-auto px-4 sm:px-6 lg:px-8",
|
|
103
|
+
xl: "max-w-[96rem] mx-auto px-4 sm:px-6 lg:px-8",
|
|
104
|
+
full: "w-full px-4 sm:px-6 lg:px-8",
|
|
105
|
+
none: "",
|
|
106
|
+
};
|
|
107
|
+
return containers[props.container];
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const roundedClass = computed(() => {
|
|
111
|
+
const r = { none: "rounded-none", sm: "rounded-sm", md: "rounded-md", lg: "rounded-lg", xl: "rounded-xl" };
|
|
112
|
+
return r[props.rounded];
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const aspectStyle = computed(() => ({ aspectRatio: props.aspectRatio.replace(/\s+/g, " ") }));
|
|
116
|
+
|
|
117
|
+
const positionStyle = computed(() =>
|
|
118
|
+
props.imagePosition ? { objectPosition: props.imagePosition } : undefined,
|
|
119
|
+
);
|
|
120
|
+
</script>
|
|
@@ -18,9 +18,7 @@
|
|
|
18
18
|
:src="footer?.bg?.img?.src"
|
|
19
19
|
:alt="footer?.bg?.img?.alt"
|
|
20
20
|
/>
|
|
21
|
-
<div
|
|
22
|
-
class="relative z-10 container mx-auto max-w-screen-2xl px-4 md:px-8"
|
|
23
|
-
>
|
|
21
|
+
<div :class="innerClass">
|
|
24
22
|
<div class="pb-24">
|
|
25
23
|
<div
|
|
26
24
|
class="flex flex-col lg:flex-row lg:justify-between gap-8 lg:gap-12"
|
|
@@ -97,4 +95,24 @@
|
|
|
97
95
|
const emit = defineEmits(["newsletter-submit"]);
|
|
98
96
|
const appConfig = useAppConfig();
|
|
99
97
|
const footer = appConfig?.xMarketing?.footer;
|
|
98
|
+
|
|
99
|
+
const props = defineProps({
|
|
100
|
+
/**
|
|
101
|
+
* Inner container (1.8.0+, opt-in). "lg" = max-w-7xl px-4 sm:px-6 lg:px-8, the
|
|
102
|
+
* XMarkHero / XMarkSection column. Empty follows appConfig.xMarketing.container
|
|
103
|
+
* ("7xl" means "lg"); otherwise the default max-w-screen-2xl output is unchanged.
|
|
104
|
+
*/
|
|
105
|
+
container: {
|
|
106
|
+
type: String,
|
|
107
|
+
default: "",
|
|
108
|
+
validator: (v) => ["", "default", "lg"].includes(v),
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const innerClass = computed(() => {
|
|
113
|
+
const c = props.container || (appConfig?.xMarketing?.container === "7xl" ? "lg" : "default");
|
|
114
|
+
return c === "lg"
|
|
115
|
+
? "relative z-10 mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"
|
|
116
|
+
: "relative z-10 container mx-auto max-w-screen-2xl px-4 md:px-8";
|
|
117
|
+
});
|
|
100
118
|
</script>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<footer
|
|
3
|
+
v-if="!resolvedDisclosure"
|
|
3
4
|
class="bg-gray-900 text-gray-400 text-xs relative overflow-visible pt-2"
|
|
4
5
|
>
|
|
5
6
|
<img
|
|
@@ -8,9 +9,41 @@
|
|
|
8
9
|
:alt="resolvedLogo.alt"
|
|
9
10
|
class="h-6 w-6 mx-auto -mt-4 relative z-10"
|
|
10
11
|
/>
|
|
11
|
-
<div
|
|
12
|
-
class="
|
|
13
|
-
|
|
12
|
+
<div :class="innerClass">
|
|
13
|
+
<div class="text-center md:text-left">
|
|
14
|
+
{{ resolvedCopyright }}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<nav
|
|
18
|
+
v-if="resolvedLinks.length"
|
|
19
|
+
class="text-center md:text-right flex flex-wrap justify-center gap-4"
|
|
20
|
+
>
|
|
21
|
+
<a
|
|
22
|
+
v-for="link in resolvedLinks"
|
|
23
|
+
:key="link.label"
|
|
24
|
+
:href="link.href"
|
|
25
|
+
class="hover:underline"
|
|
26
|
+
target="_blank"
|
|
27
|
+
>
|
|
28
|
+
{{ link.label }}
|
|
29
|
+
</a>
|
|
30
|
+
</nav>
|
|
31
|
+
</div>
|
|
32
|
+
</footer>
|
|
33
|
+
<footer
|
|
34
|
+
v-else
|
|
35
|
+
class="bg-gray-900 text-gray-400 text-xs relative overflow-visible pt-2"
|
|
36
|
+
>
|
|
37
|
+
<img
|
|
38
|
+
v-if="resolvedLogo.src"
|
|
39
|
+
:src="resolvedLogo.src"
|
|
40
|
+
:alt="resolvedLogo.alt"
|
|
41
|
+
class="h-6 w-6 mx-auto -mt-4 relative z-10"
|
|
42
|
+
/>
|
|
43
|
+
<p :class="disclosureClass" data-x-disclosure>
|
|
44
|
+
{{ resolvedDisclosure.before }}<NuxtLink :to="resolvedDisclosure.link.to" class="underline hover:text-white">{{ resolvedDisclosure.link.label }}</NuxtLink>{{ resolvedDisclosure.after }}
|
|
45
|
+
</p>
|
|
46
|
+
<div :class="innerClass">
|
|
14
47
|
<div class="text-center md:text-left">
|
|
15
48
|
{{ resolvedCopyright }}
|
|
16
49
|
</div>
|
|
@@ -51,6 +84,24 @@ const props = defineProps({
|
|
|
51
84
|
type: Array,
|
|
52
85
|
default: null,
|
|
53
86
|
},
|
|
87
|
+
/**
|
|
88
|
+
* Inner container (1.8.0+, opt-in). "lg" = max-w-7xl px-4 sm:px-6 lg:px-8. Empty follows
|
|
89
|
+
* appConfig.xMarketing.container ("7xl" means "lg"); otherwise output is unchanged.
|
|
90
|
+
*/
|
|
91
|
+
container: {
|
|
92
|
+
type: String,
|
|
93
|
+
default: "",
|
|
94
|
+
validator: (v) => ["", "default", "lg"].includes(v),
|
|
95
|
+
},
|
|
96
|
+
/**
|
|
97
|
+
* Affiliate disclosure (1.9.0+, opt-in). { before, link: { label, to }, after? } rendered as one
|
|
98
|
+
* <p> with a same-tab NuxtLink inline, above the copyright row. Falls back to
|
|
99
|
+
* appConfig.xMarketing.footer.disclosure. Pass false to suppress the appConfig value.
|
|
100
|
+
*/
|
|
101
|
+
disclosure: {
|
|
102
|
+
type: [Object, Boolean],
|
|
103
|
+
default: null,
|
|
104
|
+
},
|
|
54
105
|
/** Logo object { src, alt } (defaults to X Enterprises logo). Pass false to hide. href is ignored; logo is display-only. */
|
|
55
106
|
logo: {
|
|
56
107
|
type: [Object, Boolean],
|
|
@@ -58,6 +109,28 @@ const props = defineProps({
|
|
|
58
109
|
},
|
|
59
110
|
});
|
|
60
111
|
|
|
112
|
+
const appConfig = useAppConfig();
|
|
113
|
+
const innerClass = computed(() => {
|
|
114
|
+
const c = props.container || (appConfig?.xMarketing?.container === "7xl" ? "lg" : "default");
|
|
115
|
+
return c === "lg"
|
|
116
|
+
? "flex flex-col md:flex-row justify-between items-center px-4 sm:px-6 lg:px-8 pt-3 pb-5 max-w-7xl mx-auto gap-4"
|
|
117
|
+
: "flex flex-col md:flex-row justify-between items-center px-6 pt-3 pb-5 max-w-screen-2xl mx-auto gap-4";
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const isDisclosure = (d) =>
|
|
121
|
+
!!d && typeof d === "object" && typeof d.before === "string" && !!d.link && !!d.link.label && !!d.link.to;
|
|
122
|
+
const resolvedDisclosure = computed(() => {
|
|
123
|
+
if (props.disclosure === false) return null;
|
|
124
|
+
const d = props.disclosure || appConfig?.xMarketing?.footer?.disclosure;
|
|
125
|
+
return isDisclosure(d) ? { before: d.before, link: d.link, after: d.after || "" } : null;
|
|
126
|
+
});
|
|
127
|
+
const disclosureClass = computed(() => {
|
|
128
|
+
const c = props.container || (appConfig?.xMarketing?.container === "7xl" ? "lg" : "default");
|
|
129
|
+
return c === "lg"
|
|
130
|
+
? "px-4 sm:px-6 lg:px-8 pt-3 max-w-7xl mx-auto text-center md:text-left"
|
|
131
|
+
: "px-6 pt-3 max-w-screen-2xl mx-auto text-center md:text-left";
|
|
132
|
+
});
|
|
133
|
+
|
|
61
134
|
// Default X Enterprises values
|
|
62
135
|
const defaultLogo = {
|
|
63
136
|
src: "https://cdn.x.enterprises/logo-x.png",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<a :href="`#${target}`" class="x-skip-link" @click="onActivate">{{ label }}</a>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup>
|
|
6
|
+
/**
|
|
7
|
+
* XXSkipLink (1.8.0) - skip-to-content link. Opt-in: place it first in app.vue.
|
|
8
|
+
* Visually hidden until focused (first Tab), then shown top-left.
|
|
9
|
+
* Pair with <main id="main"> (or set target).
|
|
10
|
+
*/
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
/** id of the main content element, without # */
|
|
13
|
+
target: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: "main",
|
|
16
|
+
},
|
|
17
|
+
/** Link text */
|
|
18
|
+
label: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: "Skip to content",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** 1.9.0: move focus to the target so the next Tab continues from main content. */
|
|
25
|
+
function onActivate() {
|
|
26
|
+
if (typeof document === "undefined") return;
|
|
27
|
+
const el = document.getElementById(props.target);
|
|
28
|
+
if (!el) return;
|
|
29
|
+
if (!el.hasAttribute("tabindex") && el.tabIndex < 0) el.setAttribute("tabindex", "-1");
|
|
30
|
+
el.focus();
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 1.8.0 opt-in: appConfig.xMarketing.container === "7xl" adds html.x-container-7xl,
|
|
3
|
+
* which sets --ui-container to 80rem (max-w-7xl). Unset: no head change at all.
|
|
4
|
+
*/
|
|
5
|
+
export default defineNuxtPlugin(() => {
|
|
6
|
+
const appConfig = useAppConfig() as { xMarketing?: { container?: string } };
|
|
7
|
+
if (appConfig?.xMarketing?.container === "7xl") {
|
|
8
|
+
useHead({ htmlAttrs: { class: "x-container-7xl" } });
|
|
9
|
+
}
|
|
10
|
+
});
|