diff --git a/.gitattributes b/.gitattributes index a36bf9195..83b87289d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -29,3 +29,7 @@ *.jpg binary *.pdf binary *.sqlite binary + +# A torture fixture whose CRLF line endings ARE the trap it sets (a BOM and CRLF in one +# page); normalising it would remove the case the suite asserts. +parser/src/test-data/web/torture/quirks.html -text diff --git a/bin/axiomcode b/bin/axiomcode index 313b110aa..c3948dfad 100755 --- a/bin/axiomcode +++ b/bin/axiomcode @@ -393,7 +393,7 @@ case "$cmd" in run_suite(){ local s="$1"; shift; echo "══ $s"; bash "$ROOT/graph/test/$s/run-tests.sh" "$@" || rc=1; } case "$which" in java|typescript|python|javascript|csharp) run_suite "$which" "$@";; - parser) for t in java-extractor-tests typescript-tests python-tests javascript-tests csharp-tests gradle-tests services-tests discovery-tests; do + parser) for t in java-extractor-tests typescript-tests python-tests javascript-tests csharp-tests gradle-tests services-tests web-tests web-torture discovery-tests; do [ -f "$ROOT/parser/src/test/$t.ts" ] || continue; echo "══ parser: $t"; (cd "$ROOT/parser" && npx tsx "src/test/$t.ts") || rc=1; done;; all) for l in java typescript python javascript csharp; do run_suite "$l" "$@"; done; "$0" test parser || rc=1;; *) die "test: unknown suite $which (java | typescript | python | javascript | csharp | parser | all)";; diff --git a/package.json b/package.json index 4fd812a5e..426f256c3 100644 --- a/package.json +++ b/package.json @@ -55,10 +55,13 @@ "LICENSE.md" ], "dependencies": { + "entities": "^6.0.0", "sax": "^1.4.4", "tree-sitter": "^0.21.1", "tree-sitter-c-sharp": "0.23.1", + "tree-sitter-css": "0.23.0", "tree-sitter-groovy": "^0.1.2", + "tree-sitter-html": "0.23.2", "tree-sitter-java": "0.23.4", "tree-sitter-python": "^0.21.0", "typescript": "^5.3.3", diff --git a/parser/README.md b/parser/README.md index c6c5517ab..5b199ebaf 100644 --- a/parser/README.md +++ b/parser/README.md @@ -89,6 +89,23 @@ same tables and be compared. | **YAML** | Beta | 2 | yaml | Configuration entries with anchor and alias tracking, multi document support. | | **META-INF/services** | Stable | 2 | custom | Provider-configuration files: the service each file configures, taken from its name, and every implementation class it names, with the file and line. | | **Gradle** | Beta | 8 | tree-sitter-groovy | Scripts and their role in the build, blocks, declarations, dependency coordinates split into group/artifact/version, version catalogs, value references with resolution, comments, and parse gaps. Groovy and Kotlin DSL. | +| **HTML** | Beta | 9 | tree-sitter-html | Documents with their doctype and template dialects, the element tree as written (nothing a browser would imply) with XPath-like paths, attributes by kind, class tokens, every URL a page names (scripts, stylesheets, links, images, forms, frames, htmx requests) classified and resolved to a file where one exists, `` — the body is the code; `bodyStartLine`…`bodyEndLine` locate it. */ + INLINE = 'INLINE', +} + +/** + * How the browser treats a ` +
x

${name}

+ diff --git a/parser/src/test-data/web/fixture/app/templates/mixed.html b/parser/src/test-data/web/fixture/app/templates/mixed.html new file mode 100644 index 000000000..18100565c --- /dev/null +++ b/parser/src/test-data/web/fixture/app/templates/mixed.html @@ -0,0 +1,12 @@ +@model Shop.Models.Cart + +
+ + + + +
alpine
+
diff --git a/parser/src/test-data/web/fixture/docs/apidocs/element-list b/parser/src/test-data/web/fixture/docs/apidocs/element-list new file mode 100644 index 000000000..b80f0bd60 --- /dev/null +++ b/parser/src/test-data/web/fixture/docs/apidocs/element-list @@ -0,0 +1 @@ +app diff --git a/parser/src/test-data/web/fixture/docs/apidocs/index.html b/parser/src/test-data/web/fixture/docs/apidocs/index.html new file mode 100644 index 000000000..34e348891 --- /dev/null +++ b/parser/src/test-data/web/fixture/docs/apidocs/index.html @@ -0,0 +1 @@ +

javadoc

diff --git a/parser/src/test-data/web/fixture/site/index.html b/parser/src/test-data/web/fixture/site/index.html new file mode 100644 index 000000000..f20f8896d --- /dev/null +++ b/parser/src/test-data/web/fixture/site/index.html @@ -0,0 +1,48 @@ + + + + + Fixture site + + + + + + + + + + + +
+

Hello

unclosed paragraph + + hero +

+ + + +
+ toggle + template handler + + + + +
+ + + + + diff --git a/parser/src/test-data/web/fixture/site/js/app.js b/parser/src/test-data/web/fixture/site/js/app.js new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/fixture/site/js/vendor.js b/parser/src/test-data/web/fixture/site/js/vendor.js new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/fixture/site/page.html b/parser/src/test-data/web/fixture/site/page.html new file mode 100644 index 000000000..9595250cc --- /dev/null +++ b/parser/src/test-data/web/fixture/site/page.html @@ -0,0 +1,18 @@ + + +Second + + + + + + +

Second

+ + +self +

A page with an unclosed tag and a stray end tag.

+
+
implied tbody
+ + diff --git a/parser/src/test-data/web/fixture/site/partials/frame.html b/parser/src/test-data/web/fixture/site/partials/frame.html new file mode 100644 index 000000000..323136fbe --- /dev/null +++ b/parser/src/test-data/web/fixture/site/partials/frame.html @@ -0,0 +1,6 @@ +
+

{% block title %}Partial{% endblock %}

+ Home + bad style + +
diff --git a/parser/src/test-data/web/fixture/site/static/css/app.css b/parser/src/test-data/web/fixture/site/static/css/app.css new file mode 100644 index 000000000..dd2d582c4 --- /dev/null +++ b/parser/src/test-data/web/fixture/site/static/css/app.css @@ -0,0 +1,20 @@ +@charset "utf-8"; +/* theme tokens */ +@import url("base.css") layer(base); +@import "missing.css"; +@layer base, components; +:root { --gap: 8px; --brand: #09f; } +.nav > li.item:not(.hidden) a[href^="http" i]:hover::after { color: var(--brand, red); gap: var(--gap) } +#main .card, .card--wide, ul li:nth-child(2n of .item) { background: url(../img/bg.png) no-repeat; animation: spin 1s linear infinite; font-family: "Inter", Segoe UI, sans-serif } +@media (min-width: 40em) { .card { padding: calc(var(--gap) * 2) !important } } +@keyframes spin { from { transform: rotate(0) } 50% { opacity: .5 } to { transform: rotate(1turn) } } +@font-face { font-family: "Inter"; src: url(/fonts/inter.woff2) format("woff2") } +@supports (display: grid) { .grid { display: grid } } +@container sidebar (min-width: 400px) { .card { gap: 1rem } } +.sidebar { container-name: sidebar; container-type: inline-size } +.a { color: blue; .b & { color: red } &:hover { color: green } } +.broken { color: ; } +.title::before, .title:before { content: "" } +* { box-sizing: border-box } +h1 + p ~ em { margin: 0 } +col || td { margin: 0 } diff --git a/parser/src/test-data/web/fixture/site/static/css/base.css b/parser/src/test-data/web/fixture/site/static/css/base.css new file mode 100644 index 000000000..7493ecf6a --- /dev/null +++ b/parser/src/test-data/web/fixture/site/static/css/base.css @@ -0,0 +1 @@ +body { margin: 0; -webkit-font-smoothing: antialiased } diff --git a/parser/src/test-data/web/fixture/site/static/css/empty.css b/parser/src/test-data/web/fixture/site/static/css/empty.css new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/fixture/site/static/css/vendor.min.css b/parser/src/test-data/web/fixture/site/static/css/vendor.min.css new file mode 100644 index 000000000..d030db289 --- /dev/null +++ b/parser/src/test-data/web/fixture/site/static/css/vendor.min.css @@ -0,0 +1 @@ +.a{color:red}.b{color:blue}.c{margin:0;padding:0} diff --git a/parser/src/test-data/web/fixture/site/static/img/hero.png b/parser/src/test-data/web/fixture/site/static/img/hero.png new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/fixture/site/static/img/logo.png b/parser/src/test-data/web/fixture/site/static/img/logo.png new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/fixture/site/static/img/sprite.svg b/parser/src/test-data/web/fixture/site/static/img/sprite.svg new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/fixture/site/vendor/sassy.css b/parser/src/test-data/web/fixture/site/vendor/sassy.css new file mode 100644 index 000000000..14a04a99f --- /dev/null +++ b/parser/src/test-data/web/fixture/site/vendor/sassy.css @@ -0,0 +1,3 @@ +$primary: #333; +@mixin flex { display: flex; } +.x { color: $primary; @include flex; } diff --git a/parser/src/test-data/web/torture/css/alt.css b/parser/src/test-data/web/torture/css/alt.css new file mode 100644 index 000000000..97121e944 --- /dev/null +++ b/parser/src/test-data/web/torture/css/alt.css @@ -0,0 +1 @@ +.alt-only { color: red } diff --git a/parser/src/test-data/web/torture/css/app.css b/parser/src/test-data/web/torture/css/app.css new file mode 100644 index 000000000..04e4d5d50 --- /dev/null +++ b/parser/src/test-data/web/torture/css/app.css @@ -0,0 +1,237 @@ +@charset "utf-8"; +/* C01 import chain: app -> tokens -> theme; layered, conditional, missing, absolute, in a layer and with media */ +@import url("tokens.css") layer(tokens); +@import "layers.css"; +@import url(nested.css) supports(selector(&)) screen and (min-width: 1px); +@import "missing.css"; +@import url("https://fonts.example.com/inter.css"); +@import "strings.css" layer(vendor.strings); +@import "fonts.css"; +@import 'selectors.css'; +@import url( "gaps.css" ); +@import "app.css"; /* self import: a cycle for the recursive join */ + +@layer tokens, base, components, utilities; +@namespace svg url(http://www.w3.org/2000/svg); +@namespace url(http://www.w3.org/1999/xhtml); + +/* C02 the class joins the HTML fixtures are built around */ +.card { color: var(--brand); } +.card--wide { max-width: var(--wide, 60rem); } +.is-active { outline: 1px solid var(--brand, var(--fallback-brand, red)); } +.nav > a[href^="#"] { color: var(--anchor); } +.upper { color: red } /* HTML has Upper: no join by case */ +.SHOUT { color: red } /* HTML has SHOUT via CLASS="SHOUT": joins */ +#loud { color: red } /* HTML has ID="LOUD": no join by case */ +#LOUD { color: red } +#padded-id { color: red } /* HTML id=" padded-id ": trimmed on the element row */ +.dup { color: red } +.a\&b { color: red } /* HTML class a&b decodes to a&b */ +.tpl-class, .hbs-class, .ko-class, .srcdoc-class { color: red } /* classes written only inside script/srcdoc text */ +.in-template, .in-template-style { color: red } +.noscript-class, .ie-only, .commented-out, .in-jinja-comment { color: red } +.js-ready, .open { color: red } /* classes only a script adds */ +.never-used-anywhere { color: red } /* dead CSS */ +.div-in-p, .li-one, .li-two, .cell, .after-self-closing-div, .unclosed-at-eof { color: red } +.custom, .customized-builtin, .hidden-attr, .dup-attr { color: red } +.static-class, .item, .wrapper, .ng-static, .vue-static, .alpine-static, .htmx-static { color: red } +.xhtml-class, .cdata-class, .after-xml-self-closing { color: red } +.in-foreign, .in-annotation, .math-class, .nested-rect, .svg-rect, .use-class, .symbol-class { color: red } +.popover, .light-card, .shadow-only, .closed-only, .slotted-item, .default-slotted, .inert-class, .deep { color: red } + +/* C03 structural selectors: the HTML writes no tbody, no html, no head, no body */ +table > tbody > tr > td.cell { color: red } +table tr td.cell { color: blue } +html > body > div.card { color: red } +body > .card { color: red } +ul > li.li-two { color: red } +p > div.div-in-p { color: red } +div > span.after-self-closing-div { color: red } +a a { color: red } + +/* C04 functional pseudo-classes: parts at depth > 0 */ +:is(.card, .nav) .item { color: red } +:where(.card) .item { color: red } +.card:not(.is-active, #section-one) { color: red } +.card:has(> .item:hover, + .nav) { color: red } +li:nth-child(2n + 1 of .li-one, .li-two) { color: red } +:nth-last-child(-n+2) { color: red } +:not(:is(.a, .b)) { color: red } +.card:is(:hover, :focus-visible):not([disabled]) { color: red } +a:not([href]) { color: red } +:host(.featured) { color: red } +:host-context(.dark) .card { color: red } +::slotted(.slotted-item) { color: red } +my-card::part(title) { color: red } +input::placeholder, ::selection, ::backdrop, ::marker, ::file-selector-button, ::highlight(h), ::view-transition-group(root), ::cue(v[voice="a"]) { color: red } +:lang(en), :dir(rtl), :state(checked), :focus-within, :target, :empty, :root, :scope, :defined, :popover-open, :modal, :user-invalid, :placeholder-shown, :indeterminate, :read-write, :autofill, :fullscreen, :picture-in-picture, :playing, :paused, :link, :any-link, :visited, :local-link, :target-within, :current, :past, :future, :active-view-transition { color: red } + +/* C05 attribute selectors: every matcher, flags, quoted/unquoted, namespaced */ +[data-page] { color: red } +[data-page="index"] { color: red } +[data-page=index] { color: red } +[class~="card"] { color: red } +[class*="card"] { color: red } +[class^=card] { color: red } +[class$="wide" i] { color: red } +[class$="wide" s] { color: red } +[id|="section"] { color: red } +[href$=".pdf" i] { color: red } +[xlink|href] { color: red } +[*|href] { color: red } +[|href] { color: red } +a[target][href][rel] { color: red } +[aria-labelledby~="section-one"] { color: red } +[style*="var("] { color: red } +input[type="IMAGE" i] { color: red } + +/* C06 combinators and universal/namespace selectors */ +* { box-sizing: border-box } +*|* { color: red } +svg|* { color: red } +|rect { color: red } +svg|rect.svg-rect { color: red } +h2 + p ~ em > b { color: red } +.a>.b+.c~.d { color: red } +col || td { color: red } +.a .b .c +.d { color: red } +.card/* comment in a selector */.is-active { color: red } +.card, /* comment between selectors */ .nav, { color: red } +.card,, .nav { color: red } +.card . broken { color: red } +.123 { color: red } +.-valid-dash { color: red } +.--double-dash { color: red } +._underscore { color: red } +#1digit { color: red } +.a.b.c.d.e.f.g.h.i.j.k { color: red } +.a#b.c#d { color: red } +div#b.c[x]:hover::before { color: red } +DIV.Card:HOVER::BEFORE { color: red } +.before:before, .after::after, ::before, :after, ::first-line, ::first-letter, :first-letter { color: red } + +/* C07 the cascade torture: same element, same property, many sources */ +#section-one { color: rgb(1, 1, 1) } /* (1,0,0) */ +.card.card--wide.is-active { color: rgb(2, 2, 2) } /* (0,3,0) */ +section.card { color: rgb(3, 3, 3) !important } /* important beats everything unlayered */ +@layer base { .card { color: rgb(4, 4, 4) } section#section-one { color: rgb(5, 5, 5) !important } } +@layer utilities { .card { color: rgb(6, 6, 6) } } +@layer { .card { color: rgb(7, 7, 7) } } /* anonymous layer */ +.card { color: rgb(8, 8, 8) } +.card { color: rgb(9, 9, 9) } /* later in source order wins over the one above */ +:where(.card.card--wide.is-active#section-one) { color: rgb(10, 10, 10) } /* (0,0,0) */ +:is(#section-one, .card) { color: rgb(11, 11, 11) } /* (1,0,0) via :is */ +.card:not(#nope) { color: rgb(12, 12, 12) } /* (1,1,0) via :not */ +[id="section-one"] { color: rgb(13, 13, 13) } /* (0,1,0) attribute, not id */ +@media screen { .card { color: rgb(14, 14, 14) } } +@media print { .card { color: rgb(15, 15, 15) } } +@supports (color: red) { .card { color: rgb(16, 16, 16) } } +@supports not (display: grid) { .card { color: rgb(17, 17, 17) } } +@container sidebar (min-width: 1px) { .card { color: rgb(18, 18, 18) } } +@scope (.card) to (.item) { :scope { color: rgb(19, 19, 19) } .is-active { color: rgb(20, 20, 20) } } +@media (400px <= width <= 700px) { .card { color: rgb(21, 21, 21) } } +@media only screen and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .card { color: rgb(22, 22, 22) } } +@media (prefers-color-scheme: dark) { :root { --brand: black } .card { color: var(--brand) } } +@starting-style { .card { opacity: 0 } } +@page :first { margin: 1in } +@page { @top-center { content: "x" } } +@font-feature-values Inter { @styleset { nice-style: 12 } } +@counter-style thumbs { system: cyclic; symbols: "👍"; suffix: " " } +@position-try --fallback { top: anchor(bottom) } +@view-transition { navigation: auto } +@-moz-document url-prefix() { .card { color: red } } +@-webkit-keyframes vendor-spin { from { opacity: 0 } to { opacity: 1 } } +@media screen { @supports (display: grid) { @layer components { .card .item { color: rgb(23, 23, 23) } } } } + +/* C08 properties: custom, vendor, hacks, important variants, duplicates, uppercase, empty */ +.props { + --Brand: red; /* custom properties are case-sensitive: --Brand is not --brand */ + --brand: blue; + --empty:; + --with-brace: { a: b }; + --json: [1, 2, 3]; + --url: url(img/bg.png); + --nested: var(--brand, var(--Brand, var(--missing, red))); + color: var(--Brand); + background: var(--url); + -webkit-transition: all 1s; + -moz-transition: all 1s; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#80000000'); + *zoom: 1; + _height: 1px; + color: red\9; + width: expression(document.body.clientWidth > 800 ? "800px" : "auto"); + margin: 0 ! important; + padding: 0!important; + color: red !IMPORTANT; + color: red;; + color: ; + : red; + color red; + COLOR: RED; + Color: Red; + display: grid; display: -ms-grid; + grid-template-areas: "a b" "c d"; + content: "a; } b { c: d }"; /* string with declaration-ending characters */ + content: "var(--not-a-var) url(not-a-url.png)"; /* string that looks like functions */ + background: url( "img/bg.png" ); + background: url('img/bg.png?v=1#frag'); + background: url(img/bg.png?v=1#frag); + background: url( img/bg.png ); + background: url("img/space name.png"); + background: url(img/space\ name.png); + background: url(data:image/png;base64,iVBORw0KGgo=); + background: url(//cdn.example.com/x.png); + background: url(/img/bg.png); + background: url(../img/bg.png); + background: url(#inline-ref); + background: url(); + background: url(""), url(img/bg.png), url(img/missing.png); + background: image-set("img/hero.png" 1x, "img/hero@2x.png" 2x); + background: -webkit-image-set(url(img/hero.png) 1x); + background: src("img/bg.png"); + mask: url(img/sprite.svg#mask); + cursor: url(img/cursor.cur) 2 2, url(img/cursor.cur), pointer; + list-style: url(img/bg.png); + border-image: url(img/bg.png) 30 round; + background: URL(img/bg.png); + color: color-mix(in srgb, var(--brand) 50%, var(--Brand)); + width: calc(100% - var(--gap, 8px) * 2); + padding: env(safe-area-inset-top, var(--pad)); + font: italic bold 12px/30px Inter, "Segoe UI", serif; /* font SHORTHAND: family names at the end */ + font: 12px system-ui; + font: menu; + font-family: Inter; + font-family: "Inter", 'Roboto Mono', Segoe UI, ui-sans-serif, sans-serif; + font-family: inherit; + font-family: var(--font); + animation: spin 1s linear infinite, fade 2s; + animation: 1s ease-in-out infinite reverse spin; + animation: 2s steps(4, end) slide; + animation: "quoted-name" 1s; + animation: none; + animation: linear 1s; /* linear is a keyword, not a name */ + animation: var(--anim) 1s; + animation-name: spin, fade, missing-keyframes, vendor-spin; + animation-name: none; + animation-name: ease; /* a keyframes named like a keyword */ + container: sidebar / inline-size; + container: a b / size; + container-name: sidebar other; + container-name: none; + container-type: inline-size; + transition: color var(--duration) var(--easing, ease); + will-change: transform; + content: counter(item) ". "; + grid-area: header; + position-try-fallbacks: --fallback; + anchor-name: --anchor; + position-anchor: --anchor; + view-transition-name: hero; + offset-path: url(#motion-path); + clip-path: url(img/sprite.svg#clip); + shape-outside: url(img/bg.png); + src: url(fonts/inter.woff2); /* src outside @font-face */ +} diff --git a/parser/src/test-data/web/torture/css/base.css b/parser/src/test-data/web/torture/css/base.css new file mode 100644 index 000000000..9a0e91a38 --- /dev/null +++ b/parser/src/test-data/web/torture/css/base.css @@ -0,0 +1,2 @@ +.base-class { color: red } +.card { color: rgb(100,100,100) } diff --git a/parser/src/test-data/web/torture/css/big.css b/parser/src/test-data/web/torture/css/big.css new file mode 100644 index 000000000..217950d0f --- /dev/null +++ b/parser/src/test-data/web/torture/css/big.css @@ -0,0 +1,1500 @@ +.big-0 { color: var(--brand); background: url(img/bg.png) } +.big-1 { color: var(--brand); background: url(img/bg.png) } +.big-2 { color: var(--brand); background: url(img/bg.png) } +.big-3 { color: var(--brand); background: url(img/bg.png) } +.big-4 { color: var(--brand); background: url(img/bg.png) } +.big-5 { color: var(--brand); background: url(img/bg.png) } +.big-6 { color: var(--brand); background: url(img/bg.png) } +.big-7 { color: var(--brand); background: url(img/bg.png) } +.big-8 { color: var(--brand); background: url(img/bg.png) } +.big-9 { color: var(--brand); background: url(img/bg.png) } +.big-10 { color: var(--brand); background: url(img/bg.png) } +.big-11 { color: var(--brand); background: url(img/bg.png) } +.big-12 { color: var(--brand); background: url(img/bg.png) } +.big-13 { color: var(--brand); background: url(img/bg.png) } +.big-14 { color: var(--brand); background: url(img/bg.png) } +.big-15 { color: var(--brand); background: url(img/bg.png) } +.big-16 { color: var(--brand); background: url(img/bg.png) } +.big-17 { color: var(--brand); background: url(img/bg.png) } +.big-18 { color: var(--brand); background: url(img/bg.png) } +.big-19 { color: var(--brand); background: url(img/bg.png) } +.big-20 { color: var(--brand); background: url(img/bg.png) } +.big-21 { color: var(--brand); background: url(img/bg.png) } +.big-22 { color: var(--brand); background: url(img/bg.png) } +.big-23 { color: var(--brand); background: url(img/bg.png) } +.big-24 { color: var(--brand); background: url(img/bg.png) } +.big-25 { color: var(--brand); background: url(img/bg.png) } +.big-26 { color: var(--brand); background: url(img/bg.png) } +.big-27 { color: var(--brand); background: url(img/bg.png) } +.big-28 { color: var(--brand); background: url(img/bg.png) } +.big-29 { color: var(--brand); background: url(img/bg.png) } +.big-30 { color: var(--brand); background: url(img/bg.png) } +.big-31 { color: var(--brand); background: url(img/bg.png) } +.big-32 { color: var(--brand); background: url(img/bg.png) } +.big-33 { color: var(--brand); background: url(img/bg.png) } +.big-34 { color: var(--brand); background: url(img/bg.png) } +.big-35 { color: var(--brand); background: url(img/bg.png) } +.big-36 { color: var(--brand); background: url(img/bg.png) } +.big-37 { color: var(--brand); background: url(img/bg.png) } +.big-38 { color: var(--brand); background: url(img/bg.png) } +.big-39 { color: var(--brand); background: url(img/bg.png) } +.big-40 { color: var(--brand); background: url(img/bg.png) } +.big-41 { color: var(--brand); background: url(img/bg.png) } +.big-42 { color: var(--brand); background: url(img/bg.png) } +.big-43 { color: var(--brand); background: url(img/bg.png) } +.big-44 { color: var(--brand); background: url(img/bg.png) } +.big-45 { color: var(--brand); background: url(img/bg.png) } +.big-46 { color: var(--brand); background: url(img/bg.png) } +.big-47 { color: var(--brand); background: url(img/bg.png) } +.big-48 { color: var(--brand); background: url(img/bg.png) } +.big-49 { color: var(--brand); background: url(img/bg.png) } +.big-50 { color: var(--brand); background: url(img/bg.png) } +.big-51 { color: var(--brand); background: url(img/bg.png) } +.big-52 { color: var(--brand); background: url(img/bg.png) } +.big-53 { color: var(--brand); background: url(img/bg.png) } +.big-54 { color: var(--brand); background: url(img/bg.png) } +.big-55 { color: var(--brand); background: url(img/bg.png) } +.big-56 { color: var(--brand); background: url(img/bg.png) } +.big-57 { color: var(--brand); background: url(img/bg.png) } +.big-58 { color: var(--brand); background: url(img/bg.png) } +.big-59 { color: var(--brand); background: url(img/bg.png) } +.big-60 { color: var(--brand); background: url(img/bg.png) } +.big-61 { color: var(--brand); background: url(img/bg.png) } +.big-62 { color: var(--brand); background: url(img/bg.png) } +.big-63 { color: var(--brand); background: url(img/bg.png) } +.big-64 { color: var(--brand); background: url(img/bg.png) } +.big-65 { color: var(--brand); background: url(img/bg.png) } +.big-66 { color: var(--brand); background: url(img/bg.png) } +.big-67 { color: var(--brand); background: url(img/bg.png) } +.big-68 { color: var(--brand); background: url(img/bg.png) } +.big-69 { color: var(--brand); background: url(img/bg.png) } +.big-70 { color: var(--brand); background: url(img/bg.png) } +.big-71 { color: var(--brand); background: url(img/bg.png) } +.big-72 { color: var(--brand); background: url(img/bg.png) } +.big-73 { color: var(--brand); background: url(img/bg.png) } +.big-74 { color: var(--brand); background: url(img/bg.png) } +.big-75 { color: var(--brand); background: url(img/bg.png) } +.big-76 { color: var(--brand); background: url(img/bg.png) } +.big-77 { color: var(--brand); background: url(img/bg.png) } +.big-78 { color: var(--brand); background: url(img/bg.png) } +.big-79 { color: var(--brand); background: url(img/bg.png) } +.big-80 { color: var(--brand); background: url(img/bg.png) } +.big-81 { color: var(--brand); background: url(img/bg.png) } +.big-82 { color: var(--brand); background: url(img/bg.png) } +.big-83 { color: var(--brand); background: url(img/bg.png) } +.big-84 { color: var(--brand); background: url(img/bg.png) } +.big-85 { color: var(--brand); background: url(img/bg.png) } +.big-86 { color: var(--brand); background: url(img/bg.png) } +.big-87 { color: var(--brand); background: url(img/bg.png) } +.big-88 { color: var(--brand); background: url(img/bg.png) } +.big-89 { color: var(--brand); background: url(img/bg.png) } +.big-90 { color: var(--brand); background: url(img/bg.png) } +.big-91 { color: var(--brand); background: url(img/bg.png) } +.big-92 { color: var(--brand); background: url(img/bg.png) } +.big-93 { color: var(--brand); background: url(img/bg.png) } +.big-94 { color: var(--brand); background: url(img/bg.png) } +.big-95 { color: var(--brand); background: url(img/bg.png) } +.big-96 { color: var(--brand); background: url(img/bg.png) } +.big-97 { color: var(--brand); background: url(img/bg.png) } +.big-98 { color: var(--brand); background: url(img/bg.png) } +.big-99 { color: var(--brand); background: url(img/bg.png) } +.big-100 { color: var(--brand); background: url(img/bg.png) } +.big-101 { color: var(--brand); background: url(img/bg.png) } +.big-102 { color: var(--brand); background: url(img/bg.png) } +.big-103 { color: var(--brand); background: url(img/bg.png) } +.big-104 { color: var(--brand); background: url(img/bg.png) } +.big-105 { color: var(--brand); background: url(img/bg.png) } +.big-106 { color: var(--brand); background: url(img/bg.png) } +.big-107 { color: var(--brand); background: url(img/bg.png) } +.big-108 { color: var(--brand); background: url(img/bg.png) } +.big-109 { color: var(--brand); background: url(img/bg.png) } +.big-110 { color: var(--brand); background: url(img/bg.png) } +.big-111 { color: var(--brand); background: url(img/bg.png) } +.big-112 { color: var(--brand); background: url(img/bg.png) } +.big-113 { color: var(--brand); background: url(img/bg.png) } +.big-114 { color: var(--brand); background: url(img/bg.png) } +.big-115 { color: var(--brand); background: url(img/bg.png) } +.big-116 { color: var(--brand); background: url(img/bg.png) } +.big-117 { color: var(--brand); background: url(img/bg.png) } +.big-118 { color: var(--brand); background: url(img/bg.png) } +.big-119 { color: var(--brand); background: url(img/bg.png) } +.big-120 { color: var(--brand); background: url(img/bg.png) } +.big-121 { color: var(--brand); background: url(img/bg.png) } +.big-122 { color: var(--brand); background: url(img/bg.png) } +.big-123 { color: var(--brand); background: url(img/bg.png) } +.big-124 { color: var(--brand); background: url(img/bg.png) } +.big-125 { color: var(--brand); background: url(img/bg.png) } +.big-126 { color: var(--brand); background: url(img/bg.png) } +.big-127 { color: var(--brand); background: url(img/bg.png) } +.big-128 { color: var(--brand); background: url(img/bg.png) } +.big-129 { color: var(--brand); background: url(img/bg.png) } +.big-130 { color: var(--brand); background: url(img/bg.png) } +.big-131 { color: var(--brand); background: url(img/bg.png) } +.big-132 { color: var(--brand); background: url(img/bg.png) } +.big-133 { color: var(--brand); background: url(img/bg.png) } +.big-134 { color: var(--brand); background: url(img/bg.png) } +.big-135 { color: var(--brand); background: url(img/bg.png) } +.big-136 { color: var(--brand); background: url(img/bg.png) } +.big-137 { color: var(--brand); background: url(img/bg.png) } +.big-138 { color: var(--brand); background: url(img/bg.png) } +.big-139 { color: var(--brand); background: url(img/bg.png) } +.big-140 { color: var(--brand); background: url(img/bg.png) } +.big-141 { color: var(--brand); background: url(img/bg.png) } +.big-142 { color: var(--brand); background: url(img/bg.png) } +.big-143 { color: var(--brand); background: url(img/bg.png) } +.big-144 { color: var(--brand); background: url(img/bg.png) } +.big-145 { color: var(--brand); background: url(img/bg.png) } +.big-146 { color: var(--brand); background: url(img/bg.png) } +.big-147 { color: var(--brand); background: url(img/bg.png) } +.big-148 { color: var(--brand); background: url(img/bg.png) } +.big-149 { color: var(--brand); background: url(img/bg.png) } +.big-150 { color: var(--brand); background: url(img/bg.png) } +.big-151 { color: var(--brand); background: url(img/bg.png) } +.big-152 { color: var(--brand); background: url(img/bg.png) } +.big-153 { color: var(--brand); background: url(img/bg.png) } +.big-154 { color: var(--brand); background: url(img/bg.png) } +.big-155 { color: var(--brand); background: url(img/bg.png) } +.big-156 { color: var(--brand); background: url(img/bg.png) } +.big-157 { color: var(--brand); background: url(img/bg.png) } +.big-158 { color: var(--brand); background: url(img/bg.png) } +.big-159 { color: var(--brand); background: url(img/bg.png) } +.big-160 { color: var(--brand); background: url(img/bg.png) } +.big-161 { color: var(--brand); background: url(img/bg.png) } +.big-162 { color: var(--brand); background: url(img/bg.png) } +.big-163 { color: var(--brand); background: url(img/bg.png) } +.big-164 { color: var(--brand); background: url(img/bg.png) } +.big-165 { color: var(--brand); background: url(img/bg.png) } +.big-166 { color: var(--brand); background: url(img/bg.png) } +.big-167 { color: var(--brand); background: url(img/bg.png) } +.big-168 { color: var(--brand); background: url(img/bg.png) } +.big-169 { color: var(--brand); background: url(img/bg.png) } +.big-170 { color: var(--brand); background: url(img/bg.png) } +.big-171 { color: var(--brand); background: url(img/bg.png) } +.big-172 { color: var(--brand); background: url(img/bg.png) } +.big-173 { color: var(--brand); background: url(img/bg.png) } +.big-174 { color: var(--brand); background: url(img/bg.png) } +.big-175 { color: var(--brand); background: url(img/bg.png) } +.big-176 { color: var(--brand); background: url(img/bg.png) } +.big-177 { color: var(--brand); background: url(img/bg.png) } +.big-178 { color: var(--brand); background: url(img/bg.png) } +.big-179 { color: var(--brand); background: url(img/bg.png) } +.big-180 { color: var(--brand); background: url(img/bg.png) } +.big-181 { color: var(--brand); background: url(img/bg.png) } +.big-182 { color: var(--brand); background: url(img/bg.png) } +.big-183 { color: var(--brand); background: url(img/bg.png) } +.big-184 { color: var(--brand); background: url(img/bg.png) } +.big-185 { color: var(--brand); background: url(img/bg.png) } +.big-186 { color: var(--brand); background: url(img/bg.png) } +.big-187 { color: var(--brand); background: url(img/bg.png) } +.big-188 { color: var(--brand); background: url(img/bg.png) } +.big-189 { color: var(--brand); background: url(img/bg.png) } +.big-190 { color: var(--brand); background: url(img/bg.png) } +.big-191 { color: var(--brand); background: url(img/bg.png) } +.big-192 { color: var(--brand); background: url(img/bg.png) } +.big-193 { color: var(--brand); background: url(img/bg.png) } +.big-194 { color: var(--brand); background: url(img/bg.png) } +.big-195 { color: var(--brand); background: url(img/bg.png) } +.big-196 { color: var(--brand); background: url(img/bg.png) } +.big-197 { color: var(--brand); background: url(img/bg.png) } +.big-198 { color: var(--brand); background: url(img/bg.png) } +.big-199 { color: var(--brand); background: url(img/bg.png) } +.big-200 { color: var(--brand); background: url(img/bg.png) } +.big-201 { color: var(--brand); background: url(img/bg.png) } +.big-202 { color: var(--brand); background: url(img/bg.png) } +.big-203 { color: var(--brand); background: url(img/bg.png) } +.big-204 { color: var(--brand); background: url(img/bg.png) } +.big-205 { color: var(--brand); background: url(img/bg.png) } +.big-206 { color: var(--brand); background: url(img/bg.png) } +.big-207 { color: var(--brand); background: url(img/bg.png) } +.big-208 { color: var(--brand); background: url(img/bg.png) } +.big-209 { color: var(--brand); background: url(img/bg.png) } +.big-210 { color: var(--brand); background: url(img/bg.png) } +.big-211 { color: var(--brand); background: url(img/bg.png) } +.big-212 { color: var(--brand); background: url(img/bg.png) } +.big-213 { color: var(--brand); background: url(img/bg.png) } +.big-214 { color: var(--brand); background: url(img/bg.png) } +.big-215 { color: var(--brand); background: url(img/bg.png) } +.big-216 { color: var(--brand); background: url(img/bg.png) } +.big-217 { color: var(--brand); background: url(img/bg.png) } +.big-218 { color: var(--brand); background: url(img/bg.png) } +.big-219 { color: var(--brand); background: url(img/bg.png) } +.big-220 { color: var(--brand); background: url(img/bg.png) } +.big-221 { color: var(--brand); background: url(img/bg.png) } +.big-222 { color: var(--brand); background: url(img/bg.png) } +.big-223 { color: var(--brand); background: url(img/bg.png) } +.big-224 { color: var(--brand); background: url(img/bg.png) } +.big-225 { color: var(--brand); background: url(img/bg.png) } +.big-226 { color: var(--brand); background: url(img/bg.png) } +.big-227 { color: var(--brand); background: url(img/bg.png) } +.big-228 { color: var(--brand); background: url(img/bg.png) } +.big-229 { color: var(--brand); background: url(img/bg.png) } +.big-230 { color: var(--brand); background: url(img/bg.png) } +.big-231 { color: var(--brand); background: url(img/bg.png) } +.big-232 { color: var(--brand); background: url(img/bg.png) } +.big-233 { color: var(--brand); background: url(img/bg.png) } +.big-234 { color: var(--brand); background: url(img/bg.png) } +.big-235 { color: var(--brand); background: url(img/bg.png) } +.big-236 { color: var(--brand); background: url(img/bg.png) } +.big-237 { color: var(--brand); background: url(img/bg.png) } +.big-238 { color: var(--brand); background: url(img/bg.png) } +.big-239 { color: var(--brand); background: url(img/bg.png) } +.big-240 { color: var(--brand); background: url(img/bg.png) } +.big-241 { color: var(--brand); background: url(img/bg.png) } +.big-242 { color: var(--brand); background: url(img/bg.png) } +.big-243 { color: var(--brand); background: url(img/bg.png) } +.big-244 { color: var(--brand); background: url(img/bg.png) } +.big-245 { color: var(--brand); background: url(img/bg.png) } +.big-246 { color: var(--brand); background: url(img/bg.png) } +.big-247 { color: var(--brand); background: url(img/bg.png) } +.big-248 { color: var(--brand); background: url(img/bg.png) } +.big-249 { color: var(--brand); background: url(img/bg.png) } +.big-250 { color: var(--brand); background: url(img/bg.png) } +.big-251 { color: var(--brand); background: url(img/bg.png) } +.big-252 { color: var(--brand); background: url(img/bg.png) } +.big-253 { color: var(--brand); background: url(img/bg.png) } +.big-254 { color: var(--brand); background: url(img/bg.png) } +.big-255 { color: var(--brand); background: url(img/bg.png) } +.big-256 { color: var(--brand); background: url(img/bg.png) } +.big-257 { color: var(--brand); background: url(img/bg.png) } +.big-258 { color: var(--brand); background: url(img/bg.png) } +.big-259 { color: var(--brand); background: url(img/bg.png) } +.big-260 { color: var(--brand); background: url(img/bg.png) } +.big-261 { color: var(--brand); background: url(img/bg.png) } +.big-262 { color: var(--brand); background: url(img/bg.png) } +.big-263 { color: var(--brand); background: url(img/bg.png) } +.big-264 { color: var(--brand); background: url(img/bg.png) } +.big-265 { color: var(--brand); background: url(img/bg.png) } +.big-266 { color: var(--brand); background: url(img/bg.png) } +.big-267 { color: var(--brand); background: url(img/bg.png) } +.big-268 { color: var(--brand); background: url(img/bg.png) } +.big-269 { color: var(--brand); background: url(img/bg.png) } +.big-270 { color: var(--brand); background: url(img/bg.png) } +.big-271 { color: var(--brand); background: url(img/bg.png) } +.big-272 { color: var(--brand); background: url(img/bg.png) } +.big-273 { color: var(--brand); background: url(img/bg.png) } +.big-274 { color: var(--brand); background: url(img/bg.png) } +.big-275 { color: var(--brand); background: url(img/bg.png) } +.big-276 { color: var(--brand); background: url(img/bg.png) } +.big-277 { color: var(--brand); background: url(img/bg.png) } +.big-278 { color: var(--brand); background: url(img/bg.png) } +.big-279 { color: var(--brand); background: url(img/bg.png) } +.big-280 { color: var(--brand); background: url(img/bg.png) } +.big-281 { color: var(--brand); background: url(img/bg.png) } +.big-282 { color: var(--brand); background: url(img/bg.png) } +.big-283 { color: var(--brand); background: url(img/bg.png) } +.big-284 { color: var(--brand); background: url(img/bg.png) } +.big-285 { color: var(--brand); background: url(img/bg.png) } +.big-286 { color: var(--brand); background: url(img/bg.png) } +.big-287 { color: var(--brand); background: url(img/bg.png) } +.big-288 { color: var(--brand); background: url(img/bg.png) } +.big-289 { color: var(--brand); background: url(img/bg.png) } +.big-290 { color: var(--brand); background: url(img/bg.png) } +.big-291 { color: var(--brand); background: url(img/bg.png) } +.big-292 { color: var(--brand); background: url(img/bg.png) } +.big-293 { color: var(--brand); background: url(img/bg.png) } +.big-294 { color: var(--brand); background: url(img/bg.png) } +.big-295 { color: var(--brand); background: url(img/bg.png) } +.big-296 { color: var(--brand); background: url(img/bg.png) } +.big-297 { color: var(--brand); background: url(img/bg.png) } +.big-298 { color: var(--brand); background: url(img/bg.png) } +.big-299 { color: var(--brand); background: url(img/bg.png) } +.big-300 { color: var(--brand); background: url(img/bg.png) } +.big-301 { color: var(--brand); background: url(img/bg.png) } +.big-302 { color: var(--brand); background: url(img/bg.png) } +.big-303 { color: var(--brand); background: url(img/bg.png) } +.big-304 { color: var(--brand); background: url(img/bg.png) } +.big-305 { color: var(--brand); background: url(img/bg.png) } +.big-306 { color: var(--brand); background: url(img/bg.png) } +.big-307 { color: var(--brand); background: url(img/bg.png) } +.big-308 { color: var(--brand); background: url(img/bg.png) } +.big-309 { color: var(--brand); background: url(img/bg.png) } +.big-310 { color: var(--brand); background: url(img/bg.png) } +.big-311 { color: var(--brand); background: url(img/bg.png) } +.big-312 { color: var(--brand); background: url(img/bg.png) } +.big-313 { color: var(--brand); background: url(img/bg.png) } +.big-314 { color: var(--brand); background: url(img/bg.png) } +.big-315 { color: var(--brand); background: url(img/bg.png) } +.big-316 { color: var(--brand); background: url(img/bg.png) } +.big-317 { color: var(--brand); background: url(img/bg.png) } +.big-318 { color: var(--brand); background: url(img/bg.png) } +.big-319 { color: var(--brand); background: url(img/bg.png) } +.big-320 { color: var(--brand); background: url(img/bg.png) } +.big-321 { color: var(--brand); background: url(img/bg.png) } +.big-322 { color: var(--brand); background: url(img/bg.png) } +.big-323 { color: var(--brand); background: url(img/bg.png) } +.big-324 { color: var(--brand); background: url(img/bg.png) } +.big-325 { color: var(--brand); background: url(img/bg.png) } +.big-326 { color: var(--brand); background: url(img/bg.png) } +.big-327 { color: var(--brand); background: url(img/bg.png) } +.big-328 { color: var(--brand); background: url(img/bg.png) } +.big-329 { color: var(--brand); background: url(img/bg.png) } +.big-330 { color: var(--brand); background: url(img/bg.png) } +.big-331 { color: var(--brand); background: url(img/bg.png) } +.big-332 { color: var(--brand); background: url(img/bg.png) } +.big-333 { color: var(--brand); background: url(img/bg.png) } +.big-334 { color: var(--brand); background: url(img/bg.png) } +.big-335 { color: var(--brand); background: url(img/bg.png) } +.big-336 { color: var(--brand); background: url(img/bg.png) } +.big-337 { color: var(--brand); background: url(img/bg.png) } +.big-338 { color: var(--brand); background: url(img/bg.png) } +.big-339 { color: var(--brand); background: url(img/bg.png) } +.big-340 { color: var(--brand); background: url(img/bg.png) } +.big-341 { color: var(--brand); background: url(img/bg.png) } +.big-342 { color: var(--brand); background: url(img/bg.png) } +.big-343 { color: var(--brand); background: url(img/bg.png) } +.big-344 { color: var(--brand); background: url(img/bg.png) } +.big-345 { color: var(--brand); background: url(img/bg.png) } +.big-346 { color: var(--brand); background: url(img/bg.png) } +.big-347 { color: var(--brand); background: url(img/bg.png) } +.big-348 { color: var(--brand); background: url(img/bg.png) } +.big-349 { color: var(--brand); background: url(img/bg.png) } +.big-350 { color: var(--brand); background: url(img/bg.png) } +.big-351 { color: var(--brand); background: url(img/bg.png) } +.big-352 { color: var(--brand); background: url(img/bg.png) } +.big-353 { color: var(--brand); background: url(img/bg.png) } +.big-354 { color: var(--brand); background: url(img/bg.png) } +.big-355 { color: var(--brand); background: url(img/bg.png) } +.big-356 { color: var(--brand); background: url(img/bg.png) } +.big-357 { color: var(--brand); background: url(img/bg.png) } +.big-358 { color: var(--brand); background: url(img/bg.png) } +.big-359 { color: var(--brand); background: url(img/bg.png) } +.big-360 { color: var(--brand); background: url(img/bg.png) } +.big-361 { color: var(--brand); background: url(img/bg.png) } +.big-362 { color: var(--brand); background: url(img/bg.png) } +.big-363 { color: var(--brand); background: url(img/bg.png) } +.big-364 { color: var(--brand); background: url(img/bg.png) } +.big-365 { color: var(--brand); background: url(img/bg.png) } +.big-366 { color: var(--brand); background: url(img/bg.png) } +.big-367 { color: var(--brand); background: url(img/bg.png) } +.big-368 { color: var(--brand); background: url(img/bg.png) } +.big-369 { color: var(--brand); background: url(img/bg.png) } +.big-370 { color: var(--brand); background: url(img/bg.png) } +.big-371 { color: var(--brand); background: url(img/bg.png) } +.big-372 { color: var(--brand); background: url(img/bg.png) } +.big-373 { color: var(--brand); background: url(img/bg.png) } +.big-374 { color: var(--brand); background: url(img/bg.png) } +.big-375 { color: var(--brand); background: url(img/bg.png) } +.big-376 { color: var(--brand); background: url(img/bg.png) } +.big-377 { color: var(--brand); background: url(img/bg.png) } +.big-378 { color: var(--brand); background: url(img/bg.png) } +.big-379 { color: var(--brand); background: url(img/bg.png) } +.big-380 { color: var(--brand); background: url(img/bg.png) } +.big-381 { color: var(--brand); background: url(img/bg.png) } +.big-382 { color: var(--brand); background: url(img/bg.png) } +.big-383 { color: var(--brand); background: url(img/bg.png) } +.big-384 { color: var(--brand); background: url(img/bg.png) } +.big-385 { color: var(--brand); background: url(img/bg.png) } +.big-386 { color: var(--brand); background: url(img/bg.png) } +.big-387 { color: var(--brand); background: url(img/bg.png) } +.big-388 { color: var(--brand); background: url(img/bg.png) } +.big-389 { color: var(--brand); background: url(img/bg.png) } +.big-390 { color: var(--brand); background: url(img/bg.png) } +.big-391 { color: var(--brand); background: url(img/bg.png) } +.big-392 { color: var(--brand); background: url(img/bg.png) } +.big-393 { color: var(--brand); background: url(img/bg.png) } +.big-394 { color: var(--brand); background: url(img/bg.png) } +.big-395 { color: var(--brand); background: url(img/bg.png) } +.big-396 { color: var(--brand); background: url(img/bg.png) } +.big-397 { color: var(--brand); background: url(img/bg.png) } +.big-398 { color: var(--brand); background: url(img/bg.png) } +.big-399 { color: var(--brand); background: url(img/bg.png) } +.big-400 { color: var(--brand); background: url(img/bg.png) } +.big-401 { color: var(--brand); background: url(img/bg.png) } +.big-402 { color: var(--brand); background: url(img/bg.png) } +.big-403 { color: var(--brand); background: url(img/bg.png) } +.big-404 { color: var(--brand); background: url(img/bg.png) } +.big-405 { color: var(--brand); background: url(img/bg.png) } +.big-406 { color: var(--brand); background: url(img/bg.png) } +.big-407 { color: var(--brand); background: url(img/bg.png) } +.big-408 { color: var(--brand); background: url(img/bg.png) } +.big-409 { color: var(--brand); background: url(img/bg.png) } +.big-410 { color: var(--brand); background: url(img/bg.png) } +.big-411 { color: var(--brand); background: url(img/bg.png) } +.big-412 { color: var(--brand); background: url(img/bg.png) } +.big-413 { color: var(--brand); background: url(img/bg.png) } +.big-414 { color: var(--brand); background: url(img/bg.png) } +.big-415 { color: var(--brand); background: url(img/bg.png) } +.big-416 { color: var(--brand); background: url(img/bg.png) } +.big-417 { color: var(--brand); background: url(img/bg.png) } +.big-418 { color: var(--brand); background: url(img/bg.png) } +.big-419 { color: var(--brand); background: url(img/bg.png) } +.big-420 { color: var(--brand); background: url(img/bg.png) } +.big-421 { color: var(--brand); background: url(img/bg.png) } +.big-422 { color: var(--brand); background: url(img/bg.png) } +.big-423 { color: var(--brand); background: url(img/bg.png) } +.big-424 { color: var(--brand); background: url(img/bg.png) } +.big-425 { color: var(--brand); background: url(img/bg.png) } +.big-426 { color: var(--brand); background: url(img/bg.png) } +.big-427 { color: var(--brand); background: url(img/bg.png) } +.big-428 { color: var(--brand); background: url(img/bg.png) } +.big-429 { color: var(--brand); background: url(img/bg.png) } +.big-430 { color: var(--brand); background: url(img/bg.png) } +.big-431 { color: var(--brand); background: url(img/bg.png) } +.big-432 { color: var(--brand); background: url(img/bg.png) } +.big-433 { color: var(--brand); background: url(img/bg.png) } +.big-434 { color: var(--brand); background: url(img/bg.png) } +.big-435 { color: var(--brand); background: url(img/bg.png) } +.big-436 { color: var(--brand); background: url(img/bg.png) } +.big-437 { color: var(--brand); background: url(img/bg.png) } +.big-438 { color: var(--brand); background: url(img/bg.png) } +.big-439 { color: var(--brand); background: url(img/bg.png) } +.big-440 { color: var(--brand); background: url(img/bg.png) } +.big-441 { color: var(--brand); background: url(img/bg.png) } +.big-442 { color: var(--brand); background: url(img/bg.png) } +.big-443 { color: var(--brand); background: url(img/bg.png) } +.big-444 { color: var(--brand); background: url(img/bg.png) } +.big-445 { color: var(--brand); background: url(img/bg.png) } +.big-446 { color: var(--brand); background: url(img/bg.png) } +.big-447 { color: var(--brand); background: url(img/bg.png) } +.big-448 { color: var(--brand); background: url(img/bg.png) } +.big-449 { color: var(--brand); background: url(img/bg.png) } +.big-450 { color: var(--brand); background: url(img/bg.png) } +.big-451 { color: var(--brand); background: url(img/bg.png) } +.big-452 { color: var(--brand); background: url(img/bg.png) } +.big-453 { color: var(--brand); background: url(img/bg.png) } +.big-454 { color: var(--brand); background: url(img/bg.png) } +.big-455 { color: var(--brand); background: url(img/bg.png) } +.big-456 { color: var(--brand); background: url(img/bg.png) } +.big-457 { color: var(--brand); background: url(img/bg.png) } +.big-458 { color: var(--brand); background: url(img/bg.png) } +.big-459 { color: var(--brand); background: url(img/bg.png) } +.big-460 { color: var(--brand); background: url(img/bg.png) } +.big-461 { color: var(--brand); background: url(img/bg.png) } +.big-462 { color: var(--brand); background: url(img/bg.png) } +.big-463 { color: var(--brand); background: url(img/bg.png) } +.big-464 { color: var(--brand); background: url(img/bg.png) } +.big-465 { color: var(--brand); background: url(img/bg.png) } +.big-466 { color: var(--brand); background: url(img/bg.png) } +.big-467 { color: var(--brand); background: url(img/bg.png) } +.big-468 { color: var(--brand); background: url(img/bg.png) } +.big-469 { color: var(--brand); background: url(img/bg.png) } +.big-470 { color: var(--brand); background: url(img/bg.png) } +.big-471 { color: var(--brand); background: url(img/bg.png) } +.big-472 { color: var(--brand); background: url(img/bg.png) } +.big-473 { color: var(--brand); background: url(img/bg.png) } +.big-474 { color: var(--brand); background: url(img/bg.png) } +.big-475 { color: var(--brand); background: url(img/bg.png) } +.big-476 { color: var(--brand); background: url(img/bg.png) } +.big-477 { color: var(--brand); background: url(img/bg.png) } +.big-478 { color: var(--brand); background: url(img/bg.png) } +.big-479 { color: var(--brand); background: url(img/bg.png) } +.big-480 { color: var(--brand); background: url(img/bg.png) } +.big-481 { color: var(--brand); background: url(img/bg.png) } +.big-482 { color: var(--brand); background: url(img/bg.png) } +.big-483 { color: var(--brand); background: url(img/bg.png) } +.big-484 { color: var(--brand); background: url(img/bg.png) } +.big-485 { color: var(--brand); background: url(img/bg.png) } +.big-486 { color: var(--brand); background: url(img/bg.png) } +.big-487 { color: var(--brand); background: url(img/bg.png) } +.big-488 { color: var(--brand); background: url(img/bg.png) } +.big-489 { color: var(--brand); background: url(img/bg.png) } +.big-490 { color: var(--brand); background: url(img/bg.png) } +.big-491 { color: var(--brand); background: url(img/bg.png) } +.big-492 { color: var(--brand); background: url(img/bg.png) } +.big-493 { color: var(--brand); background: url(img/bg.png) } +.big-494 { color: var(--brand); background: url(img/bg.png) } +.big-495 { color: var(--brand); background: url(img/bg.png) } +.big-496 { color: var(--brand); background: url(img/bg.png) } +.big-497 { color: var(--brand); background: url(img/bg.png) } +.big-498 { color: var(--brand); background: url(img/bg.png) } +.big-499 { color: var(--brand); background: url(img/bg.png) } +.big-500 { color: var(--brand); background: url(img/bg.png) } +.big-501 { color: var(--brand); background: url(img/bg.png) } +.big-502 { color: var(--brand); background: url(img/bg.png) } +.big-503 { color: var(--brand); background: url(img/bg.png) } +.big-504 { color: var(--brand); background: url(img/bg.png) } +.big-505 { color: var(--brand); background: url(img/bg.png) } +.big-506 { color: var(--brand); background: url(img/bg.png) } +.big-507 { color: var(--brand); background: url(img/bg.png) } +.big-508 { color: var(--brand); background: url(img/bg.png) } +.big-509 { color: var(--brand); background: url(img/bg.png) } +.big-510 { color: var(--brand); background: url(img/bg.png) } +.big-511 { color: var(--brand); background: url(img/bg.png) } +.big-512 { color: var(--brand); background: url(img/bg.png) } +.big-513 { color: var(--brand); background: url(img/bg.png) } +.big-514 { color: var(--brand); background: url(img/bg.png) } +.big-515 { color: var(--brand); background: url(img/bg.png) } +.big-516 { color: var(--brand); background: url(img/bg.png) } +.big-517 { color: var(--brand); background: url(img/bg.png) } +.big-518 { color: var(--brand); background: url(img/bg.png) } +.big-519 { color: var(--brand); background: url(img/bg.png) } +.big-520 { color: var(--brand); background: url(img/bg.png) } +.big-521 { color: var(--brand); background: url(img/bg.png) } +.big-522 { color: var(--brand); background: url(img/bg.png) } +.big-523 { color: var(--brand); background: url(img/bg.png) } +.big-524 { color: var(--brand); background: url(img/bg.png) } +.big-525 { color: var(--brand); background: url(img/bg.png) } +.big-526 { color: var(--brand); background: url(img/bg.png) } +.big-527 { color: var(--brand); background: url(img/bg.png) } +.big-528 { color: var(--brand); background: url(img/bg.png) } +.big-529 { color: var(--brand); background: url(img/bg.png) } +.big-530 { color: var(--brand); background: url(img/bg.png) } +.big-531 { color: var(--brand); background: url(img/bg.png) } +.big-532 { color: var(--brand); background: url(img/bg.png) } +.big-533 { color: var(--brand); background: url(img/bg.png) } +.big-534 { color: var(--brand); background: url(img/bg.png) } +.big-535 { color: var(--brand); background: url(img/bg.png) } +.big-536 { color: var(--brand); background: url(img/bg.png) } +.big-537 { color: var(--brand); background: url(img/bg.png) } +.big-538 { color: var(--brand); background: url(img/bg.png) } +.big-539 { color: var(--brand); background: url(img/bg.png) } +.big-540 { color: var(--brand); background: url(img/bg.png) } +.big-541 { color: var(--brand); background: url(img/bg.png) } +.big-542 { color: var(--brand); background: url(img/bg.png) } +.big-543 { color: var(--brand); background: url(img/bg.png) } +.big-544 { color: var(--brand); background: url(img/bg.png) } +.big-545 { color: var(--brand); background: url(img/bg.png) } +.big-546 { color: var(--brand); background: url(img/bg.png) } +.big-547 { color: var(--brand); background: url(img/bg.png) } +.big-548 { color: var(--brand); background: url(img/bg.png) } +.big-549 { color: var(--brand); background: url(img/bg.png) } +.big-550 { color: var(--brand); background: url(img/bg.png) } +.big-551 { color: var(--brand); background: url(img/bg.png) } +.big-552 { color: var(--brand); background: url(img/bg.png) } +.big-553 { color: var(--brand); background: url(img/bg.png) } +.big-554 { color: var(--brand); background: url(img/bg.png) } +.big-555 { color: var(--brand); background: url(img/bg.png) } +.big-556 { color: var(--brand); background: url(img/bg.png) } +.big-557 { color: var(--brand); background: url(img/bg.png) } +.big-558 { color: var(--brand); background: url(img/bg.png) } +.big-559 { color: var(--brand); background: url(img/bg.png) } +.big-560 { color: var(--brand); background: url(img/bg.png) } +.big-561 { color: var(--brand); background: url(img/bg.png) } +.big-562 { color: var(--brand); background: url(img/bg.png) } +.big-563 { color: var(--brand); background: url(img/bg.png) } +.big-564 { color: var(--brand); background: url(img/bg.png) } +.big-565 { color: var(--brand); background: url(img/bg.png) } +.big-566 { color: var(--brand); background: url(img/bg.png) } +.big-567 { color: var(--brand); background: url(img/bg.png) } +.big-568 { color: var(--brand); background: url(img/bg.png) } +.big-569 { color: var(--brand); background: url(img/bg.png) } +.big-570 { color: var(--brand); background: url(img/bg.png) } +.big-571 { color: var(--brand); background: url(img/bg.png) } +.big-572 { color: var(--brand); background: url(img/bg.png) } +.big-573 { color: var(--brand); background: url(img/bg.png) } +.big-574 { color: var(--brand); background: url(img/bg.png) } +.big-575 { color: var(--brand); background: url(img/bg.png) } +.big-576 { color: var(--brand); background: url(img/bg.png) } +.big-577 { color: var(--brand); background: url(img/bg.png) } +.big-578 { color: var(--brand); background: url(img/bg.png) } +.big-579 { color: var(--brand); background: url(img/bg.png) } +.big-580 { color: var(--brand); background: url(img/bg.png) } +.big-581 { color: var(--brand); background: url(img/bg.png) } +.big-582 { color: var(--brand); background: url(img/bg.png) } +.big-583 { color: var(--brand); background: url(img/bg.png) } +.big-584 { color: var(--brand); background: url(img/bg.png) } +.big-585 { color: var(--brand); background: url(img/bg.png) } +.big-586 { color: var(--brand); background: url(img/bg.png) } +.big-587 { color: var(--brand); background: url(img/bg.png) } +.big-588 { color: var(--brand); background: url(img/bg.png) } +.big-589 { color: var(--brand); background: url(img/bg.png) } +.big-590 { color: var(--brand); background: url(img/bg.png) } +.big-591 { color: var(--brand); background: url(img/bg.png) } +.big-592 { color: var(--brand); background: url(img/bg.png) } +.big-593 { color: var(--brand); background: url(img/bg.png) } +.big-594 { color: var(--brand); background: url(img/bg.png) } +.big-595 { color: var(--brand); background: url(img/bg.png) } +.big-596 { color: var(--brand); background: url(img/bg.png) } +.big-597 { color: var(--brand); background: url(img/bg.png) } +.big-598 { color: var(--brand); background: url(img/bg.png) } +.big-599 { color: var(--brand); background: url(img/bg.png) } +.big-600 { color: var(--brand); background: url(img/bg.png) } +.big-601 { color: var(--brand); background: url(img/bg.png) } +.big-602 { color: var(--brand); background: url(img/bg.png) } +.big-603 { color: var(--brand); background: url(img/bg.png) } +.big-604 { color: var(--brand); background: url(img/bg.png) } +.big-605 { color: var(--brand); background: url(img/bg.png) } +.big-606 { color: var(--brand); background: url(img/bg.png) } +.big-607 { color: var(--brand); background: url(img/bg.png) } +.big-608 { color: var(--brand); background: url(img/bg.png) } +.big-609 { color: var(--brand); background: url(img/bg.png) } +.big-610 { color: var(--brand); background: url(img/bg.png) } +.big-611 { color: var(--brand); background: url(img/bg.png) } +.big-612 { color: var(--brand); background: url(img/bg.png) } +.big-613 { color: var(--brand); background: url(img/bg.png) } +.big-614 { color: var(--brand); background: url(img/bg.png) } +.big-615 { color: var(--brand); background: url(img/bg.png) } +.big-616 { color: var(--brand); background: url(img/bg.png) } +.big-617 { color: var(--brand); background: url(img/bg.png) } +.big-618 { color: var(--brand); background: url(img/bg.png) } +.big-619 { color: var(--brand); background: url(img/bg.png) } +.big-620 { color: var(--brand); background: url(img/bg.png) } +.big-621 { color: var(--brand); background: url(img/bg.png) } +.big-622 { color: var(--brand); background: url(img/bg.png) } +.big-623 { color: var(--brand); background: url(img/bg.png) } +.big-624 { color: var(--brand); background: url(img/bg.png) } +.big-625 { color: var(--brand); background: url(img/bg.png) } +.big-626 { color: var(--brand); background: url(img/bg.png) } +.big-627 { color: var(--brand); background: url(img/bg.png) } +.big-628 { color: var(--brand); background: url(img/bg.png) } +.big-629 { color: var(--brand); background: url(img/bg.png) } +.big-630 { color: var(--brand); background: url(img/bg.png) } +.big-631 { color: var(--brand); background: url(img/bg.png) } +.big-632 { color: var(--brand); background: url(img/bg.png) } +.big-633 { color: var(--brand); background: url(img/bg.png) } +.big-634 { color: var(--brand); background: url(img/bg.png) } +.big-635 { color: var(--brand); background: url(img/bg.png) } +.big-636 { color: var(--brand); background: url(img/bg.png) } +.big-637 { color: var(--brand); background: url(img/bg.png) } +.big-638 { color: var(--brand); background: url(img/bg.png) } +.big-639 { color: var(--brand); background: url(img/bg.png) } +.big-640 { color: var(--brand); background: url(img/bg.png) } +.big-641 { color: var(--brand); background: url(img/bg.png) } +.big-642 { color: var(--brand); background: url(img/bg.png) } +.big-643 { color: var(--brand); background: url(img/bg.png) } +.big-644 { color: var(--brand); background: url(img/bg.png) } +.big-645 { color: var(--brand); background: url(img/bg.png) } +.big-646 { color: var(--brand); background: url(img/bg.png) } +.big-647 { color: var(--brand); background: url(img/bg.png) } +.big-648 { color: var(--brand); background: url(img/bg.png) } +.big-649 { color: var(--brand); background: url(img/bg.png) } +.big-650 { color: var(--brand); background: url(img/bg.png) } +.big-651 { color: var(--brand); background: url(img/bg.png) } +.big-652 { color: var(--brand); background: url(img/bg.png) } +.big-653 { color: var(--brand); background: url(img/bg.png) } +.big-654 { color: var(--brand); background: url(img/bg.png) } +.big-655 { color: var(--brand); background: url(img/bg.png) } +.big-656 { color: var(--brand); background: url(img/bg.png) } +.big-657 { color: var(--brand); background: url(img/bg.png) } +.big-658 { color: var(--brand); background: url(img/bg.png) } +.big-659 { color: var(--brand); background: url(img/bg.png) } +.big-660 { color: var(--brand); background: url(img/bg.png) } +.big-661 { color: var(--brand); background: url(img/bg.png) } +.big-662 { color: var(--brand); background: url(img/bg.png) } +.big-663 { color: var(--brand); background: url(img/bg.png) } +.big-664 { color: var(--brand); background: url(img/bg.png) } +.big-665 { color: var(--brand); background: url(img/bg.png) } +.big-666 { color: var(--brand); background: url(img/bg.png) } +.big-667 { color: var(--brand); background: url(img/bg.png) } +.big-668 { color: var(--brand); background: url(img/bg.png) } +.big-669 { color: var(--brand); background: url(img/bg.png) } +.big-670 { color: var(--brand); background: url(img/bg.png) } +.big-671 { color: var(--brand); background: url(img/bg.png) } +.big-672 { color: var(--brand); background: url(img/bg.png) } +.big-673 { color: var(--brand); background: url(img/bg.png) } +.big-674 { color: var(--brand); background: url(img/bg.png) } +.big-675 { color: var(--brand); background: url(img/bg.png) } +.big-676 { color: var(--brand); background: url(img/bg.png) } +.big-677 { color: var(--brand); background: url(img/bg.png) } +.big-678 { color: var(--brand); background: url(img/bg.png) } +.big-679 { color: var(--brand); background: url(img/bg.png) } +.big-680 { color: var(--brand); background: url(img/bg.png) } +.big-681 { color: var(--brand); background: url(img/bg.png) } +.big-682 { color: var(--brand); background: url(img/bg.png) } +.big-683 { color: var(--brand); background: url(img/bg.png) } +.big-684 { color: var(--brand); background: url(img/bg.png) } +.big-685 { color: var(--brand); background: url(img/bg.png) } +.big-686 { color: var(--brand); background: url(img/bg.png) } +.big-687 { color: var(--brand); background: url(img/bg.png) } +.big-688 { color: var(--brand); background: url(img/bg.png) } +.big-689 { color: var(--brand); background: url(img/bg.png) } +.big-690 { color: var(--brand); background: url(img/bg.png) } +.big-691 { color: var(--brand); background: url(img/bg.png) } +.big-692 { color: var(--brand); background: url(img/bg.png) } +.big-693 { color: var(--brand); background: url(img/bg.png) } +.big-694 { color: var(--brand); background: url(img/bg.png) } +.big-695 { color: var(--brand); background: url(img/bg.png) } +.big-696 { color: var(--brand); background: url(img/bg.png) } +.big-697 { color: var(--brand); background: url(img/bg.png) } +.big-698 { color: var(--brand); background: url(img/bg.png) } +.big-699 { color: var(--brand); background: url(img/bg.png) } +.big-700 { color: var(--brand); background: url(img/bg.png) } +.big-701 { color: var(--brand); background: url(img/bg.png) } +.big-702 { color: var(--brand); background: url(img/bg.png) } +.big-703 { color: var(--brand); background: url(img/bg.png) } +.big-704 { color: var(--brand); background: url(img/bg.png) } +.big-705 { color: var(--brand); background: url(img/bg.png) } +.big-706 { color: var(--brand); background: url(img/bg.png) } +.big-707 { color: var(--brand); background: url(img/bg.png) } +.big-708 { color: var(--brand); background: url(img/bg.png) } +.big-709 { color: var(--brand); background: url(img/bg.png) } +.big-710 { color: var(--brand); background: url(img/bg.png) } +.big-711 { color: var(--brand); background: url(img/bg.png) } +.big-712 { color: var(--brand); background: url(img/bg.png) } +.big-713 { color: var(--brand); background: url(img/bg.png) } +.big-714 { color: var(--brand); background: url(img/bg.png) } +.big-715 { color: var(--brand); background: url(img/bg.png) } +.big-716 { color: var(--brand); background: url(img/bg.png) } +.big-717 { color: var(--brand); background: url(img/bg.png) } +.big-718 { color: var(--brand); background: url(img/bg.png) } +.big-719 { color: var(--brand); background: url(img/bg.png) } +.big-720 { color: var(--brand); background: url(img/bg.png) } +.big-721 { color: var(--brand); background: url(img/bg.png) } +.big-722 { color: var(--brand); background: url(img/bg.png) } +.big-723 { color: var(--brand); background: url(img/bg.png) } +.big-724 { color: var(--brand); background: url(img/bg.png) } +.big-725 { color: var(--brand); background: url(img/bg.png) } +.big-726 { color: var(--brand); background: url(img/bg.png) } +.big-727 { color: var(--brand); background: url(img/bg.png) } +.big-728 { color: var(--brand); background: url(img/bg.png) } +.big-729 { color: var(--brand); background: url(img/bg.png) } +.big-730 { color: var(--brand); background: url(img/bg.png) } +.big-731 { color: var(--brand); background: url(img/bg.png) } +.big-732 { color: var(--brand); background: url(img/bg.png) } +.big-733 { color: var(--brand); background: url(img/bg.png) } +.big-734 { color: var(--brand); background: url(img/bg.png) } +.big-735 { color: var(--brand); background: url(img/bg.png) } +.big-736 { color: var(--brand); background: url(img/bg.png) } +.big-737 { color: var(--brand); background: url(img/bg.png) } +.big-738 { color: var(--brand); background: url(img/bg.png) } +.big-739 { color: var(--brand); background: url(img/bg.png) } +.big-740 { color: var(--brand); background: url(img/bg.png) } +.big-741 { color: var(--brand); background: url(img/bg.png) } +.big-742 { color: var(--brand); background: url(img/bg.png) } +.big-743 { color: var(--brand); background: url(img/bg.png) } +.big-744 { color: var(--brand); background: url(img/bg.png) } +.big-745 { color: var(--brand); background: url(img/bg.png) } +.big-746 { color: var(--brand); background: url(img/bg.png) } +.big-747 { color: var(--brand); background: url(img/bg.png) } +.big-748 { color: var(--brand); background: url(img/bg.png) } +.big-749 { color: var(--brand); background: url(img/bg.png) } +.big-750 { color: var(--brand); background: url(img/bg.png) } +.big-751 { color: var(--brand); background: url(img/bg.png) } +.big-752 { color: var(--brand); background: url(img/bg.png) } +.big-753 { color: var(--brand); background: url(img/bg.png) } +.big-754 { color: var(--brand); background: url(img/bg.png) } +.big-755 { color: var(--brand); background: url(img/bg.png) } +.big-756 { color: var(--brand); background: url(img/bg.png) } +.big-757 { color: var(--brand); background: url(img/bg.png) } +.big-758 { color: var(--brand); background: url(img/bg.png) } +.big-759 { color: var(--brand); background: url(img/bg.png) } +.big-760 { color: var(--brand); background: url(img/bg.png) } +.big-761 { color: var(--brand); background: url(img/bg.png) } +.big-762 { color: var(--brand); background: url(img/bg.png) } +.big-763 { color: var(--brand); background: url(img/bg.png) } +.big-764 { color: var(--brand); background: url(img/bg.png) } +.big-765 { color: var(--brand); background: url(img/bg.png) } +.big-766 { color: var(--brand); background: url(img/bg.png) } +.big-767 { color: var(--brand); background: url(img/bg.png) } +.big-768 { color: var(--brand); background: url(img/bg.png) } +.big-769 { color: var(--brand); background: url(img/bg.png) } +.big-770 { color: var(--brand); background: url(img/bg.png) } +.big-771 { color: var(--brand); background: url(img/bg.png) } +.big-772 { color: var(--brand); background: url(img/bg.png) } +.big-773 { color: var(--brand); background: url(img/bg.png) } +.big-774 { color: var(--brand); background: url(img/bg.png) } +.big-775 { color: var(--brand); background: url(img/bg.png) } +.big-776 { color: var(--brand); background: url(img/bg.png) } +.big-777 { color: var(--brand); background: url(img/bg.png) } +.big-778 { color: var(--brand); background: url(img/bg.png) } +.big-779 { color: var(--brand); background: url(img/bg.png) } +.big-780 { color: var(--brand); background: url(img/bg.png) } +.big-781 { color: var(--brand); background: url(img/bg.png) } +.big-782 { color: var(--brand); background: url(img/bg.png) } +.big-783 { color: var(--brand); background: url(img/bg.png) } +.big-784 { color: var(--brand); background: url(img/bg.png) } +.big-785 { color: var(--brand); background: url(img/bg.png) } +.big-786 { color: var(--brand); background: url(img/bg.png) } +.big-787 { color: var(--brand); background: url(img/bg.png) } +.big-788 { color: var(--brand); background: url(img/bg.png) } +.big-789 { color: var(--brand); background: url(img/bg.png) } +.big-790 { color: var(--brand); background: url(img/bg.png) } +.big-791 { color: var(--brand); background: url(img/bg.png) } +.big-792 { color: var(--brand); background: url(img/bg.png) } +.big-793 { color: var(--brand); background: url(img/bg.png) } +.big-794 { color: var(--brand); background: url(img/bg.png) } +.big-795 { color: var(--brand); background: url(img/bg.png) } +.big-796 { color: var(--brand); background: url(img/bg.png) } +.big-797 { color: var(--brand); background: url(img/bg.png) } +.big-798 { color: var(--brand); background: url(img/bg.png) } +.big-799 { color: var(--brand); background: url(img/bg.png) } +.big-800 { color: var(--brand); background: url(img/bg.png) } +.big-801 { color: var(--brand); background: url(img/bg.png) } +.big-802 { color: var(--brand); background: url(img/bg.png) } +.big-803 { color: var(--brand); background: url(img/bg.png) } +.big-804 { color: var(--brand); background: url(img/bg.png) } +.big-805 { color: var(--brand); background: url(img/bg.png) } +.big-806 { color: var(--brand); background: url(img/bg.png) } +.big-807 { color: var(--brand); background: url(img/bg.png) } +.big-808 { color: var(--brand); background: url(img/bg.png) } +.big-809 { color: var(--brand); background: url(img/bg.png) } +.big-810 { color: var(--brand); background: url(img/bg.png) } +.big-811 { color: var(--brand); background: url(img/bg.png) } +.big-812 { color: var(--brand); background: url(img/bg.png) } +.big-813 { color: var(--brand); background: url(img/bg.png) } +.big-814 { color: var(--brand); background: url(img/bg.png) } +.big-815 { color: var(--brand); background: url(img/bg.png) } +.big-816 { color: var(--brand); background: url(img/bg.png) } +.big-817 { color: var(--brand); background: url(img/bg.png) } +.big-818 { color: var(--brand); background: url(img/bg.png) } +.big-819 { color: var(--brand); background: url(img/bg.png) } +.big-820 { color: var(--brand); background: url(img/bg.png) } +.big-821 { color: var(--brand); background: url(img/bg.png) } +.big-822 { color: var(--brand); background: url(img/bg.png) } +.big-823 { color: var(--brand); background: url(img/bg.png) } +.big-824 { color: var(--brand); background: url(img/bg.png) } +.big-825 { color: var(--brand); background: url(img/bg.png) } +.big-826 { color: var(--brand); background: url(img/bg.png) } +.big-827 { color: var(--brand); background: url(img/bg.png) } +.big-828 { color: var(--brand); background: url(img/bg.png) } +.big-829 { color: var(--brand); background: url(img/bg.png) } +.big-830 { color: var(--brand); background: url(img/bg.png) } +.big-831 { color: var(--brand); background: url(img/bg.png) } +.big-832 { color: var(--brand); background: url(img/bg.png) } +.big-833 { color: var(--brand); background: url(img/bg.png) } +.big-834 { color: var(--brand); background: url(img/bg.png) } +.big-835 { color: var(--brand); background: url(img/bg.png) } +.big-836 { color: var(--brand); background: url(img/bg.png) } +.big-837 { color: var(--brand); background: url(img/bg.png) } +.big-838 { color: var(--brand); background: url(img/bg.png) } +.big-839 { color: var(--brand); background: url(img/bg.png) } +.big-840 { color: var(--brand); background: url(img/bg.png) } +.big-841 { color: var(--brand); background: url(img/bg.png) } +.big-842 { color: var(--brand); background: url(img/bg.png) } +.big-843 { color: var(--brand); background: url(img/bg.png) } +.big-844 { color: var(--brand); background: url(img/bg.png) } +.big-845 { color: var(--brand); background: url(img/bg.png) } +.big-846 { color: var(--brand); background: url(img/bg.png) } +.big-847 { color: var(--brand); background: url(img/bg.png) } +.big-848 { color: var(--brand); background: url(img/bg.png) } +.big-849 { color: var(--brand); background: url(img/bg.png) } +.big-850 { color: var(--brand); background: url(img/bg.png) } +.big-851 { color: var(--brand); background: url(img/bg.png) } +.big-852 { color: var(--brand); background: url(img/bg.png) } +.big-853 { color: var(--brand); background: url(img/bg.png) } +.big-854 { color: var(--brand); background: url(img/bg.png) } +.big-855 { color: var(--brand); background: url(img/bg.png) } +.big-856 { color: var(--brand); background: url(img/bg.png) } +.big-857 { color: var(--brand); background: url(img/bg.png) } +.big-858 { color: var(--brand); background: url(img/bg.png) } +.big-859 { color: var(--brand); background: url(img/bg.png) } +.big-860 { color: var(--brand); background: url(img/bg.png) } +.big-861 { color: var(--brand); background: url(img/bg.png) } +.big-862 { color: var(--brand); background: url(img/bg.png) } +.big-863 { color: var(--brand); background: url(img/bg.png) } +.big-864 { color: var(--brand); background: url(img/bg.png) } +.big-865 { color: var(--brand); background: url(img/bg.png) } +.big-866 { color: var(--brand); background: url(img/bg.png) } +.big-867 { color: var(--brand); background: url(img/bg.png) } +.big-868 { color: var(--brand); background: url(img/bg.png) } +.big-869 { color: var(--brand); background: url(img/bg.png) } +.big-870 { color: var(--brand); background: url(img/bg.png) } +.big-871 { color: var(--brand); background: url(img/bg.png) } +.big-872 { color: var(--brand); background: url(img/bg.png) } +.big-873 { color: var(--brand); background: url(img/bg.png) } +.big-874 { color: var(--brand); background: url(img/bg.png) } +.big-875 { color: var(--brand); background: url(img/bg.png) } +.big-876 { color: var(--brand); background: url(img/bg.png) } +.big-877 { color: var(--brand); background: url(img/bg.png) } +.big-878 { color: var(--brand); background: url(img/bg.png) } +.big-879 { color: var(--brand); background: url(img/bg.png) } +.big-880 { color: var(--brand); background: url(img/bg.png) } +.big-881 { color: var(--brand); background: url(img/bg.png) } +.big-882 { color: var(--brand); background: url(img/bg.png) } +.big-883 { color: var(--brand); background: url(img/bg.png) } +.big-884 { color: var(--brand); background: url(img/bg.png) } +.big-885 { color: var(--brand); background: url(img/bg.png) } +.big-886 { color: var(--brand); background: url(img/bg.png) } +.big-887 { color: var(--brand); background: url(img/bg.png) } +.big-888 { color: var(--brand); background: url(img/bg.png) } +.big-889 { color: var(--brand); background: url(img/bg.png) } +.big-890 { color: var(--brand); background: url(img/bg.png) } +.big-891 { color: var(--brand); background: url(img/bg.png) } +.big-892 { color: var(--brand); background: url(img/bg.png) } +.big-893 { color: var(--brand); background: url(img/bg.png) } +.big-894 { color: var(--brand); background: url(img/bg.png) } +.big-895 { color: var(--brand); background: url(img/bg.png) } +.big-896 { color: var(--brand); background: url(img/bg.png) } +.big-897 { color: var(--brand); background: url(img/bg.png) } +.big-898 { color: var(--brand); background: url(img/bg.png) } +.big-899 { color: var(--brand); background: url(img/bg.png) } +.big-900 { color: var(--brand); background: url(img/bg.png) } +.big-901 { color: var(--brand); background: url(img/bg.png) } +.big-902 { color: var(--brand); background: url(img/bg.png) } +.big-903 { color: var(--brand); background: url(img/bg.png) } +.big-904 { color: var(--brand); background: url(img/bg.png) } +.big-905 { color: var(--brand); background: url(img/bg.png) } +.big-906 { color: var(--brand); background: url(img/bg.png) } +.big-907 { color: var(--brand); background: url(img/bg.png) } +.big-908 { color: var(--brand); background: url(img/bg.png) } +.big-909 { color: var(--brand); background: url(img/bg.png) } +.big-910 { color: var(--brand); background: url(img/bg.png) } +.big-911 { color: var(--brand); background: url(img/bg.png) } +.big-912 { color: var(--brand); background: url(img/bg.png) } +.big-913 { color: var(--brand); background: url(img/bg.png) } +.big-914 { color: var(--brand); background: url(img/bg.png) } +.big-915 { color: var(--brand); background: url(img/bg.png) } +.big-916 { color: var(--brand); background: url(img/bg.png) } +.big-917 { color: var(--brand); background: url(img/bg.png) } +.big-918 { color: var(--brand); background: url(img/bg.png) } +.big-919 { color: var(--brand); background: url(img/bg.png) } +.big-920 { color: var(--brand); background: url(img/bg.png) } +.big-921 { color: var(--brand); background: url(img/bg.png) } +.big-922 { color: var(--brand); background: url(img/bg.png) } +.big-923 { color: var(--brand); background: url(img/bg.png) } +.big-924 { color: var(--brand); background: url(img/bg.png) } +.big-925 { color: var(--brand); background: url(img/bg.png) } +.big-926 { color: var(--brand); background: url(img/bg.png) } +.big-927 { color: var(--brand); background: url(img/bg.png) } +.big-928 { color: var(--brand); background: url(img/bg.png) } +.big-929 { color: var(--brand); background: url(img/bg.png) } +.big-930 { color: var(--brand); background: url(img/bg.png) } +.big-931 { color: var(--brand); background: url(img/bg.png) } +.big-932 { color: var(--brand); background: url(img/bg.png) } +.big-933 { color: var(--brand); background: url(img/bg.png) } +.big-934 { color: var(--brand); background: url(img/bg.png) } +.big-935 { color: var(--brand); background: url(img/bg.png) } +.big-936 { color: var(--brand); background: url(img/bg.png) } +.big-937 { color: var(--brand); background: url(img/bg.png) } +.big-938 { color: var(--brand); background: url(img/bg.png) } +.big-939 { color: var(--brand); background: url(img/bg.png) } +.big-940 { color: var(--brand); background: url(img/bg.png) } +.big-941 { color: var(--brand); background: url(img/bg.png) } +.big-942 { color: var(--brand); background: url(img/bg.png) } +.big-943 { color: var(--brand); background: url(img/bg.png) } +.big-944 { color: var(--brand); background: url(img/bg.png) } +.big-945 { color: var(--brand); background: url(img/bg.png) } +.big-946 { color: var(--brand); background: url(img/bg.png) } +.big-947 { color: var(--brand); background: url(img/bg.png) } +.big-948 { color: var(--brand); background: url(img/bg.png) } +.big-949 { color: var(--brand); background: url(img/bg.png) } +.big-950 { color: var(--brand); background: url(img/bg.png) } +.big-951 { color: var(--brand); background: url(img/bg.png) } +.big-952 { color: var(--brand); background: url(img/bg.png) } +.big-953 { color: var(--brand); background: url(img/bg.png) } +.big-954 { color: var(--brand); background: url(img/bg.png) } +.big-955 { color: var(--brand); background: url(img/bg.png) } +.big-956 { color: var(--brand); background: url(img/bg.png) } +.big-957 { color: var(--brand); background: url(img/bg.png) } +.big-958 { color: var(--brand); background: url(img/bg.png) } +.big-959 { color: var(--brand); background: url(img/bg.png) } +.big-960 { color: var(--brand); background: url(img/bg.png) } +.big-961 { color: var(--brand); background: url(img/bg.png) } +.big-962 { color: var(--brand); background: url(img/bg.png) } +.big-963 { color: var(--brand); background: url(img/bg.png) } +.big-964 { color: var(--brand); background: url(img/bg.png) } +.big-965 { color: var(--brand); background: url(img/bg.png) } +.big-966 { color: var(--brand); background: url(img/bg.png) } +.big-967 { color: var(--brand); background: url(img/bg.png) } +.big-968 { color: var(--brand); background: url(img/bg.png) } +.big-969 { color: var(--brand); background: url(img/bg.png) } +.big-970 { color: var(--brand); background: url(img/bg.png) } +.big-971 { color: var(--brand); background: url(img/bg.png) } +.big-972 { color: var(--brand); background: url(img/bg.png) } +.big-973 { color: var(--brand); background: url(img/bg.png) } +.big-974 { color: var(--brand); background: url(img/bg.png) } +.big-975 { color: var(--brand); background: url(img/bg.png) } +.big-976 { color: var(--brand); background: url(img/bg.png) } +.big-977 { color: var(--brand); background: url(img/bg.png) } +.big-978 { color: var(--brand); background: url(img/bg.png) } +.big-979 { color: var(--brand); background: url(img/bg.png) } +.big-980 { color: var(--brand); background: url(img/bg.png) } +.big-981 { color: var(--brand); background: url(img/bg.png) } +.big-982 { color: var(--brand); background: url(img/bg.png) } +.big-983 { color: var(--brand); background: url(img/bg.png) } +.big-984 { color: var(--brand); background: url(img/bg.png) } +.big-985 { color: var(--brand); background: url(img/bg.png) } +.big-986 { color: var(--brand); background: url(img/bg.png) } +.big-987 { color: var(--brand); background: url(img/bg.png) } +.big-988 { color: var(--brand); background: url(img/bg.png) } +.big-989 { color: var(--brand); background: url(img/bg.png) } +.big-990 { color: var(--brand); background: url(img/bg.png) } +.big-991 { color: var(--brand); background: url(img/bg.png) } +.big-992 { color: var(--brand); background: url(img/bg.png) } +.big-993 { color: var(--brand); background: url(img/bg.png) } +.big-994 { color: var(--brand); background: url(img/bg.png) } +.big-995 { color: var(--brand); background: url(img/bg.png) } +.big-996 { color: var(--brand); background: url(img/bg.png) } +.big-997 { color: var(--brand); background: url(img/bg.png) } +.big-998 { color: var(--brand); background: url(img/bg.png) } +.big-999 { color: var(--brand); background: url(img/bg.png) } +.big-1000 { color: var(--brand); background: url(img/bg.png) } +.big-1001 { color: var(--brand); background: url(img/bg.png) } +.big-1002 { color: var(--brand); background: url(img/bg.png) } +.big-1003 { color: var(--brand); background: url(img/bg.png) } +.big-1004 { color: var(--brand); background: url(img/bg.png) } +.big-1005 { color: var(--brand); background: url(img/bg.png) } +.big-1006 { color: var(--brand); background: url(img/bg.png) } +.big-1007 { color: var(--brand); background: url(img/bg.png) } +.big-1008 { color: var(--brand); background: url(img/bg.png) } +.big-1009 { color: var(--brand); background: url(img/bg.png) } +.big-1010 { color: var(--brand); background: url(img/bg.png) } +.big-1011 { color: var(--brand); background: url(img/bg.png) } +.big-1012 { color: var(--brand); background: url(img/bg.png) } +.big-1013 { color: var(--brand); background: url(img/bg.png) } +.big-1014 { color: var(--brand); background: url(img/bg.png) } +.big-1015 { color: var(--brand); background: url(img/bg.png) } +.big-1016 { color: var(--brand); background: url(img/bg.png) } +.big-1017 { color: var(--brand); background: url(img/bg.png) } +.big-1018 { color: var(--brand); background: url(img/bg.png) } +.big-1019 { color: var(--brand); background: url(img/bg.png) } +.big-1020 { color: var(--brand); background: url(img/bg.png) } +.big-1021 { color: var(--brand); background: url(img/bg.png) } +.big-1022 { color: var(--brand); background: url(img/bg.png) } +.big-1023 { color: var(--brand); background: url(img/bg.png) } +.big-1024 { color: var(--brand); background: url(img/bg.png) } +.big-1025 { color: var(--brand); background: url(img/bg.png) } +.big-1026 { color: var(--brand); background: url(img/bg.png) } +.big-1027 { color: var(--brand); background: url(img/bg.png) } +.big-1028 { color: var(--brand); background: url(img/bg.png) } +.big-1029 { color: var(--brand); background: url(img/bg.png) } +.big-1030 { color: var(--brand); background: url(img/bg.png) } +.big-1031 { color: var(--brand); background: url(img/bg.png) } +.big-1032 { color: var(--brand); background: url(img/bg.png) } +.big-1033 { color: var(--brand); background: url(img/bg.png) } +.big-1034 { color: var(--brand); background: url(img/bg.png) } +.big-1035 { color: var(--brand); background: url(img/bg.png) } +.big-1036 { color: var(--brand); background: url(img/bg.png) } +.big-1037 { color: var(--brand); background: url(img/bg.png) } +.big-1038 { color: var(--brand); background: url(img/bg.png) } +.big-1039 { color: var(--brand); background: url(img/bg.png) } +.big-1040 { color: var(--brand); background: url(img/bg.png) } +.big-1041 { color: var(--brand); background: url(img/bg.png) } +.big-1042 { color: var(--brand); background: url(img/bg.png) } +.big-1043 { color: var(--brand); background: url(img/bg.png) } +.big-1044 { color: var(--brand); background: url(img/bg.png) } +.big-1045 { color: var(--brand); background: url(img/bg.png) } +.big-1046 { color: var(--brand); background: url(img/bg.png) } +.big-1047 { color: var(--brand); background: url(img/bg.png) } +.big-1048 { color: var(--brand); background: url(img/bg.png) } +.big-1049 { color: var(--brand); background: url(img/bg.png) } +.big-1050 { color: var(--brand); background: url(img/bg.png) } +.big-1051 { color: var(--brand); background: url(img/bg.png) } +.big-1052 { color: var(--brand); background: url(img/bg.png) } +.big-1053 { color: var(--brand); background: url(img/bg.png) } +.big-1054 { color: var(--brand); background: url(img/bg.png) } +.big-1055 { color: var(--brand); background: url(img/bg.png) } +.big-1056 { color: var(--brand); background: url(img/bg.png) } +.big-1057 { color: var(--brand); background: url(img/bg.png) } +.big-1058 { color: var(--brand); background: url(img/bg.png) } +.big-1059 { color: var(--brand); background: url(img/bg.png) } +.big-1060 { color: var(--brand); background: url(img/bg.png) } +.big-1061 { color: var(--brand); background: url(img/bg.png) } +.big-1062 { color: var(--brand); background: url(img/bg.png) } +.big-1063 { color: var(--brand); background: url(img/bg.png) } +.big-1064 { color: var(--brand); background: url(img/bg.png) } +.big-1065 { color: var(--brand); background: url(img/bg.png) } +.big-1066 { color: var(--brand); background: url(img/bg.png) } +.big-1067 { color: var(--brand); background: url(img/bg.png) } +.big-1068 { color: var(--brand); background: url(img/bg.png) } +.big-1069 { color: var(--brand); background: url(img/bg.png) } +.big-1070 { color: var(--brand); background: url(img/bg.png) } +.big-1071 { color: var(--brand); background: url(img/bg.png) } +.big-1072 { color: var(--brand); background: url(img/bg.png) } +.big-1073 { color: var(--brand); background: url(img/bg.png) } +.big-1074 { color: var(--brand); background: url(img/bg.png) } +.big-1075 { color: var(--brand); background: url(img/bg.png) } +.big-1076 { color: var(--brand); background: url(img/bg.png) } +.big-1077 { color: var(--brand); background: url(img/bg.png) } +.big-1078 { color: var(--brand); background: url(img/bg.png) } +.big-1079 { color: var(--brand); background: url(img/bg.png) } +.big-1080 { color: var(--brand); background: url(img/bg.png) } +.big-1081 { color: var(--brand); background: url(img/bg.png) } +.big-1082 { color: var(--brand); background: url(img/bg.png) } +.big-1083 { color: var(--brand); background: url(img/bg.png) } +.big-1084 { color: var(--brand); background: url(img/bg.png) } +.big-1085 { color: var(--brand); background: url(img/bg.png) } +.big-1086 { color: var(--brand); background: url(img/bg.png) } +.big-1087 { color: var(--brand); background: url(img/bg.png) } +.big-1088 { color: var(--brand); background: url(img/bg.png) } +.big-1089 { color: var(--brand); background: url(img/bg.png) } +.big-1090 { color: var(--brand); background: url(img/bg.png) } +.big-1091 { color: var(--brand); background: url(img/bg.png) } +.big-1092 { color: var(--brand); background: url(img/bg.png) } +.big-1093 { color: var(--brand); background: url(img/bg.png) } +.big-1094 { color: var(--brand); background: url(img/bg.png) } +.big-1095 { color: var(--brand); background: url(img/bg.png) } +.big-1096 { color: var(--brand); background: url(img/bg.png) } +.big-1097 { color: var(--brand); background: url(img/bg.png) } +.big-1098 { color: var(--brand); background: url(img/bg.png) } +.big-1099 { color: var(--brand); background: url(img/bg.png) } +.big-1100 { color: var(--brand); background: url(img/bg.png) } +.big-1101 { color: var(--brand); background: url(img/bg.png) } +.big-1102 { color: var(--brand); background: url(img/bg.png) } +.big-1103 { color: var(--brand); background: url(img/bg.png) } +.big-1104 { color: var(--brand); background: url(img/bg.png) } +.big-1105 { color: var(--brand); background: url(img/bg.png) } +.big-1106 { color: var(--brand); background: url(img/bg.png) } +.big-1107 { color: var(--brand); background: url(img/bg.png) } +.big-1108 { color: var(--brand); background: url(img/bg.png) } +.big-1109 { color: var(--brand); background: url(img/bg.png) } +.big-1110 { color: var(--brand); background: url(img/bg.png) } +.big-1111 { color: var(--brand); background: url(img/bg.png) } +.big-1112 { color: var(--brand); background: url(img/bg.png) } +.big-1113 { color: var(--brand); background: url(img/bg.png) } +.big-1114 { color: var(--brand); background: url(img/bg.png) } +.big-1115 { color: var(--brand); background: url(img/bg.png) } +.big-1116 { color: var(--brand); background: url(img/bg.png) } +.big-1117 { color: var(--brand); background: url(img/bg.png) } +.big-1118 { color: var(--brand); background: url(img/bg.png) } +.big-1119 { color: var(--brand); background: url(img/bg.png) } +.big-1120 { color: var(--brand); background: url(img/bg.png) } +.big-1121 { color: var(--brand); background: url(img/bg.png) } +.big-1122 { color: var(--brand); background: url(img/bg.png) } +.big-1123 { color: var(--brand); background: url(img/bg.png) } +.big-1124 { color: var(--brand); background: url(img/bg.png) } +.big-1125 { color: var(--brand); background: url(img/bg.png) } +.big-1126 { color: var(--brand); background: url(img/bg.png) } +.big-1127 { color: var(--brand); background: url(img/bg.png) } +.big-1128 { color: var(--brand); background: url(img/bg.png) } +.big-1129 { color: var(--brand); background: url(img/bg.png) } +.big-1130 { color: var(--brand); background: url(img/bg.png) } +.big-1131 { color: var(--brand); background: url(img/bg.png) } +.big-1132 { color: var(--brand); background: url(img/bg.png) } +.big-1133 { color: var(--brand); background: url(img/bg.png) } +.big-1134 { color: var(--brand); background: url(img/bg.png) } +.big-1135 { color: var(--brand); background: url(img/bg.png) } +.big-1136 { color: var(--brand); background: url(img/bg.png) } +.big-1137 { color: var(--brand); background: url(img/bg.png) } +.big-1138 { color: var(--brand); background: url(img/bg.png) } +.big-1139 { color: var(--brand); background: url(img/bg.png) } +.big-1140 { color: var(--brand); background: url(img/bg.png) } +.big-1141 { color: var(--brand); background: url(img/bg.png) } +.big-1142 { color: var(--brand); background: url(img/bg.png) } +.big-1143 { color: var(--brand); background: url(img/bg.png) } +.big-1144 { color: var(--brand); background: url(img/bg.png) } +.big-1145 { color: var(--brand); background: url(img/bg.png) } +.big-1146 { color: var(--brand); background: url(img/bg.png) } +.big-1147 { color: var(--brand); background: url(img/bg.png) } +.big-1148 { color: var(--brand); background: url(img/bg.png) } +.big-1149 { color: var(--brand); background: url(img/bg.png) } +.big-1150 { color: var(--brand); background: url(img/bg.png) } +.big-1151 { color: var(--brand); background: url(img/bg.png) } +.big-1152 { color: var(--brand); background: url(img/bg.png) } +.big-1153 { color: var(--brand); background: url(img/bg.png) } +.big-1154 { color: var(--brand); background: url(img/bg.png) } +.big-1155 { color: var(--brand); background: url(img/bg.png) } +.big-1156 { color: var(--brand); background: url(img/bg.png) } +.big-1157 { color: var(--brand); background: url(img/bg.png) } +.big-1158 { color: var(--brand); background: url(img/bg.png) } +.big-1159 { color: var(--brand); background: url(img/bg.png) } +.big-1160 { color: var(--brand); background: url(img/bg.png) } +.big-1161 { color: var(--brand); background: url(img/bg.png) } +.big-1162 { color: var(--brand); background: url(img/bg.png) } +.big-1163 { color: var(--brand); background: url(img/bg.png) } +.big-1164 { color: var(--brand); background: url(img/bg.png) } +.big-1165 { color: var(--brand); background: url(img/bg.png) } +.big-1166 { color: var(--brand); background: url(img/bg.png) } +.big-1167 { color: var(--brand); background: url(img/bg.png) } +.big-1168 { color: var(--brand); background: url(img/bg.png) } +.big-1169 { color: var(--brand); background: url(img/bg.png) } +.big-1170 { color: var(--brand); background: url(img/bg.png) } +.big-1171 { color: var(--brand); background: url(img/bg.png) } +.big-1172 { color: var(--brand); background: url(img/bg.png) } +.big-1173 { color: var(--brand); background: url(img/bg.png) } +.big-1174 { color: var(--brand); background: url(img/bg.png) } +.big-1175 { color: var(--brand); background: url(img/bg.png) } +.big-1176 { color: var(--brand); background: url(img/bg.png) } +.big-1177 { color: var(--brand); background: url(img/bg.png) } +.big-1178 { color: var(--brand); background: url(img/bg.png) } +.big-1179 { color: var(--brand); background: url(img/bg.png) } +.big-1180 { color: var(--brand); background: url(img/bg.png) } +.big-1181 { color: var(--brand); background: url(img/bg.png) } +.big-1182 { color: var(--brand); background: url(img/bg.png) } +.big-1183 { color: var(--brand); background: url(img/bg.png) } +.big-1184 { color: var(--brand); background: url(img/bg.png) } +.big-1185 { color: var(--brand); background: url(img/bg.png) } +.big-1186 { color: var(--brand); background: url(img/bg.png) } +.big-1187 { color: var(--brand); background: url(img/bg.png) } +.big-1188 { color: var(--brand); background: url(img/bg.png) } +.big-1189 { color: var(--brand); background: url(img/bg.png) } +.big-1190 { color: var(--brand); background: url(img/bg.png) } +.big-1191 { color: var(--brand); background: url(img/bg.png) } +.big-1192 { color: var(--brand); background: url(img/bg.png) } +.big-1193 { color: var(--brand); background: url(img/bg.png) } +.big-1194 { color: var(--brand); background: url(img/bg.png) } +.big-1195 { color: var(--brand); background: url(img/bg.png) } +.big-1196 { color: var(--brand); background: url(img/bg.png) } +.big-1197 { color: var(--brand); background: url(img/bg.png) } +.big-1198 { color: var(--brand); background: url(img/bg.png) } +.big-1199 { color: var(--brand); background: url(img/bg.png) } +.big-1200 { color: var(--brand); background: url(img/bg.png) } +.big-1201 { color: var(--brand); background: url(img/bg.png) } +.big-1202 { color: var(--brand); background: url(img/bg.png) } +.big-1203 { color: var(--brand); background: url(img/bg.png) } +.big-1204 { color: var(--brand); background: url(img/bg.png) } +.big-1205 { color: var(--brand); background: url(img/bg.png) } +.big-1206 { color: var(--brand); background: url(img/bg.png) } +.big-1207 { color: var(--brand); background: url(img/bg.png) } +.big-1208 { color: var(--brand); background: url(img/bg.png) } +.big-1209 { color: var(--brand); background: url(img/bg.png) } +.big-1210 { color: var(--brand); background: url(img/bg.png) } +.big-1211 { color: var(--brand); background: url(img/bg.png) } +.big-1212 { color: var(--brand); background: url(img/bg.png) } +.big-1213 { color: var(--brand); background: url(img/bg.png) } +.big-1214 { color: var(--brand); background: url(img/bg.png) } +.big-1215 { color: var(--brand); background: url(img/bg.png) } +.big-1216 { color: var(--brand); background: url(img/bg.png) } +.big-1217 { color: var(--brand); background: url(img/bg.png) } +.big-1218 { color: var(--brand); background: url(img/bg.png) } +.big-1219 { color: var(--brand); background: url(img/bg.png) } +.big-1220 { color: var(--brand); background: url(img/bg.png) } +.big-1221 { color: var(--brand); background: url(img/bg.png) } +.big-1222 { color: var(--brand); background: url(img/bg.png) } +.big-1223 { color: var(--brand); background: url(img/bg.png) } +.big-1224 { color: var(--brand); background: url(img/bg.png) } +.big-1225 { color: var(--brand); background: url(img/bg.png) } +.big-1226 { color: var(--brand); background: url(img/bg.png) } +.big-1227 { color: var(--brand); background: url(img/bg.png) } +.big-1228 { color: var(--brand); background: url(img/bg.png) } +.big-1229 { color: var(--brand); background: url(img/bg.png) } +.big-1230 { color: var(--brand); background: url(img/bg.png) } +.big-1231 { color: var(--brand); background: url(img/bg.png) } +.big-1232 { color: var(--brand); background: url(img/bg.png) } +.big-1233 { color: var(--brand); background: url(img/bg.png) } +.big-1234 { color: var(--brand); background: url(img/bg.png) } +.big-1235 { color: var(--brand); background: url(img/bg.png) } +.big-1236 { color: var(--brand); background: url(img/bg.png) } +.big-1237 { color: var(--brand); background: url(img/bg.png) } +.big-1238 { color: var(--brand); background: url(img/bg.png) } +.big-1239 { color: var(--brand); background: url(img/bg.png) } +.big-1240 { color: var(--brand); background: url(img/bg.png) } +.big-1241 { color: var(--brand); background: url(img/bg.png) } +.big-1242 { color: var(--brand); background: url(img/bg.png) } +.big-1243 { color: var(--brand); background: url(img/bg.png) } +.big-1244 { color: var(--brand); background: url(img/bg.png) } +.big-1245 { color: var(--brand); background: url(img/bg.png) } +.big-1246 { color: var(--brand); background: url(img/bg.png) } +.big-1247 { color: var(--brand); background: url(img/bg.png) } +.big-1248 { color: var(--brand); background: url(img/bg.png) } +.big-1249 { color: var(--brand); background: url(img/bg.png) } +.big-1250 { color: var(--brand); background: url(img/bg.png) } +.big-1251 { color: var(--brand); background: url(img/bg.png) } +.big-1252 { color: var(--brand); background: url(img/bg.png) } +.big-1253 { color: var(--brand); background: url(img/bg.png) } +.big-1254 { color: var(--brand); background: url(img/bg.png) } +.big-1255 { color: var(--brand); background: url(img/bg.png) } +.big-1256 { color: var(--brand); background: url(img/bg.png) } +.big-1257 { color: var(--brand); background: url(img/bg.png) } +.big-1258 { color: var(--brand); background: url(img/bg.png) } +.big-1259 { color: var(--brand); background: url(img/bg.png) } +.big-1260 { color: var(--brand); background: url(img/bg.png) } +.big-1261 { color: var(--brand); background: url(img/bg.png) } +.big-1262 { color: var(--brand); background: url(img/bg.png) } +.big-1263 { color: var(--brand); background: url(img/bg.png) } +.big-1264 { color: var(--brand); background: url(img/bg.png) } +.big-1265 { color: var(--brand); background: url(img/bg.png) } +.big-1266 { color: var(--brand); background: url(img/bg.png) } +.big-1267 { color: var(--brand); background: url(img/bg.png) } +.big-1268 { color: var(--brand); background: url(img/bg.png) } +.big-1269 { color: var(--brand); background: url(img/bg.png) } +.big-1270 { color: var(--brand); background: url(img/bg.png) } +.big-1271 { color: var(--brand); background: url(img/bg.png) } +.big-1272 { color: var(--brand); background: url(img/bg.png) } +.big-1273 { color: var(--brand); background: url(img/bg.png) } +.big-1274 { color: var(--brand); background: url(img/bg.png) } +.big-1275 { color: var(--brand); background: url(img/bg.png) } +.big-1276 { color: var(--brand); background: url(img/bg.png) } +.big-1277 { color: var(--brand); background: url(img/bg.png) } +.big-1278 { color: var(--brand); background: url(img/bg.png) } +.big-1279 { color: var(--brand); background: url(img/bg.png) } +.big-1280 { color: var(--brand); background: url(img/bg.png) } +.big-1281 { color: var(--brand); background: url(img/bg.png) } +.big-1282 { color: var(--brand); background: url(img/bg.png) } +.big-1283 { color: var(--brand); background: url(img/bg.png) } +.big-1284 { color: var(--brand); background: url(img/bg.png) } +.big-1285 { color: var(--brand); background: url(img/bg.png) } +.big-1286 { color: var(--brand); background: url(img/bg.png) } +.big-1287 { color: var(--brand); background: url(img/bg.png) } +.big-1288 { color: var(--brand); background: url(img/bg.png) } +.big-1289 { color: var(--brand); background: url(img/bg.png) } +.big-1290 { color: var(--brand); background: url(img/bg.png) } +.big-1291 { color: var(--brand); background: url(img/bg.png) } +.big-1292 { color: var(--brand); background: url(img/bg.png) } +.big-1293 { color: var(--brand); background: url(img/bg.png) } +.big-1294 { color: var(--brand); background: url(img/bg.png) } +.big-1295 { color: var(--brand); background: url(img/bg.png) } +.big-1296 { color: var(--brand); background: url(img/bg.png) } +.big-1297 { color: var(--brand); background: url(img/bg.png) } +.big-1298 { color: var(--brand); background: url(img/bg.png) } +.big-1299 { color: var(--brand); background: url(img/bg.png) } +.big-1300 { color: var(--brand); background: url(img/bg.png) } +.big-1301 { color: var(--brand); background: url(img/bg.png) } +.big-1302 { color: var(--brand); background: url(img/bg.png) } +.big-1303 { color: var(--brand); background: url(img/bg.png) } +.big-1304 { color: var(--brand); background: url(img/bg.png) } +.big-1305 { color: var(--brand); background: url(img/bg.png) } +.big-1306 { color: var(--brand); background: url(img/bg.png) } +.big-1307 { color: var(--brand); background: url(img/bg.png) } +.big-1308 { color: var(--brand); background: url(img/bg.png) } +.big-1309 { color: var(--brand); background: url(img/bg.png) } +.big-1310 { color: var(--brand); background: url(img/bg.png) } +.big-1311 { color: var(--brand); background: url(img/bg.png) } +.big-1312 { color: var(--brand); background: url(img/bg.png) } +.big-1313 { color: var(--brand); background: url(img/bg.png) } +.big-1314 { color: var(--brand); background: url(img/bg.png) } +.big-1315 { color: var(--brand); background: url(img/bg.png) } +.big-1316 { color: var(--brand); background: url(img/bg.png) } +.big-1317 { color: var(--brand); background: url(img/bg.png) } +.big-1318 { color: var(--brand); background: url(img/bg.png) } +.big-1319 { color: var(--brand); background: url(img/bg.png) } +.big-1320 { color: var(--brand); background: url(img/bg.png) } +.big-1321 { color: var(--brand); background: url(img/bg.png) } +.big-1322 { color: var(--brand); background: url(img/bg.png) } +.big-1323 { color: var(--brand); background: url(img/bg.png) } +.big-1324 { color: var(--brand); background: url(img/bg.png) } +.big-1325 { color: var(--brand); background: url(img/bg.png) } +.big-1326 { color: var(--brand); background: url(img/bg.png) } +.big-1327 { color: var(--brand); background: url(img/bg.png) } +.big-1328 { color: var(--brand); background: url(img/bg.png) } +.big-1329 { color: var(--brand); background: url(img/bg.png) } +.big-1330 { color: var(--brand); background: url(img/bg.png) } +.big-1331 { color: var(--brand); background: url(img/bg.png) } +.big-1332 { color: var(--brand); background: url(img/bg.png) } +.big-1333 { color: var(--brand); background: url(img/bg.png) } +.big-1334 { color: var(--brand); background: url(img/bg.png) } +.big-1335 { color: var(--brand); background: url(img/bg.png) } +.big-1336 { color: var(--brand); background: url(img/bg.png) } +.big-1337 { color: var(--brand); background: url(img/bg.png) } +.big-1338 { color: var(--brand); background: url(img/bg.png) } +.big-1339 { color: var(--brand); background: url(img/bg.png) } +.big-1340 { color: var(--brand); background: url(img/bg.png) } +.big-1341 { color: var(--brand); background: url(img/bg.png) } +.big-1342 { color: var(--brand); background: url(img/bg.png) } +.big-1343 { color: var(--brand); background: url(img/bg.png) } +.big-1344 { color: var(--brand); background: url(img/bg.png) } +.big-1345 { color: var(--brand); background: url(img/bg.png) } +.big-1346 { color: var(--brand); background: url(img/bg.png) } +.big-1347 { color: var(--brand); background: url(img/bg.png) } +.big-1348 { color: var(--brand); background: url(img/bg.png) } +.big-1349 { color: var(--brand); background: url(img/bg.png) } +.big-1350 { color: var(--brand); background: url(img/bg.png) } +.big-1351 { color: var(--brand); background: url(img/bg.png) } +.big-1352 { color: var(--brand); background: url(img/bg.png) } +.big-1353 { color: var(--brand); background: url(img/bg.png) } +.big-1354 { color: var(--brand); background: url(img/bg.png) } +.big-1355 { color: var(--brand); background: url(img/bg.png) } +.big-1356 { color: var(--brand); background: url(img/bg.png) } +.big-1357 { color: var(--brand); background: url(img/bg.png) } +.big-1358 { color: var(--brand); background: url(img/bg.png) } +.big-1359 { color: var(--brand); background: url(img/bg.png) } +.big-1360 { color: var(--brand); background: url(img/bg.png) } +.big-1361 { color: var(--brand); background: url(img/bg.png) } +.big-1362 { color: var(--brand); background: url(img/bg.png) } +.big-1363 { color: var(--brand); background: url(img/bg.png) } +.big-1364 { color: var(--brand); background: url(img/bg.png) } +.big-1365 { color: var(--brand); background: url(img/bg.png) } +.big-1366 { color: var(--brand); background: url(img/bg.png) } +.big-1367 { color: var(--brand); background: url(img/bg.png) } +.big-1368 { color: var(--brand); background: url(img/bg.png) } +.big-1369 { color: var(--brand); background: url(img/bg.png) } +.big-1370 { color: var(--brand); background: url(img/bg.png) } +.big-1371 { color: var(--brand); background: url(img/bg.png) } +.big-1372 { color: var(--brand); background: url(img/bg.png) } +.big-1373 { color: var(--brand); background: url(img/bg.png) } +.big-1374 { color: var(--brand); background: url(img/bg.png) } +.big-1375 { color: var(--brand); background: url(img/bg.png) } +.big-1376 { color: var(--brand); background: url(img/bg.png) } +.big-1377 { color: var(--brand); background: url(img/bg.png) } +.big-1378 { color: var(--brand); background: url(img/bg.png) } +.big-1379 { color: var(--brand); background: url(img/bg.png) } +.big-1380 { color: var(--brand); background: url(img/bg.png) } +.big-1381 { color: var(--brand); background: url(img/bg.png) } +.big-1382 { color: var(--brand); background: url(img/bg.png) } +.big-1383 { color: var(--brand); background: url(img/bg.png) } +.big-1384 { color: var(--brand); background: url(img/bg.png) } +.big-1385 { color: var(--brand); background: url(img/bg.png) } +.big-1386 { color: var(--brand); background: url(img/bg.png) } +.big-1387 { color: var(--brand); background: url(img/bg.png) } +.big-1388 { color: var(--brand); background: url(img/bg.png) } +.big-1389 { color: var(--brand); background: url(img/bg.png) } +.big-1390 { color: var(--brand); background: url(img/bg.png) } +.big-1391 { color: var(--brand); background: url(img/bg.png) } +.big-1392 { color: var(--brand); background: url(img/bg.png) } +.big-1393 { color: var(--brand); background: url(img/bg.png) } +.big-1394 { color: var(--brand); background: url(img/bg.png) } +.big-1395 { color: var(--brand); background: url(img/bg.png) } +.big-1396 { color: var(--brand); background: url(img/bg.png) } +.big-1397 { color: var(--brand); background: url(img/bg.png) } +.big-1398 { color: var(--brand); background: url(img/bg.png) } +.big-1399 { color: var(--brand); background: url(img/bg.png) } +.big-1400 { color: var(--brand); background: url(img/bg.png) } +.big-1401 { color: var(--brand); background: url(img/bg.png) } +.big-1402 { color: var(--brand); background: url(img/bg.png) } +.big-1403 { color: var(--brand); background: url(img/bg.png) } +.big-1404 { color: var(--brand); background: url(img/bg.png) } +.big-1405 { color: var(--brand); background: url(img/bg.png) } +.big-1406 { color: var(--brand); background: url(img/bg.png) } +.big-1407 { color: var(--brand); background: url(img/bg.png) } +.big-1408 { color: var(--brand); background: url(img/bg.png) } +.big-1409 { color: var(--brand); background: url(img/bg.png) } +.big-1410 { color: var(--brand); background: url(img/bg.png) } +.big-1411 { color: var(--brand); background: url(img/bg.png) } +.big-1412 { color: var(--brand); background: url(img/bg.png) } +.big-1413 { color: var(--brand); background: url(img/bg.png) } +.big-1414 { color: var(--brand); background: url(img/bg.png) } +.big-1415 { color: var(--brand); background: url(img/bg.png) } +.big-1416 { color: var(--brand); background: url(img/bg.png) } +.big-1417 { color: var(--brand); background: url(img/bg.png) } +.big-1418 { color: var(--brand); background: url(img/bg.png) } +.big-1419 { color: var(--brand); background: url(img/bg.png) } +.big-1420 { color: var(--brand); background: url(img/bg.png) } +.big-1421 { color: var(--brand); background: url(img/bg.png) } +.big-1422 { color: var(--brand); background: url(img/bg.png) } +.big-1423 { color: var(--brand); background: url(img/bg.png) } +.big-1424 { color: var(--brand); background: url(img/bg.png) } +.big-1425 { color: var(--brand); background: url(img/bg.png) } +.big-1426 { color: var(--brand); background: url(img/bg.png) } +.big-1427 { color: var(--brand); background: url(img/bg.png) } +.big-1428 { color: var(--brand); background: url(img/bg.png) } +.big-1429 { color: var(--brand); background: url(img/bg.png) } +.big-1430 { color: var(--brand); background: url(img/bg.png) } +.big-1431 { color: var(--brand); background: url(img/bg.png) } +.big-1432 { color: var(--brand); background: url(img/bg.png) } +.big-1433 { color: var(--brand); background: url(img/bg.png) } +.big-1434 { color: var(--brand); background: url(img/bg.png) } +.big-1435 { color: var(--brand); background: url(img/bg.png) } +.big-1436 { color: var(--brand); background: url(img/bg.png) } +.big-1437 { color: var(--brand); background: url(img/bg.png) } +.big-1438 { color: var(--brand); background: url(img/bg.png) } +.big-1439 { color: var(--brand); background: url(img/bg.png) } +.big-1440 { color: var(--brand); background: url(img/bg.png) } +.big-1441 { color: var(--brand); background: url(img/bg.png) } +.big-1442 { color: var(--brand); background: url(img/bg.png) } +.big-1443 { color: var(--brand); background: url(img/bg.png) } +.big-1444 { color: var(--brand); background: url(img/bg.png) } +.big-1445 { color: var(--brand); background: url(img/bg.png) } +.big-1446 { color: var(--brand); background: url(img/bg.png) } +.big-1447 { color: var(--brand); background: url(img/bg.png) } +.big-1448 { color: var(--brand); background: url(img/bg.png) } +.big-1449 { color: var(--brand); background: url(img/bg.png) } +.big-1450 { color: var(--brand); background: url(img/bg.png) } +.big-1451 { color: var(--brand); background: url(img/bg.png) } +.big-1452 { color: var(--brand); background: url(img/bg.png) } +.big-1453 { color: var(--brand); background: url(img/bg.png) } +.big-1454 { color: var(--brand); background: url(img/bg.png) } +.big-1455 { color: var(--brand); background: url(img/bg.png) } +.big-1456 { color: var(--brand); background: url(img/bg.png) } +.big-1457 { color: var(--brand); background: url(img/bg.png) } +.big-1458 { color: var(--brand); background: url(img/bg.png) } +.big-1459 { color: var(--brand); background: url(img/bg.png) } +.big-1460 { color: var(--brand); background: url(img/bg.png) } +.big-1461 { color: var(--brand); background: url(img/bg.png) } +.big-1462 { color: var(--brand); background: url(img/bg.png) } +.big-1463 { color: var(--brand); background: url(img/bg.png) } +.big-1464 { color: var(--brand); background: url(img/bg.png) } +.big-1465 { color: var(--brand); background: url(img/bg.png) } +.big-1466 { color: var(--brand); background: url(img/bg.png) } +.big-1467 { color: var(--brand); background: url(img/bg.png) } +.big-1468 { color: var(--brand); background: url(img/bg.png) } +.big-1469 { color: var(--brand); background: url(img/bg.png) } +.big-1470 { color: var(--brand); background: url(img/bg.png) } +.big-1471 { color: var(--brand); background: url(img/bg.png) } +.big-1472 { color: var(--brand); background: url(img/bg.png) } +.big-1473 { color: var(--brand); background: url(img/bg.png) } +.big-1474 { color: var(--brand); background: url(img/bg.png) } +.big-1475 { color: var(--brand); background: url(img/bg.png) } +.big-1476 { color: var(--brand); background: url(img/bg.png) } +.big-1477 { color: var(--brand); background: url(img/bg.png) } +.big-1478 { color: var(--brand); background: url(img/bg.png) } +.big-1479 { color: var(--brand); background: url(img/bg.png) } +.big-1480 { color: var(--brand); background: url(img/bg.png) } +.big-1481 { color: var(--brand); background: url(img/bg.png) } +.big-1482 { color: var(--brand); background: url(img/bg.png) } +.big-1483 { color: var(--brand); background: url(img/bg.png) } +.big-1484 { color: var(--brand); background: url(img/bg.png) } +.big-1485 { color: var(--brand); background: url(img/bg.png) } +.big-1486 { color: var(--brand); background: url(img/bg.png) } +.big-1487 { color: var(--brand); background: url(img/bg.png) } +.big-1488 { color: var(--brand); background: url(img/bg.png) } +.big-1489 { color: var(--brand); background: url(img/bg.png) } +.big-1490 { color: var(--brand); background: url(img/bg.png) } +.big-1491 { color: var(--brand); background: url(img/bg.png) } +.big-1492 { color: var(--brand); background: url(img/bg.png) } +.big-1493 { color: var(--brand); background: url(img/bg.png) } +.big-1494 { color: var(--brand); background: url(img/bg.png) } +.big-1495 { color: var(--brand); background: url(img/bg.png) } +.big-1496 { color: var(--brand); background: url(img/bg.png) } +.big-1497 { color: var(--brand); background: url(img/bg.png) } +.big-1498 { color: var(--brand); background: url(img/bg.png) } +.big-1499 { color: var(--brand); background: url(img/bg.png) } diff --git a/parser/src/test-data/web/torture/css/bom.css b/parser/src/test-data/web/torture/css/bom.css new file mode 100644 index 000000000..53fd637ba --- /dev/null +++ b/parser/src/test-data/web/torture/css/bom.css @@ -0,0 +1 @@ +.bom-class { color: red } diff --git a/parser/src/test-data/web/torture/css/disabled.css b/parser/src/test-data/web/torture/css/disabled.css new file mode 100644 index 000000000..bc3d0dc63 --- /dev/null +++ b/parser/src/test-data/web/torture/css/disabled.css @@ -0,0 +1 @@ +.disabled-only { color: red } diff --git a/parser/src/test-data/web/torture/css/fonts.css b/parser/src/test-data/web/torture/css/fonts.css new file mode 100644 index 000000000..d4d5fefe0 --- /dev/null +++ b/parser/src/test-data/web/torture/css/fonts.css @@ -0,0 +1,38 @@ +/* F01 font faces: multiple src, local(), format(), tech(), unicode-range; and the families that use them */ +@font-face { + font-family: "Inter"; + src: local("Inter"), local(Inter-Regular), + url(../fonts/inter.woff2) format("woff2") tech(variations), + url("../fonts/inter.woff") format("woff"), + url(../fonts/missing.woff) format("woff"), + url(https://fonts.example.com/inter.woff2); + font-weight: 100 900; + unicode-range: U+0000-00FF, U+0131; + font-display: swap; +} +@font-face { font-family: Inter; src: url(../fonts/inter.woff2); font-weight: bold } +@font-face { font-family: 'Roboto Mono'; src: url(../fonts/roboto.woff2) } +@font-face { font-family: "Unused Face"; src: url(../fonts/unused.woff2) } +@font-face { src: url(../fonts/nameless.woff2) } +@font-face { font-family: var(--font); src: url(../fonts/x.woff2) } +@font-face { font-family: "Segoe UI"; src: local("Segoe UI") } +.f1 { font-family: Inter } +.f2 { font-family: "Inter" } +.f3 { font-family: 'Inter' } +.f4 { font-family: inter } /* case-insensitive match in browsers */ +.f5 { font-family: Roboto Mono, monospace } +.f6 { font-family: "Roboto Mono", monospace } +.f7 { font-family: Segoe UI, Inter, Arial, sans-serif } +.f8 { font: 12px Inter } +.f9 { font: bold 12px/1 "Roboto Mono", monospace } +.f10 { font: italic small-caps bold condensed 16px/2 cursive } +.f11 { font: caption } +.f12 { font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto } +.f13 { font-family: "Nonexistent Face" } +.f14 { font-family: Inter !important } +.f15 { font-family: var(--font, Inter) } +.f16 { font-family: "Inter", } +.f17 { font-family: , Inter } +.f18 { font-family: "Inter"Inter } +.f19 { font-family: Inter Inter } +.f20 { font-family: "Inter" "Inter" } diff --git a/parser/src/test-data/web/torture/css/gaps-many.css b/parser/src/test-data/web/torture/css/gaps-many.css new file mode 100644 index 000000000..c9582e343 --- /dev/null +++ b/parser/src/test-data/web/torture/css/gaps-many.css @@ -0,0 +1,300 @@ +.k0 { color: rgb(1, } +.k1 { color: rgb(1, } +.k2 { color: rgb(1, } +.k3 { color: rgb(1, } +.k4 { color: rgb(1, } +.k5 { color: rgb(1, } +.k6 { color: rgb(1, } +.k7 { color: rgb(1, } +.k8 { color: rgb(1, } +.k9 { color: rgb(1, } +.k10 { color: rgb(1, } +.k11 { color: rgb(1, } +.k12 { color: rgb(1, } +.k13 { color: rgb(1, } +.k14 { color: rgb(1, } +.k15 { color: rgb(1, } +.k16 { color: rgb(1, } +.k17 { color: rgb(1, } +.k18 { color: rgb(1, } +.k19 { color: rgb(1, } +.k20 { color: rgb(1, } +.k21 { color: rgb(1, } +.k22 { color: rgb(1, } +.k23 { color: rgb(1, } +.k24 { color: rgb(1, } +.k25 { color: rgb(1, } +.k26 { color: rgb(1, } +.k27 { color: rgb(1, } +.k28 { color: rgb(1, } +.k29 { color: rgb(1, } +.k30 { color: rgb(1, } +.k31 { color: rgb(1, } +.k32 { color: rgb(1, } +.k33 { color: rgb(1, } +.k34 { color: rgb(1, } +.k35 { color: rgb(1, } +.k36 { color: rgb(1, } +.k37 { color: rgb(1, } +.k38 { color: rgb(1, } +.k39 { color: rgb(1, } +.k40 { color: rgb(1, } +.k41 { color: rgb(1, } +.k42 { color: rgb(1, } +.k43 { color: rgb(1, } +.k44 { color: rgb(1, } +.k45 { color: rgb(1, } +.k46 { color: rgb(1, } +.k47 { color: rgb(1, } +.k48 { color: rgb(1, } +.k49 { color: rgb(1, } +.k50 { color: rgb(1, } +.k51 { color: rgb(1, } +.k52 { color: rgb(1, } +.k53 { color: rgb(1, } +.k54 { color: rgb(1, } +.k55 { color: rgb(1, } +.k56 { color: rgb(1, } +.k57 { color: rgb(1, } +.k58 { color: rgb(1, } +.k59 { color: rgb(1, } +.k60 { color: rgb(1, } +.k61 { color: rgb(1, } +.k62 { color: rgb(1, } +.k63 { color: rgb(1, } +.k64 { color: rgb(1, } +.k65 { color: rgb(1, } +.k66 { color: rgb(1, } +.k67 { color: rgb(1, } +.k68 { color: rgb(1, } +.k69 { color: rgb(1, } +.k70 { color: rgb(1, } +.k71 { color: rgb(1, } +.k72 { color: rgb(1, } +.k73 { color: rgb(1, } +.k74 { color: rgb(1, } +.k75 { color: rgb(1, } +.k76 { color: rgb(1, } +.k77 { color: rgb(1, } +.k78 { color: rgb(1, } +.k79 { color: rgb(1, } +.k80 { color: rgb(1, } +.k81 { color: rgb(1, } +.k82 { color: rgb(1, } +.k83 { color: rgb(1, } +.k84 { color: rgb(1, } +.k85 { color: rgb(1, } +.k86 { color: rgb(1, } +.k87 { color: rgb(1, } +.k88 { color: rgb(1, } +.k89 { color: rgb(1, } +.k90 { color: rgb(1, } +.k91 { color: rgb(1, } +.k92 { color: rgb(1, } +.k93 { color: rgb(1, } +.k94 { color: rgb(1, } +.k95 { color: rgb(1, } +.k96 { color: rgb(1, } +.k97 { color: rgb(1, } +.k98 { color: rgb(1, } +.k99 { color: rgb(1, } +.k100 { color: rgb(1, } +.k101 { color: rgb(1, } +.k102 { color: rgb(1, } +.k103 { color: rgb(1, } +.k104 { color: rgb(1, } +.k105 { color: rgb(1, } +.k106 { color: rgb(1, } +.k107 { color: rgb(1, } +.k108 { color: rgb(1, } +.k109 { color: rgb(1, } +.k110 { color: rgb(1, } +.k111 { color: rgb(1, } +.k112 { color: rgb(1, } +.k113 { color: rgb(1, } +.k114 { color: rgb(1, } +.k115 { color: rgb(1, } +.k116 { color: rgb(1, } +.k117 { color: rgb(1, } +.k118 { color: rgb(1, } +.k119 { color: rgb(1, } +.k120 { color: rgb(1, } +.k121 { color: rgb(1, } +.k122 { color: rgb(1, } +.k123 { color: rgb(1, } +.k124 { color: rgb(1, } +.k125 { color: rgb(1, } +.k126 { color: rgb(1, } +.k127 { color: rgb(1, } +.k128 { color: rgb(1, } +.k129 { color: rgb(1, } +.k130 { color: rgb(1, } +.k131 { color: rgb(1, } +.k132 { color: rgb(1, } +.k133 { color: rgb(1, } +.k134 { color: rgb(1, } +.k135 { color: rgb(1, } +.k136 { color: rgb(1, } +.k137 { color: rgb(1, } +.k138 { color: rgb(1, } +.k139 { color: rgb(1, } +.k140 { color: rgb(1, } +.k141 { color: rgb(1, } +.k142 { color: rgb(1, } +.k143 { color: rgb(1, } +.k144 { color: rgb(1, } +.k145 { color: rgb(1, } +.k146 { color: rgb(1, } +.k147 { color: rgb(1, } +.k148 { color: rgb(1, } +.k149 { color: rgb(1, } +.k150 { color: rgb(1, } +.k151 { color: rgb(1, } +.k152 { color: rgb(1, } +.k153 { color: rgb(1, } +.k154 { color: rgb(1, } +.k155 { color: rgb(1, } +.k156 { color: rgb(1, } +.k157 { color: rgb(1, } +.k158 { color: rgb(1, } +.k159 { color: rgb(1, } +.k160 { color: rgb(1, } +.k161 { color: rgb(1, } +.k162 { color: rgb(1, } +.k163 { color: rgb(1, } +.k164 { color: rgb(1, } +.k165 { color: rgb(1, } +.k166 { color: rgb(1, } +.k167 { color: rgb(1, } +.k168 { color: rgb(1, } +.k169 { color: rgb(1, } +.k170 { color: rgb(1, } +.k171 { color: rgb(1, } +.k172 { color: rgb(1, } +.k173 { color: rgb(1, } +.k174 { color: rgb(1, } +.k175 { color: rgb(1, } +.k176 { color: rgb(1, } +.k177 { color: rgb(1, } +.k178 { color: rgb(1, } +.k179 { color: rgb(1, } +.k180 { color: rgb(1, } +.k181 { color: rgb(1, } +.k182 { color: rgb(1, } +.k183 { color: rgb(1, } +.k184 { color: rgb(1, } +.k185 { color: rgb(1, } +.k186 { color: rgb(1, } +.k187 { color: rgb(1, } +.k188 { color: rgb(1, } +.k189 { color: rgb(1, } +.k190 { color: rgb(1, } +.k191 { color: rgb(1, } +.k192 { color: rgb(1, } +.k193 { color: rgb(1, } +.k194 { color: rgb(1, } +.k195 { color: rgb(1, } +.k196 { color: rgb(1, } +.k197 { color: rgb(1, } +.k198 { color: rgb(1, } +.k199 { color: rgb(1, } +.k200 { color: rgb(1, } +.k201 { color: rgb(1, } +.k202 { color: rgb(1, } +.k203 { color: rgb(1, } +.k204 { color: rgb(1, } +.k205 { color: rgb(1, } +.k206 { color: rgb(1, } +.k207 { color: rgb(1, } +.k208 { color: rgb(1, } +.k209 { color: rgb(1, } +.k210 { color: rgb(1, } +.k211 { color: rgb(1, } +.k212 { color: rgb(1, } +.k213 { color: rgb(1, } +.k214 { color: rgb(1, } +.k215 { color: rgb(1, } +.k216 { color: rgb(1, } +.k217 { color: rgb(1, } +.k218 { color: rgb(1, } +.k219 { color: rgb(1, } +.k220 { color: rgb(1, } +.k221 { color: rgb(1, } +.k222 { color: rgb(1, } +.k223 { color: rgb(1, } +.k224 { color: rgb(1, } +.k225 { color: rgb(1, } +.k226 { color: rgb(1, } +.k227 { color: rgb(1, } +.k228 { color: rgb(1, } +.k229 { color: rgb(1, } +.k230 { color: rgb(1, } +.k231 { color: rgb(1, } +.k232 { color: rgb(1, } +.k233 { color: rgb(1, } +.k234 { color: rgb(1, } +.k235 { color: rgb(1, } +.k236 { color: rgb(1, } +.k237 { color: rgb(1, } +.k238 { color: rgb(1, } +.k239 { color: rgb(1, } +.k240 { color: rgb(1, } +.k241 { color: rgb(1, } +.k242 { color: rgb(1, } +.k243 { color: rgb(1, } +.k244 { color: rgb(1, } +.k245 { color: rgb(1, } +.k246 { color: rgb(1, } +.k247 { color: rgb(1, } +.k248 { color: rgb(1, } +.k249 { color: rgb(1, } +.k250 { color: rgb(1, } +.k251 { color: rgb(1, } +.k252 { color: rgb(1, } +.k253 { color: rgb(1, } +.k254 { color: rgb(1, } +.k255 { color: rgb(1, } +.k256 { color: rgb(1, } +.k257 { color: rgb(1, } +.k258 { color: rgb(1, } +.k259 { color: rgb(1, } +.k260 { color: rgb(1, } +.k261 { color: rgb(1, } +.k262 { color: rgb(1, } +.k263 { color: rgb(1, } +.k264 { color: rgb(1, } +.k265 { color: rgb(1, } +.k266 { color: rgb(1, } +.k267 { color: rgb(1, } +.k268 { color: rgb(1, } +.k269 { color: rgb(1, } +.k270 { color: rgb(1, } +.k271 { color: rgb(1, } +.k272 { color: rgb(1, } +.k273 { color: rgb(1, } +.k274 { color: rgb(1, } +.k275 { color: rgb(1, } +.k276 { color: rgb(1, } +.k277 { color: rgb(1, } +.k278 { color: rgb(1, } +.k279 { color: rgb(1, } +.k280 { color: rgb(1, } +.k281 { color: rgb(1, } +.k282 { color: rgb(1, } +.k283 { color: rgb(1, } +.k284 { color: rgb(1, } +.k285 { color: rgb(1, } +.k286 { color: rgb(1, } +.k287 { color: rgb(1, } +.k288 { color: rgb(1, } +.k289 { color: rgb(1, } +.k290 { color: rgb(1, } +.k291 { color: rgb(1, } +.k292 { color: rgb(1, } +.k293 { color: rgb(1, } +.k294 { color: rgb(1, } +.k295 { color: rgb(1, } +.k296 { color: rgb(1, } +.k297 { color: rgb(1, } +.k298 { color: rgb(1, } +.k299 { color: rgb(1, } diff --git a/parser/src/test-data/web/torture/css/gaps.css b/parser/src/test-data/web/torture/css/gaps.css new file mode 100644 index 000000000..5f8559ef5 --- /dev/null +++ b/parser/src/test-data/web/torture/css/gaps.css @@ -0,0 +1,34 @@ +/* G01 syntax the grammar must survive, with rows still emitted for the readable rest */ +.before-gaps { color: red } +.g1 { color: red +.g2 { color: blue } +.g3 { color: red }} +} +.g4 { ; color: red } +.g5 { color: red; ; ; } +.g6 { color } +.g7 { : } +.g8 { @media (x) { color: red } } +.g9 { color: red } .g10 +.g11 { color: red } } +@media { .g12 { color: red } } +@media screen { .g13 { color: red } +.g14 { color: red } +@unknown-rule foo { .g15 { color: red } } +@unknown-statement foo bar; +@import "late.css"; /* @import after rules is ignored by browsers */ +@charset "utf-8"; /* @charset not first is ignored by browsers */ +.g16 { color: red } /* unclosed comment +.g17 { color: red } +*/ +.g18 { color: red } + +.g20 { color: red } +.g21 { background: url(img/bg.png } +.g22 { color: rgb(1, 2 } +.g23 { width: calc(1px + } +.g24 { content: "x } +.g25 { color: red } +.g26 [ { color: red } +.g27 ( { color: red } +.g28 { color: red diff --git a/parser/src/test-data/web/torture/css/imported-by-style.css b/parser/src/test-data/web/torture/css/imported-by-style.css new file mode 100644 index 000000000..09a3be8f6 --- /dev/null +++ b/parser/src/test-data/web/torture/css/imported-by-style.css @@ -0,0 +1,2 @@ +.imported-by-style { color: red } +@import "tokens.css"; diff --git a/parser/src/test-data/web/torture/css/layers.css b/parser/src/test-data/web/torture/css/layers.css new file mode 100644 index 000000000..91288b036 --- /dev/null +++ b/parser/src/test-data/web/torture/css/layers.css @@ -0,0 +1,17 @@ +/* L01 cascade layers: statement, block, nested, dotted, anonymous, imported-into, media-wrapped */ +@layer reset, base, components.buttons, components.cards, utilities; +@layer reset { * { margin: 0 } } +@layer base { .card { color: red } @layer typography { h1 { font-size: 2rem } } } +@layer components { @layer buttons { .btn { color: red } } @layer cards { .card { color: blue } } } +@layer components.cards { .card .item { color: green } } +@layer base.typography { h2 { font-size: 1rem } } +@layer { .anonymous { color: red } } +@media screen { @layer utilities { .u-hide { display: none } } } +@layer utilities { @media print { .u-hide { display: block } } } +@import "print.css" layer(print-layer); +@import "alt.css" layer; +@layer sub.sub.sub { .deep-layer { color: red } } +@layer a, b, a; +@layer reset; +@layer spaced , names ; +.unlayered { color: red } diff --git a/parser/src/test-data/web/torture/css/long-line.css b/parser/src/test-data/web/torture/css/long-line.css new file mode 100644 index 000000000..af1eb44d3 --- /dev/null +++ b/parser/src/test-data/web/torture/css/long-line.css @@ -0,0 +1 @@ +.m1{color:red}.m2{color:red}.m3{color:red}.m4{color:red}.m5{color:red}.m6{color:red}.m7{color:red}.m8{color:red}.m9{color:red}.m10{color:red}.m11{color:red}.m12{color:red}.m13{color:red}.m14{color:red}.m15{color:red}.m16{color:red}.m17{color:red}.m18{color:red}.m19{color:red}.m20{color:red}.m21{color:red}.m22{color:red}.m23{color:red}.m24{color:red}.m25{color:red}.m26{color:red}.m27{color:red}.m28{color:red}.m29{color:red}.m30{color:red}.m31{color:red}.m32{color:red}.m33{color:red}.m34{color:red}.m35{color:red}.m36{color:red}.m37{color:red}.m38{color:red}.m39{color:red}.m40{color:red}.m41{color:red}.m42{color:red}.m43{color:red}.m44{color:red}.m45{color:red}.m46{color:red}.m47{color:red}.m48{color:red}.m49{color:red}.m50{color:red}.m51{color:red}.m52{color:red}.m53{color:red}.m54{color:red}.m55{color:red}.m56{color:red}.m57{color:red}.m58{color:red}.m59{color:red}.m60{color:red}.m61{color:red}.m62{color:red}.m63{color:red}.m64{color:red}.m65{color:red}.m66{color:red}.m67{color:red}.m68{color:red}.m69{color:red}.m70{color:red}.m71{color:red}.m72{color:red}.m73{color:red}.m74{color:red}.m75{color:red}.m76{color:red}.m77{color:red}.m78{color:red}.m79{color:red}.m80{color:red}.m81{color:red}.m82{color:red}.m83{color:red}.m84{color:red}.m85{color:red}.m86{color:red}.m87{color:red}.m88{color:red}.m89{color:red}.m90{color:red}.m91{color:red}.m92{color:red}.m93{color:red}.m94{color:red}.m95{color:red}.m96{color:red}.m97{color:red}.m98{color:red}.m99{color:red}.m100{color:red}.m101{color:red}.m102{color:red}.m103{color:red}.m104{color:red}.m105{color:red}.m106{color:red}.m107{color:red}.m108{color:red}.m109{color:red}.m110{color:red}.m111{color:red}.m112{color:red}.m113{color:red}.m114{color:red}.m115{color:red}.m116{color:red}.m117{color:red}.m118{color:red}.m119{color:red}.m120{color:red}.m121{color:red}.m122{color:red}.m123{color:red}.m124{color:red}.m125{color:red}.m126{color:red}.m127{color:red}.m128{color:red}.m129{color:red}.m130{color:red}.m131{color:red}.m132{color:red}.m133{color:red}.m134{color:red}.m135{color:red}.m136{color:red}.m137{color:red}.m138{color:red}.m139{color:red}.m140{color:red}.m141{color:red}.m142{color:red}.m143{color:red}.m144{color:red}.m145{color:red}.m146{color:red}.m147{color:red}.m148{color:red}.m149{color:red}.m150{color:red}.m151{color:red}.m152{color:red}.m153{color:red}.m154{color:red}.m155{color:red}.m156{color:red}.m157{color:red}.m158{color:red}.m159{color:red}.m160{color:red}.m161{color:red}.m162{color:red}.m163{color:red}.m164{color:red}.m165{color:red}.m166{color:red}.m167{color:red}.m168{color:red}.m169{color:red}.m170{color:red}.m171{color:red}.m172{color:red}.m173{color:red}.m174{color:red}.m175{color:red}.m176{color:red}.m177{color:red}.m178{color:red}.m179{color:red}.m180{color:red}.m181{color:red}.m182{color:red}.m183{color:red}.m184{color:red}.m185{color:red}.m186{color:red}.m187{color:red}.m188{color:red}.m189{color:red}.m190{color:red}.m191{color:red}.m192{color:red}.m193{color:red}.m194{color:red}.m195{color:red}.m196{color:red}.m197{color:red}.m198{color:red}.m199{color:red}.m200{color:red}.m201{color:red}.m202{color:red}.m203{color:red}.m204{color:red}.m205{color:red}.m206{color:red}.m207{color:red}.m208{color:red}.m209{color:red}.m210{color:red}.m211{color:red}.m212{color:red}.m213{color:red}.m214{color:red}.m215{color:red}.m216{color:red}.m217{color:red}.m218{color:red}.m219{color:red}.m220{color:red}.m221{color:red}.m222{color:red}.m223{color:red}.m224{color:red}.m225{color:red}.m226{color:red}.m227{color:red}.m228{color:red}.m229{color:red}.m230{color:red}.m231{color:red}.m232{color:red}.m233{color:red}.m234{color:red}.m235{color:red}.m236{color:red}.m237{color:red}.m238{color:red}.m239{color:red}.m240{color:red}.m241{color:red}.m242{color:red}.m243{color:red}.m244{color:red}.m245{color:red}.m246{color:red}.m247{color:red}.m248{color:red}.m249{color:red}.m250{color:red}.m251{color:red}.m252{color:red}.m253{color:red}.m254{color:red}.m255{color:red}.m256{color:red}.m257{color:red}.m258{color:red}.m259{color:red}.m260{color:red}.m261{color:red}.m262{color:red}.m263{color:red}.m264{color:red}.m265{color:red}.m266{color:red}.m267{color:red}.m268{color:red}.m269{color:red}.m270{color:red}.m271{color:red}.m272{color:red}.m273{color:red}.m274{color:red}.m275{color:red}.m276{color:red}.m277{color:red}.m278{color:red}.m279{color:red}.m280{color:red}.m281{color:red}.m282{color:red}.m283{color:red}.m284{color:red}.m285{color:red}.m286{color:red}.m287{color:red}.m288{color:red}.m289{color:red}.m290{color:red}.m291{color:red}.m292{color:red}.m293{color:red}.m294{color:red}.m295{color:red}.m296{color:red}.m297{color:red}.m298{color:red}.m299{color:red}.m300{color:red}.m301{color:red}.m302{color:red}.m303{color:red}.m304{color:red}.m305{color:red}.m306{color:red}.m307{color:red}.m308{color:red}.m309{color:red}.m310{color:red}.m311{color:red}.m312{color:red}.m313{color:red}.m314{color:red}.m315{color:red}.m316{color:red}.m317{color:red}.m318{color:red}.m319{color:red}.m320{color:red}.m321{color:red}.m322{color:red}.m323{color:red}.m324{color:red}.m325{color:red}.m326{color:red}.m327{color:red}.m328{color:red}.m329{color:red}.m330{color:red}.m331{color:red}.m332{color:red}.m333{color:red}.m334{color:red}.m335{color:red}.m336{color:red}.m337{color:red}.m338{color:red}.m339{color:red}.m340{color:red}.m341{color:red}.m342{color:red}.m343{color:red}.m344{color:red}.m345{color:red}.m346{color:red}.m347{color:red}.m348{color:red}.m349{color:red}.m350{color:red}.m351{color:red}.m352{color:red}.m353{color:red}.m354{color:red}.m355{color:red}.m356{color:red}.m357{color:red}.m358{color:red}.m359{color:red}.m360{color:red}.m361{color:red}.m362{color:red}.m363{color:red}.m364{color:red}.m365{color:red}.m366{color:red}.m367{color:red}.m368{color:red}.m369{color:red}.m370{color:red}.m371{color:red}.m372{color:red}.m373{color:red}.m374{color:red}.m375{color:red}.m376{color:red}.m377{color:red}.m378{color:red}.m379{color:red}.m380{color:red}.m381{color:red}.m382{color:red}.m383{color:red}.m384{color:red}.m385{color:red}.m386{color:red}.m387{color:red}.m388{color:red}.m389{color:red}.m390{color:red}.m391{color:red}.m392{color:red}.m393{color:red}.m394{color:red}.m395{color:red}.m396{color:red}.m397{color:red}.m398{color:red}.m399{color:red}.m400{color:red} \ No newline at end of file diff --git a/parser/src/test-data/web/torture/css/nested.css b/parser/src/test-data/web/torture/css/nested.css new file mode 100644 index 000000000..8832cf59a --- /dev/null +++ b/parser/src/test-data/web/torture/css/nested.css @@ -0,0 +1,45 @@ +/* N01 CSS nesting: every & position, implicit nesting, nested at-rules with bare declarations */ +.card { + color: red; + &:hover { color: blue } + & .item { color: green } + .item & { color: yellow } + & + & { margin: 0 } + &.is-active { outline: 1px } + &#section-one { outline: 2px } + & > .nav, & ~ .nav { color: red } + .item, .nav { color: red } /* implicit & descendant, two selectors */ + > .direct { color: red } /* leading combinator */ + + .sib { color: red } + ~ .gen { color: red } + :hover { color: red } /* implicit: &:hover? no, & :hover descendant */ + .a &, .b & { color: red } + &, &.x { color: red } + :is(&, .alias) .item { color: red } + &:not(.is-active) .item { color: red } + .parent & .child { color: red } + && { color: red } + & & { color: red } + @media (min-width: 1px) { color: red; & .item { color: blue } .nested-in-media { color: green } } + @supports (display: grid) { display: grid } + @container sidebar (min-width: 1px) { color: red } + @layer nested-layer { color: red } + @scope (.card) { .item { color: red } } + @starting-style { opacity: 0 } + --nested-var: 1; + .deep { .deeper { .deepest { color: var(--nested-var) } } } + @media (a) { @media (b) { @supports (c) { .x { color: red } } } } + & .item { & .leaf { color: red } } + .item { .leaf { color: red } } + @nest .parent & { color: red } /* pre-standard @nest */ + color: blue; /* declaration after nested rules */ +} +.card.is-active { & .item { color: red } } +.a, .b { & .c { color: red } .d & { color: red } } +@media (x) { .outer { .inner { color: red } } } +@layer l { .outer { & .inner { color: red } } } +.p { &__elem { color: red } &--mod { color: red } } /* BEM-style: invalid in CSS nesting, valid in Sass */ +.q { &-suffix { color: red } } +.r { &:is(.s) { color: red } } +.t { .u & .v & { color: red } } +.w { &.x.y > &.z { color: red } } diff --git a/parser/src/test-data/web/torture/css/norel.css b/parser/src/test-data/web/torture/css/norel.css new file mode 100644 index 000000000..867918fdd --- /dev/null +++ b/parser/src/test-data/web/torture/css/norel.css @@ -0,0 +1 @@ +.norel-only { color: red } diff --git a/parser/src/test-data/web/torture/css/print.css b/parser/src/test-data/web/torture/css/print.css new file mode 100644 index 000000000..8c1f9cba9 --- /dev/null +++ b/parser/src/test-data/web/torture/css/print.css @@ -0,0 +1,2 @@ +@media print { .print-rule { color: red } } +.print-only { color: red } diff --git a/parser/src/test-data/web/torture/css/sassy.css b/parser/src/test-data/web/torture/css/sassy.css new file mode 100644 index 000000000..8bb5165e0 --- /dev/null +++ b/parser/src/test-data/web/torture/css/sassy.css @@ -0,0 +1,5 @@ +$primary: red; +@mixin m { color: $primary } +.sassy { @include m; &__elem { color: red } } +// line comment +.x { .y { color: red } } diff --git a/parser/src/test-data/web/torture/css/selectors.css b/parser/src/test-data/web/torture/css/selectors.css new file mode 100644 index 000000000..d1bf6329b --- /dev/null +++ b/parser/src/test-data/web/torture/css/selectors.css @@ -0,0 +1,34 @@ +/* E01 escaped identifiers: every form the CSS syntax admits */ +.md\:flex { display: flex } +.md\3A flex { display: flex } +.md\00003Aflex { display: flex } +.w-1\/2 { width: 50% } +.\31 23 { order: 1 } +.\31 0 { order: 10 } +.\-mt-2 { margin: 0 } +.a\ b { color: red } +.\@media { color: red } +.\#hash { color: red } +.\.dot { color: red } +.\\backslash { color: red } +.caf\e9 { color: red } +.café { color: red } +.\1F680 { color: red } +.🚀 { color: red } +#\31 23 { color: red } +#id\:colon { color: red } +[data-x\:y] { color: red } +[data-x="a\"b"] { color: red } +svg|rect\:x { color: red } +.a\,b { color: red } +.a\>b { color: red } +.a\&b { color: red } +.\[\&_svg\]\:size-4 svg { color: red } +.\!font-bold { color: red } +.\32 xl\:grid-cols-\[repeat\(auto-fill\2c minmax\(200px\2c 1fr\)\)\] { color: red } +.group:hover .group-hover\:visible { color: red } +.peer:checked ~ .peer-checked\:block { color: red } +.data-\[state\=open\]\:bg-red[data-state="open"] { color: red } +.aria-\[checked\]\:bg-red[aria-checked] { color: red } +.supports-\[display\:grid\]\:grid { color: red } +.has-\[\>img\]\:p-0:has(> img) { color: red } diff --git a/parser/src/test-data/web/torture/css/strings.css b/parser/src/test-data/web/torture/css/strings.css new file mode 100644 index 000000000..a87c61972 --- /dev/null +++ b/parser/src/test-data/web/torture/css/strings.css @@ -0,0 +1,36 @@ +/* S01 strings and comments that contain CSS syntax */ +.s1 { content: "}" } +.s2 { content: "{" } +.s3 { content: ";" } +.s4 { content: "a; color: red" } +.s5 { content: "/* not a comment */" } +.s6 { content: "url(not.png)" } +.s7 { content: 'var(--not)' } +.s8 { content: "\"" } +.s9 { content: '\'' } +.s10 { content: "\201C \201D" } +.s11 { content: "line\ +continued" } +.s12 { content: "unterminated } +.s13 { color: red } +.s14 { content: "\"; } .injected { color: red }" } +.s15 { background: url("a)b.png") } +.s16 { background: url('a"b.png') } +.s17 { background: url(a\)b.png) } +.s18 { font-family: "Font; Name" } +.s19 { font-family: "Font } Name" } +.s20 { quotes: "«" "»" } +.s21 { content: attr(data-page) } +.s22 { content: counter(a) " / " counter(b) } +.s23 { grid-template-areas: "header header" "sidebar main" } +.s24 { content: "{{ not_a_template }}" } +.s25 { content: "<%= not_erb %>" } +.s26 { content: "${not_dollar}" } +.s27[data-x="}"] { color: red } +.s28[data-x=";"] { color: red } +.s29[data-x="url(x)"] { color: red } +.s30:not([data-x="{"]) { color: red } +/* .s31 { color: red } */ +/* a comment with url(img/bg.png) and var(--x) and .commented-class */ +/* unterminated comment at the end of the file +.s32 { color: red } diff --git a/parser/src/test-data/web/torture/css/theme.css b/parser/src/test-data/web/torture/css/theme.css new file mode 100644 index 000000000..f6f831995 --- /dev/null +++ b/parser/src/test-data/web/torture/css/theme.css @@ -0,0 +1,14 @@ +/* K02 the end of the import chain: app -> tokens -> theme; imports tokens back (cycle) */ +@import "tokens.css"; +.theme-light { --brand: #0af } +.theme-dark { --brand: #a0f } +@keyframes fade { from { opacity: 0 } to { opacity: 1 } } +@keyframes spin { from { transform: rotate(0) } 50% { opacity: .5 } to { transform: rotate(1turn) } } +@keyframes spin { to { transform: rotate(2turn) } } /* redefinition: the last one wins */ +@keyframes "quoted-name" { to { opacity: 0 } } +@keyframes ease { to { opacity: 0 } } /* a keyframes named like a keyword */ +@keyframes none { to { opacity: 0 } } /* invalid name, written anyway */ +@keyframes slide { 0%, 50% { left: 0 } 100% { left: 1px } 33.3% { left: 2px } from, to { left: 3px } } +@keyframes var-inside { to { color: var(--brand); background: url(img/bg.png) } } +@-webkit-keyframes spin { to { transform: rotate(0) } } +@-moz-keyframes spin { to { transform: rotate(0) } } diff --git a/parser/src/test-data/web/torture/css/tokens.css b/parser/src/test-data/web/torture/css/tokens.css new file mode 100644 index 000000000..7be1084f4 --- /dev/null +++ b/parser/src/test-data/web/torture/css/tokens.css @@ -0,0 +1,35 @@ +/* K01 custom properties: defined at :root, overridden per theme, registered, used in fallbacks and shorthands */ +@import "theme.css"; +:root { + --brand: #09f; + --gap: 8px; + --anchor: var(--brand); + --fallback-brand: pink; + --font: Inter, sans-serif; + --anim: spin; + --duration: 1s; + --easing: ease-in; + --pad: 1px; + --wide: 70rem; +} +:root.dark, .dark, [data-theme="dark"], html[data-theme=dark] body { --brand: #fff; } +@media (prefers-color-scheme: dark) { :root { --brand: #000 } } +@property --brand { syntax: ''; inherits: true; initial-value: red } +@property --registered-never-used { syntax: '*'; inherits: false } +.scoped { --gap: 4px; } +.scoped .child { gap: var(--gap); } +.uses-undefined { color: var(--undefined-anywhere); } +.uses-undefined-with-fallback { color: var(--undefined-anywhere, red); } +.defined-in-style-attr { padding: var(--local); } /* --local is defined only in a style="" attribute of index.html */ +.var-in-selector-arg:is(.x) { --in-is: 1 } +.var-name-cases { color: var(--BRAND); color: var( --brand ); color: var(--brand,); color: var(--brand , 1px 2px); color: var(--brand, url(img/bg.png)); color: var(--brand, var(--gap)) } +.var-not-a-var { color: var(brand); color: var(); color: var(--); } +.var-in-shorthand { margin: var(--gap) calc(var(--gap) * 2) 0 var(--gap, var(--pad)); } +.var-in-important { color: var(--brand) !important; } +.var-in-url { background: url(var(--url)); } /* not valid CSS, but written */ +.var-in-string { content: "var(--brand)"; } +.var-in-comment { /* color: var(--commented) */ color: red; } +.var-in-custom { --alias: var(--brand); --chain: var(--alias); color: var(--chain); } +.var-in-animation { animation: var(--anim) var(--duration); } +.var-in-font { font-family: var(--font); font: 12px var(--font); } +.var-in-container { container-name: var(--container-name); } diff --git a/parser/src/test-data/web/torture/css/upper.css b/parser/src/test-data/web/torture/css/upper.css new file mode 100644 index 000000000..f31c03643 --- /dev/null +++ b/parser/src/test-data/web/torture/css/upper.css @@ -0,0 +1 @@ +.upper-only { color: red } diff --git a/parser/src/test-data/web/torture/css/vendor.min.css b/parser/src/test-data/web/torture/css/vendor.min.css new file mode 100644 index 000000000..154c5ba59 --- /dev/null +++ b/parser/src/test-data/web/torture/css/vendor.min.css @@ -0,0 +1 @@ +.vendor{color:red}.card{color:rgb(200,200,200)} \ No newline at end of file diff --git a/parser/src/test-data/web/torture/fonts/inter.woff b/parser/src/test-data/web/torture/fonts/inter.woff new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/fonts/inter.woff2 b/parser/src/test-data/web/torture/fonts/inter.woff2 new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/forms.html b/parser/src/test-data/web/torture/forms.html new file mode 100644 index 000000000..b95f45941 --- /dev/null +++ b/parser/src/test-data/web/torture/forms.html @@ -0,0 +1,51 @@ + + + + Torture: id references + + + + + + + + +
+ + + + + + + + +
nameage
x
+
+ + +
pop
+ + + + legacy named anchor + to a name, not an id + to an id + case-different fragment + bare hash + implicit top + to the other page + to an uppercase id on the other page + cross-page fragment, dangling + cross-page fragment, page missing + + targets frame-b + targets a keyword +
+
+
+
+
+ + duplicate id with the first input + + diff --git a/parser/src/test-data/web/torture/img/bg.png b/parser/src/test-data/web/torture/img/bg.png new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/img/cursor.cur b/parser/src/test-data/web/torture/img/cursor.cur new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/img/favicon.ico b/parser/src/test-data/web/torture/img/favicon.ico new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/img/hero.png b/parser/src/test-data/web/torture/img/hero.png new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/img/hero.webp b/parser/src/test-data/web/torture/img/hero.webp new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/img/logo.png b/parser/src/test-data/web/torture/img/logo.png new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/img/logo@2x.png b/parser/src/test-data/web/torture/img/logo@2x.png new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/img/space name.png b/parser/src/test-data/web/torture/img/space name.png new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/img/sprite.svg b/parser/src/test-data/web/torture/img/sprite.svg new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/index.html b/parser/src/test-data/web/torture/index.html new file mode 100644 index 000000000..6dd32a5ac --- /dev/null +++ b/parser/src/test-data/web/torture/index.html @@ -0,0 +1,208 @@ + + + + + Torture: the main page + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
whitespace, tab entity, newline, duplicate token, padded id
+
valueless class attribute
+
empty class attribute
+
entity-bearing class tokens
+
case variants: CSS .upper must NOT match Upper
+
uppercase tag and attribute names
+
duplicate class attribute: first wins, second is a gap
+ + + + +
+

duplicate id on the page

+ +

var in style attribute

+

custom property defined in a style attribute

+

urls in style attribute, relative to the page

+

root-relative url in style attribute

+

important and font shorthand

+

template in style attribute

+

broken declaration list, second declaration must survive

+

a rule where a declaration list belongs

+

empty style

+

keyframes, font, container refs in a style attribute

+

brace in a style attribute

+

uppercase property names

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; trailingText(); + + + + comma inside a srcset url + + + + + + + + + + + + +
cite
+ + + + + + +
+ a browser makes me a CHILD of the div above +

a

div inside a p closes the p
trailing

+
  • one
  • two
+ +
no tbody written
+
c
h
body row after thead
+ outer nested anchor +
t
d
t2
+
+ + + +
+ + +

entities:   & <tag> ' ' &unknown; ©

+

aria idrefs

+ +
styled only by base.css, which this page does not load (its link is under the base)
+
+ + + diff --git a/parser/src/test-data/web/torture/js/app.js b/parser/src/test-data/web/torture/js/app.js new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/js/lib.js b/parser/src/test-data/web/torture/js/lib.js new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/js/module.js b/parser/src/test-data/web/torture/js/module.js new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/js/vendor.js b/parser/src/test-data/web/torture/js/vendor.js new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/media/clip.mp4 b/parser/src/test-data/web/torture/media/clip.mp4 new file mode 100644 index 000000000..93db0574a --- /dev/null +++ b/parser/src/test-data/web/torture/media/clip.mp4 @@ -0,0 +1 @@ +a{color:red} \ No newline at end of file diff --git a/parser/src/test-data/web/torture/media/clip.vtt b/parser/src/test-data/web/torture/media/clip.vtt new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/media/poster.png b/parser/src/test-data/web/torture/media/poster.png new file mode 100644 index 000000000..e69de29bb diff --git a/parser/src/test-data/web/torture/public/css/dup.css b/parser/src/test-data/web/torture/public/css/dup.css new file mode 100644 index 000000000..9a7bd6df9 --- /dev/null +++ b/parser/src/test-data/web/torture/public/css/dup.css @@ -0,0 +1 @@ +body{margin:0} diff --git a/parser/src/test-data/web/torture/quirks.html b/parser/src/test-data/web/torture/quirks.html new file mode 100644 index 000000000..8b5052799 --- /dev/null +++ b/parser/src/test-data/web/torture/quirks.html @@ -0,0 +1,265 @@ + + +Quirks + + + +
+

crlf

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+

+

+ + \ No newline at end of file diff --git a/parser/src/test-data/web/torture/shadow/shadow.html b/parser/src/test-data/web/torture/shadow/shadow.html new file mode 100644 index 000000000..72f1a5d9a --- /dev/null +++ b/parser/src/test-data/web/torture/shadow/shadow.html @@ -0,0 +1,70 @@ + + + + Torture: scoping boundaries + + + + +
outside any shadow root
+ + + + + slotted from the light DOM + default slot + + + + + + + + + + + + + +
zero levels in: no rule reaches me
+ + +
+ + + diff --git a/parser/src/test-data/web/torture/static/css/dup.css b/parser/src/test-data/web/torture/static/css/dup.css new file mode 100644 index 000000000..fb9395130 --- /dev/null +++ b/parser/src/test-data/web/torture/static/css/dup.css @@ -0,0 +1 @@ +body{margin:1px} diff --git a/parser/src/test-data/web/torture/svg.html b/parser/src/test-data/web/torture/svg.html new file mode 100644 index 000000000..5cec27409 --- /dev/null +++ b/parser/src/test-data/web/torture/svg.html @@ -0,0 +1,43 @@ + + + + Torture: foreign content + + + + + + + + + + + + + + + svg anchor + + +
html again: tag and attribute names lowercase here
+ +
+ svg title must not become the document title + + + +
+ x
+ + + diff --git a/parser/src/test-data/web/torture/templates/jinja.html b/parser/src/test-data/web/torture/templates/jinja.html new file mode 100644 index 000000000..08b80ea09 --- /dev/null +++ b/parser/src/test-data/web/torture/templates/jinja.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} +{% block head %} + + + +{% endblock %} +{% block body %} +
+ {% for item in items %} + {{ item.name }} + {% endfor %} + conditional class + +

style with template value

+

custom property from the template

+ {# a jinja comment with
#} + +
+ +{% endblock %} diff --git a/parser/src/test-data/web/torture/templates/mixed.html b/parser/src/test-data/web/torture/templates/mixed.html new file mode 100644 index 000000000..0d7eb298f --- /dev/null +++ b/parser/src/test-data/web/torture/templates/mixed.html @@ -0,0 +1,19 @@ + + + + + + +
x
+
thymeleaf inline
+
angular
+
vue
+
alpine
+
htmx
+
livewire, liveview, hyperscript, knockout
+
<%= body %>
+
+
    @foreach (var i in Model.Items) {
  • @i.Name
  • }
+

${name} and #{expr} and @{link} and {#block}

+ + diff --git a/parser/src/test-data/web/torture/templates/tailwind.html b/parser/src/test-data/web/torture/templates/tailwind.html new file mode 100644 index 000000000..6fe4638b5 --- /dev/null +++ b/parser/src/test-data/web/torture/templates/tailwind.html @@ -0,0 +1,13 @@ +
+ utility classes whose CSS selectors are escaped + stacked variants +
+ diff --git a/parser/src/test-data/web/torture/xhtml/page.xhtml b/parser/src/test-data/web/torture/xhtml/page.xhtml new file mode 100644 index 000000000..37a76c3de --- /dev/null +++ b/parser/src/test-data/web/torture/xhtml/page.xhtml @@ -0,0 +1,21 @@ + + + + + Torture: XHTML + + " } + //]]> + + + + +
+
+
+ a sibling in XML, a child in HTML + + + diff --git a/parser/src/test/web-tests.ts b/parser/src/test/web-tests.ts new file mode 100644 index 000000000..316994ff5 --- /dev/null +++ b/parser/src/test/web-tests.ts @@ -0,0 +1,1099 @@ +/** + * WEB (HTML + CSS) TESTS — one file, mirroring services-tests.ts. + * + * npx tsx src/test/web-tests.ts # everything + * npx tsx src/test/web-tests.ts --list # what runs, and what it proves + * npx tsx src/test/web-tests.ts --bless # rewrite the goldens + * + * NO BROWSER, NO NETWORK. Every check runs the parser and compares the result + * with expectations in this file or checked into src/test-data/web. + * + * ## What this suite can and cannot do + * + * The golden is drift detection: it was produced by this parser and agrees with + * whatever the parser currently does, mistakes included. + * + * The format checks are the part that can find a defect. Each is written from + * the HTML or CSS specification (the parsing algorithm, the Selectors specificity + * rules, the URL syntax) rather than from parser output, and each names the + * plausible wrong implementation it rules out — a reader that splits `class` on + * spaces only, a specificity that counts `:where()`, a resolver that follows + * `../..` out of the project. A check no plausible implementation fails proves + * nothing, so the `rules out` line is part of the check. + * + * The structural checks — primary-key uniqueness, foreign-key integrity across + * the sixteen relations, arity against the frozen schema, enum domains, and + * determinism across two runs — are the ones that catch what a fixture cannot + * foresee. + */ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import { CssStylesheet } from '@/analysis-types/css/CssStylesheet'; +import { WEB_CSV_FILES, WEB_PARSE_GAP_LIMIT } from '@/constants/web-constants'; +import { CssSourceProvenance, CssStylesheetSource } from '@/enums/css/CssStylesheetSource'; +import { CssParseGapKind } from '@/enums/css/CssParseGapKind'; +import { CssSelectorPartKind, CssCombinator } from '@/enums/css/CssSelectorPartKind'; +import { CssValueReferenceKind } from '@/enums/css/CssValueReferenceKind'; +import { HtmlAttributeKind } from '@/enums/html/HtmlAttributeKind'; +import { HtmlDocumentKind } from '@/enums/html/HtmlDocumentKind'; +import { HtmlHandlerSource } from '@/enums/html/HtmlHandlerSource'; +import { HtmlNamespace } from '@/enums/html/HtmlNamespace'; +import { HtmlParseGapKind } from '@/enums/html/HtmlParseGapKind'; +import { HtmlScriptKind, HtmlScriptType } from '@/enums/html/HtmlScriptKind'; +import { HtmlTemplateDialect } from '@/enums/html/HtmlTemplateDialect'; +import { WebUrlKind } from '@/enums/web/WebUrlKind'; +import * as CssEnums from '@/enums/css'; +import * as HtmlEnums from '@/enums/html'; +import * as WebEnums from '@/enums/web'; +import { CssExtraction, CssParser } from '@/parsers/css/css-parser'; +import { HtmlExtraction, HtmlParser } from '@/parsers/html/html-parser'; +import { WebProjectAnalyzer } from '@/workflows/web/web-project-analyzer'; + +const DATA = 'src/test-data/web'; +const GOLDEN = path.join(DATA, '_golden'); +const SCHEMA = JSON.parse(fs.readFileSync('src/schema/web/schema.json', 'utf-8')) as { + relations: Record; commaSets?: string[] }>; +}; + +/** The values a cell holds: one, or every member of a comma-set column. */ +function cellValues(relation: string, column: string, cell: string): string[] { + if (cell === '') return []; + return (SCHEMA.relations[relation]?.commaSets ?? []).includes(column) ? cell.split(',') : [cell]; +} +const BLESS = process.argv.includes('--bless'); + +/** Columns that cannot be compared across machines: absolute paths. */ +const VOLATILE = /^(baseMservPath|filePath|resolvedFilePath)$/; +const HASH_COLUMN = /(Hash|hash)$/; + +type Row = Record; + +const fail = (m: string): number => { console.log(' ✗ ' + m); return 1; }; + +// --------------------------------------------------------------------------- +// format checks — from the specifications, in memory, no fixture tree +// --------------------------------------------------------------------------- + +/** A throwaway directory for checks that need real files to resolve against. */ +const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'axiom-web-')); +function file(rel: string, content = ''): string { + const p = path.join(TMP, rel); + fs.mkdirSync(path.dirname(p), { recursive: true }); + fs.writeFileSync(p, content); + return p; +} + +function parseHtml(content: string, rel = 'site/page.html'): HtmlExtraction { + const p = file(rel, content); + return new HtmlParser().parse(content, p, path.join(TMP, 'site'), 'WEB_FIXTURE_VERSION'); +} + +function parseCss(content: string, rel = 'site/css/x.css', origin = { line: 1, column: 1 }): CssExtraction & { sheet: CssStylesheet } { + const p = file(rel, content); + const sheet = new CssStylesheet({ + name: 'x', fileName: path.basename(p), filePath: p, baseMservPath: path.join(TMP, 'site'), relativePath: rel, + sourceKind: CssStylesheetSource.FILE, sourceProvenance: CssSourceProvenance.PROJECT, ownerHtmlElementLinkHash: '', + htmlDocumentLinkHash: '', startLine: 1, startColumn: 1, endLine: 1, serviceVersionLinkHash: 'WEB_FIXTURE_VERSION', + }); + const x = new CssParser().parseStylesheet(content, { + stylesheet: sheet, line: origin.line, column: origin.column, filePath: p, projectRoot: path.join(TMP, 'site'), + serviceVersionLinkHash: 'WEB_FIXTURE_VERSION', + }); + return { ...x, sheet }; +} + +/** (a,b,c) of the first selector of the first rule. */ +function specificity(selector: string): string { + const s = parseCss(`${selector} { color: red }`).selectors[0]!; + return `${s.specificityA},${s.specificityB},${s.specificityC}`; +} + +function col(text: string, needle: string, occurrence = 1): { line: number; column: number } { + let from = 0; + let at = -1; + for (let i = 0; i < occurrence; i += 1) { + at = text.indexOf(needle, from); + from = at + 1; + } + const before = text.slice(0, at); + const line = before.split('\n').length; + const column = at - (before.lastIndexOf('\n') + 1) + 1; + return { line, column }; +} + +interface FormatCheck { + name: string; + /** The clause of the specification this check comes from. */ + spec: string; + /** The plausible wrong implementation this check fails. */ + rulesOut: string; + run: () => number; +} + +const formatChecks: FormatCheck[] = [ + // ── HTML: the tree ────────────────────────────────────────────────────── + { + name: 'a-fragment-is-the-elements-written-and-nothing-implied', + spec: 'the grammar builds the elements written; the html, head and body a browser implies are not in the source', + rulesOut: 'a reader that invents wrapper rows, or that calls a snippet a whole page', + run: () => { + const x = parseHtml('

only a paragraph

'); + let bad = 0; + const tags = x.elements.map((e) => e.tagName).join(' '); + if (tags !== 'p') bad += fail(`elements=${tags}, expected p alone`); + if (x.document.documentKind !== HtmlDocumentKind.FRAGMENT) bad += fail(`documentKind=${x.document.documentKind}`); + const p = x.elements[0]!; + if (p.parentElementLinkHash !== '' || p.path !== '/p[1]' || p.depth !== 0) bad += fail(`a top-level element reads as ${p.path} at depth ${p.depth} with parent ${JSON.stringify(p.parentElementLinkHash)}`); + const whole = parseHtml('

x

'); + if (whole.document.documentKind !== HtmlDocumentKind.DOCUMENT || whole.document.doctype !== 'html') { + bad += fail(`a full document reads as ${whole.document.documentKind} with doctype ${JSON.stringify(whole.document.doctype)}`); + } + if (whole.elements.map((e) => e.path).join(' ') !== '/html[1] /html[1]/body[1] /html[1]/body[1]/p[1]') bad += fail(`paths=${whole.elements.map((e) => e.path).join(' ')}`); + const noDoctype = parseHtml(''); + if (noDoctype.document.documentKind !== HtmlDocumentKind.DOCUMENT) bad += fail('a page with and no doctype reads as a fragment'); + return bad; + }, + }, + { + name: 'a-paragraph-is-closed-by-the-next-one', + spec: 'a p element\'s end tag may be omitted if it is immediately followed by another p', + rulesOut: 'a reader that nests the second p inside the first', + run: () => { + const text = '
\n

one\n

two\n

'; + const x = parseHtml(text); + const ps = x.elements.filter((e) => e.tagName === 'p'); + let bad = 0; + if (ps.length !== 2) return fail(`${ps.length} p elements, expected 2`); + if (ps[0]!.path !== '/div[1]/p[1]' || ps[1]!.path !== '/div[1]/p[2]') { + bad += fail(`paths ${ps.map((p) => p.path).join(' ')}: the second p is a sibling, not a child`); + } + if (ps[0]!.endLine !== 3) bad += fail(`first p ends on line ${ps[0]!.endLine}, expected 3 where the second opens`); + if (ps[0]!.textContent !== 'one' || ps[1]!.textContent !== 'two') bad += fail('text content crossed the implicit close'); + if (ps[0]!.position !== 0 || ps[1]!.position !== 1) bad += fail('positions are not the sibling ordinals'); + return bad; + }, + }, + { + name: 'template-content-is-walked', + spec: 'a template element\'s contents are a DocumentFragment, not children of the element', + rulesOut: 'a walker that reads childNodes and finds the template empty', + run: () => { + const x = parseHtml(''); + const span = x.elements.find((e) => e.tagName === 'span'); + if (span === undefined) return fail('no span inside the template'); + let bad = 0; + if (span.path !== '/template[1]/span[1]') bad += fail(`path=${span.path}`); + if (!x.classReferences.some((c) => c.className === 'tpl')) bad += fail('the class inside the template was not read'); + return bad; + }, + }, + { + name: 'a-void-element-has-no-end-tag-and-no-children', + spec: 'void elements have no end tag and never have contents', + rulesOut: 'a reader that swallows the following text into the img', + run: () => { + const x = parseHtml('

after

'); + const img = x.elements.find((e) => e.tagName === 'img')!; + const p = x.elements.find((e) => e.tagName === 'p')!; + let bad = 0; + if (!img.isVoid) bad += fail('img is not void'); + const expectedEnd = '

'.length + 1; + if (img.endColumn !== expectedEnd) bad += fail(`img ends at column ${img.endColumn}, expected ${expectedEnd} (just past its start tag)`); + if (p.textContent !== 'after') bad += fail(`p text=${JSON.stringify(p.textContent)}`); + return bad; + }, + }, + { + name: 'foreign-content-keeps-its-namespace-and-prefixed-attributes', + spec: 'svg and MathML elements are in their own namespaces; xlink:href is a prefixed attribute', + rulesOut: 'a reader that lowercases viewBox or loses the xlink prefix', + run: () => { + const x = parseHtml(''); + const svg = x.elements.find((e) => e.tagName === 'svg')!; + let bad = 0; + if (svg.namespace !== HtmlNamespace.SVG) bad += fail(`svg namespace=${svg.namespace}`); + if (!x.attributes.some((a) => a.name === 'viewBox')) bad += fail('viewBox lost its case'); + const xlink = x.attributes.find((a) => a.prefix === 'xlink'); + if (xlink === undefined || xlink.name !== 'href' || xlink.attributeKind !== HtmlAttributeKind.URL) { + bad += fail('xlink:href is not a prefixed URL attribute'); + } + const ref = x.references.find((r) => r.attributeName === 'xlink:href'); + if (ref === undefined || ref.urlKind !== WebUrlKind.FRAGMENT || ref.fragment !== 'a') bad += fail('xlink:href="#a" is not a FRAGMENT reference to a'); + return bad; + }, + }, + // ── HTML: attributes ──────────────────────────────────────────────────── + { + name: 'a-value-less-attribute-is-distinguished-from-an-empty-one', + spec: 'an attribute without a value has the empty string as its value', + rulesOut: 'a reader that records both as value="" and cannot tell disabled from disabled=""', + run: () => { + const x = parseHtml(''); + const by = Object.fromEntries(x.attributes.map((a) => [a.name, a])); + let bad = 0; + if (by['disabled']?.hasValue !== false) bad += fail('disabled reports a value'); + if (by['required']?.hasValue !== true) bad += fail('required="" reports no value'); + if (by['value']?.hasValue !== true) bad += fail("value='' reports no value"); + if (by['data-x']?.value !== 'y' || by['data-x']?.attributeKind !== HtmlAttributeKind.DATA) bad += fail('data-x = "y" with spaces around = was misread'); + return bad; + }, + }, + { + name: 'a-duplicate-attribute-keeps-the-first-and-records-a-gap', + spec: 'the first attribute with a name wins; a repeat is a parse error', + rulesOut: 'a reader that emits two rows for one attribute name and collides their keys (the grammar reports no duplicate)', + run: () => { + const x = parseHtml('

'); + const ids = x.attributes.filter((a) => a.name === 'id'); + let bad = 0; + if (ids.length !== 1 || ids[0]!.value !== 'a') bad += fail(`id rows=${ids.map((a) => a.value).join(',')}, expected the first, a`); + if (!x.parseGaps.some((g) => g.gapKind === HtmlParseGapKind.PARSE_ERROR && g.detail === 'duplicate-attribute')) { + bad += fail('no duplicate-attribute gap'); + } + if (x.elements.find((e) => e.tagName === 'div')!.id !== 'a') bad += fail('element id is not the first'); + return bad; + }, + }, + { + name: 'class-tokens-split-on-ascii-whitespace-and-keep-repeats', + spec: 'the class attribute is a set of space-separated tokens; the tokenizer splits on space, tab, LF, FF, CR', + rulesOut: 'a reader that splits on a single space or deduplicates', + run: () => { + const x = parseHtml('
'); + const got = x.classReferences.map((c) => `${c.position}:${c.className}`).join(' '); + let bad = 0; + if (got !== '0:a 1:b 2:c 3:a') bad += fail(`tokens=${got}`); + if (new Set(x.classReferences.map((c) => c.getHash())).size !== 4) bad += fail('two occurrences of one class share a key'); + const div = x.elements.find((e) => e.tagName === 'div')!; + if (div.toCsv().split('\t')[5] !== 'a,b,c,a') bad += fail(`classNames column=${div.toCsv().split('\t')[5]}`); + return bad; + }, + }, + { + name: 'attribute-kinds-follow-the-name', + spec: 'on* attributes are event handler content attributes; data-* and aria-* are reserved prefixes', + rulesOut: 'a classifier that calls a Vue @click or an Alpine x-on an event handler', + run: () => { + const x = parseHtml(''); + const by = Object.fromEntries(x.attributes.map((a) => [a.name, a.attributeKind])); + const want: Record = { + onclick: HtmlAttributeKind.EVENT_HANDLER, '@click': HtmlAttributeKind.TEMPLATE_DIRECTIVE, 'v-if': HtmlAttributeKind.TEMPLATE_DIRECTIVE, + 'x-data': HtmlAttributeKind.TEMPLATE_DIRECTIVE, 'hx-get': HtmlAttributeKind.TEMPLATE_DIRECTIVE, 'th:text': HtmlAttributeKind.TEMPLATE_DIRECTIVE, + 'data-q': HtmlAttributeKind.DATA, 'aria-hidden': HtmlAttributeKind.ARIA, role: HtmlAttributeKind.ARIA, for: HtmlAttributeKind.FOR, + form: HtmlAttributeKind.ID_REFERENCE, rel: HtmlAttributeKind.REL, type: HtmlAttributeKind.TYPE, name: HtmlAttributeKind.NAME, + style: HtmlAttributeKind.STYLE, 'ng-if': HtmlAttributeKind.TEMPLATE_DIRECTIVE, '[prop]': HtmlAttributeKind.TEMPLATE_DIRECTIVE, + '(ev)': HtmlAttributeKind.TEMPLATE_DIRECTIVE, + }; + let bad = 0; + for (const [name, kind] of Object.entries(want)) { + if (by[name] !== kind) bad += fail(`${name}: ${by[name]}, expected ${kind}`); + } + // `@click="b"` is a template event: Vue calls the handler it names, and the row says so by its source. + const b = x.handlerCalls.find((h) => h.calleeName === 'b'); + if (b === undefined || b.handlerSource !== HtmlHandlerSource.TEMPLATE_EVENT || b.argumentCount !== 0) bad += fail('a template event directive is not a TEMPLATE_EVENT handler call'); + if (x.handlerCalls.some((h) => h.handlerSource === HtmlHandlerSource.EVENT_ATTRIBUTE && h.calleeName !== 'a')) bad += fail('a template directive was read as an on* handler'); + const dialects = [...x.document.templateDialects].sort().join(','); + if (!dialects.includes('ALPINE') || !dialects.includes('HTMX') || !dialects.includes('THYMELEAF') || !dialects.includes('VUE') || !dialects.includes('ANGULAR')) { + bad += fail(`dialects=${dialects}`); + } + return bad; + }, + }, + // ── HTML: URLs ────────────────────────────────────────────────────────── + { + name: 'url-kinds-are-decided-from-the-text', + spec: 'URL parsing: scheme, host-relative, path-absolute, path-relative and fragment-only forms', + rulesOut: 'a classifier that treats mailto: as a relative path or #x as a file', + run: () => { + const cases: Array<[string, WebUrlKind, string?]> = [ + ['a/b.css', WebUrlKind.RELATIVE], ['./a.js', WebUrlKind.RELATIVE], ['../x', WebUrlKind.RELATIVE], + ['/static/a.js', WebUrlKind.ROOT_RELATIVE], ['https://h/x.js', WebUrlKind.ABSOLUTE], ['HTTP://h/x', WebUrlKind.ABSOLUTE], + ['//cdn/x.js', WebUrlKind.PROTOCOL_RELATIVE], ['#team', WebUrlKind.FRAGMENT, 'team'], ['data:image/png;base64,AAA', WebUrlKind.DATA_URI], + ['javascript:go()', WebUrlKind.JAVASCRIPT_URI], ['mailto:a@b', WebUrlKind.OTHER_SCHEME], ['tel:123', WebUrlKind.OTHER_SCHEME], + ['{{ url_for("x") }}', WebUrlKind.TEMPLATE_EXPRESSION], ['/static/{{ name }}.js', WebUrlKind.TEMPLATE_EXPRESSION], + ['<%= path %>', WebUrlKind.TEMPLATE_EXPRESSION], ['${ctx}/x', WebUrlKind.TEMPLATE_EXPRESSION], ['', WebUrlKind.EMPTY], + [' ', WebUrlKind.EMPTY], + ]; + let bad = 0; + for (const [url, kind, fragment] of cases) { + const x = parseHtml(`x`); + const r = x.references[0]; + if (r === undefined) { bad += fail(`${JSON.stringify(url)}: no reference`); continue; } + if (r.urlKind !== kind) bad += fail(`${JSON.stringify(url)}: ${r.urlKind}, expected ${kind}`); + if (fragment !== undefined && r.fragment !== fragment) bad += fail(`${JSON.stringify(url)}: fragment=${r.fragment}`); + } + const split = parseHtml('x').references[0]!; + if (split.path !== 'page.html' || split.query !== 'x=1&y=2' || split.fragment !== 'top') { + bad += fail(`split: path=${split.path} query=${split.query} fragment=${split.fragment}`); + } + return bad; + }, + }, + { + name: 'a-relative-url-resolves-against-the-file-and-never-outside-the-root', + spec: 'a relative reference resolves against the base URL, which defaults to the document\'s', + rulesOut: 'a resolver that follows ../.. out of the project, or resolves to a file that is not there', + run: () => { + file('site/css/present.css', 'a{}'); + file('outside.css', 'b{}'); + const x = parseHtml('', 'site/page.html'); + const by = Object.fromEntries(x.references.map((r) => [r.urlAsWritten, r])); + let bad = 0; + if (by['css/present.css']?.resolvedFilePath !== path.join(TMP, 'site/css/present.css') || !by['css/present.css']?.isResolved) { + bad += fail('a present file was not resolved'); + } + if (by['css/absent.css']?.isResolved !== false) bad += fail('an absent file reads as resolved'); + if (by['../outside.css']?.isResolved !== false) bad += fail('a file outside the project root was resolved'); + if (x.document.toCsv().split('\t')[14] !== '3') bad += fail(`stylesheetReferenceCount=${x.document.toCsv().split('\t')[14]}, expected 3`); + return bad; + }, + }, + { + name: 'a-root-relative-url-resolves-only-when-one-served-root-holds-it', + spec: 'a path-absolute URL resolves against the origin, which the repository does not record', + rulesOut: 'a resolver that guesses between two candidate roots, or never resolves /static/x', + run: () => { + file('app/public/static/one.js', ''); + file('app/pages/deep/page.html', ''); + file('app/public/static/two.js', ''); + file('app/pages/static/two.js', ''); + const content = ''; + const p = file('app/pages/deep/page.html', content); + const x = new HtmlParser().parse(content, p, path.join(TMP, 'app'), 'V'); + const by = Object.fromEntries(x.references.map((r) => [r.urlAsWritten, r])); + let bad = 0; + if (by['/static/one.js']?.resolvedFilePath !== path.join(TMP, 'app/public/static/one.js')) { + bad += fail(`one candidate: resolved to ${JSON.stringify(by['/static/one.js']?.resolvedFilePath)}`); + } + if (by['/static/two.js']?.isResolved !== false) bad += fail('two candidates were not refused'); + if (by['/static/none.js']?.isResolved !== false) bad += fail('no candidate reads as resolved'); + const script = x.scripts.find((s) => s.src === '/static/one.js')!; + if (script.resolvedFilePath !== by['/static/one.js']!.resolvedFilePath || script.referenceLinkHash !== by['/static/one.js']!.getHash()) { + bad += fail('the script row does not carry its reference\'s resolution'); + } + return bad; + }, + }, + { + name: 'reference-kinds-follow-the-element', + spec: 'link rel=stylesheet is a stylesheet, source in picture is an image, elsewhere media', + rulesOut: 'a classifier that keys on the attribute name alone', + run: () => { + const x = parseHtml( + '' + + '' + + '
' + + '' + + '' + ); + const got = x.references.map((r) => `${r.referenceKind}:${r.urlAsWritten}`).sort().join(' '); + const want = [ + 'STYLESHEET:a.css', 'LINK_RESOURCE:i.png', 'STYLESHEET:b.css', 'IMAGE:p.webp', 'IMAGE:p.png', 'MEDIA:v.mp4', 'MEDIA:v.png', + 'MEDIA:v.webm', 'IMAGE:btn.png', 'OTHER:x', 'FORM_ACTION:/f', 'FORM_ACTION:/g', 'FRAME:f.html', 'FRAME:o.swf', 'BASE:/', + 'ANCHOR:l', 'ANCHOR:m', 'META_REFRESH:next.html', + ].sort().join(' '); + return got === want ? 0 : fail(`got ${got}\n want ${want}`); + }, + }, + { + name: 'srcset-candidates-are-separate-references', + spec: 'a srcset is a comma-separated list of candidates, each a URL and an optional descriptor', + rulesOut: 'a reader that records the whole attribute as one URL', + run: () => { + const x = parseHtml(''); + const got = x.references.map((r) => `${r.position}:${r.urlAsWritten}`).join(' '); + return got === '0:a.png 1:b.png 2:c.png 0:d.png' ? 0 : fail(`got ${got}`); + }, + }, + // ── HTML: handlers and scripts ────────────────────────────────────────── + { + name: 'every-call-in-a-handler-is-a-row-at-its-column', + spec: 'an event handler content attribute\'s value is a FunctionBody', + rulesOut: 'a reader that keeps the first call only, or cites the attribute instead of the call', + run: () => { + const text = '

\n x'; + const x = parseHtml(text); + const got = x.handlerCalls.map((h) => `${h.handlerSource === 'JAVASCRIPT_URL' ? 'url' : h.eventName}:${h.calleeText}/${h.calleeName}/${h.receiverText}/${h.argumentCount}${h.isNew ? '/new' : ''}`).join(' '); + const want = 'url:toggle/toggle//0 click:track/track//2 click:confirm/confirm//1 click:app.ui.remove/remove/app.ui/1 click:Audio/Audio//1/new'; + let bad = 0; + if (got !== want) bad += fail(`got ${got}\n want ${want}`); + const remove = x.handlerCalls.find((h) => h.calleeName === 'remove')!; + const at = col(text, 'app.ui.remove'); + if (remove.startLine !== at.line || remove.startColumn !== at.column) { + bad += fail(`remove cited at ${remove.startLine}:${remove.startColumn}, written at ${at.line}:${at.column}`); + } + const toggle = x.handlerCalls.find((h) => h.calleeName === 'toggle')!; + const tat = col(text, 'toggle()'); + if (toggle.startLine !== tat.line || toggle.startColumn !== tat.column) bad += fail(`toggle cited at ${toggle.startLine}:${toggle.startColumn}, written at ${tat.line}:${tat.column}`); + if (x.parseGaps.length !== 0) bad += fail(`gaps on well-formed handlers: ${x.parseGaps.map((g) => g.detail).join('; ')}`); + return bad; + }, + }, + { + name: 'a-handler-that-is-not-javascript-is-a-gap-not-a-call', + spec: 'a template placeholder in a handler is not a FunctionBody until rendered', + rulesOut: 'a reader that silently drops the attribute, or invents a call named handler', + run: () => { + const x = parseHtml(''); + let bad = 0; + const gaps = x.parseGaps.filter((g) => g.gapKind === HtmlParseGapKind.HANDLER_SYNTAX); + if (gaps.length !== 2) bad += fail(`${gaps.length} handler gaps, expected 2: ${x.parseGaps.map((g) => g.detail).join('; ')}`); + if (x.handlerCalls.some((h) => h.calleeName === 'handler')) bad += fail('a placeholder became a call'); + if (!x.handlerCalls.some((h) => h.calleeName === 'save')) bad += fail('the call the recovered tree still holds was dropped'); + return bad; + }, + }, + { + name: 'script-type-decides-what-the-browser-does-with-the-body', + spec: 'the type attribute: absent or a JavaScript MIME type runs; module; importmap; anything else is a data block', + rulesOut: 'a reader that runs every script or none', + run: () => { + const types: Array<[string, HtmlScriptType]> = [ + ['', HtmlScriptType.CLASSIC], ['text/javascript', HtmlScriptType.CLASSIC], ['application/javascript', HtmlScriptType.CLASSIC], + ['module', HtmlScriptType.MODULE], ['importmap', HtmlScriptType.IMPORTMAP], ['speculationrules', HtmlScriptType.SPECULATION_RULES], + ['application/json', HtmlScriptType.JSON], ['application/ld+json', HtmlScriptType.JSON], ['text/x-template', HtmlScriptType.TEMPLATE], + ['text/x-handlebars-template', HtmlScriptType.TEMPLATE], ['text/babel', HtmlScriptType.TRANSPILED], ['text/x-mathjax-config', HtmlScriptType.DATA_BLOCK], + ]; + let bad = 0; + for (const [type, want] of types) { + const x = parseHtml(`1`); + if (x.scripts[0]?.scriptType !== want) bad += fail(`type=${JSON.stringify(type)}: ${x.scripts[0]?.scriptType}, expected ${want}`); + } + return bad; + }, + }, + { + name: 'an-inline-script-body-is-located-exactly', + spec: 'script content is raw text between the start tag and the end tag', + rulesOut: 'a reader that trims the body or counts from the start tag', + run: () => { + const text = '\n\n \n'; + const x = parseHtml(text); + const [inline, external] = x.scripts; + let bad = 0; + if (inline === undefined || external === undefined) return fail(`${x.scripts.length} scripts`); + if (inline.scriptKind !== HtmlScriptKind.INLINE || !inline.isDefer) bad += fail('inline/defer misread'); + const bodyStart = text.indexOf(''); + const s = col(text, text.slice(bodyStart, bodyEnd)); + if (inline.bodyStartLine !== s.line || inline.bodyStartColumn !== s.column) bad += fail(`body starts ${inline.bodyStartLine}:${inline.bodyStartColumn}, expected ${s.line}:${s.column}`); + if (inline.bodyLength !== bodyEnd - bodyStart) bad += fail(`bodyLength=${inline.bodyLength}, expected ${bodyEnd - bodyStart}`); + if (inline.bodyEndLine !== 5 || inline.bodyEndColumn !== 3) bad += fail(`body ends ${inline.bodyEndLine}:${inline.bodyEndColumn}, expected 5:3`); + if (external.scriptKind !== HtmlScriptKind.EXTERNAL || external.bodyLength !== 0) bad += fail('an external script\'s body was read'); + if (x.document.toCsv().split('\t')[12] !== '2' || x.document.toCsv().split('\t')[13] !== '1') bad += fail('script counts are wrong'); + return bad; + }, + }, + // ── HTML: inline CSS ──────────────────────────────────────────────────── + { + name: 'a-style-element-is-a-stylesheet-at-the-page-s-lines', + spec: 'a style element\'s content is a CSS stylesheet', + rulesOut: 'a reader that cites line 1 of the CSS for a rule on line 4 of the page', + run: () => { + const text = '\n \n'; + const x = parseHtml(text); + let bad = 0; + if (x.stylesheets.length !== 1) return fail(`${x.stylesheets.length} stylesheets`); + const sheet = x.stylesheets[0]!; + const style = x.elements.find((e) => e.tagName === 'style')!; + if (sheet.sourceKind !== CssStylesheetSource.HTML_STYLE_ELEMENT || sheet.ownerHtmlElementLinkHash !== style.getHash()) bad += fail('the sheet does not chain to its element'); + if (sheet.startLine !== 2 || sheet.startColumn !== 10 || sheet.endLine !== 5) bad += fail(`sheet spans ${sheet.startLine}:${sheet.startColumn}-${sheet.endLine}`); + const b = x.css.rules.find((r) => r.preludeText === '.b')!; + if (b.startLine !== 4 || b.startColumn !== 5) bad += fail(`.b at ${b.startLine}:${b.startColumn}, expected 4:5`); + const gap = x.css.declarations.find((d) => d.property === 'gap')!; + if (gap.startLine !== 4 || gap.startColumn !== 10 || gap.stylesheetLinkHash !== sheet.getHash()) bad += fail(`gap at ${gap.startLine}:${gap.startColumn}`); + if (!x.css.valueReferences.some((v) => v.name === '--g' && v.startLine === 4)) bad += fail('var(--g) not read from the inline sheet'); + if (sheet.getRuleCount() !== 2 || sheet.getDeclarationCount() !== 2) bad += fail('counts not back-patched'); + const other = parseHtml(''); + if (other.stylesheets.length !== 0) bad += fail('a non-CSS style type was parsed as CSS'); + return bad; + }, + }, + { + name: 'a-style-attribute-is-a-declaration-list-owned-by-the-attribute', + spec: 'the style attribute\'s value is a ', + rulesOut: 'a reader that needs a rule to own a declaration, or drops the good half of a bad list', + run: () => { + const text = '

x
'; + const x = parseHtml(text); + const div = x.attributes.find((a) => a.name === 'style' && a.value.startsWith('color: red'))!; + const mine = x.css.declarations.filter((d) => d.htmlAttributeLinkHash === div.getHash()); + let bad = 0; + if (mine.map((d) => `${d.property}=${d.valueText}`).join(' ') !== 'color=red margin=0') bad += fail(`declarations=${mine.map((d) => d.property).join(',')}`); + if (mine.some((d) => d.ruleLinkHash !== '' || d.stylesheetLinkHash !== '')) bad += fail('a style attribute declaration claims a rule or a sheet'); + const at = col(text, 'margin'); + if (mine[1]!.startLine !== at.line || mine[1]!.startColumn !== at.column) bad += fail(`margin at ${mine[1]!.startLine}:${mine[1]!.startColumn}, written at ${at.line}:${at.column}`); + if (!x.css.declarations.some((d) => d.property === 'top')) bad += fail('the readable declaration after a bad one was dropped'); + if (!x.parseGaps.some((g) => g.gapKind === HtmlParseGapKind.STYLE_ATTRIBUTE_SYNTAX)) bad += fail('no STYLE_ATTRIBUTE_SYNTAX gap'); + return bad; + }, + }, + { + name: 'template-dialects-are-read-from-their-markers', + spec: 'the file is a template of the family whose delimiters it uses', + rulesOut: 'a detector keyed on the file extension, which is .html for all of them', + run: () => { + const cases: Array<[string, HtmlTemplateDialect[]]> = [ + ['

{{ a }}

', [HtmlTemplateDialect.MUSTACHE]], + ['{% if a %}

{{ a }}

{% endif %}', [HtmlTemplateDialect.JINJA, HtmlTemplateDialect.MUSTACHE]], + ['{{#each items}}
  • {{this}}
  • {{/each}}', [HtmlTemplateDialect.HANDLEBARS, HtmlTemplateDialect.MUSTACHE]], + ['<% if (u) { %>

    <%= u %>

    <% } %>', [HtmlTemplateDialect.ERB]], + ['', [HtmlTemplateDialect.PHP]], + ['@model Foo\n

    @Model.Name

    ', [HtmlTemplateDialect.RAZOR]], + ['

    ${name}

    ', [HtmlTemplateDialect.DOLLAR_BRACE]], + ['

    x

    ', [HtmlTemplateDialect.DOLLAR_BRACE, HtmlTemplateDialect.THYMELEAF]], + ['

    plain

    ', []], + ]; + let bad = 0; + for (const [text, want] of cases) { + const got = [...parseHtml(text).document.templateDialects].sort().join(','); + if (got !== [...want].sort().join(',')) bad += fail(`${JSON.stringify(text)}: ${got || '(none)'}, expected ${want.join(',') || '(none)'}`); + } + return bad; + }, + }, + { + name: 'gaps-are-capped-with-a-count-of-the-rest', + spec: 'a parse error per attribute on a file with thousands of them is a count, not a table', + rulesOut: 'an extractor that emits ten thousand gap rows, or drops them with no trace', + run: () => { + const attrs = Array.from({ length: WEB_PARSE_GAP_LIMIT + 50 }, (_, i) => `a${i}="1" a${i}="2"`).join(' '); + const x = parseHtml(`
    `); + const limit = x.parseGaps.find((g) => g.gapKind === HtmlParseGapKind.GAP_LIMIT_REACHED); + let bad = 0; + if (x.parseGaps.length !== WEB_PARSE_GAP_LIMIT + 1) bad += fail(`${x.parseGaps.length} gaps, expected ${WEB_PARSE_GAP_LIMIT} + 1`); + if (limit === undefined || !limit.detail.startsWith('50 ')) bad += fail(`limit row=${limit?.detail}`); + if (x.document.getParseGapCount() !== x.parseGaps.length) bad += fail('document gap count disagrees'); + return bad; + }, + }, + // ── CSS: selectors ────────────────────────────────────────────────────── + { + name: 'specificity-follows-selectors-level-4', + spec: 'count ids (a); classes, attributes and pseudo-classes (b); types and pseudo-elements (c); :where() adds nothing; :is()/:not()/:has() add their most specific argument', + rulesOut: 'a counter that counts :where(), ignores :not()\'s argument, or counts *', + run: () => { + const cases: Array<[string, string]> = [ + ['#a .b c::before', '1,1,2'], ['*', '0,0,0'], ['ul li', '0,0,2'], [':root', '0,1,0'], + [':is(#x, .y) p', '1,0,1'], [':where(.x) p', '0,0,1'], ['div:not(.a .b)', '0,2,1'], [':has(> img)', '0,0,1'], + ['li:nth-child(2n of .x)', '0,2,1'], ['a:hover', '0,1,1'], ['a:before', '0,0,2'], ['input[type="text"]', '0,1,1'], + [':host(.a)', '0,2,0'], ['.a.b.c', '0,3,0'], ['svg|rect', '0,0,1'], ['&:hover', '0,1,0'], + ]; + let bad = 0; + for (const [selector, want] of cases) { + const got = specificity(selector); + if (got !== want) bad += fail(`${selector}: ${got}, expected ${want}`); + } + return bad; + }, + }, + { + name: 'selector-parts-keep-compounds-combinators-and-nesting', + spec: 'a complex selector is compounds separated by combinators; a functional pseudo-class takes selectors as arguments', + rulesOut: 'a flattener that loses which compound a class is in, or whether it was negated', + run: () => { + const x = parseCss('.nav > li.item:not(.hidden) + a[href^="http" i] ~ b::before, #x { color: red }'); + const got = x.selectorParts.map((p) => `${p.position}:${p.partKind}:${p.name}${p.value ? '=' + p.value : ''}${p.attributeMatcher}${p.attributeFlags}@${p.compoundIndex}${p.combinatorBefore === CssCombinator.NONE ? '' : '<' + p.combinatorBefore}${p.depth ? '^' + p.depth : ''}`).join(' '); + const want = '0:CLASS:nav@0 1:TYPE:li@1