# WEB Language Guide

WEB is a small declarative UI language that compiles `.web` source into HTML and CSS.

Run the compiler with:

```bash
node compiler.js
```

That legacy script mode reads `layout.web` from the current directory and writes `index.html` plus `styles.css`.

You can also use the CLI to compile any `.web` file next to its source:

```bash
web home.web
web home
web .
web ./code/*
```

CLI mode writes matching outputs such as `home.html` and `home.css`.

## Recommended Build Order

WEB is easiest to use in this sequence:

1. Define the HTML structure.
2. Add a `define { ... }` block for variables, semantic types, and shared style inheritance.
3. Add HTML attributes plus any top-level `::head` and `::script` blocks.
4. Add states and animations with `::pseudo` blocks.
5. Add responsive rules with `::media`.
6. Use `raw` only for CSS that WEB does not express yet.

## 1. Structure and HTML Output

Start by setting up one optional `define { ... }` block for shared declarations.

### Define block

All variables, semantic type declarations, and CSS inheritance declarations must live inside a single top-level `define { ... }` block.

```web
define {
  @pageWidth = "960px";
  Button actionButton;
  actionButton primaryButton;
  primaryButton extends actionButton;
}
```

Rules:

- `define { ... }` is optional, but required if you use variables, type declarations, or style inheritance
- it must appear before rules
- only one `define { ... }` block is allowed
- only variables, type declarations, and style inheritance are allowed inside it

### Type declarations

Use this form:

```web
define {
  BaseType NewType;
}
```

Example:

```web
define {
  Button actionButton;
  actionButton primaryButton;
  Main pageMain;
  Section heroSection;
  Paragraph heroCopy;
}
```

Rules:

- declarations live inside `define { ... }`
- types can inherit from other declared types
- the resolved base type decides which HTML tag is generated

Type declarations do not copy CSS. If you want one WEB object or class to inherit another object's styles, use `extends` inside `define { ... }`.

### Supported HTML mapping

WEB does not let you write raw HTML tags directly. It infers them from the resolved base type.

Common built-in mappings:

- `Button` -> `<button>`
- `Heading`, `Heading1`, `H1` -> `<h1>`
- `Heading2`, `H2` -> `<h2>`
- `Heading3`, `H3` -> `<h3>`
- `Heading4`, `H4` -> `<h4>`
- `Heading5`, `H5` -> `<h5>`
- `Heading6`, `H6` -> `<h6>`
- `Paragraph`, `Text` -> `<p>`
- `Span` -> `<span>`
- `Section` -> `<section>`
- `Main` -> `<main>`
- `Header` -> `<header>`
- `Footer` -> `<footer>`
- `Nav`, `Navigation` -> `<nav>`
- `Article` -> `<article>`
- `Aside` -> `<aside>`
- `Figure` -> `<figure>`
- `FigureCaption`, `FigCaption` -> `<figcaption>`
- `Link`, `Anchor` -> `<a>`
- `Picture` -> `<picture>`
- `Source` -> `<source>`
- `Image`, `Img` -> `<img>`
- `Video` -> `<video>`
- `Audio` -> `<audio>`
- `Form` -> `<form>`
- `Label` -> `<label>`
- `Dialog` -> `<dialog>`
- `Details` -> `<details>`
- `Summary` -> `<summary>`
- `Input` -> `<input>`
- `TextArea` -> `<textarea>`
- `Select` -> `<select>`
- `Option` -> `<option>`
- `List`, `UnorderedList`, `BulletList` -> `<ul>`
- `OrderedList`, `NumberedList` -> `<ol>`
- `ListItem` -> `<li>`
- `Table` -> `<table>`
- `TableHead` -> `<thead>`
- `TableBody` -> `<tbody>`
- `TableFoot` -> `<tfoot>`
- `TableRow` -> `<tr>`
- `TableHeaderCell` -> `<th>`
- `TableCell` -> `<td>`
- `Code` -> `<code>`
- `Pre`, `Preformatted` -> `<pre>`
- `Strong` -> `<strong>`
- `Em`, `Emphasis` -> `<em>`
- `LineBreak`, `Br` -> `<br>`
- anything else -> `<div>`

Void elements such as `Image`, `Input`, `LineBreak`, and `Source` cannot contain children or content.

### Dot notation

Dot notation builds hierarchy directly from the path.

```web
heroSection.primaryButton.textContent = "Launch";
heroSection.primaryButton.padding = "14px 18px";
```

That produces a `primaryButton` inside `heroSection`.

### Nested blocks

Nested blocks are syntax sugar for the same structure.

```web
pageMain {
  heroSection {
    primaryButton {
      textContent = "Launch";
      padding = "14px 18px";
    }
  }
}
```

These forms are equivalent:

```web
pageMain.heroSection.primaryButton.textContent = "Launch";
```

```web
pageMain {
  heroSection {
    primaryButton {
      textContent = "Launch";
    }
  }
}
```

Dot notation blocks are also supported:

```web
pageMain.heroSection {
  primaryButton {
    textContent = "Launch";
  }
}
```

### Global style blocks

Two special top-level blocks map directly to global CSS selectors:

```web
* {
  boxSizing = "border-box";
}

html {
  background = "#101522";
  color = "#f7f6ff";
}
```

That compiles to:

```css
* {
  box-sizing: border-box;
}

html {
  background: #101522;
  color: #f7f6ff;
}
```

Rules:

- `* { ... }` and `html { ... }` are CSS-only global selector blocks
- they do not create HTML nodes
- they must be declared at the top level
- they are also allowed inside a top-level `::media` block
- nested `html { ... }` inside an element block is not allowed

### Content properties

WEB has three reserved properties:

- `textContent`
- `innerHTML`
- `raw`

`textContent` inserts escaped text:

```web
heroSection.heroCopy.textContent = "Safe plain text";
```

`innerHTML` inserts raw HTML:

```web
heroSection.heroCopy.innerHTML = "Read the <strong>docs</strong>.";
```

### HTML attributes with `::attrs`

Use `::attrs { ... }` when you want real HTML attributes on a normal WEB node.

```web
docsLink {
  textContent = "Docs";

  ::attrs {
    href = "/docs";
    target = "_blank";
    rel = "noreferrer";
    ariaLabel = "Open docs";
  }
}
```

Rules:

- `::attrs` must be nested directly inside an element block
- attribute names use camelCase in WEB and compile to kebab-case in HTML
- `disabled = true;` emits a bare boolean attribute
- `open = true;` is useful for tags like `Dialog` and `Details`
- `false`, `null`, and `undefined` omit the attribute
- `class` and `style` are not allowed in `::attrs`
- `::attrs` is not allowed inside `styles { ... }`, pseudo blocks, or media blocks

### Head tags with `::head`

Use `::head { ... }` when you want to add `meta` and `link` tags directly to the document `<head>`.

```web
::head {
  meta {
    name = "description";
    content = "Landing page built in WEB.";
  }

  link {
    rel = "help";
    href = "compiler.md";
    type = "text/markdown";
  }
}
```

That compiles to entries like:

```html
<meta name="description" content="Landing page built in WEB.">
<link rel="help" href="compiler.md" type="text/markdown">
```

Rules:

- `::head` must be declared at the top level
- it does not participate in CSS generation
- it only supports `meta { ... }` and `link { ... }` entries
- `meta` and `link` entries only support direct HTML attribute assignments
- attribute names use camelCase in WEB and compile to kebab-case in HTML
- custom head entries are inserted into `<head>` after the compiler’s default charset, viewport, title, and stylesheet tags

### Script tags with `::script`

Use `::script { ... }` when you want the compiler to emit a literal `<script></script>` tag.

```web
::script {
  src = "/assets/app.js";
  defer = "true";

  code {
    function helloWorld () {
      alert("hello world");
    }
  }
}
```

That compiles to:

```html
<script src="/assets/app.js" defer="true">
  function helloWorld () {
    alert("hello world");
  }
</script>
```

Rules:

- `::script` must be declared at the top level
- it does not participate in CSS generation
- it supports direct HTML attribute assignments like `src = "/app.js";`
- it supports one optional `code { ... }` block for inline script content
- the `code { ... }` body is copied into the output script tag as raw text
- script blocks render near the end of `<body>` after normal WEB nodes
- `defer = true;` emits a bare boolean attribute, while `defer = "true";` emits `defer="true"`

### JavaScript injection

WEB supports compile-time JavaScript values with:

```web
heroSection.heroCopy.textContent = js`
  let count = 3;
  let output = "";

  for (let i = 0; i < count; i += 1) {
    output += `Hello ${i} `;
  }

  return output;
`;
```

How it works:

- the compiler wraps the contents in a function and executes it at compile time
- the final result is inserted into the generated output
- errors are rendered inline as text instead of crashing the compiler

### JavaScript result order

WEB resolves a `js` block in this order:

1. an explicit `return`
2. text accumulated through `emit(value)`
3. the last top-level declared variable inside the `js` block
4. an empty string

Example:

```web
heroSection.heroCopy.textContent = js`
  emit("Hello ");
  emit("world");
`;
```

### Structured fragment output

When `innerHTML` is used with `js`, the `js` block may return structured fragment nodes instead of raw tag strings.

Helpers available inside `js` blocks:

- `node(typeName, className?, options?)`
- `el(...)` as an alias for `node(...)`
- `fragment(children)`
- `text(value)`
- `html(value)`

Supported `node(...)` options:

- `textContent`
- `innerHTML`
- `children`
- `style`
- `attributes`

Use `::attrs { ... }` for normal WEB nodes. Use `node(..., { attributes: ... })` only for nodes generated inside `js`.

Example:

```web
featureGrid.innerHTML = js`
  const cards = [
    { title: "Readable", copy: "Less tag-heavy JS output." },
    { title: "Loop-friendly", copy: "Still generated at compile time." },
  ];

  return cards.map((card) =>
    node("Article", "featureCard", {
      children: [
        node("Heading3", "featureTitle", { textContent: card.title }),
        node("Paragraph", "featureCopy", { textContent: card.copy }),
      ],
    })
  );
`;
```

This keeps loops and conditional logic in JavaScript while still avoiding explicit raw HTML tags in the source.

## 2. Basic Styling and Positioning

Once the structure exists, add normal CSS properties directly in WEB.

### Property assignments

Any non-reserved property becomes CSS.

```web
heroSection {
  display = "grid";
  gap = "18px";
  padding = "32px";
  backgroundColor = "#101522";
}
```

Compiles to:

```css
.heroSection {
  display: grid;
  gap: 18px;
  padding: 32px;
  background-color: #101522;
}
```

Rules:

- CSS property names should be written in camelCase
- the compiler converts them to kebab-case in `styles.css`
- later assignments for the same property on the same rule win

### Values

WEB accepts these value types:

- quoted strings: `"grid"`
- numbers: `1`, `0.95`, `-10`
- percentages: `50%`
- bare identifiers: `none`, `auto`, `inherit`
- variable references: `@pageWidth`
- template literals: `` `...` ``
- JavaScript inject blocks: `` js`...` ``

Examples:

```web
heroSection.opacity = 0.95;
heroSection.width = 100%;
heroSection.display = grid;
heroSection.border = @borderRule;
```

### Variables

Use variables inside `define { ... }` to avoid repeating primitive values.

```web
define {
  @pageWidth = "960px";
  @panelRadius = "24px";
  @borderRule = "1px solid #dde5ee";
}
```

Then reuse them:

```web
pageMain.width = @pageWidth;
heroSection.border = @borderRule;
```

Rules:

- variable names must start with `@`
- variables must be declared inside `define { ... }`
- variables can reference other variables
- variables only support simple values, not `js` blocks

### Style inheritance

Use `extends` inside `define { ... }` when you want one WEB name to inherit another name's CSS.

Syntax:

```web
define {
  storeCard extends card;
}
```

Example:

```web
define {
  Article card;
  card storeCard;
  storeCard extends card;
}

card {
  padding = "20px";
  borderRadius = "20px";
  background = "#eef3ff";
}

storeCard {
  background = "#ffffff";
}
```

This means:

- `storeCard` inherits the CSS generated for `card`
- `storeCard` keeps its own explicit styles too
- explicit styles on `storeCard` win over inherited styles from `card`

Rules:

- style inheritance declarations must be declared inside `define { ... }`
- style inheritance is CSS-only and does not change HTML tag inference
- style inheritance is transitive
- circular style inheritance is not allowed
- closer ancestors win over farther ancestors
- explicit styles on the derived name win over inherited styles, even if the base rule appears later in the file

What gets inherited:

- normal property assignments
- nested descendant rules
- `styles { ... }` selectors
- pseudo blocks
- media-query blocks
- `raw` blocks anchored to the inherited selector

If you also want the derived object to share the same semantic HTML tag, combine type inheritance and style inheritance:

```web
define {
  Article card;
  card storeCard;
  storeCard extends card;
}
```

### Style scopes

Use `styles { ... }` when you want CSS selectors without creating matching HTML nodes.

This is especially useful for classes returned from `innerHTML = js\`...\``.

```web
featureGrid {
  innerHTML = js`
    return node("Article", ["featureCard", "featureCardCool"], {
      children: [
        node("Heading3", ["featureTitle", "featureTitleCool"], {
          textContent: "Readable",
        }),
        node("Paragraph", "featureCopy", {
          textContent: "Styled from outer WEB syntax.",
        }),
      ],
    });
  `;

  styles {
    featureCard {
      padding = "20px";
      borderRadius = "20px";
    }

    featureCardCool {
      background = "linear-gradient(180deg, #ffffff 0%, #eef6ff 100%)";
    }

    featureTitleCool {
      color = "#225fbe";
    }

    featureCopy {
      color = "#596780";
    }
  }
}
```

Rules:

- `styles { ... }` emits descendant CSS selectors
- it does not create matching HTML nodes
- `textContent` and `innerHTML` are not allowed inside `styles`
- `raw` is still allowed inside `styles`

### Selector generation

WEB maps paths to descendant class selectors.

```web
pageMain.heroSection.primaryButton.backgroundColor = "#ff7070";
```

Compiles to:

```css
.pageMain .heroSection .primaryButton {
  background-color: #ff7070;
}
```

## 3. Pseudo Selectors and Animations

WEB now has a first-class pseudo system.

### Pseudo selector syntax

All pseudo selectors are written with a `::` prefix in WEB.

Example:

```web
benefitCard {
  background = "blue";

  ::hover {
    background = "red";
  }
}
```

Compiles to:

```css
.benefitCard {
  background: blue;
}

.benefitCard:hover {
  background: red;
}
```

### Mapping rules

WEB normalizes pseudo names like this:

- pseudo-classes compile to a single colon in CSS
- pseudo-elements compile to a double colon in CSS
- camelCase pseudo names are converted to kebab-case

Examples:

- `::hover` -> `:hover`
- `::focusWithin` -> `:focus-within`
- `::nthChild(2n + 1)` -> `:nth-child(2n + 1)`
- `::before` -> `::before`
- `::firstLetter` -> `::first-letter`

### Nested descendant rules

Pseudo blocks stay attached to the scope where they are declared.

```web
benefitCard {
  icon {
    opacity = 0.4;
  }

  ::hover {
    icon {
      opacity = 1;
    }
  }
}
```

Compiles to:

```css
.benefitCard .icon {
  opacity: 0.4;
}

.benefitCard:hover .icon {
  opacity: 1;
}
```

### Pseudo selectors inside style scopes

Pseudo blocks also work inside `styles { ... }`.

```web
fragmentMount {
  styles {
    card {
      padding = "12px";

      ::hover {
        background = "#dbe7ff";
      }
    }
  }
}
```

### `::keyframes`

Animations use a top-level `::keyframes` block.

Example:

```web
card {
  animation = "slideIn 3s";
}

::keyframes slideIn {
  from {
    opacity = 0;
    transform = "translateY(16px)";
  }

  60% {
    opacity = 1;
  }

  to {
    opacity = 1;
    transform = "translateY(0)";
  }
}
```

Compiles to:

```css
.card {
  animation: slideIn 3s;
}

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(16px);
  }

  60% {
    opacity: 1;
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}
```

Rules:

- `::keyframes` must be declared at the top level
- `::keyframes` cannot be nested inside element blocks or `styles { ... }`
- keyframe stages support `from`, `to`, and percentages like `50%`
- keyframe stages accept normal CSS property assignments

Invalid:

```web
card {
  ::keyframes slideIn {
    from {
      opacity = 0;
    }
  }
}
```

## 4. Media Queries

Media queries use the same `::` syntax.

### Nested media rules

```web
benefitCard {
  padding = "20px";

  ::media (max-width: 700px) {
    padding = "14px";
  }
}
```

Compiles to:

```css
.benefitCard {
  padding: 20px;
}

@media (max-width: 700px) {
  .benefitCard {
    padding: 14px;
  }
}
```

### Top-level media blocks

You can also wrap whole rule groups:

```web
::media (max-width: 700px) {
  pageMain {
    gap = "18px";
  }

  pageMain.heroSection {
    padding = "20px";
  }
}
```

### Combining media and pseudo selectors

Media blocks and pseudo blocks can be combined.

```web
button {
  ::media (max-width: 700px) {
    ::hover {
      background = "#2d5cff";
    }
  }
}
```

This compiles to:

```css
@media (max-width: 700px) {
  .button:hover {
    background: #2d5cff;
  }
}
```

Rules:

- `::media` accepts normal CSS media query text after the keyword
- media query conditions should be written in normal CSS syntax such as `max-width`, not camelCase
- media blocks only support CSS properties, selector blocks, pseudo blocks, `styles { ... }`, and `raw`
- `textContent`, `innerHTML`, type declarations, and variable declarations are not allowed inside media blocks

## 5. Raw CSS Escape Hatch

`raw` still exists for CSS that WEB does not express yet.

Example:

```web
heroSection.primaryButton.raw = `
  transition: transform 0.3s ease;
  &:hover {
    transform: scale(1.05);
  }
`;
```

Use `raw` when you need:

- CSS selectors that WEB does not model yet
- at-rules other than `::media` and `::keyframes`
- complex CSS text that is easier to paste directly

`raw` supports:

- normal declarations
- nested selectors using `&`
- at-rules such as `@supports`

For new code, prefer:

- normal property assignments first
- `styles { ... }` for selector-only styling
- `::pseudo` blocks for states and pseudo-elements
- `::media` for responsive rules

## Comments

WEB supports:

- line comments with `//`
- block comments with `/* ... */`

## JavaScript Safety

JavaScript injection is designed not to crash the compiler.

- runtime errors are rendered inline as text
- syntax errors are rendered inline as text
- timed-out scripts are rendered inline as text
- when `innerHTML` generation fails, the error is still escaped as text rather than inserted as raw HTML

JavaScript blocks currently run with a 1-second timeout.
