@indirecttek/essentials-engine 1.0.0 → 1.1.1

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 ADDED
@@ -0,0 +1,397 @@
1
+ # @indirecttek/essentials-engine
2
+
3
+ A **white-label, config-driven Astro component library** for rapidly building beautiful marketing websites for small businesses. Define your content and branding in a single configuration file, and Essentials Engine handles the rest.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@indirecttek/essentials-engine.svg)](https://www.npmjs.com/package/@indirecttek/essentials-engine)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ---
9
+
10
+ ## ✨ Features
11
+
12
+ - **Config-Driven Architecture** – One configuration file powers your entire site
13
+ - **Zero Client-Side JavaScript** – 100% static output for blazing-fast performance
14
+ - **CSS Variable Theming** – Full control over colors with automatic propagation
15
+ - **Responsive by Default** – Mobile-first design that looks great on all devices
16
+ - **Modern Aesthetics** – Polished shadows, hover effects, and smooth transitions
17
+ - **Flexible Layouts** – Multiple hero styles, sticky navigation, and more
18
+
19
+ ---
20
+
21
+ ## 📦 Installation
22
+
23
+ ```bash
24
+ npm install @indirecttek/essentials-engine
25
+ ```
26
+
27
+ ### Peer Dependencies
28
+
29
+ Ensure your project has Astro and Tailwind CSS installed:
30
+
31
+ ```bash
32
+ npm install astro tailwindcss
33
+ ```
34
+
35
+ **Compatibility:**
36
+ - Astro 4.x or 5.x
37
+ - Tailwind CSS 3.x or 4.x
38
+
39
+ ---
40
+
41
+ ## 🚀 Quick Start
42
+
43
+ ### 1. Create Your Site Configuration
44
+
45
+ Create a configuration file (e.g., `src/config/siteConfig.ts`):
46
+
47
+ ```typescript
48
+ import type { SiteConfig } from '@indirecttek/essentials-engine';
49
+
50
+ export const siteConfig: SiteConfig = {
51
+ businessName: "Raleigh Pro Landscaping",
52
+
53
+ theme: {
54
+ primary: "#2d5a27", // Forest green
55
+ secondary: "#e8f5e3", // Light sage
56
+ accent: "#f4a460", // Sandy brown
57
+ background: "#ffffff",
58
+ foreground: "#1a1a1a",
59
+ },
60
+
61
+ contactInfo: {
62
+ phone: "(919) 555-0123",
63
+ email: "hello@raleighprolandscaping.com",
64
+ address: "123 Oak Street, Raleigh, NC 27601",
65
+ },
66
+
67
+ heroSection: {
68
+ headline: "Transform Your Outdoor Space",
69
+ subheadline: "Professional landscaping services for homes and businesses in the Triangle area.",
70
+ imageUrl: "/images/hero-garden.jpg",
71
+ imageAlt: "Beautiful landscaped garden with flowers",
72
+ callToActionLabel: "Get a Free Quote",
73
+ },
74
+
75
+ services: [
76
+ { name: "Lawn Care", description: "Weekly mowing, edging, and seasonal treatments to keep your lawn lush and healthy." },
77
+ { name: "Garden Design", description: "Custom garden layouts with native plants, flowers, and decorative elements." },
78
+ { name: "Hardscaping", description: "Patios, walkways, retaining walls, and outdoor living spaces." },
79
+ ],
80
+
81
+ seo: {
82
+ title: "Raleigh Pro Landscaping | Professional Lawn & Garden Services",
83
+ description: "Transform your outdoor space with Raleigh's most trusted landscaping company. Lawn care, garden design, and hardscaping.",
84
+ },
85
+
86
+ analytics: {
87
+ enableTracking: false,
88
+ },
89
+
90
+ // Optional features
91
+ socialLinks: {
92
+ facebook: "https://facebook.com/raleighprolandscaping",
93
+ instagram: "https://instagram.com/raleighprolandscaping",
94
+ },
95
+
96
+ layoutOptions: {
97
+ heroLayout: "image-right", // "image-left" | "image-right" | "full-width"
98
+ stickyNav: true,
99
+ },
100
+ };
101
+ ```
102
+
103
+ ### 2. Use the Layout in Your Page
104
+
105
+ Create your homepage (`src/pages/index.astro`):
106
+
107
+ ```astro
108
+ ---
109
+ import { EssentialsLayout } from '@indirecttek/essentials-engine';
110
+ import { siteConfig } from '../config/siteConfig';
111
+ ---
112
+
113
+ <EssentialsLayout config={siteConfig} />
114
+ ```
115
+
116
+ That's it! You now have a complete, professional marketing website.
117
+
118
+ ---
119
+
120
+ ## 🧩 Components
121
+
122
+ Essentials Engine provides both a full-page layout and individual components for flexibility.
123
+
124
+ ### Full-Page Layout
125
+
126
+ ```astro
127
+ ---
128
+ import { EssentialsLayout } from '@indirecttek/essentials-engine';
129
+ ---
130
+
131
+ <EssentialsLayout config={siteConfig} />
132
+ ```
133
+
134
+ Renders a complete page with: `Navbar` → `Hero` → `ServicesGrid` → `ContactForm` → `Footer`
135
+
136
+ ### Individual Components
137
+
138
+ Use components individually for custom page structures:
139
+
140
+ ```astro
141
+ ---
142
+ import { Navbar, Hero, ServicesGrid, ContactForm, Footer } from '@indirecttek/essentials-engine';
143
+ import { siteConfig } from '../config/siteConfig';
144
+ ---
145
+
146
+ <html lang="en">
147
+ <body>
148
+ <Navbar config={siteConfig} />
149
+ <Hero config={siteConfig} />
150
+
151
+ <!-- Your custom content here -->
152
+ <section class="py-16">
153
+ <h2>Why Choose Us?</h2>
154
+ <!-- ... -->
155
+ </section>
156
+
157
+ <ServicesGrid config={siteConfig} />
158
+ <ContactForm config={siteConfig} />
159
+ <Footer config={siteConfig} />
160
+ </body>
161
+ </html>
162
+ ```
163
+
164
+ ### Component Reference
165
+
166
+ | Component | Description |
167
+ |-----------|-------------|
168
+ | `EssentialsLayout` | Full-page wrapper with HTML head, all sections, and theme injection |
169
+ | `Navbar` | Header with business name, click-to-call, and "Contact Us" CTA |
170
+ | `Hero` | Above-the-fold section with headline, subheadline, image, and CTA |
171
+ | `ServicesGrid` | Responsive grid of service cards with hover effects |
172
+ | `ContactForm` | Lead capture form with name, email, phone, and message fields |
173
+ | `Footer` | Site footer with contact info, social icons, and copyright |
174
+
175
+ ---
176
+
177
+ ## 🎨 Theming
178
+
179
+ Colors are defined in the `theme` object and automatically applied throughout the site using CSS variables.
180
+
181
+ ```typescript
182
+ theme: {
183
+ primary: "#2d5a27", // Brand color, used for headings, nav, buttons
184
+ secondary: "#e8f5e3", // Section backgrounds (e.g., services grid)
185
+ accent: "#f4a460", // CTA buttons, highlights
186
+ background: "#ffffff", // Page background
187
+ foreground: "#1a1a1a", // Body text
188
+ }
189
+ ```
190
+
191
+ ### Using Theme Colors in Custom Components
192
+
193
+ The theme colors are available as CSS variables:
194
+
195
+ ```css
196
+ .my-custom-element {
197
+ background-color: var(--color-primary);
198
+ color: var(--color-background);
199
+ }
200
+ ```
201
+
202
+ Or with Tailwind's arbitrary value syntax:
203
+
204
+ ```html
205
+ <div class="bg-[color:var(--color-accent)] text-[color:var(--color-foreground)]">
206
+ Custom styled element
207
+ </div>
208
+ ```
209
+
210
+ ---
211
+
212
+ ## 🖼️ Hero Layouts
213
+
214
+ The Hero component supports three layout modes:
215
+
216
+ ### Image Right (Default)
217
+ ```typescript
218
+ layoutOptions: {
219
+ heroLayout: "image-right",
220
+ }
221
+ ```
222
+ Text on the left, image on the right – great for text-focused messaging.
223
+
224
+ ### Image Left
225
+ ```typescript
226
+ layoutOptions: {
227
+ heroLayout: "image-left",
228
+ }
229
+ ```
230
+ Image on the left, text on the right – ideal for showcasing visuals.
231
+
232
+ ### Full Width
233
+ ```typescript
234
+ layoutOptions: {
235
+ heroLayout: "full-width",
236
+ }
237
+ ```
238
+ Full-bleed background image with centered text overlay – high-impact hero style.
239
+
240
+ ---
241
+
242
+ ## 📱 Responsive Design
243
+
244
+ All components are mobile-first and responsive:
245
+
246
+ - **Navbar**: Stacks phone number, hides "Contact Us" button on mobile
247
+ - **Hero**: Single column on mobile, side-by-side on desktop
248
+ - **ServicesGrid**: 1 column → 2 columns → 3 columns as screen grows
249
+ - **ContactForm**: Fields stack on mobile
250
+ - **Footer**: 3-column layout collapses to single column on mobile
251
+
252
+ ---
253
+
254
+ ## 📋 Configuration Reference
255
+
256
+ ### SiteConfig Interface
257
+
258
+ ```typescript
259
+ interface SiteConfig {
260
+ businessName: string;
261
+ theme: Theme;
262
+ contactInfo: ContactInfo;
263
+ heroSection: HeroSection;
264
+ services: Service[];
265
+ analytics: AnalyticsConfig;
266
+ seo: SEOConfig;
267
+ imageSearchHints?: ImageSearchHints; // Optional
268
+ socialLinks?: SocialLinks; // Optional
269
+ layoutOptions?: LayoutOptions; // Optional
270
+ }
271
+ ```
272
+
273
+ ### Theme
274
+
275
+ | Property | Type | Description |
276
+ |----------|------|-------------|
277
+ | `primary` | `string` | Brand color for headings, nav, primary buttons |
278
+ | `secondary` | `string` | Section backgrounds, subtle accents |
279
+ | `accent` | `string` | CTA buttons, highlights |
280
+ | `background` | `string` | Page background color |
281
+ | `foreground` | `string` | Body text color |
282
+
283
+ ### ContactInfo
284
+
285
+ | Property | Type | Description |
286
+ |----------|------|-------------|
287
+ | `phone` | `string` | Phone number (auto-linked as `tel:`) |
288
+ | `email` | `string` | Email address (auto-linked as `mailto:`) |
289
+ | `address` | `string` | Physical address |
290
+
291
+ ### HeroSection
292
+
293
+ | Property | Type | Description |
294
+ |----------|------|-------------|
295
+ | `headline` | `string` | Main headline text |
296
+ | `subheadline` | `string` | Supporting text below headline |
297
+ | `imageUrl` | `string` | Path or URL to hero image |
298
+ | `imageAlt` | `string` | Alt text for hero image |
299
+ | `callToActionLabel` | `string` | Text for CTA button |
300
+
301
+ ### Service
302
+
303
+ | Property | Type | Description |
304
+ |----------|------|-------------|
305
+ | `name` | `string` | Service name |
306
+ | `description` | `string` | Service description |
307
+
308
+ ### SEOConfig
309
+
310
+ | Property | Type | Description |
311
+ |----------|------|-------------|
312
+ | `title` | `string` | Page title (appears in browser tab) |
313
+ | `description` | `string` | Meta description for search engines |
314
+
315
+ ### AnalyticsConfig
316
+
317
+ | Property | Type | Description |
318
+ |----------|------|-------------|
319
+ | `enableTracking` | `boolean` | Enable/disable analytics |
320
+ | `mixpanelToken` | `string` | Optional Mixpanel project token |
321
+
322
+ ### SocialLinks (Optional)
323
+
324
+ | Property | Type | Description |
325
+ |----------|------|-------------|
326
+ | `facebook` | `string` | Facebook page URL |
327
+ | `instagram` | `string` | Instagram profile URL |
328
+ | `twitter` | `string` | Twitter/X profile URL |
329
+ | `linkedin` | `string` | LinkedIn page URL |
330
+ | `youtube` | `string` | YouTube channel URL |
331
+ | `tiktok` | `string` | TikTok profile URL |
332
+
333
+ ### LayoutOptions (Optional)
334
+
335
+ | Property | Type | Default | Description |
336
+ |----------|------|---------|-------------|
337
+ | `heroLayout` | `"image-left"` \| `"image-right"` \| `"full-width"` | `"image-right"` | Hero section layout style |
338
+ | `stickyNav` | `boolean` | `false` | Make navbar stick to top on scroll |
339
+
340
+ ---
341
+
342
+ ## 📁 Project Structure
343
+
344
+ ```
345
+ @indirecttek/essentials-engine/
346
+ ├── dist/ # Built package (published to npm)
347
+ │ ├── components/
348
+ │ │ ├── ContactForm.astro
349
+ │ │ ├── Footer.astro
350
+ │ │ ├── Hero.astro
351
+ │ │ ├── Navbar.astro
352
+ │ │ └── ServicesGrid.astro
353
+ │ ├── layouts/
354
+ │ │ └── EssentialsLayout.astro
355
+ │ ├── index.ts # Main exports
356
+ │ └── types.ts # TypeScript interfaces
357
+ ├── src/ # Source files
358
+ ├── package.json
359
+ └── README.md
360
+ ```
361
+
362
+ ---
363
+
364
+ ## 🔧 Development
365
+
366
+ Clone and install:
367
+
368
+ ```bash
369
+ git clone https://github.com/indirecttek/essentials-engine.git
370
+ cd essentials-engine
371
+ npm install
372
+ ```
373
+
374
+ Build the package:
375
+
376
+ ```bash
377
+ npm run build
378
+ ```
379
+
380
+ ---
381
+
382
+ ## 📄 License
383
+
384
+ MIT © [IndirectTek](https://indirecttek.com)
385
+
386
+ ---
387
+
388
+ ## 🤝 Contributing
389
+
390
+ Contributions are welcome! Please open an issue or submit a pull request.
391
+
392
+ ---
393
+
394
+ ## 💬 Support
395
+
396
+ - **Issues**: [GitHub Issues](https://github.com/indirecttek/essentials-engine/issues)
397
+ - **Email**: support@indirecttek.com
@@ -0,0 +1,160 @@
1
+ ---
2
+ import type { SiteConfig } from "../types";
3
+
4
+ export interface Props {
5
+ config: SiteConfig;
6
+ }
7
+
8
+ const { config } = Astro.props;
9
+ const { contactInfo, socialLinks } = config;
10
+ const currentYear = new Date().getFullYear();
11
+ ---
12
+
13
+ <footer class="bg-[color:var(--color-primary)] text-[color:var(--color-background)]">
14
+ <div class="max-w-7xl mx-auto px-4 md:px-8 py-12 md:py-16">
15
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-8 md:gap-12">
16
+ <!-- Brand & Description -->
17
+ <div class="space-y-4">
18
+ <h3 class="text-2xl font-bold">{config.businessName}</h3>
19
+ <p class="opacity-80 leading-relaxed">
20
+ Serving our community with excellence and dedication.
21
+ </p>
22
+ </div>
23
+
24
+ <!-- Contact Info -->
25
+ <div class="space-y-4">
26
+ <h4 class="text-lg font-semibold">Contact Us</h4>
27
+ <ul class="space-y-3 opacity-80">
28
+ <li>
29
+ <a
30
+ href={`tel:${contactInfo.phone}`}
31
+ class="hover:opacity-100 transition-opacity flex items-center gap-2"
32
+ >
33
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
34
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"></path>
35
+ </svg>
36
+ {contactInfo.phone}
37
+ </a>
38
+ </li>
39
+ <li>
40
+ <a
41
+ href={`mailto:${contactInfo.email}`}
42
+ class="hover:opacity-100 transition-opacity flex items-center gap-2"
43
+ >
44
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
45
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
46
+ </svg>
47
+ {contactInfo.email}
48
+ </a>
49
+ </li>
50
+ <li class="flex items-start gap-2">
51
+ <svg class="w-5 h-5 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
53
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
54
+ </svg>
55
+ <span>{contactInfo.address}</span>
56
+ </li>
57
+ </ul>
58
+ </div>
59
+
60
+ <!-- Social Links -->
61
+ {socialLinks && Object.keys(socialLinks).length > 0 && (
62
+ <div class="space-y-4">
63
+ <h4 class="text-lg font-semibold">Follow Us</h4>
64
+ <div class="flex gap-4">
65
+ {socialLinks.facebook && (
66
+ <a
67
+ href={socialLinks.facebook}
68
+ target="_blank"
69
+ rel="noopener noreferrer"
70
+ class="opacity-80 hover:opacity-100 transition-opacity"
71
+ aria-label="Facebook"
72
+ >
73
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
74
+ <path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/>
75
+ </svg>
76
+ </a>
77
+ )}
78
+ {socialLinks.instagram && (
79
+ <a
80
+ href={socialLinks.instagram}
81
+ target="_blank"
82
+ rel="noopener noreferrer"
83
+ class="opacity-80 hover:opacity-100 transition-opacity"
84
+ aria-label="Instagram"
85
+ >
86
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
87
+ <path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/>
88
+ </svg>
89
+ </a>
90
+ )}
91
+ {socialLinks.twitter && (
92
+ <a
93
+ href={socialLinks.twitter}
94
+ target="_blank"
95
+ rel="noopener noreferrer"
96
+ class="opacity-80 hover:opacity-100 transition-opacity"
97
+ aria-label="Twitter"
98
+ >
99
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
100
+ <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
101
+ </svg>
102
+ </a>
103
+ )}
104
+ {socialLinks.linkedin && (
105
+ <a
106
+ href={socialLinks.linkedin}
107
+ target="_blank"
108
+ rel="noopener noreferrer"
109
+ class="opacity-80 hover:opacity-100 transition-opacity"
110
+ aria-label="LinkedIn"
111
+ >
112
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
113
+ <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
114
+ </svg>
115
+ </a>
116
+ )}
117
+ {socialLinks.youtube && (
118
+ <a
119
+ href={socialLinks.youtube}
120
+ target="_blank"
121
+ rel="noopener noreferrer"
122
+ class="opacity-80 hover:opacity-100 transition-opacity"
123
+ aria-label="YouTube"
124
+ >
125
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
126
+ <path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
127
+ </svg>
128
+ </a>
129
+ )}
130
+ {socialLinks.tiktok && (
131
+ <a
132
+ href={socialLinks.tiktok}
133
+ target="_blank"
134
+ rel="noopener noreferrer"
135
+ class="opacity-80 hover:opacity-100 transition-opacity"
136
+ aria-label="TikTok"
137
+ >
138
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
139
+ <path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/>
140
+ </svg>
141
+ </a>
142
+ )}
143
+ </div>
144
+ </div>
145
+ )}
146
+ </div>
147
+
148
+ <!-- Bottom Bar -->
149
+ <div class="mt-12 pt-8 border-t border-[color:var(--color-background)]/20">
150
+ <div class="flex flex-col md:flex-row justify-between items-center gap-4">
151
+ <p class="opacity-80 text-sm">
152
+ &copy; {currentYear} {config.businessName}. All rights reserved.
153
+ </p>
154
+ <p class="opacity-60 text-sm">
155
+ Built with ❤️ by Essentials Engine
156
+ </p>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ </footer>
@@ -7,33 +7,66 @@ export interface Props {
7
7
 
8
8
  const { config } = Astro.props;
9
9
  const { heroSection } = config;
10
+ const layout = config.layoutOptions?.heroLayout || "image-right";
10
11
  ---
11
12
 
12
- <section class="relative overflow-hidden">
13
- <div class="max-w-7xl mx-auto px-4 md:px-8 py-16 md:py-24">
14
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12 items-center">
15
- <div class="space-y-6">
16
- <h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-[color:var(--color-primary)] leading-tight">
13
+ {layout === "full-width" ? (
14
+ <section class="relative overflow-hidden">
15
+ <!-- Full-width background image -->
16
+ <div class="absolute inset-0">
17
+ <img
18
+ src={heroSection.imageUrl}
19
+ alt={heroSection.imageAlt}
20
+ class="w-full h-full object-cover"
21
+ loading="eager"
22
+ />
23
+ <div class="absolute inset-0 bg-[color:var(--color-primary)]/70"></div>
24
+ </div>
25
+
26
+ <div class="relative max-w-7xl mx-auto px-4 md:px-8 py-24 md:py-32 lg:py-40">
27
+ <div class="max-w-3xl mx-auto text-center space-y-8">
28
+ <h1 class="text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold text-[color:var(--color-background)] leading-tight">
17
29
  {heroSection.headline}
18
30
  </h1>
19
- <p class="text-lg md:text-xl text-[color:var(--color-foreground)] opacity-80">
31
+ <p class="text-xl md:text-2xl text-[color:var(--color-background)] opacity-90 leading-relaxed">
20
32
  {heroSection.subheadline}
21
33
  </p>
22
34
  <a
23
35
  href="#contact"
24
- class="inline-block bg-[color:var(--color-accent)] text-[color:var(--color-foreground)] px-6 py-3 md:px-8 md:py-4 rounded-md font-semibold text-lg hover:opacity-90 transition-opacity"
36
+ class="inline-block bg-[color:var(--color-accent)] text-[color:var(--color-foreground)] px-8 py-4 md:px-10 md:py-5 rounded-xl font-semibold text-lg md:text-xl hover:scale-105 hover:shadow-lg transition-all duration-300"
25
37
  >
26
38
  {heroSection.callToActionLabel}
27
39
  </a>
28
40
  </div>
29
- <div class="relative">
30
- <img
31
- src={heroSection.imageUrl}
32
- alt={heroSection.imageAlt}
33
- class="w-full h-auto rounded-lg shadow-xl object-cover aspect-[4/3]"
34
- loading="eager"
35
- />
41
+ </div>
42
+ </section>
43
+ ) : (
44
+ <section class="relative overflow-hidden">
45
+ <div class="max-w-7xl mx-auto px-4 md:px-8 py-20 md:py-28 lg:py-32">
46
+ <div class={`grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center ${layout === "image-left" ? "lg:flex-row-reverse" : ""}`}>
47
+ <div class={`space-y-8 ${layout === "image-left" ? "lg:order-2" : ""}`}>
48
+ <h1 class="text-4xl md:text-5xl lg:text-6xl xl:text-7xl font-bold text-[color:var(--color-primary)] leading-tight">
49
+ {heroSection.headline}
50
+ </h1>
51
+ <p class="text-lg md:text-xl lg:text-2xl text-[color:var(--color-foreground)] opacity-80 leading-relaxed">
52
+ {heroSection.subheadline}
53
+ </p>
54
+ <a
55
+ href="#contact"
56
+ class="inline-block bg-[color:var(--color-accent)] text-[color:var(--color-foreground)] px-8 py-4 md:px-10 md:py-5 rounded-xl font-semibold text-lg md:text-xl hover:scale-105 hover:shadow-lg transition-all duration-300"
57
+ >
58
+ {heroSection.callToActionLabel}
59
+ </a>
60
+ </div>
61
+ <div class={`relative ${layout === "image-left" ? "lg:order-1" : ""}`}>
62
+ <img
63
+ src={heroSection.imageUrl}
64
+ alt={heroSection.imageAlt}
65
+ class="w-full h-auto rounded-2xl shadow-2xl object-cover aspect-[4/3]"
66
+ loading="eager"
67
+ />
68
+ </div>
36
69
  </div>
37
70
  </div>
38
- </div>
39
- </section>
71
+ </section>
72
+ )}
@@ -6,9 +6,10 @@ export interface Props {
6
6
  }
7
7
 
8
8
  const { config } = Astro.props;
9
+ const isSticky = config.layoutOptions?.stickyNav ?? false;
9
10
  ---
10
11
 
11
- <nav class="bg-[color:var(--color-primary)] text-[color:var(--color-background)] px-4 py-4 md:px-8">
12
+ <nav class={`bg-[color:var(--color-primary)] text-[color:var(--color-background)] px-4 py-4 md:px-8 ${isSticky ? 'sticky top-0 z-50 shadow-lg' : ''}`}>
12
13
  <div class="max-w-7xl mx-auto flex items-center justify-between">
13
14
  <a href="/" class="text-xl md:text-2xl font-bold hover:opacity-90 transition-opacity">
14
15
  {config.businessName}
@@ -16,13 +17,16 @@ const { config } = Astro.props;
16
17
  <div class="flex items-center gap-4 md:gap-6">
17
18
  <a
18
19
  href={`tel:${config.contactInfo.phone}`}
19
- class="text-sm md:text-base font-medium hover:opacity-90 transition-opacity"
20
+ class="text-sm md:text-base font-medium hover:opacity-90 transition-opacity flex items-center gap-2"
20
21
  >
22
+ <svg class="w-4 h-4 hidden sm:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
23
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"></path>
24
+ </svg>
21
25
  {config.contactInfo.phone}
22
26
  </a>
23
27
  <a
24
28
  href="#contact"
25
- class="hidden md:inline-block bg-[color:var(--color-accent)] text-[color:var(--color-foreground)] px-4 py-2 rounded-md font-medium hover:opacity-90 transition-opacity"
29
+ class="hidden md:inline-block bg-[color:var(--color-accent)] text-[color:var(--color-foreground)] px-5 py-2.5 rounded-xl font-medium hover:scale-105 hover:shadow-md transition-all duration-300"
26
30
  >
27
31
  Contact Us
28
32
  </a>
@@ -9,18 +9,23 @@ const { config } = Astro.props;
9
9
  const { services } = config;
10
10
  ---
11
11
 
12
- <section class="px-4 md:px-8 py-16 md:py-24 bg-[color:var(--color-secondary)]">
12
+ <section class="px-4 md:px-8 py-20 md:py-28 lg:py-32 bg-[color:var(--color-secondary)]">
13
13
  <div class="max-w-7xl mx-auto">
14
- <h2 class="text-3xl md:text-4xl font-bold text-[color:var(--color-primary)] text-center mb-12">
15
- Our Services
16
- </h2>
17
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8">
14
+ <div class="text-center mb-16">
15
+ <h2 class="text-3xl md:text-4xl lg:text-5xl font-bold text-[color:var(--color-primary)] mb-4">
16
+ Our Services
17
+ </h2>
18
+ <p class="text-lg md:text-xl text-[color:var(--color-foreground)] opacity-70 max-w-2xl mx-auto">
19
+ Professional solutions tailored to your needs
20
+ </p>
21
+ </div>
22
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 md:gap-10">
18
23
  {services.map((service) => (
19
- <div class="bg-[color:var(--color-background)] p-6 md:p-8 rounded-lg shadow-md hover:shadow-lg transition-shadow">
20
- <h3 class="text-xl md:text-2xl font-semibold text-[color:var(--color-primary)] mb-3">
24
+ <div class="bg-[color:var(--color-background)] p-8 md:p-10 rounded-2xl shadow-md hover:shadow-xl hover:scale-[1.02] transition-all duration-300 cursor-default">
25
+ <h3 class="text-xl md:text-2xl font-semibold text-[color:var(--color-primary)] mb-4">
21
26
  {service.name}
22
27
  </h3>
23
- <p class="text-[color:var(--color-foreground)] opacity-80 leading-relaxed">
28
+ <p class="text-[color:var(--color-foreground)] opacity-80 leading-relaxed text-lg">
24
29
  {service.description}
25
30
  </p>
26
31
  </div>
package/dist/index.ts CHANGED
@@ -7,6 +7,8 @@ export type {
7
7
  AnalyticsConfig,
8
8
  SEOConfig,
9
9
  ImageSearchHints,
10
+ SocialLinks,
11
+ LayoutOptions,
10
12
  SiteConfig,
11
13
  } from "./types";
12
14
 
@@ -18,3 +20,4 @@ export { default as Navbar } from "./components/Navbar.astro";
18
20
  export { default as Hero } from "./components/Hero.astro";
19
21
  export { default as ServicesGrid } from "./components/ServicesGrid.astro";
20
22
  export { default as ContactForm } from "./components/ContactForm.astro";
23
+ export { default as Footer } from "./components/Footer.astro";
@@ -4,6 +4,7 @@ import Navbar from "../components/Navbar.astro";
4
4
  import Hero from "../components/Hero.astro";
5
5
  import ServicesGrid from "../components/ServicesGrid.astro";
6
6
  import ContactForm from "../components/ContactForm.astro";
7
+ import Footer from "../components/Footer.astro";
7
8
 
8
9
  export interface Props {
9
10
  config: SiteConfig;
@@ -23,20 +24,14 @@ const { config } = Astro.props;
23
24
  </head>
24
25
  <body
25
26
  style={`--color-primary: ${config.theme.primary}; --color-secondary: ${config.theme.secondary}; --color-accent: ${config.theme.accent}; --color-background: ${config.theme.background}; --color-foreground: ${config.theme.foreground};`}
26
- class="bg-[color:var(--color-background)] text-[color:var(--color-foreground)] min-h-screen"
27
+ class="bg-[color:var(--color-background)] text-[color:var(--color-foreground)] min-h-screen leading-relaxed"
27
28
  >
28
29
  <Navbar config={config} />
29
- <main class="space-y-16 md:space-y-24">
30
+ <main>
30
31
  <Hero config={config} />
31
32
  <ServicesGrid config={config} />
32
33
  <ContactForm config={config} />
33
34
  </main>
34
- <footer class="bg-[color:var(--color-primary)] text-[color:var(--color-background)] px-4 md:px-8 py-8">
35
- <div class="max-w-7xl mx-auto text-center">
36
- <p class="opacity-90">
37
- &copy; {new Date().getFullYear()} {config.businessName}. All rights reserved.
38
- </p>
39
- </div>
40
- </footer>
35
+ <Footer config={config} />
41
36
  </body>
42
37
  </html>
package/dist/types.ts CHANGED
@@ -40,6 +40,20 @@ export interface ImageSearchHints {
40
40
  services: string[];
41
41
  }
42
42
 
43
+ export interface SocialLinks {
44
+ facebook?: string;
45
+ instagram?: string;
46
+ twitter?: string;
47
+ linkedin?: string;
48
+ youtube?: string;
49
+ tiktok?: string;
50
+ }
51
+
52
+ export interface LayoutOptions {
53
+ heroLayout?: "image-left" | "image-right" | "full-width";
54
+ stickyNav?: boolean;
55
+ }
56
+
43
57
  export interface SiteConfig {
44
58
  businessName: string;
45
59
  theme: Theme;
@@ -49,4 +63,6 @@ export interface SiteConfig {
49
63
  analytics: AnalyticsConfig;
50
64
  seo: SEOConfig;
51
65
  imageSearchHints?: ImageSearchHints;
66
+ socialLinks?: SocialLinks;
67
+ layoutOptions?: LayoutOptions;
52
68
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indirecttek/essentials-engine",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/types.d.ts",