@codecavepro/brand 1.2.0 → 1.4.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/README.md +46 -23
- package/dist/src/components/common/ArticlePreview.vue +58 -0
- package/dist/src/components/common/Review.vue +64 -0
- package/dist/src/components/homepage/technologies.vue +122 -0
- package/dist/src/components/project/pain-points-item.vue +50 -0
- package/dist/src/helpers/breakpoints.ts +5 -0
- package/dist/src/helpers/date-formatter.ts +10 -0
- package/dist/theme.css +81 -0
- package/package.json +15 -2
package/README.md
CHANGED
|
@@ -142,8 +142,8 @@ Two things to know about it:
|
|
|
142
142
|
|
|
143
143
|
## The components
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
codecave.pro renders — ship as **source**, byte-for-byte the files the site builds
|
|
145
|
+
19 components and 13 icons — the buttons, form controls, menu, footer, and the
|
|
146
|
+
article, testimonial and technology cards codecave.pro renders — ship as **source**, byte-for-byte the files the site builds
|
|
147
147
|
from. Not a reimplementation of them, and not a bundle compiled from a snapshot:
|
|
148
148
|
the same files, kept identical by a check that runs on every push.
|
|
149
149
|
|
|
@@ -166,33 +166,53 @@ plugin — `@vitejs/plugin-vue`, `@astrojs/vue`, `vue-loader`. In exchange their
|
|
|
166
166
|
styles go through your own pipeline and you can read the source of anything that
|
|
167
167
|
surprises you.
|
|
168
168
|
|
|
169
|
-
`vue` is
|
|
170
|
-
|
|
169
|
+
`vue` is the only required peer. Four more are optional, each wanted by one or two
|
|
170
|
+
components; leave one out and only those are affected — `gsap` by `GlowButton.vue`,
|
|
171
|
+
`effects/TypingEffect.vue` and `forms/ContactUsForm.vue`; `vue3-carousel` by
|
|
172
|
+
`homepage/technologies.vue`; `marked` and `isomorphic-dompurify` by
|
|
173
|
+
`project/pain-points-item.vue`.
|
|
171
174
|
|
|
172
|
-
###
|
|
175
|
+
### Import the theme, not just the tokens
|
|
173
176
|
|
|
174
177
|
Every colour, radius and control height in these components is a token — but they reach
|
|
175
178
|
most of them through **Tailwind utility classes** (`bg-surface-primary`,
|
|
176
|
-
`text-heading-
|
|
177
|
-
the name.
|
|
178
|
-
|
|
179
|
+
`text-heading-md`, `rounded-custom`), and a utility class exists only if Tailwind knows
|
|
180
|
+
the name. `tokens.css` gives you the *values*; `theme.css` gives Tailwind the names, so
|
|
181
|
+
you need both:
|
|
179
182
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
```css
|
|
184
|
+
@import "tailwindcss";
|
|
185
|
+
@import "@codecavepro/brand/tokens.css";
|
|
186
|
+
@import "@codecavepro/brand/theme.css";
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**That order is load-bearing, and `tokens.css` must stay unlayered.** Several entries in
|
|
190
|
+
`theme.css` are deliberate self-references (`--color-glow-25: var(--color-glow-25)`):
|
|
191
|
+
the declaration is what makes Tailwind emit the utility, while the value comes from
|
|
192
|
+
`tokens.css`, whose unlayered `:root` outranks `@layer theme`. Wrap `tokens.css` in a
|
|
193
|
+
cascade layer — or leave it out — and those names resolve to nothing.
|
|
194
|
+
|
|
195
|
+
Import only `tokens.css` and the components mount and behave correctly and render
|
|
196
|
+
nearly unstyled.
|
|
184
197
|
|
|
185
|
-
|
|
198
|
+
### The content-shaped four resolve their own image URLs
|
|
186
199
|
|
|
187
|
-
|
|
200
|
+
`ArticlePreview`, `Review`, `pain-points-item` and `technologies` render CMS content, so
|
|
201
|
+
they used to reach a helper that knew codecave.pro's CMS host. They no longer do. The
|
|
202
|
+
three that show images take an optional resolver and default to leaving the URL alone:
|
|
203
|
+
|
|
204
|
+
```vue
|
|
205
|
+
<!-- URLs already absolute — nothing to pass -->
|
|
206
|
+
<Review :item="testimonial" />
|
|
207
|
+
|
|
208
|
+
<!-- URLs relative to your own media host -->
|
|
209
|
+
<Review :item="testimonial" :resolve-image="(u) => `https://cdn.example.com/${u}`" />
|
|
210
|
+
```
|
|
188
211
|
|
|
189
|
-
|
|
190
|
-
`
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
a brand package. Inverting that dependency site-side is
|
|
194
|
-
[CCWEB2-332](https://codecave.atlassian.net/browse/CCWEB2-332); they arrive when it
|
|
195
|
-
lands.
|
|
212
|
+
Their props are declared as the fields each one reads — `item.photo.url`,
|
|
213
|
+
`article.cover.url` — not as a generated CMS schema. Anything with those fields
|
|
214
|
+
satisfies them, so a Strapi entity, a Contentful entry or a plain object all work
|
|
215
|
+
unchanged.
|
|
196
216
|
|
|
197
217
|
## Controls are 48px, and it is a floor
|
|
198
218
|
|
|
@@ -220,8 +240,11 @@ the rules, the reasoning, the anti-patterns — is documented separately:
|
|
|
220
240
|
|
|
221
241
|
It authors nothing. Every byte is copied, extracted or compiled out of `docs/` in the
|
|
222
242
|
repository above: `css` and `fonts.css` are copied verbatim, `tokens.css` is extracted
|
|
223
|
-
from the same file `css` is copied from,
|
|
224
|
-
|
|
243
|
+
from the same file `css` is copied from, `theme.css` is extracted from the capture of
|
|
244
|
+
codecave.pro's `global.css`, and the typed module is compiled from `docs/tokens/*.ts`.
|
|
245
|
+
A name that `theme.css` and `tokens.css` both give a literal value must agree, and the
|
|
246
|
+
build fails if it does not — otherwise one of the two would be dead and nobody would
|
|
247
|
+
see it. `docs/` stays the single origin, and CI asserts on every push that
|
|
225
248
|
the copies are byte-identical and the extraction still reproduces what shipped.
|
|
226
249
|
|
|
227
250
|
Two editable copies of a palette is the exact failure this package exists to end, so it
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { formattedDate } from "../../helpers/date-formatter.ts";
|
|
3
|
+
import { paths } from "../../helpers/paths.ts";
|
|
4
|
+
|
|
5
|
+
/* The fields this component actually reads, instead of the generated Strapi
|
|
6
|
+
* `Article`. TypeScript is structural, so a real Article still satisfies this
|
|
7
|
+
* -- callers pass exactly what they passed before -- but the component no
|
|
8
|
+
* longer carries the CMS schema with it. */
|
|
9
|
+
type ArticleSummary = {
|
|
10
|
+
slug: string | null
|
|
11
|
+
title: string
|
|
12
|
+
excerpt: string
|
|
13
|
+
date: Date | null
|
|
14
|
+
locale: string | null
|
|
15
|
+
readingtime: unknown
|
|
16
|
+
cover: { url: string; name: string }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const props = defineProps<{
|
|
20
|
+
article: ArticleSummary
|
|
21
|
+
className?: string
|
|
22
|
+
/* CMS media URLs arrive relative ("uploads/x.png"). The site injects its own
|
|
23
|
+
* resolver; a caller whose URLs are already absolute passes nothing. */
|
|
24
|
+
resolveImage?: (url: string) => string
|
|
25
|
+
}>()
|
|
26
|
+
|
|
27
|
+
const imageUrl = (url: string) => props.resolveImage?.(url) ?? url
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<a :href="`${paths.insights}/${article.slug}`" :class="`mx-1 lg:mx-2 w-full h-full self-start sm:self-auto p-6 flex flex-col gap-5 sm:gap-8
|
|
32
|
+
rounded-[2.25rem] bg-surface-secondary hover:bg-surface-secondary transition-colors cursor-pointer border-surface-tertiary border
|
|
33
|
+
${className ?? ''}`">
|
|
34
|
+
<div class="flex flex-col sm:flex-row gap-5 sm:gap-8 h-fit">
|
|
35
|
+
<img loading="lazy" class="sm:w-[132px] sm:h-[132px] rounded-xl object-cover"
|
|
36
|
+
:src="imageUrl(article.cover.url)"
|
|
37
|
+
:alt="article.cover.name"
|
|
38
|
+
:width="100"
|
|
39
|
+
:height="100"/>
|
|
40
|
+
<div class="space-y-2 sm:space-y-3">
|
|
41
|
+
<time class="text-body-secondary text-xs sm:text-sm" :datetime="article.date?.toString()">
|
|
42
|
+
{{ formattedDate(article.locale, article.date) }}
|
|
43
|
+
</time>
|
|
44
|
+
<h3 class="font-bold text-lg md:text-xl text-heading">
|
|
45
|
+
{{ article.title }}
|
|
46
|
+
</h3>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="text-sm flex flex-col justify-between h-full">
|
|
50
|
+
<p class="text-body-secondary-lighter">
|
|
51
|
+
{{ article.excerpt }}
|
|
52
|
+
</p>
|
|
53
|
+
<p class="text-xs sm:text-sm text-body-secondary mt-6 sm:mt-7">
|
|
54
|
+
Reading time: {{ article.readingtime }} m.
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
</a>
|
|
58
|
+
</template>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import LinkedinIcon from "../../assets/icons/linkedin-icon.vue";
|
|
3
|
+
import VerifiedIcon from "../../assets/icons/verified-icon.vue";
|
|
4
|
+
import LazyImage from "./images/LazyImage.vue"
|
|
5
|
+
|
|
6
|
+
/* See ArticlePreview.vue: the fields read here, not the generated Strapi
|
|
7
|
+
* `Testimonial`, which a caller may still pass unchanged. */
|
|
8
|
+
type TestimonialCard = {
|
|
9
|
+
name: string
|
|
10
|
+
position: string
|
|
11
|
+
review: string
|
|
12
|
+
verification: string | null
|
|
13
|
+
linkedinurl: string
|
|
14
|
+
photo: { url: string; name: string }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = defineProps<{
|
|
18
|
+
item: TestimonialCard
|
|
19
|
+
className?: string
|
|
20
|
+
/* Injected by the site; omit it when the URLs are already absolute. */
|
|
21
|
+
resolveImage?: (url: string) => string
|
|
22
|
+
}>()
|
|
23
|
+
|
|
24
|
+
const imageUrl = (url: string) => props.resolveImage?.(url) ?? url
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<div :class="`mx-1 lg:mx-2 testimonial rounded-custom space-y-2 lg:space-y-3 py-10 px-6 lg:px-11 ${className}`">
|
|
29
|
+
<div class="flex flex-col lg:flex-row gap-5 lg:items-center">
|
|
30
|
+
<LazyImage v-if="item.photo.name !== 'no-image.svg'"
|
|
31
|
+
class="w-12 lg:w-16 h-12 lg:h-16" :src="imageUrl(item.photo.url)"
|
|
32
|
+
:alt=item.photo.name />
|
|
33
|
+
<div>
|
|
34
|
+
<div class="flex items-center gap-2">
|
|
35
|
+
<p class="text-heading text-lg lg:text-xl">{{ item.name }}</p>
|
|
36
|
+
<a
|
|
37
|
+
v-if="item.linkedinurl.length > 1"
|
|
38
|
+
target="_blank"
|
|
39
|
+
rel='noopener noreferrer'
|
|
40
|
+
class="text-default-transparent hover:text-action transition-colors"
|
|
41
|
+
:href="item.linkedinurl">
|
|
42
|
+
<component
|
|
43
|
+
:is="LinkedinIcon"
|
|
44
|
+
/>
|
|
45
|
+
</a>
|
|
46
|
+
</div>
|
|
47
|
+
<p class="text-sm text-body-secondary">{{ item.position }}</p>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<p class="text-sm text-body-secondary-lighter whitespace-pre-wrap">
|
|
51
|
+
{{ item.review }}
|
|
52
|
+
</p>
|
|
53
|
+
<div v-if="item.verification" class="flex items-center gap-1 text-action">
|
|
54
|
+
<VerifiedIcon />
|
|
55
|
+
<span>{{ item.verification }}</span>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<style scoped>
|
|
61
|
+
.testimonial {
|
|
62
|
+
backdrop-filter: blur(32px);
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
import { Carousel, type CarouselConfig, Slide } from "vue3-carousel";
|
|
4
|
+
import "vue3-carousel/carousel.css";
|
|
5
|
+
import { BREAKPOINTS } from "../../helpers/breakpoints.ts";
|
|
6
|
+
import GlowButton from "../common/GlowButton.vue";
|
|
7
|
+
import TypingEffect from "../common/effects/TypingEffect.vue";
|
|
8
|
+
import TechnologyCard from "./technology-card.vue";
|
|
9
|
+
import { paths } from "../../helpers/paths.ts";
|
|
10
|
+
|
|
11
|
+
/* See ArticlePreview.vue: the fields read here, not the generated Strapi
|
|
12
|
+
* `Technology`, which a caller may still pass unchanged. This component
|
|
13
|
+
* renders no images, so it needs no resolver. */
|
|
14
|
+
type TechnologySummary = {
|
|
15
|
+
name: string
|
|
16
|
+
title1: string
|
|
17
|
+
title2: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const props = defineProps<{
|
|
21
|
+
technologies: TechnologySummary[]
|
|
22
|
+
hideSelected?: boolean
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const activeIndex = ref(0)
|
|
26
|
+
const defaultAutoplayValue = 12000
|
|
27
|
+
const autoplay = ref(defaultAutoplayValue)
|
|
28
|
+
const handleMouseOver = (index: number) => {
|
|
29
|
+
activeIndex.value = index
|
|
30
|
+
autoplay.value = 0
|
|
31
|
+
}
|
|
32
|
+
const selectedTechnology = computed(() => {
|
|
33
|
+
return props.technologies[activeIndex.value]
|
|
34
|
+
})
|
|
35
|
+
const handleMouseLeave = () => {
|
|
36
|
+
activeIndex.value = 0
|
|
37
|
+
autoplay.value = defaultAutoplayValue
|
|
38
|
+
}
|
|
39
|
+
const config = computed<Partial<CarouselConfig>>(() => ({
|
|
40
|
+
gap: 8,
|
|
41
|
+
itemsToShow: 'auto',
|
|
42
|
+
height: 220,
|
|
43
|
+
autoplay: autoplay.value,
|
|
44
|
+
snapAlign: 'center',
|
|
45
|
+
pauseAutoplayOnHover: true,
|
|
46
|
+
wrapAround: true,
|
|
47
|
+
transition: 500,
|
|
48
|
+
breakpoints: {
|
|
49
|
+
[BREAKPOINTS.md]: {
|
|
50
|
+
gap: 16,
|
|
51
|
+
},
|
|
52
|
+
[BREAKPOINTS.xl]: {
|
|
53
|
+
enabled: false,
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}))
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<section :class="`page-container
|
|
61
|
+
${hideSelected ? 'pt-8' : 'flex flex-col pt-4 md:pt-14 pb-20 xl:pb-0 swipe-over'}`">
|
|
62
|
+
<div v-if="!hideSelected" class="flex flex-col items-center text-center">
|
|
63
|
+
<div class="space-y-3">
|
|
64
|
+
<div class="flex justify-center gap-6 font-bold text-xl">
|
|
65
|
+
<div class="flex flex-col xl:flex-row xl:gap-2">
|
|
66
|
+
<span class="text-body-primary">15+</span>
|
|
67
|
+
<span class="text-body-secondary-lighter">Years of experience</span>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="flex flex-col xl:flex-row xl:gap-2">
|
|
70
|
+
<span class="text-body-primary">4.8</span>
|
|
71
|
+
<span class="text-body-secondary-lighter">Average rating</span>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<TypingEffect
|
|
75
|
+
:text1="selectedTechnology.title1"
|
|
76
|
+
:text2="selectedTechnology.title2"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<div v-if="!hideSelected" class="self-center pt-10 pb-[4.5rem] md:pt-14 md:pb-16">
|
|
81
|
+
<GlowButton title="Get a free consultation" :href="paths.contactUs" />
|
|
82
|
+
</div>
|
|
83
|
+
<div v-else class="pt-[7.5rem] pb-20 space-y-7 text-center">
|
|
84
|
+
<h2 class="font-bold text-heading-md text-heading">
|
|
85
|
+
Looking for something specific?
|
|
86
|
+
</h2>
|
|
87
|
+
<p class="text-xl mx-auto max-w-lg text-body-secondary-lighter">
|
|
88
|
+
We offer a variety of services that might be a game-changer for your business.
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="hidden xl:block relative h-80">
|
|
92
|
+
<div
|
|
93
|
+
v-for="(item, index) in technologies"
|
|
94
|
+
:key="index"
|
|
95
|
+
@mouseover="handleMouseOver(index)"
|
|
96
|
+
@mouseleave="handleMouseLeave"
|
|
97
|
+
>
|
|
98
|
+
<TechnologyCard
|
|
99
|
+
:name="item.name"
|
|
100
|
+
:active="activeIndex === index"
|
|
101
|
+
className="w-72 h-72"
|
|
102
|
+
:index="index"
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<Carousel
|
|
107
|
+
v-model="activeIndex"
|
|
108
|
+
v-bind="config"
|
|
109
|
+
class="-mx-4 xl:mx-0 xl:hidden"
|
|
110
|
+
>
|
|
111
|
+
<Slide v-for="(item, index) in technologies" :key="index">
|
|
112
|
+
<div class="flex items-center w-[190px] h-[220px]">
|
|
113
|
+
<TechnologyCard
|
|
114
|
+
:name="item.name"
|
|
115
|
+
:active="activeIndex === index"
|
|
116
|
+
:className="`w-full h-full ${activeIndex === index ? 'max-h-full' : 'max-h-[190px]'}`"
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
</Slide>
|
|
120
|
+
</Carousel>
|
|
121
|
+
</section>
|
|
122
|
+
</template>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Marked, type RendererObject } from "marked";
|
|
3
|
+
import LazyImage from "../common/images/LazyImage.vue"
|
|
4
|
+
import { sanitize } from "isomorphic-dompurify";
|
|
5
|
+
|
|
6
|
+
/* See ArticlePreview.vue: the fields read here, not the generated Strapi
|
|
7
|
+
* `Feature`, which a caller may still pass unchanged. */
|
|
8
|
+
type PainPoint = {
|
|
9
|
+
content: unknown
|
|
10
|
+
image: { url: string; name: string }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
item: PainPoint
|
|
15
|
+
/* Injected by the site; omit it when the URLs are already absolute. */
|
|
16
|
+
resolveImage?: (url: string) => string
|
|
17
|
+
}>()
|
|
18
|
+
|
|
19
|
+
const imageUrl = (url: string) => props.resolveImage?.(url) ?? url
|
|
20
|
+
|
|
21
|
+
const marked = new Marked()
|
|
22
|
+
const renderer: RendererObject = {
|
|
23
|
+
paragraph({ tokens }) {
|
|
24
|
+
return `<p class="text-lg">${this.parser.parseInline(tokens)}</p>`;
|
|
25
|
+
},
|
|
26
|
+
list(token) {
|
|
27
|
+
const body = token.items.map(item => `<li>${this.parser.parse(item.tokens)}</li>`).join('');
|
|
28
|
+
|
|
29
|
+
const type = token.ordered ? 'ol' : 'ul';
|
|
30
|
+
return `<${type} class="pt-2 pl-5 list-disc">${body}</${type}>`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
marked.use({ renderer })
|
|
34
|
+
const contentString = (typeof props.item.content === 'string' ? props.item.content : '') || ''
|
|
35
|
+
const htmlContent = sanitize(marked.parse(contentString, { async: false }))
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<div class="mx-1 md:mx-0 p-6 w-56 h-full md:h-auto rounded-3xl bg-surface-secondary space-y-6">
|
|
40
|
+
<LazyImage
|
|
41
|
+
:src="imageUrl(item.image.url)"
|
|
42
|
+
:alt="item.image.name"
|
|
43
|
+
class="w-8 h-8"
|
|
44
|
+
/>
|
|
45
|
+
<div
|
|
46
|
+
v-html="htmlContent"
|
|
47
|
+
class="text-body-secondary-lighter"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const formattedDate = (locale: string | null, date: Date | null) => {
|
|
2
|
+
if (!locale || !date) return '';
|
|
3
|
+
|
|
4
|
+
const newDate = new Date(date)
|
|
5
|
+
return new Intl.DateTimeFormat(locale, {
|
|
6
|
+
year: 'numeric',
|
|
7
|
+
month: 'long',
|
|
8
|
+
day: 'numeric'
|
|
9
|
+
}).format(newDate)
|
|
10
|
+
}
|
package/dist/theme.css
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
* CODECAVE Tailwind theme — @codecavepro/brand/theme.css
|
|
3
|
+
* --------------------------------------------------------------------------
|
|
4
|
+
* GENERATED from docs/source_examples/styles/global.css — do not edit.
|
|
5
|
+
*
|
|
6
|
+
* Declares the names Tailwind needs in order to emit CODECAVE utility
|
|
7
|
+
* classes: bg-surface-primary, text-heading-lg, rounded-card and the rest.
|
|
8
|
+
* Without it the components in this package mount and behave correctly and
|
|
9
|
+
* render nearly unstyled.
|
|
10
|
+
*
|
|
11
|
+
* IMPORT ORDER MATTERS, and this file is wrong without it:
|
|
12
|
+
*
|
|
13
|
+
* @import "tailwindcss";
|
|
14
|
+
* @import "@codecavepro/brand/tokens.css"; <- must come first,
|
|
15
|
+
* @import "@codecavepro/brand/theme.css"; and stay unlayered
|
|
16
|
+
*
|
|
17
|
+
* Several entries here are deliberate self-references (--x: var(--x)). They
|
|
18
|
+
* exist so Tailwind emits the utility; the value comes from tokens.css,
|
|
19
|
+
* whose unlayered :root outranks @layer theme. Import tokens.css inside a
|
|
20
|
+
* cascade layer, or not at all, and those names resolve to nothing.
|
|
21
|
+
* ======================================================================== */
|
|
22
|
+
|
|
23
|
+
@theme {
|
|
24
|
+
|
|
25
|
+
/* sizes */
|
|
26
|
+
--max-width-desktop: 1280px;
|
|
27
|
+
--breakpoint-sm: 457px;
|
|
28
|
+
--font-sans: Satoshi, sans-serif;
|
|
29
|
+
--text-heading-lg: 3.5rem;
|
|
30
|
+
--text-heading-lg--line-height: 130%;
|
|
31
|
+
--text-heading-md: 2.75rem;
|
|
32
|
+
--text-heading-md--line-height: 115%;
|
|
33
|
+
--text-heading-sm: 2rem;
|
|
34
|
+
--text-heading-sm--line-height: 110%;
|
|
35
|
+
--radius-custom: 2.75rem;
|
|
36
|
+
|
|
37
|
+
/* colors */
|
|
38
|
+
|
|
39
|
+
/* primary */
|
|
40
|
+
--color-primary-25: var(--color-brand-25);
|
|
41
|
+
--color-primary-50: var(--color-brand-50);
|
|
42
|
+
--color-primary-100: var(--color-brand-100);
|
|
43
|
+
--color-primary-200: var(--color-brand-200);
|
|
44
|
+
--color-primary-300: var(--color-brand-300);
|
|
45
|
+
--color-primary-400: var(--color-brand-400);
|
|
46
|
+
--color-primary-500: var(--color-brand-500); /* button primary*/
|
|
47
|
+
--color-primary-600: var(--color-brand-600);
|
|
48
|
+
--color-primary-700: var(--color-brand-700); /* button primary hover*/
|
|
49
|
+
--color-primary-800: var(--color-brand-800);
|
|
50
|
+
--color-primary-900: var(--color-brand-900); /* button primary active*/
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/* once used? */
|
|
54
|
+
--color-glow-25: var(--color-glow-25);
|
|
55
|
+
--color-progress-0: var(--color-progress-0);
|
|
56
|
+
--color-technology-gradient-0: var(--color-technology-gradient-0);
|
|
57
|
+
--color-technology-gradient-25: var(--color-technology-gradient-25);
|
|
58
|
+
--color-technology-gradient-50: var(--color-technology-gradient-50);
|
|
59
|
+
--color-shadow-0: var(--color-shadow-0);
|
|
60
|
+
--color-failureproof-0: var(--color-gray-1000);
|
|
61
|
+
|
|
62
|
+
--color-heading: var(--color-gray-50);
|
|
63
|
+
--color-action: var(--color-brand-500);
|
|
64
|
+
--color-neutral: var(--color-gray-600);
|
|
65
|
+
--color-hovered: var(--color-brand-200);
|
|
66
|
+
--color-outline-primary-hover: var(--color-brand-800);
|
|
67
|
+
--color-body-primary: var(--color-gray-50);
|
|
68
|
+
--color-text-body-primary: var(--color-gray-200);
|
|
69
|
+
--color-body-secondary: var(--color-gray-500);
|
|
70
|
+
--color-body-secondary-lighter: var(--color-gray-300);
|
|
71
|
+
--color-surface-primary: var(--color-brand-950);
|
|
72
|
+
--color-surface-primary-hover: var(--color-gray-800);
|
|
73
|
+
--color-surface-secondary: var(--color-gray-1100); /* Surface/background-secondary*/
|
|
74
|
+
--color-surface-tertiary: var(--color-gray-900);
|
|
75
|
+
--color-surface-primary-transparent: hsl(from var(--color-surface-primary) h s l / 0.3);
|
|
76
|
+
--color-surface-quaternary: var(--color-gray-700);
|
|
77
|
+
|
|
78
|
+
--color-default-transparent: var(--color-gray-100);
|
|
79
|
+
|
|
80
|
+
--color-error: var(--color-error-300);
|
|
81
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codecavepro/brand",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "CODECAVE design system — colour, typography and layout tokens as CSS custom properties and as a typed module.",
|
|
5
5
|
"license": "Unlicense",
|
|
6
6
|
"type": "module",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"./css": "./dist/colors_and_type.css",
|
|
28
28
|
"./tokens.css": "./dist/tokens.css",
|
|
29
|
+
"./theme.css": "./dist/theme.css",
|
|
29
30
|
"./fonts.css": "./dist/fonts.css",
|
|
30
31
|
"./components/*": "./dist/src/components/*",
|
|
31
32
|
"./assets/*": "./dist/src/assets/*",
|
|
@@ -38,11 +39,23 @@
|
|
|
38
39
|
],
|
|
39
40
|
"peerDependencies": {
|
|
40
41
|
"gsap": "^3.13.0",
|
|
41
|
-
"
|
|
42
|
+
"isomorphic-dompurify": "^3.22.0",
|
|
43
|
+
"marked": "^18.0.5",
|
|
44
|
+
"vue": "^3.5.22",
|
|
45
|
+
"vue3-carousel": "^0.17.0"
|
|
42
46
|
},
|
|
43
47
|
"peerDependenciesMeta": {
|
|
44
48
|
"gsap": {
|
|
45
49
|
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"isomorphic-dompurify": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"marked": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"vue3-carousel": {
|
|
58
|
+
"optional": true
|
|
46
59
|
}
|
|
47
60
|
},
|
|
48
61
|
"scripts": {
|