pub const DEFAULT_404_HTML: &str = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>404 \u{2014} Page Not Found</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link\n href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap\"\n rel=\"stylesheet\"\n />\n <script>\n // Tailwind config must run BEFORE the CDN script loads.\n // Extends the default theme: Inter as the default sans family\n // (so `font-sans` / body text picks it up), and JetBrains Mono\n // as the default mono family (so `font-mono` / `.mono` works).\n window.tailwind = window.tailwind || {};\n window.tailwind.config = {\n theme: {\n extend: {\n fontFamily: {\n sans: [\n \"Inter\",\n \"ui-sans-serif\",\n \"system-ui\",\n \"-apple-system\",\n \"Segoe UI\",\n \"Roboto\",\n \"sans-serif\",\n ],\n mono: [\n \"JetBrains Mono\",\n \"ui-monospace\",\n \"SFMono-Regular\",\n \"Menlo\",\n \"Consolas\",\n \"monospace\",\n ],\n },\n },\n },\n };\n </script>\n <script src=\"https://cdn.tailwindcss.com?plugins=forms,container-queries\"></script>\n <style>\n /* Apply the body font to the page (Tailwind\'s preflight\n already sets ui-sans-serif on body; we override it here\n so Inter wins once it has loaded). */\n body {\n font-family: \"Inter\", ui-sans-serif, system-ui, -apple-system,\n \"Segoe UI\", Roboto, sans-serif;\n }\n /* `.mono` is the shorthand class used throughout the page\n for monospace text. Tailwind\'s `font-mono` utility also\n resolves to the same stack via the config above. */\n .mono {\n font-family: \"JetBrains Mono\", ui-monospace, SFMono-Regular,\n Menlo, Consolas, monospace;\n }\n </style>\n </head>\n <body\n class=\"min-h-screen bg-slate-950 text-slate-300 antialiased flex flex-col\"\n >\n <!-- Wordmark. Natural document flow so the footer never\n overlaps the route list when it grows tall. -->\n <header\n class=\"relative z-10 px-6 py-5 flex items-center justify-between\"\n >\n <a\n href=\"/\"\n class=\"mono text-xs tracking-widest text-slate-500 hover:text-slate-300 transition-colors\"\n >\n umbral\n </a>\n <span\n class=\"mono text-[10px] tracking-widest text-slate-600 uppercase\"\n >\n error // not_found\n </span>\n </header>\n\n <!-- Subtle grid background -->\n <div\n class=\"absolute inset-0 -z-10 bg-[linear-gradient(to_right,#0f172a_1px,transparent_1px),linear-gradient(to_bottom,#0f172a_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_40%,#000_30%,transparent_100%)] opacity-40\"\n ></div>\n <div\n class=\"absolute inset-0 -z-10 bg-[radial-gradient(circle_at_50%_30%,rgba(99,102,241,0.08),transparent_60%)]\"\n ></div>\n\n <!-- Main content. `flex-1` makes it absorb the spare\n viewport so the footer hugs the bottom when the page is\n short; when the dev-mode route panel makes it tall, the\n whole body grows and the footer simply follows. -->\n <main\n class=\"relative flex-1 flex items-center justify-center px-6 py-16\"\n >\n <div class=\"w-full max-w-2xl mx-auto text-center\">\n <!-- Status label -->\n <p\n class=\"mono text-xs tracking-[0.25em] text-slate-500 uppercase\"\n >\n Error // Not_Found\n </p>\n\n <!-- Hero code -->\n <h1\n class=\"mono mt-6 text-7xl sm:text-8xl md:text-9xl font-bold tracking-tighter text-slate-200 leading-none\"\n >\n 404\n </h1>\n\n <!-- Headline -->\n <h2\n class=\"mt-8 text-2xl sm:text-3xl font-semibold text-slate-100 tracking-tight\"\n >\n Page not found\n </h2>\n\n <!-- Sub-copy -->\n <p\n class=\"mt-4 text-base text-slate-400 max-w-md mx-auto leading-relaxed\"\n >\n The path you requested doesn\'t match any registered route on\n this server.\n </p>\n\n <!-- URL strip -->\n {% if path %}\n <div class=\"mt-8 mx-auto max-w-xl\">\n <div\n class=\"flex items-center gap-2 rounded-md border border-slate-800 bg-slate-900/60 px-3 py-2.5 backdrop-blur-sm\"\n >\n <span\n class=\"mono text-[10px] tracking-widest text-slate-600 uppercase shrink-0\"\n >GET</span\n >\n <code\n class=\"mono text-sm text-slate-300 truncate flex-1 text-left\"\n title=\"{{ path }}\"\n >{{ path }}</code\n >\n <button\n type=\"button\"\n onclick=\"navigator.clipboard?.writeText(\'{{ path }}\'); this.textContent=\'Copied\'; setTimeout(()=>this.textContent=\'Copy\',1200);\"\n class=\"mono text-[10px] tracking-widest uppercase text-slate-500 hover:text-slate-200 transition-colors shrink-0 px-2 py-1 rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-400\"\n >\n Copy\n </button>\n </div>\n </div>\n {% endif %}\n\n <!-- Actions -->\n <div\n class=\"mt-10 flex flex-col sm:flex-row items-center justify-center gap-3\"\n >\n <a\n href=\"/\"\n class=\"inline-flex items-center justify-center gap-2 rounded-md bg-indigo-500 px-5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-400 w-full sm:w-auto\"\n >\n <span aria-hidden=\"true\">\u{2190}</span>\n <span>Go home</span>\n </a>\n <button\n type=\"button\"\n onclick=\"\n if (history.length > 1) {\n history.back();\n } else {\n window.location.href = \'/\';\n }\n \"\n class=\"inline-flex items-center justify-center gap-2 rounded-md border border-slate-800 bg-slate-900/40 px-5 py-2.5 text-sm font-semibold text-slate-200 hover:bg-slate-900 hover:border-slate-700 transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-400 w-full sm:w-auto\"\n >\n Go back\n </button>\n </div>\n\n {# Dev-mode aid: list every registered route, grouped\n by plugin. Production responses set `dev_mode =\n false` so this whole block collapses to nothing. The\n list is a declared snapshot, not a live introspection\n of axum\'s routing table \u{2014} see\n `crates/umbral-core/src/routes.rs`. #}\n {% if dev_mode and routes_by_plugin %}\n <div\n class=\"mt-12 mx-auto max-w-2xl text-left rounded-md border border-slate-800 bg-slate-900/40 backdrop-blur-sm\"\n >\n <div\n class=\"flex items-center justify-between gap-3 px-4 py-2.5 border-b border-slate-800\"\n >\n <span\n class=\"mono text-[10px] tracking-widest text-slate-400 uppercase\"\n >\n Dev only // registered routes\n </span>\n <span class=\"mono text-[10px] text-slate-600\">\n {{ routes_by_plugin | length }} plugin{% if routes_by_plugin | length != 1 %}s{% endif %}\n </span>\n </div>\n <div class=\"divide-y divide-slate-800\">\n {% for group in routes_by_plugin %}\n <details class=\"group\">\n <summary\n class=\"flex items-center justify-between gap-3 px-4 py-2.5 cursor-pointer hover:bg-slate-900/60 transition-colors\"\n >\n <span\n class=\"mono text-xs text-slate-300\"\n >{{ group.plugin }}</span\n >\n <span\n class=\"mono text-[10px] text-slate-500\"\n >{{ group.routes | length }} route{% if group.routes | length != 1 %}s{% endif %}</span\n >\n </summary>\n <ul\n class=\"px-4 py-2 space-y-1.5 bg-slate-950/40 border-t border-slate-800\"\n >\n {% for route in group.routes %}\n <li class=\"flex items-baseline gap-2\">\n {# Method badge: tinted by verb so\n the eye can scan a list of mixed\n GET/POST/DELETE entries without\n reading every label. The single\n `method_label` (joined with `\u{b7}`)\n keeps the badge narrow even for\n composite routes like\n `GET\u{b7}PUT`. #}\n {% set ml = route.method_label %}\n <span class=\"mono text-[10px] tracking-wider uppercase px-1.5 py-0.5 rounded shrink-0\n {% if \'POST\' in route.methods or \'PUT\' in route.methods or \'PATCH\' in route.methods %}bg-amber-500/10 text-amber-300 ring-1 ring-amber-500/30\n {% elif \'DELETE\' in route.methods %}bg-rose-500/10 text-rose-300 ring-1 ring-rose-500/30\n {% elif \'GET\' in route.methods or \'HEAD\' in route.methods %}bg-emerald-500/10 text-emerald-300 ring-1 ring-emerald-500/30\n {% else %}bg-slate-700/30 text-slate-400 ring-1 ring-slate-700{% endif %}\"\n >{{ ml }}</span>\n <code\n class=\"mono text-xs text-slate-400 break-all\"\n >{{ route.path }}</code\n >\n </li>\n {% endfor %}\n </ul>\n </details>\n {% endfor %}\n </div>\n </div>\n {% endif %}\n </div>\n </main>\n\n <!-- Footer links. Sits in normal flow at the bottom of the\n flex column body \u{2014} no `absolute` positioning so it never\n overlaps the route panel when it grows tall. -->\n <footer\n class=\"relative z-10 px-6 py-5 flex items-center justify-center gap-6\"\n >\n <a\n href=\"/docs\"\n class=\"text-xs text-slate-500 hover:text-slate-300 transition-colors\"\n >Docs</a\n >\n <span class=\"text-slate-800\" aria-hidden=\"true\">\u{b7}</span>\n <a\n href=\"/status\"\n class=\"text-xs text-slate-500 hover:text-slate-300 transition-colors\"\n >Status</a\n >\n <span class=\"text-slate-800\" aria-hidden=\"true\">\u{b7}</span>\n <a\n href=\"https://github.com/umbral/umbral\"\n class=\"text-xs text-slate-500 hover:text-slate-300 transition-colors\"\n >GitHub</a\n >\n </footer>\n </body>\n</html>\n";Expand description
Default 404 page: centered, inline Tailwind utility classes. Degrades gracefully without Tailwind loaded — the page is functional even as unstyled HTML.