Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e0c6599
feat: initial animations based on current weather conditions
troinine Dec 12, 2025
a958a07
refactor: address rain performance issue and add intensity calculation
troinine Dec 13, 2025
3927714
feat: added rain angle based on wind bearing and speed
troinine Dec 13, 2025
4ae3882
fix: pass the correct forecast to animations
troinine Dec 13, 2025
05a4f93
refactor: improved snow effects
troinine Dec 13, 2025
f752bb2
refactor: use stylemap
troinine Dec 14, 2025
1fc7306
feat: render moon at night and when the condition is clear-night
troinine Dec 14, 2025
ae972a5
fix: fixed ResizeObserver lifecycle
troinine Dec 14, 2025
cc6f197
test: update demo app and test
troinine Dec 14, 2025
4d0146d
refactor: rename config property
troinine Dec 14, 2025
cc18059
docs: document condition effects
troinine Dec 14, 2025
7ca7592
docs: polish
troinine Dec 14, 2025
1592f96
refactor: allow external styling of effects. Make sure effects are be…
troinine Dec 15, 2025
ab3fec4
Merge branch 'main' into feature/weather-condition-animations
troinine Dec 18, 2025
ba63c05
fix: use correct card style in test app
troinine Dec 18, 2025
27586bd
Merge branch 'main' into feature/weather-condition-animations
troinine Dec 20, 2025
dfaa7eb
refactor: improved snow colors. support for theme detection
troinine Dec 20, 2025
0828d67
refactor: better stars
troinine Dec 21, 2025
e3df2af
refactor: improved night sky
troinine Dec 21, 2025
cf27007
refactor: improved night sky on light theme
troinine Dec 21, 2025
6e6ab2c
refactor: improved sunny weather effects
troinine Dec 22, 2025
4ed217e
fix: improved star distribution
troinine Dec 22, 2025
661ce25
fix: changed snow drift algorithm to make the path more natural
troinine Dec 22, 2025
a4c3bec
fix: snowflakes were too big
troinine Dec 22, 2025
981c6a5
refactor: polish demo app
troinine Dec 22, 2025
c4b81f9
refactor: animation logic selection redefined
troinine Dec 22, 2025
a2c45ed
test: added tests for condition effects
troinine Dec 22, 2025
a9bb174
chore: demo app improvements
troinine Dec 22, 2025
2a2b4eb
feat: added editor support for condition effects
troinine Dec 22, 2025
83ed52f
docs: updated weather effect docs
troinine Dec 22, 2025
36182e0
fix: linting issue
troinine Dec 22, 2025
7dc2ea3
fix: review fixes
troinine Dec 22, 2025
9b63ca2
refactor: avoid unnecessary re-calculations of effect particles
troinine Dec 23, 2025
d98f72a
refactor: polish
troinine Dec 23, 2025
ffd5d12
fix: review fix for spacing
troinine Dec 23, 2025
22ba308
docs: review fix
troinine Dec 23, 2025
d057be3
fix: cache sun rays
troinine Dec 23, 2025
2022ed2
fix: review fix
troinine Dec 23, 2025
b8c9857
test: added tests for snowy-rainy animations
troinine Dec 23, 2025
dd58e26
fix: review fix and improving spread of snow and rain
troinine Dec 23, 2025
db2e66d
fix: review fix
troinine Dec 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 70 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ This card takes inspiration from [Weather Forecast Extended Card](https://github
- **Custom icons** – Use your own weather icons
- **Customizable actions** – Configure tap, hold, and double-tap behaviors
- **Sun times** - Visualize sunrise and sunset
- **Condition effects** - Add a special touch with animated weather effects
- **Card editor** - Configure the card directly in the UI. No YAML required

## Installation
Expand Down Expand Up @@ -68,21 +69,22 @@ resources:

## Configuration

| Name | Type | Default | Description |
| :------------------- | :------ | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | **Required** | `custom:weather-forecast-card` |
| `entity` | string | **Required** | The weather entity id (e.g., `weather.home`). |
| `name` | string | optional | Custom name to display. Defaults to the entity's friendly name. |
| `temperature_entity` | string | optional | Bring your own temperature entity to override the temperature from the main weather `entity`. |
| `show_current` | boolean | `true` | Show current weather conditions. |
| `show_forecast` | boolean | `true` | Show forecast section. |
| `default_forecast` | string | `daily` | Default forecast to view (`daily` or `hourly`). |
| `icons_path` | string | optional | Path to custom icons. For example, `/local/img/my-custom-weather-icons`. See [Custom Weather Icons](#custom-weather-icons) for more details. |
| `forecast` | object | optional | Forecast configuration options. See [Forecast Object](#forecast-object). |
| `forecast_action` | object | optional | Actions for the forecast area. See [Forecast Actions](#forecast-actions). |
| `tap_action` | object | optional | Defines the type of action to perform on tap for the main card. Action defaults to `more-info`. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |
| `hold_action` | object | optional | Defines the type of action to perform on hold for the main card. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |
| `double_tap_action` | object | optional | Defines the type of action to perform on double click for the main card. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |
| Name | Type | Default | Description |
| :----------------------- | :---------------- | :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | `string` | **Required** | `custom:weather-forecast-card` |
| `entity` | `string` | **Required** | The weather entity id (e.g., `weather.home`). |
| `name` | `string` | optional | Custom name to display. Defaults to the entity's friendly name. |
| `temperature_entity` | `string` | optional | Bring your own temperature entity to override the temperature from the main weather `entity`. |
| `show_current` | `boolean` | `true` | Show current weather conditions. |
| `show_forecast` | `boolean` | `true` | Show forecast section. |
| `default_forecast` | `string` | `daily` | Default forecast to view (`daily` or `hourly`). |
| `icons_path` | `string` | optional | Path to custom icons. For example, `/local/img/my-custom-weather-icons`. See [Custom Weather Icons](#custom-weather-icons) for more details. |
| `show_condition_effects` | `boolean`/`array` | optional | Enable animated weather condition effects. Set to `true` for all conditions or provide an array of specific effects. See [Weather Condition Effects](#weather-condition-effects). |
| `forecast` | `object` | optional | Forecast configuration options. See [Forecast Object](#forecast-object). |
| `forecast_action` | `object` | optional | Actions for the forecast area. See [Forecast Actions](#forecast-actions). |
| `tap_action` | `object` | optional | Defines the type of action to perform on tap for the main card. Action defaults to `more-info`. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |
| `hold_action` | `object` | optional | Defines the type of action to perform on hold for the main card. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |
| `double_tap_action` | `object` | optional | Defines the type of action to perform on double click for the main card. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |

### Forecast Object

Expand All @@ -100,11 +102,11 @@ Actions support standard Home Assistant card actions. However, one additional ac

Forecast actions have the following options

| Name | Type | Default action | Description |
| :------------------ | :----- | :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
| `tap_action` | object | `toggle-forecast` | Defines the type action to perform on tap. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |
| `hold_action` | object | `none` | Defines the type of action to perform on hold. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |
| `double_tap_action` | object | `none` | Defines the type of action to perform on double click. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |
| Name | Type | Default action | Description |
| :------------------ | :------- | :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
| `tap_action` | `object` | `toggle-forecast` | Defines the type action to perform on tap. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |
| `hold_action` | `object` | `none` | Defines the type of action to perform on hold. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |
| `double_tap_action` | `object` | `none` | Defines the type of action to perform on double click. See [Home Assistant Actions](https://www.home-assistant.io/dashboards/actions/). |

## Example

Expand All @@ -116,6 +118,54 @@ forecast:
extra_attribute: wind_direction
```

## Weather Condition Effects

The card supports animated visual effects that respond to current weather conditions, adding an immersive layer to your weather display. These effects are rendered as background animations behind the card content.

### Configuration

Enable weather effects using the `show_condition_effects` option:

**Enable all effects:**

```yaml
type: custom:weather-forecast-card
entity: weather.home
show_condition_effects: true
```

**Enable specific effects only:**

```yaml
type: custom:weather-forecast-card
entity: weather.home
show_condition_effects:
- rain
- snow
- lightning
```

**Disable all effects (default):**

```yaml
type: custom:weather-forecast-card
entity: weather.home
show_condition_effects: false
```

### Available Effects

The card provides six different effect types that can be individually enabled or disabled:

| Effect Type | Description | Weather Conditions Applied |
| :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------- |
| **`rain`** | Animated raindrops falling with realistic wind drift. Droplet angle and speed adapt to wind conditions from weather data. Includes splash effects on landing. | `rainy`, `pouring`, `lightning-rainy` |
| **`snow`** | Snowflakes falling with smooth sinusoidal drift patterns. Each flake follows a unique non-linear trajectory with varying sizes, opacity, and depths for realistic parallax effects. | `snowy`, `snowy-rainy` |
| **`lightning`** | Dramatic lightning flash sequences with multiple strikes and residual flickers, creating an authentic storm atmosphere. | `lightning`, `lightning-rainy` |
| **`sky`** | Visually pleasing gradient sky background that adapts to time of day if sun times are enabled. | `sunny`, `clear-night` |
| **`sun`** | Animated sun with rotating rays positioned at the top of the card. Creates a warm, dynamic daytime atmosphere. Automatically switches to moon effect after sunset if sun times are shown. | `sunny` |
| **`moon`** | Crescent moon with animated twinkling stars scattered across the card. Stars have randomized positions, sizes, and twinkle animations for a serene nighttime atmosphere. | `clear-night`, `sunny` (with sun times) |

## Custom Icons

You can use your own custom weather icons by specifying the `icons_path` configuration option.
Expand Down
136 changes: 117 additions & 19 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,30 @@
<style>
:root {
--primary-text-color: #fff;
--secondary-text-color: #5e5e5e;
--secondary-text-color: #929292;
--ha-font-size-3xl: 28px;
--ha-font-size-2xl: 24px;
--ha-font-size-xl: 20px;
--ha-font-size-l: 16px;
--ha-font-size-m: 14px;
--ha-line-height-condensed: 1.2;
--ha-card-border-radius: 20px;
--ha-card-background: linear-gradient(
180deg,
rgba(0, 0, 0, 0.1) 20%,
rgba(0, 0, 0, 0.01) 100%
);
font-family: "Roboto", "Noto", sans-serif;
color: var(--primary-text-color);
--card-padding: 24px;
}
.light-theme {
--primary-text-color: #141414;
--secondary-text-color: #5e5e5e;
--ha-card-background: #fff;
color: var(--primary-text-color);
}

body {
margin: 0;
padding: 0;
Expand All @@ -28,29 +40,23 @@
font-weight: 700;
margin: 0 0 8px 0;
letter-spacing: -0.5px;
color: #fff;
}
h3 {
color: var(--secondary-text-color);
color: #cecece;
font-size: 20px;
font-weight: 400;
margin: 0 0 24px 0;
text-align: center;
}
.tagline {
font-size: 16px;
color: var(--secondary-text-color);
margin: 0 0 16px 0;
max-width: 320px;
line-height: 1.5;
}
.features {
display: flex;
flex-direction: column;
gap: 8px;
}
.feature {
font-size: 14px;
color: var(--secondary-text-color);
color: #cecece;
display: flex;
gap: 8px;
text-align: left;
Expand Down Expand Up @@ -166,14 +172,32 @@
box-shadow: 0px 0px 10px 5px rgba(0, 0, 0, 0.1);
border-radius: 20px;
}
.theme-toggle {
position: absolute;
top: 24px;
right: 24px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 8px 16px;
border-radius: 20px;
color: white;
cursor: pointer;
font-family: inherit;
font-size: 14px;
display: flex;
align-items: center;
gap: 8px;
z-index: 1001;
transition: all 0.2s ease;
}
.theme-toggle:hover {
background: rgba(255, 255, 255, 0.2);
}
.demo-card {
display: block;
width: 550px;
background: linear-gradient(
180deg,
rgba(0, 0, 0, 0.1) 20%,
rgba(0, 0, 0, 0.01) 100%
);
background: var(--ha-card-background);
backdrop-filter: blur(20px) saturate(110%) brightness(90%);
border-radius: 20px;
border-width: 1px;
Expand All @@ -189,6 +213,22 @@
</head>
<body>
<div class="view">
<button class="theme-toggle" id="theme-toggle">
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="5" />
<path
d="M12 1v2m0 18v2M4.22 4.22l1.42 1.42m12.72 12.72l1.42 1.42M1 12h2m18 0h2M4.22 19.78l1.42-1.42M17.66 6.34l1.42-1.42"
/>
</svg>
<span>Toggle Theme</span>
</button>
<div class="dashboard">
<div class="intro">
<h1>Weather Forecast Card</h1>
Expand Down Expand Up @@ -287,7 +327,31 @@ <h3>For Home Assistant</h3>
import { MockHass } from "../test/mocks/hass.ts";

const mockHass = new MockHass();
const hass = mockHass.getHass();
let theme = "dark";
mockHass.darkMode = theme === "dark";
let hass = mockHass.getHass();
const demoMode = "toggle_and_scroll"; // 'condition_effects' | 'toggle_and_scroll' | 'none'

const updateTheme = () => {
const card = document.querySelector(".demo-card");
if (theme === "light") {
card.classList.add("light-theme");
document.body.classList.add("light-theme");
} else {
card.classList.remove("light-theme");
document.body.classList.remove("light-theme");
}
mockHass.darkMode = theme === "dark";
hass = mockHass.getHass();
card.hass = hass;
};

updateTheme();

document.getElementById("theme-toggle").addEventListener("click", () => {
theme = theme === "dark" ? "light" : "dark";
updateTheme();
});

customElements.whenDefined("weather-forecast-card").then(() => {
const card = document.querySelector(".demo-card");
Expand All @@ -296,16 +360,44 @@ <h3>For Home Assistant</h3>
type: "custom:weather-forecast-card",
entity: "weather.demo",
name: "Home",
show_condition_effects: demoMode === "condition_effects",
forecast: {
mode: "chart",
extra_attribute: "wind_direction",
hourly_group_size: 2,
show_sun_times: demoMode !== "condition_effects",
},
});

card.hass = hass;

// Auto-demo: tap, scroll, scroll back, tap sequence
setTimeout(() => {
const rotateConditionsDemo = () => {
const conditions = [
"sunny",
"snowy",
"rainy",
"lightning-rainy",
"clear-night",
];
let index = 0;

setInterval(() => {
card.hass = {
...hass,
states: {
...hass.states,
"weather.demo": {
...hass.states["weather.demo"],
state: conditions[index],
},
},
};

index = (index + 1) % conditions.length;
}, 20000);
};

const toggleAndScrollDemo = () => {
const forecastContainer = card.shadowRoot?.querySelector(
".wfc-forecast-container"
);
Expand Down Expand Up @@ -398,7 +490,13 @@ <h3>For Home Assistant</h3>
"Could not find scroll container or forecast container"
);
}
}, 2000);
};

if (demoMode === "toggle_and_scroll") {
setTimeout(toggleAndScrollDemo, 2000);
} else if (demoMode === "condition_effects") {
setTimeout(rotateConditionsDemo, 2000);
}
});
</script>
</body>
Expand Down
Loading