dolphincss 1.3.0 → 1.3.2

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/bin/dolphin.js ADDED
@@ -0,0 +1,453 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import chokidar from 'chokidar';
5
+ import axios from 'axios';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ function indentHtmlOrJsx(htmlStr, initialIndent = 8) {
12
+ const lines = htmlStr.split('\n');
13
+ let currentIndent = initialIndent;
14
+ const indentStep = 2; // 2 spaces
15
+
16
+ const formattedLines = lines.map(line => {
17
+ const trimmed = line.trim();
18
+ if (!trimmed) return '';
19
+
20
+ // Count tags
21
+ const openCount = (trimmed.match(/<[a-zA-Z0-9-]+(?:\s|>)/gi) || []).length;
22
+ const closeCount = (trimmed.match(/<\/[a-zA-Z0-9-]+>/gi) || []).length;
23
+ const selfCloseCount = (trimmed.match(/\/>/g) || []).length;
24
+
25
+ const netChange = (openCount - selfCloseCount) - closeCount;
26
+
27
+ // If the line starts with a closing tag, adjust its indent before printing
28
+ let lineIndent = currentIndent;
29
+ if (trimmed.startsWith('</') || trimmed.startsWith('}')) {
30
+ lineIndent = Math.max(initialIndent, currentIndent - indentStep);
31
+ }
32
+
33
+ const spaces = ' '.repeat(lineIndent);
34
+ const result = spaces + trimmed;
35
+
36
+ // Apply net depth change for the next lines
37
+ currentIndent = Math.max(initialIndent, currentIndent + netChange * indentStep);
38
+
39
+ return result;
40
+ });
41
+
42
+ return formattedLines.filter(Boolean).join('\n');
43
+ }
44
+
45
+ console.log('🐬 Dolphin CLI [Full-Cloud] Started');
46
+ console.log('====================================');
47
+
48
+ const projectRoot = process.cwd();
49
+ const dolphinConfigPath = path.join(projectRoot, 'dolphin.config.json');
50
+ const templatesDir = path.join(__dirname, '../templates');
51
+
52
+ // Global variables to hold remote data
53
+ let remoteMarkerMap = {};
54
+ let remoteBaseUrl = '';
55
+ const fetchingMarkers = new Set(); // Fetch हुँदै गरेका मार्करहरू ट्र्याक गर्न
56
+
57
+ let templateRegistry = {};
58
+
59
+ function loadLocalMarkers() {
60
+ let localMarkersPath = path.join(__dirname, '../config/markers.json');
61
+ if (!fs.existsSync(localMarkersPath)) {
62
+ localMarkersPath = path.join(__dirname, '../marker.json');
63
+ }
64
+
65
+ if (fs.existsSync(localMarkersPath)) {
66
+ const localMarkers = JSON.parse(fs.readFileSync(localMarkersPath, 'utf8'));
67
+ let loadedCount = 0;
68
+
69
+ for (const [marker, data] of Object.entries(localMarkers)) {
70
+ const templateFile = typeof data === 'string' ? data : data.templateFile;
71
+ let fullTemplatePath = path.join(templatesDir, templateFile);
72
+ if (!fs.existsSync(fullTemplatePath)) {
73
+ fullTemplatePath = path.join(__dirname, '../core-templates', templateFile);
74
+ }
75
+
76
+ if (fs.existsSync(fullTemplatePath)) {
77
+ templateRegistry[marker] = {
78
+ content: fs.readFileSync(fullTemplatePath, 'utf8'),
79
+ addClasses: data.addClasses || '',
80
+ isJsxTemplate: templateFile.endsWith('.jsx') || templateFile.endsWith('.tsx')
81
+ };
82
+ loadedCount++;
83
+ }
84
+ }
85
+
86
+ console.log(`📂 Successfully loaded ${loadedCount} local markers.`);
87
+ return localMarkers;
88
+ }
89
+ return {};
90
+ }
91
+
92
+ // Helper to fetch content from URL
93
+ async function fetchRemote(url) {
94
+ try {
95
+ const response = await axios.get(url);
96
+ if (response.status === 404) {
97
+ throw new Error(`File not found (404) at: ${url}`);
98
+ }
99
+ return typeof response.data === 'string' ? response.data : JSON.stringify(response.data);
100
+ } catch (err) {
101
+ if (err.response && err.response.status === 404) {
102
+ throw new Error(`Server returned 404 for: ${url}. Please check if the file exists in your GitHub repo.`);
103
+ }
104
+ throw new Error(`Failed to fetch ${url}: ${err.message}`);
105
+ }
106
+ }
107
+
108
+ function setupVSCodeIntelliSense(markers) {
109
+ try {
110
+ const vscodeDir = path.join(projectRoot, '.vscode');
111
+ if (!fs.existsSync(vscodeDir)) {
112
+ fs.mkdirSync(vscodeDir);
113
+ }
114
+
115
+ // 1. Clean up old Snippets if they exist (user no longer wants code snippets)
116
+ const snippetsPath = path.join(vscodeDir, 'dolphin.code-snippets');
117
+ if (fs.existsSync(snippetsPath)) {
118
+ fs.unlinkSync(snippetsPath);
119
+ }
120
+
121
+ // 2. Generate Custom HTML Data (This provides the CSS class name suggestions)
122
+ const customData = {
123
+ version: 1.1,
124
+ tags: Object.keys(markers).map(marker => ({
125
+ name: marker,
126
+ description: `Dolphin CLI Component`
127
+ })),
128
+ globalAttributes: [
129
+ { name: "class", valueSet: "dolphin-classes" },
130
+ { name: "className", valueSet: "dolphin-classes" }
131
+ ],
132
+ valueSets: [
133
+ {
134
+ name: "dolphin-classes",
135
+ values: Object.keys(markers).map(marker => {
136
+ const data = markers[marker];
137
+ const templateFile = typeof data === 'string' ? data : data.templateFile;
138
+ return {
139
+ name: marker,
140
+ description: `Dolphin CLI Component (${templateFile})`
141
+ };
142
+ })
143
+ }
144
+ ]
145
+ };
146
+ fs.writeFileSync(path.join(vscodeDir, 'dolphin-tags.json'), JSON.stringify(customData, null, 2));
147
+
148
+ // 3. Update settings.json
149
+ const settingsPath = path.join(vscodeDir, 'settings.json');
150
+ let settings = {};
151
+ if (fs.existsSync(settingsPath)) {
152
+ try {
153
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
154
+ } catch (e) {}
155
+ }
156
+
157
+ if (!settings['html.customData']) {
158
+ settings['html.customData'] = [];
159
+ }
160
+
161
+ if (!settings['html.customData'].includes("./.vscode/dolphin-tags.json")) {
162
+ settings['html.customData'].push("./.vscode/dolphin-tags.json");
163
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
164
+ }
165
+
166
+ console.log('✨ VS Code IntelliSense (Auto-Suggest) configured!');
167
+ } catch (error) {
168
+ console.log(`⚠️ Could not setup VS Code IntelliSense: ${error.message}`);
169
+ }
170
+ }
171
+
172
+ async function init() {
173
+ let remoteUrl = '';
174
+
175
+ // 1. Load config
176
+ if (fs.existsSync(dolphinConfigPath)) {
177
+ try {
178
+ const userConfig = JSON.parse(fs.readFileSync(dolphinConfigPath, 'utf8'));
179
+ let rawUrl = userConfig.remoteUrl;
180
+
181
+ // GitHub URL Normalization: ब्राउजरको लिङ्कलाई RAW लिङ्कमा बदल्ने
182
+ if (rawUrl && rawUrl.includes('github.com') && rawUrl.includes('/blob/')) {
183
+ rawUrl = rawUrl.replace('github.com', 'raw.githubusercontent.com').replace('/blob/', '/');
184
+ }
185
+
186
+ remoteUrl = rawUrl;
187
+ } catch (e) {
188
+ console.error('❌ Error parsing dolphin.config.json');
189
+ }
190
+ }
191
+
192
+ if (!remoteUrl) {
193
+ remoteUrl = 'https://raw.githubusercontent.com/Phuyalshankar/dolphincss-template/main/config/markers.json';
194
+ console.log(`📄 No remoteUrl in dolphin.config.json. Defaulting to official remote repository: ${remoteUrl}`);
195
+ }
196
+
197
+ // Remote Sync with Retry
198
+ const maxRetries = 5;
199
+ let retryCount = 0;
200
+
201
+ async function sync() {
202
+ try {
203
+ const cacheBustUrl = `${remoteUrl}?t=${Date.now()}`;
204
+ console.log(`🌐 Syncing markers from: ${cacheBustUrl}`);
205
+ const markersData = await fetchRemote(cacheBustUrl);
206
+ remoteMarkerMap = JSON.parse(markersData);
207
+
208
+ // remoteUrl is .../config/markers.json, so we need to go up two levels to get the base URL
209
+ const configDirUrl = remoteUrl.substring(0, remoteUrl.lastIndexOf('/')); // .../config
210
+ remoteBaseUrl = configDirUrl.substring(0, configDirUrl.lastIndexOf('/')); // .../main
211
+
212
+ console.log(`✅ Remote markers metadata loaded (${Object.keys(remoteMarkerMap).length} items).`);
213
+ console.log(`🚀 On-demand fetching active. (Markers will be downloaded when used)`);
214
+
215
+ setupVSCodeIntelliSense(remoteMarkerMap);
216
+ processFile(path.join(projectRoot, 'src/App.jsx'));
217
+ startWatcher();
218
+ } catch (error) {
219
+ console.error(`❌ Sync Error: ${error.message}`);
220
+
221
+ if (retryCount >= maxRetries) {
222
+ console.log('⚠️ Falling back to local mode after maximum retries...');
223
+ const localMarkers = loadLocalMarkers();
224
+ setupVSCodeIntelliSense(localMarkers);
225
+ startWatcher();
226
+ return;
227
+ }
228
+
229
+ retryCount++;
230
+ console.log(`🔄 Retrying in 5 seconds... (Attempt ${retryCount})`);
231
+ setTimeout(sync, 5000);
232
+ }
233
+ }
234
+
235
+ sync();
236
+ }
237
+
238
+ async function processFile(filePath) {
239
+ console.log(`🔍 processFile triggered for: ${filePath}`);
240
+ if (filePath.includes('templates' + path.sep) || filePath.includes('bin' + path.sep) || filePath.includes('node_modules')) return;
241
+
242
+ const ext = path.extname(filePath).toLowerCase();
243
+ const isReact = ['.jsx', '.tsx', '.js', '.ts'].includes(ext);
244
+
245
+ try {
246
+ let content = fs.readFileSync(filePath, 'utf8');
247
+ let modified = false;
248
+
249
+ const classAttrName = isReact ? 'className' : 'class';
250
+
251
+ // On-demand fetching: यदि कोडमा 'd-' मार्कर भेटियो भने मात्र डाउनलोड गर्ने
252
+ const possibleMarkers = content.match(/dolphin-[a-zA-Z0-9-]+/g);
253
+ if (possibleMarkers) {
254
+ for (const markerClass of possibleMarkers) {
255
+ // सेफ्टी चेक: रिमोट लिस्ट लोड नभएको अवस्थामा
256
+ if (!remoteMarkerMap || Object.keys(remoteMarkerMap).length === 0) {
257
+ continue;
258
+ }
259
+
260
+ // Check if the marker is already in the registry or currently being fetched
261
+ if (!templateRegistry[markerClass] && remoteMarkerMap[markerClass] && !fetchingMarkers.has(markerClass)) {
262
+ fetchingMarkers.add(markerClass); // Mark as fetching
263
+
264
+ try {
265
+ const data = remoteMarkerMap[markerClass];
266
+ const templateFile = typeof data === 'string' ? data : data.templateFile;
267
+ const localTemplatePath = path.join(__dirname, '../core-templates', templateFile);
268
+ let templateContent = '';
269
+ if (fs.existsSync(localTemplatePath)) {
270
+ console.log('🏠 Loaded core template locally: ' + markerClass);
271
+ templateContent = fs.readFileSync(localTemplatePath, 'utf8');
272
+ } else {
273
+ const templateUrl = `${remoteBaseUrl}/templates/${templateFile}?t=${Date.now()}`;
274
+ console.log('🌐 Fetching template for: ' + markerClass + ' from ' + templateUrl + '...');
275
+ templateContent = await fetchRemote(templateUrl);
276
+ }
277
+ if (templateContent) {
278
+ templateContent = templateContent.replace(/>\s*</g, '>\n<');
279
+ templateContent = indentHtmlOrJsx(templateContent, 8);
280
+ }
281
+ templateRegistry[markerClass] = {
282
+ content: templateContent,
283
+ addClasses: data.addClasses || '',
284
+ isJsxTemplate: templateFile.endsWith('.jsx') || templateFile.endsWith('.tsx')
285
+ };
286
+ console.log(`✅ Fetched and registered ${markerClass}`);
287
+ } catch (error) {
288
+ console.error(` ❌ Failed to fetch template for ${markerClass}: ${error.message}`);
289
+ } finally {
290
+ fetchingMarkers.delete(markerClass); // Always remove from fetching set
291
+ }
292
+ } else {
293
+ console.log(`Skipping fetch for ${markerClass}: InRegistry=${!!templateRegistry[markerClass]} InRemote=${!!remoteMarkerMap[markerClass]} Fetching=${fetchingMarkers.has(markerClass)}`);
294
+ }
295
+ }
296
+ }
297
+
298
+ const markerKeys = Object.keys(templateRegistry);
299
+ if (markerKeys.length === 0) return;
300
+ // Improved regex: flexible for attribute order, quotes, spaces, and captures opening tag
301
+ const regex = new RegExp(
302
+ `(<([a-z0-9-]+)\\s+[^>]*${classAttrName}=\\s*["']([^"']*?\\s)?(dolphin-[a-zA-Z0-9-]+)(\\s[^"']*?)?["'][^>]*>)`,
303
+ 'gi'
304
+ );
305
+
306
+ let found = true;
307
+ while (found) {
308
+ found = false;
309
+ regex.lastIndex = 0;
310
+ let match;
311
+
312
+ while ((match = regex.exec(content)) !== null) {
313
+ const fullOpeningTag = match[1];
314
+ const tagName = match[2];
315
+ const beforeClasses = match[3];
316
+ const markerClass = match[4];
317
+ const afterClasses = match[5];
318
+
319
+ if (!templateRegistry[markerClass]) {
320
+ continue;
321
+ }
322
+
323
+ // Find matching closing tag
324
+ let depth = 1;
325
+ const startIndex = match.index + fullOpeningTag.length;
326
+ const tagPattern = new RegExp(`</?${tagName}(?:\\s|>|/)`, 'gi');
327
+ tagPattern.lastIndex = startIndex;
328
+
329
+ let closingTagIndex = -1;
330
+ let tagMatch;
331
+ while ((tagMatch = tagPattern.exec(content)) !== null) {
332
+ const isClosing = tagMatch[0].startsWith('</');
333
+ if (!isClosing) {
334
+ const tagEnd = content.indexOf('>', tagMatch.index);
335
+ if (tagEnd !== -1 && content[tagEnd - 1] === '/') {
336
+ continue;
337
+ }
338
+ depth++;
339
+ } else {
340
+ depth--;
341
+ }
342
+ if (depth === 0) {
343
+ closingTagIndex = tagMatch.index;
344
+ break;
345
+ }
346
+ }
347
+
348
+ if (closingTagIndex !== -1) {
349
+ const innerContent = content.substring(startIndex, closingTagIndex);
350
+ const closingTagMatch = content.substring(closingTagIndex).match(new RegExp(`^</${tagName}\\s*>`, 'i'));
351
+ const closingTag = closingTagMatch ? closingTagMatch[0] : `</${tagName}>`;
352
+
353
+ modified = true;
354
+ console.log(`✨ Expanding component: ${markerClass} in ${path.basename(filePath)}`);
355
+ const templateData = templateRegistry[markerClass];
356
+
357
+ const classParts = (beforeClasses || '') + (afterClasses || '');
358
+ let allClasses = classParts.trim().split(/\s+/).filter(Boolean);
359
+ allClasses = allClasses.filter(c => c !== markerClass && c !== '');
360
+
361
+ if (templateData.addClasses) {
362
+ const addCls = templateData.addClasses.trim().split(/\s+/).filter(Boolean);
363
+ allClasses = [...new Set([...allClasses, ...addCls])];
364
+ }
365
+
366
+ const newClassAttr = allClasses.length > 0 ? ` ${classAttrName}="${allClasses.join(' ')}"` : '';
367
+
368
+ let newOpeningTag = fullOpeningTag.replace(new RegExp(`\\s*${classAttrName}=["'][^"']*["']`, 'i'), newClassAttr ? `${newClassAttr}` : '');
369
+ newOpeningTag = newOpeningTag.replace(/\s+(?:class|className)=["']\s*["']/i, '');
370
+
371
+ let finalTemplate = templateData.content;
372
+
373
+ if (isReact) {
374
+ if (!templateData.isJsxTemplate) {
375
+ finalTemplate = finalTemplate
376
+ .replace(/class=/g, 'className=')
377
+ .replace(/for=/g, 'htmlFor=')
378
+ .replace(/tabindex=/g, 'tabIndex=')
379
+ .replace(/onclick=/g, 'onClick=')
380
+ .replace(/stroke-linecap=/g, 'strokeLinecap=')
381
+ .replace(/stroke-linejoin=/g, 'strokeLinejoin=')
382
+ .replace(/stroke-width=/g, 'strokeWidth=')
383
+ .replace(/fill-rule=/g, 'fillRule=')
384
+ .replace(/clip-rule=/g, 'clipRule=')
385
+ .replace(/<!--([\s\S]*?)-->/g, '{/*$1*/}')
386
+ .replace(/stop-color=/g, 'stopColor=')
387
+ .replace(/style="([^"]*)"/g, (_, s) => {
388
+ const obj = s.split(';').filter(Boolean).map(p => {
389
+ const [k, v] = p.split(':').map(x => x.trim());
390
+ if (!k) return '';
391
+ const camel = k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
392
+ return `${camel}: '${v}'`;
393
+ }).filter(Boolean).join(', ');
394
+ return `style={{${obj}}}`;
395
+ })
396
+ .replace(/<(input|img|br|hr|meta|link)\b([^>]*?)>/gi, (m, t, a) => {
397
+ if (a.trim().endsWith('/')) return m;
398
+ return `<${t}${a} />`;
399
+ });
400
+ }
401
+ } else {
402
+ finalTemplate = finalTemplate.replace(/className=/g, 'class=');
403
+ }
404
+
405
+ if (finalTemplate.includes('{/* INNER */}')) {
406
+ finalTemplate = finalTemplate.replace('{/* INNER */}', innerContent.trim());
407
+ }
408
+
409
+ const fullMatchString = content.substring(match.index, closingTagIndex + closingTag.length);
410
+ if (templateData.isJsxTemplate && finalTemplate.includes('export default') && content.trim() === fullMatchString.trim()) {
411
+ content = finalTemplate;
412
+ console.log(`✨ Generated FULL file component: ${markerClass} in ${path.basename(filePath)}`);
413
+ found = false;
414
+ modified = true;
415
+ break;
416
+ }
417
+
418
+ const expanded = `${newOpeningTag}\n${finalTemplate}\n${closingTag}`;
419
+ content = content.substring(0, match.index) + expanded + content.substring(closingTagIndex + closingTag.length);
420
+ found = true;
421
+ break;
422
+ }
423
+ }
424
+ }
425
+
426
+ if (modified) {
427
+ fs.writeFileSync(filePath, content);
428
+ console.log(`✅ Updated ${path.basename(filePath)}`);
429
+ }
430
+ } catch (error) {
431
+ console.log(`⚠️ Error processing ${filePath}: ${error.message}`);
432
+ }
433
+ }
434
+
435
+ function startWatcher() {
436
+ const watcher = chokidar.watch(['./src/**/*.{js,jsx,ts,tsx,html}', './*.html'], {
437
+ ignored: /(node_modules|\.git|templates|bin)/,
438
+ persistent: true,
439
+ ignoreInitial: false,
440
+ awaitWriteFinish: { stabilityThreshold: 400, pollInterval: 100 },
441
+ });
442
+
443
+ watcher.on('add', filePath => processFile(filePath)).on('change', filePath => processFile(filePath));
444
+ console.log('👁️ Watching for markers...');
445
+ }
446
+
447
+ init();
448
+
449
+ process.on('SIGINT', () => {
450
+ console.log('\n👋 Dolphin CLI stopped');
451
+ process.exit(0);
452
+ });
453
+
package/bin/watcher.js ADDED
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const chokidar = require('chokidar');
5
+
6
+ console.log('🐬 Dolphin Auto Watcher Started (Improved Regex)');
7
+ console.log('===============================================');
8
+
9
+ // ====== IMPORTANT: Separate package root and watch directory ======
10
+ const packageRoot = path.resolve(__dirname, '..'); // Package को root (config/templates यहाँबाट लिन्छ)
11
+ const watchDir = process.cwd(); // User's current project directory watch गर्छ
12
+
13
+ const configPath = path.join(packageRoot, 'config', 'markers.json');
14
+ const templatesDir = path.join(packageRoot, 'templates');
15
+
16
+ if (!fs.existsSync(configPath)) {
17
+ console.error('❌ config/markers.json not found in package!');
18
+ process.exit(1);
19
+ }
20
+
21
+ const markersConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
22
+ console.log(`📋 Loaded ${Object.keys(markersConfig).length} markers`);
23
+
24
+ // Load templates
25
+ const templates = {};
26
+ for (const [marker, cfg] of Object.entries(markersConfig)) {
27
+ const templatePath = path.join(templatesDir, cfg.templateFile);
28
+ if (fs.existsSync(templatePath)) {
29
+ templates[marker] = {
30
+ content: fs.readFileSync(templatePath, 'utf8').trim(),
31
+ addClasses: cfg.addClasses || ''
32
+ };
33
+ } else {
34
+ console.warn(`⚠️ Template not found: ${cfg.templateFile}`);
35
+ templates[marker] = {
36
+ content: `<div class="error">Template ${cfg.templateFile} missing</div>`,
37
+ addClasses: ''
38
+ };
39
+ }
40
+ }
41
+
42
+ function processFile(filePath) {
43
+ const ext = path.extname(filePath).toLowerCase();
44
+ if (!['.js', '.jsx', '.ts', '.tsx', '.html'].includes(ext)) return;
45
+
46
+ const isHTML = ext === '.html';
47
+ const classAttrName = isHTML ? 'class' : 'className';
48
+
49
+ try {
50
+ let content = fs.readFileSync(filePath, 'utf8');
51
+ let originalContent = content;
52
+ let modified = false;
53
+
54
+ // Improved regex: flexible for attribute order, quotes, spaces
55
+ const regex = new RegExp(
56
+ `(<div\\s+[^>]*${classAttrName}=\\s*["']([^"']*\\s*)?(d-[a-zA-Z0-9-]+)(\\s*[^"']*)?["'][^>]*>)([\\s\\S]*?)(</div>)`,
57
+ 'gi'
58
+ );
59
+
60
+ content = content.replace(regex, (fullMatch, openingTag, beforeClasses, marker, afterClasses, innerContent, closingTag) => {
61
+ if (!templates[marker]) return fullMatch;
62
+
63
+ modified = true;
64
+ const templateData = templates[marker];
65
+
66
+ // Collect all classes except the marker
67
+ const classParts = (beforeClasses || '') + (afterClasses || '');
68
+ let allClasses = classParts.trim().split(/\s+/).filter(Boolean);
69
+ allClasses = allClasses.filter(c => c !== marker && c !== '');
70
+
71
+ // Add configured extra classes
72
+ if (templateData.addClasses) {
73
+ const addCls = templateData.addClasses.trim().split(/\s+/).filter(Boolean);
74
+ allClasses = [...new Set([...allClasses, ...addCls])];
75
+ }
76
+
77
+ // New class attribute
78
+ const newClassAttr = allClasses.length > 0
79
+ ? ` ${classAttrName}="${allClasses.join(' ')}"`
80
+ : '';
81
+
82
+ // Prepare inner template
83
+ let newInner = templateData.content;
84
+ if (isHTML) {
85
+ newInner = newInner.replace(/className=/g, 'class=');
86
+ }
87
+
88
+ // Inject INNER content
89
+ newInner = newInner.replace('{/* INNER */}', innerContent.trim());
90
+
91
+ // Return new structure
92
+ return `<div${newClassAttr}>${newInner}\n</div>`;
93
+ });
94
+
95
+ if (modified && content !== originalContent) {
96
+ fs.writeFileSync(filePath, content);
97
+ console.log(`✅ Successfully generated in: ${path.relative(watchDir, filePath)}`);
98
+ }
99
+ } catch (error) {
100
+ console.error(`❌ Error in ${path.relative(watchDir, filePath)}: ${error.message}`);
101
+ }
102
+ }
103
+
104
+ // Watcher
105
+ const watcher = chokidar.watch('**/*.{js,jsx,ts,tsx,html}', {
106
+ cwd: watchDir,
107
+ ignored: /(node_modules|\.git|bin|config|templates|dist|build)/,
108
+ persistent: true,
109
+ ignoreInitial: false,
110
+ awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 }
111
+ });
112
+
113
+ watcher
114
+ .on('add', processFile)
115
+ .on('change', processFile)
116
+ .on('ready', () => console.log('👁️ Watching for changes in your project... Save file to generate!'));
117
+
118
+ process.on('SIGINT', () => {
119
+ console.log('\n👋 Dolphin Watcher stopped.');
120
+ process.exit(0);
121
+ });
@@ -0,0 +1,19 @@
1
+ <div className="flex flex-col gap-8 p-8 fx-crystal">
2
+ <div className="flex flex-wrap gap-4 items-center">
3
+ <button className="filled primary px-6 py-2 glow hover:-translate-y-1">Primary Filled</button>
4
+ <button className="filled secondary px-6 py-2 glow hover:-translate-y-1">Secondary</button>
5
+ <button className="outlined success px-6 py-2 glow hover:-translate-y-1">Success Outlined</button>
6
+ <button className="plain danger px-6 py-2 hover:bg-danger/10">Danger Plain</button>
7
+ </div>
8
+
9
+ <div className="flex flex-wrap gap-6 items-center">
10
+ <button className="filled info px-8 py-3 rounded-xl glow pulse font-bold">Pulsing Glow</button>
11
+ <button className="filled warning px-8 py-3 rounded-xl glow wave font-bold text-black">Wave Glow</button>
12
+ </div>
13
+
14
+ <div className="flex flex-wrap gap-4 items-center">
15
+ <button className="circle sm filled primary"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 5v14M5 12h14"/></svg></button>
16
+ <button className="circle md outlined secondary"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 5v14M5 12h14"/></svg></button>
17
+ <button className="circle lg fx-aurora glow wave"><svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M5 12l5 5L20 7"/></svg></button>
18
+ </div>
19
+ </div>
@@ -0,0 +1 @@
1
+ <div className="glass card p-6 border border-white/20 rounded-2xl max-w-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300 relative overflow-hidden" style={{ backdropFilter: 'blur(20px)' }}> <div className="absolute top-0 right-0 w-32 h-32 bg-primary-500/20 rounded-full blur-2xl"></div> <div className="flex-left gap-4 mb-4 relative z-10"> <div className="w-16 h-16 rounded-full border-2 border-primary-400 overflow-hidden shadow-lg p-0.5"> <img src="https://i.pravatar.cc/150?img=11" alt="Profile" className="w-full h-full rounded-full object-cover" /> </div> <div> <h3 className="text-xl font-bold text-white m-0">Alex Developer</h3> <p className="text-primary-300 text-sm font-medium">Software Engineer</p> </div> </div> <p className="text-white/70 text-sm leading-relaxed mb-6 relative z-10"> Passionate about building magical user experiences and world-class design systems using DolphinCSS. </p> <div className="flex-between relative z-10"> <div className="flex gap-2"> <span className="px-3 py-1 rounded-full bg-white/10 text-xs font-medium text-white/90">React</span> <span className="px-3 py-1 rounded-full bg-white/10 text-xs font-medium text-white/90">Tailwind</span> </div> <button className="circle filled primary-500 text-white p-2 hover:shadow-lg hover:scale-110 transition-all flex-center"> <ChevronRight size={16} /> </button> </div> </div>
@@ -0,0 +1 @@
1
+ <div className="card p-6 lg:p-10 border border-border rounded-2xl bg-surface-alt shadow-lg"> <h3 className="text-2xl font-bold text-text mb-2">Welcome Back</h3> <p className="text-text-muted text-sm mb-8">Please enter your details to sign in.</p> <form className="flex-col-left w-full gap-8"> <div className="w-full mt-2"> <div className="standardlabel w-full"> <input type="email" id="email-std" className="standardlabel-input lg w-full text-text placeholder:text-transparent bg-transparent" placeholder=" " /> <label htmlFor="email-std" className="standardlabel-label text-text-muted font-medium bg-transparent! left-0! px-0!">Email Address</label> </div> </div> <div className="w-full"> <div className="standardlabel w-full"> <input type="password" id="password-std" className="standardlabel-input lg w-full text-text placeholder:text-transparent bg-transparent" placeholder=" " /> <label htmlFor="password-std" className="standardlabel-label text-text-muted font-medium bg-transparent! left-0! px-0!">Password</label> </div> </div> <div className="flex-between w-full"> <label className="radio-item flex-left gap-2 cursor-pointer"> <input type="checkbox" className="accent-primary-500 w-4 h-4 rounded border-border" /> <span className="text-sm font-medium text-text-muted">Remember me</span> </label> <a href="#" className="text-sm font-bold text-primary-500 hover:underline">Forgot password?</a> </div> <button type="button" className="filled primary-500 w-full py-3.5 rounded-xl text-white font-bold hover:shadow-xl hover:-translate-y-0.5 transition-all mt-2 glow"> Sign In </button> </form> </div>
@@ -0,0 +1,5 @@
1
+ {/* Shadcn-like Accessible Modal (React/JSX Compatible) */}
2
+ {/* To use this modal with React state: 1. Add a state: const [isOpen, setIsOpen] = useState(false) 2. Conditionally render the modal-container based on `isOpen` 3. Attach `onClick={() => setIsOpen(false)}` to the backdrop and close buttons
3
+ */}
4
+ <div className="modal-container" role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-desc"> <div className="modal-wrapper"> {/* The backdrop */} <div className="absolute inset-0 bg-surface-dark/60 backdrop-blur-sm cursor-pointer" aria-hidden="true"></div> {/* The Modal Panel */} <div className="modal-panel md glass bg-surface/80 border-border/50 flex flex-col max-h-[90vh]"> <div className="modal-header"> <h3 id="modal-title">Edit Profile</h3> <button aria-label="Close modal">✕</button> </div> <div className="modal-body overflow-y-auto flex-1" id="modal-desc"> <p className="mb-4">Make changes to your profile here. Click save when you're done.</p> <div className="dolphin-form-standard mb-3"> <label htmlFor="name">Name</label> <input type="text" id="name" placeholder="John Doe" /> </div> <div className="dolphin-form-standard"> <label htmlFor="username">Username</label> <input type="text" id="username" placeholder="@johndoe" /> </div> </div> <div className="modal-footer"> <button className="outlined plain py-2 px-4 rounded-lg">Cancel</button> <button className="filled primary py-2 px-4 rounded-lg glow">Save Changes</button> </div> </div> </div>
5
+ </div>
@@ -0,0 +1 @@
1
+ <nav className="glass w-full py-4 px-6 md:px-8 border-b border-white/10 flex-between sticky top-0 z-50 shadow-sm" style={{ backdropFilter: 'blur(24px)' }}> <div className="flex-left gap-2 cursor-pointer"> <div className="circle sm filled primary-500 text-white font-bold flex-center glow">🐬</div> <span className="text-xl font-bold text-white tracking-tight">Dolphin</span> </div> <div className="hidden md:flex gap-6"> <a href="#" className="text-white/80 hover:text-white font-medium transition-colors">Home</a> <a href="#" className="text-white/80 hover:text-white font-medium transition-colors">Features</a> <a href="#" className="text-white/80 hover:text-white font-medium transition-colors">Pricing</a> <a href="#" className="text-white/80 hover:text-white font-medium transition-colors">Docs</a> </div> <div className="flex-right gap-3"> <button className="hidden md:block outlined plain py-2 px-4 rounded-lg font-medium text-sm">Log In</button> <button className="filled primary-600 text-white py-2 px-4 rounded-lg font-medium text-sm hover:shadow-lg transition-all glow">Sign Up</button> </div> </nav>