# Writing CSS for the Markstrata HTML web part

Hand this whole file to a language model before asking it to write a stylesheet
or an HTML document for **Markstrata - HTML**. It is the set of rules that
cannot be guessed from looking at the output.

Generated by `scripts/build-css-contract.js`. The token lists are read out of
the stylesheets that define them, so they are current by construction; do not
edit them here.

## What you are writing

A stylesheet, or a whole HTML document with a `<style>` block in it, that a
SharePoint web part will draw. The reader can be in light or dark mode, on any
of three themes, and can switch while reading. Your CSS has to follow them.

## The two rules

**1. Take every colour from a token, not from a literal.** The tokens below are
custom properties the web part sets. They change with the theme and the colour
mode on their own. Write a fallback after the comma so a renamed token degrades
to something readable rather than to nothing.

```css
.card {
  background: var(--strata-bg-elevated, #f6f8fa);
  color: var(--strata-text, #1f2328);
  border: 1px solid var(--strata-border, #d1d9e0);
}
```

**2. For anything a token does not cover, select on the mode.** Shadows and
picture swaps are the usual reasons. Start the selector with the attribute:

```css
[data-strata-mode="dark"]  .card { box-shadow: none; }
[data-strata-mode="light"] .card { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); }
```

`[data-strata-theme="github"]`, `"obsidian"` and `"vscode"` work the same way.

**Never use `@media (prefers-color-scheme: ...)`.** It follows the operating
system. The web part follows the setting an author chose and the reader's own
choice in the toolbar, so on a dark laptop showing a page set to light the two
disagree and the document comes out half in each.

## The three render modes, and what each changes

An author picks one. Write CSS that works in all three and you do not need to
know which.

| Mode | Your CSS | What to know |
|---|---|---|
| Inline | Narrowed to the web part, see below | The page's own styles also reach your document |
| Shadow DOM | Used as written, inside a boundary | Nothing outside reaches in, and the web part's typography does not either: you own the whole look |
| Frame | Used as written, in a document of its own | The web part copies the tokens and the mode attribute in, so both rules above still work |

## What happens to your selectors in inline mode

Every selector is rewritten to apply only inside the web part. Six things
follow from that, and each is worth knowing before you write a selector that
quietly matches nothing.

- `html`, `body`, `:root` and `:host` all become the element your document is
  drawn into. That element is the document's own column, not the whole web
  part: it is as wide as the reading measure the theme sets, with the web
  part's surface either side of it. So `body { background: ... }` colours
  behind your document and not behind the toolbar and the footer around it.
  There is no selector that reaches those; they belong to the web part.
  That boundary is deliberate rather than an oversight. The toolbar's own text
  takes its colour from the theme, so a document painting the whole web part
  dark on a page set to light would leave Markstrata's controls unreadable with
  nothing you could write to fix them. Your background stops where your
  document stops, and the surface either side stays the theme's.
- A selector starting with `[data-strata-mode]` or any other
  `[data-strata-...]` is attached to that same element, which is what makes
  rule 2 above work.
- `@media`, `@supports`, `@container`, `@layer` and `@scope` are kept and the
  rules inside them are rewritten.
- `@keyframes`, `@font-face`, `@page`, `@property`, `@counter-style`,
  `@font-feature-values` and `@viewport` are kept exactly as written, because
  what is inside them is not a selector.
- `@import` and `@charset` are **dropped**. Inline whatever you were importing.
- Your own data attributes are left alone: `[data-status="open"] .row` still
  means an element inside your document.

## What is removed before your document is drawn

The document is sanitised every time, in every mode, with one exception: a
frame with the scripts setting turned on, which an author has to choose.

- `<script>` in every spelling, and every `on*` handler. Do not write
  behaviour; write a document.
- `<form>`, `<input>`, `<textarea>`, `<select>`, `<object>`, `<embed>`,
  `<base>` and `<meta>`.
- `javascript:` in an `href` or a `src`, however it is written.
- `<link rel="stylesheet">`. A stylesheet has to be inline, in a `<style>`
  block, or chosen by the author in the property pane.
- `<iframe>` survives only when it points at one of a fixed list of hosts:
  YouTube, Vimeo, Microsoft Stream, Forms, Power BI, Teams, or any
  `*.sharepoint.com`. Anywhere else and the frame is dropped.

A `<style>` block **is** kept. The web part lifts it out of the file before
sanitising, because the sanitiser would otherwise delete it silently, and puts
it back scoped to your document.

## A worked example

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Service review</title>
  <style>
    /* Colour from tokens, measurements of your own. */
    .summary {
      background: var(--strata-bg-elevated, #f6f8fa);
      border-left: 4px solid var(--strata-accent, #2f6f4f);
      padding: 12px 16px;
    }
    table { border-collapse: collapse; width: 100%; }
    th, td {
      border: 1px solid var(--strata-border, #d1d9e0);
      padding: 6px 10px;
    }
    td.num { text-align: right; font-variant-numeric: tabular-nums; }

    /* What a token cannot say. */
    [data-strata-mode="dark"] .summary { box-shadow: none; }
    [data-strata-mode="light"] .summary { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); }
  </style>
</head>
<body>
  <h1>Service review</h1>
  <div class="summary"><p>Three fixes and one new setting.</p></div>
  <table>
    <thead><tr><th>Queue</th><th class="num">Tickets</th></tr></thead>
    <tbody><tr><td>Billing</td><td class="num">48</td></tr></tbody>
  </table>
</body>
</html>
```

## Do not

- Write a hex colour for anything a token covers.
- Use `prefers-color-scheme`.
- Use `@import`.
- Write `<script>`, an `on*` handler, or a form.
- Set a fixed `height` on the document. The author chooses the height in the
  property pane, and a document that fights it scrolls twice.
- Use `position: fixed` to place something against the window. The web part is
  a panel in a page, not the page.
- Assume a font is installed. Use `var(--strata-font-body, ...)` and
  `var(--strata-font-mono, ...)`.

## Colour tokens

Declared by all 3 themes, in light and dark, so a document using them
follows the reader without a single `[data-strata-mode]` rule.

```
--strata-bg
--strata-bg-elevated
--strata-bg-hover
--strata-block-gap-base
--strata-border
--strata-border-strong
--strata-callout-accent-width
--strata-callout-bg-alpha
--strata-callout-border-width
--strata-callout-content-padding
--strata-callout-fold-closed-pad
--strata-callout-radius
--strata-callout-title-bg-alpha
--strata-callout-title-padding
--strata-callout-title-weight
--strata-code-bg
--strata-code-border
--strata-code-font-size
--strata-code-gutter-border
--strata-code-gutter-text
--strata-code-header-bg
--strata-code-header-border
--strata-code-inline-bg
--strata-code-inline-border
--strata-code-inline-text
--strata-code-line-height
--strata-code-line-hover
--strata-code-padding
--strata-code-radius
--strata-code-text
--strata-color-blue
--strata-color-cyan
--strata-color-gray
--strata-color-green
--strata-color-orange
--strata-color-pink
--strata-color-purple
--strata-color-red
--strata-color-yellow
--strata-font-body
--strata-font-heading
--strata-font-mono
--strata-font-size
--strata-h1-rule-space
--strata-h1-rule-width
--strata-h1-size
--strata-h1-weight
--strata-h2-rule-space
--strata-h2-rule-width
--strata-h2-size
--strata-h3-size
--strata-h4-size
--strata-h5-size
--strata-h6-size
--strata-heading-color
--strata-heading-weight
--strata-line-height-base
--strata-link
--strata-link-hover
--strata-list-indent
--strata-list-item-gap
--strata-mark-alpha
--strata-mermaid-bg
--strata-on-accent
--strata-quote-bg
--strata-quote-border-color
--strata-quote-border-width
--strata-quote-padding
--strata-quote-radius
--strata-quote-text
--strata-radius-lg
--strata-radius-md
--strata-radius-sm
--strata-scrollbar-thumb
--strata-syn-addition
--strata-syn-addition-bg
--strata-syn-attr
--strata-syn-builtin
--strata-syn-bullet
--strata-syn-comment
--strata-syn-constant
--strata-syn-deletion
--strata-syn-deletion-bg
--strata-syn-entity
--strata-syn-keyword
--strata-syn-link
--strata-syn-operator
--strata-syn-property
--strata-syn-punctuation
--strata-syn-regexp
--strata-syn-section
--strata-syn-string
--strata-syn-tag
--strata-syn-type
--strata-syn-variable
--strata-table-border
--strata-table-header-bg
--strata-table-hover-bg
--strata-table-stripe-bg
--strata-text
--strata-text-faint
--strata-text-muted
```

These are declared by some themes and not others, so a document using one follows the reader in some themes and falls back in the rest. Give them a fallback value, or avoid them:

- `--strata-callout-rgb` - only obsidian

## Tokens that do not change with the theme

Declared once rather than per theme, so these are the same whichever theme a
page is set to and in both colour modes. Mostly measurements - spacing, widths,
scales - and a few fixed values the web part's own furniture uses. Safe to
read, but they will not follow the reader, so do not reach for one of these
where a colour token exists.

```
--strata-anchor-gutter
--strata-block-gap
--strata-code-called-bar
--strata-code-called-bg
--strata-code-medium-lines
--strata-code-short-lines
--strata-content-max-width
--strata-density-scale
--strata-font-scale
--strata-line-height
--strata-root-padding-block
--strata-root-padding-inline
--strata-zoom-chrome
--strata-zoom-close-size
```
