Contents
NipponDailyHow it is built

Chapter 6

Seasons

The year clock. A season sets the site's colours and shapes through a data-season attribute on <html>. The browser works the season out from the date in Japan and checks again at every midnight, so nothing about it is stored or fetched.

The four seasons

SeasonId is a closed union in types/index.ts, and shared/seasons.ts lists the ids and presets. A season may be added only once its CSS preset exists in app/assets/css/tailwind.css and its id is in both.

SeasonIdMonths
🌸 Spring (sakura)sakura (default)Mar–May
🎐 Summer (natsu)summerJun–Aug
🍁 Autumn (koyo)autumnSep–Nov
❄️ Winter (fuyu)winterDec–Feb

How a season reaches the page

In the browserdaily checkwinsDate(Japan, JST)A seasonseasonForDate()Inline scriptapplies it before paintReader's picklocalStorageuseSiteTheme()checks every midnightThe site's lookdata-season on <html>
The date in Japan gives the season; the reader's own pick wins over it
  • The calendar. seasonForDate() in shared/seasons.ts maps the month in Japan (UTC+9, no daylight saving) to a season. It is a pure function of the date, which is why no server, cron or datastore is involved: the same answer is worked out anywhere, and it cannot go stale.
  • Before first paint. An inline script in nuxt.config.ts (built by seasonBootScript()) sets data-season on <html>: the reader's season-choice if there is one, else the season for this month in Japan, read from a table of the months embedded in the script. A first visit therefore never flashes the default palette, and blocked storage cannot stop it. The season is applied in the browser, never in the HTML, which is why a page cached for everyone is safe.
  • The daily check. Once the page is up, useSiteTheme() (started from app.vue) applies the season again, then checks at the next midnight in Japan and at every midnight after, and whenever the tab becomes visible again. A page left open past the turn of a month therefore changes season without a reload, and a hidden tab, whose timers sleep, catches up the moment it returns. The check does nothing while the reader has picked a season.
  • Season button. SeasonButton.vue lets a reader pick any season or “Follow the calendar”. The pick lives only in localStorage (season-choice).
  • Music. BgmControl.vue and useBgm.ts play a looping track for each season, all encoded to the same integrated loudness (−16 LUFS, measured on the MP3s) so a season change never changes the level. It is off on every load; only the volume (bgm-volume) is remembered. Looping is gapless through a decoded audio buffer and a GainNode (iOS ignores element.volume); a browser without Web Audio falls back to a plain looping audio element, with no crossfade. When the season changes mid-song the old track keeps playing until the new one has loaded, then the two crossfade over 2 seconds. The music pauses while the tab is hidden.

The palettes and shapes themselves are in Colour and shape.

© 2025 - 2026 NipponDaily. Released under the Apache-2.0 License.

Origins quoted from Wiktionary (CC BY-SA 4.0) via Kaikki.org; meanings & kanji from JMdict, KANJIDIC2 & KRADFILE (EDRDG, CC BY-SA 4.0) — how we check it