FossilRepo

fossilrepo / templates / base.html
Source Blame History 208 lines
4ce269c… ragelink 1 <!DOCTYPE html>
4ce269c… ragelink 2 <html lang="en" class="h-full dark">
4ce269c… ragelink 3 <head>
4ce269c… ragelink 4 <meta charset="utf-8">
4ce269c… ragelink 5 <meta name="viewport" content="width=device-width, initial-scale=1">
4ce269c… ragelink 6 <meta name="csrf-token" content="{{ csrf_token }}">
4ce269c… ragelink 7 <title>{% block title %}Fossilrepo{% endblock %}</title>
4ce269c… ragelink 8 <script>
4ce269c… ragelink 9 // Apply theme before anything renders to prevent flash
4ce269c… ragelink 10 (function() {
4ce269c… ragelink 11 var theme = localStorage.getItem('theme');
4ce269c… ragelink 12 if (theme === 'light') {
4ce269c… ragelink 13 document.documentElement.classList.remove('dark');
4ce269c… ragelink 14 } else {
4ce269c… ragelink 15 document.documentElement.classList.add('dark');
4ce269c… ragelink 16 }
4ce269c… ragelink 17 })();
4ce269c… ragelink 18 </script>
4ce269c… ragelink 19 <script src="https://cdn.tailwindcss.com?plugins=typography"></script>
4ce269c… ragelink 20 {% block extra_head %}{% endblock %}
4ce269c… ragelink 21 <script>
4ce269c… ragelink 22 tailwind.config = {
4ce269c… ragelink 23 darkMode: 'class',
4ce269c… ragelink 24 theme: {
4ce269c… ragelink 25 extend: {
4ce269c… ragelink 26 colors: {
4ce269c… ragelink 27 brand: {
4ce269c… ragelink 28 DEFAULT: '#DC394C',
4ce269c… ragelink 29 dark: '#8B3138',
4ce269c… ragelink 30 hover: '#c42d3f',
4ce269c… ragelink 31 light: '#e8677a',
4ce269c… ragelink 32 }
4ce269c… ragelink 33 }
4ce269c… ragelink 34 }
4ce269c… ragelink 35 }
4ce269c… ragelink 36 }
4ce269c… ragelink 37 </script>
4ce269c… ragelink 38 <style type="text/tailwindcss">
4ce269c… ragelink 39 @layer base {
4ce269c… ragelink 40 input[type="text"], input[type="number"], input[type="email"],
4ce269c… ragelink 41 input[type="password"], input[type="search"], input[type="url"],
4ce269c… ragelink 42 textarea, select {
2f13242… ragelink 43 @apply bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600
2f13242… ragelink 44 text-gray-900 dark:text-gray-100 rounded-md
b3f4278… ragelink 45 px-3 py-2
2f13242… ragelink 46 shadow-sm dark:shadow-inner
2f13242… ragelink 47 focus:border-brand focus:ring-1 focus:ring-brand
2f13242… ragelink 48 placeholder-gray-500 dark:placeholder-gray-500
2f13242… ragelink 49 transition-colors duration-150
2f13242… ragelink 50 sm:text-sm;
4ce269c… ragelink 51 }
4ce269c… ragelink 52 }
7e1aaf6… ragelink 53 </style>
7e1aaf6… ragelink 54 <style>
313537c… ragelink 55 /* Hide scrollbar on horizontal-scroll navs (tabs, filter pills) */
313537c… ragelink 56 .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
313537c… ragelink 57 .scrollbar-hide::-webkit-scrollbar { display: none; }
0e40dc2… ragelink 58 /* Focus visible outlines for keyboard navigation */
0e40dc2… ragelink 59 a:focus-visible, button:focus-visible, [role="button"]:focus-visible {
0e40dc2… ragelink 60 outline: 2px solid #DC394C;
0e40dc2… ragelink 61 outline-offset: 2px;
0e40dc2… ragelink 62 border-radius: 4px;
0e40dc2… ragelink 63 }
7e1aaf6… ragelink 64 /* HTMX loading indicator: hidden by default, shown when htmx is in flight */
7e1aaf6… ragelink 65 .htmx-indicator { display: none; }
7e1aaf6… ragelink 66 .htmx-request .htmx-indicator, .htmx-request.htmx-indicator { display: inline-flex; }
7e1aaf6… ragelink 67 /* Spinner next to search inputs during HTMX requests */
7e1aaf6… ragelink 68 .search-wrap { position: relative; display: inline-flex; align-items: center; }
7e1aaf6… ragelink 69 .search-wrap .search-spinner {
7e1aaf6… ragelink 70 position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
7e1aaf6… ragelink 71 display: none; color: #6b7280;
7e1aaf6… ragelink 72 }
7e1aaf6… ragelink 73 .search-wrap:has(input.htmx-request) .search-spinner,
7e1aaf6… ragelink 74 .search-wrap.htmx-request .search-spinner { display: block; }
4ce269c… ragelink 75 </style>
4ce269c… ragelink 76 <style>
4ce269c… ragelink 77 /*
4ce269c… ragelink 78 * Light mode — matches Django admin dark_theme.css palette
4ce269c… ragelink 79 * Brand: #DC394C red, #8B3138 crimson, #2B2D2C charcoal
4ce269c… ragelink 80 * Nav bar stays dark. Only main content area switches.
4ce269c… ragelink 81 */
4ce269c… ragelink 82 html:not(.dark) body { background-color: #f8f8f8; color: #1a1a1a; }
4ce269c… ragelink 83 /* Surfaces */
4ce269c… ragelink 84 html:not(.dark) main .bg-gray-950 { background-color: #f8f8f8 !important; }
4ce269c… ragelink 85 html:not(.dark) main .bg-gray-900 { background-color: #eeeeee !important; }
4ce269c… ragelink 86 html:not(.dark) main .bg-gray-800 { background-color: #ffffff !important; }
4ce269c… ragelink 87 html:not(.dark) main .bg-gray-700 { background-color: #eeeeee !important; }
4ce269c… ragelink 88 html:not(.dark) main .bg-gray-800\/50,
4ce269c… ragelink 89 html:not(.dark) main .hover\:bg-gray-800\/50:hover { background-color: #eeeeee !important; }
4ce269c… ragelink 90 html:not(.dark) main .hover\:bg-gray-800:hover { background-color: #eeeeee !important; }
4ce269c… ragelink 91 html:not(.dark) main .hover\:bg-gray-700:hover { background-color: #e0e0e0 !important; }
4ce269c… ragelink 92 html:not(.dark) main .hover\:bg-gray-700\/50:hover { background-color: #eeeeee !important; }
4ce269c… ragelink 93 html:not(.dark) main .hover\:bg-gray-600:hover { background-color: #e0e0e0 !important; }
4ce269c… ragelink 94 /* Text */
4ce269c… ragelink 95 html:not(.dark) main .text-gray-100 { color: #1a1a1a !important; }
4ce269c… ragelink 96 html:not(.dark) main .text-gray-200 { color: #1a1a1a !important; }
4ce269c… ragelink 97 html:not(.dark) main .text-gray-300 { color: #666666 !important; }
4ce269c… ragelink 98 html:not(.dark) main .text-gray-400 { color: #666666 !important; }
4ce269c… ragelink 99 html:not(.dark) main .text-gray-500 { color: #a8aaa9 !important; }
4ce269c… ragelink 100 html:not(.dark) main .hover\:text-white:hover { color: #000000 !important; }
4ce269c… ragelink 101 html:not(.dark) main .hover\:text-gray-200:hover { color: #1a1a1a !important; }
4ce269c… ragelink 102 /* Borders — matches --hairline-color / --border-color */
4ce269c… ragelink 103 html:not(.dark) main .border-gray-700 { border-color: #e0e0e0 !important; }
4ce269c… ragelink 104 html:not(.dark) main .border-gray-600 { border-color: #e0e0e0 !important; }
4ce269c… ragelink 105 html:not(.dark) main .divide-gray-700 > :not([hidden]) ~ :not([hidden]) { border-color: #e0e0e0 !important; }
4ce269c… ragelink 106 html:not(.dark) main .ring-gray-700 { --tw-ring-color: #e0e0e0 !important; }
4ce269c… ragelink 107 html:not(.dark) main .ring-gray-600 { --tw-ring-color: #e0e0e0 !important; }
4ce269c… ragelink 108 html:not(.dark) main .shadow-sm { box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05) !important; }
4ce269c… ragelink 109 /* Status badges — matches admin message colors */
4ce269c… ragelink 110 html:not(.dark) main .bg-green-900\/50 { background-color: #d4edda !important; }
4ce269c… ragelink 111 html:not(.dark) main .text-green-300 { color: #155724 !important; }
4ce269c… ragelink 112 html:not(.dark) main .border-green-700 { border-color: #c3e6cb !important; }
4ce269c… ragelink 113 html:not(.dark) main .bg-yellow-900\/50 { background-color: #fff3cd !important; }
4ce269c… ragelink 114 html:not(.dark) main .text-yellow-300 { color: #856404 !important; }
4ce269c… ragelink 115 html:not(.dark) main .text-yellow-400 { color: #856404 !important; }
4ce269c… ragelink 116 html:not(.dark) main .bg-purple-900\/50 { background-color: #f3e8ff !important; }
4ce269c… ragelink 117 html:not(.dark) main .text-purple-300 { color: #9333ea !important; }
4ce269c… ragelink 118 html:not(.dark) main .bg-blue-900\/50 { background-color: #dbeafe !important; }
4ce269c… ragelink 119 html:not(.dark) main .text-blue-300 { color: #2563eb !important; }
4ce269c… ragelink 120 html:not(.dark) main .bg-red-900\/50 { background-color: #f8d7da !important; }
4ce269c… ragelink 121 html:not(.dark) main .text-red-300 { color: #721c24 !important; }
4ce269c… ragelink 122 html:not(.dark) main .border-red-700 { border-color: #f5c6cb !important; }
4ce269c… ragelink 123 html:not(.dark) main .text-red-400 { color: #c0392b !important; }
4ce269c… ragelink 124 html:not(.dark) main .hover\:text-red-300:hover { color: #721c24 !important; }
4ce269c… ragelink 125 /* Mono text */
4ce269c… ragelink 126 html:not(.dark) main .font-mono { color: #666666 !important; }
4ce269c… ragelink 127 /* Prose — matches admin link/text colors */
4ce269c… ragelink 128 html:not(.dark) main .prose-invert { --tw-prose-body: #1a1a1a; --tw-prose-headings: #000000; --tw-prose-links: #DC394C; --tw-prose-bold: #000000; --tw-prose-code: #1a1a1a; --tw-prose-th-borders: #e0e0e0; --tw-prose-td-borders: #e0e0e0; }
4ce269c… ragelink 129 /* Brand links — matches admin --link-fg */
4ce269c… ragelink 130 html:not(.dark) main .text-brand-light { color: #DC394C !important; }
4ce269c… ragelink 131 html:not(.dark) main .hover\:text-brand:hover { color: #8B3138 !important; }
4ce269c… ragelink 132 html:not(.dark) main .hover\:border-brand:hover { border-color: #DC394C !important; }
4ce269c… ragelink 133 /* Selected/hover rows — matches admin --selected-bg */
4ce269c… ragelink 134 html:not(.dark) main .group:hover { border-color: #DC394C !important; }
4ce269c… ragelink 135 </style>
c588255… ragelink 136 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
10e3c53… ragelink 137 <style>
1f36724… ragelink 138 /* highlight.js github-dark sets a dark background on .hljs code blocks. Since
1f36724… ragelink 139 our code viewer renders each line as its own <code class="hljs"> element,
1f36724… ragelink 140 this creates individual dark boxes per line. We own the background already. */
1f36724… ragelink 141 .hljs { background: transparent !important; }
10e3c53… ragelink 142 .hljs-section, .hljs-keyword { background: transparent !important; }
10e3c53… ragelink 143 </style>
c588255… ragelink 144 <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
7e1aaf6… ragelink 145 <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.1.6/purify.min.js"></script>
4ce269c… ragelink 146 <script src="https://unpkg.com/[email protected]"></script>
4ce269c… ragelink 147 <script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
4ce269c… ragelink 148 <script>
4ce269c… ragelink 149 document.body.addEventListener('htmx:configRequest', function(event) {
4ce269c… ragelink 150 var token = document.querySelector('meta[name="csrf-token"]');
4ce269c… ragelink 151 if (token) { event.detail.headers['X-CSRFToken'] = token.content; }
4ce269c… ragelink 152 });
4ce269c… ragelink 153 // Keyboard shortcut: / to open search
4ce269c… ragelink 154 document.addEventListener('keydown', function(e) {
4ce269c… ragelink 155 if (e.key === '/' && !e.ctrlKey && !e.metaKey && document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'TEXTAREA') {
4ce269c… ragelink 156 e.preventDefault();
4ce269c… ragelink 157 document.querySelector('[x-ref="searchInput"]')?.closest('[x-data]')?.__x?.$data && (document.querySelector('[x-ref="searchInput"]').closest('[x-data]').__x.$data.open = true);
4ce269c… ragelink 158 setTimeout(() => document.querySelector('[x-ref="searchInput"]')?.focus(), 100);
4ce269c… ragelink 159 }
4ce269c… ragelink 160 });
4ce269c… ragelink 161 </script>
4ce269c… ragelink 162 </head>
4ce269c… ragelink 163 <body class="h-full bg-gray-950 text-gray-100" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
2eca4eb… ragelink 164 <div class="min-h-full flex flex-col" x-data="{ mobileSidebar: false }">
4ce269c… ragelink 165 {% if user.is_authenticated %}
4ce269c… ragelink 166 {% include "includes/nav.html" %}
afe42d0… ragelink 167 {% else %}
afe42d0… ragelink 168 {% include "includes/nav_public.html" %}
4ce269c… ragelink 169 {% endif %}
4ce269c… ragelink 170
4ce269c… ragelink 171 <div class="flex flex-1 overflow-hidden">
4ce269c… ragelink 172 {% if user.is_authenticated %}
2eca4eb… ragelink 173 <!-- Mobile sidebar overlay -->
2eca4eb… ragelink 174 <div x-show="mobileSidebar" x-transition:enter="transition-opacity ease-out duration-200" x-transition:leave="transition-opacity ease-in duration-150"
2eca4eb… ragelink 175 class="fixed inset-0 z-40 bg-black/50 lg:hidden" @click="mobileSidebar = false" style="display:none"></div>
2eca4eb… ragelink 176 <div x-show="mobileSidebar" x-transition:enter="transition-transform ease-out duration-200" x-transition:enter-start="-translate-x-full" x-transition:enter-end="translate-x-0"
2eca4eb… ragelink 177 x-transition:leave="transition-transform ease-in duration-150" x-transition:leave-start="translate-x-0" x-transition:leave-end="-translate-x-full"
2eca4eb… ragelink 178 class="fixed inset-y-0 left-0 z-50 w-64 lg:hidden" style="display:none">
2eca4eb… ragelink 179 {% include "includes/sidebar.html" %}
2eca4eb… ragelink 180 </div>
2eca4eb… ragelink 181 <!-- Desktop sidebar -->
4ce269c… ragelink 182 {% include "includes/sidebar.html" %}
4ce269c… ragelink 183 {% endif %}
4ce269c… ragelink 184
afe42d0… ragelink 185 <main class="flex-1 overflow-y-auto py-6">
4ce269c… ragelink 186 <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
4ce269c… ragelink 187 {% if messages %}
4ce269c… ragelink 188 <div id="messages" class="mb-4 space-y-2">
4ce269c… ragelink 189 {% for message in messages %}
4ce269c… ragelink 190 <div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 4000)"
0e40dc2… ragelink 191 x-transition role="status" class="rounded-md p-4 {% if message.tags == 'success' %}bg-green-900/50 text-green-300 border border-green-700{% elif message.tags == 'error' %}bg-red-900/50 text-red-300 border border-red-700{% else %}bg-gray-800 text-gray-300 border border-gray-700{% endif %}">
4ce269c… ragelink 192 <div class="flex justify-between">
4ce269c… ragelink 193 <p class="text-sm font-medium">{{ message }}</p>
0e40dc2… ragelink 194 <button @click="show = false" aria-label="Dismiss" class="ml-3 text-sm font-medium underline">&times;</button>
4ce269c… ragelink 195 </div>
4ce269c… ragelink 196 </div>
4ce269c… ragelink 197 {% endfor %}
4ce269c… ragelink 198 </div>
4ce269c… ragelink 199 {% endif %}
4ce269c… ragelink 200
4ce269c… ragelink 201 {% block content %}{% endblock %}
4ce269c… ragelink 202 </div>
4ce269c… ragelink 203 </main>
4ce269c… ragelink 204 </div>
4ce269c… ragelink 205 </div>
4ce269c… ragelink 206 {% include "fossil/_keyboard_help.html" %}
4ce269c… ragelink 207 </body>
4ce269c… ragelink 208 </html>

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button