diff --git a/server/brief.go b/server/brief.go index 77a8eb3..ba26616 100644 --- a/server/brief.go +++ b/server/brief.go @@ -15,10 +15,11 @@ import ( ) type briefPageModel struct { - Number int - Prev int - Next int - Pages []briefRenderPage + Number int + Prev int + Next int + Heading letterHeadModel + Pages []briefRenderPage } type briefRenderPage struct { @@ -57,7 +58,9 @@ func (s *Server) Brief(c *echo.Context) error { return c.String(http.StatusNotFound, "brief not found") } + meta := s.app.Library().Metas.Item(num) model := renderBrief(*letter) + model.Heading = buildLetterHead(s.app.Library(), meta) model.Prev, model.Next = briefNeighbors(s.app, num) var out bytes.Buffer if err := s.tmpl.ExecuteTemplate(&out, "brief", model); err != nil { diff --git a/server/endpoints.go b/server/endpoints.go index f9f8b5a..3dde0b4 100644 --- a/server/endpoints.go +++ b/server/endpoints.go @@ -19,5 +19,7 @@ func MapStatic(e *echo.Echo) { } func MapEndpoints(e *echo.Echo, s *Server) { + e.GET("/", s.Home) + e.GET("/briefe", s.Home) e.GET("/brief/:number", s.Brief) } diff --git a/server/letterhead.go b/server/letterhead.go new file mode 100644 index 0000000..2e81cb9 --- /dev/null +++ b/server/letterhead.go @@ -0,0 +1,114 @@ +package server + +import ( + "strconv" + "strings" + + "github.com/Theodor-Springmann-Stiftung/lenz-web/xmlmodels" +) + +type letterHeadModel struct { + Number int + DateText string + IsDraft bool + HasOriginal bool + Pairs []letterHeadPairModel +} + +type letterHeadPairModel struct { + Sent string + Received string +} + +func buildLetterHead(lib *xmlmodels.Library, meta *xmlmodels.Meta) letterHeadModel { + head := letterHeadModel{ + Pairs: make([]letterHeadPairModel, 0), + } + if meta == nil { + return head + } + + head.Number = meta.Letter + head.IsDraft = meta.IsDraft.IsTrue() + head.HasOriginal = meta.HasOriginal.IsTrue() + + if earliest := meta.Earliest(); earliest != nil { + head.DateText = strings.TrimSpace(earliest.Text) + } + + for _, pair := range meta.SendReceivedPairs() { + sentNames := resolveNames(lib, pair.Sent.Persons, true) + + receivedText := "Unbekannt" + if pair.Received != nil { + receivedNames := resolveNames(lib, pair.Received.Persons, true) + receivedPlaces := resolveNames(lib, pair.Received.Places, false) + + if len(receivedNames) > 0 { + receivedText = joinGerman(receivedNames) + } else if len(receivedPlaces) > 0 { + receivedText = joinGerman(receivedPlaces) + } else { + receivedText = "" + } + + if len(receivedPlaces) > 0 && len(receivedNames) > 0 { + receivedText += " (" + joinGerman(receivedPlaces) + ")" + } + } + + head.Pairs = append(head.Pairs, letterHeadPairModel{ + Sent: joinGerman(sentNames), + Received: receivedText, + }) + } + + return head +} + +func resolveNames(lib *xmlmodels.Library, refs []xmlmodels.RefElement, person bool) []string { + ret := make([]string, 0, len(refs)) + for _, ref := range refs { + name := "" + if person { + if def := lib.Person(ref.Reference); def != nil { + name = strings.TrimSpace(def.Name) + if name == "" { + name = strings.TrimSpace(def.FirstName + " " + def.LastName) + } + } + } else { + if def := lib.Place(ref.Reference); def != nil { + name = strings.TrimSpace(def.Name) + } + } + + if name == "" { + name = strings.TrimSpace(ref.Text) + } + if name == "" && ref.Reference > 0 { + if person { + name = "Person " + strconv.Itoa(ref.Reference) + } else { + name = "Ort " + strconv.Itoa(ref.Reference) + } + } + if name != "" { + ret = append(ret, name) + } + } + return ret +} + +func joinGerman(items []string) string { + switch len(items) { + case 0: + return "" + case 1: + return items[0] + case 2: + return items[0] + " und " + items[1] + default: + return strings.Join(items[:len(items)-1], ", ") + " und " + items[len(items)-1] + } +} diff --git a/server/letters.go b/server/letters.go new file mode 100644 index 0000000..5f1e1f8 --- /dev/null +++ b/server/letters.go @@ -0,0 +1,107 @@ +package server + +import ( + "bytes" + "net/http" + "strings" + + "github.com/Theodor-Springmann-Stiftung/lenz-web/xmlmodels" + "github.com/labstack/echo/v5" +) + +type dateRange struct { + Label string + Start int + End int + Letters []letterHeadModel +} + +type lettersPageModel struct { + Ranges []dateRange + SelectedRange string + ShowAll bool + ActiveRanges []dateRange +} + +func (s *Server) Home(c *echo.Context) error { + rangeParam := c.Request().URL.Query().Get("range") + if rangeParam == "" { + rangeParam = "all" + } + model := buildLettersPageModel(s.app.Library(), rangeParam) + var out bytes.Buffer + if err := s.tmpl.ExecuteTemplate(&out, "home", model); err != nil { + return c.String(http.StatusInternalServerError, "template render failed: "+err.Error()) + } + return c.HTML(http.StatusOK, out.String()) +} + +func buildLettersPageModel(lib *xmlmodels.Library, rangeParam string) lettersPageModel { + ranges := []dateRange{ + {Label: "1756-1770", Start: 1756, End: 1770, Letters: []letterHeadModel{}}, + {Label: "1771-1775", Start: 1771, End: 1775, Letters: []letterHeadModel{}}, + {Label: "1776", Start: 1776, End: 1776, Letters: []letterHeadModel{}}, + {Label: "1777-1779", Start: 1777, End: 1779, Letters: []letterHeadModel{}}, + {Label: "1780-1792", Start: 1780, End: 1792, Letters: []letterHeadModel{}}, + } + + years, yearMap := lib.Years() + for _, year := range years { + letters, ok := yearMap[year] + if !ok { + continue + } + + target := -1 + for i := range ranges { + if year >= ranges[i].Start && year <= ranges[i].End { + target = i + break + } + } + if target == -1 { + continue + } + + for _, meta := range letters { + metaCopy := meta + ranges[target].Letters = append(ranges[target].Letters, buildLetterHead(lib, &metaCopy)) + } + } + + selected := strings.TrimSpace(rangeParam) + if selected == "" { + selected = "all" + } + + model := lettersPageModel{ + Ranges: ranges, + SelectedRange: selected, + ShowAll: selected == "all", + ActiveRanges: make([]dateRange, 0, len(ranges)), + } + if model.ShowAll { + for _, r := range ranges { + if len(r.Letters) > 0 { + model.ActiveRanges = append(model.ActiveRanges, r) + } + } + return model + } + + for _, r := range ranges { + if r.Label == selected && len(r.Letters) > 0 { + model.ActiveRanges = append(model.ActiveRanges, r) + return model + } + } + + model.ShowAll = true + model.SelectedRange = "all" + for _, r := range ranges { + if len(r.Letters) > 0 { + model.ActiveRanges = append(model.ActiveRanges, r) + } + } + return model +} diff --git a/templates/components/letterhead.gohtml b/templates/components/letterhead.gohtml new file mode 100644 index 0000000..2eb7097 --- /dev/null +++ b/templates/components/letterhead.gohtml @@ -0,0 +1,27 @@ +{{ define "letterhead" }} +
+
+ {{ if .DateText }} +
{{ .DateText }}
+ {{ end }} +
+ {{ if .IsDraft }} + + {{ end }} + {{ if .HasOriginal }} + + {{ else }} + + {{ end }} +
+
+ + {{ range .Pairs }} +
+
{{ .Sent }}
+
+
{{ .Received }}
+
+ {{ end }} +
+{{ end }} diff --git a/templates/components/letterlist.gohtml b/templates/components/letterlist.gohtml new file mode 100644 index 0000000..acbe166 --- /dev/null +++ b/templates/components/letterlist.gohtml @@ -0,0 +1,9 @@ +{{ define "letterlist" }} +
+ {{ range . }} + + {{ template "letterhead" . }} + + {{ end }} +
+{{ end }} diff --git a/templates/layouts/layout.gohtml b/templates/layouts/layout.gohtml index c2e1409..ff373ca 100644 --- a/templates/layouts/layout.gohtml +++ b/templates/layouts/layout.gohtml @@ -5,6 +5,7 @@ {{ block "title" . }}Default{{ end }} + diff --git a/templates/pages/brief.gohtml b/templates/pages/brief.gohtml index de36bad..6f1f0ee 100644 --- a/templates/pages/brief.gohtml +++ b/templates/pages/brief.gohtml @@ -4,23 +4,27 @@ - Brief {{ .Number }} + LKB - {{ .Number }} +
-
-

Brief {{ .Number }}

+
+
+ {{ template "letterhead" .Heading }} +
diff --git a/templates/pages/home.gohtml b/templates/pages/home.gohtml index 6354f0b..e69c4cd 100644 --- a/templates/pages/home.gohtml +++ b/templates/pages/home.gohtml @@ -1,8 +1,31 @@ {{ define "home" }}{{ template "layout" . }}{{ end }} -{{ define "title" }}Home{{ end }} +{{ define "title" }}Lenz-Briefe{{ end }} {{ define "body" }} -

Home

-

{{ .Message }}

+
+
+

Lenz-Briefe

+

Digitale Edition der Briefe von Jakob Michael Reinhold Lenz

+
+ + + +
+ {{ range .ActiveRanges }} +
+

{{ .Label }}

+
({{ len .Letters }} {{ if eq (len .Letters) 1 }}Brief{{ else }}Briefe{{ end }})
+ {{ template "letterlist" .Letters }} +
+ {{ end }} +
+
{{ end }} diff --git a/templates/public/fonts/LinBiolinum_RB_G.ttf b/templates/public/fonts/LinBiolinum_RB_G.ttf new file mode 100644 index 0000000..2dde5a4 Binary files /dev/null and b/templates/public/fonts/LinBiolinum_RB_G.ttf differ diff --git a/templates/public/fonts/LinBiolinum_RI_G.ttf b/templates/public/fonts/LinBiolinum_RI_G.ttf new file mode 100644 index 0000000..4c3e812 Binary files /dev/null and b/templates/public/fonts/LinBiolinum_RI_G.ttf differ diff --git a/templates/public/fonts/LinBiolinum_R_G.ttf b/templates/public/fonts/LinBiolinum_R_G.ttf new file mode 100644 index 0000000..48b4664 Binary files /dev/null and b/templates/public/fonts/LinBiolinum_R_G.ttf differ diff --git a/templates/public/fonts/LinLibertine_RBI_G.ttf b/templates/public/fonts/LinLibertine_RBI_G.ttf new file mode 100644 index 0000000..f72e79e Binary files /dev/null and b/templates/public/fonts/LinLibertine_RBI_G.ttf differ diff --git a/templates/public/fonts/LinLibertine_RB_G.ttf b/templates/public/fonts/LinLibertine_RB_G.ttf new file mode 100644 index 0000000..f679403 Binary files /dev/null and b/templates/public/fonts/LinLibertine_RB_G.ttf differ diff --git a/templates/public/fonts/LinLibertine_RI_G.ttf b/templates/public/fonts/LinLibertine_RI_G.ttf new file mode 100644 index 0000000..b8763f6 Binary files /dev/null and b/templates/public/fonts/LinLibertine_RI_G.ttf differ diff --git a/templates/public/fonts/LinLibertine_R_G.ttf b/templates/public/fonts/LinLibertine_R_G.ttf new file mode 100644 index 0000000..78ca7b0 Binary files /dev/null and b/templates/public/fonts/LinLibertine_R_G.ttf differ diff --git a/templates/public/fonts/PlayfairDisplay-Bold.ttf b/templates/public/fonts/PlayfairDisplay-Bold.ttf new file mode 100644 index 0000000..029a1a6 Binary files /dev/null and b/templates/public/fonts/PlayfairDisplay-Bold.ttf differ diff --git a/templates/public/fonts/PlayfairDisplay-BoldItalic.ttf b/templates/public/fonts/PlayfairDisplay-BoldItalic.ttf new file mode 100644 index 0000000..921435f Binary files /dev/null and b/templates/public/fonts/PlayfairDisplay-BoldItalic.ttf differ diff --git a/templates/public/fonts/PlayfairDisplay-Italic.ttf b/templates/public/fonts/PlayfairDisplay-Italic.ttf new file mode 100644 index 0000000..436dff0 Binary files /dev/null and b/templates/public/fonts/PlayfairDisplay-Italic.ttf differ diff --git a/templates/public/fonts/PlayfairDisplay-Regular.ttf b/templates/public/fonts/PlayfairDisplay-Regular.ttf new file mode 100644 index 0000000..503b7c4 Binary files /dev/null and b/templates/public/fonts/PlayfairDisplay-Regular.ttf differ diff --git a/templates/public/style.css b/templates/public/style.css index b8fd63c..0051dc7 100644 --- a/templates/public/style.css +++ b/templates/public/style.css @@ -1,448 +1,2 @@ /*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */ -@layer theme, base, components, utilities; -@layer theme { - :root, :host { - --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", - "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", - "Courier New", monospace; - --default-font-family: var(--font-sans); - --default-mono-font-family: var(--font-mono); - } -} -@layer base { - *, ::after, ::before, ::backdrop, ::file-selector-button { - box-sizing: border-box; - margin: 0; - padding: 0; - border: 0 solid; - } - html, :host { - line-height: 1.5; - -webkit-text-size-adjust: 100%; - tab-size: 4; - font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); - font-feature-settings: var(--default-font-feature-settings, normal); - font-variation-settings: var(--default-font-variation-settings, normal); - -webkit-tap-highlight-color: transparent; - } - hr { - height: 0; - color: inherit; - border-top-width: 1px; - } - abbr:where([title]) { - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; - } - h1, h2, h3, h4, h5, h6 { - font-size: inherit; - font-weight: inherit; - } - a { - color: inherit; - -webkit-text-decoration: inherit; - text-decoration: inherit; - } - b, strong { - font-weight: bolder; - } - code, kbd, samp, pre { - font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace); - font-feature-settings: var(--default-mono-font-feature-settings, normal); - font-variation-settings: var(--default-mono-font-variation-settings, normal); - font-size: 1em; - } - small { - font-size: 80%; - } - sub, sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; - } - sub { - bottom: -0.25em; - } - sup { - top: -0.5em; - } - table { - text-indent: 0; - border-color: inherit; - border-collapse: collapse; - } - :-moz-focusring { - outline: auto; - } - progress { - vertical-align: baseline; - } - summary { - display: list-item; - } - ol, ul, menu { - list-style: none; - } - img, svg, video, canvas, audio, iframe, embed, object { - display: block; - vertical-align: middle; - } - img, video { - max-width: 100%; - height: auto; - } - button, input, select, optgroup, textarea, ::file-selector-button { - font: inherit; - font-feature-settings: inherit; - font-variation-settings: inherit; - letter-spacing: inherit; - color: inherit; - border-radius: 0; - background-color: transparent; - opacity: 1; - } - :where(select:is([multiple], [size])) optgroup { - font-weight: bolder; - } - :where(select:is([multiple], [size])) optgroup option { - padding-inline-start: 20px; - } - ::file-selector-button { - margin-inline-end: 4px; - } - ::placeholder { - opacity: 1; - } - @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) { - ::placeholder { - color: currentcolor; - @supports (color: color-mix(in lab, red, red)) { - color: color-mix(in oklab, currentcolor 50%, transparent); - } - } - } - textarea { - resize: vertical; - } - ::-webkit-search-decoration { - -webkit-appearance: none; - } - ::-webkit-date-and-time-value { - min-height: 1lh; - text-align: inherit; - } - ::-webkit-datetime-edit { - display: inline-flex; - } - ::-webkit-datetime-edit-fields-wrapper { - padding: 0; - } - ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { - padding-block: 0; - } - ::-webkit-calendar-picker-indicator { - line-height: 1; - } - :-moz-ui-invalid { - box-shadow: none; - } - button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { - appearance: button; - } - ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { - height: auto; - } - [hidden]:where(:not([hidden="until-found"])) { - display: none !important; - } -} -@layer utilities { - .static { - position: static; - } - .block { - display: block; - } -} -:root { - --bg: #fff; - --ink: #2a2824; - --muted: #7f7a72; - --rule: #d8d4cc; -} -body { - margin: 0; - background: var(--bg); - color: var(--ink); - font-family: Georgia, "Times New Roman", serif; - font-size: 1.125rem; -} -html { - font-size: 112.5%; -} -.brief-page { - max-width: 1360px; - margin: 0 auto; - padding: 0; -} -.brief-head { - position: sticky; - top: 0; - z-index: 10; - display: flex; - justify-content: space-between; - align-items: center; - gap: 1rem; - padding: 0.5rem 0; - margin: 0; - border-bottom: none; - background: #fff; -} -.brief-head h1 { - margin: 0; - font-size: 1rem; - letter-spacing: 0.1em; - text-transform: uppercase; - color: var(--muted); -} -.brief-nav { - display: flex; - gap: 0.3rem; -} -.brief-nav-btn { - text-decoration: none; - border: 1px solid var(--rule); - border-radius: 999px; - color: var(--ink); - font-size: 0.86rem; - padding: 0.2rem 0.5rem; -} -.brief-nav-btn.is-disabled { - opacity: 0.35; - pointer-events: none; -} -.brief-grid { - display: block; - margin: 0; - padding: 0; -} -.grid-row { - display: grid; - grid-template-columns: 250px minmax(0, 1fr) 250px; - column-gap: 0; - margin: 0; - padding: 0; -} -.row-divider { - margin: 0; - padding: 0; -} -.page-divider-line { - border-top: 1px solid #cfc8bc; - margin: 0; - padding: 0; - width: 100%; -} -.grid-left, .grid-mid, .grid-right { - margin: 0; - padding: 0; -} -.grid-full { - grid-column: 1 / -1; -} -.page-marker { - margin: 0; - padding: 0.18rem 0 0.18rem 0; - font-size: 0.84rem; - color: #5f584f; - letter-spacing: 0.08em; - text-transform: uppercase; - line-height: 1; - text-align: right; -} -.notes-col { - padding-top: 0.18rem; -} -.notes-col > .sidenote + .sidenote { - margin-top: 0.8rem; -} -.page-lines { - margin: 0; - padding: 0; -} -.notes-band { - margin: 0; - padding: 0.1rem 0; -} -.sidenote { - margin: 0; - padding: 0; - border: none; - background: transparent; -} -.sidenote-annotation { - margin: 0; - padding: 0; - font-size: 0.78rem; - color: #8d867b; - font-family: "Times New Roman", Georgia, serif; - font-style: italic; -} -.pos-top-right, .pos-bottom-right, .pos-right { - text-align: right; -} -.pos-top-center, .pos-bottom-center { - text-align: center; -} -.line { - white-space: normal; - font-size: 1.12rem; - line-height: 1.5; - color: #2f2c27; - margin: 0; - padding: 0; -} -.notes-col .line, .notes-band .line { - font-size: 0.9rem; -} -.line-empty { - height: 1rem; -} -.line-first { - padding-left: 0; -} -.line-continuation { - color: #3b3731; -} -.line-indent-0 { - text-indent: 0; -} -.line-indent-1 { - text-indent: 0.58rem; -} -.line-indent-2 { - text-indent: 1.16rem; -} -.line-indent-3 { - text-indent: 1.74rem; -} -.line-indent-4 { - text-indent: 2.32rem; -} -.line-indent-5 { - text-indent: 2.9rem; -} -.line-indent-6 { - text-indent: 3.48rem; -} -.line-indent-7 { - text-indent: 4.06rem; -} -.line-indent-8 { - text-indent: 4.64rem; -} -.line-indent-9 { - text-indent: 5.22rem; -} -.line-indent-10 { - text-indent: 5.8rem; -} -.line-indent-11 { - text-indent: 6.38rem; -} -.line-indent-12 { - text-indent: 6.96rem; -} -.line-align-ctx { - text-align: right; -} -.line-align-ctx .tag-align.align-center { - display: inline-block; - width: 100%; - text-align: center; -} -.line-align-ctx .tag-align.align-right { - display: inline-block; - width: 100%; - text-align: right; -} -.line-tab-ctx .tag-tab { - display: inline-block; - min-width: 3.6ch; - border-left: 1px dotted #cdbfae; - padding-left: 0.34rem; -} -.tag-aq, .tag-note { - font-family: "Trebuchet MS", "Helvetica Neue", Arial, sans-serif; -} -.tag-b { - font-weight: 700; -} -.tag-del { - text-decoration: line-through; -} -.tag-dul { - text-decoration: underline; - text-decoration-style: double; -} -.tag-tul { - text-decoration: underline; - text-decoration-style: wavy; - text-decoration-thickness: 2px; -} -.tag-it { - font-style: italic; -} -.tag-ul { - text-decoration: underline; -} -.tag-note { - font-size: 0.9em; - font-weight: 700; - color: var(--muted); -} -.tag-note { - font-family: "Times New Roman", Georgia, serif; - font-style: italic; - font-weight: 400; - color: #8d867b; -} -.tag-ink { - color: #2d5e8a; -} -.tag-pe { - color: #4e667f; -} -.tag-tl { - background: #f5e4d6; - color: #8c5a41; - padding: 0 0.12rem; - border-radius: 2px; -} -.tag-nr { - color: #6f675d; -} -.tag-insertion { - position: relative; - padding-left: 0.1rem; -} -.tag-insertion::before { - content: "\2038"; - font-size: 0.78em; - vertical-align: super; - color: #9a8b76; - margin-right: 0.06rem; -} -@media (max-width: 960px) { - .brief-page { - max-width: 100%; - } - .grid-row { - grid-template-columns: 1fr; - } - .notes-col, .grid-left, .grid-right, .notes-band { - text-align: left; - } -} +@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-serif:ui-serif,Georgia,Cambria,"Times New Roman",Times,serif;--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.static{position:static}.block{display:block}}@font-face{font-family:Linux Libertine;font-style:normal;font-weight:400;src:url(/public/fonts/LinLibertine_R_G.ttf)format("truetype")}@font-face{font-family:Linux Libertine;font-style:italic;font-weight:400;src:url(/public/fonts/LinLibertine_RI_G.ttf)format("truetype")}@font-face{font-family:Linux Libertine;font-style:normal;font-weight:700;src:url(/public/fonts/LinLibertine_RB_G.ttf)format("truetype")}@font-face{font-family:Linux Libertine;font-style:italic;font-weight:700;src:url(/public/fonts/LinLibertine_RBI_G.ttf)format("truetype")}@font-face{font-family:Linux Biolinum;font-style:normal;font-weight:400;src:url(/public/fonts/LinBiolinum_R_G.ttf)format("truetype")}@font-face{font-family:Linux Biolinum;font-style:italic;font-weight:400;src:url(/public/fonts/LinBiolinum_RI_G.ttf)format("truetype")}@font-face{font-family:Linux Biolinum;font-style:normal;font-weight:700;src:url(/public/fonts/LinBiolinum_RB_G.ttf)format("truetype")}@font-face{font-family:Playfair;font-style:normal;font-weight:400;src:url(/public/fonts/PlayfairDisplay-Regular.ttf)format("truetype")}@font-face{font-family:Playfair;font-style:italic;font-weight:400;src:url(/public/fonts/PlayfairDisplay-Italic.ttf)format("truetype")}@font-face{font-family:Playfair;font-style:normal;font-weight:700;src:url(/public/fonts/PlayfairDisplay-Bold.ttf)format("truetype")}@font-face{font-family:Playfair;font-style:italic;font-weight:700;src:url(/public/fonts/PlayfairDisplay-BoldItalic.ttf)format("truetype")}:root{--bg:#fff;--ink:#2a2824;--muted:#7f7a72;--rule:#d8d4cc;--font-serif:"Linux Libertine",ui-serif,serif;--font-sans:"Linux Biolinum","Merriweather Sans",ui-sans-serif,sans-serif;--font-sansugly:Arial,"Linux Biolinum","Merriweather Sans",ui-sans-serif,sans-serif;--font-didone:"Playfair",ui-serif,serif}body{background:var(--bg);color:var(--ink);font-family:var(--font-serif);margin:0;padding:1rem;font-size:1.125rem}html{font-size:112.5%}.brief-page{max-width:1360px;margin:0 auto;padding:0}.brief-head{z-index:10;background:#fff;border-bottom:none;justify-content:space-between;align-items:center;gap:1rem;margin:0;padding:.5rem 0;display:flex;position:sticky;top:0}.brief-head-letter{border-bottom:1px solid #d7d2c8;align-items:flex-end;margin-bottom:.8rem}.brief-head-left{flex:auto}.brief-nav-mid{font-variant:small-caps}.brief-head h1{letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin:0;font-size:1rem}.brief-nav{gap:.3rem;display:flex}.brief-nav-btn{color:var(--ink);padding:0;font-size:.86rem;text-decoration:none}.brief-nav-btn.is-disabled{opacity:.35;pointer-events:none}.brief-grid{margin:0;padding:0;display:block}.grid-row{grid-template-columns:250px minmax(0,1fr) 250px;column-gap:0;margin:0;padding:0;display:grid}.row-divider{margin:0;padding:0}.page-divider-line{border-top:1px solid #cfc8bc;width:100%;margin:0;padding:0}.grid-left,.grid-mid,.grid-right{margin:0;padding:0}.grid-full{grid-column:1/-1}.page-marker{color:#5f584f;letter-spacing:.08em;text-transform:uppercase;text-align:right;margin:0;padding:.18rem 0;font-size:.84rem;line-height:1}.notes-col{padding-top:.18rem}.notes-col>.sidenote+.sidenote{margin-top:.8rem}.page-lines{margin:0;padding:0}.notes-band{margin:0;padding:.1rem 0}.sidenote{background:0 0;border:none;margin:0;padding:0}.sidenote-annotation{color:#8d867b;font-size:.78rem;font-family:var(--font-serif);margin:0;padding:0;font-style:italic}.pos-top-right,.pos-bottom-right,.pos-right{text-align:right}.pos-top-center,.pos-bottom-center{text-align:center}.line{white-space:normal;color:#2f2c27;margin:0;padding:0;font-size:1.12rem;line-height:1.5}.notes-col .line,.notes-band .line{font-size:.9rem}.line-empty{height:1rem}.line-first{padding-left:0}.line-continuation{color:#3b3731}.line-indent-0{text-indent:0}.line-indent-1{text-indent:.58rem}.line-indent-2{text-indent:1.16rem}.line-indent-3{text-indent:1.74rem}.line-indent-4{text-indent:2.32rem}.line-indent-5{text-indent:2.9rem}.line-indent-6{text-indent:3.48rem}.line-indent-7{text-indent:4.06rem}.line-indent-8{text-indent:4.64rem}.line-indent-9{text-indent:5.22rem}.line-indent-10{text-indent:5.8rem}.line-indent-11{text-indent:6.38rem}.line-indent-12{text-indent:6.96rem}.line-align-ctx{grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1fr);align-items:baseline;display:grid;overflow:visible}.line-align-ctx>*{grid-column:1;justify-self:start}.line-align-ctx .tag-align.align-left{text-align:left;grid-column:1}.line-align-ctx .tag-align.align-center,.line .tag-align.align-center{text-align:center;grid-column:2}.line-align-ctx .tag-align.align-right,.line .tag-align.align-right{text-align:right;grid-column:3}.line-tab-ctx .tag-tab,.line-indent .tag-tab,.line .tag-tab{border-left:1px dotted #cdbfae;min-width:3.6ch;padding-left:.34rem;display:inline-block}.line-tab-ctx .tag-tab[data-tab="1"],.line-indent .tag-tab[data-tab="1"],.line .tag-tab[data-tab="1"]{min-width:5ch}.line .tag-tab[data-tab="2"]{min-width:8ch}.line .tag-tab[data-tab="3"]{min-width:11ch}.line .tag-tab[data-tab="4"]{min-width:14ch}.line .tag-tab[data-tab="5"]{min-width:17ch}.line .tag-tab[data-tab="6"]{min-width:20ch}.line .tag-tab[data-tab="7"]{min-width:23ch}.line .tag-tab[data-tab="8"]{min-width:26ch}.tag-aq,.tag-note{font-family:var(--font-sansugly)}.tag-b{font-weight:700}.tag-del{text-decoration:line-through;position:relative}.tag-dul{-webkit-text-decoration:underline double;text-decoration:underline double}.tag-tul{-webkit-text-decoration:underline wavy;text-decoration:underline wavy;text-decoration-thickness:2px}.tag-it{font-style:italic}.tag-ul{text-decoration:underline}.tag-note{color:#8d867b;font-size:.9em;font-weight:700;font-family:var(--font-serif);font-style:italic;font-weight:400}.tag-ink{color:#1e3a8a}.tag-pe{color:#57534e}.tag-tl{color:#8c5a41;background:#f5e4d6;border-radius:2px;padding:0 .12rem}.tag-tl:before{content:"◌";color:#475569;font-family:var(--font-sans);margin-right:.08rem}.tag-nr{color:#6f675d}.tag-subst,.tag-ru,.tag-gr,.tag-hb,.tag-fn,.tag-generic{display:inline}.tag-hand{font-family:var(--font-didone);color:#000027;font-size:.9em;display:inline}.tag-er{--tag-er-rgb:0,0,39;background-image:repeating-linear-gradient(-45deg,rgba(var(--tag-er-rgb),.5),transparent 1px,transparent 6px);-webkit-box-decoration-break:clone;box-decoration-break:clone;color:#0000;text-shadow:0 0 rgb(var(--tag-er-rgb))}.tag-align.align-right{text-align:right;display:block}.tag-align.align-center{text-align:center;display:block}.line .tag-align,.line .tag-align *{text-indent:0!important}.tag-insertion{white-space:nowrap;position:relative}.tag-insertion:before{content:"⌞";color:#475569;margin-right:-.2em}.tag-insertion:after{content:"⌟";color:#475569;margin-left:-.4ch}@media (max-width:960px){.brief-page{max-width:100%}.grid-row{grid-template-columns:1fr}.notes-col,.grid-left,.grid-right,.notes-band{text-align:left}}.letters-home{max-width:1024px;margin:0 auto}.letters-nav{flex-wrap:wrap;gap:.4rem;margin-bottom:1.2rem;display:flex}.letters-nav-item{color:#5f584f;padding:.05rem .1rem;font-size:.85rem;text-decoration:none;display:inline-block}.letters-nav-item:hover{color:#2a2824}.letters-nav-item.is-active{color:#2a2824;text-underline-offset:.18rem;text-decoration:underline}.letters-intro h1{margin-bottom:.2rem;font-size:1.9rem}.letters-intro p{color:#6e675f;margin-bottom:1.5rem}.letters-range{margin-bottom:1.8rem;padding-left:0}.letters-range h2{font-size:1.25rem}.letters-count{color:#6e675f;margin:.25rem 0 .8rem;font-size:.95rem}.letter-list-item{margin-bottom:.75rem;padding:.15rem 0;display:block}.letter-list-item:hover{background:0 0}.letterhead-date{font-style:italic}.letterhead-meta{align-items:baseline;gap:.6rem;display:flex}.letterhead-badges{gap:.35rem;display:inline-flex}.letter-badge{color:#6e675f;padding:0;font-size:.72rem}.letter-badge i{vertical-align:middle}.letterhead-row{gap:.65rem;display:flex}.letterhead-arrow{color:#8b847a} \ No newline at end of file diff --git a/templates/src/style.css b/templates/src/style.css index 5ff5d93..e17474c 100644 --- a/templates/src/style.css +++ b/templates/src/style.css @@ -1,18 +1,100 @@ @import "tailwindcss"; +@font-face { + font-family: "Linux Libertine"; + font-style: normal; + font-weight: 400; + src: url("/public/fonts/LinLibertine_R_G.ttf") format("truetype"); +} + +@font-face { + font-family: "Linux Libertine"; + font-style: italic; + font-weight: 400; + src: url("/public/fonts/LinLibertine_RI_G.ttf") format("truetype"); +} + +@font-face { + font-family: "Linux Libertine"; + font-style: normal; + font-weight: 700; + src: url("/public/fonts/LinLibertine_RB_G.ttf") format("truetype"); +} + +@font-face { + font-family: "Linux Libertine"; + font-style: italic; + font-weight: 700; + src: url("/public/fonts/LinLibertine_RBI_G.ttf") format("truetype"); +} + +@font-face { + font-family: "Linux Biolinum"; + font-style: normal; + font-weight: 400; + src: url("/public/fonts/LinBiolinum_R_G.ttf") format("truetype"); +} + +@font-face { + font-family: "Linux Biolinum"; + font-style: italic; + font-weight: 400; + src: url("/public/fonts/LinBiolinum_RI_G.ttf") format("truetype"); +} + +@font-face { + font-family: "Linux Biolinum"; + font-style: normal; + font-weight: 700; + src: url("/public/fonts/LinBiolinum_RB_G.ttf") format("truetype"); +} + +@font-face { + font-family: "Playfair"; + font-style: normal; + font-weight: 400; + src: url("/public/fonts/PlayfairDisplay-Regular.ttf") format("truetype"); +} + +@font-face { + font-family: "Playfair"; + font-style: italic; + font-weight: 400; + src: url("/public/fonts/PlayfairDisplay-Italic.ttf") format("truetype"); +} + +@font-face { + font-family: "Playfair"; + font-style: normal; + font-weight: 700; + src: url("/public/fonts/PlayfairDisplay-Bold.ttf") format("truetype"); +} + +@font-face { + font-family: "Playfair"; + font-style: italic; + font-weight: 700; + src: url("/public/fonts/PlayfairDisplay-BoldItalic.ttf") format("truetype"); +} + :root { --bg: #fff; --ink: #2a2824; --muted: #7f7a72; --rule: #d8d4cc; + --font-serif: "Linux Libertine", ui-serif, serif; + --font-sans: "Linux Biolinum", "Merriweather Sans", ui-sans-serif, sans-serif; + --font-sansugly: Arial, "Linux Biolinum", "Merriweather Sans", ui-sans-serif, sans-serif; + --font-didone: "Playfair", ui-serif, serif; } body { margin: 0; background: var(--bg); color: var(--ink); - font-family: Georgia, "Times New Roman", serif; + font-family: var(--font-serif); font-size: 1.125rem; + padding: 1rem; } html { @@ -39,6 +121,20 @@ html { background: #fff; } +.brief-head-letter { + align-items: flex-end; + border-bottom: 1px solid #d7d2c8; + margin-bottom: 0.8rem; +} + +.brief-head-left { + flex: 1 1 auto; +} + +.brief-nav-mid { + font-variant: small-caps; +} + .brief-head h1 { margin: 0; font-size: 1rem; @@ -54,11 +150,9 @@ html { .brief-nav-btn { text-decoration: none; - border: 1px solid var(--rule); - border-radius: 999px; color: var(--ink); font-size: 0.86rem; - padding: 0.2rem 0.5rem; + padding: 0; } .brief-nav-btn.is-disabled { opacity: 0.35; pointer-events: none; } @@ -141,7 +235,7 @@ html { padding: 0; font-size: 0.78rem; color: #8d867b; - font-family: "Times New Roman", Georgia, serif; + font-family: var(--font-serif); font-style: italic; } @@ -188,49 +282,150 @@ html { .line-indent-11 { text-indent: 6.38rem; } .line-indent-12 { text-indent: 6.96rem; } -.line-align-ctx { text-align: right; } -.line-align-ctx .tag-align.align-center { display: inline-block; width: 100%; text-align: center; } -.line-align-ctx .tag-align.align-right { display: inline-block; width: 100%; text-align: right; } +.line-align-ctx { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); + align-items: baseline; + overflow: visible; +} -.line-tab-ctx .tag-tab { +.line-align-ctx > * { + grid-column: 1; + justify-self: start; +} + +.line-align-ctx .tag-align.align-left { + grid-column: 1; + text-align: left; +} + +.line-align-ctx .tag-align.align-center, +.line .tag-align.align-center { + grid-column: 2; + text-align: center; +} + +.line-align-ctx .tag-align.align-right, +.line .tag-align.align-right { + grid-column: 3; + text-align: right; +} + +.line-tab-ctx .tag-tab, +.line-indent .tag-tab, +.line .tag-tab { display: inline-block; min-width: 3.6ch; border-left: 1px dotted #cdbfae; padding-left: 0.34rem; } +.line-tab-ctx .tag-tab[data-tab="1"], +.line-indent .tag-tab[data-tab="1"], +.line .tag-tab[data-tab="1"] { + min-width: 5ch; +} + +.line .tag-tab[data-tab="2"] { min-width: 8ch; } +.line .tag-tab[data-tab="3"] { min-width: 11ch; } +.line .tag-tab[data-tab="4"] { min-width: 14ch; } +.line .tag-tab[data-tab="5"] { min-width: 17ch; } +.line .tag-tab[data-tab="6"] { min-width: 20ch; } +.line .tag-tab[data-tab="7"] { min-width: 23ch; } +.line .tag-tab[data-tab="8"] { min-width: 26ch; } + .tag-aq, -.tag-note { font-family: "Trebuchet MS", "Helvetica Neue", Arial, sans-serif; } +.tag-note { font-family: var(--font-sansugly); } .tag-b { font-weight: 700; } -.tag-del { text-decoration: line-through; } +.tag-del { + text-decoration: line-through; + position: relative; +} .tag-dul { text-decoration: underline; text-decoration-style: double; } .tag-tul { text-decoration: underline; text-decoration-style: wavy; text-decoration-thickness: 2px; } .tag-it { font-style: italic; } .tag-ul { text-decoration: underline; } .tag-note { font-size: 0.9em; font-weight: 700; color: var(--muted); } .tag-note { - font-family: "Times New Roman", Georgia, serif; + font-family: var(--font-serif); font-style: italic; font-weight: 400; color: #8d867b; } -.tag-ink { color: #2d5e8a; } -.tag-pe { color: #4e667f; } -.tag-tl { background: #f5e4d6; color: #8c5a41; padding: 0 0.12rem; border-radius: 2px; } +.tag-ink { color: #1e3a8a; } +.tag-pe { color: #57534e; } +.tag-tl { + background: #f5e4d6; + color: #8c5a41; + padding: 0 0.12rem; + border-radius: 2px; +} +.tag-tl::before { + content: "◌"; + color: #475569; + font-family: var(--font-sans); + margin-right: 0.08rem; +} .tag-nr { color: #6f675d; } +.tag-subst, +.tag-ru, +.tag-gr, +.tag-hb, +.tag-fn, +.tag-generic { display: inline; } + +.tag-hand { + display: inline; + font-family: var(--font-didone); + font-size: 0.9em; + color: #000027; +} + +.tag-er { + --tag-er-rgb: 0, 0, 39; + background-image: repeating-linear-gradient( + -45deg, + rgba(var(--tag-er-rgb), 0.5), + transparent 1px, + transparent 6px + ); + -webkit-box-decoration-break: clone; + box-decoration-break: clone; + color: transparent; + text-shadow: 0 0 rgb(var(--tag-er-rgb)); +} + +.tag-align.align-right { + display: block; + text-align: right; +} + +.tag-align.align-center { + display: block; + text-align: center; +} + +.line .tag-align, +.line .tag-align * { + text-indent: 0 !important; +} .tag-insertion { position: relative; - padding-left: 0.1rem; + white-space: nowrap; } .tag-insertion::before { - content: "\2038"; - font-size: 0.78em; - vertical-align: super; - color: #9a8b76; - margin-right: 0.06rem; + content: "⌞"; + color: #475569; + margin-right: -0.2em; +} + +.tag-insertion::after { + content: "⌟"; + color: #475569; + margin-left: -0.4ch; } @media (max-width: 960px) { @@ -247,3 +442,102 @@ html { text-align: left; } } + +.letters-home { + max-width: 1024px; + margin: 0 auto; +} + +.letters-nav { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + margin-bottom: 1.2rem; +} + +.letters-nav-item { + display: inline-block; + text-decoration: none; + padding: 0.05rem 0.1rem; + font-size: 0.85rem; + color: #5f584f; +} + +.letters-nav-item:hover { + color: #2a2824; +} + +.letters-nav-item.is-active { + color: #2a2824; + text-decoration: underline; + text-underline-offset: 0.18rem; +} + +.letters-intro h1 { + font-size: 1.9rem; + margin-bottom: 0.2rem; +} + +.letters-intro p { + color: #6e675f; + margin-bottom: 1.5rem; +} + +.letters-range { + padding-left: 0; + margin-bottom: 1.8rem; +} + +.letters-range h2 { + font-size: 1.25rem; +} + +.letters-count { + font-size: 0.95rem; + color: #6e675f; + margin: 0.25rem 0 0.8rem; +} + +.letter-list-item { + display: block; + margin-bottom: 0.75rem; + padding: 0.15rem 0; +} + +.letter-list-item:hover { + background: transparent; +} + +.letterhead-date { + font-style: italic; +} + +.letterhead-meta { + display: flex; + align-items: baseline; + gap: 0.6rem; +} + +.letterhead-badges { + display: inline-flex; + gap: 0.35rem; +} + +.letter-badge { + padding: 0; + font-size: 0.72rem; + color: #6e675f; +} + +.letter-badge i { + vertical-align: middle; +} + +.letterhead-row { + display: flex; + gap: 0.65rem; +} + +.letterhead-arrow { + color: #8b847a; +}