diff --git a/assets/dk.css b/assets/dk.css index 00e9478..f5b4c2f 100644 --- a/assets/dk.css +++ b/assets/dk.css @@ -4,23 +4,33 @@ for legibility and design, but keep the soul of the original project. */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - :root { color: #222; font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif; font-size: 16px; } +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: inherit; + font-size: 1rem; +} + body { padding: 1rem; background-image: url("/assets/images/backgrounds/snowstorm.jpg"); } +i { font-style: italic; } +b { font-weight: bold; } + +h1 { font-size: 2rem; font-weight: bold; } +h2 { font-size: 1.7rem; font-weight: bold; } +h3 { font-size: 1.4rem; font-weight: bold; } +h4 { font-size: 1.1rem; font-weight: bold; } + div#container { width: 90vw; display: flex; @@ -46,6 +56,10 @@ section#game { margin: 1rem 0; border-top: 2px solid #000; + & > aside { + padding: 0.5rem; + } + & > aside > section:not(:last-child) { margin-bottom: 1rem; } @@ -58,18 +72,16 @@ section#game { & > aside#left { grid-column: 1; border-right: 2px solid #000; - padding: 3px; } & > main { grid-column: 2; - padding: 3px; + padding: 0.5rem; } & > aside#right { grid-column: 3; border-left: 2px solid #000; - padding: 3px; } } @@ -100,7 +112,6 @@ div.title { background-color: #eeeeee; font-weight: bold; padding: 5px; - margin: 3px; } footer { @@ -202,6 +213,24 @@ form.standard { & > div.actions { margin-top: 1rem; } + + input.text { + appearance: none; + outline: none; + border: 1px solid transparent; + padding: 0.25rem 0.25rem; + background-color: rgba(0, 0, 0, 0.4); + color: white; + + &:hover { + background-color: rgba(0, 0, 0, 0.5); + } + + &:focus { + background-color: rgba(0, 0, 0, 0.6); + border-color: black; + } + } } .mb-1 { @@ -210,4 +239,28 @@ form.standard { .mb-05 { margin-bottom: 0.5rem; +} + +div.town { + & > div:not(:last-child) { + margin-bottom: 2rem; + } + + & > div.split { + width: 100%; + display: flex; + gap: 1rem; + + & > div { + width: 100%; + } + } +} + +button.img-button { + appearance: none; + border: none; + outline: none; + background: none; + cursor: pointer; } \ No newline at end of file diff --git a/assets/reset.css b/assets/reset.css new file mode 100644 index 0000000..20637d5 --- /dev/null +++ b/assets/reset.css @@ -0,0 +1,48 @@ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +body { + line-height: 1; +} + +ol, ul { + list-style: none; +} + +blockquote, q { + quotes: none; +} + +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} \ No newline at end of file diff --git a/internal/helpers/markdown/md.go b/internal/helpers/markdown/md.go new file mode 100644 index 0000000..22c54a6 --- /dev/null +++ b/internal/helpers/markdown/md.go @@ -0,0 +1,117 @@ +package markdown + +import ( + "html" + "strings" +) + +// MarkdownToHTML converts a basic subset of markdown to HTML, for +// use in the news posts, forums, or user blurbs. +func MarkdownToHTML(s string) string { + // Escape HTML entities first to sanitize user input + s = html.EscapeString(s) + + // Handle headers first (line-based) + lines := strings.Split(s, "\n") + for i, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "### ") { + lines[i] = "
" + code + "
" + s[end+1:]
+ }
+ return s
+}
+
+func replaceLinks(s string) string {
+ for {
+ linkStart := strings.Index(s, "[")
+ if linkStart == -1 {
+ break
+ }
+ linkEnd := strings.Index(s[linkStart:], "](")
+ if linkEnd == -1 {
+ break
+ }
+ linkEnd += linkStart
+ urlStart := linkEnd + 2
+ urlEnd := strings.Index(s[urlStart:], ")")
+ if urlEnd == -1 {
+ break
+ }
+ urlEnd += urlStart
+
+ text := s[linkStart+1 : linkEnd]
+ url := s[urlStart:urlEnd]
+ s = s[:linkStart] + `` + text + "" + s[urlEnd+1:]
+ }
+ return s
+}
+
+func replaceBoldItalic(s string) string {
+ // Handle bold first
+ for strings.Contains(s, "**") {
+ first := strings.Index(s, "**")
+ if first == -1 {
+ break
+ }
+ second := strings.Index(s[first+2:], "**")
+ if second == -1 {
+ break
+ }
+ second += first + 2
+ text := s[first+2 : second]
+ s = s[:first] + "" + text + "" + s[second+2:]
+ }
+
+ // Handle italic
+ for strings.Contains(s, "*") {
+ first := strings.Index(s, "*")
+ if first == -1 {
+ break
+ }
+ second := strings.Index(s[first+1:], "*")
+ if second == -1 {
+ break
+ }
+ second += first + 1
+ text := s[first+1 : second]
+ s = s[:first] + "" + text + "" + s[second+1:]
+ }
+
+ return s
+}
diff --git a/internal/news/news.go b/internal/news/news.go
index 7949be8..a06e9e8 100644
--- a/internal/news/news.go
+++ b/internal/news/news.go
@@ -21,9 +21,9 @@ type News struct {
// New creates a new News with sensible defaults
func New() *News {
return &News{
- Author: 0, // No author by default
- Posted: time.Now().Unix(), // Current time
- Content: "", // Empty content
+ Author: 0, // No author by default
+ Posted: time.Now().Unix(), // Current time
+ Content: "", // Empty content
}
}
@@ -229,6 +229,11 @@ func (n *News) Age() time.Duration {
return time.Since(n.PostedTime())
}
+// ReadableTime converts a time.Time to a human-readable date string
+func (n *News) ReadableTime() string {
+ return n.PostedTime().Format("Jan 2, 2006 3:04 PM")
+}
+
// IsAuthor returns true if the given user ID is the author of this news post
func (n *News) IsAuthor(userID int) bool {
return n.Author == userID
@@ -317,13 +322,14 @@ func (n *News) ToMap() map[string]any {
"Author": n.Author,
"Posted": n.Posted,
"Content": n.Content,
-
+
// Computed values
- "PostedTime": n.PostedTime(),
- "IsRecent": n.IsRecent(),
- "Age": n.Age(),
- "WordCount": n.WordCount(),
- "Length": n.Length(),
- "IsEmpty": n.IsEmpty(),
+ "PostedTime": n.PostedTime(),
+ "IsRecent": n.IsRecent(),
+ "Age": n.Age(),
+ "ReadableTime": n.ReadableTime(),
+ "WordCount": n.WordCount(),
+ "Length": n.Length(),
+ "IsEmpty": n.IsEmpty(),
}
}
diff --git a/internal/routes/town.go b/internal/routes/town.go
index ad61ee1..47bcc57 100644
--- a/internal/routes/town.go
+++ b/internal/routes/town.go
@@ -18,6 +18,8 @@ func RegisterTownRoutes(r *router.Router) {
func showTown(ctx router.Ctx, _ []string) {
town := ctx.UserValue("town").(*towns.Town)
components.RenderPageTemplate(ctx, town.Name, "town/town.html", map[string]any{
- "town": town,
+ "town": town,
+ "newscontent": components.GenerateTownNews(),
+ "whosonline": components.GenerateTownWhosOnline(),
})
}
diff --git a/internal/template/components/town.go b/internal/template/components/town.go
new file mode 100644
index 0000000..24ca39e
--- /dev/null
+++ b/internal/template/components/town.go
@@ -0,0 +1,26 @@
+package components
+
+import (
+ "dk/internal/helpers/markdown"
+ "dk/internal/news"
+ "fmt"
+)
+
+func GenerateTownNews() string {
+ title := `