# Placement
> Move demo services between three hosts by drag or keyboard and watch the CPU estimates. A port clash asks for another port first.
- Element: `<sv-placement>`
- React: `import { SvPlacement } from "@/components/lumesec/sv-placement"`
- Collection: Service Map (https://elements.lumesec.ai/components/service-map)
- Registry item: https://elements.lumesec.ai/r/sv-placement.json
- Page: https://elements.lumesec.ai/components/service-map/placement



Live preview: https://elements.lumesec.ai/view/sv-placement

```html
<sv-placement></sv-placement>
```

## Installation

```bash
npx shadcn@latest add @lumesec/sv-placement
```

First time with the @lumesec registry? Register it once, or install by URL:

```bash
npx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.json
npx shadcn@latest add https://elements.lumesec.ai/r/sv-placement.json
```

## Usage

React:

```tsx
import { SvPlacement } from "@/components/lumesec/sv-placement";

export function Example() {
  return (
    <SvPlacement />
  );
}
```

HTML:

```html
<script type="module" src="https://elements.lumesec.ai/cdn/sv-placement.js"></script>

<sv-placement></sv-placement>
```

## Behaviour

Move services between hosts and watch the load bars. A move onto a host where the port is taken asks for another port first.

## API reference

### Events

Events bubble and cross the shadow boundary unless the description says otherwise.

| Event    | React prop | Detail                                                                                | Description                                                                                                                          |
| -------- | ---------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `change` | `onChange` | `{ action: 'move-service', service: string, from: string, to: string, port: number }` | Fires when a service moves. `service` is the display name, `from` and `to` are host ids, `port` is the port it uses on the new host. |

## Keyboard

| Keys                   | Action                                                          |
| ---------------------- | --------------------------------------------------------------- |
| Enter / Space          | On a service: pick it up or put it down.                        |
| ArrowLeft / ArrowRight | While a service is picked up: move it to the neighbouring host. |

## Accessibility

* Services are focusable `role="button"` chips labelled with name, host and port. The picked-up chip has `aria-grabbed="true"`.
* A polite live region reports each move and each port clash; a clash message contains a button that moves the service under the suggested port.
* Reduced motion: the chip pop-in after a move and the column highlight transitions are switched off.

## Theming

The element reads your shadcn theme tokens through its shadow root, so light and dark follow your theme. The accent comes from `--lumesec`. See [Theming](/docs/theming).

This component reads `--background`, `--border`, `--card`, `--destructive`, `--foreground`, `--input`, `--lumesec-warning`, `--muted` and `--muted-foreground`.

To restyle only LumeSec elements, set the matching `--ui-*` overrides: `--ui-border`, `--ui-border-strong`, `--ui-danger`, `--ui-fg`, `--ui-fg2`, `--ui-mono`, `--ui-muted`, `--ui-page`, `--ui-raised`, `--ui-surface` and `--ui-warn`.

## Notes

* Built-in demo data: AIOP API (two instances), Admin UI, Job worker and Inference on app-01, app-02 and gpu-01, from `INFRA` with exporters left out. No attribute selects a record.
* Host CPU is an estimate: a base load plus 14% per API, 4% per web app, 9% per worker, 38% for inference and 5% otherwise. The bar turns amber above 70% and red above 85%.
* A clash means another service on the target host already uses the port, or the port is the target's node exporter port 9100.
* Container query: the host columns stack under 470 px.


