{"id":50096871,"url":"https://github.com/michailjovic/room-card","last_synced_at":"2026-07-16T15:01:40.500Z","repository":{"id":359676943,"uuid":"1247023693","full_name":"Michailjovic/Room-Card","owner":"Michailjovic","description":"Card to manage picture of a room for home assistant dashboard","archived":false,"fork":false,"pushed_at":"2026-07-15T18:07:20.000Z","size":12392,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-07-15T20:08:29.464Z","etag":null,"topics":["custom-card","dashboard","floorplan","hacs","home-assistant","lovelace"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Michailjovic.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-22T20:20:47.000Z","updated_at":"2026-07-15T18:07:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Michailjovic/Room-Card","commit_stats":null,"previous_names":["michailjovic/room-card"],"tags_count":141,"template":false,"template_full_name":null,"purl":"pkg:github/Michailjovic/Room-Card","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Michailjovic%2FRoom-Card","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Michailjovic%2FRoom-Card/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Michailjovic%2FRoom-Card/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Michailjovic%2FRoom-Card/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Michailjovic","download_url":"https://codeload.github.com/Michailjovic/Room-Card/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Michailjovic%2FRoom-Card/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35547971,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-16T02:00:06.687Z","response_time":83,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["custom-card","dashboard","floorplan","hacs","home-assistant","lovelace"],"created_at":"2026-05-23T04:02:43.283Z","updated_at":"2026-07-16T15:01:40.491Z","avatar_url":"https://github.com/Michailjovic.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Room Overlay Card\n\n[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/hacs/integration)\n[![GitHub release](https://img.shields.io/github/release/Michailjovic/Room-Card.svg)](https://github.com/Michailjovic/Room-Card/releases/latest)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\nA Home Assistant Lovelace card for **room visualization**. Take a photo of your room and bring it to life: dim it with the lights, place clickable controls on the furniture, show live sensor values, animate the blinds, and embed any other HA card on top of it.\n\n**One card adapts to every screen** — you design two layout profiles (portrait / landscape) on a % grid of the viewport and every device picks the right one by its shape. Everything is configurable from a full tabbed GUI editor; you can build a whole card by dragging elements onto the image, no YAML required.\n\n![Room Overlay Card – hero screenshot](screenshots/hero.png)\n\n---\n\n## Features at a glance\n\n| Feature | What it does |\n|---|---|\n| **Layout profiles** | Two GUI-built % grid layouts (portrait / landscape) picked by the viewport's shape; per-device pinning via browser_mod |\n| **Base image** | Any room photo, per-profile design aspect, cover/contain fit, configurable corner radius |\n| **CSS filter engine** | Brightness, saturation, sepia, blur… driven by entity states with smooth transitions |\n| **Brightness model** | Multi-stop filter interpolation: define stops (day / night / cinema…) and blend automatically |\n| **Overlay layers** | Transparent PNG layers with conditional opacity/filter; state-driven image switching |\n| **Gauges** | Animated progress bars in 6 fill directions, color gradients, per-gauge visibility |\n| **Blinds** | Roller, venetian slat, and day/night (zebra) blind animations driven by cover entities |\n| **Clickable zones** | Invisible hit areas — navigate, more-info, toggle, call-service, browser-mod popup |\n| **Slider zones** | Drag across a zone to dim lights, move covers, set volume/temperature |\n| **Status badges** | Floating chips in any corner — MDI icon, conditional color, conditional label |\n| **Icons \u0026 labels** | State-aware MDI icons and entity/template text values placed anywhere |\n| **Embedded HA cards** | Any card (tile, mini-graph, button…) placed at absolute coordinates |\n| **Companion cards** | Full HA cards stacked above / below the image (great for mobile) |\n| **Camera \u0026 weather** | Live camera snapshot as the base layer; animated rain/snow overlay |\n| **Multi-room** | One card for the whole home — define rooms, swipe \u0026 presence-follow |\n| **Auto navigation menu** | The room switcher (thumbnails / tabs / dots) is generated automatically from your rooms — you never build the menu by hand |\n| **Hold feedback** | A progress ring fills and turns green when a hold gesture registers |\n| **Tabbed GUI editor** | Build everything visually — drag, resize, reorder — without writing YAML |\n\n---\n\n## Screenshots\n\n| | |\n|---|---|\n| ![Responsive — one card across devices](screenshots/responsive.png) | ![Tabbed GUI editor](screenshots/editor.png) |\n| *One card, every screen — the same card at different widths* | *Tabbed GUI editor — build it without YAML* |\n| ![Day scene](screenshots/day.png) | ![Night scene](screenshots/night.png) |\n| *Day scene — full brightness* | *Night mode — dim filter active* |\n| ![Gauges](screenshots/gauges.png) | ![Blinds](screenshots/blinds.png) |\n| *Gauges — temperature, humidity, CO₂* | *Day/night zebra blind at 60 %* |\n| ![Test mode](screenshots/testmode.png) | |\n| *Test mode — click to select, drag to position* | |\n\n\u003e **Screenshots to capture** for v2.0.0 (drop the files into `screenshots/`):\n\u003e `hero.png` (finished card, shown at the top), `responsive.png` (the same card at two widths side by side — the headline feature), `editor.png` (the tabbed editor), `day.png` / `night.png` (same room, brightness filter off/on), `gauges.png`, `blinds.png`, `testmode.png` (test mode with one element selected). Until a file exists, GitHub shows a broken-image icon for that slot.\n\n---\n\n## Installation\n\n### Via HACS (recommended)\n\n1. Open **HACS → Frontend → ⋮ → Custom repositories**\n2. Add `https://github.com/Michailjovic/Room-Card` — type **Lovelace**\n3. Search for **Room Overlay Card** → Install\n4. Hard-refresh your browser (`Ctrl+Shift+R`)\n\n### Manual\n\n1. Download `room-overlay-card.js` from the [latest release](https://github.com/Michailjovic/Room-Card/releases/latest)\n2. Copy to `/config/www/room-overlay-card.js`\n3. **Settings → Dashboards → ⋮ → Manage resources** → add `/local/room-overlay-card.js` (type: JavaScript module)\n4. Hard-refresh\n\n---\n\n## Quick start (no YAML)\n\n1. **Add the card** to a dashboard — *Add card → Custom: Room Overlay Card*.\n2. The editor opens on a single step: **set a background image** (a room photo or floor-plan, e.g. `/local/bedroom.webp`). The rest of the editor appears once it's set.\n3. Turn on **Drag-edit preview** in the header. Now drag elements straight onto the image.\n4. In the **Elements** tab, add an icon, label, zone or embedded card and drop it on the right spot.\n5. Save. That's it — you never had to touch YAML.\n\nThe smallest possible card in YAML:\n\n```yaml\ntype: custom:room-overlay-card\nbase_image: /local/images/bedroom.webp\naspect_ratio: \"16/9\"\n```\n\n---\n\n## The editor\n\nThe GUI editor is organized into four tabs, with a persistent header on top.\n\n**Header (always visible):** the **room picker** (when the card is multi-room — switches which room the Image and Elements tabs edit), **Test mode**, and **Drag-edit preview**.\n\n- **Image** — the background (image or camera), image-swap conditions, weather overlay, CSS filters, the brightness model, filter transition and zoom. Also the **companion cards** (above/below the image).\n- **Elements** — everything you place on the image: zones, icons, labels, badges, gauges, blinds, embedded cards, overlays, and groups. Each type is a collapsible section with a count.\n- **Layout** — height source, orientation, threshold and both profile grids (see the next section).\n- **Rooms \u0026 menu** — the room list (add / remove / reorder), presence-follow, and the navigation strip.\n\n**Drag-edit preview** is a live, editable copy of the card shown inside the editor. You can drag and resize elements right there, and it shows the room picked in the header. (The preview panel Home Assistant shows on the right is its own — it follows live presence and won't track the room picker.)\n\n**Test mode** (`test_mode: true`, or the header toggle) overlays editing affordances on the card: red outlines on zones, blue dashed outlines on embedded cards, a live **viewport + active-profile badge**, region outlines with names, and a **profile switch button**. **Click an element to select it** — only the selected element shows resize handles, so the card stays readable even with many overlapping elements. Drag to move (snaps to a 0.5 % grid, magnetic alignment guides, hold **Alt** for free movement), drag a handle to resize, or nudge the selection with the **arrow keys** (Shift = 0.1 %). Drag on an empty area to **draw a new zone**. The editor also has **undo/redo** (↶ ↷ or Ctrl+Z / Ctrl+Y).\n\n---\n\n## Layout — two profiles on a % grid (v4)\n\nv4 replaces the old 4-tier width system with **two layout profiles** — **`portrait`** and **`landscape`** — chosen by the **shape of the available viewport** (width/height ratio), not by device type. Rotate a tablet and the card switches profile automatically; pin a specific device if you never want it to switch.\n\nEach profile is a **CSS grid in % of the available screen**. Every block of the card is a **region** you place on that grid: `nav`, `cards_above`, `image`, `lights`, `cards_below`, `cover` (the blind controller in dock mode). You own the percentages — rows should sum to ≤ 100.\n\n```yaml\nlayout:\n  height: viewport      # viewport (default, full view minus HA header) | container | 90vh / 800px\n  threshold: 1.0        # w/h ratio below which the card is portrait\n  # orientation: landscape          # optional: force one profile\n  # orientation:                    # …or pin per device (browser_mod ID)\n  #   by_browser: { kitchen_tablet: landscape }\n  #   default: auto\n\n  landscape:\n    columns: [88, 12]\n    rows: [10, 10, 70, 5, 5]\n    place:\n      nav:         { row: 1 }\n      cards_above: { row: 2 }\n      image:       { row: 3 }\n      lights:      { row: 4 }\n      cards_below: { row: 5 }\n      cover:       { row: 1/6, col: 2 }   # blind controller docked in the right column\n\n  portrait:\n    columns: [100]\n    rows: [8, 8, 55, 6, 6, 17]\n    place:\n      nav:         { row: 1 }\n      cards_above: { row: 2 }\n      image:       { row: 3 }\n      lights:      { row: 4 }\n      cards_below: { row: 5 }\n      cover:       { row: 6 }             # …and at the bottom on portrait\n```\n\nRules and notes:\n\n- A region **not listed** in a profile's `place:` is **hidden** in that profile.\n- `row`/`col` take a grid line number (`3`) or a span (`\"1/6\"` = rows 1–5). Per-region options: `overflow: hidden|auto` (default hidden), `align`.\n- Tracks accept `%`, `auto` (size to content), `1fr` (take the remainder) and any CSS length — mix freely, e.g. `rows: [75px, auto, 1fr, auto]`. Rows always **pack from the top**; leftover height stays at the bottom.\n- Put the **image on an `auto` row** and its box sizes itself from the image's aspect — exact fit with no crop and no letterbox (ideal when you have controls at the image edges). Use a fixed `%` row + `image_fit` when you want to dictate the height instead.\n- The card is designed for **panel-view / full-screen** dashboards. `height: viewport` measures the real available height (HA header, view padding and safe-areas accounted for). Use `container` or a fixed value when embedding.\n- The **image region** gets a fixed box from the grid; the image renders inside it at its design aspect with `image_fit: cover` (crop, default) or `contain` (letterbox). **Element `%` positions stay glued to the image** — the lock_aspect stage from v3 does this everywhere now.\n- **Test mode** shows region outlines with names, a live viewport + profile badge, and a **profile switch button** to preview the other profile.\n- Everything is editable in the GUI **Layout tab**: height source, orientation/threshold, per-device pin, both profile grids and all region placements.\n\n### Per-element profile overrides\n\nEvery element accepts `portrait:` / `landscape:` blocks merging over the base:\n\n```yaml\nlabels:\n  - id: temp\n    top: 10%\n    left: 80%\n    font_size: 2%\n    portrait: { top: 6%, left: 70%, font_size: 4% }\n```\n\n`aspect_ratio`, `border_radius` and `image_fit` accept a single value or `{portrait, landscape}`.\n\n### Migrating from v3\n\nOld configs (tiers, `breakpoints`, `max_height`, `media: mobile|desktop`, per-element `mobile:`/`tablet:`/`desktop:`/`ultrawide:` blocks) are **auto-migrated in memory** when loaded: `mobile` → `portrait`, `desktop` → `landscape`, `tablet`/`ultrawide` dropped, and a starter `layout:` block is generated mirroring the old stacked look. Open the editor and press **Save migrated config** to persist it, then tune the percentages. Details in [LAYOUT.md](LAYOUT.md).\n\n---\n\n## Configuration reference\n\n### Top-level\n\n| Key | Type | Default | Description |\n|---|---|---|---|\n| `base_image` | string | **required**¹ | Path to the room photo |\n| `base_camera` | string | — | Camera entity used as live background (¹makes `base_image` optional) |\n| `camera_refresh` | number | `10` | Camera snapshot refresh interval in seconds |\n| `base_image_conditions` | list | — | Swap the base image by entity state |\n| `weather_overlay` | object/string | — | Animated rain/snow layer (`entity`, `effect`, `opacity`, `z_index`) |\n| `aspect_ratio` | string / object | `16/9` | Design aspect of the image — single `width/height` or `{portrait, landscape}` |\n| `border_radius` | string / object | `12px` | Card corner radius — single value or `{portrait, landscape}` |\n| `layout` | object | generated | v4 layout engine — height source, orientation, threshold and the two profile grids (see the Layout section) |\n| `image_fit` | string / object | `cover` | `cover` (crop) or `contain` (letterbox) — single value or `{portrait, landscape}` |\n| `filter_transition` | string | `2s ease` | CSS transition for the base image filter |\n| `filter_conditions` | list | `[]` | Discrete CSS filter states |\n| `brightness_model` | object | — | Multi-stop filter interpolation |\n| `overlays` | list | `[]` | Overlay image layers |\n| `gauges` | list | `[]` | Progress gauge bars (linear or radial) |\n| `blinds` | list | `[]` | Window blind visualizations |\n| `zones` | list | `[]` | Clickable hit areas / sliders |\n| `badges` | list | `[]` | Corner status chips |\n| `icons` | list | `[]` | Icon overlays with actions |\n| `labels` | list | `[]` | Value/template text labels |\n| `elements` | list | `[]` | Embedded HA cards |\n| `cards_above` / `cards_below` | list | `[]` | Companion HA cards stacked above/below the image |\n| `groups` | list | `[]` | Client-side element groups (toggle/show/hide) |\n| `rooms` | list | — | Multi-room definitions (see Multi-room) |\n| `nav` | object | — | Multi-room navigation strip |\n| `test_mode` | bool | `false` | Outlines, click-to-select, resize handles, Save button |\n| `tap_action` | action | — | Action on card background click |\n| `hold_feedback` | bool | `true` | Show the hold-gesture progress ring |\n| `hold_color` | string | — | Color of the in-progress hold ring |\n| `zoom` | bool | `false` | Pinch-zoom + pan (floorplan mode) |\n| `parallax` | bool/object | — | Subtle tilt on pointer/device orientation |\n| `haptic` | bool | `true` | Haptic feedback on actions (companion app) |\n\n---\n\n### Conditions\n\nUsed throughout the config to drive visual state.\n\n```yaml\n# Simple state match\nentity: binary_sensor.window\nstate: \"on\"\n\n# Negation\nentity: light.bedroom\nstate_not: \"unavailable\"\n\n# Numeric comparison  (operators: \u003c \u003e \u003c= \u003e= == !=)\nentity: sensor.temperature\noperator: \"\u003e\"\nvalue: 25\n\n# AND / OR chaining\nentity: sensor.temperature\noperator: \"\u003e\"\nvalue: 22\nand:\n  entity: binary_sensor.night_mode\n  state: \"off\"\n```\n\n---\n\n### CSS filter engine\n\nApply conditional CSS filters to the base image.\n\n```yaml\nfilter_conditions:\n  - condition:\n      entity: binary_sensor.night_mode\n      state: \"on\"\n    filter: brightness(0.3) saturate(0.2) sepia(0.4)\n  - filter: brightness(1.0)    # default — no condition\n```\n\n---\n\n### Brightness model\n\nDefine named filter stops and let the card interpolate smoothly between them based on a sensor value — continuous ambient-light simulation.\n\n```yaml\nbrightness_model:\n  source:\n    - entity: sensor.living_room_lux\n      min_input: 0\n      max_input: 1000\n  filter_gradient:\n    - { value: 0,   filter: \"brightness(0.3) saturate(0.5) sepia(0.3)\" }\n    - { value: 50,  filter: \"brightness(1.0)\" }\n    - { value: 100, filter: \"brightness(1.2) saturate(1.1)\" }\n```\n\nThe source value is normalized to 0–100 % and the matching `filter:` CSS string is interpolated across the stops. When set, the brightness model replaces `filter_conditions`. Use any CSS filter functions (`brightness`, `contrast`, `saturate`, `sepia`, `hue-rotate`, `blur`, `opacity`, `grayscale`, `invert`).\n\n---\n\n### Overlays\n\nTransparent PNG layers stacked over the base image.\n\n```yaml\noverlays:\n  # Conditional opacity\n  - id: ceiling_light\n    image: /local/images/bedroom_light_on.png\n    transition: \"1.5s ease\"\n    conditions:\n      opacity:\n        - condition: { entity: light.bedroom_ceiling, state: \"on\" }\n          value: 1\n        - value: 0      # default\n\n  # State-driven image switching\n  - id: fan_visual\n    state_images:\n      - { entity: fan.bedroom, state: \"on\", image: /local/images/fan_on.png }\n      - { image: /local/images/fan_off.png }    # default\n    conditions:\n      opacity:\n        - value: 1\n\n  # Tint a glow PNG from a light's live color\n  - id: rgb_strip\n    image: /local/strip_glow.png\n    color_from: light.tv_strip   # follows rgb_color / color_temp\n```\n\n---\n\n### Gauges\n\nAnimated progress bars positioned anywhere on the card.\n\n```yaml\ngauges:\n  - id: temperature_bar\n    entity: sensor.bedroom_temperature\n    top: \"5%\"\n    left: \"2%\"\n    width: \"6%\"\n    height: \"40%\"\n    min: 15\n    max: 35\n    orientation: vertical      # see table below\n    background: \"rgba(0,0,0,0.4)\"\n    border_radius: \"4px\"\n    color_gradient:\n      - { value: 15, color: \"#2196F3\" }   # blue — cold\n      - { value: 22, color: \"#4CAF50\" }   # green — comfortable\n      - { value: 30, color: \"#FF5722\" }   # red — hot\n    visible_conditions: { entity: binary_sensor.show_gauges, state: \"on\" }\n```\n\n#### `orientation` values\n\n| Value | Fill direction |\n|---|---|\n| `vertical` | bottom → top (default) |\n| `top` | top → bottom |\n| `horizontal` / `left` | left → right |\n| `right` | right → left |\n| `radial` | circular SVG arc gauge |\n\n#### Radial gauges\n\n```yaml\ngauges:\n  - id: humidity_ring\n    entity: sensor.bedroom_humidity\n    orientation: radial\n    top: \"8%\"\n    left: \"80%\"\n    width: \"14%\"\n    height: \"24%\"\n    min: 0\n    max: 100\n    arc: 270          # arc length in degrees (default 270)\n    thickness: 10     # stroke width in viewBox units\n    target: 55        # optional target tick mark\n    color_gradient:\n      - { value: 30, color: \"#FF9800\" }\n      - { value: 50, color: \"#4CAF50\" }\n      - { value: 70, color: \"#2196F3\" }\n```\n\nDiscrete color based on state (instead of a smooth gradient):\n\n```yaml\ncolor:\n  - condition: { entity: sensor.co2, operator: \"\u003e\", value: 1000 }\n    value: \"rgba(255,50,50,0.9)\"\n  - value: \"rgba(100,200,100,0.8)\"\n```\n\n---\n\n### Blinds\n\nVisualize window covers directly on the room image.\n\n```yaml\nblinds:\n  - id: bedroom_blind\n    entity: cover.bedroom_blind\n    attribute: current_position   # omit to use entity state (open/closed)\n    min: 0      # entity value = fully open\n    max: 100    # entity value = fully closed\n    top: \"10%\"\n    left: \"30%\"\n    width: \"25%\"\n    height: \"50%\"\n    z_index: 6\n    blind_type: day_night         # roller | venetian | day_night\n    slat_color: \"rgba(0,0,0,0.85)\"\n    slat_count: 8\n```\n\n- **`roller`** — solid fill that grows from the top as the blind closes.\n- **`venetian`** — horizontal slats with gaps (`slat_width`, `slat_gap`, `gap_color`).\n- **`day_night`** — zebra / dual-layer blind; band pattern cycles `slat_count` times across the travel.\n\n\u003e **Inverted motor direction** — if your cover reports `0` = closed and `100` = open, swap: `min: 100`, `max: 0`.\n\n#### Cover control (roleta)\n\nAdd a `control:` block to a blind to turn its visualization into a slim, **icon-only** controller — a draggable position rail, up / stop / down buttons, and quick-jump **presets**. The controller is **hidden until you tap the window** (the blind graphic); only one shows at a time.\n\n```yaml\nblinds:\n  - id: bedroom_roller\n    entity: cover.roller_motor_bedroom\n    top: \"6%\"\n    left: \"8%\"\n    width: \"22%\"\n    height: \"30%\"\n    blind_type: roller\n    control:\n      placement: float        # float (place freely via top/left) | dock (edge rail)\n      top: \"12%\"              # float position — sized to the window height\n      left: \"34%\"\n      dock_side: right        # left | right   (dock only)\n      slider: true            # draggable position rail (auto-hidden for assumed-state covers)\n      presets:\n        - {position: 100, icon: mdi:blinds-open,         color: amber,     name: Open}\n        - {position: 65,  icon: mdi:blinds,              color: orange,    name: Day}\n        - {position: 2,   icon: mdi:roller-shade,        color: blue-grey, name: Peek}\n        - {position: 0,   icon: mdi:roller-shade-closed, color: indigo,    name: Closed}\n```\n\n- **`placement`** — `float` (place freely with `top` / `left`; the controller is sized to the window height) or `dock` (a slim rail pinned to the image edge, `dock_side: left | right`, filling the full height).\n- **`float`** (default): tap the blind to reveal / hide the controller next to it (horizontal bottom bar on portrait). **`dock`**: the controller is permanently visible in the `cover` layout region — its side and size come from where you place that region in each profile. `placement` accepts `{portrait, landscape}` (e.g. dock on landscape, float on portrait).\n- **`slider`** — show the draggable position rail (`cover.set_cover_position`). Auto-hidden when the cover reports no `current_position` (assumed-state).\n- **Up / Stop / Down** are always shown — a tap does a full `open_cover` / `close_cover`; `Stop` (`stop_cover`) highlights while the cover is moving.\n- **`presets`** — one-tap jumps to a position. Each takes `position` (0–100) and optional `icon` (use real MDI names, e.g. `mdi:roller-shade`, `mdi:blinds` — see [materialdesignicons.com](https://pictogrammers.com/library/mdi/)), `color` (HA name like `indigo` / `amber` / `blue-grey`, or any CSS colour) and `name` (shown as a tooltip). Presets render as **icons only**.\n\n---\n\n### Clickable zones\n\nInvisible hit areas over any part of the card.\n\n```yaml\nzones:\n  - id: light_zone\n    top: \"55%\"\n    left: \"8%\"\n    width: \"20%\"\n    height: \"18%\"\n    tap_action: { action: toggle, entity: light.bedroom }\n```\n\n#### Slider zones\n\nDrag across a zone to control an entity. Domains: `light` (brightness), `cover` (position), `fan` (speed), `media_player` (volume), `climate` (temperature), `number` / `input_number`.\n\n```yaml\nzones:\n  - id: dimmer\n    top: \"20%\"\n    left: \"60%\"\n    width: \"12%\"\n    height: \"45%\"\n    slider:\n      entity: light.bedroom_ceiling\n      direction: vertical    # vertical | horizontal\n      live: false            # true = send while dragging (throttled)\n      color: \"rgba(255,255,255,0.28)\"\n    tap_action: { action: toggle, entity: light.bedroom_ceiling }\n```\n\n#### Action types\n\n| Action | Required params | Description |\n|---|---|---|\n| `navigate` | `path` | Navigate to a dashboard path |\n| `url` | `url_path` | Open a URL (`new_tab: false` for same tab) |\n| `more-info` | `entity` | Open entity more-info dialog |\n| `toggle` | `entity` | Toggle entity on/off |\n| `call-service` / `perform-action` | `service` / `perform_action` | Call any HA action; supports `data:` and `target:` |\n| `browser-mod-popup` | `title`, `size`, `content` | Open a browser-mod popup |\n| `toggle-group` / `show-group` / `hide-group` | `group` | Control element groups |\n| `switch-room` / `next-room` / `prev-room` / `follow-room` | — / `room` | Multi-room navigation |\n| `none` | — | Do nothing |\n\nAny action may carry `confirmation: true` (or `confirmation: {text: \"...\"}`). A `hold_action` shows a **progress ring** while you press, which turns green the moment the hold registers.\n\n```yaml\ntap_action:\n  action: perform-action\n  perform_action: climate.set_temperature\n  target: { entity_id: climate.bedroom }\n  data: { temperature: 21.5 }\n  confirmation: { text: Set bedroom to 21.5 °C? }\n```\n\n---\n\n### Status badges\n\nFloating chips anchored to card corners.\n\n```yaml\nbadges:\n  - id: temp_chip\n    position: bottom-right    # top-left | top-right | bottom-left | bottom-right\n    icon: mdi:thermometer\n    icon_color:\n      - condition: { entity: sensor.temperature, operator: \"\u003e\", value: 26 }\n        value: \"orange\"\n      - value: \"white\"\n    label:\n      - condition: { entity: sensor.temperature, operator: \"\u003e\", value: 26 }\n        value: \"Hot!\"\n      - value: \"{{ states('sensor.temperature') | round(1) }} °C\"\n    visible: { entity: binary_sensor.someone_home, state: \"on\" }\n    tap_action: { action: more-info, entity: sensor.temperature }\n```\n\n---\n\n### Icons \u0026 labels\n\nState-aware MDI icons and text values placed anywhere on the image.\n\n```yaml\nicons:\n  - id: lamp\n    icon: mdi:lamp\n    top: \"30%\"\n    left: \"62%\"\n    size: \"22px\"            # % of card width also works (responsive)\n    color:\n      - condition: { entity: light.lamp, state: \"on\" }\n        value: \"#FFD54F\"\n      - value: \"#888\"\n    tap_action: { action: toggle, entity: light.lamp }\n\nlabels:\n  # Entity value with automatic unit\n  - id: temp_label\n    entity: sensor.bedroom_temperature\n    top: \"12%\"\n    left: \"10%\"\n    decimals: 1\n    suffix: auto           # appends the entity's unit_of_measurement\n    font_size: \"2.2%\"      # % of card width — responsive\n    color_gradient:\n      - { value: 18, color: \"#2196F3\" }\n      - { value: 26, color: \"#FF5722\" }\n\n  # Jinja template (rendered live via WebSocket)\n  - id: summary\n    template: \u003e-\n      {{ states('sensor.bedroom_temperature') | round(1) }} °C ·\n      {{ states('sensor.bedroom_humidity') | round(0) }} %\n    top: \"5%\"\n    left: \"10%\"\n\n  # Relative time\n  - id: last_motion\n    entity: binary_sensor.bedroom_motion\n    attribute: last_changed_ts\n    format: relative         # \"5 minutes ago\", localized, refreshes every 30 s\n    prefix: \"Motion: \"\n```\n\n---\n\n### Embedded HA cards\n\nPlace any Lovelace card at absolute coordinates over the room image.\n\n```yaml\nelements:\n  - id: temp_graph\n    top: \"3%\"\n    left: \"65%\"\n    width: \"32%\"\n    height: \"22%\"\n    z_index: 4\n    border_radius: \"8px\"\n    overflow: hidden\n    visible: { entity: binary_sensor.show_graph, state: \"on\" }\n    card:\n      type: custom:mini-graph-card\n      entities: [sensor.bedroom_temperature]\n      hours_to_show: 6\n```\n\n### Companion cards (above / below the image)\n\nStack full HA cards above or below the room image — handy on mobile where positioned overlays get cramped. Per room. Each entry is a card config, or `{card, height, media}`.\n\n```yaml\ncards_below:\n  - type: entities\n    entities: [light.bedroom_ceiling, fan.bedroom]\n  - card: { type: thermostat, entity: climate.bedroom }\n    height: 180px\n    media: portrait        # all | portrait | landscape (legacy mobile/desktop still map)\n```\n\nIn the GUI these live in the **Image** tab as *Cards above image* / *Cards below image* — paste a card config you built elsewhere.\n\n---\n\n### Light controls (sliders with lux ring)\n\nA GUI-configurable strip of [`material-slider-card`](https://github.com/PRProd/lovelace-slider-button-card) sliders rendered above the image, one per light. Each slider's **border colour tracks a lux sensor**: a smooth gradient interpolated in HSL between two colours you pick — dark for low lux, bright for high lux. This replaces hand-written `card_mod` + Jinja templates; the ring colour is computed in JS and pushed through the card's own CSS variables (no `card_mod` dependency for the ring). Per room.\n\n```yaml\nlight_controls:\n  entities:\n    - light.panel_bedroom_1\n    - entity: light.panel_bedroom_2\n      name: Middle            # optional per-slider name\n    - light.panel_bedroom_3\n  columns: 3                  # grid columns (default: number of lights)\n  height: 20                  # px number, or \"4vh\" / \"5%\" of screen height, or {portrait, landscape}\n  lux_sensor: sensor.kitchen_illuminance\n  lux_max: 50                 # lux value that maps to the \"bright\" colour\n  color_low: \"#261a66\"        # border colour at 0 lux (dark)\n  color_high: \"#f4c025\"       # border colour at/above lux_max (bright)\n  bg_off: \"#000000\"           # slider background while the light is off\n```\n\n`color_low` / `color_high` accept any CSS colour (`#hex`, `rgb(...)`, `hsl(...)`); the gradient is interpolated in HSL so a blue→amber ramp travels through vivid hues rather than a muddy RGB midpoint.\n\n`height` accepts a plain number (px), a **viewport unit** (`4vh`, or `5%` of the screen height) or a **per-profile** object (e.g. `{portrait: 20, landscape: 60}`) — a fixed px looks tiny on desktop, so a `vh` value scales across screens (resolved to px at render). When a light is **on**, `material-slider-card`'s `colorize` takes over the fill with the light's real colour and brightness, so `bg_off` is only visible when the light is off.\n\nRequires the `material-slider-card` resource to be installed. In the GUI these live in the **Elements** tab under *Light controls*.\n\n---\n\n### Camera background \u0026 weather effects\n\n```yaml\ntype: custom:room-overlay-card\nbase_camera: camera.living_room      # base_image becomes optional\ncamera_refresh: 5                    # seconds (paused off-screen)\nweather_overlay:\n  entity: weather.home               # auto: rainy→rain, pouring→heavy rain,\n                                     # snowy→snow, fog→fog, lightning-rainy→rain+flashes\n  # effect: rain                     # manual: rain | rain-heavy | snow | snow-heavy | fog | lightning\n  angle: 115deg                      # optional — tilt rain (wind)\n  opacity: 0.45\n```\n\n---\n\n### Template visibility\n\nAny zone, icon, badge, overlay, embedded card, gauge or blind can be driven by a Jinja2 template (rendered live over WebSocket). `visible_template` takes precedence over `visible` / `visible_conditions`:\n\n```yaml\nzones:\n  - id: tv_zone\n    visible_template: \"{{ is_state('media_player.tv', 'on') and now().hour \u003e= 18 }}\"\n\nbadges:\n  - id: power_chip\n    icon: mdi:flash\n    label_template: \"{{ states('sensor.power') | round(0) }} W\"\n```\n\n---\n\n### Groups (pop-up control panels)\n\nBundle several elements under a named group and show/hide them together — e.g. a control panel that appears when you tap an icon. Each element gets `group: \u003cname\u003e`; the `groups:` section defines the panel's background/position and default visibility; actions (`toggle-group` / `show-group` / `hide-group`) flip it.\n\n```yaml\nicons:\n  - id: open_panel\n    icon: mdi:tune\n    top: 80%\n    left: 90%\n    tap_action: { action: toggle-group, group: controls }\n\ngroups:\n  - id: controls\n    visible: false\n    style: { top: 60%, left: 60%, width: 38%, height: 35%, background: \"rgba(0,0,0,0.6)\", border_radius: 12px }\n\nelements:\n  - id: light_tile\n    group: controls\n    top: 62%\n    left: 62%\n    width: 34%\n    height: 14%\n    card: { type: tile, entity: light.bedroom }\n```\n\n---\n\n## Multi-room (one card = whole home)\n\nDefine your rooms and the card **builds the navigation menu for you automatically** — you don't lay out a single button. From the `rooms:` list it generates a switcher in the style you pick (`thumbnails`, `tabs`, `dots`, or `none`): the thumbnails are live, filtered copies of each room (so a dimmed room looks dimmed in the menu too), and you can drop `{room}` chips on them to show each room's temperature, humidity, etc. Position it top, bottom, a side rail on ultrawide, or let it choose with `position: auto`.\n\n```yaml\ntype: custom:room-overlay-card\naspect_ratio: \"16/9\"            # card-level keys are shared by all rooms\ncard_id: flat_main              # pairing key (editor/save matching)\nroom_entity:                    # string, or per-device mapping:\n  default: sensor.phone_alice_area       # Bermuda area sensor / input_select\n  by_user:                      # logged-in HA user (case-insensitive)\n    Alice: sensor.phone_alice_area\n  by_browser:                   # browser_mod browser ID (wall tablets)\n    wall_tablet_living: sensor.phone_alice_area\nfollow_hold: 60                 # s — manual navigation outranks presence\nfollow_mode: always             # always | initial (only on load) | manual (button only)\nroom_state_entity: input_text.active_room   # card mirrors the active room here\nurl_sync: true                  # opt-in: keep the active room in the URL → #room=\u003cid\u003e\nnav:\n  style: thumbnails             # thumbnails | tabs | dots | none\n  position: auto                # top | bottom | left | right | auto\n  auto_breakpoint: 1100         # auto: side rail above this card width (ultrawide)\n  height: 64px\n  live: composite               # thumbnails become MINI-ROOMS: base + active overlays + filters\n  chips:                        # {room} → room id; per-room `chips:` overrides\n    - { entity: sensor.{room}_temperature, decimals: 1, suffix: \"°\" }\nrooms:\n  - id: livingroom\n    name: Obývák\n    icon: mdi:sofa              # only shown when nav style = tabs\n    base_image: /local/livingroom.webp\n    area_match: [Living room]   # states of room_entity mapping here\n    zones:\n      - id: door_bedroom        # a door as a portal to another room\n        top: \"30%\"\n        left: \"70%\"\n        width: \"10%\"\n        height: \"40%\"\n        tap_action: { action: switch-room, room: bedroom }\n  - id: bedroom\n    name: Bedroom\n    base_image: /local/bedroom.webp\n    area_match: [Bedroom, Ložnice]\n```\n\nWith **`nav.live: composite`** every thumbnail becomes a **live mini-room**: it stacks the room's currently *active* overlay images (lit lamps, open windows, …) over its base image, applies the room's conditional base image and its `filter_conditions` / `brightness_model` filter — so the menu really is a scaled-down copy of each room's current look, updating live. It's pure CSS background compositing (no extra card instances or subscriptions), with two approximations: an overlay shows whenever its opacity resolves above 0, and grouped (pop-up panel) or `visible_template`-driven overlays are skipped.\n\nSwitching works several ways: nav thumbnails/tabs, the **follow button** (crosshair that lights up when you're away from your presence room; `{action: follow-room}`), **finger-attached swipe** (the room follows your finger; release past 25 % or fling to commit), **mouse-wheel** on desktop (`nav.wheel: horizontal | vertical | both`), the `switch-room` / `next-room` / `prev-room` actions, and automatic **presence follow** via `room_entity`. With **`url_sync: true`** the active room is written to the page URL as `#room=\u003cid\u003e` (set `url_sync: \u003ckey\u003e` for a custom hash key), so rooms become **bookmarkable and shareable** — opening a `#room=bedroom` link jumps straight there, and browser back/forward navigates rooms. Updates, templates and camera refresh run only for the active room. Top-level room-scoped keys act as defaults for every room. Without `rooms:` the card behaves as a single room; the editor has a one-click **Convert to multi-room** button, and you can **reorder rooms** with the ▲▼ buttons in the *Rooms \u0026 menu* tab.\n\n---\n\n## Complete example\n\n```yaml\ntype: custom:room-overlay-card\nbase_image: /local/images/bedroom.webp\naspect_ratio: { mobile: 4/3, desktop: 16/9, ultrawide: 21/9 }\nmax_height: { desktop: 70vh, ultrawide: 80vh }\nborder_radius: \"12px\"\n\nfilter_conditions:\n  - condition: { entity: binary_sensor.night_mode, state: \"on\" }\n    filter: brightness(0.25) saturate(0.1) sepia(0.3)\n  - filter: brightness(1.0)\n\noverlays:\n  - id: ceiling_light\n    image: /local/images/bedroom_ceiling_on.png\n    transition: \"1.5s ease\"\n    conditions:\n      opacity:\n        - condition: { entity: light.bedroom_ceiling, state: \"on\" }\n          value: 1\n        - value: 0\n\ngauges:\n  - id: temp_gauge\n    entity: sensor.bedroom_temperature\n    top: \"5%\"\n    left: \"2%\"\n    width: \"5%\"\n    height: \"35%\"\n    min: 16\n    max: 30\n    color_gradient:\n      - { value: 16, color: \"#2196F3\" }\n      - { value: 22, color: \"#4CAF50\" }\n      - { value: 30, color: \"#FF5722\" }\n\nzones:\n  - id: ceiling_switch\n    top: \"55%\"\n    left: \"8%\"\n    width: \"18%\"\n    height: \"20%\"\n    tap_action: { action: toggle, entity: light.bedroom_ceiling }\n    hold_action: { action: more-info, entity: light.bedroom_ceiling }\n\nbadges:\n  - id: humidity\n    position: bottom-left\n    icon: mdi:water-percent\n    label:\n      - value: \"{{ states('sensor.bedroom_humidity') | round(0) }} %\"\n    tap_action: { action: more-info, entity: sensor.bedroom_humidity }\n```\n\nSee **[PRESETS.md](PRESETS.md)** for a gallery of copy-paste recipes (day/night filters, weather moods, dimmer zones, door portals, Bermuda presence multiroom…).\n\n---\n\n## Positioning tips\n\nAll `top`, `left`, `width`, `height` values are percentage strings relative to the card. Enable **Test mode** to position by eye: click an element to select it, drag to move (snaps to 0.5 %, Alt = free), drag handles to resize, arrow keys to nudge, or draw a new zone on an empty area. The live viewport/profile badge shows which layout profile is active.\n\n---\n\n## Development\n\n`room-overlay-card.js` is the single source of truth — hand-maintained vanilla JS, no build step, zero external runtime dependencies. Edit the file directly and hard-refresh your browser. A small smoke-test suite lives in `tests/` (`node tests/smoke.test.js`).\n\n---\n\n## License\n\nMIT © 2025–2026 Michailjovic\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichailjovic%2Froom-card","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichailjovic%2Froom-card","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichailjovic%2Froom-card/lists"}