Commit 7f130593fc972ef3178827c582d63c2fa8451fe7
Merge branch 'new-zine-form' into 'master'
new zine form added See merge request zachmandeville/dat-zine-library!2zach mandeville committed on 6/25/2018, 2:12:17 PM
Parent: 66d7bd513270b804eb7c6a61a46eb1103afd519a
Parent: bec5f66e733bc5d6fc51b8bbfaf642b0064ccdae
Files changed
building/aesthetic/stylesheets/main.css | ||
---|---|---|
@@ -1,21 +1,27 @@ | ||
1 | 1 … | body{ |
2 | - font-family: 'mononoki'; | |
2 … | + font-family: var(--primary-text); | |
3 | 3 … | background: var(--background-color); |
4 | 4 … | color: var(--text); |
5 | 5 … | padding: 0; |
6 | 6 … | margin: 0; |
7 | 7 … | box-sizing: border-box; |
8 | 8 … | } |
9 | 9 … | |
10 … | + | |
10 | 11 … | #zine-shelves{ |
11 | 12 … | display: grid; |
12 | 13 … | width: 80%; |
13 | 14 … | margin: auto; |
14 | 15 … | grid-template-columns: 1fr 1fr; |
15 | 16 … | grid-gap: 2em; |
16 | 17 … | } |
17 | 18 … | |
19 … | +#welcome-title{ | |
20 … | + font-family: arial; | |
21 … | + font-weight: 100; | |
22 … | + letter-spacing: 0.15em; | |
23 … | +} | |
18 | 24 … | ul{ |
19 | 25 … | padding: none; |
20 | 26 … | margin: none; |
21 | 27 … | } |
@@ -39,9 +45,9 @@ | ||
39 | 45 … | align-items: flex-start; |
40 | 46 … | |
41 | 47 … | } |
42 | 48 … | |
43 | -#zine-shelves img{ | |
49 … | +.zine-card img{ | |
44 | 50 … | width: 90%; |
45 | 51 … | margin: auto; |
46 | 52 … | height: auto; |
47 | 53 … | } |
@@ -55,30 +61,112 @@ | ||
55 | 61 … | color: var(--title-text); |
56 | 62 … | text-align: center; |
57 | 63 … | } |
58 | 64 … | |
65 … | +#add-button{ | |
66 … | + position: fixed; | |
67 … | + top: 0; | |
68 … | + right: 0; | |
69 … | + background-color: var(--add-button-bg); | |
70 … | + color: var(--new-zine-form-text); | |
71 … | + border-radius: 0px; | |
72 … | + font-size: 1.3em; | |
73 … | +} | |
59 | 74 … | |
75 … | +#add-button:hover{ | |
76 … | + background-color: var(--add-button-hover); | |
77 … | + color: indigo; | |
78 … | +} | |
79 … | + | |
80 … | +#add-button:focus{ | |
81 … | + outline-width: 0px; | |
82 … | +} | |
83 … | + | |
84 … | + | |
85 … | + | |
60 | 86 … | #new-zine-form{ |
61 | 87 … | position: fixed; |
62 | 88 … | z-index: 1; |
63 | - left: -60%; | |
64 | - background: peachpuff; | |
89 … | + right: -60%; | |
90 … | + background: var(--new-zine-form-bg); | |
65 | 91 … | color: indigo; |
66 | - height: 200px; | |
67 | - width: 50%; | |
92 … | + width: 40%; | |
68 | 93 … | display: flex; |
69 | 94 … | flex-direction: column; |
70 | - justify-content: center; | |
95 … | + justify-content: space-between; | |
71 | 96 … | align-items: left; |
72 | - padding: 1em; | |
97 … | + border: 1px outset gray; | |
98 … | + box-shadow: -2px 4px 20px 6px var(--new-zine-form-shadow); | |
73 | 99 … | } |
74 | 100 … | |
101 … | +#new-zine-form h1{ | |
102 … | + margin-top: 0; | |
103 … | + font-family: arial; | |
104 … | + padding: 0.3em; | |
105 … | +} | |
106 … | + | |
107 … | +#new-zine-form form{ | |
108 … | + height: 100%; | |
109 … | + width: 100%; | |
110 … | + display: flex; | |
111 … | + flex-direction: column; | |
112 … | + justify-content: space-between; | |
113 … | + font-size: 24px; | |
114 … | + | |
115 … | +} | |
116 … | + | |
117 … | +#new-zine-form label{ | |
118 … | + padding-left: 1em; | |
119 … | + margin-bottom: 1em; | |
120 … | +} | |
121 … | + | |
122 … | +#new-zine-form input{ | |
123 … | + width: 75%; | |
124 … | + padding-left: 0.5em; | |
125 … | + border: none; | |
126 … | + background-color: peachpuff; | |
127 … | + font-size: 1em; | |
128 … | + font-family: 'mononoki'; | |
129 … | +} | |
130 … | + | |
131 … | +#new-zine-form input:focus { | |
132 … | + outline-width: 0px; | |
133 … | + border-bottom: 1px solid indigo; | |
134 … | +} | |
135 … | + | |
136 … | +#new-zine-form input[type=submit]{ | |
137 … | + width: 100%; | |
138 … | + cursor: pointer; | |
139 … | + padding: 0.25em; | |
140 … | + color: peachpuff; | |
141 … | + margin-bottom: 0; | |
142 … | + background-color: white; | |
143 … | + color: indigo; | |
144 … | + bottom: 0; | |
145 … | +} | |
146 … | + | |
147 … | +#new-zine-form input[type=submit]:hover{ | |
148 … | + background-color: lavender; | |
149 … | +} | |
150 … | + | |
151 … | +#new-zine-notes{ | |
152 … | + height: 2em; | |
153 … | + background-color: peachpuff; | |
154 … | + margin: -0.75em 1em 1em 1em; | |
155 … | + border: none; | |
156 … | + font-size: 1em; | |
157 … | +} | |
158 … | + | |
159 … | +#new-zine-notes:focus{ | |
160 … | + outline-width: 0px; | |
161 … | +} | |
162 … | + | |
75 | 163 … | .revealed { |
76 | - left: 0 !important; | |
164 … | + right: 0 !important; | |
77 | 165 … | } |
78 | 166 … | |
79 | 167 … | .transition { |
80 | - transition: left 0.3s ease-in; | |
168 … | + transition: right 0.3s ease-in; | |
81 | 169 … | } |
82 | 170 … | |
83 | 171 … | .details{ |
84 | 172 … | background: var(--details-bg); |
@@ -99,49 +187,4 @@ | ||
99 | 187 … | .details p{ |
100 | 188 … | padding-left: 1em; |
101 | 189 … | padding-right: 1em; |
102 | 190 … | } |
103 | - | |
104 | - | |
105 | -/* Fonts! | |
106 | ---------*/ | |
107 | - | |
108 | - @font-face { | |
109 | - font-family: 'mononoki'; | |
110 | - src: url('../fonts/mononoki/mononoki-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ | |
111 | - url('../fonts/mononoki/mononoki-Regular.woff2') format('woff2'), /* Super Modern Browsers */ | |
112 | - url('../fonts/mononoki/mononoki-Regular.woff') format('woff'), /* Pretty Modern Browsers */ | |
113 | - url('../fonts/mononoki/mononoki-Regular.ttf') format('truetype'); /* Safari, Android, iOS */ | |
114 | - font-weight: normal; | |
115 | - font-style: normal; | |
116 | - } | |
117 | - | |
118 | - @font-face { | |
119 | - font-family: 'mononoki'; | |
120 | - src: url('../fonts/mononoki/mononoki-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ | |
121 | - url('../fonts/mononoki/mononoki-Bold.woff2') format('woff2'), /* Super Modern Browsers */ | |
122 | - url('../fonts/mononoki/mononoki-Bold.woff') format('woff'), /* Pretty Modern Browsers */ | |
123 | - url('../fonts/mononoki/mononoki-Bold.ttf') format('truetype'); /* Safari, Android, iOS */ | |
124 | - font-weight: bold; | |
125 | - font-style: normal; | |
126 | - } | |
127 | - | |
128 | - @font-face { | |
129 | - font-family: 'mononoki'; | |
130 | - src: url('../fonts/mononoki/mononoki-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ | |
131 | - url('../fonts/mononoki/mononoki-Italic.woff2') format('woff2'), /* Super Modern Browsers */ | |
132 | - url('../fonts/mononoki/mononoki-Italic.woff') format('woff'), /* Pretty Modern Browsers */ | |
133 | - url('../fonts/mononoki/mononoki-Italic.ttf') format('truetype'); /* Safari, Android, iOS */ | |
134 | - font-weight: normal; | |
135 | - font-style: italic; | |
136 | - | |
137 | - } | |
138 | - | |
139 | - @font-face { | |
140 | - font-family: 'mononoki'; | |
141 | - src: url('../fonts/mononoki/mononoki-BoldItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ | |
142 | - url('../fonts/mononoki/mononoki-BoldItalic.woff2') format('woff2'), /* Super Modern Browsers */ | |
143 | - url('../fonts/mononoki/mononoki-BoldItalic.woff') format('woff'), /* Pretty Modern Browsers */ | |
144 | - url('../fonts/mononoki/mononoki-BoldItalic.ttf') format('truetype'); /* Safari, Android, iOS */ | |
145 | - font-weight: bold; | |
146 | - font-style: italic; | |
147 | - } |
building/aesthetic/stylesheets/colors.css | ||
---|---|---|
@@ -1,7 +1,0 @@ | ||
1 | -:root{ | |
2 | - --background-color: lightcyan; | |
3 | - --text: midnightblue; | |
4 | - --title-bg: darkslateblue; | |
5 | - --title-text: khaki; | |
6 | - --details-bg: khaki; | |
7 | -} |
building/aesthetic/stylesheets/colors-and-fonts.css | ||
---|---|---|
@@ -1,0 +1,103 @@ | ||
1 … | +/* Colors | |
2 … | +-------*/ | |
3 … | +:root{ | |
4 … | + --background-color: lightcyan; | |
5 … | + --text: midnightblue; | |
6 … | + | |
7 … | + --title-bg: darkslateblue; | |
8 … | + --title-text: khaki; | |
9 … | + --details-bg: khaki; /* bg means 'background' now */ | |
10 … | + | |
11 … | + --add-button-bg: peachpuff; | |
12 … | + --add-button-text: indigo; | |
13 … | + --add-button-hover: lavender; | |
14 … | + | |
15 … | + --new-zine-form-bg: peachpuff; | |
16 … | + --new-zine-form-text: indigo; | |
17 … | + --new-zine-form-border: gray; | |
18 … | + --new-zine-form-shadow: rgba(93, 87, 87, 0.53); | |
19 … | + --new-zine-form-submit-bg: white; | |
20 … | + --new-zine-form-submit-bg-hover: lavender; | |
21 … | + --new-zine-form-submit-text: indigo; | |
22 … | +} | |
23 … | + | |
24 … | +/* Fonts | |
25 … | +------*/ | |
26 … | + | |
27 … | +:root{ | |
28 … | + --primary-text: mononoki; | |
29 … | + --accent-text: arial; /* being used for titles mostly */ | |
30 … | +} | |
31 … | + | |
32 … | +/* Other Fonts Available:! | |
33 … | +-------------------- | |
34 … | + | |
35 … | +These are Web Safe Fonts that seemed to work on the browser: | |
36 … | + | |
37 … | + Serif: | |
38 … | + Times New Roman | |
39 … | + Georgia | |
40 … | + cochin | |
41 … | + | |
42 … | + Sans Serif: | |
43 … | + arial | |
44 … | + arial black | |
45 … | + comic sans ms | |
46 … | + impact | |
47 … | + trebuchet ms | |
48 … | + | |
49 … | + Monospace: | |
50 … | + courier new | |
51 … | + monospace | |
52 … | + | |
53 … | + Custom (added by me, Zach!): | |
54 … | + mononoki (my fave) | |
55 … | + | |
56 … | +----------------------- | |
57 … | +*/ | |
58 … | + | |
59 … | +/* This is Code added to get custom fonts working. | |
60 … | + Add yr own here if you wanna use a custom font | |
61 … | +--------------------------------------------------*/ | |
62 … | + | |
63 … | + | |
64 … | +@font-face { | |
65 … | + font-family: 'mononoki'; | |
66 … | + src: url('../fonts/mononoki/mononoki-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ | |
67 … | + url('../fonts/mononoki/mononoki-Regular.woff2') format('woff2'), /* Super Modern Browsers */ | |
68 … | + url('../fonts/mononoki/mononoki-Regular.woff') format('woff'), /* Pretty Modern Browsers */ | |
69 … | + url('../fonts/mononoki/mononoki-Regular.ttf') format('truetype'); /* Safari, Android, iOS */ | |
70 … | + font-weight: normal; | |
71 … | + font-style: normal; | |
72 … | +} | |
73 … | + | |
74 … | +@font-face { | |
75 … | + font-family: 'mononoki'; | |
76 … | + src: url('../fonts/mononoki/mononoki-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ | |
77 … | + url('../fonts/mononoki/mononoki-Bold.woff2') format('woff2'), /* Super Modern Browsers */ | |
78 … | + url('../fonts/mononoki/mononoki-Bold.woff') format('woff'), /* Pretty Modern Browsers */ | |
79 … | + url('../fonts/mononoki/mononoki-Bold.ttf') format('truetype'); /* Safari, Android, iOS */ | |
80 … | + font-weight: bold; | |
81 … | + font-style: normal; | |
82 … | +} | |
83 … | + | |
84 … | +@font-face { | |
85 … | + font-family: 'mononoki'; | |
86 … | + src: url('../fonts/mononoki/mononoki-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ | |
87 … | + url('../fonts/mononoki/mononoki-Italic.woff2') format('woff2'), /* Super Modern Browsers */ | |
88 … | + url('../fonts/mononoki/mononoki-Italic.woff') format('woff'), /* Pretty Modern Browsers */ | |
89 … | + url('../fonts/mononoki/mononoki-Italic.ttf') format('truetype'); /* Safari, Android, iOS */ | |
90 … | + font-weight: normal; | |
91 … | + font-style: italic; | |
92 … | + | |
93 … | +} | |
94 … | + | |
95 … | +@font-face { | |
96 … | + font-family: 'mononoki'; | |
97 … | + src: url('../fonts/mononoki/mononoki-BoldItalic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ | |
98 … | + url('../fonts/mononoki/mononoki-BoldItalic.woff2') format('woff2'), /* Super Modern Browsers */ | |
99 … | + url('../fonts/mononoki/mononoki-BoldItalic.woff') format('woff'), /* Pretty Modern Browsers */ | |
100 … | + url('../fonts/mononoki/mononoki-BoldItalic.ttf') format('truetype'); /* Safari, Android, iOS */ | |
101 … | + font-weight: bold; | |
102 … | + font-style: italic; | |
103 … | +} |
building/components/library.js | ||
---|---|---|
@@ -6,10 +6,10 @@ | ||
6 | 6 … | |
7 | 7 … | function view (state, emit) { |
8 | 8 … | return html` |
9 | 9 … | <body> |
10 | - <h1>Welcome to your Zine Library, Good Friend!</h1> | |
11 | - ${newZineForm()} | |
10 … | + <h1 id='welcome-title'>Welcome to your Zine Library, Good Friend!</h1> | |
11 … | + ${newZineForm(emit)} | |
12 | 12 … | ${shelving(state)} |
13 | 13 … | </body> |
14 | 14 … | ` |
15 | 15 … | // newZineForm is a button for adding new zines |
building/components/newZineForm.js | ||
---|---|---|
@@ -1,19 +1,48 @@ | ||
1 | 1 … | var html = require('nanohtml') |
2 … | +var choo = require('choo') | |
2 | 3 … | |
3 | -function view (state, emit) { | |
4 … | +module.exports = view | |
5 … | + | |
6 … | +function view (emit) { | |
7 … | +//I would like this to be form validated, but cannot figure out how to | |
8 … | +// Do so, the regEx pattern matching doesn't seem to work... | |
4 | 9 … | return html` |
5 | 10 … | <section id='add-zines'> |
6 | - <button onclick=${revealForm}>Add Zine</button> | |
7 | - <form id='new-zine-form' class='transition'> | |
8 | - <label for='title'>Title:<input type='text' name='title' placeholder='name of the zine' /></label> | |
9 | - <label for='url'>Dat URL:<input type='text' name='url' placeholder='the dat url' /></label> | |
11 … | + <button id='add-button' onclick=${toggleForm}>Add Zine</button> | |
12 … | + <div id='new-zine-form' class='transition'> | |
13 … | + <h1>Add A Zine</h1> | |
14 … | + <form onsubmit=${submitZine}> | |
15 … | + <label for='title'>Title: | |
16 … | + <input id='title' name='title' | |
17 … | + placeholder='name of the zine' /> | |
18 … | + </label> | |
19 … | + <label for='url'>Dat URL: | |
20 … | + <input id='url' name='url' | |
21 … | + type='text' | |
22 … | + required | |
23 … | + placeholder='the dat url' /> | |
24 … | + </label> | |
25 … | + <label for='notes'>Notes:</label> <textarea id='new-zine-notes' name='notes' placeholder='how you found it, maybe yr feelings?'/></textarea> | |
10 | 26 … | <input type='submit' /> |
11 | 27 … | </form> |
28 … | + </div> | |
12 | 29 … | </section> |
13 | 30 … | ` |
31 … | + function submitZine (e) { | |
32 … | + e.preventDefault() | |
33 … | + var zine = e.currentTarget | |
34 … | + emit('zine submitted', zine) | |
35 … | + } | |
14 | 36 … | } |
15 | 37 … | |
16 | -function revealForm () { | |
17 | - document.getElementById('new-zine-form').classList.toggle('revealed') | |
38 … | +function toggleForm () { | |
39 … | + var addButton = document.getElementById('add-button') | |
40 … | + var form = document.getElementById('new-zine-form') | |
41 … | + | |
42 … | + form.classList.toggle('revealed') | |
43 … | + if (addButton.textContent === 'close') { | |
44 … | + addButton.textContent = 'Add Zine' | |
45 … | + } else { | |
46 … | + addButton.textContent = 'close' | |
47 … | + } | |
18 | 48 … | } |
19 | -module.exports = view |
building/components/shelving.js | ||
---|---|---|
@@ -22,9 +22,9 @@ | ||
22 | 22 … | <h1>${zine.title}</h1> |
23 | 23 … | </div> |
24 | 24 … | <div class='details'> |
25 | 25 … | <img src=${imagePath}/> |
26 | - <p>${zine.adoration}</p> | |
26 … | + <p>${zine.notes}</p> | |
27 | 27 … | ${renderInfo(zine, state)} |
28 | 28 … | </div> |
29 | 29 … | </div> |
30 | 30 … | </a> |
index.html | ||
---|---|---|
@@ -1,9 +1,9 @@ | ||
1 | 1 … | |
2 | 2 … | <head> |
3 | 3 … | <title>Dat Zine Library</title> |
4 | 4 … | <link rel='stylesheet' type='text/css' href='building/aesthetic/stylesheets/main.css'/> |
5 | - <link rel='stylesheet' type='text/css' href='building/aesthetic/stylesheets/colors.css'/> | |
5 … | + <link rel='stylesheet' type='text/css' href='building/aesthetic/stylesheets/colors-and-fonts.css'/> | |
6 | 6 … | </head> |
7 | 7 … | <body> |
8 | 8 … | <script src='bundle.js' type='text/javascript'></script> |
9 | 9 … | </body> |
index.js | ||
---|---|---|
@@ -3,7 +3,9 @@ | ||
3 | 3 … | const library = require('./building/components/library') |
4 | 4 … | |
5 | 5 … | const app = choo() |
6 | 6 … | |
7 … | +//Jobs to be done by the javascript volunteers | |
7 | 8 … | app.use(require('./volunteers/jobs')) |
9 … | +app.use(require('./volunteers/addingZines')) | |
8 | 10 … | app.mount('body') |
9 | 11 … | app.route('/', library) |
volunteers/jobs.js | ||
---|---|---|
@@ -1,26 +1,36 @@ | ||
1 | 1 … | const smarkt = require('smarkt') |
2 | 2 … | const _ = require('lodash') |
3 … | +const html = require('nanohtml') | |
3 | 4 … | |
5 … | + | |
4 | 6 … | var archive = new DatArchive(window.location) |
5 | 7 … | |
6 | 8 … | function store (state, emitter) { |
7 | 9 … | state.library = [] |
8 | 10 … | state.info = {} |
9 | 11 … | state.covers = {} |
12 … | + | |
10 | 13 … | emitter.on('DOMContentLoaded', function () { |
11 | - archive.readdir('zines').then(zines => mapToState(zines)) | |
14 … | + archive.readdir('zines') | |
15 … | + .then(zines => mapToState(zines)) | |
12 | 16 … | }) |
13 | 17 … | |
18 … | + emitter.on('update library', function (zine) { | |
19 … | + addToLibrary(zine) | |
20 … | + addZineInfo(zine.url) | |
21 … | + addZineCover(zine.url) | |
22 … | + }) | |
23 … | + | |
14 | 24 … | function mapToState (zines) { |
15 | 25 … | zines.map(zine => { |
16 | 26 … | archive.readFile(`zines/${zine}`) |
17 | 27 … | .then(rawText => { |
18 | - var textJson = smarkt.parse(rawText) | |
19 | - addToLibrary(textJson) | |
20 | - addZineInfo(textJson.url) | |
21 | - addZineCover(textJson.url) | |
22 | - }) | |
28 … | + var textJson = smarkt.parse(rawText) | |
29 … | + addToLibrary(textJson) | |
30 … | + addZineInfo(textJson.url) | |
31 … | + addZineCover(textJson.url) | |
32 … | + }) | |
23 | 33 … | }) |
24 | 34 … | } |
25 | 35 … | |
26 | 36 … | function addToLibrary (text) { |
@@ -52,9 +62,8 @@ | ||
52 | 62 … | |
53 | 63 … | function mapCoverToState (files, datUrl) { |
54 | 64 … | var cover = files.find(file => sansExtension(file) === 'cover') |
55 | 65 … | state.covers[datUrl] = `${datUrl}/distro/${cover}` |
56 | - console.log(state.covers) | |
57 | 66 … | emitter.emit('pushState') |
58 | 67 … | } |
59 | 68 … | } |
60 | 69 … |
volunteers/addingZines.js | ||
---|---|---|
@@ -1,0 +1,31 @@ | ||
1 … | +const smarkt = require('smarkt') | |
2 … | +const _ = require('lodash') | |
3 … | + | |
4 … | +var archive = new DatArchive(window.location) | |
5 … | + | |
6 … | +function store (state, emitter) { | |
7 … | + emitter.on('zine submitted', function (zineSubmission) { | |
8 … | + var details = getDetails(zineSubmission) | |
9 … | + var zineJson = JSON.stringify(details, null, 2) | |
10 … | + writeZineTxt(zineJson, emitter) | |
11 … | + }) | |
12 … | +} | |
13 … | + | |
14 … | +function getDetails (form) { | |
15 … | + var formData = new FormData(form) | |
16 … | + var data = {} | |
17 … | + for (var pair of formData.entries()) { | |
18 … | + data[pair[0]] = pair[1] | |
19 … | + } | |
20 … | + return data | |
21 … | +} | |
22 … | + | |
23 … | +function writeZineTxt (zine, emitter) { | |
24 … | + zine = JSON.parse(zine) | |
25 … | + var fileName = zine.title.replace(/ /g, '-') + '.txt' | |
26 … | + var pleasantText = smarkt.stringify(zine) | |
27 … | + archive.writeFile(`zines/${fileName}`, pleasantText) | |
28 … | + .then(() => emitter.emit('update library', zine)) | |
29 … | +} | |
30 … | + | |
31 … | +module.exports = store |
zines/a-future-vision.txt | ||
---|---|---|
@@ -1,7 +1,5 @@ | ||
1 | -title: A Future Vision | |
1 … | +title: a future vision | |
2 | 2 … | ---- |
3 | -url: dat://b36fd62869568a05bc9e96c1acee778a1b94cd8647ec55e42159d12846d8c00f | |
3 … | +url: dat://b36fd62869568a05bc9e96c1acee778a1b94cd8647ec55e42159d12846d8c00f/ | |
4 | 4 … | ---- |
5 | -notes: | |
6 | - | |
7 | -Written for a spoken performance about solarpunk futures. Now it exists as a strange sorta fragment! | |
5 … | +notes: good notes. |
Built with git-ssb-web