| Comment: | more work on UI and circles |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
03ea3dffe75fb71e9ab8b0502034c8c9 |
| User & Date: | lexi on 2021-01-14 01:11:51 |
| Other Links: | manifest | tags |
|
2021-01-14
| ||
| 05:31 | more progress on ui & circles check-in: e78334fe04 user: lexi tags: trunk | |
| 01:11 | more work on UI and circles check-in: 03ea3dffe7 user: lexi tags: trunk | |
|
2021-01-13
| ||
| 15:01 | begin work on circles check-in: a4b4af5ca4 user: lexi tags: trunk | |
Modified backend/pgsql.t from [96ec125a09] to [dc07991d31].
13 13 params = {uint64}, sql = [[ 14 14 select domain, key, knownsince, parsav from parsav_servers 15 15 where id = $1::bigint 16 16 ]]; 17 17 }; 18 18 19 19 conf_get = { 20 - params = {rawstring}, sql = [[ 20 + params = {pstring}, sql = [[ 21 21 select value from parsav_config 22 22 where key = $1::text limit 1 23 23 ]]; 24 24 }; 25 25 26 26 conf_set = { 27 - params = {rawstring,rawstring}, cmd=true, sql = [[ 27 + params = {pstring,pstring}, cmd=true, sql = [[ 28 28 insert into parsav_config (key, value) 29 29 values ($1::text, $2::text) 30 30 on conflict (key) do update set value = $2::text 31 31 ]]; 32 32 }; 33 33 34 34 conf_reset = { ................................................................................ 291 291 params = {uint64,uint64}, sql = [[ 292 292 select name, id, owner, array_length(members,1) from parsav_circles where 293 293 ($1::bigint = 0 or $1::bigint = owner) and 294 294 ($2::bigint = 0 or $2::bigint = id) 295 295 ]]; 296 296 }; 297 297 298 - circle_members_fetch_cid = { 299 - params = {uint64, uint64}, sql = [[ 300 - select unnest(members) from parsav_circles where 301 - ($1::bigint = 0 or owner = $1::bigint) and 298 + circle_create = { 299 + params = {uint64,pstring}, sql = [[ 300 + insert into parsav_circles (owner,name) 301 + values ($1::bigint, $2::text) 302 + returning id 303 + ]]; 304 + }; 305 + 306 + circle_destroy = { 307 + params = {uint64,uint64}, cmd = true, sql = [[ 308 + delete from parsav_circles where 309 + owner = $1::bigint and 302 310 id = $2::bigint 303 311 ]]; 304 312 }; 313 + 314 + circle_memberships_uid = { 315 + params = {uint64, uint64}, sql = [[ 316 + select name, id, owner, array_length(members,1) from parsav_circles where 317 + owner = $1::bigint and 318 + members @> array[$2::bigint] 319 + ]]; 320 + }; 321 + 322 + circle_members_fetch_cid = { 323 + params = {uint64}, sql = [[ 324 + select unnest(members) from parsav_circles where 325 + id = $1::bigint 326 + ]]; 327 + }; 305 328 306 329 circle_members_fetch_name = { 307 330 params = {uint64, pstring}, sql = [[ 308 331 select unnest(members) from parsav_circles where 309 332 ($1::bigint = 0 or owner = $1::bigint) and 310 333 name = $2::text 311 334 ]]; ................................................................................ 557 580 order by c.tltime desc 558 581 559 582 limit case when $3::bigint = 0 then null 560 583 else $3::bigint end 561 584 offset $4::bigint 562 585 ]]; 563 586 }; 587 + 588 + timeline_circle_fetch = { 589 + params = {uint64, uint64, uint64, uint64, uint64}, sql = [[ 590 + with circle as ( 591 + select unnest(members) from parsav_circles where id = $1::bigint 592 + ) 593 + 594 + select (c.post).* 595 + from pg_temp.parsavpg_known_content as c 596 + 597 + where ($2::bigint = 0 or c.tltime <= $2::bigint) and 598 + ($3::bigint = 0 or $3::bigint < c.tltime) and 599 + (c.promoter in (table circle) or 600 + c.promoter = (select owner from parsav_circles where id = $1::bigint)) 601 + 602 + order by c.tltime desc 603 + 604 + limit case when $4::bigint = 0 then null 605 + else $4::bigint end 606 + offset $5::bigint 607 + ]]; 608 + }; 564 609 565 610 timeline_actor_fetch = { 566 611 params = {uint64, uint64, uint64, uint64, uint64}, sql = [[ 567 612 with followed as ( 568 613 select relatee from parsav_rels where 569 614 kind = <rel:follow> and 570 615 relator = $1::bigint 571 - ), avoided as ( 616 + ), avoided as ( -- not strictly necessary but lets us minimize how much data needs to be sent back to parsav for filtering 572 617 select relatee as avoidee from parsav_rels where 573 618 kind = <rel:avoid> or kind = <rel:mute> and 574 619 relator = $1::bigint 575 620 union select relator as avoidee from parsav_rels where 576 621 kind = <rel:exclude> and 577 622 relatee = $1::bigint 578 623 ) ................................................................................ 580 625 select (c.post).* 581 626 from pg_temp.parsavpg_known_content as c 582 627 583 628 where ($2::bigint = 0 or c.tltime <= $2::bigint) and 584 629 ($3::bigint = 0 or $3::bigint < c.tltime) and 585 630 (c.promoter in (table followed) or 586 631 c.promoter = $1::bigint) and 587 - not ((c.post).author in (table avoided)) 632 + not ((c.post).author in (table avoided)) and 633 + not (c.promoter in (table avoided)) 588 634 order by c.tltime desc 589 635 590 636 limit case when $4::bigint = 0 then null 591 637 else $4::bigint end 592 638 offset $5::bigint 593 639 ]]; 594 640 }; ................................................................................ 696 742 artifacts = array_remove(artifacts, $2::bigint) 697 743 where id = $1::bigint and 698 744 artifacts @> array[$2::bigint] 699 745 ]]; 700 746 }; 701 747 702 748 actor_conf_str_get = { 703 - params = {uint64, rawstring}, sql = [[ 749 + params = {uint64, pstring}, sql = [[ 704 750 select value from parsav_actor_conf_strs where 705 751 uid = $1::bigint and 706 752 key = $2::text 707 753 limit 1 708 754 ]]; 709 755 }; 710 756 actor_conf_str_set = { 711 - params = {uint64, rawstring, rawstring}, cmd = true, sql = [[ 757 + params = {uint64, pstring, pstring}, cmd = true, sql = [[ 712 758 insert into parsav_actor_conf_strs (uid,key,value) 713 759 values ($1::bigint, $2::text, $3::text) 714 760 on conflict (uid,key) do update set value = $3::text 715 761 ]]; 716 762 }; 717 763 actor_conf_str_enum = { 718 764 params = {uint64}, sql = [[ 719 765 select value from parsav_actor_conf_strs where uid = $1::bigint 720 766 ]]; 721 767 }; 722 768 actor_conf_str_reset = { 723 - params = {uint64, rawstring}, cmd = true, sql = [[ 769 + params = {uint64, pstring}, cmd = true, sql = [[ 724 770 delete from parsav_actor_conf_strs where 725 771 uid = $1::bigint and ($2::text is null or key = $2::text) 726 772 ]] 727 773 }; 728 774 729 775 actor_conf_int_get = { 730 - params = {uint64, rawstring}, sql = [[ 776 + params = {uint64, pstring}, sql = [[ 731 777 select value from parsav_actor_conf_ints where 732 778 uid = $1::bigint and 733 779 key = $2::text 734 780 limit 1 735 781 ]]; 736 782 }; 737 783 actor_conf_int_set = { 738 - params = {uint64, rawstring, uint64}, cmd = true, sql = [[ 784 + params = {uint64, pstring, uint64}, cmd = true, sql = [[ 739 785 insert into parsav_actor_conf_ints (uid,key,value) 740 786 values ($1::bigint, $2::text, $3::bigint) 741 787 on conflict (uid,key) do update set value = $3::bigint 742 788 ]]; 743 789 }; 744 790 actor_conf_int_enum = { 745 791 params = {uint64}, sql = [[ 746 792 select value from parsav_actor_conf_ints where uid = $1::bigint 747 793 ]]; 748 794 }; 749 795 actor_conf_int_reset = { 750 - params = {uint64, rawstring}, cmd = true, sql = [[ 796 + params = {uint64, pstring}, cmd = true, sql = [[ 751 797 delete from parsav_actor_conf_ints where 752 798 uid = $1::bigint and ($2::text is null or key = $2::text) 753 799 ]] 754 800 }; 755 801 } 756 802 757 803 local struct pqr { ................................................................................ 1274 1320 return true 1275 1321 else 1276 1322 lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res)) 1277 1323 return false 1278 1324 end 1279 1325 end]; 1280 1326 1281 - conf_get = [terra(src: &lib.store.source, key: rawstring) 1327 + conf_get = [terra(src: &lib.store.source, key: pstring) 1282 1328 var r = queries.conf_get.exec(src, key) 1283 1329 if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else 1284 1330 defer r:free() 1285 1331 return r:String(0,0) 1286 1332 end 1287 1333 end]; 1288 - conf_set = [terra(src: &lib.store.source, key: rawstring, val: rawstring) 1334 + conf_set = [terra(src: &lib.store.source, key: pstring, val: pstring) 1289 1335 queries.conf_set.exec(src, key, val):free() end]; 1290 1336 conf_reset = [terra(src: &lib.store.source, key: rawstring) 1291 1337 queries.conf_reset.exec(src, key):free() end]; 1292 1338 1293 1339 actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64) 1294 1340 var r = queries.actor_fetch_uid.exec(src, uid) 1295 1341 if r.sz == 0 then ................................................................................ 1546 1592 timeline_instance_fetch = [terra( 1547 1593 src: &lib.store.source, 1548 1594 rg: lib.store.range 1549 1595 ): lib.mem.lstptr(lib.store.post) 1550 1596 var r = pqr { sz = 0 } 1551 1597 var A,B,C,D = rg:matrix() -- :/ 1552 1598 r = queries.timeline_instance_fetch.exec(src,A,B,C,D) 1599 + 1600 + var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz) 1601 + for i=0,r.sz do 1602 + ret.ptr[i] = row_to_post(&r, i) -- MUST FREE ALL 1603 + ret.ptr[i].ptr.source = src 1604 + end 1605 + 1606 + return ret 1607 + end]; 1608 + 1609 + timeline_circle_fetch = [terra( 1610 + src: &lib.store.source, 1611 + cid: uint64, 1612 + rg: lib.store.range 1613 + ): lib.mem.lstptr(lib.store.post) 1614 + var r = pqr { sz = 0 } 1615 + var A,B,C,D = rg:matrix() -- :/ 1616 + r = queries.timeline_circle_fetch.exec(src,cid,A,B,C,D) 1553 1617 1554 1618 var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz) 1555 1619 for i=0,r.sz do 1556 1620 ret.ptr[i] = row_to_post(&r, i) -- MUST FREE ALL 1557 1621 ret.ptr[i].ptr.source = src 1558 1622 end 1559 1623 ................................................................................ 2032 2096 auth_sigtime_user_alter = [terra( 2033 2097 src: &lib.store.source, 2034 2098 uid: uint64, 2035 2099 time: lib.store.timepoint 2036 2100 ): {} queries.auth_sigtime_user_alter.exec(src, uid, time) end]; 2037 2101 2038 2102 actor_conf_str_enum = nil; 2039 - actor_conf_str_get = [terra(src: &lib.store.source, uid: uint64, key: rawstring): pstring 2103 + actor_conf_str_get = [terra( 2104 + src: &lib.store.source, 2105 + pool: &lib.mem.pool, 2106 + uid: uint64, 2107 + key: pstring 2108 + ): pstring 2040 2109 var r = queries.actor_conf_str_get.exec(src, uid, key) 2041 2110 if r.sz > 0 then 2042 - var ret = r:String(0,0) 2043 - r:free() 2044 - return ret 2111 + return r:_string(0,0):pdup(pool) 2045 2112 else return pstring.null() end 2046 2113 end]; 2047 - actor_conf_str_set = [terra(src: &lib.store.source, uid: uint64, key: rawstring, value: rawstring): {} 2114 + actor_conf_str_set = [terra(src: &lib.store.source, uid: uint64, key: pstring, value: pstring): {} 2048 2115 queries.actor_conf_str_set.exec(src,uid,key,value) end]; 2049 - actor_conf_str_reset = [terra(src: &lib.store.source, uid: uint64, key: rawstring): {} 2116 + actor_conf_str_reset = [terra(src: &lib.store.source, uid: uint64, key: pstring): {} 2050 2117 queries.actor_conf_str_reset.exec(src,uid,key) end]; 2051 2118 2052 2119 actor_conf_int_enum = nil; 2053 - actor_conf_int_get = [terra(src: &lib.store.source, uid: uint64, key: rawstring) 2120 + actor_conf_int_get = [terra(src: &lib.store.source, uid: uint64, key: pstring) 2054 2121 var r = queries.actor_conf_int_get.exec(src, uid, key) 2055 2122 if r.sz > 0 then 2056 2123 var ret = r:int(uint64,0,0) 2057 2124 r:free() 2058 2125 return ret, true 2059 2126 end 2060 2127 return 0, false 2061 2128 end]; 2062 - actor_conf_int_set = [terra(src: &lib.store.source, uid: uint64, key: rawstring, value: uint64): {} 2129 + actor_conf_int_set = [terra(src: &lib.store.source, uid: uint64, key: pstring, value: uint64): {} 2063 2130 queries.actor_conf_int_set.exec(src,uid,key,value) end]; 2064 - actor_conf_int_reset = [terra(src: &lib.store.source, uid: uint64, key: rawstring): {} 2131 + actor_conf_int_reset = [terra(src: &lib.store.source, uid: uint64, key: pstring): {} 2065 2132 queries.actor_conf_int_reset.exec(src,uid,key) end]; 2066 2133 2067 2134 circle_search = [terra( 2068 2135 src: &lib.store.source, 2069 2136 pool:&lib.mem.pool, 2070 2137 uid: uint64, 2071 2138 cid: uint64 ................................................................................ 2074 2141 if res.sz == 0 then return [lib.mem.ptr(lib.store.circle)].null() end 2075 2142 defer res:free() 2076 2143 2077 2144 var rt = pool:alloc(lib.store.circle, res.sz) 2078 2145 for i = 0, res.sz do 2079 2146 var name = res:_string(i,0) 2080 2147 rt(i) = lib.store.circle { 2081 - name = name:pdup(pool); 2082 - cid = res:int(uint64,i,1); 2083 - owner = res:int(uint64,i,2); 2084 - memcount = res:int(uint64,i,3); 2148 + name = name:pdup(pool); cid = res:int(uint64,i,1); 2149 + owner = res:int(uint64,i,2); memcount = res:int(uint64,i,3); 2150 + } 2151 + end 2152 + 2153 + return rt 2154 + end]; 2155 + 2156 + circle_create = [terra( 2157 + src: &lib.store.source, 2158 + owner: uint64, 2159 + name: pstring 2160 + ): uint64 2161 + var r = queries.circle_create.exec(src, owner, name) 2162 + if r.sz > 0 then defer r:free() return r:int(uint64,0,0) end 2163 + return 0 2164 + end]; 2165 + 2166 + circle_destroy = [terra( 2167 + src: &lib.store.source, 2168 + owner: uint64, 2169 + cid: uint64 2170 + ): {} queries.circle_destroy.exec(src, owner, cid) end]; 2171 + 2172 + circle_memberships_uid = [terra( 2173 + src: &lib.store.source, 2174 + pool:&lib.mem.pool, 2175 + owner: uint64, 2176 + subject: uint64 2177 + ): lib.mem.ptr(lib.store.circle) 2178 + var res = queries.circle_memberships_uid.exec(src, owner, subject) 2179 + if res.sz == 0 then return [lib.mem.ptr(lib.store.circle)].null() end 2180 + defer res:free() 2181 + 2182 + var rt = pool:alloc(lib.store.circle, res.sz) 2183 + for i = 0, res.sz do 2184 + var name = res:_string(i,0) 2185 + rt(i) = lib.store.circle { 2186 + name = name:pdup(pool); cid = res:int(uint64,i,1); 2187 + owner = res:int(uint64,i,2); memcount = res:int(uint64,i,3); 2085 2188 } 2086 2189 end 2087 2190 2088 2191 return rt 2089 2192 end]; 2090 2193 2091 2194 circle_members_fetch_cid = [terra( 2092 2195 src: &lib.store.source, 2093 2196 pool:&lib.mem.pool, 2094 - uid: uint64, 2095 2197 cid: uint64 2096 2198 ): lib.mem.ptr(uint64) 2097 - var res = queries.circle_members_fetch_cid.exec(src,uid,cid) 2199 + var res = queries.circle_members_fetch_cid.exec(src,cid) 2098 2200 if res.sz == 0 then return [lib.mem.ptr(uint64)].null() end 2099 2201 defer res:free() 2100 2202 2101 2203 var rt = pool:alloc(uint64, res.sz) 2102 2204 for i = 0, res.sz do rt(i) = res:int(uint64,i,0) end 2103 2205 2104 2206 return rt 2105 2207 end]; 2106 2208 2107 2209 actor_auth_register_uid = nil; -- TODO better support non-view based auth 2108 2210 } 2109 2211 2110 2212 return b
Modified config.lua from [a1f76576fd] to [b2a2580dbd].
51 51 -- TODO with gzip compression, svg is dramatically superior to webp 52 52 -- we should add support for content-encoding headers and pre-compress 53 53 -- the damn things before compiling (also making the binary smaller) 54 54 {'style.css', 'text/css'}; 55 55 {'live.js', 'text/javascript'}; -- rrrrrrrr 56 56 {'default-avatar.webp', 'image/webp'}; -- needs inkscape-exclusive svg features 57 57 {'bell.svg', 'image/svg+xml'}; 58 + {'gear.svg', 'image/svg+xml'}; 58 59 {'heart.webp', 'image/webp'}; 59 60 {'retweet.webp', 'image/webp'}; 60 61 {'padlock.svg', 'image/svg+xml'}; 61 62 {'warn.svg', 'image/svg+xml'}; 62 63 {'query.webp', 'image/webp'}; 63 64 {'reply.webp', 'image/webp'}; 64 65 {'file.webp', 'image/webp'};
Modified mem.t from [a251bec86e] to [8c252399a5].
108 108 end 109 109 terra t.methods.null(): t return t { ptr = nil, ct = 0 } end -- maybe should be a macro? 110 110 terra t:ref() return self.ptr ~= nil end 111 111 t.metamethods.__not = macro(function(self) return `not self:ref() end) 112 112 t.metamethods.__apply = macro(function(self,idx) return `self.ptr[ [idx or 0] ] end) 113 113 t.metamethods.__update = macro(function(self,idx,rhs) 114 114 return quote self.ptr[idx] = rhs end end) 115 + t.metamethods.__cast = function(from,to,exp) 116 + if to == t then 117 + if from == niltype then return `t.null() 118 + elseif from == &ty then return `t {ptr = exp, ct = 1} 119 + elseif from == ty then return `t {ptr = &exp, ct = 1} 120 + elseif from.N and from.type == ty then 121 + return `t {ptr = &exp[0], ct = from.N } 122 + end 123 + error('invalid cast to ' .. t.name .. ' from ' .. tostring(from)) 124 + elseif from == t then 125 + if to == &ty then return `exp.ptr 126 + elseif to == ty then return `@exp.ptr 127 + elseif to == bool then return `exp:ref() end 128 + error('invalid cast from ' .. t.name .. ' to ' .. tostring(to)) 129 + end 130 + error('invalid pointer cast') 131 + end 115 132 116 133 if not ty:isstruct() then 117 134 terra t:cmp_raw(other: &ty) 118 135 for i=0, self.ct do 119 136 if self.ptr[i] ~= other[i] then return false end 120 137 end 121 138 return true
Modified parsav.md from [23851a52a8] to [d83ecc5b3e].
142 142 * ldap for auth (and maybe actors?) 143 143 * cdb (for static content, maybe? does this make sense?) 144 144 * mariadb/mysql 145 145 * the various nosql horrors, e.g. redis, mongo, and so on 146 146 147 147 parsav urgently needs an internationalization framework as well. right now everything is just hardcoded in english. yuck. 148 148 149 -parsav could be significantly improved by adjusting its memory management strategy. instead of allocating everything with lib.mem.heapa (which currently maps to malloc on all platforms), we should allocate a static buffer for the server overlord object which can simply be cleared and re-used for each http request, and enlarged with `realloc` when necessary. the entire region could be `mlock`ed for better performance, and it would no longer be necessary to track and free memory, as the entire buffer would simply be discarded after use (similar to PHP's original memory management strategy). this would remove possibly the largest source of latency in the codebase, as `parsav` is regrettably quite heavy on malloc, performing numerous allocations for each page rendered. **update:** this is now in progress 149 +parsav could be significantly improved by adjusting its memory management strategy. instead of allocating everything with lib.mem.heapa (which currently maps to malloc on all platforms), we can allocate a static buffer for the server overlord object which can simply be cleared and re-used for each http request, and enlarged with `realloc` when necessary. the entire region could be `mlock`ed for better performance, and it would no longer be necessary to track and free memory, as the entire buffer would simply be discarded after use (similar to PHP's original memory management strategy). this would remove possibly the largest source of latency in the codebase, as `parsav` is regrettably quite heavy on malloc, performing numerous allocations for each page rendered. **update:** this is now in progress, and much of the UI code has been converted; the database code will also need to be converted, however, and this will be too time-consuming to be worth tackling any time soon. new functions should be written to use the memory pooling strategy, however.
Modified parsav.t from [d3351b7874] to [8d6924c4e0].
490 490 'render:notices'; 491 491 492 492 'render:media-gallery'; 493 493 494 494 'render:docpage'; 495 495 496 496 'render:conf:profile'; 497 + 'render:conf:circles'; 497 498 'render:conf:sec'; 498 499 'render:conf:users'; 499 500 'render:conf:avi'; 500 501 'render:conf'; 501 502 'route'; 502 503 } 503 504
Added render/conf/circles.t version [2450dbe8a2].
1 +-- vim: ft=terra 2 +local pstr = lib.mem.ptr(int8) 3 +local pref = lib.mem.ref(int8) 4 + 5 + 6 +local terra 7 +render_conf_circles(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr 8 + if path.ct == 2 then 9 + var circs = co.srv:circle_search(&co.srv.pool, co.who.id, 0) 10 + var ui: data.view.conf_circles 11 + if circs.ct == 0 then 12 + ui.newattr = ' open'; 13 + else 14 + ui.newattr = ''; 15 + var circlst = co:stra(86) 16 + for i = 0, circs.ct do 17 + circlst:lpush '<li><a href="/conf/circles/':shpush(circs(i).cid):lpush'">' 18 + :ppush(circs(i).name) 19 + :lpush '</a></li>' 20 + end 21 + ui.circles = circlst:finalize() 22 + end 23 + 24 + return ui:poolstr(&co.srv.pool) 25 + elseif path.ct == 3 then 26 + var cid, cid_ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct) 27 + if not cid_ok then return pstr.null() end 28 + var circ = co.srv:circle_search(&co.srv.pool, co.who.id, cid) 29 + if not circ then return pstr.null() end 30 + var actrs = co.srv:circle_members_fetch_cid(&co.srv.pool, cid) 31 + 32 + var acta = co:stra(86) 33 + if actrs:ref() then 34 + for i=0, actrs.ct do 35 + var a = co:uid2actor(actrs(i)) 36 + if a ~= nil then 37 + acta:lpush '<li><a class="username" href="/':push(a.xid,0):lpush '#rel">' 38 + lib.render.nym(a, 0, &acta, false) 39 + acta:lpush '</a></li>' 40 + end 41 + end 42 + end 43 + 44 + var ui = data.view.conf_circle_view { 45 + circle = circ().name; 46 + actors = acta:finalize(); 47 + } 48 + 49 + return ui:poolstr(&co.srv.pool) 50 + else return pstr.null() end 51 +end 52 + 53 +return render_conf_circles
Modified render/conf/profile.t from [5b3736f2f4] to [5726a3ab36].
10 10 render_conf_profile(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr 11 11 var hue: int8[21] 12 12 var c = data.view.conf_profile { 13 13 handle = cs(co.who.handle); 14 14 nym = cs(lib.coalesce(co.who.nym,'')); 15 15 bio = cs(lib.coalesce(co.who.bio,'')); 16 16 hue = lib.math.decstr(co.ui_hue, &hue[20]); 17 + acl_follow = co:usercfg_str(co.who.id, 'acl-follow'); 18 + acl_follow_req = co:usercfg_str(co.who.id, 'acl-follow-req'); 17 19 } 18 20 return c:poolstr(&co.srv.pool) 19 21 end 20 22 21 23 return render_conf_profile
Modified render/conf/users.t from [24aad61532] to [be5a9e1656].
368 368 var users: lib.mem.lstptr(lib.store.actor) 369 369 if mode == mode_local then 370 370 users = co.srv:actor_enum_local() 371 371 else 372 372 users = co.srv:actor_enum() 373 373 end 374 374 ulst:lpush('</em></div>') 375 - ulst:lpush('<ul class="user-list">') 375 + ulst:lpush('<ul class="directory">') 376 376 for i=0,users.ct do var usr = users(i).ptr 377 377 if mode == mode_staff and usr.rights.rank == 0 then goto skip 378 378 elseif mode == mode_peons and usr.rights.rank ~= 0 then goto skip 379 379 elseif mode == mode_remote and usr.origin == 0 then goto skip 380 380 elseif mode == mode_peers and usr.epithet == nil then goto skip end 381 381 var idlen = lib.math.shorthand.gen(usr.id, &idbuf[0]) 382 382 ulst:lpush('<li>')
Modified render/nav.t from [9f2c55cf5b] to [5e68ddf43b].
1 1 -- vim: ft=terra 2 2 local terra 3 3 render_nav(co: &lib.srv.convo) 4 4 var t = co:stra(64) 5 5 if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then 6 - t:lpush(' <a accesskey="t" href="/">timeline</a>') 6 + t:lpush(' <a accesskey="t" href="/"><u>t</u>imeline</a>') 7 7 end 8 8 if co.who ~= nil then 9 - t:lpush(' <a accesskey="c" href="/compose">compose</a> <a accesskey="p" href="/'):push(co.who.xid,0) 10 - t:lpush('">profile</a> <a accesskey="m" href="/media">media</a> <a accesskey="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <div class="ident">@') 9 + t:lpush(' <a accesskey="c" href="/compose"><u>c</u>ompose</a> <a accesskey="m" href="/media"><u>m</u>edia</a> <a accesskey="d" href="/doc"><u>d</u>ocs</a> <hr> <a accesskey="p" href="'):push(co.who.xid,0):lpush('" class="ident">') 11 10 t:push(co.who.handle,0) 12 - t:lpush('</div> <a accesskey="g" href="/logout">log out</a> <a class="bell" accesskey="x" href="/notices">notices</a>') 11 + t:lpush('</a> <a accesskey="g" href="/logout">lo<u>g</u> out</a> <a class="gear" accesskey="o" href="/conf">c<u>o</u>nfigure</a> <a class="bell" accesskey="x" href="/notices">notices (<u>x</u>)</a>') 13 12 else 14 - t:lpush(' <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/login">log in</a>') 13 + t:lpush(' <a accesskey="d" href="/doc"><u>d</u>ocs</a> <a accesskey="g" href="/login">lo<u>g</u> in</a>') 15 14 end 16 15 return t:finalize() 17 16 end 18 17 return render_nav
Modified render/profile.t from [0e11aad870] to [06727d1481].
58 58 { id = 'disemvowel', start = { 59 59 text = 'disemvowel'; -- translations should not translate this literally 60 60 desc = "this user's posts will be ritually mutilated in a humorous fashion as appropriate to the script in which they are written; e.g. the removal of vowels in roman text and deletion of kana in japanese text"; 61 61 }, stop = { 62 62 text = 're-emvowel'; 63 63 desc = "this user's posts will once again appear normally"; 64 64 }}; 65 + 66 + { id = 'attenuate', start = { 67 + text = 'attenuate'; 68 + desc = "this user will no longer be able to retweet things into your timeline"; 69 + }, stop = { 70 + text = 'amplify'; 71 + desc = "this user's retweets will be allowed to reach your timeline again"; 72 + }}; 65 73 66 74 { id = 'block', start = { 67 75 text = 'block'; 68 76 desc = "this user will not be able to interact with you in any fashion and they will be forced to unfollow you"; 69 77 }, stop = { 70 78 text = 'unblock'; 71 79 desc = "this user will once again be able to interact with you";
Modified render/timeline.t from [900216dd8b] to [5c434424ea].
34 34 sz = 0, run = 0 35 35 } 36 36 var fetchmode = lib.store.range { 37 37 mode = 1; -- T->I 38 38 from_time = stoptime; 39 39 to_idx = 64; 40 40 } 41 + var circ: lib.mem.ptr(lib.store.circle) = nil 41 42 if mode == modes.follow or mode == modes.mutual then 42 43 posts = co.srv:timeline_actor_fetch_uid(co.who.id,fetchmode) 43 44 elseif mode == [modes['local']] then 44 45 posts = co.srv:timeline_instance_fetch(fetchmode) 45 46 elseif mode == modes.fedi then 46 - elseif mode == modes.circle then 47 + elseif mode == modes.circle and spec:ref() then 48 + var cid, ok = lib.math.shorthand.parse(spec.ptr,spec.ct) 49 + if ok then 50 + circ = co.srv:circle_search(&co.srv.pool,co.who.id,cid) 51 + if circ.ct == 1 then 52 + posts = co.srv:timeline_circle_fetch(cid,fetchmode) 53 + end 54 + end 47 55 end 48 56 49 57 var acc = co:stra(1024) 50 - var modelabels = arrayof(pstr, 'followed', 'mutuals', 'local instance', 'fediverse', 'circle') 58 + var modelabels = arrayof(pstr, '<u>f</u>ollowed', 'm<u>u</u>tuals', '<u>l</u>ocal instance', 'fedi<u>v</u>erse', 'ci<u>r</u>cle') 59 + var keybinds = arrayof(pstr, 'f', 'u', 'l', 'v', 'r') 51 60 var modelinks = arrayof(pstr, [modes.members]) 52 - acc:lpush('<div style="text-align: right"><em>showing ') 61 + acc:lpush('<div class="kind-picker">showing ') 53 62 for i=0, [modelabels.type.N] do 54 63 if co.aid ~= 0 or not requires_login(i) then 55 64 if i > 0 then acc:lpush(' · ') end 56 - if i == mode and not (mode == modes.circle and spec:ref()) then 65 + if i == mode and not circ then 57 66 acc:lpush('<strong>'):ppush(modelabels[i]):lpush('</strong>') 58 67 else 59 - acc:lpush('<a href="/tl/'):ppush(modelinks[i]):lpush('">'):ppush(modelabels[i]):lpush('</a>') 68 + acc:lpush('<a href="/tl/'):ppush(modelinks[i]):lpush('" accesskey="'):ppush(keybinds[i]):lpush('">') 69 + if i == mode and circ:ref() then 70 + acc:lpush'<strong>':ppush(modelabels[i]):lpush'</strong> (':ppush(circ().name):lpush(')') 71 + else 72 + acc:ppush(modelabels[i]) 73 + end 74 + acc:lpush('</a>') 60 75 end 61 76 end 62 77 end 63 - acc:lpush('</em></div>') 78 + acc:lpush('</div>') 64 79 var newest: lib.store.timepoint = 0 65 80 if mode == modes.circle and not spec then 66 81 var circles = co.srv:circle_search(&co.srv.pool, co.who.id, 0) 67 82 acc:lpush '<menu class="circles">' 68 83 for i:intptr = 0, circles.ct do 69 - acc:lpush '<li><a href="/tl/circle/' 84 + acc:lpush '<a href="/tl/circle/' 70 85 :shpush(circles(i).cid) 71 - :lpush '">' 86 + if i <= 10 then 87 + acc:lpush '" accesskey="':ipush((i+1) % 10) 88 + end 89 + acc:lpush '">' 72 90 :ppush(circles(i).name) 73 - :lpush '</a></li>' 91 + :lpush '</a>' 74 92 end 75 93 -- TODO list circles 76 94 acc:lpush '</menu>' 77 95 else 78 96 acc:lpush('<div id="tl" data-live="10">') 79 97 for i = 0, posts.sz do 80 98 var author = co:uid2actor(posts(i).ptr.author) 81 99 if mode == modes.mutual and posts(i).ptr.author ~= co.who.id then 82 100 if not author.relationship.recip.follow() then goto skip end 83 101 end 84 102 if author.relationship.rel.mute() or 85 103 author.relationship.rel.avoid() or 86 104 author.relationship.recip.exclude() then goto skip end 105 + if posts(i).ptr.rtdby ~= 0 then 106 + var rter = co:uid2actor(posts(i).ptr.rtdby) 107 + if rter.relationship.rel.mute() 108 + or rter.relationship.rel.attenuate() 109 + or rter.relationship.rel.avoid() 110 + or rter.relationship.recip.exclude() then goto skip end 111 + end 87 112 lib.render.tweet(co, posts(i).ptr, &acc) 88 113 var t = lib.math.biggest(lib.math.biggest(posts(i).ptr.posted, posts(i).ptr.discovered),posts(i).ptr.edited) 89 114 if t > newest then newest = t end 90 115 ::skip:: posts(i):free() 91 116 end 92 117 if posts.run > 0 then posts:free() end 93 118 acc:lpush('</div>')
Modified render/tweet-page.t from [6d8fb63eed] to [1e93b5b498].
38 38 if co.aid ~= 0 then 39 39 pg:lpush('<form class="action-bar" method="post">') 40 40 if not co.srv:post_liked_uid(co.who.id, p.id) 41 41 then pg:lpush('<button class="pos" name="act" accesskey="l" value="like">like</button>') 42 42 else pg:lpush('<button class="neg" name="act" accesskey="l" value="dislike">dislike</button>') 43 43 end 44 44 pg:lpush('<button class="pos" name="act" accesskey="r" value="rt">retweet</button>') 45 + if co.who.rights.powers.crier() then 46 + pg:lpush('<button name="act" accesskey="p" value="promote">promote</button>') 47 + end 45 48 if p.author == co.who.id then 46 49 if co.who.rights.powers.edit() then 47 50 pg:lpush('<a class="button" accesskey="e" href="/post/'):rpush(path(1)):lpush('/edit">edit</a>') 48 51 end 49 52 pg:lpush('<a class="neg button" accesskey="d" href="/post/'):rpush(path(1)):lpush('/del">delete</a>') 50 53 elseif co.who.rights.powers.snitch() then 51 - pg:lpush('<a class="neg button" accesskey="s" href="/post/'):rpush(path(1)):lpush('/report">report</a>') 54 + pg:lpush('<a class="neg button" accesskey="s" href="/post/'):rpush(path(1)):lpush('/snitch">report</a>') 52 55 end 53 56 -- TODO list user's chosen reaction emoji 54 57 pg:lpush('</form>') 55 58 56 59 end 57 60 pg:lpush('<div id="convo" data-live="10">') 58 61 render_tweet_replies(co, &pg, p.id)
Modified route.t from [bb5bfba2ee] to [89c9cb6c12].
254 254 end 255 255 end 256 256 defer post:free() -- NOP on null 257 257 258 258 if path.ct == 3 then 259 259 var lnk: lib.str.acc lnk:compose('/post/', path(1)) 260 260 var lnkp = lnk:finalize() defer lnkp:free() 261 - if post:ref() and post(0).author ~= co.who.id then 261 + if post:ref() and path(2):cmp(lib.str.lit 'snitch') then 262 + if meth_get(meth) then 263 + var ui = data.view.report { 264 + badtweet = lib.render.tweet(co, post.ptr, nil); 265 + clnk = lnkp; 266 + } 267 + 268 + co:stdpage([lib.srv.convo.page] { 269 + title = 'post :: report'; 270 + class = 'report'; 271 + body = ui:poolstr(&co.srv.pool); 272 + cache = false; 273 + }) 274 + else 275 + end 276 + return 277 + elseif post:ref() and post(0).author ~= co.who.id then 262 278 co:complain(403, 'forbidden', 'you cannot alter other people\'s posts') 263 279 return 264 280 elseif post:ref() and path(2):cmp(lib.str.lit 'edit') then 265 281 if not co:assertpow('edit') then return end 266 282 if meth_get(meth) then 267 283 lib.render.compose(co, post.ptr, nil) 268 284 return ................................................................................ 289 305 else 290 306 conf = data.view.confirm { 291 307 title = 'cancel retweet'; 292 308 query = 'are you sure you want to undo this retweet?'; 293 309 cancel = '/'; 294 310 } 295 311 end 296 - var fr = co.srv.pool:frame() 297 312 var body = conf:poolstr(&co.srv.pool) --defer body:free() 298 313 co:stdpage([lib.srv.convo.page] { 299 314 title = 'post :: delete'; 300 315 class = 'query'; 301 316 body = body; cache = false; 302 317 }) 303 - co.srv.pool:reset(fr) 304 318 return 305 319 elseif meth == method.post then 306 320 var act = co:ppostv('act') 307 - if act:cmp( 'confirm') then 321 + if act:cmp('confirm') then 308 322 if post:ref() then 309 - post(0).source:post_destroy(post(0).id) 323 + post().source:post_destroy(post().id) 310 324 elseif rt.kind ~= 0 then 311 325 co.srv:post_act_cancel(pid) 312 326 end 313 327 co:reroute('/') -- TODO maybe return to parent or conversation if possible 314 328 return 315 329 else goto badop end 316 330 end ................................................................................ 480 494 path(1):cmp('brand') 481 495 ) then goto nopriv 482 496 483 497 elseif not co.who.rights.powers.account() and ( 484 498 path(1):cmp('profile') or 485 499 path(1):cmp('sec') or 486 500 path(1):cmp('avi') or 487 - path(1):cmp('ui') 501 + path(1):cmp('ui') or 502 + path(1):cmp('circles') 488 503 ) then goto nopriv 489 504 490 505 elseif not co.who.rights.powers:affect_users() and ( 491 506 path(1):cmp(lib.str.lit 'users') 492 507 ) then goto nopriv end 493 508 end 494 509 495 510 if meth == method.post and path.ct >= 1 then 496 511 var user_refresh = false var fail = false 497 - if path(1):cmp(lib.str.lit 'profile') then 512 + if path(1):cmp('profile') then 498 513 lib.dbg('updating profile') 499 514 co.who.bio = co:postv('bio')._0 500 515 co.who.nym = co:postv('nym')._0 501 516 if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end 502 517 if co.who.nym ~= nil and @co.who.nym == 0 then co.who.nym = nil end 503 518 co.who.source:actor_save(co.who) 504 519 ................................................................................ 519 534 co.ui_hue = nhue 520 535 end 521 536 end 522 537 if resethue then 523 538 co.srv:actor_conf_int_reset(co.who.id, 'ui-accent') 524 539 co.ui_hue = co.srv.cfg.ui_hue 525 540 end 541 + 542 + var aclfollow = co:ppostv('acl-follow') 543 + var aclfollowreq = co:ppostv('acl-follow-req') 544 + if aclfollow:ref() and aclfollow.ct > 0 then 545 + co.srv:actor_conf_str_set(co.who.id, 'acl-follow', aclfollow) 546 + end 547 + if aclfollowreq:ref() and aclfollowreq.ct > 0 then 548 + co.srv:actor_conf_str_set(co.who.id, 'acl-follow-req', aclfollowreq) 549 + end 526 550 527 551 msg = 'profile changes saved' 528 552 --user_refresh = true -- not really necessary here, actually 529 553 530 554 elseif path(1):cmp('sec') then 531 555 if not credsec_for_uid(co, co.who.id) then return end 532 556 elseif path(1):cmp('avi') then 533 557 var act = co:ppostv('act') 534 558 if act:ref() and act:cmp('clear') then 535 559 co.who.avatarid = 0 536 560 co.who.source:actor_save(co.who) 537 561 msg = 'avatar reset to default' 538 562 else goto badop end 563 + elseif path(1):cmp('circles') then 564 + if meth == method.post then 565 + var act = co:ppostv('act') 566 + if path.ct == 2 and act:cmp('create') then 567 + var newcirc = co:ppostv('name') 568 + if newcirc.ct > 0 then 569 + co.srv:circle_create(co.who.id, newcirc) 570 + end 571 + elseif path.ct == 3 and act:cmp('del') then 572 + var id, ok = lib.math.shorthand.parse(path(2).ptr,path(2).ct) 573 + if not ok then goto e404 end 574 + co.srv:circle_destroy(co.who.id, id) 575 + co:reroute('/conf/circles') 576 + return 577 + else goto badop end 578 + end 539 579 elseif path(1):cmp('users') then 540 580 if path.ct >= 3 then 541 581 var userid, ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct) 542 582 if ok then 543 583 var usr = co.srv:actor_fetch_uid(userid) 544 584 if usr:ref() then --defer usr:free() 545 585 if not co.who:overpowers(usr.ptr) then ................................................................................ 674 714 end 675 715 end 676 716 lib.render.conf(co,path,msg) 677 717 do return end 678 718 679 719 ::nopriv:: do co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action') return end 680 720 ::badop:: do co:complain(400,'bad request','the operation you have requested is not meaningful in this context') return end 721 + ::e404:: do co:complain(404,'not found','the resource you have requested is not known to this server') return end 681 722 end 682 723 683 724 terra http.user_notices(co: &lib.srv.convo, meth: method.t) 684 725 if meth == method.post then 685 726 var act = co:ppostv('act') 686 727 if act:cmp('clear') then 687 728 co.srv:actor_conf_int_set(co.who.id, 'notice-clear-time', lib.osclock.time(nil)) ................................................................................ 848 889 -- we run through those first before giving up and parsing the URI 849 890 if uri.ptr == nil or uri.ptr[0] ~= @'/' then 850 891 co:complain(404, 'what the hell', 'how did you do that') 851 892 elseif uri.ct == 1 then -- root 852 893 if (co.srv.cfg.pol_sec == lib.srv.secmode.private or 853 894 co.srv.cfg.pol_sec == lib.srv.secmode.lockdown) and co.aid == 0 then 854 895 http.login_form(co, meth) 855 - else http.timeline(co, hpath {ptr=nil}) end 896 + else http.timeline(co, hpath {ptr=nil,ct=0}) end 856 897 elseif uri.ptr[1] == @'@' then 857 898 http.actor_profile_xid(co, uri, meth) 858 899 elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then 859 900 if not meth_get(meth) then goto wrongmeth end 860 901 if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end 861 902 elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then 862 903 http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5}) 863 904 elseif lib.str.ncmp('/file/', uri.ptr, 6) == 0 then 864 905 http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6}) 865 - elseif uri:cmp( '/notices') then 906 + elseif uri:cmp('/notices') then 866 907 if co.aid == 0 then co:reroute('/login') return end 867 908 http.user_notices(co,meth) 868 - elseif uri:cmp( '/compose') then 909 + elseif uri:cmp('/compose') then 869 910 if co.aid == 0 then co:reroute('/login') return end 870 911 http.post_compose(co,meth) 871 912 elseif uri:cmp( '/login') then 872 913 if co.aid == 0 873 914 then http.login_form(co, meth) 874 915 else co:reroute('/') 875 916 end
Modified srv.t from [6a947d38ca] to [36e7e8aa84].
15 15 overlord: &srv 16 16 ui_cue_staff: pstring 17 17 ui_cue_founder: pstring 18 18 ui_hue: uint16 19 19 nranks: uint16 20 20 maxinvites: uint16 21 21 master: uint64 22 + 23 + usrdef_pol_follow: pstring 24 + usrdef_pol_follow_req: pstring 22 25 } 23 26 local struct srv { 24 27 sources: lib.mem.ptr(lib.store.source) 25 28 webmgr: lib.net.mg_mgr 26 29 webcon: &lib.net.mg_connection 27 30 cfg: cfgcache 28 31 id: rawstring ................................................................................ 30 33 } 31 34 32 35 terra cfgcache:free() -- :/ 33 36 self.secret:free() 34 37 self.instance:free() 35 38 self.ui_cue_staff:free() 36 39 self.ui_cue_founder:free() 40 + self.usrdef_pol_follow:free() 41 + self.usrdef_pol_follow_req:free() 37 42 end 38 43 39 44 terra srv:post_enum_author_uid(uid: uint64, r: lib.store.range): lib.mem.vec(lib.mem.ptr(lib.store.post)) 40 45 var all: lib.mem.vec(lib.mem.ptr(lib.store.post)) all:init(64) 41 46 for i=0,self.sources.ct do var src = self.sources.ptr + i 42 47 if src.handle ~= nil and src.backend.timeline_instance_fetch ~= nil then 43 48 var lst = src:post_enum_author_uid(uid,r) ................................................................................ 66 71 end 67 72 return all 68 73 end 69 74 end 70 75 71 76 deftlfetch('instance_fetch') 72 77 deftlfetch('actor_fetch_uid', uint64) 78 +deftlfetch('circle_fetch', uint64) 73 79 74 80 srv.metamethods.__methodmissing = macro(function(meth, self, ...) 75 81 local primary, ptr, stat, simple, oid = 0,1,2,3,4 76 82 local tk, rt = primary 77 83 local expr = {...} 78 84 for _,f in pairs(lib.store.backend.entries) do 79 85 local fn = f.field or f[1] ................................................................................ 163 169 164 170 struct convo.page { 165 171 title: pstring 166 172 body: pstring 167 173 class: pstring 168 174 cache: bool 169 175 } 176 + 177 +local usrdefs = { 178 + str = { 179 + ['acl-follow' ] = {cfgfld = 'usrdef_pol_follow', fallback = 'local'}; 180 + ['acl-follow-req'] = {cfgfld = 'usrdef_pol_follow_req', fallback = 'all'}; 181 + }; 182 +} 183 + 184 +terra convo:usercfg_str(uid: uint64, setting: pstring): pstring 185 + var set = self.srv:actor_conf_str_get(&self.srv.pool, uid, setting) 186 + if not set then 187 + [(function() 188 + local q = quote return pstring.null() end 189 + for key, dfl in pairs(usrdefs.str) do 190 + local rv 191 + if dfl.cfgfld then 192 + rv = quote 193 + var cf = self.srv.cfg.[dfl.cfgfld] 194 + in terralib.select(not cf, pstring([dfl.fallback]), cf) end 195 + elseif dfl.lit then rv = dfl.lit end 196 + q = quote 197 + if setting:cmp([key]) then return [rv] else [q] end 198 + end 199 + end 200 + return q 201 + end)()] 202 + else return set end 203 +end 170 204 171 205 -- this is unfortunately necessary to work around a terra bug 172 206 -- it can't seem to handle forward-declarations of structs in C 173 207 174 208 local getpeer 175 209 do local struct strucheader { 176 210 next: &lib.net.mg_connection ................................................................................ 1078 1112 self.master = wma(0).id 1079 1113 wma:free() 1080 1114 end 1081 1115 end 1082 1116 1083 1117 self.ui_cue_staff = self.overlord:conf_get('ui-profile-cue-staff') 1084 1118 self.ui_cue_founder = self.overlord:conf_get('ui-profile-cue-master') 1119 + 1120 + self.usrdef_pol_follow = self.overlord:conf_get('user-default-acl-follow') 1121 + self.usrdef_pol_follow_req = self.overlord:conf_get('user-default-acl-follow-req') 1085 1122 end 1086 1123 1087 1124 return { 1088 1125 overlord = srv; 1089 1126 convo = convo; 1090 1127 route = route; 1091 1128 secmode = secmode; 1092 1129 }
Added static/gear.svg version [0623970aec].
1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 +<!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 +<svg 5 + xmlns:dc="http://purl.org/dc/elements/1.1/" 6 + xmlns:cc="http://creativecommons.org/ns#" 7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 + xmlns:svg="http://www.w3.org/2000/svg" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:xlink="http://www.w3.org/1999/xlink" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 13 + width="0.2in" 14 + height="0.2in" 15 + viewBox="0 0 5.0800002 5.0800002" 16 + version="1.1" 17 + id="svg8" 18 + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" 19 + sodipodi:docname="gear.svg"> 20 + <defs 21 + id="defs2"> 22 + <linearGradient 23 + inkscape:collect="always" 24 + id="linearGradient863"> 25 + <stop 26 + style="stop-color:#ffffff;stop-opacity:0.5518868" 27 + offset="0" 28 + id="stop859" /> 29 + <stop 30 + style="stop-color:#ffffff;stop-opacity:0;" 31 + offset="1" 32 + id="stop861" /> 33 + </linearGradient> 34 + <radialGradient 35 + inkscape:collect="always" 36 + xlink:href="#linearGradient863" 37 + id="radialGradient865" 38 + cx="2.5399754" 39 + cy="295.73944" 40 + fx="2.5399754" 41 + fy="295.91135" 42 + r="1.6620824" 43 + gradientTransform="matrix(2.9231276,0,0,2.7982783,-11.452033,-531.55554)" 44 + gradientUnits="userSpaceOnUse" /> 45 + <radialGradient 46 + inkscape:collect="always" 47 + xlink:href="#linearGradient863" 48 + id="radialGradient865-3" 49 + cx="2.5399754" 50 + cy="295.73944" 51 + fx="2.5399754" 52 + fy="295.91135" 53 + r="1.6620824" 54 + gradientTransform="matrix(11.048041,0,0,10.57617,-21.779867,-3114.7234)" 55 + gradientUnits="userSpaceOnUse" /> 56 + <radialGradient 57 + inkscape:collect="always" 58 + xlink:href="#linearGradient863" 59 + id="radialGradient980" 60 + gradientUnits="userSpaceOnUse" 61 + gradientTransform="matrix(2.9231276,0,0,2.7982783,-11.452033,-531.55554)" 62 + cx="4.5320868" 63 + cy="295.40598" 64 + fx="4.5320868" 65 + fy="295.57788" 66 + r="1.6620824" /> 67 + <radialGradient 68 + inkscape:collect="always" 69 + xlink:href="#linearGradient863" 70 + id="radialGradient980-9" 71 + gradientUnits="userSpaceOnUse" 72 + gradientTransform="matrix(11.048041,0,0,10.57617,-45.140697,-3114.3717)" 73 + cx="4.5320868" 74 + cy="295.40598" 75 + fx="4.5320868" 76 + fy="295.57788" 77 + r="1.6620824" /> 78 + <radialGradient 79 + inkscape:collect="always" 80 + xlink:href="#linearGradient863" 81 + id="radialGradient1008" 82 + gradientUnits="userSpaceOnUse" 83 + gradientTransform="matrix(2.9231276,0,0,2.7982783,-11.452033,-531.55554)" 84 + cx="5.1548281" 85 + cy="294.85077" 86 + fx="5.1548281" 87 + fy="295.02267" 88 + r="1.6620824" /> 89 + <radialGradient 90 + inkscape:collect="always" 91 + xlink:href="#linearGradient863" 92 + id="radialGradient1028" 93 + gradientUnits="userSpaceOnUse" 94 + gradientTransform="matrix(2.7672014,0,0,2.6490119,-10.744057,-487.4606)" 95 + cx="4.5320868" 96 + cy="295.40598" 97 + fx="4.5320868" 98 + fy="295.57788" 99 + r="1.6620824" /> 100 + <radialGradient 101 + inkscape:collect="always" 102 + xlink:href="#linearGradient863" 103 + id="radialGradient1031" 104 + gradientUnits="userSpaceOnUse" 105 + gradientTransform="matrix(11.048041,0,0,10.57617,-43.283274,-3112.3484)" 106 + cx="4.5320868" 107 + cy="295.40598" 108 + fx="4.5320868" 109 + fy="295.57788" 110 + r="1.6620824" /> 111 + </defs> 112 + <sodipodi:namedview 113 + id="base" 114 + pagecolor="#2f000f" 115 + bordercolor="#666666" 116 + borderopacity="1.0" 117 + inkscape:pageopacity="0" 118 + inkscape:pageshadow="2" 119 + inkscape:zoom="11.2" 120 + inkscape:cx="5.6507928" 121 + inkscape:cy="22.571234" 122 + inkscape:document-units="mm" 123 + inkscape:current-layer="layer1" 124 + showgrid="false" 125 + units="in" 126 + inkscape:window-width="1920" 127 + inkscape:window-height="1042" 128 + inkscape:window-x="0" 129 + inkscape:window-y="38" 130 + inkscape:window-maximized="0" /> 131 + <metadata 132 + id="metadata5"> 133 + <rdf:RDF> 134 + <cc:Work 135 + rdf:about=""> 136 + <dc:format>image/svg+xml</dc:format> 137 + <dc:type 138 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 139 + <dc:title></dc:title> 140 + </cc:Work> 141 + </rdf:RDF> 142 + </metadata> 143 + <g 144 + inkscape:label="Layer 1" 145 + inkscape:groupmode="layer" 146 + id="layer1" 147 + transform="translate(0,-291.91998)"> 148 + <path 149 + style="opacity:1;vector-effect:none;fill:url(#radialGradient1031);fill-opacity:1;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 150 + d="M 12.113281 2.0234375 A 4.1239589 4.1239589 0 0 0 11.5625 2.1816406 A 4.1239589 4.1239589 0 0 0 11.044922 2.4238281 L 11.435547 3.4628906 A 3.0271613 3.0271613 0 0 0 10.544922 4.2949219 L 9.5332031 3.8359375 A 4.1239589 4.1239589 0 0 0 9.0625 4.875 L 10.068359 5.3320312 A 3.0271613 3.0271613 0 0 0 10.029297 6.5527344 L 8.9921875 6.9414062 A 4.1239589 4.1239589 0 0 0 9.1503906 7.4921875 A 4.1239589 4.1239589 0 0 0 9.3925781 8.0117188 L 10.429688 7.6210938 A 3.0271613 3.0271613 0 0 0 11.263672 8.5117188 L 10.804688 9.5234375 A 4.1239589 4.1239589 0 0 0 11.84375 9.9921875 L 12.302734 8.984375 A 3.0271613 3.0271613 0 0 0 13.521484 9.0253906 L 13.910156 10.0625 A 4.1239589 4.1239589 0 0 0 14.458984 9.90625 A 4.1239589 4.1239589 0 0 0 14.978516 9.6640625 L 14.587891 8.625 A 3.0271613 3.0271613 0 0 0 15.478516 7.7910156 L 16.490234 8.2519531 A 4.1239589 4.1239589 0 0 0 16.958984 7.2109375 L 15.953125 6.7539062 A 3.0271613 3.0271613 0 0 0 15.994141 5.5351562 L 17.03125 5.1464844 A 4.1239589 4.1239589 0 0 0 16.875 4.5957031 A 4.1239589 4.1239589 0 0 0 16.630859 4.0761719 L 15.591797 4.4667969 A 3.0271613 3.0271613 0 0 0 14.759766 3.5761719 L 15.21875 2.5664062 A 4.1239589 4.1239589 0 0 0 14.179688 2.09375 L 13.720703 3.1015625 A 3.0271613 3.0271613 0 0 0 12.501953 3.0605469 L 12.113281 2.0234375 z M 13.074219 4.421875 A 1.6230732 1.5835511 84.711522 0 1 14.494141 5.4765625 A 1.6230732 1.5835511 84.711522 0 1 13.572266 7.5644531 A 1.6230732 1.5835511 84.711522 0 1 11.529297 6.609375 A 1.6230732 1.5835511 84.711522 0 1 12.453125 4.5214844 A 1.6230732 1.5835511 84.711522 0 1 12.916016 4.4238281 A 1.6230732 1.5835511 84.711522 0 1 13.074219 4.421875 z " 151 + transform="matrix(0.26458333,0,0,0.26458333,0,291.91998)" 152 + id="path1024" /> 153 + <path 154 + style="opacity:1;vector-effect:none;fill:url(#radialGradient1028);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 155 + d="m 1.8205469,293.82898 a 1.2578638,1.2578638 0 0 0 -0.1824712,0.0142 v 0.29596 a 0.96596175,0.96596175 0 0 0 -0.359072,0.14921 l -0.2093772,-0.20938 a 1.2578638,1.2578638 0 0 0 -0.25731862,0.25781 l 0.20839872,0.2084 a 0.96596175,0.96596175 0 0 0 -0.14871646,0.35956 H 0.57749238 a 1.2578638,1.2578638 0 0 0 -0.0151652,0.18198 1.2578638,1.2578638 0 0 0 0.014676,0.18247 h 0.29498693 a 0.96596175,0.96596175 0 0 0 0.14920569,0.35956 l -0.20839876,0.20791 a 1.2578638,1.2578638 0 0 0 0.25731866,0.25781 l 0.2083988,-0.2084 a 0.96596175,0.96596175 0 0 0 0.3595612,0.14921 v 0.2945 a 1.2578638,1.2578638 0 0 0 0.1824712,0.0147 1.2578638,1.2578638 0 0 0 0.1814929,-0.0142 v -0.29547 a 0.96596175,0.96596175 0 0 0 0.3595612,-0.14872 l 0.2088879,0.20889 a 1.2578638,1.2578638 0 0 0 0.2573188,-0.25732 l -0.208399,-0.2084 a 0.96596175,0.96596175 0 0 0 0.1487165,-0.36005 h 0.2954763 a 1.2578638,1.2578638 0 0 0 0.014675,-0.18247 1.2578638,1.2578638 0 0 0 -0.014187,-0.18198 H 2.7681252 a 0.96596175,0.96596175 0 0 0 -0.1487165,-0.35907 l 0.2088881,-0.20938 a 1.2578638,1.2578638 0 0 0 -0.2578079,-0.25732 l -0.2083987,0.2084 a 0.96596175,0.96596175 0 0 0 -0.3600504,-0.14872 v -0.29498 a 1.2578638,1.2578638 0 0 0 -0.1814929,-0.0147 z m 0,0.67656 a 0.58097694,0.58097694 0 0 1 0.5806792,0.58117 0.58097694,0.58097694 0 0 1 -0.5806792,0.58068 0.58097694,0.58097694 0 0 1 -0.5811683,-0.58068 0.58097694,0.58097694 0 0 1 0.5811683,-0.58117 z" 156 + id="path1020" 157 + inkscape:connector-curvature="0" /> 158 + <path 159 + id="circle917" 160 + style="opacity:1;vector-effect:none;fill:url(#radialGradient1008);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;font-variant-east_asian:normal" 161 + d="m 3.6161875,293.51895 a 0.1735429,0.1735429 0 0 1 -0.1735429,0.17354 0.1735429,0.1735429 0 0 1 -0.1735429,-0.17354 0.1735429,0.1735429 0 0 1 0.1735429,-0.17354 0.1735429,0.1735429 0 0 1 0.1735429,0.17354 z m -1.5573776,1.56775 a 0.23850755,0.23850755 0 0 1 -0.2385075,0.23851 0.23850755,0.23850755 0 0 1 -0.2385076,-0.23851 0.23850755,0.23850755 0 0 1 0.2385076,-0.23851 0.23850755,0.23850755 0 0 1 0.2385075,0.23851 z" /> 162 + </g> 163 +</svg>
Added static/logo.svg version [0ada266140].
1 +<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 +<!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 +<svg 5 + xmlns:dc="http://purl.org/dc/elements/1.1/" 6 + xmlns:cc="http://creativecommons.org/ns#" 7 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 8 + xmlns:svg="http://www.w3.org/2000/svg" 9 + xmlns="http://www.w3.org/2000/svg" 10 + xmlns:xlink="http://www.w3.org/1999/xlink" 11 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 12 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 13 + width="1in" 14 + height="1in" 15 + viewBox="0 0 25.4 25.400001" 16 + version="1.1" 17 + id="svg8" 18 + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" 19 + sodipodi:docname="logo.svg"> 20 + <defs 21 + id="defs2"> 22 + <linearGradient 23 + id="linearGradient920" 24 + inkscape:collect="always"> 25 + <stop 26 + id="stop916" 27 + offset="0" 28 + style="stop-color:#f8bac8;stop-opacity:1" /> 29 + <stop 30 + id="stop918" 31 + offset="1" 32 + style="stop-color:#ea2b55;stop-opacity:0" /> 33 + </linearGradient> 34 + <linearGradient 35 + id="linearGradient3597" 36 + inkscape:collect="always"> 37 + <stop 38 + id="stop3593" 39 + offset="0" 40 + style="stop-color:#bc0087;stop-opacity:1" /> 41 + <stop 42 + id="stop3595" 43 + offset="1" 44 + style="stop-color:#6f004e;stop-opacity:1" /> 45 + </linearGradient> 46 + <marker 47 + inkscape:stockid="Arrow1Lstart" 48 + orient="auto" 49 + refY="0.0" 50 + refX="0.0" 51 + id="marker3321" 52 + style="overflow:visible" 53 + inkscape:isstock="true"> 54 + <path 55 + id="path3319" 56 + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " 57 + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1" 58 + transform="scale(0.8) translate(12.5,0)" /> 59 + </marker> 60 + <marker 61 + inkscape:stockid="Arrow1Lstart" 62 + orient="auto" 63 + refY="0.0" 64 + refX="0.0" 65 + id="marker3281" 66 + style="overflow:visible" 67 + inkscape:isstock="true"> 68 + <path 69 + id="path3279" 70 + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " 71 + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1" 72 + transform="scale(0.8) translate(12.5,0)" /> 73 + </marker> 74 + <marker 75 + inkscape:stockid="Arrow1Lstart" 76 + orient="auto" 77 + refY="0.0" 78 + refX="0.0" 79 + id="marker3247" 80 + style="overflow:visible" 81 + inkscape:isstock="true"> 82 + <path 83 + id="path3245" 84 + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " 85 + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1" 86 + transform="scale(0.8) translate(12.5,0)" /> 87 + </marker> 88 + <marker 89 + inkscape:stockid="Arrow1Lstart" 90 + orient="auto" 91 + refY="0.0" 92 + refX="0.0" 93 + id="marker3219" 94 + style="overflow:visible" 95 + inkscape:isstock="true"> 96 + <path 97 + id="path3217" 98 + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " 99 + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1" 100 + transform="scale(0.8) translate(12.5,0)" /> 101 + </marker> 102 + <marker 103 + inkscape:stockid="Arrow1Lstart" 104 + orient="auto" 105 + refY="0.0" 106 + refX="0.0" 107 + id="marker3197" 108 + style="overflow:visible" 109 + inkscape:isstock="true"> 110 + <path 111 + id="path3195" 112 + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " 113 + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1" 114 + transform="scale(0.8) translate(12.5,0)" /> 115 + </marker> 116 + <marker 117 + inkscape:stockid="Arrow1Lstart" 118 + orient="auto" 119 + refY="0.0" 120 + refX="0.0" 121 + id="Arrow1Lstart" 122 + style="overflow:visible" 123 + inkscape:isstock="true"> 124 + <path 125 + id="path2919" 126 + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " 127 + style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#5e003e;fill-opacity:1" 128 + transform="scale(0.8) translate(12.5,0)" /> 129 + </marker> 130 + <linearGradient 131 + inkscape:collect="always" 132 + id="linearGradient2907"> 133 + <stop 134 + style="stop-color:#4d003a;stop-opacity:0.12264151" 135 + offset="0" 136 + id="stop2903" /> 137 + <stop 138 + style="stop-color:#4d003a;stop-opacity:0.91509432" 139 + offset="1" 140 + id="stop2905" /> 141 + </linearGradient> 142 + <linearGradient 143 + id="linearGradient2893" 144 + inkscape:collect="always"> 145 + <stop 146 + id="stop2889" 147 + offset="0" 148 + style="stop-color:#e00092;stop-opacity:1;" /> 149 + <stop 150 + id="stop2891" 151 + offset="1" 152 + style="stop-color:#e00092;stop-opacity:0.01886792" /> 153 + </linearGradient> 154 + <linearGradient 155 + inkscape:collect="always" 156 + id="linearGradient1775"> 157 + <stop 158 + style="stop-color:#fef5f7;stop-opacity:1;" 159 + offset="0" 160 + id="stop1771" /> 161 + <stop 162 + style="stop-color:#ea2b55;stop-opacity:0" 163 + offset="1" 164 + id="stop1773" /> 165 + </linearGradient> 166 + <linearGradient 167 + inkscape:collect="always" 168 + id="linearGradient929"> 169 + <stop 170 + style="stop-color:#e00092;stop-opacity:1;" 171 + offset="0" 172 + id="stop925" /> 173 + <stop 174 + style="stop-color:#e00092;stop-opacity:0" 175 + offset="1" 176 + id="stop927" /> 177 + </linearGradient> 178 + <linearGradient 179 + inkscape:collect="always" 180 + id="linearGradient871"> 181 + <stop 182 + style="stop-color:#fff8fd;stop-opacity:1;" 183 + offset="0" 184 + id="stop867" /> 185 + <stop 186 + style="stop-color:#ff85dc;stop-opacity:1" 187 + offset="1" 188 + id="stop869" /> 189 + </linearGradient> 190 + <linearGradient 191 + inkscape:collect="always" 192 + id="linearGradient863"> 193 + <stop 194 + style="stop-color:#ff0ebb;stop-opacity:1" 195 + offset="0" 196 + id="stop859" /> 197 + <stop 198 + style="stop-color:#98006b;stop-opacity:1" 199 + offset="1" 200 + id="stop861" /> 201 + </linearGradient> 202 + <linearGradient 203 + inkscape:collect="always" 204 + id="linearGradient855"> 205 + <stop 206 + style="stop-color:#ff1ab2;stop-opacity:1" 207 + offset="0" 208 + id="stop851" /> 209 + <stop 210 + style="stop-color:#5e003e;stop-opacity:1" 211 + offset="1" 212 + id="stop853" /> 213 + </linearGradient> 214 + <linearGradient 215 + inkscape:collect="always" 216 + xlink:href="#linearGradient855" 217 + id="linearGradient857" 218 + x1="204.78238" 219 + y1="192.05012" 220 + x2="215.23915" 221 + y2="192.05012" 222 + gradientUnits="userSpaceOnUse" 223 + gradientTransform="matrix(1.1660945,0,0,1.1660945,-34.675674,-31.692532)" /> 224 + <radialGradient 225 + inkscape:collect="always" 226 + xlink:href="#linearGradient871" 227 + id="radialGradient873" 228 + cx="12.700097" 229 + cy="284.29999" 230 + fx="12.700097" 231 + fy="284.29999" 232 + r="4.7926919" 233 + gradientTransform="matrix(1.5582358,0,0,1.558162,-7.0897352,-158.99167)" 234 + gradientUnits="userSpaceOnUse" /> 235 + <linearGradient 236 + inkscape:collect="always" 237 + xlink:href="#linearGradient2893" 238 + id="linearGradient931" 239 + x1="10.734375" 240 + y1="68.553711" 241 + x2="74.320656" 242 + y2="68.553711" 243 + gradientUnits="userSpaceOnUse" 244 + gradientTransform="matrix(0.30281136,0,0,0.30279703,-1.8350659,269.43243)" /> 245 + <linearGradient 246 + inkscape:collect="always" 247 + xlink:href="#linearGradient929" 248 + id="linearGradient935" 249 + gradientUnits="userSpaceOnUse" 250 + gradientTransform="matrix(-0.30281136,0,0,-0.30279703,27.235067,299.16751)" 251 + x1="10.734375" 252 + y1="68.553711" 253 + x2="72.836235" 254 + y2="68.553711" /> 255 + <radialGradient 256 + inkscape:collect="always" 257 + xlink:href="#linearGradient1775" 258 + id="radialGradient1777" 259 + cx="35.70248" 260 + cy="275.61121" 261 + fx="35.70248" 262 + fy="275.61121" 263 + r="5.0648633" 264 + gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)" 265 + gradientUnits="userSpaceOnUse" /> 266 + <radialGradient 267 + inkscape:collect="always" 268 + xlink:href="#linearGradient1775" 269 + id="radialGradient1785" 270 + cx="54.650433" 271 + cy="276.4209" 272 + fx="55.498787" 273 + fy="276.4209" 274 + r="5.0648627" 275 + gradientTransform="matrix(-2.5056647,-4.662863e-8,0,-0.4043156,191.5861,387.46216)" 276 + gradientUnits="userSpaceOnUse" /> 277 + <radialGradient 278 + inkscape:collect="always" 279 + xlink:href="#linearGradient1775" 280 + id="radialGradient1787" 281 + gradientUnits="userSpaceOnUse" 282 + gradientTransform="matrix(-2.5354014,-4.7182009e-8,0,-0.40911394,193.02586,388.9687)" 283 + cx="54.650433" 284 + cy="276.4209" 285 + fx="55.498787" 286 + fy="276.4209" 287 + r="5.0648627" /> 288 + <radialGradient 289 + inkscape:collect="always" 290 + xlink:href="#linearGradient1775" 291 + id="radialGradient1789" 292 + gradientUnits="userSpaceOnUse" 293 + gradientTransform="matrix(-2.5354014,-4.7182009e-8,0,-0.40911394,193.02586,388.9687)" 294 + cx="54.650433" 295 + cy="276.4209" 296 + fx="55.498787" 297 + fy="276.4209" 298 + r="5.0648627" /> 299 + <radialGradient 300 + inkscape:collect="always" 301 + xlink:href="#linearGradient1775" 302 + id="radialGradient1791" 303 + gradientUnits="userSpaceOnUse" 304 + gradientTransform="matrix(-2.5056647,-4.662863e-8,0,-0.4043156,191.5861,387.46216)" 305 + cx="54.650433" 306 + cy="276.4209" 307 + fx="55.498787" 308 + fy="276.4209" 309 + r="5.0648627" /> 310 + <radialGradient 311 + inkscape:collect="always" 312 + xlink:href="#linearGradient1775" 313 + id="radialGradient1793" 314 + gradientUnits="userSpaceOnUse" 315 + gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)" 316 + cx="35.70248" 317 + cy="275.61121" 318 + fx="35.70248" 319 + fy="275.61121" 320 + r="5.0648633" /> 321 + <radialGradient 322 + inkscape:collect="always" 323 + xlink:href="#linearGradient1775" 324 + id="radialGradient1795" 325 + gradientUnits="userSpaceOnUse" 326 + gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)" 327 + cx="35.70248" 328 + cy="275.61121" 329 + fx="35.70248" 330 + fy="275.61121" 331 + r="5.0648633" /> 332 + <radialGradient 333 + inkscape:collect="always" 334 + xlink:href="#linearGradient1775" 335 + id="radialGradient1797" 336 + gradientUnits="userSpaceOnUse" 337 + gradientTransform="matrix(2.6183476,-4.2817295e-8,0,0.33968294,-59.424913,181.99078)" 338 + cx="35.70248" 339 + cy="275.61121" 340 + fx="35.70248" 341 + fy="275.61121" 342 + r="5.0648633" /> 343 + <linearGradient 344 + inkscape:collect="always" 345 + xlink:href="#linearGradient855" 346 + id="linearGradient1805" 347 + gradientUnits="userSpaceOnUse" 348 + gradientTransform="matrix(1.1660945,0,0,1.1660945,-68.579304,2.5050309)" 349 + x1="204.78238" 350 + y1="192.05012" 351 + x2="215.23915" 352 + y2="192.05012" /> 353 + <linearGradient 354 + inkscape:collect="always" 355 + xlink:href="#linearGradient863" 356 + id="linearGradient1807" 357 + gradientUnits="userSpaceOnUse" 358 + gradientTransform="matrix(1.1660945,0,0,1.1660945,-50.264227,-46.989234)" 359 + x1="20.094147" 360 + y1="284.52951" 361 + x2="12.70009" 362 + y2="277.13544" /> 363 + <linearGradient 364 + inkscape:collect="always" 365 + xlink:href="#linearGradient855" 366 + id="linearGradient1812" 367 + gradientUnits="userSpaceOnUse" 368 + gradientTransform="matrix(0.94368798,0.94364332,-0.94368798,0.94364332,-4.2492401,-95.102051)" 369 + x1="204.78238" 370 + y1="192.05012" 371 + x2="215.23915" 372 + y2="192.05012" /> 373 + <radialGradient 374 + inkscape:collect="always" 375 + xlink:href="#linearGradient1775" 376 + id="radialGradient2879" 377 + gradientUnits="userSpaceOnUse" 378 + gradientTransform="matrix(-2.5056647,-4.662863e-8,0,-0.4043156,191.5861,387.46216)" 379 + cx="54.650433" 380 + cy="276.4209" 381 + fx="55.498787" 382 + fy="276.4209" 383 + r="5.0648627" /> 384 + <radialGradient 385 + inkscape:collect="always" 386 + xlink:href="#linearGradient1775" 387 + id="radialGradient2901" 388 + gradientUnits="userSpaceOnUse" 389 + gradientTransform="matrix(-2.5354014,-4.7182009e-8,0,-0.40911394,193.02586,388.9687)" 390 + cx="54.650433" 391 + cy="276.4209" 392 + fx="55.498787" 393 + fy="276.4209" 394 + r="5.0648627" /> 395 + <radialGradient 396 + inkscape:collect="always" 397 + xlink:href="#linearGradient2907" 398 + id="radialGradient2909" 399 + cx="210.21639" 400 + cy="192.25669" 401 + fx="210.21639" 402 + fy="192.25669" 403 + r="5.949986" 404 + gradientTransform="matrix(1.1727006,0,0,1.1727006,-36.505524,-33.40402)" 405 + gradientUnits="userSpaceOnUse" /> 406 + <linearGradient 407 + inkscape:collect="always" 408 + xlink:href="#linearGradient3597" 409 + id="linearGradient2917" 410 + x1="15.985148" 411 + y1="289.71997" 412 + x2="7.5198202" 413 + y2="281.35651" 414 + gradientUnits="userSpaceOnUse" 415 + gradientTransform="matrix(1.1727283,0,0,1.1726728,-2.1931076,-49.432382)" /> 416 + <radialGradient 417 + inkscape:collect="always" 418 + xlink:href="#linearGradient1775" 419 + id="radialGradient904" 420 + cx="203.03343" 421 + cy="185.0731" 422 + fx="203.03343" 423 + fy="185.0731" 424 + r="7.9787183" 425 + gradientUnits="userSpaceOnUse" 426 + gradientTransform="matrix(1.484981,0,0,1.484981,-99.129567,-90.419502)" /> 427 + <radialGradient 428 + inkscape:collect="always" 429 + xlink:href="#linearGradient920" 430 + id="radialGradient914" 431 + gradientUnits="userSpaceOnUse" 432 + gradientTransform="matrix(1.484981,0,0,1.484981,-501.1906,-492.48021)" 433 + cx="203.03343" 434 + cy="185.0731" 435 + fx="203.03343" 436 + fy="185.0731" 437 + r="7.9787183" /> 438 + </defs> 439 + <sodipodi:namedview 440 + id="base" 441 + pagecolor="#48032c" 442 + bordercolor="#666666" 443 + borderopacity="1.0" 444 + inkscape:pageopacity="0" 445 + inkscape:pageshadow="2" 446 + inkscape:zoom="4.0000001" 447 + inkscape:cx="52.332667" 448 + inkscape:cy="84.328551" 449 + inkscape:document-units="mm" 450 + inkscape:current-layer="layer1" 451 + showgrid="false" 452 + units="in" 453 + inkscape:window-width="1920" 454 + inkscape:window-height="1042" 455 + inkscape:window-x="0" 456 + inkscape:window-y="38" 457 + inkscape:window-maximized="0" 458 + inkscape:snap-intersection-paths="true" 459 + inkscape:object-paths="true" 460 + inkscape:snap-global="true" /> 461 + <metadata 462 + id="metadata5"> 463 + <rdf:RDF> 464 + <cc:Work 465 + rdf:about=""> 466 + <dc:format>image/svg+xml</dc:format> 467 + <dc:type 468 + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> 469 + <dc:title /> 470 + </cc:Work> 471 + </rdf:RDF> 472 + </metadata> 473 + <g 474 + inkscape:label="Layer 1" 475 + inkscape:groupmode="layer" 476 + id="layer1" 477 + transform="translate(0,-271.59998)"> 478 + <rect 479 + transform="matrix(0.70710678,-0.70710678,-0.70710678,-0.70710678,0,0)" 480 + y="-217.65018" 481 + x="-199.68983" 482 + height="15.957437" 483 + width="15.957437" 484 + id="rect912" 485 + style="opacity:1;vector-effect:none;fill:url(#radialGradient914);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> 486 + <rect 487 + style="opacity:1;vector-effect:none;fill:url(#linearGradient1805);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 488 + id="rect1801" 489 + width="12.193584" 490 + height="12.193584" 491 + x="170.21625" 492 + y="220.35686" 493 + transform="rotate(45)" /> 494 + <path 495 + inkscape:connector-curvature="0" 496 + id="path1803" 497 + style="fill:none;stroke:url(#linearGradient1807);stroke-width:2.0650003;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" 498 + d="m -35.454712,279.49101 v 10.08081 m 2e-6,-10.08081 5.040403,5.04041 -5.040403,5.0404 -5.040405,-5.0404 z" /> 499 + <path 500 + id="path1693" 501 + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 502 + d="m 34.893237,267.49632 v -3.19965 m -1.685,-0.0372 h 3.369999 v 3.27405 h -3.369999 z" /> 503 + <path 504 + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 505 + d="m 37.184806,267.53352 v -3.27405 h 3.369999 v 3.27405" 506 + id="rect1709" 507 + inkscape:connector-curvature="0" 508 + sodipodi:nodetypes="cccc" /> 509 + <path 510 + id="path1717" 511 + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 512 + d="m 41.180522,265.89649 h 3.331704 m 0.01915,-1.63702 v 3.27405 h -3.369999 v -3.27405" /> 513 + <path 514 + id="path1723" 515 + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 516 + d="m 45.157089,265.89649 h 3.331704 m -3.350851,1.63703 v -3.27405 h 3.369999 v 3.27405" /> 517 + <path 518 + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 519 + d="m 49.11451,267.53352 v -3.27405 h 3.369999 v 3.27405" 520 + id="rect1727" 521 + inkscape:connector-curvature="0" 522 + sodipodi:nodetypes="cccc" /> 523 + <path 524 + id="path1737" 525 + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#fef5f7;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 526 + d="m 54.776077,267.49632 v -3.19965 m -1.684997,3.23685 v -3.27405 h 3.369999 v 3.27405" /> 527 + <rect 528 + style="opacity:1;vector-effect:none;fill:url(#radialGradient2909);fill-opacity:1;stroke:none;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 529 + id="rect2877" 530 + width="13.955104" 531 + height="13.955101" 532 + x="203.03781" 533 + y="185.07791" 534 + transform="matrix(0.7071235,0.70709006,-0.7071235,0.70709006,0,0)" /> 535 + <path 536 + style="opacity:1;vector-effect:none;fill:url(#linearGradient1812);fill-opacity:1;stroke:url(#linearGradient2917);stroke-width:0.07055556;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 537 + d="m 12.699879,274.43273 -9.8679832,9.86752 9.8679832,9.86692 9.867983,-9.86692 z m -0.0012,2.12077 7.441943,7.441 -7.441944,7.43864 -7.4389871,-7.43864 6.6056641,-6.60535 z m -1.179899,4.52421 -2.9169251,2.91679 2.9169251,2.91679 z m 2.362757,0 v 5.83121 l 2.916924,-2.91442 z" 538 + id="rect836" 539 + inkscape:connector-curvature="0" /> 540 + <path 541 + id="path845" 542 + style="fill:none;stroke:url(#radialGradient873);stroke-width:0.41804168;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" 543 + d="m 12.700008,278.22531 v 11.53678 m 3e-6,-11.53678 5.768659,5.76839 -5.768659,5.76839 -5.7686618,-5.76839 z" 544 + inkscape:connector-curvature="0" /> 545 + <path 546 + style="opacity:1;vector-effect:none;fill:url(#linearGradient931);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 547 + d="M 24.425539,283.38063 12.699879,295.10395 2.3593445,284.77218 1.4154249,285.71666 12.699879,296.99998 25.370051,284.32449 Z" 548 + id="path883" 549 + inkscape:connector-curvature="0" 550 + sodipodi:nodetypes="ccccccc" /> 551 + <path 552 + sodipodi:nodetypes="ccccccc" 553 + inkscape:connector-curvature="0" 554 + id="path933" 555 + d="M 0.9744608,285.21933 12.700121,273.496 23.040656,283.82778 23.984576,282.8833 12.700121,271.59998 0.0299491,284.27546 Z" 556 + style="opacity:1;vector-effect:none;fill:url(#linearGradient935);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> 557 + <g 558 + id="g1764" 559 + transform="matrix(0.50352276,-0.50349892,0.50352276,0.50349892,-154.92304,162.62301)" 560 + style="stroke:url(#radialGradient1777);stroke-width:0.28087056;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none"> 561 + <path 562 + inkscape:connector-curvature="0" 563 + id="path1749" 564 + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:url(#radialGradient1793);stroke-width:0.28087056;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 565 + d="m 34.056591,276.36215 v -1.50187 m -0.848355,-0.0175 h 1.696711 v 1.5368 h -1.696711 z" /> 566 + <path 567 + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:url(#radialGradient1795);stroke-width:0.28087056;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 568 + d="m 37.327005,276.37961 v -1.5368 h 1.69671 v 1.5368" 569 + id="path1751" 570 + inkscape:connector-curvature="0" 571 + sodipodi:nodetypes="cccc" /> 572 + <path 573 + inkscape:connector-curvature="0" 574 + id="path1753" 575 + style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:url(#radialGradient1797);stroke-width:0.28087056;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 576 + d="m 41.455454,275.61121 h 1.677431 m 0.0096,-0.7684 v 1.5368 h -1.696711 v -1.5368" /> 577 + </g> 578 + <g 579 + id="g1769" 580 + transform="matrix(0.4999787,-0.49995505,0.4999787,0.49995505,-140.73822,173.71849)" 581 + style="stroke:url(#radialGradient2879);stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none"> 582 + <path 583 + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#radialGradient1787);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" 584 + d="m 45.103283,274.84576 v 1.72137 0.16798 h 0.335972 v -0.16798 -0.60871 h 1.381436 v 0.60871 0.16798 h 0.335972 v -0.16798 -1.72137 z m 0.335972,0.334 h 1.381436 v 0.44269 h -1.381436 z" 585 + id="path1755" 586 + inkscape:connector-curvature="0" /> 587 + <path 588 + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#radialGradient1789);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" 589 + d="m 49.271309,274.84576 v 1.72137 0.16798 h 0.335971 v -0.16798 -1.38737 h 1.381437 v 1.38737 0.16798 h 0.333996 v -0.16798 -1.72137 z" 590 + id="path1757" 591 + inkscape:connector-curvature="0" /> 592 + <path 593 + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#radialGradient2901);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28286147;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" 594 + d="m 53.439335,274.84576 v 1.72137 0.16798 h 0.335971 v -0.16798 -1.38737 h 0.521745 v 1.36958 0.16798 h 0.335971 v -0.16798 -1.36958 h 0.521744 v 1.38737 0.16798 h 0.335972 v -0.16798 -1.72137 z" 595 + id="path1759" 596 + inkscape:connector-curvature="0" /> 597 + </g> 598 + <rect 599 + style="opacity:1;vector-effect:none;fill:url(#radialGradient904);fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" 600 + id="rect896" 601 + width="15.957437" 602 + height="15.957437" 603 + x="202.37122" 604 + y="184.41054" 605 + transform="rotate(45)" /> 606 + </g> 607 +</svg>
Modified static/style.scss from [d08e77f56c] to [2db0a5b279].
33 33 text-underline-offset: 0.1em; 34 34 &:hover, &:focus { 35 35 color: white; 36 36 text-shadow: 0 0 15px tone(20%); 37 37 text-decoration-color: tone(10%,-0.1); 38 38 outline: none; 39 39 } 40 + u { 41 + text-decoration-color: tone(10%,-0.3); 42 + } 40 43 } 41 44 a[href^="//"], 42 45 a[href^="http://"], 43 46 a[href^="https://"] { // external link 44 47 &:hover::after { 45 48 color: black; 46 49 background-color: white; ................................................................................ 70 73 71 74 .button, a[href].button { // 🙄 72 75 @extend %sans; 73 76 font-size: 14pt; 74 77 box-sizing: border-box; 75 78 padding: 0.1in 0.2in; 76 79 border: 1px solid black; 80 + border-bottom: 2px solid black; 77 81 color: otone(25%); 78 82 text-shadow: 1px 1px black; 79 83 text-decoration: none; 80 84 text-align: center; 81 85 cursor: default; 82 86 user-select: none; 83 87 -webkit-user-drag: none; 84 88 -webkit-app-region: no-drag; 85 89 --icon: url(/s/heart.webp); 86 90 background-image: linear-gradient(to bottom, 87 - otone(-47%), 88 - otone(-50%) 15%, 89 - otone(-50%) 75%, 90 - otone(-53%) 91 + otone(-41%), 92 + otone(-43%) 15%, 93 + otone(-46%) 75%, 94 + otone(-50%) 91 95 ); 92 96 &:hover, &:focus { 93 97 @extend %glow; 94 98 outline: none; 95 99 color: tone(-55%); 96 100 text-shadow: none; 97 101 background: linear-gradient(to bottom, 98 102 otone(-27%), 99 103 otone(-30%) 15%, 100 - otone(-30%) 75%, 104 + otone(-32%) 75%, 101 105 otone(-35%) 102 106 ); 103 107 } 104 108 &:active { 105 109 color: black; 106 110 padding-bottom: calc(0.1in - 2px); 107 111 padding-top: calc(0.1in + 2px); 112 + border: 1px solid black; 113 + border-top: 2px solid black; 108 114 background: linear-gradient(to top, 109 115 otone(-25%), 110 116 otone(-30%) 15%, 111 - otone(-30%) 75%, 117 + otone(-32%) 75%, 112 118 otone(-35%) 113 119 ); 114 120 } 115 121 } 116 122 117 123 button { @extend .button; 118 - &:first-of-type { 124 + form > &:first-of-type, menu > &:first-of-type { 119 125 @extend .button; 120 126 color: white; 121 127 box-shadow: inset 0 1px otone(-25%), 122 128 inset 0 -1px otone(-50%); 123 129 background: linear-gradient(to bottom, 124 130 otone(-35%), 125 131 otone(-40%) 15%, 126 - otone(-40%) 75%, 132 + otone(-43%) 75%, 127 133 otone(-45%) 128 134 ); 129 135 &:hover, &:focus { 130 136 box-shadow: inset 0 1px otone(-15%), 131 137 inset 0 -1px otone(-40%); 132 138 } 133 139 &:active { 134 140 box-shadow: inset 0 1px otone(-50%), 135 141 inset 0 -1px otone(-25%); 136 142 background: linear-gradient(to top, 137 143 otone(-30%), 138 144 otone(-35%) 15%, 139 - otone(-35%) 75%, 145 + otone(-38%) 75%, 140 146 otone(-40%) 141 147 ); 142 148 } 143 149 } 144 150 //&:hover { font-weight: bold; } 145 151 } 146 152 ................................................................................ 209 215 } 210 216 nav { 211 217 all: unset; 212 218 display: flex; 213 219 justify-content: flex-end; 214 220 align-items: center; 215 221 grid-column: 2/3; grid-row: 1/2; 216 - .ident { 222 + hr { 223 + width: 1px; 224 + height: 1.5em; 225 + border: none; 226 + border-left: 1px solid tone(-40%); 227 + margin-left: 0.5em; 228 + } 229 + a[href].ident { 217 230 color: tone(-20%); 218 231 margin-left: 0.2em; 219 - border-left: 1px solid tone(-40%); 220 232 padding-left: 0.5em; 233 + &::before { 234 + content: '@'; 235 + display: inline-block; // remove underline - i don't want to know why this works 236 + opacity: 0.7; 237 + } 221 238 } 222 239 > a[href] { 223 240 display: block; 224 241 padding: 0.25in 0.10in; 225 242 //padding: calc((25% - 1em)/2) 0.15in; 226 243 &, &::after { transition: 0.3s; } 227 244 text-shadow: 1px 1px 1px black; 228 245 &:hover{ 229 246 transform: scale(1.2); 230 247 } 231 248 } 232 - > a[href].bell { 233 - content: url(/s/bell.svg); 249 + > a[href].bell, a[href].gear { 234 250 height: 2em; 235 - padding: 0.125in 0.10in; 251 + padding: 0.125in 0.05in; 236 252 filter: drop-shadow(1px 1px 3px tone(-5%)); 237 253 &:hover { 238 254 filter: drop-shadow(1px 1px 3px tone(-5%)) 239 255 drop-shadow(0 0 10px tone(-5%)); 240 256 } 241 257 } 258 + > a[href].bell { content: url(/s/bell.svg); } 259 + > a[href].gear { content: url(/s/gear.svg); } 242 260 } 243 261 } 244 262 } 245 263 246 264 main { 247 265 @extend %content; 248 266 display: block; ................................................................................ 285 303 border-radius: 5px; 286 304 border: 1px solid transparent; 287 305 &.on { 288 306 background-color: tone(-30%, -0.7); 289 307 box-shadow: 0 0 10px tone(-30%); 290 308 border-color: tone(-20%); 291 309 } 292 - > button, > p { display: block; } 310 + > button, > p, > a[href] { display: block; } 293 311 > p { text-align: center; font-size: 80%; margin: 0; margin-top: 0.1in; } 294 - > button { 312 + > button, > a[href] { 313 + width: max-content; 295 314 margin: auto; 296 315 } 297 316 &:last-child:nth-child(2n-1) { 298 317 grid-column: 1/3; 299 318 } 300 319 } 301 320 ................................................................................ 479 498 padding: 0.1in; 480 499 > img { grid-column: 1/2; grid-row: 1/3; width: 1in; height: 1in;} 481 500 > textarea { 482 501 grid-column: 2/6; grid-row: 1/2; height: 3in; 483 502 resize: vertical; 484 503 margin-bottom: 0.08in; 485 504 } 486 - > input[name="acl"] { grid-column: 2/3; grid-row: 2/3; } 487 505 > button[value="post"] { grid-column: 5/6; grid-row: 2/3; } 488 506 > button[value="attach"] { grid-column: 4/5; grid-row: 2/3; } 489 507 a.help[href] { margin-right: 0.05in } 490 508 } 509 + 510 +input[name="acl"] { grid-column: 2/3; grid-row: 2/3; } 491 511 492 512 a.help[href] { 493 513 display: block; 494 514 text-align: center; 495 515 padding: 0.09in 0.2in; 496 516 background: tone(-40%); 497 517 border: 1px solid black; ................................................................................ 694 714 > a[href] { 695 715 display: block; 696 716 text-align: left; 697 717 } 698 718 > a[href] + a[href] { 699 719 border-top: none; 700 720 } 721 + .button, .button:active { 722 + border: 1px solid black; 723 + } 701 724 hr { 702 725 border: none; 703 726 } 704 727 } 705 728 706 729 menu { all: unset; display: block; } 707 730 body.conf main { ................................................................................ 887 910 .color-picker { 888 911 /* implemented using javascript, alas */ 889 912 @extend %box; 890 913 label { text-shadow: 1px 1px black; } 891 914 padding: 0.1in; 892 915 } 893 916 894 -ul.user-list { 917 +ul.directory { 895 918 list-style-type: none; 896 919 margin: 0.5em 0; 897 920 padding: 0; 898 921 box-shadow: 0 0 10px -3px black inset; 899 922 border: 1px solid tone(-50%); 900 923 li { 901 924 background-color: tone(-20%, -0.8); ................................................................................ 921 944 text-align: center; 922 945 padding: 0.3em 0; 923 946 margin: 0.2em 0.1em; 924 947 cursor: default; 925 948 } 926 949 } 927 950 928 -.button, a[href] { 929 - .neg { --co: 30 } 930 - .pos { --co: -30 } 931 -} 951 +.neg { --co: 30 !important } 952 +.pos { --co: -30 !important } 932 953 933 954 .pick-list { 934 955 display: flex; 935 956 flex-flow: row wrap; 936 957 padding: 0.1in; 937 958 background-color: tone(-50%); 938 959 border: 1px solid tone(-53%); ................................................................................ 1210 1231 padding: 0 0.2in; 1211 1232 max-height: calc(100vh - 3in); 1212 1233 overflow-y: scroll; 1213 1234 text-align: justify; 1214 1235 } 1215 1236 } 1216 1237 } 1238 + 1239 +div.kind-picker { 1240 + text-align: right; 1241 + font-style: italic; 1242 + padding: 0.2em; 1243 +} 1244 + 1245 +body.timeline { 1246 + menu.circles { 1247 + @extend %box; 1248 + width: 3in; 1249 + margin-right: 0; 1250 + margin-left: auto; 1251 + padding: 0.1in; 1252 + a[href] { 1253 + transition: 0.4s; 1254 + text-align: center; 1255 + display: block; 1256 + padding: 0.4em; 1257 + background: linear-gradient(to right, tone(-30%, -0.6), transparent) no-repeat; 1258 + background-position: -3in 0; 1259 + text-decoration: none; 1260 + & + a[href] { 1261 + border-bottom: 1px solid tone(-40%); 1262 + border-image: linear-gradient(to right, transparent, tone(-45%), transparent) 1 0 0 / 1px; 1263 + } 1264 + &:hover { 1265 + background-position: 0 0; 1266 + } 1267 + } 1268 + } 1269 +}
Modified store.t from [8e00371af1] to [0871cb7e85].
11 11 'follow', 12 12 'sub', -- get a notification for every post 13 13 'mute', -- posts will be completely hidden at all times 14 14 'block', -- no interactions will be permitted, but posts will remain visible 15 15 'silence', -- messages will not be accepted 16 16 'collapse', -- posts will be collapsed by default 17 17 'disemvowel', -- posts will be ritually humiliated, but shown 18 + 'attenuate', -- user's retweets will not be shown 18 19 'avoid', -- posts will be kept out of the timeline but will show on users' posts and in conversations 19 20 'exclude', -- own posts will not be visible to this user 20 21 }; 21 22 credset = lib.set { 22 23 'pw', 'otp', 'challenge', 'trust' 23 24 }; 24 25 privset = lib.set { ................................................................................ 254 255 owner: uint64 255 256 desc: str 256 257 folder: str 257 258 mime: str 258 259 url: str 259 260 } 260 261 261 -m.user_conf_funcs = function(be,n,ty,rty,rty2) 262 +m.user_conf_funcs = function(be,n,mem,ty,rty,rty2) 262 263 rty = rty or ty 263 264 local gt 264 - if not rty2 -- what the fuck? 265 - then gt = {&m.source, uint64, rawstring} -> rty; 266 - else gt = {&m.source, uint64, rawstring} -> {rty, rty2}; 265 + if not mem then 266 + if not rty2 -- what the fuck? 267 + then gt = {&m.source, uint64, lib.str.t} -> rty; 268 + else gt = {&m.source, uint64, lib.str.t} -> {rty, rty2}; 269 + end 270 + else 271 + if not rty2 -- what the fuck? 272 + then gt = {&m.source, &lib.mem.pool, uint64, lib.str.t} -> rty; 273 + else gt = {&m.source, &lib.mem.pool, uint64, lib.str.t} -> {rty, rty2}; 274 + end 267 275 end 268 276 for k, t in pairs { 269 - enum = {&m.source, uint64, rawstring} -> lib.mem.ptr(rty); 277 + enum = {&m.source, &lib.mem.pool, uint64, lib.str.t} -> lib.mem.ptr(rty); 270 278 get = gt; 271 - set = {&m.source, uint64, rawstring, ty} -> {}; 272 - reset = {&m.source, uint64, rawstring} -> {}; 279 + set = {&m.source, uint64, lib.str.t, ty} -> {}; 280 + reset = {&m.source, uint64, lib.str.t} -> {}; 273 281 } do 274 282 be.entries[#be.entries+1] = { 275 283 field = 'actor_conf_'..n..'_'..k, type = t 276 284 } 277 285 end 278 286 end 279 287 ................................................................................ 345 353 comment: str 346 354 netmask: m.inet 347 355 privs: m.privset 348 356 blacklist: bool 349 357 } 350 358 351 359 -- backends only handle content on the local server 360 +local pstring = lib.str.t 352 361 struct m.backend { id: rawstring 353 362 open: &m.source -> &opaque 354 363 close: &m.source -> {} 355 364 dbsetup: &m.source -> bool -- creates the schema needed to call conprep (called only once per database e.g. with `parsav db init`) 356 365 conprep: {&m.source, m.prepmode.t} -> {} -- prepares queries and similar tasks that require the schema to already be in place 357 366 obliterate_everything: &m.source -> bool -- wipes everything parsav-related out of the database 358 367 ................................................................................ 361 370 -- these two functions are special, in that they should be called 362 371 -- directly on a specific backend, rather than passed down to the 363 372 -- backends by the server; that is pathological behavior that will 364 373 -- not have the desired effect 365 374 366 375 server_setup_self: {&m.source, rawstring, lib.mem.ptr(uint8)} -> {} 367 376 368 - conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8) 369 - conf_set: {&m.source, rawstring, rawstring} -> {} 377 + conf_get: {&m.source, lib.str.t} -> lib.mem.ptr(int8) 378 + conf_set: {&m.source, lib.str.t, lib.str.t} -> {} 370 379 conf_reset: {&m.source, rawstring} -> {} 371 380 372 381 actor_create: {&m.source, &m.actor} -> uint64 373 382 actor_save: {&m.source, &m.actor} -> {} 374 383 actor_save_privs: {&m.source, &m.actor} -> {} 375 384 actor_purge_uid: {&m.source, uint64} -> {} 376 385 actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor) ................................................................................ 471 480 -- emoji: pstring (null to delete previous reaction, otherwise adds/changes) 472 481 post_act_cancel: {&m.source, uint64} -> {} 473 482 post_liked_uid: {&m.source, uint64, uint64} -> bool 474 483 post_reacted_uid: {&m.source, uint64, uint64} -> bool 475 484 post_act_fetch_notice: {&m.source, uint64} -> m.notice 476 485 477 486 circle_search: {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(m.circle) 478 - circle_create: {&m.source, uint64, pstring} -> {} 487 + circle_create: {&m.source, uint64, lib.str.t} -> uint64 479 488 circle_destroy: {&m.source, uint64, uint64} -> {} 480 - circle_members_fetch_cid: {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(uint64) 489 + circle_members_fetch_cid: {&m.source, &lib.mem.pool, uint64} -> lib.mem.ptr(uint64) 481 490 circle_members_fetch_name: {&m.source, &lib.mem.pool, uint64, pstring} -> lib.mem.ptr(uint64) 482 491 circle_members_add_uid: {&m.source, uint64, uint64} -> {} 483 492 circle_members_del_uid: {&m.source, uint64, uint64} -> {} 493 + circle_memberships_uid: {&m.source, &lib.mem.pool, uint64, uint64} -> lib.mem.ptr(m.circle) 484 494 485 495 thread_latest_arrival_calc: {&m.source, uint64} -> m.timepoint 486 496 487 497 artifact_instantiate: {&m.source, lib.mem.ptr(uint8), lib.mem.ptr(int8)} -> uint64 488 498 -- instantiate an artifact in the database, either installing a new 489 499 -- artifact or returning the id of an existing artifact with the same hash 490 500 -- artifact: bytea ................................................................................ 539 549 nkvd_sanction_vacate: {&m.source, uint64} -> {} 540 550 nkvd_sanction_enum_target: {&m.source, uint64} -> {} 541 551 nkvd_sanction_enum_issuer: {&m.source, uint64} -> {} 542 552 nkvd_sanction_review: {&m.source, m.timepoint} -> {} 543 553 544 554 timeline_actor_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.lstptr(m.post) 545 555 timeline_instance_fetch: {&m.source, m.range} -> lib.mem.lstptr(m.post) 556 + timeline_circle_fetch: {&m.source, uint64, m.range} -> lib.mem.lstptr(m.post) 546 557 } 547 558 548 -m.user_conf_funcs(m.backend, 'str', rawstring, lib.mem.ptr(int8)) 549 -m.user_conf_funcs(m.backend, 'int', intptr, intptr, bool) 559 +m.user_conf_funcs(m.backend, 'str', true, lib.str.t, lib.str.t) 560 +m.user_conf_funcs(m.backend, 'int', false, intptr, intptr, bool) 550 561 551 562 struct m.source { 552 563 backend: &m.backend 553 564 id: lib.mem.ptr(int8) 554 565 handle: &opaque 555 566 string: lib.mem.ptr(int8) 556 567 }
Modified tpl.t from [586a17ee3f] to [7216746946].
34 34 str = str:gsub('%s+[\n$]','') 35 35 str = str:gsub('\n','') 36 36 str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed 37 37 str = str:gsub(tplchar .. '%?([-%w]+)', function(file) 38 38 if not docs[file] then docs[file] = data.doc[file] end 39 39 return string.format('<a href="#help-%s" class="help">?</a>', file) 40 40 end) 41 - for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!]?)(%w+)()') do 41 + for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!]?)([-a-zA-Z0-9_]+)()') do 42 42 if string.sub(str,start-1,start-1) ~= '\\' then 43 43 segs[#segs+1] = string.sub(str,last,start-1) 44 - fields[#segs] = { key = key, mode = (mode ~= '' and mode or nil) } 44 + fields[#segs] = { key = key:gsub('-','_'), mode = (mode ~= '' and mode or nil) } 45 45 last = stop 46 46 end 47 47 end 48 48 segs[#segs+1] = string.sub(str,last) 49 49 50 50 for i, s in ipairs(segs) do 51 51 segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o)
Added view/conf-circles.tpl version [9128c77c5c].
1 +<ul class="directory"> 2 + @circles 3 +</ul> 4 + 5 +<details@newattr> 6 + <summary>create new circle</summary> 7 + <form method="post"> 8 + <div class="elem"> 9 + <label for="name">circle name</label> 10 + <input type="text" id="name" name="name" placeholder="dorks"> 11 + </div> 12 + <button name="act" value="create">create</button> 13 + </form> 14 +</details>
Modified view/conf-profile.tpl from [48e88ad45a] to [7ff9d56e20].
1 1 <form method="post"> 2 2 <div class="elem"><label>handle</label> <div class="txtbox">@!handle</div></div> 3 3 <div class="elem"><label for="nym">display name</label> <input type="text" name="nym" id="nym" placeholder="j. random poster" value="@:nym"></div> 4 4 <div class="elem"><label for="bio">bio</label><textarea name="bio" id="bio" placeholder="tall, dark, and mysterious">@!bio</textarea></div> 5 5 <div class="elem color-picker"><label for="hue">accent</label><input type="range" min="0" max="360" value="@hue" name="hue" id="hue" data-color-pick></div> 6 + <div class="elem-group"> 7 + <div class="elem"> 8 + <label for="acl-follow">who can follow me</label> 9 + <input type="text" class="acl" name="acl-follow" 10 + id="acl-follow" placeholder="allow local" value="@:acl-follow"> 11 + </div> 12 + <div class="elem"> 13 + <label for="acl-follow-req">who can request to follow me</label> 14 + <input type="text" class="acl" name="acl-follow-req" 15 + id="acl-follow-req" placeholder="deny +dorks" value="@:acl-follow-req"> 16 + </div> 17 + </div> 6 18 <menu class="choice vertical"> 7 19 <button>commit all</button> 8 20 <button name="act" value="reset-hue">use server colors</button> 9 21 </menu> 10 22 </form>
Modified view/docskel.tpl from [e84fb6bf18] to [e2ac83ad65].
5 5 <link rel="stylesheet" type="text/css" href="/s/style.css"> 6 6 <script type="text/javascript" src="/s/live.js" async></script> 7 7 </head> 8 8 <body class="@class"@attr> 9 9 <header><div> 10 10 <h1>@title</h1> 11 11 <nav> 12 - <a accesskey="i" href="/instance">instance</a> 12 + <a accesskey="i" href="/instance"><u>i</u>nstance</a> 13 13 @navlinks 14 14 </nav> 15 15 </div></header> 16 16 <main> 17 17 @body 18 18 </main> 19 19 </body> 20 20 </html>
Modified view/load.lua from [3d222d2b19] to [bd867ef191].
6 6 local sources = { 7 7 'docskel'; 8 8 'confirm'; 9 9 'tweet'; 10 10 'profile'; 11 11 'compose'; 12 12 'notice'; 13 + 'report'; 13 14 14 15 'media-gallery'; 15 16 'media-upload'; 16 17 'media-image'; 17 18 'media-text'; 18 19 19 20 'login-username'; 20 21 'login-challenge'; 21 22 22 23 'conf'; 23 24 'conf-profile'; 25 + 'conf-circles'; 26 + 'conf-circle-view'; 24 27 'conf-sec'; 25 28 'conf-sec-credmg'; 26 29 'conf-sec-pwnew'; 27 30 'conf-sec-keynew'; 28 31 'conf-user-ctl'; 29 32 } 30 33
Modified view/profile.tpl from [240a608504] to [ae30d737ce].
38 38 <button name="act" value="encircle">commit</button> 39 39 </details> 40 40 <details> 41 41 <summary>sanctions</summary> 42 42 <menu> 43 43 @sanctions 44 44 <div class="opt"> 45 - <button class="neg" name="act" value="report">report</button> 45 + <a class="neg button" href="/@:xid/report">report</a> 46 46 <p>if this user is violating instance rules, you can report this behavior to moderation staff and ask them to take action. please do not report users simply because you dislike them; this is what the above options are for.</p> 47 47 </div> 48 48 </menu> 49 49 </details> 50 50 </div></form>
Added view/report.tpl version [34643d2a6d].
1 +<form method="post"> 2 + <p>if you feel that this post has violated the rules of this instance, you can report it to the local moderation team and request that they take action. this may include suppressing the offending post, suspending the user, or passing complaints onwards to the offending user's instance, if remote. please explain how you believe the post violates instance rules and what, if any, action you believe is appropriate.</p> 3 + @badtweet 4 + <div class="elem"> 5 + <label for="report">report</label> 6 + <textarea id="report" name="report" placeholder="this running dog is spreading counterrevolutionary agitprop amongst the people, sir! he must be dealt with by the cadres at once"></textarea> 7 + </div> 8 + <menu class="horizontal choice"> 9 + <button>issue report</button> 10 + <a class="button" href="@clnk">cancel</a> 11 + </menu> 12 +</form>