I get told to shut up a lot by my friend. This is the microsite that documents this in detail. shutup.jp
postcards microsite
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fully functional site #1

+147 -60
+21
funcs.go
··· 1 + package main 2 + 3 + import ( 4 + _ "embed" 5 + "html/template" 6 + ) 7 + 8 + func lastPC(pcs []Postcard) Postcard { 9 + return pcs[len(pcs)-1] 10 + } 11 + 12 + func safeHTML(str string) template.HTML { 13 + return template.HTML(str) 14 + } 15 + 16 + //go:embed index.html.tmpl 17 + var indexStr string 18 + var indexTmpl = template.Must(template.New("index").Funcs(template.FuncMap{ 19 + "safeHTML": safeHTML, 20 + "lastPC": lastPC, 21 + }).Parse(indexStr))
+8 -9
index.html.tmpl
··· 11 11 </head> 12 12 <body> 13 13 <section class="explain"> 14 - <hgroup> 15 - <h1>"Shut up JP"</h1> 16 - </hgroup> 17 - 18 - <p>One day, after months of silence, my friend <a href="">Claire</a> sent me a text message with only 3 words in it &ldquo;<strong>Shut up, JP.</strong>&rdquo; Not long afterwards I started receiving this advice by mail as well, from every country she visited and in every language she could convince friendly, and presumably confused, strangers to help her write in.</p> 14 + <p>One day, after months of silence, my friend <a href="https://www.instagram.com/claire.durrant88/">Claire</a> sent me a text message of only 3 words:</p> 15 + <h1>Shut up, JP</h1> 16 + <p>Knowing my <a href="https://www.byjp.me/posts/thoughts-on-postcards/" target="_blank">love of postcards</a>, she started sending me this invaluable advice by mail too – from every country she visited, and in every language the friendly (and presumably deeply confused) locals help her write in.</p> 17 + <p>It's just shy of a decade later and, apparently, I've <em>still</em> not got the message.</p> 18 + <cite><a target="_blank" href="https://www.byjp.me">JP</a></cite> 19 19 </section> 20 20 21 21 {{ range . }} ··· 28 28 </div> 29 29 </label> 30 30 31 - <!--<figcaption style="--map: url(seattle.svg)">--> 32 31 <figcaption> 33 - {{- .Back.Transcription | safeHTML -}} 34 - {{ with .SentOn }}<time datetime="{{ . }}">{{ .Format "2 Jan, 2006" }}</time>{{ end }} 32 + <div class="title">{{ with .Location }}<span>{{ .Name }}</span>{{ end }}{{ with .SentOn }} <time datetime="{{ . }}">{{ .Format "January 2, 2006" }}</time>{{ end }}</div> 33 + <div class="transcription">{{- .Back.Transcription | safeHTML -}}</div> 35 34 </figcaption> 36 35 </figure> 37 36 {{ end }} 38 37 39 - <section class="explain">How fun is this??</section> 38 + <section class="explain">Shut up Claire.</section> 40 39 </body> 41 40 </html>
-10
main.go
··· 1 1 package main 2 2 3 3 import ( 4 - _ "embed" 5 4 "encoding/json" 6 - "html/template" 7 5 "io" 8 6 "os" 9 7 "path/filepath" 10 8 "sort" 11 9 ) 12 - 13 - //go:embed index.html.tmpl 14 - var indexStr string 15 - var indexTmpl = template.Must(template.New("index").Funcs(template.FuncMap{ 16 - "safeHTML": func(str string) template.HTML { 17 - return template.HTML(str) 18 - }, 19 - }).Parse(indexStr)) 20 10 21 11 func check(e error) { 22 12 if e != nil {
+102 -19
static/shutup.css
··· 36 36 padding: 0; 37 37 } 38 38 section, figure { 39 - height: 100vh; 39 + height: calc(100vh - 2em); 40 40 padding: 1em; 41 41 margin: 0; 42 42 display: flex; ··· 46 46 flex-direction: row; 47 47 } 48 48 49 + figcaption { 50 + min-width: 25vw; 51 + position: relative; 52 + text-align: right; 53 + margin: 1em; 54 + position: relative; 55 + height: 50vh; 56 + text-wrap: balance; 57 + } 58 + 59 + figcaption > * { 60 + width: 100%; 61 + transition: opacity 500ms ease-in-out 0ms; 62 + position: absolute; 63 + right: 0; 64 + top: 50%; 65 + transform: translateY(-50%); 66 + } 67 + 68 + figcaption .title { 69 + transition-delay: 500ms; 70 + font-size: 1.5em; 71 + font-weight: bold; 72 + } 73 + 74 + .title time { 75 + font-style: italic; 76 + font-size: 0.65em; 77 + font-weight: normal; 78 + } 79 + 80 + .title * { 81 + display: block; 82 + margin-bottom: 0.5em; 83 + } 84 + 85 + figcaption .transcription { 86 + opacity: 0; 87 + white-space: pre; 88 + text-wrap: balance; 89 + font-size: 1.2em; 90 + } 91 + 49 92 @media (orientation: portrait) { 50 93 figure { 51 94 flex-direction: column; ··· 53 96 54 97 figcaption { 55 98 min-height: 25vh; 99 + text-align: center; 100 + width: 50vw; 101 + margin: 2em 0; 56 102 } 57 - } 58 103 59 - figcaption { 60 - min-width: 25vw; 61 - position: relative; 62 - white-space: pre; 104 + figcaption > * { 105 + top: 0; 106 + transform: none; 107 + } 63 108 } 64 109 65 110 section.explain { 66 111 display: grid; 67 112 place-content: center; 68 - flex-direction: column; 69 113 padding-inline: calc(max((100vw - 500px)/2, 2em)); 70 114 } 71 115 72 116 p { 73 117 text-align:justify; 74 118 } 119 + cite { 120 + text-align: right; 121 + } 122 + cite::before { 123 + content: '–'; 124 + margin-right: 0.25em; 125 + } 75 126 76 127 .postcard { 77 - height: 90%; 78 - max-width: 90%; 128 + max-width: 90vw; 129 + max-height: 75vh; 79 130 aspect-ratio:1/1; 80 131 --flip: 0deg; 132 + margin-inline: auto; 81 133 } 82 134 83 135 label { ··· 94 146 --flip: 180deg; 95 147 } 96 148 97 - .postcard::before,.postcard::after { 98 - transition: transform 1s ease-in-out; 149 + input:checked ~ figcaption .transcription { 150 + opacity: 1; 151 + transition-delay: 500ms; 99 152 } 100 153 101 - svg polygon { 102 - fill:#eee; 103 - stroke:black; 104 - stroke-width:2; 105 - stroke-linecap:round; 106 - stroke-linejoin:round; 154 + input:checked ~ figcaption .title { 155 + opacity: 0; 156 + transition-delay: 0s; 107 157 } 108 - svg * { 109 - transform-box: fill-box; 158 + 159 + .postcard::before,.postcard::after { 160 + transition: transform 1s ease-in-out; 110 161 } 162 + 163 + [lang]::after { margin-left: 0.5em } 164 + [lang="ar-MA"]::after { content: '🇲🇦' } 165 + [lang="cy-GB"]::after { content: '🏴󠁧󠁢󠁷󠁬󠁳󠁿' } 166 + [lang="de-DE"]::after { content: '🇩🇪' } 167 + [lang="el-GR"]::after { content: '🇬🇷' } 168 + [lang="en-CA"]::after { content: '🇨🇦' } 169 + [lang="en-GB"]::after { content: '🇬🇧' } 170 + [lang="en-SG"]::after { content: '🇸🇬/🇬🇧' } 171 + [lang="en-US"]::after { content: '🇺🇸' } 172 + [lang="es-ES"]::after { content: '🇪🇸' } 173 + [lang="es-GT"]::after { content: '🇬🇹' } 174 + [lang="fr-FR"]::after { content: '🇫🇷' } 175 + [lang="fr-IM"]::after { content: '🇮🇲/🇫🇷' } 176 + [lang="gd-GB"]::after { content: '🏴󠁧󠁢󠁳󠁣󠁴󠁿' } 177 + [lang="gv-IM"]::after { content: '🇮🇲' } 178 + [lang="hr-HR"]::after { content: '🇭🇷' } 179 + [lang="id-MY"]::after { content: '🇲🇾' } 180 + [lang="it-IT"]::after { content: '🇮🇹' } 181 + [lang="ka-GE"]::after { content: '🇬🇪' } 182 + [lang="km-KH"]::after { content: '🇰🇭' } 183 + [lang="ko-KR"]::after { content: '🇰🇷' } 184 + [lang="mi-NZ"]::after { content: '🇳🇿' } 185 + [lang="ms-SG"]::after { content: '🇸🇬/🇲🇾' } 186 + [lang="pt-PT"]::after { content: '🇵🇹' } 187 + [lang="se-SE"]::after { content: '🇸🇪' } 188 + [lang="ta-LK"]::after { content: '🇱🇰' } 189 + [lang="ta-SG"]::after { content: '🇸🇬/🇮🇳' } 190 + [lang="th-TH"]::after { content: '🇹🇭' } 191 + [lang="vi-VN"]::after { content: '🇻🇳' } 192 + [lang="zh-CN"]::after { content: '🇨🇳' } 193 + [lang="zh-SG"]::after { content: '🇸🇬/🇨🇳' }
+16 -22
types.go
··· 1 1 package main 2 2 3 3 import ( 4 - "fmt" 5 4 "math/big" 6 5 "time" 7 6 ) ··· 41 40 FlipRightHand Flip = "right-hand" 42 41 ) 43 42 44 - type Date string 45 - 46 - func (d Date) Valid() bool { 47 - _, _, _, err := d.Components() 48 - return err == nil 49 - } 50 - 51 - func (d Date) Components() (year int, month int, day int, err error) { 52 - _, err = fmt.Sscanf(string(d), "%d-%d-%d", &year, &month, &day) 53 - return year, month, day, err 43 + type Date struct { 44 + time.Time 54 45 } 55 46 56 - func (d Date) Time() (time.Time, error) { 57 - year, month, day, err := d.Components() 58 - if err != nil { 59 - return time.Time{}, err 47 + func (d *Date) UnmarshalJSON(b []byte) (err error) { 48 + str := string(b) 49 + if str == "null" { 50 + d.Time = time.Time{} 51 + return nil 60 52 } 61 - return time.Date(year, time.Month(month), day, 23, 0, 0, 0, time.UTC), nil 53 + 54 + // Specify your custom layout here 55 + d.Time, err = time.Parse(`"2006-01-02"`, str) 56 + return err 62 57 } 63 58 64 - func (d Date) Format(layout string) string { 65 - t, err := d.Time() 66 - if err != nil { 67 - return "" 59 + func (d Date) MarshalJSON() ([]byte, error) { 60 + if d.Time.IsZero() { 61 + return []byte("null"), nil 68 62 } 69 - return t.Format(layout) 63 + return []byte(d.Time.Format(`"2006-01-02"`)), nil 70 64 } 71 65 72 66 type Size struct { ··· 81 75 // Implement the sort.Interface - Len, Less, and Swap methods 82 76 func (a BySentOn) Len() int { return len(a) } 83 77 func (a BySentOn) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 84 - func (a BySentOn) Less(i, j int) bool { return a[i].SentOn < a[j].SentOn } 78 + func (a BySentOn) Less(i, j int) bool { return a[i].SentOn.Unix() > a[j].SentOn.Unix() }