redesign site interface
This commit is contained in:
		
							parent
							
								
									d26f309cf5
								
							
						
					
					
						commit
						7a1952b100
					
				
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							| @ -7,6 +7,7 @@ require ( | |||||||
| 	github.com/alexedwards/scs/sqlite3store v0.0.0-20250212122300-421ef1d8611c | 	github.com/alexedwards/scs/sqlite3store v0.0.0-20250212122300-421ef1d8611c | ||||||
| 	github.com/alexedwards/scs/v2 v2.8.0 | 	github.com/alexedwards/scs/v2 v2.8.0 | ||||||
| 	github.com/coreos/go-oidc/v3 v3.14.1 | 	github.com/coreos/go-oidc/v3 v3.14.1 | ||||||
|  | 	github.com/golang-migrate/migrate/v4 v4.18.3 | ||||||
| 	github.com/gorilla/schema v1.4.1 | 	github.com/gorilla/schema v1.4.1 | ||||||
| 	github.com/joho/godotenv v1.5.1 | 	github.com/joho/godotenv v1.5.1 | ||||||
| 	github.com/justinas/alice v1.2.0 | 	github.com/justinas/alice v1.2.0 | ||||||
| @ -16,4 +17,9 @@ require ( | |||||||
| 	golang.org/x/oauth2 v0.30.0 | 	golang.org/x/oauth2 v0.30.0 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| require github.com/go-jose/go-jose/v4 v4.0.5 // indirect | require ( | ||||||
|  | 	github.com/go-jose/go-jose/v4 v4.0.5 // indirect | ||||||
|  | 	github.com/hashicorp/errwrap v1.1.0 // indirect | ||||||
|  | 	github.com/hashicorp/go-multierror v1.1.1 // indirect | ||||||
|  | 	go.uber.org/atomic v1.7.0 // indirect | ||||||
|  | ) | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								go.sum
									
									
									
									
									
								
							| @ -6,27 +6,41 @@ github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZx | |||||||
| github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= | github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= | ||||||
| github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= | github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= | ||||||
| github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= | github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= | ||||||
|  | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= | github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= | ||||||
| github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= | github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= | ||||||
|  | github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs= | ||||||
|  | github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= | ||||||
| github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
| github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
| github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= | github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E= | ||||||
| github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= | github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM= | ||||||
|  | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||||||
|  | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= | ||||||
|  | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||||||
|  | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= | ||||||
|  | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= | ||||||
| github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= | ||||||
| github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= | ||||||
| github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= | github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= | ||||||
| github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= | github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= | ||||||
| github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk= | github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk= | ||||||
| github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= | github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ= | ||||||
|  | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= | ||||||
|  | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= | ||||||
| github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= | ||||||
| github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= | github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= | ||||||
| github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= | github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= | ||||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
|  | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
|  | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
|  | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= | ||||||
|  | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= | ||||||
| golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= | golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= | ||||||
| golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= | golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= | ||||||
| golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= | ||||||
|  | |||||||
| @ -1,86 +1,882 @@ | |||||||
| /* html { | /* CSS Reset and Base Styles */ | ||||||
|   background: lightgray; | *, | ||||||
| } */ | *::before, | ||||||
|  | *::after { | ||||||
|  |   box-sizing: border-box; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|  | * { | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | html { | ||||||
|  |   font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; | ||||||
|  |   line-height: 1.6; | ||||||
|  |   -webkit-font-smoothing: antialiased; | ||||||
|  |   -moz-osx-font-smoothing: grayscale; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* CSS Custom Properties for Theming */ | ||||||
|  | :root { | ||||||
|  |   /* Light mode colors */ | ||||||
|  |   --color-primary: #2563eb; | ||||||
|  |   --color-primary-hover: #1d4ed8; | ||||||
|  |   --color-danger: #dc2626; | ||||||
|  |   --color-danger-hover: #b91c1c; | ||||||
|  |   --color-warning: #d97706; | ||||||
|  | 
 | ||||||
|  |   --color-text: #1f2937; | ||||||
|  |   --color-text-muted: #6b7280; | ||||||
|  |   --color-background: #ffffff; | ||||||
|  |   --color-surface: #f9fafb; | ||||||
|  |   --color-border: #e5e7eb; | ||||||
|  |   --color-border-light: #f3f4f6; | ||||||
|  | 
 | ||||||
|  |   --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); | ||||||
|  | 
 | ||||||
|  |   /* Spacing scale */ | ||||||
|  |   --space-xs: 0.25rem; | ||||||
|  |   --space-sm: 0.5rem; | ||||||
|  |   --space-md: 1rem; | ||||||
|  |   --space-lg: 1.5rem; | ||||||
|  |   --space-xl: 2rem; | ||||||
|  |   --space-2xl: 3rem; | ||||||
|  |   --space-3xl: 4rem; | ||||||
|  | 
 | ||||||
|  |   /* Typography scale */ | ||||||
|  |   --text-xs: 0.75rem; | ||||||
|  |   --text-sm: 0.875rem; | ||||||
|  |   --text-base: 1rem; | ||||||
|  |   --text-lg: 1.125rem; | ||||||
|  |   --text-xl: 1.25rem; | ||||||
|  |   --text-2xl: 1.5rem; | ||||||
|  |   --text-3xl: 1.875rem; | ||||||
|  |   --text-4xl: 2.25rem; | ||||||
|  | 
 | ||||||
|  |   /* Border radius */ | ||||||
|  |   --radius-sm: 0.125rem; | ||||||
|  |   --radius-md: 0.375rem; | ||||||
|  |   --radius-lg: 0.5rem; | ||||||
|  | 
 | ||||||
|  |   /* Layout */ | ||||||
|  |   --max-width: 1200px; | ||||||
|  |   --header-height: 60px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Dark mode colors */ | ||||||
|  | @media (prefers-color-scheme: dark) { | ||||||
|  |   :root { | ||||||
|  |     --color-text: #f9fafb; | ||||||
|  |     --color-text-muted: #9ca3af; | ||||||
|  |     --color-background: #111827; | ||||||
|  |     --color-surface: #1f2937; | ||||||
|  |     --color-border: #374151; | ||||||
|  |     --color-border-light: #4b5563; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Base Typography */ | ||||||
| body { | body { | ||||||
|   max-width: 1024px; |   font-size: var(--text-base); | ||||||
|   margin: 1rem auto; |   color: var(--color-text); | ||||||
|   padding: 1rem; |   background-color: var(--color-background); | ||||||
|   /* background: white; */ |   min-height: 100vh; | ||||||
|   font-size: 1.2rem; |  | ||||||
|   line-height: 1.5; |  | ||||||
|   font-family: Arial, Helvetica, sans-serif; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| header { |  | ||||||
|   text-align: center; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| body > nav { |  | ||||||
|   display: flex; |   display: flex; | ||||||
|   justify-content: space-between; |   flex-direction: column; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| body > nav ul { | h1, h2, h3, h4, h5, h6 { | ||||||
|   list-style: none; |   font-weight: 600; | ||||||
|   margin: 0 1rem; |   line-height: 1.25; | ||||||
|   padding: 0; |   margin-bottom: var(--space-md); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| body > nav li { | h1 { font-size: var(--text-3xl); } | ||||||
|   display: inline-block; | h2 { font-size: var(--text-2xl); } | ||||||
|   padding: 0 0.5rem; | h3 { font-size: var(--text-xl); } | ||||||
| } | h4 { font-size: var(--text-lg); } | ||||||
| 
 | 
 | ||||||
| nav form { | p { | ||||||
|   display: inline-block; |   margin-bottom: var(--space-md); | ||||||
| } |  | ||||||
| 
 |  | ||||||
| nav button { |  | ||||||
|   border: none; |  | ||||||
|   background: none; |  | ||||||
|   font-family: unset; |  | ||||||
|   font-size: unset; |  | ||||||
|   /* color: blue; */ |  | ||||||
|   /* text-decoration: underline; */ |  | ||||||
|   cursor: pointer; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| main { |  | ||||||
|   padding: 1rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| div#dashboard { |  | ||||||
|   display: flex; |  | ||||||
|   flex-flow: row wrap; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| div#dashboard nav { |  | ||||||
|   flex: 1 1 25%; |  | ||||||
|   /* margin-top: 2rem; */ |  | ||||||
|   min-width: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| div#dashboard > div { |  | ||||||
|   flex: 10 1 40%; |  | ||||||
|   min-width: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| div > pre { |  | ||||||
|     max-width: 100%; |  | ||||||
|     overflow: auto; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| main nav ul { |  | ||||||
|   list-style: none; |  | ||||||
|   margin: 1rem; |  | ||||||
|   padding: 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| footer { |  | ||||||
|   text-align: center; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| a { | a { | ||||||
|   /* color: blue; */ |   color: var(--color-primary); | ||||||
|  |   text-decoration: none; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | a:hover { | ||||||
|  |   color: var(--color-primary-hover); | ||||||
|  |   text-decoration: underline; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Layout Components */ | ||||||
|  | body > header { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border-bottom: 1px solid var(--color-border); | ||||||
|  |   height: var(--header-height); | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   padding: 0 var(--space-lg); | ||||||
|  |   position: sticky; | ||||||
|  |   top: 0; | ||||||
|  |   z-index: 100; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body > header h1 { | ||||||
|  |   margin: 0; | ||||||
|  |   font-size: var(--text-2xl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body > header h1 a { | ||||||
|  |   color: var(--color-text); | ||||||
|  |   font-weight: 700; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | body > header h1 a:hover { | ||||||
|  |   text-decoration: none; | ||||||
|  |   color: var(--color-primary); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | nav { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border-bottom: 1px solid var(--color-border); | ||||||
|  |   padding: var(--space-md) var(--space-lg); | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: center; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   gap: var(--space-md); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .nav-welcome { | ||||||
|  |   font-weight: 500; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .nav-links { | ||||||
|  |   display: flex; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   gap: var(--space-md); | ||||||
|  |   list-style: none; | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 0; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .nav-links li { | ||||||
|  |   margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .nav-links a { | ||||||
|  |   padding: var(--space-sm) var(--space-md); | ||||||
|  |   border-radius: var(--radius-md); | ||||||
|  |   transition: background-color 0.2s ease; | ||||||
|  |   white-space: nowrap; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .nav-links a:hover { | ||||||
|  |   background-color: var(--color-border-light); | ||||||
|  |   text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | main { | ||||||
|  |   flex: 1; | ||||||
|  |   max-width: var(--max-width); | ||||||
|  |   margin: 0 auto; | ||||||
|  |   padding: var(--space-2xl) var(--space-lg); | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | footer { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border-top: 1px solid var(--color-border); | ||||||
|  |   padding: var(--space-lg); | ||||||
|  |   text-align: center; | ||||||
|  |   margin-top: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Dashboard Layout */ | ||||||
|  | #dashboard { | ||||||
|  |   display: grid; | ||||||
|  |   grid-template-columns: 280px 1fr; | ||||||
|  |   gap: var(--space-2xl); | ||||||
|  |   margin-top: var(--space-xl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #dashboard nav { | ||||||
|  |   background: none; | ||||||
|  |   border: none; | ||||||
|  |   padding: 0; | ||||||
|  |   display: block; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #dashboard nav > div { | ||||||
|  |   margin-bottom: var(--space-xl); | ||||||
|  |   padding: var(--space-lg); | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  |   border-radius: var(--radius-lg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #dashboard nav h3 { | ||||||
|  |   font-size: var(--text-lg); | ||||||
|  |   margin-bottom: var(--space-md); | ||||||
|  |   padding-bottom: var(--space-sm); | ||||||
|  |   border-bottom: 1px solid var(--color-border); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #dashboard nav ul { | ||||||
|  |   list-style: none; | ||||||
|  |   margin-bottom: var(--space-md); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #dashboard nav ul:last-child { | ||||||
|  |   margin-bottom: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #dashboard nav li { | ||||||
|  |   margin-bottom: var(--space-xs); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #dashboard nav a { | ||||||
|  |   display: block; | ||||||
|  |   padding: var(--space-sm) var(--space-md); | ||||||
|  |   border-radius: var(--radius-md); | ||||||
|  |   transition: background-color 0.2s ease; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #dashboard nav a:hover { | ||||||
|  |   background-color: var(--color-border-light); | ||||||
|  |   text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Forms */ | ||||||
|  | form { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   padding: var(--space-xl); | ||||||
|  |   border-radius: var(--radius-lg); | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  |   margin-bottom: var(--space-xl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-group { | ||||||
|  |   margin-bottom: var(--space-lg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .form-group small { | ||||||
|  |   display: block; | ||||||
|  |   margin-top: var(--space-xs); | ||||||
|  |   font-size: var(--text-sm); | ||||||
|  |   color: var(--color-text-muted); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fieldset { | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  |   border-radius: var(--radius-md); | ||||||
|  |   padding: var(--space-lg); | ||||||
|  |   margin-bottom: var(--space-lg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fieldset.radio-group { | ||||||
|  |   border: none; | ||||||
|  |   padding: 0; | ||||||
|  |   margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fieldset.danger-zone { | ||||||
|  |   border-color: var(--color-danger); | ||||||
|  |   background-color: rgba(220, 38, 38, 0.05); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | legend { | ||||||
|  |   padding: 0 var(--space-sm); | ||||||
|  |   font-weight: 600; | ||||||
|  |   color: var(--color-text); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label { | ||||||
|  |   display: block; | ||||||
|  |   font-weight: 500; | ||||||
|  |   margin-bottom: var(--space-sm); | ||||||
|  |   color: var(--color-text); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[type="text"], | ||||||
|  | input[type="email"], | ||||||
|  | input[type="url"], | ||||||
|  | textarea, | ||||||
|  | select { | ||||||
|  |   width: 100%; | ||||||
|  |   padding: var(--space-md); | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  |   border-radius: var(--radius-md); | ||||||
|  |   background-color: var(--color-background); | ||||||
|  |   color: var(--color-text); | ||||||
|  |   font-size: var(--text-base); | ||||||
|  |   transition: border-color 0.2s ease, box-shadow 0.2s ease; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input[type="text"]:focus, | ||||||
|  | input[type="email"]:focus, | ||||||
|  | input[type="url"]:focus, | ||||||
|  | textarea:focus, | ||||||
|  | select:focus { | ||||||
|  |   outline: none; | ||||||
|  |   border-color: var(--color-primary); | ||||||
|  |   box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | textarea { | ||||||
|  |   min-height: 120px; | ||||||
|  |   resize: vertical; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Radio buttons */ | ||||||
|  | input[type="radio"] { | ||||||
|  |   margin-right: var(--space-sm); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | label:has(input[type="radio"]) { | ||||||
|  |   display: inline-flex; | ||||||
|  |   align-items: center; | ||||||
|  |   margin-right: var(--space-lg); | ||||||
|  |   margin-bottom: var(--space-sm); | ||||||
|  |   font-weight: normal; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Buttons */ | ||||||
|  | button, | ||||||
|  | input[type="submit"] { | ||||||
|  |   display: inline-flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   padding: var(--space-md) var(--space-lg); | ||||||
|  |   border: none; | ||||||
|  |   border-radius: var(--radius-md); | ||||||
|  |   background-color: var(--color-primary); | ||||||
|  |   color: white; | ||||||
|  |   font-size: var(--text-base); | ||||||
|  |   font-weight: 500; | ||||||
|  |   cursor: pointer; | ||||||
|  |   transition: background-color 0.2s ease, transform 0.1s ease; | ||||||
|  |   min-height: 44px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button:hover, | ||||||
|  | input[type="submit"]:hover { | ||||||
|  |   background-color: var(--color-primary-hover); | ||||||
|  |   transform: translateY(-1px); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button:active, | ||||||
|  | input[type="submit"]:active { | ||||||
|  |   transform: translateY(0); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.danger { | ||||||
|  |   background-color: var(--color-danger); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.danger:hover { | ||||||
|  |   background-color: var(--color-danger-hover); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.outline { | ||||||
|  |   background-color: transparent; | ||||||
|  |   color: var(--color-text); | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | button.outline:hover { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border-color: var(--color-text-muted); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Button variants */ | ||||||
|  | .btn { | ||||||
|  |   display: inline-flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   padding: var(--space-md) var(--space-lg); | ||||||
|  |   border: none; | ||||||
|  |   border-radius: var(--radius-md); | ||||||
|  |   font-size: var(--text-base); | ||||||
|  |   font-weight: 500; | ||||||
|  |   cursor: pointer; | ||||||
|  |   text-decoration: none; | ||||||
|  |   transition: all 0.2s ease; | ||||||
|  |   min-height: 44px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .btn-primary { | ||||||
|  |   background-color: var(--color-primary); | ||||||
|  |   color: white; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .btn-primary:hover { | ||||||
|  |   background-color: var(--color-primary-hover); | ||||||
|  |   color: white; | ||||||
|  |   text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .btn-outline { | ||||||
|  |   background-color: transparent; | ||||||
|  |   color: var(--color-text); | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .btn-outline:hover { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border-color: var(--color-text-muted); | ||||||
|  |   text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Comments */ | ||||||
|  | #comments { | ||||||
|  |   margin-top: var(--space-2xl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .comment { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  |   border-radius: var(--radius-lg); | ||||||
|  |   padding: var(--space-lg); | ||||||
|  |   margin-bottom: var(--space-lg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .comment-header { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: flex-start; | ||||||
|  |   margin-bottom: var(--space-md); | ||||||
|  |   gap: var(--space-md); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .comment-meta { | ||||||
|  |   flex: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .comment-author { | ||||||
|  |   margin: 0 0 var(--space-xs) 0; | ||||||
|  |   font-size: var(--text-lg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .comment time { | ||||||
|  |   font-size: var(--text-sm); | ||||||
|  |   color: var(--color-text-muted); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .comment-content p:last-of-type { | ||||||
|  |   margin-bottom: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .comment-actions { | ||||||
|  |   display: flex; | ||||||
|  |   gap: var(--space-sm); | ||||||
|  |   flex-shrink: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .comments-list { | ||||||
|  |   margin-top: var(--space-lg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Code blocks */ | ||||||
|  | pre { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  |   border-radius: var(--radius-md); | ||||||
|  |   padding: var(--space-lg); | ||||||
|  |   overflow-x: auto; | ||||||
|  |   margin-bottom: var(--space-lg); | ||||||
|  |   white-space: pre-wrap; | ||||||
|  |   word-break: break-all; | ||||||
|  |   line-height: 1.4; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | code { | ||||||
|  |   font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; | ||||||
|  |   font-size: var(--text-sm); | ||||||
|  |   background-color: var(--color-border-light); | ||||||
|  |   padding: var(--space-xs) var(--space-sm); | ||||||
|  |   border-radius: var(--radius-sm); | ||||||
|  |   word-break: break-all; | ||||||
|  |   white-space: pre-wrap; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pre code { | ||||||
|  |   background: none; | ||||||
|  |   padding: 0; | ||||||
|  |   white-space: pre-wrap; | ||||||
|  |   word-break: break-all; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .code-example { | ||||||
|  |   margin: var(--space-lg) 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .code-example figcaption { | ||||||
|  |   font-size: var(--text-sm); | ||||||
|  |   color: var(--color-text-muted); | ||||||
|  |   margin-bottom: var(--space-sm); | ||||||
|  |   font-weight: 500; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Lists */ | ||||||
|  | ul, ol { | ||||||
|  |   padding-left: var(--space-xl); | ||||||
|  |   margin-bottom: var(--space-lg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | li { | ||||||
|  |   margin-bottom: var(--space-sm); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ul#websites { | ||||||
|  |   list-style: none; | ||||||
|  |   padding: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ul#websites li { | ||||||
|  |   margin-bottom: var(--space-md); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Website cards */ | ||||||
|  | .website-card { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  |   border-radius: var(--radius-lg); | ||||||
|  |   padding: var(--space-xl); | ||||||
|  |   transition: box-shadow 0.2s ease; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .website-card:hover { | ||||||
|  |   box-shadow: var(--shadow-md); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .website-header { | ||||||
|  |   margin-bottom: var(--space-lg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .website-name { | ||||||
|  |   margin: 0 0 var(--space-sm) 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .website-name a { | ||||||
|  |   color: var(--color-text); | ||||||
|  |   font-weight: 600; | ||||||
|  |   font-size: var(--text-xl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .website-url { | ||||||
|  |   font-size: var(--text-sm); | ||||||
|  |   color: var(--color-text-muted); | ||||||
|  |   font-family: monospace; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .website-actions { | ||||||
|  |   display: flex; | ||||||
|  |   gap: var(--space-sm); | ||||||
|  |   flex-wrap: wrap; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Screen reader only content */ | ||||||
|  | .sr-only { | ||||||
|  |   position: absolute; | ||||||
|  |   width: 1px; | ||||||
|  |   height: 1px; | ||||||
|  |   padding: 0; | ||||||
|  |   margin: -1px; | ||||||
|  |   overflow: hidden; | ||||||
|  |   clip: rect(0, 0, 0, 0); | ||||||
|  |   white-space: nowrap; | ||||||
|  |   border: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Notices and alerts */ | ||||||
|  | .notice, | ||||||
|  | .warning-notice { | ||||||
|  |   padding: var(--space-md); | ||||||
|  |   border-radius: var(--radius-md); | ||||||
|  |   margin-bottom: var(--space-lg); | ||||||
|  |   border-left: 4px solid var(--color-warning); | ||||||
|  |   background-color: rgba(217, 119, 6, 0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .warning-notice { | ||||||
|  |   border-left-color: var(--color-danger); | ||||||
|  |   background-color: rgba(220, 38, 38, 0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Section headers */ | ||||||
|  | .section-header { | ||||||
|  |   margin-bottom: var(--space-xl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .section-header:has(.btn) { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: space-between; | ||||||
|  |   align-items: flex-start; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   gap: var(--space-md); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .section-header h1 { | ||||||
|  |   margin-bottom: var(--space-sm); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .section-description { | ||||||
|  |   color: var(--color-text-muted); | ||||||
|  |   margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Hero section (index.html) */ | ||||||
|  | .hero { | ||||||
|  |   text-align: center; | ||||||
|  |   padding: var(--space-3xl) 0; | ||||||
|  |   background: linear-gradient(135deg, var(--color-surface) 0%, var(--color-background) 100%); | ||||||
|  |   border-radius: var(--radius-lg); | ||||||
|  |   margin-bottom: var(--space-2xl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .hero-header h1 { | ||||||
|  |   font-size: var(--text-4xl); | ||||||
|  |   margin-bottom: var(--space-md); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .hero-subtitle { | ||||||
|  |   font-size: var(--text-xl); | ||||||
|  |   color: var(--color-text-muted); | ||||||
|  |   margin: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Instruction sections (dashboard_main.html) */ | ||||||
|  | .instruction-overview { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  |   border-radius: var(--radius-md); | ||||||
|  |   padding: var(--space-lg); | ||||||
|  |   margin-bottom: var(--space-2xl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .instruction-method { | ||||||
|  |   margin-bottom: var(--space-2xl); | ||||||
|  |   padding-bottom: var(--space-2xl); | ||||||
|  |   border-bottom: 1px solid var(--color-border); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .instruction-method:last-child { | ||||||
|  |   border-bottom: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .instruction-step { | ||||||
|  |   margin-bottom: var(--space-xl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .instruction-step h3 { | ||||||
|  |   color: var(--color-primary); | ||||||
|  |   margin-bottom: var(--space-md); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .method-note { | ||||||
|  |   background-color: rgba(59, 130, 246, 0.1); | ||||||
|  |   border: 1px solid rgba(59, 130, 246, 0.2); | ||||||
|  |   border-radius: var(--radius-md); | ||||||
|  |   padding: var(--space-md); | ||||||
|  |   margin-top: var(--space-lg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .help-section { | ||||||
|  |   background-color: var(--color-surface); | ||||||
|  |   border: 1px solid var(--color-border); | ||||||
|  |   border-radius: var(--radius-lg); | ||||||
|  |   padding: var(--space-xl); | ||||||
|  |   margin-top: var(--space-2xl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Utilities */ | ||||||
|  | hr { | ||||||
|  |   border: none; | ||||||
|  |   height: 1px; | ||||||
|  |   background-color: var(--color-border); | ||||||
|  |   margin: var(--space-lg) 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .htmx-indicator { | ||||||
|  |   opacity: 0; | ||||||
|  |   transition: opacity 200ms ease-in; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .htmx-request .htmx-indicator, | ||||||
|  | .htmx-request.htmx-indicator { | ||||||
|  |   opacity: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Responsive Design */ | ||||||
|  | @media (max-width: 768px) { | ||||||
|  |   :root { | ||||||
|  |     --space-lg: 1rem; | ||||||
|  |     --space-xl: 1.5rem; | ||||||
|  |     --space-2xl: 2rem; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   body > header { | ||||||
|  |     padding: 0 var(--space-md); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   nav { | ||||||
|  |     padding: var(--space-md); | ||||||
|  |     flex-direction: column; | ||||||
|  |     align-items: stretch; | ||||||
|  |     gap: var(--space-sm); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .nav-welcome { | ||||||
|  |     text-align: center; | ||||||
|  |     padding-bottom: var(--space-sm); | ||||||
|  |     border-bottom: 1px solid var(--color-border); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .nav-links { | ||||||
|  |     justify-content: center; | ||||||
|  |     gap: var(--space-sm); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .nav-links a { | ||||||
|  |     padding: var(--space-sm); | ||||||
|  |     font-size: var(--text-sm); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   main { | ||||||
|  |     padding: var(--space-lg) var(--space-md); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #dashboard { | ||||||
|  |     grid-template-columns: 1fr; | ||||||
|  |     gap: var(--space-lg); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #dashboard nav > div { | ||||||
|  |     padding: var(--space-md); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   form { | ||||||
|  |     padding: var(--space-lg); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   h1 { font-size: var(--text-2xl); } | ||||||
|  |   h2 { font-size: var(--text-xl); } | ||||||
|  | 
 | ||||||
|  |   label:has(input[type="radio"]) { | ||||||
|  |     display: block; | ||||||
|  |     margin-bottom: var(--space-sm); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .hero { | ||||||
|  |     padding: var(--space-2xl) 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .hero-header h1 { | ||||||
|  |     font-size: var(--text-3xl); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .hero-subtitle { | ||||||
|  |     font-size: var(--text-lg); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .section-header:has(.btn) { | ||||||
|  |     flex-direction: column; | ||||||
|  |     align-items: stretch; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .section-header .btn { | ||||||
|  |     width: 100%; | ||||||
|  |     text-align: center; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .section-description { | ||||||
|  |     font-size: var(--text-sm); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .website-actions { | ||||||
|  |     flex-direction: column; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .website-actions .btn { | ||||||
|  |     width: 100%; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .comment-header { | ||||||
|  |     flex-direction: column; | ||||||
|  |     align-items: stretch; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .comment-actions { | ||||||
|  |     margin-top: var(--space-sm); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   pre { | ||||||
|  |     font-size: var(--text-xs); | ||||||
|  |     padding: var(--space-md); | ||||||
|  |     white-space: pre-wrap; | ||||||
|  |     word-break: break-all; | ||||||
|  |     overflow-x: hidden; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   code { | ||||||
|  |     font-size: var(--text-xs); | ||||||
|  |     word-break: break-all; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (max-width: 480px) { | ||||||
|  |   body > header h1 { | ||||||
|  |     font-size: var(--text-xl); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .nav-links { | ||||||
|  |     flex-direction: column; | ||||||
|  |     gap: var(--space-xs); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   .nav-links a { | ||||||
|  |     width: 100%; | ||||||
|  |     text-align: center; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   button, | ||||||
|  |   input[type="submit"] { | ||||||
|  |     width: 100%; | ||||||
|  |     margin-bottom: var(--space-sm); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Focus styles for better accessibility */ | ||||||
|  | button:focus-visible, | ||||||
|  | input:focus-visible, | ||||||
|  | textarea:focus-visible, | ||||||
|  | select:focus-visible { | ||||||
|  |   outline: 2px solid var(--color-primary); | ||||||
|  |   outline-offset: 2px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Print styles */ | ||||||
|  | @media print { | ||||||
|  |   body > header, | ||||||
|  |   nav, | ||||||
|  |   footer, | ||||||
|  |   button, | ||||||
|  |   input[type="submit"] { | ||||||
|  |     display: none; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   main { | ||||||
|  |     max-width: none; | ||||||
|  |     padding: 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #dashboard { | ||||||
|  |     grid-template-columns: 1fr; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -41,31 +41,33 @@ templ commonHeader() { | |||||||
| 
 | 
 | ||||||
| templ topNav(data CommonData) { | templ topNav(data CommonData) { | ||||||
| 	{{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }} | 	{{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }} | ||||||
| 	<nav> | 	<nav aria-label="Site navigation"> | ||||||
| 		<div> | 		<div class="nav-welcome"> | ||||||
|  | 			<span> | ||||||
| 				if data.IsAuthenticated { | 				if data.IsAuthenticated { | ||||||
| 					Welcome, { data.CurrentUser.Username } | 					Welcome, { data.CurrentUser.Username } | ||||||
| 				} | 				} | ||||||
|  | 			</span> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div> | 		<ul class="nav-links"> | ||||||
| 			if data.IsAuthenticated { | 			if data.IsAuthenticated { | ||||||
| 				<a href="/guestbooks">All Guestbooks</a> | | 				<li><a href="/guestbooks">All Guestbooks</a></li> | ||||||
| 				<a href="/websites">My Websites</a> |  | 				<li><a href="/websites">My Websites</a></li> | ||||||
| 				<a href="/users/settings">Settings</a> |  | 				<li><a href="/users/settings">Settings</a></li> | ||||||
| 				<a href="#" hx-post="/users/logout" hx-headers={ hxHeaders }>Logout</a> | 				<li><a href="#" hx-post="/users/logout" hx-headers={ hxHeaders }>Logout</a></li> | ||||||
| 			} else { | 			} else { | ||||||
| 				if data.LocalAuthEnabled { | 				if data.LocalAuthEnabled { | ||||||
| 					<a href="/users/register">Create an Account</a> |  | 					<li><a href="/users/register">Create an Account</a></li> |  | ||||||
| 				} | 				} | ||||||
| 				<a href="/users/login">Login</a> | 				<li><a href="/users/login">Login</a></li> | ||||||
| 			} | 			} | ||||||
| 		</div> | 		</ul> | ||||||
| 	</nav> | 	</nav> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| templ commonFooter() { | templ commonFooter() { | ||||||
| 	<footer> | 	<footer> | ||||||
| 		<p>A <a href="https://32bit.cafe">32bit.cafe</a> Project</p> | 		<p>A <a href="https://32bit.cafe" rel="noopener">32bit.cafe</a> Project</p> | ||||||
| 	</footer> | 	</footer> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -76,18 +78,16 @@ templ base(title string, data CommonData) { | |||||||
| 			<title>{ title } - webweav.ing</title> | 			<title>{ title } - webweav.ing</title> | ||||||
| 			<meta charset="UTF-8"/> | 			<meta charset="UTF-8"/> | ||||||
| 			<meta name="viewport" content="width=device-width, initial-scale=1"/> | 			<meta name="viewport" content="width=device-width, initial-scale=1"/> | ||||||
| 			<link href="/static/css/classless.min.css" rel="stylesheet"/> |  | ||||||
| 			<link href="/static/css/style.css" rel="stylesheet"/> | 			<link href="/static/css/style.css" rel="stylesheet"/> | ||||||
| 			<script src="/static/js/htmx.min.js"></script> | 			<script src="/static/js/htmx.min.js"></script> | ||||||
| 		</head> | 		</head> | ||||||
| 		<body> | 		<body> | ||||||
| 			@commonHeader() | 			@commonHeader() | ||||||
| 			@topNav(data) | 			@topNav(data) | ||||||
| 			<main> | 			<main role="main"> | ||||||
| 				if data.Flash != "" { | 				if data.Flash != "" { | ||||||
| 					<div class="flash">{ data.Flash }</div> | 					<div class="notice flash">{ data.Flash }</div> | ||||||
| 				} | 				} | ||||||
| 				<h1>{ title }</h1> |  | ||||||
| 				{ children... } | 				{ children... } | ||||||
| 			</main> | 			</main> | ||||||
| 			@commonFooter() | 			@commonFooter() | ||||||
|  | |||||||
| @ -92,7 +92,7 @@ func topNav(data CommonData) templ.Component { | |||||||
| 		} | 		} | ||||||
| 		ctx = templ.ClearChildren(ctx) | 		ctx = templ.ClearChildren(ctx) | ||||||
| 		hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) | 		hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav><div>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav aria-label=\"Site navigation\"><div class=\"nav-welcome\"><span>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| @ -104,48 +104,48 @@ func topNav(data CommonData) templ.Component { | |||||||
| 			var templ_7745c5c3_Var3 string | 			var templ_7745c5c3_Var3 string | ||||||
| 			templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username) | 			templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 47, Col: 40} | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 48, Col: 41} | ||||||
| 			} | 			} | ||||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div><div>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</span></div><ul class=\"nav-links\">") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		if data.IsAuthenticated { | 		if data.IsAuthenticated { | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<a href=\"/guestbooks\">All Guestbooks</a> | <a href=\"/websites\">My Websites</a> |  <a href=\"/users/settings\">Settings</a> |  <a href=\"#\" hx-post=\"/users/logout\" hx-headers=\"") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<li><a href=\"/guestbooks\">All Guestbooks</a></li><li><a href=\"/websites\">My Websites</a></li><li><a href=\"/users/settings\">Settings</a></li><li><a href=\"#\" hx-post=\"/users/logout\" hx-headers=\"") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 			var templ_7745c5c3_Var4 string | 			var templ_7745c5c3_Var4 string | ||||||
| 			templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders) | 			templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 55, Col: 62} | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 57, Col: 66} | ||||||
| 			} | 			} | ||||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">Logout</a>") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">Logout</a></li>") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			if data.LocalAuthEnabled { | 			if data.LocalAuthEnabled { | ||||||
| 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a href=\"/users/register\">Create an Account</a> | ") | 				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<li><a href=\"/users/register\">Create an Account</a></li>| ") | ||||||
| 				if templ_7745c5c3_Err != nil { | 				if templ_7745c5c3_Err != nil { | ||||||
| 					return templ_7745c5c3_Err | 					return templ_7745c5c3_Err | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " <a href=\"/users/login\">Login</a>") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " <li><a href=\"/users/login\">Login</a></li>") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></nav>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</ul></nav>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| @ -174,7 +174,7 @@ func commonFooter() templ.Component { | |||||||
| 			templ_7745c5c3_Var5 = templ.NopComponent | 			templ_7745c5c3_Var5 = templ.NopComponent | ||||||
| 		} | 		} | ||||||
| 		ctx = templ.ClearChildren(ctx) | 		ctx = templ.ClearChildren(ctx) | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<footer><p>A <a href=\"https://32bit.cafe\">32bit.cafe</a> Project</p></footer>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<footer><p>A <a href=\"https://32bit.cafe\" rel=\"noopener\">32bit.cafe</a> Project</p></footer>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| @ -210,13 +210,13 @@ func base(title string, data CommonData) templ.Component { | |||||||
| 		var templ_7745c5c3_Var7 string | 		var templ_7745c5c3_Var7 string | ||||||
| 		templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) | 		templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 76, Col: 17} | 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 78, Col: 17} | ||||||
| 		} | 		} | ||||||
| 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) | 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/classless.min.css\" rel=\"stylesheet\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| @ -228,19 +228,19 @@ func base(title string, data CommonData) templ.Component { | |||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<main>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<main role=\"main\">") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		if data.Flash != "" { | 		if data.Flash != "" { | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"flash\">") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"notice flash\">") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 			var templ_7745c5c3_Var8 string | 			var templ_7745c5c3_Var8 string | ||||||
| 			templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) | 			templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 88, Col: 36} | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 89, Col: 43} | ||||||
| 			} | 			} | ||||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| @ -251,28 +251,11 @@ func base(title string, data CommonData) templ.Component { | |||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h1>") |  | ||||||
| 		if templ_7745c5c3_Err != nil { |  | ||||||
| 			return templ_7745c5c3_Err |  | ||||||
| 		} |  | ||||||
| 		var templ_7745c5c3_Var9 string |  | ||||||
| 		templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(title) |  | ||||||
| 		if templ_7745c5c3_Err != nil { |  | ||||||
| 			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 90, Col: 15} |  | ||||||
| 		} |  | ||||||
| 		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) |  | ||||||
| 		if templ_7745c5c3_Err != nil { |  | ||||||
| 			return templ_7745c5c3_Err |  | ||||||
| 		} |  | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</h1>") |  | ||||||
| 		if templ_7745c5c3_Err != nil { |  | ||||||
| 			return templ_7745c5c3_Err |  | ||||||
| 		} |  | ||||||
| 		templ_7745c5c3_Err = templ_7745c5c3_Var6.Render(ctx, templ_7745c5c3_Buffer) | 		templ_7745c5c3_Err = templ_7745c5c3_Var6.Render(ctx, templ_7745c5c3_Buffer) | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</main>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</main>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| @ -280,7 +263,7 @@ func base(title string, data CommonData) templ.Component { | |||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
| 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</body></html>") | 		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</body></html>") | ||||||
| 		if templ_7745c5c3_Err != nil { | 		if templ_7745c5c3_Err != nil { | ||||||
| 			return templ_7745c5c3_Err | 			return templ_7745c5c3_Err | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -10,14 +10,19 @@ templ GuestbookDashboardCommentsView(title string, data CommonData, website mode | |||||||
| 		<div id="dashboard"> | 		<div id="dashboard"> | ||||||
| 			@wSidebar(website) | 			@wSidebar(website) | ||||||
| 			<div> | 			<div> | ||||||
| 				<h1>Comments on { website.Name }</h1> | 				<section aria-labelledby="comments-management-heading"> | ||||||
| 				<hr/> | 					<header class="section-header"> | ||||||
|  | 						<h1 id="comments-management-heading">Comments on yq</h1> | ||||||
|  | 						<p class="section-description">Manage, moderate, and organize comments on your guestbook</p> | ||||||
|  | 					</header> | ||||||
|  | 					<hr role="separator"/> | ||||||
| 					if len(comments) == 0 { | 					if len(comments) == 0 { | ||||||
| 						<p>No comments yet!</p> | 						<p>No comments yet!</p> | ||||||
| 					} | 					} | ||||||
| 					for  _, c := range comments { | 					for  _, c := range comments { | ||||||
| 						@GuestbookDashboardCommentView(data, website, c) | 						@GuestbookDashboardCommentView(data, website, c) | ||||||
| 					} | 					} | ||||||
|  | 				</section> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	} | 	} | ||||||
| @ -60,41 +65,48 @@ templ GuestbookDashboardCommentView(data CommonData, w models.Website, c models. | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| templ commentForm(form forms.CommentCreateForm) { | templ commentForm(form forms.CommentCreateForm) { | ||||||
| 	<div> | 	<fieldset> | ||||||
| 		<label for="authorname">Name</label> | 		<legend>Leave a comment</legend> | ||||||
|  | 		<div class="form-group"> | ||||||
|  | 			<label for="authorname">Name <span aria-label="required">*</span></label> | ||||||
| 			{{ error, exists := form.FieldErrors["authorName"] }} | 			{{ error, exists := form.FieldErrors["authorName"] }} | ||||||
| 			if exists { | 			if exists { | ||||||
| 				<label class="error">{ error }</label> | 				<label class="error">{ error }</label> | ||||||
| 			} | 			} | ||||||
| 		<input type="text" name="authorname" id="authorname"/> | 			<input type="text" name="authorname" id="authorname" required aria-describedby="authorname-help"/> | ||||||
|  | 			<small id="authorname-help">Your name or handle</small> | ||||||
| 		</div> | 		</div> | ||||||
| 	<div> | 		<div class="form-group"> | ||||||
| 		<label for="authoremail">Email (Optional) </label> | 			<label for="authoremail">Email (Optional)</label> | ||||||
| 			{{ error, exists = form.FieldErrors["authorEmail"] }} | 			{{ error, exists = form.FieldErrors["authorEmail"] }} | ||||||
| 			if exists { | 			if exists { | ||||||
| 				<label class="error">{ error }</label> | 				<label class="error">{ error }</label> | ||||||
| 			} | 			} | ||||||
| 		<input type="text" name="authoremail" id="authoremail"/> | 			<input type="email" name="authoremail" id="authoremail" aria-describedby="authoremail-help"/> | ||||||
|  | 			<small id="authoremail-help">We won't share your email address, except with the guestbook's owner</small> | ||||||
| 		</div> | 		</div> | ||||||
| 	<div> | 		<div class="form-group"> | ||||||
| 		<label for="authorsite">Site Url (Optional) </label> | 			<label for="authorsite">Website URL (Optional)</label> | ||||||
| 			{{ error, exists = form.FieldErrors["authorSite"] }} | 			{{ error, exists = form.FieldErrors["authorSite"] }} | ||||||
| 			if exists { | 			if exists { | ||||||
| 				<label class="error">{ error }</label> | 				<label class="error">{ error }</label> | ||||||
| 			} | 			} | ||||||
| 		<input type="text" name="authorsite" id="authorsite"/> | 			<input type="url" name="authorsite" id="authorsite" aria-describedby="authorsite-help"/> | ||||||
|  | 			<small id="authorsite-help">Link to your website or social profile</small> | ||||||
| 		</div> | 		</div> | ||||||
| 	<div> | 		<div class="form-group"> | ||||||
| 		<label for="content">Comment</label> | 			<label for="content">Comment <span aria-label="required">*</span></label> | ||||||
| 			{{ error, exists = form.FieldErrors["content"] }} | 			{{ error, exists = form.FieldErrors["content"] }} | ||||||
| 			if exists { | 			if exists { | ||||||
| 				<label class="error">{ error }</label> | 				<label class="error">{ error }</label> | ||||||
| 			} | 			} | ||||||
| 		<textarea name="content" id="content"></textarea> | 			<textarea name="content" id="content" required aria-describedby="content-help"></textarea> | ||||||
|  | 			<small id="content-help">Share your thoughts, feedback, or just say hello!</small> | ||||||
| 		</div> | 		</div> | ||||||
| 	<div> | 		<div class="form-group"> | ||||||
| 		<input type="submit" value="Submit"/> | 			<button type="submit">Submit Comment</button> | ||||||
| 		</div> | 		</div> | ||||||
|  | 	</fieldset> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| templ GuestbookView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment, form forms.CommentCreateForm) { | templ GuestbookView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment, form forms.CommentCreateForm) { | ||||||
| @ -105,26 +117,30 @@ templ GuestbookView(title string, data CommonData, website models.Website, guest | |||||||
| 		<html> | 		<html> | ||||||
| 			<head> | 			<head> | ||||||
| 				<title>{ title }</title> | 				<title>{ title }</title> | ||||||
| 				<link href="/static/css/classless.min.css" rel="stylesheet"/> | 				<meta name="viewport" content="width=device-width, initial-scale=1"/> | ||||||
|  | 				<link href="/static/css/style.css" rel="stylesheet"/> | ||||||
| 				<script src="/static/js/main.js" defer></script> | 				<script src="/static/js/main.js" defer></script> | ||||||
| 			</head> | 			</head> | ||||||
| 			<body> | 			<body> | ||||||
| 				<main> | 				<main role="main"> | ||||||
| 					<div> | 					<section aria-labelledby="guestbook-heading"> | ||||||
| 						<h1>{ website.Name } Guestbook</h1> | 						<h1>{ website.Name } Guestbook</h1> | ||||||
| 						{ data.Flash } | 						{ data.Flash } | ||||||
| 						<form action={ templ.URL(postUrl) } method="post"> | 						<form action={ templ.URL(postUrl) } method="post"> | ||||||
| 							<input type="hidden" name="csrf_token" value={ data.CSRFToken }/> | 							<input type="hidden" name="csrf_token" value={ data.CSRFToken }/> | ||||||
| 							@commentForm(form) | 							@commentForm(form) | ||||||
| 						</form> | 						</form> | ||||||
| 					</div> | 					</section> | ||||||
| 					<div id="comments"> | 					<section id="comments" aria-labelledby="comments-heading"> | ||||||
|  | 						<h2 id="comments-heading">Comments</h2> | ||||||
| 						if len(comments) == 0 { | 						if len(comments) == 0 { | ||||||
| 							<p>No comments yet!</p> | 							<p>No comments yet!</p> | ||||||
| 						} | 						} | ||||||
| 						for _, c := range comments { | 						for i, c := range comments { | ||||||
| 							<div> | 							{{ commentAuthorRole := fmt.Sprintf("comment-author-%d", i+1) }} | ||||||
| 								<h3> | 							<article class="comment" role="article" aria-labelledby={ commentAuthorRole }> | ||||||
|  | 								<header class="comment-header"> | ||||||
|  | 									<h3 id={ commentAuthorRole } class="comment-author"> | ||||||
| 										if c.AuthorSite != "" { | 										if c.AuthorSite != "" { | ||||||
| 											<a href={ templ.URL(externalUrl(c.AuthorSite)) } target="_blank">{ c.AuthorName }</a> | 											<a href={ templ.URL(externalUrl(c.AuthorSite)) } target="_blank">{ c.AuthorName }</a> | ||||||
| 										} else { | 										} else { | ||||||
| @ -132,12 +148,15 @@ templ GuestbookView(title string, data CommonData, website models.Website, guest | |||||||
| 										} | 										} | ||||||
| 									</h3> | 									</h3> | ||||||
| 									<time datetime={ c.Created.Format(time.RFC3339) }>{ c.Created.Format("01-02-2006 03:04PM") }</time> | 									<time datetime={ c.Created.Format(time.RFC3339) }>{ c.Created.Format("01-02-2006 03:04PM") }</time> | ||||||
|  | 								</header> | ||||||
|  | 								<div class="comment-content"> | ||||||
| 									<p> | 									<p> | ||||||
| 										{ c.CommentText } | 										{ c.CommentText } | ||||||
| 									</p> | 									</p> | ||||||
| 								</div> | 								</div> | ||||||
|  | 							</article> | ||||||
| 						} | 						} | ||||||
| 					</div> | 					</section> | ||||||
| 				</main> | 				</main> | ||||||
| 			</body> | 			</body> | ||||||
| 		</html> | 		</html> | ||||||
|  | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -2,13 +2,14 @@ package views | |||||||
| 
 | 
 | ||||||
| templ Home(title string, data CommonData) { | templ Home(title string, data CommonData) { | ||||||
| 	@base(title, data) { | 	@base(title, data) { | ||||||
| 		<h2>Welcome</h2> | 		<section aria-labelledby="welcome-heading"> | ||||||
| 		<p> | 			<h2 id="welcome-heading">Welcome</h2> | ||||||
| 			Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe">32-Bit Cafe</a>. | 			<p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe" rel="noopener">32-Bit Cafe</a>.</p> | ||||||
| 		</p> | 			<aside class="notice" role="alert" aria-labelledby="service-status"> | ||||||
| 		<p> | 				<h3 id="service-status" class="sr-only">Service Status Notice</h3> | ||||||
| 			Note this service is in a pre-alpha state. Your account and data can disappear at any time.  | 				<p><strong>Important:</strong> This service is in a pre-alpha state. Your account and data can disappear at any time.</p> | ||||||
| 		</p> | 			</aside> | ||||||
|  | 		</section> | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ func Home(title string, data CommonData) templ.Component { | |||||||
| 				}() | 				}() | ||||||
| 			} | 			} | ||||||
| 			ctx = templ.InitializeContext(ctx) | 			ctx = templ.InitializeContext(ctx) | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h2>Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\">32-Bit Cafe</a>.</p><p>Note this service is in a pre-alpha state. Your account and data can disappear at any time. </p>") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section aria-labelledby=\"welcome-heading\"><h2 id=\"welcome-heading\">Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\" rel=\"noopener\">32-Bit Cafe</a>.</p><aside class=\"notice\" role=\"alert\" aria-labelledby=\"service-status\"><h3 id=\"service-status\" class=\"sr-only\">Service Status Notice</h3><p><strong>Important:</strong> This service is in a pre-alpha state. Your account and data can disappear at any time.</p></aside></section>") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
|  | |||||||
| @ -86,8 +86,12 @@ templ UserSettingsView(data CommonData, timezones []string) { | |||||||
| 	{{ user := data.CurrentUser }} | 	{{ user := data.CurrentUser }} | ||||||
| 	@base("User Settings", data) { | 	@base("User Settings", data) { | ||||||
| 		<div> | 		<div> | ||||||
|  | 			<section aria-labelledby="user-settings-heading"> | ||||||
| 				<form hx-put="/users/settings"> | 				<form hx-put="/users/settings"> | ||||||
| 					<input type="hidden" name="csrf_token" value={ data.CSRFToken }/> | 					<input type="hidden" name="csrf_token" value={ data.CSRFToken }/> | ||||||
|  | 					<fieldset> | ||||||
|  | 						<legend id="user-settings-heading">User Settings</legend> | ||||||
|  | 						<div class="form-group"> | ||||||
| 							<label>Local Timezone</label> | 							<label>Local Timezone</label> | ||||||
| 							<select name="timezones" id="timezone-select"> | 							<select name="timezones" id="timezone-select"> | ||||||
| 								for _, tz := range timezones { | 								for _, tz := range timezones { | ||||||
| @ -98,8 +102,11 @@ templ UserSettingsView(data CommonData, timezones []string) { | |||||||
| 									} | 									} | ||||||
| 								} | 								} | ||||||
| 							</select> | 							</select> | ||||||
| 				<input type="submit" value="Submit"/> | 						</div> | ||||||
|  | 					</fieldset> | ||||||
|  | 					<button type="submit">Save Settings</button> | ||||||
| 				</form> | 				</form> | ||||||
|  | 			</section> | ||||||
| 		</div> | 		</div> | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -444,20 +444,20 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { | |||||||
| 				}() | 				}() | ||||||
| 			} | 			} | ||||||
| 			ctx = templ.InitializeContext(ctx) | 			ctx = templ.InitializeContext(ctx) | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div><form hx-put=\"/users/settings\"><input type=\"hidden\" name=\"csrf_token\" value=\"") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div><section aria-labelledby=\"user-settings-heading\"><form hx-put=\"/users/settings\"><input type=\"hidden\" name=\"csrf_token\" value=\"") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 			var templ_7745c5c3_Var22 string | 			var templ_7745c5c3_Var22 string | ||||||
| 			templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken) | 			templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 90, Col: 65} | 				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 91, Col: 66} | ||||||
| 			} | 			} | ||||||
| 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) | 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"> <label>Local Timezone</label> <select name=\"timezones\" id=\"timezone-select\">") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"><fieldset><legend id=\"user-settings-heading\">User Settings</legend><div class=\"form-group\"><label>Local Timezone</label> <select name=\"timezones\" id=\"timezone-select\">") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
| @ -470,7 +470,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { | |||||||
| 					var templ_7745c5c3_Var23 string | 					var templ_7745c5c3_Var23 string | ||||||
| 					templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | 					templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | ||||||
| 					if templ_7745c5c3_Err != nil { | 					if templ_7745c5c3_Err != nil { | ||||||
| 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 95, Col: 25} | 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 99, Col: 28} | ||||||
| 					} | 					} | ||||||
| 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) | 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) | ||||||
| 					if templ_7745c5c3_Err != nil { | 					if templ_7745c5c3_Err != nil { | ||||||
| @ -483,7 +483,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { | |||||||
| 					var templ_7745c5c3_Var24 string | 					var templ_7745c5c3_Var24 string | ||||||
| 					templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | 					templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | ||||||
| 					if templ_7745c5c3_Err != nil { | 					if templ_7745c5c3_Err != nil { | ||||||
| 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 95, Col: 48} | 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 99, Col: 51} | ||||||
| 					} | 					} | ||||||
| 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) | ||||||
| 					if templ_7745c5c3_Err != nil { | 					if templ_7745c5c3_Err != nil { | ||||||
| @ -501,7 +501,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { | |||||||
| 					var templ_7745c5c3_Var25 string | 					var templ_7745c5c3_Var25 string | ||||||
| 					templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | 					templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | ||||||
| 					if templ_7745c5c3_Err != nil { | 					if templ_7745c5c3_Err != nil { | ||||||
| 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 97, Col: 25} | 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 101, Col: 28} | ||||||
| 					} | 					} | ||||||
| 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) | ||||||
| 					if templ_7745c5c3_Err != nil { | 					if templ_7745c5c3_Err != nil { | ||||||
| @ -514,7 +514,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { | |||||||
| 					var templ_7745c5c3_Var26 string | 					var templ_7745c5c3_Var26 string | ||||||
| 					templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | 					templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(tz) | ||||||
| 					if templ_7745c5c3_Err != nil { | 					if templ_7745c5c3_Err != nil { | ||||||
| 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 97, Col: 32} | 						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 101, Col: 35} | ||||||
| 					} | 					} | ||||||
| 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | 					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) | ||||||
| 					if templ_7745c5c3_Err != nil { | 					if templ_7745c5c3_Err != nil { | ||||||
| @ -526,7 +526,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component { | |||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</select> <input type=\"submit\" value=\"Submit\"></form></div>") | 			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</select></div></fieldset><button type=\"submit\">Save Settings</button></form></section></div>") | ||||||
| 			if templ_7745c5c3_Err != nil { | 			if templ_7745c5c3_Err != nil { | ||||||
| 				return templ_7745c5c3_Err | 				return templ_7745c5c3_Err | ||||||
| 			} | 			} | ||||||
|  | |||||||
| @ -14,38 +14,47 @@ func wUrl(w models.Website) string { | |||||||
| templ wSidebar(website models.Website) { | templ wSidebar(website models.Website) { | ||||||
| 	{{ dashUrl := wUrl(website) + "/dashboard" }} | 	{{ dashUrl := wUrl(website) + "/dashboard" }} | ||||||
| 	{{ gbUrl := wUrl(website) + "/guestbook" }} | 	{{ gbUrl := wUrl(website) + "/guestbook" }} | ||||||
| 	<nav> | 	<nav aria-label="Dashboard navigation"> | ||||||
| 		<div> | 		<div> | ||||||
| 			<ul> | 			<section aria-labelledby="main-nav-heading"> | ||||||
| 				<li><a href={ templ.URL(dashUrl) }>Dashboard</a></li> | 				<h3 id="main-nav-heading">Website</h3> | ||||||
|  | 				<ul role="list"> | ||||||
|  | 					<li><a href={ templ.URL(dashUrl) } aria-current="page">Dashboard</a></li> | ||||||
| 					<li><a href={ templ.URL(dashUrl + "/settings") }>Settings</a></li> | 					<li><a href={ templ.URL(dashUrl + "/settings") }>Settings</a></li> | ||||||
| 					<li><a href={ templ.URL(externalUrl(website.Url.String())) } target="_blank">View Website</a></li> | 					<li><a href={ templ.URL(externalUrl(website.Url.String())) } target="_blank">View Website</a></li> | ||||||
| 				</ul> | 				</ul> | ||||||
| 			<h3>Guestbook</h3> | 			</section> | ||||||
| 			<ul> | 		</div> | ||||||
|  | 		<div> | ||||||
|  | 			<section aria-labelledby="guestbook-nav-heading"> | ||||||
|  | 				<h3 id="guestbook-nav-heading">Guestbook</h3> | ||||||
|  | 				<ul role="list"> | ||||||
| 					<li><a href={ templ.URL(gbUrl) } target="_blank">View Guestbook</a></li> | 					<li><a href={ templ.URL(gbUrl) } target="_blank">View Guestbook</a></li> | ||||||
| 			</ul> |  | ||||||
| 			<ul> |  | ||||||
| 					<li><a href={ templ.URL(dashUrl + "/guestbook/comments") }>Manage messages</a></li> | 					<li><a href={ templ.URL(dashUrl + "/guestbook/comments") }>Manage messages</a></li> | ||||||
| 					<li><a href={ templ.URL(dashUrl + "/guestbook/comments/queue") }>Review message queue</a></li> | 					<li><a href={ templ.URL(dashUrl + "/guestbook/comments/queue") }>Review message queue</a></li> | ||||||
| 					<li><a href={ templ.URL(dashUrl + "/guestbook/comments/trash") }>Trash</a></li> | 					<li><a href={ templ.URL(dashUrl + "/guestbook/comments/trash") }>Trash</a></li> | ||||||
| 				</ul> | 				</ul> | ||||||
| 			<ul> | 			</section> | ||||||
|  | 			//<ul> | ||||||
| 			//<li><a href={ templ.URL(dashUrl + "/guestbook/themes") }>Themes</a></li> | 			//<li><a href={ templ.URL(dashUrl + "/guestbook/themes") }>Themes</a></li> | ||||||
| 			//<li><a href={ templ.URL(dashUrl + "/guestbook/customize") }>Custom CSS</a></li> | 			//<li><a href={ templ.URL(dashUrl + "/guestbook/customize") }>Custom CSS</a></li> | ||||||
| 			</ul> | 			//</ul> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div> | 		<div> | ||||||
| 			<h3>Feeds</h3> | 			<section aria-labelledby="feeds-nav-heading"> | ||||||
|  | 				<h3 id="feeds-nav-heading">Feeds</h3> | ||||||
| 				<p>Coming Soon</p> | 				<p>Coming Soon</p> | ||||||
|  | 			</section> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div> | 		<div> | ||||||
| 			<h3>Account</h3> | 			<section aria-labelledby="account-nav-heading"> | ||||||
| 			<ul> | 				<h3 id="account-nav-heading">Account</h3> | ||||||
|  | 				<ul role="list"> | ||||||
| 					<li><a href="/users/settings">Settings</a></li> | 					<li><a href="/users/settings">Settings</a></li> | ||||||
| 					<li><a href="/users/privacy">Privacy</a></li> | 					<li><a href="/users/privacy">Privacy</a></li> | ||||||
| 					<li><a href="/help">Help</a></li> | 					<li><a href="/help">Help</a></li> | ||||||
| 				</ul> | 				</ul> | ||||||
|  | 			</section> | ||||||
| 		</div> | 		</div> | ||||||
| 	</nav> | 	</nav> | ||||||
| } | } | ||||||
| @ -83,22 +92,48 @@ templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) { | |||||||
| 
 | 
 | ||||||
| templ WebsiteList(title string, data CommonData, websites []models.Website) { | templ WebsiteList(title string, data CommonData, websites []models.Website) { | ||||||
| 	@base(title, data) { | 	@base(title, data) { | ||||||
| 		<div> | 		<section aria-labelledby="websites-heading"> | ||||||
| 			<a href="/websites/create">Add Website</a> | 			<header class="section-header"> | ||||||
| 		</div> | 				<h1 id="websites-heading">My Websites</h1> | ||||||
| 		<div> | 				<a href="/websites/create" class="btn btn-primary" role="button" aria-label="Create a new website">Add Website</a> | ||||||
|  | 			</header> | ||||||
|  | 			<div class="websites-container"> | ||||||
| 				if len(websites) == 0 { | 				if len(websites) == 0 { | ||||||
| 				<p>No Websites yet. <a href="">Register a website.</a></p> | 					<h2>No Websites yet.</h2> | ||||||
|  | 					<p>Create your first website to get started with webweav.ing tools.</p> | ||||||
|  | 					<a href="/websites/create" class="btn btn-primary">Create Your First Website</a> | ||||||
| 				} else { | 				} else { | ||||||
| 				<ul id="websites" hx-get="/websites" hx-trigger="newWebsite from:body" hx-swap="outerHTML"> | 					<ul | ||||||
|  | 						id="websites" | ||||||
|  | 						role="list" | ||||||
|  | 						aria-label="Your websites" | ||||||
|  | 						hx-get="/websites" | ||||||
|  | 						hx-trigger="newWebsite from:body" | ||||||
|  | 						hx-swap="outerHTML" | ||||||
|  | 					> | ||||||
| 						for _, w := range websites { | 						for _, w := range websites { | ||||||
| 						<li> | 							<li role="listitem"> | ||||||
| 							<a href={ templ.URL(wUrl(w) + "/dashboard") }>{ w.Name }</a> | 								<article class="website-card"> | ||||||
|  | 									<header class="website-header"> | ||||||
|  | 										<h2 class="website-name"> | ||||||
|  | 											<a href={ templ.URL(wUrl(w) + "/dashboard") } aria-label={ fmt.Sprintf("Manage %s website dashboard", w.Name) }> | ||||||
|  | 												{ w.Name } | ||||||
|  | 											</a> | ||||||
|  | 										</h2> | ||||||
|  | 										<span class="website-url" aria-label="Website URL">{ w.Url.String() }</span> | ||||||
|  | 									</header> | ||||||
|  | 									<div class="website-actions"> | ||||||
|  | 										<a href={ templ.URL(wUrl(w) + "/dashboard") } class="btn btn-outline" aria-label={ fmt.Sprintf("Open %s dashboard", w.Name) }>Dashboard</a> | ||||||
|  | 										<a href={ templ.URL(wUrl(w) + "/guestbook") } target="_blank" rel="noopener" class="btn btn-outline" aria-label={ fmt.Sprintf("View %s website guestbook", w.Name) }>View Guestbook</a> | ||||||
|  | 										<a href={ templ.URL(externalUrl(w.Url.String())) } target="_blank" rel="noopener" class="btn btn-outline" aria-label={ fmt.Sprintf("View %s website", w.Name) }>Visit Site</a> | ||||||
|  | 									</div> | ||||||
|  | 								</article> | ||||||
| 							</li> | 							</li> | ||||||
| 						} | 						} | ||||||
| 					</ul> | 					</ul> | ||||||
| 				} | 				} | ||||||
| 			</div> | 			</div> | ||||||
|  | 		</section> | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -107,103 +142,142 @@ templ WebsiteDashboard(title string, data CommonData, website models.Website) { | |||||||
| 		<div id="dashboard"> | 		<div id="dashboard"> | ||||||
| 			@wSidebar(website) | 			@wSidebar(website) | ||||||
| 			<div> | 			<div> | ||||||
| 				<h1>Embed your Guestbook</h1> | 				{{ gbUrl := fmt.Sprintf("https://%s/websites/%s/guestbook", data.RootUrl, shortIdToSlug(website.ShortId)) }} | ||||||
| 				<p> | 				<section aria-labelledby="embed-instructions"> | ||||||
| 					Upload <a href="/static/js/guestbook.js" download>this JavaScript WebComponent</a> to your site and include it in your <code>{ `<head>` }</code> tag. | 					<h1 id="embed-instructions">Embed your Guestbook</h1> | ||||||
| 				</p> | 					<div class="instruction-overview"> | ||||||
| 				<div> | 						<p>There are two ways to add your guestbook to your website: using our JavaScript component (recommended) or with an iframe.</p> | ||||||
| 					//<button>Copy to Clipboard</button> | 					</div> | ||||||
|  | 					<article class="instruction-method"> | ||||||
|  | 						<h2>Method 1: JavaScript Component (Recommended)</h2> | ||||||
|  | 						<div class="instruction-step"> | ||||||
|  | 							<h3>Step 1: Download and upload the component</h3> | ||||||
|  | 							<p>Download <a href="/static/js/guestbook.js" download>this JavaScript WebComponent</a> and upload it to your website's JavaScript folder.</p> | ||||||
|  | 						</div> | ||||||
|  | 						<div class="instruction-step"> | ||||||
|  | 							<h3>Step 2: Include in your HTML head</h3> | ||||||
|  | 							<figure class="code-example"> | ||||||
|  | 								<figcaption>Add this script tag to your <head> section:</figcaption> | ||||||
| 								<pre> | 								<pre> | ||||||
| 						<code id="guestbookSnippet"> | 									<code id="guestbookSnippet" aria-label="HTML head script tag"> | ||||||
| 							{  | 										<head> | ||||||
| `<head> | 										<script type="module" src="js/guestbook.js"></script> | ||||||
|     <script type="module" src="js/guestbook.js"></script> | 										</head> | ||||||
| </head>` } |  | ||||||
| 									</code> | 									</code> | ||||||
| 								</pre> | 								</pre> | ||||||
| 					<p> | 							</figure> | ||||||
| 						Then add the custom elements where you want your form and comments to show up | 						</div> | ||||||
| 					</p> | 						<div class="instruction-step"> | ||||||
| 					{{ gbUrl := fmt.Sprintf("https://%s/websites/%s/guestbook", data.RootUrl, shortIdToSlug(website.ShortId)) }} | 							<h3>Step 3: Add the custom elements</h3> | ||||||
| 					//<button>Copy to Clipboard</button> | 							<figure class="code-example"> | ||||||
|  | 								<figcaption>Place these elements where you want your guestbook to appear:</figcaption> | ||||||
| 								<pre> | 								<pre> | ||||||
| 						<code> | 									<code aria-label="Custom guestbook elements"> | ||||||
| 										{ fmt.Sprintf(`<guestbook-form guestbook="%s"></guestbook-form> | 										{ fmt.Sprintf(`<guestbook-form guestbook="%s"></guestbook-form> | ||||||
| <guestbook-comments guestbook="%s"></guestbook-comments>`, gbUrl, gbUrl) } | <guestbook-comments guestbook="%s"></guestbook-comments>`, gbUrl, gbUrl) } | ||||||
| 									</code> | 									</code> | ||||||
| 								</pre> | 								</pre> | ||||||
|  | 							</figure> | ||||||
| 						</div> | 						</div> | ||||||
| 				<p> | 					</article> | ||||||
| 					If your web host does not allow CORS requests, use an iframe instead | 					<article class="instruction-method"> | ||||||
| 				</p> | 						<h2>Method 2: iframe (Alternative)</h2> | ||||||
| 				<div> | 						<details> | ||||||
|  | 							<summary>Use this method if your web host doesn't allow CORS requests</summary> | ||||||
|  | 							<div class="instruction-step"> | ||||||
|  | 								<p>If your hosting provider blocks cross-origin requests, you can embed the guestbook using an iframe instead:</p> | ||||||
|  | 								<figure class="code-example"> | ||||||
|  | 									<figcaption>iframe embedding code:</figcaption> | ||||||
| 									<pre> | 									<pre> | ||||||
| 						<code> | 										<code aria-label="iframe embedding code"> | ||||||
| 							{ fmt.Sprintf(`<iframe src="%s" title="Guestbook"></iframe>`, gbUrl) } | 											{ fmt.Sprintf(`<iframe src="%s"  | ||||||
|  |     title="Guestbook" | ||||||
|  |     width="100%%" | ||||||
|  |     height="600" | ||||||
|  |     style="border: 1px solid #ccc; border-radius: 8px;" | ||||||
|  |     > | ||||||
|  |         <p>Your browser does not support iframes. <a href="%s">View the guestbook directly.</a></p> | ||||||
|  | </iframe>`, gbUrl, gbUrl) } | ||||||
| 										</code> | 										</code> | ||||||
| 									</pre> | 									</pre> | ||||||
|  | 								</figure> | ||||||
|  | 								<aside role="note" class="method-note"> | ||||||
|  | 									<p><strong>Note:</strong> The iframe method may have styling limitations and won't integrate as seamlessly with your site's design.</p> | ||||||
|  | 								</aside> | ||||||
| 							</div> | 							</div> | ||||||
|  | 						</details> | ||||||
|  | 					</article> | ||||||
|  | 					<aside role="complementary" class="help-section"> | ||||||
|  | 						<h2>Need Help?</h2> | ||||||
|  | 						<p>If you're having trouble embedding your guestbook, check out our <a href="/help">help documentation</a> or contact support.</p> | ||||||
|  | 					</aside> | ||||||
|  | 				</section> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| templ websiteSettingsForm(data CommonData, website models.Website, form forms.WebsiteSettingsForm) { | templ websiteSettingsForm(data CommonData, website models.Website, form forms.WebsiteSettingsForm) { | ||||||
| 	<h3>Website Settings</h3> | 	<legend id="website-settings-heading">Website Settings</legend> | ||||||
| 	<div> | 	<div class="form-group"> | ||||||
| 		{{ err, exists := form.FieldErrors["ws_name"] }} | 		{{ err, exists := form.FieldErrors["ws_name"] }} | ||||||
| 		<label for="ws_name">Site Name: </label> | 		<label for="ws_name">Site Name <span aria-label="required">*</span></label> | ||||||
| 		if exists { | 		if exists { | ||||||
| 			<label class="error">{ err }</label> | 			<label class="error">{ err }</label> | ||||||
| 		} | 		} | ||||||
| 		if form.SiteName != "" { | 		if form.SiteName != "" { | ||||||
| 			<input type="text" name="ws_name" id="sitename" value={ form.SiteName } required/> | 			<input type="text" name="ws_name" id="sitename" value={ form.SiteName } required aria-describedby="sitename-help"/> | ||||||
| 		} else { | 		} else { | ||||||
| 			<input type="text" name="ws_name" id="sitename" value={ website.Name } required/> | 			<input type="text" name="ws_name" id="sitename" value={ website.Name } required aria-describedby="sitename-help"/> | ||||||
| 		} | 		} | ||||||
|  | 		<small id="sitename-help">The display name for your website</small> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div> | 	<div class="form-group"> | ||||||
| 		{{ err, exists = form.FieldErrors["ws_url"] }} | 		{{ err, exists = form.FieldErrors["ws_url"] }} | ||||||
| 		<label for="ws_url">Site URL: </label> | 		<label for="ws_url">Site URL <span aria-label="required">*</span></label> | ||||||
| 		if exists { | 		if exists { | ||||||
| 			<label class="error">{ err }</label> | 			<label class="error">{ err }</label> | ||||||
| 		} | 		} | ||||||
| 		if form.SiteUrl != "" { | 		if form.SiteUrl != "" { | ||||||
| 			<input type="text" name="ws_url" id="ws_url" value={ form.SiteUrl } required/> | 			<input type="url" name="ws_url" id="ws_url" value={ form.SiteUrl } required aria-describedby="siteurl-help"/> | ||||||
| 		} else { | 		} else { | ||||||
| 			<input type="text" name="ws_url" id="ws_url" value={ website.Url.String() } required/> | 			<input type="url" name="ws_url" id="ws_url" value={ website.Url.String() } required aria-describedby="siteurl-help"/> | ||||||
| 		} | 		} | ||||||
|  | 		<small id="siteurl-help">The full URL where your website can be accessed</small> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div> | 	<div> | ||||||
| 		{{ err, exists = form.FieldErrors["ws_author"] }} | 		{{ err, exists = form.FieldErrors["ws_author"] }} | ||||||
| 		<label for="ws_author">Site Author: </label> | 		<label for="ws_author">Site Author <span aria-label="required">*</span></label> | ||||||
| 		if exists { | 		if exists { | ||||||
| 			<label class="error">{ err }</label> | 			<label class="error">{ err }</label> | ||||||
| 		} | 		} | ||||||
| 		if form.AuthorName != "" { | 		if form.AuthorName != "" { | ||||||
| 			<input type="text" name="ws_author" id="authorname" value={ form.AuthorName } required/> | 			<input type="text" name="ws_author" id="authorname" value={ form.AuthorName } required aria-describedby="authorname-help"/> | ||||||
| 		} else { | 		} else { | ||||||
| 			<input type="text" name="ws_author" id="authorname" value={ website.AuthorName } required/> | 			<input type="text" name="ws_author" id="authorname" value={ website.AuthorName } required aria-describedby="authorname-help"/> | ||||||
| 		} | 		} | ||||||
|  | 		<small id="authorname-help">Your name or the website owner's name</small> | ||||||
| 	</div> | 	</div> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| templ guestbookSettingsForm(data CommonData, website models.Website, gb models.Guestbook, form forms.WebsiteSettingsForm) { | templ guestbookSettingsForm(data CommonData, website models.Website, gb models.Guestbook, form forms.WebsiteSettingsForm) { | ||||||
| 	<h3>Guestbook Settings</h3> | 	<legend>Guestbook Settings</legend> | ||||||
| 	<div> | 	<div class="form-group"> | ||||||
| 		<label>Guestbook Visibility</label> | 		<fieldset class="radio-group"> | ||||||
| 		<label for="gb_visible_true"> | 			<legend>Guestbook Visibility</legend> | ||||||
|  | 			<label> | ||||||
| 				<input type="radio" name="gb_visible" id="gb_visible_true" value="true" checked?={ gb.Settings.IsVisible }/> | 				<input type="radio" name="gb_visible" id="gb_visible_true" value="true" checked?={ gb.Settings.IsVisible }/> | ||||||
| 				Public | 				Public | ||||||
| 			</label> | 			</label> | ||||||
| 		<label for="gb_visible_false"> | 			<label> | ||||||
| 				<input type="radio" name="gb_visible" id="gb_visible_false" value="false" checked?={ !gb.Settings.IsVisible }/> | 				<input type="radio" name="gb_visible" id="gb_visible_false" value="false" checked?={ !gb.Settings.IsVisible }/> | ||||||
| 				Private | 				Private | ||||||
| 			</label> | 			</label> | ||||||
|  | 		</fieldset> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div> | 	<div class="form-group"> | ||||||
| 		<label>Guestbook Commenting</label> | 		<label for="gb-commenting">Guestbook Commenting</label> | ||||||
| 		<select name="gb_commenting" id="gb-commenting"> | 		<select name="gb_commenting" id="gb-commenting" aria-describedby="commenting-help"> | ||||||
| 			<option value="true" selected?={ gb.Settings.IsCommentingEnabled }>Enabled</option> | 			<option value="true" selected?={ gb.Settings.IsCommentingEnabled }>Enabled</option> | ||||||
| 			<option value="1h">Disabled for 1 Hour</option> | 			<option value="1h">Disabled for 1 Hour</option> | ||||||
| 			<option value="4h">Disabled for 4 Hours</option> | 			<option value="4h">Disabled for 4 Hours</option> | ||||||
| @ -217,9 +291,11 @@ templ guestbookSettingsForm(data CommonData, website models.Website, gb models.G | |||||||
| 			{{ localtime := gb.Settings.ReenableCommenting.In(data.CurrentUser.Settings.LocalTimezone) }} | 			{{ localtime := gb.Settings.ReenableCommenting.In(data.CurrentUser.Settings.LocalTimezone) }} | ||||||
| 			<label>Commenting re-enabled on <time value={ localtime.Format(time.RFC3339) }>{ localtime.Format("2 January 2006") } at { localtime.Format("3:04PM MST") }</time></label> | 			<label>Commenting re-enabled on <time value={ localtime.Format(time.RFC3339) }>{ localtime.Format("2 January 2006") } at { localtime.Format("3:04PM MST") }</time></label> | ||||||
| 		} | 		} | ||||||
|  | 		<small id="commenting-help">Control when users can post new comments</small> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div> | 	<div class="form-group"> | ||||||
| 		<label>Enable Widgets</label> | 		<fieldset class="radio-group"> | ||||||
|  | 			<legend>Enable Widgets</legend> | ||||||
| 			<label for="gb_remote_true"> | 			<label for="gb_remote_true"> | ||||||
| 				<input type="radio" name="gb_remote" id="gb_remote_true" value="true" checked?={ gb.Settings.AllowRemoteHostAccess }/> | 				<input type="radio" name="gb_remote" id="gb_remote_true" value="true" checked?={ gb.Settings.AllowRemoteHostAccess }/> | ||||||
| 				Yes | 				Yes | ||||||
| @ -228,6 +304,8 @@ templ guestbookSettingsForm(data CommonData, website models.Website, gb models.G | |||||||
| 				<input type="radio" name="gb_remote" id="gb_remote_false" value="false" checked?={ !gb.Settings.AllowRemoteHostAccess }/> | 				<input type="radio" name="gb_remote" id="gb_remote_false" value="false" checked?={ !gb.Settings.AllowRemoteHostAccess }/> | ||||||
| 				No | 				No | ||||||
| 			</label> | 			</label> | ||||||
|  | 		</fieldset> | ||||||
|  | 		<small>Allow embedding guestbook on external websites</small> | ||||||
| 	</div> | 	</div> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -239,27 +317,36 @@ templ SettingsForm(data CommonData, website models.Website, form forms.WebsiteSe | |||||||
| 			{ msg } | 			{ msg } | ||||||
| 		</p> | 		</p> | ||||||
| 		<input type="hidden" name="csrf_token" value={ data.CSRFToken }/> | 		<input type="hidden" name="csrf_token" value={ data.CSRFToken }/> | ||||||
| 		<div id="settings_form"> | 		<fieldset> | ||||||
| 			@websiteSettingsForm(data, website, form) | 			@websiteSettingsForm(data, website, form) | ||||||
|  | 		</fieldset> | ||||||
|  | 		<fieldset> | ||||||
| 			@guestbookSettingsForm(data, website, gb, form) | 			@guestbookSettingsForm(data, website, gb, form) | ||||||
| 		</div> | 		</fieldset> | ||||||
| 		<input type="submit" value="Submit"/> | 		<button type="submit">Save Settings</button> | ||||||
| 	</form> | 	</form> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| templ DeleteForm(data CommonData, website models.Website, form forms.WebsiteDeleteForm) { | templ DeleteForm(data CommonData, website models.Website, form forms.WebsiteDeleteForm) { | ||||||
| 	{{ putUrl := fmt.Sprintf("/websites/%s", shortIdToSlug(website.ShortId)) }} | 	{{ putUrl := fmt.Sprintf("/websites/%s", shortIdToSlug(website.ShortId)) }} | ||||||
| 	<form hx-put={ putUrl } hx-swap="outerHTML"> | 	<form hx-put={ putUrl } hx-swap="outerHTML" aria-label="Delete website"> | ||||||
| 		<input type="hidden" name="csrf_token" value={ data.CSRFToken }/> | 		<input type="hidden" name="csrf_token" value={ data.CSRFToken }/> | ||||||
| 		<h3>Delete Website</h3> | 		<fieldset class="danger-zone"> | ||||||
| 		<p>Deleting a website is permanent. Be absolutely sure before proceeding.</p> | 			<legend id="danger-zone-heading">Delete Website</legend> | ||||||
|  | 			<aside role="alert" class="warning-notice"> | ||||||
|  | 				<p><strong>Warning:</strong> Deleting a website is permanent. Be absolutely sure before proceeding.</p> | ||||||
|  | 			</aside> | ||||||
| 			{{ err, exists := form.FieldErrors["delete"] }} | 			{{ err, exists := form.FieldErrors["delete"] }} | ||||||
| 		<label for="delete">Type your site name in the form.</label> | 			<div class="form-group"> | ||||||
| 				if exists { | 				if exists { | ||||||
| 					<label class="error">{ err }</label> | 					<label class="error">{ err }</label> | ||||||
| 				} | 				} | ||||||
| 		<input type="text" name="delete" id="delete" required/> | 				<label for="delete">Type your site name to confirm deletion</label> | ||||||
| 		<input type="submit" value="Delete" class="danger"/> | 				<input type="text" name="delete" id="delete" required aria-describedby="delete-help" placeholder={ website.Name }/> | ||||||
|  | 				<small id="delete-help">Type { website.Name } exactly as shown to confirm deletion</small> | ||||||
|  | 			</div> | ||||||
|  | 			<button type="submit" class="danger">Delete Website</button> | ||||||
|  | 		</fieldset> | ||||||
| 	</form> | 	</form> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -269,8 +356,12 @@ templ WebsiteDashboardSettings(data CommonData, website models.Website, form for | |||||||
| 		<div id="dashboard"> | 		<div id="dashboard"> | ||||||
| 			@wSidebar(website) | 			@wSidebar(website) | ||||||
| 			<div> | 			<div> | ||||||
|  | 				<section aria-labelledby="website-settings-heading"> | ||||||
| 					@SettingsForm(data, website, form, "") | 					@SettingsForm(data, website, form, "") | ||||||
|  | 				</section> | ||||||
|  | 				<section aria-labelledby="danger-zone-heading"> | ||||||
| 					@DeleteForm(data, website, forms.WebsiteDeleteForm{}) | 					@DeleteForm(data, website, forms.WebsiteDeleteForm{}) | ||||||
|  | 				</section> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	} | 	} | ||||||
|  | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user