mirror of
https://github.com/Theodor-Springmann-Stiftung/lenz-web.git
synced 2026-03-21 05:45:32 +00:00
more stuff
This commit is contained in:
@@ -15,10 +15,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type briefPageModel struct {
|
type briefPageModel struct {
|
||||||
Number int
|
Number int
|
||||||
Prev int
|
Prev int
|
||||||
Next int
|
Next int
|
||||||
Pages []briefRenderPage
|
Heading letterHeadModel
|
||||||
|
Pages []briefRenderPage
|
||||||
}
|
}
|
||||||
|
|
||||||
type briefRenderPage struct {
|
type briefRenderPage struct {
|
||||||
@@ -57,7 +58,9 @@ func (s *Server) Brief(c *echo.Context) error {
|
|||||||
return c.String(http.StatusNotFound, "brief not found")
|
return c.String(http.StatusNotFound, "brief not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meta := s.app.Library().Metas.Item(num)
|
||||||
model := renderBrief(*letter)
|
model := renderBrief(*letter)
|
||||||
|
model.Heading = buildLetterHead(s.app.Library(), meta)
|
||||||
model.Prev, model.Next = briefNeighbors(s.app, num)
|
model.Prev, model.Next = briefNeighbors(s.app, num)
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
if err := s.tmpl.ExecuteTemplate(&out, "brief", model); err != nil {
|
if err := s.tmpl.ExecuteTemplate(&out, "brief", model); err != nil {
|
||||||
|
|||||||
@@ -19,5 +19,7 @@ func MapStatic(e *echo.Echo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MapEndpoints(e *echo.Echo, s *Server) {
|
func MapEndpoints(e *echo.Echo, s *Server) {
|
||||||
|
e.GET("/", s.Home)
|
||||||
|
e.GET("/briefe", s.Home)
|
||||||
e.GET("/brief/:number", s.Brief)
|
e.GET("/brief/:number", s.Brief)
|
||||||
}
|
}
|
||||||
|
|||||||
114
server/letterhead.go
Normal file
114
server/letterhead.go
Normal file
@@ -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]
|
||||||
|
}
|
||||||
|
}
|
||||||
107
server/letters.go
Normal file
107
server/letters.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
27
templates/components/letterhead.gohtml
Normal file
27
templates/components/letterhead.gohtml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{{ define "letterhead" }}
|
||||||
|
<div class="letterhead">
|
||||||
|
<div class="letterhead-meta">
|
||||||
|
{{ if .DateText }}
|
||||||
|
<div class="letterhead-date">{{ .DateText }}</div>
|
||||||
|
{{ end }}
|
||||||
|
<div class="letterhead-badges">
|
||||||
|
{{ if .IsDraft }}
|
||||||
|
<span class="letter-badge" title="Entwurf"><i class="ri-draft-line"></i></span>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .HasOriginal }}
|
||||||
|
<span class="letter-badge" title="Der Brieftext wurde anhand des Originals kritisch geprüft."><i class="ri-file-list-2-line"></i></span>
|
||||||
|
{{ else }}
|
||||||
|
<span class="letter-badge" title="Der Brieftext wurde sekundär überliefert."><i class="ri-file-copy-2-line"></i></span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ range .Pairs }}
|
||||||
|
<div class="letterhead-row">
|
||||||
|
<div class="letterhead-side">{{ .Sent }}</div>
|
||||||
|
<div class="letterhead-arrow"><i class="ri-arrow-right-long-line"></i></div>
|
||||||
|
<div class="letterhead-side">{{ .Received }}</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
9
templates/components/letterlist.gohtml
Normal file
9
templates/components/letterlist.gohtml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{{ define "letterlist" }}
|
||||||
|
<div class="letter-list">
|
||||||
|
{{ range . }}
|
||||||
|
<a class="letter-list-item" href="/brief/{{ .Number }}">
|
||||||
|
{{ template "letterhead" . }}
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
<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">
|
||||||
<title>{{ block "title" . }}Default{{ end }}</title>
|
<title>{{ block "title" . }}Default{{ end }}</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.css">
|
||||||
<link rel="stylesheet" href="/public/style.css">
|
<link rel="stylesheet" href="/public/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -4,23 +4,27 @@
|
|||||||
<head>
|
<head>
|
||||||
<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">
|
||||||
<title>Brief {{ .Number }}</title>
|
<title>LKB - {{ .Number }}</title>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.css">
|
||||||
<link rel="stylesheet" href="/public/style.css">
|
<link rel="stylesheet" href="/public/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main class="brief-page">
|
<main class="brief-page">
|
||||||
<header class="brief-head">
|
<header class="brief-head brief-head-letter">
|
||||||
<h1>Brief {{ .Number }}</h1>
|
<div class="brief-head-left">
|
||||||
|
{{ template "letterhead" .Heading }}
|
||||||
|
</div>
|
||||||
<nav class="brief-nav">
|
<nav class="brief-nav">
|
||||||
{{ if gt .Prev 0 }}
|
{{ if gt .Prev 0 }}
|
||||||
<a class="brief-nav-btn" href="/brief/{{ .Prev }}">← Brief {{ .Prev }}</a>
|
<a class="brief-nav-btn" href="/brief/{{ .Prev }}"><i class="ri-arrow-left-long-line"></i></a>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<span class="brief-nav-btn is-disabled">← Kein früherer</span>
|
<span class="brief-nav-btn is-disabled"><i class="ri-arrow-left-long-line"></i></span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
<a class="brief-nav-btn brief-nav-mid" href="/">LKB</a>
|
||||||
{{ if gt .Next 0 }}
|
{{ if gt .Next 0 }}
|
||||||
<a class="brief-nav-btn" href="/brief/{{ .Next }}">Brief {{ .Next }} →</a>
|
<a class="brief-nav-btn" href="/brief/{{ .Next }}"><i class="ri-arrow-right-long-line"></i></a>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<span class="brief-nav-btn is-disabled">Kein späterer →</span>
|
<span class="brief-nav-btn is-disabled"><i class="ri-arrow-right-long-line"></i></span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -1,8 +1,31 @@
|
|||||||
{{ define "home" }}{{ template "layout" . }}{{ end }}
|
{{ define "home" }}{{ template "layout" . }}{{ end }}
|
||||||
|
|
||||||
{{ define "title" }}Home{{ end }}
|
{{ define "title" }}Lenz-Briefe{{ end }}
|
||||||
|
|
||||||
{{ define "body" }}
|
{{ define "body" }}
|
||||||
<h1>Home</h1>
|
<main class="letters-home">
|
||||||
<p>{{ .Message }}</p>
|
<section class="letters-intro">
|
||||||
|
<h1>Lenz-Briefe</h1>
|
||||||
|
<p>Digitale Edition der Briefe von Jakob Michael Reinhold Lenz</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<nav class="letters-nav" aria-label="Jahrgruppen">
|
||||||
|
<a class="letters-nav-item {{ if .ShowAll }}is-active{{ end }}" href="/briefe?range=all">Alle</a>
|
||||||
|
{{ range .Ranges }}
|
||||||
|
{{ if .Letters }}
|
||||||
|
<a class="letters-nav-item {{ if eq $.SelectedRange .Label }}is-active{{ end }}" href="/briefe?range={{ .Label }}">{{ .Label }}</a>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="letters-ranges">
|
||||||
|
{{ range .ActiveRanges }}
|
||||||
|
<div class="letters-range">
|
||||||
|
<h2>{{ .Label }}</h2>
|
||||||
|
<div class="letters-count">({{ len .Letters }} {{ if eq (len .Letters) 1 }}Brief{{ else }}Briefe{{ end }})</div>
|
||||||
|
{{ template "letterlist" .Letters }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
BIN
templates/public/fonts/LinBiolinum_RB_G.ttf
Normal file
BIN
templates/public/fonts/LinBiolinum_RB_G.ttf
Normal file
Binary file not shown.
BIN
templates/public/fonts/LinBiolinum_RI_G.ttf
Normal file
BIN
templates/public/fonts/LinBiolinum_RI_G.ttf
Normal file
Binary file not shown.
BIN
templates/public/fonts/LinBiolinum_R_G.ttf
Normal file
BIN
templates/public/fonts/LinBiolinum_R_G.ttf
Normal file
Binary file not shown.
BIN
templates/public/fonts/LinLibertine_RBI_G.ttf
Normal file
BIN
templates/public/fonts/LinLibertine_RBI_G.ttf
Normal file
Binary file not shown.
BIN
templates/public/fonts/LinLibertine_RB_G.ttf
Normal file
BIN
templates/public/fonts/LinLibertine_RB_G.ttf
Normal file
Binary file not shown.
BIN
templates/public/fonts/LinLibertine_RI_G.ttf
Normal file
BIN
templates/public/fonts/LinLibertine_RI_G.ttf
Normal file
Binary file not shown.
BIN
templates/public/fonts/LinLibertine_R_G.ttf
Normal file
BIN
templates/public/fonts/LinLibertine_R_G.ttf
Normal file
Binary file not shown.
BIN
templates/public/fonts/PlayfairDisplay-Bold.ttf
Normal file
BIN
templates/public/fonts/PlayfairDisplay-Bold.ttf
Normal file
Binary file not shown.
BIN
templates/public/fonts/PlayfairDisplay-BoldItalic.ttf
Normal file
BIN
templates/public/fonts/PlayfairDisplay-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
templates/public/fonts/PlayfairDisplay-Italic.ttf
Normal file
BIN
templates/public/fonts/PlayfairDisplay-Italic.ttf
Normal file
Binary file not shown.
BIN
templates/public/fonts/PlayfairDisplay-Regular.ttf
Normal file
BIN
templates/public/fonts/PlayfairDisplay-Regular.ttf
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,18 +1,100 @@
|
|||||||
@import "tailwindcss";
|
@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 {
|
:root {
|
||||||
--bg: #fff;
|
--bg: #fff;
|
||||||
--ink: #2a2824;
|
--ink: #2a2824;
|
||||||
--muted: #7f7a72;
|
--muted: #7f7a72;
|
||||||
--rule: #d8d4cc;
|
--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 {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-family: Georgia, "Times New Roman", serif;
|
font-family: var(--font-serif);
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@@ -39,6 +121,20 @@ html {
|
|||||||
background: #fff;
|
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 {
|
.brief-head h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -54,11 +150,9 @@ html {
|
|||||||
|
|
||||||
.brief-nav-btn {
|
.brief-nav-btn {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border: 1px solid var(--rule);
|
|
||||||
border-radius: 999px;
|
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-size: 0.86rem;
|
font-size: 0.86rem;
|
||||||
padding: 0.2rem 0.5rem;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brief-nav-btn.is-disabled { opacity: 0.35; pointer-events: none; }
|
.brief-nav-btn.is-disabled { opacity: 0.35; pointer-events: none; }
|
||||||
@@ -141,7 +235,7 @@ html {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
color: #8d867b;
|
color: #8d867b;
|
||||||
font-family: "Times New Roman", Georgia, serif;
|
font-family: var(--font-serif);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,49 +282,150 @@ html {
|
|||||||
.line-indent-11 { text-indent: 6.38rem; }
|
.line-indent-11 { text-indent: 6.38rem; }
|
||||||
.line-indent-12 { text-indent: 6.96rem; }
|
.line-indent-12 { text-indent: 6.96rem; }
|
||||||
|
|
||||||
.line-align-ctx { text-align: right; }
|
.line-align-ctx {
|
||||||
.line-align-ctx .tag-align.align-center { display: inline-block; width: 100%; text-align: center; }
|
display: grid;
|
||||||
.line-align-ctx .tag-align.align-right { display: inline-block; width: 100%; text-align: right; }
|
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;
|
display: inline-block;
|
||||||
min-width: 3.6ch;
|
min-width: 3.6ch;
|
||||||
border-left: 1px dotted #cdbfae;
|
border-left: 1px dotted #cdbfae;
|
||||||
padding-left: 0.34rem;
|
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-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-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-dul { text-decoration: underline; text-decoration-style: double; }
|
||||||
.tag-tul { text-decoration: underline; text-decoration-style: wavy; text-decoration-thickness: 2px; }
|
.tag-tul { text-decoration: underline; text-decoration-style: wavy; text-decoration-thickness: 2px; }
|
||||||
.tag-it { font-style: italic; }
|
.tag-it { font-style: italic; }
|
||||||
.tag-ul { text-decoration: underline; }
|
.tag-ul { text-decoration: underline; }
|
||||||
.tag-note { font-size: 0.9em; font-weight: 700; color: var(--muted); }
|
.tag-note { font-size: 0.9em; font-weight: 700; color: var(--muted); }
|
||||||
.tag-note {
|
.tag-note {
|
||||||
font-family: "Times New Roman", Georgia, serif;
|
font-family: var(--font-serif);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: #8d867b;
|
color: #8d867b;
|
||||||
}
|
}
|
||||||
.tag-ink { color: #2d5e8a; }
|
.tag-ink { color: #1e3a8a; }
|
||||||
.tag-pe { color: #4e667f; }
|
.tag-pe { color: #57534e; }
|
||||||
.tag-tl { background: #f5e4d6; color: #8c5a41; padding: 0 0.12rem; border-radius: 2px; }
|
.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-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 {
|
.tag-insertion {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 0.1rem;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-insertion::before {
|
.tag-insertion::before {
|
||||||
content: "\2038";
|
content: "⌞";
|
||||||
font-size: 0.78em;
|
color: #475569;
|
||||||
vertical-align: super;
|
margin-right: -0.2em;
|
||||||
color: #9a8b76;
|
}
|
||||||
margin-right: 0.06rem;
|
|
||||||
|
.tag-insertion::after {
|
||||||
|
content: "⌟";
|
||||||
|
color: #475569;
|
||||||
|
margin-left: -0.4ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
@@ -247,3 +442,102 @@ html {
|
|||||||
text-align: left;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user