Nordlys

Reference

Configuration

Everything Nordlys shows - pages, layout, tiles, charts, thresholds, theme, live updates - is configured in the [Nordlys] section of skin.conf. No template or JavaScript edits are ever needed.

Overview

Any option can also be overridden per station from weewx.conf, in the report’s own section, without touching the skin at all. weewx merges these over the skin’s skin.conf, so weewx.conf wins.

weewx.conf

[StdReport]
    [[NordlysReport]]
        skin = Nordlys
        [[[Nordlys]]]
            site_name = My station
            [[[[theme]]]]
                mode = dark

Top level

skin.conf

[Nordlys]
    site_name = Aldersundet weather   # display name; default: station location
    user_css = user.css               # optional extra stylesheet
    auto_refresh = false              # opt out of timed auto-refresh (default true)

Pages, rows, tiles

A page is a list of rows. A row is a list of tiles. A tile is a type plus a handful of options. The section name of a tile is its default observation, and the order of the page sections is the order of the navigation.

skin.conf

    [[pages]]
        [[[today]]]               # page id; order = nav order
            title = Today
            [[[[now]]]]           # a row
                title = Now       # optional row heading
                columns = 4       # max columns on wide screens (1-6)
                [[[[[outTemp]]]]] # a tile; section name = default obs
                    type = gauge
        [[[week]]]
            title = Week
            picker = week         # period dropdown: week | month | year

Rows collapse to two columns under 900 px and one column under 560 px.

Every page is emitted as a real, canonical HTML file - index.html for the first page and <id>.html for the rest - so pages are shareable, bookmarkable, and work with the browser’s back and forward buttons. Navigation links are real <a href> elements that work with JavaScript disabled, using each page’s server-rendered fallback; with JavaScript, clicks navigate in place through the History API from the shared payload.

A page-level picker adds a dropdown on the first row to jump to any past week, month or year. Weeks are real pages generated by Nordlys’s own week generator; months and years come from weewx’s SummaryBy generators.

Tile types

typeshowsneeds
gaugeSVG arc gauge, or a wind compassobs
statValue with today's min, max and trendobs
chartuPlot chart, wind rose, or calendar heatmapobs
tableStats table or archive records tableobs
climatologyPer-month and per-year climatological days matrix-
celestialSun and moon combo, or one almanac section-
forecastZambretti pressure forecast-
reportsLinks to archive pages and NOAA reports-
historyCross-year records for this day or monthobs
textA static text tile-

Common tile keys are type, obs (one name, or a comma list for charts and tables) and title, which defaults to the observation’s label.

gauge

    min = -20            # gauge scale bounds (report units)
    max = 35
    style = compass      # wind-direction compass variant
    color = accent-2     # token or CSS color for the band and now-marker
    cold_below = 0       # switch to --nl-cold below this value
    hot_above = 25       # switch to --nl-hot above this value

stat

color, cold_below and hot_above work as above, colouring the value. Add a span (day yesterday week month year alltime archive) and the tile becomes a period stat: the hero value is the period average, or the total for rain, the detail row shows the period minimum and maximum, and there is no trend or live update. Without a span it shows current conditions.

chart

    chart = line         # line | area | bar | scatter | windrose | calendar
    span = day           # 24h | day | yesterday | week | month | year
    obs = outTemp, dewpoint     # one or many series
    overlay = rainRate          # optional 2nd-axis line (e.g. over rain bars)
    colors = accent, accent-3   # optional per-series colors
  • Spans. day is calendar today, midnight to now, and yesterday the previous calendar day. 24h is a trailing 24-hour window. week, month and year are rolling windows.
  • Aggregation. Series are aggregated per span: raw for the day spans, hourly average for a week, three-hourly for a month, daily for a year. Rain is summed instead - hourly buckets on the day spans, daily beyond - so pair it with chart = bar.
  • Fills. A single-series line chart fills its area; charts with several series stay unfilled so they do not obscure each other. area forces a fill.
  • Overlay. overlay draws one more observation as a line on a second, right-hand axis - a rain rate line over rain bars, say. It is bucket-averaged onto the base series’ timestamps, so it lines up at every span.
  • Wind rose extras. bands = 2, 4, 6, 9, 12 sets the speed band upper bounds in report units, and calm_below = 0.5 the calm threshold.
  • Calendar extras. aggregate = avg (or min max sum of each day), coloured between --nl-cold and --nl-hot over a rolling year.

table

    table = stats        # stats | records
    span = month         # stats:   day | yesterday | week | month | year | alltime
                         # records: 24h | day | yesterday | week | month | year
    obs = outTemp, outHumidity, barometer, windSpeed, rain

stats shows minimum, average and maximum with times - for rain, the total and the wettest day. records is a sortable, paginated table of raw archive records, built from the same series data the charts use.

Stats table extras

    style = cards        # render extremes as record cards (label, value, date)
    spans = year, alltime   # compare several spans in one card per metric

style = cards shows each observation’s highest and lowest as record cards instead of a min/avg/max table. Use spans instead of a single span to compare periods: each metric becomes one card with a labelled row per span, such as “This year” over “All-time”.

celestial

    section = sunpath    # sun | sunpath | moon | seasons | planets

A bare celestial tile with no section is the compact sun-and-moon card. With a section it renders one detailed panel; the Celestial page lays out all five. Everything beyond sunrise, sunset and moon phase - sun path, twilight, moon and planet rise and set, seasons - needs the optional ephem package. Without it those panels show the basic data plus a hint.

reports

stats = outTemp, rain adds at-a-glance period stats - average, or total per observation - to each month and year in the period browser.

history

    span = day           # day | month  (historical window to compare)
    obs = outTemp, windGust, rain

Cross-year “on this day in history” records: for each observation, the record high, low and mean over every matching day in the archive, tagged with the year, and the time for span = day. span = month compares the whole current month across years instead. Summed observations such as rain report the wettest day and no low, and wind, rain rate, UV and radiation carry no low either.

Archive pages

weewx generates one page per calendar month and year, plus NOAA text reports. [[archive]] defines the layout used by all of them - the same rows and tiles as a normal page, with span = archive meaning “this page’s own period”.

skin.conf

    [[archive]]
        [[[charts]]]
            columns = 2
            [[[[temperature]]]]
                type = chart
                chart = line
                obs = outTemp, dewpoint
                span = archive

Archive series are aggregated three-hourly on month pages and daily on year pages, with rain per day on both. Current-conditions tiles - gauge, stat, celestial and forecast - are not available on archive pages.

Theme

Nordlys is themed entirely through CSS design tokens, the --nl-* custom properties. The dark polar night palette is the hero theme and the light arctic daylight palette is derived from it. There is no shadow DOM and no compiled-in styling: anything you can see, you can re-theme from config.

skin.conf

    [[theme]]
        mode = auto        # auto (default) | dark | light
        switcher = true    # header theme switch (default true)
        [[[dark]]]         # --nl-* token overrides for dark mode
            accent = "#e86bc1"
        [[[light]]]        # ... and for light mode
            accent = "#7c3aed"

mode is the station default. The header carries a three-state switch - auto, light, dark - whose choice persists per browser in localStorage and overrides mode for that visitor, so a forced mode = dark still lets someone pick light. A tiny script in the <head> applies the saved choice before first paint, so there is no flash. Precedence is visitor choice, then mode, then system. Set switcher = false to hide the switch and pin the mode.

Token reference

Defaults below, dark first and light second.

Surfaces and text

tokendarklightrole
bg#0b1220#f6f9fcpage background
surface#111a2c#fffffftiles and cards
border#1e2a40#d7e0eb1px hairlines
text#e8eef7#17263bprimary text
text-dim#8fa3bf#5b6c82labels, units, secondary text

Aurora accents

tokendarklightrole
accent#3ddc97#0e9f6eprimary accent: gauge arcs, links, active nav
accent-2#4cc9f0#0e7490ice teal
accent-3#a78bfa#7c3aedaurora violet
accent-4#e86bc1#c2338fmagenta, use sparingly
warm#f0b860#b07817muted amber

Data semantics

tokendarklightrole
cold#4cc9f0#0e7490cold end of thresholds and ramps
hot#f08a5c#c2410chot end of thresholds and ramps
ok#3ddc97#0e9f6estatus: live indicator
alert#f27e7e#c0362cstatus: threshold breach, errors
series-1 … series-6aurora palettedarkenedchart series order

Space, shape and type

tokendefaultrole
space-0 … space-84 - 64 pxspacing scale, an 8px grid
radius8pxtile corner radius
max-width1200pxcontent width
fontsystem UI stackthe one typeface
fs-sm / fs-base / fs-lg / fs-xl13 / 15 / 20 / 32 pxtype scale

The full authoritative list is src/theme/tokens.css in the repository.

Guidance

  • Keep surfaces monochrome. The design language reserves colour for data, state and interaction. If everything is coloured, nothing is.
  • Mind contrast. Body text tokens are chosen for at least 4.5:1 against their backgrounds, and accents for at least 3:1 as non-text elements. If you override accent in light mode, darken it: the dark-mode value will usually fail contrast on white.
  • Where tiles colour values, prefer the semantic and accent tokens over raw hex so your palette stays consistent and re-themeable.

Beyond tokens: user.css

For changes tokens cannot express - layout tweaks, hiding elements - ship an extra stylesheet. Put user.css next to the generated site, either by copying it with a [CopyGenerator] copy_once entry or by dropping it in HTML_ROOT. It loads after nordlys.css, so its rules win.

skin.conf

[Nordlys]
    user_css = user.css

Climatological days

First-class day-counting definitions, rendered by climatology tiles. A day is counted when its daily aggregate of obs compares true against value.

skin.conf

    [[climatological_days]]
        span = year               # counting window (calendar year)
        [[[frost_days]]]
            label = Frost days
            obs = outTemp
            aggregate = min       # min | max | avg | sum (of the day)
            op = <                # < | <= | > | >=
            value = 0             # threshold, in report units

The shipped defaults follow Norwegian MET conventions: frost (frostdøgn), ice (isdøgn), summer days (sommerdager ≥ 20), tropical days (tropedager ≥ 30), tropical nights (tropenetter), precipitation (nedbørdøgn), growing (vekstdøgn) and storm days. Labels ship in English.

The climatology tile renders these as a matrix: one row per definition, one column per month plus a year total. Months outside the station’s archive coverage render blank rather than a misleading zero.

weewx.conf

    [[[Nordlys]]]
        [[[[climatological_days]]]]
            [[[[[summer_days]]]]]        # override a threshold
                value = 25               # German/DWD summer day
            [[[[[ice_days]]]]]           # remove a shipped default
                enable = false
            [[[[[hot_days]]]]]           # add a new one
                label = Hot days
                obs = outTemp
                aggregate = max
                op = >=
                value = 28

With weewx daily summaries, tropical_nights counts the 24-hour minimum at or above 20 °C (tropedøgn). The strict tropenatt, a minimum over 20:00-08:00, is not expressible from daily summaries.

Live updates

skin.conf

    [[live]]
        broker = wss://example.com:9001   # MQTT over WebSocket
        topic = weather/loop              # default: weather/loop

The topic must carry JSON loop packets keyed by weewx observation names. Unit suffixes such as outTemp_C are stripped, and dayRain feeds the rain tile. Values must be in the report’s unit system. When configured, the page header shows a live indicator and tiles update in place.

Date and time formats

strftime patterns shared by the search-list extension and the front-end, so extreme times, chart axes, the footer and the records table all format the same way. Defaults are 24-hour time and European dates with abbreviated months.

skin.conf

    [[formats]]
        time = %H:%M                 # day-span extremes, records, live
        date = %d %b                 # month/year extremes, chart date ticks
        date_year = %d %b %Y         # all-time extremes, footer date
        datetime = %d %b %Y, %H:%M   # footer "Generated"
        weekday_time = %a %H:%M      # week-span extremes, records rows

The front-end understands a strftime subset - %H %M %S %d %m %b %a %Y %I %p - with month and day names in English. These keys are separate from weewx’s [Units][[TimeFormats]], which formats Cheetah tags such as NOAA reports rather than the payload.

Search and social

Every page ships a meta description and OpenGraph and Twitter cards automatically. og:url, og:image and sitemap.xml need an absolute site root, resolved as base_url, then weewx’s [Station] station_url, then none. With no base URL those absolute-only parts are skipped.

skin.conf

    [[seo]]
        base_url = https://weather.example.com/nordlys
        description = Custom description for the whole site
        image = og-image.png     # static fallback card, relative to the site root
        dynamic_card = true      # live per-station cards when Pillow is present
        robots = true            # emit robots.txt + sitemap.xml (default)
  • Descriptions are page-aware by default; override them with description.
  • robots = false writes a Disallow: / robots.txt and an empty sitemap.
  • A JSON-LD WebSite and Place block ships on every page.

Dynamic social cards

With Pillow installed - an optional dependency, like ephem - Nordlys renders the card live per station on every report cycle instead of shipping one generic image. Live pages show current conditions; archive pages get a per-period card summarising that period’s temperature range and rain total.

The card uses the polar night palette and a bundled Inter font, so non-ASCII station names render correctly. The current period is redrawn each cycle and completed historical periods are drawn once and cached. Without Pillow, or with dynamic_card = false, pages fall back to the static image.

Labels and units

skin.conf

[Labels]
    [[Generic]]
        outTemp = Outside temperature

Nordlys uses the report’s unit system; weewx converts everything before serialization. Set it per report in weewx.conf with unit_system = metricwx.

Regeneration

Nordlys generates the whole site every report cycle - there are no write-once pages - so config and theme changes take effect on the next cycle, or immediately with:

weectl report run --config /path/to/weewx.conf

Auto-refresh

Open pages refresh themselves shortly after each report cycle, timed to weewx’s [StdArchive] archive_interval, so a tab left open stays current. The page re-fetches its own freshly generated HTML and swaps the new numbers, charts and records in place; scroll position, the active page, the theme and any live MQTT connection are all preserved. Refresh pauses while the tab is hidden and catches up when it becomes visible again.

Building on Nordlys rather than configuring it? The data contract documents the JSON payload the skin emits.