on master help sign in
htmlang's own attributes are layout; everything else is CSS

Drop the style aliases that duplicated CSS: bold, italic, underline, hidden, size, font, rounded, padding-x/y, margin-x/y, and the solid implied by border N COLOR. Each now reports its CSS replacement, and htmlang upgrade rewrites them (inside if() too). Border shorthands and outline get px for bare widths like other lengths, and font-family keeps unquoting a font stack. This also fixes border none and multi-value border radii, which produced invalid CSS. The upgrade no longer mistakes a @let inside a verbatim body for a user function.

commit81f89d28ea0d2e8ff7e8ef01885e372f2aabd306 authoriko <mail@iko.soy> dateSat 26 Sep 2026, 03:19 (2 weeks ago) parentf15f89b
63 files changed+629−598
DESIGN.md+27−29 README.md+6−6 crates/htmlang-core/src/codegen.rs+12−89 crates/htmlang-core/src/codegen_tests.rs+1−1 crates/htmlang-core/src/parser.rs+12−4 crates/htmlang-core/src/std.hl+1−1 crates/htmlang-core/src/vocab.rs+6−5 editors/vscode/snippets/htmlang.json+13−13 examples/blog.hl+7−7 examples/demo.hl+64−64 examples/demo.html+1−1 examples/docs.hl+17−17 examples/landing.hl+15−15 src/bin/htmlang_lsp/completion.rs+2−2 src/bin/htmlang_lsp/docs.rs+1−13 src/fmt.rs+7−7 src/upgrade.rs+105−4 tests/regressions.rs+21−9 tests/snapshots.rs+14−14 tests/snapshots/attributes.hl+2−2 tests/snapshots/basic_elements.hl+1−1 tests/snapshots/chain_operator.hl+1−1 tests/snapshots/clamp_css.hl+1−1 tests/snapshots/color_scheme.hl+1−1 tests/snapshots/compat_test.hl+1−1 tests/snapshots/computed_let.hl+2−2 tests/snapshots/css_shorthands.hl+3−3 tests/snapshots/css_units.hl+2−2 tests/snapshots/css_vars.hl+2−2 tests/snapshots/dark_mode.hl+2−2 tests/snapshots/data_directive.hl+1−1 tests/snapshots/each_destructuring.hl+1−1 tests/snapshots/each_step.hl+2−2 tests/snapshots/extra_css.hl+1−1 tests/snapshots/fn_defaults.hl+1−1 tests/snapshots/functions.hl+2−2 tests/snapshots/has_pseudo.hl+1−1 tests/snapshots/hidden_attr.hl+1−1 tests/snapshots/inline_text.hl+2−2 tests/snapshots/list_elements.hl+2−2 tests/snapshots/mixin_spread.hl+2−2 tests/snapshots/named_slots.hl+1−1 tests/snapshots/new_css.hl+1−1 tests/snapshots/new_css_properties_3.hl+1−1 tests/snapshots/new_css_properties_4.hl+1−1 tests/snapshots/new_directives.hl+1−1 tests/snapshots/new_elements_2.hl+1−1 tests/snapshots/new_elements_4.hl+2−2 tests/snapshots/new_elements_5.hl+2−2 tests/snapshots/new_elements_6.hl+1−1 tests/snapshots/new_pseudos.hl+2−2 tests/snapshots/no_page.hl+1−1 tests/snapshots/popover_api.hl+1−1 tests/snapshots/pseudo_states_extended.hl+1−1 tests/snapshots/pseudo_states_extended.html+1−1 tests/snapshots/repeat_directive.hl+1−1 tests/snapshots/responsive.hl+1−1 tests/snapshots/semantic_elements.hl+4−4 tests/snapshots/stress_deeply_nested.hl+114−114 tests/snapshots/stress_large.hl+122−122 tests/snapshots/stress_large.html+1−1 tests/snapshots/variables_defines.hl+2−2 tests/snapshots/view_transitions.hl+1−1
DESIGN.md+27−29
@@ -29,7 +29,7 @@ Children are indented under their parent. Attributes are comma-separated inside
2929@el [
3030 padding 20,
3131 background white,
32− rounded 8,
32+ border-radius 8,
3333 box-shadow 0 2px 4px rgba(0,0,0,0.1)
3434]
3535 Content
@@ -39,7 +39,7 @@ An attribute is either a **style**, written `key value` (`padding 20`,
3939`color red`), or an **HTML attribute**, written `key=value` (`id=main`,
4040`type=email`, `aria-label=Close`). Boolean HTML attributes are written bare
4141(`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"`.
4343
4444### Text
4545
@@ -48,8 +48,8 @@ attributes is its content, and `{...}` puts elements inside a line of text:
4848
4949```
5050@paragraph
51− This is {@text [bold] important} and this is a {@link https://example.com link}.
52−@text [bold, size 24, color #333] Hello world
51+ This is {@text [font-weight bold] important} and this is a {@link https://example.com link}.
52+@text [font-weight bold, font-size 24, color #333] Hello world
5353@section [padding 8] Text after the attributes is content too.
5454```
5555
@@ -70,7 +70,7 @@ line: `--` later in a line is ordinary text.
7070gets the indented children.
7171
7272```
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
7474 @text [color white] Get Started
7575```
7676
@@ -181,39 +181,34 @@ Use them like elements; your own `@let` with the same name takes precedence.
181181
182182## Attributes
183183
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.
185188
186189| Attribute | Effect |
187190|---|---|
188191| `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`) |
191192| `width fill` / `width N` / `width shrink` | Take remaining space, exact size, or fit content |
192193| `height fill` / `height N` / `height shrink` | Same for height |
193194| `center-x`, `center-y` | Center within the parent |
194195| `align-left`, `align-right`, `align-top`, `align-bottom` | Align within the parent |
195196| `wrap` | Let a row wrap |
196197| `grid-cols N`, `grid-rows N`, `col-span N`, `row-span N` | Grid layout |
197−| `hidden` | `display: none` |
198198
199199### Style
200200
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
211−name and value: `background red`, `opacity 0.5`, `cursor pointer`,
212−`z-index 10`, `margin-top 16`, `grid-template-areas "a b"`, and so on.
201+Everything else is CSS: **any standard CSS property** is an attribute, with
202+the same name and value: `padding 20`, `font-weight bold`, `font-size 18`,
203+`border 1 solid #e5e7eb`, `border-radius 8`, `background red`,
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.
213207
214−**Units.** For lengths, a bare number is pixels (`padding 20` is `20px`).
215−Values with a unit, keywords and CSS functions are passed through: `width 50%`,
216−`margin 0 auto`, `max-width min(100%, 800px)`.
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
210+functions are passed through: `width 50%`, `margin 0 auto`,
211+`max-width min(100%, 800px)`.
217212
218213### HTML attributes
219214
@@ -225,7 +220,7 @@ share a name without clashing:
225220
226221```
227222@image [width=800, width 200, alt=A photo] photo.jpg
228−@select [size=4, size 18]
223+@select [size=4, font-size 18]
229224 @option One
230225```
231226
@@ -262,7 +257,7 @@ attribute out.
262257
263258```
264259@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)]
266261 Conditionally styled
267262```
268263
@@ -278,11 +273,11 @@ attribute out.
278273-- A quoted string, with $variables interpolated
279274@let greeting "Hello $primary"
280275-- 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]
282277-- A function: a @let with an indented body
283278@let panel $title $tone=neutral
284279 @el [$card]
285− @text [bold] $title
280+ @text [font-weight bold] $title
286281 @children
287282
288283@panel [title Welcome]
@@ -307,7 +302,7 @@ A function is called like an element:
307302@let note $kind=info
308303 @style
309304 .title { font-weight: bold; }
310− @el [padding 12, rounded 6, background #eff6ff]
305+ @el [padding 12, border-radius 6, background #eff6ff]
311306 @text [class=title] $kind
312307 @children
313308
@@ -500,6 +495,9 @@ removed form with its replacement.
500495| `$a ~ " " ~ $b` | `"$a $b"` |
501496| `@each $x in LIST [page N]` | Split the list, or filter it with `@if` |
502497| `...$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` |
503501| `@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` |
504502| `$_index` | `@each $item, $i in ...` |
505503| `$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
1111@let primary #3b82f6
1212
1313@let card $title
14− @el [padding 20, background white, rounded 8, border 1 #e5e7eb, hover:border 1 $primary, transition all 0.15s ease]
15− @text [bold] $title
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 [font-weight bold] $title
1616 @children
1717
1818@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
2020
2121 @paragraph
22− Built with {@text [bold, color $primary] htmlang}.
22+ Built with {@text [font-weight bold, color $primary] htmlang}.
2323
2424 @row [wrap, spacing 10]
2525 @card [title Simple]
@@ -97,10 +97,10 @@ Prefixes apply a style conditionally:
9797```
9898@let primary #3b82f6
9999@let gap = 8 * 2
100−@let card [padding 20, rounded 8]
100+@let card [padding 20, border-radius 8]
101101@let button $label
102102 @el [$card, background $primary]
103− @text [color white, bold] $label
103+ @text [color white, font-weight bold] $label
104104 @children
105105
106106@button [label Click me, padding 12]
crates/htmlang-core/src/codegen.rs+12−89
@@ -1736,19 +1736,6 @@ fn attrs_to_css(
17361736 push_css(&mut css, "padding", &css_px_multi(v));
17371737 }
17381738 }
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− }
17521739 "padding-top" => {
17531740 if let Some(v) = val {
17541741 push_css(&mut css, "padding-top", &css_px(v));
@@ -1860,68 +1847,6 @@ fn attrs_to_css(
18601847 _ => push_css(&mut css, "margin-top", "auto"),
18611848 },
18621849
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−
19251850 // Typography
19261851 "line-height" => {
19271852 if let Some(v) = val {
@@ -1956,9 +1881,6 @@ fn attrs_to_css(
19561881 }
19571882 }
19581883
1959− // Display & visibility
1960− "hidden" => push_css(&mut css, "display", "none"),
1961−
19621884 // Effects
19631885
19641886 // Flow
@@ -2132,17 +2054,6 @@ fn attrs_to_css(
21322054 push_css(&mut css, "margin", &css_px_multi(v));
21332055 }
21342056 }
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−
21462057 // Container queries
21472058 "container" => {
21482059 push_css(&mut css, "container-type", "inline-size");
@@ -2253,6 +2164,18 @@ fn attrs_to_css(
22532164
22542165 // Any other standard CSS property is copied through, with `px`
22552166 // 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+
22562179 key if crate::vocab::is_css_property(key) => {
22572180 if let Some(v) = val {
22582181 if crate::vocab::is_length_property(key) {
crates/htmlang-core/src/codegen_tests.rs+1−1
@@ -47,7 +47,7 @@ mod tests {
4747
4848 #[test]
4949 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";
5151 let r1 = parse(src);
5252 let r2 = parse(src);
5353 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> {
15341534 "truncate" => Some("use the `$truncate` bundle"),
15351535 "critical" => Some("remove it"),
15361536 "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`"),
15371547 _ => None,
15381548 }
15391549}
@@ -1730,8 +1740,6 @@ const NUMERIC_ATTRS: &[&str] = &[
17301740 "spacing",
17311741 "gap",
17321742 "padding",
1733− "padding-x",
1734− "padding-y",
17351743 "padding-top",
17361744 "padding-bottom",
17371745 "padding-left",
@@ -1740,8 +1748,8 @@ const NUMERIC_ATTRS: &[&str] = &[
17401748 "max-width",
17411749 "min-height",
17421750 "max-height",
1743− "rounded",
1744− "size",
1751+ "border-radius",
1752+ "font-size",
17451753 "column-gap",
17461754 "row-gap",
17471755 "top",
crates/htmlang-core/src/std.hl+1−1
@@ -15,7 +15,7 @@
1515
1616-- Outlined, fully rounded label.
1717@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]
1919 @children
2020
2121-- Circular frame for an image or initials.
crates/htmlang-core/src/vocab.rs+6−5
@@ -10,11 +10,9 @@
1010/// htmlang's own attributes (those that are neither a CSS property nor an
1111/// HTML attribute).
1212pub const HTMLANG_ATTRIBUTES: &[&str] = &[
13− "align-bottom", "align-left", "align-right", "align-top", "bold",
14− "center-x", "center-y", "col-span", "grid-cols", "grid-rows",
15− "hidden", "inline", "italic", "margin-x", "margin-y", "ordered", "padding-x", "padding-y",
16− "responsive", "rounded", "row-span", "size", "spacing", "underline",
17− "wrap",
13+ "align-bottom", "align-left", "align-right", "align-top", "center-x", "center-y",
14+ "col-span", "grid-cols", "grid-rows", "inline", "ordered", "responsive", "row-span",
15+ "spacing", "wrap",
1816];
1917
2018/// Standard CSS properties, sorted. Any of these can be written as an
@@ -153,6 +151,9 @@ pub fn is_length_property(name: &str) -> bool {
153151 "top", "right", "bottom", "left", "width", "height", "font-size", "block-size",
154152 "inline-size", "min-block-size", "max-block-size", "min-inline-size", "max-inline-size",
155153 "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",
156157 ];
157158 EXACT.contains(&name)
158159 || PREFIXES.iter().any(|p| name.starts_with(p))
editors/vscode/snippets/htmlang.json+13−13
@@ -14,8 +14,8 @@
1414 "prefix": "@let-fn",
1515 "body": [
1616 "@let ${1:component} \\$${2:title}",
17− " @el [${3:padding 20, background white, rounded 8}]",
18− " @text [bold] \\$$2",
17+ " @el [${3:padding 20, background white, border-radius 8}]",
18+ " @text [font-weight bold] \\$$2",
1919 " @children",
2020 "",
2121 "@$1 [$2 ${4:Hello}]",
@@ -29,7 +29,7 @@
2929 "@let ${1:layout} \\$${2:title}",
3030 " @column [${3:max-width 800, center-x, padding 40}]",
3131 " @slot ${4:header}",
32− " @text [bold, size 32] \\$$2",
32+ " @text [font-weight bold, font-size 32] \\$$2",
3333 " @slot ${5:content}",
3434 " @slot ${6:footer}",
3535 "",
@@ -42,8 +42,8 @@
4242 "Card component": {
4343 "prefix": "@card",
4444 "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]",
46− " @text [bold, size ${6:18}] ${7:Title}",
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 [font-weight bold, font-size ${6:18}] ${7:Title}",
4747 " $0"
4848 ],
4949 "description": "Card with border, hover effect, and title"
@@ -63,9 +63,9 @@
6363 "Navigation bar": {
6464 "prefix": "@navbar",
6565 "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}]",
6767 " @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}",
6969 " @el [width fill]",
7070 " @data \\$links [{\"label\": \"${8:Home}\", \"url\": \"/\"}, {\"label\": \"About\", \"url\": \"/about\"}]",
7171 " @each \\$link in \\$links",
@@ -127,8 +127,8 @@
127127 "body": [
128128 "@form [method=${1:post}, spacing ${2:16}] ${3:/submit}",
129129 " @label [for=${4:email}] ${5:Email}",
130− " @input [type=${6:email}, name=$4, id=$4, required, padding 8, border 1 #d1d5db, rounded 4]",
131− " @button [type=submit, padding 10, background ${7:\\$primary}, color white, rounded 4, bold]",
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, border-radius 4, font-weight bold]",
132132 " ${8:Submit}",
133133 " $0"
134134 ],
@@ -146,11 +146,11 @@
146146 "prefix": "@hero",
147147 "body": [
148148 "@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}",
150150 " @paragraph [max-width ${6:600}, text-align center, color ${7:#666}, line-height 1.6]",
151151 " ${8:Hero description text}",
152− " @el [padding 12 24, background ${9:\\$primary}, rounded 8] > @link ${10:#}",
153− " @text [color white, bold] ${11:Get Started}",
152+ " @el [padding 12 24, background ${9:\\$primary}, border-radius 8] > @link ${10:#}",
153+ " @text [color white, font-weight bold] ${11:Get Started}",
154154 " $0"
155155 ],
156156 "description": "Hero section with title, description, and CTA"
@@ -237,7 +237,7 @@
237237 "prefix": "@address",
238238 "body": [
239239 "@address",
240− " @text [bold] ${1:Name}",
240+ " @text [font-weight bold] ${1:Name}",
241241 " @link mailto:${2:email@example.com} $2",
242242 " $0"
243243 ],
examples/blog.hl+7−7
@@ -7,24 +7,24 @@
77@let card [
88 padding 24,
99 background white,
10− rounded 10,
11− border 1 #e5e7eb,
10+ border-radius 10,
11+ border 1 solid #e5e7eb,
1212 box-shadow 0 1px 3px rgba(0,0,0,0.05),
1313 transition all 0.15s ease
1414]
1515
1616@let post $title $date $excerpt
1717 @article [$card, spacing 8]
18− @text [bold, size 20, color $ink] $title
19− @text [size 13, color $muted] $date
18+ @text [font-weight bold, font-size 20, color $ink] $title
19+ @text [font-size 13, color $muted] $date
2020 @paragraph [color $ink, line-height 1.6]
2121 $excerpt
22− @link [color $primary, bold] /posts/$title
22+ @link [color $primary, font-weight bold] /posts/$title
2323 Read more
2424
2525@column [max-width 720, center-x, padding 40, spacing 32]
2626 @header [spacing 8]
27− @text [bold, size 36, color $ink] My Blog
27+ @text [font-weight bold, font-size 36, color $ink] My Blog
2828 @text [color $muted] Thoughts on static HTML, layout, and nice defaults.
2929
3030 @main [spacing 20]
@@ -44,5 +44,5 @@
4444 excerpt "Dev-loop ergonomics beat feature counts. Here's the setup I use."
4545 ]
4646
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]
4848 Built with htmlang.
examples/demo.hl+64−64
@@ -24,8 +24,8 @@
2424@let card [
2525 padding $gap,
2626 background white,
27− rounded $base,
28− border 1 #e5e7eb,
27+ border-radius $base,
28+ border 1 solid #e5e7eb,
2929 box-shadow 0 1px 3px rgba(0,0,0,0.05)
3030]
3131
@@ -51,14 +51,14 @@
5151@let card $title $icon=◆
5252 @article [$card, $interactive, animation fade-up 0.3s ease]
5353 @row [spacing 10, align-items center]
54− @text [size 20, color $primary] $icon
55− @text [bold, size 16, color $ink] $title
54+ @text [font-size 20, color $primary] $icon
55+ @text [font-weight bold, font-size 16, color $ink] $title
5656 @children
5757
5858-- Component that wraps children in a titled section (function + slot)
5959@let titled $title
60− @section [spacing 12, padding-y $base]
61− @text [bold, size 22, color $ink] $title
60+ @section [spacing 12, padding-block $base]
61+ @text [font-weight bold, font-size 22, color $ink] $title
6262 @children
6363
6464-- Top-level layout
@@ -74,7 +74,7 @@
7474
7575 -- Header: nav + hero
7676 @header [spacing $base]
77− @nav [spacing 6, color $muted, size 13]
77+ @nav [spacing 6, color $muted, font-size 13]
7878 @row [spacing 6, align-items center]
7979 @link [color $muted] / Home
8080 @text /
@@ -84,14 +84,14 @@
8484
8585 @row [spacing $gap, align-items center]
8686 @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
8888 @paragraph [line-height 1.6, color $muted]
8989 A minimalist layout language that compiles to
90− {@text [bold, color $primary] static HTML} — no JS, no build step.
91− Visit the {@link [color $primary, underline] https://github.com repo} for more.
92− @image [width 72, height 72, rounded 36, alt="Avatar"] https://picsum.photos/72
90+ {@text [font-weight bold, color $primary] static HTML} — no JS, no build step.
91+ Visit the {@link [color $primary, text-decoration underline] https://github.com repo} for more.
92+ @image [width 72, height 72, border-radius 36, alt="Avatar"] https://picsum.photos/72
9393
94− @hr [border-top 1 #e5e7eb]
94+ @hr [border-top 1 solid #e5e7eb]
9595
9696 -- Feature grid using @card + @titled components
9797 @titled [title Features]
@@ -108,23 +108,23 @@
108108 -- Typography showcase: inline elements, blockquote, code
109109 @titled [title Typography]
110110 @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}.
112112 Press {@kbd [
113− padding 2 6, background #f1f5f9, rounded 4,
114− font monospace, size 13
113+ padding 2 6, background #f1f5f9, border-radius 4,
114+ font-family monospace, font-size 13
115115 ] Ctrl+K} to search. {@mark [background #fef08a] Highlighted} uses mark.
116116 {@abbr [title=HyperText Markup Language + language] htmlang} is an abbreviation.
117117
118118 @blockquote [
119− padding-inline-start $gap, border-left 3 $primary,
120− color $muted, italic
119+ padding-inline-start $gap, border-left 3 solid $primary,
120+ color $muted, font-style italic
121121 ]
122122 Layout is code. Style is code. Everything should compose.
123123 @cite — Anonymous
124124
125125 @pre [
126126 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,
128128 overflow-x auto
129129 ]
130130 @raw
@@ -142,7 +142,7 @@
142142 ]
143143 @each $person in $team
144144 @row [spacing 10, align-items baseline]
145− @text [bold, min-width 80] $person.name
145+ @text [font-weight bold, min-width 80] $person.name
146146 @text [color $muted] $person.role
147147
148148 -- @each ... @else fallback for empty list
@@ -150,40 +150,40 @@
150150 @each $x in $nothing
151151 @text $x
152152 @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)
154154
155155 -- repeat N times
156156 @row [spacing 6]
157157 @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]
159159
160160 -- numeric range
161161 @row [spacing 4]
162162 @each $i in 1..6
163163 @el [
164− width 24, height 24, rounded 4,
164+ width 24, height 24, border-radius 4,
165165 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
167167 ]
168168 $i
169169
170170 -- @if / @else if / @else
171171 @let count 3
172172 @row [spacing 6, align-items center]
173− @text [bold, min-width 64] Count:
173+ @text [font-weight bold, min-width 64] Count:
174174 @if $count == 0
175175 @text [color $muted] Empty
176176 @else if $count == 1
177177 @text [color $primary] Exactly one
178178 @else
179− @text [color $primary, bold] $count items
179+ @text [color $primary, font-weight bold] $count items
180180
181181 -- @match
182182 @let status ready
183183 @if $status == "pending"
184184 @text [color #f59e0b] Pending…
185185 @else if $status == "ready"
186− @text [color #10b981, bold] ✓ Ready
186+ @text [color #10b981, font-weight bold] ✓ Ready
187187 @else if $status == "error"
188188 @text [color #ef4444] Error
189189 @else
@@ -192,41 +192,41 @@
192192 -- Conditional attribute value + before: pseudo-element
193193 @let emphasized true
194194 @el [
195− padding 12, rounded 6,
195+ padding 12, border-radius 6,
196196 background if($emphasized, $primary, #e5e7eb),
197197 color if($emphasized, white, $ink),
198− before:content "→ ", before:bold
198+ before:content "→ ", before:font-weight bold
199199 ]
200200 Conditional styling via {@code if(cond, a, b)}.
201201
202202 -- Forms + pseudo-states
203203 @titled [title Forms]
204204 @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
206206 @input [
207207 id=email, type=email, name=email,
208208 placeholder=you@example.com, required,
209− padding 10, border 1 #e5e7eb, rounded 6,
210− focus:border 1 $primary,
211− valid:border 1 #10b981,
212− invalid:border 1 #ef4444,
209+ padding 10, border 1 solid #e5e7eb, border-radius 6,
210+ focus:border 1 solid $primary,
211+ valid:border 1 solid #10b981,
212+ invalid:border 1 solid #ef4444,
213213 transition border 0.15s ease
214214 ]
215− @label [bold, size 13, for=role] Role
216− @select [id=role, name=role, padding 10, border 1 #e5e7eb, rounded 6]
215+ @label [font-weight bold, font-size 13, for=role] Role
216+ @select [id=role, name=role, padding 10, border 1 solid #e5e7eb, border-radius 6]
217217 @option [value=dev] Developer
218218 @option [value=designer] Designer
219219 @option [value=other] Other
220− @label [bold, size 13, for=msg] Message
220+ @label [font-weight bold, font-size 13, for=msg] Message
221221 @textarea [
222222 id=msg, name=msg, rows=3,
223− padding 10, border 1 #e5e7eb, rounded 6
223+ padding 10, border 1 solid #e5e7eb, border-radius 6
224224 ]
225225 @row [spacing 8]
226226 @button [
227227 type=submit,
228− padding 10 16, background $primary, color white, bold,
229− rounded 6, border 0,
228+ padding 10 16, background $primary, color white, font-weight bold,
229+ border-radius 6, border 0,
230230 hover:background ${darken($primary, 10)},
231231 active:background ${darken($primary, 20)},
232232 transition background 0.15s ease,
@@ -235,7 +235,7 @@
235235 @button [
236236 type=reset,
237237 padding 10 16, background white,
238− border 1 #e5e7eb, rounded 6, cursor pointer
238+ border 1 solid #e5e7eb, border-radius 6, cursor pointer
239239 ] Reset
240240 @progress [value=60, max=100]
241241
@@ -243,19 +243,19 @@
243243 @titled [title Data]
244244 @row [gap $gap, wrap]
245245 @column [width fill, spacing $base, min-width 240]
246− @text [bold] Unordered
246+ @text [font-weight bold] Unordered
247247 @list [spacing 4, list-style disc, padding-inline-start 20]
248248 @item [display list-item] Composable with @each
249249 @item [display list-item] Indent to nest children
250250 @item [display list-item] Bold list items work too
251− @text [bold] Ordered
251+ @text [font-weight bold] Ordered
252252 @list [ordered, list-style decimal, padding-inline-start 20]
253253 @item [display list-item] First
254254 @item [display list-item] Second
255255 @item [display list-item] Third
256256
257257 @column [width fill, spacing $base, min-width 240]
258− @text [bold] Table
258+ @text [font-weight bold] Table
259259 @table
260260 @thead
261261 @tr
@@ -263,21 +263,21 @@
263263 @th [text-align right, padding 8] Qty
264264 @tbody
265265 @tr
266− @td [padding 8, border-top 1 #e5e7eb] Widgets
267− @td [padding 8, border-top 1 #e5e7eb, text-align right] 12
266+ @td [padding 8, border-top 1 solid #e5e7eb] Widgets
267+ @td [padding 8, border-top 1 solid #e5e7eb, text-align right] 12
268268 @tr
269− @td [padding 8, border-top 1 #e5e7eb] Gadgets
270− @td [padding 8, border-top 1 #e5e7eb, text-align right] 7
269+ @td [padding 8, border-top 1 solid #e5e7eb] Gadgets
270+ @td [padding 8, border-top 1 solid #e5e7eb, text-align right] 7
271271
272272 @figure [spacing 6]
273− @image [alt="A random placeholder", rounded 8] https://picsum.photos/640/200
274− @figcaption [color $muted, size 13, text-align center]
273+ @image [alt="A random placeholder", border-radius 8] https://picsum.photos/640/200
274+ @figcaption [color $muted, font-size 13, text-align center]
275275 A placeholder image.
276276
277277 -- Disclosure + named grid areas
278278 @titled [title Disclosure + grid areas]
279− @details [padding 12, border 1 #e5e7eb, rounded 6, background $subtle]
280− @summary [bold, cursor pointer] What is htmlang?
279+ @details [padding 12, border 1 solid #e5e7eb, border-radius 6, background $subtle]
280+ @summary [font-weight bold, cursor pointer] What is htmlang?
281281 @paragraph [color $muted, line-height 1.6, padding-block-start 6]
282282 htmlang is a layout-first language that compiles to a single,
283283 self-contained HTML file with embedded CSS.
@@ -287,39 +287,39 @@
287287 grid-template-columns 1fr 2fr,
288288 grid-template-areas "h h" "s m" "f f",
289289 gap $base,
290− padding $base, background $subtle, rounded 8
290+ padding $base, background $subtle, border-radius 8
291291 ]
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]
293293 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]
295295 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]
297297 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]
299299 Footer
300300
301301 -- Misc: address, time, meter
302302 @titled [title Misc]
303303 @address [spacing 4, font-style normal]
304− @text [bold] htmlang HQ
304+ @text [font-weight bold] htmlang HQ
305305 @text 123 Flexbox Avenue
306306 @link [color $primary] mailto:hi@htmlang.dev hi@htmlang.dev
307307 @row [spacing 10, align-items center]
308− @text [size 13, color $muted] Published
309− @time [datetime=2026-04-16, size 13] April 16 2026
308+ @text [font-size 13, color $muted] Published
309+ @time [datetime=2026-04-16, font-size 13] April 16 2026
310310 @row [spacing 10, align-items center]
311− @text [size 13, color $muted] Confidence
311+ @text [font-size 13, color $muted] Confidence
312312 @meter [value=8, min=0, max=10, low=3, high=7] 8/10
313313
314314 -- Footer: hover/active button, visited link, print-hidden
315315 @footer [
316316 padding-block-start $gutter, margin-block-start $gutter,
317− border-top 1 #e5e7eb, spacing $base,
317+ border-top 1 solid #e5e7eb, spacing $base,
318318 print:display none
319319 ]
320320 @row [spacing $base, align-items center]
321321 @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,
323323 hover:background ${darken($primary, 10)},
324324 active:background ${darken($primary, 20)},
325325 transition all 0.15s ease,
@@ -329,7 +329,7 @@
329329 @link [
330330 color $primary,
331331 visited:color #8b5cf6,
332− hover:underline
332+ hover:text-decoration underline
333333 ] https://github.com Browse source
334334 @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
22 @el [padding 12, background blue]
33 @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 @@
77
88@let nav-link $label $href
99 @link [
10− padding 8, padding-x 12, rounded 6, color $ink,
10+ padding 8, padding-inline 12, border-radius 6, color $ink,
1111 hover:background #f1f5f9, transition all 0.1s ease
1212 ] $href
1313 $label
@@ -15,15 +15,15 @@
1515@row [min-height 100vh]
1616 -- Sidebar
1717 @aside [
18− width 240, padding 32, padding-x 20,
19− background #fafafa, border-right 1 #e5e7eb, spacing 4
18+ width 240, padding 32, padding-inline 20,
19+ background #fafafa, border-right 1 solid #e5e7eb, spacing 4
2020 ]
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
2222 @nav-link [label Overview, href #overview]
2323 @nav-link [label Installation, href #install]
2424 @nav-link [label First page, href #first-page]
2525 @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,
2727 margin-block-start 20
2828 ] Guides
2929 @nav-link [label Layout, href #layout]
@@ -31,40 +31,40 @@
3131 @nav-link [label Functions, href #functions]
3232
3333 -- Prose
34− @main [padding 48, padding-x 56, max-width 800, spacing 24]
35− @text [bold, size 36, color $ink] Getting started
34+ @main [padding 48, padding-inline 56, max-width 800, spacing 24]
35+ @text [font-weight bold, font-size 36, color $ink] Getting started
3636
37− @paragraph [color $ink, line-height 1.7, size 17]
37+ @paragraph [color $ink, line-height 1.7, font-size 17]
3838 htmlang is a minimalist layout language that compiles to a single static
3939 HTML file. This guide walks through the essentials in about five minutes.
4040
41− @text [bold, size 24, color $ink] Installation
41+ @text [font-weight bold, font-size 24, color $ink] Installation
4242
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]
4444 cargo install --path .
4545
46− @text [bold, size 24, color $ink] Your first page
46+ @text [font-weight bold, font-size 24, color $ink] Your first page
4747
4848 @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:
5050
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]
5252 @page Hello
5353 @column [padding 40, spacing 16]
54− @text [bold, size 32] Hi there
54+ @text [font-weight bold, font-size 32] Hi there
5555
5656 @paragraph [color $ink, line-height 1.7]
5757 Compile it:
5858
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]
6060 htmlang hello.hl
6161
6262 @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},
6464 a single self-contained file with embedded CSS. No dependencies, no
6565 JavaScript, no build step beyond the one command you just ran.
6666
67− @text [bold, size 24, color $ink] Next steps
67+ @text [font-weight bold, font-size 24, color $ink] Next steps
6868
6969 @list [spacing 8]
7070 @item
examples/landing.hl+15−15
@@ -6,8 +6,8 @@
66@let bg-subtle #f8fafc
77
88@let feature $title $body
9− @column [padding 24, background white, rounded 12, border 1 #e2e8f0, spacing 8]
10− @text [bold, size 18, color $ink] $title
9+ @column [padding 24, background white, border-radius 12, border 1 solid #e2e8f0, spacing 8]
10+ @text [font-weight bold, font-size 18, color $ink] $title
1111 @paragraph [color $muted, line-height 1.6]
1212 $body
1313
@@ -15,33 +15,33 @@
1515 -- Hero
1616 @section [
1717 padding 80,
18− padding-x 24,
18+ padding-inline 24,
1919 background $bg-subtle,
2020 text-align center,
2121 spacing 24
2222 ]
23− @text [bold, size 48, color $ink] Build faster with less CSS.
24− @paragraph [max-width 560, center-x, color $muted, line-height 1.6, size 18]
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, font-size 18]
2525 Launchpad turns your static pages into a polished product in minutes.
2626 Ship faster, iterate smaller, sleep at night.
2727 @row [center-x, spacing 12]
2828 @link [
29− padding 14, padding-x 24, rounded 8,
30− background $primary, color white, bold,
29+ padding 14, padding-inline 24, border-radius 8,
30+ background $primary, color white, font-weight bold,
3131 transition all 0.15s ease
3232 ] /signup
3333 Start free
3434 @link [
35− padding 14, padding-x 24, rounded 8,
36− border 1 #e2e8f0, color $ink, bold,
35+ padding 14, padding-inline 24, border-radius 8,
36+ border 1 solid #e2e8f0, color $ink, font-weight bold,
3737 transition all 0.15s ease
3838 ] /demo
3939 Watch demo
4040
4141 -- 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]
4343 @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
4545 @text [color $muted] Opinionated where it matters, flexible where it counts.
4646 @row [wrap, gap 20]
4747 @feature [
@@ -58,13 +58,13 @@
5858 ]
5959
6060 -- CTA
61− @section [padding 60, padding-x 24, background $primary, color white, text-align center, spacing 16]
62− @text [bold, size 28] Ready to launch?
61+ @section [padding 60, padding-inline 24, background $primary, color white, text-align center, spacing 16]
62+ @text [font-weight bold, font-size 28] Ready to launch?
6363 @paragraph [max-width 520, center-x, line-height 1.6]
6464 Join thousands of teams building their marketing sites with htmlang.
6565 @link [
66− padding 14, padding-x 28, rounded 8,
67− background white, color $primary, bold,
66+ padding 14, padding-inline 28, border-radius 8,
67+ background white, color $primary, font-weight bold,
6868 transition all 0.15s ease
6969 ] /signup
7070 Start for free
src/bin/htmlang_lsp/completion.rs+2−2
@@ -907,7 +907,7 @@ mod tests {
907907
908908 #[test]
909909 fn owning_element_finds_element_on_same_line() {
910− let text = "@input [type text, ";
910+ let text = "@input [type=text, ";
911911 // Cursor at end of line — inside the unclosed `[`.
912912 assert_eq!(
913913 owning_element(text, pos(0, text.len() as u32)),
@@ -966,7 +966,7 @@ mod tests {
966966 }
967967 };
968968 assert_eq!(insert("spacing"), "spacing ");
969− assert_eq!(insert("bold"), "bold");
969+ assert_eq!(insert("wrap"), "wrap");
970970 assert_eq!(insert("opacity"), "opacity ");
971971 assert_eq!(insert("type="), "type=");
972972 assert_eq!(insert("required"), "required");
src/bin/htmlang_lsp/docs.rs+1−13
@@ -36,7 +36,7 @@ pub(crate) const ELEMENTS: &[Doc] = &[
3636 doc("row", "Horizontal layout: a flex row.", "@row [spacing 10]\n @text A\n @text B"),
3737 doc("column", "Vertical layout: a flex column.", "@column [spacing 10]\n @text A\n @text B"),
3838 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"),
4040 doc(
4141 "paragraph",
4242 "Flowing text (`<p>`); holds inline `{@...}` elements.",
@@ -114,11 +114,7 @@ pub(crate) const DIRECTIVES: &[Doc] = &[
114114pub(crate) const ATTRIBUTES: &[Doc] = &[
115115 doc("spacing", "Gap between children.", "spacing 20"),
116116 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"),
119117 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"),
122118 doc("width", "`fill` takes the remaining space, `shrink` fits the content, or a size.", "width fill"),
123119 doc("height", "`fill` takes the remaining space, `shrink` fits the content, or a size.", "height 200"),
124120 doc("center-x", "Centers the element horizontally in its parent.", "center-x"),
@@ -132,14 +128,6 @@ pub(crate) const ATTRIBUTES: &[Doc] = &[
132128 doc("grid-rows", "Number of equal grid rows.", "grid-rows 2"),
133129 doc("col-span", "Columns a grid child spans.", "col-span 2"),
134130 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\""),
143131 doc("line-height", "Line height; integers of 2 or more are px.", "line-height 1.5"),
144132 doc("ordered", "On `@list`: a numbered list (`<ol>`).", "ordered"),
145133 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 {
465465 #[test]
466466 fn idempotent_at_wrap_threshold() {
467467 // 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";
469469 let a = format(src);
470470 let b = format(&a);
471471 assert_eq!(a, b, "formatter must be idempotent across runs");
@@ -473,7 +473,7 @@ mod tests {
473473
474474 #[test]
475475 fn preserves_trailing_comment() {
476− let src = "@text [bold] hello -- greeting\n";
476+ let src = "@text [font-weight bold] hello -- greeting\n";
477477 let out = format(src);
478478 assert!(
479479 out.contains("-- greeting"),
@@ -483,7 +483,7 @@ mod tests {
483483
484484 #[test]
485485 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";
487487 let out = format(src);
488488 // Single-line emission — no multi-line bracket block should be triggered.
489489 assert_eq!(out.lines().count(), 1, "got:\n{out}");
@@ -498,7 +498,7 @@ mod tests {
498498
499499 #[test]
500500 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";
502502 assert_eq!(format(src), src);
503503 }
504504
@@ -510,14 +510,14 @@ mod tests {
510510
511511 #[test]
512512 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";
514514 assert_eq!(format(src), src);
515515 }
516516
517517 #[test]
518518 fn commas_inside_parens_stay_in_value() {
519− let src = "@el [shadow 0 1px 3px rgba(0,0,0,0.1),bold] hi\n";
520− assert_eq!(format(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 [box-shadow 0 1px 3px rgba(0,0,0,0.1), font-weight bold] hi\n");
521521 }
522522
523523 #[test]
src/upgrade.rs+105−4
@@ -151,7 +151,8 @@ pub fn upgrade(input: &str) -> Upgrade {
151151 let mut i = 0;
152152 // A file may define its own function named like an old alias (e.g.
153153 // `@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.)
155156 let mut user_defined: Vec<&str> = Vec::new();
156157 let mut j = 0;
157158 while j < lines.len() {
@@ -159,6 +160,10 @@ pub fn upgrade(input: &str) -> Upgrade {
159160 j = end;
160161 continue;
161162 }
163+ if VERBATIM_BODIES.iter().any(|d| starts_directive(lines[j].trim(), d)) {
164+ j = block_end(&lines, j);
165+ continue;
166+ }
162167 if let Some(name) = lines[j]
163168 .trim()
164169 .strip_prefix("@let ")
@@ -358,7 +363,11 @@ fn upgrade_lists(text: &str, manual: &mut Vec<(usize, String)>) -> (String, usiz
358363 i += added;
359364 continue;
360365 }
361− if vars.len() > 2 || (vars.len() == 2 && list.starts_with('$')) {
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()));
370+ if vars.len() > 2 || (vars.len() == 2 && list.starts_with('$') && !index_name) {
362371 manual.push((
363372 i + 1,
364373 format!(
@@ -1380,6 +1389,8 @@ fn rewrite_attr_list(list: &str, element_attrs: bool) -> String {
13801389 }
13811390 _ => body,
13821391 };
1392+ // Style aliases that became their CSS properties
1393+ let body = if element_attrs { css_alias_or_choice(&body).unwrap_or(body) } else { body };
13831394 // Renamed keys (keeping any `hover:` / `md:` style prefix)
13841395 let key_end = body.find(char::is_whitespace).unwrap_or(body.len());
13851396 let (key, value) = body.split_at(key_end);
@@ -1399,6 +1410,70 @@ fn rewrite_attr_list(list: &str, element_attrs: bool) -> String {
13991410 out
14001411}
14011412
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+
14021477/// Does the attribute list at the start of `rest` (if any) have `key`?
14031478fn has_attr_key(rest: &str, key: &str) -> bool {
14041479 let Some(list) = rest.strip_prefix('[').and_then(|r| r.split(']').next()) else {
@@ -1637,7 +1712,7 @@ mod tests {
16371712 fn directives() {
16381713 assert_eq!(up("@unless $x\n hi"), "@if not $x\n hi");
16391714 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]");
16411716 assert_eq!(up("@for $i in 1..3\n $i"), "@each $i in 1..3\n $i");
16421717 assert_eq!(up("@use \"lib.hl\" a, b"), "@include lib.hl");
16431718 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 {
16861761 assert_eq!(up("@el [\n animate spin 1s\n]"), "@el [\n animation spin 1s\n]");
16871762 assert_eq!(up("@text $name|upper|len"), "@text ${length(uppercase($name))}");
16881763 // 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");
16901765 }
16911766
16921767 #[test]
@@ -1826,6 +1901,32 @@ mod tests {
18261901 );
18271902 }
18281903
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+
18291930 #[test]
18301931 fn loops_and_lists() {
18311932 assert_eq!(
tests/regressions.rs+21−9
@@ -81,7 +81,7 @@ fn range_at_integer_limit_terminates() {
8181
8282#[test]
8383fn 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");
8585 assert!(
8686 out.contains("@layer hl-reset,htmlang;@layer hl-reset{"),
8787 "reset must be layered so class rules can override it: {}",
@@ -91,7 +91,7 @@ fn reset_css_is_layered_below_generated_rules() {
9191
9292#[test]
9393fn 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");
9595 assert!(out.contains("padding:10px"), "{}", out);
9696 assert!(out.contains("margin:0 auto"), "{}", out);
9797 assert!(out.contains("max-width:none"), "{}", out);
@@ -100,7 +100,7 @@ fn px_values_are_not_doubled() {
100100
101101#[test]
102102fn 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}.");
104104 let out = codegen::generate_minified(&result.document);
105105 assert!(out.contains("Built with <span"), "{}", out);
106106}
@@ -141,7 +141,7 @@ fn image_with_css_width_gets_no_intrinsic_height() {
141141 r#"<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000"></svg>"#,
142142 )
143143 .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()));
145145 assert!(!out.contains("height=\"1000\""), "{}", out);
146146}
147147
@@ -158,7 +158,7 @@ fn source_map_uses_real_output_lines() {
158158
159159#[test]
160160fn 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");
162162 assert!(out.contains("Use [ to open a list"), "{}", out);
163163 assert!(out.contains(">Second<"), "{}", out);
164164 assert!(out.contains(">Third<"), "{}", out);
@@ -229,7 +229,7 @@ fn quoted_let_values_are_not_evaluated_as_arithmetic() {
229229
230230#[test]
231231fn 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");
233233 assert!(out.contains("font-family:Inter, sans-serif"), "{}", out);
234234 assert!(out.contains("font-weight:bold"), "{}", out);
235235}
@@ -294,7 +294,7 @@ fn html_attributes_use_equals_and_are_all_emitted() {
294294
295295#[test]
296296fn 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");
298298 assert!(out.contains("size=\"4\""), "{}", out);
299299 assert!(out.contains("font-size:20px"), "{}", out);
300300 let out = compile("@image [width=800, width 200, alt=x] a.png");
@@ -346,7 +346,7 @@ fn invalid_expressions_are_errors() {
346346
347347#[test]
348348fn 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");
350350 assert!(
351351 result.diagnostics.is_empty(),
352352 "unexpected diagnostics: {:?}",
@@ -396,7 +396,7 @@ fn inline_svg_resolves_from_the_page_and_keeps_attributes() {
396396#[test]
397397fn inline_element_attributes_can_continue_on_the_next_line() {
398398 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",
400400 );
401401 assert!(result.diagnostics.is_empty(), "{:?}", result.diagnostics);
402402 let html = htmlang::codegen::generate(&result.document);
@@ -428,3 +428,15 @@ fn raw_takes_an_indented_body() {
428428 result.diagnostics
429429 );
430430}
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() {
12991299fn css_margin_x() {
13001300 // margin-x emits the `margin-inline` logical shorthand (single property,
13011301 // 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]");
13031303 assert!(output.contains("margin-inline:10px"));
13041304}
13051305
13061306#[test]
13071307fn 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]");
13091309 assert!(output.contains("margin-block:10px"));
13101310}
13111311
@@ -1560,7 +1560,7 @@ fn snapshot_lang_directive() {
15601560
15611561#[test]
15621562fn 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]");
15641564 assert!(output.contains(":focus-visible"));
15651565 assert!(output.contains("border:2px solid blue"));
15661566}
@@ -1633,7 +1633,7 @@ fn fragment_no_wrapper() {
16331633
16341634#[test]
16351635fn hidden_generates_display_none() {
1636− let output = compile("@page T\n@el [hidden]");
1636+ let output = compile("@page T\n@el [display none]");
16371637 assert!(output.contains("display:none"));
16381638}
16391639
@@ -1744,7 +1744,7 @@ fn favicon_fallback_href() {
17441744#[test]
17451745fn no_warning_new_pseudo_prefixes() {
17461746 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]",
17481748 );
17491749 assert!(
17501750 !diags
@@ -1772,7 +1772,7 @@ fn no_warning_child_selectors() {
17721772#[test]
17731773fn no_warning_new_css_attrs() {
17741774 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]",
17761776 );
17771777 assert!(
17781778 !diags
@@ -3522,13 +3522,13 @@ fn target_pseudo() {
35223522
35233523#[test]
35243524fn 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]");
35263526 assert!(output.contains(":valid"), "valid pseudo: {}", output);
35273527}
35283528
35293529#[test]
35303530fn 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");
35323532 assert!(
35333533 output.contains("text-underline-offset:4px"),
35343534 "text-underline-offset: {}",
@@ -3595,7 +3595,7 @@ fn snapshot_mixin_spread() {
35953595
35963596#[test]
35973597fn 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");
35993599 assert!(
36003600 output.contains("padding:20px"),
36013601 "mixin should expand padding: {}",
@@ -3610,7 +3610,7 @@ fn mixin_expands_in_attrs() {
36103610
36113611#[test]
36123612fn 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");
36143614 assert!(
36153615 output.contains("padding:20px"),
36163616 "mixin with $ syntax should expand: {}",
@@ -3668,7 +3668,7 @@ fn snapshot_clamp_css() {
36683668
36693669#[test]
36703670fn clamp_passthrough() {
3671− let output = compile("@el [size clamp(16px, 2vw, 24px)]");
3671+ let output = compile("@el [font-size clamp(16px, 2vw, 24px)]");
36723672 assert!(
36733673 output.contains("font-size:clamp(16px, 2vw, 24px)"),
36743674 "clamp should pass through: {}",
@@ -3848,7 +3848,7 @@ fn switch_with_attrs() {
38483848
38493849#[test]
38503850fn 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";
38523852 let result = htmlang::parser::parse(input);
38533853 let normal = htmlang::codegen::generate(&result.document);
38543854 let minified = htmlang::codegen::generate_minified(&result.document);
@@ -4134,7 +4134,7 @@ fn test_children_fallback_content() {
41344134
41354135#[test]
41364136fn 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");
41384138 assert!(
41394139 html.contains("padding:12px"),
41404140 "spread define should apply padding"
@@ -4410,7 +4410,7 @@ fn image_auto_preload() {
44104410
44114411#[test]
44124412fn source_map_generation() {
4413− let input = "@page Test\n@text [bold] Hello";
4413+ let input = "@page Test\n@text [font-weight bold] Hello";
44144414 let result = htmlang::parser::parse(input);
44154415 let map = htmlang::codegen::generate_source_map(&result.document, "test.hl");
44164416 assert!(
tests/snapshots/attributes.hl+2−2
@@ -1,6 +1,6 @@
11@page Attributes
2−@el [width 200, height 100, padding 10 20, background #fff, color #333, border 1 #ccc, rounded 8, opacity 0.9]
3− @text [bold, italic, underline, size 16, font monospace] Styled
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 [font-weight bold, font-style italic, text-decoration underline, font-size 16, font-family monospace] Styled
44@row [wrap, column-gap 10, row-gap 5, spacing 8]
55 @el [min-width 50, max-width 200, min-height 30, max-height 100]
66@column [overflow hidden, position relative, z-index 10]
tests/snapshots/basic_elements.hl+1−1
@@ -1,6 +1,6 @@
11@page Basic Elements
22@row [spacing 10]
3− @text [bold] Hello
3+ @text [font-weight bold] Hello
44 @text World
55@column [spacing 5]
66 @el [padding 10]
tests/snapshots/chain_operator.hl+1−1
@@ -1,4 +1,4 @@
11@page Chain
22@el [padding 10] > @link https://example.com
3− @text [bold] Click
3+ @text [font-weight bold] Click
44@row [spacing 5] > @el [padding 20] > @text [color red] Deep chain
tests/snapshots/clamp_css.hl+1−1
@@ -1,4 +1,4 @@
11@page Clamp CSS Test
22
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)]
44 Fluid typography and sizing
tests/snapshots/color_scheme.hl+1−1
@@ -3,4 +3,4 @@
33@el [color-scheme light dark, padding 20]
44 @text Adapts to system theme
55
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 @@
66@el [user-select none, padding 10]
77 @text Cannot select this
88
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 @@
33@let large = $base * 2
44@let gap = $base + 4
55@column [spacing $gap, padding 20]
6− @text [size $base] Base size
7− @text [size $large] Large size
6+ @text [font-size $base] Base size
7+ @text [font-size $large] Large size
tests/snapshots/css_shorthands.hl+3−3
@@ -24,10 +24,10 @@
2424 @el [width 200, padding 20] Item 3
2525
2626 -- Skeleton
27− @el [width fill, height 20, rounded 4, $skeleton]
27+ @el [width fill, height 20, border-radius 4, $skeleton]
2828
2929 -- Gradient
30− @el [background linear-gradient(#3b82f6,#8b5cf6), padding 20, rounded 8]
30+ @el [background linear-gradient(#3b82f6,#8b5cf6), padding 20, border-radius 8]
3131 @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]
3333 @text [color white] Angled gradient
tests/snapshots/css_units.hl+2−2
@@ -1,8 +1,8 @@
11@page CSS Units
22@column [padding 2rem, spacing 1.5em]
33 @el [width 50%, height 100vh, max-width 80ch]
4− @text [size 1.2rem] Hello
5− @el [padding 10 2rem, rounded 0.5rem]
4+ @text [font-size 1.2rem] Hello
5+ @el [padding 10 2rem, border-radius 0.5rem]
66 @text Responsive padding
77 @el [min-height 50vh, gap 2rem]
88 @text Viewport units
tests/snapshots/css_vars.hl+2−2
@@ -4,7 +4,7 @@
44@let --radius 8
55
66@column [spacing $--spacing]
7− @el [background var(--primary), rounded $--radius, padding $--spacing]
7+ @el [background var(--primary), border-radius $--radius, padding $--spacing]
88 @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]
1010 @text [color var(--primary)] Border with var
tests/snapshots/dark_mode.hl+2−2
@@ -1,9 +1,9 @@
11@page Dark Mode
22
33@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
55
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]
77 @text Theme-aware card
88
99@el [background #f0f0f0, dark:background #333, print:display none, padding 10]
tests/snapshots/data_directive.hl+1−1
@@ -1,7 +1,7 @@
11@page Data Test
22@data $site test_data.json
33
4−@text [bold, size 24] $site.title
4+@text [font-weight bold, font-size 24] $site.title
55@text Version: $site.version
66
77@each $tag in $site.tags
tests/snapshots/each_destructuring.hl+1−1
@@ -3,5 +3,5 @@
33@data $name-role [{"name": "Alice", "role": "Admin"}, {"name": "Bob", "role": "User"}, {"name": "Carol", "role": "Editor"}]
44@each $item in $name-role
55 @row [spacing 10]
6− @text [bold] $item.name
6+ @text [font-weight bold] $item.name
77 @text $item.role
tests/snapshots/each_step.hl+2−2
@@ -8,8 +8,8 @@
88-- Step with large range
99@row [spacing 8]
1010 @each $n in 0..100 step 25
11− @el [width 50, height 50, background #3b82f6, rounded 4]
12− @text [color white, size 12] $n
11+ @el [width 50, height 50, background #3b82f6, border-radius 4]
12+ @text [color white, font-size 12] $n
1313
1414-- Reverse range with step
1515@column [spacing 4]
tests/snapshots/extra_css.hl+1−1
@@ -7,7 +7,7 @@
77 Styled
88 @el [letter-spacing 2, text-transform uppercase, white-space nowrap]
99 Typography
10− @el [border-bottom 1 #ccc, border-top 2 red]
10+ @el [border-bottom 1 solid #ccc, border-top 2 solid red]
1111 Borders
1212 @el [display grid, grid-cols 3, gap 10]
1313 @el [col-span 2]
tests/snapshots/fn_defaults.hl+1−1
@@ -1,5 +1,5 @@
11@let badge $label $color=blue
2− @el [padding 4 8, background $color, rounded 4]
2+ @el [padding 4 8, background $color, border-radius 4]
33 @text $label
44
55@badge [label Hello]
tests/snapshots/functions.hl+2−2
@@ -1,7 +1,7 @@
11@page Functions
22@let card $title
3− @el [padding 20, rounded 8]
4− @text [bold] $title
3+ @el [padding 20, border-radius 8]
4+ @text [font-weight bold] $title
55 @children
66
77@let wrapper
tests/snapshots/has_pseudo.hl+1−1
@@ -2,5 +2,5 @@
22@column [spacing 20, padding 20]
33 @el [padding 20, background white, has(.active):background #dbeafe, has(img):padding 0]
44 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]
66 Container
tests/snapshots/hidden_attr.hl+1−1
@@ -1,6 +1,6 @@
11@page Hidden Test
22
3−@el [hidden]
3+@el [display none]
44 @text This is hidden
55
66@el [padding 10]
tests/snapshots/inline_text.hl+2−2
@@ -1,5 +1,5 @@
11@page Inline Text
22@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.
44@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 @@
1111 @item Step three
1212
1313@list
14− @item [padding 10, background #f0f0f0, rounded 4]
15− @text [bold] Styled item
14+ @item [padding 10, background #f0f0f0, border-radius 4]
15+ @text [font-weight bold] Styled item
tests/snapshots/mixin_spread.hl+2−2
@@ -1,10 +1,10 @@
11@page Mixin Test
22
3−@let card-style [padding 20, rounded 8, background #f3f4f6]
3+@let card-style [padding 20, border-radius 8, background #f3f4f6]
44@let interactive [cursor pointer, hover:background #e5e7eb, transition all 0.15s ease]
55
66@el [$card-style, $interactive]
77 Hello mixin world
88
9−@el [$card-style, border 1 #ccc]
9+@el [$card-style, border 1 solid #ccc]
1010 Using $define syntax too
tests/snapshots/named_slots.hl+1−1
@@ -7,7 +7,7 @@
77
88@layout
99 @slot header
10− @text [bold] Custom Header
10+ @text [font-weight bold] Custom Header
1111 @slot footer
1212 @text Footer content
1313 @text Main body
tests/snapshots/new_css.hl+1−1
@@ -1,6 +1,6 @@
11@page New CSS
22
3−@el [margin 10, margin-x 20, margin-y 30]
3+@el [margin 10, margin-inline 20, margin-block 30]
44 @text Margins
55
66@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 @@
33@column [spacing 20]
44 @text [font-weight 600] Semi-bold text
55 @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
77 @el [will-change transform, transition transform 0.3s ease]
88 Optimized for animation
99 @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]
22 Styled underline
33
44@el [column-count 3, column-width 200, column-rule 1px solid #ccc]
tests/snapshots/new_directives.hl+1−1
@@ -17,4 +17,4 @@
1717 </script>
1818
1919@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 @@
11@page New Elements 2
22
33@dialog [id=modal, open]
4− @text [bold] Dialog Title
4+ @text [font-weight bold] Dialog Title
55 @text Dialog content here
66 @button Close
77
tests/snapshots/new_elements_4.hl+2−2
@@ -30,6 +30,6 @@
3030-- Tooltip element
3131@row [spacing 16]
3232 @tooltip [tip Hover me for info] Hover me for info
33− @text [underline] Helpful text
33+ @text [text-decoration underline] Helpful text
3434 @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 @@
77
88 -- Carousel
99 @carousel [gap 16]
10− @el [width 200, padding 20, background #f3f4f6, rounded 8]
10+ @el [width 200, padding 20, background #f3f4f6, border-radius 8]
1111 Slide 1
12− @el [width 200, padding 20, background #f3f4f6, rounded 8]
12+ @el [width 200, padding 20, background #f3f4f6, border-radius 8]
1313 Slide 2
1414
1515 -- Chip
tests/snapshots/new_elements_6.hl+1−1
@@ -2,7 +2,7 @@
22 @text JavaScript is required for this page.
33
44@address
5− @text [bold] John Doe
5+ @text [font-weight bold] John Doe
66 @link mailto:john@example.com john@example.com
77
88@search
tests/snapshots/new_pseudos.hl+2−2
@@ -4,5 +4,5 @@
44@el [empty:display none, target:background yellow]
55 Conditional content
66
7−@input [type=email, valid:border 2 green]
8−@input [type=text, invalid:border 2 red]
7+@input [type=email, valid:border 2 solid green]
8+@input [type=text, invalid:border 2 solid red]
tests/snapshots/no_page.hl+1−1
@@ -1,2 +1,2 @@
11@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 @@
22
33@button [popovertarget=my-popover] Toggle Info
44
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]
66 @text This is a popover panel
tests/snapshots/pseudo_states_extended.hl+1−1
@@ -1,6 +1,6 @@
11@page Pseudo States Extended
22
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]
44 @input [type=text, placeholder=Name]
55
66@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 @@
11@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 @@
11@page Responsive
22@row [spacing 10, sm:spacing 20, lg:spacing 40]
33 @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
55 @el [padding 10, sm:padding 20]
tests/snapshots/semantic_elements.hl+4−4
@@ -1,7 +1,7 @@
11@page Semantic Elements
22
33@header [padding 20, background #f0f0f0]
4− @text [bold, size 24] My Site
4+ @text [font-weight bold, font-size 24] My Site
55
66@nav [spacing 10]
77 @link / Home
@@ -9,12 +9,12 @@
99 @link /contact Contact
1010
1111@main [padding 20]
12− @article [padding 16, border 1 #ddd, rounded 8]
13− @text [bold] Article Title
12+ @article [padding 16, border 1 solid #ddd, border-radius 8]
13+ @text [font-weight bold] Article Title
1414 @paragraph Some article content here
1515
1616 @section [padding 16]
17− @text [bold] Section Title
17+ @text [font-weight bold] Section Title
1818 @paragraph Section content
1919
2020@aside [padding 16, background #f8f8f8]
tests/snapshots/stress_deeply_nested.hl+114−114
@@ -5,100 +5,100 @@
55@let --radius 8
66
77@let deep-card $title
8− @el [background white, dark:background #1e293b, rounded $--radius, padding 16, border 1 $--border]
9− @text [bold, size 16] $title
8+ @el [background white, dark:background #1e293b, border-radius $--radius, padding 16, border 1 solid $--border]
9+ @text [font-weight bold, font-size 16] $title
1010 @children
1111
1212@let inner-panel $label
13− @el [background #f8fafc, dark:background #0f172a, rounded 6, padding 12, margin-top 8]
14− @text [size 13, color #64748b] $label
13+ @el [background #f8fafc, dark:background #0f172a, border-radius 6, padding 12, margin-top 8]
14+ @text [font-size 13, color #64748b] $label
1515 @children
1616
1717@column [spacing 20, padding 24]
1818
19− @el [background #f0f9ff, rounded 12, padding 20, border 1 #bae6fd]
20− @text [bold, size 20, color #0369a1] Level 1 - App Shell
19+ @el [background #f0f9ff, border-radius 12, padding 20, border 1 solid #bae6fd]
20+ @text [font-weight bold, font-size 20, color #0369a1] Level 1 - App Shell
2121 @row [spacing 16, margin-top 16]
22− @el [background white, rounded 8, padding 16, border 1 $--border, width fill]
23− @text [bold, size 16, color #1e293b] Level 2 - Sidebar
22+ @el [background white, border-radius 8, padding 16, border 1 solid $--border, width fill]
23+ @text [font-weight bold, font-size 16, color #1e293b] Level 2 - Sidebar
2424 @column [spacing 8, margin-top 12]
25− @el [background #f1f5f9, rounded 6, padding 12]
26− @text [size 14, color #475569] Level 3 - Nav Group
25+ @el [background #f1f5f9, border-radius 6, padding 12]
26+ @text [font-size 14, color #475569] Level 3 - Nav Group
2727 @column [spacing 4, margin-top 8]
28− @el [padding 8 12, hover:background #e2e8f0, rounded 4, transition all 0.15s]
29− @text [size 13, color #64748b] Level 4 - Nav Item
28+ @el [padding 8 12, hover:background #e2e8f0, border-radius 4, transition all 0.15s]
29+ @text [font-size 13, color #64748b] Level 4 - Nav Item
3030 @row [spacing 6, margin-top 4, align-items center]
31− @el [width 6, height 6, rounded 50%, background #10b981]
32− @el [padding 4 8, background #dbeafe, rounded 4]
33− @text [size 11, color #1d4ed8] Level 5 - Badge
31+ @el [width 6, height 6, border-radius 50%, background #10b981]
32+ @el [padding 4 8, background #dbeafe, border-radius 4]
33+ @text [font-size 11, color #1d4ed8] Level 5 - Badge
3434 @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
3636 @el [margin-top 2]
37− @text [size 9, color #93c5fd] Level 7 - Sub Detail
38− @el [padding 2 4, background #eff6ff, rounded 2, margin-top 1]
39− @text [size 8, color #bfdbfe] Level 8 - Deepest Badge
37+ @text [font-size 9, color #93c5fd] Level 7 - Sub Detail
38+ @el [padding 2 4, background #eff6ff, border-radius 2, margin-top 1]
39+ @text [font-size 8, color #bfdbfe] Level 8 - Deepest Badge
4040
41− @el [padding 8 12, hover:background #e2e8f0, rounded 4]
42− @text [size 13, color #64748b] Level 4 - Second Nav Item
43− @el [margin-top 4, padding 6, background #f0fdf4, rounded 4]
44− @text [size 12, color #166534] Level 5 - Status
41+ @el [padding 8 12, hover:background #e2e8f0, border-radius 4]
42+ @text [font-size 13, color #64748b] Level 4 - Second Nav Item
43+ @el [margin-top 4, padding 6, background #f0fdf4, border-radius 4]
44+ @text [font-size 12, color #166534] Level 5 - Status
4545 @row [spacing 4, margin-top 2]
46− @el [padding 2 6, background #bbf7d0, rounded 2]
47− @text [size 10, color #15803d] Level 6 - Online
48− @el [padding 2 6, background #fef3c7, rounded 2]
49− @text [size 10, color #92400e] Level 6 - Away
46+ @el [padding 2 6, background #bbf7d0, border-radius 2]
47+ @text [font-size 10, color #15803d] Level 6 - Online
48+ @el [padding 2 6, background #fef3c7, border-radius 2]
49+ @text [font-size 10, color #92400e] Level 6 - Away
5050
51− @el [background #f1f5f9, rounded 6, padding 12]
52− @text [size 14, color #475569] Level 3 - Second Group
51+ @el [background #f1f5f9, border-radius 6, padding 12]
52+ @text [font-size 14, color #475569] Level 3 - Second Group
5353 @column [spacing 4, margin-top 8]
5454 @each $item in Settings,Profile,Security
55− @el [padding 8 12, hover:background #e2e8f0, rounded 4]
56− @text [size 13, color #64748b] $item
55+ @el [padding 8 12, hover:background #e2e8f0, border-radius 4]
56+ @text [font-size 13, color #64748b] $item
5757
58− @el [background white, rounded 8, padding 16, border 1 $--border, width fill]
59− @text [bold, size 16, color #1e293b] Level 2 - Main Content
58+ @el [background white, border-radius 8, padding 16, border 1 solid $--border, width fill]
59+ @text [font-weight bold, font-size 16, color #1e293b] Level 2 - Main Content
6060 @column [spacing 12, margin-top 12]
6161 @deep-card [title Level 3 - Dashboard Card]
6262 @inner-panel [label Level 4 - Chart Area]
6363 @row [spacing 8, margin-top 8]
64− @el [width fill, background #eff6ff, rounded 4, padding 8]
65− @text [size 12, color #1d4ed8, bold] Level 5 - Metric A
66− @el [margin-top 4, height 60, background #dbeafe, rounded 4, padding 8]
67− @text [size 20, bold, color #1e40af] Level 6 - Value
64+ @el [width fill, background #eff6ff, border-radius 4, padding 8]
65+ @text [font-size 12, color #1d4ed8, font-weight bold] Level 5 - Metric A
66+ @el [margin-top 4, height 60, background #dbeafe, border-radius 4, padding 8]
67+ @text [font-size 20, font-weight bold, color #1e40af] Level 6 - Value
6868 @el [margin-top 2]
69− @text [size 10, color #3b82f6] Level 7 - Trend
69+ @text [font-size 10, color #3b82f6] Level 7 - Trend
7070 @row [spacing 2, margin-top 2]
71− @el [width 4, height 12, background #93c5fd, rounded 1]
72− @el [width 4, height 18, background #60a5fa, rounded 1]
73− @el [width 4, height 8, background #93c5fd, rounded 1]
74− @el [width 4, height 22, background #3b82f6, rounded 1]
75− @el [width 2, height 2, rounded 50%, background white, margin-top 1]
76− @text [size 6, color white] Level 8 - Spark Dot
77− @el [width fill, background #f0fdf4, rounded 4, padding 8]
78− @text [size 12, color #166534, bold] Level 5 - Metric B
79− @el [margin-top 4, height 60, background #dcfce7, rounded 4, padding 8]
80− @text [size 20, bold, color #166534] Level 6 - Value
71+ @el [width 4, height 12, background #93c5fd, border-radius 1]
72+ @el [width 4, height 18, background #60a5fa, border-radius 1]
73+ @el [width 4, height 8, background #93c5fd, border-radius 1]
74+ @el [width 4, height 22, background #3b82f6, border-radius 1]
75+ @el [width 2, height 2, border-radius 50%, background white, margin-top 1]
76+ @text [font-size 6, color white] Level 8 - Spark Dot
77+ @el [width fill, background #f0fdf4, border-radius 4, padding 8]
78+ @text [font-size 12, color #166534, font-weight bold] Level 5 - Metric B
79+ @el [margin-top 4, height 60, background #dcfce7, border-radius 4, padding 8]
80+ @text [font-size 20, font-weight bold, color #166534] Level 6 - Value
8181 @el [margin-top 2]
82− @text [size 10, color #16a34a] Level 7 - Change
83− @el [padding 1 4, background #bbf7d0, rounded 2, display inline-block]
84− @text [size 8, color #15803d] Level 8 - Delta
82+ @text [font-size 10, color #16a34a] Level 7 - Change
83+ @el [padding 1 4, background #bbf7d0, border-radius 2, display inline-block]
84+ @text [font-size 8, color #15803d] Level 8 - Delta
8585
8686 @deep-card [title Level 3 - Form Card]
8787 @inner-panel [label Level 4 - Form Section]
8888 @column [spacing 8, margin-top 8]
89− @el [padding 8, border 1 $--border, rounded 4]
90− @text [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]
92− @el [margin-top 4, padding 4 8, background #fef2f2, rounded 4]
93− @text [size 11, color #991b1b] Level 6 - Validation Error
89+ @el [padding 8, border 1 solid $--border, border-radius 4]
90+ @text [font-size 12, color #64748b, margin-bottom 4] Level 5 - Field Group
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, border-radius 4]
93+ @text [font-size 11, color #991b1b] Level 6 - Validation Error
9494 @el [margin-top 2]
95− @text [size 10, color #dc2626] Level 7 - Error Detail
96− @el [margin-top 1, padding 2 4, background #fee2e2, rounded 2]
97− @text [size 9, color #b91c1c] Level 8 - Error Code
95+ @text [font-size 10, color #dc2626] Level 7 - Error Detail
96+ @el [margin-top 1, padding 2 4, background #fee2e2, border-radius 2]
97+ @text [font-size 9, color #b91c1c] Level 8 - Error Code
9898
99− @el [padding 8, border 1 $--border, rounded 4]
100− @text [size 12, color #64748b, margin-bottom 4] Level 5 - Select Group
101− @select [padding 6 10, rounded 4, border 1 $--border, width fill]
99+ @el [padding 8, border 1 solid $--border, border-radius 4]
100+ @text [font-size 12, color #64748b, margin-bottom 4] Level 5 - Select Group
101+ @select [padding 6 10, border-radius 4, border 1 solid $--border, width fill]
102102 @option [value=a] Option A
103103 @option [value=b] Option B
104104 @option [value=c] Option C
@@ -108,77 +108,77 @@
108108 @column [spacing 6, margin-top 8]
109109 @let top-categories Electronics,Clothing,Books
110110 @each $cat in $top-categories
111− @el [padding 8, border 1 $--border, rounded 4, hover:background #f8fafc]
112− @text [size 14, bold, color #1e293b] Level 5 - $cat
111+ @el [padding 8, border 1 solid $--border, border-radius 4, hover:background #f8fafc]
112+ @text [font-size 14, font-weight bold, color #1e293b] Level 5 - $cat
113113 @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]
115115 @row [spacing 8, align-items center]
116− @el [width 8, height 8, rounded 2, background $--primary]
117− @text [size 12, color #475569] Level 6 - Subcategory
116+ @el [width 8, height 8, border-radius 2, background $--primary]
117+ @text [font-size 12, color #475569] Level 6 - Subcategory
118118 @el [margin-top 4, margin-left 16]
119− @text [size 11, color #94a3b8] Level 7 - Item
120− @el [margin-left 12, margin-top 2, padding 2 6, background #eff6ff, rounded 2]
121− @text [size 10, color #3b82f6] Level 8 - Variant
119+ @text [font-size 11, color #94a3b8] Level 7 - Item
120+ @el [margin-left 12, margin-top 2, padding 2 6, background #eff6ff, border-radius 2]
121+ @text [font-size 10, color #3b82f6] Level 8 - Variant
122122
123− @el [background #fefce8, rounded 12, padding 20, border 1 #fde68a, margin-top 20]
124− @text [bold, size 20, color #854d0e] Level 1 - Settings Panel
123+ @el [background #fefce8, border-radius 12, padding 20, border 1 solid #fde68a, margin-top 20]
124+ @text [font-weight bold, font-size 20, color #854d0e] Level 1 - Settings Panel
125125 @column [spacing 12, margin-top 16]
126− @el [background white, rounded 8, padding 16, border 1 $--border]
127− @text [bold, size 16, color #1e293b] Level 2 - Account Settings
126+ @el [background white, border-radius 8, padding 16, border 1 solid $--border]
127+ @text [font-weight bold, font-size 16, color #1e293b] Level 2 - Account Settings
128128 @column [spacing 8, margin-top 12]
129− @el [background #f8fafc, rounded 6, padding 12]
130− @text [size 14, color #475569] Level 3 - Profile Section
131− @el [margin-top 8, padding 10, border 1 $--border, rounded 6]
132− @text [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]
134− @text [size 24, color #94a3b8, 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]
136− @text [size 10, color white] Level 6 - Edit Icon
129+ @el [background #f8fafc, border-radius 6, padding 12]
130+ @text [font-size 14, color #475569] Level 3 - Profile Section
131+ @el [margin-top 8, padding 10, border 1 solid $--border, border-radius 6]
132+ @text [font-size 13, color #1e293b] Level 4 - Avatar Upload
133+ @el [margin-top 6, width 80, height 80, border-radius 50%, background #e2e8f0, display flex, align-items center, justify-content center]
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, border-radius 50%, background $--primary, display flex, align-items center, justify-content center]
136+ @text [font-size 10, color white] Level 6 - Edit Icon
137137 @el [margin-top 1]
138− @text [size 8, color #bfdbfe] Level 7 - Tooltip
139− @el [padding 1 3, background #1e3a5f, rounded 2, margin-top 1]
140− @text [size 7, color white] Level 8 - Tooltip Arrow
138+ @text [font-size 8, color #bfdbfe] Level 7 - Tooltip
139+ @el [padding 1 3, background #1e3a5f, border-radius 2, margin-top 1]
140+ @text [font-size 7, color white] Level 8 - Tooltip Arrow
141141
142− @el [background #f8fafc, rounded 6, padding 12]
143− @text [size 14, color #475569] Level 3 - Preferences
142+ @el [background #f8fafc, border-radius 6, padding 12]
143+ @text [font-size 14, color #475569] Level 3 - Preferences
144144 @column [spacing 6, margin-top 8]
145145 @each $pref in Language,Timezone,Currency
146− @row [spacing 8, align-items center, padding 6 8, border 1 $--border, rounded 4]
147− @text [size 13, color #1e293b, width fill] Level 4 - $pref
148− @el [padding 4 12, background #f1f5f9, rounded 4]
149− @text [size 12, color #64748b] Level 5 - Current Value
146+ @row [spacing 8, align-items center, padding 6 8, border 1 solid $--border, border-radius 4]
147+ @text [font-size 13, color #1e293b, width fill] Level 4 - $pref
148+ @el [padding 4 12, background #f1f5f9, border-radius 4]
149+ @text [font-size 12, color #64748b] Level 5 - Current Value
150150
151− @el [background white, rounded 8, padding 16, border 1 $--border]
152− @text [bold, size 16, color #1e293b] Level 2 - Notification Preferences
151+ @el [background white, border-radius 8, padding 16, border 1 solid $--border]
152+ @text [font-weight bold, font-size 16, color #1e293b] Level 2 - Notification Preferences
153153 @column [spacing 6, margin-top 12]
154154 @let channels Email,Push,SMS,Slack
155155 @each $channel in $channels
156− @row [spacing 8, align-items center, padding 8, border-bottom 1 $--border]
157− @text [size 14, color #1e293b, width fill] $channel Notifications
158− @el [width 36, height 20, rounded 10, background $--primary, padding 2]
159− @el [width 16, height 16, rounded 50%, background white, margin-left 16]
156+ @row [spacing 8, align-items center, padding 8, border-bottom 1 solid $--border]
157+ @text [font-size 14, color #1e293b, width fill] $channel Notifications
158+ @el [width 36, height 20, border-radius 10, background $--primary, padding 2]
159+ @el [width 16, height 16, border-radius 50%, background white, margin-left 16]
160160
161− @el [background #fdf2f8, rounded 12, padding 20, border 1 #fbcfe8, margin-top 20]
162− @text [bold, size 20, color #9d174d] Level 1 - Monitoring
161+ @el [background #fdf2f8, border-radius 12, padding 20, border 1 solid #fbcfe8, margin-top 20]
162+ @text [font-weight bold, font-size 20, color #9d174d] Level 1 - Monitoring
163163 @row [spacing 16, margin-top 16]
164164 @each $service in API,Database,Cache,Queue
165− @el [background white, rounded 8, padding 12, border 1 $--border, width fill]
166− @text [bold, size 14, color #1e293b] Level 2 - $service
167− @el [margin-top 8, background #f0fdf4, rounded 6, padding 8]
168− @text [size 12, color #166534] Level 3 - Healthy
165+ @el [background white, border-radius 8, padding 12, border 1 solid $--border, width fill]
166+ @text [font-weight bold, font-size 14, color #1e293b] Level 2 - $service
167+ @el [margin-top 8, background #f0fdf4, border-radius 6, padding 8]
168+ @text [font-size 12, color #166534] Level 3 - Healthy
169169 @el [margin-top 4]
170170 @row [spacing 4]
171− @el [width fill, height 4, background #86efac, rounded 2]
172− @el [width fill, height 4, background #4ade80, rounded 2]
173− @el [width fill, height 4, background #22c55e, rounded 2]
174− @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, border-radius 2]
173+ @el [width fill, height 4, background #22c55e, border-radius 2]
174+ @el [width fill, height 4, background #86efac, border-radius 2]
175175 @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%
177177 @el [margin-top 2]
178− @text [size 10, color #22c55e] Level 5 - Response 42ms
179− @el [margin-top 2, padding 2 6, background #dcfce7, rounded 2]
180− @text [size 9, color #15803d] Level 6 - p99 120ms
178+ @text [font-size 10, color #22c55e] Level 5 - Response 42ms
179+ @el [margin-top 2, padding 2 6, background #dcfce7, border-radius 2]
180+ @text [font-size 9, color #15803d] Level 6 - p99 120ms
181181 @el [margin-top 1]
182− @text [size 8, color #4ade80] Level 7 - Throughput 1.2k/s
183− @el [padding 1 3, background #bbf7d0, rounded 1, margin-top 1]
184− @text [size 7, color #166534] Level 8 - Peak 2.4k/s
182+ @text [font-size 8, color #4ade80] Level 7 - Throughput 1.2k/s
183+ @el [padding 1 3, background #bbf7d0, border-radius 1, margin-top 1]
184+ @text [font-size 7, color #166534] Level 8 - Peak 2.4k/s
tests/snapshots/stress_large.hl+122−122
@@ -24,94 +24,94 @@
2424 }
2525
2626@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]
2828 $label
2929
3030@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]
3232 @text $label
3333
3434@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]
36− @text [bold, size 18, color $--dark, dark:color white] $title
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 [font-weight bold, font-size 18, color $--dark, dark:color white] $title
3737 @el [margin-top 8]
3838 @children
3939
4040@let stat-card $label $value $change
41− @el [background white, dark:background #1e293b, rounded $--radius, padding $--gap, border 1 $--border, dark:border 1 #334155]
42− @text [size 14, color $--gray] $label
43− @text [bold, size 28, color $--dark, dark:color white, margin-top 4] $value
44− @text [size 12, color $--secondary, margin-top 2] $change
41+ @el [background white, dark:background #1e293b, border-radius $--radius, padding $--gap, border 1 solid $--border, dark:border 1 solid #334155]
42+ @text [font-size 14, color $--gray] $label
43+ @text [font-weight bold, font-size 28, color $--dark, dark:color white, margin-top 4] $value
44+ @text [font-size 12, color $--secondary, margin-top 2] $change
4545
4646@let nav-link $url $label $active=false
4747 @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
4949 @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
5151
5252@let sidebar-section $title
5353 @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
5555 @children
5656
5757@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]
5959 @text $initials
6060
6161@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]
6363 @el [width 200]
64− @text [size 14] $col1
64+ @text [font-size 14] $col1
6565 @el [width 150]
66− @text [size 14, color $--gray] $col2
66+ @text [font-size 14, color $--gray] $col2
6767 @el [width 120]
68− @text [size 14] $col3
68+ @text [font-size 14] $col3
6969 @el [width fill]
70− @text [size 14, color $--gray] $col4
70+ @text [font-size 14, color $--gray] $col4
7171
7272@let breadcrumb-item $url $label $is-last=false
7373 @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
7575 @else
7676 @row [spacing 8]
77− @link [color $--gray, hover:color $--primary, size 14] $url $label
78− @text [color $--border, size 14] /
77+ @link [color $--gray, hover:color $--primary, font-size 14] $url $label
78+ @text [color $--border, font-size 14] /
7979
8080@let alert $message $type=info
8181 @if $type == info
82− @el [background #eff6ff, border 1 #bfdbfe, rounded $--radius, padding 12 16]
83− @text [color #1e40af, size 14] $message
82+ @el [background #eff6ff, border 1 solid #bfdbfe, border-radius $--radius, padding 12 16]
83+ @text [color #1e40af, font-size 14] $message
8484 @else if $type == success
85− @el [background #f0fdf4, border 1 #bbf7d0, rounded $--radius, padding 12 16]
86− @text [color #166534, size 14] $message
85+ @el [background #f0fdf4, border 1 solid #bbf7d0, border-radius $--radius, padding 12 16]
86+ @text [color #166534, font-size 14] $message
8787 @else if $type == warning
88− @el [background #fffbeb, border 1 #fde68a, rounded $--radius, padding 12 16]
89− @text [color #92400e, size 14] $message
88+ @el [background #fffbeb, border 1 solid #fde68a, border-radius $--radius, padding 12 16]
89+ @text [color #92400e, font-size 14] $message
9090 @else
91− @el [background #fef2f2, border 1 #fecaca, rounded $--radius, padding 12 16]
92− @text [color #991b1b, size 14] $message
91+ @el [background #fef2f2, border 1 solid #fecaca, border-radius $--radius, padding 12 16]
92+ @text [color #991b1b, font-size 14] $message
9393
9494@let progress-bar $percent $color=var(--primary)
95− @el [width fill, height 8, background #e2e8f0, dark:background #334155, rounded 4, overflow hidden]
96− @el [width $percent%, height fill, background $color, rounded 4]
95+ @el [width fill, height 8, background #e2e8f0, dark:background #334155, border-radius 4, overflow hidden]
96+ @el [width $percent%, height fill, background $color, border-radius 4]
9797
9898@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]
100100 @text $label
101101
102102@let notification-dot
103− @el [width 8, height 8, rounded 50%, background $--danger]
103+ @el [width 8, height 8, border-radius 50%, background $--danger]
104104
105105@let divider
106106 @el [width fill, height 1, background $--border, dark:background #334155, margin 8 0]
107107
108108@let toggle $checked=false
109109 @if $checked == true
110− @el [width 40, height 22, rounded 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]
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, border-radius 50%, background white, margin-left 18, transition all 0.2s]
112112 @else
113− @el [width 40, height 22, rounded 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]
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, border-radius 50%, background white, transition all 0.2s]
115115
116116@let nav-items home,products,team,blog,docs,pricing
117117@let categories all,design,development,marketing,analytics
@@ -121,13 +121,13 @@
121121
122122@row [spacing 0, min-height 100vh]
123123
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]
125125
126126 @row [spacing 12, padding 8, align-items center]
127127 @avatar [initials HL, size 36]
128128 @column [spacing 2]
129− @text [bold, size 16, color $--dark, dark:color white] htmlang
130− @text [size 12, color $--gray] Dashboard v2.0
129+ @text [font-weight bold, font-size 16, color $--dark, dark:color white] htmlang
130+ @text [font-size 12, color $--gray] Dashboard v2.0
131131
132132 @divider
133133
@@ -151,36 +151,36 @@
151151 @sidebar-section [title Shortcuts]
152152 @column [spacing 4]
153153 @row [spacing 8, align-items center]
154− @el [width 6, height 6, rounded 50%, background $--secondary]
155− @text [size 13, color $--gray] Online (12)
154+ @el [width 6, height 6, border-radius 50%, background $--secondary]
155+ @text [font-size 13, color $--gray] Online (12)
156156 @row [spacing 8, align-items center]
157− @el [width 6, height 6, rounded 50%, background $--accent]
158− @text [size 13, color $--gray] Away (3)
157+ @el [width 6, height 6, border-radius 50%, background $--accent]
158+ @text [font-size 13, color $--gray] Away (3)
159159 @row [spacing 8, align-items center]
160− @el [width 6, height 6, rounded 50%, background #cbd5e1]
161− @text [size 13, color $--gray] Offline (5)
160+ @el [width 6, height 6, border-radius 50%, background #cbd5e1]
161+ @text [font-size 13, color $--gray] Offline (5)
162162
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]
164164 @row [spacing 8, align-items center]
165165 @avatar [initials JD, size 28]
166166 @column [spacing 0]
167− @text [size 13, bold, color $--dark, dark:color white] Jane Doe
168− @text [size 11, color $--gray] jane@example.com
167+ @text [font-size 13, font-weight bold, color $--dark, dark:color white] Jane Doe
168+ @text [font-size 11, color $--gray] jane@example.com
169169
170170 @column [width fill, background $--light, dark:background #0f172a]
171171
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]
173173 @row [spacing 16, align-items center]
174174 @column [spacing 2]
175175 @row [spacing 8]
176176 @breadcrumb-item [url /, label Home]
177177 @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
179179
180180 @el [width fill]
181181
182182 @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]
184184 @el [position relative]
185185 @icon-button [label Alerts]
186186 @el [position absolute, top -2, right -2]
@@ -200,19 +200,19 @@
200200 @row [spacing $--gap, margin-top $--gap-lg]
201201
202202 @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]
204204 @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]
206206
207207 @card [title Activity Feed]
208208 @column [spacing 12, margin-top 12]
209209 @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]
211211 @avatar [initials $member, size 32]
212212 @column [spacing 2, width fill]
213− @text [size 14, bold, color $--dark, dark:color white] $member
214− @text [size 12, color $--gray] Updated project files
215− @text [size 11, color $--gray] 2h ago
213+ @text [font-size 14, font-weight bold, color $--dark, dark:color white] $member
214+ @text [font-size 12, color $--gray] Updated project files
215+ @text [font-size 11, color $--gray] 2h ago
216216
217217 @row [spacing $--gap, margin-top $--gap-lg, sm:flex-direction column, lg:flex-direction row]
218218
@@ -220,15 +220,15 @@
220220
221221 @card [title Project Status]
222222 @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]
224224 @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
226226 @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
228228 @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
230230 @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
232232
233233 @table-row [col1 Project Alpha, col2 In Progress, col3 75%, col4 Dec 15 2025]
234234 @table-row [col1 Project Beta, col2 Review, col3 90%, col4 Nov 30 2025]
@@ -241,8 +241,8 @@
241241 @each $project in $projects
242242 @column [spacing 4]
243243 @row [spacing 0]
244− @text [size 13, color $--dark, dark:color white, width fill] $project
245− @text [size 13, color $--gray] 67%
244+ @text [font-size 13, color $--dark, dark:color white, width fill] $project
245+ @text [font-size 13, color $--gray] 67%
246246 @progress-bar [percent 67, color var(--primary)]
247247
248248 @card [title Recent Activity Log]
@@ -250,10 +250,10 @@
250250 @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
251251 @each $entry, $i in $log-entries
252252 @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]
254− @text [size 10, color $--primary, bold] $i
255− @text [size 14, color $--dark, dark:color white, width fill] $entry
256− @text [size 11, color $--gray] just now
253+ @el [width 24, height 24, border-radius 50%, background #eff6ff, dark:background #1e3a5f, display flex, align-items center, justify-content center]
254+ @text [font-size 10, color $--primary, font-weight bold] $i
255+ @text [font-size 14, color $--dark, dark:color white, width fill] $entry
256+ @text [font-size 11, color $--gray] just now
257257
258258 @column [width 340, sm:width fill, spacing $--gap]
259259
@@ -267,12 +267,12 @@
267267 @card [title Team Members]
268268 @column [spacing 8, margin-top 12]
269269 @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]
271271 @avatar [initials $member, size 28]
272272 @column [spacing 0, width fill]
273− @text [size 13, bold, color $--dark, dark:color white] $member
274− @text [size 11, color $--gray] Developer
275− @el [width 8, height 8, rounded 50%, background $--secondary]
273+ @text [font-size 13, font-weight bold, color $--dark, dark:color white] $member
274+ @text [font-size 11, color $--gray] Developer
275+ @el [width 8, height 8, border-radius 50%, background $--secondary]
276276
277277 @card [title Tags]
278278 @row [spacing 0, wrap, margin-top 8]
@@ -293,104 +293,104 @@
293293 @column [spacing 0, margin-top 12]
294294 @let notifications New deployment completed,PR review requested,Build failed on staging,Meeting in 30 minutes,Subscription renewal due
295295 @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]
297297 @notification-dot
298− @text [size 13, color $--dark, dark:color white, width fill] $note
299− @text [size 11, color $--gray] now
298+ @text [font-size 13, color $--dark, dark:color white, width fill] $note
299+ @text [font-size 11, color $--gray] now
300300
301301 @card [title Settings]
302302 @column [spacing 12, margin-top 12]
303303 @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
305305 @toggle [checked true]
306306 @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
308308 @toggle [checked true]
309309 @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
311311 @toggle [checked false]
312312 @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
314314 @toggle [checked false]
315315
316316 @el [margin-top $--gap-lg]
317317 @card [title Data Table]
318318 @column [spacing 0, margin-top 12]
319319 @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]
321321 @el [width fill]
322322 @row [spacing 8]
323323 @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
325325
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]
327327 @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
329329 @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
331331 @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
333333 @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
335335 @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
337337
338338 @each $status in $statuses
339339 @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]
341341 @el [width 60]
342− @text [size 13, color $--gray] #42
342+ @text [font-size 13, color $--gray] #42
343343 @el [width 200]
344344 @row [spacing 8, align-items center]
345345 @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
347347 @el [width 150]
348− @text [size 13, color $--gray] $status
348+ @text [font-size 13, color $--gray] $status
349349 @el [width 120]
350350 @badge [label $status, variant var(--secondary)]
351351 @el [width fill]
352− @text [size 13, color $--gray] 2 hours ago
352+ @text [font-size 13, color $--gray] 2 hours ago
353353
354− @row [spacing 16, padding 12 16, align-items center, border-top 1 $--border]
355− @text [size 13, color $--gray] Showing 1-20 of 142 results
354+ @row [spacing 16, padding 12 16, align-items center, border-top 1 solid $--border]
355+ @text [font-size 13, color $--gray] Showing 1-20 of 142 results
356356 @el [width fill]
357357 @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
359− @button [padding 6 10, rounded 4, border 1 $--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
361− @button [padding 6 10, rounded 4, border 1 $--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
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, border-radius 4, border 1 solid $--primary, background $--primary, color white, font-size 13, cursor pointer] 1
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, 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, border-radius 4, border 1 solid $--border, background white, dark:background #1e293b, color $--gray, font-size 13, cursor pointer] Next
363363
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]
365365 @row [spacing $--gap-lg, sm:flex-direction column, lg:flex-direction row]
366366 @column [spacing 8, width fill]
367− @text [bold, size 14, color $--dark, dark:color white] htmlang Dashboard
368− @text [size 12, color $--gray] Built with htmlang - a declarative HTML language
369− @text [size 11, color $--gray] Copyright 2025 htmlang contributors
367+ @text [font-weight bold, font-size 14, color $--dark, dark:color white] htmlang Dashboard
368+ @text [font-size 12, color $--gray] Built with htmlang - a declarative HTML language
369+ @text [font-size 11, color $--gray] Copyright 2025 htmlang contributors
370370
371371 @column [spacing 4]
372− @text [bold, size 12, color $--gray, text-transform uppercase, margin-bottom 4] Product
373− @link [color $--gray, hover:color $--primary, size 13] /features Features
374− @link [color $--gray, hover:color $--primary, size 13] /pricing Pricing
375− @link [color $--gray, hover:color $--primary, size 13] /changelog Changelog
376− @link [color $--gray, hover:color $--primary, size 13] /roadmap Roadmap
372+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase, margin-bottom 4] Product
373+ @link [color $--gray, hover:color $--primary, font-size 13] /features Features
374+ @link [color $--gray, hover:color $--primary, font-size 13] /pricing Pricing
375+ @link [color $--gray, hover:color $--primary, font-size 13] /changelog Changelog
376+ @link [color $--gray, hover:color $--primary, font-size 13] /roadmap Roadmap
377377
378378 @column [spacing 4]
379− @text [bold, size 12, color $--gray, text-transform uppercase, margin-bottom 4] Resources
380− @link [color $--gray, hover:color $--primary, size 13] /docs Documentation
381− @link [color $--gray, hover:color $--primary, size 13] /api API Reference
382− @link [color $--gray, hover:color $--primary, size 13] /guides Guides
383− @link [color $--gray, hover:color $--primary, size 13] /examples Examples
379+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase, margin-bottom 4] Resources
380+ @link [color $--gray, hover:color $--primary, font-size 13] /docs Documentation
381+ @link [color $--gray, hover:color $--primary, font-size 13] /api API Reference
382+ @link [color $--gray, hover:color $--primary, font-size 13] /guides Guides
383+ @link [color $--gray, hover:color $--primary, font-size 13] /examples Examples
384384
385385 @column [spacing 4]
386− @text [bold, size 12, color $--gray, text-transform uppercase, margin-bottom 4] Company
387− @link [color $--gray, hover:color $--primary, size 13] /about About
388− @link [color $--gray, hover:color $--primary, size 13] /blog Blog
389− @link [color $--gray, hover:color $--primary, size 13] /careers Careers
390− @link [color $--gray, hover:color $--primary, size 13] /contact Contact
386+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase, margin-bottom 4] Company
387+ @link [color $--gray, hover:color $--primary, font-size 13] /about About
388+ @link [color $--gray, hover:color $--primary, font-size 13] /blog Blog
389+ @link [color $--gray, hover:color $--primary, font-size 13] /careers Careers
390+ @link [color $--gray, hover:color $--primary, font-size 13] /contact Contact
391391
392392 @column [spacing 4]
393− @text [bold, size 12, color $--gray, text-transform uppercase, margin-bottom 4] Legal
394− @link [color $--gray, hover:color $--primary, size 13] /privacy Privacy Policy
395− @link [color $--gray, hover:color $--primary, size 13] /terms Terms of Service
396− @link [color $--gray, hover:color $--primary, size 13] /cookies Cookie Policy
393+ @text [font-weight bold, font-size 12, color $--gray, text-transform uppercase, margin-bottom 4] Legal
394+ @link [color $--gray, hover:color $--primary, font-size 13] /privacy Privacy Policy
395+ @link [color $--gray, hover:color $--primary, font-size 13] /terms Terms of Service
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 @@
11@page Variables
22@let primary #3b82f6
33@let gap 20
4−@let card-style [padding 20, rounded 8, background white]
4+@let card-style [padding 20, border-radius 8, background white]
55
66@column [spacing $gap]
77 @el [$card-style]
88 @text [color $primary] Hello
9− @el [$card-style, border 1 $primary]
9+ @el [$card-style, border 1 solid $primary]
1010 @text World
tests/snapshots/view_transitions.hl+1−1
@@ -1,6 +1,6 @@
11@page View Transitions
22@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]
44 Hero element
55 @el [view-transition-name sidebar, padding 10]
66 Sidebar element