clou-lang 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/clou-ai-prompt.md +126 -6
- package/bin/clou.js +89 -1
- package/package.json +1 -1
- package/playground/clou-browser.js +434 -2
- package/playground/index.html +4 -7
- package/src/compiler.js +284 -2
- package/src/errors.js +2 -0
- package/src/lexer.js +28 -0
- package/src/parser.js +122 -0
- package/src/terminal-parser.js +68 -0
- package/src/terminal-runtime.js +56 -0
|
@@ -60,6 +60,17 @@ const TokenType = {
|
|
|
60
60
|
CODE: 'CODE',
|
|
61
61
|
SLIDER: 'SLIDER',
|
|
62
62
|
SUBMIT: 'SUBMIT',
|
|
63
|
+
COUNTDOWN: 'COUNTDOWN',
|
|
64
|
+
TYPING: 'TYPING',
|
|
65
|
+
CAROUSEL: 'CAROUSEL',
|
|
66
|
+
EMBED: 'EMBED',
|
|
67
|
+
BADGE: 'BADGE',
|
|
68
|
+
AVATAR: 'AVATAR',
|
|
69
|
+
STARS: 'STARS',
|
|
70
|
+
SEARCH: 'SEARCH',
|
|
71
|
+
TOAST: 'TOAST',
|
|
72
|
+
TOOLTIP: 'TOOLTIP',
|
|
73
|
+
DARKMODE: 'DARKMODE',
|
|
63
74
|
|
|
64
75
|
// Keywords - Actions
|
|
65
76
|
SHOW: 'SHOW',
|
|
@@ -147,6 +158,9 @@ const TokenType = {
|
|
|
147
158
|
FUNCTION: 'FUNCTION',
|
|
148
159
|
CALL: 'CALL',
|
|
149
160
|
RETURN: 'RETURN',
|
|
161
|
+
RANDOM: 'RANDOM',
|
|
162
|
+
FETCH: 'FETCH',
|
|
163
|
+
MENU: 'MENU',
|
|
150
164
|
LPAREN: 'LPAREN',
|
|
151
165
|
RPAREN: 'RPAREN',
|
|
152
166
|
COMMA: 'COMMA',
|
|
@@ -202,6 +216,17 @@ const KEYWORDS = {
|
|
|
202
216
|
'code': TokenType.CODE,
|
|
203
217
|
'slider': TokenType.SLIDER,
|
|
204
218
|
'submit': TokenType.SUBMIT,
|
|
219
|
+
'countdown': TokenType.COUNTDOWN,
|
|
220
|
+
'typing': TokenType.TYPING,
|
|
221
|
+
'carousel': TokenType.CAROUSEL,
|
|
222
|
+
'embed': TokenType.EMBED,
|
|
223
|
+
'badge': TokenType.BADGE,
|
|
224
|
+
'avatar': TokenType.AVATAR,
|
|
225
|
+
'stars': TokenType.STARS,
|
|
226
|
+
'search': TokenType.SEARCH,
|
|
227
|
+
'toast': TokenType.TOAST,
|
|
228
|
+
'tooltip': TokenType.TOOLTIP,
|
|
229
|
+
'darkmode': TokenType.DARKMODE,
|
|
205
230
|
'show': TokenType.SHOW,
|
|
206
231
|
'message': TokenType.MESSAGE,
|
|
207
232
|
'hide': TokenType.HIDE,
|
|
@@ -281,6 +306,9 @@ const KEYWORDS = {
|
|
|
281
306
|
'function': TokenType.FUNCTION,
|
|
282
307
|
'call': TokenType.CALL,
|
|
283
308
|
'return': TokenType.RETURN,
|
|
309
|
+
'random': TokenType.RANDOM,
|
|
310
|
+
'fetch': TokenType.FETCH,
|
|
311
|
+
'menu': TokenType.MENU,
|
|
284
312
|
'import': TokenType.IMPORT,
|
|
285
313
|
'at': TokenType.AT,
|
|
286
314
|
'to': TokenType.TO,
|
|
@@ -1149,6 +1177,17 @@ class Parser {
|
|
|
1149
1177
|
case TokenType.CODE: return this.parseCode();
|
|
1150
1178
|
case TokenType.SLIDER: return this.parseSlider();
|
|
1151
1179
|
case TokenType.SUBMIT: return this.parseSubmit();
|
|
1180
|
+
case TokenType.COUNTDOWN: return this.parseCountdown();
|
|
1181
|
+
case TokenType.TYPING: return this.parseTyping();
|
|
1182
|
+
case TokenType.CAROUSEL: return this.parseCarousel();
|
|
1183
|
+
case TokenType.EMBED: return this.parseEmbed();
|
|
1184
|
+
case TokenType.BADGE: return this.parseBadge();
|
|
1185
|
+
case TokenType.AVATAR: return this.parseAvatar();
|
|
1186
|
+
case TokenType.STARS: return this.parseStars();
|
|
1187
|
+
case TokenType.SEARCH: return this.parseSearch();
|
|
1188
|
+
case TokenType.TOAST: return this.parseToast();
|
|
1189
|
+
case TokenType.TOOLTIP: return this.parseTooltip();
|
|
1190
|
+
case TokenType.DARKMODE: return this.parseDarkmode();
|
|
1152
1191
|
case TokenType.SET: return this.parseSetVariable();
|
|
1153
1192
|
case TokenType.USE: return this.parseUse();
|
|
1154
1193
|
case TokenType.REPEAT: return this.parseRepeat();
|
|
@@ -1667,6 +1706,117 @@ class Parser {
|
|
|
1667
1706
|
return { type: 'Submit', label };
|
|
1668
1707
|
}
|
|
1669
1708
|
|
|
1709
|
+
// countdown "2026-12-31" OR countdown "2026-12-31" "Event Name"
|
|
1710
|
+
parseCountdown() {
|
|
1711
|
+
this.expect(TokenType.COUNTDOWN);
|
|
1712
|
+
const date = this.expect(TokenType.STRING).value;
|
|
1713
|
+
let label = null;
|
|
1714
|
+
if (this.peek() && this.peek().type === TokenType.STRING) {
|
|
1715
|
+
label = this.advance().value;
|
|
1716
|
+
}
|
|
1717
|
+
return { type: 'Countdown', date, label };
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// typing "Hello World"
|
|
1721
|
+
parseTyping() {
|
|
1722
|
+
this.expect(TokenType.TYPING);
|
|
1723
|
+
const value = this.expect(TokenType.STRING).value;
|
|
1724
|
+
return { type: 'Typing', value };
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// carousel:
|
|
1728
|
+
parseCarousel() {
|
|
1729
|
+
this.expect(TokenType.CAROUSEL);
|
|
1730
|
+
const node = { type: 'Carousel', children: [] };
|
|
1731
|
+
|
|
1732
|
+
if (this.peek() && this.peek().type === TokenType.COLON) {
|
|
1733
|
+
this.advance();
|
|
1734
|
+
this.skipNewlines();
|
|
1735
|
+
node.children = this.parseBlock();
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
return node;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// embed "url"
|
|
1742
|
+
parseEmbed() {
|
|
1743
|
+
this.expect(TokenType.EMBED);
|
|
1744
|
+
const url = this.expect(TokenType.STRING).value;
|
|
1745
|
+
return { type: 'Embed', url };
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// badge "text"
|
|
1749
|
+
parseBadge() {
|
|
1750
|
+
this.expect(TokenType.BADGE);
|
|
1751
|
+
const value = this.expect(TokenType.STRING).value;
|
|
1752
|
+
const node = { type: 'Badge', value, children: [] };
|
|
1753
|
+
|
|
1754
|
+
if (this.peek() && this.peek().type === TokenType.COLON) {
|
|
1755
|
+
this.advance();
|
|
1756
|
+
this.skipNewlines();
|
|
1757
|
+
node.children = this.parseBlock();
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
return node;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// avatar "url"
|
|
1764
|
+
parseAvatar() {
|
|
1765
|
+
this.expect(TokenType.AVATAR);
|
|
1766
|
+
const src = this.expect(TokenType.STRING).value;
|
|
1767
|
+
const node = { type: 'Avatar', src, children: [] };
|
|
1768
|
+
|
|
1769
|
+
if (this.peek() && this.peek().type === TokenType.COLON) {
|
|
1770
|
+
this.advance();
|
|
1771
|
+
this.skipNewlines();
|
|
1772
|
+
node.children = this.parseBlock();
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
return node;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
// stars 4 OR stars 4 "Rating"
|
|
1779
|
+
parseStars() {
|
|
1780
|
+
this.expect(TokenType.STARS);
|
|
1781
|
+
const value = this.expect(TokenType.NUMBER).value;
|
|
1782
|
+
let label = null;
|
|
1783
|
+
if (this.peek() && this.peek().type === TokenType.STRING) {
|
|
1784
|
+
label = this.advance().value;
|
|
1785
|
+
}
|
|
1786
|
+
return { type: 'Stars', value: parseInt(value, 10), label };
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// search "placeholder"
|
|
1790
|
+
parseSearch() {
|
|
1791
|
+
this.expect(TokenType.SEARCH);
|
|
1792
|
+
const placeholder = this.expect(TokenType.STRING).value;
|
|
1793
|
+
return { type: 'Search', placeholder };
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
// toast "message" (as action inside button)
|
|
1797
|
+
parseToast() {
|
|
1798
|
+
this.expect(TokenType.TOAST);
|
|
1799
|
+
const value = this.expect(TokenType.STRING).value;
|
|
1800
|
+
return { type: 'Toast', value };
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// tooltip "text" "hover text"
|
|
1804
|
+
parseTooltip() {
|
|
1805
|
+
this.expect(TokenType.TOOLTIP);
|
|
1806
|
+
const text = this.expect(TokenType.STRING).value;
|
|
1807
|
+
let tip = '';
|
|
1808
|
+
if (this.peek() && this.peek().type === TokenType.STRING) {
|
|
1809
|
+
tip = this.advance().value;
|
|
1810
|
+
}
|
|
1811
|
+
return { type: 'Tooltip', text, tip };
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
// darkmode
|
|
1815
|
+
parseDarkmode() {
|
|
1816
|
+
this.expect(TokenType.DARKMODE);
|
|
1817
|
+
return { type: 'Darkmode' };
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1670
1820
|
// button "text":
|
|
1671
1821
|
parseButton() {
|
|
1672
1822
|
this.expect(TokenType.BUTTON);
|
|
@@ -2070,6 +2220,14 @@ class Compiler {
|
|
|
2070
2220
|
this.hasAccordion = false;
|
|
2071
2221
|
this.hasProgress = false;
|
|
2072
2222
|
this.hasSlider = false;
|
|
2223
|
+
this.hasCountdown = false;
|
|
2224
|
+
this.hasTyping = false;
|
|
2225
|
+
this.hasCarousel = false;
|
|
2226
|
+
this.hasToast = false;
|
|
2227
|
+
this.hasSearch = false;
|
|
2228
|
+
this.hasTooltip = false;
|
|
2229
|
+
this.hasDarkmode = false;
|
|
2230
|
+
this.hasStars = false;
|
|
2073
2231
|
this.hoverStyles = [];
|
|
2074
2232
|
this.variables = {};
|
|
2075
2233
|
this.templates = {};
|
|
@@ -2133,6 +2291,14 @@ class Compiler {
|
|
|
2133
2291
|
const accordionCSS = this.hasAccordion ? this.buildAccordionCSS() : '';
|
|
2134
2292
|
const progressCSS = this.hasProgress ? this.buildProgressCSS() : '';
|
|
2135
2293
|
const sliderCSS = this.hasSlider ? this.buildSliderCSS() : '';
|
|
2294
|
+
const countdownCSS = this.hasCountdown ? this.buildCountdownCSS() : '';
|
|
2295
|
+
const typingCSS = this.hasTyping ? this.buildTypingCSS() : '';
|
|
2296
|
+
const carouselCSS = this.hasCarousel ? this.buildCarouselCSS() : '';
|
|
2297
|
+
const toastCSS = this.hasToast ? this.buildToastCSS() : '';
|
|
2298
|
+
const searchCSS = this.hasSearch ? this.buildSearchCSS() : '';
|
|
2299
|
+
const tooltipCSS = this.hasTooltip ? this.buildTooltipCSS() : '';
|
|
2300
|
+
const darkmodeCSS = this.hasDarkmode ? this.buildDarkmodeCSS() : '';
|
|
2301
|
+
const starsCSS = this.hasStars ? this.buildStarsCSS() : '';
|
|
2136
2302
|
const hoverCSS = this.hoverStyles.join('\n');
|
|
2137
2303
|
const themeCSS = this.activeTheme ? `\n /* Theme: ${this.activeTheme.name} */\n${generateThemeCSS(this.activeTheme)}` : '';
|
|
2138
2304
|
|
|
@@ -2393,7 +2559,7 @@ class Compiler {
|
|
|
2393
2559
|
opacity: 0.7;
|
|
2394
2560
|
border-top: 1px solid rgba(128,128,128,0.2);
|
|
2395
2561
|
}
|
|
2396
|
-
${navbarCSS}${modalCSS}${tabsCSS}${accordionCSS}${progressCSS}${sliderCSS}${animationCSS}${hoverCSS}${themeCSS}
|
|
2562
|
+
${navbarCSS}${modalCSS}${tabsCSS}${accordionCSS}${progressCSS}${sliderCSS}${countdownCSS}${typingCSS}${carouselCSS}${toastCSS}${searchCSS}${tooltipCSS}${darkmodeCSS}${starsCSS}${animationCSS}${hoverCSS}${themeCSS}
|
|
2397
2563
|
</style>
|
|
2398
2564
|
</head>
|
|
2399
2565
|
<body>
|
|
@@ -2542,6 +2708,92 @@ ${scripts}
|
|
|
2542
2708
|
`;
|
|
2543
2709
|
}
|
|
2544
2710
|
|
|
2711
|
+
buildCountdownCSS() {
|
|
2712
|
+
const accent = this.buttonColorCSS || '#4A90D9';
|
|
2713
|
+
return `
|
|
2714
|
+
.clou-countdown { display: flex; gap: 16px; justify-content: center; margin: 16px 0; flex-wrap: wrap; }
|
|
2715
|
+
.clou-countdown-label { width: 100%; text-align: center; font-size: 14px; opacity: 0.8; margin-bottom: 8px; }
|
|
2716
|
+
.clou-countdown-unit { display: flex; flex-direction: column; align-items: center; padding: 16px 20px; background: rgba(128,128,128,0.1); border-radius: 12px; min-width: 80px; }
|
|
2717
|
+
.clou-countdown-num { font-size: 2em; font-weight: 700; color: ${accent}; }
|
|
2718
|
+
.clou-countdown-text { font-size: 12px; opacity: 0.7; text-transform: uppercase; letter-spacing: 1px; }
|
|
2719
|
+
`;
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
buildTypingCSS() {
|
|
2723
|
+
return `
|
|
2724
|
+
.clou-typing { overflow: hidden; border-right: 3px solid currentColor; white-space: nowrap; margin: 16px 0; font-size: 1.5em; font-weight: 600; animation: clou-typing-text 3s steps(40) 1s forwards, clou-typing-cursor 0.75s step-end infinite; width: 0; }
|
|
2725
|
+
@keyframes clou-typing-text { from { width: 0; } to { width: 100%; } }
|
|
2726
|
+
@keyframes clou-typing-cursor { 50% { border-color: transparent; } }
|
|
2727
|
+
`;
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
buildCarouselCSS() {
|
|
2731
|
+
const accent = this.buttonColorCSS || '#4A90D9';
|
|
2732
|
+
return `
|
|
2733
|
+
.clou-carousel { position: relative; overflow: hidden; border-radius: 12px; margin: 16px 0; }
|
|
2734
|
+
.clou-carousel-track { display: flex; transition: transform 0.5s ease; }
|
|
2735
|
+
.clou-carousel-slide { min-width: 100%; }
|
|
2736
|
+
.clou-carousel-slide img { width: 100%; height: auto; display: block; }
|
|
2737
|
+
.clou-carousel-btn { position: absolute; top: 50%; transform: translateY(-50%); background: rgba(0,0,0,0.5); color: white; border: none; padding: 12px 16px; cursor: pointer; font-size: 18px; border-radius: 50%; z-index: 10; transition: background 0.2s; }
|
|
2738
|
+
.clou-carousel-btn:hover { background: ${accent}; transform: translateY(-50%) scale(1.05); box-shadow: none; }
|
|
2739
|
+
.clou-carousel-prev { left: 12px; }
|
|
2740
|
+
.clou-carousel-next { right: 12px; }
|
|
2741
|
+
.clou-carousel-dots { display: flex; justify-content: center; gap: 8px; padding: 12px 0; }
|
|
2742
|
+
.clou-carousel-dot { width: 10px; height: 10px; border-radius: 50%; background: rgba(128,128,128,0.4); border: none; cursor: pointer; padding: 0; transition: background 0.2s; }
|
|
2743
|
+
.clou-carousel-dot:hover { transform: none; box-shadow: none; }
|
|
2744
|
+
.clou-carousel-dot.active { background: ${accent}; }
|
|
2745
|
+
`;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
buildToastCSS() {
|
|
2749
|
+
return `
|
|
2750
|
+
.clou-toast-container { position: fixed; top: 20px; right: 20px; z-index: 99999; display: flex; flex-direction: column; gap: 8px; }
|
|
2751
|
+
.clou-toast { padding: 14px 24px; background: #333; color: white; border-radius: 8px; font-size: 15px; box-shadow: 0 4px 20px rgba(0,0,0,0.3); animation: clou-toast-in 0.3s ease, clou-toast-out 0.3s ease 2.7s forwards; }
|
|
2752
|
+
@keyframes clou-toast-in { from { opacity: 0; transform: translateX(40px); } to { opacity: 1; transform: translateX(0); } }
|
|
2753
|
+
@keyframes clou-toast-out { from { opacity: 1; } to { opacity: 0; transform: translateX(40px); } }
|
|
2754
|
+
`;
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
buildSearchCSS() {
|
|
2758
|
+
const accent = this.buttonColorCSS || '#4A90D9';
|
|
2759
|
+
return `
|
|
2760
|
+
.clou-search { position: relative; margin: 12px 0; max-width: 500px; }
|
|
2761
|
+
.clou-search input { width: 100%; padding: 14px 20px 14px 48px; font-size: 16px; border: 2px solid rgba(128,128,128,0.2); border-radius: 50px; outline: none; background: rgba(255,255,255,0.1); color: inherit; transition: border-color 0.2s, box-shadow 0.2s; }
|
|
2762
|
+
.clou-search input:focus { border-color: ${accent}; box-shadow: 0 0 0 3px ${accent}33; }
|
|
2763
|
+
.clou-search-icon { position: absolute; left: 18px; top: 50%; transform: translateY(-50%); font-size: 18px; opacity: 0.5; pointer-events: none; }
|
|
2764
|
+
`;
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
buildTooltipCSS() {
|
|
2768
|
+
return `
|
|
2769
|
+
.clou-tooltip { position: relative; display: inline-block; cursor: help; border-bottom: 1px dotted currentColor; }
|
|
2770
|
+
.clou-tooltip-text { visibility: hidden; background: #333; color: white; text-align: center; padding: 8px 14px; border-radius: 6px; font-size: 13px; position: absolute; z-index: 1000; bottom: 125%; left: 50%; transform: translateX(-50%); white-space: nowrap; opacity: 0; transition: opacity 0.2s; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
|
|
2771
|
+
.clou-tooltip-text::after { content: ""; position: absolute; top: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: #333 transparent transparent transparent; }
|
|
2772
|
+
.clou-tooltip:hover .clou-tooltip-text { visibility: visible; opacity: 1; }
|
|
2773
|
+
`;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
buildDarkmodeCSS() {
|
|
2777
|
+
return `
|
|
2778
|
+
.clou-darkmode-toggle { position: fixed; top: 16px; right: 16px; z-index: 9999; width: 48px; height: 48px; border-radius: 50%; border: 2px solid rgba(128,128,128,0.3); background: rgba(128,128,128,0.1); backdrop-filter: blur(8px); cursor: pointer; font-size: 22px; display: flex; align-items: center; justify-content: center; transition: background 0.3s, transform 0.2s; padding: 0; }
|
|
2779
|
+
.clou-darkmode-toggle:hover { background: rgba(128,128,128,0.2); transform: scale(1.1); box-shadow: none; }
|
|
2780
|
+
body.clou-light { background: #ffffff !important; color: #333333 !important; }
|
|
2781
|
+
body.clou-light .clou-card { background: rgba(0,0,0,0.05); }
|
|
2782
|
+
body.clou-light .clou-navbar { background: rgba(255,255,255,0.8); }
|
|
2783
|
+
body.clou-light input, body.clou-light textarea { background: rgba(0,0,0,0.05); border-color: rgba(0,0,0,0.15); color: #333; }
|
|
2784
|
+
`;
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
buildStarsCSS() {
|
|
2788
|
+
return `
|
|
2789
|
+
.clou-stars { display: inline-flex; gap: 4px; margin: 8px 0; align-items: center; }
|
|
2790
|
+
.clou-star { font-size: 1.4em; }
|
|
2791
|
+
.clou-star.filled { color: #FFD700; }
|
|
2792
|
+
.clou-star.empty { color: rgba(128,128,128,0.3); }
|
|
2793
|
+
.clou-stars-label { margin-left: 8px; font-size: 14px; opacity: 0.7; }
|
|
2794
|
+
`;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2545
2797
|
buildAnimationCSS() {
|
|
2546
2798
|
let css = '';
|
|
2547
2799
|
|
|
@@ -2596,6 +2848,52 @@ ${scripts}
|
|
|
2596
2848
|
`;
|
|
2597
2849
|
}
|
|
2598
2850
|
|
|
2851
|
+
if (this.animations.has('pulse')) {
|
|
2852
|
+
css += `
|
|
2853
|
+
@keyframes clou-pulse {
|
|
2854
|
+
0%, 100% { transform: scale(1); opacity: 1; }
|
|
2855
|
+
50% { transform: scale(1.05); opacity: 0.8; }
|
|
2856
|
+
}
|
|
2857
|
+
.clou-animate-pulse { animation: clou-pulse 2s ease infinite; }
|
|
2858
|
+
`;
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
if (this.animations.has('shake')) {
|
|
2862
|
+
css += `
|
|
2863
|
+
@keyframes clou-shake {
|
|
2864
|
+
0%, 100% { transform: translateX(0); }
|
|
2865
|
+
20% { transform: translateX(-8px); }
|
|
2866
|
+
40% { transform: translateX(8px); }
|
|
2867
|
+
60% { transform: translateX(-4px); }
|
|
2868
|
+
80% { transform: translateX(4px); }
|
|
2869
|
+
}
|
|
2870
|
+
.clou-animate-shake { animation: clou-shake 0.6s ease; }
|
|
2871
|
+
`;
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
if (this.animations.has('float')) {
|
|
2875
|
+
css += `
|
|
2876
|
+
@keyframes clou-float {
|
|
2877
|
+
0%, 100% { transform: translateY(0); }
|
|
2878
|
+
50% { transform: translateY(-10px); }
|
|
2879
|
+
}
|
|
2880
|
+
.clou-animate-float { animation: clou-float 3s ease-in-out infinite; }
|
|
2881
|
+
`;
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
if (this.animations.has('typewrite')) {
|
|
2885
|
+
css += `
|
|
2886
|
+
@keyframes clou-typewrite {
|
|
2887
|
+
from { width: 0; }
|
|
2888
|
+
to { width: 100%; }
|
|
2889
|
+
}
|
|
2890
|
+
@keyframes clou-blink {
|
|
2891
|
+
50% { border-color: transparent; }
|
|
2892
|
+
}
|
|
2893
|
+
.clou-animate-typewrite { overflow: hidden; white-space: nowrap; border-right: 3px solid currentColor; animation: clou-typewrite 3s steps(40) forwards, clou-blink 0.75s step-end infinite; }
|
|
2894
|
+
`;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2599
2897
|
return css;
|
|
2600
2898
|
}
|
|
2601
2899
|
|
|
@@ -2889,6 +3187,137 @@ ${scripts}
|
|
|
2889
3187
|
case 'Submit':
|
|
2890
3188
|
return ` <button type="submit">${this.escapeHtml(this.interpolate(node.label))}</button>`;
|
|
2891
3189
|
|
|
3190
|
+
case 'Countdown': {
|
|
3191
|
+
this.hasCountdown = true;
|
|
3192
|
+
const cdId = this.uid();
|
|
3193
|
+
const label = node.label ? `<div class="clou-countdown-label">${this.escapeHtml(this.interpolate(node.label))}</div>` : '';
|
|
3194
|
+
this.scripts.push(
|
|
3195
|
+
`(function() {\n` +
|
|
3196
|
+
` var target = new Date(${JSON.stringify(node.date)}).getTime();\n` +
|
|
3197
|
+
` var el = document.getElementById('${cdId}');\n` +
|
|
3198
|
+
` function update() {\n` +
|
|
3199
|
+
` var now = Date.now();\n` +
|
|
3200
|
+
` var diff = Math.max(0, target - now);\n` +
|
|
3201
|
+
` var d = Math.floor(diff / 86400000);\n` +
|
|
3202
|
+
` var h = Math.floor((diff % 86400000) / 3600000);\n` +
|
|
3203
|
+
` var m = Math.floor((diff % 3600000) / 60000);\n` +
|
|
3204
|
+
` var s = Math.floor((diff % 60000) / 1000);\n` +
|
|
3205
|
+
` el.querySelector('.cd-d').textContent = d;\n` +
|
|
3206
|
+
` el.querySelector('.cd-h').textContent = h;\n` +
|
|
3207
|
+
` el.querySelector('.cd-m').textContent = m;\n` +
|
|
3208
|
+
` el.querySelector('.cd-s').textContent = s;\n` +
|
|
3209
|
+
` if (diff > 0) setTimeout(update, 1000);\n` +
|
|
3210
|
+
` }\n` +
|
|
3211
|
+
` update();\n` +
|
|
3212
|
+
`})();`
|
|
3213
|
+
);
|
|
3214
|
+
return ` <div class="clou-countdown" id="${cdId}">\n ${label}\n <div class="clou-countdown-unit"><span class="clou-countdown-num cd-d">0</span><span class="clou-countdown-text">Days</span></div>\n <div class="clou-countdown-unit"><span class="clou-countdown-num cd-h">0</span><span class="clou-countdown-text">Hours</span></div>\n <div class="clou-countdown-unit"><span class="clou-countdown-num cd-m">0</span><span class="clou-countdown-text">Min</span></div>\n <div class="clou-countdown-unit"><span class="clou-countdown-num cd-s">0</span><span class="clou-countdown-text">Sec</span></div>\n </div>`;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
case 'Typing': {
|
|
3218
|
+
this.hasTyping = true;
|
|
3219
|
+
return ` <div class="clou-typing">${this.escapeHtml(this.interpolate(node.value))}</div>`;
|
|
3220
|
+
}
|
|
3221
|
+
|
|
3222
|
+
case 'Carousel': {
|
|
3223
|
+
this.hasCarousel = true;
|
|
3224
|
+
const carId = this.uid();
|
|
3225
|
+
const slides = (node.children || []).filter(c => c.type === 'Image' || c.type === 'Card');
|
|
3226
|
+
let slidesHtml = '';
|
|
3227
|
+
slides.forEach((slide, i) => {
|
|
3228
|
+
slidesHtml += `\n <div class="clou-carousel-slide">${this.compileNode(slide)}</div>`;
|
|
3229
|
+
});
|
|
3230
|
+
let dotsHtml = '';
|
|
3231
|
+
slides.forEach((_, i) => {
|
|
3232
|
+
dotsHtml += `<button class="clou-carousel-dot${i === 0 ? ' active' : ''}" data-idx="${i}"></button>`;
|
|
3233
|
+
});
|
|
3234
|
+
this.scripts.push(
|
|
3235
|
+
`(function() {\n` +
|
|
3236
|
+
` var el = document.getElementById('${carId}');\n` +
|
|
3237
|
+
` var track = el.querySelector('.clou-carousel-track');\n` +
|
|
3238
|
+
` var dots = el.querySelectorAll('.clou-carousel-dot');\n` +
|
|
3239
|
+
` var total = ${slides.length};\n` +
|
|
3240
|
+
` var idx = 0;\n` +
|
|
3241
|
+
` function go(n) { idx = (n + total) % total; track.style.transform = 'translateX(-' + (idx * 100) + '%)'; dots.forEach(function(d,i) { d.classList.toggle('active', i === idx); }); }\n` +
|
|
3242
|
+
` el.querySelector('.clou-carousel-prev').onclick = function() { go(idx - 1); };\n` +
|
|
3243
|
+
` el.querySelector('.clou-carousel-next').onclick = function() { go(idx + 1); };\n` +
|
|
3244
|
+
` dots.forEach(function(d) { d.onclick = function() { go(parseInt(d.dataset.idx)); }; });\n` +
|
|
3245
|
+
`})();`
|
|
3246
|
+
);
|
|
3247
|
+
return ` <div class="clou-carousel" id="${carId}">\n <div class="clou-carousel-track">${slidesHtml}\n </div>\n <button class="clou-carousel-btn clou-carousel-prev">❮</button>\n <button class="clou-carousel-btn clou-carousel-next">❯</button>\n <div class="clou-carousel-dots">${dotsHtml}</div>\n </div>`;
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
case 'Embed': {
|
|
3251
|
+
const url = this.interpolate(node.url);
|
|
3252
|
+
// Convert YouTube watch URLs to embed
|
|
3253
|
+
let embedUrl = url;
|
|
3254
|
+
if (url.includes('youtube.com/watch')) {
|
|
3255
|
+
const vid = url.split('v=')[1];
|
|
3256
|
+
if (vid) embedUrl = 'https://www.youtube.com/embed/' + vid.split('&')[0];
|
|
3257
|
+
} else if (url.includes('youtu.be/')) {
|
|
3258
|
+
embedUrl = 'https://www.youtube.com/embed/' + url.split('youtu.be/')[1];
|
|
3259
|
+
}
|
|
3260
|
+
return ` <iframe src="${this.escapeHtml(embedUrl)}" style="width: 100%; height: 400px; border: none; border-radius: 12px; margin: 12px 0;" allowfullscreen></iframe>`;
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
case 'Badge': {
|
|
3264
|
+
const style = this.extractInlineStyles(node.children || []);
|
|
3265
|
+
const accent = this.buttonColorCSS || '#4A90D9';
|
|
3266
|
+
const styleStr = style ? style + '; ' : '';
|
|
3267
|
+
return ` <span style="${styleStr}display: inline-block; padding: 4px 12px; font-size: 12px; font-weight: 600; border-radius: 20px; background: ${accent}; color: white; text-transform: uppercase; letter-spacing: 0.5px;">${this.escapeHtml(this.interpolate(node.value))}</span>`;
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
case 'Avatar': {
|
|
3271
|
+
const style = this.extractInlineStyles(node.children || []);
|
|
3272
|
+
const styleStr = style ? style + '; ' : '';
|
|
3273
|
+
return ` <img src="${this.escapeHtml(this.interpolate(node.src))}" alt="avatar" style="${styleStr}width: 80px; height: 80px; border-radius: 50%; object-fit: cover; margin: 8px 0;">`;
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
case 'Stars': {
|
|
3277
|
+
this.hasStars = true;
|
|
3278
|
+
const count = Math.min(Math.max(node.value, 0), 5);
|
|
3279
|
+
let starsHtml = '';
|
|
3280
|
+
for (let i = 0; i < 5; i++) {
|
|
3281
|
+
starsHtml += `<span class="clou-star ${i < count ? 'filled' : 'empty'}">${i < count ? '\u2605' : '\u2606'}</span>`;
|
|
3282
|
+
}
|
|
3283
|
+
const label = node.label ? `<span class="clou-stars-label">${this.escapeHtml(this.interpolate(node.label))}</span>` : '';
|
|
3284
|
+
return ` <div class="clou-stars">${starsHtml}${label}</div>`;
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
case 'Search': {
|
|
3288
|
+
this.hasSearch = true;
|
|
3289
|
+
const id = this.uid();
|
|
3290
|
+
return ` <div class="clou-search">\n <span class="clou-search-icon">\u{1F50D}</span>\n <input id="${id}" type="text" placeholder="${this.escapeHtml(this.interpolate(node.placeholder))}">\n </div>`;
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
case 'Toast': {
|
|
3294
|
+
// Toast as action (inside button)
|
|
3295
|
+
this.hasToast = true;
|
|
3296
|
+
return '';
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
case 'Tooltip': {
|
|
3300
|
+
this.hasTooltip = true;
|
|
3301
|
+
return ` <span class="clou-tooltip">${this.escapeHtml(this.interpolate(node.text))}<span class="clou-tooltip-text">${this.escapeHtml(this.interpolate(node.tip))}</span></span>`;
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
case 'Darkmode': {
|
|
3305
|
+
this.hasDarkmode = true;
|
|
3306
|
+
const dmId = this.uid();
|
|
3307
|
+
this.scripts.push(
|
|
3308
|
+
`(function() {\n` +
|
|
3309
|
+
` var btn = document.getElementById('${dmId}');\n` +
|
|
3310
|
+
` var isDark = true;\n` +
|
|
3311
|
+
` btn.onclick = function() {\n` +
|
|
3312
|
+
` isDark = !isDark;\n` +
|
|
3313
|
+
` document.body.classList.toggle('clou-light', !isDark);\n` +
|
|
3314
|
+
` btn.textContent = isDark ? '\\u263E' : '\\u2600';\n` +
|
|
3315
|
+
` };\n` +
|
|
3316
|
+
`})();`
|
|
3317
|
+
);
|
|
3318
|
+
return ` <button class="clou-darkmode-toggle" id="${dmId}">\u263E</button>`;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
2892
3321
|
case 'Icon':
|
|
2893
3322
|
return ` <span class="clou-icon">${node.value}</span>`;
|
|
2894
3323
|
|
|
@@ -2995,6 +3424,9 @@ ${scripts}
|
|
|
2995
3424
|
return `document.getElementById(${JSON.stringify(node.target)} + '-overlay').classList.remove('active');`;
|
|
2996
3425
|
case 'GoTo':
|
|
2997
3426
|
return `window.location.href = ${JSON.stringify(node.url)};`;
|
|
3427
|
+
case 'Toast':
|
|
3428
|
+
this.hasToast = true;
|
|
3429
|
+
return `(function() { var c = document.querySelector('.clou-toast-container'); if (!c) { c = document.createElement('div'); c.className = 'clou-toast-container'; document.body.appendChild(c); } var t = document.createElement('div'); t.className = 'clou-toast'; t.textContent = ${JSON.stringify(this.interpolate(node.value))}; c.appendChild(t); setTimeout(function() { t.remove(); }, 3000); })();`;
|
|
2998
3430
|
default:
|
|
2999
3431
|
return '';
|
|
3000
3432
|
}
|
|
@@ -3041,7 +3473,7 @@ ${scripts}
|
|
|
3041
3473
|
}
|
|
3042
3474
|
|
|
3043
3475
|
isAction(node) {
|
|
3044
|
-
return ['ShowMessage', 'Show', 'Hide', 'Toggle', 'GoTo', 'Open', 'Close'].includes(node.type);
|
|
3476
|
+
return ['ShowMessage', 'Show', 'Hide', 'Toggle', 'GoTo', 'Open', 'Close', 'Toast'].includes(node.type);
|
|
3045
3477
|
}
|
|
3046
3478
|
|
|
3047
3479
|
// Filter only action nodes
|
package/playground/index.html
CHANGED
|
@@ -228,7 +228,7 @@ body {
|
|
|
228
228
|
<body>
|
|
229
229
|
|
|
230
230
|
<div class="header">
|
|
231
|
-
<div class="logo">Clou Playground <span>v0.
|
|
231
|
+
<div class="logo">Clou Playground <span>v0.4</span></div>
|
|
232
232
|
<div class="header-actions">
|
|
233
233
|
<select class="theme-select" id="themeSelect">
|
|
234
234
|
<option value="">No Theme</option>
|
|
@@ -252,7 +252,7 @@ body {
|
|
|
252
252
|
<div class="resize-handle" id="resizeHandle"></div>
|
|
253
253
|
<div class="preview-panel">
|
|
254
254
|
<div class="panel-header">Preview</div>
|
|
255
|
-
<iframe id="preview" sandbox="allow-scripts allow-modals"></iframe>
|
|
255
|
+
<iframe id="preview" sandbox="allow-scripts allow-modals allow-same-origin"></iframe>
|
|
256
256
|
</div>
|
|
257
257
|
</div>
|
|
258
258
|
|
|
@@ -660,11 +660,8 @@ page "{name}'s Portfolio":
|
|
|
660
660
|
const result = Clou.buildClou(source);
|
|
661
661
|
lastHtml = result.html;
|
|
662
662
|
|
|
663
|
-
// Write to iframe
|
|
664
|
-
|
|
665
|
-
doc.open();
|
|
666
|
-
doc.write(lastHtml);
|
|
667
|
-
doc.close();
|
|
663
|
+
// Write to iframe using srcdoc (avoids cross-origin issues)
|
|
664
|
+
preview.srcdoc = lastHtml;
|
|
668
665
|
|
|
669
666
|
status.textContent = 'Compiled';
|
|
670
667
|
status.className = 'status status-ok';
|