# Swiss Army Knife Card

> [object Object]

- Skill: `bobsilvio/swiss-army-knife-card` (Agent Skill)
- Install (CLI): `npx skillmds@latest add bobsilvio/swiss-army-knife-card`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bobsilvio/swiss-army-knife-card/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Bobsilvio (https://skillmd.com/u/bobsilvio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/bobsilvio/swiss-army-knife-card

---


You are an expert in **Swiss Army Knife Card** (SAK) for Home Assistant Lovelace.
SAK is a HACS custom card that renders an SVG canvas with precise coordinate-based positioning, built-in tools (shapes, gauges, sliders, entity displays, images), JavaScript templates, CSS animations, and full HA state binding.

## Installation

1. Install via HACS → Frontend → Swiss Army Knife Card
2. In Home Assistant: **Settings → Dashboards → Resources**
3. Add resource: `/hacsfiles/swiss-army-knife-card/swiss-army-knife-card.js`, Type: JavaScript Module
4. Refresh the browser

## Coordinate System

Every tool is positioned on an SVG viewport whose size depends on `aspectratio`.

```
aspectratio: 1/1  →  100×100 grid
aspectratio: 2/1  →  200×100 grid  (width = 100 × ratio numerator)
aspectratio: 3/2  →  150×100 grid
```

With `aspectratio: 1/1` (default):
```
(0,0) ──────────────── (100,0)
  │   cx=50, cy=50         │
(0,100) ──────────────(100,100)
```

With `aspectratio: 2/1` — grid is 200 wide, 100 tall:
```
(0,0) ────────── (100,0) ────────── (200,0)
  │  left half        │  right half       │
(0,100) ───────(100,100)──────────(200,100)
```

The ratio can be **decimal** or have a denominator ≠ 1:
- `aspectratio: 3/3.35` → grid ≈ 90×100 (taller than wide)
- `aspectratio: 6/2` → grid 300×200 (denominator scales the height)

Formula: **width = 100 × W**, **height = 100 × H** (both dimensions scale from the ratio numbers × 100).

- `cx` / `cy` — center X / Y in the scaled grid
- `w` / `h` — width / height
- Toolset positions and tool offsets are all in the same absolute grid

## Top-Level Card Structure

```yaml
type: custom:swiss-army-knife-card
entities:
  - entity: sensor.temperature
    name: Temperature
  - entity: light.living_room
    name: Living Room
layout:
  aspectratio: 1/1        # width/height ratio of the card (e.g. 1/1, 2/1, 3/2)
  styles:
    card:
  toolsets:
    - toolset: my_toolset
      position:
        cx: 50
        cy: 50
      tools:
        - type: circle
          position:
            cx: 0
            cy: 0
            radius: 40
          styles:
            circle:
              fill: var(--primary-color)
```

## Entities Binding

```yaml
entities:
  - entity: sensor.temperature        # index 0
    name: Temperature
    decimals: 1
  - entity: sensor.humidity           # index 1
    name: Humidity
  - entity: light.living_room         # index 2
    name: Living Room
    icon: mdi:lightbulb
  - entity: sensor.power              # index 3  — can be used just for background icon
    icon: mdi:washing-machine
```

You can also bind to an **attribute** instead of the state:

```yaml
  - entity: sensor.my_sensor
    attribute: cost_monthly    # reads sensor.my_sensor.attributes.cost_monthly
    area: Monthly Cost         # overrides the area label shown by `type: area`
    icon: mdi:currency-eur
    decimals: 2
```

### Singular vs plural entity reference

Most tools use the singular form:
```yaml
entity_index: 0
```

Some tools (`name`, `state`) accept the plural array form — use this when you want to explicitly declare which entity a tool uses:
```yaml
entity_indexes:
  - entity_index: 0
```

Both forms are valid; the array form is typically used in more complex templates.

## Toolsets

A **toolset** is a group of tools sharing a common origin position. Tools within a toolset use coordinates relative to the toolset's `cx`/`cy`.

```yaml
layout:
  toolsets:
    - toolset: group_name
      disabled: false          # or '[[variable]]' to conditionally hide the toolset
      position:
        cx: 50    # toolset center X on the layout grid
        cy: 50    # toolset center Y
      tools:
        - type: circle
          disabled: false      # 'disabled:' also works on individual tools
          position:
            cx: 0    # offset from toolset center
            cy: 0
            radius: 30
```

### Toolset-level templates

A toolset can itself be instantiated from a reusable template, passing variables to it. This lets you repeat complex column-style layouts across a wide card:

```yaml
layout:
  aspectratio: 6/1    # 600×100 grid — 6 columns side by side
  toolsets:
    - toolset: column-production
      template:
        name: sak_toolset_tutorial_02_part1   # SAK built-in or custom template name
        variables:
          - var_entity_index: 0
          - var_toolset_position_cx: 260
          - var_segarc_scale_min: 0
          - var_segarc_scale_max: 8900

    - toolset: column-consumption
      template:
        name: sak_toolset_tutorial_02_part1
        variables:
          - var_entity_index: 2
          - var_toolset_position_cx: 450
          - var_segarc_scale_max: 6000
```

> **Note**: when a toolset uses `template:`, the template completely replaces the toolset definition. Any extra fields (like `palette:`) on the same toolset are ignored — only the template content is used.

## Tool Names (Exact Syntax)

The correct `type:` values are:

| Type | Description |
|------|-------------|
| `circle` | Circle shape |
| `ellipse` | Ellipse shape |
| `line` | Line (horizontal / vertical / fromto) |
| `rectangle` | Rectangle with uniform border radius |
| `rectex` | Rectangle with per-corner radius + colorstops + foreground/background |
| `polygon` | Regular polygon (triangle, hexagon, etc.) |
| `text` | Static text |
| `name` | Entity friendly name (NOT `entity_name`) |
| `state` | Entity state value (NOT `entity_state`) |
| `icon` | Entity icon (NOT `entity_icon`) |
| `area` | Entity area / label text (NOT `entity_area`) |
| `segarc` | Segmented arc / gauge |
| `horseshoe` | Horseshoe gauge |
| `slider` | Interactive numeric slider |
| `switch` | Toggle switch |
| `sparkline` | Mini history bar/line chart |
| `entity_history` | History graph |
| `usersvg` | Image / SVG / GIF with state-based switching |
| `bar` | Horizontal/vertical bar chart from history data |
| `circslider` | Interactive circular (arc) slider |

## The Tools

### circle
```yaml
- type: circle
  position:
    cx: 50
    cy: 50
    radius: 28
  entity_index: 0
  show:
    style: 'colorstops'     # optional: fill circle color from colorstops
  colorstops:
    fill: true
    colors:
      0: 'var(--theme-sys-elevation-surface-neutral5)'
      10: '#A8E05F'
      500: '#fdae61'
      1000: '#F44336'
  styles:
    circle:
      fill: var(--primary-background-color)
      stroke: var(--divider-color)
      stroke-width: 1em
      opacity: 1
      transition: fill 0.5s
      # animations:
      # stroke-dasharray: 20, 5
      # animation: spin 10s linear infinite
```

### ellipse
```yaml
- type: ellipse
  position:
    cx: 50
    cy: 50
    radiusx: 40
    radiusy: 20
  styles:
    ellipse:
      fill: var(--card-background-color)
```

### line
Three orientation modes:

```yaml
# horizontal or vertical
- type: line
  position:
    orientation: 'horizontal'   # 'horizontal' | 'vertical'
    cx: 50
    cy: 50
    length: 16
  styles:
    line:
      stroke: var(--theme-gradient-color-01)
      stroke-width: 2
      opacity: 0.70
      transition: fill 0.5s

# fromto (absolute coordinates within toolset)
- type: line
  position:
    orientation: 'fromto'
    x1: 10
    y1: 50
    x2: -7
    y2: 68
  styles:
    line:
      stroke-width: 2
```

### rectangle
```yaml
- type: rectangle
  position:
    cx: 50
    cy: 50
    width: 60
    height: 30
    rx: 10   # horizontal border radius
    ry: 10   # vertical border radius (can differ from rx for asymmetric rounding)
  styles:
    rectangle:
      fill: var(--card-background-color)
      stroke: var(--green)
      stroke-width: 1em
      opacity: 0.8
```

### rectex (Rectangle Extended)
Supports individual corner radii, colorstops fill, and separate `foreground` / `background` layers.

```yaml
- type: rectex
  id: 5
  position:
    cx: 50
    cy: 50
    width: 50
    height: 17
    radius:
      all: 2           # shorthand for all 4 corners
      # top_left: 5
      # top_right: 5
      # bottom_left: 0
      # bottom_right: 0
  entity_index: 0
  show:
    style: 'colorstops'    # optional: fill from colorstops
  colorstops:
    fill: true
    colors:
      0: 'var(--theme-sys-elevation-surface-neutral5)'
      10: '#A8E05F'
      500: '#fdae61'
  styles:
    rectex:
      stroke: var(--theme-gradient-color-01)
      stroke-width: 0.5em
      opacity: 0.70
      transition: fill 0.5s
    foreground:             # foreground layer (progress fill)
      fill: var(--theme-gradient-color-01)
    background:             # background layer (track)
      fill: var(--theme-sys-elevation-surface-neutral5)
      filter: url(#is-1)    # optional: inner shadow filter
```

### polygon
```yaml
- type: polygon
  position:
    cx: 50
    cy: 50
    radius: 30
    side_count: 6     # 3=triangle, 4=square, 6=hexagon
    side_skew: 0      # rotation offset in degrees
  styles:
    polygon:
      fill: var(--primary-color)
```

### text
```yaml
- type: text
  position:
    cx: 50
    cy: 50
  text: 'Hello'
  styles:
    text:
      font-size: 12em
      fill: var(--primary-text-color)
      text-anchor: middle
```

### name (entity friendly name)
```yaml
- type: name
  position:
    cx: 50
    cy: 50
  entity_index: 0
  show:
    ellipsis: 16      # truncate at N characters with ellipsis (also works on state and text)
  styles:
    name:
      font-size: 7em
      font-weight: 400
      text-anchor: center
      opacity: 0.6
```

### state (entity state value)
```yaml
- type: state
  position:
    cx: 50
    cy: 50
  entity_index: 0
  show:
    uom: end      # 'end'    — inline after value (default)
    ellipsis: 9   # truncate state text at N characters (useful for string states)
                  # 'bottom' — UOM rendered below the value
                  # 'none'   — UOM hidden completely
                  # true     — show UOM
                  # false    — hide UOM
  styles:
    state:
      font-size: 6em
      font-weight: 500
      text-anchor: middle
      alignment-baseline: central
      opacity: 0.7
    uom:
      fill: var(--primary-text-color)
      font-weight: 700
```

### icon (entity icon or static icon)
```yaml
# Entity icon (from entity's icon attribute)
- type: icon
  position:
    cx: 50
    cy: 50
    align: center     # center | right | left
    icon_size: 25
  entity_index: 0
  styles:
    icon:
      fill: var(--theme-sys-color-tertiary)
      opacity: 0.8

# Static icon — specify directly in the tool (no entity needed)
- type: icon
  position:
    cx: 50
    cy: 8
    align: center
    icon_size: 13
  icon: mdi:flash     # ← static MDI icon, ignores entity
  styles:
    icon:
      fill: var(--yellow)
      opacity: 1
      paint-order: stroke
      stroke-width: 2em
```

### area (entity area / label)
Displays the entity `area:` override set in the entities list (or the HA area if not overridden).

```yaml
- type: area
  position:
    cx: 50
    cy: 45
  entity_index: 1
  styles:
    area:
      font-size: 3em
      font-weight: 400
      text-anchor: start
```

### segarc (Segmented Arc — gauge-style arc)
```yaml
- type: segarc
  position:
    cx: 50
    cy: 50
    start_angle: 0      # 0=right, 90=bottom, 180=left, 270=top (clockwise)
    end_angle: 360      # 360 = full circle
    width: 2            # arc stroke width
    radius: 30          # arc radius
  entity_index: 0
  scale:
    min: 0
    max: 3000
    width: 6
    offset: 12
  animation:
    duration: 5         # arc fill animation duration in seconds
  show:
    scale: false
    style: 'colorstops'   # 'colorstops' | 'colorlist' | 'single'
  segments:               # ← colorstops/colorlist go inside `segments:`
    colorstops:
      gap: 1
      colors:
        0: 'var(--theme-sys-elevation-surface-neutral5)'
        10: '#A8E05F'
        50: '#abdda4'
        150: '#e6f598'
        300: '#fee08b'
        700: '#fdae61'
        1000: '#f46d43'
        2000: '#d53e4f'
        3000: '#9e0142'
  styles:
    foreground:
      fill: rgba(255,255,255,0.12)
    background:
      fill: var(--theme-sys-elevation-surface-neutral5)
      filter: url(#is-1)
```

For `colorlist` style:
```yaml
  show:
    style: 'colorlist'
  segments:
    colorlist:
      gap: 1
      colors:
        - var(--theme-sys-palette-primary50)
        - '#FF5722'
```

### horseshoe
```yaml
- type: horseshoe
  position:
    cx: 50
    cy: 50
    radius: 45
  entity_index: 0
  scale:
    min: 0
    max: 100
    width: 2
    offset: 8
  show:
    horseshoe_style: 'colorstops'
  colorstops:
    fill: true
    colors:
      - value: 0
        color: '#03A9F4'
      - value: 68
        color: '#FF9800'
      - value: 85
        color: '#F44336'
  styles:
    horseshoe:
      stroke: '#03A9F4'
    scale:
      stroke: var(--secondary-text-color)
```

### slider
```yaml
- type: slider
  position:
    cx: 50
    cy: 75
    width: 70
    height: 15
    orientation: horizontal
  entity_index: 2
  scale:
    min: 0
    max: 255
  show:
    active: true
  user_actions:
    tap_action:
      haptic: success
      actions:
        - action: call-service
          service: light.turn_on
          service_data:
            entity_id: light.living_room
            brightness: '[[value]]'   # [[value]] = current slider value
  styles:
    track:
      fill: var(--divider-color)
    active:
      fill: var(--primary-color)
    thumb:
      fill: '#FFFFFF'
```

### switch
```yaml
- type: switch
  position:
    cx: 50
    cy: 50
    orientation: horizontal
    width: 20
    height: 10
  entity_index: 0
  user_actions:
    tap_action:
      haptic: success
      actions:
        - action: toggle
  styles:
    track:
      fill: var(--divider-color)
    thumb:
      fill: '#FFFFFF'
    track_on:
      fill: var(--primary-color)
```

### usersvg (image / SVG / GIF with state switching)
Displays a static or state-driven image. Supports PNG, SVG, GIF. Use `images:` list and `animations:` to switch image based on entity state.

```yaml
- type: usersvg
  disabled: false              # or '[[variable]]' — can disable individual tools
  entity_index: 0
  position:
    cx: 50
    cy: 50
    height: 45
    width: 45
  clip_path:
    position:
      cx: 50
      cy: 50
      height: 60
      width: 60
      radius:
        all: 60         # use high value to clip to circle; 0 = no rounding
  options:
    svginject: false    # false for PNG/GIF; true only for SVG files
  style: 'images'
  styles:
    usersvg:
      stroke-width: 1em
      fill: rgba(255,255,255,0.12)
      opacity: 0.6
    mask:
      fill: url(#sak-sparkline-area-mask-tb-0)   # SAK built-in top→bottom fade mask

  # ── Static image ───────────────────────────────────────────────────
  images:
    - default: '[[silviosmart_icona_off]]'    # fallback/static image

  # ── State-switched images ─────────────────────────────────────────
  images:
    - default: '[[silviosmart_icona_off]]'
    - imageoff: '[[silviosmart_icona_off]]'
    - imageon: '[[silviosmart_icona_on]]'
  animations:
    - state: '[[silviosmart_state_off]]'
      operator: <=
      image: imageoff
    - state: '[[silviosmart_state_on]]'
      operator: <=
      image: imageon

  # ── Dynamic image path via JS template ───────────────────────────
  # Use tool_config.variables for local per-tool variables, and
  # 'state' for the current entity state value
  variables:
    path: '[[sak_layout_tomorrow_pollen_image_path]]'   # can hold template vars
    prefix: 'pollen_tree_'
  images:
    - default: >
        [[[
          return "/local/images/"
            + tool_config.variables.path
            + tool_config.variables.prefix
            + state + ".svg";
        ]]]
```

**Built-in SAK mask IDs** (for `mask: fill: url(#...)`):
- `#sak-sparkline-area-mask-tb-0` — top-to-bottom fade (transparent at bottom)
- `#sak-mask-radial-gradient` — radial gradient fade from center

### bar (history bar chart)

Displays historical entity data as vertical or horizontal bars. Simpler than sparkline.

```yaml
- type: bar
  id: 2
  entity_index: 0
  position:
    orientation: vertical    # vertical | horizontal
    cx: 50
    cy: 77.5
    width: 85
    height: 30
    margin: 1
  hours: 24                  # total hours of history to fetch
  barhours: 1                # hours per bar
  show:
    style: 'minmaxgradient'  # 'minmaxgradient' | 'colorstops'
  minmaxgradient:
    colors:
      min: '#03A9F4'         # color at minimum value
      max: '#F44336'         # color at maximum value
  styles:
    bar:
      stroke-linecap: round
```

### circslider (circular arc slider)

Interactive circular slider for controlling numeric entities (brightness, temperature, volume).

```yaml
- type: circslider
  position:
    cx: 50
    cy: 50
    start_angle: 135
    end_angle: 45
    radius: 35
    width: 8
  entity_index: 0
  scale:
    min: 0
    max: 100
  styles:
    track:
      fill: var(--divider-color)
    active:
      fill: var(--primary-color)
    thumb:
      fill: '#FFFFFF'
```

### sparkline

The sparkline tool is the most complex in SAK. It renders mini charts (area, line, bar, graded, barcode) with rich configuration.

```yaml
- type: sparkline
  id: 2                        # optional ID (can be reused across toolsets)
  position:
    cx: 50
    cy: 50
    width: 270
    height: 50
    margin:                    # inner margins
      l: 0
      r: 0
      t: 2
      b: 2
      x: 0
      y: 0
  entity_index: 0
  entity_indexes:              # can use either or both
    - entity_index: 0

  # ── Period / data source ──────────────────────────────────────────
  period:
    # Option A: calendar-based window
    calendar:
      period: day              # day | week | month | year
      offset: 0                # 0=current, 1=previous, etc.
      duration:
        hour: 24               # how many hours of data
      bins:
        per_hour: 2            # how many data points per hour
    group_by: date
    rolling_window:            # alternative to calendar
      duration:
        hour: 24
      bins:
        per_hour: 1

    # Option B: real-time (latest value only)
    real_time: true

  # ── Chart configuration ───────────────────────────────────────────
  sparkline:
    animate: true

    show:
      chart_type: area         # area | line | bar | graded | barcode | equalizer
      chart_variant: rank_order  # rank_order | audio  (optional, changes rendering)
      fill: fade               # fade | solid (for area chart)

    state_values:
      aggregate_func: avg      # avg | last | min | max | sum | max
      lower_bound: -1          # clamp values below this to lower_bound
      upper_bound: 5           # clamp values above this to upper_bound

    # chart_type: area
    area:
      show_minmax: true        # show min/max envelope
      line_width: 1.5
      fill: fade

    # chart_type: graded (traffic-light style colored circles/squares)
    graded:
      square: true             # true = square cells, false = round
      line_width: 1.5

    # chart_type: barcode (dense vertical bars)
    barcode:
      line_width: 0.01
      column_spacing: 0.01
      row_spacing: 0.01

    # ── State map ────────────────────────────────────────────────────
    # Maps entity state values to display values before rendering
    state_map:
      template:
        name: sak_statemap_pollen    # reference a named SAK state map template

    # ── Colorstops ───────────────────────────────────────────────────
    colorstops_transition: hard    # hard = sharp edges | smooth = gradient

    # Colorstops can reference a named SAK template:
    colorstops:
      template:
        name: sak_colorstops_pollen_v2

    # Or defined inline:
    colorstops:
      colors:
        # Simple form (value → color):
        - value: 0
          color: '#49ce4c'
        - value: 60
          color: '#fb8600'
        - value: 100
          color: '#e63740'

        # Rank form (for rank_order variant — lower rank = better):
        - value: 0
          color: '#49ce4c'
          rank: 0              # best (green)
        - value: 60
          color: '#fb8600'
          rank: 1              # mid (orange)
        - value: 80
          color: '#e63740'
          rank: 2              # worst (red)

    # ── chart_type: radial_barcode ───────────────────────────────────
    show:
      chart_type: radial_barcode
      chart_variant: sunburst    # sunburst | other variants
      chart_viz: rice_grain      # rice_grain | other viz styles
    radial_barcode:
      size: 15
      column_spacing: 0.1
      line_width: 0.1
      face:
        show_day_night: false
        show_hour_marks: false
        show_hour_numbers: none    # none | absolute | relative
        hour_marks_radius: 23
        hour_marks_count: 12
        hour_numbers_radius: 18

    # ── X-axis lines ─────────────────────────────────────────────────
    x_lines:
      lines:
        - name: x_axis         # or 'ticks', 'center' — name used in styles
          zpos: below          # below | above the chart
          yshift: 27           # vertical offset in grid units
        - name: ticks
          zpos: below
          yshift: 28

    # ── Styles ───────────────────────────────────────────────────────
    styles:
      tool:
        opacity: 0.5
      x_axis:
        stroke: lightgray
        stroke-width: 0.2rem
      ticks:
        stroke: lightgray
        stroke-dasharray: 1, 118.5
        stroke-width: 1.5rem
      center:
        stroke: lightgray
        stroke-width: 0.1rem
        stroke-dasharray: 5 2.5
        stroke-dashoffset: 0.2
      graded_background:        # background circles/squares
        transition: fill 2s ease
        rx: 50%
      graded_foreground:        # foreground fill
        transition: fill 2s ease
        rx: 50%
      barcode_graph:
        opacity: 0.6
```

## User Actions (tap / hold / double_tap)

The correct structure uses `user_actions:` wrapping the action types:

```yaml
user_actions:
  tap_action:
    haptic: success       # none | success | warning | failure | light | medium | heavy | selection
    actions:
      - action: more-info

  tap_action:
    haptic: success
    actions:
      - action: toggle

  tap_action:
    haptic: success
    actions:
      - action: call-service
        service: light.turn_on
        service_data:
          entity_id: light.living_room
          brightness_pct: 80

  tap_action:
    haptic: success
    actions:
      - action: navigate
        navigation_path: /lovelace/lights

  # Browser Mod popup integration (basic)
  tap_action:
    haptic: success
    actions:
      - action: fire-dom-event
        browser_mod:
          service: browser_mod.popup
          data:
            '[[silviosmart_setting_browsermod]]'   # variable with popup config

  # Browser Mod popup with explicit size
  tap_action:
    haptic: success
    actions:
      - action: fire-dom-event
        browser_mod:
          service: browser_mod.popup
          width: 900px
          height: 80%
          data:
            '[[silviosmart_setting_browsermod]]'
```

## Template System

You can define **reusable layout templates** with default variable values:

### Defining a template

```yaml
silvio_layout_elettrodomestici:
  template:
    type: layout
    defaults:
      - my_icon_off: /local/png/device_off.png
      - my_icon_on: /local/png/device_on.gif
      - my_state_off: '5'
      - my_state_on: '3000'
      - my_setting_disable: false
      - my_popup_config: ''

  layout:
    aspectratio: 1/1
    toolsets:
      - toolset: main
        position:
          cx: 50
          cy: 50
        tools:
          - type: usersvg
            images:
              - default: '[[my_icon_off]]'
              - imageoff: '[[my_icon_off]]'
              - imageon: '[[my_icon_on]]'
```

### Using a template

```yaml
type: custom:swiss-army-knife-card
entities:
  - entity: sensor.power
layout:
  template:
    name: silvio_layout_elettrodomestici
    variables:
      - my_icon_off: /local/png/washer_off.png
      - my_icon_on: /local/png/washer_on.gif
      - my_state_off: '10'
      - my_state_on: '2000'
      - my_setting_disable: true
```

## Variable Substitution (`[[variable]]`)

`[[variable]]` substitutes values from `defaults:` / `variables:` into any YAML field:

```yaml
disabled: '[[my_setting_disable]]'
images:
  - default: '[[my_icon_off]]'
animations:
  - state: '[[my_state_off]]'
    operator: <=
    image: imageoff
```

## JavaScript Templates (`[[[...]]]`)

Triple brackets execute JavaScript. Available: `states`, `entity`, `user`, `hass`.

```yaml
styles:
  state:
    fill: >
      [[[
        if (entity.state > 30) return 'var(--error-color)';
        if (entity.state > 20) return 'var(--warning-color)';
        return 'var(--success-color)';
      ]]]
```

## Colorstops (map value → color)

Use in `circle`, `rectex` (via `show: style: 'colorstops'`), or `segarc` (via `segments: colorstops:`).

For `circle` and `rectex`:
```yaml
show:
  style: 'colorstops'
colorstops:
  fill: true
  colors:
    0: 'var(--theme-sys-elevation-surface-neutral5)'
    10: '#A8E05F'
    300: '#fee08b'
    1000: '#F44336'
```

For `segarc`:
```yaml
show:
  style: 'colorstops'
segments:
  colorstops:
    gap: 1
    colors:
      0: '#03A9F4'
      50: '#4CAF50'
      100: '#F44336'
```

YAML anchors can be used for **any value type** — maps, lists, scalars:

```yaml
# ── Anchor on a scalar (number/string) ──────────────────────────────
position:
  cy: &sensor_cy 220     # defines anchor 'sensor_cy' = 220

# reuse:
position:
  cy: *sensor_cy         # → 220

# ── Anchor on a map/object ───────────────────────────────────────────
position: &graph_pos
  cx: 50
  cy: 50
  width: 10
  height: 60

# reuse:
position: *graph_pos

# ── Anchor on a style sub-object ────────────────────────────────────
styles:
  name: &name_styling
    text-anchor: middle
    font-size: 10em
    font-weight: 500
    opacity: 0.6

# reuse in another tool:
styles:
  name: *name_styling

# ── Anchor on colorstop colors map ───────────────────────────────────
colorstops:
  fill: true
  colors: &my_colors
    0: '#A8E05F'
    500: '#fdae61'
    1000: '#F44336'

# reuse:
colorstops:
  fill: true
  colors: *my_colors

# ── Anchor on a styles block ─────────────────────────────────────────
styles: &trafficlight_styles
  graded_background:
    transition: fill 2s ease
    rx: 50%
  graded_foreground:
    transition: fill 2s ease
    rx: 50%

# reuse:
styles: *trafficlight_styles
```

Use anchors aggressively in complex templates to avoid repetition across many toolsets.

## CSS Style Notes

- Use `opacity:` to control the overall opacity of a tool
- Use `fill-opacity:` to control only the fill opacity (keeps stroke visible):
  ```yaml
  styles:
    rectex:
      fill: var(--green)
      fill-opacity: 0.4    # semi-transparent fill, stroke at full opacity
  ```
- `styles: tool:` is a SAK-level target (can be left empty as a placeholder):
  ```yaml
  styles:
    tool:        # SAK tool wrapper — can be empty
    state:
      font-size: 12em
  ```
- `stroke-width: 0em` effectively hides a stroke without removing it
- `paint-order: stroke` draws stroke behind fill (useful for icon outlines)
- `pointer-events: none` makes a tool non-interactive (click passes through to tools below)
- Toolsets support `scale:` and `rotate:` in their position to resize/spin the entire group

## CSS Animations (state-driven)

`animations:` can be placed on any tool. Each entry matches a state condition and overrides styles.

### Match exact state string
```yaml
animations:
  - state: 'on'
    styles:
      icon:
        filter: drop-shadow(0 0 6px var(--primary-color))
  - state: 'off'
    styles:
      icon:
        filter: none
        fill: var(--secondary-text-color)
```

### Match with operator (numeric comparisons)
Use `operator:` (`<=`, `>=`, `<`, `>`, `==`) to match numeric ranges. Rules are evaluated **in order** — first match wins:

```yaml
- type: circle
  entity_index: 0
  styles:
    circle:
      fill: var(--primary-background-color)
  animations:
    - state: '30'
      operator: <=
      styles:
        circle:
          fill: '#e63740'    # red — state ≤ 30
    - state: '60'
      operator: <=
      styles:
        circle:
          fill: '#fb8600'    # orange — state ≤ 60
    - state: '100'
      operator: <=
      styles:
        circle:
          fill: '#49ce4c'    # green — state ≤ 100
```

This works on **any tool** with `entity_index:`: `circle`, `rectex`, `rectangle`, `icon`, `text`, etc.

### Animation entry flags

Each animation entry supports additional flags:

```yaml
animations:
  - state: 'on'
    entity_index: 1    # override which entity this animation watches
    debug: false       # enable debug logging for this animation rule
    reuse: false       # if true, reuse the previous animation's config (no styles needed)
    styles:
      icon:
        fill: red
  - state: 'off'
    reuse: true        # inherits styles from previous matched animation
```

### JS template in state matching (quadruple brackets)

Use `[[[[...]]]]]` (4 brackets) for JavaScript in `disabled`, `animation`, and `state` matching fields:

```yaml
disabled: '[[[[ return !tool_config.variables.sak_layout_light_use_light_color; ]]]]'

animations:
  - state: >
      [[[ if (tool_config.variables.sak_layout_binary_icon_animation === "spin")
          return "spin 3s linear infinite";
          return "";
      ]]]
    styles:
      icon:
        animation: >
          [[[ return "spin 3s linear infinite"; ]]]
```

### Always-on CSS animation (spinning, blinking)
```yaml
styles:
  circle:
    animation: spin 10s linear infinite
    stroke-dasharray: 20, 5
```

### segarc: keep last color when full
```yaml
- type: segarc
  show:
    style: 'colorstops'
    lastcolor: true    # keep the color of the last filled segment at 100%
```

## Complete Example: Appliance Card (Power + Cost)

```yaml
type: custom:swiss-army-knife-card
entities:
  - entity: sensor.washer_power          # index 0 — current power (W)
    name: Washer
    icon: mdi:home-lightning-bolt
    decimals: 0
  - entity: sensor.washer_stats          # index 1 — last cycle cost
    area: Last Cost
    icon: mdi:currency-eur
    attribute: cost_last_cycle
    decimals: 2
  - entity: sensor.washer_stats          # index 2 — monthly cost
    area: Monthly Cost
    icon: mdi:power-plug
    attribute: cost_monthly
  - entity: sensor.washer_power          # index 3 — background icon only
    icon: mdi:washing-machine

layout:
  aspectratio: 1/1
  toolsets:
    - toolset: background-icon
      position:
        cx: 100
        cy: 50
      tools:
        - type: icon
          position:
            cx: 50
            cy: 50
            align: center
            icon_size: 70
          entity_index: 3
          styles:
            icon:
              fill: rgba(255,255,255,0.12)

    - toolset: power-circle
      position:
        cx: 32
        cy: 50
      tools:
        - type: circle
          entity_index: 0
          position:
            cx: 50
            cy: 50
            radius: 28
          show:
            style: 'colorstops'
          colorstops:
            fill: true
            colors: &energy_colors
              0: 'var(--theme-sys-elevation-surface-neutral5)'
              5: '#A8E05F'
              300: '#fee08b'
              700: '#fdae61'
              1500: '#d53e4f'
              3000: '#9e0142'
          styles:
            circle:
              opacity: 1
              transition: fill 0.5s

        - type: usersvg
          entity_index: 0
          position:
            cx: 50
            cy: 50
            height: 45
            width: 45
          clip_path:
            position:
              cx: 50
              cy: 50
              height: 60
              width: 60
              radius:
                all: 60
          options:
            svginject: false
          style: 'images'
          images:
            - default: /local/png/washer_off.png
            - imageoff: /local/png/washer_off.png
            - imageon: /local/png/washer_on.gif
          animations:
            - state: '5'
              operator: <=
              image: imageoff
            - state: '3000'
              operator: <=
              image: imageon

        - type: segarc
          position:
            cx: 50
            cy: 50
            start_angle: 0
            end_angle: 360
            width: 2
            radius: 30
          entity_index: 0
          scale:
            min: 0
            max: 3000
          show:
            scale: false
            style: 'colorstops'
          segments:
            colorstops:
              gap: 1
              colors:
                0: 'var(--theme-sys-elevation-surface-neutral5)'
                5: '#A8E05F'
                300: '#fee08b'
                700: '#fdae61'
                1500: '#d53e4f'
                3000: '#9e0142'
          styles:
            foreground:
              fill: rgba(255,255,255,0.12)
            background:
              fill: var(--theme-sys-elevation-surface-neutral5)

    - toolset: power-value
      position:
        cx: 75
        cy: 50
      tools:
        - type: rectex
          position:
            cx: 50
            cy: 50
            width: 40
            height: 17
            radius:
              all: 2
          entity_index: 0
          show:
            style: 'colorstops'
          colorstops:
            fill: true
            colors: *energy_colors
          styles:
            rectex:
              stroke: var(--theme-gradient-color-01)
              stroke-width: 0.5em
              opacity: 0.70
              transition: fill 0.5s
            foreground:
              fill: darkgrey
            background:
              fill: var(--theme-sys-elevation-surface-neutral4)
              opacity: 1

        - type: state
          position:
            cx: 53
            cy: 50
          entity_index: 0
          show:
            uom: end
          styles:
            state:
              text-anchor: middle
              font-size: 6em
              font-weight: 500
              opacity: 0.7
            uom:
              fill: var(--primary-text-color)
              font-weight: 700

        - type: icon
          position:
            cx: 41
            cy: 50
            align: right
            icon_size: 6
          entity_index: 0
          styles:
            icon:
              fill: var(--theme-sys-color-tertiary)
              opacity: 0.8
```

## SAK Template Types

SAK supports 6 template types beyond `layout`. These live in separate YAML files and are referenced by name.

### `type: colorstops` — reusable color gradient definitions

```yaml
sak_colorstops_awair_voc_v1:
  template:
    type: colorstops
    defaults:
      - thegap: 0
      - theFill: true
      - theStroke: false
  colorstops:
    gap: '[[thegap]]'
    fill: '[[theFill]]'
    stroke: '[[theStroke]]'
    colors:
      0: '#49ce4c'
      333: '#fdd125'
      1000: '#faaa00'
      3333: '#fb8600'
      8332: '#e63740'
```

Referenced in tools as:
```yaml
colorstops:
  template:
    name: sak_colorstops_awair_voc_v1
    variables:
      - thegap: 1
```

### `type: state_map` — map discrete state strings to indices

```yaml
sak_statemap_pollen:
  template:
    type: state_map
  variables:
    dummy: true
  state_map:
    map:
      - value: none
      - value: very_low
      - value: low
      - value: medium
      - value: high
      - value: very_high

sak_statemap_binary:
  template:
    type: state_map
  variables:
    dummy: true
  state_map:
    map:
      - value: 'off'
      - value: 'on'
```

### `type: derived_entity` — transform entity state via JavaScript

```yaml
sak_derived_entity_brightness:
  template:
    type: derived_entity
    defaults:
      - dummyvar: 'dummy'
  derived_entity:
    input: '[[[ return state ]]]'
    state: >
      [[[
        if (typeof(entity) === 'undefined') return;
        var bri = Math.round(state / 2.55);
        return (bri ? bri : '0');
      ]]]
    unit: >
      [[[
        if (typeof(state) === 'undefined') return undefined;
        return '%';
      ]]]
```

### `type: colorswatch` — define CSS color variables (light/dark modes)

```yaml
sak_color_swatch_google_brand_colors:
  template:
    type: colorswatch
  colorswatch:
    modes:
      light:
        brand-google-blue: 'blue'
        brand-google-red: 'red'
      dark:
        brand-google-blue: 'lightskyblue'
        brand-google-red: 'salmon'
```

### `type: toolset` — reusable tool groups

```yaml
sak_toolset_segarc_icon_state:
  template:
    type: toolset
    defaults:
      - entity: 0
      - show_scale: false
  toolset:
    toolset: segarc-icon-state
    position:
      cx: 50
      cy: 50
      scale: 1       # scale the entire toolset
      rotate: 0      # rotate the entire toolset in degrees
    tools:
      - type: segarc
        entity_index: '[[entity]]'
        # ...
```

## Derived Entity (Inline in Tools)

You can transform a tool's displayed value inline without a separate template:

```yaml
- type: state
  id: 7
  position:
    cx: 95
    cy: 95
  entity_index: 0
  derived_entity:
    state: >
      [[[
        if (typeof(entity) === 'undefined') return;
        if ('brightness' in entity.attributes) {
          var bri = Math.round(entity.attributes.brightness / 2.55);
          return (bri ? bri : '0');
        }
      ]]]
    unit: '%'
```

## Built-in SAK SVG Filter IDs

Reference these in `filter: url(#...)` within styles:

| Filter ID | Effect |
|-----------|--------|
| `#sak-nm-default` | Neumorphic (embossed) drop shadow |
| `#sak-inset-1` | Inset shadow (recessed look) |
| `#is-1` | Inner shadow variant |
| `#filter` | Default card filter |
| `#sak-sparkline-area-mask-tb-0` | Top-to-bottom fade mask (for usersvg) |
| `#sak-mask-radial-gradient` | Radial gradient fade mask (for usersvg) |

```yaml
styles:
  circle:
    filter: url(#sak-nm-default)    # neumorphic effect
  background:
    filter: url(#is-1)              # inset shadow (common on rectex/segarc background)
```

## SAK Theme / Palette CSS Variables

SAK exposes its own theme CSS variables (populated by a SAK-compatible theme):

```css
/* Semantic colors */
var(--theme-sys-color-primary)
var(--theme-sys-color-secondary)
var(--theme-sys-color-tertiary)

/* Palette tones (0 = black, 100 = white; 50 = mid) */
var(--theme-sys-palette-primary50)
var(--theme-sys-palette-primary45)
var(--theme-sys-palette-secondary50)
var(--theme-sys-palette-tertiary45)
var(--theme-sys-palette-tertiary50)

/* Elevation / surface neutrals */
var(--theme-sys-elevation-surface-neutral4)
var(--theme-sys-elevation-surface-neutral5)
var(--theme-sys-elevation-surface-neutral9)

/* Gradient */
var(--theme-gradient-color-01)

/* Generic custom vars (from user's theme) */
var(--silvio-cerchio-bk)
var(--green)
var(--blue)
var(--yellow)
```

Use standard HA variables (`var(--primary-color)`, `var(--primary-text-color)`, etc.) if the user does not have a SAK-specific theme installed.

## Common Patterns

### Ring indicator (segarc surrounding a value)

Place a `segarc` at the same `cx`/`cy` as a `state` tool to draw a circular progress ring around the displayed value:

```yaml
- toolset: battery-column
  position:
    cx: 450
    cy: 50
  tools:
    - type: icon
      position:
        cx: 30
        cy: 50
        align: right
        icon_size: 25
      entity_index: 3
      styles:
        icon:
          fill: var(--theme-sys-color-tertiary)
          opacity: 0.8

    - type: state                   # ← value at cx:70, cy:50
      position:
        cx: 70
        cy: 50
      entity_index: 3
      show:
        uom: none
      styles:
        state:
          text-anchor: middle
          font-size: 15em
          font-weight: 700

    - type: segarc                  # ← ring centered on cx:70, cy:50 (same as state)
      position:
        cx: 70
        cy: 50
        sta

…(truncated)
