# UI-кит и оформление

> Оформляйте карточку как угодно — или выглядите как родная с необязательным китом ui.css на живой теме приложения; классы, переменные и правила песочницы для стилей, шрифтов и картинок.

Source: https://docs.neurosquad.ai/ru/card-sdk/styling

Внутри своей коробки карточка — обычная веб-страница: любой фреймворк, любой CSS, canvas, WebGL,
WebAssembly. Оформить её можно двумя путями:

- **Как родная** — необязательный кит `ui.css`: небольшой тёмный набор кнопок, полей, списков и
бейджей на живой теме приложения. Карточки на нём выглядят частью NeuroSquad и следуют его теме.
- **Свой дизайн** — просто не подключайте кит. Переменные темы всё равно под рукой, если захочется
позаимствовать цвет.

## UI-кит

```html
<link rel="stylesheet" href="vendor/ui.css" />   <!-- plain template -->
```

```ts
import '@neurosquad/card-sdk/ui.css'              // with a bundler (React template)
```

Поставьте `class="ns-kit"` на `<body>` — это даст базовую типографику, фон и полосы прокрутки, — и
пользуйтесь классами:

```html
<body class="ns-kit">
  <header class="ns-spread">
    <h1 class="ns-title ns-truncate">Test radar</h1>
    <span class="ns-badge ns-badge--danger">3 failed</span>
  </header>

  <ul class="ns-list ns-scroll">
    <li class="ns-list-item"><span class="ns-dot ns-dot--danger"></span> checkout › pays</li>
    <li class="ns-list-item"><span class="ns-dot ns-dot--success"></span> auth › logs in</li>
  </ul>

  <label class="ns-field">
    <span class="ns-label">Filter</span>
    <input class="ns-input" placeholder="checkout" />
  </label>

  <div class="ns-row">
    <button class="ns-btn ns-btn--primary">Run</button>
    <button class="ns-btn ns-btn--ghost ns-btn--sm">Clear</button>
  </div>
</body>
```

| Группа | Классы |
| --- | --- |
| Раскладка | `ns-kit`, `ns-stack` (вертикально), `ns-row` (горизонтально), `ns-spread` (space-between), `ns-grow`, `ns-scroll`, `ns-pad`, `ns-divider` |
| Поверхности | `ns-surface`, `ns-surface--inset`, `ns-callout`, `ns-callout--success`, `ns-callout--danger`, `ns-empty` |
| Текст | `ns-title`, `ns-subtitle`, `ns-muted`, `ns-small`, `ns-mono`, `ns-truncate`, `ns-kbd`, `ns-help`, `ns-error` |
| Кнопки | `ns-btn` + `--primary`, `--secondary`, `--outline`, `--ghost`, `--danger`, `--sm`, `--lg`, `--icon` |
| Поля | `ns-field`, `ns-label`, `ns-input`, `ns-textarea`, `ns-select`, `ns-switch` |
| Списки | `ns-list`, `ns-list-item` |
| Статус | `ns-badge` + `--accent`, `--success`, `--warning`, `--danger`; `ns-dot` + те же; `ns-spinner`, `ns-progress` |

Вне приложения (в превью в браузере) кит откатывается к тёмной теме приложения, так что и превью
выглядит правильно.

## Переменные темы

`connect()` кладёт живую тему приложения на `<html>` в виде переменных `--ns-<token>` —
`--ns-background`, `--ns-surface`, `--ns-foreground`, `--ns-muted`, `--ns-accent`, `--ns-success`,
`--ns-warning`, `--ns-danger`, их варианты `-foreground` и `-soft`, `--ns-border`, `--ns-focus`,
`--ns-field-*`, `--ns-radius`, `--ns-font-sans`, `--ns-font-mono` и другие (полный список — в разделе
[Тема](https://docs.neurosquad.ai/ru/card-sdk/api/environment#theme)). Пользуйтесь ими в своём CSS, и карточка будет следовать за
приложением:

```css
:root { color-scheme: dark; }
body {
  margin: 0;
  background: var(--ns-background, #0b0b0f);
  color: var(--ns-foreground, #fafafa);
  font: 13px/1.45 var(--ns-font-sans, system-ui, sans-serif);
}
.chip {
  border-radius: calc(var(--ns-radius, 0.5rem) * 2);
  background: var(--ns-accent-soft);
  color: var(--ns-accent-soft-foreground);
}
```

Давайте переменным запасные значения (как выше), если страница должна отображаться и вне
приложения. Чтобы ваши цвета никто не трогал, подключайтесь через `connect({ theme: false })`.

## Правила песочницы для страниц

Ваша страница отдаётся из собственного пакета со строгой политикой безопасности контента (CSP). На
практике:

- **Никаких встроенных скриптов и атрибутов `onclick="…"`** — код кладите в файлы `.js`. `eval` и
`new Function` тоже заблокированы (WebAssembly разрешён).
- **Никаких внешних файлов.** Скрипты, стили, шрифты и картинки должны лежать в пакете;
`<link href="https://fonts…">` или `<img src="https://…">` заблокируются. Картинки `data:` и `blob:`
работают. За удалёнными данными ходите через [`card.net.fetch`](https://docs.neurosquad.ai/ru/card-sdk/api/network), а картинки
превращайте в URL `blob:`.
- **Встроенные стили можно** (`style="…"` и `<style>`).
- **Относительные URL.** Страница отдаётся с `nscard://<id>/<path>`; собирайте с относительными
путями к ресурсам (в Vite — `base: './'`, как в шаблоне React).
- **Никаких `localStorage`, cookies, `alert`, всплывающих окон, загрузок, полноэкранного режима,
камеры и микрофона.** Используйте `card.storage`, `card.ui.confirm`, `card.openLink`.
- **Никакой записи в буфер обмена со страницы.** Обработчики `copy`/`cut` и
`document.execCommand('copy')` ничего не делают (иначе любая карточка могла бы подменить буфер
обмена по клику). Используйте [`card.copyText`](https://docs.neurosquad.ai/ru/card-sdk/api/files#clipboard); обычное
копирование выделенного текста пользователем по-прежнему работает.
- **Никаких вложенных фреймов.** Элементы `iframe`, `frame`, `object` и `embed` удаляются.
- **Только blob-воркеры.** `new Worker('worker.js')` в источнике карточки не работает; скачайте
скрипт, заверните его в `Blob` и запустите `new Worker(URL.createObjectURL(blob))`.
- **Никогда не доверяйте событиям `message`.** Другие карточки могут делать `postMessage` в ваш
фрейм.

`npx @neurosquad/card-sdk validate` предупреждает о встроенных скриптах, встроенных обработчиках и
внешних файлах во входной странице.

> **Формы никуда не уводят.** Событие `submit` у `<form>` срабатывает, и ваш обработчик выполняется,
> но саму отправку приложение отменяет, а `form.submit()` ничего не делает — страница карточки не
> может ничего никуда отправить или перезагрузиться. Всё равно вызывайте в обработчике
> `event.preventDefault()` и делайте работу в JavaScript.

## Дизайн для холста

- **Маленькая и большая.** Карточка часто размером 400×300, а иногда развёрнута на весь экран.
Пусть оба вида выглядят законченными: переключайте раскладку через `card.lifecycle.onExpanded` или
`useExpanded()`.
- **Пусто, загрузка, ошибка.** Показывайте что-то осмысленное, пока данные не пришли, когда их нет и
когда вызов упал.
- **Плитка обзора — это ваша карточка издалека.** Держите [`setOverview`](https://docs.neurosquad.ai/ru/card-sdk/api/card-ui#overview)
актуальным, с одним главным фактом.
- **Тихо, когда не видно.** Останавливайте анимации, когда `usePaused()` возвращает true, — на холсте
бывает пятьдесят карточек.
- **Шапка — забота приложения.** Не повторяйте в теле имя карточки или кнопку закрытия: в шапке они
уже есть.

> Всё, что внутри тела карточки, принадлежит самой карточке, и пользователям об этом сказано. Не
> подражайте диалогам NeuroSquad и не спрашивайте пароли и ключи внутри карточки — используйте
> настройки `secret`, которые приложение запрашивает в своей собственной форме.
