fixed syntax and view page issues

This commit is contained in:
StormyCloud
2025-06-24 16:22:48 -05:00
parent 4dacd8863d
commit 79d715f3bd
9 changed files with 1651 additions and 1542 deletions

View File

@ -1,9 +1,9 @@
#i2p-drop
to do:
pastebin increase width
shrink logo
synxtax color not working
add syntax change post?
#i2p-drop
to do:
pastebin increase width
shrink logo
synxtax color not working
add syntax change post?
need owner view pages with Paste Created Successfully and "customer" view pages

1193
app.py

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
/*
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
*/
/* 1. Preflight (Tailwind's base styles) */
*,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}::before,::after{--tw-content:''}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}
/* 2. Utility Classes */
*,::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 / .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: }::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 / .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: }
.h-6{height:1.5rem}.h-12{height:3rem}.h-20{height:5rem}.h-24{height:6rem}.w-6{width:1.5rem}.w-12{width:3rem}.w-20{width:5rem}.w-24{width:6rem}.w-full{width:100%}.max-w-2xl{max-width:42rem}.max-w-4xl{max-w:56rem}.flex-shrink-0{flex-shrink:0}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.mt-16{margin-top:4rem}.inline-block{display:inline-block}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.list-disc{list-style-type:disc}.list-inside{list-style-position:inside}.min-h-screen{min-height:100vh}.max-h-60vh{max-height:60vh}.object-contain{object-fit:contain}.justify-center{justify-content:center}.items-center{align-items:center}.gap-8{gap:2rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.overflow-x-auto{overflow-x:auto}.overflow-hidden{overflow:hidden}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99 / var(--tw-border-opacity))}.border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81 / var(--tw-border-opacity))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81 / var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246 / var(--tw-bg-opacity))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-6{padding-top:1.5rem}.text-center{text-align:center}.font-sans{font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}.text-xs{font-size:.75rem;line-height:1rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.font-mono{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}.text-white{--tw-text-opacity:1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-blue-300{--tw-text-opacity:1;color:rgb(147 197 253 / var(--tw-text-opacity))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250 / var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}.shadow-lg{--tw-shadow:0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)}.ring-blue-500{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246 / var(--tw-ring-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255 / var(--tw-text-opacity))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:shadow-outline:focus{box-shadow:0 0 0 3px rgb(96 165 250 / .5);outline:none}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246 / var(--tw-ring-opacity))}@media (min-width: 768px){.md\:grid-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr))}}
/*
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
*/
/* 1. Preflight (Tailwind's base styles) */
*,::before,::after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}::before,::after{--tw-content:''}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}
/* 2. Utility Classes */
*,::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 / .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: }::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 / .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: }
.h-6{height:1.5rem}.h-12{height:3rem}.h-20{height:5rem}.h-24{height:6rem}.w-6{width:1.5rem}.w-12{width:3rem}.w-20{width:5rem}.w-24{width:6rem}.w-full{width:100%}.max-w-2xl{max-width:42rem}.max-w-4xl{max-w:56rem}.flex-shrink-0{flex-shrink:0}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.mt-16{margin-top:4rem}.inline-block{display:inline-block}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.list-disc{list-style-type:disc}.list-inside{list-style-position:inside}.min-h-screen{min-height:100vh}.max-h-60vh{max-height:60vh}.object-contain{object-fit:contain}.justify-center{justify-content:center}.items-center{align-items:center}.gap-8{gap:2rem}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.overflow-x-auto{overflow-x:auto}.overflow-hidden{overflow:hidden}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99 / var(--tw-border-opacity))}.border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81 / var(--tw-border-opacity))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81 / var(--tw-bg-opacity))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246 / var(--tw-bg-opacity))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.pt-6{padding-top:1.5rem}.text-center{text-align:center}.font-sans{font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}.text-xs{font-size:.75rem;line-height:1rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.font-mono{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace}.text-white{--tw-text-opacity:1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-blue-300{--tw-text-opacity:1;color:rgb(147 197 253 / var(--tw-text-opacity))}.text-blue-400{--tw-text-opacity:1;color:rgb(96 165 250 / var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity))}.shadow-lg{--tw-shadow:0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)}.ring-blue-500{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246 / var(--tw-ring-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255 / var(--tw-text-opacity))}.hover\:text-blue-300:hover{--tw-text-opacity:1;color:rgb(147 197 253 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:shadow-outline:focus{box-shadow:0 0 0 3px rgb(96 165 250 / .5);outline:none}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246 / var(--tw-ring-opacity))}@media (min-width: 768px){.md\:grid-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr))}}

View File

@ -1,73 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
viewBox="0 135 1500 636"
style="enable-background:new 0 135 1500 636;"
xml:space="preserve"
>
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:url(#SVGID_1_);}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
.st4{fill:url(#SVGID_4_);}
.st5{fill:url(#SVGID_5_);}
.st6{fill:#006BCC;}
</style>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="378.2952" y1="456.0995" x2="378.2952" y2="131.6513">
<stop offset="0" style="stop-color:#006FCD"/>
<stop offset="1" style="stop-color:#00A9F2"/>
</linearGradient>
<path class="st1" d="M600.3,324.7c-11.5-34.2-45.1-41.5-45.1-41.5c-13.7-62-73.4-64.4-73.4-64.4c-31.4-91.3-119.5-87.1-119.5-87.1
C260.2,135,242.6,223,242.6,223c-92.5,15.4-90.1,94.4-90.1,94.4c3.5,93,81.8,100.3,81.8,100.3h33.5c1,18.1,8.6,27.7,15.9,32.8
c5.4,3.8,11.9,5.6,18.5,5.6h162c3.5,0,7-0.3,10.4-1.2c27.6-7.2,26.7-37.2,26.7-37.2h39.4c24.4-3.3,39.5-14.9,48.7-27
C604,372,607.9,347.1,600.3,324.7z M489.4,415.4c0,15.4-12.5,27.8-27.8,27.8H304.2c-12.1,0-21.9-9.8-21.9-21.9V228.7
c0-15.5,12.6-28.1,28.1-28.1H405c9.8,0,19.2,3.7,26.4,10.4l43.8,40.9c9.1,8.5,14.3,20.4,14.3,32.8V415.4z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="432.1597" y1="283.199" x2="432.1597" y2="227.7854">
<stop offset="0" style="stop-color:#008CE2"/>
<stop offset="0.9017" style="stop-color:#0098E8"/>
</linearGradient>
<path class="st2" d="M414.1,229.4l43.6,40.8c5,4.7,1.7,13.1-5.2,13.1h-39.4c-4.9,0-8.9-4-8.9-8.9v-40.6
C404.2,228.5,410.4,225.9,414.1,229.4z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="380.3181" y1="417.6981" x2="380.3181" y2="294.9461">
<stop offset="0" style="stop-color:#006FCE"/>
<stop offset="0.9017" style="stop-color:#0082D9"/>
</linearGradient>
<path class="st3" d="M425.9,376.3L385,415.6c-1.4,1.3-3.2,2.1-5.2,2.1c-2,0-3.9-0.8-5.3-2.2l-39.9-40.1c-1.8-1.8-2.6-4.1-2.6-6.4
c0-2.7,1.2-5.4,3.5-7.2c3.7-2.9,8.9-2.5,12.2,0.9l23.6,24.6v-82.7c0-5.3,4.3-9.6,9.6-9.6h0c5.3,0,9.6,4.3,9.6,9.6v80.6l23-22
c1.8-1.7,4-2.5,6.3-2.5c2.5,0,5,1,6.8,3C429.6,367.4,429.4,372.9,425.9,376.3z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="490.3123" y1="553.3344" x2="490.3123" y2="461.3674">
<stop offset="4.469245e-02" style="stop-color:#007BD8"/>
<stop offset="1" style="stop-color:#007DD8"/>
</linearGradient>
<path class="st4" d="M468.5,520.7l34.1-37.2c0.6-0.7,1.4-1.3,2.2-1.8c5.7-3.4,29.9-17.6,39.9-20.4c0,0-50,86.7-108.6,92
c0,0,21.3-5,30.6-29.4C467,522.7,467.7,521.6,468.5,520.7z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="327.0562" y1="771.1231" x2="327.0562" y2="426.3489">
<stop offset="0.1117" style="stop-color:#0067C7"/>
<stop offset="1" style="stop-color:#0082DA"/>
</linearGradient>
<path class="st5" d="M40.5,692.5c0,0,89.9,15,139.8,78.6c0,0,22.9-70.8,57.5-100.4c1.5-1.3,2.9-2.6,4.3-3.9
c5.8-5.6,27.6-22,79.3-17.5c0,0,121.1,15.3,198.9-60c2.6-2.6,5.4-5.1,8.1-7.5c9.3-8.3,33.6-32.7,54.2-76.2l29.8-61
c2.6-5.3,0.8-11.7-4.2-14.9l0,0c-7.5-4.8-17.1-4.6-24.3,0.4c-8.1,5.6-19.1,16.2-32.6,36.3c-1.8,2.7-3.6,5.5-5.2,8.4
c-7.3,12.4-35.7,57.5-71.6,76.5c0,0-54.2,34-162.2-0.4H390c0,0,37.9,1.6,54.4-15.1c11-11,6.6-29.5-8.3-33.9
c-3-0.9-6.5-1.5-10.7-1.6l-55.7,0.9c-13.6,0.2-27.2-1.8-40-6.4c-2.5-0.9-5-1.9-7.4-3c-5.4-2.5-10.9-4.7-16.6-6.3
c-29-8.5-104.6-22.6-157.3,39.4c0,0-22.8,27.8-39.6,75.3C108.8,600.3,85.4,664.8,40.5,692.5z"/>
<path class="st6" d="M758.1,315h-90.7v270H737c3.1,0,6.3,0,9.4,0.1c19.9,0.3,139.1-3.4,139.1-141.8
C885.5,443.2,890.6,317.9,758.1,315z M827.7,461.1c0,41.1-33.3,74.5-74.5,74.5h-29.3V361.8h29.3c41.1,0,74.5,33.3,74.5,74.5V461.1z"/>
<path class="st6" d="M911.9,384.8v198.4h54.3v-98.1c0-11,2.8-21.9,8.6-31.3c8.2-13.3,23.6-26.4,51.6-19.3l8.5-49.7
c0,0-44.1-15.5-68.7,29.7v-29.7H911.9z"/>
<path class="st6" d="M1152.2,381.1c-8.3-1.3-16.7-1.4-24.9-0.4c-86.2,10.5-87.8,93-87.8,93c-3.4,117.7,94.8,115,94.8,115
c101.9-0.6,101.9-96.1,101.9-96.1C1240,405.4,1180.4,385.6,1152.2,381.1z M1137.4,545.1c-24.9,0-45.1-27.2-45.1-60.6
c0-33.5,20.2-60.6,45.1-60.6s45.1,27.1,45.1,60.6C1182.5,518,1162.3,545.1,1137.4,545.1z"/>
<path class="st6" d="M1441.8,431.3c0,0-12.4-40.5-60.7-49.6c-11.9-2.2-24.2-1.7-35.8,2c-10.5,3.3-23.4,9.6-33.8,21.3l-0.3-20.6
l-52.2,0.6l-0.6,262.3h53.5v-83.5c0,0,45.6,50.3,104.7,6.3C1416.5,570.2,1472.8,530.3,1441.8,431.3z M1363.4,543.6
c-20.6,3.3-33.6-5.1-41.6-14.8c-7.7-9.3-11.5-21.2-11.5-33.2V470c0-7.3,1.4-14.5,4.3-21.1c12.1-27.1,35.9-25.1,35.9-25.1
c43.7-2.7,47.5,48.3,47.5,48.3C1403.6,537.3,1363.4,543.6,1363.4,543.6z"/>
</g>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
viewBox="0 135 1500 636"
style="enable-background:new 0 135 1500 636;"
xml:space="preserve"
>
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:url(#SVGID_1_);}
.st2{fill:url(#SVGID_2_);}
.st3{fill:url(#SVGID_3_);}
.st4{fill:url(#SVGID_4_);}
.st5{fill:url(#SVGID_5_);}
.st6{fill:#006BCC;}
</style>
<g>
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="378.2952" y1="456.0995" x2="378.2952" y2="131.6513">
<stop offset="0" style="stop-color:#006FCD"/>
<stop offset="1" style="stop-color:#00A9F2"/>
</linearGradient>
<path class="st1" d="M600.3,324.7c-11.5-34.2-45.1-41.5-45.1-41.5c-13.7-62-73.4-64.4-73.4-64.4c-31.4-91.3-119.5-87.1-119.5-87.1
C260.2,135,242.6,223,242.6,223c-92.5,15.4-90.1,94.4-90.1,94.4c3.5,93,81.8,100.3,81.8,100.3h33.5c1,18.1,8.6,27.7,15.9,32.8
c5.4,3.8,11.9,5.6,18.5,5.6h162c3.5,0,7-0.3,10.4-1.2c27.6-7.2,26.7-37.2,26.7-37.2h39.4c24.4-3.3,39.5-14.9,48.7-27
C604,372,607.9,347.1,600.3,324.7z M489.4,415.4c0,15.4-12.5,27.8-27.8,27.8H304.2c-12.1,0-21.9-9.8-21.9-21.9V228.7
c0-15.5,12.6-28.1,28.1-28.1H405c9.8,0,19.2,3.7,26.4,10.4l43.8,40.9c9.1,8.5,14.3,20.4,14.3,32.8V415.4z"/>
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="432.1597" y1="283.199" x2="432.1597" y2="227.7854">
<stop offset="0" style="stop-color:#008CE2"/>
<stop offset="0.9017" style="stop-color:#0098E8"/>
</linearGradient>
<path class="st2" d="M414.1,229.4l43.6,40.8c5,4.7,1.7,13.1-5.2,13.1h-39.4c-4.9,0-8.9-4-8.9-8.9v-40.6
C404.2,228.5,410.4,225.9,414.1,229.4z"/>
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="380.3181" y1="417.6981" x2="380.3181" y2="294.9461">
<stop offset="0" style="stop-color:#006FCE"/>
<stop offset="0.9017" style="stop-color:#0082D9"/>
</linearGradient>
<path class="st3" d="M425.9,376.3L385,415.6c-1.4,1.3-3.2,2.1-5.2,2.1c-2,0-3.9-0.8-5.3-2.2l-39.9-40.1c-1.8-1.8-2.6-4.1-2.6-6.4
c0-2.7,1.2-5.4,3.5-7.2c3.7-2.9,8.9-2.5,12.2,0.9l23.6,24.6v-82.7c0-5.3,4.3-9.6,9.6-9.6h0c5.3,0,9.6,4.3,9.6,9.6v80.6l23-22
c1.8-1.7,4-2.5,6.3-2.5c2.5,0,5,1,6.8,3C429.6,367.4,429.4,372.9,425.9,376.3z"/>
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="490.3123" y1="553.3344" x2="490.3123" y2="461.3674">
<stop offset="4.469245e-02" style="stop-color:#007BD8"/>
<stop offset="1" style="stop-color:#007DD8"/>
</linearGradient>
<path class="st4" d="M468.5,520.7l34.1-37.2c0.6-0.7,1.4-1.3,2.2-1.8c5.7-3.4,29.9-17.6,39.9-20.4c0,0-50,86.7-108.6,92
c0,0,21.3-5,30.6-29.4C467,522.7,467.7,521.6,468.5,520.7z"/>
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="327.0562" y1="771.1231" x2="327.0562" y2="426.3489">
<stop offset="0.1117" style="stop-color:#0067C7"/>
<stop offset="1" style="stop-color:#0082DA"/>
</linearGradient>
<path class="st5" d="M40.5,692.5c0,0,89.9,15,139.8,78.6c0,0,22.9-70.8,57.5-100.4c1.5-1.3,2.9-2.6,4.3-3.9
c5.8-5.6,27.6-22,79.3-17.5c0,0,121.1,15.3,198.9-60c2.6-2.6,5.4-5.1,8.1-7.5c9.3-8.3,33.6-32.7,54.2-76.2l29.8-61
c2.6-5.3,0.8-11.7-4.2-14.9l0,0c-7.5-4.8-17.1-4.6-24.3,0.4c-8.1,5.6-19.1,16.2-32.6,36.3c-1.8,2.7-3.6,5.5-5.2,8.4
c-7.3,12.4-35.7,57.5-71.6,76.5c0,0-54.2,34-162.2-0.4H390c0,0,37.9,1.6,54.4-15.1c11-11,6.6-29.5-8.3-33.9
c-3-0.9-6.5-1.5-10.7-1.6l-55.7,0.9c-13.6,0.2-27.2-1.8-40-6.4c-2.5-0.9-5-1.9-7.4-3c-5.4-2.5-10.9-4.7-16.6-6.3
c-29-8.5-104.6-22.6-157.3,39.4c0,0-22.8,27.8-39.6,75.3C108.8,600.3,85.4,664.8,40.5,692.5z"/>
<path class="st6" d="M758.1,315h-90.7v270H737c3.1,0,6.3,0,9.4,0.1c19.9,0.3,139.1-3.4,139.1-141.8
C885.5,443.2,890.6,317.9,758.1,315z M827.7,461.1c0,41.1-33.3,74.5-74.5,74.5h-29.3V361.8h29.3c41.1,0,74.5,33.3,74.5,74.5V461.1z"/>
<path class="st6" d="M911.9,384.8v198.4h54.3v-98.1c0-11,2.8-21.9,8.6-31.3c8.2-13.3,23.6-26.4,51.6-19.3l8.5-49.7
c0,0-44.1-15.5-68.7,29.7v-29.7H911.9z"/>
<path class="st6" d="M1152.2,381.1c-8.3-1.3-16.7-1.4-24.9-0.4c-86.2,10.5-87.8,93-87.8,93c-3.4,117.7,94.8,115,94.8,115
c101.9-0.6,101.9-96.1,101.9-96.1C1240,405.4,1180.4,385.6,1152.2,381.1z M1137.4,545.1c-24.9,0-45.1-27.2-45.1-60.6
c0-33.5,20.2-60.6,45.1-60.6s45.1,27.1,45.1,60.6C1182.5,518,1162.3,545.1,1137.4,545.1z"/>
<path class="st6" d="M1441.8,431.3c0,0-12.4-40.5-60.7-49.6c-11.9-2.2-24.2-1.7-35.8,2c-10.5,3.3-23.4,9.6-33.8,21.3l-0.3-20.6
l-52.2,0.6l-0.6,262.3h53.5v-83.5c0,0,45.6,50.3,104.7,6.3C1416.5,570.2,1472.8,530.3,1441.8,431.3z M1363.4,543.6
c-20.6,3.3-33.6-5.1-41.6-14.8c-7.7-9.3-11.5-21.2-11.5-33.2V470c0-7.3,1.4-14.5,4.3-21.1c12.1-27.1,35.9-25.1,35.9-25.1
c43.7-2.7,47.5,48.3,47.5,48.3C1403.6,537.3,1363.4,543.6,1363.4,543.6z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,151 +1,149 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard - I2P Secure Share</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/tailwind.css') }}">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<style>
body { background-color: #1a202c; color: #cbd5e0; }
.content-container { background-color: #2d3748; border: 1px solid #4a5568; }
.btn { background-color: #4299e1; transition: background-color 0.3s ease; }
.btn:hover { background-color: #3182ce; }
.btn-danger { background-color: #e53e3e; }
.btn-danger:hover { background-color: #c53030; }
input { background-color: #4a5568; border: 1px solid #718096; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #4a5568; padding: 0.75rem; text-align: left; }
th { background-color: #1a202c; }
tr:nth-child(even) { background-color: #2d3748; }
.alert-error { background-color: #e53e3e; }
.announcement-bar { background-color: #2563eb; border-bottom: 1px solid #1e3a8a; }
</style>
</head>
<body class="font-sans">
{% if announcement_enabled and announcement_message %}
<div id="announcement-bar" class="announcement-bar text-white text-center p-2 relative shadow-lg">
<span>{{ announcement_message }}</span>
<button id="close-announcement" class="absolute top-0 right-0 mt-2 mr-4 text-white hover:text-gray-200 text-2xl leading-none">&times;</button>
</div>
{% endif %}
<div class="flex items-center justify-center min-h-screen py-8">
<div class="w-full max-w-4xl mx-auto p-4">
<header class="text-center mb-8">
<a href="/" class="inline-block mb-4">
<img src="{{ url_for('static', filename='images/stormycloud.svg') }}" alt="StormyCloud Logo" style="width: 550px; max-width: 100%;" class="mx-auto">
</a>
<h1 class="text-4xl font-bold text-white">Admin Dashboard</h1>
</header>
<main class="content-container rounded-lg p-8 shadow-lg">
{% if auth_success %}
<h2 class="text-2xl font-semibold text-white mb-6">Active Images</h2>
{% if images %}
<div class="overflow-x-auto">
<table>
<thead>
<tr>
<th>Filename</th>
<th>Expires On (UTC)</th>
<th>Time Left</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for image in images %}
<tr>
<td class="font-mono text-sm break-all">{{ image[0] }}</td>
<td class="font-mono text-sm">{{ image[1] }}</td>
<td class="font-mono text-sm">{{ image[2] }}</td>
<td>
<form action="{{ url_for('delete_image', filename=image[0]) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this image?');">
<button type="submit" class="btn-danger text-white font-bold py-1 px-3 rounded text-sm">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-gray-400">No active images.</p>
{% endif %}
<h2 class="text-2xl font-semibold text-white mt-12 mb-6">Active Pastes</h2>
{% if pastes %}
<div class="overflow-x-auto">
<table>
<thead>
<tr>
<th>ID</th>
<th>Language</th>
<th>Expires On (UTC)</th>
<th>Time Left</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for paste in pastes %}
<tr>
<td class="font-mono text-sm break-all">{{ paste[0] }}</td>
<td class="font-mono text-sm">{{ paste[1] }}</td>
<td class="font-mono text-sm">{{ paste[2] }}</td>
<td class="font-mono text-sm">{{ paste[3] }}</td>
<td>
<form action="{{ url_for('delete_paste', paste_id=paste[0]) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this paste?');">
<button type="submit" class="btn-danger text-white font-bold py-1 px-3 rounded text-sm">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-gray-400">No active pastes.</p>
{% endif %}
{% else %}
<h2 class="text-2xl font-semibold text-white mb-6">Admin Login</h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mb-4">
{% for category, message in messages %}
<div class="alert-{{ category }} text-white p-3 rounded-md shadow-lg" role="alert">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('admin_dashboard') }}">
<div class="mb-6">
<label for="password" class="block text-gray-300 text-sm font-bold mb-2">Password:</label>
<input type="password" name="password" id="password" class="w-full p-2 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div><button type="submit" class="btn w-full text-white font-bold py-3 px-5 rounded-md focus:shadow-outline">Login</button></div>
</form>
{% endif %}
<div class="text-center mt-8 border-t border-gray-700 pt-6">
<a href="{{ url_for('index') }}" class="text-blue-400 hover:text-blue-300">Back to Homepage</a>
</div>
</main>
<footer class="text-center text-gray-500 mt-16 border-t border-gray-700 pt-8 pb-8">
<a href="http://stormycloud.i2p" class="hover:text-gray-400">StormyCloud</a>
</footer>
</div>
</div>
<script>
const announcementBar = document.getElementById('announcement-bar');
const closeButton = document.getElementById('close-announcement');
if (closeButton) {
closeButton.addEventListener('click', () => {
announcementBar.style.display = 'none';
});
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard - I2P Secure Share</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/tailwind.css') }}">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<style>
body { background-color: #1a202c; color: #cbd5e0; }
.content-container { background-color: #2d3748; border: 1px solid #4a5568; }
.btn { background-color: #4299e1; transition: background-color 0.3s ease; }
.btn:hover { background-color: #3182ce; }
.btn-danger { background-color: #e53e3e; }
.btn-danger:hover { background-color: #c53030; }
input { background-color: #4a5568; border: 1px solid #718096; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #4a5568; padding: 0.75rem; text-align: left; }
th { background-color: #1a202c; }
tr:nth-child(even) { background-color: #2d3748; }
.alert-error { background-color: #e53e3e; }
.announcement-bar { background-color: #2563eb; border-bottom: 1px solid #1e3a8a; }
</style>
</head>
<body class="font-sans">
{% if announcement_enabled and announcement_message %}
<div id="announcement-bar" class="announcement-bar text-white text-center p-2 relative shadow-lg">
<span>{{ announcement_message }}</span>
<button id="close-announcement" class="absolute top-0 right-0 mt-2 mr-4 text-white hover:text-gray-200 text-2xl leading-none">&times;</button>
</div>
{% endif %}
<div class="flex items-center justify-center min-h-screen py-8">
<a href="/" class="inline-block mb-4">
<img src="/static/images/stormycloud.svg" alt="StormyCloud Logo" style="width:350px; max-width:100%;" class="mx-auto"/>
</a>
<h1 class="text-4xl font-bold text-white">Admin Dashboard</h1>
</header>
<main class="content-container rounded-lg p-8 shadow-lg">
{% if auth_success %}
<h2 class="text-2xl font-semibold text-white mb-6">Active Images</h2>
{% if images %}
<div class="overflow-x-auto">
<table>
<thead>
<tr>
<th>Filename</th>
<th>Expires On (UTC)</th>
<th>Time Left</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for image in images %}
<tr>
<td class="font-mono text-sm break-all">{{ image[0] }}</td>
<td class="font-mono text-sm">{{ image[1] }}</td>
<td class="font-mono text-sm">{{ image[2] }}</td>
<td>
<form action="{{ url_for('delete_image', filename=image[0]) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this image?');">
<button type="submit" class="btn-danger text-white font-bold py-1 px-3 rounded text-sm">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-gray-400">No active images.</p>
{% endif %}
<h2 class="text-2xl font-semibold text-white mt-12 mb-6">Active Pastes</h2>
{% if pastes %}
<div class="overflow-x-auto">
<table>
<thead>
<tr>
<th>ID</th>
<th>Language</th>
<th>Expires On (UTC)</th>
<th>Time Left</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for paste in pastes %}
<tr>
<td class="font-mono text-sm break-all">{{ paste[0] }}</td>
<td class="font-mono text-sm">{{ paste[1] }}</td>
<td class="font-mono text-sm">{{ paste[2] }}</td>
<td class="font-mono text-sm">{{ paste[3] }}</td>
<td>
<form action="{{ url_for('delete_paste', paste_id=paste[0]) }}" method="POST" onsubmit="return confirm('Are you sure you want to delete this paste?');">
<button type="submit" class="btn-danger text-white font-bold py-1 px-3 rounded text-sm">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-gray-400">No active pastes.</p>
{% endif %}
{% else %}
<h2 class="text-2xl font-semibold text-white mb-6">Admin Login</h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mb-4">
{% for category, message in messages %}
<div class="alert-{{ category }} text-white p-3 rounded-md shadow-lg" role="alert">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('admin_dashboard') }}">
<div class="mb-6">
<label for="password" class="block text-gray-300 text-sm font-bold mb-2">Password:</label>
<input type="password" name="password" id="password" class="w-full p-2 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500" required>
</div>
<div><button type="submit" class="btn w-full text-white font-bold py-3 px-5 rounded-md focus:shadow-outline">Login</button></div>
</form>
{% endif %}
<div class="text-center mt-8 border-t border-gray-700 pt-6">
<a href="{{ url_for('index') }}" class="text-blue-400 hover:text-blue-300">Back to Homepage</a>
</div>
</main>
<footer class="text-center text-gray-500 mt-16 border-t border-gray-700 pt-8 pb-8">
<a href="http://stormycloud.i2p" class="hover:text-gray-400">StormyCloud</a>
</footer>
</div>
</div>
<script>
const announcementBar = document.getElementById('announcement-bar');
const closeButton = document.getElementById('close-announcement');
if (closeButton) {
closeButton.addEventListener('click', () => {
announcementBar.style.display = 'none';
});
}
</script>
</body>
</html>

View File

@ -1,66 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Support the Project - I2P Secure Share</title>
<link rel="stylesheet" href="/static/css/tailwind.css">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<style>
body { background-color: #1a202c; color: #cbd5e0; }
.content-container { background-color: #2d3748; border: 1px solid #4a5568; }
.announcement-bar { background-color: #2563eb; border-bottom: 1px solid #1e3a8a; }
.address-box { background-color: #1a202c; padding: 1rem; border-radius: 0.5rem; word-break: break-all; }
</style>
</head>
<body class="font-sans">
{% if announcement_enabled and announcement_message %}
<div id="announcement-bar" class="announcement-bar text-white text-center p-2 relative shadow-lg">
<span>{{ announcement_message }}</span>
<button id="close-announcement" class="absolute top-0 right-0 mt-2 mr-4 text-white hover:text-gray-200 text-2xl leading-none">&times;</button>
</div>
{% endif %}
<div class="flex items-center justify-center min-h-screen py-8">
<div class="w-full max-w-2xl mx-auto p-4">
<header class="text-center mb-8">
<a href="/" class="inline-block mb-4">
<img src="/static/images/stormycloud.svg" alt="StormyCloud Logo" style="width: 550px; max-width: 100%;" class="mx-auto">
</a>
<h1 class="text-3xl font-bold text-white">Support the Service</h1>
</header>
<main class="content-container rounded-lg p-8 shadow-lg">
<p class="text-center text-gray-400 mb-6">This service is developed and maintained for the I2P community free of charge. If you find it valuable, please consider a small donation to help cover server costs and support future development.</p>
<div class="text-center">
<h2 class="text-2xl font-semibold text-white mb-2">Monero (XMR)</h2>
<div class="address-box font-mono text-sm">
45Gtj5tkhs4EsbnV7kkhMCRpbZUdqCQqR5qmLFVLAvbFCYaPL4pFbBkEBLJ7beHqkiJxdTBkPwFsT5EMu5jDrYBHPjQzPuv
</div>
</div>
<div class="text-center mt-12 border-t border-gray-700 pt-6">
<a href="/" class="text-blue-400 hover:text-blue-300">Back to Uploader</a>
</div>
</main>
<footer class="text-center text-gray-500 mt-16 border-t border-gray-700 pt-8 pb-8">
<a href="http://stormycloud.i2p" class="hover:text-gray-400">StormyCloud</a>
<span class="mx-2">|</span>
<a href="/donate" class="hover:text-gray-400">Donate</a>
</footer>
</div>
</div>
<script>
const announcementBar = document.getElementById('announcement-bar');
const closeButton = document.getElementById('close-announcement');
if (closeButton) {
closeButton.addEventListener('click', () => {
announcementBar.style.display = 'none';
});
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Support the Project - I2P Secure Share</title>
<link rel="stylesheet" href="/static/css/tailwind.css">
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<style>
body { background-color: #1a202c; color: #cbd5e0; }
.content-container { background-color: #2d3748; border: 1px solid #4a5568; }
.announcement-bar { background-color: #2563eb; border-bottom: 1px solid #1e3a8a; }
.address-box { background-color: #1a202c; padding: 1rem; border-radius: 0.5rem; word-break: break-all; }
</style>
</head>
<body class="font-sans">
{% if announcement_enabled and announcement_message %}
<div id="announcement-bar" class="announcement-bar text-white text-center p-2 relative shadow-lg">
<span>{{ announcement_message }}</span>
<button id="close-announcement" class="absolute top-0 right-0 mt-2 mr-4 text-white hover:text-gray-200 text-2xl leading-none">&times;</button>
</div>
{% endif %}
<div class="flex items-center justify-center min-h-screen py-8">
<div class="w-full max-w-2xl mx-auto p-4">
<header class="text-center mb-8">
<a href="/" class="inline-block mb-4">
<img src="/static/images/stormycloud.svg" alt="StormyCloud Logo" style="width:350px; max-width:100%;" class="mx-auto"/>
</a>
<h1 class="text-3xl font-bold text-white">Support the Service</h1>
</header>
<main class="content-container rounded-lg p-8 shadow-lg">
<p class="text-center text-gray-400 mb-6">This service is developed and maintained for the I2P community free of charge. If you find it valuable, please consider a small donation to help cover server costs and support future development.</p>
<div class="text-center">
<h2 class="text-2xl font-semibold text-white mb-2">Monero (XMR)</h2>
<div class="address-box font-mono text-sm">
45Gtj5tkhs4EsbnV7kkhMCRpbZUdqCQqR5qmLFVLAvbFCYaPL4pFbBkEBLJ7beHqkiJxdTBkPwFsT5EMu5jDrYBHPjQzPuv
</div>
</div>
<div class="text-center mt-12 border-t border-gray-700 pt-6">
<a href="/" class="text-blue-400 hover:text-blue-300">Back to Uploader</a>
</div>
</main>
<footer class="text-center text-gray-500 mt-16 border-t border-gray-700 pt-8 pb-8">
<a href="http://stormycloud.i2p" class="hover:text-gray-400">StormyCloud</a>
<span class="mx-2">|</span>
<a href="/donate" class="hover:text-gray-400">Donate</a>
</footer>
</div>
</div>
<script>
const announcementBar = document.getElementById('announcement-bar');
const closeButton = document.getElementById('close-announcement');
if (closeButton) {
closeButton.addEventListener('click', () => {
announcementBar.style.display = 'none';
});
}
</script>
</body>
</html>

View File

@ -1,419 +1,409 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>I2P Secure Share</title>
<link rel="stylesheet" href="/static/css/tailwind.css"/>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<style>
body { background-color: #1a202c; color: #cbd5e0; }
.content-container { background-color: #2d3748; border:1px solid #4a5568; border-radius:0.5rem; }
.tab { border-bottom:2px solid transparent; cursor:pointer; }
.tab.active { border-bottom-color:#63b3ed; color:#ffffff; }
.btn { background-color:#4299e1; transition:background-color .3s ease; }
.btn:hover { background-color:#3182ce; }
select,textarea,input[type="text"],input[type="password"],input[type="number"] {
background-color:#4a5568; border:1px solid #718096; color:#cbd5e0;
}
.alert-success { background-color:#38a169; }
.alert-error { background-color:#e53e3e; }
.announcement-bar { background-color:#2563eb; border-bottom:1px solid #1e3a8a; }
.reveal { display:none; }
input[type="file"] {
width:100%; padding:0.5rem 1rem; border-radius:0.375rem;
background-color:#4a5568; border:1px solid #718096; color:#cbd5e0; cursor:pointer;
}
input[type="file"]::file-selector-button {
background-color:#2d3748; color:#cbd5e0; border:none;
padding:0.5rem 1rem; margin-right:1rem; border-radius:0.375rem; cursor:pointer;
transition:background-color .3s ease;
}
input[type="file"]::file-selector-button:hover { background-color:#3a4a5a; }
.stat-card {
background-color:#2d3748; border:1px solid #4a5568; border-radius:0.5rem;
}
.stat-value { color:#63b3ed; }
.label-with-icon {
display:inline-flex; align-items:center; gap:0.5rem; font-size:0.875rem; color:#cbd5e0;
}
.label-with-icon svg {
width:1rem; height:1rem; color:#4299e1; flex-shrink:0;
}
.feature-card {
background-color: #2d3748;
border: 1px solid #4a5568;
border-radius: 0.5rem;
}
/* API Docs Styling */
.docs-container h3 {
font-size: 1.5rem;
font-weight: 600;
color: #ffffff;
margin-top: 2rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #4a5568;
}
.docs-container p { margin-bottom: 1rem; color: #a0aec0; }
.docs-container ul { list-style-position: inside; margin-bottom: 1rem; }
.docs-container li { margin-bottom: 0.5rem; color: #cbd5e0;}
.docs-container code {
background-color:#1a202c;
color:#f7fafc;
padding:0.2rem 0.4rem;
border-radius:0.25rem;
font-family:monospace;
font-size: 0.875rem;
}
.docs-container pre {
background-color:#1a202c;
padding:1rem;
border-radius:0.5rem;
overflow-x:auto;
color:#f7fafc;
font-family:monospace;
}
</style>
<noscript>
<style>
.tab-nav-container{display:none;}
.tab-content{display:none!important;}
#image-form,#paste-form{display:block!important;margin-bottom:2rem;}
#api-docs,#stats-content,#tos-content,.features-section{display:none!important;}
@media(min-width:1024px){.noscript-forms-container{display:flex;gap:1.5rem;} .noscript-forms-container>div{flex:1;}}
.reveal{display:block!important;}
</style>
</noscript>
</head>
<body class="font-sans">
{% if announcement_enabled and announcement_message %}
<div id="announcement-bar" class="announcement-bar text-white text-center p-2 relative shadow-lg">
<span>{{ announcement_message }}</span>
<button id="close-announcement"
class="absolute top-0 right-0 mt-2 mr-4 text-white hover:text-gray-200 text-2xl leading-none">&times;</button>
</div>
{% endif %}
<div class="flex items-center justify-center min-h-screen py-8">
<div id="main-container" class="w-full max-w-2xl mx-auto p-4">
<header class="text-center mb-8">
<a href="/" class="inline-block mb-4">
<img src="/static/images/stormycloud.svg"
alt="StormyCloud Logo"
style="width:550px;max-width:100%;"
class="mx-auto"/>
</a>
<h1 class="text-4xl font-bold text-white">I2P Secure Share</h1>
<p class="text-gray-400">Anonymously share images and text pastes.</p>
</header>
<main>
<div id="js-error-container"
class="hidden alert-error text-white p-3 rounded-md shadow-lg mb-4"
role="alert"></div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mb-4">
{% for category,message in messages %}
<div class="alert-{{category}} text-white p-3 rounded-md shadow-lg"
role="alert">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="mb-4 border-b border-gray-700 tab-nav-container">
<nav class="flex -mb-px" id="tab-nav">
<a href="#image"
class="tab active text-gray-300 py-4 px-6 block hover:text-white">Image Uploader</a>
<a href="#paste"
class="tab text-gray-300 py-4 px-6 block hover:text-white">Pastebin</a>
<a href="#api"
class="tab text-gray-300 py-4 px-6 block hover:text-white">API</a>
<a href="#stats"
class="tab text-gray-300 py-4 px-6 block hover:text-white">Stats</a>
<a href="#tos"
class="tab text-gray-300 py-4 px-6 block hover:text-white">Terms</a>
</nav>
</div>
<div class="noscript-forms-container">
<!-- IMAGE FORM -->
<div id="image-form" class="content-container rounded-lg p-8 shadow-lg tab-content">
<form action="/upload/image" method="POST" enctype="multipart/form-data">
<h2 class="text-2xl font-semibold mb-6 text-white">Upload an Image</h2>
<div class="mb-6">
<label for="image-file" class="block text-gray-300 text-sm font-bold mb-2">Image File:</label>
<input type="file" name="file" id="image-file" required>
<p class="text-xs text-gray-500 mt-1">Max 10MB; WebP conversion.</p>
</div>
<div class="mb-6">
<label for="image-expiry" class="block text-gray-300 text-sm font-bold mb-2">Delete after:</label>
<select name="expiry" id="image-expiry" class="w-full p-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="15m">15 minutes</option>
<option value="1h" selected>1 hour</option>
<option value="2h">2 hours</option>
<option value="4h">4 hours</option>
<option value="8h">8 hours</option>
<option value="12h">12 hours</option>
<option value="24h">24 hours</option>
<option value="48h">48 hours</option>
</select>
</div>
<div class="mb-6 text-gray-300" style="display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem;align-items:start;">
<label class="label-with-icon">
<input type="checkbox" name="keep_exif">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6M7 7h10M4 6h16M4 6a2 2 0 012-2h8l2 2h6a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V6z"/></svg>
<span>Keep EXIF Data</span>
</label>
<label class="label-with-icon">
<input type="checkbox" id="image-pw-protect" name="password_protect">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c1.657 0 3-1.343 3-3V6a3 3 0 10-6 0v2c0 1.657 1.343 3 3 3z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 11h14a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2v-8a2 2 0 012-2z"/></svg>
<span>Password</span>
</label>
<label class="label-with-icon" title="Removed after this many successful views">
<input type="checkbox" id="image-views-protect" name="views_protect">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
<span>Max Views</span>
</label>
</div>
<div id="image-pw-options" class="reveal mb-6">
<label for="image-password" class="block text-gray-300 text-sm font-bold mb-1">Password:</label>
<input type="password" name="password" id="image-password" class="w-full p-2 rounded-md border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div id="image-views-options" class="reveal mb-6">
<label for="image-max-views" class="block text-gray-300 text-sm font-bold mb-1">Max views:</label>
<input type="number" name="max_views" id="image-max-views" min="1" class="w-full p-2 rounded-md border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<button type="submit" class="btn w-full text-white font-bold py-3 px-5 rounded-md focus:shadow-outline">Upload Image</button>
</form>
</div>
<!-- PASTE FORM -->
<div id="paste-form" class="content-container rounded-lg p-8 shadow-lg hidden tab-content">
<form action="/upload/paste" method="POST">
<h2 class="text-2xl font-semibold mb-6 text-white">Create a Paste</h2>
<div class="mb-6">
<label for="paste-content" class="block text-gray-300 text-sm font-bold mb-2">Paste Content:</label>
<textarea name="content" id="paste-content" rows="10" class="w-full p-2 rounded-md font-mono focus:outline-none focus:ring-2 focus:ring-blue-500" required></textarea>
</div>
<div class="mb-6">
<label for="paste-language" class="block text-gray-300 text-sm font-bold mb-2">Language:</label>
<select name="language" id="paste-language" class="w-full p-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="text">Plain Text</option>
{% for lang in languages %}
<option value="{{ lang }}">{{ lang|capitalize }}</option>
{% endfor %}
</select>
</div>
<div class="mb-6">
<label for="paste-expiry" class="block text-gray-300 text-sm font-bold mb-2">Delete after:</label>
<select name="expiry" id="paste-expiry" class="w-full p-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="15m">15 minutes</option>
<option value="1h" selected>1 hour</option>
<option value="2h">2 hours</option>
<option value="4h">4 hours</option>
<option value="8h">8 hours</option>
<option value="12h">12 hours</option>
<option value="24h">24 hours</option>
<option value="48h">48 hours</option>
</select>
</div>
<div class="mb-6 text-gray-300" style="display:grid;grid-template-columns:repeat(2,1fr);gap:2rem;align-items:start;">
<label class="label-with-icon">
<input type="checkbox" id="paste-pw-protect" name="password_protect">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c1.657 0 3-1.343 3-3V6a3 3 0 10-6 0v2c0 1.657 1.343 3 3 3z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 11h14a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2v-8a2 2 0 012-2z"/></svg>
<span>Password</span>
</label>
<label class="label-with-icon" title="Removed after this many successful views">
<input type="checkbox" id="paste-views-protect" name="views_protect">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
<span>Max Views</span>
</label>
</div>
<div id="paste-pw-options" class="reveal mb-6">
<label for="paste-password" class="block text-gray-300 text-sm font-bold mb-1">Password:</label>
<input type="password" name="password" id="paste-password" class="w-full p-2 rounded-md border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div id="paste-views-options" class="reveal mb-6">
<label for="paste-max-views" class="block text-gray-300 text-sm font-bold mb-1">Max views:</label>
<input type="number" name="max_views" id="paste-max-views" min="1" class="w-full p-2 rounded-md border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<button type="submit" class="btn w-full text-white font-bold py-3 px-5 rounded-md focus:shadow-outline">Create Paste</button>
</form>
</div>
<!-- API DOCS -->
<div id="api-docs" class="content-container docs-container rounded-lg p-8 shadow-lg hidden tab-content">
<h3>Introduction</h3>
<p>The API allows programmatic uploads. All endpoints are rate-limited. No API key is required.</p>
<h3>Uploading an Image</h3>
<p>Send a <code>POST</code> request with <code>multipart/form-data</code>.</p>
<ul>
<li><strong>Endpoint:</strong> <code>POST /api/upload/image</code></li>
<li><strong>Parameter <code>file</code>:</strong> (Required) The image file.</li>
<li><strong>Parameter <code>expiry</code>:</strong> (Optional) Values: <code>15m</code>, <code>1h</code>, <code>2h</code>, <code>4h</code>, <code>8h</code>, <code>12h</code>, <code>24h</code>, <code>48h</code>.</li>
<li><strong>Parameter <code>password</code>:</strong> (Optional) A password to protect the content.</li>
<li><strong>Parameter <code>max_views</code>:</strong> (Optional) An integer for auto-deletion after N views.</li>
</ul>
<pre>curl -X POST -F "file=@/path/to/image.jpg" http://{{ request.host }}/api/upload/image</pre>
<h3>Creating a Paste</h3>
<p>Send a <code>POST</code> request with a JSON payload.</p>
<ul>
<li><strong>Endpoint:</strong> <code>POST /api/upload/paste</code></li>
<li><strong>JSON Field <code>content</code>:</strong> (Required) The paste text.</li>
<li><strong>JSON Field <code>language</code>:</strong> (Optional) A valid language for syntax highlighting. Defaults to 'text'.</li>
<li><strong>JSON Field <code>expiry</code>:</strong> (Optional) Same values as image expiry.</li>
<li><strong>JSON Field <code>password</code>:</strong> (Optional) A password to protect the content.</li>
<li><strong>JSON Field <code>max_views</code>:</strong> (Optional) An integer for auto-deletion after N views.</li>
</ul>
<pre>curl -X POST -H "Content-Type: application/json" \
-d '{"content":"Hello World", "expiry":"1h"}' \
http://{{ request.host }}/api/upload/paste</pre>
</div>
<!-- STATS -->
<div id="stats-content" class="content-container rounded-lg p-8 shadow-lg hidden tab-content">
<h2 class="text-3xl font-bold text-white text-center mb-8">Service Statistics</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="stat-card p-6 text-center">
<h3 class="text-xl font-semibold text-white mb-2">Total Image Uploads</h3>
<p class="text-5xl font-bold stat-value">{{ stats.total_images }}</p>
</div>
<div class="stat-card p-6 text-center">
<h3 class="text-xl font-semibold text-white mb-2">Total Paste Uploads</h3>
<p class="text-5xl font-bold stat-value">{{ stats.total_pastes }}</p>
</div>
<div class="stat-card p-6 text-center">
<h3 class="text-xl font-semibold text-white mb-2">Total API Uploads</h3>
<p class="text-5xl font-bold stat-value">{{ stats.total_api_uploads }}</p>
</div>
</div>
</div>
<!-- TERMS OF SERVICE -->
<div id="tos-content" class="content-container tos-container rounded-lg p-8 shadow-lg hidden tab-content">
<h2 class="text-3xl font-bold text-white mb-4">Terms of Service</h2>
<p><strong>Last Updated: June 20, 2025</strong></p>
<p>By using this service you agree to these terms. The service is provided “as-is” with a focus on privacy.</p>
<h3 class="mt-6 text-white text-xl font-semibold mb-2">1. Privacy & Data</h3>
<ul class="list-disc list-inside text-gray-300 mb-4">
<li>No logs of your identity or IP.</li>
<li>All uploads are encrypted at rest.</li>
<li>Data auto-deletes after expiry or view limit.</li>
<li>EXIF metadata only kept if opted-in.</li>
</ul>
<h3 class="mt-6 text-white text-xl font-semibold mb-2">2. Acceptable Use</h3>
<p class="text-gray-300 mb-4">Do not upload illegal or harmful content. We reserve the right to remove content that violates these terms.</p>
<h3 class="mt-6 text-white text-xl font-semibold mb-2">3. Liability</h3>
<p class="text-gray-300">This free service comes with no warranties. We are not responsible for data loss.</p>
</div>
</div>
<!-- Version Info (Moved outside the tabbed content) -->
<div class="text-center text-xs text-gray-500 mt-6 mb-8">
<a href="https://github.com/your-username/your-repo-name" target="_blank" rel="noopener noreferrer" class="hover:text-gray-400 transition-colors">
Version 1.1
</a>
</div>
<!-- FEATURES -->
<section class="text-center features-section">
<h2 class="text-3xl font-bold text-white mb-8">Features</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="feature-card p-6 rounded-lg">
<div class="flex items-center justify-center h-12 w-12 rounded-md bg-blue-500 text-white mx-auto mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
</div>
<h3 class="text-xl font-semibold text-white mb-2">Encrypted at Rest</h3>
<p class="text-gray-400">All uploaded files and pastes are fully encrypted on the server, ensuring your data is protected.</p>
</div>
<div class="feature-card p-6 rounded-lg">
<div class="flex items-center justify-center h-12 w-12 rounded-md bg-blue-500 text-white mx-auto mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0 3.517-1.009 6.786-2.673 9.356 m-2.673-9.356C6.673 8.214 9.327 5.5 12 5.5 c2.673 0 5.327 2.714 5.327 5.5s-1.009 6.786-2.673 9.356m-2.673-9.356h0z" /></svg>
</div>
<h3 class="text-xl font-semibold text-white mb-2">Anonymous by Design</h3>
<p class="text-gray-400">Image metadata (EXIF) is stripped and no unnecessary logs are kept. Built for the I2P network.</p>
</div>
<div class="feature-card p-6 rounded-lg">
<div class="flex items-center justify-center h-12 w-12 rounded-md bg-blue-500 text-white mx-auto mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" /></svg>
</div>
<h3 class="text-xl font-semibold text-white mb-2">StormyCloud Infrastructure</h3>
<p class="text-gray-400">A fast, reliable, and secure platform dedicated to the privacy of the I2P community.</p>
</div>
</div>
</section>
<footer class="text-center text-gray-500 mt-16 border-t border-gray-700 pt-8 pb-8">
<a href="http://stormycloud.i2p" class="hover:text-gray-400">StormyCloud</a>
<span class="mx-2">|</span>
<a href="/donate" class="hover:text-gray-400">Donate</a>
</footer>
</main>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const contentDivs = {
'#image': document.querySelector('#image-form'),
'#paste': document.querySelector('#paste-form'),
'#api': document.querySelector('#api-docs'),
'#stats': document.querySelector('#stats-content'),
'#tos': document.querySelector('#tos-content')
};
const tabs = document.querySelectorAll('#tab-nav a');
function showTab(hash) {
if (!hash || !contentDivs[hash]) hash = '#image';
Object.values(contentDivs).forEach(d => {
if(d) d.classList.add('hidden');
});
tabs.forEach(t => t.classList.remove('active'));
if(contentDivs[hash]) {
contentDivs[hash].classList.remove('hidden');
}
const activeTab = document.querySelector(`#tab-nav a[href="${hash}"]`);
if(activeTab) {
activeTab.classList.add('active');
}
}
tabs.forEach(tab => tab.addEventListener('click', e => {
e.preventDefault();
const h = e.target.hash;
window.history.replaceState(null, null, ' ' + h);
showTab(h);
}));
showTab(window.location.hash);
function toggle(cbId, tgtId) {
const cb = document.getElementById(cbId), tgt = document.getElementById(tgtId);
if (!cb||!tgt) return;
cb.addEventListener('change', () => tgt.style.display = cb.checked ? 'block' : 'none');
tgt.style.display = cb.checked ? 'block' : 'none';
}
toggle('image-pw-protect','image-pw-options');
toggle('image-views-protect','image-views-options');
toggle('paste-pw-protect','paste-pw-options');
toggle('paste-views-protect','paste-views-options');
const closeBtn = document.getElementById('close-announcement');
if (closeBtn) closeBtn.addEventListener('click', ()=>
document.getElementById('announcement-bar').style.display = 'none'
);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>I2P Secure Share</title>
<link rel="stylesheet" href="/static/css/tailwind.css"/>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<style>
body { background-color: #1a202c; color: #cbd5e0; }
.content-container { background-color: #2d3748; border:1px solid #4a5568; border-radius:0.5rem; }
.tab { border-bottom:2px solid transparent; cursor:pointer; }
.tab.active { border-bottom-color:#63b3ed; color:#ffffff; }
.btn { background-color:#4299e1; transition:background-color .3s ease; }
.btn:hover { background-color:#3182ce; }
select,textarea,input[type="text"],input[type="password"],input[type="number"] {
background-color:#4a5568; border:1px solid #718096; color:#cbd5e0;
}
.alert-success { background-color:#38a169; }
.alert-error { background-color:#e53e3e; }
.announcement-bar { background-color:#2563eb; border-bottom:1px solid #1e3a8a; }
.reveal { display:none; }
input[type="file"] {
width:100%; padding:0.5rem 1rem; border-radius:0.375rem;
background-color:#4a5568; border:1px solid #718096; color:#cbd5e0; cursor:pointer;
}
input[type="file"]::file-selector-button {
background-color:#2d3748; color:#cbd5e0; border:none;
padding:0.5rem 1rem; margin-right:1rem; border-radius:0.375rem; cursor:pointer;
transition:background-color .3s ease;
}
input[type="file"]::file-selector-button:hover { background-color:#3a4a5a; }
.stat-card {
background-color:#2d3748; border:1px solid #4a5568; border-radius:0.5rem;
}
.stat-value { color:#63b3ed; }
.label-with-icon {
display:inline-flex; align-items:center; gap:0.5rem; font-size:0.875rem; color:#cbd5e0;
}
.label-with-icon svg {
width:1rem; height:1rem; color:#4299e1; flex-shrink:0;
}
.feature-card {
background-color: #2d3748;
border: 1px solid #4a5568;
border-radius: 0.5rem;
}
/* API Docs Styling */
.docs-container h3 {
font-size: 1.5rem;
font-weight: 600;
color: #ffffff;
margin-top: 2rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #4a5568;
}
.docs-container p { margin-bottom: 1rem; color: #a0aec0; }
.docs-container ul { list-style-position: inside; margin-bottom: 1rem; }
.docs-container li { margin-bottom: 0.5rem; color: #cbd5e0;}
.docs-container code {
background-color:#1a202c;
color:#f7fafc;
padding:0.2rem 0.4rem;
border-radius:0.25rem;
font-family:monospace;
font-size: 0.875rem;
}
.docs-container pre {
background-color:#1a202c;
padding:1rem;
border-radius:0.5rem;
overflow-x:auto;
color:#f7fafc;
font-family:monospace;
}
</style>
<noscript>
<style>
.tab-nav-container{display:none;}
.tab-content{display:none!important;}
#image-form,#paste-form{display:block!important;margin-bottom:2rem;}
#api-docs,#stats-content,#tos-content,.features-section{display:none!important;}
@media(min-width:1024px){.noscript-forms-container{display:flex;gap:1.5rem;} .noscript-forms-container>div{flex:1;}}
.reveal{display:block!important;}
</style>
</noscript>
</head>
<body class="font-sans">
{% if announcement_enabled and announcement_message %}
<div id="announcement-bar" class="announcement-bar text-white text-center p-2 relative shadow-lg">
<span>{{ announcement_message }}</span>
<button id="close-announcement"
class="absolute top-0 right-0 mt-2 mr-4 text-white hover:text-gray-200 text-2xl leading-none">&times;</button>
</div>
{% endif %}
<div class="flex items-center justify-center min-h-screen py-8">
<div id="main-container" class="w-full max-w-2xl mx-auto p-4">
<header class="text-center mb-8">
<a href="/" class="inline-block mb-4">
<img src="/static/images/stormycloud.svg" alt="StormyCloud Logo" style="width:350px; max-width:100%;" class="mx-auto"/>
</a>
<h1 class="text-4xl font-bold text-white">I2P Secure Share</h1>
<p class="text-gray-400">Anonymously share images and text pastes.</p>
</header>
<main>
<div id="js-error-container"
class="hidden alert-error text-white p-3 rounded-md shadow-lg mb-4"
role="alert"></div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mb-4">
{% for category,message in messages %}
<div class="alert-{{category}} text-white p-3 rounded-md shadow-lg"
role="alert">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="mb-4 border-b border-gray-700 tab-nav-container">
<nav class="flex -mb-px" id="tab-nav">
<a href="#image"
class="tab active text-gray-300 py-4 px-6 block hover:text-white">Image Uploader</a>
<a href="#paste"
class="tab text-gray-300 py-4 px-6 block hover:text-white">Pastebin</a>
<a href="#api"
class="tab text-gray-300 py-4 px-6 block hover:text-white">API</a>
<a href="#stats"
class="tab text-gray-300 py-4 px-6 block hover:text-white">Stats</a>
<a href="#tos"
class="tab text-gray-300 py-4 px-6 block hover:text-white">Terms</a>
</nav>
</div>
<div class="noscript-forms-container">
<div id="image-form" class="content-container rounded-lg p-8 shadow-lg tab-content">
<form action="/upload/image" method="POST" enctype="multipart/form-data">
<h2 class="text-2xl font-semibold mb-6 text-white">Upload an Image</h2>
<div class="mb-6">
<label for="image-file" class="block text-gray-300 text-sm font-bold mb-2">Image File:</label>
<input type="file" name="file" id="image-file" required>
<p class="text-xs text-gray-500 mt-1">Max 10MB; WebP conversion.</p>
</div>
<div class="mb-6">
<label for="image-expiry" class="block text-gray-300 text-sm font-bold mb-2">Delete after:</label>
<select name="expiry" id="image-expiry" class="w-full p-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="15m">15 minutes</option>
<option value="1h" selected>1 hour</option>
<option value="2h">2 hours</option>
<option value="4h">4 hours</option>
<option value="8h">8 hours</option>
<option value="12h">12 hours</option>
<option value="24h">24 hours</option>
<option value="48h">48 hours</option>
</select>
</div>
<div class="mb-6 text-gray-300" style="display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem;align-items:start;">
<label class="label-with-icon">
<input type="checkbox" name="keep_exif">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6M7 7h10M4 6h16M4 6a2 2 0 012-2h8l2 2h6a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V6z"/></svg>
<span>Keep EXIF Data</span>
</label>
<label class="label-with-icon">
<input type="checkbox" id="image-pw-protect" name="password_protect">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c1.657 0 3-1.343 3-3V6a3 3 0 10-6 0v2c0 1.657 1.343 3 3 3z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 11h14a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2v-8a2 2 0 012-2z"/></svg>
<span>Password</span>
</label>
<label class="label-with-icon" title="Removed after this many successful views">
<input type="checkbox" id="image-views-protect" name="views_protect">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
<span>Max Views</span>
</label>
</div>
<div id="image-pw-options" class="reveal mb-6">
<label for="image-password" class="block text-gray-300 text-sm font-bold mb-1">Password:</label>
<input type="password" name="password" id="image-password" class="w-full p-2 rounded-md border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div id="image-views-options" class="reveal mb-6">
<label for="image-max-views" class="block text-gray-300 text-sm font-bold mb-1">Max views:</label>
<input type="number" name="max_views" id="image-max-views" min="1" class="w-full p-2 rounded-md border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<button type="submit" class="btn w-full text-white font-bold py-3 px-5 rounded-md focus:shadow-outline">Upload Image</button>
</form>
</div>
<div id="paste-form" class="content-container rounded-lg p-8 shadow-lg hidden tab-content">
<form action="/upload/paste" method="POST">
<h2 class="text-2xl font-semibold mb-6 text-white">Create a Paste</h2>
<div class="mb-6">
<label for="paste-content" class="block text-gray-300 text-sm font-bold mb-2">Paste Content:</label>
<textarea name="content" id="paste-content" rows="10" class="w-full p-2 rounded-md font-mono focus:outline-none focus:ring-2 focus:ring-blue-500" required></textarea>
</div>
<div class="mb-6">
<label for="paste-language" class="block text-gray-300 text-sm font-bold mb-2">Language:</label>
<select name="language" id="paste-language" class="w-full p-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="text">Plain Text</option>
{% for lang in languages %}
<option value="{{ lang }}">{{ lang|capitalize }}</option>
{% endfor %}
</select>
</div>
<div class="mb-6">
<label for="paste-expiry" class="block text-gray-300 text-sm font-bold mb-2">Delete after:</label>
<select name="expiry" id="paste-expiry" class="w-full p-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="15m">15 minutes</option>
<option value="1h" selected>1 hour</option>
<option value="2h">2 hours</option>
<option value="4h">4 hours</option>
<option value="8h">8 hours</option>
<option value="12h">12 hours</option>
<option value="24h">24 hours</option>
<option value="48h">48 hours</option>
</select>
</div>
<div class="mb-6 text-gray-300" style="display:grid;grid-template-columns:repeat(2,1fr);gap:2rem;align-items:start;">
<label class="label-with-icon">
<input type="checkbox" id="paste-pw-protect" name="password_protect">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c1.657 0 3-1.343 3-3V6a3 3 0 10-6 0v2c0 1.657 1.343 3 3 3z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 11h14a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2v-8a2 2 0 012-2z"/></svg>
<span>Password</span>
</label>
<label class="label-with-icon" title="Removed after this many successful views">
<input type="checkbox" id="paste-views-protect" name="views_protect">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
<span>Max Views</span>
</label>
</div>
<div id="paste-pw-options" class="reveal mb-6">
<label for="paste-password" class="block text-gray-300 text-sm font-bold mb-1">Password:</label>
<input type="password" name="password" id="paste-password" class="w-full p-2 rounded-md border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div id="paste-views-options" class="reveal mb-6">
<label for="paste-max-views" class="block text-gray-300 text-sm font-bold mb-1">Max views:</label>
<input type="number" name="max_views" id="paste-max-views" min="1" class="w-full p-2 rounded-md border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<button type="submit" class="btn w-full text-white font-bold py-3 px-5 rounded-md focus:shadow-outline">Create Paste</button>
</form>
</div>
<div id="api-docs" class="content-container docs-container rounded-lg p-8 shadow-lg hidden tab-content">
<h3>Introduction</h3>
<p>The API allows programmatic uploads. All endpoints are rate-limited. No API key is required.</p>
<h3>Uploading an Image</h3>
<p>Send a <code>POST</code> request with <code>multipart/form-data</code>.</p>
<ul>
<li><strong>Endpoint:</strong> <code>POST /api/upload/image</code></li>
<li><strong>Parameter <code>file</code>:</strong> (Required) The image file.</li>
<li><strong>Parameter <code>expiry</code>:</strong> (Optional) Values: <code>15m</code>, <code>1h</code>, <code>2h</code>, <code>4h</code>, <code>8h</code>, <code>12h</code>, <code>24h</code>, <code>48h</code>.</li>
<li><strong>Parameter <code>password</code>:</strong> (Optional) A password to protect the content.</li>
<li><strong>Parameter <code>max_views</code>:</strong> (Optional) An integer for auto-deletion after N views.</li>
</ul>
<pre>curl -X POST -F "file=@/path/to/image.jpg" http://{{ request.host }}/api/upload/image</pre>
<h3>Creating a Paste</h3>
<p>Send a <code>POST</code> request with a JSON payload.</p>
<ul>
<li><strong>Endpoint:</strong> <code>POST /api/upload/paste</code></li>
<li><strong>JSON Field <code>content</code>:</strong> (Required) The paste text.</li>
<li><strong>JSON Field <code>language</code>:</strong> (Optional) A valid language for syntax highlighting. Defaults to 'text'.</li>
<li><strong>JSON Field <code>expiry</code>:</strong> (Optional) Same values as image expiry.</li>
<li><strong>JSON Field <code>password</code>:</strong> (Optional) A password to protect the content.</li>
<li><strong>JSON Field <code>max_views</code>:</strong> (Optional) An integer for auto-deletion after N views.</li>
</ul>
<pre>curl -X POST -H "Content-Type: application/json" \
-d '{"content":"Hello World", "expiry":"1h"}' \
http://{{ request.host }}/api/upload/paste</pre>
</div>
<div id="stats-content" class="content-container rounded-lg p-8 shadow-lg hidden tab-content">
<h2 class="text-3xl font-bold text-white text-center mb-8">Service Statistics</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="stat-card p-6 text-center">
<h3 class="text-xl font-semibold text-white mb-2">Total Image Uploads</h3>
<p class="text-5xl font-bold stat-value">{{ stats.total_images }}</p>
</div>
<div class="stat-card p-6 text-center">
<h3 class="text-xl font-semibold text-white mb-2">Total Paste Uploads</h3>
<p class="text-5xl font-bold stat-value">{{ stats.total_pastes }}</p>
</div>
<div class="stat-card p-6 text-center">
<h3 class="text-xl font-semibold text-white mb-2">Total API Uploads</h3>
<p class="text-5xl font-bold stat-value">{{ stats.total_api_uploads }}</p>
</div>
</div>
</div>
<div id="tos-content" class="content-container tos-container rounded-lg p-8 shadow-lg hidden tab-content">
<h2 class="text-3xl font-bold text-white mb-4">Terms of Service</h2>
<p><strong>Last Updated: June 20, 2025</strong></p>
<p>By using this service you agree to these terms. The service is provided “as-is” with a focus on privacy.</p>
<h3 class="mt-6 text-white text-xl font-semibold mb-2">1. Privacy & Data</h3>
<ul class="list-disc list-inside text-gray-300 mb-4">
<li>No logs of your identity or IP.</li>
<li>All uploads are encrypted at rest.</li>
<li>Data auto-deletes after expiry or view limit.</li>
<li>EXIF metadata only kept if opted-in.</li>
</ul>
<h3 class="mt-6 text-white text-xl font-semibold mb-2">2. Acceptable Use</h3>
<p class="text-gray-300 mb-4">Do not upload illegal or harmful content. We reserve the right to remove content that violates these terms.</p>
<h3 class="mt-6 text-white text-xl font-semibold mb-2">3. Liability</h3>
<p class="text-gray-300">This free service comes with no warranties. We are not responsible for data loss.</p>
</div>
</div>
<div class="text-center text-xs text-gray-500 mt-6 mb-8">
<a href="https://github.com/your-username/your-repo-name" target="_blank" rel="noopener noreferrer" class="hover:text-gray-400 transition-colors">
Version 1.1
</a>
</div>
<section class="text-center features-section">
<h2 class="text-3xl font-bold text-white mb-8">Features</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="feature-card p-6 rounded-lg">
<div class="flex items-center justify-center h-12 w-12 rounded-md bg-blue-500 text-white mx-auto mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
</div>
<h3 class="text-xl font-semibold text-white mb-2">Encrypted at Rest</h3>
<p class="text-gray-400">All uploaded files and pastes are fully encrypted on the server, ensuring your data is protected.</p>
</div>
<div class="feature-card p-6 rounded-lg">
<div class="flex items-center justify-center h-12 w-12 rounded-md bg-blue-500 text-white mx-auto mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0 3.517-1.009 6.786-2.673 9.356 m-2.673-9.356C6.673 8.214 9.327 5.5 12 5.5 c2.673 0 5.327 2.714 5.327 5.5s-1.009 6.786-2.673 9.356m-2.673-9.356h0z" /></svg>
</div>
<h3 class="text-xl font-semibold text-white mb-2">Anonymous by Design</h3>
<p class="text-gray-400">Image metadata (EXIF) is stripped and no unnecessary logs are kept. Built for the I2P network.</p>
</div>
<div class="feature-card p-6 rounded-lg">
<div class="flex items-center justify-center h-12 w-12 rounded-md bg-blue-500 text-white mx-auto mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" /></svg>
</div>
<h3 class="text-xl font-semibold text-white mb-2">StormyCloud Infrastructure</h3>
<p class="text-gray-400">A fast, reliable, and secure platform dedicated to the privacy of the I2P community.</p>
</div>
</div>
</section>
<footer class="text-center text-gray-500 mt-16 border-t border-gray-700 pt-8 pb-8">
<a href="http://stormycloud.i2p" class="hover:text-gray-400">StormyCloud</a>
<span class="mx-2">|</span>
<a href="/donate" class="hover:text-gray-400">Donate</a>
</footer>
</main>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const contentDivs = {
'#image': document.querySelector('#image-form'),
'#paste': document.querySelector('#paste-form'),
'#api': document.querySelector('#api-docs'),
'#stats': document.querySelector('#stats-content'),
'#tos': document.querySelector('#tos-content')
};
const tabs = document.querySelectorAll('#tab-nav a');
function showTab(hash) {
if (!hash || !contentDivs[hash]) hash = '#image';
Object.values(contentDivs).forEach(d => {
if(d) d.classList.add('hidden');
});
tabs.forEach(t => t.classList.remove('active'));
if(contentDivs[hash]) {
contentDivs[hash].classList.remove('hidden');
}
const activeTab = document.querySelector(`#tab-nav a[href="${hash}"]`);
if(activeTab) {
activeTab.classList.add('active');
}
}
tabs.forEach(tab => tab.addEventListener('click', e => {
e.preventDefault();
const h = e.target.hash;
window.history.replaceState(null, null, ' ' + h);
showTab(h);
}));
showTab(window.location.hash);
function toggle(cbId, tgtId) {
const cb = document.getElementById(cbId), tgt = document.getElementById(tgtId);
if (!cb||!tgt) return;
cb.addEventListener('change', () => tgt.style.display = cb.checked ? 'block' : 'none');
tgt.style.display = cb.checked ? 'block' : 'none';
}
toggle('image-pw-protect','image-pw-options');
toggle('image-views-protect','image-views-options');
toggle('paste-pw-protect','paste-pw-options');
toggle('paste-views-protect','paste-views-options');
const closeBtn = document.getElementById('close-announcement');
if (closeBtn) closeBtn.addEventListener('click', ()=>
document.getElementById('announcement-bar').style.display = 'none'
);
});
</script>
</body>
</html>

View File

@ -1,108 +1,120 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Image Uploaded - I2P Secure Share</title>
<link rel="stylesheet" href="/static/css/tailwind.css"/>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<style>
body { background-color: #1a202c; color: #cbd5e0; }
.link-box { background-color: #2d3748; border:1px solid #4a5568; word-break:break-all; }
.btn { background-color:#4299e1; transition:background-color .3s ease; }
.btn:hover { background-color:#3182ce; }
.thumbnail-container { border:2px dashed #4a5568; max-width:100%; }
.thumbnail { max-width:100%; max-height:60vh; object-fit:contain; }
.announcement-bar { background-color:#2563eb; border-bottom:1px solid #1e3a8a; }
</style>
</head>
<body class="font-sans">
{% if announcement_enabled and announcement_message %}
<div id="announcement-bar" class="announcement-bar text-white text-center p-2 relative shadow-lg">
<span>{{ announcement_message }}</span>
<button id="close-announcement" class="absolute top-0 right-0 mt-2 mr-4 text-white hover:text-gray-200 text-2xl leading-none">&times;</button>
</div>
{% endif %}
<div class="flex items-center justify-center min-h-screen py-8">
<div class="w-full max-w-2xl mx-auto p-4">
{% if password_required %}
<div class="content-container rounded-lg p-8 shadow-lg">
<h2 class="text-2xl font-semibold text-white mb-6">Enter Password to View Image</h2>
<form method="POST">
<div class="mb-6">
<label for="password" class="block text-gray-300 text-sm font-bold mb-2">Password:</label>
<input type="password" name="password" id="password"
class="w-full p-2 rounded-md text-white bg-gray-700 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
required>
</div>
<button type="submit" class="btn w-full text-white font-bold py-3 px-5 rounded-md">Unlock Image</button>
</form>
</div>
{% else %}
<header class="text-center mb-8">
<a href="/" class="inline-block mb-4">
<img src="/static/images/stormycloud.svg"
alt="StormyCloud Logo"
style="width:550px; max-width:100%;" class="mx-auto"/>
</a>
<h1 class="text-3xl font-bold text-white">Image Uploaded Successfully</h1>
<p class="text-gray-400 mt-2 text-xl">Expires in: {{ time_left }}</p>
</header>
<main>
<div class="thumbnail-container rounded-lg p-4 mb-6 flex justify-center items-center">
<img src="/uploads/{{ filename }}"
alt="Uploaded Image" class="thumbnail rounded-md"/>
</div>
<div class="link-box rounded-lg p-4 mb-4">
<label for="share-link" class="block text-gray-300 text-sm font-bold mb-2">Direct Image Link:</label>
<div class="flex items-center space-x-2">
<input type="text" id="share-link"
class="bg-gray-700 text-white w-full p-2 border border-gray-600 rounded-md"
value="{{ request.host_url }}uploads/{{ filename }}" readonly>
<button id="copy-button"
class="btn whitespace-nowrap flex-shrink-0 text-white font-bold py-3 px-5 rounded-md">
Copy
</button>
</div>
</div>
<div class="text-center mt-8">
<a href="/" class="text-blue-400 hover:text-blue-300">Upload another file</a>
</div>
<div class="text-center mt-4 text-sm">
<p class="text-gray-500">Find this service useful? <a href="/donate" class="text-blue-400 hover:underline">Consider supporting its future.</a></p>
</div>
</main>
{% endif %}
<footer class="text-center text-gray-500 mt-16 border-t border-gray-700 pt-8 pb-8">
<a href="http://stormycloud.i2p" class="hover:text-gray-400">StormyCloud</a>
<span class="mx-2">|</span>
<a href="/donate" class="hover:text-gray-400">Donate</a>
</footer>
</div>
</div>
<script>
const copyBtn = document.getElementById('copy-button');
if (copyBtn) {
copyBtn.addEventListener('click', () => {
const inp = document.getElementById('share-link');
inp.select(); document.execCommand('copy');
copyBtn.textContent = 'Copied!';
setTimeout(() => copyBtn.textContent = 'Copy', 2000);
});
}
const closeBtn = document.getElementById('close-announcement');
if (closeBtn) closeBtn.addEventListener('click', () =>
document.getElementById('announcement-bar').style.display = 'none'
);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>View Image - I2P Secure Share</title>
<link rel="stylesheet" href="/static/css/tailwind.css"/>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<style>
body { background-color: #1a202c; color: #cbd5e0; }
.content-container { background-color: #2d3748; border: 1px solid #4a5568; }
.link-box { background-color: #2d3748; border:1px solid #4a5568; word-break:break-all; }
.btn { background-color:#4299e1; transition:background-color .3s ease; }
.btn:hover { background-color:#3182ce; }
.thumbnail-container { border:2px dashed #4a5568; max-width:100%; }
.thumbnail { max-width:100%; max-height:60vh; object-fit:contain; }
.announcement-bar { background-color:#2563eb; border-bottom:1px solid #1e3a8a; }
.alert-success { background-color:#38a169; }
.alert-error { background-color:#e53e3e; }
</style>
</head>
<body class="font-sans">
{% if announcement_enabled and announcement_message %}
<div id="announcement-bar" class="announcement-bar text-white text-center p-2 relative shadow-lg">
<span>{{ announcement_message }}</span>
<button id="close-announcement" class="absolute top-0 right-0 mt-2 mr-4 text-white hover:text-gray-200 text-2xl leading-none">&times;</button>
</div>
{% endif %}
<div class="flex items-center justify-center min-h-screen py-8">
<div class="w-full max-w-2xl mx-auto p-4">
{% if password_required %}
<div class="content-container rounded-lg p-8 shadow-lg">
<h2 class="text-2xl font-semibold text-white mb-6">Enter Password to View Image</h2>
<form method="POST">
<div class="mb-6">
<label for="password" class="block text-gray-300 text-sm font-bold mb-2">Password:</label>
<input type="password" name="password" id="password"
class="w-full p-2 rounded-md text-white bg-gray-700 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
required>
</div>
<button type="submit" class="btn w-full text-white font-bold py-3 px-5 rounded-md">Unlock Image</button>
</form>
</div>
{% else %}
<header class="text-center mb-8">
<a href="/" class="inline-block mb-4">
<img src="/static/images/stormycloud.svg" alt="StormyCloud Logo" style="width:350px; max-width:100%;" class="mx-auto"/>
</a>
<h1 class="text-3xl font-bold text-white">View Image</h1>
<p class="text-gray-400 mt-2 text-xl">Expires in: {{ time_left }}</p>
</header>
<main>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mb-4">
{% for category, message in messages %}
<div class="alert-{{category}} text-white p-3 rounded-md shadow-lg"
role="alert">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="thumbnail-container rounded-lg p-4 mb-6 flex justify-center items-center">
<img src="/uploads/{{ filename }}"
alt="Uploaded Image" class="thumbnail rounded-md"/>
</div>
<div class="link-box rounded-lg p-4 mb-4">
<label for="share-link" class="block text-gray-300 text-sm font-bold mb-2">Direct Image Link:</label>
<div class="flex items-center space-x-2">
<input type="text" id="share-link"
class="bg-gray-700 text-white w-full p-2 border border-gray-600 rounded-md"
value="{{ request.host_url }}uploads/{{ filename }}" readonly>
<button id="copy-button"
class="btn whitespace-nowrap flex-shrink-0 text-white font-bold py-3 px-5 rounded-md">
Copy
</button>
</div>
</div>
<div class="text-center mt-8">
<a href="/" class="text-blue-400 hover:text-blue-300">Upload another file</a>
</div>
<div class="text-center mt-4 text-sm">
<p class="text-gray-500">Find this service useful? <a href="/donate" class="text-blue-400 hover:underline">Consider supporting its future.</a></p>
</div>
</main>
{% endif %}
<footer class="text-center text-gray-500 mt-16 border-t border-gray-700 pt-8 pb-8">
<a href="http://stormycloud.i2p" class="hover:text-gray-400">StormyCloud</a>
<span class="mx-2">|</span>
<a href="/donate" class="hover:text-gray-400">Donate</a>
</footer>
</div>
</div>
<script>
const copyBtn = document.getElementById('copy-button');
if (copyBtn) {
copyBtn.addEventListener('click', () => {
const inp = document.getElementById('share-link');
inp.select(); document.execCommand('copy');
copyBtn.textContent = 'Copied!';
setTimeout(() => copyBtn.textContent = 'Copy', 2000);
});
}
const closeBtn = document.getElementById('close-announcement');
if (closeBtn) closeBtn.addEventListener('click', () =>
document.getElementById('announcement-bar').style.display = 'none'
);
</script>
</body>
</html>

View File

@ -1,118 +1,214 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Paste Created - I2P Secure Share</title>
<link rel="stylesheet" href="/static/css/tailwind.css"/>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<style>
body { background-color: #1a202c; color: #cbd5e0; }
.link-box { background-color: #2d3748; border:1px solid #4a5568; word-break:break-all; }
.btn { background-color:#4299e1; transition:background-color .3s ease; }
.btn:hover { background-color:#3182ce; }
.code-container {
background-color:#2d3748; border-radius:0.5rem; border:1px solid #4a5568; overflow:hidden;
}
.syntax pre {
margin:0; padding:1rem; white-space:pre-wrap; word-wrap:break-word;
}
.announcement-bar { background-color:#2563eb; border-bottom:1px solid #1e3a8a; }
.wide-container { max-width: 85rem; }
</style>
</head>
<body class="font-sans">
{% if announcement_enabled and announcement_message %}
<div id="announcement-bar" class="announcement-bar text-white text-center p-2 relative shadow-lg">
<span>{{ announcement_message }}</span>
<button id="close-announcement" class="absolute top-0 right-0 mt-2 mr-4 text-white hover:text-gray-200 text-2xl leading-none">&times;</button>
</div>
{% endif %}
<div class="flex items-center justify-center min-h-screen py-8">
<div class="w-full wide-container mx-auto p-6">
{% if password_required %}
<div class="content-container rounded-lg p-10 shadow-lg"></div>
<h2 class="text-2xl font-semibold text-white mb-6">Enter Password to View Paste</h2>
<form method="POST">
<div class="mb-6">
<label for="password" class="block text-gray-300 text-sm font-bold mb-2">Password:</label>
<input type="password" name="password" id="password"
class="w-full p-2 rounded-md text-white bg-gray-700 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
required>
</div>
<button type="submit" class="btn w-full text-white font-bold py-3 px-5 rounded-md">Unlock Paste</button>
</form>
</div>
{% else %}
<header class="text-center mb-8">
<a href="/" class="inline-block mb-4">
<img src="/static/images/stormycloud.svg"
alt="StormyCloud Logo"
style="width:550px; max-width:100%;" class="mx-auto"/>
</a>
<h1 class="text-3xl font-bold text-white">Paste Created Successfully</h1>
<p class="text-gray-400 mt-2 text-xl">Expires in: {{ time_left }}</p>
</header>
<main>
<div class="code-container mb-6 syntax">
{{ highlighted_content|safe }}
</div>
<div class="link-box rounded-lg p-4 mb-4">
<label for="share-link" class="block text-gray-300 text-sm font-bold mb-2">Shareable Link:</label>
<div class="flex items-center space-x-2">
<input type="text" id="share-link"
class="bg-gray-700 text-white w-full p-2 border border-gray-600 rounded-md"
value="{{ request.url }}" readonly>
<button id="copy-button"
class="btn whitespace-nowrap flex-shrink-0 text-white font-bold py-3 px-5 rounded-md">
Copy
</button>
<a href="/paste/{{ paste_id }}/raw"
target="_blank"
class="btn whitespace-nowrap flex-shrink-0 text-white font-bold py-3 px-5 rounded-md">
Raw
</a>
</div>
</div>
<div class="text-center mt-8">
<a href="/" class="text-blue-400 hover:text-blue-300">Create another paste</a>
</div>
<div class="text-center mt-4 text-sm">
<p class="text-gray-500">Find this service useful? <a href="/donate" class="text-blue-400 hover:underline">Consider supporting its future.</a></p>
</div>
</main>
{% endif %}
<footer class="text-center text-gray-500 mt-16 border-t border-gray-700 pt-8 pb-8">
<a href="http://stormycloud.i2p" class="hover:text-gray-400">StormyCloud</a>
<span class="mx-2">|</span>
<a href="/donate" class="hover:text-gray-400">Donate</a>
</footer>
</div>
</div>
<script>
const copyBtn = document.getElementById('copy-button');
if (copyBtn) {
copyBtn.addEventListener('click', () => {
const inp = document.getElementById('share-link');
inp.select(); document.execCommand('copy');
copyBtn.textContent = 'Copied!';
setTimeout(() => copyBtn.textContent = 'Copy', 2000);
});
}
const closeBtn = document.getElementById('close-announcement');
if (closeBtn) closeBtn.addEventListener('click', () =>
document.getElementById('announcement-bar').style.display = 'none'
);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>View Paste - I2P Secure Share</title>
<link rel="stylesheet" href="/static/css/tailwind.css"/>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
<style>
body { background-color: #1a202c; color: #cbd5e0; }
.link-box { background-color: #2d3748; border:1px solid #4a5568; word-break:break-all; }
.btn { background-color:#4299e1; transition:background-color .3s ease; }
.btn:hover { background-color:#3182ce; }
.code-container {
background-color:#2d3748; border-radius:0.5rem; border:1px solid #4a5568; overflow:hidden;
}
.syntax pre {
margin:0; padding:1rem; white-space:pre-wrap; word-wrap:break-word;
}
.announcement-bar { background-color:#2563eb; border-bottom:1px solid #1e3a8a; }
.wide-container { max-width: 85rem; }
.alert-success { background-color:#38a169; }
.alert-error { background-color:#e53e3e; }
/* Pygments 'monokai' theme CSS */
.syntax .hll { background-color: #49483e }
.syntax { background: #272822; color: #f8f8f2 }
.syntax .c { color: #75715e } /* Comment */
.syntax .err { color: #960050; background-color: #1e0010 } /* Error */
.syntax .k { color: #66d9ef } /* Keyword */
.syntax .l { color: #ae81ff } /* Literal */
.syntax .n { color: #f8f8f2 } /* Name */
.syntax .o { color: #f92672 } /* Operator */
.syntax .p { color: #f8f8f2 } /* Punctuation */
.syntax .ch { color: #75715e } /* Comment.Hashbang */
.syntax .cm { color: #75715e } /* Comment.Multiline */
.syntax .cp { color: #75715e } /* Comment.Preproc */
.syntax .cpf { color: #75715e } /* Comment.PreprocFile */
.syntax .c1 { color: #75715e } /* Comment.Single */
.syntax .cs { color: #75715e } /* Comment.Special */
.syntax .gd { color: #f92672 } /* Generic.Deleted */
.syntax .ge { font-style: italic } /* Generic.Emph */
.syntax .gi { color: #a6e22e } /* Generic.Inserted */
.syntax .gs { font-weight: bold } /* Generic.Strong */
.syntax .gu { color: #75715e } /* Generic.Subheading */
.syntax .kc { color: #66d9ef } /* Keyword.Constant */
.syntax .kd { color: #66d9ef } /* Keyword.Declaration */
.syntax .kn { color: #f92672 } /* Keyword.Namespace */
.syntax .kp { color: #66d9ef } /* Keyword.Pseudo */
.syntax .kr { color: #66d9ef } /* Keyword.Reserved */
.syntax .kt { color: #66d9ef } /* Keyword.Type */
.syntax .ld { color: #e6db74 } /* Literal.Date */
.syntax .m { color: #ae81ff } /* Literal.Number */
.syntax .s { color: #e6db74 } /* Literal.String */
.syntax .na { color: #a6e22e } /* Name.Attribute */
.syntax .nb { color: #f8f8f2 } /* Name.Builtin */
.syntax .nc { color: #a6e22e } /* Name.Class */
.syntax .no { color: #66d9ef } /* Name.Constant */
.syntax .nd { color: #a6e22e } /* Name.Decorator */
.syntax .ni { color: #f8f8f2 } /* Name.Entity */
.syntax .ne { color: #a6e22e } /* Name.Exception */
.syntax .nf { color: #a6e22e } /* Name.Function */
.syntax .nl { color: #f8f8f2 } /* Name.Label */
.syntax .nn { color: #f8f8f2 } /* Name.Namespace */
.syntax .nx { color: #a6e22e } /* Name.Other */
.syntax .py { color: #f8f8f2 } /* Name.Property */
.syntax .nt { color: #f92672 } /* Name.Tag */
.syntax .nv { color: #f8f8f2 } /* Name.Variable */
.syntax .ow { color: #f92672 } /* Operator.Word */
.syntax .w { color: #f8f8f2 } /* Text.Whitespace */
.syntax .mb { color: #ae81ff } /* Literal.Number.Bin */
.syntax .mf { color: #ae81ff } /* Literal.Number.Float */
.syntax .mh { color: #ae81ff } /* Literal.Number.Hex */
.syntax .mi { color: #ae81ff } /* Literal.Number.Integer */
.syntax .mo { color: #ae81ff } /* Literal.Number.Oct */
.syntax .sa { color: #e6db74 } /* Literal.String.Affix */
.syntax .sb { color: #e6db74 } /* Literal.String.Backtick */
.syntax .sc { color: #e6db74 } /* Literal.String.Char */
.syntax .dl { color: #e6db74 } /* Literal.String.Delimiter */
.syntax .sd { color: #e6db74 } /* Literal.String.Doc */
.syntax .s2 { color: #e6db74 } /* Literal.String.Double */
.syntax .se { color: #ae81ff } /* Literal.String.Escape */
.syntax .sh { color: #e6db74 } /* Literal.String.Heredoc */
.syntax .si { color: #e6db74 } /* Literal.String.Interpol */
.syntax .sx { color: #e6db74 } /* Literal.String.Other */
.syntax .sr { color: #e6db74 } /* Literal.String.Regex */
.syntax .s1 { color: #e6db74 } /* Literal.String.Single */
.syntax .ss { color: #e6db74 } /* Literal.String.Symbol */
.syntax .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.syntax .fm { color: #a6e22e } /* Name.Function.Magic */
.syntax .vc { color: #f8f8f2 } /* Name.Variable.Class */
.syntax .vg { color: #f8f8f2 } /* Name.Variable.Global */
.syntax .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.syntax .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.syntax .il { color: #ae81ff } /* Literal.Number.Integer.Long */
</style>
</head>
<body class="font-sans">
{% if announcement_enabled and announcement_message %}
<div id="announcement-bar" class="announcement-bar text-white text-center p-2 relative shadow-lg">
<span>{{ announcement_message }}</span>
<button id="close-announcement" class="absolute top-0 right-0 mt-2 mr-4 text-white hover:text-gray-200 text-2xl leading-none">&times;</button>
</div>
{% endif %}
<div class="flex items-center justify-center min-h-screen py-8">
<div class="w-full wide-container mx-auto p-6">
{% if password_required %}
<div class="content-container rounded-lg p-10 shadow-lg"></div>
<h2 class="text-2xl font-semibold text-white mb-6">Enter Password to View Paste</h2>
<form method="POST">
<div class="mb-6">
<label for="password" class="block text-gray-300 text-sm font-bold mb-2">Password:</label>
<input type="password" name="password" id="password"
class="w-full p-2 rounded-md text-white bg-gray-700 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
required>
</div>
<button type="submit" class="btn w-full text-white font-bold py-3 px-5 rounded-md">Unlock Paste</button>
</form>
</div>
{% else %}
<header class="text-center mb-8">
<a href="/" class="inline-block mb-4">
<img src="/static/images/stormycloud.svg" alt="StormyCloud Logo" style="width:350px; max-width:100%;" class="mx-auto"/>
</a>
<h1 class="text-3xl font-bold text-white">View Paste</h1>
<p class="text-gray-400 mt-2 text-xl">Expires in: {{ time_left }}</p>
</header>
<main>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="mb-4">
{% for category, message in messages %}
<div class="alert-{{category}} text-white p-3 rounded-md shadow-lg"
role="alert">{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="flex justify-end mb-2">
<form method="GET" action="" class="flex items-center space-x-2">
<label for="language-switcher" class="text-sm text-gray-400">Syntax:</label>
<select name="lang" id="language-switcher" onchange="this.form.submit()" class="text-sm rounded-md p-1 bg-gray-700 border border-gray-600 focus:outline-none focus:ring-1 focus:ring-blue-500 cursor-pointer">
{% for lang in languages %}
<option value="{{ lang }}" {% if lang == selected_language %}selected{% endif %}>
{{ lang|capitalize }}
</option>
{% endfor %}
</select>
<noscript><button type="submit" class="btn text-sm py-1 px-3">Update</button></noscript>
</form>
</div>
<div class="code-container mb-6 syntax">
{{ highlighted_content|safe }}
</div>
<div class="link-box rounded-lg p-4 mb-4">
<label for="share-link" class="block text-gray-300 text-sm font-bold mb-2">Shareable Link:</label>
<div class="flex items-center space-x-2">
<input type="text" id="share-link"
class="bg-gray-700 text-white w-full p-2 border border-gray-600 rounded-md"
value="{{ request.url }}" readonly>
<button id="copy-button"
class="btn whitespace-nowrap flex-shrink-0 text-white font-bold py-3 px-5 rounded-md">
Copy
</button>
<a href="/paste/{{ paste_id }}/raw"
target="_blank"
class="btn whitespace-nowrap flex-shrink-0 text-white font-bold py-3 px-5 rounded-md">
Raw
</a>
</div>
</div>
<div class="text-center mt-8">
<a href="/" class="text-blue-400 hover:text-blue-300">Create another paste</a>
</div>
<div class="text-center mt-4 text-sm">
<p class="text-gray-500">Find this service useful? <a href="/donate" class="text-blue-400 hover:underline">Consider supporting its future.</a></p>
</div>
</main>
{% endif %}
<footer class="text-center text-gray-500 mt-16 border-t border-gray-700 pt-8 pb-8">
<a href="http://stormycloud.i2p" class="hover:text-gray-400">StormyCloud</a>
<span class="mx-2">|</span>
<a href="/donate" class="hover:text-gray-400">Donate</a>
</footer>
</div>
</div>
<script>
const copyBtn = document.getElementById('copy-button');
if (copyBtn) {
copyBtn.addEventListener('click', () => {
const inp = document.getElementById('share-link');
inp.select(); document.execCommand('copy');
copyBtn.textContent = 'Copied!';
setTimeout(() => copyBtn.textContent = 'Copy', 2000);
});
}
const closeBtn = document.getElementById('close-announcement');
if (closeBtn) closeBtn.addEventListener('click', () =>
document.getElementById('announcement-bar').style.display = 'none'
);
</script>
</body>
</html>