|
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"> |
|
c588255…
|
ragelink
|
137 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> |
|
7e1aaf6…
|
ragelink
|
138 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.1.6/purify.min.js"></script> |
|
4ce269c…
|
ragelink
|
139 |
<script src="https://unpkg.com/[email protected]"></script> |
|
4ce269c…
|
ragelink
|
140 |
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script> |
|
4ce269c…
|
ragelink
|
141 |
<script> |
|
4ce269c…
|
ragelink
|
142 |
document.body.addEventListener('htmx:configRequest', function(event) { |
|
4ce269c…
|
ragelink
|
143 |
var token = document.querySelector('meta[name="csrf-token"]'); |
|
4ce269c…
|
ragelink
|
144 |
if (token) { event.detail.headers['X-CSRFToken'] = token.content; } |
|
4ce269c…
|
ragelink
|
145 |
}); |
|
4ce269c…
|
ragelink
|
146 |
// Keyboard shortcut: / to open search |
|
4ce269c…
|
ragelink
|
147 |
document.addEventListener('keydown', function(e) { |
|
4ce269c…
|
ragelink
|
148 |
if (e.key === '/' && !e.ctrlKey && !e.metaKey && document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'TEXTAREA') { |
|
4ce269c…
|
ragelink
|
149 |
e.preventDefault(); |
|
4ce269c…
|
ragelink
|
150 |
document.querySelector('[x-ref="searchInput"]')?.closest('[x-data]')?.__x?.$data && (document.querySelector('[x-ref="searchInput"]').closest('[x-data]').__x.$data.open = true); |
|
4ce269c…
|
ragelink
|
151 |
setTimeout(() => document.querySelector('[x-ref="searchInput"]')?.focus(), 100); |
|
4ce269c…
|
ragelink
|
152 |
} |
|
4ce269c…
|
ragelink
|
153 |
}); |
|
4ce269c…
|
ragelink
|
154 |
</script> |
|
4ce269c…
|
ragelink
|
155 |
</head> |
|
4ce269c…
|
ragelink
|
156 |
<body class="h-full bg-gray-950 text-gray-100" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'> |
|
2eca4eb…
|
ragelink
|
157 |
<div class="min-h-full flex flex-col" x-data="{ mobileSidebar: false }"> |
|
4ce269c…
|
ragelink
|
158 |
{% if user.is_authenticated %} |
|
4ce269c…
|
ragelink
|
159 |
{% include "includes/nav.html" %} |
|
afe42d0…
|
ragelink
|
160 |
{% else %} |
|
afe42d0…
|
ragelink
|
161 |
{% include "includes/nav_public.html" %} |
|
4ce269c…
|
ragelink
|
162 |
{% endif %} |
|
4ce269c…
|
ragelink
|
163 |
|
|
4ce269c…
|
ragelink
|
164 |
<div class="flex flex-1 overflow-hidden"> |
|
4ce269c…
|
ragelink
|
165 |
{% if user.is_authenticated %} |
|
2eca4eb…
|
ragelink
|
166 |
<!-- Mobile sidebar overlay --> |
|
2eca4eb…
|
ragelink
|
167 |
<div x-show="mobileSidebar" x-transition:enter="transition-opacity ease-out duration-200" x-transition:leave="transition-opacity ease-in duration-150" |
|
2eca4eb…
|
ragelink
|
168 |
class="fixed inset-0 z-40 bg-black/50 lg:hidden" @click="mobileSidebar = false" style="display:none"></div> |
|
2eca4eb…
|
ragelink
|
169 |
<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
|
170 |
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
|
171 |
class="fixed inset-y-0 left-0 z-50 w-64 lg:hidden" style="display:none"> |
|
2eca4eb…
|
ragelink
|
172 |
{% include "includes/sidebar.html" %} |
|
2eca4eb…
|
ragelink
|
173 |
</div> |
|
2eca4eb…
|
ragelink
|
174 |
<!-- Desktop sidebar --> |
|
4ce269c…
|
ragelink
|
175 |
{% include "includes/sidebar.html" %} |
|
4ce269c…
|
ragelink
|
176 |
{% endif %} |
|
4ce269c…
|
ragelink
|
177 |
|
|
afe42d0…
|
ragelink
|
178 |
<main class="flex-1 overflow-y-auto py-6"> |
|
4ce269c…
|
ragelink
|
179 |
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> |
|
4ce269c…
|
ragelink
|
180 |
{% if messages %} |
|
4ce269c…
|
ragelink
|
181 |
<div id="messages" class="mb-4 space-y-2"> |
|
4ce269c…
|
ragelink
|
182 |
{% for message in messages %} |
|
4ce269c…
|
ragelink
|
183 |
<div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 4000)" |
|
0e40dc2…
|
ragelink
|
184 |
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
|
185 |
<div class="flex justify-between"> |
|
4ce269c…
|
ragelink
|
186 |
<p class="text-sm font-medium">{{ message }}</p> |
|
0e40dc2…
|
ragelink
|
187 |
<button @click="show = false" aria-label="Dismiss" class="ml-3 text-sm font-medium underline">×</button> |
|
4ce269c…
|
ragelink
|
188 |
</div> |
|
4ce269c…
|
ragelink
|
189 |
</div> |
|
4ce269c…
|
ragelink
|
190 |
{% endfor %} |
|
4ce269c…
|
ragelink
|
191 |
</div> |
|
4ce269c…
|
ragelink
|
192 |
{% endif %} |
|
4ce269c…
|
ragelink
|
193 |
|
|
4ce269c…
|
ragelink
|
194 |
{% block content %}{% endblock %} |
|
4ce269c…
|
ragelink
|
195 |
</div> |
|
4ce269c…
|
ragelink
|
196 |
</main> |
|
4ce269c…
|
ragelink
|
197 |
</div> |
|
4ce269c…
|
ragelink
|
198 |
</div> |
|
4ce269c…
|
ragelink
|
199 |
{% include "fossil/_keyboard_help.html" %} |
|
4ce269c…
|
ragelink
|
200 |
</body> |
|
4ce269c…
|
ragelink
|
201 |
</html> |