Render the season. Then, if it turns up, render the weather.
The whole layer is one rule expressed twice: the seasonal default is computed at build time from the server clock and is always correct, and every live value hydrates in afterwards without ever being allowed to block the first paint.
The degradation rule, in four steps.
-
Build time: the season is stamped into the HTML
dse_season() reads the server month and returns spring, summer, autumn or winter. That value is written onto the root element as data-season and onto the body as a season- class before a byte leaves the server.
The CSS for all four states ships in the same stylesheet. Switching state is one attribute redefining two custom properties, which costs nothing and cannot fail.
No request, no fetch, no flash of the wrong colour.
-
First paint: the page is already complete
At this point the palette is right, the illustration set is right, the ambient line is the seasonal default, and every placeholder occupies exactly the box its live value will occupy.
If the visitor closes the tab here they have seen a correct page. That is the bar.
-
After first paint: live values hydrate in place
Weather, crop stage and yield are fetched after the page is interactive and replace their placeholders in the same boxes. There is no layout shift because there is no new content, only better content in an existing slot.
Each has a cache in front of it. Fifteen minutes for the weather, an hour for the yield, twenty-four hours for the crop stage.
-
On failure: nothing happens, visibly
If a feed times out, errors, or returns nonsense, the placeholder simply stays. No spinner, no error state, no retry loop, and above all no stale reading presented as current.
If the weather API is down the site looks like the season it is. Most people would not notice, and the ones who did would be looking at a page that was still entirely usable.
The failure mode of this layer is: it looks slightly less clever.
Three implementation decisions that make it work.
The season is server-side, everything else is not
Putting the season in JavaScript would mean a flash of the wrong palette on every cold load, on every page, forever. Putting it server-side costs one function call and removes the problem entirely.
Everything else is deliberately client-side, because everything else is optional.
- Season
- Server-side, always
- Live values
- Client-side, optional
Placeholders are the same shape as the real thing
The ambient line has a seasonal default of the same length as a weather line. The ticker has five lines whether or not the weighbridge is talking. The crop-stage illustration has a seasonal default frame.
That is why hydration causes no reflow: nothing is being inserted, only replaced.
- Layout shift
- None
- Spinners
- None
One function owns all five inputs
Every live number on this site is produced by a single PHP function that currently returns deterministic seeded values. Nothing else in the theme reads a feed.
Replacing the placeholders with the Met Office, the weighbridge and the ops entry means rewriting that one function. That is the entire integration surface.
- Integration points
- One
- Files that read a feed
- One
Caching, in full.
Time to live
- Date and season
- no TTLSystem clock, server-side
- Weather at OX7
- 15 minMet Office DataPoint or equivalent
- Crop growth stage
- 24 hFarm ops entry
- Live yield
- 1 hWeighbridge, or an ops entry when the bridge is down
- Series window
- no TTLManual flag
Why those numbers
- Weather, 15 minutes
- Nobody needs to know it started raining ninety seconds ago
- Yield, 1 hour
- The weighbridge produces a number every few loads, not continuously
- Crop stage, 24 hours
- A growth stage that changed in an hour would be a data error
- Season, no TTL
- It is the clock
- Series window, no TTL
- It is a flag a person sets, and it changes four times a year
Failure behaviour
- Timeout
- Placeholder stays. No retry storm
- Error response
- Placeholder stays. Logged, not surfaced
- Stale cache
- Served, and only until the TTL expires
- Expired and unreachable
- Falls back to the seasonal default, not to the stale value
The last row is the one that matters. An expired reading is worse than no reading, because a wrong temperature presented confidently is a lie and an absent one is merely quiet.
What each input changes, and what happens without it.
| Input | What it changes | TTL | Without it |
|---|---|---|---|
| Date and season | Field palette and illustration set | no TTL | Cannot happen |
| Weather at OX7 | Sky treatment and ambient copy | 15 min | Seasonal sky, seasonal line |
| Crop growth stage | Beast and crop illustration stage, Plotting status | 24 h | Seasonal default stage |
| Live yield | The ambient ticker | 1 h | That ticker line is omitted |
| Series window | Second-screen mode | no TTL | Defaults to closed |
The one rule, stated once more
Live data must never block first paint. The seasonal default renders at build time and is always correct; live values hydrate afterwards or they do not. If the weather API is down, the site simply looks like the season it is.
Build time
The season, stamped server-side.
Runtime
Weather, crop stage and yield, hydrated after paint.
Failure
Placeholder stays. No spinner, no error, no stale value.
One function
The entire integration surface.
The two palettes it moves between.
One follows the season. The other deliberately does not.