diff --git a/lib/microwaveprop_web.ex b/lib/microwaveprop_web.ex index 2193b172..130dd1e7 100644 --- a/lib/microwaveprop_web.ex +++ b/lib/microwaveprop_web.ex @@ -18,7 +18,7 @@ defmodule MicrowavepropWeb do """ @spec static_paths :: [String.t()] - def static_paths, do: ~w(assets fonts images downloads favicon.ico robots.txt sitemap.xml) + def static_paths, do: ~w(assets fonts images downloads favicon.ico favicon.svg robots.txt sitemap.xml) @spec router :: Macro.t() def router do diff --git a/lib/microwaveprop_web/components/layouts.ex b/lib/microwaveprop_web/components/layouts.ex index 86449c4c..0ec5b1bb 100644 --- a/lib/microwaveprop_web/components/layouts.ex +++ b/lib/microwaveprop_web/components/layouts.ex @@ -172,6 +172,14 @@ defmodule MicrowavepropWeb.Layouts do """ end + @spec page_title(map()) :: String.t() + def page_title(assigns) do + case assigns[:page_title] do + nil -> "NTMS Microwave Propagation" + title -> title <> " · NTMS Microwave Propagation" + end + end + defp format_time_ago(%DateTime{} = dt) do diff = max(DateTime.diff(DateTime.utc_now(), dt, :second), 0) diff --git a/lib/microwaveprop_web/components/layouts/root.html.heex b/lib/microwaveprop_web/components/layouts/root.html.heex index 1e6aeaab..60c492ed 100644 --- a/lib/microwaveprop_web/components/layouts/root.html.heex +++ b/lib/microwaveprop_web/components/layouts/root.html.heex @@ -12,6 +12,44 @@ the Referer if a future browser default changes. --%> + + + + + + + + + + + + + + + + + + + + <.live_title default="NTMS Microwave Propagation" suffix=" · NTMS Microwave Propagation"> {assigns[:page_title]} @@ -39,10 +77,26 @@ setTheme(localStorage.getItem("phx:theme") || "system"); } window.addEventListener("storage", (e) => e.key === "phx:theme" && setTheme(e.newValue || "system")); - + window.addEventListener("phx:set-theme", (e) => setTheme(e.target.dataset.phxTheme)); })(); + {@inner_content} diff --git a/lib/microwaveprop_web/controllers/page_controller.ex b/lib/microwaveprop_web/controllers/page_controller.ex index 726de104..12029dca 100644 --- a/lib/microwaveprop_web/controllers/page_controller.ex +++ b/lib/microwaveprop_web/controllers/page_controller.ex @@ -12,4 +12,49 @@ defmodule MicrowavepropWeb.PageController do def redirect_contact(conn, %{"id" => id}) do redirect(conn, to: ~p"/contacts/#{id}") end + + @llms_txt """ + # NTMS Microwave Propagation + + > The North Texas Microwave Society's propagation prediction service for amateur radio bands 10–241 GHz. Scores propagation conditions across CONUS using HRRR numerical weather prediction, sounding data, ITU-R atmospheric models, and calibration against 58,000+ recorded QSOs. + + A real-time propagation prediction system for microwave amateur radio bands, built and operated by the North Texas Microwave Society. + + ## Tools and maps + + - [Propagation map](/map) — real-time CONUS propagation scores with 18-hour forecast overlay. + - [Weather map](/weather) — HRRR weather data overlay (temperature, dewpoint, refractivity, ducting). + - [Weather map (Canada)](/weather-ca) — HRDPS/RDPS-based weather for Canadian coverage. + - [Contact map](/contacts/map) — geographic view of 58,000+ recorded QSOs. + - [Path calculator](/path) — point-to-point propagation prediction between two locations. + - [EME calculator](/eme) — Earth-Moon-Earth communication prediction for 144–10 GHz. + - [Skew-T diagram](/skewt) — atmospheric sounding visualization from HRRR profiles and RAOB data. + - [Rover planner](/rover) — propagation heatmap with ranked drive-to candidates. + - [Rover locations](/rover-locations) — shared directory of rover-friendly parking spots. + - [Rover planning](/rover-planning) — planned rover missions and their paths. + + ## Data + + - [Algorithm documentation](/algo) — full scoring methodology, factor weights, ITU-R model references. + - [Contact database](/contacts) — 58,000+ historical QSOs used for calibration. + - [Beacon directory](/beacons) — microwave beacon listings across North America. + + ## About + + - [About](/about) — project background. + - [Privacy](/privacy) — data collection and usage policy. + + ## API + + - [API documentation](/docs/api) — REST API reference. + - [OpenAPI spec](/openapi.json) — machine-readable API description. + - [Contact map data](/api/contacts/map) — JSON dump of all map contacts. + """ + + @spec llms_txt(Plug.Conn.t(), map()) :: Plug.Conn.t() + def llms_txt(conn, _params) do + conn + |> put_resp_content_type("text/markdown", "utf-8") + |> send_resp(200, @llms_txt) + end end diff --git a/lib/microwaveprop_web/router.ex b/lib/microwaveprop_web/router.ex index 59336853..913e8211 100644 --- a/lib/microwaveprop_web/router.ex +++ b/lib/microwaveprop_web/router.ex @@ -17,10 +17,11 @@ defmodule MicrowavepropWeb.Router do plug :fetch_live_flash plug :put_root_layout, html: {MicrowavepropWeb.Layouts, :root} plug :protect_from_forgery - plug :put_secure_browser_headers + plug :put_secure_browser_headers, %{"referrer-policy" => "strict-origin-when-cross-origin"} plug :serve_markdown_if_requested plug :accepts, ["html"] plug :put_agent_link_headers + plug :put_seo_assigns plug :fetch_current_scope_for_user end @@ -42,6 +43,14 @@ defmodule MicrowavepropWeb.Router do Plug.Conn.put_resp_header(conn, "link", @agent_link_header) end + defp put_seo_assigns(conn, _opts) do + url = MicrowavepropWeb.Endpoint.url() + + conn + |> assign(:canonical_url, url <> conn.request_path) + |> assign(:og_image, url <> "/images/og-default.svg") + end + # Markdown for Agents: if the client prefers `text/markdown`, serve real # markdown for the paths where we have markdown source. Other paths fall # through to the `:accepts` plug and 406 — we don't synthesize markdown @@ -202,6 +211,10 @@ defmodule MicrowavepropWeb.Router do MicrowavepropWeb.AgentSkillsController, :skill + # llms.txt — a curated markdown index for LLMs (llmstxt.org proposal). + # Serves direct content; no pipeline needed. + get "/llms.txt", MicrowavepropWeb.PageController, :llms_txt + # Admin-only routes must come first so that literal segments like # `/beacons/:id/edit` are registered before the public `/beacons/:id`. scope "/", MicrowavepropWeb do diff --git a/priv/static/favicon.svg b/priv/static/favicon.svg new file mode 100644 index 00000000..eb26a88e --- /dev/null +++ b/priv/static/favicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/priv/static/images/og-default.svg b/priv/static/images/og-default.svg new file mode 100644 index 00000000..cf84bf16 --- /dev/null +++ b/priv/static/images/og-default.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + NTMS Microwave Propagation + + + Real-time propagation prediction for amateur radio bands 10–241 GHz + + + prop.w5isp.com + + + diff --git a/priv/static/sitemap.xml b/priv/static/sitemap.xml index 6b423b2e..05c0e536 100644 --- a/priv/static/sitemap.xml +++ b/priv/static/sitemap.xml @@ -10,6 +10,46 @@ hourly 0.9 + + https://prop.w5isp.com/weather + hourly + 0.8 + + + https://prop.w5isp.com/weather-ca + hourly + 0.7 + + + https://prop.w5isp.com/path + weekly + 0.8 + + + https://prop.w5isp.com/eme + weekly + 0.7 + + + https://prop.w5isp.com/skewt + weekly + 0.6 + + + https://prop.w5isp.com/rover + weekly + 0.7 + + + https://prop.w5isp.com/rover-locations + weekly + 0.6 + + + https://prop.w5isp.com/rover-planning + weekly + 0.6 + https://prop.w5isp.com/submit monthly @@ -20,9 +60,34 @@ daily 0.6 + + https://prop.w5isp.com/contacts/map + daily + 0.6 + + + https://prop.w5isp.com/beacons + weekly + 0.6 + https://prop.w5isp.com/algo monthly 0.5 + + https://prop.w5isp.com/about + monthly + 0.4 + + + https://prop.w5isp.com/privacy + monthly + 0.3 + + + https://prop.w5isp.com/docs/api + monthly + 0.4 +