@squeditor/squeditor-framework 1.0.3 → 1.0.5

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
@@ -1,34 +1,28 @@
1
1
  **Squeditor Framework** is a high-performance, developer-first framework for building lightning-fast, static websites. It combines the power of **PHP-style templating** with the modern performance of **Tailwind CSS**, the interactivity of **UIKit 3**, and the speed of **Vite**.
2
2
 
3
- [Full Documentation](https://docs.squeditor.com)
3
+ We implement AI Agents skills to help you build your website easily in minutes. You can use AI Agents to generate well organized and clean code, components, sections, and pages and ship landing pages and saas and agency websites 10x faster.
4
4
 
5
- ## 🚀 Key Features
5
+ ### Showcase and Demo
6
6
 
7
- ### 🛠 Zero-Runtime PHP Templating
8
- Develop using familiar WordPress-style functions like `get_template_part()` and `get_section()`, use PHP arrays as your data layer, and leverage loops to build complex layouts. The production build "snaps" everything into pure HTML, requiring zero server-side processing.
7
+ [Main Demo](https://squeditor.com/showcase/main-demo/) |
8
+ [All Components](https://squeditor.com/showcase/all-components/) |
9
+ [Style Guide](https://squeditor.com/showcase/style-guide/) |
10
+ [GSAP Animations](https://squeditor.com/showcase/gsap-animations/)
9
11
 
10
- ### 🧩 Selective UIKit 3 Integration
11
- Stop loading massive component libraries. Squeditor Framework uses a **Component Manifest System** (via `uikit-manifest.json`) to tree-shake UIKit. Use only what you need (like a Slider or Modal) and Squeditor Framework will automatically bundle just the necessary SCSS and JS.
12
+ [Full Documentation](https://docs.squeditor.com/framework/getting-started/introduction)
12
13
 
13
- ### 🎨 Token-Based Styling Architecture
14
- Fully themeable via a centralized Design Token system (`_tokens.scss`). Every component and layout utility consumes CSS Custom Properties (`--sq-*`), allowing you to swap entire visual identities or color schemas (Light/Dark) instantly across the site.
14
+ ### Key Features
15
15
 
16
- ### 🌓 Built-in Light/Dark Mode
17
- Native support for `sq-theme-dark` classes and Tailwind's `dark:` utilities. The framework detects schema settings from the environment (or URL) and applies them globally to the body, ensuring a seamless and premium dark mode experience.
18
-
19
- ### 📚 Section-Based Workflow
20
- Build pages in minutes using a library of pre-built, token-aware section variants:
21
- - **Hero**: Video, Split, Centered
22
- - **Cards**: Grids, Horizontal, Feature lists
23
- - **CTA**: Split, Banner, In-line
24
- - **Global**: Responsive Header, Deep-linked Footer
25
-
26
- ### 📖 Automatic Style Guide
27
- Every project includes an automatically generated `style-guide.php` page that renders every active component and its variants, serving as your project's living documentation.
16
+ - Zero-Runtime PHP Templating
17
+ - Selective UIKit 3 Integration
18
+ - Token-Based Styling Architecture
19
+ - Built-in Light/Dark Mode
20
+ - Section-Based Workflow
21
+ - Automatic Style Guide
28
22
 
29
23
  ---
30
24
 
31
- ## 🏗 Project Architecture
25
+ ### Project Architecture
32
26
 
33
27
  ```bash
34
28
  [workspace-root]/
@@ -40,9 +34,9 @@ Every project includes an automatically generated `style-guide.php` page that re
40
34
 
41
35
  ---
42
36
 
43
- ## 🏁 Getting Started
37
+ ### Getting Started
44
38
 
45
- ### 1. Scaffold a New Project
39
+ #### 1. Scaffold a New Project
46
40
  Squeditor includes a robust CLI scaffolding tool. It generates a completely clean, minimalist project instance fully pre-configured to utilize the advanced GSAP and UIKit engines without demo bloat.
47
41
 
48
42
  To scaffold your new project, simply run the following command in your terminal:
@@ -51,7 +45,7 @@ npx @squeditor/squeditor-framework your-project-name
51
45
  ```
52
46
  *(Replace `your-project-name` with your desired folder name).*
53
47
 
54
- ### 2. Development
48
+ #### 2. Development
55
49
  Navigate into your newly generated project folder, install its dependencies, and start the development server:
56
50
  ```bash
57
51
  cd your-project-name
@@ -64,7 +58,7 @@ npm run dev
64
58
  > Vite will show a link to `http://127.0.0.1:5173/` in your terminal. **Do not use that link.**
65
59
  > Instead, always visit the PHP Server address: **`http://127.0.0.1:3001`**. This is because Squeditor Framework uses PHP for template rendering, and Vite only serves the static assets.
66
60
 
67
- ### 3. Build for Production
61
+ #### 3. Build for Production
68
62
  Generate the static `dist/` folder and a distributable ZIP archive:
69
63
  ```bash
70
64
  npm run build
@@ -72,7 +66,7 @@ npm run build
72
66
 
73
67
  ---
74
68
 
75
- ## 🛠 Technology Stack
69
+ ### Technology Stack
76
70
 
77
71
  - **Dev Engine**: PHP 8.2+ (as a templating pre-processor)
78
72
  - **CSS Architecture**: Tailwind CSS + SCSS (Layered Overrides)
@@ -83,13 +77,13 @@ npm run build
83
77
 
84
78
  ---
85
79
 
86
- ## 🤝 Contributing
80
+ ### Contributing
87
81
 
88
82
  We welcome contributions! Whether it's adding new section variants, improving the build pipeline, or refining the token system, feel free to open a PR.
89
83
 
90
84
  ---
91
85
 
92
- ## 📄 License
86
+ ### License
93
87
 
94
88
  Squeditor Framework is released under the [MIT License](LICENSE).
95
89
 
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@squeditor/squeditor-framework",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Squeditor Framework is a high-performance, developer-first framework for building lightning-fast, static websites. It combines the power of PHP-style templating with the modern performance of Tailwind CSS, the interactivity of UIKit 3, and the speed of Vite.",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
9
  "scripts": {
10
+ "scaffold": "node scripts/scaffold.js",
10
11
  "test": "echo \"Error: no test specified\" && exit 1"
11
12
  },
12
13
  "repository": {
package/php/functions.php CHANGED
@@ -90,3 +90,48 @@ function parse_frontmatter(string $file): array {
90
90
  }
91
91
  return [];
92
92
  }
93
+
94
+ /**
95
+ * Load an asset file's contents directly (e.g., for inline SVG).
96
+ *
97
+ * @param string $path Path relative to src/assets/, e.g., 'static/icons/vite.svg'
98
+ * @return string File contents or empty string if not found
99
+ */
100
+ function get_asset(string $path): string {
101
+ $full_path = SRC_PATH . '/assets/' . ltrim($path, '/');
102
+ if (!file_exists($full_path)) {
103
+ trigger_error("Asset not found: {$full_path}", E_USER_WARNING);
104
+ return '';
105
+ }
106
+ return file_get_contents($full_path);
107
+ }
108
+
109
+ /**
110
+ * Load an image file's contents directly (e.g., for inline SVG images) or return an <img> tag.
111
+ *
112
+ * @param string $filename Filename inside src/assets/static/images/, e.g., 'logo.svg'
113
+ * @param string $alt Alt text for the image (if returning an <img> tag)
114
+ * @param string $class Custom CSS class(es) to add to the <img> tag
115
+ * @param bool $inline Whether to return raw file contents (e.g. inline SVG). Default false.
116
+ * @return string Image tag or file contents, or empty string if not found
117
+ */
118
+ function get_image(string $filename, string $alt = '', string $class = '', bool $inline = false): string {
119
+ $full_path = SRC_PATH . '/assets/static/images/' . ltrim($filename, '/');
120
+ if (!file_exists($full_path)) {
121
+ trigger_error("Image not found: {$full_path}", E_USER_WARNING);
122
+ return '';
123
+ }
124
+
125
+ if ($inline) {
126
+ return file_get_contents($full_path);
127
+ }
128
+
129
+ // We assume images are served from /assets/static/images/ relative to document root
130
+ // This path might need to be adjusted based on the server setup, but for now
131
+ // it returns a relative web path.
132
+ $web_path = '/assets/static/images/' . ltrim($filename, '/');
133
+
134
+ $class_attr = $class !== '' ? sprintf(' class="%s"', htmlspecialchars($class)) : '';
135
+
136
+ return sprintf('<img src="%s" alt="%s"%s>', htmlspecialchars($web_path), htmlspecialchars($alt), $class_attr);
137
+ }
@@ -0,0 +1,2 @@
1
+ /* Add your custom fonts @font-face or cdn urls here to load your fonts */
2
+ /* For local fonts use this correct path ’../static/fonts/your-font-name/..’ */
@@ -24,16 +24,8 @@ h6,
24
24
  font-family: var(--sq-font-heading);
25
25
  color: var(--sq-color-heading-text);
26
26
  font-weight: 600;
27
- line-height: 1.2;
28
- }
29
-
30
- h1,
31
- h2,
32
- h3,
33
- h4,
34
- h5,
35
- h6 {
36
- margin-bottom: 0.75em;
27
+ line-height: var(--sq-heading-line-height, 1.2);
28
+ letter-spacing: var(--sq-heading-letter-spacing, normal);
37
29
  }
38
30
 
39
31
  h1 {
@@ -84,7 +76,8 @@ h6 {
84
76
  // Only apply paragraph and list styles if the author didn't supply their own layout classes
85
77
  p:not([class]) {
86
78
  margin-bottom: 1rem;
87
- line-height: 1.6;
79
+ line-height: var(--sq-body-line-height, 1.6);
80
+ letter-spacing: var(--sq-body-letter-spacing, normal);
88
81
  }
89
82
 
90
83
  ul:not([class]),
@@ -10,7 +10,7 @@
10
10
  font-size: var(--sq-btn-font-size);
11
11
  font-weight: var(--sq-btn-font-weight);
12
12
 
13
- @apply inline-flex items-center justify-center gap-2 py-0 px-4 transition-colors transition-all duration-200 cursor-pointer border-none outline-none no-underline;
13
+ @apply inline-flex items-center justify-center gap-2 py-0 px-4 transition-all duration-200 cursor-pointer outline-none no-underline;
14
14
  }
15
15
 
16
16
  .btn-sm {
@@ -18,8 +18,13 @@
18
18
  height: 2rem;
19
19
  }
20
20
 
21
+ .btn-md {
22
+ @apply text-base px-4;
23
+ height: 2.5rem;
24
+ }
25
+
21
26
  .btn-lg {
22
- @apply text-lg px-6;
27
+ @apply text-base px-6;
23
28
  height: 3rem;
24
29
  }
25
30
 
@@ -110,7 +115,7 @@
110
115
  }
111
116
  }
112
117
 
113
- .sq--header {
118
+ .sq-header {
114
119
  z-index: var(--sq-z-sticky);
115
120
 
116
121
  &--sticky {
@@ -125,6 +130,99 @@
125
130
  }
126
131
  }
127
132
 
133
+ // ---------------------------------------------------------
134
+ // Container + Expand Container
135
+ // ---------------------------------------------------------
136
+ .container {
137
+ width: 100%;
138
+
139
+ @screen sm { max-width: 540px; }
140
+ @screen md { max-width: 720px; }
141
+ @screen lg { max-width: 960px; }
142
+ @screen xl { max-width: 1140px; }
143
+ @screen 2xl { max-width: 1280px; }
144
+ }
145
+
146
+ /* Base breakout — expands equally both sides */
147
+ .expand-container {
148
+ --scroll-width: var(--body-scroll-width, 17px);
149
+ --expand-size: calc((100vw - var(--scroll-width) - 100%) / -2);
150
+ margin-right: var(--expand-size);
151
+ margin-left: var(--expand-size);
152
+ }
153
+
154
+ .expand-container-end {
155
+ --scroll-width: var(--body-scroll-width, 17px);
156
+ /* Use width increase instead of negative margin */
157
+ --expand-size: calc((100vw - var(--scroll-width) - 100%) / 2);
158
+ width: calc(100% + var(--expand-size));
159
+ overflow: visible; /* let image overflow naturally */
160
+ }
161
+
162
+ .expand-container-start {
163
+ --scroll-width: var(--body-scroll-width, 17px);
164
+ --expand-size: calc((100vw - var(--scroll-width) - 100%) / 2);
165
+ width: calc(100% + var(--expand-size));
166
+ margin-left: calc(var(--expand-size) * -1);
167
+ overflow: visible;
168
+ }
169
+
170
+ // ---------------------------------------------------------
171
+ // Form icon group
172
+ // ---------------------------------------------------------
173
+ /* Base */
174
+ .form-icon-group {
175
+ position: relative;
176
+ display: flex;
177
+ flex-wrap: wrap;
178
+ align-items: stretch;
179
+ width: 100%;
180
+ }
181
+
182
+ .form-icon-group .form-icon {
183
+ display: inline-flex;
184
+ align-items: center;
185
+ justify-content: center;
186
+ position: absolute;
187
+ height: 100%;
188
+ background: transparent;
189
+ border: 0;
190
+ outline: 0;
191
+ text-decoration: none;
192
+ user-select: none;
193
+ }
194
+
195
+ /* Icon widths based on input size */
196
+ .form-icon-group .form-control + .form-icon { width: 3rem !important; }
197
+ .form-icon-group .form-control.form-control-lg + .form-icon { width: 3.5rem !important; }
198
+ .form-icon-group .form-control.form-control-sm + .form-icon { width: 2.5rem !important; }
199
+ .form-icon-group .form-control.form-control-xs + .form-icon { width: 2rem !important; }
200
+
201
+ /* Icon on the LEFT (default) */
202
+ .form-icon-group:not(.form-icon-flip) .form-control { padding-left: 2.75rem !important; }
203
+ .form-icon-group:not(.form-icon-flip) .form-control.form-control-lg { padding-left: 3.25rem !important; }
204
+ .form-icon-group:not(.form-icon-flip) .form-control.form-control-sm { padding-left: 2.25rem !important; }
205
+ .form-icon-group:not(.form-icon-flip) .form-control.form-control-xs { padding-left: 1.75rem !important; }
206
+
207
+ /* RTL — icon on the LEFT */
208
+ [dir="rtl"] .form-icon-group:not(.form-icon-flip) .form-control { padding-left: 1rem !important; padding-right: 2.75rem !important; }
209
+ [dir="rtl"] .form-icon-group:not(.form-icon-flip) .form-control.form-control-lg { padding-left: 1rem !important; padding-right: 3.25rem !important; }
210
+ [dir="rtl"] .form-icon-group:not(.form-icon-flip) .form-control.form-control-sm { padding-left: 1rem !important; padding-right: 2.25rem !important; }
211
+ [dir="rtl"] .form-icon-group:not(.form-icon-flip) .form-control.form-control-xs { padding-left: 1rem !important; padding-right: 1.75rem !important; }
212
+
213
+ /* Icon on the RIGHT (flip) */
214
+ .form-icon-group.form-icon-flip .form-icon { right: 0; }
215
+ .form-icon-group.form-icon-flip .form-control { padding-right: 2.75rem !important; }
216
+ .form-icon-group.form-icon-flip .form-control.form-control-lg { padding-right: 3.25rem !important; }
217
+ .form-icon-group.form-icon-flip .form-control.form-control-sm { padding-right: 2.25rem !important; }
218
+ .form-icon-group.form-icon-flip .form-control.form-control-xs { padding-right: 1.75rem !important; }
219
+
220
+ /* RTL — icon on the RIGHT (flip) */
221
+ [dir="rtl"] .form-icon-group.form-icon-flip .form-control { padding-right: 1rem !important; padding-left: 2.75rem !important; }
222
+ [dir="rtl"] .form-icon-group.form-icon-flip .form-control.form-control-lg { padding-right: 1rem !important; padding-left: 3.25rem !important; }
223
+ [dir="rtl"] .form-icon-group.form-icon-flip .form-control.form-control-sm { padding-right: 1rem !important; padding-left: 2.25rem !important; }
224
+ [dir="rtl"] .form-icon-group.form-icon-flip .form-control.form-control-xs { padding-right: 1rem !important; padding-left: 1.75rem !important; }
225
+
128
226
  // ---------------------------------------------------------
129
227
  // Page Transitions
130
228
  // ---------------------------------------------------------
@@ -1,16 +1,17 @@
1
1
  // _tokens.scss — Design tokens as CSS custom properties
2
2
 
3
3
  // 1. Define SASS Variables for calculations
4
- $color-primary: #1c2020 !default;
5
- $color-primary-dark: #ffffff !default;
6
- $color-secondary: #038677 !default;
7
- $color-secondary-dark: #d5f37b !default;
8
- $color-accent: #F0FF44 !default;
9
- $color-accent-dark: #F0FF44 !default;
4
+ $color-primary: #107466 !default;
5
+ $color-primary-dark: #F0FF44 !default;
6
+ $color-secondary: #1c2020 !default;
7
+ $color-secondary-dark: #fef1e7 !default;
8
+ $color-accent: #e5fdc5 !default;
9
+ $color-accent-dark: #e5fdc5 !default;
10
10
  $color-white: #FFFFFF !default;
11
- $color-muted-text: #777f7f !default;
12
- $color-muted-bg: #f8f8f8 !default;
11
+ $color-muted-text: #707070 !default;
12
+ $color-muted-bg: #fef1e7 !default;
13
13
  $color-light: #f8f8f8 !default;
14
+ $color-dark: #19191b !default;
14
15
  $color-body-bg: #FFFFFF !default;
15
16
  $color-body-text: #19191b !default;
16
17
  $color-heading-text: #19191b !default;
@@ -28,6 +29,7 @@ $color-border: rgba(138, 138, 138, 0.196) !default;
28
29
  --sq-color-muted-text: #{$color-muted-text};
29
30
  --sq-color-muted-bg: #{$color-muted-bg};
30
31
  --sq-color-light: #{$color-light};
32
+ --sq-color-dark: #{$color-dark};
31
33
  --sq-color-body-bg: #{$color-body-bg};
32
34
  --sq-color-body-text: #{$color-body-text};
33
35
  --sq-color-heading-text: #{$color-heading-text};
@@ -76,12 +78,12 @@ $color-border: rgba(138, 138, 138, 0.196) !default;
76
78
  // --- Component Tokens ---
77
79
 
78
80
  // Buttons
79
- --sq-btn-height: 2.25rem; // h-9
81
+ --sq-btn-height: 2.75rem;
80
82
  --sq-btn-bg: transparent;
81
83
  --sq-btn-text: var(--sq-color-body-text);
82
- --sq-btn-radius: var(--sq-radius-lg);
83
- --sq-btn-font-size: 1rem; // text-base
84
- --sq-btn-font-weight: 500;
84
+ --sq-btn-radius: var(--sq-radius-md);
85
+ --sq-btn-font-size: 1rem;
86
+ --sq-btn-font-weight: 600;
85
87
 
86
88
  --sq-btn-default-bg: var(--sq-color-light);
87
89
  --sq-btn-default-text: var(--sq-color-muted-text);
@@ -113,7 +115,7 @@ $color-border: rgba(138, 138, 138, 0.196) !default;
113
115
  // Dropdown
114
116
  --sq-dropdown-width: 300px;
115
117
  --sq-dropdown-padding: 2rem;
116
- --sq-dropdown-bg: var(--sq-color-white);
118
+ --sq-dropdown-bg: var(--sq-color-muted-bg);
117
119
  --sq-dropdown-text: var(--sq-color-body-text);
118
120
 
119
121
  // Accordion
@@ -226,4 +228,7 @@ $color-border: rgba(138, 138, 138, 0.196) !default;
226
228
  --sq-card-bg: #19191b;
227
229
  --sq-card-shadow: none;
228
230
  --sq-card-shadow-hover: none;
231
+
232
+ // Dropdown
233
+ --sq-dropdown-bg: var(--sq-color-muted-bg);
229
234
  }
@@ -5,6 +5,7 @@
5
5
  $site = [
6
6
  'name' => 'My Project',
7
7
  'lang' => 'en',
8
+ 'dir' => 'ltr', // 'ltr' or 'rtl'
8
9
  'logo' => 'assets/static/images/logo.svg',
9
10
  'description' => 'Squeditor project starter template.',
10
11
  'og_image' => 'assets/static/images/og-default.png',
@@ -41,10 +41,10 @@ ob_start();
41
41
  </p>
42
42
 
43
43
  <div class="flex items-center justify-center gap-4" data-gsap="position: '<-0.5'; from: {y: 20, opacity: 0, filter: 'blur(5px)'}">
44
- <a href="https://squeditor.com/" class="btn btn-lg btn-secondary !px-8 !py-3 !rounded-full text-white font-medium hover:scale-105 transition-transform">
44
+ <a href="https://squeditor.com/" class="btn btn-lg btn-secondary hover:scale-105 transition-transform">
45
45
  Try Squeditor
46
46
  </a>
47
- <a href="https://docs.squeditor.com/" class="btn btn-lg !px-8 !py-3 !rounded-full text-white font-medium !border !border-1 !border-solid !border-zinc-200 hover:scale-105 transition-transform">
47
+ <a href="https://docs.squeditor.com/" class="btn btn-lg !border !border-1 !border-solid !border-zinc-200 hover:scale-105 transition-transform">
48
48
  View Docs
49
49
  </a>
50
50
  </div>
@@ -40,14 +40,16 @@ if (file_exists(__DIR__ . '/../config/active-slider.php')) {
40
40
  <?php if ($is_vite): ?>
41
41
  <!-- Vite Development Environment -->
42
42
  <script type="module" src="<?= $vite_server ?>/@vite/client"></script>
43
- <link rel="stylesheet" href="<?= $vite_server ?>/assets/css/tailwind.css">
43
+ <link rel="stylesheet" href="<?= $vite_server ?>/assets/css/fonts.css">
44
44
  <link rel="stylesheet" href="<?= $vite_server ?>/assets/css/squeditor-icons.css">
45
+ <link rel="stylesheet" href="<?= $vite_server ?>/assets/css/tailwind.css">
45
46
  <?php if ($active_slider): ?><link rel="stylesheet" href="<?= $vite_server ?>/assets/css/slider.min.css"><?php endif; ?>
46
47
  <script type="module" src="<?= $vite_server ?>/assets/scss/main.scss"></script>
47
48
  <style>html.js-fouc { opacity: 0; transition: opacity 0.15s ease-out; }</style>
48
49
  <script>document.documentElement.classList.add('js-fouc');</script>
49
50
  <?php else: ?>
50
51
  <!-- Production Static Assets -->
52
+ <link rel="stylesheet" href="assets/css/fonts.css">
51
53
  <link rel="stylesheet" href="assets/css/squeditor-icons.css">
52
54
  <link rel="stylesheet" href="assets/css/tailwind.css">
53
55
  <?php if ($active_slider): ?><link rel="stylesheet" href="assets/css/slider.min.css"><?php endif; ?>
@@ -17,7 +17,7 @@ $items = $items ?? [];
17
17
  <li class="flex items-center gap-2"><i class="sq-icon-chevron-right opacity-30"></i> <?= htmlspecialchars($feature) ?></li>
18
18
  <?php endforeach; ?>
19
19
  </ul>
20
- <span class="absolute top-6 end-6 w-10 h-10 p-0 rounded-full flex items-center justify-center border border-zinc-200 dark:border-zinc-800 group-hover:-rotate-45 transition-transform duration-300">
20
+ <span class="absolute top-6 end-6 w-10 h-10 p-0 rounded-full flex items-center justify-center bg-white group-hover:-rotate-45 transition-transform duration-300">
21
21
  <i class="sq-icon-arrow-right"></i>
22
22
  </span>
23
23
  </div>
@@ -7,7 +7,7 @@ $button_url = $button_url ?? '#';
7
7
  $secondary_button_text = $secondary_button_text ?? '';
8
8
  $hint_text = $hint_text ?? '';
9
9
  ?>
10
- <section class="py-32 text-center border-t border-zinc-300 border-opacity-30 dark:border-opacity-5">
10
+ <section class="sq-cta-banner py-16 md:py-24 lg:py-32 text-center border-t border-zinc-300 border-opacity-30 dark:border-opacity-5">
11
11
  <div class="max-w-4xl mx-auto px-6">
12
12
  <h2 class="text-4xl md:text-5xl font-bold mb-4 tracking-tighter">
13
13
  <?= $heading ?>
@@ -3,7 +3,7 @@
3
3
  $heading = $heading ?? 'Subscribe to our newsletter';
4
4
  $subheading = $subheading ?? 'Get the latest updates and news from our team.';
5
5
  ?>
6
- <section class="py-20 bg-zinc-50 dark:bg-zinc-800 dark:bg-opacity-40">
6
+ <section class="py-20 bg-muted dark:bg-opacity-40">
7
7
  <div class="max-w-7xl mx-auto px-6 flex flex-col items-center justify-between gap-8">
8
8
  <div class="mb-8 md:mb-0 md:w-1/2">
9
9
  <h2 class="text-4xl font-bold text-center m-0"><?= htmlspecialchars($heading) ?></h2>
@@ -4,7 +4,7 @@
4
4
  $isDark = $isDark ?? false;
5
5
  $isFloat = $isFloat ?? false;
6
6
 
7
- $headerClasses = 'sq--header w-full z-[1000]';
7
+ $headerClasses = 'sq-header w-full z-[1000]';
8
8
  if ($isFloat) $headerClasses .= ' absolute top-0';
9
9
  if ($isDark) $headerClasses .= ' dark-mode text-white bg-zinc-900';
10
10
  else $headerClasses .= ' bg-white border-b';
@@ -62,13 +62,13 @@ $processed_heading = str_replace(
62
62
  </h2>
63
63
 
64
64
  <!-- Subheading -->
65
- <p class="text-lg md:text-xl text-muted mb-10 max-w-md leading-relaxed"
65
+ <p class="text-lg md:text-xl text-muted mb-10 max-w-md"
66
66
  data-gsap="from: {opacity: 0, y: 20}; duration: 0.8; delay: 0.4">
67
67
  <?= htmlspecialchars($args['subheading']) ?>
68
68
  </p>
69
69
 
70
70
  <!-- CTA Button -->
71
- <div data-gsap="from: {opacity: 0, y: 16}; duration: 0.6; delay: 0.7" class="self-start mb-16">
71
+ <div data-gsap="from: {opacity: 0, y: 16}; duration: 0.6; delay: 0.7" class="self-start mb-8 lg:mb-16">
72
72
  <a href="<?= htmlspecialchars($args['cta_url']) ?>" class="btn btn-secondary min-h-[48px] !px-8 !py-4 !text-base !shadow-lg !shadow-sq-primary/30 flex items-center gap-2 transition-transform hover:scale-105">
73
73
  <?= htmlspecialchars($args['cta_label']) ?>
74
74
  <i class="sq-icon sq-icon-arrow-up-right text-lg leading-none mt-0.5"></i>
@@ -96,8 +96,8 @@ $processed_heading = str_replace(
96
96
  <div class="relative h-[400px] md:h-[700px]">
97
97
 
98
98
  <!-- Image Wrapper -->
99
- <div class="h-full rounded-[2rem] overflow-hidden" data-gsap="from: {clipPath: 'inset(0 0 100% 0)'}; duration: 2; ease: expo.inOut;">
100
- <img src="<?= htmlspecialchars($args['image_src']) ?>" alt="<?= htmlspecialchars($args['image_alt']) ?>" class="w-full h-full object-cover rounded-[2rem]">
99
+ <div class="h-full rounded-3xl overflow-hidden" data-gsap="from: {clipPath: 'inset(0 0 100% 0)'}; duration: 2; ease: expo.inOut;">
100
+ <img src="<?= htmlspecialchars($args['image_src']) ?>" alt="<?= htmlspecialchars($args['image_alt']) ?>" class="w-full h-full object-cover">
101
101
  </div>
102
102
 
103
103
  <!-- Floating Services Container -->
@@ -9,8 +9,8 @@ if ($layout !== 'default') {
9
9
  }
10
10
  ?>
11
11
  <!-- Site Footer -->
12
- <footer id="sq_footer" class="sq--footer py-24 border-t dark:border-transparent dark:bg-black/40">
13
- <div class="sq--footer--inner max-w-7xl mx-auto px-6 grid grid-cols-1 md:grid-cols-5 gap-12 text-body">
12
+ <footer id="sq_footer" class="sq-footer py-12 md:py-18 lg:py-24 border-t dark:border-transparent dark:bg-black/40">
13
+ <div class="sq-footer-inner max-w-7xl mx-auto px-6 grid grid-cols-1 md:grid-cols-5 gap-6 md:gap-8 lg:gap-12">
14
14
  <div class="sq--footer--brand md:col-span-2">
15
15
  <div class="flex items-center gap-2 mb-6">
16
16
  <a href="index.html" class="sq--logo flex items-center gap-2 group">
@@ -20,7 +20,7 @@ if ($layout !== 'default') {
20
20
  <h1 class="font-bold text-xl m-0 tracking-tight"><?= htmlspecialchars($site['name']) ?></h1>
21
21
  </a>
22
22
  </div>
23
- <p class="text-sm max-w-xs leading-relaxed mb-8">
23
+ <p class="text-sm max-w-xs leading-relaxed mb-6">
24
24
  <?= htmlspecialchars($site['description']) ?>
25
25
  </p>
26
26
  <div class="flex gap-4">
@@ -61,11 +61,11 @@ if ($layout !== 'default') {
61
61
  </ul>
62
62
  </div>
63
63
  </div>
64
- <div class="sq--footer--divider max-w-7xl mx-auto px-6 mt-20 mb-8">
64
+ <div class="sq-footer-divider max-w-7xl mx-auto px-6 mt-8 md:mt-12 lg:mt-16 mb-8">
65
65
  <hr>
66
66
  </div>
67
- <div class="sq--footer--bottom max-w-7xl mx-auto px-6 flex flex-col md:flex-row justify-between items-center gap-4 text-xs">
68
- <p>&copy; <?= date('Y') ?> <?= htmlspecialchars($site['name']) ?>. Built locally by Developers.</p>
67
+ <div class="sq-footer-bottom max-w-7xl mx-auto px-6 flex flex-col md:flex-row justify-between items-center gap-4 text-sm text-muted">
68
+ <p>&copy; <?= date('Y') ?> <?= htmlspecialchars($site['name']) ?>. Built with ❤️ by Expert Developers.</p>
69
69
  <div class="flex gap-8">
70
70
  <a href="#" class="hover:text-secondary transition-colors">Terms of Service</a>
71
71
  <a href="#" class="hover:text-secondary transition-colors">Privacy Policy</a>
@@ -11,8 +11,8 @@ if ($layout !== 'default') {
11
11
  }
12
12
  ?>
13
13
  <!-- Site Header -->
14
- <header id="sq_header" class="sq--header absolute w-full top-0 z-[1000] bg-white dark:bg-zinc-900" data-gsap="from: {y: -80, opacity: 0}; to: {y: 0, opacity: 1}; duration: 1.2; ease: expo.inOut;">
15
- <div class="sq--header--inner max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
14
+ <header id="sq_header" class="sq-header absolute w-full top-0 z-[1000]" data-gsap="from: {y: -80, opacity: 0}; to: {y: 0, opacity: 1}; duration: 1.2; ease: expo.inOut;">
15
+ <div class="sq-header-inner max-w-7xl mx-auto px-6 h-20 flex items-center justify-between">
16
16
 
17
17
  <!-- Logo -->
18
18
  <a href="index.html" class="sq--logo flex items-center gap-2 group">
@@ -23,17 +23,17 @@ if ($layout !== 'default') {
23
23
  </a>
24
24
 
25
25
  <!-- Desktop Nav (Centered) -->
26
- <div class="sq--header--nav hidden md:flex items-center absolute left-1/2 -translate-x-1/2">
26
+ <div class="sq-header-nav items-center absolute left-1/2 -translate-x-1/2 hidden lg:flex">
27
27
  <?php get_template_part('nav'); ?>
28
28
  </div>
29
29
 
30
30
  <!-- Right Side / CTA -->
31
- <div class="sq--header--cta flex items-center gap-6">
31
+ <div class="sq-header-cta flex items-center gap-6">
32
32
  <a href="#" class="sq-nav-link text-base font-medium text-zinc-900 dark:text-white !text-opacity-70 hover:text-secondary hover:!text-opacity-100 transition-colors hidden md:inline-block">Log in</a>
33
33
  <a href="https://squeditor.com/" class="btn btn-secondary !hidden md:!inline-flex">Try Squeditor</a>
34
34
 
35
35
  <!-- Mobile Toggle -->
36
- <button class="sq--header--mobile--toggle md:hidden" data-gsap-toggle="#sq_offcanvas_creative" type="button" aria-label="Menu">
36
+ <button class="sq-header-mobile-toggle md:hidden" data-gsap-toggle="#sq_offcanvas_creative" type="button" aria-label="Menu">
37
37
  <span data-uk-icon="icon: menu; ratio: 1.5"></span>
38
38
  </button>
39
39
  </div>
@@ -1,13 +1,13 @@
1
1
  <?php
2
2
  // src/template-parts/nav.php
3
3
  $is_mobile = $mobile ?? false;
4
- $nav_class = $is_mobile ? 'flex flex-col space-y-4' : 'flex space-x-2 items-center m-0 p-0';
4
+ $nav_class = $is_mobile ? 'flex flex-col space-y-4' : 'flex space-x-8 items-center m-0 p-0';
5
5
  ?>
6
6
  <nav>
7
7
  <ul class="<?= $nav_class ?>">
8
8
  <?php foreach ($site['nav'] as $item): ?>
9
9
  <li>
10
- <a href="<?= htmlspecialchars($item['url']) ?>" class="sq-nav-link text-base font-medium text-zinc-900 dark:text-white !text-opacity-70 hover:text-secondary hover:!text-opacity-100 transition-colors py-2 px-3">
10
+ <a href="<?= htmlspecialchars($item['url']) ?>" class="sq-nav-link">
11
11
  <?= htmlspecialchars($item['label']) ?>
12
12
  </a>
13
13
  </li>
@@ -2,7 +2,7 @@
2
2
  // src/template-parts/page-title-bar.php
3
3
  $title = $title ?? 'Page Title';
4
4
  $subtitle = $subtitle ?? '';
5
- $class = $class ?? 'py-20 text-center bg-zinc-50 dark:bg-zinc-800 dark:bg-opacity-40';
5
+ $class = $class ?? 'py-20 text-center bg-muted dark:bg-opacity-40';
6
6
  ?>
7
7
  <div class="<?= htmlspecialchars($class) ?>">
8
8
  <div class="max-w-7xl mx-auto px-6">
@@ -7,6 +7,17 @@ module.exports = {
7
7
  ],
8
8
  theme: {
9
9
  extend: {
10
+ screens: {
11
+ 'sm': '459px',
12
+ 'md': '768px',
13
+ 'lg': '992px',
14
+ 'xl': '1200px',
15
+ '2xl': '1400px',
16
+ },
17
+ container: {
18
+ center: true,
19
+ padding: '1rem', // 16px each side = 32px gutter (--bs-gutter-x)
20
+ },
10
21
  colors: {
11
22
  primary: 'var(--sq-color-primary)',
12
23
  secondary: 'var(--sq-color-secondary)',
@@ -20,7 +31,24 @@ module.exports = {
20
31
  serif: ['var(--sq-font-serif)'],
21
32
  mono: ['var(--sq-font-mono)'],
22
33
  },
34
+ fontSize: {
35
+ // Display sizes
36
+ 'display-1': ['8rem', { lineHeight: '1', letterSpacing: '-0.32rem' }],
37
+ 'display-2': ['6rem', { lineHeight: '1', letterSpacing: '-0.24rem' }],
38
+ 'display-3': ['5rem', { lineHeight: '1', letterSpacing: '-0.2rem' }],
39
+ 'display-4': ['4.5rem', { lineHeight: '1', letterSpacing: '-0.18rem' }],
40
+ 'display-5': ['4rem', { lineHeight: '1', letterSpacing: '-0.16rem' }],
41
+ 'display-6': ['3.5rem', { lineHeight: '1', letterSpacing: '-0.14rem' }],
42
+
43
+ // Heading sizes
44
+ 'h1': ['3rem', { lineHeight: '1.1', letterSpacing: '-0.12rem' }],
45
+ 'h2': ['2.5rem', { lineHeight: '1.1', letterSpacing: '-0.08rem' }],
46
+ 'h3': ['2rem', { lineHeight: '1.1', letterSpacing: '-0.07rem' }],
47
+ 'h4': ['1.5rem', { lineHeight: '1.2', letterSpacing: '-0.06rem' }],
48
+ 'h5': ['1.25rem', { lineHeight: '1.2', letterSpacing: '-0.05rem' }],
49
+ 'h6': ['1rem', { lineHeight: '1.2', letterSpacing: '-0.004rem' }],
50
+ },
23
51
  },
24
52
  },
25
53
  plugins: [],
26
- }
54
+ }
@@ -19,6 +19,7 @@ export default defineConfig({
19
19
  main_css: path.resolve(__dirname, 'src/assets/scss/main.scss'),
20
20
  tailwind: path.resolve(__dirname, 'src/assets/css/tailwind.css'),
21
21
  'squeditor-icons': path.resolve(__dirname, 'src/assets/css/squeditor-icons.css'),
22
+ 'fonts': path.resolve(__dirname, 'src/assets/css/fonts.css'),
22
23
  },
23
24
  output: {
24
25
  entryFileNames: 'assets/js/[name].js',
@@ -60,6 +61,13 @@ export default defineConfig({
60
61
  name: 'php-watch',
61
62
  handleHotUpdate({ file, server }) {
62
63
  if (file.endsWith('.php')) {
64
+ // Force Vite to invalidate CSS modules so Tailwind generates new classes
65
+ const tailwindModule = server.moduleGraph.getModuleById(path.resolve(__dirname, 'src/assets/css/tailwind.css'));
66
+ if (tailwindModule) server.moduleGraph.invalidateModule(tailwindModule);
67
+
68
+ const scssModule = server.moduleGraph.getModuleById(path.resolve(__dirname, 'src/assets/scss/main.scss'));
69
+ if (scssModule) server.moduleGraph.invalidateModule(scssModule);
70
+
63
71
  server.ws.send({ type: 'full-reload' });
64
72
  }
65
73
  }
@@ -77,8 +77,7 @@ scssPaths.forEach(f => {
77
77
  fs.writeFileSync(path.join(outputJsDir, 'uikit-components.js'), jsBundle);
78
78
  fs.writeFileSync(path.join(outputScssDir, '_uikit_dynamic.scss'), scssImports);
79
79
 
80
- console.log('[Squeditor] ✅ uikit-components.js and _uikit_dynamic.scss built successfully.');
81
- console.log(` Components included: _core, ${selectedComponents.join(', ')}`);
80
+ console.log('[Squeditor] ✅ UIkit3 components built successfully.');
82
81
 
83
82
  // Generate src/config/active-components.php for the style-guide page
84
83
  const phpConfigDir = path.join(projectRoot, 'src/config');
@@ -128,7 +127,7 @@ if (fs.existsSync(mainScssPath) && config.themes) {
128
127
  // Ensure exactly one trailing newline
129
128
  mainScss = mainScss.trim() + '\n' + themeImports;
130
129
  fs.writeFileSync(mainScssPath, mainScss);
131
- console.log(`[Squeditor] 🎨 Injected themes: ${Object.keys(config.themes).join(', ')}`);
130
+ console.log(`[Squeditor] 🎨 Ready themes: ${Object.keys(config.themes).join(', ')}`);
132
131
  }
133
132
 
134
133
  // Generate Dynamic Slider Config Import
@@ -141,7 +140,7 @@ if (sliderConfig.library === 'swiper') {
141
140
  sliderImportCode += 'import \'./modules/splide-init.js\';\n';
142
141
  }
143
142
  fs.writeFileSync(dynamicSliderPath, sliderImportCode);
144
- console.log(`[Squeditor] 🎠 Injected slider library: ${sliderConfig.library || 'none'}`);
143
+ console.log(`[Squeditor] 🎠 Selected slider library: ${sliderConfig.library || 'none'}`);
145
144
 
146
145
  // Copy slider library CSS to src/assets/css/slider.min.css
147
146
  // This keeps the CSS separate from main.js and gives it a clear, descriptive filename
@@ -150,7 +149,6 @@ if (sliderConfig.library === 'splide') {
150
149
  const splideCssPath = path.join(projectRoot, 'node_modules/@splidejs/splide/dist/css/splide.min.css');
151
150
  if (fs.existsSync(splideCssPath)) {
152
151
  fs.copyFileSync(splideCssPath, sliderCssDest);
153
- console.log('[Squeditor] 📎 Copied Splide CSS → src/assets/css/slider.min.css');
154
152
  } else {
155
153
  console.warn('[Squeditor] ⚠️ Splide CSS not found at expected path.');
156
154
  fs.writeFileSync(sliderCssDest, '/* Splide CSS not found */\n');
@@ -172,7 +170,6 @@ if (sliderConfig.library === 'splide') {
172
170
  }
173
171
  }
174
172
  fs.writeFileSync(sliderCssDest, combinedCss);
175
- console.log('[Squeditor] 📎 Copied Swiper CSS → src/assets/css/slider.min.css');
176
173
  } else {
177
174
  // No slider configured — write an empty placeholder so head.php link doesn't 404
178
175
  fs.writeFileSync(sliderCssDest, '/* No slider library configured */\n');
@@ -14,7 +14,24 @@ if (!projectName) {
14
14
  let destIndex = args.indexOf('--dest');
15
15
  let destPath = destIndex !== -1 ? args[destIndex + 1] : projectName;
16
16
  const sourceDir = path.join(__dirname, '..', 'project-template');
17
- const targetDir = path.resolve(process.cwd(), destPath);
17
+
18
+ // Check if running directly inside the cloned framework repository
19
+ let isInsideRepo = false;
20
+ try {
21
+ const pkgPath = path.join(process.cwd(), 'package.json');
22
+ if (fs.existsSync(pkgPath)) {
23
+ const pkg = require(pkgPath);
24
+ if (pkg.name === '@squeditor/squeditor-framework') {
25
+ isInsideRepo = true;
26
+ }
27
+ }
28
+ } catch (e) {}
29
+
30
+ // If running inside the repo, scaffold as a sibling (../destPath).
31
+ // Otherwise scaffold in the current directory.
32
+ const targetDir = isInsideRepo
33
+ ? path.resolve(process.cwd(), '..', destPath)
34
+ : path.resolve(process.cwd(), destPath);
18
35
 
19
36
  if (!fs.existsSync(sourceDir)) {
20
37
  console.error(`Source template directory does not exist: ${sourceDir}`);
@@ -47,14 +64,18 @@ function copyDirectory(src, dest, ignoreList = []) {
47
64
  console.log(`[Squeditor] Scaffolding new project: ${projectName}...`);
48
65
 
49
66
  // 1. Copy the framework core
50
- const frameworkSourceDir = path.join(__dirname, '..');
51
- const frameworkTargetDir = path.resolve(process.cwd(), 'squeditor-framework');
52
-
53
- if (!fs.existsSync(frameworkTargetDir)) {
54
- console.log(`[Squeditor] Installing local framework core at ./squeditor-framework...`);
55
- // Pass the name of the target directory to the ignore list to prevent infinite loop
56
- const ignoreCoreList = ['project-template', 'showcase', 'node_modules', '.git', '.github', 'squeditor-framework'];
57
- copyDirectory(frameworkSourceDir, frameworkTargetDir, ignoreCoreList);
67
+ if (!isInsideRepo) {
68
+ const frameworkSourceDir = path.join(__dirname, '..');
69
+ const frameworkTargetDir = path.resolve(process.cwd(), 'squeditor-framework');
70
+
71
+ if (!fs.existsSync(frameworkTargetDir)) {
72
+ console.log(`[Squeditor] Installing local framework core at ./squeditor-framework...`);
73
+ // Pass the name of the target directory to the ignore list to prevent infinite loop
74
+ const ignoreCoreList = ['project-template', 'showcase', 'node_modules', '.git', '.github', 'squeditor-framework'];
75
+ copyDirectory(frameworkSourceDir, frameworkTargetDir, ignoreCoreList);
76
+ }
77
+ } else {
78
+ console.log(`[Squeditor] Running inside framework repo. Skipping core installation (using repo).`);
58
79
  }
59
80
 
60
81
  // 2. Copy the project template