@bytefaceinc/web-lang 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,945 @@
1
+ # WEB Language Guide
2
+
3
+ WEB is a small declarative UI language that compiles `.web` source into HTML and CSS.
4
+
5
+ Run the compiler with:
6
+
7
+ ```bash
8
+ node compiler.js
9
+ ```
10
+
11
+ That legacy script mode reads `layout.web` from the current directory and writes `index.html` plus `styles.css`.
12
+
13
+ You can also use the CLI to compile any `.web` file next to its source:
14
+
15
+ ```bash
16
+ web home.web
17
+ web home
18
+ web .
19
+ web ./code/*
20
+ ```
21
+
22
+ CLI mode writes matching outputs such as `home.html` and `home.css`.
23
+
24
+ ## Recommended Build Order
25
+
26
+ WEB is easiest to use in this sequence:
27
+
28
+ 1. Define the HTML structure.
29
+ 2. Add a `define { ... }` block for variables, semantic types, and shared style inheritance.
30
+ 3. Add HTML attributes plus any top-level `::head` and `::script` blocks.
31
+ 4. Add states and animations with `::pseudo` blocks.
32
+ 5. Add responsive rules with `::media`.
33
+ 6. Use `raw` only for CSS that WEB does not express yet.
34
+
35
+ ## 1. Structure and HTML Output
36
+
37
+ Start by setting up one optional `define { ... }` block for shared declarations.
38
+
39
+ ### Define block
40
+
41
+ All variables, semantic type declarations, and CSS inheritance declarations must live inside a single top-level `define { ... }` block.
42
+
43
+ ```web
44
+ define {
45
+ @pageWidth = "960px";
46
+ Button actionButton;
47
+ actionButton primaryButton;
48
+ primaryButton extends actionButton;
49
+ }
50
+ ```
51
+
52
+ Rules:
53
+
54
+ - `define { ... }` is optional, but required if you use variables, type declarations, or style inheritance
55
+ - it must appear before rules
56
+ - only one `define { ... }` block is allowed
57
+ - only variables, type declarations, and style inheritance are allowed inside it
58
+
59
+ ### Type declarations
60
+
61
+ Use this form:
62
+
63
+ ```web
64
+ define {
65
+ BaseType NewType;
66
+ }
67
+ ```
68
+
69
+ Example:
70
+
71
+ ```web
72
+ define {
73
+ Button actionButton;
74
+ actionButton primaryButton;
75
+ Main pageMain;
76
+ Section heroSection;
77
+ Paragraph heroCopy;
78
+ }
79
+ ```
80
+
81
+ Rules:
82
+
83
+ - declarations live inside `define { ... }`
84
+ - types can inherit from other declared types
85
+ - the resolved base type decides which HTML tag is generated
86
+
87
+ Type declarations do not copy CSS. If you want one WEB object or class to inherit another object's styles, use `extends` inside `define { ... }`.
88
+
89
+ ### Supported HTML mapping
90
+
91
+ WEB does not let you write raw HTML tags directly. It infers them from the resolved base type.
92
+
93
+ Common built-in mappings:
94
+
95
+ - `Button` -> `<button>`
96
+ - `Heading`, `Heading1`, `H1` -> `<h1>`
97
+ - `Heading2`, `H2` -> `<h2>`
98
+ - `Heading3`, `H3` -> `<h3>`
99
+ - `Heading4`, `H4` -> `<h4>`
100
+ - `Heading5`, `H5` -> `<h5>`
101
+ - `Heading6`, `H6` -> `<h6>`
102
+ - `Paragraph`, `Text` -> `<p>`
103
+ - `Span` -> `<span>`
104
+ - `Section` -> `<section>`
105
+ - `Main` -> `<main>`
106
+ - `Header` -> `<header>`
107
+ - `Footer` -> `<footer>`
108
+ - `Nav`, `Navigation` -> `<nav>`
109
+ - `Article` -> `<article>`
110
+ - `Aside` -> `<aside>`
111
+ - `Figure` -> `<figure>`
112
+ - `FigureCaption`, `FigCaption` -> `<figcaption>`
113
+ - `Link`, `Anchor` -> `<a>`
114
+ - `Picture` -> `<picture>`
115
+ - `Source` -> `<source>`
116
+ - `Image`, `Img` -> `<img>`
117
+ - `Video` -> `<video>`
118
+ - `Audio` -> `<audio>`
119
+ - `Form` -> `<form>`
120
+ - `Label` -> `<label>`
121
+ - `Dialog` -> `<dialog>`
122
+ - `Details` -> `<details>`
123
+ - `Summary` -> `<summary>`
124
+ - `Input` -> `<input>`
125
+ - `TextArea` -> `<textarea>`
126
+ - `Select` -> `<select>`
127
+ - `Option` -> `<option>`
128
+ - `List`, `UnorderedList`, `BulletList` -> `<ul>`
129
+ - `OrderedList`, `NumberedList` -> `<ol>`
130
+ - `ListItem` -> `<li>`
131
+ - `Table` -> `<table>`
132
+ - `TableHead` -> `<thead>`
133
+ - `TableBody` -> `<tbody>`
134
+ - `TableFoot` -> `<tfoot>`
135
+ - `TableRow` -> `<tr>`
136
+ - `TableHeaderCell` -> `<th>`
137
+ - `TableCell` -> `<td>`
138
+ - `Code` -> `<code>`
139
+ - `Pre`, `Preformatted` -> `<pre>`
140
+ - `Strong` -> `<strong>`
141
+ - `Em`, `Emphasis` -> `<em>`
142
+ - `LineBreak`, `Br` -> `<br>`
143
+ - anything else -> `<div>`
144
+
145
+ Void elements such as `Image`, `Input`, `LineBreak`, and `Source` cannot contain children or content.
146
+
147
+ ### Dot notation
148
+
149
+ Dot notation builds hierarchy directly from the path.
150
+
151
+ ```web
152
+ heroSection.primaryButton.textContent = "Launch";
153
+ heroSection.primaryButton.padding = "14px 18px";
154
+ ```
155
+
156
+ That produces a `primaryButton` inside `heroSection`.
157
+
158
+ ### Nested blocks
159
+
160
+ Nested blocks are syntax sugar for the same structure.
161
+
162
+ ```web
163
+ pageMain {
164
+ heroSection {
165
+ primaryButton {
166
+ textContent = "Launch";
167
+ padding = "14px 18px";
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ These forms are equivalent:
174
+
175
+ ```web
176
+ pageMain.heroSection.primaryButton.textContent = "Launch";
177
+ ```
178
+
179
+ ```web
180
+ pageMain {
181
+ heroSection {
182
+ primaryButton {
183
+ textContent = "Launch";
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ Dot notation blocks are also supported:
190
+
191
+ ```web
192
+ pageMain.heroSection {
193
+ primaryButton {
194
+ textContent = "Launch";
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### Global style blocks
200
+
201
+ Two special top-level blocks map directly to global CSS selectors:
202
+
203
+ ```web
204
+ * {
205
+ boxSizing = "border-box";
206
+ }
207
+
208
+ html {
209
+ background = "#101522";
210
+ color = "#f7f6ff";
211
+ }
212
+ ```
213
+
214
+ That compiles to:
215
+
216
+ ```css
217
+ * {
218
+ box-sizing: border-box;
219
+ }
220
+
221
+ html {
222
+ background: #101522;
223
+ color: #f7f6ff;
224
+ }
225
+ ```
226
+
227
+ Rules:
228
+
229
+ - `* { ... }` and `html { ... }` are CSS-only global selector blocks
230
+ - they do not create HTML nodes
231
+ - they must be declared at the top level
232
+ - they are also allowed inside a top-level `::media` block
233
+ - nested `html { ... }` inside an element block is not allowed
234
+
235
+ ### Content properties
236
+
237
+ WEB has three reserved properties:
238
+
239
+ - `textContent`
240
+ - `innerHTML`
241
+ - `raw`
242
+
243
+ `textContent` inserts escaped text:
244
+
245
+ ```web
246
+ heroSection.heroCopy.textContent = "Safe plain text";
247
+ ```
248
+
249
+ `innerHTML` inserts raw HTML:
250
+
251
+ ```web
252
+ heroSection.heroCopy.innerHTML = "Read the <strong>docs</strong>.";
253
+ ```
254
+
255
+ ### HTML attributes with `::attrs`
256
+
257
+ Use `::attrs { ... }` when you want real HTML attributes on a normal WEB node.
258
+
259
+ ```web
260
+ docsLink {
261
+ textContent = "Docs";
262
+
263
+ ::attrs {
264
+ href = "/docs";
265
+ target = "_blank";
266
+ rel = "noreferrer";
267
+ ariaLabel = "Open docs";
268
+ }
269
+ }
270
+ ```
271
+
272
+ Rules:
273
+
274
+ - `::attrs` must be nested directly inside an element block
275
+ - attribute names use camelCase in WEB and compile to kebab-case in HTML
276
+ - `disabled = true;` emits a bare boolean attribute
277
+ - `open = true;` is useful for tags like `Dialog` and `Details`
278
+ - `false`, `null`, and `undefined` omit the attribute
279
+ - `class` and `style` are not allowed in `::attrs`
280
+ - `::attrs` is not allowed inside `styles { ... }`, pseudo blocks, or media blocks
281
+
282
+ ### Head tags with `::head`
283
+
284
+ Use `::head { ... }` when you want to add `meta` and `link` tags directly to the document `<head>`.
285
+
286
+ ```web
287
+ ::head {
288
+ meta {
289
+ name = "description";
290
+ content = "Landing page built in WEB.";
291
+ }
292
+
293
+ link {
294
+ rel = "help";
295
+ href = "compiler.md";
296
+ type = "text/markdown";
297
+ }
298
+ }
299
+ ```
300
+
301
+ That compiles to entries like:
302
+
303
+ ```html
304
+ <meta name="description" content="Landing page built in WEB.">
305
+ <link rel="help" href="compiler.md" type="text/markdown">
306
+ ```
307
+
308
+ Rules:
309
+
310
+ - `::head` must be declared at the top level
311
+ - it does not participate in CSS generation
312
+ - it only supports `meta { ... }` and `link { ... }` entries
313
+ - `meta` and `link` entries only support direct HTML attribute assignments
314
+ - attribute names use camelCase in WEB and compile to kebab-case in HTML
315
+ - custom head entries are inserted into `<head>` after the compiler’s default charset, viewport, title, and stylesheet tags
316
+
317
+ ### Script tags with `::script`
318
+
319
+ Use `::script { ... }` when you want the compiler to emit a literal `<script></script>` tag.
320
+
321
+ ```web
322
+ ::script {
323
+ src = "/assets/app.js";
324
+ defer = "true";
325
+
326
+ code {
327
+ function helloWorld () {
328
+ alert("hello world");
329
+ }
330
+ }
331
+ }
332
+ ```
333
+
334
+ That compiles to:
335
+
336
+ ```html
337
+ <script src="/assets/app.js" defer="true">
338
+ function helloWorld () {
339
+ alert("hello world");
340
+ }
341
+ </script>
342
+ ```
343
+
344
+ Rules:
345
+
346
+ - `::script` must be declared at the top level
347
+ - it does not participate in CSS generation
348
+ - it supports direct HTML attribute assignments like `src = "/app.js";`
349
+ - it supports one optional `code { ... }` block for inline script content
350
+ - the `code { ... }` body is copied into the output script tag as raw text
351
+ - script blocks render near the end of `<body>` after normal WEB nodes
352
+ - `defer = true;` emits a bare boolean attribute, while `defer = "true";` emits `defer="true"`
353
+
354
+ ### JavaScript injection
355
+
356
+ WEB supports compile-time JavaScript values with:
357
+
358
+ ```web
359
+ heroSection.heroCopy.textContent = js`
360
+ let count = 3;
361
+ let output = "";
362
+
363
+ for (let i = 0; i < count; i += 1) {
364
+ output += `Hello ${i} `;
365
+ }
366
+
367
+ return output;
368
+ `;
369
+ ```
370
+
371
+ How it works:
372
+
373
+ - the compiler wraps the contents in a function and executes it at compile time
374
+ - the final result is inserted into the generated output
375
+ - errors are rendered inline as text instead of crashing the compiler
376
+
377
+ ### JavaScript result order
378
+
379
+ WEB resolves a `js` block in this order:
380
+
381
+ 1. an explicit `return`
382
+ 2. text accumulated through `emit(value)`
383
+ 3. the last top-level declared variable inside the `js` block
384
+ 4. an empty string
385
+
386
+ Example:
387
+
388
+ ```web
389
+ heroSection.heroCopy.textContent = js`
390
+ emit("Hello ");
391
+ emit("world");
392
+ `;
393
+ ```
394
+
395
+ ### Structured fragment output
396
+
397
+ When `innerHTML` is used with `js`, the `js` block may return structured fragment nodes instead of raw tag strings.
398
+
399
+ Helpers available inside `js` blocks:
400
+
401
+ - `node(typeName, className?, options?)`
402
+ - `el(...)` as an alias for `node(...)`
403
+ - `fragment(children)`
404
+ - `text(value)`
405
+ - `html(value)`
406
+
407
+ Supported `node(...)` options:
408
+
409
+ - `textContent`
410
+ - `innerHTML`
411
+ - `children`
412
+ - `style`
413
+ - `attributes`
414
+
415
+ Use `::attrs { ... }` for normal WEB nodes. Use `node(..., { attributes: ... })` only for nodes generated inside `js`.
416
+
417
+ Example:
418
+
419
+ ```web
420
+ featureGrid.innerHTML = js`
421
+ const cards = [
422
+ { title: "Readable", copy: "Less tag-heavy JS output." },
423
+ { title: "Loop-friendly", copy: "Still generated at compile time." },
424
+ ];
425
+
426
+ return cards.map((card) =>
427
+ node("Article", "featureCard", {
428
+ children: [
429
+ node("Heading3", "featureTitle", { textContent: card.title }),
430
+ node("Paragraph", "featureCopy", { textContent: card.copy }),
431
+ ],
432
+ })
433
+ );
434
+ `;
435
+ ```
436
+
437
+ This keeps loops and conditional logic in JavaScript while still avoiding explicit raw HTML tags in the source.
438
+
439
+ ## 2. Basic Styling and Positioning
440
+
441
+ Once the structure exists, add normal CSS properties directly in WEB.
442
+
443
+ ### Property assignments
444
+
445
+ Any non-reserved property becomes CSS.
446
+
447
+ ```web
448
+ heroSection {
449
+ display = "grid";
450
+ gap = "18px";
451
+ padding = "32px";
452
+ backgroundColor = "#101522";
453
+ }
454
+ ```
455
+
456
+ Compiles to:
457
+
458
+ ```css
459
+ .heroSection {
460
+ display: grid;
461
+ gap: 18px;
462
+ padding: 32px;
463
+ background-color: #101522;
464
+ }
465
+ ```
466
+
467
+ Rules:
468
+
469
+ - CSS property names should be written in camelCase
470
+ - the compiler converts them to kebab-case in `styles.css`
471
+ - later assignments for the same property on the same rule win
472
+
473
+ ### Values
474
+
475
+ WEB accepts these value types:
476
+
477
+ - quoted strings: `"grid"`
478
+ - numbers: `1`, `0.95`, `-10`
479
+ - percentages: `50%`
480
+ - bare identifiers: `none`, `auto`, `inherit`
481
+ - variable references: `@pageWidth`
482
+ - template literals: `` `...` ``
483
+ - JavaScript inject blocks: `` js`...` ``
484
+
485
+ Examples:
486
+
487
+ ```web
488
+ heroSection.opacity = 0.95;
489
+ heroSection.width = 100%;
490
+ heroSection.display = grid;
491
+ heroSection.border = @borderRule;
492
+ ```
493
+
494
+ ### Variables
495
+
496
+ Use variables inside `define { ... }` to avoid repeating primitive values.
497
+
498
+ ```web
499
+ define {
500
+ @pageWidth = "960px";
501
+ @panelRadius = "24px";
502
+ @borderRule = "1px solid #dde5ee";
503
+ }
504
+ ```
505
+
506
+ Then reuse them:
507
+
508
+ ```web
509
+ pageMain.width = @pageWidth;
510
+ heroSection.border = @borderRule;
511
+ ```
512
+
513
+ Rules:
514
+
515
+ - variable names must start with `@`
516
+ - variables must be declared inside `define { ... }`
517
+ - variables can reference other variables
518
+ - variables only support simple values, not `js` blocks
519
+
520
+ ### Style inheritance
521
+
522
+ Use `extends` inside `define { ... }` when you want one WEB name to inherit another name's CSS.
523
+
524
+ Syntax:
525
+
526
+ ```web
527
+ define {
528
+ storeCard extends card;
529
+ }
530
+ ```
531
+
532
+ Example:
533
+
534
+ ```web
535
+ define {
536
+ Article card;
537
+ card storeCard;
538
+ storeCard extends card;
539
+ }
540
+
541
+ card {
542
+ padding = "20px";
543
+ borderRadius = "20px";
544
+ background = "#eef3ff";
545
+ }
546
+
547
+ storeCard {
548
+ background = "#ffffff";
549
+ }
550
+ ```
551
+
552
+ This means:
553
+
554
+ - `storeCard` inherits the CSS generated for `card`
555
+ - `storeCard` keeps its own explicit styles too
556
+ - explicit styles on `storeCard` win over inherited styles from `card`
557
+
558
+ Rules:
559
+
560
+ - style inheritance declarations must be declared inside `define { ... }`
561
+ - style inheritance is CSS-only and does not change HTML tag inference
562
+ - style inheritance is transitive
563
+ - circular style inheritance is not allowed
564
+ - closer ancestors win over farther ancestors
565
+ - explicit styles on the derived name win over inherited styles, even if the base rule appears later in the file
566
+
567
+ What gets inherited:
568
+
569
+ - normal property assignments
570
+ - nested descendant rules
571
+ - `styles { ... }` selectors
572
+ - pseudo blocks
573
+ - media-query blocks
574
+ - `raw` blocks anchored to the inherited selector
575
+
576
+ If you also want the derived object to share the same semantic HTML tag, combine type inheritance and style inheritance:
577
+
578
+ ```web
579
+ define {
580
+ Article card;
581
+ card storeCard;
582
+ storeCard extends card;
583
+ }
584
+ ```
585
+
586
+ ### Style scopes
587
+
588
+ Use `styles { ... }` when you want CSS selectors without creating matching HTML nodes.
589
+
590
+ This is especially useful for classes returned from `innerHTML = js\`...\``.
591
+
592
+ ```web
593
+ featureGrid {
594
+ innerHTML = js`
595
+ return node("Article", ["featureCard", "featureCardCool"], {
596
+ children: [
597
+ node("Heading3", ["featureTitle", "featureTitleCool"], {
598
+ textContent: "Readable",
599
+ }),
600
+ node("Paragraph", "featureCopy", {
601
+ textContent: "Styled from outer WEB syntax.",
602
+ }),
603
+ ],
604
+ });
605
+ `;
606
+
607
+ styles {
608
+ featureCard {
609
+ padding = "20px";
610
+ borderRadius = "20px";
611
+ }
612
+
613
+ featureCardCool {
614
+ background = "linear-gradient(180deg, #ffffff 0%, #eef6ff 100%)";
615
+ }
616
+
617
+ featureTitleCool {
618
+ color = "#225fbe";
619
+ }
620
+
621
+ featureCopy {
622
+ color = "#596780";
623
+ }
624
+ }
625
+ }
626
+ ```
627
+
628
+ Rules:
629
+
630
+ - `styles { ... }` emits descendant CSS selectors
631
+ - it does not create matching HTML nodes
632
+ - `textContent` and `innerHTML` are not allowed inside `styles`
633
+ - `raw` is still allowed inside `styles`
634
+
635
+ ### Selector generation
636
+
637
+ WEB maps paths to descendant class selectors.
638
+
639
+ ```web
640
+ pageMain.heroSection.primaryButton.backgroundColor = "#ff7070";
641
+ ```
642
+
643
+ Compiles to:
644
+
645
+ ```css
646
+ .pageMain .heroSection .primaryButton {
647
+ background-color: #ff7070;
648
+ }
649
+ ```
650
+
651
+ ## 3. Pseudo Selectors and Animations
652
+
653
+ WEB now has a first-class pseudo system.
654
+
655
+ ### Pseudo selector syntax
656
+
657
+ All pseudo selectors are written with a `::` prefix in WEB.
658
+
659
+ Example:
660
+
661
+ ```web
662
+ benefitCard {
663
+ background = "blue";
664
+
665
+ ::hover {
666
+ background = "red";
667
+ }
668
+ }
669
+ ```
670
+
671
+ Compiles to:
672
+
673
+ ```css
674
+ .benefitCard {
675
+ background: blue;
676
+ }
677
+
678
+ .benefitCard:hover {
679
+ background: red;
680
+ }
681
+ ```
682
+
683
+ ### Mapping rules
684
+
685
+ WEB normalizes pseudo names like this:
686
+
687
+ - pseudo-classes compile to a single colon in CSS
688
+ - pseudo-elements compile to a double colon in CSS
689
+ - camelCase pseudo names are converted to kebab-case
690
+
691
+ Examples:
692
+
693
+ - `::hover` -> `:hover`
694
+ - `::focusWithin` -> `:focus-within`
695
+ - `::nthChild(2n + 1)` -> `:nth-child(2n + 1)`
696
+ - `::before` -> `::before`
697
+ - `::firstLetter` -> `::first-letter`
698
+
699
+ ### Nested descendant rules
700
+
701
+ Pseudo blocks stay attached to the scope where they are declared.
702
+
703
+ ```web
704
+ benefitCard {
705
+ icon {
706
+ opacity = 0.4;
707
+ }
708
+
709
+ ::hover {
710
+ icon {
711
+ opacity = 1;
712
+ }
713
+ }
714
+ }
715
+ ```
716
+
717
+ Compiles to:
718
+
719
+ ```css
720
+ .benefitCard .icon {
721
+ opacity: 0.4;
722
+ }
723
+
724
+ .benefitCard:hover .icon {
725
+ opacity: 1;
726
+ }
727
+ ```
728
+
729
+ ### Pseudo selectors inside style scopes
730
+
731
+ Pseudo blocks also work inside `styles { ... }`.
732
+
733
+ ```web
734
+ fragmentMount {
735
+ styles {
736
+ card {
737
+ padding = "12px";
738
+
739
+ ::hover {
740
+ background = "#dbe7ff";
741
+ }
742
+ }
743
+ }
744
+ }
745
+ ```
746
+
747
+ ### `::keyframes`
748
+
749
+ Animations use a top-level `::keyframes` block.
750
+
751
+ Example:
752
+
753
+ ```web
754
+ card {
755
+ animation = "slideIn 3s";
756
+ }
757
+
758
+ ::keyframes slideIn {
759
+ from {
760
+ opacity = 0;
761
+ transform = "translateY(16px)";
762
+ }
763
+
764
+ 60% {
765
+ opacity = 1;
766
+ }
767
+
768
+ to {
769
+ opacity = 1;
770
+ transform = "translateY(0)";
771
+ }
772
+ }
773
+ ```
774
+
775
+ Compiles to:
776
+
777
+ ```css
778
+ .card {
779
+ animation: slideIn 3s;
780
+ }
781
+
782
+ @keyframes slideIn {
783
+ from {
784
+ opacity: 0;
785
+ transform: translateY(16px);
786
+ }
787
+
788
+ 60% {
789
+ opacity: 1;
790
+ }
791
+
792
+ to {
793
+ opacity: 1;
794
+ transform: translateY(0);
795
+ }
796
+ }
797
+ ```
798
+
799
+ Rules:
800
+
801
+ - `::keyframes` must be declared at the top level
802
+ - `::keyframes` cannot be nested inside element blocks or `styles { ... }`
803
+ - keyframe stages support `from`, `to`, and percentages like `50%`
804
+ - keyframe stages accept normal CSS property assignments
805
+
806
+ Invalid:
807
+
808
+ ```web
809
+ card {
810
+ ::keyframes slideIn {
811
+ from {
812
+ opacity = 0;
813
+ }
814
+ }
815
+ }
816
+ ```
817
+
818
+ ## 4. Media Queries
819
+
820
+ Media queries use the same `::` syntax.
821
+
822
+ ### Nested media rules
823
+
824
+ ```web
825
+ benefitCard {
826
+ padding = "20px";
827
+
828
+ ::media (max-width: 700px) {
829
+ padding = "14px";
830
+ }
831
+ }
832
+ ```
833
+
834
+ Compiles to:
835
+
836
+ ```css
837
+ .benefitCard {
838
+ padding: 20px;
839
+ }
840
+
841
+ @media (max-width: 700px) {
842
+ .benefitCard {
843
+ padding: 14px;
844
+ }
845
+ }
846
+ ```
847
+
848
+ ### Top-level media blocks
849
+
850
+ You can also wrap whole rule groups:
851
+
852
+ ```web
853
+ ::media (max-width: 700px) {
854
+ pageMain {
855
+ gap = "18px";
856
+ }
857
+
858
+ pageMain.heroSection {
859
+ padding = "20px";
860
+ }
861
+ }
862
+ ```
863
+
864
+ ### Combining media and pseudo selectors
865
+
866
+ Media blocks and pseudo blocks can be combined.
867
+
868
+ ```web
869
+ button {
870
+ ::media (max-width: 700px) {
871
+ ::hover {
872
+ background = "#2d5cff";
873
+ }
874
+ }
875
+ }
876
+ ```
877
+
878
+ This compiles to:
879
+
880
+ ```css
881
+ @media (max-width: 700px) {
882
+ .button:hover {
883
+ background: #2d5cff;
884
+ }
885
+ }
886
+ ```
887
+
888
+ Rules:
889
+
890
+ - `::media` accepts normal CSS media query text after the keyword
891
+ - media query conditions should be written in normal CSS syntax such as `max-width`, not camelCase
892
+ - media blocks only support CSS properties, selector blocks, pseudo blocks, `styles { ... }`, and `raw`
893
+ - `textContent`, `innerHTML`, type declarations, and variable declarations are not allowed inside media blocks
894
+
895
+ ## 5. Raw CSS Escape Hatch
896
+
897
+ `raw` still exists for CSS that WEB does not express yet.
898
+
899
+ Example:
900
+
901
+ ```web
902
+ heroSection.primaryButton.raw = `
903
+ transition: transform 0.3s ease;
904
+ &:hover {
905
+ transform: scale(1.05);
906
+ }
907
+ `;
908
+ ```
909
+
910
+ Use `raw` when you need:
911
+
912
+ - CSS selectors that WEB does not model yet
913
+ - at-rules other than `::media` and `::keyframes`
914
+ - complex CSS text that is easier to paste directly
915
+
916
+ `raw` supports:
917
+
918
+ - normal declarations
919
+ - nested selectors using `&`
920
+ - at-rules such as `@supports`
921
+
922
+ For new code, prefer:
923
+
924
+ - normal property assignments first
925
+ - `styles { ... }` for selector-only styling
926
+ - `::pseudo` blocks for states and pseudo-elements
927
+ - `::media` for responsive rules
928
+
929
+ ## Comments
930
+
931
+ WEB supports:
932
+
933
+ - line comments with `//`
934
+ - block comments with `/* ... */`
935
+
936
+ ## JavaScript Safety
937
+
938
+ JavaScript injection is designed not to crash the compiler.
939
+
940
+ - runtime errors are rendered inline as text
941
+ - syntax errors are rendered inline as text
942
+ - timed-out scripts are rendered inline as text
943
+ - when `innerHTML` generation fails, the error is still escaped as text rather than inserted as raw HTML
944
+
945
+ JavaScript blocks currently run with a 1-second timeout.