From 6fa807f4b69f2f13fe2950394e00c12532a97ee8 Mon Sep 17 00:00:00 2001
From: pacnpal <183241239+pacnpal@users.noreply.github.com>
Date: Thu, 6 Feb 2025 14:20:48 -0500
Subject: [PATCH] Add photo gallery functionality with file upload, sharing,
and fullscreen display features
---
staticfiles/css/tailwind.css | 909 ++++++++++++++++++-----
staticfiles/gis/css/ol3.css | 39 +
staticfiles/gis/img/draw_line_off.svg | 1 +
staticfiles/gis/img/draw_line_on.svg | 1 +
staticfiles/gis/img/draw_point_off.svg | 1 +
staticfiles/gis/img/draw_point_on.svg | 1 +
staticfiles/gis/img/draw_polygon_off.svg | 1 +
staticfiles/gis/img/draw_polygon_on.svg | 1 +
staticfiles/gis/js/OLMapWidget.js | 233 ++++++
staticfiles/js/photo-gallery.js | 91 +++
10 files changed, 1097 insertions(+), 181 deletions(-)
create mode 100644 staticfiles/gis/css/ol3.css
create mode 100644 staticfiles/gis/img/draw_line_off.svg
create mode 100644 staticfiles/gis/img/draw_line_on.svg
create mode 100644 staticfiles/gis/img/draw_point_off.svg
create mode 100644 staticfiles/gis/img/draw_point_on.svg
create mode 100644 staticfiles/gis/img/draw_polygon_off.svg
create mode 100644 staticfiles/gis/img/draw_polygon_on.svg
create mode 100644 staticfiles/gis/js/OLMapWidget.js
create mode 100644 staticfiles/js/photo-gallery.js
diff --git a/staticfiles/css/tailwind.css b/staticfiles/css/tailwind.css
index 97a1eb47..dab10d4b 100644
--- a/staticfiles/css/tailwind.css
+++ b/staticfiles/css/tailwind.css
@@ -1,5 +1,113 @@
+*, ::before, ::after {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
+}
+
+::backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --tw-translate-x: 0;
+ --tw-translate-y: 0;
+ --tw-rotate: 0;
+ --tw-skew-x: 0;
+ --tw-skew-y: 0;
+ --tw-scale-x: 1;
+ --tw-scale-y: 1;
+ --tw-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: rgb(59 130 246 / 0.5);
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ --tw-contain-size: ;
+ --tw-contain-layout: ;
+ --tw-contain-paint: ;
+ --tw-contain-style: ;
+}
+
/*
-! tailwindcss v3.4.11 | MIT License | https://tailwindcss.com
+! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com
*/
/*
@@ -641,112 +749,8 @@ select {
outline: 1px auto -webkit-focus-ring-color;
}
-*, ::before, ::after {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- --tw-contain-size: ;
- --tw-contain-layout: ;
- --tw-contain-paint: ;
- --tw-contain-style: ;
-}
-
-::backdrop {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --tw-translate-x: 0;
- --tw-translate-y: 0;
- --tw-rotate: 0;
- --tw-skew-x: 0;
- --tw-skew-y: 0;
- --tw-scale-x: 1;
- --tw-scale-y: 1;
- --tw-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: rgb(59 130 246 / 0.5);
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
- --tw-contain-size: ;
- --tw-contain-layout: ;
- --tw-contain-paint: ;
- --tw-contain-style: ;
+.\!container {
+ width: 100% !important;
}
.container {
@@ -754,30 +758,50 @@ select {
}
@media (min-width: 640px) {
+ .\!container {
+ max-width: 640px !important;
+ }
+
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
+ .\!container {
+ max-width: 768px !important;
+ }
+
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
+ .\!container {
+ max-width: 1024px !important;
+ }
+
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
+ .\!container {
+ max-width: 1280px !important;
+ }
+
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
+ .\!container {
+ max-width: 1536px !important;
+ }
+
.container {
max-width: 1536px;
}
@@ -2157,6 +2181,10 @@ select {
justify-content: center;
}
+.visible {
+ visibility: visible;
+}
+
.static {
position: static;
}
@@ -2225,6 +2253,14 @@ select {
z-index: 60;
}
+.col-span-1 {
+ grid-column: span 1 / span 1;
+}
+
+.col-span-12 {
+ grid-column: span 12 / span 12;
+}
+
.col-span-2 {
grid-column: span 2 / span 2;
}
@@ -2237,11 +2273,26 @@ select {
grid-column: 1 / -1;
}
+.-mx-1\.5 {
+ margin-left: -0.375rem;
+ margin-right: -0.375rem;
+}
+
+.-my-1\.5 {
+ margin-top: -0.375rem;
+ margin-bottom: -0.375rem;
+}
+
.mx-1 {
margin-left: 0.25rem;
margin-right: 0.25rem;
}
+.mx-2 {
+ margin-left: 0.5rem;
+ margin-right: 0.5rem;
+}
+
.mx-4 {
margin-left: 1rem;
margin-right: 1rem;
@@ -2257,6 +2308,11 @@ select {
margin-right: auto;
}
+.my-auto {
+ margin-top: auto;
+ margin-bottom: auto;
+}
+
.-mb-px {
margin-bottom: -1px;
}
@@ -2265,6 +2321,10 @@ select {
margin-bottom: 0.25rem;
}
+.mb-10 {
+ margin-bottom: 2.5rem;
+}
+
.mb-12 {
margin-bottom: 3rem;
}
@@ -2289,34 +2349,66 @@ select {
margin-bottom: 2rem;
}
+.ml-0\.5 {
+ margin-left: 0.125rem;
+}
+
.ml-1 {
margin-left: 0.25rem;
}
+.ml-1\.5 {
+ margin-left: 0.375rem;
+}
+
.ml-2 {
margin-left: 0.5rem;
}
+.ml-3 {
+ margin-left: 0.75rem;
+}
+
.ml-6 {
margin-left: 1.5rem;
}
+.ml-auto {
+ margin-left: auto;
+}
+
.mr-1 {
margin-right: 0.25rem;
}
+.mr-1\.5 {
+ margin-right: 0.375rem;
+}
+
.mr-2 {
margin-right: 0.5rem;
}
+.mr-2\.5 {
+ margin-right: 0.625rem;
+}
+
.mr-3 {
margin-right: 0.75rem;
}
+.mt-0\.5 {
+ margin-top: 0.125rem;
+}
+
.mt-1 {
margin-top: 0.25rem;
}
+.mt-1\.5 {
+ margin-top: 0.375rem;
+}
+
.mt-2 {
margin-top: 0.5rem;
}
@@ -2401,6 +2493,10 @@ select {
height: 300px;
}
+.h-auto {
+ height: auto;
+}
+
.h-full {
height: 100%;
}
@@ -2413,10 +2509,6 @@ select {
max-height: 90vh;
}
-.min-h-\[calc\(100vh-16rem\)\] {
- min-height: calc(100vh - 16rem);
-}
-
.min-h-screen {
min-height: 100vh;
}
@@ -2462,6 +2554,10 @@ select {
width: 100%;
}
+.min-w-\[200px\] {
+ min-width: 200px;
+}
+
.max-w-2xl {
max-width: 42rem;
}
@@ -2474,10 +2570,18 @@ select {
max-width: 56rem;
}
+.max-w-6xl {
+ max-width: 72rem;
+}
+
.max-w-7xl {
max-width: 80rem;
}
+.max-w-\[800px\] {
+ max-width: 800px;
+}
+
.max-w-lg {
max-width: 32rem;
}
@@ -2490,10 +2594,18 @@ select {
max-width: none;
}
+.max-w-xs {
+ max-width: 20rem;
+}
+
.flex-1 {
flex: 1 1 0%;
}
+.flex-shrink-0 {
+ flex-shrink: 0;
+}
+
.flex-grow {
flex-grow: 1;
}
@@ -2513,6 +2625,11 @@ select {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
+.translate-y-full {
+ --tw-translate-y: 100%;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
.scale-100 {
--tw-scale-x: 1;
--tw-scale-y: 1;
@@ -2543,18 +2660,26 @@ select {
cursor: pointer;
}
-.resize {
- resize: both;
+.resize-none {
+ resize: none;
}
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
+.grid-cols-12 {
+ grid-template-columns: repeat(12, minmax(0, 1fr));
+}
+
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
+.grid-cols-3 {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
.grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
@@ -2591,10 +2716,18 @@ select {
justify-content: space-between;
}
+.gap-1 {
+ gap: 0.25rem;
+}
+
.gap-2 {
gap: 0.5rem;
}
+.gap-3 {
+ gap: 0.75rem;
+}
+
.gap-4 {
gap: 1rem;
}
@@ -2603,12 +2736,24 @@ select {
gap: 1.5rem;
}
+.space-x-1 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(0.25rem * var(--tw-space-x-reverse));
+ margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
}
+.space-x-3 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(0.75rem * var(--tw-space-x-reverse));
+ margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
@@ -2653,6 +2798,10 @@ select {
overflow: hidden;
}
+.overflow-y-auto {
+ overflow-y: auto;
+}
+
.rounded {
border-radius: 0.25rem;
}
@@ -2669,16 +2818,31 @@ select {
border-radius: 0.375rem;
}
+.rounded-b-lg {
+ border-bottom-right-radius: 0.5rem;
+ border-bottom-left-radius: 0.5rem;
+}
+
.rounded-l-lg {
border-top-left-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
+.rounded-l-md {
+ border-top-left-radius: 0.375rem;
+ border-bottom-left-radius: 0.375rem;
+}
+
.rounded-r-lg {
border-top-right-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
}
+.rounded-r-md {
+ border-top-right-radius: 0.375rem;
+ border-bottom-right-radius: 0.375rem;
+}
+
.rounded-t-lg {
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
@@ -2712,6 +2876,10 @@ select {
border-style: dashed;
}
+.border-blue-200\/50 {
+ border-color: rgb(191 219 254 / 0.5);
+}
+
.border-blue-500 {
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity));
@@ -2736,11 +2904,21 @@ select {
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
+.border-gray-700 {
+ --tw-border-opacity: 1;
+ border-color: rgb(55 65 81 / var(--tw-border-opacity));
+}
+
.border-green-500 {
--tw-border-opacity: 1;
border-color: rgb(34 197 94 / var(--tw-border-opacity));
}
+.border-primary {
+ --tw-border-opacity: 1;
+ border-color: rgb(79 70 229 / var(--tw-border-opacity));
+}
+
.border-red-400 {
--tw-border-opacity: 1;
border-color: rgb(248 113 113 / var(--tw-border-opacity));
@@ -2788,6 +2966,15 @@ select {
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
+.bg-blue-900\/40 {
+ background-color: rgb(30 58 138 / 0.4);
+}
+
+.bg-gray-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity));
+}
+
.bg-gray-200 {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
@@ -2798,6 +2985,25 @@ select {
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
}
+.bg-gray-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(107 114 128 / var(--tw-bg-opacity));
+}
+
+.bg-gray-800 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity));
+}
+
+.bg-gray-900 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(17 24 39 / var(--tw-bg-opacity));
+}
+
+.bg-gray-900\/80 {
+ background-color: rgb(17 24 39 / 0.8);
+}
+
.bg-green-100 {
--tw-bg-opacity: 1;
background-color: rgb(220 252 231 / var(--tw-bg-opacity));
@@ -2813,6 +3019,10 @@ select {
background-color: rgb(22 163 74 / var(--tw-bg-opacity));
}
+.bg-green-900\/40 {
+ background-color: rgb(20 83 45 / 0.4);
+}
+
.bg-red-100 {
--tw-bg-opacity: 1;
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
@@ -2828,6 +3038,10 @@ select {
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
}
+.bg-red-900\/40 {
+ background-color: rgb(127 29 29 / 0.4);
+}
+
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
@@ -2837,6 +3051,10 @@ select {
background-color: rgb(255 255 255 / 0.1);
}
+.bg-white\/80 {
+ background-color: rgb(255 255 255 / 0.8);
+}
+
.bg-white\/90 {
background-color: rgb(255 255 255 / 0.9);
}
@@ -2856,10 +3074,18 @@ select {
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
}
+.bg-yellow-900\/40 {
+ background-color: rgb(113 63 18 / 0.4);
+}
+
.bg-opacity-50 {
--tw-bg-opacity: 0.5;
}
+.bg-opacity-75 {
+ --tw-bg-opacity: 0.75;
+}
+
.bg-opacity-90 {
--tw-bg-opacity: 0.9;
}
@@ -2902,11 +3128,28 @@ select {
background-clip: text;
}
+.object-contain {
+ -o-object-fit: contain;
+ object-fit: contain;
+}
+
.object-cover {
-o-object-fit: cover;
object-fit: cover;
}
+.p-0\.5 {
+ padding: 0.125rem;
+}
+
+.p-1 {
+ padding: 0.25rem;
+}
+
+.p-1\.5 {
+ padding: 0.375rem;
+}
+
.p-2 {
padding: 0.5rem;
}
@@ -2972,6 +3215,11 @@ select {
padding-bottom: 0.5rem;
}
+.py-2\.5 {
+ padding-top: 0.625rem;
+ padding-bottom: 0.625rem;
+}
+
.py-3 {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
@@ -2996,6 +3244,10 @@ select {
padding-bottom: 1rem;
}
+.text-left {
+ text-align: left;
+}
+
.text-center {
text-align: center;
}
@@ -3015,6 +3267,11 @@ select {
line-height: 2.5rem;
}
+.text-5xl {
+ font-size: 3rem;
+ line-height: 1;
+}
+
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
@@ -3043,10 +3300,31 @@ select {
font-weight: 500;
}
+.font-normal {
+ font-weight: 400;
+}
+
.font-semibold {
font-weight: 600;
}
+.uppercase {
+ text-transform: uppercase;
+}
+
+.lowercase {
+ text-transform: lowercase;
+}
+
+.leading-tight {
+ line-height: 1.25;
+}
+
+.text-blue-400 {
+ --tw-text-opacity: 1;
+ color: rgb(96 165 250 / var(--tw-text-opacity));
+}
+
.text-blue-500 {
--tw-text-opacity: 1;
color: rgb(59 130 246 / var(--tw-text-opacity));
@@ -3057,16 +3335,21 @@ select {
color: rgb(37 99 235 / var(--tw-text-opacity));
}
-.text-blue-700 {
- --tw-text-opacity: 1;
- color: rgb(29 78 216 / var(--tw-text-opacity));
-}
-
.text-blue-800 {
--tw-text-opacity: 1;
color: rgb(30 64 175 / var(--tw-text-opacity));
}
+.text-blue-900 {
+ --tw-text-opacity: 1;
+ color: rgb(30 58 138 / var(--tw-text-opacity));
+}
+
+.text-gray-200 {
+ --tw-text-opacity: 1;
+ color: rgb(229 231 235 / var(--tw-text-opacity));
+}
+
.text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
@@ -3097,6 +3380,21 @@ select {
color: rgb(17 24 39 / var(--tw-text-opacity));
}
+.text-green-400 {
+ --tw-text-opacity: 1;
+ color: rgb(74 222 128 / var(--tw-text-opacity));
+}
+
+.text-green-600 {
+ --tw-text-opacity: 1;
+ color: rgb(22 163 74 / var(--tw-text-opacity));
+}
+
+.text-green-700 {
+ --tw-text-opacity: 1;
+ color: rgb(21 128 61 / var(--tw-text-opacity));
+}
+
.text-green-800 {
--tw-text-opacity: 1;
color: rgb(22 101 52 / var(--tw-text-opacity));
@@ -3107,6 +3405,11 @@ select {
color: rgb(79 70 229 / var(--tw-text-opacity));
}
+.text-red-400 {
+ --tw-text-opacity: 1;
+ color: rgb(248 113 113 / var(--tw-text-opacity));
+}
+
.text-red-500 {
--tw-text-opacity: 1;
color: rgb(239 68 68 / var(--tw-text-opacity));
@@ -3127,6 +3430,11 @@ select {
color: rgb(153 27 27 / var(--tw-text-opacity));
}
+.text-sky-900 {
+ --tw-text-opacity: 1;
+ color: rgb(12 74 110 / var(--tw-text-opacity));
+}
+
.text-transparent {
color: transparent;
}
@@ -3151,16 +3459,16 @@ select {
color: rgb(202 138 4 / var(--tw-text-opacity));
}
+.text-yellow-700 {
+ --tw-text-opacity: 1;
+ color: rgb(161 98 7 / var(--tw-text-opacity));
+}
+
.text-yellow-800 {
--tw-text-opacity: 1;
color: rgb(133 77 14 / var(--tw-text-opacity));
}
-.text-green-600 {
- --tw-text-opacity: 1;
- color: rgb(22 163 74 / var(--tw-text-opacity));
-}
-
.opacity-0 {
opacity: 0;
}
@@ -3224,6 +3532,12 @@ select {
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
+.backdrop-blur-sm {
+ --tw-backdrop-blur: blur(4px);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
+ backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
+}
+
.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
@@ -3250,6 +3564,12 @@ select {
transition-duration: 150ms;
}
+.transition-shadow {
+ transition-property: box-shadow;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 150ms;
+}
+
.transition-transform {
transition-property: transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -3310,22 +3630,33 @@ select {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
-.hover\:translate-x-2:hover {
- --tw-translate-x: 0.5rem;
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
-}
-
.hover\:scale-105:hover {
--tw-scale-x: 1.05;
--tw-scale-y: 1.05;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
+.hover\:scale-\[1\.02\]:hover {
+ --tw-scale-x: 1.02;
+ --tw-scale-y: 1.02;
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
+}
+
.hover\:border-gray-300:hover {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
}
+.hover\:bg-blue-100:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(219 234 254 / var(--tw-bg-opacity));
+}
+
+.hover\:bg-blue-500:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(59 130 246 / var(--tw-bg-opacity));
+}
+
.hover\:bg-blue-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
@@ -3336,6 +3667,11 @@ select {
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
}
+.hover\:bg-gray-200:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity));
+}
+
.hover\:bg-gray-300:hover {
--tw-bg-opacity: 1;
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
@@ -3346,9 +3682,9 @@ select {
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
}
-.hover\:bg-green-700:hover {
+.hover\:bg-green-500:hover {
--tw-bg-opacity: 1;
- background-color: rgb(21 128 61 / var(--tw-bg-opacity));
+ background-color: rgb(34 197 94 / var(--tw-bg-opacity));
}
.hover\:bg-red-50:hover {
@@ -3356,6 +3692,11 @@ select {
background-color: rgb(254 242 242 / var(--tw-bg-opacity));
}
+.hover\:bg-red-500:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+}
+
.hover\:bg-red-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(185 28 28 / var(--tw-bg-opacity));
@@ -3365,6 +3706,11 @@ select {
background-color: rgb(255 255 255 / 0.2);
}
+.hover\:bg-yellow-500:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(234 179 8 / var(--tw-bg-opacity));
+}
+
.hover\:bg-yellow-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
@@ -3385,6 +3731,16 @@ select {
color: rgb(29 78 216 / var(--tw-text-opacity));
}
+.hover\:text-blue-800:hover {
+ --tw-text-opacity: 1;
+ color: rgb(30 64 175 / var(--tw-text-opacity));
+}
+
+.hover\:text-blue-900:hover {
+ --tw-text-opacity: 1;
+ color: rgb(30 58 138 / var(--tw-text-opacity));
+}
+
.hover\:text-gray-300:hover {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
@@ -3400,6 +3756,11 @@ select {
color: rgb(55 65 81 / var(--tw-text-opacity));
}
+.hover\:text-gray-900:hover {
+ --tw-text-opacity: 1;
+ color: rgb(17 24 39 / var(--tw-text-opacity));
+}
+
.hover\:text-primary:hover {
--tw-text-opacity: 1;
color: rgb(79 70 229 / var(--tw-text-opacity));
@@ -3409,10 +3770,27 @@ select {
color: rgb(79 70 229 / 0.8);
}
+.hover\:text-sky-800:hover {
+ --tw-text-opacity: 1;
+ color: rgb(7 89 133 / var(--tw-text-opacity));
+}
+
.hover\:underline:hover {
text-decoration-line: underline;
}
+.hover\:shadow-md:hover {
+ --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
+.hover\:shadow-xl:hover {
+ --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+ --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
+}
+
.focus\:border-blue-500:focus {
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity));
@@ -3446,6 +3824,10 @@ select {
--tw-ring-offset-width: 2px;
}
+.disabled\:opacity-50:disabled {
+ opacity: 0.5;
+}
+
.group:hover .group-hover\:scale-105 {
--tw-scale-x: 1.05;
--tw-scale-y: 1.05;
@@ -3456,6 +3838,10 @@ select {
opacity: 1;
}
+.dark\:border-blue-700\/50:is(.dark *) {
+ border-color: rgb(29 78 216 / 0.5);
+}
+
.dark\:border-gray-600:is(.dark *) {
--tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity));
@@ -3484,13 +3870,12 @@ select {
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
}
-.dark\:bg-blue-900:is(.dark *) {
- --tw-bg-opacity: 1;
- background-color: rgb(30 58 138 / var(--tw-bg-opacity));
+.dark\:bg-blue-900\/30:is(.dark *) {
+ background-color: rgb(30 58 138 / 0.3);
}
-.dark\:bg-blue-900\/50:is(.dark *) {
- background-color: rgb(30 58 138 / 0.5);
+.dark\:bg-blue-900\/40:is(.dark *) {
+ background-color: rgb(30 58 138 / 0.4);
}
.dark\:bg-gray-600:is(.dark *) {
@@ -3516,19 +3901,23 @@ select {
background-color: rgb(31 41 55 / 0.9);
}
+.dark\:bg-gray-900:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(17 24 39 / var(--tw-bg-opacity));
+}
+
+.dark\:bg-gray-900\/80:is(.dark *) {
+ background-color: rgb(17 24 39 / 0.8);
+}
+
.dark\:bg-green-200:is(.dark *) {
--tw-bg-opacity: 1;
background-color: rgb(187 247 208 / var(--tw-bg-opacity));
}
-.dark\:bg-green-500:is(.dark *) {
+.dark\:bg-green-700:is(.dark *) {
--tw-bg-opacity: 1;
- background-color: rgb(34 197 94 / var(--tw-bg-opacity));
-}
-
-.dark\:bg-green-900:is(.dark *) {
- --tw-bg-opacity: 1;
- background-color: rgb(20 83 45 / var(--tw-bg-opacity));
+ background-color: rgb(21 128 61 / var(--tw-bg-opacity));
}
.dark\:bg-red-200:is(.dark *) {
@@ -3536,14 +3925,14 @@ select {
background-color: rgb(254 202 202 / var(--tw-bg-opacity));
}
-.dark\:bg-red-500:is(.dark *) {
+.dark\:bg-red-700:is(.dark *) {
--tw-bg-opacity: 1;
- background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+ background-color: rgb(185 28 28 / var(--tw-bg-opacity));
}
-.dark\:bg-red-900:is(.dark *) {
+.dark\:bg-yellow-200:is(.dark *) {
--tw-bg-opacity: 1;
- background-color: rgb(127 29 29 / var(--tw-bg-opacity));
+ background-color: rgb(254 240 138 / var(--tw-bg-opacity));
}
.dark\:bg-yellow-400\/30:is(.dark *) {
@@ -3555,9 +3944,13 @@ select {
background-color: rgb(202 138 4 / var(--tw-bg-opacity));
}
-.dark\:bg-yellow-900:is(.dark *) {
+.dark\:bg-yellow-700:is(.dark *) {
--tw-bg-opacity: 1;
- background-color: rgb(113 63 18 / var(--tw-bg-opacity));
+ background-color: rgb(161 98 7 / var(--tw-bg-opacity));
+}
+
+.dark\:bg-yellow-900\/50:is(.dark *) {
+ background-color: rgb(113 63 18 / 0.5);
}
.dark\:from-gray-950:is(.dark *) {
@@ -3580,6 +3973,11 @@ select {
color: rgb(191 219 254 / var(--tw-text-opacity));
}
+.dark\:text-blue-300:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(147 197 253 / var(--tw-text-opacity));
+}
+
.dark\:text-blue-400:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(96 165 250 / var(--tw-text-opacity));
@@ -3620,9 +4018,19 @@ select {
color: rgb(75 85 99 / var(--tw-text-opacity));
}
-.dark\:text-green-200:is(.dark *) {
+.dark\:text-green-400:is(.dark *) {
--tw-text-opacity: 1;
- color: rgb(187 247 208 / var(--tw-text-opacity));
+ color: rgb(74 222 128 / var(--tw-text-opacity));
+}
+
+.dark\:text-green-50:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(240 253 244 / var(--tw-text-opacity));
+}
+
+.dark\:text-green-800:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(22 101 52 / var(--tw-text-opacity));
}
.dark\:text-green-900:is(.dark *) {
@@ -3630,11 +4038,6 @@ select {
color: rgb(20 83 45 / var(--tw-text-opacity));
}
-.dark\:text-red-200:is(.dark *) {
- --tw-text-opacity: 1;
- color: rgb(254 202 202 / var(--tw-text-opacity));
-}
-
.dark\:text-red-400:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(248 113 113 / var(--tw-text-opacity));
@@ -3650,6 +4053,11 @@ select {
color: rgb(127 29 29 / var(--tw-text-opacity));
}
+.dark\:text-sky-400:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(56 189 248 / var(--tw-text-opacity));
+}
+
.dark\:text-white:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
@@ -3675,9 +4083,9 @@ select {
color: rgb(254 252 232 / var(--tw-text-opacity));
}
-.dark\:text-green-400:is(.dark *) {
+.dark\:text-yellow-800:is(.dark *) {
--tw-text-opacity: 1;
- color: rgb(74 222 128 / var(--tw-text-opacity));
+ color: rgb(133 77 14 / var(--tw-text-opacity));
}
.dark\:ring-1:is(.dark *) {
@@ -3694,6 +4102,11 @@ select {
--tw-ring-color: rgb(250 204 21 / 0.3);
}
+.dark\:hover\:bg-blue-500:hover:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(59 130 246 / var(--tw-bg-opacity));
+}
+
.dark\:hover\:bg-blue-600:hover:is(.dark *) {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
@@ -3704,6 +4117,10 @@ select {
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
}
+.dark\:hover\:bg-blue-900\/40:hover:is(.dark *) {
+ background-color: rgb(30 58 138 / 0.4);
+}
+
.dark\:hover\:bg-gray-500:hover:is(.dark *) {
--tw-bg-opacity: 1;
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
@@ -3733,6 +4150,11 @@ select {
background-color: rgb(127 29 29 / 0.2);
}
+.dark\:hover\:bg-yellow-600:hover:is(.dark *) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(202 138 4 / var(--tw-bg-opacity));
+}
+
.dark\:hover\:text-blue-300:hover:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(147 197 253 / var(--tw-text-opacity));
@@ -3743,6 +4165,11 @@ select {
color: rgb(96 165 250 / var(--tw-text-opacity));
}
+.dark\:hover\:text-gray-200:hover:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(229 231 235 / var(--tw-text-opacity));
+}
+
.dark\:hover\:text-gray-300:hover:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
@@ -3753,11 +4180,61 @@ select {
color: rgb(79 70 229 / var(--tw-text-opacity));
}
+.dark\:hover\:text-sky-300:hover:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(125 211 252 / var(--tw-text-opacity));
+}
+
+.dark\:hover\:text-white:hover:is(.dark *) {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
@media (min-width: 640px) {
+ .sm\:col-span-3 {
+ grid-column: span 3 / span 3;
+ }
+
+ .sm\:col-span-4 {
+ grid-column: span 4 / span 4;
+ }
+
+ .sm\:col-span-8 {
+ grid-column: span 8 / span 8;
+ }
+
+ .sm\:col-span-9 {
+ grid-column: span 9 / span 9;
+ }
+
+ .sm\:mb-16 {
+ margin-bottom: 4rem;
+ }
+
+ .sm\:grid-cols-12 {
+ grid-template-columns: repeat(12, minmax(0, 1fr));
+ }
+
.sm\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
+ .sm\:grid-cols-3 {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .sm\:flex-row {
+ flex-direction: row;
+ }
+
+ .sm\:items-end {
+ align-items: flex-end;
+ }
+
+ .sm\:items-center {
+ align-items: center;
+ }
+
.sm\:space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
@@ -3769,11 +4246,63 @@ select {
margin-right: calc(1.5rem * var(--tw-space-x-reverse));
margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse)));
}
+
+ .sm\:px-6 {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ }
+
+ .sm\:text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ }
+
+ .sm\:text-3xl {
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+ }
+
+ .sm\:text-base {
+ font-size: 1rem;
+ line-height: 1.5rem;
+ }
+
+ .sm\:text-lg {
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+ }
+
+ .sm\:text-sm {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ }
+
+ .sm\:text-xl {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ }
+
+ .sm\:text-xs {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ }
}
@media (min-width: 768px) {
- .md\:mt-0 {
- margin-top: 0px;
+ .md\:col-span-1 {
+ grid-column: span 1 / span 1;
+ }
+
+ .md\:col-span-2 {
+ grid-column: span 2 / span 2;
+ }
+
+ .md\:mb-8 {
+ margin-bottom: 2rem;
+ }
+
+ .md\:h-\[140px\] {
+ height: 140px;
}
.md\:grid-cols-2 {
@@ -3796,6 +4325,17 @@ select {
align-items: center;
}
+ .md\:space-x-3 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(0.75rem * var(--tw-space-x-reverse));
+ margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
+ }
+
+ .md\:py-12 {
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+ }
+
.md\:text-2xl {
font-size: 1.5rem;
line-height: 2rem;
@@ -3805,6 +4345,11 @@ select {
font-size: 3rem;
line-height: 1;
}
+
+ .md\:text-lg {
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+ }
}
@media (min-width: 1024px) {
@@ -3816,14 +4361,6 @@ select {
grid-column: span 2 / span 2;
}
- .lg\:mb-0 {
- margin-bottom: 0px;
- }
-
- .lg\:mr-6 {
- margin-right: 1.5rem;
- }
-
.lg\:flex {
display: flex;
}
@@ -3832,14 +4369,6 @@ select {
display: none;
}
- .lg\:w-1\/3 {
- width: 33.333333%;
- }
-
- .lg\:flex-1 {
- flex: 1 1 0%;
- }
-
.lg\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
@@ -3848,20 +4377,38 @@ select {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
- .lg\:flex-row {
- flex-direction: row;
+ .lg\:px-8 {
+ padding-left: 2rem;
+ padding-right: 2rem;
}
- .lg\:items-start {
- align-items: flex-start;
+ .lg\:text-2xl {
+ font-size: 1.5rem;
+ line-height: 2rem;
}
- .lg\:justify-between {
- justify-content: space-between;
+ .lg\:text-3xl {
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+ }
+
+ .lg\:text-4xl {
+ font-size: 2.25rem;
+ line-height: 2.5rem;
}
.lg\:text-6xl {
font-size: 3.75rem;
line-height: 1;
}
+
+ .lg\:text-base {
+ font-size: 1rem;
+ line-height: 1.5rem;
+ }
+
+ .lg\:text-lg {
+ font-size: 1.125rem;
+ line-height: 1.75rem;
+ }
}
diff --git a/staticfiles/gis/css/ol3.css b/staticfiles/gis/css/ol3.css
new file mode 100644
index 00000000..ac8f0a8e
--- /dev/null
+++ b/staticfiles/gis/css/ol3.css
@@ -0,0 +1,39 @@
+.dj_map_wrapper {
+ position: relative;
+ float: left;
+}
+html[dir="rtl"] .dj_map_wrapper {
+ float: right;
+}
+
+.switch-type {
+ background-repeat: no-repeat;
+ cursor: pointer;
+ top: 0.5em;
+ width: 22px;
+ height: 20px;
+}
+
+.type-Point {
+ background-image: url("../img/draw_point_off.svg");
+ right: 5px;
+}
+.type-Point.type-active {
+ background-image: url("../img/draw_point_on.svg");
+}
+
+.type-LineString {
+ background-image: url("../img/draw_line_off.svg");
+ right: 30px;
+}
+.type-LineString.type-active {
+ background-image: url("../img/draw_line_on.svg");
+}
+
+.type-Polygon {
+ background-image: url("../img/draw_polygon_off.svg");
+ right: 55px;
+}
+.type-Polygon.type-active {
+ background-image: url("../img/draw_polygon_on.svg");
+}
diff --git a/staticfiles/gis/img/draw_line_off.svg b/staticfiles/gis/img/draw_line_off.svg
new file mode 100644
index 00000000..c403af60
--- /dev/null
+++ b/staticfiles/gis/img/draw_line_off.svg
@@ -0,0 +1 @@
+
diff --git a/staticfiles/gis/img/draw_line_on.svg b/staticfiles/gis/img/draw_line_on.svg
new file mode 100644
index 00000000..5eb0d6d1
--- /dev/null
+++ b/staticfiles/gis/img/draw_line_on.svg
@@ -0,0 +1 @@
+
diff --git a/staticfiles/gis/img/draw_point_off.svg b/staticfiles/gis/img/draw_point_off.svg
new file mode 100644
index 00000000..5a290342
--- /dev/null
+++ b/staticfiles/gis/img/draw_point_off.svg
@@ -0,0 +1 @@
+
diff --git a/staticfiles/gis/img/draw_point_on.svg b/staticfiles/gis/img/draw_point_on.svg
new file mode 100644
index 00000000..f006524a
--- /dev/null
+++ b/staticfiles/gis/img/draw_point_on.svg
@@ -0,0 +1 @@
+
diff --git a/staticfiles/gis/img/draw_polygon_off.svg b/staticfiles/gis/img/draw_polygon_off.svg
new file mode 100644
index 00000000..b02dd357
--- /dev/null
+++ b/staticfiles/gis/img/draw_polygon_off.svg
@@ -0,0 +1 @@
+
diff --git a/staticfiles/gis/img/draw_polygon_on.svg b/staticfiles/gis/img/draw_polygon_on.svg
new file mode 100644
index 00000000..303a02a2
--- /dev/null
+++ b/staticfiles/gis/img/draw_polygon_on.svg
@@ -0,0 +1 @@
+
diff --git a/staticfiles/gis/js/OLMapWidget.js b/staticfiles/gis/js/OLMapWidget.js
new file mode 100644
index 00000000..a545036c
--- /dev/null
+++ b/staticfiles/gis/js/OLMapWidget.js
@@ -0,0 +1,233 @@
+/* global ol */
+'use strict';
+class GeometryTypeControl extends ol.control.Control {
+ // Map control to switch type when geometry type is unknown
+ constructor(opt_options) {
+ const options = opt_options || {};
+
+ const element = document.createElement('div');
+ element.className = 'switch-type type-' + options.type + ' ol-control ol-unselectable';
+ if (options.active) {
+ element.classList.add("type-active");
+ }
+
+ super({
+ element: element,
+ target: options.target
+ });
+ const self = this;
+ const switchType = function(e) {
+ e.preventDefault();
+ if (options.widget.currentGeometryType !== self) {
+ options.widget.map.removeInteraction(options.widget.interactions.draw);
+ options.widget.interactions.draw = new ol.interaction.Draw({
+ features: options.widget.featureCollection,
+ type: options.type
+ });
+ options.widget.map.addInteraction(options.widget.interactions.draw);
+ options.widget.currentGeometryType.element.classList.remove('type-active');
+ options.widget.currentGeometryType = self;
+ element.classList.add("type-active");
+ }
+ };
+
+ element.addEventListener('click', switchType, false);
+ element.addEventListener('touchstart', switchType, false);
+ }
+}
+
+// TODO: allow deleting individual features (#8972)
+class MapWidget {
+ constructor(options) {
+ this.map = null;
+ this.interactions = {draw: null, modify: null};
+ this.typeChoices = false;
+ this.ready = false;
+
+ // Default options
+ this.options = {
+ default_lat: 0,
+ default_lon: 0,
+ default_zoom: 12,
+ is_collection: options.geom_name.includes('Multi') || options.geom_name.includes('Collection')
+ };
+
+ // Altering using user-provided options
+ for (const property in options) {
+ if (Object.hasOwn(options, property)) {
+ this.options[property] = options[property];
+ }
+ }
+ if (!options.base_layer) {
+ this.options.base_layer = new ol.layer.Tile({source: new ol.source.OSM()});
+ }
+
+ this.map = this.createMap();
+ this.featureCollection = new ol.Collection();
+ this.featureOverlay = new ol.layer.Vector({
+ map: this.map,
+ source: new ol.source.Vector({
+ features: this.featureCollection,
+ useSpatialIndex: false // improve performance
+ }),
+ updateWhileAnimating: true, // optional, for instant visual feedback
+ updateWhileInteracting: true // optional, for instant visual feedback
+ });
+
+ // Populate and set handlers for the feature container
+ const self = this;
+ this.featureCollection.on('add', function(event) {
+ const feature = event.element;
+ feature.on('change', function() {
+ self.serializeFeatures();
+ });
+ if (self.ready) {
+ self.serializeFeatures();
+ if (!self.options.is_collection) {
+ self.disableDrawing(); // Only allow one feature at a time
+ }
+ }
+ });
+
+ const initial_value = document.getElementById(this.options.id).value;
+ if (initial_value) {
+ const jsonFormat = new ol.format.GeoJSON();
+ const features = jsonFormat.readFeatures('{"type": "Feature", "geometry": ' + initial_value + '}');
+ const extent = ol.extent.createEmpty();
+ features.forEach(function(feature) {
+ this.featureOverlay.getSource().addFeature(feature);
+ ol.extent.extend(extent, feature.getGeometry().getExtent());
+ }, this);
+ // Center/zoom the map
+ this.map.getView().fit(extent, {minResolution: 1});
+ } else {
+ this.map.getView().setCenter(this.defaultCenter());
+ }
+ this.createInteractions();
+ if (initial_value && !this.options.is_collection) {
+ this.disableDrawing();
+ }
+ const clearNode = document.getElementById(this.map.getTarget()).nextElementSibling;
+ if (clearNode.classList.contains('clear_features')) {
+ clearNode.querySelector('a').addEventListener('click', (ev) => {
+ ev.preventDefault();
+ self.clearFeatures();
+ });
+ }
+ this.ready = true;
+ }
+
+ createMap() {
+ return new ol.Map({
+ target: this.options.map_id,
+ layers: [this.options.base_layer],
+ view: new ol.View({
+ zoom: this.options.default_zoom
+ })
+ });
+ }
+
+ createInteractions() {
+ // Initialize the modify interaction
+ this.interactions.modify = new ol.interaction.Modify({
+ features: this.featureCollection,
+ deleteCondition: function(event) {
+ return ol.events.condition.shiftKeyOnly(event) &&
+ ol.events.condition.singleClick(event);
+ }
+ });
+
+ // Initialize the draw interaction
+ let geomType = this.options.geom_name;
+ if (geomType === "Geometry" || geomType === "GeometryCollection") {
+ // Default to Point, but create icons to switch type
+ geomType = "Point";
+ this.currentGeometryType = new GeometryTypeControl({widget: this, type: "Point", active: true});
+ this.map.addControl(this.currentGeometryType);
+ this.map.addControl(new GeometryTypeControl({widget: this, type: "LineString", active: false}));
+ this.map.addControl(new GeometryTypeControl({widget: this, type: "Polygon", active: false}));
+ this.typeChoices = true;
+ }
+ this.interactions.draw = new ol.interaction.Draw({
+ features: this.featureCollection,
+ type: geomType
+ });
+
+ this.map.addInteraction(this.interactions.draw);
+ this.map.addInteraction(this.interactions.modify);
+ }
+
+ defaultCenter() {
+ const center = [this.options.default_lon, this.options.default_lat];
+ if (this.options.map_srid) {
+ return ol.proj.transform(center, 'EPSG:4326', this.map.getView().getProjection());
+ }
+ return center;
+ }
+
+ enableDrawing() {
+ this.interactions.draw.setActive(true);
+ if (this.typeChoices) {
+ // Show geometry type icons
+ const divs = document.getElementsByClassName("switch-type");
+ for (let i = 0; i !== divs.length; i++) {
+ divs[i].style.visibility = "visible";
+ }
+ }
+ }
+
+ disableDrawing() {
+ if (this.interactions.draw) {
+ this.interactions.draw.setActive(false);
+ if (this.typeChoices) {
+ // Hide geometry type icons
+ const divs = document.getElementsByClassName("switch-type");
+ for (let i = 0; i !== divs.length; i++) {
+ divs[i].style.visibility = "hidden";
+ }
+ }
+ }
+ }
+
+ clearFeatures() {
+ this.featureCollection.clear();
+ // Empty textarea widget
+ document.getElementById(this.options.id).value = '';
+ this.enableDrawing();
+ }
+
+ serializeFeatures() {
+ // Three use cases: GeometryCollection, multigeometries, and single geometry
+ let geometry = null;
+ const features = this.featureOverlay.getSource().getFeatures();
+ if (this.options.is_collection) {
+ if (this.options.geom_name === "GeometryCollection") {
+ const geometries = [];
+ for (let i = 0; i < features.length; i++) {
+ geometries.push(features[i].getGeometry());
+ }
+ geometry = new ol.geom.GeometryCollection(geometries);
+ } else {
+ geometry = features[0].getGeometry().clone();
+ for (let j = 1; j < features.length; j++) {
+ switch (geometry.getType()) {
+ case "MultiPoint":
+ geometry.appendPoint(features[j].getGeometry().getPoint(0));
+ break;
+ case "MultiLineString":
+ geometry.appendLineString(features[j].getGeometry().getLineString(0));
+ break;
+ case "MultiPolygon":
+ geometry.appendPolygon(features[j].getGeometry().getPolygon(0));
+ }
+ }
+ }
+ } else {
+ if (features[0]) {
+ geometry = features[0].getGeometry();
+ }
+ }
+ const jsonFormat = new ol.format.GeoJSON();
+ document.getElementById(this.options.id).value = jsonFormat.writeGeometry(geometry);
+ }
+}
diff --git a/staticfiles/js/photo-gallery.js b/staticfiles/js/photo-gallery.js
new file mode 100644
index 00000000..2705dcab
--- /dev/null
+++ b/staticfiles/js/photo-gallery.js
@@ -0,0 +1,91 @@
+document.addEventListener('alpine:init', () => {
+ Alpine.data('photoDisplay', ({ photos, contentType, objectId, csrfToken, uploadUrl }) => ({
+ photos,
+ fullscreenPhoto: null,
+ uploading: false,
+ uploadProgress: 0,
+ error: null,
+ showSuccess: false,
+
+ showFullscreen(photo) {
+ this.fullscreenPhoto = photo;
+ },
+
+ async handleFileSelect(event) {
+ const files = Array.from(event.target.files);
+ if (!files.length) {
+ return;
+ }
+
+ this.uploading = true;
+ this.uploadProgress = 0;
+ this.error = null;
+ this.showSuccess = false;
+
+ const totalFiles = files.length;
+ let completedFiles = 0;
+
+ for (const file of files) {
+ const formData = new FormData();
+ formData.append('image', file);
+ formData.append('app_label', contentType.split('.')[0]);
+ formData.append('model', contentType.split('.')[1]);
+ formData.append('object_id', objectId);
+
+ try {
+ const response = await fetch(uploadUrl, {
+ method: 'POST',
+ headers: {
+ 'X-CSRFToken': csrfToken,
+ },
+ body: formData
+ });
+
+ if (!response.ok) {
+ const data = await response.json();
+ throw new Error(data.error || 'Upload failed');
+ }
+
+ const photo = await response.json();
+ this.photos.push(photo);
+ completedFiles++;
+ this.uploadProgress = (completedFiles / totalFiles) * 100;
+ } catch (err) {
+ this.error = err.message || 'Failed to upload photo. Please try again.';
+ console.error('Upload error:', err);
+ break;
+ }
+ }
+
+ this.uploading = false;
+ event.target.value = ''; // Reset file input
+
+ if (!this.error) {
+ this.showSuccess = true;
+ setTimeout(() => {
+ this.showSuccess = false;
+ }, 3000);
+ }
+ },
+
+ async sharePhoto(photo) {
+ if (navigator.share) {
+ try {
+ await navigator.share({
+ title: photo.caption || 'Shared photo',
+ url: photo.url
+ });
+ } catch (err) {
+ if (err.name !== 'AbortError') {
+ console.error('Error sharing:', err);
+ }
+ }
+ } else {
+ // Fallback: copy URL to clipboard
+ navigator.clipboard.writeText(photo.url)
+ .then(() => alert('Photo URL copied to clipboard!'))
+ .catch(err => console.error('Error copying to clipboard:', err));
+ }
+ }
+ }));
+});