@codecavepro/brand 2.8.1 → 3.0.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 CHANGED
@@ -155,7 +155,7 @@ Two things to know about it:
155
155
 
156
156
  ## The components
157
157
 
158
- 22 components and 13 icons — the buttons, the form controls and the forms
158
+ 20 components and 13 icons — the buttons, the form controls and the forms
159
159
  themselves, the nav bar, the footer link group, the article and technology
160
160
  cards, the review card and the typing effect codecave.pro renders — ship as
161
161
  **source**, byte-for-byte the files the site builds from. Not a reimplementation
@@ -212,8 +212,7 @@ surprises you.
212
212
 
213
213
  `vue` is the only required peer. Four more are optional, each wanted by one or two
214
214
  components; leave one out and only those are affected — `gsap` by `GlowButton.vue`,
215
- `effects/TypingEffect.vue` and `forms/ContactUsForm.vue`; `vue3-carousel` by
216
- `homepage/technologies.vue`; `marked` and `isomorphic-dompurify` by
215
+ `effects/TypingEffect.vue` and `forms/ContactUsForm.vue`; `marked` and `isomorphic-dompurify` by
217
216
  `project/pain-points-item.vue`.
218
217
 
219
218
  ### Import the theme, not just the tokens
@@ -619,7 +619,7 @@ a:hover { color: var(--color-hovered); }
619
619
 
620
620
  /* ---------------------------------------------------------------------------
621
621
  * Cards — sources: homepage/projects.astro, common/ArticlePreview.vue,
622
- * homepage/technology-card.vue
622
+ * common/technology-card.vue
623
623
  * Depth is never built from surface contrast: the card sits ONE narrow step
624
624
  * off the page (#0F0F15 on #0A0A0B). Radius and border carry the separation.
625
625
  * ------------------------------------------------------------------------ */
@@ -636,7 +636,7 @@ a:hover { color: var(--color-hovered); }
636
636
  .card-article { border-radius: var(--radius-article); }
637
637
 
638
638
  /* Feature card — gradient hairline drawn with mask-composite, blurred
639
- * backdrop, no fill. Source: homepage/technology-card.vue.
639
+ * backdrop, no fill. Source: common/technology-card.vue.
640
640
  * Since the rebuild brand-400 is violet (#5F3ABD), so the ring reads violet
641
641
  * end to end; cyan survives only in the wash tokens above. */
642
642
  .card-feature {
@@ -1,12 +1,21 @@
1
+ <script setup lang="ts">
2
+ import Button from "./Button.vue";
3
+
4
+ const props = defineProps<{
5
+ brows: {
6
+ header: string;
7
+ description: string;
8
+ }[]
9
+ }>()
10
+ </script>
11
+
1
12
  <template>
2
13
  <div class="flex flex-col md:flex-row gap-4 md:gap-6 text-lg md:text-xl">
14
+ <div v-for="brow in props.brows">
3
15
  <div class="space-x-1">
4
- <span class="font-bold text-body-primary">15+</span>
5
- <span class="text-body-secondary">Years of experience</span>
16
+ <span class="font-bold text-body-primary">{{ brow.header }}</span>
17
+ <span class="text-body-secondary">{{ brow.description }}</span>
6
18
  </div>
7
- <div class="space-x-1">
8
- <span class="font-bold text-body-primary">4.7</span>
9
- <span class="text-body-secondary">Average rating</span>
10
- </div>
11
- </div>
19
+ </div>
20
+ </div>
12
21
  </template>
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import Button from "../common/Button.vue";
2
+ import Button from "./Button.vue";
3
3
  import { computed } from 'vue'
4
4
 
5
5
  const props = defineProps<{
@@ -1,5 +1,11 @@
1
1
  <script setup lang="ts">
2
2
  import Button from "../common/Button.vue";
3
+
4
+ const props = defineProps<{
5
+ header?: string;
6
+ description?: string;
7
+ subDescription?: string;
8
+ }>()
3
9
  </script>
4
10
 
5
11
  <template>
@@ -8,13 +14,13 @@ import Button from "../common/Button.vue";
8
14
  >
9
15
  <div class="flex-1">
10
16
  <h3 class="text-heading font-bold xl:text-[32px] text-2xl">
11
- Grab our AI trends research!
17
+ {{ props.header }}
12
18
  </h3>
13
19
 
14
20
  <p class="md:mt-1 mt-4 text-body-secondary-lighter text-pretty">
15
- We’ve researched current state of the AI in 3 industries so you don’t have to.
21
+ {{ props.description }}
16
22
  <br>
17
- Check what we’ve found about yours.
23
+ {{ props.subDescription }}
18
24
  </p>
19
25
  </div>
20
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codecavepro/brand",
3
- "version": "2.8.1",
3
+ "version": "3.0.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",
@@ -41,8 +41,7 @@
41
41
  "gsap": "^3.13.0",
42
42
  "isomorphic-dompurify": "^3.22.0",
43
43
  "marked": "^18.0.5",
44
- "vue": "^3.5.22",
45
- "vue3-carousel": "^0.17.0"
44
+ "vue": "^3.5.22"
46
45
  },
47
46
  "peerDependenciesMeta": {
48
47
  "gsap": {
@@ -53,9 +52,6 @@
53
52
  },
54
53
  "marked": {
55
54
  "optional": true
56
- },
57
- "vue3-carousel": {
58
- "optional": true
59
55
  }
60
56
  },
61
57
  "scripts": {
@@ -1,56 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref } from 'vue'
3
- import InputText from '../InputText.vue'
4
- import Button from '../Button.vue'
5
- import { isCorrectEmailFormat } from "../../helpers/form-validator.ts";
6
-
7
- const email = ref('')
8
-
9
- const hasError = ref(false)
10
- const emailErrorMessage = ref('')
11
-
12
- const submitForm = () => {
13
- hasError.value = false
14
- emailErrorMessage.value = ''
15
-
16
- if (!email.value.trim()) {
17
- hasError.value = true
18
- emailErrorMessage.value = 'This field is required'
19
- return
20
- }
21
-
22
- if (!isCorrectEmailFormat(email.value)) {
23
- hasError.value = true
24
- emailErrorMessage.value = 'Please enter valid email'
25
- return
26
- }
27
- }
28
- </script>
29
-
30
- <template>
31
- <form
32
- novalidate
33
- class="flex flex-col gap-4 md:flex-row md:items-center"
34
- @submit.prevent="submitForm"
35
- >
36
- <div class="md:w-64 xl:w-80">
37
- <InputText
38
- id="file-email"
39
- v-model="email"
40
- is-required
41
- :isError="hasError"
42
- :errorMessage="emailErrorMessage"
43
- label="E-mail"
44
- type="email"
45
- placeholder="Where should we send it?"
46
- autocomplete="email"
47
- />
48
- </div>
49
-
50
- <Button
51
- title="Grab research"
52
- variant="tertiary"
53
- class="w-40 flex-shrink-0"
54
- />
55
- </form>
56
- </template>
@@ -1,5 +0,0 @@
1
- export const BREAKPOINTS = {
2
- md: 768,
3
- lg: 1024,
4
- xl: 1280,
5
- }
@@ -1,72 +0,0 @@
1
- import { onMounted, onUnmounted, ref } from 'vue';
2
-
3
- export function carouselGestureLocker() {
4
- const carouselRoot = ref<HTMLElement | null>(null)
5
-
6
- let viewport: HTMLElement | null = null
7
-
8
- let startX = 0
9
- let startY = 0
10
- let gestureLocked = false
11
- let isVerticalGesture = false
12
-
13
- const onTouchStart = (e: TouchEvent) => {
14
- startX = e.touches[0].clientX
15
- startY = e.touches[0].clientY
16
-
17
- gestureLocked = false
18
- isVerticalGesture = false
19
- }
20
-
21
- const onTouchMove = (e: TouchEvent) => {
22
- const dx = Math.abs(e.touches[0].clientX - startX)
23
- const dy = Math.abs(e.touches[0].clientY - startY)
24
-
25
- if (!gestureLocked) {
26
- if (dx < 10 && dy < 10) return
27
-
28
- gestureLocked = true
29
- isVerticalGesture = dy > dx
30
- }
31
-
32
- if (isVerticalGesture) {
33
- e.stopImmediatePropagation()
34
- }
35
- }
36
-
37
- const onTouchEnd = () => {
38
- gestureLocked = false
39
- isVerticalGesture = false
40
- }
41
-
42
- onMounted(() => {
43
-
44
- viewport =
45
- carouselRoot.value?.querySelector('.carousel__viewport') ?? null
46
-
47
- if (!viewport) return
48
-
49
- viewport.addEventListener('touchstart', onTouchStart, {
50
- passive: true,
51
- })
52
-
53
- viewport.addEventListener('touchmove', onTouchMove, {
54
- passive: false,
55
- })
56
-
57
- viewport.addEventListener('touchend', onTouchEnd)
58
-
59
- viewport.addEventListener('touchcancel', onTouchEnd)
60
- })
61
-
62
- onUnmounted(() => {
63
- if (!viewport) return
64
-
65
- viewport.removeEventListener('touchstart', onTouchStart)
66
- viewport.removeEventListener('touchmove', onTouchMove)
67
- viewport.removeEventListener('touchend', onTouchEnd)
68
- viewport.removeEventListener('touchcancel', onTouchEnd)
69
- })
70
-
71
- return carouselRoot;
72
- }
@@ -1,136 +0,0 @@
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 HeaderBrows from "../common/HeaderBrows.vue";
9
- import TechnologyCard from "./technology-card.vue";
10
- import { carouselGestureLocker } from "../helpers/carousel-gesture-locker.ts";
11
-
12
- /* See ArticlePreview.vue: the fields read here, not the generated Strapi
13
- * `Technology`, which a caller may still pass unchanged. This component
14
- * renders no images, so it needs no resolver. */
15
- type TechnologySummary = {
16
- name: string
17
- title1: string
18
- title2: string
19
- }
20
-
21
- const props = defineProps<{
22
- technologies: TechnologySummary[]
23
- hideSelected?: boolean
24
- /* Where the consultation button goes. Was `paths.contactUs`, read straight
25
- * off this site's route table. */
26
- ctaHref?: string
27
- /* Technology name to service URL. Plain data rather than a resolver function
28
- * because every call site mounts this with `client:only`, and Astro can only
29
- * hand an island props it can serialise. @helpers/service-links.ts holds the
30
- * site's map; a name that is missing renders an inert button. */
31
- serviceLinks?: Record<string, string>
32
- }>()
33
-
34
- const activeIndex = ref(0)
35
- const defaultAutoplayValue = 12000
36
- const autoplay = ref(defaultAutoplayValue)
37
- const handleMouseOver = (index: number) => {
38
- activeIndex.value = index
39
- autoplay.value = 0
40
- }
41
- const selectedTechnology = computed(() => {
42
- return props.technologies[activeIndex.value]
43
- })
44
- const handleMouseLeave = () => {
45
- activeIndex.value = 0
46
- autoplay.value = defaultAutoplayValue
47
- }
48
- const config = computed<Partial<CarouselConfig>>(() => ({
49
- gap: 8,
50
- itemsToShow: 'auto',
51
- autoplay: autoplay.value,
52
- wrapAround: true,
53
- height: 220,
54
- snapAlign: 'center',
55
- pauseAutoplayOnHover: true,
56
- transition: 500,
57
- breakpoints: {
58
- [BREAKPOINTS.md]: {
59
- gap: 16,
60
- },
61
- [BREAKPOINTS.xl]: {
62
- enabled: false,
63
- },
64
- }
65
- }))
66
- const carouselLocker = carouselGestureLocker()
67
- </script>
68
-
69
- <template>
70
- <section :class="`page-container
71
- ${hideSelected ? 'pt-8' : 'flex flex-col pt-4 md:pt-14 pb-20 xl:pb-0 swipe-over'}`">
72
- <div v-if="!hideSelected" class="flex flex-col items-center text-center">
73
- <div class="space-y-3">
74
- <div class="flex justify-center gap-6 text-xl">
75
- <div class="space-x-1">
76
- <span class="font-bold text-body-primary">15+</span>
77
- <span class="text-body-secondary-lighter">Years of experience</span>
78
- </div>
79
- <div class="space-x-1">
80
- <span class="font-bold text-body-primary">4.7</span>
81
- <span class="text-body-secondary-lighter">Average rating</span>
82
- </div>
83
- </div>
84
- <TypingEffect
85
- :text1="selectedTechnology.title1"
86
- :text2="selectedTechnology.title2"
87
- />
88
- </div>
89
- </div>
90
- <div v-if="!hideSelected" class="self-center pt-10 pb-[4.5rem] md:pt-14 md:pb-16">
91
- <GlowButton title="Get a free consultation" :href="ctaHref" />
92
- </div>
93
- <div v-else class="pt-[7.5rem] pb-20 space-y-7 text-center">
94
- <h2 class="font-bold text-heading-md text-heading">
95
- Looking for something specific?
96
- </h2>
97
- <p class="text-xl mx-auto max-w-lg text-body-secondary-lighter">
98
- We offer a variety of services that might be a game-changer for your business.
99
- </p>
100
- </div>
101
- <div class="hidden xl:block relative h-80">
102
- <div
103
- v-for="(item, index) in technologies"
104
- :key="index"
105
- @mouseover="handleMouseOver(index)"
106
- @mouseleave="handleMouseLeave"
107
- >
108
- <TechnologyCard
109
- :name="item.name"
110
- :href="serviceLinks?.[item.name]"
111
- :active="activeIndex === index"
112
- className="w-72 h-72"
113
- :index="index"
114
- />
115
- </div>
116
- </div>
117
- <div ref="carouselLocker">
118
- <Carousel
119
- v-model="activeIndex"
120
- v-bind="config"
121
- class="-mx-4 xl:mx-0 xl:hidden"
122
- >
123
- <Slide v-for="(item, index) in technologies" :key="index">
124
- <div class="flex items-center w-[190px] h-[220px]">
125
- <TechnologyCard
126
- :name="item.name"
127
- :href="serviceLinks?.[item.name]"
128
- :active="activeIndex === index"
129
- :className="`w-full h-full ${activeIndex === index ? 'max-h-full' : 'max-h-[190px]'}`"
130
- />
131
- </div>
132
- </Slide>
133
- </Carousel>
134
- </div>
135
- </section>
136
- </template>