structscript 1.4.1 → 1.5.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/lib/editor.html +1079 -500
- package/lib/interpreter.js +326 -243
- package/package.json +1 -1
package/lib/editor.html
CHANGED
|
@@ -714,147 +714,584 @@ code { font-family: 'DM Mono', monospace; font-size: 11px; color: var(--accent);
|
|
|
714
714
|
</div><!-- playground -->
|
|
715
715
|
</div><!-- page-playground -->
|
|
716
716
|
|
|
717
|
-
<!-- DOCS PAGE -->
|
|
718
|
-
<div id="page-docs" class="page">
|
|
719
|
-
<div class="docs-page">
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
<
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
<div class="docs-card"><h4
|
|
728
|
-
<div class="docs-card"><h4
|
|
729
|
-
<div class="docs-card"><h4
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
<
|
|
735
|
-
|
|
736
|
-
<tr><
|
|
737
|
-
<tr><td><code>
|
|
738
|
-
<tr><td><code>
|
|
739
|
-
<tr><td><code>set
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
<tr><
|
|
749
|
-
<tr><td>
|
|
750
|
-
<tr><td>
|
|
751
|
-
<tr><td>
|
|
752
|
-
<tr><td>
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
<tr><
|
|
763
|
-
<tr><td>
|
|
764
|
-
|
|
765
|
-
</
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
<
|
|
771
|
-
|
|
772
|
-
<
|
|
773
|
-
|
|
774
|
-
<span class="kw">
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
<span class="kw">
|
|
778
|
-
<span class="kw">
|
|
779
|
-
|
|
780
|
-
<span class="kw">
|
|
781
|
-
<span class="kw">
|
|
782
|
-
|
|
783
|
-
<span class="kw">
|
|
784
|
-
<span class="kw">say</span>
|
|
785
|
-
|
|
786
|
-
<span class="kw">
|
|
787
|
-
<span class="kw">say</span>
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
<
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
<
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
<span class="kw">define</span> <span class="fn">
|
|
797
|
-
<span class="kw">
|
|
798
|
-
|
|
799
|
-
<span class="
|
|
800
|
-
<span class="kw">
|
|
801
|
-
<span class="kw">return</span>
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
<
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
<span class="kw">
|
|
811
|
-
<span class="kw">
|
|
812
|
-
<span class="kw">
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
<span class="
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
<span class="kw">
|
|
820
|
-
|
|
821
|
-
</
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
<tr><
|
|
841
|
-
<tr><td><code>
|
|
842
|
-
<tr><td><code>
|
|
843
|
-
<tr><td><code>
|
|
844
|
-
<tr><td><code>
|
|
845
|
-
<tr><td><code>
|
|
846
|
-
<tr><td><code>
|
|
847
|
-
<tr><td><code>
|
|
848
|
-
<tr><td><code>
|
|
849
|
-
<tr><td><code>
|
|
850
|
-
<tr><td><code>
|
|
851
|
-
<tr><td><code>
|
|
852
|
-
<tr><td><code>
|
|
853
|
-
|
|
854
|
-
|
|
717
|
+
<!-- DOCS PAGE -->
|
|
718
|
+
<div id="page-docs" class="page">
|
|
719
|
+
<div class="docs-page">
|
|
720
|
+
|
|
721
|
+
<div class="docs-hero">
|
|
722
|
+
<h2><em>StructScript</em> v1.4<br>Language Reference</h2>
|
|
723
|
+
<p>A clean, readable language with Python-style indentation. Script, process data, and build complete web pages — all in one syntax.</p>
|
|
724
|
+
</div>
|
|
725
|
+
|
|
726
|
+
<div class="docs-grid">
|
|
727
|
+
<div class="docs-card"><h4>🧱 Structured</h4><p>Indentation-based blocks. No braces. Reads like English.</p></div>
|
|
728
|
+
<div class="docs-card"><h4>📖 Readable</h4><p><code>say</code>, <code>define</code>, <code>count from to</code>, <code>repeat times</code> — it just makes sense.</p></div>
|
|
729
|
+
<div class="docs-card"><h4>🌐 Web-Ready</h4><p>Every HTML tag, every CSS property, every browser event. <code>page</code> → <code>add</code> → done.</p></div>
|
|
730
|
+
<div class="docs-card"><h4>⚡ Capable</h4><p>Classes, structs, error handling, 40+ built-ins, file imports.</p></div>
|
|
731
|
+
</div>
|
|
732
|
+
|
|
733
|
+
<div class="docs-section">
|
|
734
|
+
<h3>Variables & Constants</h3>
|
|
735
|
+
<table class="docs-table">
|
|
736
|
+
<tr><th>Syntax</th><th>Example</th><th>Notes</th></tr>
|
|
737
|
+
<tr><td><code>let name = value</code></td><td><code>let x = 42</code></td><td>Mutable variable</td></tr>
|
|
738
|
+
<tr><td><code>const name = value</code></td><td><code>const PI = 3.14159</code></td><td>Immutable constant</td></tr>
|
|
739
|
+
<tr><td><code>set name = value</code></td><td><code>set x = x + 1</code></td><td>Reassign</td></tr>
|
|
740
|
+
<tr><td><code>set name += val</code></td><td><code>set x += 5</code></td><td>+= -= *= /= %= **= //=</td></tr>
|
|
741
|
+
<tr><td><code>set obj.field = val</code></td><td><code>set p.age = 30</code></td><td>Object field</td></tr>
|
|
742
|
+
</table>
|
|
743
|
+
</div>
|
|
744
|
+
|
|
745
|
+
<div class="docs-section">
|
|
746
|
+
<h3>Types</h3>
|
|
747
|
+
<table class="docs-table">
|
|
748
|
+
<tr><th>Type</th><th>Examples</th><th>Notes</th></tr>
|
|
749
|
+
<tr><td>Number</td><td><code>42</code>, <code>3.14</code>, <code>-7</code></td><td>Integer or float</td></tr>
|
|
750
|
+
<tr><td>String</td><td><code>"hello {name}!"</code></td><td>Interpolation with { }</td></tr>
|
|
751
|
+
<tr><td>Boolean</td><td><code>true</code>, <code>false</code></td><td></td></tr>
|
|
752
|
+
<tr><td>Nothing</td><td><code>nothing</code></td><td>Null/None equivalent</td></tr>
|
|
753
|
+
<tr><td>List</td><td><code>[1, 2, 3]</code></td><td>Ordered, mutable</td></tr>
|
|
754
|
+
<tr><td>Struct</td><td><code>Point()</code></td><td>Custom data type</td></tr>
|
|
755
|
+
<tr><td>Class</td><td><code>Dog()</code></td><td>Methods + <code>self</code></td></tr>
|
|
756
|
+
</table>
|
|
757
|
+
</div>
|
|
758
|
+
|
|
759
|
+
<div class="docs-section">
|
|
760
|
+
<h3>Operators</h3>
|
|
761
|
+
<table class="docs-table">
|
|
762
|
+
<tr><th>Category</th><th>Operators</th><th>Notes</th></tr>
|
|
763
|
+
<tr><td>Arithmetic</td><td><code>+ - * / % ** //</code></td><td>// = floor division</td></tr>
|
|
764
|
+
<tr><td>Comparison</td><td><code>== != > < >= <=</code></td><td></td></tr>
|
|
765
|
+
<tr><td>Logical</td><td><code>and or not</code></td><td></td></tr>
|
|
766
|
+
<tr><td>Ternary</td><td><code>a if cond else b</code></td><td>Inline conditional</td></tr>
|
|
767
|
+
</table>
|
|
768
|
+
</div>
|
|
769
|
+
|
|
770
|
+
<div class="docs-section">
|
|
771
|
+
<h3>Control Flow</h3>
|
|
772
|
+
<div class="docs-code">
|
|
773
|
+
<span class="kw">if</span> x > <span class="num">0</span>:
|
|
774
|
+
<span class="kw">say</span> <span class="str">"positive"</span>
|
|
775
|
+
<span class="kw">else if</span> x < <span class="num">0</span>:
|
|
776
|
+
<span class="kw">say</span> <span class="str">"negative"</span>
|
|
777
|
+
<span class="kw">else</span>:
|
|
778
|
+
<span class="kw">say</span> <span class="str">"zero"</span>
|
|
779
|
+
|
|
780
|
+
<span class="kw">while</span> x < <span class="num">100</span>:
|
|
781
|
+
<span class="kw">set</span> x *= <span class="num">2</span>
|
|
782
|
+
|
|
783
|
+
<span class="kw">repeat</span> <span class="num">5</span> <span class="kw">times</span>:
|
|
784
|
+
<span class="kw">say</span> <span class="str">"hello"</span>
|
|
785
|
+
|
|
786
|
+
<span class="kw">count</span> i <span class="kw">from</span> <span class="num">1</span> <span class="kw">to</span> <span class="num">10</span> <span class="kw">step</span> <span class="num">2</span>:
|
|
787
|
+
<span class="kw">say</span> i
|
|
788
|
+
|
|
789
|
+
<span class="kw">for each</span> item <span class="kw">in</span> myList:
|
|
790
|
+
<span class="kw">say</span> item</div>
|
|
791
|
+
</div>
|
|
792
|
+
|
|
793
|
+
<div class="docs-section">
|
|
794
|
+
<h3>Functions</h3>
|
|
795
|
+
<div class="docs-code">
|
|
796
|
+
<span class="kw">define</span> <span class="fn">greet</span>(name, msg = <span class="str">"Hello"</span>):
|
|
797
|
+
<span class="kw">say</span> <span class="str">"{msg}, {name}!"</span>
|
|
798
|
+
|
|
799
|
+
<span class="kw">define</span> <span class="fn">fib</span>(n):
|
|
800
|
+
<span class="kw">if</span> n <= <span class="num">1</span>: <span class="kw">return</span> n
|
|
801
|
+
<span class="kw">return</span> <span class="fn">fib</span>(n - <span class="num">1</span>) + <span class="fn">fib</span>(n - <span class="num">2</span>)</div>
|
|
802
|
+
</div>
|
|
803
|
+
|
|
804
|
+
<div class="docs-section">
|
|
805
|
+
<h3>Classes & Structs</h3>
|
|
806
|
+
<div class="docs-code">
|
|
807
|
+
<span class="kw">class</span> Animal:
|
|
808
|
+
name = <span class="str">""</span>
|
|
809
|
+
<span class="kw">define</span> <span class="fn">init</span>(n):
|
|
810
|
+
<span class="kw">set</span> self.name = n
|
|
811
|
+
<span class="kw">define</span> <span class="fn">speak</span>():
|
|
812
|
+
<span class="kw">say</span> <span class="str">"{self.name} says hi!"</span>
|
|
813
|
+
|
|
814
|
+
<span class="kw">let</span> dog = <span class="fn">Animal</span>()
|
|
815
|
+
dog.<span class="fn">init</span>(<span class="str">"Rex"</span>)
|
|
816
|
+
dog.<span class="fn">speak</span>()
|
|
817
|
+
|
|
818
|
+
<span class="cmt">// Struct (lightweight data container)</span>
|
|
819
|
+
<span class="kw">struct</span> Point:
|
|
820
|
+
x = <span class="num">0</span>
|
|
821
|
+
y = <span class="num">0</span>
|
|
822
|
+
<span class="kw">let</span> p = <span class="fn">Point</span>()
|
|
823
|
+
<span class="kw">set</span> p.x = <span class="num">10</span></div>
|
|
824
|
+
</div>
|
|
825
|
+
|
|
826
|
+
<div class="docs-section">
|
|
827
|
+
<h3>Error Handling</h3>
|
|
828
|
+
<div class="docs-code">
|
|
829
|
+
<span class="kw">try</span>:
|
|
830
|
+
<span class="fn">riskyFunction</span>()
|
|
831
|
+
<span class="kw">catch</span> e:
|
|
832
|
+
<span class="kw">say</span> <span class="str">"Error: {e}"</span>
|
|
833
|
+
<span class="kw">finally</span>:
|
|
834
|
+
<span class="kw">say</span> <span class="str">"Always runs"</span></div>
|
|
835
|
+
</div>
|
|
836
|
+
|
|
837
|
+
<div class="docs-section">
|
|
838
|
+
<h3>Built-in Functions</h3>
|
|
839
|
+
<table class="docs-table">
|
|
840
|
+
<tr><th>Function</th><th>Description</th><th>Example</th></tr>
|
|
841
|
+
<tr><td><code>say(x)</code></td><td>Print output</td><td><code>say "hi"</code></td></tr>
|
|
842
|
+
<tr><td><code>input(prompt)</code></td><td>Read user input</td><td><code>let n = input("Name?")</code></td></tr>
|
|
843
|
+
<tr><td><code>length(x)</code></td><td>String or list length</td><td><code>length([1,2,3])</code> → 3</td></tr>
|
|
844
|
+
<tr><td><code>push / pop / shift</code></td><td>List mutation</td><td><code>push(nums, 4)</code></td></tr>
|
|
845
|
+
<tr><td><code>join(list, sep)</code></td><td>List → string</td><td><code>join(items, ", ")</code></td></tr>
|
|
846
|
+
<tr><td><code>split(str, sep)</code></td><td>String → list</td><td><code>split("a,b", ",")</code></td></tr>
|
|
847
|
+
<tr><td><code>upper / lower / trim</code></td><td>String transform</td><td><code>upper("hi")</code> → "HI"</td></tr>
|
|
848
|
+
<tr><td><code>replace(s, old, new)</code></td><td>Replace substring</td><td><code>replace("hi","h","b")</code></td></tr>
|
|
849
|
+
<tr><td><code>contains(s, sub)</code></td><td>Substring check</td><td><code>contains("hello","ell")</code></td></tr>
|
|
850
|
+
<tr><td><code>slice(x, a, b)</code></td><td>Sub-list or substring</td><td><code>slice(nums, 0, 3)</code></td></tr>
|
|
851
|
+
<tr><td><code>sort / reverse</code></td><td>List order</td><td><code>sort([3,1,2])</code></td></tr>
|
|
852
|
+
<tr><td><code>map / filter / reduce</code></td><td>List transforms</td><td><code>map(nums, double)</code></td></tr>
|
|
853
|
+
<tr><td><code>sum / avg / unique</code></td><td>List aggregation</td><td><code>sum([1,2,3])</code> → 6</td></tr>
|
|
854
|
+
<tr><td><code>range(a, b)</code></td><td>Number list</td><td><code>range(0, 5)</code> → [0..4]</td></tr>
|
|
855
|
+
<tr><td><code>abs / sqrt / floor / ceil</code></td><td>Math</td><td><code>sqrt(16)</code> → 4</td></tr>
|
|
856
|
+
<tr><td><code>max / min</code></td><td>Largest / smallest</td><td><code>max(1,5,3)</code> → 5</td></tr>
|
|
857
|
+
<tr><td><code>random()</code></td><td>Float 0–1</td><td><code>random()</code></td></tr>
|
|
858
|
+
<tr><td><code>randint(a, b)</code></td><td>Random integer</td><td><code>randint(1,6)</code></td></tr>
|
|
859
|
+
<tr><td><code>str / num / bool / type</code></td><td>Type conversion</td><td><code>num("42")</code> → 42</td></tr>
|
|
860
|
+
</table>
|
|
861
|
+
</div>
|
|
862
|
+
|
|
863
|
+
<!-- ═══ WEB MODE ════════════════════════════════════════════ -->
|
|
864
|
+
<div class="docs-section" style="border-top:2px solid var(--lime);margin-top:40px;padding-top:32px">
|
|
865
|
+
<h3 style="color:var(--lime)">🌐 Web Mode — Build Real Pages</h3>
|
|
866
|
+
<p style="color:var(--ink2);margin-bottom:20px">Any file starting with <code>page</code> or <code>add</code> enters <strong>Web Mode</strong>. StructScript compiles to a complete, standalone HTML file with full CSS and JavaScript support. Every HTML element, CSS property, pseudo-class, media query, and browser event is supported.</p>
|
|
867
|
+
|
|
868
|
+
<h4 style="margin:0 0 10px">Page Setup</h4>
|
|
869
|
+
<div class="docs-code">
|
|
870
|
+
<span class="kw">page</span> <span class="str">"My Site"</span>:
|
|
871
|
+
<span class="cmt">// Body styles</span>
|
|
872
|
+
style background <span class="str">"#0d1f1e"</span>
|
|
873
|
+
style fontFamily <span class="str">"Manrope, sans-serif"</span>
|
|
874
|
+
style color <span class="str">"#e0f0ee"</span>
|
|
875
|
+
<span class="cmt">// <head> metadata</span>
|
|
876
|
+
viewport <span class="str">"width=device-width, initial-scale=1"</span>
|
|
877
|
+
meta description <span class="str">"My awesome page"</span>
|
|
878
|
+
meta author <span class="str">"You"</span>
|
|
879
|
+
favicon <span class="str">"/icon.ico"</span>
|
|
880
|
+
font <span class="str">"Manrope"</span>
|
|
881
|
+
<span class="cmt">// External resources</span>
|
|
882
|
+
link <span class="str">'rel="stylesheet" href="styles.css"'</span>
|
|
883
|
+
script src <span class="str">"https://cdn.jsdelivr.net/npm/chart.js"</span>
|
|
884
|
+
<span class="cmt">// Open Graph</span>
|
|
885
|
+
og:title <span class="str">"My Site"</span>
|
|
886
|
+
og:image <span class="str">"https://example.com/thumb.png"</span>
|
|
887
|
+
canonical <span class="str">"https://example.com"</span>
|
|
888
|
+
lang <span class="str">"en"</span></div>
|
|
889
|
+
|
|
890
|
+
<h4 style="margin:20px 0 10px">Elements — Every HTML Tag</h4>
|
|
891
|
+
<p style="color:var(--ink2);margin-bottom:10px">Use <code>add TAG</code> for any HTML element. Nesting follows indentation.</p>
|
|
892
|
+
<div class="docs-code">
|
|
893
|
+
<span class="cmt">// Selector formats:</span>
|
|
894
|
+
<span class="kw">add</span> div <span class="str">"myId"</span>: <span class="cmt">// → <div id="myId"></span>
|
|
895
|
+
<span class="kw">add</span> div <span class="str">".card"</span>: <span class="cmt">// → <div class="card"></span>
|
|
896
|
+
<span class="kw">add</span> div <span class="str">"#hero .big"</span>: <span class="cmt">// → <div id="hero" class="big"></span>
|
|
897
|
+
<span class="kw">add</span> div: <span class="cmt">// → <div> (no id/class)</span>
|
|
898
|
+
|
|
899
|
+
<span class="cmt">// Semantic HTML5 structure:</span>
|
|
900
|
+
<span class="kw">add</span> header <span class="str">""</span>:
|
|
901
|
+
<span class="kw">add</span> nav <span class="str">""</span>:
|
|
902
|
+
<span class="kw">add</span> a <span class="str">""</span>: text <span class="str">"Home"</span> href <span class="str">"/"</span>
|
|
903
|
+
<span class="kw">add</span> main <span class="str">""</span>:
|
|
904
|
+
<span class="kw">add</span> section <span class="str">"#about"</span>:
|
|
905
|
+
<span class="kw">add</span> article <span class="str">""</span>:
|
|
906
|
+
<span class="kw">add</span> h1 <span class="str">""</span>: text <span class="str">"About Me"</span>
|
|
907
|
+
<span class="kw">add</span> p <span class="str">""</span>: text <span class="str">"Developer & maker."</span>
|
|
908
|
+
<span class="kw">add</span> aside <span class="str">""</span>:
|
|
909
|
+
<span class="kw">add</span> p <span class="str">""</span>: text <span class="str">"Sidebar content"</span>
|
|
910
|
+
<span class="kw">add</span> footer <span class="str">""</span>:
|
|
911
|
+
<span class="kw">add</span> p <span class="str">""</span>: text <span class="str">"© 2025"</span></div>
|
|
912
|
+
|
|
913
|
+
<h4 style="margin:20px 0 10px">Content & Common Attributes</h4>
|
|
914
|
+
<table class="docs-table">
|
|
915
|
+
<tr><th>Command</th><th>HTML equivalent</th></tr>
|
|
916
|
+
<tr><td><code>text "hello"</code></td><td>Inner text content</td></tr>
|
|
917
|
+
<tr><td><code>html "<b>bold</b>"</code></td><td>Raw inner HTML</td></tr>
|
|
918
|
+
<tr><td><code>src "url"</code></td><td><code>src="url"</code> — img, video, audio, iframe, script</td></tr>
|
|
919
|
+
<tr><td><code>href "url"</code></td><td><code>href="url"</code> — a, link</td></tr>
|
|
920
|
+
<tr><td><code>alt "text"</code></td><td><code>alt="text"</code></td></tr>
|
|
921
|
+
<tr><td><code>title "text"</code></td><td><code>title="text"</code> tooltip</td></tr>
|
|
922
|
+
<tr><td><code>type "email"</code></td><td><code>type="email"</code> — text email password number tel url date range color file</td></tr>
|
|
923
|
+
<tr><td><code>name "field"</code></td><td><code>name="field"</code></td></tr>
|
|
924
|
+
<tr><td><code>value "x"</code></td><td><code>value="x"</code></td></tr>
|
|
925
|
+
<tr><td><code>placeholder "hint"</code></td><td><code>placeholder="hint"</code></td></tr>
|
|
926
|
+
<tr><td><code>for "id"</code></td><td><code>for="id"</code> — label</td></tr>
|
|
927
|
+
<tr><td><code>action "url"</code></td><td>form action</td></tr>
|
|
928
|
+
<tr><td><code>method "post"</code></td><td>form method</td></tr>
|
|
929
|
+
<tr><td><code>target "_blank"</code></td><td>link/form target</td></tr>
|
|
930
|
+
<tr><td><code>rel "noopener"</code></td><td>link rel</td></tr>
|
|
931
|
+
<tr><td><code>width "300"</code> / <code>height "200"</code></td><td>Element dimensions</td></tr>
|
|
932
|
+
<tr><td><code>rows "5"</code> / <code>cols "40"</code></td><td>textarea size</td></tr>
|
|
933
|
+
<tr><td><code>min "0"</code> / <code>max "100"</code> / <code>step "1"</code></td><td>number/range input</td></tr>
|
|
934
|
+
<tr><td><code>accept ".jpg,.png"</code></td><td>file input accept</td></tr>
|
|
935
|
+
<tr><td><code>pattern "[A-Z]+"</code></td><td>input validation regex</td></tr>
|
|
936
|
+
<tr><td><code>maxlength "100"</code></td><td>max character count</td></tr>
|
|
937
|
+
<tr><td><code>srcset "img.png 2x"</code></td><td>responsive images</td></tr>
|
|
938
|
+
<tr><td><code>loading "lazy"</code></td><td>lazy-load images/iframes</td></tr>
|
|
939
|
+
<tr><td><code>poster "thumb.jpg"</code></td><td>video poster frame</td></tr>
|
|
940
|
+
<tr><td><code>sandbox "allow-scripts"</code></td><td>iframe sandbox</td></tr>
|
|
941
|
+
<tr><td><code>colspan "2"</code> / <code>rowspan "3"</code></td><td>table cell spanning</td></tr>
|
|
942
|
+
<tr><td><code>role "button"</code></td><td>ARIA role</td></tr>
|
|
943
|
+
<tr><td><code>tabindex "0"</code></td><td>keyboard tab order</td></tr>
|
|
944
|
+
<tr><td><code>aria-label "Close"</code></td><td>any <code>aria-*</code> attribute</td></tr>
|
|
945
|
+
<tr><td><code>data-id "123"</code></td><td>any <code>data-*</code> attribute</td></tr>
|
|
946
|
+
<tr><td><code>class "extra"</code></td><td>add CSS class(es)</td></tr>
|
|
947
|
+
<tr><td><code>attr NAME VALUE</code></td><td>any other HTML attribute</td></tr>
|
|
948
|
+
</table>
|
|
949
|
+
<p style="margin-top:8px;color:var(--ink2);font-size:12px">Boolean attributes (no value): <code>checked disabled readonly required autoplay loop controls muted multiple autofocus hidden open selected novalidate async defer reversed ismap allowfullscreen</code></p>
|
|
950
|
+
|
|
951
|
+
<h4 style="margin:20px 0 10px">All Styling — CSS Properties</h4>
|
|
952
|
+
<p style="color:var(--ink2);margin-bottom:10px">Every CSS property works. Use camelCase or kebab-case — both accepted.</p>
|
|
953
|
+
<div class="docs-code">
|
|
954
|
+
<span class="kw">add</span> div <span class="str">".card"</span>:
|
|
955
|
+
<span class="cmt">// Any CSS property via style</span>
|
|
956
|
+
style display <span class="str">"flex"</span>
|
|
957
|
+
style flexDirection <span class="str">"column"</span> <span class="cmt">// or: flex-direction</span>
|
|
958
|
+
style backgroundColor <span class="str">"#162b28"</span>
|
|
959
|
+
style borderRadius <span class="str">"12px"</span>
|
|
960
|
+
style padding <span class="str">"24px"</span>
|
|
961
|
+
style boxShadow <span class="str">"0 4px 24px rgba(0,0,0,0.4)"</span>
|
|
962
|
+
style background <span class="str">"linear-gradient(135deg,#0b7a75,#162b28)"</span>
|
|
963
|
+
style backgroundImage <span class="str">"url('/hero.jpg')"</span>
|
|
964
|
+
style backgroundSize <span class="str">"cover"</span>
|
|
965
|
+
style backdropFilter <span class="str">"blur(12px)"</span>
|
|
966
|
+
style webkitBackdropFilter <span class="str">"blur(12px)"</span>
|
|
967
|
+
style clipPath <span class="str">"polygon(0 0,100% 0,100% 85%,0 100%)"</span>
|
|
968
|
+
<span class="cmt">// Shorthand helpers</span>
|
|
969
|
+
transition <span class="str">"all 0.25s ease"</span>
|
|
970
|
+
transform <span class="str">"rotate(45deg) scale(1.1)"</span>
|
|
971
|
+
filter <span class="str">"drop-shadow(0 4px 8px rgba(0,0,0,0.5))"</span>
|
|
972
|
+
flex <span class="str">"1 0 200px"</span>
|
|
973
|
+
grid <span class="str">"repeat(3, 1fr)"</span>
|
|
974
|
+
gap <span class="str">"16px"</span>
|
|
975
|
+
bg <span class="str">"#0b7a75"</span>
|
|
976
|
+
shadow <span class="str">"0 8px 32px rgba(0,0,0,0.3)"</span>
|
|
977
|
+
cursor <span class="str">"pointer"</span>
|
|
978
|
+
opacity <span class="str">"0.8"</span>
|
|
979
|
+
zindex <span class="str">"100"</span>
|
|
980
|
+
outline <span class="str">"2px solid #b8f000"</span>
|
|
981
|
+
overflow <span class="str">"hidden"</span>
|
|
982
|
+
<span class="cmt">// CSS custom properties</span>
|
|
983
|
+
var --brand <span class="str">"#b8f000"</span></div>
|
|
984
|
+
|
|
985
|
+
<h4 style="margin:20px 0 10px">Pseudo-classes & States</h4>
|
|
986
|
+
<p style="color:var(--ink2);margin-bottom:10px">All CSS pseudo-classes are supported using the same syntax: <code>PSEUDO property value</code>.</p>
|
|
987
|
+
<table class="docs-table">
|
|
988
|
+
<tr><th>Command</th><th>CSS Pseudo-class</th><th>Example</th></tr>
|
|
989
|
+
<tr><td><code>hover PROP VAL</code></td><td><code>:hover</code></td><td><code>hover background "#12a89e"</code></td></tr>
|
|
990
|
+
<tr><td><code>focus PROP VAL</code></td><td><code>:focus</code></td><td><code>focus outline "2px solid #b8f000"</code></td></tr>
|
|
991
|
+
<tr><td><code>active PROP VAL</code></td><td><code>:active</code></td><td><code>active transform "scale(0.97)"</code></td></tr>
|
|
992
|
+
<tr><td><code>visited PROP VAL</code></td><td><code>:visited</code></td><td><code>visited color "#8ab8b4"</code></td></tr>
|
|
993
|
+
<tr><td><code>checked PROP VAL</code></td><td><code>:checked</code></td><td><code>checked background "#b8f000"</code></td></tr>
|
|
994
|
+
<tr><td><code>disabled PROP VAL</code></td><td><code>:disabled</code></td><td><code>disabled opacity "0.4"</code></td></tr>
|
|
995
|
+
<tr><td><code>focus-visible PROP VAL</code></td><td><code>:focus-visible</code></td><td><code>focus-visible outline "3px solid #b8f000"</code></td></tr>
|
|
996
|
+
<tr><td><code>placeholder PROP VAL</code></td><td><code>::placeholder</code></td><td><code>placeholder color "#4a7470"</code></td></tr>
|
|
997
|
+
<tr><td><code>first-child PROP VAL</code></td><td><code>:first-child</code></td><td><code>first-child marginTop "0"</code></td></tr>
|
|
998
|
+
<tr><td><code>last-child PROP VAL</code></td><td><code>:last-child</code></td><td><code>last-child marginBottom "0"</code></td></tr>
|
|
999
|
+
<tr><td><code>invalid PROP VAL</code></td><td><code>:invalid</code></td><td><code>invalid borderColor "red"</code></td></tr>
|
|
1000
|
+
<tr><td><code>valid PROP VAL</code></td><td><code>:valid</code></td><td><code>valid borderColor "#b8f000"</code></td></tr>
|
|
1001
|
+
</table>
|
|
1002
|
+
|
|
1003
|
+
<h4 style="margin:20px 0 10px">Pseudo-elements</h4>
|
|
1004
|
+
<div class="docs-code">
|
|
1005
|
+
<span class="kw">add</span> li <span class="str">".item"</span>:
|
|
1006
|
+
text <span class="str">"List item"</span>
|
|
1007
|
+
before <span class="str">"→ "</span> <span class="cmt">// injects ::before { content: "→ " }</span>
|
|
1008
|
+
after <span class="str">" ✓"</span> <span class="cmt">// injects ::after { content: " ✓" }</span></div>
|
|
1009
|
+
|
|
1010
|
+
<h4 style="margin:20px 0 10px">Responsive Design — Media Queries</h4>
|
|
1011
|
+
<div class="docs-code">
|
|
1012
|
+
<span class="kw">add</span> div <span class="str">"#grid"</span>:
|
|
1013
|
+
grid <span class="str">"repeat(3, 1fr)"</span>
|
|
1014
|
+
gap <span class="str">"24px"</span>
|
|
1015
|
+
media <span class="str">"max-width:900px"</span> gridTemplateColumns <span class="str">"repeat(2,1fr)"</span>
|
|
1016
|
+
media <span class="str">"max-width:600px"</span> gridTemplateColumns <span class="str">"1fr"</span>
|
|
1017
|
+
media <span class="str">"max-width:600px"</span> gap <span class="str">"12px"</span>
|
|
1018
|
+
|
|
1019
|
+
<span class="cmt">// Full @media / @container / @supports via the css: block</span>
|
|
1020
|
+
<span class="kw">css</span>:
|
|
1021
|
+
@media (prefers-color-scheme: dark) { body { background: #000 } }
|
|
1022
|
+
@container sidebar (min-width: 300px) { .widget { flex-direction: row } }
|
|
1023
|
+
@supports (backdrop-filter: blur(1px)) { .glass { backdrop-filter: blur(12px) } }</div>
|
|
1024
|
+
|
|
1025
|
+
<h4 style="margin:20px 0 10px">Events — Every Browser Event</h4>
|
|
1026
|
+
<p style="color:var(--ink2);margin-bottom:10px">Write plain JavaScript in the handler body. The <code>event</code> object and <code>this</code> (the element) are available.</p>
|
|
1027
|
+
<div class="docs-code">
|
|
1028
|
+
<span class="kw">add</span> button <span class="str">"btn"</span>:
|
|
1029
|
+
text <span class="str">"Save"</span>
|
|
1030
|
+
<span class="kw">on</span> click:
|
|
1031
|
+
const data = document.getElementById('editor').value;
|
|
1032
|
+
localStorage.setItem('draft', data);
|
|
1033
|
+
this.textContent = 'Saved!';
|
|
1034
|
+
|
|
1035
|
+
<span class="kw">add</span> input <span class="str">"search"</span>:
|
|
1036
|
+
type <span class="str">"text"</span>
|
|
1037
|
+
placeholder <span class="str">"Search..."</span>
|
|
1038
|
+
<span class="kw">on</span> input:
|
|
1039
|
+
filterResults(event.target.value)
|
|
1040
|
+
<span class="kw">on</span> keydown:
|
|
1041
|
+
if (event.key === 'Enter') submitSearch();
|
|
1042
|
+
|
|
1043
|
+
<span class="kw">add</span> div <span class="str">"dropzone"</span>:
|
|
1044
|
+
<span class="kw">on</span> dragover:
|
|
1045
|
+
event.preventDefault();
|
|
1046
|
+
this.style.background = '#1e3530';
|
|
1047
|
+
<span class="kw">on</span> drop:
|
|
1048
|
+
event.preventDefault();
|
|
1049
|
+
const file = event.dataTransfer.files[0];
|
|
1050
|
+
handleFile(file);</div>
|
|
1051
|
+
<p style="margin-top:8px;color:var(--ink2);font-size:12px">All events: <code>click dblclick contextmenu mousedown mouseup mouseover mouseout mouseenter mouseleave mousemove keydown keyup keypress input change focus blur submit reset select scroll resize wheel drag drop touch pointer animationend transitionend load error play pause ended copy paste</code> and more.</p>
|
|
1052
|
+
|
|
1053
|
+
<h4 style="margin:20px 0 10px">Animations</h4>
|
|
1054
|
+
<table class="docs-table">
|
|
1055
|
+
<tr><th>Command</th><th>Effect</th><th>Loop?</th></tr>
|
|
1056
|
+
<tr><td><code>animate fadeIn 0.5</code></td><td>Fade + rise from below</td><td>No</td></tr>
|
|
1057
|
+
<tr><td><code>animate fadeOut 0.3</code></td><td>Fade out</td><td>No</td></tr>
|
|
1058
|
+
<tr><td><code>animate slideIn 0.4</code></td><td>Slide from left</td><td>No</td></tr>
|
|
1059
|
+
<tr><td><code>animate slideInRight 0.4</code></td><td>Slide from right</td><td>No</td></tr>
|
|
1060
|
+
<tr><td><code>animate slideUp 0.4</code></td><td>Rise from below</td><td>No</td></tr>
|
|
1061
|
+
<tr><td><code>animate slideDown 0.4</code></td><td>Drop from above</td><td>No</td></tr>
|
|
1062
|
+
<tr><td><code>animate pop 0.4</code></td><td>Scale pop-in</td><td>No</td></tr>
|
|
1063
|
+
<tr><td><code>animate flip 0.5</code></td><td>3D Y-axis flip</td><td>No</td></tr>
|
|
1064
|
+
<tr><td><code>animate zoom 0.4</code></td><td>Scale from zero</td><td>No</td></tr>
|
|
1065
|
+
<tr><td><code>animate typewriter 2</code></td><td>Text reveal left→right</td><td>No</td></tr>
|
|
1066
|
+
<tr><td><code>animate bounce 0.6</code></td><td>Vertical bounce</td><td>∞</td></tr>
|
|
1067
|
+
<tr><td><code>animate spin 1</code></td><td>Continuous rotation</td><td>∞</td></tr>
|
|
1068
|
+
<tr><td><code>animate pulse 1.5</code></td><td>Opacity pulse</td><td>∞</td></tr>
|
|
1069
|
+
<tr><td><code>animate shake 0.4</code></td><td>Horizontal shake</td><td>No</td></tr>
|
|
1070
|
+
<tr><td><code>animate wiggle 0.4</code></td><td>Rotation wiggle</td><td>No</td></tr>
|
|
1071
|
+
</table>
|
|
1072
|
+
<p style="margin-top:8px;color:var(--ink2);font-size:12px">Optional third argument: easing — <code>ease ease-in ease-out ease-in-out linear cubic-bezier(…)</code></p>
|
|
1073
|
+
|
|
1074
|
+
<h4 style="margin:20px 0 10px">Raw CSS & JavaScript Blocks</h4>
|
|
1075
|
+
<div class="docs-code">
|
|
1076
|
+
<span class="cmt">// Inject anything into <style></span>
|
|
1077
|
+
<span class="kw">css</span>:
|
|
1078
|
+
:root { --brand: #b8f000; --bg: #0d1f1e; }
|
|
1079
|
+
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;800&display=swap');
|
|
1080
|
+
::selection { background: #b8f000; color: #0d1f1e; }
|
|
1081
|
+
::-webkit-scrollbar { width: 6px }
|
|
1082
|
+
::-webkit-scrollbar-thumb { background: #234440; border-radius: 3px }
|
|
1083
|
+
html { scroll-behavior: smooth }
|
|
1084
|
+
@keyframes myCustomAnim { 0% { opacity:0 } 100% { opacity:1 } }
|
|
1085
|
+
@media print { nav { display: none } }
|
|
1086
|
+
|
|
1087
|
+
<span class="cmt">// Inject JavaScript — runs after DOM is ready</span>
|
|
1088
|
+
<span class="kw">script</span>:
|
|
1089
|
+
const cards = document.querySelectorAll('.card');
|
|
1090
|
+
cards.forEach((card, i) => {
|
|
1091
|
+
card.style.animationDelay = (i * 0.1) + 's';
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
function filterResults(query) {
|
|
1095
|
+
cards.forEach(c => {
|
|
1096
|
+
c.style.display = c.textContent.includes(query) ? '' : 'none';
|
|
1097
|
+
});
|
|
1098
|
+
}</div>
|
|
1099
|
+
|
|
1100
|
+
<h4 style="margin:20px 0 10px">Forms, Tables, Media</h4>
|
|
1101
|
+
<div class="docs-code">
|
|
1102
|
+
<span class="cmt">// Form with validation</span>
|
|
1103
|
+
<span class="kw">add</span> form <span class="str">"#contact"</span>:
|
|
1104
|
+
action <span class="str">"/submit"</span>
|
|
1105
|
+
method <span class="str">"post"</span>
|
|
1106
|
+
<span class="kw">on</span> submit:
|
|
1107
|
+
event.preventDefault();
|
|
1108
|
+
const fd = new FormData(this);
|
|
1109
|
+
fetch('/api', {method:'POST', body:fd});
|
|
1110
|
+
|
|
1111
|
+
<span class="kw">add</span> label <span class="str">""</span>: text <span class="str">"Email"</span> for <span class="str">"email"</span>
|
|
1112
|
+
<span class="kw">add</span> input <span class="str">"email"</span>:
|
|
1113
|
+
type <span class="str">"email"</span> name <span class="str">"email"</span> required
|
|
1114
|
+
placeholder <span class="str">"you@example.com"</span>
|
|
1115
|
+
pattern <span class="str">"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}"</span>
|
|
1116
|
+
focus outline <span class="str">"2px solid #b8f000"</span>
|
|
1117
|
+
valid borderColor <span class="str">"#b8f000"</span>
|
|
1118
|
+
invalid borderColor <span class="str">"#f07070"</span>
|
|
1119
|
+
|
|
1120
|
+
<span class="kw">add</span> select <span class="str">"topic"</span>:
|
|
1121
|
+
name <span class="str">"topic"</span>
|
|
1122
|
+
<span class="kw">add</span> option <span class="str">""</span>: text <span class="str">"General"</span> value <span class="str">"general"</span>
|
|
1123
|
+
<span class="kw">add</span> option <span class="str">""</span>: text <span class="str">"Support"</span> value <span class="str">"support"</span> selected
|
|
1124
|
+
|
|
1125
|
+
<span class="cmt">// Responsive table</span>
|
|
1126
|
+
<span class="kw">add</span> table <span class="str">""</span>:
|
|
1127
|
+
style width <span class="str">"100%"</span>
|
|
1128
|
+
style borderCollapse <span class="str">"collapse"</span>
|
|
1129
|
+
<span class="kw">add</span> thead <span class="str">""</span>:
|
|
1130
|
+
<span class="kw">add</span> tr <span class="str">""</span>:
|
|
1131
|
+
<span class="kw">add</span> th <span class="str">""</span>: text <span class="str">"Name"</span> style padding <span class="str">"12px"</span>
|
|
1132
|
+
<span class="kw">add</span> th <span class="str">""</span>: text <span class="str">"Score"</span> colspan <span class="str">"2"</span>
|
|
1133
|
+
<span class="kw">add</span> tbody <span class="str">""</span>:
|
|
1134
|
+
<span class="kw">add</span> tr <span class="str">""</span>:
|
|
1135
|
+
<span class="kw">add</span> td <span class="str">""</span>: text <span class="str">"Alice"</span>
|
|
1136
|
+
<span class="kw">add</span> td <span class="str">""</span>: text <span class="str">"95"</span>
|
|
1137
|
+
|
|
1138
|
+
<span class="cmt">// Video with controls</span>
|
|
1139
|
+
<span class="kw">add</span> video <span class="str">"player"</span>:
|
|
1140
|
+
src <span class="str">"movie.mp4"</span>
|
|
1141
|
+
poster <span class="str">"thumb.jpg"</span>
|
|
1142
|
+
controls
|
|
1143
|
+
loop
|
|
1144
|
+
style width <span class="str">"100%"</span>
|
|
1145
|
+
style borderRadius <span class="str">"12px"</span>
|
|
1146
|
+
<span class="kw">on</span> play:
|
|
1147
|
+
console.log('Video started')
|
|
1148
|
+
|
|
1149
|
+
<span class="cmt">// Canvas</span>
|
|
1150
|
+
<span class="kw">add</span> canvas <span class="str">"myCanvas"</span>:
|
|
1151
|
+
width <span class="str">"800"</span> height <span class="str">"400"</span>
|
|
1152
|
+
style border <span class="str">"1px solid #234440"</span>
|
|
1153
|
+
|
|
1154
|
+
<span class="kw">script</span>:
|
|
1155
|
+
const ctx = document.getElementById('myCanvas').getContext('2d');
|
|
1156
|
+
ctx.fillStyle = '#b8f000';
|
|
1157
|
+
ctx.fillRect(10, 10, 200, 100);</div>
|
|
1158
|
+
|
|
1159
|
+
<h4 style="margin:20px 0 10px">Complete Example — Landing Page</h4>
|
|
1160
|
+
<div class="docs-code">
|
|
1161
|
+
<span class="kw">page</span> <span class="str">"Dev Portfolio"</span>:
|
|
1162
|
+
style background <span class="str">"#0a1614"</span>
|
|
1163
|
+
style color <span class="str">"#e0f0ee"</span>
|
|
1164
|
+
style fontFamily <span class="str">"system-ui, sans-serif"</span>
|
|
1165
|
+
style lineHeight <span class="str">"1.6"</span>
|
|
1166
|
+
meta description <span class="str">"Portfolio"</span>
|
|
1167
|
+
font <span class="str">"Manrope"</span>
|
|
1168
|
+
|
|
1169
|
+
<span class="kw">add</span> nav <span class="str">"#nav"</span>:
|
|
1170
|
+
style position <span class="str">"fixed"</span>
|
|
1171
|
+
style top <span class="str">"0"</span> style left <span class="str">"0"</span> style right <span class="str">"0"</span>
|
|
1172
|
+
style display <span class="str">"flex"</span>
|
|
1173
|
+
style justifyContent <span class="str">"space-between"</span>
|
|
1174
|
+
style alignItems <span class="str">"center"</span>
|
|
1175
|
+
style padding <span class="str">"0 40px"</span>
|
|
1176
|
+
style height <span class="str">"60px"</span>
|
|
1177
|
+
style background <span class="str">"rgba(10,22,20,0.9)"</span>
|
|
1178
|
+
style backdropFilter <span class="str">"blur(10px)"</span>
|
|
1179
|
+
style zIndex <span class="str">"1000"</span>
|
|
1180
|
+
style borderBottom <span class="str">"1px solid #234440"</span>
|
|
1181
|
+
|
|
1182
|
+
<span class="kw">add</span> span <span class="str">""</span>:
|
|
1183
|
+
text <span class="str">"MyPortfolio"</span>
|
|
1184
|
+
style fontWeight <span class="str">"800"</span>
|
|
1185
|
+
style color <span class="str">"#b8f000"</span>
|
|
1186
|
+
style letterSpacing <span class="str">"-0.5px"</span>
|
|
1187
|
+
|
|
1188
|
+
<span class="kw">add</span> div <span class="str">".nav-links"</span>:
|
|
1189
|
+
style display <span class="str">"flex"</span>
|
|
1190
|
+
style gap <span class="str">"28px"</span>
|
|
1191
|
+
<span class="kw">add</span> a <span class="str">""</span>: text <span class="str">"Work"</span> href <span class="str">"#work"</span> style color <span class="str">"#8ab8b4"</span> hover color <span class="str">"#b8f000"</span> transition <span class="str">"color 0.2s"</span> style textDecoration <span class="str">"none"</span>
|
|
1192
|
+
<span class="kw">add</span> a <span class="str">""</span>: text <span class="str">"About"</span> href <span class="str">"#about"</span> style color <span class="str">"#8ab8b4"</span> hover color <span class="str">"#b8f000"</span> transition <span class="str">"color 0.2s"</span> style textDecoration <span class="str">"none"</span>
|
|
1193
|
+
|
|
1194
|
+
<span class="kw">add</span> section <span class="str">"#hero"</span>:
|
|
1195
|
+
style minHeight <span class="str">"100vh"</span>
|
|
1196
|
+
style display <span class="str">"flex"</span>
|
|
1197
|
+
style flexDirection <span class="str">"column"</span>
|
|
1198
|
+
style justifyContent <span class="str">"center"</span>
|
|
1199
|
+
style alignItems <span class="str">"center"</span>
|
|
1200
|
+
style textAlign <span class="str">"center"</span>
|
|
1201
|
+
style padding <span class="str">"80px 24px 40px"</span>
|
|
1202
|
+
<span class="kw">add</span> h1 <span class="str">""</span>:
|
|
1203
|
+
text <span class="str">"I build things for the web"</span>
|
|
1204
|
+
style fontSize <span class="str">"clamp(2.5rem,7vw,5rem)"</span>
|
|
1205
|
+
style fontWeight <span class="str">"800"</span>
|
|
1206
|
+
style lineHeight <span class="str">"1.1"</span>
|
|
1207
|
+
style letterSpacing <span class="str">"-2px"</span>
|
|
1208
|
+
animate fadeIn 1
|
|
1209
|
+
<span class="kw">add</span> p <span class="str">""</span>:
|
|
1210
|
+
text <span class="str">"Full-stack developer. Open source contributor."</span>
|
|
1211
|
+
style color <span class="str">"#8ab8b4"</span>
|
|
1212
|
+
style fontSize <span class="str">"1.2rem"</span>
|
|
1213
|
+
style marginTop <span class="str">"16px"</span>
|
|
1214
|
+
animate fadeIn 1.2
|
|
1215
|
+
<span class="kw">add</span> a <span class="str">".cta"</span>:
|
|
1216
|
+
text <span class="str">"View my work ↓"</span>
|
|
1217
|
+
href <span class="str">"#work"</span>
|
|
1218
|
+
style display <span class="str">"inline-block"</span>
|
|
1219
|
+
style marginTop <span class="str">"40px"</span>
|
|
1220
|
+
style background <span class="str">"#b8f000"</span>
|
|
1221
|
+
style color <span class="str">"#0a1614"</span>
|
|
1222
|
+
style padding <span class="str">"14px 36px"</span>
|
|
1223
|
+
style borderRadius <span class="str">"50px"</span>
|
|
1224
|
+
style fontWeight <span class="str">"700"</span>
|
|
1225
|
+
style textDecoration <span class="str">"none"</span>
|
|
1226
|
+
style fontSize <span class="str">"1rem"</span>
|
|
1227
|
+
hover background <span class="str">"#d4ff33"</span>
|
|
1228
|
+
hover transform <span class="str">"translateY(-2px)"</span>
|
|
1229
|
+
active transform <span class="str">"translateY(0)"</span>
|
|
1230
|
+
transition <span class="str">"all 0.2s"</span>
|
|
1231
|
+
animate pop 1.4
|
|
1232
|
+
|
|
1233
|
+
<span class="kw">add</span> section <span class="str">"#work"</span>:
|
|
1234
|
+
style padding <span class="str">"100px 24px"</span>
|
|
1235
|
+
style maxWidth <span class="str">"1100px"</span>
|
|
1236
|
+
style margin <span class="str">"0 auto"</span>
|
|
1237
|
+
<span class="kw">add</span> h2 <span class="str">""</span>:
|
|
1238
|
+
text <span class="str">"Selected Work"</span>
|
|
1239
|
+
style fontSize <span class="str">"2.5rem"</span>
|
|
1240
|
+
style fontWeight <span class="str">"800"</span>
|
|
1241
|
+
style marginBottom <span class="str">"48px"</span>
|
|
1242
|
+
animate slideIn 0.6
|
|
1243
|
+
<span class="kw">add</span> div <span class="str">".project-grid"</span>:
|
|
1244
|
+
style display <span class="str">"grid"</span>
|
|
1245
|
+
grid <span class="str">"repeat(auto-fill, minmax(320px,1fr))"</span>
|
|
1246
|
+
gap <span class="str">"24px"</span>
|
|
1247
|
+
media <span class="str">"max-width:640px"</span> gridTemplateColumns <span class="str">"1fr"</span>
|
|
1248
|
+
|
|
1249
|
+
<span class="kw">add</span> article <span class="str">".project-card"</span>:
|
|
1250
|
+
style background <span class="str">"#162b28"</span>
|
|
1251
|
+
style borderRadius <span class="str">"16px"</span>
|
|
1252
|
+
style overflow <span class="str">"hidden"</span>
|
|
1253
|
+
style border <span class="str">"1px solid #234440"</span>
|
|
1254
|
+
hover borderColor <span class="str">"#b8f000"</span>
|
|
1255
|
+
hover transform <span class="str">"translateY(-6px)"</span>
|
|
1256
|
+
hover boxShadow <span class="str">"0 16px 48px rgba(0,0,0,0.4)"</span>
|
|
1257
|
+
transition <span class="str">"all 0.25s ease"</span>
|
|
1258
|
+
animate slideUp 0.5
|
|
1259
|
+
<span class="kw">add</span> div <span class="str">""</span>:
|
|
1260
|
+
style padding <span class="str">"28px"</span>
|
|
1261
|
+
<span class="kw">add</span> h3 <span class="str">""</span>:
|
|
1262
|
+
text <span class="str">"Project Alpha"</span>
|
|
1263
|
+
style color <span class="str">"#b8f000"</span>
|
|
1264
|
+
style fontSize <span class="str">"1.2rem"</span>
|
|
1265
|
+
style fontWeight <span class="str">"700"</span>
|
|
1266
|
+
style marginBottom <span class="str">"8px"</span>
|
|
1267
|
+
<span class="kw">add</span> p <span class="str">""</span>:
|
|
1268
|
+
text <span class="str">"A high-performance web application built with modern tooling."</span>
|
|
1269
|
+
style color <span class="str">"#8ab8b4"</span>
|
|
1270
|
+
style lineHeight <span class="str">"1.6"</span>
|
|
1271
|
+
<span class="kw">add</span> a <span class="str">""</span>:
|
|
1272
|
+
text <span class="str">"View Project →"</span>
|
|
1273
|
+
href <span class="str">"#"</span>
|
|
1274
|
+
style display <span class="str">"inline-block"</span>
|
|
1275
|
+
style marginTop <span class="str">"16px"</span>
|
|
1276
|
+
style color <span class="str">"#b8f000"</span>
|
|
1277
|
+
style textDecoration <span class="str">"none"</span>
|
|
1278
|
+
style fontWeight <span class="str">"600"</span>
|
|
1279
|
+
style fontSize <span class="str">"0.9rem"</span>
|
|
1280
|
+
hover textDecoration <span class="str">"underline"</span>
|
|
1281
|
+
|
|
1282
|
+
<span class="kw">css</span>:
|
|
1283
|
+
::selection { background: #b8f000; color: #0a1614 }
|
|
1284
|
+
::-webkit-scrollbar { width: 5px }
|
|
1285
|
+
::-webkit-scrollbar-thumb { background: #234440; border-radius: 3px }
|
|
1286
|
+
html { scroll-behavior: smooth }
|
|
1287
|
+
.project-card:nth-child(2) { animation-delay: 0.15s }
|
|
1288
|
+
.project-card:nth-child(3) { animation-delay: 0.3s }</div>
|
|
1289
|
+
|
|
1290
|
+
</div><!-- docs-section web mode -->
|
|
1291
|
+
|
|
1292
|
+
</div><!-- docs-page -->
|
|
1293
|
+
</div><!-- page-docs -->
|
|
855
1294
|
|
|
856
|
-
</div>
|
|
857
|
-
</div>
|
|
858
1295
|
|
|
859
1296
|
<div id="input-modal-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.65);z-index:999;align-items:center;justify-content:center;backdrop-filter:blur(3px)">
|
|
860
1297
|
<div style="background:var(--surface);border:1px solid var(--border2);border-radius:12px;padding:24px 28px;min-width:360px;max-width:480px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,0.5)">
|
|
@@ -878,23 +1315,7 @@ code { font-family: 'DM Mono', monospace; font-size: 11px; color: var(--accent);
|
|
|
878
1315
|
|
|
879
1316
|
</div><!-- app-body -->
|
|
880
1317
|
|
|
881
|
-
|
|
882
|
-
<div id="input-modal-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.65);z-index:999;align-items:center;justify-content:center;backdrop-filter:blur(3px)">
|
|
883
|
-
<div style="background:var(--surface);border:1px solid var(--border2);border-radius:12px;padding:24px 28px;min-width:360px;max-width:480px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,0.5)">
|
|
884
|
-
<div style="display:flex;align-items:center;gap:10px;margin-bottom:18px">
|
|
885
|
-
<div style="width:28px;height:28px;border-radius:6px;background:var(--accent-dim);border:1px solid var(--accent);display:flex;align-items:center;justify-content:center;font-size:13px">⌨</div>
|
|
886
|
-
<div>
|
|
887
|
-
<div style="font-size:13px;font-weight:700;color:var(--ink)">Program needs input</div>
|
|
888
|
-
<div style="font-size:10px;color:var(--muted);font-family:'DM Mono',monospace">Fill in values then click Run</div>
|
|
889
|
-
</div>
|
|
890
|
-
</div>
|
|
891
|
-
<div id="input-modal-fields"></div>
|
|
892
|
-
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:20px;padding-top:16px;border-top:1px solid var(--border)">
|
|
893
|
-
<button onclick="cancelInputModal()" style="padding:7px 16px;border-radius:6px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid var(--border2);background:var(--surface2);color:var(--ink2);font-family:'Manrope',sans-serif">Cancel</button>
|
|
894
|
-
<button onclick="submitInputModal()" style="padding:7px 20px;border-radius:6px;font-size:12px;font-weight:800;cursor:pointer;border:none;background:var(--lime);color:#0d1f1e;font-family:'Manrope',sans-serif">▶ Run</button>
|
|
895
|
-
</div>
|
|
896
|
-
</div>
|
|
897
|
-
</div>
|
|
1318
|
+
|
|
898
1319
|
|
|
899
1320
|
<!-- BROWSE DIALOG -->
|
|
900
1321
|
<div class="browse-overlay" id="browse-overlay">
|
|
@@ -1977,94 +2398,160 @@ count i from 0 to 14:
|
|
|
1977
2398
|
push(fibs, fibIterative(i))
|
|
1978
2399
|
say fibs`,
|
|
1979
2400
|
|
|
1980
|
-
webpage:`// StructScript Web —
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
style
|
|
1985
|
-
style
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
style
|
|
2003
|
-
style
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
style
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
add
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
style
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
add
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
add
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
style
|
|
2067
|
-
style
|
|
2401
|
+
webpage:`// StructScript Web Mode — every HTML tag, CSS property & browser event!
|
|
2402
|
+
// Run this to see a full landing page with nav, hero, cards, form & animations.
|
|
2403
|
+
|
|
2404
|
+
page "My App":
|
|
2405
|
+
style background "#0a1614"
|
|
2406
|
+
style color "#e0f0ee"
|
|
2407
|
+
style fontFamily "system-ui, sans-serif"
|
|
2408
|
+
meta description "Built with StructScript"
|
|
2409
|
+
|
|
2410
|
+
add nav "":
|
|
2411
|
+
style display "flex"
|
|
2412
|
+
style justifyContent "space-between"
|
|
2413
|
+
style alignItems "center"
|
|
2414
|
+
style padding "0 32px"
|
|
2415
|
+
style height "56px"
|
|
2416
|
+
style background "rgba(10,22,20,0.95)"
|
|
2417
|
+
style borderBottom "1px solid #234440"
|
|
2418
|
+
style position "sticky"
|
|
2419
|
+
style top "0"
|
|
2420
|
+
style zIndex "100"
|
|
2421
|
+
add span "": text "MyApp" style fontWeight "800" style color "#b8f000"
|
|
2422
|
+
add div "":
|
|
2423
|
+
style display "flex" gap "20px"
|
|
2424
|
+
add a "": text "Home" href "#" style color "#8ab8b4" hover color "#b8f000" transition "color 0.2s" style textDecoration "none"
|
|
2425
|
+
add a "": text "About" href "#about" style color "#8ab8b4" hover color "#b8f000" transition "color 0.2s" style textDecoration "none"
|
|
2426
|
+
add a "": text "Contact" href "#contact" style color "#8ab8b4" hover color "#b8f000" transition "color 0.2s" style textDecoration "none"
|
|
2427
|
+
|
|
2428
|
+
add section "#hero":
|
|
2429
|
+
style minHeight "80vh"
|
|
2430
|
+
style display "flex"
|
|
2431
|
+
style flexDirection "column"
|
|
2432
|
+
style justifyContent "center"
|
|
2433
|
+
style alignItems "center"
|
|
2434
|
+
style textAlign "center"
|
|
2435
|
+
style padding "60px 24px"
|
|
2436
|
+
add h1 "":
|
|
2437
|
+
text "Build pages in StructScript"
|
|
2438
|
+
style fontSize "clamp(2rem,6vw,4rem)"
|
|
2439
|
+
style fontWeight "800"
|
|
2440
|
+
style letterSpacing "-1px"
|
|
2441
|
+
animate fadeIn 0.8
|
|
2442
|
+
add p "":
|
|
2443
|
+
text "Every HTML element. Every CSS property. Zero boilerplate."
|
|
2444
|
+
style color "#8ab8b4" style fontSize "1.1rem" style marginTop "16px"
|
|
2445
|
+
animate fadeIn 1.1
|
|
2446
|
+
add div "":
|
|
2447
|
+
style display "flex" gap "12px" style marginTop "36px" style flexWrap "wrap" style justifyContent "center"
|
|
2448
|
+
add button ".btn-primary":
|
|
2449
|
+
text "Get started"
|
|
2450
|
+
hover background "#d4ff33" hover transform "translateY(-2px)"
|
|
2451
|
+
active transform "scale(0.97)"
|
|
2452
|
+
transition "all 0.2s"
|
|
2453
|
+
animate pop 1.3
|
|
2454
|
+
on click:
|
|
2455
|
+
document.getElementById('about').scrollIntoView({behavior:'smooth'})
|
|
2456
|
+
add button ".btn-secondary":
|
|
2457
|
+
text "Learn more"
|
|
2458
|
+
hover borderColor "#b8f000" hover color "#b8f000"
|
|
2459
|
+
transition "all 0.2s"
|
|
2460
|
+
animate pop 1.5
|
|
2461
|
+
|
|
2462
|
+
add section "#about":
|
|
2463
|
+
style padding "80px 24px"
|
|
2464
|
+
style maxWidth "900px"
|
|
2465
|
+
style margin "0 auto"
|
|
2466
|
+
add h2 "": text "Features" style fontSize "2rem" style fontWeight "800" style marginBottom "40px" animate slideIn 0.5
|
|
2467
|
+
add div ".feature-grid":
|
|
2468
|
+
style display "grid"
|
|
2469
|
+
grid "repeat(auto-fill, minmax(240px, 1fr))"
|
|
2470
|
+
gap "20px"
|
|
2471
|
+
media "max-width:600px" gridTemplateColumns "1fr"
|
|
2472
|
+
add div ".feature-card":
|
|
2473
|
+
hover borderColor "#b8f000" hover transform "translateY(-4px)"
|
|
2474
|
+
transition "all 0.2s" animate slideUp 0.4
|
|
2475
|
+
add span "": text "🌐" style fontSize "2rem"
|
|
2476
|
+
add h3 "": text "Web-Ready" style color "#b8f000" style margin "12px 0 6px"
|
|
2477
|
+
add p "": text "Full HTML output. Share as a real webpage." style color "#8ab8b4" style fontSize "0.9rem"
|
|
2478
|
+
add div ".feature-card":
|
|
2479
|
+
hover borderColor "#b8f000" hover transform "translateY(-4px)"
|
|
2480
|
+
transition "all 0.2s" animate slideUp 0.55
|
|
2481
|
+
add span "": text "🎨" style fontSize "2rem"
|
|
2482
|
+
add h3 "": text "CSS Power" style color "#b8f000" style margin "12px 0 6px"
|
|
2483
|
+
add p "": text "Every property, pseudo-class, and animation." style color "#8ab8b4" style fontSize "0.9rem"
|
|
2484
|
+
add div ".feature-card":
|
|
2485
|
+
hover borderColor "#b8f000" hover transform "translateY(-4px)"
|
|
2486
|
+
transition "all 0.2s" animate slideUp 0.7
|
|
2487
|
+
add span "": text "⚡" style fontSize "2rem"
|
|
2488
|
+
add h3 "": text "Events" style color "#b8f000" style margin "12px 0 6px"
|
|
2489
|
+
add p "": text "Click, drag, keyboard, touch — all covered." style color "#8ab8b4" style fontSize "0.9rem"
|
|
2490
|
+
|
|
2491
|
+
add section "#contact":
|
|
2492
|
+
style padding "80px 24px" style background "#0d1f1e"
|
|
2493
|
+
add div "":
|
|
2494
|
+
style maxWidth "480px" style margin "0 auto"
|
|
2495
|
+
add h2 "": text "Get in touch" style fontSize "2rem" style fontWeight "800" style marginBottom "32px" animate slideIn 0.4
|
|
2496
|
+
add form "":
|
|
2497
|
+
style display "flex" style flexDirection "column" gap "14px"
|
|
2498
|
+
on submit:
|
|
2499
|
+
event.preventDefault();
|
|
2500
|
+
document.getElementById('form-msg').textContent = '✓ Sent!';
|
|
2501
|
+
this.reset();
|
|
2502
|
+
add input "":
|
|
2503
|
+
type "text" placeholder "Your name" required
|
|
2504
|
+
style padding "12px 16px" style borderRadius "8px"
|
|
2505
|
+
style border "1px solid #234440" style background "#162b28"
|
|
2506
|
+
style color "#e0f0ee"
|
|
2507
|
+
focus outline "none" focus borderColor "#b8f000"
|
|
2508
|
+
transition "border-color 0.2s"
|
|
2509
|
+
add input "":
|
|
2510
|
+
type "email" placeholder "email@example.com" required
|
|
2511
|
+
style padding "12px 16px" style borderRadius "8px"
|
|
2512
|
+
style border "1px solid #234440" style background "#162b28"
|
|
2513
|
+
style color "#e0f0ee"
|
|
2514
|
+
focus outline "none" focus borderColor "#b8f000"
|
|
2515
|
+
valid borderColor "#b8f000"
|
|
2516
|
+
invalid borderColor "#f07070"
|
|
2517
|
+
transition "border-color 0.2s"
|
|
2518
|
+
add textarea "":
|
|
2519
|
+
placeholder "Your message..." rows "4" required
|
|
2520
|
+
style padding "12px 16px" style borderRadius "8px"
|
|
2521
|
+
style border "1px solid #234440" style background "#162b28"
|
|
2522
|
+
style color "#e0f0ee" style resize "vertical"
|
|
2523
|
+
focus outline "none" focus borderColor "#b8f000"
|
|
2524
|
+
transition "border-color 0.2s"
|
|
2525
|
+
add button "":
|
|
2526
|
+
text "Send message" type "submit"
|
|
2527
|
+
style background "#0b7a75" style color "#b8f000"
|
|
2528
|
+
style border "none" style padding "12px 24px"
|
|
2529
|
+
style borderRadius "8px" style fontWeight "700"
|
|
2530
|
+
style cursor "pointer"
|
|
2531
|
+
hover background "#12a89e"
|
|
2532
|
+
transition "background 0.2s"
|
|
2533
|
+
add p "form-msg": style color "#b8f000" style fontWeight "600" style marginTop "8px"
|
|
2534
|
+
|
|
2535
|
+
css:
|
|
2536
|
+
::selection { background: #b8f000; color: #0a1614 }
|
|
2537
|
+
::-webkit-scrollbar { width: 5px }
|
|
2538
|
+
::-webkit-scrollbar-thumb { background: #234440; border-radius: 3px }
|
|
2539
|
+
html { scroll-behavior: smooth }
|
|
2540
|
+
.btn-primary {
|
|
2541
|
+
background: #b8f000; color: #0a1614;
|
|
2542
|
+
border: none; padding: 13px 32px;
|
|
2543
|
+
border-radius: 50px; font-weight: 700;
|
|
2544
|
+
font-size: 1rem; cursor: pointer;
|
|
2545
|
+
}
|
|
2546
|
+
.btn-secondary {
|
|
2547
|
+
background: transparent; color: #e0f0ee;
|
|
2548
|
+
border: 1.5px solid #234440; padding: 13px 32px;
|
|
2549
|
+
border-radius: 50px; font-weight: 600;
|
|
2550
|
+
font-size: 1rem; cursor: pointer;
|
|
2551
|
+
}
|
|
2552
|
+
.feature-card { background: #162b28; border: 1px solid #234440; border-radius: 14px; padding: 28px; }
|
|
2553
|
+
.feature-grid .feature-card:nth-child(2) { animation-delay: 0.15s }
|
|
2554
|
+
.feature-grid .feature-card:nth-child(3) { animation-delay: 0.3s }`,
|
|
2068
2555
|
|
|
2069
2556
|
fizzbuzz:`// FizzBuzz — classic interview question
|
|
2070
2557
|
|
|
@@ -2314,6 +2801,8 @@ function updateAll() {
|
|
|
2314
2801
|
updateLineNumbers();
|
|
2315
2802
|
markModified(true);
|
|
2316
2803
|
}
|
|
2804
|
+
function updateHighlight() { highlightLayer.innerHTML = highlight(editor.value); highlightLayer.scrollTop = editor.scrollTop; highlightLayer.scrollLeft = editor.scrollLeft; }
|
|
2805
|
+
function setModified(v) { markModified(v); }
|
|
2317
2806
|
|
|
2318
2807
|
function markModified(v) {
|
|
2319
2808
|
isModified = v;
|
|
@@ -2487,250 +2976,332 @@ function _execCode(code, inputValues) {
|
|
|
2487
2976
|
// ============================================================
|
|
2488
2977
|
|
|
2489
2978
|
// Detects if the code uses any web commands
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
function buildWebDoc(
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
let
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
function
|
|
2515
|
-
|
|
2516
|
-
function
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
i
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
if (nr.
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
const
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
//
|
|
2647
|
-
if (el.
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
}
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
}
|
|
2690
|
-
|
|
2691
|
-
const
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
${
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
}
|
|
2733
|
-
|
|
2979
|
+
|
|
2980
|
+
function isWebCode(code) {
|
|
2981
|
+
return /^\s*(page\b|add\s+\w|css\s*:?$|script\s*:?$)/m.test(code);
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
function buildWebDoc(rawCode) {
|
|
2985
|
+
// Normalise line endings
|
|
2986
|
+
const code = rawCode.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
2987
|
+
|
|
2988
|
+
let pageTitle = 'StructScript Page';
|
|
2989
|
+
let pageLang = 'en';
|
|
2990
|
+
let pageStyles = {};
|
|
2991
|
+
let headTags = [];
|
|
2992
|
+
let cssBlocks = [];
|
|
2993
|
+
let jsBlocks = [];
|
|
2994
|
+
let bodyAttrs = {};
|
|
2995
|
+
const elements = [];
|
|
2996
|
+
const elStack = [];
|
|
2997
|
+
let i = 0;
|
|
2998
|
+
let _uid = 0;
|
|
2999
|
+
const lines = code.split('\n');
|
|
3000
|
+
|
|
3001
|
+
function indent(l) { let n=0; while(n<l.length&&l[n]===' ')n++; return n; }
|
|
3002
|
+
function str(s) { s=s.trim(); return (s[0]==='"'&&s[s.length-1]==='"')||(s[0]==="'"&&s[s.length-1]==="'") ? s.slice(1,-1) : s; }
|
|
3003
|
+
function kebab(s) { return s.replace(/([A-Z])/g,'-$1').toLowerCase(); }
|
|
3004
|
+
function camel(s) { return s.replace(/-([a-z])/g,(_,c)=>c.toUpperCase()); }
|
|
3005
|
+
function uid() { return '_ss'+(++_uid); }
|
|
3006
|
+
|
|
3007
|
+
// ── parse one line, advancing i if it consumes more ──────────
|
|
3008
|
+
while (i < lines.length) {
|
|
3009
|
+
const raw = lines[i], t = raw.trim(), ind = indent(raw);
|
|
3010
|
+
i++;
|
|
3011
|
+
if (!t || t.startsWith('//')) continue;
|
|
3012
|
+
|
|
3013
|
+
// page "Title" lang? :
|
|
3014
|
+
if (/^page\b/.test(t)) {
|
|
3015
|
+
const m = t.match(/^page\s+"([^"]*)"|^page\s+'([^']*)'/);
|
|
3016
|
+
if (m) pageTitle = m[1]||m[2];
|
|
3017
|
+
const lm = t.match(/\blang\s+"([^"]+)"/);
|
|
3018
|
+
if (lm) pageLang = lm[1];
|
|
3019
|
+
// consume indented page-level directives
|
|
3020
|
+
while (i < lines.length) {
|
|
3021
|
+
const nr = lines[i].trim(), ni = indent(lines[i]);
|
|
3022
|
+
if (!nr || nr.startsWith('//')) { i++; continue; }
|
|
3023
|
+
if (ni <= ind) break;
|
|
3024
|
+
i++;
|
|
3025
|
+
let m2;
|
|
3026
|
+
if ((m2=nr.match(/^style\s+([\w-]+)\s+(.+)$/))) { pageStyles[camel(m2[1])]=str(m2[2]); continue; }
|
|
3027
|
+
if ((m2=nr.match(/^charset\s+(.+)$/))) { headTags.push(`<meta charset="${str(m2[1])}">`); continue; }
|
|
3028
|
+
if ((m2=nr.match(/^viewport\s+(.+)$/))) { headTags.push(`<meta name="viewport" content="${str(m2[1])}">`); continue; }
|
|
3029
|
+
if ((m2=nr.match(/^meta\s+([\w-]+)\s+(.+)$/))) { headTags.push(`<meta name="${m2[1]}" content="${str(m2[2])}">`); continue; }
|
|
3030
|
+
if ((m2=nr.match(/^metahttp\s+([\w-]+)\s+(.+)$/))) { headTags.push(`<meta http-equiv="${m2[1]}" content="${str(m2[2])}">`); continue; }
|
|
3031
|
+
if ((m2=nr.match(/^link\s+(.+)$/))) { headTags.push(`<link ${str(m2[1])}>`); continue; }
|
|
3032
|
+
if ((m2=nr.match(/^favicon\s+(.+)$/))) { headTags.push(`<link rel="icon" href="${str(m2[1])}">`); continue; }
|
|
3033
|
+
if ((m2=nr.match(/^font\s+(.+)$/))) { headTags.push(`<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=${str(m2[1]).replace(/ /g,'+')}:wght@300;400;600;700;800&display=swap">`); continue; }
|
|
3034
|
+
if ((m2=nr.match(/^script\s+src\s+(.+)$/))) { headTags.push(`<script src="${str(m2[1])}"><\/script>`); continue; }
|
|
3035
|
+
if ((m2=nr.match(/^bodyattr\s+([\w-]+)\s+(.+)$/))) { bodyAttrs[m2[1]]=str(m2[2]); continue; }
|
|
3036
|
+
if ((m2=nr.match(/^(og|twitter):([\w:]+)\s+(.+)$/))){ headTags.push(`<meta property="${m2[1]}:${m2[2]}" content="${str(m2[3])}">`); continue; }
|
|
3037
|
+
if ((m2=nr.match(/^canonical\s+(.+)$/))) { headTags.push(`<link rel="canonical" href="${str(m2[1])}">`); continue; }
|
|
3038
|
+
if ((m2=nr.match(/^base\s+(.+)$/))) { headTags.push(`<base href="${str(m2[1])}">`); continue; }
|
|
3039
|
+
}
|
|
3040
|
+
continue;
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// css: raw CSS block
|
|
3044
|
+
if (/^css\s*:?$/.test(t)) {
|
|
3045
|
+
const buf=[];
|
|
3046
|
+
while(i<lines.length){ const nr=lines[i],ni=indent(nr); if(!nr.trim()||ni>ind){buf.push(nr.slice(Math.min(ni,ind+2)));i++;}else break; }
|
|
3047
|
+
cssBlocks.push(buf.join('\n'));
|
|
3048
|
+
continue;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
// script: raw JS block
|
|
3052
|
+
if (/^script\s*:?$/.test(t)) {
|
|
3053
|
+
const buf=[];
|
|
3054
|
+
while(i<lines.length){ const nr=lines[i],ni=indent(nr); if(!nr.trim()||ni>ind){buf.push(nr.slice(Math.min(ni,ind+2)));i++;}else break; }
|
|
3055
|
+
jsBlocks.push(buf.join('\n'));
|
|
3056
|
+
continue;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// add TAG "id/classes": — the workhorse
|
|
3060
|
+
// Supports: add div "myId" add div ".btn.active" add div "#main .hero big"
|
|
3061
|
+
const addM = t.match(/^add\s+([\w-]+)(?:\s+"([^"]*)")?(?:\s+'([^']*)')?\s*:?$/);
|
|
3062
|
+
if (addM) {
|
|
3063
|
+
while (elStack.length>1 && elStack[elStack.length-1].indent>=ind) elStack.pop();
|
|
3064
|
+
const parent = elStack.length ? elStack[elStack.length-1].el : null;
|
|
3065
|
+
const rawSel = (addM[2]||addM[3]||'').trim();
|
|
3066
|
+
const el = {
|
|
3067
|
+
tag:addM[1].toLowerCase(), id:'', classes:[],
|
|
3068
|
+
text:'', html:'',
|
|
3069
|
+
styles:{}, pseudo:{}, mediaRules:[],
|
|
3070
|
+
beforeContent:null, afterContent:null,
|
|
3071
|
+
attrs:{}, events:[], children:[],
|
|
3072
|
+
_indent:ind, _anim:null,
|
|
3073
|
+
};
|
|
3074
|
+
// parse selector tokens: #id .class plain-word→id
|
|
3075
|
+
rawSel.split(/\s+/).filter(Boolean).forEach(tok => {
|
|
3076
|
+
if (tok.startsWith('#')) el.id = tok.slice(1);
|
|
3077
|
+
else if (tok.startsWith('.')) el.classes.push(tok.slice(1));
|
|
3078
|
+
else if (!el.id) el.id = tok;
|
|
3079
|
+
else el.classes.push(tok);
|
|
3080
|
+
});
|
|
3081
|
+
if (parent && !parent._isPage) parent.children.push(el);
|
|
3082
|
+
else elements.push(el);
|
|
3083
|
+
elStack.push({el, indent:ind});
|
|
3084
|
+
continue;
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
// ── properties inside an element ─────────────────────────
|
|
3088
|
+
const pe = elStack[elStack.length-1];
|
|
3089
|
+
if (!pe || ind <= pe.indent) continue;
|
|
3090
|
+
const el = pe.el;
|
|
3091
|
+
|
|
3092
|
+
let m2;
|
|
3093
|
+
|
|
3094
|
+
// content
|
|
3095
|
+
if ((m2=t.match(/^text\s+(.+)$/))) { el.text=str(m2[1]); continue; }
|
|
3096
|
+
if ((m2=t.match(/^html\s+(.+)$/))) { el.html=str(m2[1]); continue; }
|
|
3097
|
+
|
|
3098
|
+
// common attrs — shorthand keywords
|
|
3099
|
+
const ATTR_KW = {
|
|
3100
|
+
src:1,href:1,alt:1,title:1,type:1,name:1,value:1,
|
|
3101
|
+
for:1,action:1,method:1,target:1,rel:1,rows:1,cols:1,
|
|
3102
|
+
min:1,max:1,step:1,role:1,tabindex:1,colspan:1,rowspan:1,
|
|
3103
|
+
width:1,height:1,loading:1,decoding:1,crossorigin:1,
|
|
3104
|
+
enctype:1,accept:1,pattern:1,maxlength:1,minlength:1,
|
|
3105
|
+
download:1,sizes:1,srcset:1,poster:1,preload:1,
|
|
3106
|
+
sandbox:1,allow:1,frameborder:1,scrolling:1,
|
|
3107
|
+
};
|
|
3108
|
+
if ((m2=t.match(/^([\w-]+)\s+(.+)$/)) && ATTR_KW[m2[1]]) { el.attrs[m2[1]]=str(m2[2]); continue; }
|
|
3109
|
+
|
|
3110
|
+
// boolean attrs
|
|
3111
|
+
if (/^(checked|disabled|readonly|required|autoplay|loop|controls|muted|multiple|autofocus|hidden|open|selected|novalidate|async|defer|reversed|ismap|allowfullscreen|default|formnovalidate|spellcheck)$/.test(t))
|
|
3112
|
+
{ el.attrs[t]=''; continue; }
|
|
3113
|
+
|
|
3114
|
+
// aria-*, data-*
|
|
3115
|
+
if ((m2=t.match(/^aria-([\w-]+)\s+(.+)$/))) { el.attrs[`aria-${m2[1]}`]=str(m2[2]); continue; }
|
|
3116
|
+
if ((m2=t.match(/^data-([\w-]+)\s+(.+)$/))) { el.attrs[`data-${m2[1]}`]=str(m2[2]); continue; }
|
|
3117
|
+
|
|
3118
|
+
// generic attr fallback
|
|
3119
|
+
if ((m2=t.match(/^attr\s+([\w-]+)\s+(.+)$/))) { el.attrs[m2[1]]=str(m2[2]); continue; }
|
|
3120
|
+
|
|
3121
|
+
// class
|
|
3122
|
+
if ((m2=t.match(/^class\s+(.+)$/))) { str(m2[1]).split(/\s+/).forEach(c=>c&&el.classes.push(c)); continue; }
|
|
3123
|
+
|
|
3124
|
+
// style — any CSS property, camelCase or kebab
|
|
3125
|
+
if ((m2=t.match(/^style\s+([\w-]+)\s+(.+)$/))) { el.styles[camel(m2[1])]=str(m2[2]); continue; }
|
|
3126
|
+
|
|
3127
|
+
// pseudo-class styles: hover / focus / active / visited / checked / disabled / focusVisible / focusWithin / placeholder / selection
|
|
3128
|
+
if ((m2=t.match(/^(hover|focus|active|visited|checked|disabled|focus-visible|focus-within|placeholder|first-child|last-child|nth-child|not|invalid|valid)\s+([\w-]+)\s+(.+)$/)))
|
|
3129
|
+
{ if(!el.pseudo[m2[1]])el.pseudo[m2[1]]={}; el.pseudo[m2[1]][camel(m2[2])]=str(m2[3]); continue; }
|
|
3130
|
+
|
|
3131
|
+
// pseudo-elements: before / after / first-line / first-letter / marker / selection / backdrop
|
|
3132
|
+
if ((m2=t.match(/^before\s+(.+)$/))) { el.beforeContent=str(m2[1]); continue; }
|
|
3133
|
+
if ((m2=t.match(/^after\s+(.+)$/))) { el.afterContent=str(m2[1]); continue; }
|
|
3134
|
+
|
|
3135
|
+
// shorthand style helpers
|
|
3136
|
+
if ((m2=t.match(/^transition\s+(.+)$/))) { el.styles.transition=str(m2[1]); continue; }
|
|
3137
|
+
if ((m2=t.match(/^transform\s+(.+)$/))) { el.styles.transform=str(m2[1]); continue; }
|
|
3138
|
+
if ((m2=t.match(/^filter\s+(.+)$/))) { el.styles.filter=str(m2[1]); continue; }
|
|
3139
|
+
if ((m2=t.match(/^grid\s+(.+)$/))) { el.styles.gridTemplateColumns=str(m2[1]); continue; }
|
|
3140
|
+
if ((m2=t.match(/^flex\s+(.+)$/))) { el.styles.flex=str(m2[1]); continue; }
|
|
3141
|
+
if ((m2=t.match(/^gap\s+(.+)$/))) { el.styles.gap=str(m2[1]); continue; }
|
|
3142
|
+
if ((m2=t.match(/^bg\s+(.+)$/))) { el.styles.background=str(m2[1]); continue; }
|
|
3143
|
+
if ((m2=t.match(/^shadow\s+(.+)$/))) { el.styles.boxShadow=str(m2[1]); continue; }
|
|
3144
|
+
if ((m2=t.match(/^clip\s+(.+)$/))) { el.styles.clipPath=str(m2[1]); continue; }
|
|
3145
|
+
if ((m2=t.match(/^mask\s+(.+)$/))) { el.styles.mask=str(m2[1]); continue; }
|
|
3146
|
+
if ((m2=t.match(/^outline\s+(.+)$/))) { el.styles.outline=str(m2[1]); continue; }
|
|
3147
|
+
if ((m2=t.match(/^overflow\s+(.+)$/))) { el.styles.overflow=str(m2[1]); continue; }
|
|
3148
|
+
if ((m2=t.match(/^cursor\s+(.+)$/))) { el.styles.cursor=str(m2[1]); continue; }
|
|
3149
|
+
if ((m2=t.match(/^opacity\s+(.+)$/))) { el.styles.opacity=str(m2[1]); continue; }
|
|
3150
|
+
if ((m2=t.match(/^zindex\s+(.+)$/))) { el.styles.zIndex=str(m2[1]); continue; }
|
|
3151
|
+
if ((m2=t.match(/^var\s+(--[\w-]+)\s+(.+)$/))) { el.styles[m2[1]]=str(m2[2]); continue; }
|
|
3152
|
+
|
|
3153
|
+
// @media per-element: media "max-width:600px" prop value
|
|
3154
|
+
if ((m2=t.match(/^media\s+"([^"]+)"\s+([\w-]+)\s+(.+)$/)))
|
|
3155
|
+
{ el.mediaRules.push({q:m2[1], p:camel(m2[2]), v:str(m2[3])}); continue; }
|
|
3156
|
+
// media "value" (HTML attribute, e.g. on <link> or <source>)
|
|
3157
|
+
if ((m2=t.match(/^media\s+(.+)$/)) && !m2[1].trim().match(/^"[^"]*"\s+[\w-]+/))
|
|
3158
|
+
{ el.attrs.media=str(m2[1]); continue; }
|
|
3159
|
+
// placeholder "text" as HTML attribute (not pseudo-class)
|
|
3160
|
+
if ((m2=t.match(/^placeholder\s+(.+)$/)) && !m2[1].match(/^[\w-]/))
|
|
3161
|
+
{ el.attrs.placeholder=str(m2[1]); continue; }
|
|
3162
|
+
|
|
3163
|
+
// animate NAME DURATION? EASING?
|
|
3164
|
+
if ((m2=t.match(/^animate\s+(\w+)(?:\s+([\d.]+))?(?:\s+([\w-]+))?$/)))
|
|
3165
|
+
{ el._anim={name:m2[1], dur:parseFloat(m2[2]||0.4), ease:m2[3]||'ease'}; continue; }
|
|
3166
|
+
|
|
3167
|
+
// on EVENT: — raw JS handler, indented body
|
|
3168
|
+
if ((m2=t.match(/^on\s+([\w]+)\s*:?$/))) {
|
|
3169
|
+
const buf=[];
|
|
3170
|
+
while(i<lines.length){ const nr=lines[i],ni=indent(nr); if(!nr.trim()||ni<=ind)break; buf.push(nr.slice(ni)); i++; }
|
|
3171
|
+
el.events.push({ev:m2[1], code:buf.join('\n')});
|
|
3172
|
+
continue;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
// ── Render ────────────────────────────────────────────────
|
|
3177
|
+
const pseudoCSS = [];
|
|
3178
|
+
const mediaCSS = {};
|
|
3179
|
+
|
|
3180
|
+
const VOID = new Set(['area','base','br','col','embed','hr','img','input','link','meta','param','source','track','wbr']);
|
|
3181
|
+
|
|
3182
|
+
function ensureId(el) { if (!el.id) el.id=uid(); return el.id; }
|
|
3183
|
+
|
|
3184
|
+
function renderEl(el) {
|
|
3185
|
+
const tag = el.tag;
|
|
3186
|
+
|
|
3187
|
+
// Build inline style string
|
|
3188
|
+
let styleStr = Object.entries(el.styles).map(([k,v])=>`${kebab(k)}:${v}`).join(';');
|
|
3189
|
+
|
|
3190
|
+
// Animations
|
|
3191
|
+
if (el._anim) {
|
|
3192
|
+
const ANIMS = {
|
|
3193
|
+
fadeIn:'fadeIn',fadeOut:'fadeOut',
|
|
3194
|
+
slideIn:'slideIn',slideInRight:'slideInRight',
|
|
3195
|
+
slideUp:'slideUp',slideDown:'slideDown',
|
|
3196
|
+
pop:'pop',bounce:'bounce',spin:'spin',
|
|
3197
|
+
pulse:'pulse',shake:'shake',flip:'flip',
|
|
3198
|
+
zoom:'zoom',wiggle:'wiggle',typewriter:'typewriter',
|
|
3199
|
+
};
|
|
3200
|
+
const aname = ANIMS[el._anim.name]||el._anim.name;
|
|
3201
|
+
const fill = /^(bounce|spin|pulse)$/.test(el._anim.name)?'infinite':'both';
|
|
3202
|
+
const ease = el._anim.name==='spin'?'linear':el._anim.ease;
|
|
3203
|
+
styleStr += (styleStr?';':'')+`animation:${aname} ${el._anim.dur}s ${ease} ${fill}`;
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
// Pseudo-class rules
|
|
3207
|
+
Object.entries(el.pseudo).forEach(([pseudo, styles]) => {
|
|
3208
|
+
const r = Object.entries(styles).map(([k,v])=>`${kebab(k)}:${v}`).join(';');
|
|
3209
|
+
if (r) { ensureId(el); pseudoCSS.push(`#${el.id}:${pseudo}{${r}}`); }
|
|
3210
|
+
});
|
|
3211
|
+
|
|
3212
|
+
// before/after pseudo-elements
|
|
3213
|
+
if (el.beforeContent!==null) { ensureId(el); pseudoCSS.push(`#${el.id}::before{content:"${el.beforeContent.replace(/"/g,'\\"')}"}`); }
|
|
3214
|
+
if (el.afterContent!==null) { ensureId(el); pseudoCSS.push(`#${el.id}::after{content:"${el.afterContent.replace(/"/g,'\\"')}"}`); }
|
|
3215
|
+
|
|
3216
|
+
// @media rules
|
|
3217
|
+
el.mediaRules.forEach(({q,p,v}) => {
|
|
3218
|
+
const s = el.id ? `#${el.id}` : (el.classes[0] ? `.${el.classes[0]}` : tag);
|
|
3219
|
+
if (!mediaCSS[q]) mediaCSS[q]=[];
|
|
3220
|
+
mediaCSS[q].push(`${s}{${kebab(p)}:${v}}`);
|
|
3221
|
+
});
|
|
3222
|
+
|
|
3223
|
+
// Build attribute string
|
|
3224
|
+
const idA = el.id ? ` id="${el.id}"` : '';
|
|
3225
|
+
const clsA = el.classes.length ? ` class="${el.classes.join(' ')}"` : '';
|
|
3226
|
+
const styA = styleStr ? ` style="${styleStr}"` : '';
|
|
3227
|
+
const xtra = Object.entries(el.attrs).map(([k,v])=>v===''?` ${k}`:` ${k}="${v}"`).join('');
|
|
3228
|
+
const evts = el.events.map(({ev,code})=>{
|
|
3229
|
+
return ` data-ss-${ev}="${code.replace(/"/g,'"').replace(/\n/g,' ')}"`;
|
|
3230
|
+
}).join('');
|
|
3231
|
+
|
|
3232
|
+
if (VOID.has(tag)) return `<${tag}${idA}${clsA}${styA}${xtra}>\n`;
|
|
3233
|
+
const inner = el.html || el.text || el.children.map(renderEl).join('');
|
|
3234
|
+
return `<${tag}${idA}${clsA}${styA}${xtra}${evts}>${inner}</${tag}>\n`;
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
const bodyStyleStr = Object.entries(pageStyles).map(([k,v])=>`${kebab(k)}:${v}`).join(';');
|
|
3238
|
+
const bodyXtra = Object.entries(bodyAttrs).map(([k,v])=>` ${k}="${v}"`).join('');
|
|
3239
|
+
const bodyHtml = elements.map(renderEl).join('');
|
|
3240
|
+
|
|
3241
|
+
const mediaCSSStr = Object.entries(mediaCSS)
|
|
3242
|
+
.map(([q,rules])=>`@media (${q}){${rules.join('')}}`).join('\n');
|
|
3243
|
+
|
|
3244
|
+
const KEYFRAMES = `
|
|
3245
|
+
@keyframes fadeIn {0%{opacity:0;transform:translateY(16px)}100%{opacity:1;transform:none}}
|
|
3246
|
+
@keyframes fadeOut {0%{opacity:1}100%{opacity:0}}
|
|
3247
|
+
@keyframes slideIn {0%{transform:translateX(-40px);opacity:0}100%{transform:none;opacity:1}}
|
|
3248
|
+
@keyframes slideInRight {0%{transform:translateX(40px);opacity:0}100%{transform:none;opacity:1}}
|
|
3249
|
+
@keyframes slideUp {0%{transform:translateY(40px);opacity:0}100%{transform:none;opacity:1}}
|
|
3250
|
+
@keyframes slideDown {0%{transform:translateY(-40px);opacity:0}100%{transform:none;opacity:1}}
|
|
3251
|
+
@keyframes bounce {0%,100%{transform:translateY(0)}50%{transform:translateY(-18px)}}
|
|
3252
|
+
@keyframes pulse {0%,100%{opacity:1}50%{opacity:0.4}}
|
|
3253
|
+
@keyframes spin {to{transform:rotate(360deg)}}
|
|
3254
|
+
@keyframes shake {0%,100%{transform:translateX(0)}20%{transform:translateX(-8px)}40%{transform:translateX(8px)}60%{transform:translateX(-5px)}80%{transform:translateX(5px)}}
|
|
3255
|
+
@keyframes pop {0%{transform:scale(0.5);opacity:0}70%{transform:scale(1.1)}100%{transform:scale(1);opacity:1}}
|
|
3256
|
+
@keyframes flip {0%{transform:rotateY(-90deg);opacity:0}100%{transform:rotateY(0);opacity:1}}
|
|
3257
|
+
@keyframes zoom {0%{transform:scale(0);opacity:0}100%{transform:scale(1);opacity:1}}
|
|
3258
|
+
@keyframes wiggle {0%,100%{transform:rotate(0)}25%{transform:rotate(-8deg)}75%{transform:rotate(8deg)}}
|
|
3259
|
+
@keyframes typewriter {from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}`;
|
|
3260
|
+
|
|
3261
|
+
// All DOM events wired up
|
|
3262
|
+
const ALL_EVENTS = [
|
|
3263
|
+
'click','dblclick','contextmenu',
|
|
3264
|
+
'mousedown','mouseup','mouseover','mouseout','mouseenter','mouseleave','mousemove',
|
|
3265
|
+
'keydown','keyup','keypress',
|
|
3266
|
+
'input','change','focus','blur','submit','reset','select',
|
|
3267
|
+
'scroll','resize','wheel',
|
|
3268
|
+
'dragstart','drag','dragend','dragover','dragenter','dragleave','drop',
|
|
3269
|
+
'touchstart','touchmove','touchend','touchcancel',
|
|
3270
|
+
'pointerdown','pointermove','pointerup','pointercancel','pointerenter','pointerleave',
|
|
3271
|
+
'animationstart','animationend','animationiteration',
|
|
3272
|
+
'transitionstart','transitionend',
|
|
3273
|
+
'load','error','abort','canplay','play','pause','ended','timeupdate','volumechange',
|
|
3274
|
+
'copy','cut','paste','beforeinput',
|
|
3275
|
+
'fullscreenchange','visibilitychange',
|
|
3276
|
+
];
|
|
3277
|
+
const evScript = ALL_EVENTS.map(ev =>
|
|
3278
|
+
`document.querySelectorAll('[data-ss-${ev}]').forEach(function(el){el.addEventListener('${ev}',function(event){try{(new Function('event',this.getAttribute('data-ss-${ev}'))).call(this,event)}catch(e){console.error(e)}}.bind(el));});`
|
|
3279
|
+
).join('\n');
|
|
3280
|
+
|
|
3281
|
+
return `<!DOCTYPE html>
|
|
3282
|
+
<html lang="${pageLang}">
|
|
3283
|
+
<head>
|
|
3284
|
+
<meta charset="UTF-8">
|
|
3285
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
3286
|
+
<title>${pageTitle}</title>
|
|
3287
|
+
${headTags.join('\n')}
|
|
3288
|
+
<style>
|
|
3289
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
3290
|
+
${KEYFRAMES}
|
|
3291
|
+
${pseudoCSS.join('\n')}
|
|
3292
|
+
${mediaCSSStr}
|
|
3293
|
+
${cssBlocks.join('\n')}
|
|
3294
|
+
</style>
|
|
3295
|
+
</head>
|
|
3296
|
+
<body${bodyStyleStr?` style="${bodyStyleStr}"`:''}>
|
|
3297
|
+
${bodyHtml}<script>(function(){
|
|
3298
|
+
${evScript}
|
|
3299
|
+
${jsBlocks.join('\n')}
|
|
3300
|
+
})()</script>
|
|
3301
|
+
</body>
|
|
3302
|
+
</html>`;
|
|
3303
|
+
}
|
|
3304
|
+
|
|
2734
3305
|
// Saved web HTML for download
|
|
2735
3306
|
let _lastWebHTML = '';
|
|
2736
3307
|
|
|
@@ -2931,7 +3502,7 @@ function showToast(msg = '✓ Saved') {
|
|
|
2931
3502
|
// ── Update sidebar + filepath bar ───────────────────────────
|
|
2932
3503
|
function setCurrentFile(fullPath, name) {
|
|
2933
3504
|
currentFilePath = fullPath;
|
|
2934
|
-
currentFile = name || fullPath.split(/[\\/]/).pop();
|
|
3505
|
+
currentFile = name || (fullPath ? fullPath.split(/[\\/]/).pop() : 'untitled.ss');
|
|
2935
3506
|
document.getElementById('current-filename').textContent = currentFile;
|
|
2936
3507
|
document.getElementById('fp-path').textContent = fullPath || '';
|
|
2937
3508
|
document.getElementById('fp-path').title = fullPath || '';
|
|
@@ -2975,8 +3546,10 @@ async function doSave(filePath, toast = true) {
|
|
|
2975
3546
|
|
|
2976
3547
|
// ── File operations (override playground versions) ──────────
|
|
2977
3548
|
async function newFile() {
|
|
2978
|
-
|
|
2979
|
-
|
|
3549
|
+
// prompt() is broken in Electron with contextIsolation
|
|
3550
|
+
let name = 'untitled.ss';
|
|
3551
|
+
if (!window.electronAPI) { name = prompt('File name:', 'untitled.ss') || 'untitled.ss'; }
|
|
3552
|
+
const n = name.endsWith('.ss') ? name : name + '.ss';
|
|
2980
3553
|
const result = await api('/api/new', 'POST', { name: n });
|
|
2981
3554
|
if (result.ok) {
|
|
2982
3555
|
editor.value = result.content;
|
|
@@ -3242,21 +3815,27 @@ document.addEventListener('keydown', e => {
|
|
|
3242
3815
|
|
|
3243
3816
|
// ── Init ────────────────────────────────────────────────────
|
|
3244
3817
|
window.addEventListener('load', async () => {
|
|
3245
|
-
// Check if a file was passed in the URL
|
|
3246
3818
|
const params = new URLSearchParams(location.search);
|
|
3247
3819
|
const initFile = params.get('file');
|
|
3248
3820
|
if (initFile) {
|
|
3249
3821
|
await openFilePath(initFile);
|
|
3250
3822
|
} else {
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3823
|
+
try {
|
|
3824
|
+
const r = await api('/api/recent');
|
|
3825
|
+
if (r && r.files && r.files.length) {
|
|
3826
|
+
await openFilePath(r.files[0]);
|
|
3827
|
+
} else {
|
|
3828
|
+
editor.value = EXAMPLES.hello;
|
|
3829
|
+
updateHighlight(); updateLineNumbers();
|
|
3830
|
+
setCurrentFile(null, 'untitled.ss');
|
|
3831
|
+
}
|
|
3832
|
+
} catch(e) {
|
|
3833
|
+
editor.value = EXAMPLES.hello;
|
|
3834
|
+
updateHighlight(); updateLineNumbers();
|
|
3835
|
+
setCurrentFile(null, 'untitled.ss');
|
|
3257
3836
|
}
|
|
3258
3837
|
}
|
|
3259
|
-
refreshSidebar();
|
|
3838
|
+
try { refreshSidebar(); } catch(e) {}
|
|
3260
3839
|
});
|
|
3261
3840
|
|
|
3262
3841
|
// Hook editor input to markUnsaved (override plain updateEditor)
|