parsav  Check-in [00a6815988]

Overview
Comment:add follow notices
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 00a6815988cf730166006380c81ff2645a4d232a40ce2ffabb8ef89c2a4f9ae1
User & Date: lexi on 2021-01-10 11:17:29
Other Links: manifest | tags
Context
2021-01-10
14:26
get some user admin shit working, general cleanups check-in: e1ff4f301e user: lexi tags: trunk
11:17
add follow notices check-in: 00a6815988 user: lexi tags: trunk
08:19
begin replacing inefficient memory management with a pool-based solution; fix memory leaks check-in: 7c8769bf96 user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [794844a9c8] to [b388ba7244].

   240    240   			delete from parsav_rights where
   241    241   				actor = $1::bigint and
   242    242   				key = $2::text
   243    243   		]]
   244    244   	};
   245    245   
   246    246   	actor_rel_create = {
   247         -		params = {uint16,uint64, uint64}, cmd = true, sql = [[
   248         -			insert into parsav_rels (kind,relator,relatee)
   249         -				values($1::smallint, $2::bigint, $3::bigint)
          247  +		params = {uint16,uint64, uint64, int64}, cmd = true, sql = [[
          248  +			insert into parsav_rels (kind,relator,relatee,since)
          249  +				values($1::smallint, $2::bigint, $3::bigint, $4::bigint)
   250    250   			on conflict do nothing
   251    251   		]];
   252    252   	};
   253    253   
   254    254   	actor_rel_destroy = {
   255    255   		params = {uint16,uint64, uint64}, cmd = true, sql = [[
   256    256   			delete from parsav_rels where
................................................................................
  1499   1499   	end];
  1500   1500   
  1501   1501   	actor_rel_create = [terra(
  1502   1502   		src: &lib.store.source,
  1503   1503   		kind:    uint16,
  1504   1504   		relator: uint64,
  1505   1505   		relatee: uint64
  1506         -	): {} queries.actor_rel_create.exec(src,kind,relator,relatee) end];
         1506  +	): {} queries.actor_rel_create.exec(src,kind,relator,relatee,lib.osclock.time(nil)) end];
  1507   1507   
  1508   1508   	actor_rel_destroy = [terra(
  1509   1509   		src: &lib.store.source,
  1510   1510   		kind:    uint16,
  1511   1511   		relator: uint64,
  1512   1512   		relatee: uint64
  1513   1513   	): {} queries.actor_rel_destroy.exec(src,kind,relator,relatee) end];

Modified backend/schema/pgsql-views.sql from [c997d2f5ad] to [0fea8fa8b6].

    53     53   			($1).time,
    54     54   			($1).actor,
    55     55   			($1).subject,
    56     56   			null::bigint,
    57     57   			($1).body
    58     58   		)::pg_temp.parsavpg_intern_notice as notice
    59     59   	from (values
    60         -		('rt',    <notice:rt>   ),
    61         -		('like',  <notice:like> ),
    62         -		('react', <notice:react>)
           60  +		('rt',    <notice:rt>    ),
           61  +		('like',  <notice:like>  ),
           62  +		('react', <notice:react> ),
           63  +		('follow',<notice:follow>)
    63     64   	) as kmap(kstr,kind) where kmap.kstr = ($1).kind
    64     65   $$ language sql;
    65     66   
    66     67   create type pg_temp.parsavpg_intern_actor as (
    67     68   	id			bigint,
    68     69   	nym			text,
    69     70   	handle		text,
................................................................................
   208    209   				null::text
   209    210   			)::pg_temp.parsavpg_intern_notice as notice,
   210    211   			par.author as rcpt
   211    212   		from parsav_posts as p
   212    213   			inner join parsav_posts as par on p.parent = par.id
   213    214   			left  join ntimes as nt on nt.uid = p.author
   214    215   		where p.discovered >= coalesce(nt.when,0)
   215         -	), allnotices as (select * from acts union select * from replies)
          216  +	), follows as (
          217  +		select row(
          218  +				<notice:follow>::smallint,
          219  +				r.since,
          220  +				r.relator,
          221  +				r.relatee,
          222  +				null::bigint,
          223  +				null::text
          224  +			)::pg_temp.parsavpg_intern_notice as notice,
          225  +			r.relatee as rcpt
          226  +		from parsav_rels as r
          227  +			left  join ntimes as nt on nt.uid = r.relatee
          228  +		where
          229  +			r.since >= coalesce(nt.when,0) and
          230  +			r.kind = <rel:follow>
          231  +	), allnotices as (table acts union table replies union table follows)
   216    232   
   217    233   	table allnotices order by (notice).when desc
   218    234   );
   219    235   

Modified backend/schema/pgsql.sql from [bc736c4c1d] to [daac991db5].

    85     85   create index on parsav_posts (parent);
    86     86   
    87     87   create table parsav_rels (
    88     88   	relator bigint references parsav_actors(id)
    89     89   		on delete cascade, -- e.g. follower
    90     90   	relatee bigint references parsav_actors(id)
    91     91   		on delete cascade, -- e.g. followed
    92         -	kind    smallint, -- e.g. follow, block, mute
           92  +	kind    smallint not null, -- e.g. follow, block, mute
           93  +	since	bigint not null,
    93     94   
    94     95   	primary key (relator, relatee, kind)
    95     96   );
    96     97   comment on table parsav_rels is
    97     98   'all relationships, positive and negative, between local users and other users; kind is a version-specific integer mapping to a type-of-relationship enum in store.t';
    98     99   
    99    100   create table parsav_acts (

Modified config.lua from [6cff716428] to [a1f76576fd].

    58     58   		{'heart.webp', 'image/webp'};
    59     59   		{'retweet.webp', 'image/webp'};
    60     60   		{'padlock.svg', 'image/svg+xml'};
    61     61   		{'warn.svg', 'image/svg+xml'};
    62     62   		{'query.webp', 'image/webp'};
    63     63   		{'reply.webp', 'image/webp'};
    64     64   		{'file.webp', 'image/webp'};
           65  +		{'follow.webp', 'image/webp'};
    65     66   		-- keep in mind before you add anything to this list: these are not
    66     67   		-- just files parsav can access, they are files that are *kept in
    67     68   		-- memory* for fast access the entire time parsav is running, and
    68     69   		-- which need to be loaded into memory before the program can even
    69     70   		-- start. it's imperative to keep these as small and few in number
    70     71   		-- as is realistically possible.
    71     72   	};

Modified http.t from [4c9f723184] to [5f40c98144].

    59     59   end
    60     60   m.codestr = terra(code: uint16)
    61     61   	var [resptext] var [resplen]
    62     62   	switch code do [respbranches] end
    63     63   	return resptext, resplen
    64     64   end
    65     65   
    66         -terra m.hier(uri: lib.mem.ptr(int8)): lib.mem.ptr(lib.mem.ref(int8))
           66  +terra m.hier(pool: &lib.mem.pool, uri: lib.mem.ptr(int8)): lib.mem.ptr(lib.mem.ref(int8))
    67     67   	if uri.ct == 0 then return [lib.mem.ptr(lib.mem.ref(int8))] { ptr = nil, ct = 0 } end
    68     68   	var sz = 1
    69     69   	var start = 0 if uri.ptr[0] == @'/' then start = 1 end
    70     70   	for i = start, uri.ct do if uri.ptr[i] == @'/' then sz = sz + 1 end end
    71         -	var lst = lib.mem.heapa([lib.mem.ref(int8)], sz)
           71  +	var lst = pool:alloc([lib.mem.ref(int8)], sz)
    72     72   	if sz == 0 then
    73     73   		lst.ptr[0].ptr = uri.ptr
    74     74   		lst.ptr[0].ct = uri.ct
    75     75   		return lst
    76     76   	end
    77     77   
    78     78   	var idx: intptr = 0

Modified makefile from [8559ac7b2c] to [d43e9d6c44].

     1      1   dl = git
     2      2   dbg-flags = $(if $(dbg),-g)
     3      3   
     4         -images = static/default-avatar.webp static/query.webp static/heart.webp static/retweet.webp static/reply.webp static/file.webp
            4  +images = static/default-avatar.webp static/query.webp static/heart.webp static/retweet.webp static/reply.webp static/file.webp static/follow.webp
     5      5   #$(addsuffix .webp, $(basename $(wildcard static/*.svg)))
     6      6   styles = $(addsuffix .css, $(basename $(wildcard static/*.scss)))
     7      7   
     8      8   parsav parsavd: parsav.t config.lua pkgdata.lua $(images) $(styles)
     9      9   	terra $(dbg-flags) $<
    10     10   parsav.o parsavd.o: parsav.t config.lua pkgdata.lua $(images) $(styles)
    11     11   	env parsav_link=no terra $(dbg-flags) $<

Modified mem.t from [7e2b478eaf] to [05a21ff4d9].

   207    207   	self.sz = sz
   208    208   	self.debris = [&m.pool](b)
   209    209   	self.debris.storage = nil
   210    210   	return self
   211    211   end
   212    212   
   213    213   terra m.pool:free(): {}
   214         -lib.io.fmt('DRAINING POOL %p\n',self.storage)
   215    214   	if self.storage == nil then return end
   216    215   	if self.debris.storage ~= nil then self.debris:free() end
   217    216   	m.heapf(self.debris) -- storage + debris field allocated in one block
   218    217   	self.storage = nil
   219    218   	self.cursor = nil
   220    219   	self.sz = 0
   221    220   	self.debris = nil
................................................................................
   224    223   terra m.pool:clear()
   225    224   	if self.debris.storage ~= nil then self.debris:free() end
   226    225   	self.cursor = self.storage
   227    226   	return self
   228    227   end
   229    228   
   230    229   terra m.pool:alloc_bytes(sz: intptr): &opaque
   231         -	var space = self.sz - ([&uint8](self.cursor) - [&uint8](self.storage))
   232         -lib.io.fmt('%p / %p @ allocating %llu bytes in %llu of space\n',self.storage,self.cursor,sz,space)
          230  +	var space: intptr = self.sz - ([&uint8](self.cursor) - [&uint8](self.storage))
   233    231   	if space < sz then
   234         -lib.dbg('reserving more space')
   235         -		self:cue(space + sz + 256) end
          232  +		self:cue(self.sz + sz + 256) end
   236    233   	var ptr = self.cursor
   237    234   	self.cursor = [&opaque]([&uint8](self.cursor) + sz)
   238    235   	return ptr
   239    236   end
   240    237   
   241    238   terra m.pool:realloc_bytes(oldptr: &opaque, oldsz: intptr, newsz: intptr): &opaque
   242         -	var space = self.sz - ([&uint8](self.cursor) - [&uint8](self.storage))
          239  +	var space: intptr = self.sz - ([&uint8](self.cursor) - [&uint8](self.storage))
   243    240   	var cur = [&uint8](self.cursor)
   244    241   	if cur - [&uint8](oldptr) == oldsz and newsz - oldsz < space then
   245    242   		lib.dbg('moving pool cursor')
   246    243   		cur = cur + (newsz - oldsz)
   247    244   		self.cursor = [&opaque](cur)
   248    245   		return oldptr
   249    246   	else

Modified parsav.md from [1e18c01a8c] to [af14d66fc5].

   135    135   * ldap for auth (and maybe actors?)
   136    136   * cdb (for static content, maybe? does this make sense?)
   137    137   * mariadb/mysql
   138    138   * the various nosql horrors, e.g. redis, mongo, and so on
   139    139   
   140    140   parsav urgently needs an internationalization framework as well. right now everything is just hardcoded in english. yuck.
   141    141   
   142         -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.
          142  +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

Modified render/notices.t from [745afb2f25] to [1e81acf0af].

    30     30   		                      else pflink:lpush('/@') end
    31     31   		pflink:push(who(0).xid,0)
    32     32   		var n = data.view.notice {
    33     33   			avatar = cs(who(0).avatar);
    34     34   			nym = lib.render.nym(who.ptr,0,nil,true);
    35     35   			pflink = pstr{ptr = pflink.buf; ct = pflink.sz};
    36     36   		}
    37         -		var notweet = true
    38         -		var what = co.srv:post_fetch(notes(i).what) defer what:free()
           37  +		var notweet, nopost = true, false
    39     38   		switch notes(i).kind do
    40     39   			case lib.store.noticetype.rt then
    41     40   				n.kind = P'rt'
    42     41   				n.act = P'retweeted your post'
    43     42   			end
    44     43   			case lib.store.noticetype.like then
    45     44   				n.kind = P'like'
    46     45   				n.act = P'likes your post'
    47     46   			end
    48     47   			case lib.store.noticetype.reply then
    49     48   				n.kind = P'reply'
    50     49   				n.act = P'replied to your post'
    51     50   				notweet = false
           51  +			end
           52  +			case lib.store.noticetype.follow then
           53  +				n.kind = P'follow'
           54  +				n.act = P'followed you!'
           55  +				nopost = true
    52     56   			end
    53     57   		else goto skip end
    54         -		do var idbuf: int8[lib.math.shorthand.maxlen]
    55         -			var idlen = lib.math.shorthand.gen(notes(i).what, idbuf)
           58  +		if not nopost then
           59  +			var what = co.srv:post_fetch(notes(i).what) defer what:free()
    56     60   			var b = lib.smackdown.html(&co.srv.pool, pstr {ptr=what(0).body,ct=0},true) --defer b:free()
    57         -			body:lpush(' <a class="quote" href="/post/'):push(&idbuf[0],idlen):lpush('">'):ppush(b):lpush('</a>')
           61  +			body:lpush(' <a class="quote" href="/post/'):shpush(notes(i).what):lpush('">'):ppush(b):lpush('</a>')
    58     62   		end
    59     63   		if not notweet then
    60     64   			var reply = co.srv:post_fetch(notes(i).reply)
    61     65   				lib.render.tweet(co,reply.ptr,&body)
    62     66   			reply:free()
    63     67   		end
    64     68   		n.ref = pstr {ptr = body.buf, ct = body.sz}
    65         -
    66     69   		n:append(&pg)
           70  +
    67     71   		::skip:: n.nym:free()
    68     72   		         pflink:reset()
    69     73   				 body:reset()
    70     74   	end
    71     75   	--pflink:free()
    72     76   	pg:lpush('<form method="post"><button name="act" value="clear">clear all notices</button></form>')
    73     77   	co:livepage([lib.srv.convo.page] {

Modified render/profile.t from [a033243372] to [cdb26ba3d8].

    66     66   		end
    67     67   
    68     68   		-- this is really more what epithets are for, i think
    69     69   		if actor.rights.rank > 0 and stafftxt:ref() then
    70     70   			comments:lpush('<li>'):ppush(stafftxt):lpush('</li>')
    71     71   		end
    72     72   
    73         -		if co.who:outranks(actor) then
    74         -			comments:lpush('<li style="--co:50">underling</li>')
    75         -		elseif actor:outranks(co.who) then
    76         -			comments:lpush('<li style="--co:-50">outranks you</li>')
           73  +		if co.who.rights.rank ~= 0 then
           74  +			if co.who:outranks(actor) then
           75  +				comments:lpush('<li style="--co:50">underling</li>')
           76  +			elseif actor:outranks(co.who) then
           77  +				comments:lpush('<li style="--co:-50">outranks you</li>')
           78  +			end
    77     79   		end
    78     80   
    79     81   		if relationship.recip.follow() then
    80     82   			comments:lpush('<li style="--co:30">follows you</li>')
    81     83   		end
    82     84   	end
    83     85   

Modified route.t from [a232000d86] to [47b3eeec8d].

    11     11   
    12     12   terra http.actor_profile(co: &lib.srv.convo, actor: &lib.store.actor, meth: method.t)
    13     13   	var rel: lib.store.relationship
    14     14   	if co.aid ~= 0 then
    15     15   		rel = co.srv:actor_rel_calc(co.who.id, actor.id)
    16     16   		if meth == method.post then
    17     17   			var act = co:ppostv('act')
    18         -			if act:cmp(lib.str.plit 'follow') and not rel.rel.follow() then
    19         -				if rel.recip.block() then
           18  +			if rel.recip.block() then
           19  +				if act:cmp(lib.str.plit 'follow') or act:cmp(lib.str.plit 'subscribe') then
    20     20   					co:complain(403,'blocked','you cannot follow a user you are blocked by') return
    21     21   				end
    22         -				(rel.rel.follow << true)
    23         -				co.srv:actor_rel_create([lib.store.relation.idvmap.follow], co.who.id, actor.id)
    24         -			elseif act:cmp(lib.str.plit 'unfollow') and rel.rel.follow() then
    25         -				(rel.rel.follow << false)
    26         -				co.srv:actor_rel_destroy([lib.store.relation.idvmap.follow], co.who.id, actor.id)
           22  +			end
           23  +			if act:cmp(lib.str.plit 'block') and not rel.rel.block() then
           24  +				(rel.rel.block << true) ; (rel.recip.follow << false)
           25  +				co.srv:actor_rel_create([lib.store.relation.idvmap.block], co.who.id, actor.id)
           26  +				co.srv:actor_rel_destroy([lib.store.relation.idvmap.follow], actor.id, co.who.id)
           27  +			else
           28  +				[(function()
           29  +					local tests = quote co:complain(400,'bad request','the action you have attempted on this user is not meaningful') return end
           30  +					for i,v in ipairs(lib.store.relation.members) do
           31  +						tests = quote
           32  +							if [v ~= 'block'] and act:cmp(lib.str.plit([v])) and not rel.rel.[v]() then -- rely on dead code elimination :/
           33  +								(rel.rel.[v] << true)
           34  +								co.srv:actor_rel_create([lib.store.relation.idvmap[v]], co.who.id, actor.id)
           35  +							elseif act:cmp(lib.str.plit(['un'..v])) and rel.rel.[v]() then
           36  +								(rel.rel.[v] << false)
           37  +								co.srv:actor_rel_destroy([lib.store.relation.idvmap[v]], co.who.id, actor.id)
           38  +							else [tests] end
           39  +						end
           40  +					end
           41  +					return tests
           42  +				end)()]
    27     43   			end
    28     44   		end
    29     45   	else
    30     46   		rel.rel:clear()
    31     47   		rel.recip:clear()
    32     48   	end
    33     49   
................................................................................
    42     58   	if handle.ct == 0 then
    43     59   		handle.ct = uri.ct - 2
    44     60   		uri:advance(uri.ct)
    45     61   	elseif handle.ct + 2 < uri.ct then uri:advance(handle.ct + 2) end
    46     62   
    47     63   	lib.dbg('looking up user by xid "', {handle.ptr,handle.ct} ,'", path: ', {uri.ptr,uri.ct})
    48     64   
    49         -	var path = lib.http.hier(uri) defer path:free()
           65  +	var path = lib.http.hier(&co.srv.pool, uri) --defer path:free()
    50     66   	for i=0,path.ct do
    51     67   		lib.dbg('got path component ', {path.ptr[i].ptr, path.ptr[i].ct})
    52     68   	end
    53     69   
    54     70   	var actor = co.srv:actor_fetch_xid(handle)
    55     71   	if actor.ptr == nil then
    56     72   		co:complain(404,'no such user','no such user known to this server')
................................................................................
   318    334   credsec_for_uid(co: &lib.srv.convo, uid: uint64)
   319    335   	var act = co:ppostv('act')
   320    336   	lib.dbg('showing credentials')
   321    337   	if act:cmp(lib.str.plit 'invalidate') then
   322    338   		lib.dbg('setting user\'s cookie validation time to now')
   323    339   		co.who.source:auth_sigtime_user_alter(uid, lib.osclock.time(nil))
   324    340   		-- the current session has been invalidated as well, so we need to immediately install a new authentication cookie with the same aid so the user doesn't need to log back in all over again
   325         -		co:installkey('/conf/sec',co.aid)
          341  +		co:installkey('?',co.aid)
   326    342   		return
   327    343   	elseif act:cmp(lib.str.plit 'newcred') then
   328    344   		var cmt = co:ppostv('comment')
   329    345   		var pw = co:ppostv('newpw')
   330    346   		var aid: uint64 = 0
   331    347   		if pw:ref() then
   332    348   			var cpw = co:ppostv('rptpw')
................................................................................
   692    708   		end
   693    709   	elseif uri:cmp(lib.str.plit '/logout') then
   694    710   		if co.aid == 0
   695    711   			then goto notfound
   696    712   			else co:reroute_cookie('/','auth=; Path=/')
   697    713   		end
   698    714   	else -- hierarchical routes
   699         -		var path = lib.http.hier(uri) defer path:free()
          715  +		var path = lib.http.hier(&co.srv.pool, uri) --defer path:free()
   700    716   		if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then
   701    717   			http.actor_profile_uid(co, path, meth)
   702    718   		elseif path.ct > 1 and path(0):cmp(lib.str.lit('post')) then
   703    719   			http.tweet_page(co, path, meth)
   704    720   		elseif path(0):cmp(lib.str.lit('tl')) then
   705    721   			http.timeline(co, path)
   706    722   		elseif path(0):cmp(lib.str.lit('media')) then

Added static/follow.svg version [61b9b2bee5].

            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="20"
           14  +   height="20"
           15  +   viewBox="0 0 5.2916664 5.2916665"
           16  +   version="1.1"
           17  +   id="svg8"
           18  +   inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
           19  +   sodipodi:docname="follow.svg">
           20  +  <defs
           21  +     id="defs2">
           22  +    <linearGradient
           23  +       inkscape:collect="always"
           24  +       id="linearGradient1054">
           25  +      <stop
           26  +         style="stop-color:#ff73e0;stop-opacity:1"
           27  +         offset="0"
           28  +         id="stop1050" />
           29  +      <stop
           30  +         style="stop-color:#ff00cb;stop-opacity:0.98431373"
           31  +         offset="1"
           32  +         id="stop1052" />
           33  +    </linearGradient>
           34  +    <linearGradient
           35  +       id="linearGradient1034"
           36  +       inkscape:collect="always">
           37  +      <stop
           38  +         id="stop1030"
           39  +         offset="0"
           40  +         style="stop-color:#ff55dd;stop-opacity:0.50869566" />
           41  +      <stop
           42  +         id="stop1032"
           43  +         offset="1"
           44  +         style="stop-color:#ff55dd;stop-opacity:0.02173913" />
           45  +    </linearGradient>
           46  +    <linearGradient
           47  +       id="linearGradient1019"
           48  +       inkscape:collect="always">
           49  +      <stop
           50  +         id="stop1013"
           51  +         offset="0"
           52  +         style="stop-color:#680054;stop-opacity:0.01304348" />
           53  +      <stop
           54  +         style="stop-color:#ff40d9;stop-opacity:0.23913044"
           55  +         offset="0.30000001"
           56  +         id="stop1015" />
           57  +      <stop
           58  +         id="stop1017"
           59  +         offset="1"
           60  +         style="stop-color:#d400aa;stop-opacity:0;" />
           61  +    </linearGradient>
           62  +    <linearGradient
           63  +       id="linearGradient1005"
           64  +       inkscape:collect="always">
           65  +      <stop
           66  +         id="stop1001"
           67  +         offset="0"
           68  +         style="stop-color:#fff6fd;stop-opacity:1" />
           69  +      <stop
           70  +         id="stop1003"
           71  +         offset="1"
           72  +         style="stop-color:#ff3cd7;stop-opacity:0.98260868" />
           73  +    </linearGradient>
           74  +    <linearGradient
           75  +       inkscape:collect="always"
           76  +       id="linearGradient979">
           77  +      <stop
           78  +         style="stop-color:#680054;stop-opacity:1"
           79  +         offset="0"
           80  +         id="stop975" />
           81  +      <stop
           82  +         id="stop983"
           83  +         offset="0.30000001"
           84  +         style="stop-color:#d400aa;stop-opacity:0.77254902;" />
           85  +      <stop
           86  +         style="stop-color:#d400aa;stop-opacity:0;"
           87  +         offset="1"
           88  +         id="stop977" />
           89  +    </linearGradient>
           90  +    <linearGradient
           91  +       inkscape:collect="always"
           92  +       id="linearGradient971">
           93  +      <stop
           94  +         style="stop-color:#ff55dd;stop-opacity:1;"
           95  +         offset="0"
           96  +         id="stop967" />
           97  +      <stop
           98  +         style="stop-color:#ff55dd;stop-opacity:0.97826087"
           99  +         offset="1"
          100  +         id="stop969" />
          101  +    </linearGradient>
          102  +    <linearGradient
          103  +       inkscape:collect="always"
          104  +       id="linearGradient963">
          105  +      <stop
          106  +         style="stop-color:#c839ac;stop-opacity:0.00392157"
          107  +         offset="0"
          108  +         id="stop959" />
          109  +      <stop
          110  +         style="stop-color:#852572;stop-opacity:1"
          111  +         offset="1"
          112  +         id="stop961" />
          113  +    </linearGradient>
          114  +    <linearGradient
          115  +       inkscape:collect="always"
          116  +       id="linearGradient943">
          117  +      <stop
          118  +         style="stop-color:#f4d7ee;stop-opacity:1;"
          119  +         offset="0"
          120  +         id="stop939" />
          121  +      <stop
          122  +         style="stop-color:#f4d7ee;stop-opacity:0;"
          123  +         offset="1"
          124  +         id="stop941" />
          125  +    </linearGradient>
          126  +    <linearGradient
          127  +       inkscape:collect="always"
          128  +       id="linearGradient954">
          129  +      <stop
          130  +         style="stop-color:#ffffff;stop-opacity:1;"
          131  +         offset="0"
          132  +         id="stop950" />
          133  +      <stop
          134  +         style="stop-color:#ffffff;stop-opacity:0;"
          135  +         offset="1"
          136  +         id="stop952" />
          137  +    </linearGradient>
          138  +    <linearGradient
          139  +       inkscape:collect="always"
          140  +       id="linearGradient938">
          141  +      <stop
          142  +         style="stop-color:#d9fff6;stop-opacity:1;"
          143  +         offset="0"
          144  +         id="stop934" />
          145  +      <stop
          146  +         style="stop-color:#d9fff6;stop-opacity:0;"
          147  +         offset="1"
          148  +         id="stop936" />
          149  +    </linearGradient>
          150  +    <linearGradient
          151  +       inkscape:collect="always"
          152  +       id="linearGradient1403">
          153  +      <stop
          154  +         style="stop-color:#ccaaff;stop-opacity:1;"
          155  +         offset="0"
          156  +         id="stop1399" />
          157  +      <stop
          158  +         style="stop-color:#ccaaff;stop-opacity:0;"
          159  +         offset="1"
          160  +         id="stop1401" />
          161  +    </linearGradient>
          162  +    <linearGradient
          163  +       id="linearGradient1395"
          164  +       inkscape:collect="always">
          165  +      <stop
          166  +         id="stop1391"
          167  +         offset="0"
          168  +         style="stop-color:#ff1616;stop-opacity:1" />
          169  +      <stop
          170  +         id="stop1393"
          171  +         offset="1"
          172  +         style="stop-color:#ff1d1d;stop-opacity:0" />
          173  +    </linearGradient>
          174  +    <linearGradient
          175  +       inkscape:collect="always"
          176  +       id="linearGradient1383">
          177  +      <stop
          178  +         style="stop-color:#980000;stop-opacity:1;"
          179  +         offset="0"
          180  +         id="stop1379" />
          181  +      <stop
          182  +         style="stop-color:#980000;stop-opacity:0;"
          183  +         offset="1"
          184  +         id="stop1381" />
          185  +    </linearGradient>
          186  +    <linearGradient
          187  +       inkscape:collect="always"
          188  +       id="linearGradient832">
          189  +      <stop
          190  +         style="stop-color:#ffcfcf;stop-opacity:1;"
          191  +         offset="0"
          192  +         id="stop828" />
          193  +      <stop
          194  +         style="stop-color:#ffcfcf;stop-opacity:0;"
          195  +         offset="1"
          196  +         id="stop830" />
          197  +    </linearGradient>
          198  +    <radialGradient
          199  +       inkscape:collect="always"
          200  +       xlink:href="#linearGradient832"
          201  +       id="radialGradient834"
          202  +       cx="3.2286437"
          203  +       cy="286.62921"
          204  +       fx="3.2286437"
          205  +       fy="286.62921"
          206  +       r="1.0866126"
          207  +       gradientTransform="matrix(1.8608797,0.8147617,-0.38242057,0.87343168,106.71446,33.692223)"
          208  +       gradientUnits="userSpaceOnUse" />
          209  +    <radialGradient
          210  +       inkscape:collect="always"
          211  +       xlink:href="#linearGradient1383"
          212  +       id="radialGradient1385"
          213  +       cx="4.1787109"
          214  +       cy="286.89261"
          215  +       fx="4.1787109"
          216  +       fy="286.89261"
          217  +       r="1.2260786"
          218  +       gradientTransform="matrix(1.7016464,0,0,1.6348586,-2.9319775,-182.10895)"
          219  +       gradientUnits="userSpaceOnUse" />
          220  +    <radialGradient
          221  +       inkscape:collect="always"
          222  +       xlink:href="#linearGradient1395"
          223  +       id="radialGradient1389"
          224  +       gradientUnits="userSpaceOnUse"
          225  +       gradientTransform="matrix(0.66230313,-1.6430738,1.0154487,0.40931507,-290.06307,177.39489)"
          226  +       cx="4.02069"
          227  +       cy="287.79269"
          228  +       fx="4.02069"
          229  +       fy="287.79269"
          230  +       r="1.0866126" />
          231  +    <linearGradient
          232  +       inkscape:collect="always"
          233  +       xlink:href="#linearGradient1403"
          234  +       id="linearGradient1405"
          235  +       x1="8.3939333"
          236  +       y1="288.1091"
          237  +       x2="7.0158253"
          238  +       y2="287.32819"
          239  +       gradientUnits="userSpaceOnUse" />
          240  +    <linearGradient
          241  +       inkscape:collect="always"
          242  +       xlink:href="#linearGradient938"
          243  +       id="linearGradient940"
          244  +       x1="7.609839"
          245  +       y1="288.73215"
          246  +       x2="7.609839"
          247  +       y2="283.78305"
          248  +       gradientUnits="userSpaceOnUse" />
          249  +    <linearGradient
          250  +       inkscape:collect="always"
          251  +       xlink:href="#linearGradient954"
          252  +       id="linearGradient956"
          253  +       x1="3.0150654"
          254  +       y1="285.94464"
          255  +       x2="3.0150654"
          256  +       y2="282.40109"
          257  +       gradientUnits="userSpaceOnUse" />
          258  +    <linearGradient
          259  +       inkscape:collect="always"
          260  +       xlink:href="#linearGradient954"
          261  +       id="linearGradient1138"
          262  +       gradientUnits="userSpaceOnUse"
          263  +       x1="3.0150654"
          264  +       y1="285.94464"
          265  +       x2="3.0150654"
          266  +       y2="284.62277" />
          267  +    <radialGradient
          268  +       inkscape:collect="always"
          269  +       xlink:href="#linearGradient1005"
          270  +       id="radialGradient945"
          271  +       cx="6.1517248"
          272  +       cy="285.09021"
          273  +       fx="6.1517248"
          274  +       fy="285.09021"
          275  +       r="1.3844374"
          276  +       gradientTransform="matrix(2.4674713,0,0,2.4674669,-5.8821073,-417.49152)"
          277  +       gradientUnits="userSpaceOnUse" />
          278  +    <radialGradient
          279  +       inkscape:collect="always"
          280  +       xlink:href="#linearGradient943"
          281  +       id="radialGradient953"
          282  +       gradientUnits="userSpaceOnUse"
          283  +       gradientTransform="matrix(2.4674713,0,0,2.4674669,-9.027479,-418.36044)"
          284  +       cx="6.1517248"
          285  +       cy="285.09021"
          286  +       fx="6.1517248"
          287  +       fy="285.09021"
          288  +       r="1.3844374" />
          289  +    <radialGradient
          290  +       inkscape:collect="always"
          291  +       xlink:href="#linearGradient963"
          292  +       id="radialGradient965"
          293  +       cx="6.1523438"
          294  +       cy="285.08984"
          295  +       fx="6.1523438"
          296  +       fy="285.08984"
          297  +       r="1.6679688"
          298  +       gradientTransform="matrix(1,0,0,0.99999775,0,6.4095141e-4)"
          299  +       gradientUnits="userSpaceOnUse" />
          300  +    <radialGradient
          301  +       inkscape:collect="always"
          302  +       xlink:href="#linearGradient971"
          303  +       id="radialGradient973"
          304  +       cx="4.6300988"
          305  +       cy="285.0715"
          306  +       fx="4.6300988"
          307  +       fy="285.0715"
          308  +       r="0.88396439"
          309  +       gradientTransform="matrix(3.5121044,0,0,4.5073949,-11.771195,-1000.0836)"
          310  +       gradientUnits="userSpaceOnUse" />
          311  +    <linearGradient
          312  +       inkscape:collect="always"
          313  +       xlink:href="#linearGradient979"
          314  +       id="linearGradient981"
          315  +       x1="6.3269596"
          316  +       y1="286.08289"
          317  +       x2="6.3263793"
          318  +       y2="288.44873"
          319  +       gradientUnits="userSpaceOnUse" />
          320  +    <linearGradient
          321  +       inkscape:collect="always"
          322  +       xlink:href="#linearGradient1019"
          323  +       id="linearGradient1009"
          324  +       gradientUnits="userSpaceOnUse"
          325  +       x1="6.3269596"
          326  +       y1="286.08289"
          327  +       x2="6.3263793"
          328  +       y2="288.44873" />
          329  +    <radialGradient
          330  +       inkscape:collect="always"
          331  +       xlink:href="#linearGradient1034"
          332  +       id="radialGradient1028"
          333  +       cx="7.8964839"
          334  +       cy="10.825195"
          335  +       fx="7.8964839"
          336  +       fy="10.825195"
          337  +       r="6.1388507"
          338  +       gradientTransform="matrix(1.5553588,0,0,2.1211746,-4.385382,-12.136934)"
          339  +       gradientUnits="userSpaceOnUse" />
          340  +    <radialGradient
          341  +       inkscape:collect="always"
          342  +       xlink:href="#linearGradient1034"
          343  +       id="radialGradient1038"
          344  +       gradientUnits="userSpaceOnUse"
          345  +       gradientTransform="matrix(-1.5553588,0,0,-1.3840186,20.178349,25.807467)"
          346  +       cx="7.8964839"
          347  +       cy="10.825195"
          348  +       fx="7.8964839"
          349  +       fy="10.825195"
          350  +       r="6.1388507" />
          351  +    <radialGradient
          352  +       inkscape:collect="always"
          353  +       xlink:href="#linearGradient1054"
          354  +       id="radialGradient1048"
          355  +       gradientUnits="userSpaceOnUse"
          356  +       gradientTransform="matrix(2.4674713,0,0,2.4674669,-5.8821075,-417.49152)"
          357  +       cx="6.1517248"
          358  +       cy="285.09021"
          359  +       fx="6.1517248"
          360  +       fy="285.09021"
          361  +       r="1.3844374" />
          362  +    <filter
          363  +       inkscape:collect="always"
          364  +       style="color-interpolation-filters:sRGB"
          365  +       id="filter1132"
          366  +       x="-0.27479975"
          367  +       width="1.5495995"
          368  +       y="-0.27480025"
          369  +       height="1.5496005">
          370  +      <feGaussianBlur
          371  +         inkscape:collect="always"
          372  +         stdDeviation="0.3170359"
          373  +         id="feGaussianBlur1134" />
          374  +    </filter>
          375  +    <radialGradient
          376  +       inkscape:collect="always"
          377  +       xlink:href="#linearGradient1054"
          378  +       id="radialGradient1140"
          379  +       gradientUnits="userSpaceOnUse"
          380  +       gradientTransform="matrix(2.4674713,0,0,2.4674669,-5.8821075,-417.49152)"
          381  +       cx="6.1517248"
          382  +       cy="285.09021"
          383  +       fx="6.1517248"
          384  +       fy="285.09021"
          385  +       r="1.3844374" />
          386  +    <radialGradient
          387  +       inkscape:collect="always"
          388  +       xlink:href="#linearGradient1005"
          389  +       id="radialGradient1142"
          390  +       gradientUnits="userSpaceOnUse"
          391  +       gradientTransform="matrix(2.4674713,0,0,2.4674669,-5.8821073,-417.49152)"
          392  +       cx="6.1517248"
          393  +       cy="285.09021"
          394  +       fx="6.1517248"
          395  +       fy="285.09021"
          396  +       r="1.3844374" />
          397  +    <radialGradient
          398  +       inkscape:collect="always"
          399  +       xlink:href="#linearGradient1034"
          400  +       id="radialGradient1149"
          401  +       gradientUnits="userSpaceOnUse"
          402  +       gradientTransform="matrix(-1.5553588,0,0,-1.3840186,20.178349,25.807467)"
          403  +       cx="7.8964839"
          404  +       cy="10.825195"
          405  +       fx="7.8964839"
          406  +       fy="10.825195"
          407  +       r="6.1388507" />
          408  +    <linearGradient
          409  +       inkscape:collect="always"
          410  +       xlink:href="#linearGradient979"
          411  +       id="linearGradient1151"
          412  +       gradientUnits="userSpaceOnUse"
          413  +       x1="6.3269596"
          414  +       y1="286.08289"
          415  +       x2="6.3263793"
          416  +       y2="288.44873" />
          417  +    <radialGradient
          418  +       inkscape:collect="always"
          419  +       xlink:href="#linearGradient971"
          420  +       id="radialGradient1153"
          421  +       gradientUnits="userSpaceOnUse"
          422  +       gradientTransform="matrix(3.5121044,0,0,4.5073949,-11.771195,-1000.0836)"
          423  +       cx="4.6300988"
          424  +       cy="285.0715"
          425  +       fx="4.6300988"
          426  +       fy="285.0715"
          427  +       r="0.88396439" />
          428  +  </defs>
          429  +  <sodipodi:namedview
          430  +     id="base"
          431  +     pagecolor="#181818"
          432  +     bordercolor="#666666"
          433  +     borderopacity="1.0"
          434  +     inkscape:pageopacity="0"
          435  +     inkscape:pageshadow="2"
          436  +     inkscape:zoom="11.2"
          437  +     inkscape:cx="30.205871"
          438  +     inkscape:cy="-5.2770461"
          439  +     inkscape:document-units="mm"
          440  +     inkscape:current-layer="layer1"
          441  +     showgrid="false"
          442  +     units="px"
          443  +     inkscape:window-width="1920"
          444  +     inkscape:window-height="1042"
          445  +     inkscape:window-x="0"
          446  +     inkscape:window-y="38"
          447  +     inkscape:window-maximized="0"
          448  +     showguides="true"
          449  +     fit-margin-top="0"
          450  +     fit-margin-left="0"
          451  +     fit-margin-right="0"
          452  +     fit-margin-bottom="0" />
          453  +  <metadata
          454  +     id="metadata5">
          455  +    <rdf:RDF>
          456  +      <cc:Work
          457  +         rdf:about="">
          458  +        <dc:format>image/svg+xml</dc:format>
          459  +        <dc:type
          460  +           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
          461  +        <dc:title></dc:title>
          462  +      </cc:Work>
          463  +    </rdf:RDF>
          464  +  </metadata>
          465  +  <g
          466  +     inkscape:label="Layer 1"
          467  +     inkscape:groupmode="layer"
          468  +     id="layer1"
          469  +     transform="translate(-2.6134661,-283.36966)">
          470  +    <g
          471  +       id="g1147"
          472  +       transform="matrix(0.9120087,0,0,0.9120087,0.33514277,25.128587)"
          473  +       style="stroke-width:1.09648085">
          474  +      <path
          475  +         transform="matrix(0.26458333,0,0,0.26458333,2.6134661,283.36966)"
          476  +         d="m 7.9257812,0.8359375 a 1.6178892,1.6178892 0 0 0 -0.2207031,0.009766 C 5.1410214,1.1501128 3.6301435,2.94586 3.1875,4.4550781 2.798777,5.7804512 2.9557893,6.822698 3.0019531,7.09375 c 0.045127,0.9218304 0.3822636,1.5885831 0.7910157,2.2910156 0.3127802,0.5375068 0.7335894,1.0229114 1.15625,1.4980464 0.028572,0.111047 0.050755,0.187185 0.082031,0.326172 0.0073,0.03244 0.00535,0.02259 0.011719,0.05664 -0.3761238,0.02572 -0.8216256,0.06774 -1.2910157,0.138672 -0.4857631,0.07341 -0.9830994,0.168253 -1.4765625,0.332031 -0.493463,0.163778 -1.0998654,0.233591 -1.6191406,1.203125 -0.23350107,0.435868 -0.24245693,0.654459 -0.2890625,0.923828 -0.0466056,0.269369 -0.0772406,0.547444 -0.10351562,0.84961 -0.0525501,0.604332 -0.0820372,1.293158 -0.0996094,1.955078 -0.0351443,1.323838 -0.0214844,2.544922 -0.0214844,2.544922 A 1.6178892,1.6178892 0 0 0 1.7597656,20.814453 H 14.033203 a 1.6178892,1.6178892 0 0 0 1.617188,-1.601562 c 0,0 0.01366,-1.221084 -0.02149,-2.544922 -0.01757,-0.661919 -0.04706,-1.350747 -0.09961,-1.955078 -0.02628,-0.302166 -0.05691,-0.580241 -0.103516,-0.84961 -0.04661,-0.269368 -0.05556,-0.487961 -0.289062,-0.923828 C 14.61759,11.970465 14.010855,11.900125 13.517578,11.736328 13.024302,11.572531 12.526554,11.477735 12.041016,11.404297 11.571088,11.33322 11.126348,11.28939 10.75,11.263672 c 0.0028,-0.01394 8.95e-4,-0.0098 0.0039,-0.02344 0.02862,-0.129185 0.05214,-0.210465 0.08008,-0.320312 0.433111,-0.472266 0.867515,-0.9510681 1.1875,-1.498047 0.424707,-0.7259885 0.791976,-1.4348115 0.777344,-2.4375 l -0.02734,0.3183594 c 0,0 0.276624,-1.338406 -0.166015,-2.8476563 C 12.162829,2.9458278 10.651988,1.1499497 8.0878906,0.84570312 A 1.6178892,1.6178892 0 0 0 7.9257812,0.8359375 Z M 10.701172,11.511719 c -0.0029,-0.0036 0.007,0.0064 0.0039,0.002 -0.04632,-0.06737 -0.0293,-0.353973 -0.0293,0.134766 0,0.04526 0.01972,-0.102408 0.02539,-0.136719 z m -5.6093751,0.002 c 0.00534,0.02911 0.025391,0.180144 0.025391,0.134766 0,-0.486237 0.016732,-0.199942 -0.029297,-0.132813 -0.00293,0.0043 0.00675,-0.0055 0.00391,-0.002 z"
          477  +         id="path1021"
          478  +         style="fill:url(#radialGradient1149);fill-opacity:1;stroke:none;stroke-width:1.09648073px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
          479  +         inkscape:original="M 7.8964844 2.453125 C 3.8572424 2.9326715 4.6113281 7.0078125 4.6113281 7.0078125 C 4.6485531 7.8381593 5.5094605 9.2817797 6.4453125 10.195312 C 6.4986525 10.392157 6.734375 11.271085 6.734375 11.648438 C 6.734375 12.069175 6.1908578 12.91084 5.8691406 12.861328 C 5.5474216 12.811888 2.4532451 13.010035 2.0820312 13.703125 C 1.7108136 14.396064 1.7597656 19.197266 1.7597656 19.197266 L 14.033203 19.197266 C 14.033203 19.197266 14.082152 14.396064 13.710938 13.703125 C 13.339721 13.010224 10.247498 12.811816 9.9257812 12.861328 C 9.6040608 12.910798 9.0585937 12.069174 9.0585938 11.648438 C 9.0585938 11.282797 9.2742792 10.466705 9.3378906 10.230469 C 10.300978 9.3271083 11.194239 7.8711535 11.181641 7.0078125 C 11.181641 7.0078125 11.935727 2.9324069 7.8964844 2.453125 z "
          480  +         inkscape:radius="1.6177274"
          481  +         sodipodi:type="inkscape:offset" />
          482  +      <path
          483  +         sodipodi:nodetypes="cccsccscccc"
          484  +         inkscape:connector-curvature="0"
          485  +         d="m 3.0792354,288.44873 c 0,0 -0.013096,-1.27028 0.085122,-1.45362 0.098217,-0.18338 0.9166976,-0.23571 1.0018191,-0.22263 0.085121,0.0131 0.2291744,-0.20953 0.2291744,-0.32085 0,-0.11131 -0.085123,-0.41905 -0.085123,-0.41905 h 0.7851589 c 0,0 -0.085122,0.30774 -0.085122,0.41905 0,0.11132 0.1440525,0.33394 0.2291744,0.32085 0.085121,-0.0131 0.9036015,0.0393 1.0018191,0.22263 0.098217,0.18334 0.085121,1.45362 0.085121,1.45362 z"
          486  +         style="fill:url(#linearGradient1151);fill-opacity:1;stroke:none;stroke-width:0.29011053px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
          487  +         id="path893" />
          488  +      <path
          489  +         sodipodi:nodetypes="cczcc"
          490  +         inkscape:connector-curvature="0"
          491  +         d="m 4.7028071,284.01884 c -1.0687161,0.12688 -0.8692216,1.20492 -0.8692216,1.20492 0.015116,0.33718 0.5431676,1.06402 0.8692218,1.06402 0.3260542,0 0.8742883,-0.71682 0.8692217,-1.06402 0,0 0.1994946,-1.07811 -0.8692216,-1.20492"
          492  +         style="fill:url(#radialGradient1153);fill-opacity:1;stroke:none;stroke-width:0.18091933;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
          493  +         id="path888" />
          494  +    </g>
          495  +    <g
          496  +       id="g1138"
          497  +       transform="matrix(0.83362971,0,0,0.83362961,-1.263316,47.635689)"
          498  +       style="stroke-width:1.1995734">
          499  +      <path
          500  +         id="path1046"
          501  +         style="fill:url(#radialGradient1140);fill-opacity:1;stroke:none;stroke-width:0.19792962;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1132)"
          502  +         d="m 9.0817743,287.34356 h 0.43065 v -1.16911 H 10.681534 V 285.7438 H 9.5124243 v -1.16911 h -0.43065 v 1.16911 h -1.169115 v 0.43065 h 1.169115 z"
          503  +         inkscape:connector-curvature="0"
          504  +         sodipodi:nodetypes="ccccccccccccc" />
          505  +      <path
          506  +         sodipodi:nodetypes="ccccccccccccc"
          507  +         inkscape:connector-curvature="0"
          508  +         d="m 9.0817743,287.34356 h 0.43065 v -1.16911 H 10.681534 V 285.7438 H 9.5124243 v -1.16911 h -0.43065 v 1.16911 h -1.169115 v 0.43065 h 1.169115 z"
          509  +         style="fill:url(#radialGradient1142);fill-opacity:1;stroke:none;stroke-width:0.19792962;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
          510  +         id="path932" />
          511  +    </g>
          512  +  </g>
          513  +</svg>

Modified static/style.scss from [ba9256aed1] to [08446e37b3].

   585    585   			}
   586    586   			&:empty {
   587    587   				transition: 0.3s;
   588    588   				opacity: 0.0001; // qutebrowser won't show hints if opacity=0 :(
   589    589   				&:hover, &:focus { opacity: 0.6 !important; }
   590    590   			}
   591    591   		}
   592         -		> .like { background-image: url(/s/heart.webp); }
   593         -		> .rt   { background-image: url(/s/retweet.webp); }
          592  +		> .like   { background-image: url(/s/heart.webp); }
          593  +		> .rt     { background-image: url(/s/retweet.webp); }
   594    594   	}
   595    595   
   596    596   	// used for keyboard navigation
   597    597   	&.live-selected {
   598    598   		//margin-left: 0.4in; margin-right: -0.4in;
   599    599   		box-shadow: 0 0 0 1px tone(15%), 0 0 1in tone(5%, -0.5);
   600    600   		transform: scale(1.05) translateX(0.1in);
................................................................................
   988    988   body.notices {
   989    989   	form { text-align: center; }
   990    990   	div.notice {
   991    991   		padding: 0.15in;
   992    992   		background: linear-gradient(to bottom, tone(10%, -0.9), transparent);
   993    993   		border: 1px solid tone(-60%);
   994    994   		& + div.notice { border-top: none; }
   995         -		&.rt, &.like, &.reply { &::before {
          995  +		&.rt, &.like, &.reply, &.follow { &::before {
   996    996   			display: inline-block;
   997    997   			width: 1em; height: 1em;
   998    998   			margin-right: 1ex;
   999    999   			background-size: contain;
  1000   1000   			vertical-align: bottom;
  1001   1001   			content: ""; // 🙄
  1002   1002   		}}
  1003         -		&.rt::before    { background-image: url(/s/retweet.webp); }
  1004         -		&.like::before  { background-image: url(/s/heart.webp);   }
  1005         -		&.reply::before { background-image: url(/s/reply.webp);   }
         1003  +		&.rt::before     { background-image: url(/s/retweet.webp); }
         1004  +		&.like::before   { background-image: url(/s/heart.webp);   }
         1005  +		&.reply::before  { background-image: url(/s/reply.webp);   }
         1006  +		&.follow::before { background-image: url(/s/follow.webp);   }
  1006   1007   		> .action {
  1007   1008   			display: inline-block;
  1008   1009   			color: tone(5%);
  1009   1010   			> .id {
  1010   1011   				display: inline-block;
  1011   1012   				> img {
  1012   1013   					width: 1em; height: 1em;

Modified store.t from [adf1545306] to [8b07464dc2].

     2      2   local m = {
     3      3   	timepoint = lib.osclock.time_t;
     4      4   	scope = lib.enum {
     5      5   		'public', 'private', 'local';
     6      6   		'personal', 'direct', 'circle';
     7      7   	};
     8      8   	noticetype = lib.enum {
     9         -		'none', 'mention', 'reply', 'like', 'rt', 'react'
            9  +		'none', 'mention', 'reply', 'like', 'rt', 'react', 'follow'
    10     10   	};
    11     11   
    12     12   	relation = lib.set {
    13     13   		'follow',
    14     14   		'subscribe', -- get a notification for every post
    15     15   		'mute', -- posts will be completely hidden at all times
    16     16   		'block', -- no interactions will be permitted, but posts will remain visible