umbral-core 0.0.4

umbral internals: ORM, migrations, routing, DB backends, the Plugin trait. Do not depend on this directly; use the `umbral` facade.
Documentation
<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>404 — Page Not Found</title>
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
        <link
            href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap"
            rel="stylesheet"
        />
        <script>
            // Tailwind config must run BEFORE the CDN script loads.
            // Extends the default theme: Inter as the default sans family
            // (so `font-sans` / body text picks it up), and JetBrains Mono
            // as the default mono family (so `font-mono` / `.mono` works).
            window.tailwind = window.tailwind || {};
            window.tailwind.config = {
                theme: {
                    extend: {
                        fontFamily: {
                            sans: [
                                "Inter",
                                "ui-sans-serif",
                                "system-ui",
                                "-apple-system",
                                "Segoe UI",
                                "Roboto",
                                "sans-serif",
                            ],
                            mono: [
                                "JetBrains Mono",
                                "ui-monospace",
                                "SFMono-Regular",
                                "Menlo",
                                "Consolas",
                                "monospace",
                            ],
                        },
                    },
                },
            };
        </script>
        <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
        <style>
            /* Apply the body font to the page (Tailwind's preflight
               already sets ui-sans-serif on body; we override it here
               so Inter wins once it has loaded). */
            body {
                font-family: "Inter", ui-sans-serif, system-ui, -apple-system,
                    "Segoe UI", Roboto, sans-serif;
            }
            /* `.mono` is the shorthand class used throughout the page
               for monospace text. Tailwind's `font-mono` utility also
               resolves to the same stack via the config above. */
            .mono {
                font-family: "JetBrains Mono", ui-monospace, SFMono-Regular,
                    Menlo, Consolas, monospace;
            }
        </style>
    </head>
    <body
        class="min-h-screen bg-slate-950 text-slate-300 antialiased flex flex-col"
    >
        <!-- Wordmark. Natural document flow so the footer never
             overlaps the route list when it grows tall. -->
        <header
            class="relative z-10 px-6 py-5 flex items-center justify-between"
        >
            <a
                href="/"
                class="mono text-xs tracking-widest text-slate-500 hover:text-slate-300 transition-colors"
            >
                umbral
            </a>
            <span
                class="mono text-[10px] tracking-widest text-slate-600 uppercase"
            >
                error // not_found
            </span>
        </header>

        <!-- Subtle grid background -->
        <div
            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"
        ></div>
        <div
            class="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_50%_30%,rgba(99,102,241,0.08),transparent_60%)]"
        ></div>

        <!-- Main content. `flex-1` makes it absorb the spare
             viewport so the footer hugs the bottom when the page is
             short; when the dev-mode route panel makes it tall, the
             whole body grows and the footer simply follows. -->
        <main
            class="relative flex-1 flex items-center justify-center px-6 py-16"
        >
            <div class="w-full max-w-2xl mx-auto text-center">
                <!-- Status label -->
                <p
                    class="mono text-xs tracking-[0.25em] text-slate-500 uppercase"
                >
                    Error // Not_Found
                </p>

                <!-- Hero code -->
                <h1
                    class="mono mt-6 text-7xl sm:text-8xl md:text-9xl font-bold tracking-tighter text-slate-200 leading-none"
                >
                    404
                </h1>

                <!-- Headline -->
                <h2
                    class="mt-8 text-2xl sm:text-3xl font-semibold text-slate-100 tracking-tight"
                >
                    Page not found
                </h2>

                <!-- Sub-copy -->
                <p
                    class="mt-4 text-base text-slate-400 max-w-md mx-auto leading-relaxed"
                >
                    The path you requested doesn't match any registered route on
                    this server.
                </p>

                <!-- URL strip -->
                {% if path %}
                <div class="mt-8 mx-auto max-w-xl">
                    <div
                        class="flex items-center gap-2 rounded-md border border-slate-800 bg-slate-900/60 px-3 py-2.5 backdrop-blur-sm"
                    >
                        <span
                            class="mono text-[10px] tracking-widest text-slate-600 uppercase shrink-0"
                            >GET</span
                        >
                        <code
                            class="mono text-sm text-slate-300 truncate flex-1 text-left"
                            title="{{ path }}"
                            >{{ path }}</code
                        >
                        <button
                            type="button"
                            onclick="navigator.clipboard?.writeText('{{ path }}'); this.textContent='Copied'; setTimeout(()=>this.textContent='Copy',1200);"
                            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"
                        >
                            Copy
                        </button>
                    </div>
                </div>
                {% endif %}

                <!-- Actions -->
                <div
                    class="mt-10 flex flex-col sm:flex-row items-center justify-center gap-3"
                >
                    <a
                        href="/"
                        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"
                    >
                        <span aria-hidden="true"></span>
                        <span>Go home</span>
                    </a>
                    <button
                        type="button"
                        onclick="
                            if (history.length > 1) {
                                history.back();
                            } else {
                                window.location.href = '/';
                            }
                        "
                        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"
                    >
                        Go back
                    </button>
                </div>

                {# Dev-mode aid: list every registered route, grouped
                   by plugin. Production responses set `dev_mode =
                   false` so this whole block collapses to nothing. The
                   list is a declared snapshot, not a live introspection
                   of axum's routing table — see
                   `crates/umbral-core/src/routes.rs`. #}
                {% if dev_mode and routes_by_plugin %}
                <div
                    class="mt-12 mx-auto max-w-2xl text-left rounded-md border border-slate-800 bg-slate-900/40 backdrop-blur-sm"
                >
                    <div
                        class="flex items-center justify-between gap-3 px-4 py-2.5 border-b border-slate-800"
                    >
                        <span
                            class="mono text-[10px] tracking-widest text-slate-400 uppercase"
                        >
                            Dev only // registered routes
                        </span>
                        <span class="mono text-[10px] text-slate-600">
                            {{ routes_by_plugin | length }} plugin{% if routes_by_plugin | length != 1 %}s{% endif %}
                        </span>
                    </div>
                    <div class="divide-y divide-slate-800">
                        {% for group in routes_by_plugin %}
                        <details class="group">
                            <summary
                                class="flex items-center justify-between gap-3 px-4 py-2.5 cursor-pointer hover:bg-slate-900/60 transition-colors"
                            >
                                <span
                                    class="mono text-xs text-slate-300"
                                    >{{ group.plugin }}</span
                                >
                                <span
                                    class="mono text-[10px] text-slate-500"
                                    >{{ group.routes | length }} route{% if group.routes | length != 1 %}s{% endif %}</span
                                >
                            </summary>
                            <ul
                                class="px-4 py-2 space-y-1.5 bg-slate-950/40 border-t border-slate-800"
                            >
                                {% for route in group.routes %}
                                <li class="flex items-baseline gap-2">
                                    {# Method badge: tinted by verb so
                                       the eye can scan a list of mixed
                                       GET/POST/DELETE entries without
                                       reading every label. The single
                                       `method_label` (joined with `·`)
                                       keeps the badge narrow even for
                                       composite routes like
                                       `GET·PUT`. #}
                                    {% set ml = route.method_label %}
                                    <span class="mono text-[10px] tracking-wider uppercase px-1.5 py-0.5 rounded shrink-0
                                        {% 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
                                        {% elif 'DELETE' in route.methods %}bg-rose-500/10 text-rose-300 ring-1 ring-rose-500/30
                                        {% elif 'GET' in route.methods or 'HEAD' in route.methods %}bg-emerald-500/10 text-emerald-300 ring-1 ring-emerald-500/30
                                        {% else %}bg-slate-700/30 text-slate-400 ring-1 ring-slate-700{% endif %}"
                                        >{{ ml }}</span>
                                    <code
                                        class="mono text-xs text-slate-400 break-all"
                                        >{{ route.path }}</code
                                    >
                                </li>
                                {% endfor %}
                            </ul>
                        </details>
                        {% endfor %}
                    </div>
                </div>
                {% endif %}
            </div>
        </main>

        <!-- Footer links. Sits in normal flow at the bottom of the
             flex column body — no `absolute` positioning so it never
             overlaps the route panel when it grows tall. -->
        <footer
            class="relative z-10 px-6 py-5 flex items-center justify-center gap-6"
        >
            <a
                href="/docs"
                class="text-xs text-slate-500 hover:text-slate-300 transition-colors"
                >Docs</a
            >
            <span class="text-slate-800" aria-hidden="true">·</span>
            <a
                href="/status"
                class="text-xs text-slate-500 hover:text-slate-300 transition-colors"
                >Status</a
            >
            <span class="text-slate-800" aria-hidden="true">·</span>
            <a
                href="https://github.com/umbral/umbral"
                class="text-xs text-slate-500 hover:text-slate-300 transition-colors"
                >GitHub</a
            >
        </footer>
    </body>
</html>