on master help sign in
htmlang's own attributes are layout; everything else is CSS
81f89d2iko
DESIGN.md+27−29
@@ -29,7 +29,7 @@ Children are indented under their parent. Attributes are comma-separated inside
29@el [
29@el [
30 padding 20,
30 padding 20,
31 background white,
31 background white,
32− rounded 8,
32+ border-radius 8,
33 box-shadow 0 2px 4px rgba(0,0,0,0.1)
33 box-shadow 0 2px 4px rgba(0,0,0,0.1)
34]
34]
35 Content
35 Content
@@ -39,7 +39,7 @@ An attribute is either a **style**, written `key value` (`padding 20`,
39`color red`), or an **HTML attribute**, written `key=value` (`id=main`,
39`color red`), or an **HTML attribute**, written `key=value` (`id=main`,
40`type=email`, `aria-label=Close`). Boolean HTML attributes are written bare
40`type=email`, `aria-label=Close`). Boolean HTML attributes are written bare
41(`required`, `disabled`, `open`). A comma inside `(...)` or `"..."` does not
41(`required`, `disabled`, `open`). A comma inside `(...)` or `"..."` does not
42−split attributes, so a font stack is written `font "Inter, sans-serif"`.
42+split attributes, so a font stack is written `font-family "Inter, sans-serif"`.
43
43
44### Text
44### Text
45
45
@@ -48,8 +48,8 @@ attributes is its content, and `{...}` puts elements inside a line of text:
48
48
49```
49```
50@paragraph
50@paragraph
51− This is {@text [bold] important} and this is a {@link https://example.com link}.
51+ This is {@text [font-weight bold] important} and this is a {@link https://example.com link}.
52−@text [bold, size 24, color #333] Hello world
52+@text [font-weight bold, font-size 24, color #333] Hello world
53@section [padding 8] Text after the attributes is content too.
53@section [padding 8] Text after the attributes is content too.
54```
54```
55
55
@@ -70,7 +70,7 @@ line: `--` later in a line is ordinary text.
70gets the indented children.
70gets the indented children.
71
71
72```
72```
73−@el [padding 16, background blue, rounded 8] > @link https://example.com
73+@el [padding 16, background blue, border-radius 8] > @link https://example.com
74 @text [color white] Get Started
74 @text [color white] Get Started
75```
75```
76
76
@@ -181,39 +181,34 @@ Use them like elements; your own `@let` with the same name takes precedence.
181
181
182## Attributes
182## Attributes
183
183
184−### Layout and sizing
184+### Layout
185+
186+htmlang's own attributes are about layout, in the spirit of elm-ui: how an
187+element sits in its row or column.
185
188
186| Attribute | Effect |
189| Attribute | Effect |
187|---|---|
190|---|---|
188| `spacing N` | Gap between children |
191| `spacing N` | Gap between children |
189−| `padding N` / `padding Y X` / `padding T R B L` | Padding (also `padding-x`, `padding-y`) |
190−| `margin ...` | Margin, same forms as padding (also `margin-x`, `margin-y`) |
191| `width fill` / `width N` / `width shrink` | Take remaining space, exact size, or fit content |
192| `width fill` / `width N` / `width shrink` | Take remaining space, exact size, or fit content |
192| `height fill` / `height N` / `height shrink` | Same for height |
193| `height fill` / `height N` / `height shrink` | Same for height |
193| `center-x`, `center-y` | Center within the parent |
194| `center-x`, `center-y` | Center within the parent |
194| `align-left`, `align-right`, `align-top`, `align-bottom` | Align within the parent |
195| `align-left`, `align-right`, `align-top`, `align-bottom` | Align within the parent |
195| `wrap` | Let a row wrap |
196| `wrap` | Let a row wrap |
196| `grid-cols N`, `grid-rows N`, `col-span N`, `row-span N` | Grid layout |
197| `grid-cols N`, `grid-rows N`, `col-span N`, `row-span N` | Grid layout |
197−| `hidden` | `display: none` |
198
198
199### Style
199### Style
200
200
201−| Attribute | Effect |
202−|---|---|
203−| `border N COLOR` (also `border-top` etc.) | Solid border |
204−| `rounded N` | Border radius |
205−| `bold`, `italic`, `underline` | Text style |
206−| `size N` | Font size |
207−| `font NAME` | Font family |
208−| `line-clamp N` | Cut text off after N lines |
209−
210−**Any standard CSS property** can also be used as an attribute, with the same
201+Everything else is CSS: **any standard CSS property** is an attribute, with
211−name and value: `background red`, `opacity 0.5`, `cursor pointer`,
202+the same name and value: `padding 20`, `font-weight bold`, `font-size 18`,
203+`border 1 solid #e5e7eb`, `border-radius 8`, `background red`,
212−`z-index 10`, `margin-top 16`, `grid-template-areas "a b"`, and so on.
204+`display none`, `grid-template-areas "a b"`, and so on. `line-clamp N` also
205+adds the `-webkit-box` declarations browsers still need to cut text off after
206+N lines.
213
207
214−**Units.** For lengths, a bare number is pixels (`padding 20` is `20px`).
208+**Units.** For lengths, a bare number is pixels (`padding 20` is `20px`,
209+`border 1 solid red` is `1px solid red`). Values with a unit, keywords and CSS
215−Values with a unit, keywords and CSS functions are passed through: `width 50%`,
210+functions are passed through: `width 50%`, `margin 0 auto`,
216−`margin 0 auto`, `max-width min(100%, 800px)`.
211+`max-width min(100%, 800px)`.
217
212
218### HTML attributes
213### HTML attributes
219
214
@@ -225,7 +220,7 @@ share a name without clashing:
225
220
226```
221```
227@image [width=800, width 200, alt=A photo] photo.jpg
222@image [width=800, width 200, alt=A photo] photo.jpg
228−@select [size=4, size 18]
223+@select [size=4, font-size 18]
229 @option One
224 @option One
230```
225```
231
226
@@ -262,7 +257,7 @@ attribute out.
262
257
263```
258```
264@let active true
259@let active true
265−@el [background if($active, blue, gray), if($active, bold), padding if($active, 12)]
260+@el [background if($active, blue, gray), if($active, font-weight bold), padding if($active, 12)]
266 Conditionally styled
261 Conditionally styled
267```
262```
268
263
@@ -278,11 +273,11 @@ attribute out.
278-- A quoted string, with $variables interpolated
273-- A quoted string, with $variables interpolated
279@let greeting "Hello $primary"
274@let greeting "Hello $primary"
280-- An attribute bundle, used as [$card]
275-- An attribute bundle, used as [$card]
281−@let card [padding 20, background white, rounded 8]
276+@let card [padding 20, background white, border-radius 8]
282-- A function: a @let with an indented body
277-- A function: a @let with an indented body
283@let panel $title $tone=neutral
278@let panel $title $tone=neutral
284 @el [$card]
279 @el [$card]
285− @text [bold] $title
280+ @text [font-weight bold] $title
286 @children
281 @children
287
282
288@panel [title Welcome]
283@panel [title Welcome]
@@ -307,7 +302,7 @@ A function is called like an element:
307@let note $kind=info
302@let note $kind=info
308 @style
303 @style
309 .title { font-weight: bold; }
304 .title { font-weight: bold; }
310− @el [padding 12, rounded 6, background #eff6ff]
305+ @el [padding 12, border-radius 6, background #eff6ff]
311 @text [class=title] $kind
306 @text [class=title] $kind
312 @children
307 @children
313
308
@@ -500,6 +495,9 @@ removed form with its replacement.
500| `$a ~ " " ~ $b` | `"$a $b"` |
495| `$a ~ " " ~ $b` | `"$a $b"` |
501| `@each $x in LIST [page N]` | Split the list, or filter it with `@if` |
496| `@each $x in LIST [page N]` | Split the list, or filter it with `@if` |
502| `...$bundle` | `$bundle` |
497| `...$bundle` | `$bundle` |
498+| `bold`, `italic`, `underline`, `hidden` | `font-weight bold`, `font-style italic`, `text-decoration underline`, `display none` |
499+| `size`, `font`, `rounded`, `padding-x/y`, `margin-x/y` | `font-size`, `font-family`, `border-radius`, `padding-inline/block`, `margin-inline/block` |
500+| `border N COLOR` (solid implied) | `border N solid COLOR` |
503| `@each $a, $b in A x, B y` (items split on spaces) | Records: `@data $rows [{"a": "A", "b": "x"}, ...]` and `@each $row in $rows` with `$row.a` |
501| `@each $a, $b in A x, B y` (items split on spaces) | Records: `@data $rows [{"a": "A", "b": "x"}, ...]` and `@each $row in $rows` with `$row.a` |
504| `$_index` | `@each $item, $i in ...` |
502| `$_index` | `@each $item, $i in ...` |
505| `$posts._count`, `$posts._keys`, `$posts.STEM.key` | `length($posts)`; nothing; `@each $post in $posts` with `$post.file` |
503| `$posts._count`, `$posts._keys`, `$posts.STEM.key` | `length($posts)`; nothing; `@each $post in $posts` with `$post.file` |
README.md+6−6
@@ -11,15 +11,15 @@ A minimalist layout language inspired by [elm-ui](https://package.elm-lang.org/p
11@let primary #3b82f6
11@let primary #3b82f6
12
12
13@let card $title
13@let card $title
14− @el [padding 20, background white, rounded 8, border 1 #e5e7eb, hover:border 1 $primary, transition all 0.15s ease]
14+ @el [padding 20, background white, border-radius 8, border 1 solid #e5e7eb, hover:border 1 solid $primary, transition all 0.15s ease]
15− @text [bold] $title
15+ @text [font-weight bold] $title
16 @children
16 @children
17
17
18@column [max-width 800, center-x, padding 40, spacing 20]
18@column [max-width 800, center-x, padding 40, spacing 20]
19− @text [bold, size 32] Hello
19+ @text [font-weight bold, font-size 32] Hello
20
20
21 @paragraph
21 @paragraph
22− Built with {@text [bold, color $primary] htmlang}.
22+ Built with {@text [font-weight bold, color $primary] htmlang}.
23
23
24 @row [wrap, spacing 10]
24 @row [wrap, spacing 10]
25 @card [title Simple]
25 @card [title Simple]
@@ -97,10 +97,10 @@ Prefixes apply a style conditionally:
97```
97```
98@let primary #3b82f6
98@let primary #3b82f6
99@let gap = 8 * 2
99@let gap = 8 * 2
100−@let card [padding 20, rounded 8]
100+@let card [padding 20, border-radius 8]
101@let button $label
101@let button $label
102 @el [$card, background $primary]
102 @el [$card, background $primary]
103− @text [color white, bold] $label
103+ @text [color white, font-weight bold] $label
104 @children
104 @children
105
105
106@button [label Click me, padding 12]
106@button [label Click me, padding 12]
crates/htmlang-core/src/codegen.rs+12−89
@@ -1736,19 +1736,6 @@ fn attrs_to_css(
1736 push_css(&mut css, "padding", &css_px_multi(v));
1736 push_css(&mut css, "padding", &css_px_multi(v));
1737 }
1737 }
1738 }
1738 }
1739− "padding-x" => {
1740− if let Some(v) = val {
1741− // Logical property covers both inline sides in one
1742− // declaration; for symmetric values this is visually
1743− // identical to padding-left/right in LTR and RTL.
1744− push_css(&mut css, "padding-inline", &css_px(v));
1745− }
1746− }
1747− "padding-y" => {
1748− if let Some(v) = val {
1749− push_css(&mut css, "padding-block", &css_px(v));
1750− }
1751− }
1752 "padding-top" => {
1739 "padding-top" => {
1753 if let Some(v) = val {
1740 if let Some(v) = val {
1754 push_css(&mut css, "padding-top", &css_px(v));
1741 push_css(&mut css, "padding-top", &css_px(v));
@@ -1860,68 +1847,6 @@ fn attrs_to_css(
1860 _ => push_css(&mut css, "margin-top", "auto"),
1847 _ => push_css(&mut css, "margin-top", "auto"),
1861 },
1848 },
1862
1849
1863− // Style
1864− "border" => {
1865− if let Some(v) = val {
1866− let parts: Vec<&str> = v.splitn(2, ' ').collect();
1867− if parts.len() == 2 {
1868− push_css(
1869− &mut css,
1870− "border",
1871− &format!("{} solid {}", css_px(parts[0]), parts[1]),
1872− );
1873− } else {
1874− push_css(
1875− &mut css,
1876− "border",
1877− &format!("{} solid currentColor", css_px(parts[0])),
1878− );
1879− }
1880− }
1881− }
1882− "border-top" | "border-bottom" | "border-left" | "border-right" => {
1883− if let Some(v) = val {
1884− let parts: Vec<&str> = v.splitn(2, ' ').collect();
1885− if parts.len() == 2 {
1886− push_css(
1887− &mut css,
1888− effective_key,
1889− &format!("{} solid {}", css_px(parts[0]), parts[1]),
1890− );
1891− } else {
1892− push_css(
1893− &mut css,
1894− effective_key,
1895− &format!("{} solid currentColor", css_px(parts[0])),
1896− );
1897− }
1898− }
1899− }
1900− "rounded" => {
1901− if let Some(v) = val {
1902− push_css(&mut css, "border-radius", &css_px(v));
1903− }
1904− }
1905− "bold" => push_css(&mut css, "font-weight", "bold"),
1906− "italic" => push_css(&mut css, "font-style", "italic"),
1907− "underline" => push_css(&mut css, "text-decoration", "underline"),
1908− "size" => {
1909− if let Some(v) = val {
1910− push_css(&mut css, "font-size", &css_px(v));
1911− }
1912− }
1913− "font" => {
1914− if let Some(v) = val {
1915− // `font "Inter, sans-serif"` quotes a whole font stack so
1916− // its commas don't split the attribute list.
1917− let v = match v.strip_prefix('"').and_then(|v| v.strip_suffix('"')) {
1918− Some(stack) if stack.contains(',') => stack,
1919− _ => v,
1920− };
1921− push_css(&mut css, "font-family", v);
1922− }
1923− }
1924−
1925 // Typography
1850 // Typography
1926 "line-height" => {
1851 "line-height" => {
1927 if let Some(v) = val {
1852 if let Some(v) = val {
@@ -1956,9 +1881,6 @@ fn attrs_to_css(
1956 }
1881 }
1957 }
1882 }
1958
1883
1959− // Display & visibility
1960− "hidden" => push_css(&mut css, "display", "none"),
1961−
1962 // Effects
1884 // Effects
1963
1885
1964 // Flow
1886 // Flow
@@ -2132,17 +2054,6 @@ fn attrs_to_css(
2132 push_css(&mut css, "margin", &css_px_multi(v));
2054 push_css(&mut css, "margin", &css_px_multi(v));
2133 }
2055 }
2134 }
2056 }
2135− "margin-x" => {
2136− if let Some(v) = val {
2137− push_css(&mut css, "margin-inline", &css_px(v));
2138− }
2139− }
2140− "margin-y" => {
2141− if let Some(v) = val {
2142− push_css(&mut css, "margin-block", &css_px(v));
2143− }
2144− }
2145−
2146 // Container queries
2057 // Container queries
2147 "container" => {
2058 "container" => {
2148 push_css(&mut css, "container-type", "inline-size");
2059 push_css(&mut css, "container-type", "inline-size");
@@ -2253,6 +2164,18 @@ fn attrs_to_css(
2253
2164
2254 // Any other standard CSS property is copied through, with `px`
2165 // Any other standard CSS property is copied through, with `px`
2255 // added to bare numbers where the property takes a length.
2166 // added to bare numbers where the property takes a length.
2167+ // `font-family "Inter, sans-serif"`: the quotes only keep the
2168+ // stack's commas from splitting the attribute list.
2169+ "font-family" => {
2170+ if let Some(v) = val {
2171+ let v = match v.strip_prefix('"').and_then(|v| v.strip_suffix('"')) {
2172+ Some(stack) if stack.contains(',') => stack,
2173+ _ => v,
2174+ };
2175+ push_css(&mut css, "font-family", v);
2176+ }
2177+ }
2178+
2256 key if crate::vocab::is_css_property(key) => {
2179 key if crate::vocab::is_css_property(key) => {
2257 if let Some(v) = val {
2180 if let Some(v) = val {
2258 if crate::vocab::is_length_property(key) {
2181 if crate::vocab::is_length_property(key) {
crates/htmlang-core/src/codegen_tests.rs+1−1
@@ -47,7 +47,7 @@ mod tests {
47
47
48 #[test]
48 #[test]
49 fn dev_mode_is_deterministic_across_runs() {
49 fn dev_mode_is_deterministic_across_runs() {
50− let src = "@row [spacing 10]\n @text [bold] a\n @text [italic] b\n";
50+ let src = "@row [spacing 10]\n @text [font-weight bold] a\n @text [font-style italic] b\n";
51 let r1 = parse(src);
51 let r1 = parse(src);
52 let r2 = parse(src);
52 let r2 = parse(src);
53 assert_eq!(generate_dev(&r1.document), generate_dev(&r2.document));
53 assert_eq!(generate_dev(&r1.document), generate_dev(&r2.document));
crates/htmlang-core/src/parser.rs+12−4
@@ -1534,6 +1534,16 @@ fn removed_attribute_hint(name: &str) -> Option<&'static str> {
1534 "truncate" => Some("use the `$truncate` bundle"),
1534 "truncate" => Some("use the `$truncate` bundle"),
1535 "critical" => Some("remove it"),
1535 "critical" => Some("remove it"),
1536 "grid" => Some("use `@grid`, or `display grid`"),
1536 "grid" => Some("use `@grid`, or `display grid`"),
1537+ "bold" => Some("use `font-weight bold`"),
1538+ "italic" => Some("use `font-style italic`"),
1539+ "underline" => Some("use `text-decoration underline`"),
1540+ "size" => Some("use `font-size`"),
1541+ "rounded" => Some("use `border-radius`"),
1542+ "hidden" => Some("use `display none`"),
1543+ "padding-x" => Some("use `padding-inline`"),
1544+ "padding-y" => Some("use `padding-block`"),
1545+ "margin-x" => Some("use `margin-inline`"),
1546+ "margin-y" => Some("use `margin-block`"),
1537 _ => None,
1547 _ => None,
1538 }
1548 }
1539}
1549}
@@ -1730,8 +1740,6 @@ const NUMERIC_ATTRS: &[&str] = &[
1730 "spacing",
1740 "spacing",
1731 "gap",
1741 "gap",
1732 "padding",
1742 "padding",
1733− "padding-x",
1734− "padding-y",
1735 "padding-top",
1743 "padding-top",
1736 "padding-bottom",
1744 "padding-bottom",
1737 "padding-left",
1745 "padding-left",
@@ -1740,8 +1748,8 @@ const NUMERIC_ATTRS: &[&str] = &[
1740 "max-width",
1748 "max-width",
1741 "min-height",
1749 "min-height",
1742 "max-height",
1750 "max-height",
1743− "rounded",
1751+ "border-radius",
1744− "size",
1752+ "font-size",
1745 "column-gap",
1753 "column-gap",
1746 "row-gap",
1754 "row-gap",
1747 "top",
1755 "top",
crates/htmlang-core/src/std.hl+1−1
@@ -15,7 +15,7 @@
15
15
16-- Outlined, fully rounded label.
16-- Outlined, fully rounded label.
17@let chip
17@let chip
18− @text [display inline-flex, align-items center, column-gap 4px, padding 4px 12px, border-radius 9999px, font-size 0.875rem, border 1 currentColor]
18+ @text [display inline-flex, align-items center, column-gap 4px, padding 4px 12px, border-radius 9999px, font-size 0.875rem, border 1 solid currentColor]
19 @children
19 @children
20
20
21-- Circular frame for an image or initials.
21-- Circular frame for an image or initials.
crates/htmlang-core/src/vocab.rs+6−5
@@ -10,11 +10,9 @@
10/// htmlang's own attributes (those that are neither a CSS property nor an
10/// htmlang's own attributes (those that are neither a CSS property nor an
11/// HTML attribute).
11/// HTML attribute).
12pub const HTMLANG_ATTRIBUTES: &[&str] = &[
12pub const HTMLANG_ATTRIBUTES: &[&str] = &[
13− "align-bottom", "align-left", "align-right", "align-top", "bold",
13+ "align-bottom", "align-left", "align-right", "align-top", "center-x", "center-y",
14− "center-x", "center-y", "col-span", "grid-cols", "grid-rows",
14+ "col-span", "grid-cols", "grid-rows", "inline", "ordered", "responsive", "row-span",
15− "hidden", "inline", "italic", "margin-x", "margin-y", "ordered", "padding-x", "padding-y",
16− "responsive", "rounded", "row-span", "size", "spacing", "underline",
17− "wrap",
15+ "spacing", "wrap",
18];
16];
19
17
20/// Standard CSS properties, sorted. Any of these can be written as an
18/// Standard CSS properties, sorted. Any of these can be written as an
@@ -153,6 +151,9 @@ pub fn is_length_property(name: &str) -> bool {
153 "top", "right", "bottom", "left", "width", "height", "font-size", "block-size",
151 "top", "right", "bottom", "left", "width", "height", "font-size", "block-size",
154 "inline-size", "min-block-size", "max-block-size", "min-inline-size", "max-inline-size",
152 "inline-size", "min-block-size", "max-block-size", "min-inline-size", "max-inline-size",
155 "text-indent", "flex-basis", "perspective", "text-decoration-thickness",
153 "text-indent", "flex-basis", "perspective", "text-decoration-thickness",
154+ // Shorthands that start with a width: `border 1 solid red`
155+ "border", "border-top", "border-right", "border-bottom", "border-left", "border-block",
156+ "border-inline", "outline", "column-rule",
156 ];
157 ];
157 EXACT.contains(&name)
158 EXACT.contains(&name)
158 || PREFIXES.iter().any(|p| name.starts_with(p))
159 || PREFIXES.iter().any(|p| name.starts_with(p))
editors/vscode/snippets/htmlang.json+13−13
@@ -14,8 +14,8 @@
14 "prefix": "@let-fn",
14 "prefix": "@let-fn",
15 "body": [
15 "body": [
16 "@let ${1:component} \\$${2:title}",
16 "@let ${1:component} \\$${2:title}",
17− " @el [${3:padding 20, background white, rounded 8}]",
17+ " @el [${3:padding 20, background white, border-radius 8}]",
18− " @text [bold] \\$$2",
18+ " @text [font-weight bold] \\$$2",
19 " @children",
19 " @children",
20 "",
20 "",
21 "@$1 [$2 ${4:Hello}]",
21 "@$1 [$2 ${4:Hello}]",
@@ -29,7 +29,7 @@
29 "@let ${1:layout} \\$${2:title}",
29 "@let ${1:layout} \\$${2:title}",
30 " @column [${3:max-width 800, center-x, padding 40}]",
30 " @column [${3:max-width 800, center-x, padding 40}]",
31 " @slot ${4:header}",
31 " @slot ${4:header}",
32− " @text [bold, size 32] \\$$2",
32+ " @text [font-weight bold, font-size 32] \\$$2",
33 " @slot ${5:content}",
33 " @slot ${5:content}",
34 " @slot ${6:footer}",
34 " @slot ${6:footer}",
35 "",
35 "",
@@ -42,8 +42,8 @@
42 "Card component": {
42 "Card component": {
43 "prefix": "@card",
43 "prefix": "@card",
44 "body": [
44 "body": [
45− "@el [padding ${1:20}, background ${2:white}, rounded ${3:8}, border 1 ${4:#e5e7eb}, hover:border 1 ${5:\\$primary}, transition all 0.15s ease]",
45+ "@el [padding ${1:20}, background ${2:white}, border-radius ${3:8}, border 1 solid ${4:#e5e7eb}, hover:border 1 solid ${5:\\$primary}, transition all 0.15s ease]",
46− " @text [bold, size ${6:18}] ${7:Title}",
46+ " @text [font-weight bold, font-size ${6:18}] ${7:Title}",
47 " $0"
47 " $0"
48 ],
48 ],
49 "description": "Card with border, hover effect, and title"
49 "description": "Card with border, hover effect, and title"
@@ -63,9 +63,9 @@
63 "Navigation bar": {
63 "Navigation bar": {
64 "prefix": "@navbar",
64 "prefix": "@navbar",
65 "body": [
65 "body": [
66− "@nav [padding-x ${1:20}, padding-y ${2:12}, background ${3:#1a1a2e}]",
66+ "@nav [padding-inline ${1:20}, padding-block ${2:12}, background ${3:#1a1a2e}]",
67 " @row [spacing ${4:20}, align-items center]",
67 " @row [spacing ${4:20}, align-items center]",
68− " @text [bold, color ${5:white}, size ${6:18}] ${7:Logo}",
68+ " @text [font-weight bold, color ${5:white}, font-size ${6:18}] ${7:Logo}",
69 " @el [width fill]",
69 " @el [width fill]",
70 " @data \\$links [{\"label\": \"${8:Home}\", \"url\": \"/\"}, {\"label\": \"About\", \"url\": \"/about\"}]",
70 " @data \\$links [{\"label\": \"${8:Home}\", \"url\": \"/\"}, {\"label\": \"About\", \"url\": \"/about\"}]",
71 " @each \\$link in \\$links",
71 " @each \\$link in \\$links",
@@ -127,8 +127,8 @@
127 "body": [
127 "body": [
128 "@form [method=${1:post}, spacing ${2:16}] ${3:/submit}",
128 "@form [method=${1:post}, spacing ${2:16}] ${3:/submit}",
129 " @label [for=${4:email}] ${5:Email}",
129 " @label [for=${4:email}] ${5:Email}",
130− " @input [type=${6:email}, name=$4, id=$4, required, padding 8, border 1 #d1d5db, rounded 4]",
130+ " @input [type=${6:email}, name=$4, id=$4, required, padding 8, border 1 solid #d1d5db, border-radius 4]",
131− " @button [type=submit, padding 10, background ${7:\\$primary}, color white, rounded 4, bold]",
131+ " @button [type=submit, padding 10, background ${7:\\$primary}, color white, border-radius 4, font-weight bold]",
132 " ${8:Submit}",
132 " ${8:Submit}",
133 " $0"
133 " $0"
134 ],
134 ],
@@ -146,11 +146,11 @@
146 "prefix": "@hero",
146 "prefix": "@hero",
147 "body": [
147 "body": [
148 "@column [center-x, center-y, min-height ${1:80vh}, padding ${2:40}, spacing ${3:20}]",
148 "@column [center-x, center-y, min-height ${1:80vh}, padding ${2:40}, spacing ${3:20}]",
149− " @text [bold, size ${4:48}, text-align center] ${5:Hero Title}",
149+ " @text [font-weight bold, font-size ${4:48}, text-align center] ${5:Hero Title}",
150 " @paragraph [max-width ${6:600}, text-align center, color ${7:#666}, line-height 1.6]",
150 " @paragraph [max-width ${6:600}, text-align center, color ${7:#666}, line-height 1.6]",
151 " ${8:Hero description text}",
151 " ${8:Hero description text}",
152− " @el [padding 12 24, background ${9:\\$primary}, rounded 8] > @link ${10:#}",
152+ " @el [padding 12 24, background ${9:\\$primary}, border-radius 8] > @link ${10:#}",
153− " @text [color white, bold] ${11:Get Started}",
153+ " @text [color white, font-weight bold] ${11:Get Started}",
154 " $0"
154 " $0"
155 ],
155 ],
156 "description": "Hero section with title, description, and CTA"
156 "description": "Hero section with title, description, and CTA"
@@ -237,7 +237,7 @@
237 "prefix": "@address",
237 "prefix": "@address",
238 "body": [
238 "body": [
239 "@address",
239 "@address",
240− " @text [bold] ${1:Name}",
240+ " @text [font-weight bold] ${1:Name}",
241 " @link mailto:${2:email@example.com} $2",
241 " @link mailto:${2:email@example.com} $2",
242 " $0"
242 " $0"
243 ],
243 ],
examples/blog.hl+7−7
@@ -7,24 +7,24 @@
7@let card [
7@let card [
8 padding 24,
8 padding 24,
9 background white,
9 background white,
10− rounded 10,
10+ border-radius 10,
11− border 1 #e5e7eb,
11+ border 1 solid #e5e7eb,
12 box-shadow 0 1px 3px rgba(0,0,0,0.05),
12 box-shadow 0 1px 3px rgba(0,0,0,0.05),
13 transition all 0.15s ease
13 transition all 0.15s ease
14]
14]
15
15
16@let post $title $date $excerpt
16@let post $title $date $excerpt
17 @article [$card, spacing 8]
17 @article [$card, spacing 8]
18− @text [bold, size 20, color $ink] $title
18+ @text [font-weight bold, font-size 20, color $ink] $title
19− @text [size 13, color $muted] $date
19+ @text [font-size 13, color $muted] $date
20 @paragraph [color $ink, line-height 1.6]
20 @paragraph [color $ink, line-height 1.6]
21 $excerpt
21 $excerpt
22− @link [color $primary, bold] /posts/$title
22+ @link [color $primary, font-weight bold] /posts/$title
23 Read more
23 Read more
24
24
25@column [max-width 720, center-x, padding 40, spacing 32]
25@column [max-width 720, center-x, padding 40, spacing 32]
26 @header [spacing 8]
26 @header [spacing 8]
27− @text [bold, size 36, color $ink] My Blog
27+ @text [font-weight bold, font-size 36, color $ink] My Blog
28 @text [color $muted] Thoughts on static HTML, layout, and nice defaults.
28 @text [color $muted] Thoughts on static HTML, layout, and nice defaults.
29
29
30 @main [spacing 20]
30 @main [spacing 20]
@@ -44,5 +44,5 @@
44 excerpt "Dev-loop ergonomics beat feature counts. Here's the setup I use."
44 excerpt "Dev-loop ergonomics beat feature counts. Here's the setup I use."
45 ]
45 ]
46
46
47− @footer [padding-y 24, text-align center, color $muted, size 13]
47+ @footer [padding-block 24, text-align center, color $muted, font-size 13]
48 Built with htmlang.
48 Built with htmlang.
examples/demo.hl+64−64
@@ -24,8 +24,8 @@
24@let card [
24@let card [
25 padding $gap,
25 padding $gap,
26 background white,
26 background white,
27− rounded $base,
27+ border-radius $base,
28− border 1 #e5e7eb,
28+ border 1 solid #e5e7eb,
29 box-shadow 0 1px 3px rgba(0,0,0,0.05)
29 box-shadow 0 1px 3px rgba(0,0,0,0.05)
30]
30]
31
31
@@ -51,14 +51,14 @@
51@let card $title $icon=◆
51@let card $title $icon=◆
52 @article [$card, $interactive, animation fade-up 0.3s ease]
52 @article [$card, $interactive, animation fade-up 0.3s ease]
53 @row [spacing 10, align-items center]
53 @row [spacing 10, align-items center]
54− @text [size 20, color $primary] $icon
54+ @text [font-size 20, color $primary] $icon
55− @text [bold, size 16, color $ink] $title
55+ @text [font-weight bold, font-size 16, color $ink] $title
56 @children
56 @children
57
57
58-- Component that wraps children in a titled section (function + slot)
58-- Component that wraps children in a titled section (function + slot)
59@let titled $title
59@let titled $title
60− @section [spacing 12, padding-y $base]
60+ @section [spacing 12, padding-block $base]
61− @text [bold, size 22, color $ink] $title
61+ @text [font-weight bold, font-size 22, color $ink] $title
62 @children
62 @children
63
63
64-- Top-level layout
64-- Top-level layout
@@ -74,7 +74,7 @@
74
74
75 -- Header: nav + hero
75 -- Header: nav + hero
76 @header [spacing $base]
76 @header [spacing $base]
77− @nav [spacing 6, color $muted, size 13]
77+ @nav [spacing 6, color $muted, font-size 13]
78 @row [spacing 6, align-items center]
78 @row [spacing 6, align-items center]
79 @link [color $muted] / Home
79 @link [color $muted] / Home
80 @text /
80 @text /
@@ -84,14 +84,14 @@
84
84
85 @row [spacing $gap, align-items center]
85 @row [spacing $gap, align-items center]
86 @column [width fill, spacing 6]
86 @column [width fill, spacing 6]
87− @text [bold, size 40, color $ink, dark:color white] Welcome to htmlang
87+ @text [font-weight bold, font-size 40, color $ink, dark:color white] Welcome to htmlang
88 @paragraph [line-height 1.6, color $muted]
88 @paragraph [line-height 1.6, color $muted]
89 A minimalist layout language that compiles to
89 A minimalist layout language that compiles to
90− {@text [bold, color $primary] static HTML} — no JS, no build step.
90+ {@text [font-weight bold, color $primary] static HTML} — no JS, no build step.
91− Visit the {@link [color $primary, underline] https://github.com repo} for more.
91+ Visit the {@link [color $primary, text-decoration underline] https://github.com repo} for more.
92− @image [width 72, height 72, rounded 36, alt="Avatar"] https://picsum.photos/72
92+ @image [width 72, height 72, border-radius 36, alt="Avatar"] https://picsum.photos/72
93
93
94− @hr [border-top 1 #e5e7eb]
94+ @hr [border-top 1 solid #e5e7eb]
95
95
96 -- Feature grid using @card + @titled components
96 -- Feature grid using @card + @titled components
97 @titled [title Features]
97 @titled [title Features]
@@ -108,23 +108,23 @@
108 -- Typography showcase: inline elements, blockquote, code
108 -- Typography showcase: inline elements, blockquote, code
109 @titled [title Typography]
109 @titled [title Typography]
110 @paragraph [line-height 1.7]
110 @paragraph [line-height 1.7]
111− A quick brown fox jumps over {@text [bold] the lazy} {@text [italic] dog}.
111+ A quick brown fox jumps over {@text [font-weight bold] the lazy} {@text [font-style italic] dog}.
112 Press {@kbd [
112 Press {@kbd [
113− padding 2 6, background #f1f5f9, rounded 4,
113+ padding 2 6, background #f1f5f9, border-radius 4,
114− font monospace, size 13
114+ font-family monospace, font-size 13
115 ] Ctrl+K} to search. {@mark [background #fef08a] Highlighted} uses mark.
115 ] Ctrl+K} to search. {@mark [background #fef08a] Highlighted} uses mark.
116 {@abbr [title=HyperText Markup Language + language] htmlang} is an abbreviation.
116 {@abbr [title=HyperText Markup Language + language] htmlang} is an abbreviation.
117
117
118 @blockquote [
118 @blockquote [
119− padding-inline-start $gap, border-left 3 $primary,
119+ padding-inline-start $gap, border-left 3 solid $primary,
120− color $muted, italic
120+ color $muted, font-style italic
121 ]
121 ]
122 Layout is code. Style is code. Everything should compose.
122 Layout is code. Style is code. Everything should compose.
123 @cite — Anonymous
123 @cite — Anonymous
124
124
125 @pre [
125 @pre [
126 background #0f172a, color #e2e8f0,
126 background #0f172a, color #e2e8f0,
127− padding 16, rounded 8, font monospace, size 13,
127+ padding 16, border-radius 8, font-family monospace, font-size 13,
128 overflow-x auto
128 overflow-x auto
129 ]
129 ]
130 @raw
130 @raw
@@ -142,7 +142,7 @@
142 ]
142 ]
143 @each $person in $team
143 @each $person in $team
144 @row [spacing 10, align-items baseline]
144 @row [spacing 10, align-items baseline]
145− @text [bold, min-width 80] $person.name
145+ @text [font-weight bold, min-width 80] $person.name
146 @text [color $muted] $person.role
146 @text [color $muted] $person.role
147
147
148 -- @each ... @else fallback for empty list
148 -- @each ... @else fallback for empty list
@@ -150,40 +150,40 @@
150 @each $x in $nothing
150 @each $x in $nothing
151 @text $x
151 @text $x
152 @else
152 @else
153− @text [color $muted, italic, data-state=$nothing] (Nothing — @each ... @else fallback)
153+ @text [color $muted, font-style italic, data-state=$nothing] (Nothing — @each ... @else fallback)
154
154
155 -- repeat N times
155 -- repeat N times
156 @row [spacing 6]
156 @row [spacing 6]
157 @each $_ in 1..6
157 @each $_ in 1..6
158− @el [width 18, height 18, rounded 9, background $primary, opacity 0.4]
158+ @el [width 18, height 18, border-radius 9, background $primary, opacity 0.4]
159
159
160 -- numeric range
160 -- numeric range
161 @row [spacing 4]
161 @row [spacing 4]
162 @each $i in 1..6
162 @each $i in 1..6
163 @el [
163 @el [
164− width 24, height 24, rounded 4,
164+ width 24, height 24, border-radius 4,
165 background $primary, color white,
165 background $primary, color white,
166− text-align center, size 12, bold, line-height 24
166+ text-align center, font-size 12, font-weight bold, line-height 24
167 ]
167 ]
168 $i
168 $i
169
169
170 -- @if / @else if / @else
170 -- @if / @else if / @else
171 @let count 3
171 @let count 3
172 @row [spacing 6, align-items center]
172 @row [spacing 6, align-items center]
173− @text [bold, min-width 64] Count:
173+ @text [font-weight bold, min-width 64] Count:
174 @if $count == 0
174 @if $count == 0
175 @text [color $muted] Empty
175 @text [color $muted] Empty
176 @else if $count == 1
176 @else if $count == 1
177 @text [color $primary] Exactly one
177 @text [color $primary] Exactly one
178 @else
178 @else
179− @text [color $primary, bold] $count items
179+ @text [color $primary, font-weight bold] $count items
180
180
181 -- @match
181 -- @match
182 @let status ready
182 @let status ready
183 @if $status == "pending"
183 @if $status == "pending"
184 @text [color #f59e0b] Pending…
184 @text [color #f59e0b] Pending…
185 @else if $status == "ready"
185 @else if $status == "ready"
186− @text [color #10b981, bold] ✓ Ready
186+ @text [color #10b981, font-weight bold] ✓ Ready
187 @else if $status == "error"
187 @else if $status == "error"
188 @text [color #ef4444] Error
188 @text [color #ef4444] Error
189 @else
189 @else
@@ -192,41 +192,41 @@
192 -- Conditional attribute value + before: pseudo-element
192 -- Conditional attribute value + before: pseudo-element
193 @let emphasized true
193 @let emphasized true
194 @el [
194 @el [
195− padding 12, rounded 6,
195+ padding 12, border-radius 6,
196 background if($emphasized, $primary, #e5e7eb),
196 background if($emphasized, $primary, #e5e7eb),
197 color if($emphasized, white, $ink),
197 color if($emphasized, white, $ink),
198− before:content "→ ", before:bold
198+ before:content "→ ", before:font-weight bold
199 ]
199 ]
200 Conditional styling via {@code if(cond, a, b)}.
200 Conditional styling via {@code if(cond, a, b)}.
201
201
202 -- Forms + pseudo-states
202 -- Forms + pseudo-states
203 @titled [title Forms]
203 @titled [title Forms]
204 @form [spacing 10, max-width 420] /subscribe
204 @form [spacing 10, max-width 420] /subscribe
205− @label [bold, size 13, for=email] Email
205+ @label [font-weight bold, font-size 13, for=email] Email
206 @input [
206 @input [
207 id=email, type=email, name=email,
207 id=email, type=email, name=email,
208 placeholder=you@example.com, required,
208 placeholder=you@example.com, required,
209− padding 10, border 1 #e5e7eb, rounded 6,
209+ padding 10, border 1 solid #e5e7eb, border-radius 6,
210− focus:border 1 $primary,
210+ focus:border 1 solid $primary,
211− valid:border 1 #10b981,
211+ valid:border 1 solid #10b981,
212− invalid:border 1 #ef4444,
212+ invalid:border 1 solid #ef4444,
213 transition border 0.15s ease
213 transition border 0.15s ease
214 ]
214 ]
215− @label [bold, size 13, for=role] Role
215+ @label [font-weight bold, font-size 13, for=role] Role
216− @select [id=role, name=role, padding 10, border 1 #e5e7eb, rounded 6]
216+ @select [id=role, name=role, padding 10, border 1 solid #e5e7eb, border-radius 6]
217 @option [value=dev] Developer
217 @option [value=dev] Developer
218 @option [value=designer] Designer
218 @option [value=designer] Designer
219 @option [value=other] Other
219 @option [value=other] Other
220− @label [bold, size 13, for=msg] Message
220+ @label [font-weight bold, font-size 13, for=msg] Message
221 @textarea [
221 @textarea [
222 id=msg, name=msg, rows=3,
222 id=msg, name=msg, rows=3,
223− padding 10, border 1 #e5e7eb, rounded 6
223+ padding 10, border 1 solid #e5e7eb, border-radius 6
224 ]
224 ]
225 @row [spacing 8]
225 @row [spacing 8]
226 @button [
226 @button [
227 type=submit,
227 type=submit,
228− padding 10 16, background $primary, color white, bold,
228+ padding 10 16, background $primary, color white, font-weight bold,
229− rounded 6, border 0,
229+ border-radius 6, border 0,
230 hover:background ${darken($primary, 10)},
230 hover:background ${darken($primary, 10)},
231 active:background ${darken($primary, 20)},
231 active:background ${darken($primary, 20)},
232 transition background 0.15s ease,
232 transition background 0.15s ease,
@@ -235,7 +235,7 @@
235 @button [
235 @button [
236 type=reset,
236 type=reset,
237 padding 10 16, background white,
237 padding 10 16, background white,
238− border 1 #e5e7eb, rounded 6, cursor pointer
238+ border 1 solid #e5e7eb, border-radius 6, cursor pointer
239 ] Reset
239 ] Reset
240 @progress [value=60, max=100]
240 @progress [value=60, max=100]
241
241
@@ -243,19 +243,19 @@
243 @titled [title Data]
243 @titled [title Data]
244 @row [gap $gap, wrap]
244 @row [gap $gap, wrap]
245 @column [width fill, spacing $base, min-width 240]
245 @column [width fill, spacing $base, min-width 240]
246− @text [bold] Unordered
246+ @text [font-weight bold] Unordered
247 @list [spacing 4, list-style disc, padding-inline-start 20]
247 @list [spacing 4, list-style disc, padding-inline-start 20]
248 @item [display list-item] Composable with @each
248 @item [display list-item] Composable with @each
249 @item [display list-item] Indent to nest children
249 @item [display list-item] Indent to nest children
250 @item [display list-item] Bold list items work too
250 @item [display list-item] Bold list items work too
251− @text [bold] Ordered
251+ @text [font-weight bold] Ordered
252 @list [ordered, list-style decimal, padding-inline-start 20]
252 @list [ordered, list-style decimal, padding-inline-start 20]
253 @item [display list-item] First
253 @item [display list-item] First
254 @item [display list-item] Second
254 @item [display list-item] Second
255 @item [display list-item] Third
255 @item [display list-item] Third
256
256
257 @column [width fill, spacing $base, min-width 240]
257 @column [width fill, spacing $base, min-width 240]
258− @text [bold] Table
258+ @text [font-weight bold] Table
259 @table
259 @table
260 @thead
260 @thead
261 @tr
261 @tr
@@ -263,21 +263,21 @@
263 @th [text-align right, padding 8] Qty
263 @th [text-align right, padding 8] Qty
264 @tbody
264 @tbody
265 @tr
265 @tr
266− @td [padding 8, border-top 1 #e5e7eb] Widgets
266+ @td [padding 8, border-top 1 solid #e5e7eb] Widgets
267− @td [padding 8, border-top 1 #e5e7eb, text-align right] 12
267+ @td [padding 8, border-top 1 solid #e5e7eb, text-align right] 12
268 @tr
268 @tr
269− @td [padding 8, border-top 1 #e5e7eb] Gadgets
269+ @td [padding 8, border-top 1 solid #e5e7eb] Gadgets
270− @td [padding 8, border-top 1 #e5e7eb, text-align right] 7
270+ @td [padding 8, border-top 1 solid #e5e7eb, text-align right] 7
271
271
272 @figure [spacing 6]
272 @figure [spacing 6]
273− @image [alt="A random placeholder", rounded 8] https://picsum.photos/640/200
273+ @image [alt="A random placeholder", border-radius 8] https://picsum.photos/640/200
274− @figcaption [color $muted, size 13, text-align center]
274+ @figcaption [color $muted, font-size 13, text-align center]
275 A placeholder image.
275 A placeholder image.
276
276
277 -- Disclosure + named grid areas
277 -- Disclosure + named grid areas
278 @titled [title Disclosure + grid areas]
278 @titled [title Disclosure + grid areas]
279− @details [padding 12, border 1 #e5e7eb, rounded 6, background $subtle]
279+ @details [padding 12, border 1 solid #e5e7eb, border-radius 6, background $subtle]
280− @summary [bold, cursor pointer] What is htmlang?
280+ @summary [font-weight bold, cursor pointer] What is htmlang?
281 @paragraph [color $muted, line-height 1.6, padding-block-start 6]
281 @paragraph [color $muted, line-height 1.6, padding-block-start 6]
282 htmlang is a layout-first language that compiles to a single,
282 htmlang is a layout-first language that compiles to a single,
283 self-contained HTML file with embedded CSS.
283 self-contained HTML file with embedded CSS.
@@ -287,39 +287,39 @@
287 grid-template-columns 1fr 2fr,
287 grid-template-columns 1fr 2fr,
288 grid-template-areas "h h" "s m" "f f",
288 grid-template-areas "h h" "s m" "f f",
289 gap $base,
289 gap $base,
290− padding $base, background $subtle, rounded 8
290+ padding $base, background $subtle, border-radius 8
291 ]
291 ]
292− @el [grid-area h, padding 10, background $primary, color white, rounded 6, text-align center]
292+ @el [grid-area h, padding 10, background $primary, color white, border-radius 6, text-align center]
293 Header
293 Header
294− @el [grid-area s, padding 10, background white, rounded 6, border 1 #e5e7eb]
294+ @el [grid-area s, padding 10, background white, border-radius 6, border 1 solid #e5e7eb]
295 Sidebar
295 Sidebar
296− @el [grid-area m, padding 10, background white, rounded 6, border 1 #e5e7eb]
296+ @el [grid-area m, padding 10, background white, border-radius 6, border 1 solid #e5e7eb]
297 Main
297 Main
298− @el [grid-area f, padding 10, background $ink, color white, rounded 6, text-align center]
298+ @el [grid-area f, padding 10, background $ink, color white, border-radius 6, text-align center]
299 Footer
299 Footer
300
300
301 -- Misc: address, time, meter
301 -- Misc: address, time, meter
302 @titled [title Misc]
302 @titled [title Misc]
303 @address [spacing 4, font-style normal]
303 @address [spacing 4, font-style normal]
304− @text [bold] htmlang HQ
304+ @text [font-weight bold] htmlang HQ
305 @text 123 Flexbox Avenue
305 @text 123 Flexbox Avenue
306 @link [color $primary] mailto:hi@htmlang.dev hi@htmlang.dev
306 @link [color $primary] mailto:hi@htmlang.dev hi@htmlang.dev
307 @row [spacing 10, align-items center]
307 @row [spacing 10, align-items center]
308− @text [size 13, color $muted] Published
308+ @text [font-size 13, color $muted] Published
309− @time [datetime=2026-04-16, size 13] April 16 2026
309+ @time [datetime=2026-04-16, font-size 13] April 16 2026
310 @row [spacing 10, align-items center]
310 @row [spacing 10, align-items center]
311− @text [size 13, color $muted] Confidence
311+ @text [font-size 13, color $muted] Confidence
312 @meter [value=8, min=0, max=10, low=3, high=7] 8/10
312 @meter [value=8, min=0, max=10, low=3, high=7] 8/10
313
313
314 -- Footer: hover/active button, visited link, print-hidden
314 -- Footer: hover/active button, visited link, print-hidden
315 @footer [
315 @footer [
316 padding-block-start $gutter, margin-block-start $gutter,
316 padding-block-start $gutter, margin-block-start $gutter,
317− border-top 1 #e5e7eb, spacing $base,
317+ border-top 1 solid #e5e7eb, spacing $base,
318 print:display none
318 print:display none
319 ]
319 ]
320 @row [spacing $base, align-items center]
320 @row [spacing $base, align-items center]
321 @el [
321 @el [
322− padding 12 20, background $primary, color white, bold, rounded 8,
322+ padding 12 20, background $primary, color white, font-weight bold, border-radius 8,
323 hover:background ${darken($primary, 10)},
323 hover:background ${darken($primary, 10)},
324 active:background ${darken($primary, 20)},
324 active:background ${darken($primary, 20)},
325 transition all 0.15s ease,
325 transition all 0.15s ease,
@@ -329,7 +329,7 @@
329 @link [
329 @link [
330 color $primary,
330 color $primary,
331 visited:color #8b5cf6,
331 visited:color #8b5cf6,
332− hover:underline
332+ hover:text-decoration underline
333 ] https://github.com Browse source
333 ] https://github.com Browse source
334 @el [width fill]
334 @el [width fill]
335− @text [color $muted, size 13] © 2026 htmlang
335+ @text [color $muted, font-size 13] © 2026 htmlang
examples/demo.html+1−1
@@ -1,3 +1,3 @@
1−<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>htmlang Demo</title><link rel="preload" href="https://picsum.photos/72" as="image"><link rel="preload" href="https://picsum.photos/640/200" as="image"><meta name="description" content="A one-page tour of htmlang."><meta name="theme-color" content="#3b82f6"><meta property="og:title" content="htmlang"><meta property="og:type" content="website"><style>@layer hl-reset,htmlang;@layer hl-reset{*,*::before,*::after{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,sans-serif}img{display:block}a{text-decoration:none;color:inherit}a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid currentColor;outline-offset:2px}}:root{--primary:#3b82f6;--ink:#0f172a;--muted:#64748b;--subtle:#f8fafc;}@layer htmlang{.a{display:flex;flex-direction:column;max-width:960px;margin-left:auto;margin-right:auto;padding:32px;gap:24px;background:white;}.b{display:flex;flex-direction:column;gap:8px;}.c{display:flex;flex-direction:column;gap:6px;color:var(--muted);font-size:13px;}.d{display:flex;flex-direction:row;gap:6px;align-items:center;}.e{color:var(--muted);}.f{color:var(--ink);}.g{display:flex;flex-direction:row;gap:16px;align-items:center;}.h{display:flex;flex-direction:column;flex:1;min-width:0;gap:6px;}.i{font-weight:bold;font-size:40px;color:var(--ink);}.j{margin:0;line-height:1.6;color:var(--muted);}.k{font-weight:bold;color:var(--primary);}.l{color:var(--primary);text-decoration:underline;}.m{width:72px;height:72px;border-radius:36px;}.n{border-top:1px solid #e5e7eb;}.o{display:flex;flex-direction:column;gap:12px;padding-block:8px;}.p{font-weight:bold;font-size:22px;color:var(--ink);}.q{display:flex;flex-direction:row;flex-wrap:wrap;gap:8px;}.r{display:flex;flex-direction:column;padding:16px;background:white;border-radius:8px;border:1px solid #e5e7eb;box-shadow:0 1px 3px rgba(0,0,0,0.05);cursor:pointer;transition:all 0.2s ease;animation:fade-up 0.3s ease;}.s{display:flex;flex-direction:row;gap:10px;align-items:center;}.t{font-size:20px;color:var(--primary);}.u{font-weight:bold;font-size:16px;color:var(--ink);}.v{margin:0;line-height:1.7;}.w{font-weight:bold;}.x{font-style:italic;}.y{padding:2px 6px;background:#f1f5f9;border-radius:4px;font-family:monospace;font-size:13px;}.z{background:#fef08a;}.aa{display:flex;flex-direction:column;margin:0;padding-inline-start:16px;border-left:3px solid var(--primary);color:var(--muted);font-style:italic;}.ab{margin:0;white-space:pre;background:var(--ink);color:#e2e8f0;padding:16px;border-radius:8px;font-family:monospace;font-size:13px;overflow-x:auto;}.ac{display:flex;flex-direction:column;gap:6px;}.ad{display:flex;flex-direction:row;gap:10px;align-items:baseline;}.ae{font-weight:bold;min-width:80px;}.af{color:var(--muted);font-style:italic;}.ag{display:flex;flex-direction:row;gap:6px;}.ah{display:flex;flex-direction:column;width:18px;height:18px;border-radius:9px;background:var(--primary);opacity:0.4;}.ai{display:flex;flex-direction:row;gap:4px;}.aj{display:flex;flex-direction:column;width:24px;height:24px;border-radius:4px;background:var(--primary);color:white;text-align:center;font-size:12px;font-weight:bold;line-height:24px;}.ak{font-weight:bold;min-width:64px;}.al{color:var(--primary);font-weight:bold;}.am{color:#10b981;font-weight:bold;}.an{display:flex;flex-direction:column;padding:12px;border-radius:6px;background:var(--primary);color:white;}.ao{font-family:ui-monospace,monospace;}.ap{display:flex;flex-direction:column;gap:10px;max-width:420px;}.aq{font-weight:bold;font-size:13px;}.ar{padding:10px;border:1px solid #e5e7eb;border-radius:6px;transition:border 0.15s ease;}.as{padding:10px;border:1px solid #e5e7eb;border-radius:6px;}.at{display:flex;flex-direction:row;gap:8px;}.au{padding:10px 16px;background:var(--primary);color:white;font-weight:bold;border-radius:6px;border:0 solid currentColor;transition:background 0.15s ease;cursor:pointer;}.av{padding:10px 16px;background:white;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;}.aw{display:flex;flex-direction:row;gap:16px;flex-wrap:wrap;}.ax{display:flex;flex-direction:column;flex:1;gap:8px;min-width:240px;}.ay{margin:0;padding-left:0;gap:4px;list-style:disc;padding-inline-start:20px;}.az{flex-direction:column;display:list-item;}.a0{margin:0;padding-left:0;list-style:decimal;padding-inline-start:20px;}.a1{text-align:left;padding:8px;}.a2{text-align:right;padding:8px;}.a3{padding:8px;border-top:1px solid #e5e7eb;}.a4{padding:8px;border-top:1px solid #e5e7eb;text-align:right;}.a5{display:flex;flex-direction:column;margin:0;gap:6px;}.a6{border-radius:8px;}.a7{color:var(--muted);font-size:13px;text-align:center;}.a8{display:flex;flex-direction:column;padding:12px;border:1px solid #e5e7eb;border-radius:6px;background:var(--subtle);}.a9{font-weight:bold;cursor:pointer;}.ba{margin:0;color:var(--muted);line-height:1.6;padding-block-start:6px;}.bb{flex-direction:column;display:grid;grid-template-columns:1fr 2fr;grid-template-areas:"h h" "s m" "f f";gap:8px;padding:8px;background:var(--subtle);border-radius:8px;}.bc{display:flex;flex-direction:column;grid-area:h;padding:10px;background:var(--primary);color:white;border-radius:6px;text-align:center;}.bd{display:flex;flex-direction:column;grid-area:s;padding:10px;background:white;border-radius:6px;border:1px solid #e5e7eb;}.be{display:flex;flex-direction:column;grid-area:m;padding:10px;background:white;border-radius:6px;border:1px solid #e5e7eb;}.bf{display:flex;flex-direction:column;grid-area:f;padding:10px;background:var(--ink);color:white;border-radius:6px;text-align:center;}.bg{gap:4px;font-style:normal;}.bh,.bn{color:var(--primary);}.bi{font-size:13px;color:var(--muted);}.bj{font-size:13px;}.bk{display:flex;flex-direction:column;padding-block-start:24px;margin-block-start:24px;border-top:1px solid #e5e7eb;gap:8px;}.bl{display:flex;flex-direction:row;gap:8px;align-items:center;}.bm{display:flex;flex-direction:column;padding:12px 20px;background:var(--primary);color:white;font-weight:bold;border-radius:8px;transition:all 0.15s ease;cursor:pointer;}.bo{display:flex;flex-direction:column;flex:1;min-width:0;}.bp{color:var(--muted);font-size:13px;}.r:hover{box-shadow:0 4px 12px rgba(0,0,0,0.08);}.au:hover,.bm:hover{background:#3575dd;}.bn:hover{text-decoration:underline;}.an::before{content:"→ ";font-weight:bold;}.ar:focus{border:1px solid var(--primary);}.ar:valid{border:1px solid #10b981;}.ar:invalid{border:1px solid #ef4444;}.au:active,.bm:active{background:#2f68c5;}.bn:visited{color:#8b5cf6;}@media(prefers-color-scheme:dark){.a{background:#0b1220;color:#e2e8f0;}.i{color:white;}}@media print{.bk{display:none;}}}@keyframes fade-up {from { opacity: 0; transform: translateY(8px); }to { opacity: 1; transform: translateY(0); }}</style></head><body><noscript><style>.needs-js{display:none}</style></noscript><div class="a"><header class="b"><nav class="c"><div class="d"><a href="/" class="e">Home</a><span>/</span><a href="/demos" class="e">Demos</a><span>/</span><span class="f">htmlang</span></div></nav><div class="g"><div class="h"><span class="i">Welcome to htmlang</span><p class="j">A minimalist layout language that compiles to <span class="k">static HTML</span> — no JS, no build step. Visit the <a href="https://github.com" rel="noopener noreferrer" target="_blank" class="l">repo</a> for more.</p></div><img src="https://picsum.photos/72" class="m" alt="Avatar" fetchpriority="high"></div></header><hr class="n"><section class="o"><span class="p">Features</span><div class="q"><article class="r"><div class="s"><span class="t">✦</span><span class="u">Simple</span></div><span>Write layouts without CSS classes.</span></article><article class="r"><div class="s"><span class="t">⚡</span><span class="u">Fast</span></div><span>Compiles to a single self-contained HTML file.</span></article><article class="r"><div class="s"><span class="t">○</span><span class="u">Minimal</span></div><span>Zero runtime JavaScript by default.</span></article><article class="r"><div class="s"><span class="t">◆</span><span class="u">Typed</span></div><span>LSP with diagnostics and go-to-definition.</span></article></div></section><section class="o"><span class="p">Typography</span><p class="v">A quick brown fox jumps over <span class="w">the lazy</span> <span class="x">dog</span>. Press <kbd class="y">Ctrl+K</kbd> to search. <mark class="z">Highlighted</mark> uses mark. <abbr title="HyperText Markup Language + language">htmlang</abbr> is an abbreviation.</p><blockquote class="aa"><span>Layout is code. Style is code. Everything should compose.</span><cite>— Anonymous</cite></blockquote><pre class="ab">@let button $label
1+<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>htmlang Demo</title><link rel="preload" href="https://picsum.photos/72" as="image"><link rel="preload" href="https://picsum.photos/640/200" as="image"><meta name="description" content="A one-page tour of htmlang."><meta name="theme-color" content="#3b82f6"><meta property="og:title" content="htmlang"><meta property="og:type" content="website"><style>@layer hl-reset,htmlang;@layer hl-reset{*,*::before,*::after{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,sans-serif}img{display:block}a{text-decoration:none;color:inherit}a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid currentColor;outline-offset:2px}}:root{--primary:#3b82f6;--ink:#0f172a;--muted:#64748b;--subtle:#f8fafc;}@layer htmlang{.a{display:flex;flex-direction:column;max-width:960px;margin-left:auto;margin-right:auto;padding:32px;gap:24px;background:white;}.b{display:flex;flex-direction:column;gap:8px;}.c{display:flex;flex-direction:column;gap:6px;color:var(--muted);font-size:13px;}.d{display:flex;flex-direction:row;gap:6px;align-items:center;}.e{color:var(--muted);}.f{color:var(--ink);}.g{display:flex;flex-direction:row;gap:16px;align-items:center;}.h{display:flex;flex-direction:column;flex:1;min-width:0;gap:6px;}.i{font-weight:bold;font-size:40px;color:var(--ink);}.j{margin:0;line-height:1.6;color:var(--muted);}.k{font-weight:bold;color:var(--primary);}.l{color:var(--primary);text-decoration:underline;}.m{width:72px;height:72px;border-radius:36px;}.n{border-top:1px solid #e5e7eb;}.o{display:flex;flex-direction:column;gap:12px;padding-block:8px;}.p{font-weight:bold;font-size:22px;color:var(--ink);}.q{display:flex;flex-direction:row;flex-wrap:wrap;gap:8px;}.r{display:flex;flex-direction:column;padding:16px;background:white;border-radius:8px;border:1px solid #e5e7eb;box-shadow:0 1px 3px rgba(0,0,0,0.05);cursor:pointer;transition:all 0.2s ease;animation:fade-up 0.3s ease;}.s{display:flex;flex-direction:row;gap:10px;align-items:center;}.t{font-size:20px;color:var(--primary);}.u{font-weight:bold;font-size:16px;color:var(--ink);}.v{margin:0;line-height:1.7;}.w{font-weight:bold;}.x{font-style:italic;}.y{padding:2px 6px;background:#f1f5f9;border-radius:4px;font-family:monospace;font-size:13px;}.z{background:#fef08a;}.aa{display:flex;flex-direction:column;margin:0;padding-inline-start:16px;border-left:3px solid var(--primary);color:var(--muted);font-style:italic;}.ab{margin:0;white-space:pre;background:var(--ink);color:#e2e8f0;padding:16px;border-radius:8px;font-family:monospace;font-size:13px;overflow-x:auto;}.ac{display:flex;flex-direction:column;gap:6px;}.ad{display:flex;flex-direction:row;gap:10px;align-items:baseline;}.ae{font-weight:bold;min-width:80px;}.af{color:var(--muted);font-style:italic;}.ag{display:flex;flex-direction:row;gap:6px;}.ah{display:flex;flex-direction:column;width:18px;height:18px;border-radius:9px;background:var(--primary);opacity:0.4;}.ai{display:flex;flex-direction:row;gap:4px;}.aj{display:flex;flex-direction:column;width:24px;height:24px;border-radius:4px;background:var(--primary);color:white;text-align:center;font-size:12px;font-weight:bold;line-height:24px;}.ak{font-weight:bold;min-width:64px;}.al{color:var(--primary);font-weight:bold;}.am{color:#10b981;font-weight:bold;}.an{display:flex;flex-direction:column;padding:12px;border-radius:6px;background:var(--primary);color:white;}.ao{font-family:ui-monospace,monospace;}.ap{display:flex;flex-direction:column;gap:10px;max-width:420px;}.aq{font-weight:bold;font-size:13px;}.ar{padding:10px;border:1px solid #e5e7eb;border-radius:6px;transition:border 0.15s ease;}.as{padding:10px;border:1px solid #e5e7eb;border-radius:6px;}.at{display:flex;flex-direction:row;gap:8px;}.au{padding:10px 16px;background:var(--primary);color:white;font-weight:bold;border-radius:6px;border:0;transition:background 0.15s ease;cursor:pointer;}.av{padding:10px 16px;background:white;border:1px solid #e5e7eb;border-radius:6px;cursor:pointer;}.aw{display:flex;flex-direction:row;gap:16px;flex-wrap:wrap;}.ax{display:flex;flex-direction:column;flex:1;gap:8px;min-width:240px;}.ay{margin:0;padding-left:0;gap:4px;list-style:disc;padding-inline-start:20px;}.az{flex-direction:column;display:list-item;}.a0{margin:0;padding-left:0;list-style:decimal;padding-inline-start:20px;}.a1{text-align:left;padding:8px;}.a2{text-align:right;padding:8px;}.a3{padding:8px;border-top:1px solid #e5e7eb;}.a4{padding:8px;border-top:1px solid #e5e7eb;text-align:right;}.a5{display:flex;flex-direction:column;margin:0;gap:6px;}.a6{border-radius:8px;}.a7{color:var(--muted);font-size:13px;text-align:center;}.a8{display:flex;flex-direction:column;padding:12px;border:1px solid #e5e7eb;border-radius:6px;background:var(--subtle);}.a9{font-weight:bold;cursor:pointer;}.ba{margin:0;color:var(--muted);line-height:1.6;padding-block-start:6px;}.bb{flex-direction:column;display:grid;grid-template-columns:1fr 2fr;grid-template-areas:"h h" "s m" "f f";gap:8px;padding:8px;background:var(--subtle);border-radius:8px;}.bc{display:flex;flex-direction:column;grid-area:h;padding:10px;background:var(--primary);color:white;border-radius:6px;text-align:center;}.bd{display:flex;flex-direction:column;grid-area:s;padding:10px;background:white;border-radius:6px;border:1px solid #e5e7eb;}.be{display:flex;flex-direction:column;grid-area:m;padding:10px;background:white;border-radius:6px;border:1px solid #e5e7eb;}.bf{display:flex;flex-direction:column;grid-area:f;padding:10px;background:var(--ink);color:white;border-radius:6px;text-align:center;}.bg{gap:4px;font-style:normal;}.bh,.bn{color:var(--primary);}.bi{font-size:13px;color:var(--muted);}.bj{font-size:13px;}.bk{display:flex;flex-direction:column;padding-block-start:24px;margin-block-start:24px;border-top:1px solid #e5e7eb;gap:8px;}.bl{display:flex;flex-direction:row;gap:8px;align-items:center;}.bm{display:flex;flex-direction:column;padding:12px 20px;background:var(--primary);color:white;font-weight:bold;border-radius:8px;transition:all 0.15s ease;cursor:pointer;}.bo{display:flex;flex-direction:column;flex:1;min-width:0;}.bp{color:var(--muted);font-size:13px;}.r:hover{box-shadow:0 4px 12px rgba(0,0,0,0.08);}.au:hover,.bm:hover{background:#3575dd;}.bn:hover{text-decoration:underline;}.an::before{content:"→ ";font-weight:bold;}.ar:focus{border:1px solid var(--primary);}.ar:valid{border:1px solid #10b981;}.ar:invalid{border:1px solid #ef4444;}.au:active,.bm:active{background:#2f68c5;}.bn:visited{color:#8b5cf6;}@media(prefers-color-scheme:dark){.a{background:#0b1220;color:#e2e8f0;}.i{color:white;}}@media print{.bk{display:none;}}}@keyframes fade-up {from { opacity: 0; transform: translateY(8px); }to { opacity: 1; transform: translateY(0); }}</style></head><body><noscript><style>.needs-js{display:none}</style></noscript><div class="a"><header class="b"><nav class="c"><div class="d"><a href="/" class="e">Home</a><span>/</span><a href="/demos" class="e">Demos</a><span>/</span><span class="f">htmlang</span></div></nav><div class="g"><div class="h"><span class="i">Welcome to htmlang</span><p class="j">A minimalist layout language that compiles to <span class="k">static HTML</span> — no JS, no build step. Visit the <a href="https://github.com" rel="noopener noreferrer" target="_blank" class="l">repo</a> for more.</p></div><img src="https://picsum.photos/72" class="m" alt="Avatar" fetchpriority="high"></div></header><hr class="n"><section class="o"><span class="p">Features</span><div class="q"><article class="r"><div class="s"><span class="t">✦</span><span class="u">Simple</span></div><span>Write layouts without CSS classes.</span></article><article class="r"><div class="s"><span class="t">⚡</span><span class="u">Fast</span></div><span>Compiles to a single self-contained HTML file.</span></article><article class="r"><div class="s"><span class="t">○</span><span class="u">Minimal</span></div><span>Zero runtime JavaScript by default.</span></article><article class="r"><div class="s"><span class="t">◆</span><span class="u">Typed</span></div><span>LSP with diagnostics and go-to-definition.</span></article></div></section><section class="o"><span class="p">Typography</span><p class="v">A quick brown fox jumps over <span class="w">the lazy</span> <span class="x">dog</span>. Press <kbd class="y">Ctrl+K</kbd> to search. <mark class="z">Highlighted</mark> uses mark. <abbr title="HyperText Markup Language + language">htmlang</abbr> is an abbreviation.</p><blockquote class="aa"><span>Layout is code. Style is code. Everything should compose.</span><cite>— Anonymous</cite></blockquote><pre class="ab">@let button $label
2 @el [padding 12, background blue]
2 @el [padding 12, background blue]
3 @text $label</pre></section><section class="o"><span class="p">Control flow</span><div class="ac"><div class="ad"><span class="ae">Alice</span><span class="e">Engineer</span></div><div class="ad"><span class="ae">Bob</span><span class="e">Designer</span></div><div class="ad"><span class="ae">Carol</span><span class="e">PM</span></div></div><span class="af" data-state="">(Nothing — @each ... @else fallback)</span><div class="ag"><div class="ah"></div><div class="ah"></div><div class="ah"></div><div class="ah"></div><div class="ah"></div><div class="ah"></div></div><div class="ai"><div class="aj"><span>1</span></div><div class="aj"><span>2</span></div><div class="aj"><span>3</span></div><div class="aj"><span>4</span></div><div class="aj"><span>5</span></div><div class="aj"><span>6</span></div></div><div class="d"><span class="ak">Count:</span><span class="al">3 items</span></div><span class="am">✓ Ready</span><div class="an"><span>Conditional styling via <code class="ao">if(cond, a, b)</code>.</span></div></section><section class="o"><span class="p">Forms</span><form action="/subscribe" class="ap"><label class="aq" for="email">Email</label><input class="ar" id="email" type="email" name="email" placeholder="you@example.com" required><label class="aq" for="role">Role</label><select class="as" id="role" name="role"><option value="dev">Developer</option><option value="designer">Designer</option><option value="other">Other</option></select><label class="aq" for="msg">Message</label><textarea class="as" id="msg" name="msg" rows="3"></textarea><div class="at"><button class="au" type="submit">Subscribe</button><button class="av" type="reset">Reset</button></div><progress value="60" max="100"></progress></form></section><section class="o"><span class="p">Data</span><div class="aw"><div class="ax"><span class="w">Unordered</span><ul class="ay"><li class="az">Composable with @each</li><li class="az">Indent to nest children</li><li class="az">Bold list items work too</li></ul><span class="w">Ordered</span><ol class="a0"><li class="az">First</li><li class="az">Second</li><li class="az">Third</li></ol></div><div class="ax"><span class="w">Table</span><table><thead><tr><th class="a1">Item</th><th class="a2">Qty</th></tr></thead><tbody><tr><td class="a3">Widgets</td><td class="a4">12</td></tr><tr><td class="a3">Gadgets</td><td class="a4">7</td></tr></tbody></table></div></div><figure class="a5"><img src="https://picsum.photos/640/200" class="a6" alt="A random placeholder" fetchpriority="high"><figcaption class="a7">A placeholder image.</figcaption></figure></section><section class="o"><span class="p">Disclosure + grid areas</span><details class="a8"><summary class="a9">What is htmlang?</summary><p class="ba">htmlang is a layout-first language that compiles to a single, self-contained HTML file with embedded CSS.</p></details><div class="bb"><div class="bc"><span>Header</span></div><div class="bd"><span>Sidebar</span></div><div class="be"><span>Main</span></div><div class="bf"><span>Footer</span></div></div></section><section class="o"><span class="p">Misc</span><address class="bg"><span class="w">htmlang HQ</span><span>123 Flexbox Avenue</span><a href="mailto:hi@htmlang.dev" class="bh">hi@htmlang.dev</a></address><div class="s"><span class="bi">Published</span><time class="bj" datetime="2026-04-16">April 16 2026</time></div><div class="s"><span class="bi">Confidence</span><meter value="8" min="0" max="10" low="3" high="7">8/10</meter></div></section><footer class="bk"><div class="bl"><div class="bm"><a href="https://github.com" rel="noopener noreferrer" target="_blank">Get Started</a></div><a href="https://github.com" rel="noopener noreferrer" target="_blank" class="bn">Browse source</a><div class="bo"></div><span class="bp">© 2026 htmlang</span></div></footer></div></body></html>
3 @text $label</pre></section><section class="o"><span class="p">Control flow</span><div class="ac"><div class="ad"><span class="ae">Alice</span><span class="e">Engineer</span></div><div class="ad"><span class="ae">Bob</span><span class="e">Designer</span></div><div class="ad"><span class="ae">Carol</span><span class="e">PM</span></div></div><span class="af" data-state="">(Nothing — @each ... @else fallback)</span><div class="ag"><div class="ah"></div><div class="ah"></div><div class="ah"></div><div class="ah"></div><div class="ah"></div><div class="ah"></div></div><div class="ai"><div class="aj"><span>1</span></div><div class="aj"><span>2</span></div><div class="aj"><span>3</span></div><div class="aj"><span>4</span></div><div class="aj"><span>5</span></div><div class="aj"><span>6</span></div></div><div class="d"><span class="ak">Count:</span><span class="al">3 items</span></div><span class="am">✓ Ready</span><div class="an"><span>Conditional styling via <code class="ao">if(cond, a, b)</code>.</span></div></section><section class="o"><span class="p">Forms</span><form action="/subscribe" class="ap"><label class="aq" for="email">Email</label><input class="ar" id="email" type="email" name="email" placeholder="you@example.com" required><label class="aq" for="role">Role</label><select class="as" id="role" name="role"><option value="dev">Developer</option><option value="designer">Designer</option><option value="other">Other</option></select><label class="aq" for="msg">Message</label><textarea class="as" id="msg" name="msg" rows="3"></textarea><div class="at"><button class="au" type="submit">Subscribe</button><button class="av" type="reset">Reset</button></div><progress value="60" max="100"></progress></form></section><section class="o"><span class="p">Data</span><div class="aw"><div class="ax"><span class="w">Unordered</span><ul class="ay"><li class="az">Composable with @each</li><li class="az">Indent to nest children</li><li class="az">Bold list items work too</li></ul><span class="w">Ordered</span><ol class="a0"><li class="az">First</li><li class="az">Second</li><li class="az">Third</li></ol></div><div class="ax"><span class="w">Table</span><table><thead><tr><th class="a1">Item</th><th class="a2">Qty</th></tr></thead><tbody><tr><td class="a3">Widgets</td><td class="a4">12</td></tr><tr><td class="a3">Gadgets</td><td class="a4">7</td></tr></tbody></table></div></div><figure class="a5"><img src="https://picsum.photos/640/200" class="a6" alt="A random placeholder" fetchpriority="high"><figcaption class="a7">A placeholder image.</figcaption></figure></section><section class="o"><span class="p">Disclosure + grid areas</span><details class="a8"><summary class="a9">What is htmlang?</summary><p class="ba">htmlang is a layout-first language that compiles to a single, self-contained HTML file with embedded CSS.</p></details><div class="bb"><div class="bc"><span>Header</span></div><div class="bd"><span>Sidebar</span></div><div class="be"><span>Main</span></div><div class="bf"><span>Footer</span></div></div></section><section class="o"><span class="p">Misc</span><address class="bg"><span class="w">htmlang HQ</span><span>123 Flexbox Avenue</span><a href="mailto:hi@htmlang.dev" class="bh">hi@htmlang.dev</a></address><div class="s"><span class="bi">Published</span><time class="bj" datetime="2026-04-16">April 16 2026</time></div><div class="s"><span class="bi">Confidence</span><meter value="8" min="0" max="10" low="3" high="7">8/10</meter></div></section><footer class="bk"><div class="bl"><div class="bm"><a href="https://github.com" rel="noopener noreferrer" target="_blank">Get Started</a></div><a href="https://github.com" rel="noopener noreferrer" target="_blank" class="bn">Browse source</a><div class="bo"></div><span class="bp">© 2026 htmlang</span></div></footer></div></body></html>
examples/docs.hl+17−17
@@ -7,7 +7,7 @@
7
7
8@let nav-link $label $href
8@let nav-link $label $href
9 @link [
9 @link [
10− padding 8, padding-x 12, rounded 6, color $ink,
10+ padding 8, padding-inline 12, border-radius 6, color $ink,
11 hover:background #f1f5f9, transition all 0.1s ease
11 hover:background #f1f5f9, transition all 0.1s ease
12 ] $href
12 ] $href
13 $label
13 $label
@@ -15,15 +15,15 @@
15@row [min-height 100vh]
15@row [min-height 100vh]
16 -- Sidebar
16 -- Sidebar
17 @aside [
17 @aside [
18− width 240, padding 32, padding-x 20,
18+ width 240, padding 32, padding-inline 20,
19− background #fafafa, border-right 1 #e5e7eb, spacing 4
19+ background #fafafa, border-right 1 solid #e5e7eb, spacing 4
20 ]
20 ]
21− @text [bold, size 13, color $muted, text-transform uppercase, letter-spacing 1] Introduction
21+ @text [font-weight bold, font-size 13, color $muted, text-transform uppercase, letter-spacing 1] Introduction
22 @nav-link [label Overview, href #overview]
22 @nav-link [label Overview, href #overview]
23 @nav-link [label Installation, href #install]
23 @nav-link [label Installation, href #install]
24 @nav-link [label First page, href #first-page]
24 @nav-link [label First page, href #first-page]
25 @text [
25 @text [
26− bold, size 13, color $muted, text-transform uppercase, letter-spacing 1,
26+ font-weight bold, font-size 13, color $muted, text-transform uppercase, letter-spacing 1,
27 margin-block-start 20
27 margin-block-start 20
28 ] Guides
28 ] Guides
29 @nav-link [label Layout, href #layout]
29 @nav-link [label Layout, href #layout]
@@ -31,40 +31,40 @@
31 @nav-link [label Functions, href #functions]
31 @nav-link [label Functions, href #functions]
32
32
33 -- Prose
33 -- Prose
34− @main [padding 48, padding-x 56, max-width 800, spacing 24]
34+ @main [padding 48, padding-inline 56, max-width 800, spacing 24]
35− @text [bold, size 36, color $ink] Getting started
35+ @text [font-weight bold, font-size 36, color $ink] Getting started
36
36
37− @paragraph [color $ink, line-height 1.7, size 17]
37+ @paragraph [color $ink, line-height 1.7, font-size 17]
38 htmlang is a minimalist layout language that compiles to a single static
38 htmlang is a minimalist layout language that compiles to a single static
39 HTML file. This guide walks through the essentials in about five minutes.
39 HTML file. This guide walks through the essentials in about five minutes.
40
40
41− @text [bold, size 24, color $ink] Installation
41+ @text [font-weight bold, font-size 24, color $ink] Installation
42
42
43− @pre [background $code-bg, padding 16, rounded 8, overflow-x auto]
43+ @pre [background $code-bg, padding 16, border-radius 8, overflow-x auto]
44 cargo install --path .
44 cargo install --path .
45
45
46− @text [bold, size 24, color $ink] Your first page
46+ @text [font-weight bold, font-size 24, color $ink] Your first page
47
47
48 @paragraph [color $ink, line-height 1.7]
48 @paragraph [color $ink, line-height 1.7]
49− Create a file called {@code [background $code-bg, padding 2 6, rounded 4] hello.hl} with:
49+ Create a file called {@code [background $code-bg, padding 2 6, border-radius 4] hello.hl} with:
50
50
51− @pre [background $code-bg, padding 16, rounded 8, overflow-x auto]
51+ @pre [background $code-bg, padding 16, border-radius 8, overflow-x auto]
52 @page Hello
52 @page Hello
53 @column [padding 40, spacing 16]
53 @column [padding 40, spacing 16]
54− @text [bold, size 32] Hi there
54+ @text [font-weight bold, font-size 32] Hi there
55
55
56 @paragraph [color $ink, line-height 1.7]
56 @paragraph [color $ink, line-height 1.7]
57 Compile it:
57 Compile it:
58
58
59− @pre [background $code-bg, padding 16, rounded 8, overflow-x auto]
59+ @pre [background $code-bg, padding 16, border-radius 8, overflow-x auto]
60 htmlang hello.hl
60 htmlang hello.hl
61
61
62 @paragraph [color $ink, line-height 1.7]
62 @paragraph [color $ink, line-height 1.7]
63− You now have {@code [background $code-bg, padding 2 6, rounded 4] hello.html},
63+ You now have {@code [background $code-bg, padding 2 6, border-radius 4] hello.html},
64 a single self-contained file with embedded CSS. No dependencies, no
64 a single self-contained file with embedded CSS. No dependencies, no
65 JavaScript, no build step beyond the one command you just ran.
65 JavaScript, no build step beyond the one command you just ran.
66
66
67− @text [bold, size 24, color $ink] Next steps
67+ @text [font-weight bold, font-size 24, color $ink] Next steps
68
68
69 @list [spacing 8]
69 @list [spacing 8]
70 @item
70 @item
examples/landing.hl+15−15
@@ -6,8 +6,8 @@
6@let bg-subtle #f8fafc
6@let bg-subtle #f8fafc
7
7
8@let feature $title $body
8@let feature $title $body
9− @column [padding 24, background white, rounded 12, border 1 #e2e8f0, spacing 8]
9+ @column [padding 24, background white, border-radius 12, border 1 solid #e2e8f0, spacing 8]
10− @text [bold, size 18, color $ink] $title
10+ @text [font-weight bold, font-size 18, color $ink] $title
11 @paragraph [color $muted, line-height 1.6]
11 @paragraph [color $muted, line-height 1.6]
12 $body
12 $body
13
13
@@ -15,33 +15,33 @@
15 -- Hero
15 -- Hero
16 @section [
16 @section [
17 padding 80,
17 padding 80,
18− padding-x 24,
18+ padding-inline 24,
19 background $bg-subtle,
19 background $bg-subtle,
20 text-align center,
20 text-align center,
21 spacing 24
21 spacing 24
22 ]
22 ]
23− @text [bold, size 48, color $ink] Build faster with less CSS.
23+ @text [font-weight bold, font-size 48, color $ink] Build faster with less CSS.
24− @paragraph [max-width 560, center-x, color $muted, line-height 1.6, size 18]
24+ @paragraph [max-width 560, center-x, color $muted, line-height 1.6, font-size 18]
25 Launchpad turns your static pages into a polished product in minutes.
25 Launchpad turns your static pages into a polished product in minutes.
26 Ship faster, iterate smaller, sleep at night.
26 Ship faster, iterate smaller, sleep at night.
27 @row [center-x, spacing 12]
27 @row [center-x, spacing 12]
28 @link [
28 @link [
29− padding 14, padding-x 24, rounded 8,
29+ padding 14, padding-inline 24, border-radius 8,
30− background $primary, color white, bold,
30+ background $primary, color white, font-weight bold,
31 transition all 0.15s ease
31 transition all 0.15s ease
32 ] /signup
32 ] /signup
33 Start free
33 Start free
34 @link [
34 @link [
35− padding 14, padding-x 24, rounded 8,
35+ padding 14, padding-inline 24, border-radius 8,
36− border 1 #e2e8f0, color $ink, bold,
36+ border 1 solid #e2e8f0, color $ink, font-weight bold,
37 transition all 0.15s ease
37 transition all 0.15s ease
38 ] /demo
38 ] /demo
39 Watch demo
39 Watch demo
40
40
41 -- Features
41 -- Features
42− @section [padding 80, padding-x 24, max-width 1100, center-x, spacing 40]
42+ @section [padding 80, padding-inline 24, max-width 1100, center-x, spacing 40]
43 @column [spacing 8, text-align center]
43 @column [spacing 8, text-align center]
44− @text [bold, size 32, color $ink] Everything you need
44+ @text [font-weight bold, font-size 32, color $ink] Everything you need
45 @text [color $muted] Opinionated where it matters, flexible where it counts.
45 @text [color $muted] Opinionated where it matters, flexible where it counts.
46 @row [wrap, gap 20]
46 @row [wrap, gap 20]
47 @feature [
47 @feature [
@@ -58,13 +58,13 @@
58 ]
58 ]
59
59
60 -- CTA
60 -- CTA
61− @section [padding 60, padding-x 24, background $primary, color white, text-align center, spacing 16]
61+ @section [padding 60, padding-inline 24, background $primary, color white, text-align center, spacing 16]
62− @text [bold, size 28] Ready to launch?
62+ @text [font-weight bold, font-size 28] Ready to launch?
63 @paragraph [max-width 520, center-x, line-height 1.6]
63 @paragraph [max-width 520, center-x, line-height 1.6]
64 Join thousands of teams building their marketing sites with htmlang.
64 Join thousands of teams building their marketing sites with htmlang.
65 @link [
65 @link [
66− padding 14, padding-x 28, rounded 8,
66+ padding 14, padding-inline 28, border-radius 8,
67− background white, color $primary, bold,
67+ background white, color $primary, font-weight bold,
68 transition all 0.15s ease
68 transition all 0.15s ease
69 ] /signup
69 ] /signup
70 Start for free
70 Start for free
src/bin/htmlang_lsp/completion.rs+2−2
@@ -907,7 +907,7 @@ mod tests {
907
907
908 #[test]
908 #[test]
909 fn owning_element_finds_element_on_same_line() {
909 fn owning_element_finds_element_on_same_line() {
910− let text = "@input [type text, ";
910+ let text = "@input [type=text, ";
911 // Cursor at end of line — inside the unclosed `[`.
911 // Cursor at end of line — inside the unclosed `[`.
912 assert_eq!(
912 assert_eq!(
913 owning_element(text, pos(0, text.len() as u32)),
913 owning_element(text, pos(0, text.len() as u32)),
@@ -966,7 +966,7 @@ mod tests {
966 }
966 }
967 };
967 };
968 assert_eq!(insert("spacing"), "spacing ");
968 assert_eq!(insert("spacing"), "spacing ");
969− assert_eq!(insert("bold"), "bold");
969+ assert_eq!(insert("wrap"), "wrap");
970 assert_eq!(insert("opacity"), "opacity ");
970 assert_eq!(insert("opacity"), "opacity ");
971 assert_eq!(insert("type="), "type=");
971 assert_eq!(insert("type="), "type=");
972 assert_eq!(insert("required"), "required");
972 assert_eq!(insert("required"), "required");
src/bin/htmlang_lsp/docs.rs+1−13
@@ -36,7 +36,7 @@ pub(crate) const ELEMENTS: &[Doc] = &[
36 doc("row", "Horizontal layout: a flex row.", "@row [spacing 10]\n @text A\n @text B"),
36 doc("row", "Horizontal layout: a flex row.", "@row [spacing 10]\n @text A\n @text B"),
37 doc("column", "Vertical layout: a flex column.", "@column [spacing 10]\n @text A\n @text B"),
37 doc("column", "Vertical layout: a flex column.", "@column [spacing 10]\n @text A\n @text B"),
38 doc("el", "Generic container (a flex column).", "@el [padding 20, background white]\n Content"),
38 doc("el", "Generic container (a flex column).", "@el [padding 20, background white]\n Content"),
39− doc("text", "Styled inline text (`<span>`).", "@text [bold, size 24] Hello"),
39+ doc("text", "Styled inline text (`<span>`).", "@text [font-weight bold, font-size 24] Hello"),
40 doc(
40 doc(
41 "paragraph",
41 "paragraph",
42 "Flowing text (`<p>`); holds inline `{@...}` elements.",
42 "Flowing text (`<p>`); holds inline `{@...}` elements.",
@@ -114,11 +114,7 @@ pub(crate) const DIRECTIVES: &[Doc] = &[
114pub(crate) const ATTRIBUTES: &[Doc] = &[
114pub(crate) const ATTRIBUTES: &[Doc] = &[
115 doc("spacing", "Gap between children.", "spacing 20"),
115 doc("spacing", "Gap between children.", "spacing 20"),
116 doc("padding", "Inner space: 1 to 4 values; bare numbers are px.", "padding 12 24"),
116 doc("padding", "Inner space: 1 to 4 values; bare numbers are px.", "padding 12 24"),
117− doc("padding-x", "Horizontal padding.", "padding-x 16"),
118− doc("padding-y", "Vertical padding.", "padding-y 8"),
119 doc("margin", "Outer space: 1 to 4 values; bare numbers are px.", "margin 0 auto"),
117 doc("margin", "Outer space: 1 to 4 values; bare numbers are px.", "margin 0 auto"),
120− doc("margin-x", "Horizontal margin.", "margin-x 16"),
121− doc("margin-y", "Vertical margin.", "margin-y 8"),
122 doc("width", "`fill` takes the remaining space, `shrink` fits the content, or a size.", "width fill"),
118 doc("width", "`fill` takes the remaining space, `shrink` fits the content, or a size.", "width fill"),
123 doc("height", "`fill` takes the remaining space, `shrink` fits the content, or a size.", "height 200"),
119 doc("height", "`fill` takes the remaining space, `shrink` fits the content, or a size.", "height 200"),
124 doc("center-x", "Centers the element horizontally in its parent.", "center-x"),
120 doc("center-x", "Centers the element horizontally in its parent.", "center-x"),
@@ -132,14 +128,6 @@ pub(crate) const ATTRIBUTES: &[Doc] = &[
132 doc("grid-rows", "Number of equal grid rows.", "grid-rows 2"),
128 doc("grid-rows", "Number of equal grid rows.", "grid-rows 2"),
133 doc("col-span", "Columns a grid child spans.", "col-span 2"),
129 doc("col-span", "Columns a grid child spans.", "col-span 2"),
134 doc("row-span", "Rows a grid child spans.", "row-span 2"),
130 doc("row-span", "Rows a grid child spans.", "row-span 2"),
135− doc("hidden", "Hides the element (`display: none`).", "hidden"),
136− doc("border", "Solid border: width, then color.", "border 1 #e5e7eb"),
137− doc("rounded", "Corner radius.", "rounded 8"),
138− doc("bold", "Bold text.", "bold"),
139− doc("italic", "Italic text.", "italic"),
140− doc("underline", "Underlined text.", "underline"),
141− doc("size", "Font size; bare numbers are px.", "size 18"),
142− doc("font", "Font family; quote a stack with commas.", "font \"Inter, sans-serif\""),
143 doc("line-height", "Line height; integers of 2 or more are px.", "line-height 1.5"),
131 doc("line-height", "Line height; integers of 2 or more are px.", "line-height 1.5"),
144 doc("ordered", "On `@list`: a numbered list (`<ol>`).", "ordered"),
132 doc("ordered", "On `@list`: a numbered list (`<ol>`).", "ordered"),
145 doc("inline", "On `@image`: embed the file (SVG markup, or other images as base64) in the page.", "inline"),
133 doc("inline", "On `@image`: embed the file (SVG markup, or other images as base64) in the page.", "inline"),
src/fmt.rs+7−7
@@ -465,7 +465,7 @@ mod tests {
465 #[test]
465 #[test]
466 fn idempotent_at_wrap_threshold() {
466 fn idempotent_at_wrap_threshold() {
467 // Build a line long enough to cross MAX_LINE_WIDTH after the first format.
467 // Build a line long enough to cross MAX_LINE_WIDTH after the first format.
468− let src = "@row [padding 20, background white, rounded 8, border 1 #e5e7eb, color #333, shadow 0 2px 4px rgba(0,0,0,0.1)]\n child\n";
468+ let src = "@row [padding 20, background white, border-radius 8, border 1 solid #e5e7eb, color #333, box-shadow 0 2px 4px rgba(0,0,0,0.1)]\n child\n";
469 let a = format(src);
469 let a = format(src);
470 let b = format(&a);
470 let b = format(&a);
471 assert_eq!(a, b, "formatter must be idempotent across runs");
471 assert_eq!(a, b, "formatter must be idempotent across runs");
@@ -473,7 +473,7 @@ mod tests {
473
473
474 #[test]
474 #[test]
475 fn preserves_trailing_comment() {
475 fn preserves_trailing_comment() {
476− let src = "@text [bold] hello -- greeting\n";
476+ let src = "@text [font-weight bold] hello -- greeting\n";
477 let out = format(src);
477 let out = format(src);
478 assert!(
478 assert!(
479 out.contains("-- greeting"),
479 out.contains("-- greeting"),
@@ -483,7 +483,7 @@ mod tests {
483
483
484 #[test]
484 #[test]
485 fn bracket_inside_string_is_ignored() {
485 fn bracket_inside_string_is_ignored() {
486− let src = "@text [content \"a [b] c\", bold] hi\n";
486+ let src = "@text [content \"a [b] c\", font-weight bold] hi\n";
487 let out = format(src);
487 let out = format(src);
488 // Single-line emission — no multi-line bracket block should be triggered.
488 // Single-line emission — no multi-line bracket block should be triggered.
489 assert_eq!(out.lines().count(), 1, "got:\n{out}");
489 assert_eq!(out.lines().count(), 1, "got:\n{out}");
@@ -498,7 +498,7 @@ mod tests {
498
498
499 #[test]
499 #[test]
500 fn apostrophe_does_not_swallow_file() {
500 fn apostrophe_does_not_swallow_file() {
501− let src = "@button [aria-label Don't click, padding 10] Go\n@text after\n";
501+ let src = "@button [aria-label=Don't click, padding 10] Go\n@text after\n";
502 assert_eq!(format(src), src);
502 assert_eq!(format(src), src);
503 }
503 }
504
504
@@ -510,14 +510,14 @@ mod tests {
510
510
511 #[test]
511 #[test]
512 fn bracket_in_text_does_not_join_lines() {
512 fn bracket_in_text_does_not_join_lines() {
513− let src = "@column\n Use [ to open a list\n @text [bold] Second\n";
513+ let src = "@column\n Use [ to open a list\n @text [font-weight bold] Second\n";
514 assert_eq!(format(src), src);
514 assert_eq!(format(src), src);
515 }
515 }
516
516
517 #[test]
517 #[test]
518 fn commas_inside_parens_stay_in_value() {
518 fn commas_inside_parens_stay_in_value() {
519− let src = "@el [shadow 0 1px 3px rgba(0,0,0,0.1),bold] hi\n";
519+ let src = "@el [box-shadow 0 1px 3px rgba(0,0,0,0.1),font-weight bold] hi\n";
520− assert_eq!(format(src), "@el [shadow 0 1px 3px rgba(0,0,0,0.1), bold] hi\n");
520+ assert_eq!(format(src), "@el [box-shadow 0 1px 3px rgba(0,0,0,0.1), font-weight bold] hi\n");
521 }
521 }
522
522
523 #[test]
523 #[test]
src/upgrade.rs+105−4
@@ -151,7 +151,8 @@ pub fn upgrade(input: &str) -> Upgrade {
151 let mut i = 0;
151 let mut i = 0;
152 // A file may define its own function named like an old alias (e.g.
152 // A file may define its own function named like an old alias (e.g.
153 // `@let divider`); calls to it must not be renamed.
153 // `@let divider`); calls to it must not be renamed.
154− // (Lines inside `"""` strings don't count: they're text.)
154+ // (Lines inside `"""` strings and verbatim bodies don't count: they're
155+ // text.)
155 let mut user_defined: Vec<&str> = Vec::new();
156 let mut user_defined: Vec<&str> = Vec::new();
156 let mut j = 0;
157 let mut j = 0;
157 while j < lines.len() {
158 while j < lines.len() {
@@ -159,6 +160,10 @@ pub fn upgrade(input: &str) -> Upgrade {
159 j = end;
160 j = end;
160 continue;
161 continue;
161 }
162 }
163+ if VERBATIM_BODIES.iter().any(|d| starts_directive(lines[j].trim(), d)) {
164+ j = block_end(&lines, j);
165+ continue;
166+ }
162 if let Some(name) = lines[j]
167 if let Some(name) = lines[j]
163 .trim()
168 .trim()
164 .strip_prefix("@let ")
169 .strip_prefix("@let ")
@@ -358,7 +363,11 @@ fn upgrade_lists(text: &str, manual: &mut Vec<(usize, String)>) -> (String, usiz
358 i += added;
363 i += added;
359 continue;
364 continue;
360 }
365 }
366+ // (A second variable named like an index already is one.)
367+ let index_name = vars
368+ .get(1)
369+ .is_some_and(|v| ["i", "j", "k", "n", "idx", "index", "pos", "position", "num"].contains(&v.as_str()));
361− if vars.len() > 2 || (vars.len() == 2 && list.starts_with('$')) {
370+ if vars.len() > 2 || (vars.len() == 2 && list.starts_with('$') && !index_name) {
362 manual.push((
371 manual.push((
363 i + 1,
372 i + 1,
364 format!(
373 format!(
@@ -1380,6 +1389,8 @@ fn rewrite_attr_list(list: &str, element_attrs: bool) -> String {
1380 }
1389 }
1381 _ => body,
1390 _ => body,
1382 };
1391 };
1392+ // Style aliases that became their CSS properties
1393+ let body = if element_attrs { css_alias_or_choice(&body).unwrap_or(body) } else { body };
1383 // Renamed keys (keeping any `hover:` / `md:` style prefix)
1394 // Renamed keys (keeping any `hover:` / `md:` style prefix)
1384 let key_end = body.find(char::is_whitespace).unwrap_or(body.len());
1395 let key_end = body.find(char::is_whitespace).unwrap_or(body.len());
1385 let (key, value) = body.split_at(key_end);
1396 let (key, value) = body.split_at(key_end);
@@ -1399,6 +1410,70 @@ fn rewrite_attr_list(list: &str, element_attrs: bool) -> String {
1399 out
1410 out
1400}
1411}
1401
1412
1413+/// `css_alias`, also inside the branches of a whole-attribute
1414+/// `if(cond, a, b)`.
1415+fn css_alias_or_choice(attr: &str) -> Option<String> {
1416+ let Some(inner) = attr.trim_end().strip_prefix("if(").and_then(|a| a.strip_suffix(')')) else {
1417+ return css_alias(attr);
1418+ };
1419+ let args = split_top_level_commas(inner);
1420+ let [condition, branches @ ..] = args.as_slice() else {
1421+ return None;
1422+ };
1423+ let branches: Vec<String> =
1424+ branches.iter().map(|b| css_alias(b.trim()).unwrap_or_else(|| b.trim().to_string())).collect();
1425+ let new = format!("if({}, {})", condition.trim(), branches.join(", "));
1426+ (new != attr.trim_end()).then_some(new)
1427+}
1428+
1429+/// A style alias as the CSS it stood for (keeping any `hover:` / `md:`
1430+/// prefix): `bold` → `font-weight bold`, `size 18` → `font-size 18`,
1431+/// `border 1 red` → `border 1 solid red`. `None` if `attr` isn't one.
1432+fn css_alias(attr: &str) -> Option<String> {
1433+ let (key, value) = match attr.split_once(' ') {
1434+ Some((key, value)) => (key, Some(value.trim())),
1435+ None => (attr.trim(), None),
1436+ };
1437+ let (prefix, base) = key.rsplit_once(':').map_or(("", key), |(p, b)| (p, b));
1438+ let prefix = if prefix.is_empty() { String::new() } else { format!("{prefix}:") };
1439+ let css = match (base, value) {
1440+ ("bold", None) => "font-weight bold".to_string(),
1441+ ("italic", None) => "font-style italic".to_string(),
1442+ ("underline", None) => "text-decoration underline".to_string(),
1443+ ("hidden", None) => "display none".to_string(),
1444+ ("size", Some(v)) => format!("font-size {v}"),
1445+ ("rounded", Some(v)) => format!("border-radius {v}"),
1446+ ("padding-x", Some(v)) => format!("padding-inline {v}"),
1447+ ("padding-y", Some(v)) => format!("padding-block {v}"),
1448+ ("margin-x", Some(v)) => format!("margin-inline {v}"),
1449+ ("margin-y", Some(v)) => format!("margin-block {v}"),
1450+ // `font Inter` named a family; a CSS `font` shorthand has a size
1451+ ("font", Some(v)) if !v.split_whitespace().any(|t| t.starts_with(|c: char| c.is_ascii_digit())) => {
1452+ format!("font-family {v}")
1453+ }
1454+ ("border" | "border-top" | "border-right" | "border-bottom" | "border-left", Some(v)) => {
1455+ const STYLES: &[&str] = &[
1456+ "none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge",
1457+ "inset", "outset",
1458+ ];
1459+ let mut parts = v.split_whitespace();
1460+ let width = parts.next()?;
1461+ let is_width = width.starts_with(|c: char| c.is_ascii_digit())
1462+ || ["thin", "medium", "thick"].contains(&width);
1463+ let rest: Vec<&str> = parts.collect();
1464+ if !is_width || width == "0" || rest.first().is_some_and(|s| STYLES.contains(s)) {
1465+ return None;
1466+ }
1467+ match rest.as_slice() {
1468+ [] => format!("{base} {width} solid"),
1469+ rest => format!("{base} {width} solid {}", rest.join(" ")),
1470+ }
1471+ }
1472+ _ => return None,
1473+ };
1474+ Some(format!("{prefix}{css}"))
1475+}
1476+
1402/// Does the attribute list at the start of `rest` (if any) have `key`?
1477/// Does the attribute list at the start of `rest` (if any) have `key`?
1403fn has_attr_key(rest: &str, key: &str) -> bool {
1478fn has_attr_key(rest: &str, key: &str) -> bool {
1404 let Some(list) = rest.strip_prefix('[').and_then(|r| r.split(']').next()) else {
1479 let Some(list) = rest.strip_prefix('[').and_then(|r| r.split(']').next()) else {
@@ -1637,7 +1712,7 @@ mod tests {
1637 fn directives() {
1712 fn directives() {
1638 assert_eq!(up("@unless $x\n hi"), "@if not $x\n hi");
1713 assert_eq!(up("@unless $x\n hi"), "@if not $x\n hi");
1639 assert_eq!(up("@fn card $t\n @text $t"), "@let card $t\n @text $t");
1714 assert_eq!(up("@fn card $t\n @text $t"), "@let card $t\n @text $t");
1640− assert_eq!(up("@define c [bold]\n@mixin m [italic]"), "@let c [bold]\n@let m [italic]");
1715+ assert_eq!(up("@define c [bold]\n@mixin m [italic]"), "@let c [font-weight bold]\n@let m [font-style italic]");
1641 assert_eq!(up("@for $i in 1..3\n $i"), "@each $i in 1..3\n $i");
1716 assert_eq!(up("@for $i in 1..3\n $i"), "@each $i in 1..3\n $i");
1642 assert_eq!(up("@use \"lib.hl\" a, b"), "@include lib.hl");
1717 assert_eq!(up("@use \"lib.hl\" a, b"), "@include lib.hl");
1643 assert_eq!(up("@import theme.hl\n@import \"ui.hl\" as ui\n@ui.card [x $ui.gap]"), "@include theme.hl\n@include \"ui.hl\"\n@card [x $gap]");
1718 assert_eq!(up("@import theme.hl\n@import \"ui.hl\" as ui\n@ui.card [x $ui.gap]"), "@include theme.hl\n@include \"ui.hl\"\n@card [x $gap]");
@@ -1686,7 +1761,7 @@ mod tests {
1686 assert_eq!(up("@el [\n animate spin 1s\n]"), "@el [\n animation spin 1s\n]");
1761 assert_eq!(up("@el [\n animate spin 1s\n]"), "@el [\n animation spin 1s\n]");
1687 assert_eq!(up("@text $name|upper|len"), "@text ${length(uppercase($name))}");
1762 assert_eq!(up("@text $name|upper|len"), "@text ${length(uppercase($name))}");
1688 // Text that merely mentions a renamed word is untouched.
1763 // Text that merely mentions a renamed word is untouched.
1689− assert_eq!(up("@text [bold] please animate this"), "@text [bold] please animate this");
1764+ assert_eq!(up("@text [opacity 1] please animate this"), "@text [opacity 1] please animate this");
1690 }
1765 }
1691
1766
1692 #[test]
1767 #[test]
@@ -1826,6 +1901,32 @@ mod tests {
1826 );
1901 );
1827 }
1902 }
1828
1903
1904+ #[test]
1905+ fn definitions_in_verbatim_bodies_are_text() {
1906+ assert_eq!(
1907+ up("@raw\n @let button $x\n@button [bold] Go\n"),
1908+ "@raw\n @let button $x\n@button [font-weight bold] Go\n"
1909+ );
1910+ }
1911+
1912+ #[test]
1913+ fn style_aliases_become_css() {
1914+ assert_eq!(
1915+ up("@el [bold, hover:underline, size 18, rounded 8, padding-x 4, md:hidden, font \"Inter, sans-serif\"]\n"),
1916+ "@el [font-weight bold, hover:text-decoration underline, font-size 18, border-radius 8, padding-inline 4, md:display none, font-family \"Inter, sans-serif\"]\n"
1917+ );
1918+ assert_eq!(
1919+ up("@el [border 1 #e5e7eb, focus:border-top 2px $c, border 0, border 1px dashed red, font 16px Inter]\n"),
1920+ "@el [border 1 solid #e5e7eb, focus:border-top 2px solid $c, border 0, border 1px dashed red, font 16px Inter]\n"
1921+ );
1922+ // HTML attributes and function parameters are left alone
1923+ assert_eq!(up("@select [size=4]\n@let card $size\n @el\n@card [size 3]\n"), "@select [size=4]\n@let card $size\n @el\n@card [size 3]\n");
1924+ assert_eq!(up("@el [if($on, bold, italic)]\n"), "@el [if($on, font-weight bold, font-style italic)]\n");
1925+ // Idempotent
1926+ let once = up("@el [bold, border 1 red]\n");
1927+ assert_eq!(up(&once), once);
1928+ }
1929+
1829 #[test]
1930 #[test]
1830 fn loops_and_lists() {
1931 fn loops_and_lists() {
1831 assert_eq!(
1932 assert_eq!(
tests/regressions.rs+21−9
@@ -81,7 +81,7 @@ fn range_at_integer_limit_terminates() {
81
81
82#[test]
82#[test]
83fn reset_css_is_layered_below_generated_rules() {
83fn reset_css_is_layered_below_generated_rules() {
84− let out = compile("@page T\n@link [color red, underline] /x Home");
84+ let out = compile("@page T\n@link [color red, text-decoration underline] /x Home");
85 assert!(
85 assert!(
86 out.contains("@layer hl-reset,htmlang;@layer hl-reset{"),
86 out.contains("@layer hl-reset,htmlang;@layer hl-reset{"),
87 "reset must be layered so class rules can override it: {}",
87 "reset must be layered so class rules can override it: {}",
@@ -91,7 +91,7 @@ fn reset_css_is_layered_below_generated_rules() {
91
91
92#[test]
92#[test]
93fn px_values_are_not_doubled() {
93fn px_values_are_not_doubled() {
94− let out = compile("@el [padding 10px, margin 0 auto, max-width none, blur 4px] x");
94+ let out = compile("@el [padding 10px, margin 0 auto, max-width none, filter blur(4px)] x");
95 assert!(out.contains("padding:10px"), "{}", out);
95 assert!(out.contains("padding:10px"), "{}", out);
96 assert!(out.contains("margin:0 auto"), "{}", out);
96 assert!(out.contains("margin:0 auto"), "{}", out);
97 assert!(out.contains("max-width:none"), "{}", out);
97 assert!(out.contains("max-width:none"), "{}", out);
@@ -100,7 +100,7 @@ fn px_values_are_not_doubled() {
100
100
101#[test]
101#[test]
102fn minify_keeps_significant_spaces() {
102fn minify_keeps_significant_spaces() {
103− let result = parser::parse("@page T\n@paragraph\n Built with {@text [bold] htmlang}.");
103+ let result = parser::parse("@page T\n@paragraph\n Built with {@text [font-weight bold] htmlang}.");
104 let out = codegen::generate_minified(&result.document);
104 let out = codegen::generate_minified(&result.document);
105 assert!(out.contains("Built with <span"), "{}", out);
105 assert!(out.contains("Built with <span"), "{}", out);
106}
106}
@@ -141,7 +141,7 @@ fn image_with_css_width_gets_no_intrinsic_height() {
141 r#"<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000"></svg>"#,
141 r#"<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000"></svg>"#,
142 )
142 )
143 .unwrap();
143 .unwrap();
144− let out = compile(&format!("@image [width 200, alt x] {}", svg.display()));
144+ let out = compile(&format!("@image [width 200, alt=x] {}", svg.display()));
145 assert!(!out.contains("height=\"1000\""), "{}", out);
145 assert!(!out.contains("height=\"1000\""), "{}", out);
146}
146}
147
147
@@ -158,7 +158,7 @@ fn source_map_uses_real_output_lines() {
158
158
159#[test]
159#[test]
160fn bracket_in_text_does_not_join_lines() {
160fn bracket_in_text_does_not_join_lines() {
161− let out = compile("@column\n Use [ to open a list\n @text [bold] Second\n @text Third");
161+ let out = compile("@column\n Use [ to open a list\n @text [font-weight bold] Second\n @text Third");
162 assert!(out.contains("Use [ to open a list"), "{}", out);
162 assert!(out.contains("Use [ to open a list"), "{}", out);
163 assert!(out.contains(">Second<"), "{}", out);
163 assert!(out.contains(">Second<"), "{}", out);
164 assert!(out.contains(">Third<"), "{}", out);
164 assert!(out.contains(">Third<"), "{}", out);
@@ -229,7 +229,7 @@ fn quoted_let_values_are_not_evaluated_as_arithmetic() {
229
229
230#[test]
230#[test]
231fn quoted_font_stack_is_one_attribute() {
231fn quoted_font_stack_is_one_attribute() {
232− let out = compile("@el [font \"Inter, sans-serif\", bold] x");
232+ let out = compile("@el [font-family \"Inter, sans-serif\", font-weight bold] x");
233 assert!(out.contains("font-family:Inter, sans-serif"), "{}", out);
233 assert!(out.contains("font-family:Inter, sans-serif"), "{}", out);
234 assert!(out.contains("font-weight:bold"), "{}", out);
234 assert!(out.contains("font-weight:bold"), "{}", out);
235}
235}
@@ -294,7 +294,7 @@ fn html_attributes_use_equals_and_are_all_emitted() {
294
294
295#[test]
295#[test]
296fn html_attribute_and_style_with_the_same_name_are_distinct() {
296fn html_attribute_and_style_with_the_same_name_are_distinct() {
297− let out = compile("@select [size=4, size 20]\n @option A");
297+ let out = compile("@select [size=4, font-size 20]\n @option A");
298 assert!(out.contains("size=\"4\""), "{}", out);
298 assert!(out.contains("size=\"4\""), "{}", out);
299 assert!(out.contains("font-size:20px"), "{}", out);
299 assert!(out.contains("font-size:20px"), "{}", out);
300 let out = compile("@image [width=800, width 200, alt=x] a.png");
300 let out = compile("@image [width=800, width 200, alt=x] a.png");
@@ -346,7 +346,7 @@ fn invalid_expressions_are_errors() {
346
346
347#[test]
347#[test]
348fn htmlang_attributes_sharing_html_names_do_not_warn() {
348fn htmlang_attributes_sharing_html_names_do_not_warn() {
349− let result = parser::parse("@row [size 18, wrap, hidden]\n @text x");
349+ let result = parser::parse("@row [font-size 18, wrap, display none]\n @text x");
350 assert!(
350 assert!(
351 result.diagnostics.is_empty(),
351 result.diagnostics.is_empty(),
352 "unexpected diagnostics: {:?}",
352 "unexpected diagnostics: {:?}",
@@ -396,7 +396,7 @@ fn inline_svg_resolves_from_the_page_and_keeps_attributes() {
396#[test]
396#[test]
397fn inline_element_attributes_can_continue_on_the_next_line() {
397fn inline_element_attributes_can_continue_on_the_next_line() {
398 let result = htmlang::parser::parse(
398 let result = htmlang::parser::parse(
399− "@paragraph\n Press {@kbd [\n padding 2 6, rounded 4\n ] Ctrl+K} to search.\n",
399+ "@paragraph\n Press {@kbd [\n padding 2 6, border-radius 4\n ] Ctrl+K} to search.\n",
400 );
400 );
401 assert!(result.diagnostics.is_empty(), "{:?}", result.diagnostics);
401 assert!(result.diagnostics.is_empty(), "{:?}", result.diagnostics);
402 let html = htmlang::codegen::generate(&result.document);
402 let html = htmlang::codegen::generate(&result.document);
@@ -428,3 +428,15 @@ fn raw_takes_an_indented_body() {
428 result.diagnostics
428 result.diagnostics
429 );
429 );
430}
430}
431+
432+#[test]
433+fn style_aliases_point_to_css() {
434+ let result = parser::parse("@el [bold, size 18, font-weight bold]\n x");
435+ let messages: Vec<&str> = result.diagnostics.iter().map(|d| d.message.as_str()).collect();
436+ assert!(messages.iter().any(|m| m.contains("`font-weight bold`")), "{:?}", messages);
437+ assert!(messages.iter().any(|m| m.contains("`font-size`")), "{:?}", messages);
438+ // Border shorthands get px like other lengths
439+ let out = compile("@el [border 1 solid red, border-radius 4 4 0 0] x");
440+ assert!(out.contains("border:1px solid red"), "{}", out);
441+ assert!(out.contains("border-radius:4px 4px 0 0"), "{}", out);
442+}
tests/snapshots.rs+14−14
@@ -1299,13 +1299,13 @@ fn css_margin() {
1299fn css_margin_x() {
1299fn css_margin_x() {
1300 // margin-x emits the `margin-inline` logical shorthand (single property,
1300 // margin-x emits the `margin-inline` logical shorthand (single property,
1301 // covers both inline sides, stays symmetric under RTL).
1301 // covers both inline sides, stays symmetric under RTL).
1302− let output = compile("@page T\n@el [margin-x 10]");
1302+ let output = compile("@page T\n@el [margin-inline 10]");
1303 assert!(output.contains("margin-inline:10px"));
1303 assert!(output.contains("margin-inline:10px"));
1304}
1304}
1305
1305
1306#[test]
1306#[test]
1307fn css_margin_y() {
1307fn css_margin_y() {
1308− let output = compile("@page T\n@el [margin-y 10]");
1308+ let output = compile("@page T\n@el [margin-block 10]");
1309 assert!(output.contains("margin-block:10px"));
1309 assert!(output.contains("margin-block:10px"));
1310}
1310}
1311
1311
@@ -1560,7 +1560,7 @@ fn snapshot_lang_directive() {
1560
1560
1561#[test]
1561#[test]
1562fn focus_visible_generates_pseudo() {
1562fn focus_visible_generates_pseudo() {
1563− let output = compile("@page T\n@el [focus-visible:border 2 blue]");
1563+ let output = compile("@page T\n@el [focus-visible:border 2 solid blue]");
1564 assert!(output.contains(":focus-visible"));
1564 assert!(output.contains(":focus-visible"));
1565 assert!(output.contains("border:2px solid blue"));
1565 assert!(output.contains("border:2px solid blue"));
1566}
1566}
@@ -1633,7 +1633,7 @@ fn fragment_no_wrapper() {
1633
1633
1634#[test]
1634#[test]
1635fn hidden_generates_display_none() {
1635fn hidden_generates_display_none() {
1636− let output = compile("@page T\n@el [hidden]");
1636+ let output = compile("@page T\n@el [display none]");
1637 assert!(output.contains("display:none"));
1637 assert!(output.contains("display:none"));
1638}
1638}
1639
1639
@@ -1744,7 +1744,7 @@ fn favicon_fallback_href() {
1744#[test]
1744#[test]
1745fn no_warning_new_pseudo_prefixes() {
1745fn no_warning_new_pseudo_prefixes() {
1746 let diags = parse_diagnostics(
1746 let diags = parse_diagnostics(
1747− "@el [focus-visible:border 2 blue, disabled:opacity 0.5, checked:background green]",
1747+ "@el [focus-visible:border 2 solid blue, disabled:opacity 0.5, checked:background green]",
1748 );
1748 );
1749 assert!(
1749 assert!(
1750 !diags
1750 !diags
@@ -1772,7 +1772,7 @@ fn no_warning_child_selectors() {
1772#[test]
1772#[test]
1773fn no_warning_new_css_attrs() {
1773fn no_warning_new_css_attrs() {
1774 let diags = parse_diagnostics(
1774 let diags = parse_diagnostics(
1775− "@el [overflow-x hidden, overflow-y auto, inset 0, accent-color blue, hidden]",
1775+ "@el [overflow-x hidden, overflow-y auto, inset 0, accent-color blue, display none]",
1776 );
1776 );
1777 assert!(
1777 assert!(
1778 !diags
1778 !diags
@@ -3522,13 +3522,13 @@ fn target_pseudo() {
3522
3522
3523#[test]
3523#[test]
3524fn valid_invalid_pseudo() {
3524fn valid_invalid_pseudo() {
3525− let output = compile("@input [type=email, valid:border 2 green]");
3525+ let output = compile("@input [type=email, valid:border 2 solid green]");
3526 assert!(output.contains(":valid"), "valid pseudo: {}", output);
3526 assert!(output.contains(":valid"), "valid pseudo: {}", output);
3527}
3527}
3528
3528
3529#[test]
3529#[test]
3530fn text_underline_offset_property() {
3530fn text_underline_offset_property() {
3531− let output = compile("@text [underline, text-underline-offset 4] Link");
3531+ let output = compile("@text [text-decoration underline, text-underline-offset 4] Link");
3532 assert!(
3532 assert!(
3533 output.contains("text-underline-offset:4px"),
3533 output.contains("text-underline-offset:4px"),
3534 "text-underline-offset: {}",
3534 "text-underline-offset: {}",
@@ -3595,7 +3595,7 @@ fn snapshot_mixin_spread() {
3595
3595
3596#[test]
3596#[test]
3597fn mixin_expands_in_attrs() {
3597fn mixin_expands_in_attrs() {
3598− let output = compile("@let card [padding 20, rounded 8]\n@el [$card]\n Hi");
3598+ let output = compile("@let card [padding 20, border-radius 8]\n@el [$card]\n Hi");
3599 assert!(
3599 assert!(
3600 output.contains("padding:20px"),
3600 output.contains("padding:20px"),
3601 "mixin should expand padding: {}",
3601 "mixin should expand padding: {}",
@@ -3610,7 +3610,7 @@ fn mixin_expands_in_attrs() {
3610
3610
3611#[test]
3611#[test]
3612fn mixin_with_dollar_syntax() {
3612fn mixin_with_dollar_syntax() {
3613− let output = compile("@let card [padding 20, rounded 8]\n@el [$card]\n Hi");
3613+ let output = compile("@let card [padding 20, border-radius 8]\n@el [$card]\n Hi");
3614 assert!(
3614 assert!(
3615 output.contains("padding:20px"),
3615 output.contains("padding:20px"),
3616 "mixin with $ syntax should expand: {}",
3616 "mixin with $ syntax should expand: {}",
@@ -3668,7 +3668,7 @@ fn snapshot_clamp_css() {
3668
3668
3669#[test]
3669#[test]
3670fn clamp_passthrough() {
3670fn clamp_passthrough() {
3671− let output = compile("@el [size clamp(16px, 2vw, 24px)]");
3671+ let output = compile("@el [font-size clamp(16px, 2vw, 24px)]");
3672 assert!(
3672 assert!(
3673 output.contains("font-size:clamp(16px, 2vw, 24px)"),
3673 output.contains("font-size:clamp(16px, 2vw, 24px)"),
3674 "clamp should pass through: {}",
3674 "clamp should pass through: {}",
@@ -3848,7 +3848,7 @@ fn switch_with_attrs() {
3848
3848
3849#[test]
3849#[test]
3850fn minified_output_is_smaller() {
3850fn minified_output_is_smaller() {
3851− let input = "@page Test\n@column [padding 20]\n @text [bold] Hello World\n @paragraph\n Some text here\n";
3851+ let input = "@page Test\n@column [padding 20]\n @text [font-weight bold] Hello World\n @paragraph\n Some text here\n";
3852 let result = htmlang::parser::parse(input);
3852 let result = htmlang::parser::parse(input);
3853 let normal = htmlang::codegen::generate(&result.document);
3853 let normal = htmlang::codegen::generate(&result.document);
3854 let minified = htmlang::codegen::generate_minified(&result.document);
3854 let minified = htmlang::codegen::generate_minified(&result.document);
@@ -4134,7 +4134,7 @@ fn test_children_fallback_content() {
4134
4134
4135#[test]
4135#[test]
4136fn test_spread_define() {
4136fn test_spread_define() {
4137− let html = compile("@let btn [padding 12, bold]\n@el [$btn]\n Click");
4137+ let html = compile("@let btn [padding 12, font-weight bold]\n@el [$btn]\n Click");
4138 assert!(
4138 assert!(
4139 html.contains("padding:12px"),
4139 html.contains("padding:12px"),
4140 "spread define should apply padding"
4140 "spread define should apply padding"
@@ -4410,7 +4410,7 @@ fn image_auto_preload() {
4410
4410
4411#[test]
4411#[test]
4412fn source_map_generation() {
4412fn source_map_generation() {
4413− let input = "@page Test\n@text [bold] Hello";
4413+ let input = "@page Test\n@text [font-weight bold] Hello";
4414 let result = htmlang::parser::parse(input);
4414 let result = htmlang::parser::parse(input);
4415 let map = htmlang::codegen::generate_source_map(&result.document, "test.hl");
4415 let map = htmlang::codegen::generate_source_map(&result.document, "test.hl");
4416 assert!(
4416 assert!(
tests/snapshots/attributes.hl+2−2
@@ -1,6 +1,6 @@
1@page Attributes
1@page Attributes
2−@el [width 200, height 100, padding 10 20, background #fff, color #333, border 1 #ccc, rounded 8, opacity 0.9]
2+@el [width 200, height 100, padding 10 20, background #fff, color #333, border 1 solid #ccc, border-radius 8, opacity 0.9]
3− @text [bold, italic, underline, size 16, font monospace] Styled
3+ @text [font-weight bold, font-style italic, text-decoration underline, font-size 16, font-family monospace] Styled
4@row [wrap, column-gap 10, row-gap 5, spacing 8]
4@row [wrap, column-gap 10, row-gap 5, spacing 8]
5 @el [min-width 50, max-width 200, min-height 30, max-height 100]
5 @el [min-width 50, max-width 200, min-height 30, max-height 100]
6@column [overflow hidden, position relative, z-index 10]
6@column [overflow hidden, position relative, z-index 10]
tests/snapshots/basic_elements.hl+1−1
@@ -1,6 +1,6 @@
1@page Basic Elements
1@page Basic Elements
2@row [spacing 10]
2@row [spacing 10]
3− @text [bold] Hello
3+ @text [font-weight bold] Hello
4 @text World
4 @text World
5@column [spacing 5]
5@column [spacing 5]
6 @el [padding 10]
6 @el [padding 10]
tests/snapshots/chain_operator.hl+1−1
@@ -1,4 +1,4 @@
1@page Chain
1@page Chain
2@el [padding 10] > @link https://example.com
2@el [padding 10] > @link https://example.com
3− @text [bold] Click
3+ @text [font-weight bold] Click
4@row [spacing 5] > @el [padding 20] > @text [color red] Deep chain
4@row [spacing 5] > @el [padding 20] > @text [color red] Deep chain
tests/snapshots/clamp_css.hl+1−1
@@ -1,4 +1,4 @@
1@page Clamp CSS Test
1@page Clamp CSS Test
2
2
3−@el [size clamp(16px, 2vw, 24px), width min(100%, 800px), padding max(10px, 2vw)]
3+@el [font-size clamp(16px, 2vw, 24px), width min(100%, 800px), padding max(10px, 2vw)]
4 Fluid typography and sizing
4 Fluid typography and sizing
tests/snapshots/color_scheme.hl+1−1
@@ -3,4 +3,4 @@
3@el [color-scheme light dark, padding 20]
3@el [color-scheme light dark, padding 20]
4 @text Adapts to system theme
4 @text Adapts to system theme
5
5
6−@input [appearance none, padding 10, border 1 #ccc, rounded 4]
6+@input [appearance none, padding 10, border 1 solid #ccc, border-radius 4]
tests/snapshots/compat_test.hl+1−1
@@ -6,4 +6,4 @@
6@el [user-select none, padding 10]
6@el [user-select none, padding 10]
7 @text Cannot select this
7 @text Cannot select this
8
8
9−@input [appearance none, border 1 #ccc, rounded 4, padding 8]
9+@input [appearance none, border 1 solid #ccc, border-radius 4, padding 8]
tests/snapshots/computed_let.hl+2−2
@@ -3,5 +3,5 @@
3@let large = $base * 2
3@let large = $base * 2
4@let gap = $base + 4
4@let gap = $base + 4
5@column [spacing $gap, padding 20]
5@column [spacing $gap, padding 20]
6− @text [size $base] Base size
6+ @text [font-size $base] Base size
7− @text [size $large] Large size
7+ @text [font-size $large] Large size
tests/snapshots/css_shorthands.hl+3−3
@@ -24,10 +24,10 @@
24 @el [width 200, padding 20] Item 3
24 @el [width 200, padding 20] Item 3
25
25
26 -- Skeleton
26 -- Skeleton
27− @el [width fill, height 20, rounded 4, $skeleton]
27+ @el [width fill, height 20, border-radius 4, $skeleton]
28
28
29 -- Gradient
29 -- Gradient
30− @el [background linear-gradient(#3b82f6,#8b5cf6), padding 20, rounded 8]
30+ @el [background linear-gradient(#3b82f6,#8b5cf6), padding 20, border-radius 8]
31 @text [color white] Gradient background
31 @text [color white] Gradient background
32− @el [background linear-gradient(45deg,#ff6b6b,#feca57), padding 20, rounded 8]
32+ @el [background linear-gradient(45deg,#ff6b6b,#feca57), padding 20, border-radius 8]
33 @text [color white] Angled gradient
33 @text [color white] Angled gradient
tests/snapshots/css_units.hl+2−2
@@ -1,8 +1,8 @@
1@page CSS Units
1@page CSS Units
2@column [padding 2rem, spacing 1.5em]
2@column [padding 2rem, spacing 1.5em]
3 @el [width 50%, height 100vh, max-width 80ch]
3 @el [width 50%, height 100vh, max-width 80ch]
4− @text [size 1.2rem] Hello
4+ @text [font-size 1.2rem] Hello
5− @el [padding 10 2rem, rounded 0.5rem]
5+ @el [padding 10 2rem, border-radius 0.5rem]
6 @text Responsive padding
6 @text Responsive padding
7 @el [min-height 50vh, gap 2rem]
7 @el [min-height 50vh, gap 2rem]
8 @text Viewport units
8 @text Viewport units
tests/snapshots/css_vars.hl+2−2
@@ -4,7 +4,7 @@
4@let --radius 8
4@let --radius 8
5
5
6@column [spacing $--spacing]
6@column [spacing $--spacing]
7− @el [background var(--primary), rounded $--radius, padding $--spacing]
7+ @el [background var(--primary), border-radius $--radius, padding $--spacing]
8 @text [color white] Using CSS vars
8 @text [color white] Using CSS vars
9− @el [border 1 var(--primary), rounded $--radius, padding $--spacing]
9+ @el [border 1 solid var(--primary), border-radius $--radius, padding $--spacing]
10 @text [color var(--primary)] Border with var
10 @text [color var(--primary)] Border with var
tests/snapshots/dark_mode.hl+2−2
@@ -1,9 +1,9 @@
1@page Dark Mode
1@page Dark Mode
2
2
3@el [background white, dark:background #1a1a2e, color #333, dark:color #eee, padding 20]
3@el [background white, dark:background #1a1a2e, color #333, dark:color #eee, padding 20]
4− @text [size 24, bold] Hello World
4+ @text [font-size 24, font-weight bold] Hello World
5
5
6−@el [border 1 #ccc, dark:border 1 #444, rounded 8, padding 16]
6+@el [border 1 solid #ccc, dark:border 1 solid #444, border-radius 8, padding 16]
7 @text Theme-aware card
7 @text Theme-aware card
8
8
9@el [background #f0f0f0, dark:background #333, print:display none, padding 10]
9@el [background #f0f0f0, dark:background #333, print:display none, padding 10]
tests/snapshots/data_directive.hl+1−1
@@ -1,7 +1,7 @@
1@page Data Test
1@page Data Test
2@data $site test_data.json
2@data $site test_data.json
3
3
4−@text [bold, size 24] $site.title
4+@text [font-weight bold, font-size 24] $site.title
5@text Version: $site.version
5@text Version: $site.version
6
6
7@each $tag in $site.tags
7@each $tag in $site.tags
tests/snapshots/each_destructuring.hl+1−1
@@ -3,5 +3,5 @@
3@data $name-role [{"name": "Alice", "role": "Admin"}, {"name": "Bob", "role": "User"}, {"name": "Carol", "role": "Editor"}]
3@data $name-role [{"name": "Alice", "role": "Admin"}, {"name": "Bob", "role": "User"}, {"name": "Carol", "role": "Editor"}]
4@each $item in $name-role
4@each $item in $name-role
5 @row [spacing 10]
5 @row [spacing 10]
6− @text [bold] $item.name
6+ @text [font-weight bold] $item.name
7 @text $item.role
7 @text $item.role
tests/snapshots/each_step.hl+2−2
@@ -8,8 +8,8 @@
8-- Step with large range
8-- Step with large range
9@row [spacing 8]
9@row [spacing 8]
10 @each $n in 0..100 step 25
10 @each $n in 0..100 step 25
11− @el [width 50, height 50, background #3b82f6, rounded 4]
11+ @el [width 50, height 50, background #3b82f6, border-radius 4]
12− @text [color white, size 12] $n
12+ @text [color white, font-size 12] $n
13
13
14-- Reverse range with step
14-- Reverse range with step
15@column [spacing 4]
15@column [spacing 4]
tests/snapshots/extra_css.hl+1−1
@@ -7,7 +7,7 @@
7 Styled
7 Styled
8 @el [letter-spacing 2, text-transform uppercase, white-space nowrap]
8 @el [letter-spacing 2, text-transform uppercase, white-space nowrap]
9 Typography
9 Typography
10− @el [border-bottom 1 #ccc, border-top 2 red]
10+ @el [border-bottom 1 solid #ccc, border-top 2 solid red]
11 Borders
11 Borders
12 @el [display grid, grid-cols 3, gap 10]
12 @el [display grid, grid-cols 3, gap 10]
13 @el [col-span 2]
13 @el [col-span 2]
tests/snapshots/fn_defaults.hl+1−1
@@ -1,5 +1,5 @@
1@let badge $label $color=blue
1@let badge $label $color=blue
2− @el [padding 4 8, background $color, rounded 4]
2+ @el [padding 4 8, background $color, border-radius 4]
3 @text $label
3 @text $label
4
4
5@badge [label Hello]
5@badge [label Hello]
tests/snapshots/functions.hl+2−2
@@ -1,7 +1,7 @@
1@page Functions
1@page Functions
2@let card $title
2@let card $title
3− @el [padding 20, rounded 8]
3+ @el [padding 20, border-radius 8]
4− @text [bold] $title
4+ @text [font-weight bold] $title
5 @children
5 @children
6
6
7@let wrapper
7@let wrapper
tests/snapshots/has_pseudo.hl+1−1
@@ -2,5 +2,5 @@
2@column [spacing 20, padding 20]
2@column [spacing 20, padding 20]
3 @el [padding 20, background white, has(.active):background #dbeafe, has(img):padding 0]
3 @el [padding 20, background white, has(.active):background #dbeafe, has(img):padding 0]
4 Content
4 Content
5− @el [border 1 #e5e7eb, has(.selected):border 2 #3b82f6, rounded 8]
5+ @el [border 1 solid #e5e7eb, has(.selected):border 2 solid #3b82f6, border-radius 8]
6 Container
6 Container
tests/snapshots/hidden_attr.hl+1−1
@@ -1,6 +1,6 @@
1@page Hidden Test
1@page Hidden Test
2
2
3−@el [hidden]
3+@el [display none]
4 @text This is hidden
4 @text This is hidden
5
5
6@el [padding 10]
6@el [padding 10]
tests/snapshots/inline_text.hl+2−2
@@ -1,5 +1,5 @@
1@page Inline Text
1@page Inline Text
2@paragraph
2@paragraph
3− This is {@text [bold] bold} and {@text [italic, color red] styled} text.
3+ This is {@text [font-weight bold] bold} and {@text [font-style italic, color red] styled} text.
4@paragraph
4@paragraph
5− Multiple {@text [underline] inline} elements {@text [size 20] here}.
5+ Multiple {@text [text-decoration underline] inline} elements {@text [font-size 20] here}.
tests/snapshots/list_elements.hl+2−2
@@ -11,5 +11,5 @@
11 @item Step three
11 @item Step three
12
12
13@list
13@list
14− @item [padding 10, background #f0f0f0, rounded 4]
14+ @item [padding 10, background #f0f0f0, border-radius 4]
15− @text [bold] Styled item
15+ @text [font-weight bold] Styled item
tests/snapshots/mixin_spread.hl+2−2
@@ -1,10 +1,10 @@
1@page Mixin Test
1@page Mixin Test
2
2
3−@let card-style [padding 20, rounded 8, background #f3f4f6]
3+@let card-style [padding 20, border-radius 8, background #f3f4f6]
4@let interactive [cursor pointer, hover:background #e5e7eb, transition all 0.15s ease]
4@let interactive [cursor pointer, hover:background #e5e7eb, transition all 0.15s ease]
5
5
6@el [$card-style, $interactive]
6@el [$card-style, $interactive]
7 Hello mixin world
7 Hello mixin world
8
8
9−@el [$card-style, border 1 #ccc]
9+@el [$card-style, border 1 solid #ccc]
10 Using $define syntax too
10 Using $define syntax too
tests/snapshots/named_slots.hl+1−1
@@ -7,7 +7,7 @@
7
7
8@layout
8@layout
9 @slot header
9 @slot header
10− @text [bold] Custom Header
10+ @text [font-weight bold] Custom Header
11 @slot footer
11 @slot footer
12 @text Footer content
12 @text Footer content
13 @text Main body
13 @text Main body
tests/snapshots/new_css.hl+1−1
@@ -1,6 +1,6 @@
1@page New CSS
1@page New CSS
2
2
3−@el [margin 10, margin-x 20, margin-y 30]
3+@el [margin 10, margin-inline 20, margin-block 30]
4 @text Margins
4 @text Margins
5
5
6@image [width 200, height 200, object-fit cover, object-position center] photo.jpg
6@image [width 200, height 200, object-fit cover, object-position center] photo.jpg
tests/snapshots/new_css_properties_3.hl+1−1
@@ -3,7 +3,7 @@
3@column [spacing 20]
3@column [spacing 20]
4 @text [font-weight 600] Semi-bold text
4 @text [font-weight 600] Semi-bold text
5 @text [font-style oblique] Oblique text
5 @text [font-style oblique] Oblique text
6− @text [text-wrap balance, size 32] A heading that wraps nicely
6+ @text [text-wrap balance, font-size 32] A heading that wraps nicely
7 @el [will-change transform, transition transform 0.3s ease]
7 @el [will-change transform, transition transform 0.3s ease]
8 Optimized for animation
8 Optimized for animation
9 @el [touch-action none]
9 @el [touch-action none]
tests/snapshots/new_css_properties_4.hl+1−1
@@ -1,4 +1,4 @@
1−@text [underline, text-underline-offset 4, text-decoration-thickness 2]
1+@text [text-decoration underline, text-underline-offset 4, text-decoration-thickness 2]
2 Styled underline
2 Styled underline
3
3
4@el [column-count 3, column-width 200, column-rule 1px solid #ccc]
4@el [column-count 3, column-width 200, column-rule 1px solid #ccc]
tests/snapshots/new_directives.hl+1−1
@@ -17,4 +17,4 @@
17 </script>
17 </script>
18
18
19@column [spacing 10]
19@column [spacing 10]
20− @text [font Inter] Hello with custom fonts
20+ @text [font-family Inter] Hello with custom fonts
tests/snapshots/new_elements_2.hl+1−1
@@ -1,7 +1,7 @@
1@page New Elements 2
1@page New Elements 2
2
2
3@dialog [id=modal, open]
3@dialog [id=modal, open]
4− @text [bold] Dialog Title
4+ @text [font-weight bold] Dialog Title
5 @text Dialog content here
5 @text Dialog content here
6 @button Close
6 @button Close
7
7
tests/snapshots/new_elements_4.hl+2−2
@@ -30,6 +30,6 @@
30-- Tooltip element
30-- Tooltip element
31@row [spacing 16]
31@row [spacing 16]
32 @tooltip [tip Hover me for info] Hover me for info
32 @tooltip [tip Hover me for info] Hover me for info
33− @text [underline] Helpful text
33+ @text [text-decoration underline] Helpful text
34 @tooltip [tip This is a tooltip message] This is a tooltip message
34 @tooltip [tip This is a tooltip message] This is a tooltip message
35− @text [bold] Important
35+ @text [font-weight bold] Important
tests/snapshots/new_elements_5.hl+2−2
@@ -7,9 +7,9 @@
7
7
8 -- Carousel
8 -- Carousel
9 @carousel [gap 16]
9 @carousel [gap 16]
10− @el [width 200, padding 20, background #f3f4f6, rounded 8]
10+ @el [width 200, padding 20, background #f3f4f6, border-radius 8]
11 Slide 1
11 Slide 1
12− @el [width 200, padding 20, background #f3f4f6, rounded 8]
12+ @el [width 200, padding 20, background #f3f4f6, border-radius 8]
13 Slide 2
13 Slide 2
14
14
15 -- Chip
15 -- Chip
tests/snapshots/new_elements_6.hl+1−1
@@ -2,7 +2,7 @@
2 @text JavaScript is required for this page.
2 @text JavaScript is required for this page.
3
3
4@address
4@address
5− @text [bold] John Doe
5+ @text [font-weight bold] John Doe
6 @link mailto:john@example.com john@example.com
6 @link mailto:john@example.com john@example.com
7
7
8@search
8@search
tests/snapshots/new_pseudos.hl+2−2
@@ -4,5 +4,5 @@
4@el [empty:display none, target:background yellow]
4@el [empty:display none, target:background yellow]
5 Conditional content
5 Conditional content
6
6
7−@input [type=email, valid:border 2 green]
7+@input [type=email, valid:border 2 solid green]
8−@input [type=text, invalid:border 2 red]
8+@input [type=text, invalid:border 2 solid red]
tests/snapshots/no_page.hl+1−1
@@ -1,2 +1,2 @@
1@el [padding 10, background #fff]
1@el [padding 10, background #fff]
2− @text [bold] Fragment without @page
2+ @text [font-weight bold] Fragment without @page
tests/snapshots/popover_api.hl+1−1
@@ -2,5 +2,5 @@
2
2
3@button [popovertarget=my-popover] Toggle Info
3@button [popovertarget=my-popover] Toggle Info
4
4
5−@el [popover, id=my-popover, padding 20, background white, rounded 8, border 1 #ccc]
5+@el [popover, id=my-popover, padding 20, background white, border-radius 8, border 1 solid #ccc]
6 @text This is a popover panel
6 @text This is a popover panel
tests/snapshots/pseudo_states_extended.hl+1−1
@@ -1,6 +1,6 @@
1@page Pseudo States Extended
1@page Pseudo States Extended
2
2
3−@el [padding 10, focus-visible:border 2 blue, focus-within:background #eee]
3+@el [padding 10, focus-visible:border 2 solid blue, focus-within:background #eee]
4 @input [type=text, placeholder=Name]
4 @input [type=text, placeholder=Name]
5
5
6@el [disabled:opacity 0.5, checked:background green]
6@el [disabled:opacity 0.5, checked:background green]
tests/snapshots/pseudo_states_extended.html+1−1
@@ -1 +1 @@
1−<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Pseudo States Extended</title><style>@layer hl-reset,htmlang;@layer hl-reset{*,*::before,*::after{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,sans-serif}img{display:block}a{text-decoration:none;color:inherit}}@layer htmlang{.a,.f{display:flex;flex-direction:column;padding:10px;}.b,.d{display:flex;flex-direction:column;}.e{display:flex;flex-direction:row;gap:10px;}.a:focus-visible{border:2px solid blue;}.a:focus-within{background:#eee;}.b:disabled{opacity:0.5;}.b:checked{background:green;}.c::placeholder{color:#999;}.d:first-child{border-top:0 solid currentColor;}.d:last-child{border-bottom:0 solid currentColor;}.f:nth-child(odd){background:#f5f5f5;}.f:nth-child(even){background:white;}}</style></head><body><div class="a"><input type="text" placeholder="Name"></div><div class="b"></div><input class="c" type="text"><div class="d"></div><div class="e"><div class="f"><span>Item</span></div></div></body></html>
1+<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Pseudo States Extended</title><style>@layer hl-reset,htmlang;@layer hl-reset{*,*::before,*::after{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,sans-serif}img{display:block}a{text-decoration:none;color:inherit}}@layer htmlang{.a,.f{display:flex;flex-direction:column;padding:10px;}.b,.d{display:flex;flex-direction:column;}.e{display:flex;flex-direction:row;gap:10px;}.a:focus-visible{border:2px solid blue;}.a:focus-within{background:#eee;}.b:disabled{opacity:0.5;}.b:checked{background:green;}.c::placeholder{color:#999;}.d:first-child{border-top:0;}.d:last-child{border-bottom:0;}.f:nth-child(odd){background:#f5f5f5;}.f:nth-child(even){background:white;}}</style></head><body><div class="a"><input type="text" placeholder="Name"></div><div class="b"></div><input class="c" type="text"><div class="d"></div><div class="e"><div class="f"><span>Item</span></div></div></body></html>
tests/snapshots/repeat_directive.hl+1−1
@@ -1,2 +1,2 @@
1@each $_, $index in 1..3
1@each $_, $index in 1..3
2− @text [bold] Item $index
2+ @text [font-weight bold] Item $index
tests/snapshots/responsive.hl+1−1
@@ -1,5 +1,5 @@
1@page Responsive
1@page Responsive
2@row [spacing 10, sm:spacing 20, lg:spacing 40]
2@row [spacing 10, sm:spacing 20, lg:spacing 40]
3 @el [width fill, sm:width 200, md:width 300, lg:width 400, xl:width 500]
3 @el [width fill, sm:width 200, md:width 300, lg:width 400, xl:width 500]
4− @text [size 14, md:size 18, lg:size 24] Responsive text
4+ @text [font-size 14, md:font-size 18, lg:font-size 24] Responsive text
5 @el [padding 10, sm:padding 20]
5 @el [padding 10, sm:padding 20]
tests/snapshots/semantic_elements.hl+4−4
@@ -1,7 +1,7 @@
1@page Semantic Elements
1@page Semantic Elements
2
2
3@header [padding 20, background #f0f0f0]
3@header [padding 20, background #f0f0f0]
4− @text [bold, size 24] My Site
4+ @text [font-weight bold, font-size 24] My Site
5
5
6@nav [spacing 10]
6@nav [spacing 10]
7 @link / Home
7 @link / Home
@@ -9,12 +9,12 @@
9 @link /contact Contact
9 @link /contact Contact
10
10
11@main [padding 20]
11@main [padding 20]
12− @article [padding 16, border 1 #ddd, rounded 8]
12+ @article [padding 16, border 1 solid #ddd, border-radius 8]
13− @text [bold] Article Title
13+ @text [font-weight bold] Article Title
14 @paragraph Some article content here
14 @paragraph Some article content here
15
15
16 @section [padding 16]
16 @section [padding 16]
17− @text [bold] Section Title
17+ @text [font-weight bold] Section Title
18 @paragraph Section content
18 @paragraph Section content
19
19
20@aside [padding 16, background #f8f8f8]
20@aside [padding 16, background #f8f8f8]
tests/snapshots/stress_deeply_nested.hl+114−114
@@ -5,100 +5,100 @@
5@let --radius 8
5@let --radius 8
6
6
7@let deep-card $title
7@let deep-card $title
8− @el [background white, dark:background #1e293b, rounded $--radius, padding 16, border 1 $--border]
8+ @el [background white, dark:background #1e293b, border-radius $--radius, padding 16, border 1 solid $--border]
9− @text [bold, size 16] $title
9+ @text [font-weight bold, font-size 16] $title
10 @children
10 @children
11
11
12@let inner-panel $label
12@let inner-panel $label
13− @el [background #f8fafc, dark:background #0f172a, rounded 6, padding 12, margin-top 8]
13+ @el [background #f8fafc, dark:background #0f172a, border-radius 6, padding 12, margin-top 8]
14− @text [size 13, color #64748b] $label
14+ @text [font-size 13, color #64748b] $label
15 @children
15 @children
16
16
17@column [spacing 20, padding 24]
17@column [spacing 20, padding 24]
18
18
19− @el [background #f0f9ff, rounded 12, padding 20, border 1 #bae6fd]
19+ @el [background #f0f9ff, border-radius 12, padding 20, border 1 solid #bae6fd]
20− @text [bold, size 20, color #0369a1] Level 1 - App Shell
20+ @text [font-weight bold, font-size 20, color #0369a1] Level 1 - App Shell
21 @row [spacing 16, margin-top 16]
21 @row [spacing 16, margin-top 16]
22− @el [background white, rounded 8, padding 16, border 1 $--border, width fill]
22+ @el [background white, border-radius 8, padding 16, border 1 solid $--border, width fill]
23− @text [bold, size 16, color #1e293b] Level 2 - Sidebar
23+ @text [font-weight bold, font-size 16, color #1e293b] Level 2 - Sidebar
24 @column [spacing 8, margin-top 12]
24 @column [spacing 8, margin-top 12]
25− @el [background #f1f5f9, rounded 6, padding 12]
25+ @el [background #f1f5f9, border-radius 6, padding 12]
26− @text [size 14, color #475569] Level 3 - Nav Group
26+ @text [font-size 14, color #475569] Level 3 - Nav Group
27 @column [spacing 4, margin-top 8]
27 @column [spacing 4, margin-top 8]
28− @el [padding 8 12, hover:background #e2e8f0, rounded 4, transition all 0.15s]
28+ @el [padding 8 12, hover:background #e2e8f0, border-radius 4, transition all 0.15s]
29− @text [size 13, color #64748b] Level 4 - Nav Item
29+ @text [font-size 13, color #64748b] Level 4 - Nav Item
30 @row [spacing 6, margin-top 4, align-items center]
30 @row [spacing 6, margin-top 4, align-items center]
31− @el [width 6, height 6, rounded 50%, background #10b981]
31+ @el [width 6, height 6, border-radius 50%, background #10b981]
32− @el [padding 4 8, background #dbeafe, rounded 4]
32+ @el [padding 4 8, background #dbeafe, border-radius 4]
33− @text [size 11, color #1d4ed8] Level 5 - Badge
33+ @text [font-size 11, color #1d4ed8] Level 5 - Badge
34 @el [display inline-block, margin-left 4]
34 @el [display inline-block, margin-left 4]
35− @text [size 10, color #3b82f6] Level 6 - Badge Detail
35+ @text [font-size 10, color #3b82f6] Level 6 - Badge Detail
36 @el [margin-top 2]
36 @el [margin-top 2]
37− @text [size 9, color #93c5fd] Level 7 - Sub Detail
37+ @text [font-size 9, color #93c5fd] Level 7 - Sub Detail
38− @el [padding 2 4, background #eff6ff, rounded 2, margin-top 1]
38+ @el [padding 2 4, background #eff6ff, border-radius 2, margin-top 1]
39− @text [size 8, color #bfdbfe] Level 8 - Deepest Badge
39+ @text [font-size 8, color #bfdbfe] Level 8 - Deepest Badge
40
40
41− @el [padding 8 12, hover:background #e2e8f0, rounded 4]
41+ @el [padding 8 12, hover:background #e2e8f0, border-radius 4]
42− @text [size 13, color #64748b] Level 4 - Second Nav Item
42+ @text [font-size 13, color #64748b] Level 4 - Second Nav Item
43− @el [margin-top 4, padding 6, background #f0fdf4, rounded 4]
43+ @el [margin-top 4, padding 6, background #f0fdf4, border-radius 4]
44− @text [size 12, color #166534] Level 5 - Status
44+ @text [font-size 12, color #166534] Level 5 - Status
45 @row [spacing 4, margin-top 2]
45 @row [spacing 4, margin-top 2]
46− @el [padding 2 6, background #bbf7d0, rounded 2]
46+ @el [padding 2 6, background #bbf7d0, border-radius 2]
47− @text [size 10, color #15803d] Level 6 - Online
47+ @text [font-size 10, color #15803d] Level 6 - Online
48− @el [padding 2 6, background #fef3c7, rounded 2]
48+ @el [padding 2 6, background #fef3c7, border-radius 2]
49− @text [size 10, color #92400e] Level 6 - Away
49+ @text [font-size 10, color #92400e] Level 6 - Away
50
50
51− @el [background #f1f5f9, rounded 6, padding 12]
51+ @el [background #f1f5f9, border-radius 6, padding 12]
52− @text [size 14, color #475569] Level 3 - Second Group
52+ @text [font-size 14, color #475569] Level 3 - Second Group
53 @column [spacing 4, margin-top 8]
53 @column [spacing 4, margin-top 8]
54 @each $item in Settings,Profile,Security
54 @each $item in Settings,Profile,Security
55− @el [padding 8 12, hover:background #e2e8f0, rounded 4]
55+ @el [padding 8 12, hover:background #e2e8f0, border-radius 4]
56− @text [size 13, color #64748b] $item
56+ @text [font-size 13, color #64748b] $item
57
57
58− @el [background white, rounded 8, padding 16, border 1 $--border, width fill]
58+ @el [background white, border-radius 8, padding 16, border 1 solid $--border, width fill]
59− @text [bold, size 16, color #1e293b] Level 2 - Main Content
59+ @text [font-weight bold, font-size 16, color #1e293b] Level 2 - Main Content
60 @column [spacing 12, margin-top 12]
60 @column [spacing 12, margin-top 12]
61 @deep-card [title Level 3 - Dashboard Card]
61 @deep-card [title Level 3 - Dashboard Card]
62 @inner-panel [label Level 4 - Chart Area]
62 @inner-panel [label Level 4 - Chart Area]
63 @row [spacing 8, margin-top 8]
63 @row [spacing 8, margin-top 8]
64− @el [width fill, background #eff6ff, rounded 4, padding 8]
64+ @el [width fill, background #eff6ff, border-radius 4, padding 8]
65− @text [size 12, color #1d4ed8, bold] Level 5 - Metric A
65+ @text [font-size 12, color #1d4ed8, font-weight bold] Level 5 - Metric A
66− @el [margin-top 4, height 60, background #dbeafe, rounded 4, padding 8]
66+ @el [margin-top 4, height 60, background #dbeafe, border-radius 4, padding 8]
67− @text [size 20, bold, color #1e40af] Level 6 - Value
67+ @text [font-size 20, font-weight bold, color #1e40af] Level 6 - Value
68 @el [margin-top 2]
68 @el [margin-top 2]
69− @text [size 10, color #3b82f6] Level 7 - Trend
69+ @text [font-size 10, color #3b82f6] Level 7 - Trend
70 @row [spacing 2, margin-top 2]
70 @row [spacing 2, margin-top 2]
71− @el [width 4, height 12, background #93c5fd, rounded 1]
71+ @el [width 4, height 12, background #93c5fd, border-radius 1]
72− @el [width 4, height 18, background #60a5fa, rounded 1]
72+ @el [width 4, height 18, background #60a5fa, border-radius 1]
73− @el [width 4, height 8, background #93c5fd, rounded 1]
73+ @el [width 4, height 8, background #93c5fd, border-radius 1]
74− @el [width 4, height 22, background #3b82f6, rounded 1]
74+ @el [width 4, height 22, background #3b82f6, border-radius 1]
75− @el [width 2, height 2, rounded 50%, background white, margin-top 1]
75+ @el [width 2, height 2, border-radius 50%, background white, margin-top 1]
76− @text [size 6, color white] Level 8 - Spark Dot
76+ @text [font-size 6, color white] Level 8 - Spark Dot
77− @el [width fill, background #f0fdf4, rounded 4, padding 8]
77+ @el [width fill, background #f0fdf4, border-radius 4, padding 8]
78− @text [size 12, color #166534, bold] Level 5 - Metric B
78+ @text [font-size 12, color #166534, font-weight bold] Level 5 - Metric B
79− @el [margin-top 4, height 60, background #dcfce7, rounded 4, padding 8]
79+ @el [margin-top 4, height 60, background #dcfce7, border-radius 4, padding 8]
80− @text [size 20, bold, color #166534] Level 6 - Value
80+ @text [font-size 20, font-weight bold, color #166534] Level 6 - Value
81 @el [margin-top 2]
81 @el [margin-top 2]
82− @text [size 10, color #16a34a] Level 7 - Change
82+ @text [font-size 10, color #16a34a] Level 7 - Change
83− @el [padding 1 4, background #bbf7d0, rounded 2, display inline-block]
83+ @el [padding 1 4, background #bbf7d0, border-radius 2, display inline-block]
84− @text [size 8, color #15803d] Level 8 - Delta
84+ @text [font-size 8, color #15803d] Level 8 - Delta
85
85
86 @deep-card [title Level 3 - Form Card]
86 @deep-card [title Level 3 - Form Card]
87 @inner-panel [label Level 4 - Form Section]
87 @inner-panel [label Level 4 - Form Section]
88 @column [spacing 8, margin-top 8]
88 @column [spacing 8, margin-top 8]
89− @el [padding 8, border 1 $--border, rounded 4]
89+ @el [padding 8, border 1 solid $--border, border-radius 4]
90− @text [size 12, color #64748b, margin-bottom 4] Level 5 - Field Group
90+ @text [font-size 12, color #64748b, margin-bottom 4] Level 5 - Field Group
91− @input [type=text, placeholder=Name, padding 6 10, rounded 4, border 1 $--border, width fill]
91+ @input [type=text, placeholder=Name, padding 6 10, border-radius 4, border 1 solid $--border, width fill]
92− @el [margin-top 4, padding 4 8, background #fef2f2, rounded 4]
92+ @el [margin-top 4, padding 4 8, background #fef2f2, border-radius 4]
93− @text [size 11, color #991b1b] Level 6 - Validation Error
93+ @text [font-size 11, color #991b1b] Level 6 - Validation Error
94 @el [margin-top 2]
94 @el [margin-top 2]
95− @text [size 10, color #dc2626] Level 7 - Error Detail
95+ @text [font-size 10, color #dc2626] Level 7 - Error Detail
96− @el [margin-top 1, padding 2 4, background #fee2e2, rounded 2]
96+ @el [margin-top 1, padding 2 4, background #fee2e2, border-radius 2]
97− @text [size 9, color #b91c1c] Level 8 - Error Code
97+ @text [font-size 9, color #b91c1c] Level 8 - Error Code
98
98
99− @el [padding 8, border 1 $--border, rounded 4]
99+ @el [padding 8, border 1 solid $--border, border-radius 4]
100− @text [size 12, color #64748b, margin-bottom 4] Level 5 - Select Group
100+ @text [font-size 12, color #64748b, margin-bottom 4] Level 5 - Select Group
101− @select [padding 6 10, rounded 4, border 1 $--border, width fill]
101+ @select [padding 6 10, border-radius 4, border 1 solid $--border, width fill]
102 @option [value=a] Option A
102 @option [value=a] Option A
103 @option [value=b] Option B
103 @option [value=b] Option B
104 @option [value=c] Option C
104 @option [value=c] Option C
@@ -108,77 +108,77 @@
108 @column [spacing 6, margin-top 8]
108 @column [spacing 6, margin-top 8]
109 @let top-categories Electronics,Clothing,Books
109 @let top-categories Electronics,Clothing,Books
110 @each $cat in $top-categories
110 @each $cat in $top-categories
111− @el [padding 8, border 1 $--border, rounded 4, hover:background #f8fafc]
111+ @el [padding 8, border 1 solid $--border, border-radius 4, hover:background #f8fafc]
112− @text [size 14, bold, color #1e293b] Level 5 - $cat
112+ @text [font-size 14, font-weight bold, color #1e293b] Level 5 - $cat
113 @column [spacing 4, margin-top 6, margin-left 12]
113 @column [spacing 4, margin-top 6, margin-left 12]
114− @el [padding 6, background #f8fafc, rounded 4]
114+ @el [padding 6, background #f8fafc, border-radius 4]
115 @row [spacing 8, align-items center]
115 @row [spacing 8, align-items center]
116− @el [width 8, height 8, rounded 2, background $--primary]
116+ @el [width 8, height 8, border-radius 2, background $--primary]
117− @text [size 12, color #475569] Level 6 - Subcategory
117+ @text [font-size 12, color #475569] Level 6 - Subcategory
118 @el [margin-top 4, margin-left 16]
118 @el [margin-top 4, margin-left 16]
119− @text [size 11, color #94a3b8] Level 7 - Item
119+ @text [font-size 11, color #94a3b8] Level 7 - Item
120− @el [margin-left 12, margin-top 2, padding 2 6, background #eff6ff, rounded 2]
120+ @el [margin-left 12, margin-top 2, padding 2 6, background #eff6ff, border-radius 2]
121− @text [size 10, color #3b82f6] Level 8 - Variant
121+ @text [font-size 10, color #3b82f6] Level 8 - Variant
122
122
123− @el [background #fefce8, rounded 12, padding 20, border 1 #fde68a, margin-top 20]
123+ @el [background #fefce8, border-radius 12, padding 20, border 1 solid #fde68a, margin-top 20]
124− @text [bold, size 20, color #854d0e] Level 1 - Settings Panel
124+ @text [font-weight bold, font-size 20, color #854d0e] Level 1 - Settings Panel
125 @column [spacing 12, margin-top 16]
125 @column [spacing 12, margin-top 16]
126− @el [background white, rounded 8, padding 16, border 1 $--border]
126+ @el [background white, border-radius 8, padding 16, border 1 solid $--border]
127− @text [bold, size 16, color #1e293b] Level 2 - Account Settings
127+ @text [font-weight bold, font-size 16, color #1e293b] Level 2 - Account Settings
128 @column [spacing 8, margin-top 12]
128 @column [spacing 8, margin-top 12]
129− @el [background #f8fafc, rounded 6, padding 12]
129+ @el [background #f8fafc, border-radius 6, padding 12]
130− @text [size 14, color #475569] Level 3 - Profile Section
130+ @text [font-size 14, color #475569] Level 3 - Profile Section
131− @el [margin-top 8, padding 10, border 1 $--border, rounded 6]
131+ @el [margin-top 8, padding 10, border 1 solid $--border, border-radius 6]
132− @text [size 13, color #1e293b] Level 4 - Avatar Upload
132+ @text [font-size 13, color #1e293b] Level 4 - Avatar Upload
133− @el [margin-top 6, width 80, height 80, rounded 50%, background #e2e8f0, display flex, align-items center, justify-content center]
133+ @el [margin-top 6, width 80, height 80, border-radius 50%, background #e2e8f0, display flex, align-items center, justify-content center]
134− @text [size 24, color #94a3b8, bold] Level 5 - Initials
134+ @text [font-size 24, color #94a3b8, font-weight bold] Level 5 - Initials
135− @el [position absolute, bottom 0, right 0, width 24, height 24, rounded 50%, background $--primary, display flex, align-items center, justify-content center]
135+ @el [position absolute, bottom 0, right 0, width 24, height 24, border-radius 50%, background $--primary, display flex, align-items center, justify-content center]
136− @text [size 10, color white] Level 6 - Edit Icon
136+ @text [font-size 10, color white] Level 6 - Edit Icon
137 @el [margin-top 1]
137 @el [margin-top 1]
138− @text [size 8, color #bfdbfe] Level 7 - Tooltip
138+ @text [font-size 8, color #bfdbfe] Level 7 - Tooltip
139− @el [padding 1 3, background #1e3a5f, rounded 2, margin-top 1]
139+ @el [padding 1 3, background #1e3a5f, border-radius 2, margin-top 1]
140− @text [size 7, color white] Level 8 - Tooltip Arrow
140+ @text [font-size 7, color white] Level 8 - Tooltip Arrow
141
141
142− @el [background #f8fafc, rounded 6, padding 12]
142+ @el [background #f8fafc, border-radius 6, padding 12]
143− @text [size 14, color #475569] Level 3 - Preferences
143+ @text [font-size 14, color #475569] Level 3 - Preferences
144 @column [spacing 6, margin-top 8]
144 @column [spacing 6, margin-top 8]
145 @each $pref in Language,Timezone,Currency
145 @each $pref in Language,Timezone,Currency
146− @row [spacing 8, align-items center, padding 6 8, border 1 $--border, rounded 4]
146+ @row [spacing 8, align-items center, padding 6 8, border 1 solid $--border, border-radius 4]
147− @text [size 13, color #1e293b, width fill] Level 4 - $pref
147+ @text [font-size 13, color #1e293b, width fill] Level 4 - $pref
148− @el [padding 4 12, background #f1f5f9, rounded 4]
148+ @el [padding 4 12, background #f1f5f9, border-radius 4]
149− @text [size 12, color #64748b] Level 5 - Current Value
149+ @text [font-size 12, color #64748b] Level 5 - Current Value
150
150
151− @el [background white, rounded 8, padding 16, border 1 $--border]
151+ @el [background white, border-radius 8, padding 16, border 1 solid $--border]
152− @text [bold, size 16, color #1e293b] Level 2 - Notification Preferences
152+ @text [font-weight bold, font-size 16, color #1e293b] Level 2 - Notification Preferences
153 @column [spacing 6, margin-top 12]
153 @column [spacing 6, margin-top 12]
154 @let channels Email,Push,SMS,Slack
154 @let channels Email,Push,SMS,Slack
155 @each $channel in $channels
155 @each $channel in $channels
156− @row [spacing 8, align-items center, padding 8, border-bottom 1 $--border]
156+ @row [spacing 8, align-items center, padding 8, border-bottom 1 solid $--border]
157− @text [size 14, color #1e293b, width fill] $channel Notifications
157+ @text [font-size 14, color #1e293b, width fill] $channel Notifications
158− @el [width 36, height 20, rounded 10, background $--primary, padding 2]
158+ @el [width 36, height 20, border-radius 10, background $--primary, padding 2]
159− @el [width 16, height 16, rounded 50%, background white, margin-left 16]
159+ @el [width 16, height 16, border-radius 50%, background white, margin-left 16]
160
160
161− @el [background #fdf2f8, rounded 12, padding 20, border 1 #fbcfe8, margin-top 20]
161+ @el [background #fdf2f8, border-radius 12, padding 20, border 1 solid #fbcfe8, margin-top 20]
162− @text [bold, size 20, color #9d174d] Level 1 - Monitoring
162+ @text [font-weight bold, font-size 20, color #9d174d] Level 1 - Monitoring
163 @row [spacing 16, margin-top 16]
163 @row [spacing 16, margin-top 16]
164 @each $service in API,Database,Cache,Queue
164 @each $service in API,Database,Cache,Queue
165− @el [background white, rounded 8, padding 12, border 1 $--border, width fill]
165+ @el [background white, border-radius 8, padding 12, border 1 solid $--border, width fill]
166− @text [bold, size 14, color #1e293b] Level 2 - $service
166+ @text [font-weight bold, font-size 14, color #1e293b] Level 2 - $service
167− @el [margin-top 8, background #f0fdf4, rounded 6, padding 8]
167+ @el [margin-top 8, background #f0fdf4, border-radius 6, padding 8]
168− @text [size 12, color #166534] Level 3 - Healthy
168+ @text [font-size 12, color #166534] Level 3 - Healthy
169 @el [margin-top 4]
169 @el [margin-top 4]
170 @row [spacing 4]
170 @row [spacing 4]
171− @el [width fill, height 4, background #86efac, rounded 2]
171+ @el [width fill, height 4, background #86efac, border-radius 2]
172− @el [width fill, height 4, background #4ade80, rounded 2]
172+ @el [width fill, height 4, background #4ade80, border-radius 2]
173− @el [width fill, height 4, background #22c55e, rounded 2]
173+ @el [width fill, height 4, background #22c55e, border-radius 2]
174− @el [width fill, height 4, background #86efac, rounded 2]
174+ @el [width fill, height 4, background #86efac, border-radius 2]
175 @el [margin-top 4]
175 @el [margin-top 4]
176− @text [size 11, color #16a34a] Level 4 - Uptime 99.9%
176+ @text [font-size 11, color #16a34a] Level 4 - Uptime 99.9%
177 @el [margin-top 2]
177 @el [margin-top 2]
178− @text [size 10, color #22c55e] Level 5 - Response 42ms
178+ @text [font-size 10, color #22c55e] Level 5 - Response 42ms
179− @el [margin-top 2, padding 2 6, background #dcfce7, rounded 2]
179+ @el [margin-top 2, padding 2 6, background #dcfce7, border-radius 2]
180− @text [size 9, color #15803d] Level 6 - p99 120ms
180+ @text [font-size 9, color #15803d] Level 6 - p99 120ms
181 @el [margin-top 1]
181 @el [margin-top 1]
182− @text [size 8, color #4ade80] Level 7 - Throughput 1.2k/s
182+ @text [font-size 8, color #4ade80] Level 7 - Throughput 1.2k/s
183− @el [padding 1 3, background #bbf7d0, rounded 1, margin-top 1]
183+ @el [padding 1 3, background #bbf7d0, border-radius 1, margin-top 1]
184− @text [size 7, color #166534] Level 8 - Peak 2.4k/s
184+ @text [font-size 7, color #166534] Level 8 - Peak 2.4k/s
tests/snapshots/stress_large.hl+122−122
@@ -24,94 +24,94 @@
24 }
24 }
25
25
26@let icon-button $label $color=var(--primary)
26@let icon-button $label $color=var(--primary)
27− @button [padding 8 16, background $color, hover:background $--dark, color white, rounded 6, border none, cursor pointer, transition all 0.2s, font-size 14]
27+ @button [padding 8 16, background $color, hover:background $--dark, color white, border-radius 6, border none, cursor pointer, transition all 0.2s, font-size 14]
28 $label
28 $label
29
29
30@let badge $label $variant=blue
30@let badge $label $variant=blue
31− @el [display inline-block, padding 2 8, background $variant, color white, rounded 12, font-size 12]
31+ @el [display inline-block, padding 2 8, background $variant, color white, border-radius 12, font-size 12]
32 @text $label
32 @text $label
33
33
34@let card $title
34@let card $title
35− @el [background white, dark:background #1e293b, rounded $--radius, padding $--gap, border 1 $--border, dark:border 1 #334155, hover:box-shadow 0 4 12 rgba(0,0,0,0.1), transition all 0.2s, animation fade-in 0.3s ease]
35+ @el [background white, dark:background #1e293b, border-radius $--radius, padding $--gap, border 1 solid $--border, dark:border 1 solid #334155, hover:box-shadow 0 4 12 rgba(0,0,0,0.1), transition all 0.2s, animation fade-in 0.3s ease]
36− @text [bold, size 18, color $--dark, dark:color white] $title
36+ @text [font-weight bold, font-size 18, color $--dark, dark:color white] $title
37 @el [margin-top 8]
37 @el [margin-top 8]
38 @children
38 @children
39
39
40@let stat-card $label $value $change
40@let stat-card $label $value $change
41− @el [background white, dark:background #1e293b, rounded $--radius, padding $--gap, border 1 $--border, dark:border 1 #334155]
41+ @el [background white, dark:background #1e293b, border-radius $--radius, padding $--gap, border 1 solid $--border, dark:border 1 solid #334155]
42− @text [size 14, color $--gray] $label
42+ @text [font-size 14, color $--gray] $label
43− @text [bold, size 28, color $--dark, dark:color white, margin-top 4] $value
43+ @text [font-weight bold, font-size 28, color $--dark, dark:color white, margin-top 4] $value
44− @text [size 12, color $--secondary, margin-top 2] $change
44+ @text [font-size 12, color $--secondary, margin-top 2] $change
45
45
46@let nav-link $url $label $active=false
46@let nav-link $url $label $active=false
47 @if $active == true
47 @if $active == true
48− @link [color $--primary, bold, padding 8 12, background #eff6ff, dark:background #1e3a5f, rounded 6] $url $label
48+ @link [color $--primary, font-weight bold, padding 8 12, background #eff6ff, dark:background #1e3a5f, border-radius 6] $url $label
49 @else
49 @else
50− @link [color $--gray, hover:color $--primary, padding 8 12, hover:background #f1f5f9, dark:hover:background #334155, rounded 6, transition all 0.15s] $url $label
50+ @link [color $--gray, hover:color $--primary, padding 8 12, hover:background #f1f5f9, dark:hover:background #334155, border-radius 6, transition all 0.15s] $url $label
51
51
52@let sidebar-section $title
52@let sidebar-section $title
53 @el [margin-bottom $--gap-lg]
53 @el [margin-bottom $--gap-lg]
54− @text [bold, size 12, color $--gray, text-transform uppercase, letter-spacing 1, margin-bottom $--gap-sm] $title
54+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase, letter-spacing 1, margin-bottom $--gap-sm] $title
55 @children
55 @children
56
56
57@let avatar $initials $size=32
57@let avatar $initials $size=32
58− @el [width $size, height $size, rounded 50%, background $--primary, color white, display flex, align-items center, justify-content center, font-size 14, bold]
58+ @el [width $size, height $size, border-radius 50%, background $--primary, color white, display flex, align-items center, justify-content center, font-size 14, font-weight bold]
59 @text $initials
59 @text $initials
60
60
61@let table-row $col1 $col2 $col3 $col4
61@let table-row $col1 $col2 $col3 $col4
62− @row [spacing 0, padding 12 16, border-bottom 1 $--border, dark:border-bottom 1 #334155, hover:background #f8fafc, dark:hover:background #1e293b]
62+ @row [spacing 0, padding 12 16, border-bottom 1 solid $--border, dark:border-bottom 1 solid #334155, hover:background #f8fafc, dark:hover:background #1e293b]
63 @el [width 200]
63 @el [width 200]
64− @text [size 14] $col1
64+ @text [font-size 14] $col1
65 @el [width 150]
65 @el [width 150]
66− @text [size 14, color $--gray] $col2
66+ @text [font-size 14, color $--gray] $col2
67 @el [width 120]
67 @el [width 120]
68− @text [size 14] $col3
68+ @text [font-size 14] $col3
69 @el [width fill]
69 @el [width fill]
70− @text [size 14, color $--gray] $col4
70+ @text [font-size 14, color $--gray] $col4
71
71
72@let breadcrumb-item $url $label $is-last=false
72@let breadcrumb-item $url $label $is-last=false
73 @if $is-last == true
73 @if $is-last == true
74− @text [color $--dark, dark:color white, size 14] $label
74+ @text [color $--dark, dark:color white, font-size 14] $label
75 @else
75 @else
76 @row [spacing 8]
76 @row [spacing 8]
77− @link [color $--gray, hover:color $--primary, size 14] $url $label
77+ @link [color $--gray, hover:color $--primary, font-size 14] $url $label
78− @text [color $--border, size 14] /
78+ @text [color $--border, font-size 14] /
79
79
80@let alert $message $type=info
80@let alert $message $type=info
81 @if $type == info
81 @if $type == info
82− @el [background #eff6ff, border 1 #bfdbfe, rounded $--radius, padding 12 16]
82+ @el [background #eff6ff, border 1 solid #bfdbfe, border-radius $--radius, padding 12 16]
83− @text [color #1e40af, size 14] $message
83+ @text [color #1e40af, font-size 14] $message
84 @else if $type == success
84 @else if $type == success
85− @el [background #f0fdf4, border 1 #bbf7d0, rounded $--radius, padding 12 16]
85+ @el [background #f0fdf4, border 1 solid #bbf7d0, border-radius $--radius, padding 12 16]
86− @text [color #166534, size 14] $message
86+ @text [color #166534, font-size 14] $message
87 @else if $type == warning
87 @else if $type == warning
88− @el [background #fffbeb, border 1 #fde68a, rounded $--radius, padding 12 16]
88+ @el [background #fffbeb, border 1 solid #fde68a, border-radius $--radius, padding 12 16]
89− @text [color #92400e, size 14] $message
89+ @text [color #92400e, font-size 14] $message
90 @else
90 @else
91− @el [background #fef2f2, border 1 #fecaca, rounded $--radius, padding 12 16]
91+ @el [background #fef2f2, border 1 solid #fecaca, border-radius $--radius, padding 12 16]
92− @text [color #991b1b, size 14] $message
92+ @text [color #991b1b, font-size 14] $message
93
93
94@let progress-bar $percent $color=var(--primary)
94@let progress-bar $percent $color=var(--primary)
95− @el [width fill, height 8, background #e2e8f0, dark:background #334155, rounded 4, overflow hidden]
95+ @el [width fill, height 8, background #e2e8f0, dark:background #334155, border-radius 4, overflow hidden]
96− @el [width $percent%, height fill, background $color, rounded 4]
96+ @el [width $percent%, height fill, background $color, border-radius 4]
97
97
98@let tag $label
98@let tag $label
99− @el [display inline-block, padding 4 10, background #f1f5f9, dark:background #334155, color $--gray, dark:color #94a3b8, rounded 4, font-size 12, margin 2]
99+ @el [display inline-block, padding 4 10, background #f1f5f9, dark:background #334155, color $--gray, dark:color #94a3b8, border-radius 4, font-size 12, margin 2]
100 @text $label
100 @text $label
101
101
102@let notification-dot
102@let notification-dot
103− @el [width 8, height 8, rounded 50%, background $--danger]
103+ @el [width 8, height 8, border-radius 50%, background $--danger]
104
104
105@let divider
105@let divider
106 @el [width fill, height 1, background $--border, dark:background #334155, margin 8 0]
106 @el [width fill, height 1, background $--border, dark:background #334155, margin 8 0]
107
107
108@let toggle $checked=false
108@let toggle $checked=false
109 @if $checked == true
109 @if $checked == true
110− @el [width 40, height 22, rounded 11, background $--primary, padding 2, cursor pointer, transition all 0.2s]
110+ @el [width 40, height 22, border-radius 11, background $--primary, padding 2, cursor pointer, transition all 0.2s]
111− @el [width 18, height 18, rounded 50%, background white, margin-left 18, transition all 0.2s]
111+ @el [width 18, height 18, border-radius 50%, background white, margin-left 18, transition all 0.2s]
112 @else
112 @else
113− @el [width 40, height 22, rounded 11, background #cbd5e1, dark:background #475569, padding 2, cursor pointer, transition all 0.2s]
113+ @el [width 40, height 22, border-radius 11, background #cbd5e1, dark:background #475569, padding 2, cursor pointer, transition all 0.2s]
114− @el [width 18, height 18, rounded 50%, background white, transition all 0.2s]
114+ @el [width 18, height 18, border-radius 50%, background white, transition all 0.2s]
115
115
116@let nav-items home,products,team,blog,docs,pricing
116@let nav-items home,products,team,blog,docs,pricing
117@let categories all,design,development,marketing,analytics
117@let categories all,design,development,marketing,analytics
@@ -121,13 +121,13 @@
121
121
122@row [spacing 0, min-height 100vh]
122@row [spacing 0, min-height 100vh]
123
123
124− @column [width 260, background white, dark:background #0f172a, border-right 1 $--border, dark:border-right 1 #1e293b, padding $--gap, spacing $--gap-sm]
124+ @column [width 260, background white, dark:background #0f172a, border-right 1 solid $--border, dark:border-right 1 solid #1e293b, padding $--gap, spacing $--gap-sm]
125
125
126 @row [spacing 12, padding 8, align-items center]
126 @row [spacing 12, padding 8, align-items center]
127 @avatar [initials HL, size 36]
127 @avatar [initials HL, size 36]
128 @column [spacing 2]
128 @column [spacing 2]
129− @text [bold, size 16, color $--dark, dark:color white] htmlang
129+ @text [font-weight bold, font-size 16, color $--dark, dark:color white] htmlang
130− @text [size 12, color $--gray] Dashboard v2.0
130+ @text [font-size 12, color $--gray] Dashboard v2.0
131
131
132 @divider
132 @divider
133
133
@@ -151,36 +151,36 @@
151 @sidebar-section [title Shortcuts]
151 @sidebar-section [title Shortcuts]
152 @column [spacing 4]
152 @column [spacing 4]
153 @row [spacing 8, align-items center]
153 @row [spacing 8, align-items center]
154− @el [width 6, height 6, rounded 50%, background $--secondary]
154+ @el [width 6, height 6, border-radius 50%, background $--secondary]
155− @text [size 13, color $--gray] Online (12)
155+ @text [font-size 13, color $--gray] Online (12)
156 @row [spacing 8, align-items center]
156 @row [spacing 8, align-items center]
157− @el [width 6, height 6, rounded 50%, background $--accent]
157+ @el [width 6, height 6, border-radius 50%, background $--accent]
158− @text [size 13, color $--gray] Away (3)
158+ @text [font-size 13, color $--gray] Away (3)
159 @row [spacing 8, align-items center]
159 @row [spacing 8, align-items center]
160− @el [width 6, height 6, rounded 50%, background #cbd5e1]
160+ @el [width 6, height 6, border-radius 50%, background #cbd5e1]
161− @text [size 13, color $--gray] Offline (5)
161+ @text [font-size 13, color $--gray] Offline (5)
162
162
163− @el [margin-top auto, padding 12, background #f1f5f9, dark:background #1e293b, rounded $--radius]
163+ @el [margin-top auto, padding 12, background #f1f5f9, dark:background #1e293b, border-radius $--radius]
164 @row [spacing 8, align-items center]
164 @row [spacing 8, align-items center]
165 @avatar [initials JD, size 28]
165 @avatar [initials JD, size 28]
166 @column [spacing 0]
166 @column [spacing 0]
167− @text [size 13, bold, color $--dark, dark:color white] Jane Doe
167+ @text [font-size 13, font-weight bold, color $--dark, dark:color white] Jane Doe
168− @text [size 11, color $--gray] jane@example.com
168+ @text [font-size 11, color $--gray] jane@example.com
169
169
170 @column [width fill, background $--light, dark:background #0f172a]
170 @column [width fill, background $--light, dark:background #0f172a]
171
171
172− @header [padding 12 24, background white, dark:background #0f172a, border-bottom 1 $--border, dark:border-bottom 1 #1e293b]
172+ @header [padding 12 24, background white, dark:background #0f172a, border-bottom 1 solid $--border, dark:border-bottom 1 solid #1e293b]
173 @row [spacing 16, align-items center]
173 @row [spacing 16, align-items center]
174 @column [spacing 2]
174 @column [spacing 2]
175 @row [spacing 8]
175 @row [spacing 8]
176 @breadcrumb-item [url /, label Home]
176 @breadcrumb-item [url /, label Home]
177 @breadcrumb-item [url /dashboard, label Dashboard, is-last true]
177 @breadcrumb-item [url /dashboard, label Dashboard, is-last true]
178− @text [bold, size 22, color $--dark, dark:color white] Dashboard Overview
178+ @text [font-weight bold, font-size 22, color $--dark, dark:color white] Dashboard Overview
179
179
180 @el [width fill]
180 @el [width fill]
181
181
182 @row [spacing 12, align-items center]
182 @row [spacing 12, align-items center]
183− @input [type=search, placeholder=Search..., padding 8 12, rounded 6, border 1 $--border, dark:border 1 #334155, dark:background #1e293b, dark:color white, width 240, focus:border 1 $--primary, transition all 0.2s]
183+ @input [type=search, placeholder=Search..., padding 8 12, border-radius 6, border 1 solid $--border, dark:border 1 solid #334155, dark:background #1e293b, dark:color white, width 240, focus:border 1 solid $--primary, transition all 0.2s]
184 @el [position relative]
184 @el [position relative]
185 @icon-button [label Alerts]
185 @icon-button [label Alerts]
186 @el [position absolute, top -2, right -2]
186 @el [position absolute, top -2, right -2]
@@ -200,19 +200,19 @@
200 @row [spacing $--gap, margin-top $--gap-lg]
200 @row [spacing $--gap, margin-top $--gap-lg]
201
201
202 @card [title Revenue Overview]
202 @card [title Revenue Overview]
203− @el [height 200, background #f8fafc, dark:background #0f172a, rounded 6, margin-top 12, display flex, align-items flex-end, padding 16, gap 8]
203+ @el [height 200, background #f8fafc, dark:background #0f172a, border-radius 6, margin-top 12, display flex, align-items flex-end, padding 16, gap 8]
204 @each $height in 40,65,45,80,55,70,90,60,75,85,50,95
204 @each $height in 40,65,45,80,55,70,90,60,75,85,50,95
205− @el [width fill, height $height%, background $--primary, hover:background #2563eb, rounded 4 4 0 0, transition all 0.2s, min-width 20]
205+ @el [width fill, height $height%, background $--primary, hover:background #2563eb, border-radius 4 4 0 0, transition all 0.2s, min-width 20]
206
206
207 @card [title Activity Feed]
207 @card [title Activity Feed]
208 @column [spacing 12, margin-top 12]
208 @column [spacing 12, margin-top 12]
209 @each $member in $team-members
209 @each $member in $team-members
210− @row [spacing 12, align-items center, padding 8, hover:background #f8fafc, dark:hover:background #1e293b, rounded 6]
210+ @row [spacing 12, align-items center, padding 8, hover:background #f8fafc, dark:hover:background #1e293b, border-radius 6]
211 @avatar [initials $member, size 32]
211 @avatar [initials $member, size 32]
212 @column [spacing 2, width fill]
212 @column [spacing 2, width fill]
213− @text [size 14, bold, color $--dark, dark:color white] $member
213+ @text [font-size 14, font-weight bold, color $--dark, dark:color white] $member
214− @text [size 12, color $--gray] Updated project files
214+ @text [font-size 12, color $--gray] Updated project files
215− @text [size 11, color $--gray] 2h ago
215+ @text [font-size 11, color $--gray] 2h ago
216
216
217 @row [spacing $--gap, margin-top $--gap-lg, sm:flex-direction column, lg:flex-direction row]
217 @row [spacing $--gap, margin-top $--gap-lg, sm:flex-direction column, lg:flex-direction row]
218
218
@@ -220,15 +220,15 @@
220
220
221 @card [title Project Status]
221 @card [title Project Status]
222 @column [spacing 0, margin-top 12]
222 @column [spacing 0, margin-top 12]
223− @row [spacing 0, padding 8 16, background #f8fafc, dark:background #0f172a, border-bottom 1 $--border]
223+ @row [spacing 0, padding 8 16, background #f8fafc, dark:background #0f172a, border-bottom 1 solid $--border]
224 @el [width 200]
224 @el [width 200]
225− @text [bold, size 12, color $--gray, text-transform uppercase] Project
225+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase] Project
226 @el [width 150]
226 @el [width 150]
227− @text [bold, size 12, color $--gray, text-transform uppercase] Status
227+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase] Status
228 @el [width 120]
228 @el [width 120]
229− @text [bold, size 12, color $--gray, text-transform uppercase] Progress
229+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase] Progress
230 @el [width fill]
230 @el [width fill]
231− @text [bold, size 12, color $--gray, text-transform uppercase] Due Date
231+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase] Due Date
232
232
233 @table-row [col1 Project Alpha, col2 In Progress, col3 75%, col4 Dec 15 2025]
233 @table-row [col1 Project Alpha, col2 In Progress, col3 75%, col4 Dec 15 2025]
234 @table-row [col1 Project Beta, col2 Review, col3 90%, col4 Nov 30 2025]
234 @table-row [col1 Project Beta, col2 Review, col3 90%, col4 Nov 30 2025]
@@ -241,8 +241,8 @@
241 @each $project in $projects
241 @each $project in $projects
242 @column [spacing 4]
242 @column [spacing 4]
243 @row [spacing 0]
243 @row [spacing 0]
244− @text [size 13, color $--dark, dark:color white, width fill] $project
244+ @text [font-size 13, color $--dark, dark:color white, width fill] $project
245− @text [size 13, color $--gray] 67%
245+ @text [font-size 13, color $--gray] 67%
246 @progress-bar [percent 67, color var(--primary)]
246 @progress-bar [percent 67, color var(--primary)]
247
247
248 @card [title Recent Activity Log]
248 @card [title Recent Activity Log]
@@ -250,10 +250,10 @@
250 @let log-entries Deployed v2.3.1 to production,Merged feature/auth-redesign,Closed issue #482: Login timeout,Updated CI/CD pipeline config,Added unit tests for payment module,Reviewed PR #156: API refactor,Fixed memory leak in worker pool,Released hotfix 2.3.2
250 @let log-entries Deployed v2.3.1 to production,Merged feature/auth-redesign,Closed issue #482: Login timeout,Updated CI/CD pipeline config,Added unit tests for payment module,Reviewed PR #156: API refactor,Fixed memory leak in worker pool,Released hotfix 2.3.2
251 @each $entry, $i in $log-entries
251 @each $entry, $i in $log-entries
252 @row [spacing 12, padding 8 12, align-items center]
252 @row [spacing 12, padding 8 12, align-items center]
253− @el [width 24, height 24, rounded 50%, background #eff6ff, dark:background #1e3a5f, display flex, align-items center, justify-content center]
253+ @el [width 24, height 24, border-radius 50%, background #eff6ff, dark:background #1e3a5f, display flex, align-items center, justify-content center]
254− @text [size 10, color $--primary, bold] $i
254+ @text [font-size 10, color $--primary, font-weight bold] $i
255− @text [size 14, color $--dark, dark:color white, width fill] $entry
255+ @text [font-size 14, color $--dark, dark:color white, width fill] $entry
256− @text [size 11, color $--gray] just now
256+ @text [font-size 11, color $--gray] just now
257
257
258 @column [width 340, sm:width fill, spacing $--gap]
258 @column [width 340, sm:width fill, spacing $--gap]
259
259
@@ -267,12 +267,12 @@
267 @card [title Team Members]
267 @card [title Team Members]
268 @column [spacing 8, margin-top 12]
268 @column [spacing 8, margin-top 12]
269 @each $member in $team-members
269 @each $member in $team-members
270− @row [spacing 10, align-items center, padding 6 8, hover:background #f1f5f9, dark:hover:background #1e293b, rounded 6, transition all 0.15s]
270+ @row [spacing 10, align-items center, padding 6 8, hover:background #f1f5f9, dark:hover:background #1e293b, border-radius 6, transition all 0.15s]
271 @avatar [initials $member, size 28]
271 @avatar [initials $member, size 28]
272 @column [spacing 0, width fill]
272 @column [spacing 0, width fill]
273− @text [size 13, bold, color $--dark, dark:color white] $member
273+ @text [font-size 13, font-weight bold, color $--dark, dark:color white] $member
274− @text [size 11, color $--gray] Developer
274+ @text [font-size 11, color $--gray] Developer
275− @el [width 8, height 8, rounded 50%, background $--secondary]
275+ @el [width 8, height 8, border-radius 50%, background $--secondary]
276
276
277 @card [title Tags]
277 @card [title Tags]
278 @row [spacing 0, wrap, margin-top 8]
278 @row [spacing 0, wrap, margin-top 8]
@@ -293,104 +293,104 @@
293 @column [spacing 0, margin-top 12]
293 @column [spacing 0, margin-top 12]
294 @let notifications New deployment completed,PR review requested,Build failed on staging,Meeting in 30 minutes,Subscription renewal due
294 @let notifications New deployment completed,PR review requested,Build failed on staging,Meeting in 30 minutes,Subscription renewal due
295 @each $note in $notifications
295 @each $note in $notifications
296− @row [spacing 10, padding 10 12, border-bottom 1 $--border, dark:border-bottom 1 #334155, align-items center]
296+ @row [spacing 10, padding 10 12, border-bottom 1 solid $--border, dark:border-bottom 1 solid #334155, align-items center]
297 @notification-dot
297 @notification-dot
298− @text [size 13, color $--dark, dark:color white, width fill] $note
298+ @text [font-size 13, color $--dark, dark:color white, width fill] $note
299− @text [size 11, color $--gray] now
299+ @text [font-size 11, color $--gray] now
300
300
301 @card [title Settings]
301 @card [title Settings]
302 @column [spacing 12, margin-top 12]
302 @column [spacing 12, margin-top 12]
303 @row [spacing 0, align-items center]
303 @row [spacing 0, align-items center]
304− @text [size 14, color $--dark, dark:color white, width fill] Dark Mode
304+ @text [font-size 14, color $--dark, dark:color white, width fill] Dark Mode
305 @toggle [checked true]
305 @toggle [checked true]
306 @row [spacing 0, align-items center]
306 @row [spacing 0, align-items center]
307− @text [size 14, color $--dark, dark:color white, width fill] Notifications
307+ @text [font-size 14, color $--dark, dark:color white, width fill] Notifications
308 @toggle [checked true]
308 @toggle [checked true]
309 @row [spacing 0, align-items center]
309 @row [spacing 0, align-items center]
310− @text [size 14, color $--dark, dark:color white, width fill] Auto-save
310+ @text [font-size 14, color $--dark, dark:color white, width fill] Auto-save
311 @toggle [checked false]
311 @toggle [checked false]
312 @row [spacing 0, align-items center]
312 @row [spacing 0, align-items center]
313− @text [size 14, color $--dark, dark:color white, width fill] Compact View
313+ @text [font-size 14, color $--dark, dark:color white, width fill] Compact View
314 @toggle [checked false]
314 @toggle [checked false]
315
315
316 @el [margin-top $--gap-lg]
316 @el [margin-top $--gap-lg]
317 @card [title Data Table]
317 @card [title Data Table]
318 @column [spacing 0, margin-top 12]
318 @column [spacing 0, margin-top 12]
319 @row [spacing 16, margin-bottom 12, align-items center]
319 @row [spacing 16, margin-bottom 12, align-items center]
320− @input [type=search, placeholder=Filter results..., padding 8 12, rounded 6, border 1 $--border, width 240]
320+ @input [type=search, placeholder=Filter results..., padding 8 12, border-radius 6, border 1 solid $--border, width 240]
321 @el [width fill]
321 @el [width fill]
322 @row [spacing 8]
322 @row [spacing 8]
323 @each $cat in $categories
323 @each $cat in $categories
324− @button [padding 6 12, rounded 4, border 1 $--border, background white, dark:background #1e293b, hover:background #f1f5f9, color $--gray, hover:color $--dark, font-size 13, cursor pointer, transition all 0.15s] $cat
324+ @button [padding 6 12, border-radius 4, border 1 solid $--border, background white, dark:background #1e293b, hover:background #f1f5f9, color $--gray, hover:color $--dark, font-size 13, cursor pointer, transition all 0.15s] $cat
325
325
326− @row [spacing 0, padding 8 16, background #f8fafc, dark:background #0f172a, border-bottom 2 $--border, rounded 6 6 0 0]
326+ @row [spacing 0, padding 8 16, background #f8fafc, dark:background #0f172a, border-bottom 2 solid $--border, border-radius 6 6 0 0]
327 @el [width 60]
327 @el [width 60]
328− @text [bold, size 12, color $--gray, text-transform uppercase] ID
328+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase] ID
329 @el [width 200]
329 @el [width 200]
330− @text [bold, size 12, color $--gray, text-transform uppercase] Name
330+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase] Name
331 @el [width 150]
331 @el [width 150]
332− @text [bold, size 12, color $--gray, text-transform uppercase] Category
332+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase] Category
333 @el [width 120]
333 @el [width 120]
334− @text [bold, size 12, color $--gray, text-transform uppercase] Status
334+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase] Status
335 @el [width fill]
335 @el [width fill]
336− @text [bold, size 12, color $--gray, text-transform uppercase] Updated
336+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase] Updated
337
337
338 @each $status in $statuses
338 @each $status in $statuses
339 @each $project in $projects
339 @each $project in $projects
340− @row [spacing 0, padding 10 16, border-bottom 1 $--border, dark:border-bottom 1 #334155, hover:background #f8fafc, dark:hover:background #1e293b, cursor pointer, transition all 0.1s]
340+ @row [spacing 0, padding 10 16, border-bottom 1 solid $--border, dark:border-bottom 1 solid #334155, hover:background #f8fafc, dark:hover:background #1e293b, cursor pointer, transition all 0.1s]
341 @el [width 60]
341 @el [width 60]
342− @text [size 13, color $--gray] #42
342+ @text [font-size 13, color $--gray] #42
343 @el [width 200]
343 @el [width 200]
344 @row [spacing 8, align-items center]
344 @row [spacing 8, align-items center]
345 @avatar [initials $project, size 24]
345 @avatar [initials $project, size 24]
346− @text [size 14, color $--dark, dark:color white] $project
346+ @text [font-size 14, color $--dark, dark:color white] $project
347 @el [width 150]
347 @el [width 150]
348− @text [size 13, color $--gray] $status
348+ @text [font-size 13, color $--gray] $status
349 @el [width 120]
349 @el [width 120]
350 @badge [label $status, variant var(--secondary)]
350 @badge [label $status, variant var(--secondary)]
351 @el [width fill]
351 @el [width fill]
352− @text [size 13, color $--gray] 2 hours ago
352+ @text [font-size 13, color $--gray] 2 hours ago
353
353
354− @row [spacing 16, padding 12 16, align-items center, border-top 1 $--border]
354+ @row [spacing 16, padding 12 16, align-items center, border-top 1 solid $--border]
355− @text [size 13, color $--gray] Showing 1-20 of 142 results
355+ @text [font-size 13, color $--gray] Showing 1-20 of 142 results
356 @el [width fill]
356 @el [width fill]
357 @row [spacing 4]
357 @row [spacing 4]
358− @button [padding 6 10, rounded 4, border 1 $--border, background white, dark:background #1e293b, color $--gray, font-size 13, cursor pointer] Prev
358+ @button [padding 6 10, border-radius 4, border 1 solid $--border, background white, dark:background #1e293b, color $--gray, font-size 13, cursor pointer] Prev
359− @button [padding 6 10, rounded 4, border 1 $--primary, background $--primary, color white, font-size 13, cursor pointer] 1
359+ @button [padding 6 10, border-radius 4, border 1 solid $--primary, background $--primary, color white, font-size 13, cursor pointer] 1
360− @button [padding 6 10, rounded 4, border 1 $--border, background white, dark:background #1e293b, color $--gray, font-size 13, cursor pointer] 2
360+ @button [padding 6 10, border-radius 4, border 1 solid $--border, background white, dark:background #1e293b, color $--gray, font-size 13, cursor pointer] 2
361− @button [padding 6 10, rounded 4, border 1 $--border, background white, dark:background #1e293b, color $--gray, font-size 13, cursor pointer] 3
361+ @button [padding 6 10, border-radius 4, border 1 solid $--border, background white, dark:background #1e293b, color $--gray, font-size 13, cursor pointer] 3
362− @button [padding 6 10, rounded 4, border 1 $--border, background white, dark:background #1e293b, color $--gray, font-size 13, cursor pointer] Next
362+ @button [padding 6 10, border-radius 4, border 1 solid $--border, background white, dark:background #1e293b, color $--gray, font-size 13, cursor pointer] Next
363
363
364− @footer [padding $--gap-lg 24, background white, dark:background #0f172a, border-top 1 $--border, dark:border-top 1 #1e293b]
364+ @footer [padding $--gap-lg 24, background white, dark:background #0f172a, border-top 1 solid $--border, dark:border-top 1 solid #1e293b]
365 @row [spacing $--gap-lg, sm:flex-direction column, lg:flex-direction row]
365 @row [spacing $--gap-lg, sm:flex-direction column, lg:flex-direction row]
366 @column [spacing 8, width fill]
366 @column [spacing 8, width fill]
367− @text [bold, size 14, color $--dark, dark:color white] htmlang Dashboard
367+ @text [font-weight bold, font-size 14, color $--dark, dark:color white] htmlang Dashboard
368− @text [size 12, color $--gray] Built with htmlang - a declarative HTML language
368+ @text [font-size 12, color $--gray] Built with htmlang - a declarative HTML language
369− @text [size 11, color $--gray] Copyright 2025 htmlang contributors
369+ @text [font-size 11, color $--gray] Copyright 2025 htmlang contributors
370
370
371 @column [spacing 4]
371 @column [spacing 4]
372− @text [bold, size 12, color $--gray, text-transform uppercase, margin-bottom 4] Product
372+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase, margin-bottom 4] Product
373− @link [color $--gray, hover:color $--primary, size 13] /features Features
373+ @link [color $--gray, hover:color $--primary, font-size 13] /features Features
374− @link [color $--gray, hover:color $--primary, size 13] /pricing Pricing
374+ @link [color $--gray, hover:color $--primary, font-size 13] /pricing Pricing
375− @link [color $--gray, hover:color $--primary, size 13] /changelog Changelog
375+ @link [color $--gray, hover:color $--primary, font-size 13] /changelog Changelog
376− @link [color $--gray, hover:color $--primary, size 13] /roadmap Roadmap
376+ @link [color $--gray, hover:color $--primary, font-size 13] /roadmap Roadmap
377
377
378 @column [spacing 4]
378 @column [spacing 4]
379− @text [bold, size 12, color $--gray, text-transform uppercase, margin-bottom 4] Resources
379+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase, margin-bottom 4] Resources
380− @link [color $--gray, hover:color $--primary, size 13] /docs Documentation
380+ @link [color $--gray, hover:color $--primary, font-size 13] /docs Documentation
381− @link [color $--gray, hover:color $--primary, size 13] /api API Reference
381+ @link [color $--gray, hover:color $--primary, font-size 13] /api API Reference
382− @link [color $--gray, hover:color $--primary, size 13] /guides Guides
382+ @link [color $--gray, hover:color $--primary, font-size 13] /guides Guides
383− @link [color $--gray, hover:color $--primary, size 13] /examples Examples
383+ @link [color $--gray, hover:color $--primary, font-size 13] /examples Examples
384
384
385 @column [spacing 4]
385 @column [spacing 4]
386− @text [bold, size 12, color $--gray, text-transform uppercase, margin-bottom 4] Company
386+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase, margin-bottom 4] Company
387− @link [color $--gray, hover:color $--primary, size 13] /about About
387+ @link [color $--gray, hover:color $--primary, font-size 13] /about About
388− @link [color $--gray, hover:color $--primary, size 13] /blog Blog
388+ @link [color $--gray, hover:color $--primary, font-size 13] /blog Blog
389− @link [color $--gray, hover:color $--primary, size 13] /careers Careers
389+ @link [color $--gray, hover:color $--primary, font-size 13] /careers Careers
390− @link [color $--gray, hover:color $--primary, size 13] /contact Contact
390+ @link [color $--gray, hover:color $--primary, font-size 13] /contact Contact
391
391
392 @column [spacing 4]
392 @column [spacing 4]
393− @text [bold, size 12, color $--gray, text-transform uppercase, margin-bottom 4] Legal
393+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase, margin-bottom 4] Legal
394− @link [color $--gray, hover:color $--primary, size 13] /privacy Privacy Policy
394+ @link [color $--gray, hover:color $--primary, font-size 13] /privacy Privacy Policy
395− @link [color $--gray, hover:color $--primary, size 13] /terms Terms of Service
395+ @link [color $--gray, hover:color $--primary, font-size 13] /terms Terms of Service
396− @link [color $--gray, hover:color $--primary, size 13] /cookies Cookie Policy
396+ @link [color $--gray, hover:color $--primary, font-size 13] /cookies Cookie Policy
tests/snapshots/stress_large.html+1−1
@@ -1 +1 @@
1−<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Stress Test Large Document</title><style>@layer hl-reset,htmlang;@layer hl-reset{*,*::before,*::after{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,sans-serif}img{display:block}a{text-decoration:none;color:inherit}a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid currentColor;outline-offset:2px}}:root{--primary:#3b82f6;--secondary:#10b981;--accent:#f59e0b;--danger:#ef4444;--dark:#1e293b;--light:#f8fafc;--gray:#64748b;--border:#e2e8f0;--radius:8;--gap:16;--gap-sm:8;--gap-lg:24;}@layer htmlang{.a{display:flex;flex-direction:row;gap:0;min-height:100vh;}.b{display:flex;flex-direction:column;width:260px;background:white;border-right:1px solid var(--border);padding:16px;gap:8px;}.c{display:flex;flex-direction:row;gap:12px;padding:8px;align-items:center;}.d{flex-direction:column;width:36px;height:36px;border-radius:50%;background:var(--primary);color:white;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;}.e{display:flex;flex-direction:column;gap:2px;}.f{font-weight:bold;font-size:16px;color:var(--dark);}.g{font-size:12px;color:var(--gray);}.h{display:flex;flex-direction:column;width:100%;height:1px;background:var(--border);margin:8px 0;}.i{display:flex;flex-direction:column;margin-bottom:24px;}.j{font-weight:bold;font-size:12px;color:var(--gray);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;}.k{color:var(--primary);font-weight:bold;padding:8px 12px;background:#eff6ff;border-radius:6px;}.l{color:var(--gray);padding:8px 12px;border-radius:6px;transition:all 0.15s;}.m{display:flex;flex-direction:column;gap:4px;}.n{display:flex;flex-direction:row;gap:8px;align-items:center;}.o{display:flex;flex-direction:column;width:6px;height:6px;border-radius:50%;background:var(--secondary);}.p{font-size:13px;color:var(--gray);}.q{display:flex;flex-direction:column;width:6px;height:6px;border-radius:50%;background:var(--accent);}.r{display:flex;flex-direction:column;width:6px;height:6px;border-radius:50%;background:#cbd5e1;}.s{display:flex;flex-direction:column;margin-top:auto;padding:12px;background:#f1f5f9;border-radius:8px;}.t{flex-direction:column;width:28px;height:28px;border-radius:50%;background:var(--primary);color:white;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;}.u{display:flex;flex-direction:column;gap:0;}.v{font-size:13px;font-weight:bold;color:var(--dark);}.w{font-size:11px;color:var(--gray);}.x{display:flex;flex-direction:column;flex:1;min-width:0;background:var(--light);}.y{display:flex;flex-direction:column;padding:12px 24px;background:white;border-bottom:1px solid var(--border);}.z{display:flex;flex-direction:row;gap:16px;align-items:center;}.aa{display:flex;flex-direction:row;gap:8px;}.ab{color:var(--gray);font-size:14px;}.ac{color:var(--border);font-size:14px;}.ad{color:var(--dark);font-size:14px;}.ae{font-weight:bold;font-size:22px;color:var(--dark);}.af{display:flex;flex-direction:column;flex:1;min-width:0;}.ag{display:flex;flex-direction:row;gap:12px;align-items:center;}.ah{padding:8px 12px;border-radius:6px;border:1px solid var(--border);width:240px;transition:all 0.2s;}.ai{display:flex;flex-direction:column;position:relative;}.aj{padding:8px 16px;background:var(--primary);color:white;border-radius:6px;border:none solid currentColor;cursor:pointer;transition:all 0.2s;font-size:14px;}.ak{display:flex;flex-direction:column;position:absolute;top:-2px;right:-2px;}.al{display:flex;flex-direction:column;width:8px;height:8px;border-radius:50%;background:var(--danger);}.am{padding:8px 16px;background:var(--gray);color:white;border-radius:6px;border:none solid currentColor;cursor:pointer;transition:all 0.2s;font-size:14px;}.an{display:flex;flex-direction:column;padding:24px;overflow-y:auto;}.ao{display:flex;flex-direction:column;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;padding:12px 16px;}.ap{color:#1e40af;font-size:14px;}.aq,.av{display:flex;flex-direction:row;gap:16px;margin-top:24px;}.ar{display:flex;flex-direction:column;background:white;border-radius:8px;padding:16px;border:1px solid var(--border);}.as{font-size:14px;color:var(--gray);}.at{font-weight:bold;font-size:28px;color:var(--dark);margin-top:4px;}.au{font-size:12px;color:var(--secondary);margin-top:2px;}.aw{display:flex;flex-direction:column;background:white;border-radius:8px;padding:16px;border:1px solid var(--border);transition:all 0.2s;animation:fade-in 0.3s ease;}.ax{font-weight:bold;font-size:18px;color:var(--dark);}.ay{display:flex;flex-direction:column;margin-top:8px;}.az{flex-direction:column;height:200px;background:var(--light);border-radius:6px;margin-top:12px;display:flex;align-items:flex-end;padding:16px;gap:8px;}.a0{display:flex;flex-direction:column;width:100%;height:40%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.a1{display:flex;flex-direction:column;width:100%;height:65%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.a2{display:flex;flex-direction:column;width:100%;height:45%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.a3{display:flex;flex-direction:column;width:100%;height:80%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.a4{display:flex;flex-direction:column;width:100%;height:55%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.a5{display:flex;flex-direction:column;width:100%;height:70%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.a6{display:flex;flex-direction:column;width:100%;height:90%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.a7{display:flex;flex-direction:column;width:100%;height:60%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.a8{display:flex;flex-direction:column;width:100%;height:75%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.a9{display:flex;flex-direction:column;width:100%;height:85%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.ba{display:flex;flex-direction:column;width:100%;height:50%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.bb{display:flex;flex-direction:column;width:100%;height:95%;background:var(--primary);border-radius:4 4 0 0;transition:all 0.2s;min-width:20px;}.bc{display:flex;flex-direction:column;gap:12px;margin-top:12px;}.bd{display:flex;flex-direction:row;gap:12px;align-items:center;padding:8px;border-radius:6px;}.be{flex-direction:column;width:32px;height:32px;border-radius:50%;background:var(--primary);color:white;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;}.bf{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0;}.bg{font-size:14px;font-weight:bold;color:var(--dark);}.bh{display:flex;flex-direction:column;flex:1;min-width:0;gap:16px;}.bi{display:flex;flex-direction:column;gap:0;margin-top:12px;}.bj{display:flex;flex-direction:row;gap:0;padding:8px 16px;background:var(--light);border-bottom:1px solid var(--border);}.bk{display:flex;flex-direction:column;width:200px;}.bl{font-weight:bold;font-size:12px;color:var(--gray);text-transform:uppercase;}.bm{display:flex;flex-direction:column;width:150px;}.bn{display:flex;flex-direction:column;width:120px;}.bo{display:flex;flex-direction:row;gap:0;padding:12px 16px;border-bottom:1px solid var(--border);}.bp{font-size:14px;}.bq{display:flex;flex-direction:row;gap:0;}.br{font-size:13px;color:var(--dark);flex:1;min-width:0;}.bs{display:flex;flex-direction:column;width:100%;height:8px;background:var(--border);border-radius:4px;overflow:hidden;}.bt{display:flex;flex-direction:column;width:67%;height:100%;background:var(--primary);border-radius:4px;}.bu{display:flex;flex-direction:column;gap:8px;margin-top:12px;}.bv{display:flex;flex-direction:row;gap:12px;padding:8px 12px;align-items:center;}.bw{flex-direction:column;width:24px;height:24px;border-radius:50%;background:#eff6ff;display:flex;align-items:center;justify-content:center;}.bx{font-size:10px;color:var(--primary);font-weight:bold;}.by{font-size:14px;color:var(--dark);flex:1;min-width:0;}.bz{display:flex;flex-direction:column;width:340px;gap:16px;}.b0{padding:8px 16px;background:var(--secondary);color:white;border-radius:6px;border:none solid currentColor;cursor:pointer;transition:all 0.2s;font-size:14px;}.b1{padding:8px 16px;background:var(--accent);color:white;border-radius:6px;border:none solid currentColor;cursor:pointer;transition:all 0.2s;font-size:14px;}.b2{padding:8px 16px;background:var(--gray);color:white;border-radius:6px;border:none solid currentColor;cursor:pointer;transition:all 0.2s;font-size:14px;}.b3{display:flex;flex-direction:row;gap:10px;align-items:center;padding:6px 8px;border-radius:6px;transition:all 0.15s;}.b4{display:flex;flex-direction:column;gap:0;flex:1;min-width:0;}.b5{display:flex;flex-direction:column;width:8px;height:8px;border-radius:50%;background:var(--secondary);}.b6{display:flex;flex-direction:row;gap:0;flex-wrap:wrap;margin-top:8px;}.b7{flex-direction:column;display:inline-block;padding:4px 10px;background:#f1f5f9;color:var(--gray);border-radius:4px;font-size:12px;margin:2px;}.b8{display:flex;flex-direction:row;gap:10px;padding:10px 12px;border-bottom:1px solid var(--border);align-items:center;}.b9{display:flex;flex-direction:row;gap:0;align-items:center;}.ca{display:flex;flex-direction:column;width:40px;height:22px;border-radius:11px;background:var(--primary);padding:2px;cursor:pointer;transition:all 0.2s;}.cb{display:flex;flex-direction:column;width:18px;height:18px;border-radius:50%;background:white;margin-left:18px;transition:all 0.2s;}.cc{display:flex;flex-direction:column;width:40px;height:22px;border-radius:11px;background:#cbd5e1;padding:2px;cursor:pointer;transition:all 0.2s;}.cd{display:flex;flex-direction:column;width:18px;height:18px;border-radius:50%;background:white;transition:all 0.2s;}.ce{display:flex;flex-direction:column;margin-top:24px;}.cf{display:flex;flex-direction:row;gap:16px;margin-bottom:12px;align-items:center;}.cg{padding:8px 12px;border-radius:6px;border:1px solid var(--border);width:240px;}.ch{padding:6px 12px;border-radius:4px;border:1px solid var(--border);background:white;color:var(--gray);font-size:13px;cursor:pointer;transition:all 0.15s;}.ci{display:flex;flex-direction:row;gap:0;padding:8px 16px;background:var(--light);border-bottom:2px solid var(--border);border-radius:6 6 0 0;}.cj{display:flex;flex-direction:column;width:60px;}.ck{display:flex;flex-direction:row;gap:0;padding:10px 16px;border-bottom:1px solid var(--border);cursor:pointer;transition:all 0.1s;}.cl{flex-direction:column;width:24px;height:24px;border-radius:50%;background:var(--primary);color:white;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;}.cm{font-size:14px;color:var(--dark);}.cn{flex-direction:column;display:inline-block;padding:2px 8px;background:var(--secondary);color:white;border-radius:12px;font-size:12px;}.co{display:flex;flex-direction:row;gap:16px;padding:12px 16px;align-items:center;border-top:1px solid var(--border);}.cp{display:flex;flex-direction:row;gap:4px;}.cq{padding:6px 10px;border-radius:4px;border:1px solid var(--border);background:white;color:var(--gray);font-size:13px;cursor:pointer;}.cr{padding:6px 10px;border-radius:4px;border:1px solid var(--primary);background:var(--primary);color:white;font-size:13px;cursor:pointer;}.cs{display:flex;flex-direction:column;padding:24px 24px;background:white;border-top:1px solid var(--border);}.ct{display:flex;flex-direction:row;gap:24px;}.cu{display:flex;flex-direction:column;gap:8px;flex:1;min-width:0;}.cv{font-weight:bold;font-size:14px;color:var(--dark);}.cw{font-weight:bold;font-size:12px;color:var(--gray);text-transform:uppercase;margin-bottom:4px;}.cx{color:var(--gray);font-size:13px;}.l:hover{color:var(--primary);background:#f1f5f9;}.ab:hover,.cx:hover{color:var(--primary);}.aj:hover,.am:hover,.b0:hover,.b1:hover,.b2:hover{background:var(--dark);}.aw:hover{box-shadow:0 4 12 rgba(0,0,0,0.1);}.a0:hover,.a1:hover,.a2:hover,.a3:hover,.a4:hover,.a5:hover,.a6:hover,.a7:hover,.a8:hover,.a9:hover,.ba:hover,.bb:hover{background:#2563eb;}.bd:hover,.bo:hover,.ck:hover{background:var(--light);}.b3:hover{background:#f1f5f9;}.ch:hover{background:#f1f5f9;color:var(--dark);}.ah:focus{border:1px solid var(--primary);}@media(min-width:640px){.aq,.ct{flex-direction:column;}.bz{flex:1;min-width:0;}}@media(min-width:1024px){.aq,.ct{flex-direction:row;}}@media(prefers-color-scheme:dark){.b{background:#0f172a;border-right:1px solid var(--dark);}.f,.v,.ad,.ae,.at,.ax,.bg,.br,.by,.cm,.cv{color:white;}.h,.bs{background:#334155;}.k,.bw{background:#1e3a5f;}.s,.ch,.cq{background:var(--dark);}.x,.az,.bj,.ci{background:#0f172a;}.y{background:#0f172a;border-bottom:1px solid var(--dark);}.ah{border:1px solid #334155;background:var(--dark);color:white;}.ar,.aw{background:var(--dark);border:1px solid #334155;}.bo,.b8,.ck{border-bottom:1px solid #334155;}.b7{background:#334155;color:#94a3b8;}.cc{background:#475569;}.cs{background:#0f172a;border-top:1px solid var(--dark);}}}@keyframes fade-in {from{opacity:0}to{opacity:1}}@keyframes slide-down {0%{transform:translateY(-10px);opacity:0}100%{transform:translateY(0);opacity:1}}</style></head><body><div class="a"><div class="b"><div class="c"><div class="d"><span>HL</span></div><div class="e"><span class="f">htmlang</span><span class="g">Dashboard v2.0</span></div></div><div class="h"></div><div class="i"><span class="j">Navigation</span><div class="e"><a href="/dashboard" class="k">Dashboard</a><a href="/analytics" class="l">Analytics</a><a href="/projects" class="l">Projects</a><a href="/team" class="l">Team</a><a href="/messages" class="l">Messages</a><a href="/calendar" class="l">Calendar</a><a href="/documents" class="l">Documents</a><a href="/settings" class="l">Settings</a></div></div><div class="i"><span class="j">Favorites</span><div class="e"><a href="/project/alpha" class="l">Project Alpha</a><a href="/project/beta" class="l">Project Beta</a><a href="/reports/q4" class="l">Q4 Reports</a></div></div><div class="i"><span class="j">Shortcuts</span><div class="m"><div class="n"><div class="o"></div><span class="p">Online (12)</span></div><div class="n"><div class="q"></div><span class="p">Away (3)</span></div><div class="n"><div class="r"></div><span class="p">Offline (5)</span></div></div></div><div class="s"><div class="n"><div class="t"><span>JD</span></div><div class="u"><span class="v">Jane Doe</span><span class="w">jane@example.com</span></div></div></div></div><div class="x"><header class="y"><div class="z"><div class="e"><div class="aa"><div class="aa"><a href="/" class="ab">Home</a><span class="ac">/</span></div><span class="ad">Dashboard</span></div><span class="ae">Dashboard Overview</span></div><div class="af"></div><div class="ag"><input class="ah" type="search" placeholder="Search..."><div class="ai"><button class="aj">Alerts</button><div class="ak"><div class="al"></div></div></div><button class="am">Settings</button></div></div></header><div class="an"><div class="ao"><span class="ap">Welcome back! You have 3 new notifications and 2 pending tasks.</span></div><div class="aq"><div class="ar"><span class="as">Total Revenue</span><span class="at">$48</span><span class="au">+12.5% from last month</span></div><div class="ar"><span class="as">Active Users</span><span class="at">2</span><span class="au">+8.2% from last week</span></div><div class="ar"><span class="as">Conversion Rate</span><span class="at">3.24%</span><span class="au">+0.4% from yesterday</span></div><div class="ar"><span class="as">Open Tickets</span><span class="at">142</span><span class="au">-5.1% from last month</span></div></div><div class="av"><div class="aw"><span class="ax">Revenue Overview</span><div class="ay"><div class="az"><div class="a0"></div><div class="a1"></div><div class="a2"></div><div class="a3"></div><div class="a4"></div><div class="a5"></div><div class="a6"></div><div class="a7"></div><div class="a8"></div><div class="a9"></div><div class="ba"></div><div class="bb"></div></div></div></div><div class="aw"><span class="ax">Activity Feed</span><div class="ay"><div class="bc"><div class="bd"><div class="be"><span>Alice</span></div><div class="bf"><span class="bg">Alice</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Bob</span></div><div class="bf"><span class="bg">Bob</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Carol</span></div><div class="bf"><span class="bg">Carol</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Dave</span></div><div class="bf"><span class="bg">Dave</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Eve</span></div><div class="bf"><span class="bg">Eve</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Frank</span></div><div class="bf"><span class="bg">Frank</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Grace</span></div><div class="bf"><span class="bg">Grace</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Hank</span></div><div class="bf"><span class="bg">Hank</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div></div></div></div></div><div class="aq"><div class="bh"><div class="aw"><span class="ax">Project Status</span><div class="ay"><div class="bi"><div class="bj"><div class="bk"><span class="bl">Project</span></div><div class="bm"><span class="bl">Status</span></div><div class="bn"><span class="bl">Progress</span></div><div class="af"><span class="bl">Due Date</span></div></div><div class="bo"><div class="bk"><span class="bp">Project Alpha</span></div><div class="bm"><span class="as">In Progress</span></div><div class="bn"><span class="bp">75%</span></div><div class="af"><span class="as">Dec 15 2025</span></div></div><div class="bo"><div class="bk"><span class="bp">Project Beta</span></div><div class="bm"><span class="as">Review</span></div><div class="bn"><span class="bp">90%</span></div><div class="af"><span class="as">Nov 30 2025</span></div></div><div class="bo"><div class="bk"><span class="bp">Project Gamma</span></div><div class="bm"><span class="as">Planning</span></div><div class="bn"><span class="bp">20%</span></div><div class="af"><span class="as">Jan 20 2026</span></div></div><div class="bo"><div class="bk"><span class="bp">Project Delta</span></div><div class="bm"><span class="as">Completed</span></div><div class="bn"><span class="bp">100%</span></div><div class="af"><span class="as">Oct 10 2025</span></div></div><div class="bo"><div class="bk"><span class="bp">Project Epsilon</span></div><div class="bm"><span class="as">On Hold</span></div><div class="bn"><span class="bp">45%</span></div><div class="af"><span class="as">Feb 28 2026</span></div></div></div></div></div><div class="aw"><span class="ax">Progress Breakdown</span><div class="ay"><div class="bc"><div class="m"><div class="bq"><span class="br">Alpha</span><span class="p">67%</span></div><div class="bs"><div class="bt"></div></div></div><div class="m"><div class="bq"><span class="br">Beta</span><span class="p">67%</span></div><div class="bs"><div class="bt"></div></div></div><div class="m"><div class="bq"><span class="br">Gamma</span><span class="p">67%</span></div><div class="bs"><div class="bt"></div></div></div><div class="m"><div class="bq"><span class="br">Delta</span><span class="p">67%</span></div><div class="bs"><div class="bt"></div></div></div><div class="m"><div class="bq"><span class="br">Epsilon</span><span class="p">67%</span></div><div class="bs"><div class="bt"></div></div></div></div></div></div><div class="aw"><span class="ax">Recent Activity Log</span><div class="ay"><div class="bu"><div class="bv"><div class="bw"><span class="bx">0</span></div><span class="by">Deployed v2.3.1 to production</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">1</span></div><span class="by">Merged feature/auth-redesign</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">2</span></div><span class="by">Closed issue #482: Login timeout</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">3</span></div><span class="by">Updated CI/CD pipeline config</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">4</span></div><span class="by">Added unit tests for payment module</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">5</span></div><span class="by">Reviewed PR #156: API refactor</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">6</span></div><span class="by">Fixed memory leak in worker pool</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">7</span></div><span class="by">Released hotfix 2.3.2</span><span class="w">just now</span></div></div></div></div></div><div class="bz"><div class="aw"><span class="ax">Quick Actions</span><div class="ay"><div class="bu"><button class="aj">New Project</button><button class="b0">Create Report</button><button class="b1">Invite Member</button><button class="b2">View Backlog</button></div></div></div><div class="aw"><span class="ax">Team Members</span><div class="ay"><div class="bu"><div class="b3"><div class="t"><span>Alice</span></div><div class="b4"><span class="v">Alice</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Bob</span></div><div class="b4"><span class="v">Bob</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Carol</span></div><div class="b4"><span class="v">Carol</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Dave</span></div><div class="b4"><span class="v">Dave</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Eve</span></div><div class="b4"><span class="v">Eve</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Frank</span></div><div class="b4"><span class="v">Frank</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Grace</span></div><div class="b4"><span class="v">Grace</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Hank</span></div><div class="b4"><span class="v">Hank</span><span class="w">Developer</span></div><div class="b5"></div></div></div></div></div><div class="aw"><span class="ax">Tags</span><div class="ay"><div class="b6"><div class="b7"><span>frontend</span></div><div class="b7"><span>backend</span></div><div class="b7"><span>devops</span></div><div class="b7"><span>design</span></div><div class="b7"><span>mobile</span></div><div class="b7"><span>security</span></div><div class="b7"><span>performance</span></div><div class="b7"><span>testing</span></div><div class="b7"><span>docs</span></div><div class="b7"><span>infra</span></div><div class="b7"><span>ux</span></div><div class="b7"><span>api</span></div></div></div></div><div class="aw"><span class="ax">Notifications</span><div class="ay"><div class="bi"><div class="b8"><div class="al"></div><span class="br">New deployment completed</span><span class="w">now</span></div><div class="b8"><div class="al"></div><span class="br">PR review requested</span><span class="w">now</span></div><div class="b8"><div class="al"></div><span class="br">Build failed on staging</span><span class="w">now</span></div><div class="b8"><div class="al"></div><span class="br">Meeting in 30 minutes</span><span class="w">now</span></div><div class="b8"><div class="al"></div><span class="br">Subscription renewal due</span><span class="w">now</span></div></div></div></div><div class="aw"><span class="ax">Settings</span><div class="ay"><div class="bc"><div class="b9"><span class="by">Dark Mode</span><div class="ca"><div class="cb"></div></div></div><div class="b9"><span class="by">Notifications</span><div class="ca"><div class="cb"></div></div></div><div class="b9"><span class="by">Auto-save</span><div class="cc"><div class="cd"></div></div></div><div class="b9"><span class="by">Compact View</span><div class="cc"><div class="cd"></div></div></div></div></div></div></div></div><div class="ce"><div class="aw"><span class="ax">Data Table</span><div class="ay"><div class="bi"><div class="cf"><input class="cg" type="search" placeholder="Filter results..."><div class="af"></div><div class="aa"><button class="ch">all</button><button class="ch">design</button><button class="ch">development</button><button class="ch">marketing</button><button class="ch">analytics</button></div></div><div class="ci"><div class="cj"><span class="bl">ID</span></div><div class="bk"><span class="bl">Name</span></div><div class="bm"><span class="bl">Category</span></div><div class="bn"><span class="bl">Status</span></div><div class="af"><span class="bl">Updated</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Alpha</span></div><span class="cm">Alpha</span></div></div><div class="bm"><span class="p">active</span></div><div class="bn"><div class="cn"><span>active</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Beta</span></div><span class="cm">Beta</span></div></div><div class="bm"><span class="p">active</span></div><div class="bn"><div class="cn"><span>active</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Gamma</span></div><span class="cm">Gamma</span></div></div><div class="bm"><span class="p">active</span></div><div class="bn"><div class="cn"><span>active</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Delta</span></div><span class="cm">Delta</span></div></div><div class="bm"><span class="p">active</span></div><div class="bn"><div class="cn"><span>active</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Epsilon</span></div><span class="cm">Epsilon</span></div></div><div class="bm"><span class="p">active</span></div><div class="bn"><div class="cn"><span>active</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Alpha</span></div><span class="cm">Alpha</span></div></div><div class="bm"><span class="p">pending</span></div><div class="bn"><div class="cn"><span>pending</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Beta</span></div><span class="cm">Beta</span></div></div><div class="bm"><span class="p">pending</span></div><div class="bn"><div class="cn"><span>pending</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Gamma</span></div><span class="cm">Gamma</span></div></div><div class="bm"><span class="p">pending</span></div><div class="bn"><div class="cn"><span>pending</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Delta</span></div><span class="cm">Delta</span></div></div><div class="bm"><span class="p">pending</span></div><div class="bn"><div class="cn"><span>pending</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Epsilon</span></div><span class="cm">Epsilon</span></div></div><div class="bm"><span class="p">pending</span></div><div class="bn"><div class="cn"><span>pending</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Alpha</span></div><span class="cm">Alpha</span></div></div><div class="bm"><span class="p">completed</span></div><div class="bn"><div class="cn"><span>completed</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Beta</span></div><span class="cm">Beta</span></div></div><div class="bm"><span class="p">completed</span></div><div class="bn"><div class="cn"><span>completed</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Gamma</span></div><span class="cm">Gamma</span></div></div><div class="bm"><span class="p">completed</span></div><div class="bn"><div class="cn"><span>completed</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Delta</span></div><span class="cm">Delta</span></div></div><div class="bm"><span class="p">completed</span></div><div class="bn"><div class="cn"><span>completed</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Epsilon</span></div><span class="cm">Epsilon</span></div></div><div class="bm"><span class="p">completed</span></div><div class="bn"><div class="cn"><span>completed</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Alpha</span></div><span class="cm">Alpha</span></div></div><div class="bm"><span class="p">archived</span></div><div class="bn"><div class="cn"><span>archived</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Beta</span></div><span class="cm">Beta</span></div></div><div class="bm"><span class="p">archived</span></div><div class="bn"><div class="cn"><span>archived</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Gamma</span></div><span class="cm">Gamma</span></div></div><div class="bm"><span class="p">archived</span></div><div class="bn"><div class="cn"><span>archived</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Delta</span></div><span class="cm">Delta</span></div></div><div class="bm"><span class="p">archived</span></div><div class="bn"><div class="cn"><span>archived</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Epsilon</span></div><span class="cm">Epsilon</span></div></div><div class="bm"><span class="p">archived</span></div><div class="bn"><div class="cn"><span>archived</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="co"><span class="p">Showing 1-20 of 142 results</span><div class="af"></div><div class="cp"><button class="cq">Prev</button><button class="cr">1</button><button class="cq">2</button><button class="cq">3</button><button class="cq">Next</button></div></div></div></div></div></div></div><footer class="cs"><div class="ct"><div class="cu"><span class="cv">htmlang Dashboard</span><span class="g">Built with htmlang - a declarative HTML language</span><span class="w">Copyright 2025 htmlang contributors</span></div><div class="m"><span class="cw">Product</span><a href="/features" class="cx">Features</a><a href="/pricing" class="cx">Pricing</a><a href="/changelog" class="cx">Changelog</a><a href="/roadmap" class="cx">Roadmap</a></div><div class="m"><span class="cw">Resources</span><a href="/docs" class="cx">Documentation</a><a href="/api" class="cx">API Reference</a><a href="/guides" class="cx">Guides</a><a href="/examples" class="cx">Examples</a></div><div class="m"><span class="cw">Company</span><a href="/about" class="cx">About</a><a href="/blog" class="cx">Blog</a><a href="/careers" class="cx">Careers</a><a href="/contact" class="cx">Contact</a></div><div class="m"><span class="cw">Legal</span><a href="/privacy" class="cx">Privacy Policy</a><a href="/terms" class="cx">Terms of Service</a><a href="/cookies" class="cx">Cookie Policy</a></div></div></footer></div></div></body></html>
1+<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Stress Test Large Document</title><style>@layer hl-reset,htmlang;@layer hl-reset{*,*::before,*::after{box-sizing:border-box}body{margin:0;font-family:system-ui,-apple-system,sans-serif}img{display:block}a{text-decoration:none;color:inherit}a:focus-visible,button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid currentColor;outline-offset:2px}}:root{--primary:#3b82f6;--secondary:#10b981;--accent:#f59e0b;--danger:#ef4444;--dark:#1e293b;--light:#f8fafc;--gray:#64748b;--border:#e2e8f0;--radius:8;--gap:16;--gap-sm:8;--gap-lg:24;}@layer htmlang{.a{display:flex;flex-direction:row;gap:0;min-height:100vh;}.b{display:flex;flex-direction:column;width:260px;background:white;border-right:1px solid var(--border);padding:16px;gap:8px;}.c{display:flex;flex-direction:row;gap:12px;padding:8px;align-items:center;}.d{flex-direction:column;width:36px;height:36px;border-radius:50%;background:var(--primary);color:white;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;}.e{display:flex;flex-direction:column;gap:2px;}.f{font-weight:bold;font-size:16px;color:var(--dark);}.g{font-size:12px;color:var(--gray);}.h{display:flex;flex-direction:column;width:100%;height:1px;background:var(--border);margin:8px 0;}.i{display:flex;flex-direction:column;margin-bottom:24px;}.j{font-weight:bold;font-size:12px;color:var(--gray);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px;}.k{color:var(--primary);font-weight:bold;padding:8px 12px;background:#eff6ff;border-radius:6px;}.l{color:var(--gray);padding:8px 12px;border-radius:6px;transition:all 0.15s;}.m{display:flex;flex-direction:column;gap:4px;}.n{display:flex;flex-direction:row;gap:8px;align-items:center;}.o{display:flex;flex-direction:column;width:6px;height:6px;border-radius:50%;background:var(--secondary);}.p{font-size:13px;color:var(--gray);}.q{display:flex;flex-direction:column;width:6px;height:6px;border-radius:50%;background:var(--accent);}.r{display:flex;flex-direction:column;width:6px;height:6px;border-radius:50%;background:#cbd5e1;}.s{display:flex;flex-direction:column;margin-top:auto;padding:12px;background:#f1f5f9;border-radius:8px;}.t{flex-direction:column;width:28px;height:28px;border-radius:50%;background:var(--primary);color:white;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;}.u{display:flex;flex-direction:column;gap:0;}.v{font-size:13px;font-weight:bold;color:var(--dark);}.w{font-size:11px;color:var(--gray);}.x{display:flex;flex-direction:column;flex:1;min-width:0;background:var(--light);}.y{display:flex;flex-direction:column;padding:12px 24px;background:white;border-bottom:1px solid var(--border);}.z{display:flex;flex-direction:row;gap:16px;align-items:center;}.aa{display:flex;flex-direction:row;gap:8px;}.ab{color:var(--gray);font-size:14px;}.ac{color:var(--border);font-size:14px;}.ad{color:var(--dark);font-size:14px;}.ae{font-weight:bold;font-size:22px;color:var(--dark);}.af{display:flex;flex-direction:column;flex:1;min-width:0;}.ag{display:flex;flex-direction:row;gap:12px;align-items:center;}.ah{padding:8px 12px;border-radius:6px;border:1px solid var(--border);width:240px;transition:all 0.2s;}.ai{display:flex;flex-direction:column;position:relative;}.aj{padding:8px 16px;background:var(--primary);color:white;border-radius:6px;border:none;cursor:pointer;transition:all 0.2s;font-size:14px;}.ak{display:flex;flex-direction:column;position:absolute;top:-2px;right:-2px;}.al{display:flex;flex-direction:column;width:8px;height:8px;border-radius:50%;background:var(--danger);}.am{padding:8px 16px;background:var(--gray);color:white;border-radius:6px;border:none;cursor:pointer;transition:all 0.2s;font-size:14px;}.an{display:flex;flex-direction:column;padding:24px;overflow-y:auto;}.ao{display:flex;flex-direction:column;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;padding:12px 16px;}.ap{color:#1e40af;font-size:14px;}.aq,.av{display:flex;flex-direction:row;gap:16px;margin-top:24px;}.ar{display:flex;flex-direction:column;background:white;border-radius:8px;padding:16px;border:1px solid var(--border);}.as{font-size:14px;color:var(--gray);}.at{font-weight:bold;font-size:28px;color:var(--dark);margin-top:4px;}.au{font-size:12px;color:var(--secondary);margin-top:2px;}.aw{display:flex;flex-direction:column;background:white;border-radius:8px;padding:16px;border:1px solid var(--border);transition:all 0.2s;animation:fade-in 0.3s ease;}.ax{font-weight:bold;font-size:18px;color:var(--dark);}.ay{display:flex;flex-direction:column;margin-top:8px;}.az{flex-direction:column;height:200px;background:var(--light);border-radius:6px;margin-top:12px;display:flex;align-items:flex-end;padding:16px;gap:8px;}.a0{display:flex;flex-direction:column;width:100%;height:40%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.a1{display:flex;flex-direction:column;width:100%;height:65%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.a2{display:flex;flex-direction:column;width:100%;height:45%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.a3{display:flex;flex-direction:column;width:100%;height:80%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.a4{display:flex;flex-direction:column;width:100%;height:55%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.a5{display:flex;flex-direction:column;width:100%;height:70%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.a6{display:flex;flex-direction:column;width:100%;height:90%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.a7{display:flex;flex-direction:column;width:100%;height:60%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.a8{display:flex;flex-direction:column;width:100%;height:75%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.a9{display:flex;flex-direction:column;width:100%;height:85%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.ba{display:flex;flex-direction:column;width:100%;height:50%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.bb{display:flex;flex-direction:column;width:100%;height:95%;background:var(--primary);border-radius:4px 4px 0 0;transition:all 0.2s;min-width:20px;}.bc{display:flex;flex-direction:column;gap:12px;margin-top:12px;}.bd{display:flex;flex-direction:row;gap:12px;align-items:center;padding:8px;border-radius:6px;}.be{flex-direction:column;width:32px;height:32px;border-radius:50%;background:var(--primary);color:white;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;}.bf{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0;}.bg{font-size:14px;font-weight:bold;color:var(--dark);}.bh{display:flex;flex-direction:column;flex:1;min-width:0;gap:16px;}.bi{display:flex;flex-direction:column;gap:0;margin-top:12px;}.bj{display:flex;flex-direction:row;gap:0;padding:8px 16px;background:var(--light);border-bottom:1px solid var(--border);}.bk{display:flex;flex-direction:column;width:200px;}.bl{font-weight:bold;font-size:12px;color:var(--gray);text-transform:uppercase;}.bm{display:flex;flex-direction:column;width:150px;}.bn{display:flex;flex-direction:column;width:120px;}.bo{display:flex;flex-direction:row;gap:0;padding:12px 16px;border-bottom:1px solid var(--border);}.bp{font-size:14px;}.bq{display:flex;flex-direction:row;gap:0;}.br{font-size:13px;color:var(--dark);flex:1;min-width:0;}.bs{display:flex;flex-direction:column;width:100%;height:8px;background:var(--border);border-radius:4px;overflow:hidden;}.bt{display:flex;flex-direction:column;width:67%;height:100%;background:var(--primary);border-radius:4px;}.bu{display:flex;flex-direction:column;gap:8px;margin-top:12px;}.bv{display:flex;flex-direction:row;gap:12px;padding:8px 12px;align-items:center;}.bw{flex-direction:column;width:24px;height:24px;border-radius:50%;background:#eff6ff;display:flex;align-items:center;justify-content:center;}.bx{font-size:10px;color:var(--primary);font-weight:bold;}.by{font-size:14px;color:var(--dark);flex:1;min-width:0;}.bz{display:flex;flex-direction:column;width:340px;gap:16px;}.b0{padding:8px 16px;background:var(--secondary);color:white;border-radius:6px;border:none;cursor:pointer;transition:all 0.2s;font-size:14px;}.b1{padding:8px 16px;background:var(--accent);color:white;border-radius:6px;border:none;cursor:pointer;transition:all 0.2s;font-size:14px;}.b2{padding:8px 16px;background:var(--gray);color:white;border-radius:6px;border:none;cursor:pointer;transition:all 0.2s;font-size:14px;}.b3{display:flex;flex-direction:row;gap:10px;align-items:center;padding:6px 8px;border-radius:6px;transition:all 0.15s;}.b4{display:flex;flex-direction:column;gap:0;flex:1;min-width:0;}.b5{display:flex;flex-direction:column;width:8px;height:8px;border-radius:50%;background:var(--secondary);}.b6{display:flex;flex-direction:row;gap:0;flex-wrap:wrap;margin-top:8px;}.b7{flex-direction:column;display:inline-block;padding:4px 10px;background:#f1f5f9;color:var(--gray);border-radius:4px;font-size:12px;margin:2px;}.b8{display:flex;flex-direction:row;gap:10px;padding:10px 12px;border-bottom:1px solid var(--border);align-items:center;}.b9{display:flex;flex-direction:row;gap:0;align-items:center;}.ca{display:flex;flex-direction:column;width:40px;height:22px;border-radius:11px;background:var(--primary);padding:2px;cursor:pointer;transition:all 0.2s;}.cb{display:flex;flex-direction:column;width:18px;height:18px;border-radius:50%;background:white;margin-left:18px;transition:all 0.2s;}.cc{display:flex;flex-direction:column;width:40px;height:22px;border-radius:11px;background:#cbd5e1;padding:2px;cursor:pointer;transition:all 0.2s;}.cd{display:flex;flex-direction:column;width:18px;height:18px;border-radius:50%;background:white;transition:all 0.2s;}.ce{display:flex;flex-direction:column;margin-top:24px;}.cf{display:flex;flex-direction:row;gap:16px;margin-bottom:12px;align-items:center;}.cg{padding:8px 12px;border-radius:6px;border:1px solid var(--border);width:240px;}.ch{padding:6px 12px;border-radius:4px;border:1px solid var(--border);background:white;color:var(--gray);font-size:13px;cursor:pointer;transition:all 0.15s;}.ci{display:flex;flex-direction:row;gap:0;padding:8px 16px;background:var(--light);border-bottom:2px solid var(--border);border-radius:6px 6px 0 0;}.cj{display:flex;flex-direction:column;width:60px;}.ck{display:flex;flex-direction:row;gap:0;padding:10px 16px;border-bottom:1px solid var(--border);cursor:pointer;transition:all 0.1s;}.cl{flex-direction:column;width:24px;height:24px;border-radius:50%;background:var(--primary);color:white;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;}.cm{font-size:14px;color:var(--dark);}.cn{flex-direction:column;display:inline-block;padding:2px 8px;background:var(--secondary);color:white;border-radius:12px;font-size:12px;}.co{display:flex;flex-direction:row;gap:16px;padding:12px 16px;align-items:center;border-top:1px solid var(--border);}.cp{display:flex;flex-direction:row;gap:4px;}.cq{padding:6px 10px;border-radius:4px;border:1px solid var(--border);background:white;color:var(--gray);font-size:13px;cursor:pointer;}.cr{padding:6px 10px;border-radius:4px;border:1px solid var(--primary);background:var(--primary);color:white;font-size:13px;cursor:pointer;}.cs{display:flex;flex-direction:column;padding:24px 24px;background:white;border-top:1px solid var(--border);}.ct{display:flex;flex-direction:row;gap:24px;}.cu{display:flex;flex-direction:column;gap:8px;flex:1;min-width:0;}.cv{font-weight:bold;font-size:14px;color:var(--dark);}.cw{font-weight:bold;font-size:12px;color:var(--gray);text-transform:uppercase;margin-bottom:4px;}.cx{color:var(--gray);font-size:13px;}.l:hover{color:var(--primary);background:#f1f5f9;}.ab:hover,.cx:hover{color:var(--primary);}.aj:hover,.am:hover,.b0:hover,.b1:hover,.b2:hover{background:var(--dark);}.aw:hover{box-shadow:0 4 12 rgba(0,0,0,0.1);}.a0:hover,.a1:hover,.a2:hover,.a3:hover,.a4:hover,.a5:hover,.a6:hover,.a7:hover,.a8:hover,.a9:hover,.ba:hover,.bb:hover{background:#2563eb;}.bd:hover,.bo:hover,.ck:hover{background:var(--light);}.b3:hover{background:#f1f5f9;}.ch:hover{background:#f1f5f9;color:var(--dark);}.ah:focus{border:1px solid var(--primary);}@media(min-width:640px){.aq,.ct{flex-direction:column;}.bz{flex:1;min-width:0;}}@media(min-width:1024px){.aq,.ct{flex-direction:row;}}@media(prefers-color-scheme:dark){.b{background:#0f172a;border-right:1px solid var(--dark);}.f,.v,.ad,.ae,.at,.ax,.bg,.br,.by,.cm,.cv{color:white;}.h,.bs{background:#334155;}.k,.bw{background:#1e3a5f;}.s,.ch,.cq{background:var(--dark);}.x,.az,.bj,.ci{background:#0f172a;}.y{background:#0f172a;border-bottom:1px solid var(--dark);}.ah{border:1px solid #334155;background:var(--dark);color:white;}.ar,.aw{background:var(--dark);border:1px solid #334155;}.bo,.b8,.ck{border-bottom:1px solid #334155;}.b7{background:#334155;color:#94a3b8;}.cc{background:#475569;}.cs{background:#0f172a;border-top:1px solid var(--dark);}}}@keyframes fade-in {from{opacity:0}to{opacity:1}}@keyframes slide-down {0%{transform:translateY(-10px);opacity:0}100%{transform:translateY(0);opacity:1}}</style></head><body><div class="a"><div class="b"><div class="c"><div class="d"><span>HL</span></div><div class="e"><span class="f">htmlang</span><span class="g">Dashboard v2.0</span></div></div><div class="h"></div><div class="i"><span class="j">Navigation</span><div class="e"><a href="/dashboard" class="k">Dashboard</a><a href="/analytics" class="l">Analytics</a><a href="/projects" class="l">Projects</a><a href="/team" class="l">Team</a><a href="/messages" class="l">Messages</a><a href="/calendar" class="l">Calendar</a><a href="/documents" class="l">Documents</a><a href="/settings" class="l">Settings</a></div></div><div class="i"><span class="j">Favorites</span><div class="e"><a href="/project/alpha" class="l">Project Alpha</a><a href="/project/beta" class="l">Project Beta</a><a href="/reports/q4" class="l">Q4 Reports</a></div></div><div class="i"><span class="j">Shortcuts</span><div class="m"><div class="n"><div class="o"></div><span class="p">Online (12)</span></div><div class="n"><div class="q"></div><span class="p">Away (3)</span></div><div class="n"><div class="r"></div><span class="p">Offline (5)</span></div></div></div><div class="s"><div class="n"><div class="t"><span>JD</span></div><div class="u"><span class="v">Jane Doe</span><span class="w">jane@example.com</span></div></div></div></div><div class="x"><header class="y"><div class="z"><div class="e"><div class="aa"><div class="aa"><a href="/" class="ab">Home</a><span class="ac">/</span></div><span class="ad">Dashboard</span></div><span class="ae">Dashboard Overview</span></div><div class="af"></div><div class="ag"><input class="ah" type="search" placeholder="Search..."><div class="ai"><button class="aj">Alerts</button><div class="ak"><div class="al"></div></div></div><button class="am">Settings</button></div></div></header><div class="an"><div class="ao"><span class="ap">Welcome back! You have 3 new notifications and 2 pending tasks.</span></div><div class="aq"><div class="ar"><span class="as">Total Revenue</span><span class="at">$48</span><span class="au">+12.5% from last month</span></div><div class="ar"><span class="as">Active Users</span><span class="at">2</span><span class="au">+8.2% from last week</span></div><div class="ar"><span class="as">Conversion Rate</span><span class="at">3.24%</span><span class="au">+0.4% from yesterday</span></div><div class="ar"><span class="as">Open Tickets</span><span class="at">142</span><span class="au">-5.1% from last month</span></div></div><div class="av"><div class="aw"><span class="ax">Revenue Overview</span><div class="ay"><div class="az"><div class="a0"></div><div class="a1"></div><div class="a2"></div><div class="a3"></div><div class="a4"></div><div class="a5"></div><div class="a6"></div><div class="a7"></div><div class="a8"></div><div class="a9"></div><div class="ba"></div><div class="bb"></div></div></div></div><div class="aw"><span class="ax">Activity Feed</span><div class="ay"><div class="bc"><div class="bd"><div class="be"><span>Alice</span></div><div class="bf"><span class="bg">Alice</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Bob</span></div><div class="bf"><span class="bg">Bob</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Carol</span></div><div class="bf"><span class="bg">Carol</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Dave</span></div><div class="bf"><span class="bg">Dave</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Eve</span></div><div class="bf"><span class="bg">Eve</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Frank</span></div><div class="bf"><span class="bg">Frank</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Grace</span></div><div class="bf"><span class="bg">Grace</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div><div class="bd"><div class="be"><span>Hank</span></div><div class="bf"><span class="bg">Hank</span><span class="g">Updated project files</span></div><span class="w">2h ago</span></div></div></div></div></div><div class="aq"><div class="bh"><div class="aw"><span class="ax">Project Status</span><div class="ay"><div class="bi"><div class="bj"><div class="bk"><span class="bl">Project</span></div><div class="bm"><span class="bl">Status</span></div><div class="bn"><span class="bl">Progress</span></div><div class="af"><span class="bl">Due Date</span></div></div><div class="bo"><div class="bk"><span class="bp">Project Alpha</span></div><div class="bm"><span class="as">In Progress</span></div><div class="bn"><span class="bp">75%</span></div><div class="af"><span class="as">Dec 15 2025</span></div></div><div class="bo"><div class="bk"><span class="bp">Project Beta</span></div><div class="bm"><span class="as">Review</span></div><div class="bn"><span class="bp">90%</span></div><div class="af"><span class="as">Nov 30 2025</span></div></div><div class="bo"><div class="bk"><span class="bp">Project Gamma</span></div><div class="bm"><span class="as">Planning</span></div><div class="bn"><span class="bp">20%</span></div><div class="af"><span class="as">Jan 20 2026</span></div></div><div class="bo"><div class="bk"><span class="bp">Project Delta</span></div><div class="bm"><span class="as">Completed</span></div><div class="bn"><span class="bp">100%</span></div><div class="af"><span class="as">Oct 10 2025</span></div></div><div class="bo"><div class="bk"><span class="bp">Project Epsilon</span></div><div class="bm"><span class="as">On Hold</span></div><div class="bn"><span class="bp">45%</span></div><div class="af"><span class="as">Feb 28 2026</span></div></div></div></div></div><div class="aw"><span class="ax">Progress Breakdown</span><div class="ay"><div class="bc"><div class="m"><div class="bq"><span class="br">Alpha</span><span class="p">67%</span></div><div class="bs"><div class="bt"></div></div></div><div class="m"><div class="bq"><span class="br">Beta</span><span class="p">67%</span></div><div class="bs"><div class="bt"></div></div></div><div class="m"><div class="bq"><span class="br">Gamma</span><span class="p">67%</span></div><div class="bs"><div class="bt"></div></div></div><div class="m"><div class="bq"><span class="br">Delta</span><span class="p">67%</span></div><div class="bs"><div class="bt"></div></div></div><div class="m"><div class="bq"><span class="br">Epsilon</span><span class="p">67%</span></div><div class="bs"><div class="bt"></div></div></div></div></div></div><div class="aw"><span class="ax">Recent Activity Log</span><div class="ay"><div class="bu"><div class="bv"><div class="bw"><span class="bx">0</span></div><span class="by">Deployed v2.3.1 to production</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">1</span></div><span class="by">Merged feature/auth-redesign</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">2</span></div><span class="by">Closed issue #482: Login timeout</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">3</span></div><span class="by">Updated CI/CD pipeline config</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">4</span></div><span class="by">Added unit tests for payment module</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">5</span></div><span class="by">Reviewed PR #156: API refactor</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">6</span></div><span class="by">Fixed memory leak in worker pool</span><span class="w">just now</span></div><div class="bv"><div class="bw"><span class="bx">7</span></div><span class="by">Released hotfix 2.3.2</span><span class="w">just now</span></div></div></div></div></div><div class="bz"><div class="aw"><span class="ax">Quick Actions</span><div class="ay"><div class="bu"><button class="aj">New Project</button><button class="b0">Create Report</button><button class="b1">Invite Member</button><button class="b2">View Backlog</button></div></div></div><div class="aw"><span class="ax">Team Members</span><div class="ay"><div class="bu"><div class="b3"><div class="t"><span>Alice</span></div><div class="b4"><span class="v">Alice</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Bob</span></div><div class="b4"><span class="v">Bob</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Carol</span></div><div class="b4"><span class="v">Carol</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Dave</span></div><div class="b4"><span class="v">Dave</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Eve</span></div><div class="b4"><span class="v">Eve</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Frank</span></div><div class="b4"><span class="v">Frank</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Grace</span></div><div class="b4"><span class="v">Grace</span><span class="w">Developer</span></div><div class="b5"></div></div><div class="b3"><div class="t"><span>Hank</span></div><div class="b4"><span class="v">Hank</span><span class="w">Developer</span></div><div class="b5"></div></div></div></div></div><div class="aw"><span class="ax">Tags</span><div class="ay"><div class="b6"><div class="b7"><span>frontend</span></div><div class="b7"><span>backend</span></div><div class="b7"><span>devops</span></div><div class="b7"><span>design</span></div><div class="b7"><span>mobile</span></div><div class="b7"><span>security</span></div><div class="b7"><span>performance</span></div><div class="b7"><span>testing</span></div><div class="b7"><span>docs</span></div><div class="b7"><span>infra</span></div><div class="b7"><span>ux</span></div><div class="b7"><span>api</span></div></div></div></div><div class="aw"><span class="ax">Notifications</span><div class="ay"><div class="bi"><div class="b8"><div class="al"></div><span class="br">New deployment completed</span><span class="w">now</span></div><div class="b8"><div class="al"></div><span class="br">PR review requested</span><span class="w">now</span></div><div class="b8"><div class="al"></div><span class="br">Build failed on staging</span><span class="w">now</span></div><div class="b8"><div class="al"></div><span class="br">Meeting in 30 minutes</span><span class="w">now</span></div><div class="b8"><div class="al"></div><span class="br">Subscription renewal due</span><span class="w">now</span></div></div></div></div><div class="aw"><span class="ax">Settings</span><div class="ay"><div class="bc"><div class="b9"><span class="by">Dark Mode</span><div class="ca"><div class="cb"></div></div></div><div class="b9"><span class="by">Notifications</span><div class="ca"><div class="cb"></div></div></div><div class="b9"><span class="by">Auto-save</span><div class="cc"><div class="cd"></div></div></div><div class="b9"><span class="by">Compact View</span><div class="cc"><div class="cd"></div></div></div></div></div></div></div></div><div class="ce"><div class="aw"><span class="ax">Data Table</span><div class="ay"><div class="bi"><div class="cf"><input class="cg" type="search" placeholder="Filter results..."><div class="af"></div><div class="aa"><button class="ch">all</button><button class="ch">design</button><button class="ch">development</button><button class="ch">marketing</button><button class="ch">analytics</button></div></div><div class="ci"><div class="cj"><span class="bl">ID</span></div><div class="bk"><span class="bl">Name</span></div><div class="bm"><span class="bl">Category</span></div><div class="bn"><span class="bl">Status</span></div><div class="af"><span class="bl">Updated</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Alpha</span></div><span class="cm">Alpha</span></div></div><div class="bm"><span class="p">active</span></div><div class="bn"><div class="cn"><span>active</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Beta</span></div><span class="cm">Beta</span></div></div><div class="bm"><span class="p">active</span></div><div class="bn"><div class="cn"><span>active</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Gamma</span></div><span class="cm">Gamma</span></div></div><div class="bm"><span class="p">active</span></div><div class="bn"><div class="cn"><span>active</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Delta</span></div><span class="cm">Delta</span></div></div><div class="bm"><span class="p">active</span></div><div class="bn"><div class="cn"><span>active</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Epsilon</span></div><span class="cm">Epsilon</span></div></div><div class="bm"><span class="p">active</span></div><div class="bn"><div class="cn"><span>active</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Alpha</span></div><span class="cm">Alpha</span></div></div><div class="bm"><span class="p">pending</span></div><div class="bn"><div class="cn"><span>pending</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Beta</span></div><span class="cm">Beta</span></div></div><div class="bm"><span class="p">pending</span></div><div class="bn"><div class="cn"><span>pending</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Gamma</span></div><span class="cm">Gamma</span></div></div><div class="bm"><span class="p">pending</span></div><div class="bn"><div class="cn"><span>pending</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Delta</span></div><span class="cm">Delta</span></div></div><div class="bm"><span class="p">pending</span></div><div class="bn"><div class="cn"><span>pending</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Epsilon</span></div><span class="cm">Epsilon</span></div></div><div class="bm"><span class="p">pending</span></div><div class="bn"><div class="cn"><span>pending</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Alpha</span></div><span class="cm">Alpha</span></div></div><div class="bm"><span class="p">completed</span></div><div class="bn"><div class="cn"><span>completed</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Beta</span></div><span class="cm">Beta</span></div></div><div class="bm"><span class="p">completed</span></div><div class="bn"><div class="cn"><span>completed</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Gamma</span></div><span class="cm">Gamma</span></div></div><div class="bm"><span class="p">completed</span></div><div class="bn"><div class="cn"><span>completed</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Delta</span></div><span class="cm">Delta</span></div></div><div class="bm"><span class="p">completed</span></div><div class="bn"><div class="cn"><span>completed</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Epsilon</span></div><span class="cm">Epsilon</span></div></div><div class="bm"><span class="p">completed</span></div><div class="bn"><div class="cn"><span>completed</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Alpha</span></div><span class="cm">Alpha</span></div></div><div class="bm"><span class="p">archived</span></div><div class="bn"><div class="cn"><span>archived</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Beta</span></div><span class="cm">Beta</span></div></div><div class="bm"><span class="p">archived</span></div><div class="bn"><div class="cn"><span>archived</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Gamma</span></div><span class="cm">Gamma</span></div></div><div class="bm"><span class="p">archived</span></div><div class="bn"><div class="cn"><span>archived</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Delta</span></div><span class="cm">Delta</span></div></div><div class="bm"><span class="p">archived</span></div><div class="bn"><div class="cn"><span>archived</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="ck"><div class="cj"><span class="p">#42</span></div><div class="bk"><div class="n"><div class="cl"><span>Epsilon</span></div><span class="cm">Epsilon</span></div></div><div class="bm"><span class="p">archived</span></div><div class="bn"><div class="cn"><span>archived</span></div></div><div class="af"><span class="p">2 hours ago</span></div></div><div class="co"><span class="p">Showing 1-20 of 142 results</span><div class="af"></div><div class="cp"><button class="cq">Prev</button><button class="cr">1</button><button class="cq">2</button><button class="cq">3</button><button class="cq">Next</button></div></div></div></div></div></div></div><footer class="cs"><div class="ct"><div class="cu"><span class="cv">htmlang Dashboard</span><span class="g">Built with htmlang - a declarative HTML language</span><span class="w">Copyright 2025 htmlang contributors</span></div><div class="m"><span class="cw">Product</span><a href="/features" class="cx">Features</a><a href="/pricing" class="cx">Pricing</a><a href="/changelog" class="cx">Changelog</a><a href="/roadmap" class="cx">Roadmap</a></div><div class="m"><span class="cw">Resources</span><a href="/docs" class="cx">Documentation</a><a href="/api" class="cx">API Reference</a><a href="/guides" class="cx">Guides</a><a href="/examples" class="cx">Examples</a></div><div class="m"><span class="cw">Company</span><a href="/about" class="cx">About</a><a href="/blog" class="cx">Blog</a><a href="/careers" class="cx">Careers</a><a href="/contact" class="cx">Contact</a></div><div class="m"><span class="cw">Legal</span><a href="/privacy" class="cx">Privacy Policy</a><a href="/terms" class="cx">Terms of Service</a><a href="/cookies" class="cx">Cookie Policy</a></div></div></footer></div></div></body></html>
tests/snapshots/variables_defines.hl+2−2
@@ -1,10 +1,10 @@
1@page Variables
1@page Variables
2@let primary #3b82f6
2@let primary #3b82f6
3@let gap 20
3@let gap 20
4−@let card-style [padding 20, rounded 8, background white]
4+@let card-style [padding 20, border-radius 8, background white]
5
5
6@column [spacing $gap]
6@column [spacing $gap]
7 @el [$card-style]
7 @el [$card-style]
8 @text [color $primary] Hello
8 @text [color $primary] Hello
9− @el [$card-style, border 1 $primary]
9+ @el [$card-style, border 1 solid $primary]
10 @text World
10 @text World
tests/snapshots/view_transitions.hl+1−1
@@ -1,6 +1,6 @@
1@page View Transitions
1@page View Transitions
2@column [spacing 20, padding 20]
2@column [spacing 20, padding 20]
3− @el [view-transition-name hero, padding 20, background #3b82f6, color white, rounded 8]
3+ @el [view-transition-name hero, padding 20, background #3b82f6, color white, border-radius 8]
4 Hero element
4 Hero element
5 @el [view-transition-name sidebar, padding 10]
5 @el [view-transition-name sidebar, padding 10]
6 Sidebar element
6 Sidebar element