on main help sign in
improve star chart and update docs
46ff0d6John Hyde
desk/app/astrolabe.hoon+69−42
@@ -1,26 +1,35 @@
1/- *astrolabe
1/- *astrolabe
2−/+ *astrolabe, *search, naive, default-agent, dbug, agentio
2+/+ *astrolabe, *search, *mip, naive, default-agent, dbug, agentio
3/$ udon-to-docu %udon %docu
3/$ udon-to-docu %udon %docu
4|%
4|%
5+$ versioned-state
5+$ versioned-state
6 $% state-0
6 $% state-0
7 state-1
7 state-1
8+ state-2
8 ==
9 ==
9+$ state-0
10+$ state-0
10 $: %0
11 $: %0
11 spa=spawned
12 spa=spawned
12 ==
13 ==
14++$ fropoints-1
15+ $: fopoints=opoints :: forward ordered points
16+ ropoints=opoints :: reversed ordered points, e.g. .~palnet-sampel
17+ ==
13+$ state-1
18+$ state-1
14 $: %1
19 $: %1
15− fopoints=opoints :: forward ordered points
20+ fropoints-1
16− ropoints=opoints :: reversed ordered points, e.g. .~palnet-sampel
21+ ==
22++$ state-2
23+ $: %2
24+ spam=spawn-mip
25+ fropoints-1
17 ==
26 ==
18::
27::
19+$ spawned (jug ship ship)
28+$ spawned (jug ship ship)
20+$ card card:agent:gall
29+$ card card:agent:gall
21--
30--
22%- agent:dbug
31%- agent:dbug
23−=| state-1
32+=| state-2
24=* state -
33=* state -
25^- agent:gall
34^- agent:gall
26=<
35=<
@@ -39,11 +48,11 @@
39 |= old-state=vase
48 |= old-state=vase
40 ^- (quip card _this)
49 ^- (quip card _this)
41 =/ old !<(versioned-state old-state)
50 =/ old !<(versioned-state old-state)
42− =^ cards-0 old
51+ =^ cards-01 old
43− ?. ?=(%0 -.old) `old
52+ ?. ?=(?(%0 %1) -.old) `old
44 (on-naive-state:hc get-nas:hc)
53 (on-naive-state:hc get-nas:hc)
45− ?> ?=(%1 -.old)
54+ ?> ?=(%2 -.old)
46− [cards-0 this(state old)]
55+ [cards-01 this(state old)]
47++ on-poke on-poke:def
56++ on-poke on-poke:def
48++ on-watch on-watch:def
57++ on-watch on-watch:def
49++ on-leave on-leave:def
58++ on-leave on-leave:def
@@ -66,7 +75,7 @@
66 =/ peers .^((map ship ?(%alien %known)) %ax (scrio:hc %$ /peers))
75 =/ peers .^((map ship ?(%alien %known)) %ax (scrio:hc %$ /peers))
67 ``astrolabe-point-set+!>((turn ~(tap in peers) head))
76 ``astrolabe-point-set+!>((turn ~(tap in peers) head))
68 [%x %chart-data ~]
77 [%x %chart-data ~]
69− ``astrolabe-chart-data+!>(get-chart-data:hc)
78+ ``json+!>(get-chart-data:hc)
70 [%x %doc ^]
79 [%x %doc ^]
71 =/ doc-path=^path t.t.path
80 =/ doc-path=^path t.t.path
72 =/ doc (read-doc:hc doc-path)
81 =/ doc (read-doc:hc doc-path)
@@ -120,7 +129,7 @@
120++ scrio ~(scry agentio bowl)
129++ scrio ~(scry agentio bowl)
121++ az-events-card [%pass /azimuth-events %agent [our.bowl %azimuth] %watch /event]
130++ az-events-card [%pass /azimuth-events %agent [our.bowl %azimuth] %watch /event]
122++ get-nas .^(^state:naive %gx (scrio %azimuth /nas/noun))
131++ get-nas .^(^state:naive %gx (scrio %azimuth /nas/noun))
123−++ put-in-opoints
132+++ put-in-fropoints
124 |= =ship
133 |= =ship
125 ^- _state
134 ^- _state
126 =. fopoints
135 =. fopoints
@@ -128,6 +137,28 @@
128 =. ropoints
137 =. ropoints
129 (put:ors ropoints (switch-words (to-q ship)) ~)
138 (put:ors ropoints (switch-words (to-q ship)) ~)
130 state
139 state
140+::
141+++ put-in-spawn-mip
142+ |= [child=ship nas=^state:naive]
143+ ^- _state
144+ =/ =npoint (get-npoint-from-nas child nas)
145+ =. spam
146+ =/ parent (^sein:title child)
147+ ?: =(parent child) spam
148+ ?: (is-npoint-locked npoint) spam
149+ =/ pdata (pdata:smel (pmeta:smel child npoint) ~)
150+ =* spo sponsor.net.npoint
151+ =? spam &(has.spo !=(parent who.spo))
152+ %^ ~(put bi-meta spam)
153+ who.spo
154+ child
155+ pdata
156+ %^ ~(put bi-meta spam)
157+ parent
158+ child
159+ pdata
160+ state
161+::
131++ on-naive-state
162++ on-naive-state
132 |= nas=^state:naive
163 |= nas=^state:naive
133 ^- (quip card _state)
164 ^- (quip card _state)
@@ -137,7 +168,8 @@
137 ^- _state
168 ^- _state
138 ?~ points state
169 ?~ points state
139 =/ =ship -.n.points
170 =/ =ship -.n.points
140− =. state (put-in-opoints ship)
171+ =. state (put-in-fropoints ship)
172+ =. state (put-in-spawn-mip ship nas)
141 $(points l.points, state $(points r.points))
173 $(points l.points, state $(points r.points))
142::
174::
143++ on-naive-diff
175++ on-naive-diff
@@ -150,11 +182,15 @@
150 :: %point " ship: {<ship.diff>} - {<+>-.diff>}"
182 :: %point " ship: {<ship.diff>} - {<+>-.diff>}"
151 :: ==
183 :: ==
152 =? state ?=([%tx [* * * %spawn ship *] *] diff)
184 =? state ?=([%tx [* * * %spawn ship *] *] diff)
153− %- put-in-opoints
185+ %- put-in-fropoints
154 ship.tx.raw-tx.diff
186 ship.tx.raw-tx.diff
155 =? state ?=([%point @ %owner *] diff)
187 =? state ?=([%point @ %owner *] diff)
156− %- put-in-opoints
188+ %- put-in-fropoints
157 ship.diff
189 ship.diff
190+ =? state ?=([%point @ *] diff)
191+ %+ put-in-spawn-mip
192+ ship.diff
193+ get-nas
158 `state
194 `state
159::
195::
160++ is-spawn-tx
196++ is-spawn-tx
@@ -181,6 +217,10 @@
181 (sponsor-chain ship unpoint)
217 (sponsor-chain ship unpoint)
182 (probable-dominion ship unpoint)
218 (probable-dominion ship unpoint)
183 ==
219 ==
220+++ get-npoint-from-nas
221+ |= [=ship nas=^state:naive]
222+ ^- npoint
223+ (need (get:orm:naive points.nas ship))
184::
224::
185++ run-spawned-points
225++ run-spawned-points
186 |* [agg=mold targ=ship nas=^state:naive filter=spawned-filter]
226 |* [agg=mold targ=ship nas=^state:naive filter=spawned-filter]
@@ -265,37 +305,24 @@
265 $(ship parent, unpoint parent-point)
305 $(ship parent, unpoint parent-point)
266::
306::
267++ get-chart-data
307++ get-chart-data
268− ^- chart-data
308+ ^- json
269 =/ nas get-nas
309 =/ nas get-nas
270− :: =/ galaxies (gulf 0 30) :: todo remove
310+ =/ main-data
311+ ^- tape
312+ %- ~(rep by spam)
313+ |= [[=ship kids=(map ship tape)] agg=tape]
314+ ^- tape
315+ %+ weld agg
316+ %+ pdata:smel (ship:smel ship)
317+ (flop ~(val by kids))
318+ =/ gal-data
319+ %+ pdata:smel "base"
271− %+ turn galaxies
320+ %+ turn galaxies
272− |= =ship
321+ |= gal=ship
273− =/ point (need (get:orm:naive points.nas ship))
322+ =/ npoint (get-npoint-from-nas gal nas)
274− (get-point-chart-data nas ship point)
323+ (pdata:smel (pmeta:smel gal npoint) ~)
275−::
324+ s+(crip :(weld "[" gal-data main-data "]"))
276−++ get-point-chart-data
277− |= [nas=^state:naive =ship =npoint]
278− ^- point-chart-data
279− =, sponsor.net.npoint
280− =/ desc-has
281− ?. has %none
282− ?: =(who (^sein:title ship))
283− %same
284− %diff
285− :- :* ship
286− dominion.npoint
287− [desc-has who]
288− ==
289− (get-spawned-chart-data nas ship)
290::
325::
291−++ get-spawned-chart-data
292− |= [nas=^state:naive =ship]
293− ^- chart-data
294− :: [*point-meta ~]~
295− %- (run-spawned-points chart-data ship nas %unlocked)
296− |= [agg=chart-data [spawn=^ship spawn-point=npoint]]
297− :_ agg
298− (get-point-chart-data nas spawn spawn-point)
299++ read-doc
326++ read-doc
300 |= =path
327 |= =path
301 =/ doc-path=^path :(weld /doc/usr path /udon)
328 =/ doc-path=^path :(weld /doc/usr path /udon)
desk/desk.docket-0+1−1
@@ -4,7 +4,7 @@
4 image+'https://raw.githubusercontent.com/johnhyde/astrolabe/main/ui/src/assets/astrolabe-tile.svg'
4 image+'https://raw.githubusercontent.com/johnhyde/astrolabe/main/ui/src/assets/astrolabe-tile.svg'
5 glob-ames+[~dister-midlev-mindyr 0v0]
5 glob-ames+[~dister-midlev-mindyr 0v0]
6 base+'astrolabe'
6 base+'astrolabe'
7− version+[0 1 4]
7+ version+[0 2 2]
8 website+'https://github.com/johnhyde/astrolabe'
8 website+'https://github.com/johnhyde/astrolabe'
9 license+'MIT'
9 license+'MIT'
10==
10==
desk/doc/usr/astrolabe.udon+17−1
@@ -26,10 +26,26 @@ if you search for `***hec`, it will search for all stars under `~hec`,
26and all planets that match `~***hec-******`, but planets matching `~******-***hec`
26and all planets that match `~***hec-******`, but planets matching `~******-***hec`
27will be ignored to prevent the search from taking too long.
27will be ignored to prevent the search from taking too long.
28
28
29+## The Star Chart
30+
31+Click and drag to move your view, use the mouse wheel to zoom in and out. Click on a dot
32+to see the full Ship View. Enter full `@p`s in the search bar to find a specific point.
33+
34+Please note that stars [locked in the Linear Release Contract](/~/scry/docs/usr/astrolabe/star.html)
35+are not shown in the Star Chart, as they are not yet a part of the network (though they've technically
36+been spawned).
37+
38+The Sponsors/Parents toggle lets you switch between two different views of the network.
39+The default Sponsors view shows each planet under its sponsoring star, and each
40+star under its sponsoring galaxy. Planets and Stars without sponsors are not
41+present in this view.
42+The Parent view simply shows each point under the point that spawned it. All spawned points
43+are shown in this view, with the exception of locked stars.
44+In both views, all galaxies are shown for completeness.
45+
29## Upcoming Features
46## Upcoming Features
30
47
31In addition to improving the search feature over time, Astrolabe will one day feature:
48In addition to improving the search feature over time, Astrolabe will one day feature:
32−- a Star Chart, where you can visually see the layout of the network and explore freely.
33- Sigil Search, with which you can select the shapes of a sigil and search for ships
49- Sigil Search, with which you can select the shapes of a sigil and search for ships
34 that match.
50 that match.
35- Integrations with Groups, `%pals`, `%gora`, and whatever else seems fun and/or useful.
51- Integrations with Groups, `%pals`, `%gora`, and whatever else seems fun and/or useful.
desk/doc/usr/star.udon+7
@@ -12,3 +12,10 @@ Stars can also choose to de-sponsor planets which abuse the network.
12
12
13Stars are NFTs, but they can be exchanged 1:1 for WSTR at [star.market](https://star.market/),
13Stars are NFTs, but they can be exchanged 1:1 for WSTR at [star.market](https://star.market/),
14and you can trade fractional amounts of WSTR for ETH [on Uniswap V3](https://info.uniswap.org/#/pools/0x7f4a0822a6e3c085df795a8d78acbcd36f210ebf).
14and you can trade fractional amounts of WSTR for ETH [on Uniswap V3](https://info.uniswap.org/#/pools/0x7f4a0822a6e3c085df795a8d78acbcd36f210ebf).
15+
16+## Locked stars and the Linear Release Contract
17+
18+To prevent galaxies from spawning too many stars all at once, many galaxies' stars were locked
19+in an Ethereum contract, to be slowly released (at a linear rate) over time.
20+Stars which are locked in this way will show the contract address as their owner,
21+and any stars held by this contract are considered locked, even if they are ready to be released.
desk/lib/astrolabe.hoon+53−51
@@ -1,5 +1,5 @@
1/- sur=astrolabe
1/- sur=astrolabe
2−/+ azimuth
2+/+ azimuth, naive, mip
3=< [sur .]
3=< [sur .]
4=, sur
4=, sur
5|%
5|%
@@ -25,8 +25,20 @@
25 ^- ?
25 ^- ?
26 (rev-f lte a b)
26 (rev-f lte a b)
27::
27::
28−+$ opoints (tree [@ @]) :: ordered points [ship ~]
28++$ opoints (tree [@ @]) :: ordered points [ship ~]
29++ ors ((on @q @) rpor)
29++ ors ((on @q @) rpor)
30+++ or-meta ((on @p tape) por:naive)
31+++ bi-meta
32+ =| a=(map * (map))
33+ |@
34+ ++ put
35+ |* [b=* c=* d=*]
36+ %+ ~(put by a) b
37+ %+ put:or-meta
38+ (~(gut by a) b ~)
39+ [c d]
40+ --
41+::
30+$ page-info [size=_100 page=_0]
42+$ page-info [size=_100 page=_0]
31++ fits-on-page
43++ fits-on-page
32 |= [i=@ =page-info]
44 |= [i=@ =page-info]
@@ -37,27 +49,50 @@
37 =/ page-end (add page-start size)
49 =/ page-end (add page-start size)
38 &((gte i page-start) (lth i page-end))
50 &((gte i page-start) (lth i page-end))
39::
51::
40−++ palor :: @p alphabetical order
41− ~/ %por
42− |= [a=@p b=@p]
43− ^- ?
44− ?: =(a b) &
45− =| i=@
46− |-
47− ?: =(i 2)
48− :: second two bytes
49− (lte a b)
50− :: first two bytes
51− =+ [c=(end 3 a) d=(end 3 b)]
52− ?: =(c d)
53− $(a (rsh 3 a), b (rsh 3 b), i +(i))
54− (lth c d)
55−::
56++ is-npoint-locked
52++ is-npoint-locked
57 |= p=npoint
53 |= p=npoint
58 .= linear-star-release:contracts:azimuth
54 .= linear-star-release:contracts:azimuth
59 address.owner.own.p
55 address.owner.own.p
60::
56::
57+++ smel
58+ |%
59+ ++ ship
60+ |= =^ship
61+ ^- tape
62+ (slag 1 (scow %p ship))
63+ ++ dom
64+ |= =dominion:naive
65+ ?- dominion
66+ %l1 ""
67+ %l2 "t"
68+ %spawn "s"
69+ ==
70+ ++ spo
71+ |= [self=^ship has=? spo=^ship]
72+ ?. has "~"
73+ =/ parent (^sein:title self)
74+ ?: =(spo parent)
75+ ""
76+ :(weld ":" (ship spo) "!" (ship parent))
77+ ++ pmeta
78+ |= [self=^ship point=npoint]
79+ ^- tape
80+ =/ self-str (ship self)
81+ =/ extras (weld (dom dominion.point) (spo self sponsor.net.point))
82+ ?~ extras self-str
83+ :(weld self-str ":" extras)
84+ ++ spa
85+ |= children=(list tape)
86+ =/ chil-str=tape (zing children)
87+ :: =/ chil-str=tape (reel children |=([a=tape b=tape] (weld a b)))
88+ ?~ chil-str ";"
89+ :(weld "[" chil-str "]")
90+ ++ pdata
91+ |= [pmeta=tape children=(list tape)]
92+ ^- tape
93+ (weld pmeta (spa children))
94+ --
95+::
61++ nu :: parse number as hex
96++ nu :: parse number as hex
62 |= jon=json
97 |= jon=json
63 ?> ?=([%s *] jon)
98 ?> ?=([%s *] jon)
@@ -143,39 +178,6 @@
143 ~
178 ~
144 ?: =('.' i.b) $(b t.b)
179 ?: =('.' i.b) $(b t.b)
145 [i.b $(b t.b)]
180 [i.b $(b t.b)]
146− ++ point-meta
147− |= m=^point-meta
148− ^- (list [@tas json])
149− =/ main-pairs=(list [@tas json])
150− :~
151− p+(ship ship.m)
152− :: spa-count+(numb spa-count.m)
153− :: [%d s+dom.m]
154− ==
155− =? main-pairs !=(dom.m %l1)
156− :_ main-pairs
157− d+s+dom.m
158− ?: =(has.spo.m %same) main-pairs
159− :_ main-pairs
160− :- %s
161− ?: =(has.spo.m %none) ~
162− (ship who.spo.m)
163− ::
164− ++ point-chart-data
165− |= d=^point-chart-data
166− ^- json
167− =/ point-meta-pairs (point-meta -.d)
168− %- pairs
169− ?~ spa.d point-meta-pairs
170− %+ snoc point-meta-pairs
171− c+(chart-data spa.d)
172− :: spa+a+~
173− ::
174− ++ chart-data
175− |= d=^chart-data
176− ^- json
177− :- %a
178− (turn d point-chart-data)
179 --
181 --
180::
182::
181++ dejs
183++ dejs
desk/mar/astrolabe/chart-data.hoondeleted−14
@@ -1,14 +0,0 @@
1−/+ *astrolabe
2−|_ data=chart-data
3−++ grab
4− |%
5− ++ noun chart-data
6− :: ++ json point:dejs
7− --
8−++ grow
9− |%
10− ++ noun data
11− ++ json (chart-data:enjs data)
12− --
13−++ grad %noun
14−--
desk/sur/astrolabe.hoon+3−21
@@ -1,4 +1,4 @@
1−/+ naive
1+/+ naive, *mip
2|%
2|%
3+$ npoint point:naive
3+$ npoint point:naive
4++ unpoint (unit npoint)
4++ unpoint (unit npoint)
@@ -11,24 +11,6 @@
11 ==
11 ==
12+$ spawned-filter ?(%all %locked %unlocked)
12+$ spawned-filter ?(%all %locked %unlocked)
13::
13::
14−+$ point-meta
14++$ spawn-mip (mip ship ship tape)
15− $: =ship
15+::
16− :: spa-count=@ud
17− dom=dominion:naive
18− spo=[has=?(%none %same %diff) who=ship]
19− :: moons=(list ship)
20− ==
21−:: +$ point-chart-data
22−:: $% [%czar point-meta spa=chart-data]
23−:: [%king point-meta spa=chart-data]
24−:: [%duke point-meta]
25−:: ==
26−+$ point-chart-data [point-meta spa=chart-data]
27−:: +$ star-chart-data [self=point planets=(list point)]
28−:: +$ galaxy-chart-data stars=(list star-chart-data)
29−++ chart-data
30− |-
31− $@ ~
32− (list [point-meta spa=$])
33−
34--
16--
ui/src/components/ChartLink.sveltenew file+11
@@ -0,0 +1,11 @@
1+<script lang="ts">
2+ import { link } from 'svelte-spa-router'
3+ import { linkToChart } from '../lib/link';
4+
5+ export let patp = null;
6+</script>
7+
8+<a href={linkToChart(patp)} use:link
9+ class="bg-navy text-white px-2 py-1 rounded no-underline">
10+ <slot/>
11+</a>
ui/src/components/HelpModal.svelte+2−2
@@ -12,8 +12,8 @@
12</script>
12</script>
13
13
14{#if $docs.show}
14{#if $docs.show}
15− <div class="absolute w-full h-full bg-navy opacity-50 cursor-pointer z-10" on:click/>
15+ <div class="absolute w-full h-full bg-navy opacity-50 cursor-pointer z-50" on:click/>
16− <div class="absolute w-full h-full z-10 overflow-y-auto" on:click={close}>
16+ <div class="absolute w-full h-full z-50 overflow-y-auto" on:click={close}>
17 <div class="mx-4 2xs:mx-8 md:max-w-xl md:mx-auto">
17 <div class="mx-4 2xs:mx-8 md:max-w-xl md:mx-auto">
18 <div class="py-4 2xs:py-8">
18 <div class="py-4 2xs:py-8">
19 <div on:click|stopPropagation>
19 <div on:click|stopPropagation>
ui/src/components/LoadingSpinner.sveltenew file+8
@@ -0,0 +1,8 @@
1+<script lang="ts">
2+ export let spinnerClass = "text-white";
3+</script>
4+
5+<svg class="animate-spin h-5 w-5 mx-auto {spinnerClass}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
6+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
7+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
8+</svg>
ui/src/components/SearchBar.svelte+1−1
@@ -9,7 +9,7 @@
9 export let pauseAnalysis: boolean = false;
9 export let pauseAnalysis: boolean = false;
10 let debouncedSearch = search;
10 let debouncedSearch = search;
11
11
12− const placeholderText = "Search by @p or #. '*' matches any character";
12+ export let placeholderText = "Search by @p or #. '*' matches any character";
13
13
14 $: {
14 $: {
15 if (!pauseAnalysis) {
15 if (!pauseAnalysis) {
ui/src/components/ShipView.svelte+12−1
@@ -7,10 +7,12 @@
7 import GoldBadge from './GoldBadge.svelte';
7 import GoldBadge from './GoldBadge.svelte';
8 import TooltipAndDocLink from "./TooltipAndDocLink.svelte";
8 import TooltipAndDocLink from "./TooltipAndDocLink.svelte";
9 import ShipLink from './ShipLink.svelte';
9 import ShipLink from './ShipLink.svelte';
10+ import ChartLink from './ChartLink.svelte';
10 import EthAddressLink from './EthAddressLink.svelte';
11 import EthAddressLink from './EthAddressLink.svelte';
11 import CollapsibleContent from './CollapsibleContent.svelte';
12 import CollapsibleContent from './CollapsibleContent.svelte';
12 import ShipChain from './ShipChain.svelte';
13 import ShipChain from './ShipChain.svelte';
13 import ShipListings from './ShipListings.svelte';
14 import ShipListings from './ShipListings.svelte';
15+ import LoadingSpinner from './LoadingSpinner.svelte';
14
16
15 export let patp: string;
17 export let patp: string;
16
18
@@ -109,6 +111,7 @@
109 $: {
111 $: {
110 pointInfo = {};
112 pointInfo = {};
111 let spawnStatus: any,
113 let spawnStatus: any,
114+ spawnedUnlocked: boolean = false,
112 layer: any,
115 layer: any,
113 life: number,
116 life: number,
114 rift: number,
117 rift: number,
@@ -138,8 +141,10 @@
138 spawnStatus = spawnStatusOptions.locked;
141 spawnStatus = spawnStatusOptions.locked;
139 } else if (keysSet) {
142 } else if (keysSet) {
140 spawnStatus = spawnStatusOptions.spawned;
143 spawnStatus = spawnStatusOptions.spawned;
144+ spawnedUnlocked = true;
141 } else {
145 } else {
142 spawnStatus = spawnStatusOptions.spawnedNoKeys;
146 spawnStatus = spawnStatusOptions.spawnedNoKeys;
147+ spawnedUnlocked = true;
143 }
148 }
144 // const { sponsor } = npoint.net;
149 // const { sponsor } = npoint.net;
145 // if (sponsor.has) {
150 // if (sponsor.has) {
@@ -164,6 +169,7 @@
164 ...pointInfo,
169 ...pointInfo,
165 layer,
170 layer,
166 spawnStatus,
171 spawnStatus,
172+ spawnedUnlocked,
167 keysSet,
173 keysSet,
168 life,
174 life,
169 rift,
175 rift,
@@ -216,6 +222,11 @@
216 <ShipLink patp={parentChain.at(-1)}></ShipLink>
222 <ShipLink patp={parentChain.at(-1)}></ShipLink>
217 {/if}
223 {/if}
218 </div>
224 </div>
225+ {#if pointInfo?.spawnedUnlocked}
226+ <div class="w-full text-center my-3">
227+ <ChartLink {patp}>View in Star Chart ↗</ChartLink>
228+ </div>
229+ {/if}
219 <div class="mt-4 w-full break-words">
230 <div class="mt-4 w-full break-words">
220 {#if parentChain.length > 0}
231 {#if parentChain.length > 0}
221 <p>
232 <p>
@@ -228,7 +239,7 @@
228 <b slot="title">Azimuth Details</b>
239 <b slot="title">Azimuth Details</b>
229 <div slot="content">
240 <div slot="content">
230 {#await rawPointInfoPromise}
241 {#await rawPointInfoPromise}
231− Loading Azimuth info...
242+ <LoadingSpinner spinnerClass="text-black" />
232 {:then}
243 {:then}
233 {#if shipClass !== 'galaxy'}
244 {#if shipClass !== 'galaxy'}
234 <p>
245 <p>
ui/src/components/StarChart.svelte+98−28
@@ -1,23 +1,35 @@
1<script lang="ts">
1<script lang="ts">
2+ import LoadingSpinner from './LoadingSpinner.svelte';
3+
2 import { push } from 'svelte-spa-router'
4 import { push } from 'svelte-spa-router'
3
5
4 import Sigma from 'sigma';
6 import Sigma from 'sigma';
5 import Graph from 'graphology';
7 import Graph from 'graphology';
6
8
7 import { clan } from 'urbit-ob';
9 import { clan } from 'urbit-ob';
10+ import DocLink from './docs/DocLink.svelte';
8 import { cite, normalizeId } from '../lib/id';
11 import { cite, normalizeId } from '../lib/id';
9− import { populateGraph } from '../lib/graph';
12+ import { parseChartData, populateGraph } from '../lib/graph';
10 import { getChartData } from '../lib/api';
13 import { getChartData } from '../lib/api';
11 import { linkToShip } from '../lib/link';
14 import { linkToShip } from '../lib/link';
12
15
16+ import InlineSelect from './buttons/InlineSelect.svelte';
17+
13 export let patp: string;
18 export let patp: string;
14 let s;
19 let s;
15 let g = new Graph();
20 let g = new Graph();
21+ let highlightedPatp;
22+
23+ let firstRender = true;
24+ let mouseDown = false;
25+ let isDragging = false;
16
26
17 function sigma(container: HTMLElement, params: any = {}) {
27 function sigma(container: HTMLElement, params: any = {}) {
18 try {
28 try {
19 window['s'] = s = new Sigma(g, container, {
29 window['s'] = s = new Sigma(g, container, {
20 renderLabels: false,
30 renderLabels: false,
31+ // hideEdgesOnMove: true,
32+ allowInvalidContainer: true,
21 // labelColor: { color: WHITE },
33 // labelColor: { color: WHITE },
22 labelFont: 'monospace',
34 labelFont: 'monospace',
23 labelRenderedSizeThreshold: 9,
35 labelRenderedSizeThreshold: 9,
@@ -25,9 +37,32 @@
25 maxCameraRatio: 10,
37 maxCameraRatio: 10,
26 });
38 });
27
39
28− s.on('downNode', ({ node }) => {
40+ s.on('clickNode', ({ node }) => {
29 push(linkToShip(node));
41 push(linkToShip(node));
30 });
42 });
43+ s.on('afterRender', () => {
44+ if (firstRender) {
45+ firstRender = false;
46+ searchFunc();
47+ }
48+ });
49+ const mc = s.getMouseCaptor();
50+ mc.on("mousedown", () => {
51+ mouseDown = true;
52+ });
53+ mc.on('mousemove', () => {
54+ if (mouseDown) {
55+ isDragging = true;
56+ }
57+ });
58+ mc.on('mouseup', () => {
59+ if (!isDragging) {
60+ clearHighlight();
61+ }
62+ mouseDown = false;
63+ isDragging = false;
64+ })
65+
31 } catch(e) {
66 } catch(e) {
32 console.error('error in initializing sigma:', e);
67 console.error('error in initializing sigma:', e);
33 if (!params.retried) {
68 if (!params.retried) {
@@ -53,60 +88,70 @@
53 rawChartData = { error };
88 rawChartData = { error };
54 });
89 });
55 }
90 }
56− $: rawChartData, populateGraph(g, rawChartData), s?.scheduleRefresh(), searchFunc();
91+ $: parsedChartData = parseChartData(rawChartData);
92+ $: [parsedChartData, bySponsor], populateGraph(g, parsedChartData, bySponsor, () => {
93+ searchFunc();
94+ });
95+
96+ function clearHighlight() {
97+ const id = highlightedPatp;
98+ if (id) {
99+ setTimeout(() => {
100+ s.graph.removeNodeAttribute(id, 'highlighted');
101+ if (id == highlightedPatp)
102+ highlightedPatp = null;
103+ });
104+ }
105+ }
57
106
58− let highlightedPatp;
59 function searchFunc() {
107 function searchFunc() {
108+ // console.log('searching for ' + patp);
60 if (!s) return;
109 if (!s) return;
61− if (highlightedPatp) {
110+ if (highlightedPatp !== patp) clearHighlight();
62− s.graph.removeNodeAttribute(highlightedPatp, 'highlighted');
63− highlightedPatp = null;
64− }
65 if (!patp) return;
111 if (!patp) return;
66 let point = s.getNodeDisplayData(patp);
112 let point = s.getNodeDisplayData(patp);
67 if (!point) return;
113 if (!point) return;
68 s.graph.setNodeAttribute(patp, 'highlighted', true);
114 s.graph.setNodeAttribute(patp, 'highlighted', true);
69 highlightedPatp = patp;
115 highlightedPatp = patp;
70
116
71− let ratio = 0.05;
117+ let ratio = 0.03;
72 if (clan(patp) === 'star') {
118 if (clan(patp) === 'star') {
73− ratio = 0.035;
119+ ratio = 0.008;
74 } else if (clan(patp) === 'planet') {
120 } else if (clan(patp) === 'planet') {
75− ratio = 0.0025;
121+ ratio = 0.0005;
76 }
122 }
77
123
78 s.camera.animate({
124 s.camera.animate({
79 x: point.x,
125 x: point.x,
80 y: point.y,
126 y: point.y,
81 ratio: ratio,
127 ratio: ratio,
82− // ratio: 1,
83 angle: 0
128 angle: 0
84 }, {
129 }, {
85 duration: 500,
130 duration: 500,
86 });
131 });
87− s.scheduleRefresh();
88 };
132 };
89
133
90− $: s, patp, searchFunc();
134+ $: [s, patp], searchFunc();
91
135
92 let helpBoxExpanded = false;
136 let helpBoxExpanded = false;
93 let helpBoxClasses = '';
137 let helpBoxClasses = '';
94 $: {
138 $: {
95 helpBoxClasses = helpBoxExpanded ?
139 helpBoxClasses = helpBoxExpanded ?
96− "" :
140+ "w-full" :
97− "absolute border-r";
141+ "border-r";
98− // refreshGraph();
99− if (s) {
100− s.resize();
101− s.scheduleRefresh();
102− }
103 }
142 }
143+ let bySponsor = true;
104</script>
144</script>
105
145
106−<div class="border border-gold bg-navy w-full grow flex flex-col">
146+<div class="border border-gold bg-navy w-full grow flex flex-col relative">
107− <div class="flex flex-wrap {helpBoxClasses} border-b border-gold bg-navy text-white z-10 cursor-pointer justify-center px-2 py-1 gap-4"
147+ <div class="flex flex-wrap absolute {helpBoxClasses} border-b border-gold bg-navy text-white z-20 cursor-pointer justify-items-center px-2 py-1 gap-4"
108 on:click={() => helpBoxExpanded = !helpBoxExpanded }>
148 on:click={() => helpBoxExpanded = !helpBoxExpanded }>
109 {#if helpBoxExpanded}
149 {#if helpBoxExpanded}
150+ <DocLink modal path="astrolabe">
151+ <div class="text-center justify-self-start">
152+ About Astrolabe
153+ </div>
154+ </DocLink>
110 <div class="flex flex-wrap items-center gap-2">
155 <div class="flex flex-wrap items-center gap-2">
111 <div class="w-3 h-3 rounded-full bg-white"></div>
156 <div class="w-3 h-3 rounded-full bg-white"></div>
112 Galaxy
157 Galaxy
@@ -117,18 +162,43 @@
117 </div>
162 </div>
118 <div class="flex flex-wrap items-center gap-2">
163 <div class="flex flex-wrap items-center gap-2">
119 <div class="w-3 h-3 rounded-full bg-l1-blue"></div>
164 <div class="w-3 h-3 rounded-full bg-l1-blue"></div>
120− L1 Planet
165+ Planet (L1)
121 </div>
166 </div>
122 <div class="flex flex-wrap items-center gap-2">
167 <div class="flex flex-wrap items-center gap-2">
123 <div class="w-3 h-3 rounded-full bg-l2-green"></div>
168 <div class="w-3 h-3 rounded-full bg-l2-green"></div>
124− L2 Planet
169+ Planet (L2)
125 </div>
170 </div>
171+ {#if s.nodeDataCache['~' + window.ship]}
172+ <div class="flex flex-wrap items-center gap-2">
173+ <div class="w-3 h-3 rounded-full bg-you"></div>
174+ You
175+ </div>
176+ {/if}
126 {:else}
177 {:else}
127− Help ({patp})
178+ ?
128 {/if}
179 {/if}
129 </div>
180 </div>
130− <div class="grow flex">
181+ <div class="flex flex-wrap absolute right-0 border-l border-b border-gold bg-navy text-white z-10 cursor-pointer justify-center px-2 py-1 gap-4"
131− <div class="w-full" use:sigma>
182+ on:click={() => push('/') }>
183+ X
184+ </div>
185+ <div class="flex flex-col absolute -bottom-2 inset-x-0 text-white z-10 2xs:-bottom-3.5">
186+ <!-- <div class="ml-8 px-4 py-1 flex gap-x-1 items-center text-white"> -->
187+ <!-- <span>Arrange by</span> -->
188+ <InlineSelect bind:value={bySponsor}
189+ options={[
190+ ['Sponsors', true],
191+ ['Parents', false]
192+ ]}
193+ ></InlineSelect>
194+ <!-- </div> -->
195+ </div>
196+ {#await chartDataPromise}
197+ <div class="flex items-center justify-center absolute inset-0">
198+ <LoadingSpinner/>
132 </div>
199 </div>
200+ {/await}
201+ <div class="grow flex">
202+ <div class="w-full min-h-[20px]" use:sigma></div>
133 </div>
203 </div>
134</div>
204</div>
ui/src/components/buttons/InlineSelect.sveltenew file+16
@@ -0,0 +1,16 @@
1+<script lang="ts">
2+ export let options: [string, any][];
3+ export let value: any;
4+ export let borderClass = "border-gold";
5+ export let onClass = "bg-navy";
6+ export let offClass = "bg-white";
7+</script>
8+
9+<div class="flex mx-auto border {borderClass} rounded divide-x divide-gold bg-navy">
10+ {#each options as option}
11+ <div class="px-1.5 cursor-pointer" class:text-gray-400={value !== option[1]} on:click={() => value = option[1]}>
12+ {option[0]}
13+ </div>
14+ {/each}
15+</div>
16+
ui/src/components/buttons/ToggleButton.svelte+4−3
@@ -1,10 +1,11 @@
1<script lang="ts">
1<script lang="ts">
2 export let on: boolean = true;
2 export let on: boolean = true;
3+ export let borderClass = "border-navy";
4+ export let onClass = "bg-navy";
5+ export let offClass = "bg-white";
3</script>
6</script>
4
7
5<button on:click
8<button on:click
6− class="w-4 h-4 rounded-full border border-navy"
9+ class={`w-4 h-4 rounded-full border ${borderClass} ${on ? onClass : offClass}`}
7− class:bg-white={!on}
8− class:bg-navy={on}
9/>
10/>
10
11
ui/src/components/docs/DocContent.svelte+3−1
@@ -4,6 +4,8 @@
4
4
5 import docLinks from "../../actions/docLinks";
5 import docLinks from "../../actions/docLinks";
6 import docs from "../../stores/docs";
6 import docs from "../../stores/docs";
7+
8+ import LoadingSpinner from "../LoadingSpinner.svelte";
7
9
8 export let path:string;
10 export let path:string;
9
11
@@ -22,7 +24,7 @@
22
24
23<div class="doc">
25<div class="doc">
24 {#await docs.getDoc(path)}
26 {#await docs.getDoc(path)}
25− <h3>Loading page...</h3>
27+ <LoadingSpinner spinnerClass="text-black" />
26 {:then docHtml}
28 {:then docHtml}
27 <div use:docLinks={navigate}>
29 <div use:docLinks={navigate}>
28 {@html convertLinks(docHtml)}
30 {@html convertLinks(docHtml)}
ui/src/lib/api.ts+4−2
@@ -31,10 +31,12 @@ export async function searchPoints(search: string): Promise<Patp[]> {
31 return points.map(normalizeId);
31 return points.map(normalizeId);
32}
32}
33
33
34−export function getChartData(): Promise<any> {
34+export function getChartData(old = false): Promise<any> {
35− return scry<any>(`/chart-data`);
35+ return scry<any>('/chart-data' + (old ? '-old' : ''));
36}
36}
37
37
38+window['gcd'] = getChartData;
39+
38export function getDoc(path: string): Promise<string> {
40export function getDoc(path: string): Promise<string> {
39 return scry<string>(`/doc/${path}`);
41 return scry<string>(`/doc/${path}`);
40}
42}
ui/src/lib/colors.ts+4
@@ -5,3 +5,7 @@ export const LGREY = '#BBB';
5export const DGREY = '#777';
5export const DGREY = '#777';
6export const BLUE = '#1792c7';
6export const BLUE = '#1792c7';
7export const GREEN = '#17c76a';
7export const GREEN = '#17c76a';
8+export const YOU_ARE_HERE = '#802';
9+export const YOU = '#b01';
10+export const YOUR_STAR = '#d71';
11+export const YOUR_GAL = '#fb1';
ui/src/lib/graph.ts+187−41
@@ -1,8 +1,139 @@
1import Graph from 'graphology';
1import Graph from 'graphology';
2−import { clan, sein, patp2dec, patp } from 'urbit-ob';
3import * as colors from './colors';
2import * as colors from './colors';
4
3
4+const example = '[0[3216291:t;9219219;4608:s[156546;1612196;219621:~;321965149:4608;]1280[4289594624;4278191360;426672256;4260168960;4260824320;4246668544;4246144256;4253746432;4235396352;4230415616;4225697024;4227007744;4219667712;4222616832;4224648448;4214490368;4215997696;4219143424;4237296896;]]]'
5+
6+export interface ChartPoint {
7+ patp: string,
8+ dominion: ('l1' | 'l2' | 'spawn'),
9+ sponsor?: string,
10+ parent?: string,
11+ children?: ChartPoint[],
12+ sponsees?: ChartPoint[],
13+}
14+
15+function parseMetaString(metaString: string): ChartPoint {
16+ let idString, subMetaString = '';
17+ const colIndex = metaString.indexOf(':');
18+ if (colIndex === -1) {
19+ idString = metaString;
20+ } else {
21+ idString = metaString.slice(0, colIndex);
22+ subMetaString = metaString.slice(colIndex + 1);
23+ }
24+ const meta: ChartPoint = {
25+ patp: '~' + idString,
26+ dominion: 'l1',
27+ }
28+ if (subMetaString) {
29+ const firstChar = subMetaString[0];
30+ if (firstChar === 't') {
31+ meta.dominion = 'l2';
32+ subMetaString = subMetaString.slice(1);
33+ } else if (firstChar === 's') {
34+ meta.dominion = 'spawn';
35+ subMetaString = subMetaString.slice(1);
36+ }
37+ if (subMetaString) {
38+ if (subMetaString[0] === '~') {
39+ meta.sponsor = null;
40+ } else {
41+ let [sponsor, parent] = subMetaString.slice(1).split('!');
42+ meta.sponsor = '~' + sponsor;
43+ meta.parent = '~' + parent;
44+ }
45+ }
46+ }
47+ return meta;
48+}
49+
50+function extractMeta(data: string): [ChartPoint, string] {
51+ const index = data.search(/[[;]/);
52+ if (index === -1) {
53+ throw new Error(`No point meta present in ${data}`);
54+ }
55+ const metaString = data.slice(0, index);
56+ data = data.slice(index);
57+ return [parseMetaString(metaString), data];
58+}
59+
60+function extractChildren(data: string, sup: string = undefined): [ChartPoint[], ChartPoint[], string] {
61+ if (data[0] === ';') return [[], [], data.slice(1)];
62+ if (data[0] !== '[') throw new Error('Invalid children in data: ' + data.slice(0, 20));
63+
64+ let endFound = false;
65+ let counter = 0;
66+ data = data.slice(1);
67+ const children = [];
68+ const sponsees = [];
69+ while (!endFound && data.length && counter < 20000) {
70+ if (data[0] === ']') {
71+ endFound = true;
72+ data = data.slice(1);
73+ } else {
74+ let sub;
75+ [sub, data] = extractChartPoint(data);
76+ if (!sub.parent || sub.parent === sup)
77+ children.push(sub);
78+ if (sub.sponsor === undefined || sub.sponsor === sup)
79+ sponsees.push(sub);
80+ }
81+ counter++;
82+ }
83+ return [children, sponsees, data];
84+}
85+
86+function extractChartPoint(data: string): [ChartPoint, string] {
87+ let point, children, sponsees;
88+ [point, data] = extractMeta(data);
89+ [children, sponsees, data] = extractChildren(data, point.patp);
90+ point.children = children;
91+ point.sponsees = sponsees;
92+ return [point, data];
93+}
94+
95+function linkChartData(points: ChartPoint[], store: any): ChartPoint[] {
96+ if (!points) return points;
97+ return points.map(point => {
98+ if (!store[point.patp]) return point;
99+ if (!point.children?.length) {
100+ point.children = linkChartData(store[point.patp].children, store);
101+ }
102+ if (!point.sponsees?.length) {
103+ point.sponsees = linkChartData(store[point.patp].sponsees, store);
104+ }
105+ return point;
106+ });
107+}
108+
109+function _parseChartData(str: string): ChartPoint[] {
110+ if (!str) return [];
111+ let [baseChildren, _] = extractChildren(str);
112+ let store = {};
113+ baseChildren.forEach(child => {
114+ store[child.patp] = {
115+ children: child.children,
116+ sponsees: child.sponsees,
117+ };
118+ });
119+
120+ return linkChartData(store['~base'].children, store);
121+}
122+
123+export function parseChartData(data: string): ChartPoint[] {
124+ let start = Date.now();
125+ let parsed = _parseChartData(data);
126+ // console.log(`finished parsing chart data to js object in ${Date.now() - start} ms`);
127+ return parsed;
128+}
129+
5const circle = 2 * Math.PI;
130const circle = 2 * Math.PI;
131+function chordLength(radius, radians) {
132+ return 2 * radius * Math.sin(radians / 2);
133+}
134+function distancePolar(r1, th1, r2, th2) {
135+ return Math.sqrt((r1 * r1) + (r2 * r2) - (2 * r1 * r2 * Math.cos(th2 - th1)));
136+}
6export function arrangePointsInSpirals(
137export function arrangePointsInSpirals(
7 numPoints,
138 numPoints,
8 {
139 {
@@ -10,18 +141,22 @@ export function arrangePointsInSpirals(
10 startX = 0, startY = 0,
141 startX = 0, startY = 0,
11 maxArms = 6, maxPerArm = 45,
142 maxArms = 6, maxPerArm = 45,
12 makeSingleArmCircle = false,
143 makeSingleArmCircle = false,
13− }): { coords: any[], numArms: number } {
144+ }): { coords: any[], numArms: number, minDistanceBetween: number } {
14 const numSmallEnoughArms = Math.ceil(numPoints / maxPerArm);
145 const numSmallEnoughArms = Math.ceil(numPoints / maxPerArm);
15 let eRootNumArms = Math.max(1, Math.floor(Math.pow(numPoints, 1 / Math.E)));
146 let eRootNumArms = Math.max(1, Math.floor(Math.pow(numPoints, 1 / Math.E)));
16 const numArms = Math.max(numSmallEnoughArms, eRootNumArms);
147 const numArms = Math.max(numSmallEnoughArms, eRootNumArms);
17 const singleArmCircle = makeSingleArmCircle && numArms === 1;
148 const singleArmCircle = makeSingleArmCircle && numArms === 1;
18 const numPerArm = numPoints / numArms;
149 const numPerArm = numPoints / numArms;
19− // todo refine theta range, and rename maxPerArm (misnomer)
20 let armThetaRange = numPerArm * Math.min(Math.PI / 12, 0.25 * circle / numArms);
150 let armThetaRange = numPerArm * Math.min(Math.PI / 12, 0.25 * circle / numArms);
21 if (singleArmCircle) {
151 if (singleArmCircle) {
22 minDistance = maxDistance;
152 minDistance = maxDistance;
23− armThetaRange = 2 * Math.PI;
153+ armThetaRange = circle;
24 }
154 }
155+ const distanceRange = maxDistance - minDistance;
156+ const pointWeight = 1 / numPerArm;
157+ let minDistanceBetween = distancePolar(minDistance, 0,
158+ minDistance + distanceRange * pointWeight, armThetaRange * pointWeight);
159+ if (numPoints <= 3) minDistanceBetween = maxDistance * 0.75;
25 const coords = [];
160 const coords = [];
26 let lastArm = null;
161 let lastArm = null;
27 let indexInArm = null;
162 let indexInArm = null;
@@ -34,15 +169,11 @@ export function arrangePointsInSpirals(
34 indexInArm = 0;
169 indexInArm = 0;
35 }
170 }
36 const interArmProgress = arm / numArms;
171 const interArmProgress = arm / numArms;
37− // (i - (numarms * i/numpoints) * (numpoints/numarms)) / numperarm
38− // const intraArmProgress = (i - arm * numPerArm) / numPerArm;
39 const beginningOfNextArm = Math.floor(numPerArm * (arm + 1));
172 const beginningOfNextArm = Math.floor(numPerArm * (arm + 1));
40 const numInThisArm = beginningOfNextArm - (i - indexInArm);
173 const numInThisArm = beginningOfNextArm - (i - indexInArm);
41 const intraArmProgress = indexInArm / numInThisArm;
174 const intraArmProgress = indexInArm / numInThisArm;
42− // const intraArmProgress = (numArms * overallProgress) % 1;
43 const theta = circle * interArmProgress + armThetaRange * intraArmProgress;
175 const theta = circle * interArmProgress + armThetaRange * intraArmProgress;
44− let baseLine = maxDistance - minDistance;
45− let line = (minDistance + baseLine * intraArmProgress);
176+ let line = (minDistance + distanceRange * intraArmProgress);
46 let x = 0 + startX + line * Math.cos(theta);
177 let x = 0 + startX + line * Math.cos(theta);
47 let y = 0 + startY + line * Math.sin(theta);
178 let y = 0 + startY + line * Math.sin(theta);
48 coords.push({
179 coords.push({
@@ -54,17 +185,19 @@ export function arrangePointsInSpirals(
54 return {
185 return {
55 coords,
186 coords,
56 numArms,
187 numArms,
188+ minDistanceBetween,
57 };
189 };
58}
190}
59
191
60−export
61−
62−function populateGraph(g: Graph, rawChartData: any[]) {
192+export function populateGraph(g: Graph, chartData: ChartPoint[], bySponsor: boolean, callback?): Graph {
63− if (!(rawChartData instanceof Array)) return;
193+ if (!(chartData instanceof Array)) return;
64 if (!g) {
194 if (!g) {
65 g = new Graph();
195 g = new Graph();
196+ } else {
197+ g.clear();
66 }
198 }
67
199
200+ let subsKey = bySponsor ? 'sponsees' : 'children';
68 let sponsorEdges = [];
201 let sponsorEdges = [];
69
202
70 let offsetX = 0;
203 let offsetX = 0;
@@ -74,34 +207,35 @@ function populateGraph(g: Graph, rawChartData: any[]) {
74 let b = 9000000;
207 let b = 9000000;
75 let theta = 0;
208 let theta = 0;
76
209
77− rawChartData.forEach((galaxy, gi) => {
210+ chartData.forEach((galaxy, gi) => {
78 offsetX = 0 + (a + b * theta) * Math.cos(theta);
211 offsetX = 0 + (a + b * theta) * Math.cos(theta);
79 offsetY = 0 + (a + b * theta) * Math.sin(theta);
212 offsetY = 0 + (a + b * theta) * Math.sin(theta);
80
213
81 theta = 0.15 * (gi+1) * (1 - .15 * (gi / 256));
214 theta = 0.15 * (gi+1) * (1 - .15 * (gi / 256));
82− const gPatp = '~' + galaxy.p;
215+ const gPatp = galaxy.patp;
216+ const itsYou = gPatp === '~' + window.ship;
83 g.addNode(gPatp, {
217 g.addNode(gPatp, {
84− // id: gPatp,
85 label: gPatp,
218 label: gPatp,
86 size: 3,
219 size: 3,
87− color: colors.WHITE,
220+ color: itsYou ? colors.YOU : colors.WHITE,
88− // color: colors.'#fcd34d',
89 x: offsetX,
221 x: offsetX,
90 y: offsetY,
222 y: offsetY,
91 zIndex: 10,
223 zIndex: 10,
92 });
224 });
93
225
94− const stars = galaxy.c || [];
226+ const stars = galaxy[subsKey] || [];
95 const numStars = stars.length;
227 const numStars = stars.length;
96 const maxStarDistance = 60000 * 128;
228 const maxStarDistance = 60000 * 128;
229+ const minStarDistance = maxStarDistance / 4;
97 const starSize = 1.2 / Math.pow(numStars, 1 / 7);
230 const starSize = 1.2 / Math.pow(numStars, 1 / 7);
98 const {
231 const {
99 coords: starCoords,
232 coords: starCoords,
100 numArms: numStarArms,
233 numArms: numStarArms,
234+ minDistanceBetween: minDistanceBetweenStars,
101 } = arrangePointsInSpirals(numStars,
235 } = arrangePointsInSpirals(numStars,
102 {
236 {
103 maxDistance: maxStarDistance,
237 maxDistance: maxStarDistance,
104− minDistance: maxStarDistance / 4,
238+ minDistance: minStarDistance,
105 startX: offsetX,
239 startX: offsetX,
106 startY: offsetY,
240 startY: offsetY,
107 makeSingleArmCircle: true,
241 makeSingleArmCircle: true,
@@ -109,15 +243,19 @@ function populateGraph(g: Graph, rawChartData: any[]) {
109 let lastStarArm = null;
243 let lastStarArm = null;
110 let lastStarPatp = null;
244 let lastStarPatp = null;
111 stars.forEach((star, si) => {
245 stars.forEach((star, si) => {
112− const sPatp = '~' + star.p;
246+ const sPatp = star.patp;
113
247
114 const [starX, starY] = starCoords[si].coords;
248 const [starX, starY] = starCoords[si].coords;
115 const arm = starCoords[si].arm;
249 const arm = starCoords[si].arm;
250+
251+ const itsYou = sPatp === '~' + window.ship;
252+ if (itsYou) {
253+ g.setNodeAttribute(gPatp, 'color', colors.YOUR_GAL);
254+ }
116 g.addNode(sPatp, {
255 g.addNode(sPatp, {
117 label: sPatp,
256 label: sPatp,
118 size: starSize,
257 size: starSize,
119− // color: colors.arm % 2 == 0 ? LGREY : DGREY,
120− color: colors.GOLDT2,
258+ color: itsYou ? colors.YOU : colors.GOLDT2,
121 x: starX,
259 x: starX,
122 y: starY,
260 y: starY,
123 zIndex: 8,
261 zIndex: 8,
@@ -137,24 +275,27 @@ function populateGraph(g: Graph, rawChartData: any[]) {
137 }
275 }
138 lastStarArm = arm;
276 lastStarArm = arm;
139 lastStarPatp = sPatp;
277 lastStarPatp = sPatp;
140− if (star.s) {
278+ if (star.sponsor) {
141− sponsorEdges.push([sPatp, '~' + star.s, {
279+ sponsorEdges.push([sPatp, star.sponsor, {
142 color: colors.GREEN,
280 color: colors.GREEN,
143 size: 0.2,
281 size: 0.2,
144 zIndex: 11,
282 zIndex: 11,
145 }]);
283 }]);
146 }
284 }
147
285
148− const planets = star.c || [];
286+ const planets = star[subsKey] || [];
149 const numPlanets = planets.length;
287 const numPlanets = planets.length;
150− const maxPlanetDistance = starSize * 1.6 * 4000 * 64;
288+ // const maxPlanetDistance = starSize * 1.6 * 4000 * 64;
289+ const maxPlanetDistance = minDistanceBetweenStars * 0.45;
290+ const minPlanetDistance = maxPlanetDistance / 4;
151 const {
291 const {
152 coords: planetCoords,
292 coords: planetCoords,
153 numArms: numPlanetArms,
293 numArms: numPlanetArms,
294+ minDistanceBetween: minDistanceBetweenPlanets,
154 } = arrangePointsInSpirals(numPlanets,
295 } = arrangePointsInSpirals(numPlanets,
155 {
296 {
156 maxDistance: maxPlanetDistance,
297 maxDistance: maxPlanetDistance,
157− minDistance: maxPlanetDistance / 4,
298+ minDistance: minPlanetDistance,
158 startX: starX,
299 startX: starX,
159 startY: starY,
300 startY: starY,
160 maxArms: 30,
301 maxArms: 30,
@@ -163,18 +304,20 @@ function populateGraph(g: Graph, rawChartData: any[]) {
163 let lastPlanetArm = null;
304 let lastPlanetArm = null;
164 let lastPlanetPatp = null;
305 let lastPlanetPatp = null;
165 planets.forEach((planet, pi) => {
306 planets.forEach((planet, pi) => {
166− const pPatp = '~' + planet.p;
307+ const pPatp = planet.patp;
167− // const circle = 2 * Math.PI;
168− // let line = 16000 * 64;
169− // let planetX = 0 + starX + Math.cos(circle * pi / numPlanets) * line;
170− // let planetY = 0 + starY + Math.sin(circle * pi / numPlanets) * line;
171−
172 const [planetX, planetY] = planetCoords[pi].coords;
308 const [planetX, planetY] = planetCoords[pi].coords;
173 const arm = planetCoords[pi].arm;
309 const arm = planetCoords[pi].arm;
310+ const planetSize = Math.sqrt(Math.min(minPlanetDistance, minDistanceBetweenPlanets)) / 1300;
311+ const planetEdgeSize = planetSize / 3;
312+ const itsYou = pPatp === '~' + window.ship;
313+ if (itsYou) {
314+ g.setNodeAttribute(sPatp, 'color', colors.YOUR_STAR);
315+ g.setNodeAttribute(gPatp, 'color', colors.YOUR_GAL);
316+ }
174 g.addNode(pPatp, {
317 g.addNode(pPatp, {
175 label: pPatp,
318 label: pPatp,
176− size: .15,
319+ size: planetSize,
177− color: planet.d == 'l2' ? colors.GREEN : colors.BLUE,
320+ color: itsYou ? colors.YOU : (planet.dominion == 'l2' ? colors.GREEN : colors.BLUE),
178 x: planetX,
321 x: planetX,
179 y: planetY,
322 y: planetY,
180 zIndex: 6,
323 zIndex: 6,
@@ -182,22 +325,22 @@ function populateGraph(g: Graph, rawChartData: any[]) {
182 if (lastPlanetArm == arm) {
325 if (lastPlanetArm == arm) {
183 g.addEdge(pPatp, lastPlanetPatp, {
326 g.addEdge(pPatp, lastPlanetPatp, {
184 color: colors.DGREY,
327 color: colors.DGREY,
185− size: 0.05,
328+ size: planetEdgeSize,
186 zIndex: 5,
329 zIndex: 5,
187 });
330 });
188 } else {
331 } else {
189 if (numPlanetArms == 1) {
332 if (numPlanetArms == 1) {
190 g.addEdge(pPatp, sPatp, {
333 g.addEdge(pPatp, sPatp, {
191 color: colors.DGREY,
334 color: colors.DGREY,
192− size: 0.05,
335+ size: planetEdgeSize,
193 zIndex: 5,
336 zIndex: 5,
194 });
337 });
195 }
338 }
196 }
339 }
197− if (planet.s) {
340+ if (planet.sponsor) {
198− sponsorEdges.push([pPatp, '~' + planet.s, {
341+ sponsorEdges.push([pPatp, planet.sponsor, {
199 color: colors.GREEN,
342 color: colors.GREEN,
200− size: 0.05,
343+ size: planetEdgeSize,
201 zIndex: 11,
344 zIndex: 11,
202 }]);
345 }]);
203 }
346 }
@@ -209,5 +352,8 @@ function populateGraph(g: Graph, rawChartData: any[]) {
209 sponsorEdges.forEach(([point, sponsor, attrs]) => {
352 sponsorEdges.forEach(([point, sponsor, attrs]) => {
210 // g.addEdge(point, sponsor, attrs);
353 // g.addEdge(point, sponsor, attrs);
211 });
354 });
355+ if (callback) {
356+ setTimeout(callback, 0);
357+ }
212 return g;
358 return g;
213}
359}
ui/src/lib/id.ts+2
@@ -498,6 +498,8 @@ function analyzeSearch(search: string): SearchAnalysis {
498}
498}
499
499
500export {
500export {
501+ prefixes,
502+ suffixes,
501 SearchAnalysis,
503 SearchAnalysis,
502 normalizeId,
504 normalizeId,
503 normalizeIdAndDesig,
505 normalizeIdAndDesig,
ui/src/lib/link.ts+7
@@ -3,3 +3,10 @@ import { buildQuerystring } from './utils';
3export function linkToShip(patp: string, queryParams = {}): string {
3export function linkToShip(patp: string, queryParams = {}): string {
4 return `/ship/${patp}` + buildQuerystring(queryParams);
4 return `/ship/${patp}` + buildQuerystring(queryParams);
5}
5}
6+
7+export function linkToChart(patp: string): string {
8+ if (patp) {
9+ return `/chart/${patp}`;
10+ }
11+ return `/chart`;
12+}
ui/src/routes/Home.svelte+20−7
@@ -3,7 +3,7 @@
3 import { link, push, replace, location, querystring } from 'svelte-spa-router'
3 import { link, push, replace, location, querystring } from 'svelte-spa-router'
4 import pageName from '../stores/pageName';
4 import pageName from '../stores/pageName';
5 import { SearchAnalysis } from '../lib/id';
5 import { SearchAnalysis } from '../lib/id';
6− import { linkToShip } from '../lib/link';
6+ import { linkToShip, linkToChart } from '../lib/link';
7 import SearchBar from "../components/SearchBar.svelte";
7 import SearchBar from "../components/SearchBar.svelte";
8 import SearchResults from "../components/SearchResults.svelte";
8 import SearchResults from "../components/SearchResults.svelte";
9 import ShipView from '../components/ShipView.svelte';
9 import ShipView from '../components/ShipView.svelte';
@@ -57,6 +57,11 @@
57 nav(`/search/${search}`, addToHistory);
57 nav(`/search/${search}`, addToHistory);
58 }
58 }
59 }
59 }
60+ function navToChart(addToHistory = true) {
61+ params.mode = 'chart';
62+ params.arg = search;
63+ nav(linkToChart(search), addToHistory);
64+ }
60
65
61 function navToMode(mode, addToHistory = false) {
66 function navToMode(mode, addToHistory = false) {
62 switch (mode) {
67 switch (mode) {
@@ -72,15 +77,19 @@
72 }
77 }
73 // $: urlChangedSinceAnalysisPatp = (urlChanged - analysisPatpChanged) > 1000;
78 // $: urlChangedSinceAnalysisPatp = (urlChanged - analysisPatpChanged) > 1000;
74 function autoNav() {
79 function autoNav() {
75− if (params.mode !== 'chart' && search !== urlSearch) {
80+ if (search !== urlSearch) {
76 if (urlChangedSinceAnalysisPatp) {
81 if (urlChangedSinceAnalysisPatp) {
77 search = urlSearch;
82 search = urlSearch;
78 } else if (analysis.search === search) {
83 } else if (analysis.search === search) {
79 navigating = true;
84 navigating = true;
80− if (analysis.patpIsValid) {
85+ if (params.mode === 'chart') {
81− navToShipView(true);
86+ navToChart(analysis.patpIsValid);
82 } else {
87 } else {
88+ if (analysis.patpIsValid) {
89+ navToShipView(true);
90+ } else {
83− navToSearch(params.mode === 'ship');
91+ navToSearch(params.mode === 'ship');
92+ }
84 }
93 }
85 }
94 }
86 }
95 }
@@ -102,8 +111,10 @@
102 }
111 }
103 }
112 }
104
113
114+ let placeholderText;
105 $: {
115 $: {
106 if (analysis.search === urlSearch) {
116 if (analysis.search === urlSearch) {
117+ placeholderText = undefined;
107 switch (params.mode) {
118 switch (params.mode) {
108 case 'ship':
119 case 'ship':
109 $pageName = params.arg;
120 $pageName = params.arg;
@@ -113,6 +124,8 @@
113 break;
124 break;
114 case 'chart':
125 case 'chart':
115 $pageName = 'Star Chart';
126 $pageName = 'Star Chart';
127+ placeholderText = 'Search by @p or #.';
128+ break;
116 default:
129 default:
117 $pageName = null;
130 $pageName = null;
118 }
131 }
@@ -122,8 +135,8 @@
122
135
123</script>
136</script>
124
137
125−<div class="p-4 min-h-full flex flex-col items-center space-y-8 2xs:p-8">
138+<div class="p-4 space-y-4 min-h-full flex flex-col items-center 2xs:p-8 2xs:space-y-8">
126− <SearchBar bind:analysis bind:search />
139+ <SearchBar bind:analysis bind:search {placeholderText} />
127 <!-- <p class="bg-white">navigating</p> -->
140 <!-- <p class="bg-white">navigating</p> -->
128 {#if params.mode == "chart"}
141 {#if params.mode == "chart"}
129 <StarChart patp={analysis.patpIsValid ? analysis.patp : null} />
142 <StarChart patp={analysis.patpIsValid ? analysis.patp : null} />
ui/src/styles/tailwind-output.css+151−21
@@ -487,18 +487,60 @@ Ensure the default browser behavior of the `hidden` attribute.
487.relative {
487.relative {
488 position: relative;
488 position: relative;
489}
489}
490+.inset-0 {
491+ top: 0px;
492+ right: 0px;
493+ bottom: 0px;
494+ left: 0px;
495+}
496+.inset-x-0 {
497+ left: 0px;
498+ right: 0px;
499+}
500+.inset-x-auto {
501+ left: auto;
502+ right: auto;
503+}
504+.inset-x-full {
505+ left: 100%;
506+ right: 100%;
507+}
490.right-0 {
508.right-0 {
491 right: 0px;
509 right: 0px;
492}
510}
511+.-bottom-2 {
512+ bottom: -0.5rem;
513+}
493.top-0 {
514.top-0 {
494 top: 0px;
515 top: 0px;
495}
516}
517+.bottom-0 {
518+ bottom: 0px;
519+}
520+.-bottom-12 {
521+ bottom: -3rem;
522+}
523+.-bottom-8 {
524+ bottom: -2rem;
525+}
526+.-bottom-4 {
527+ bottom: -1rem;
528+}
529+.-bottom-5 {
530+ bottom: -1.25rem;
531+}
532+.-bottom-3 {
533+ bottom: -0.75rem;
534+}
496.z-10 {
535.z-10 {
497 z-index: 10;
536 z-index: 10;
498}
537}
499.z-20 {
538.z-20 {
500 z-index: 20;
539 z-index: 20;
501}
540}
541+.z-50 {
542+ z-index: 50;
543+}
502.m-4 {
544.m-4 {
503 margin: 1rem;
545 margin: 1rem;
504}
546}
@@ -512,6 +554,10 @@ Ensure the default browser behavior of the `hidden` attribute.
512 margin-left: 1rem;
554 margin-left: 1rem;
513 margin-right: 1rem;
555 margin-right: 1rem;
514}
556}
557+.mx-auto {
558+ margin-left: auto;
559+ margin-right: auto;
560+}
515.mx-8 {
561.mx-8 {
516 margin-left: 2rem;
562 margin-left: 2rem;
517 margin-right: 2rem;
563 margin-right: 2rem;
@@ -524,9 +570,9 @@ Ensure the default browser behavior of the `hidden` attribute.
524 margin-top: 0.25rem;
570 margin-top: 0.25rem;
525 margin-bottom: 0.25rem;
571 margin-bottom: 0.25rem;
526}
572}
527−.mx-auto {
573+.my-3 {
528− margin-left: auto;
574+ margin-top: 0.75rem;
529− margin-right: auto;
575+ margin-bottom: 0.75rem;
530}
576}
531.my-4 {
577.my-4 {
532 margin-top: 1rem;
578 margin-top: 1rem;
@@ -544,6 +590,10 @@ Ensure the default browser behavior of the `hidden` attribute.
544 margin-top: 0px;
590 margin-top: 0px;
545 margin-bottom: 0px;
591 margin-bottom: 0px;
546}
592}
593+.my-auto {
594+ margin-top: auto;
595+ margin-bottom: auto;
596+}
547.mt-2 {
597.mt-2 {
548 margin-top: 0.5rem;
598 margin-top: 0.5rem;
549}
599}
@@ -568,6 +618,9 @@ Ensure the default browser behavior of the `hidden` attribute.
568.mt-5 {
618.mt-5 {
569 margin-top: 1.25rem;
619 margin-top: 1.25rem;
570}
620}
621+.ml-8 {
622+ margin-left: 2rem;
623+}
571.-mt-1 {
624.-mt-1 {
572 margin-top: -0.25rem;
625 margin-top: -0.25rem;
573}
626}
@@ -595,6 +648,12 @@ Ensure the default browser behavior of the `hidden` attribute.
595.mt-20 {
648.mt-20 {
596 margin-top: 5rem;
649 margin-top: 5rem;
597}
650}
651+.-ml-1 {
652+ margin-left: -0.25rem;
653+}
654+.mr-3 {
655+ margin-right: 0.75rem;
656+}
598.-mt-0\.5 {
657.-mt-0\.5 {
599 margin-top: -0.125rem;
658 margin-top: -0.125rem;
600}
659}
@@ -625,6 +684,9 @@ Ensure the default browser behavior of the `hidden` attribute.
625.h-full {
684.h-full {
626 height: 100%;
685 height: 100%;
627}
686}
687+.h-5 {
688+ height: 1.25rem;
689+}
628.h-10 {
690.h-10 {
629 height: 2.5rem;
691 height: 2.5rem;
630}
692}
@@ -658,6 +720,9 @@ Ensure the default browser behavior of the `hidden` attribute.
658.h-24 {
720.h-24 {
659 height: 6rem;
721 height: 6rem;
660}
722}
723+.min-h-\[20px\] {
724+ min-height: 20px;
725+}
661.min-h-full {
726.min-h-full {
662 min-height: 100%;
727 min-height: 100%;
663}
728}
@@ -667,6 +732,9 @@ Ensure the default browser behavior of the `hidden` attribute.
667.w-full {
732.w-full {
668 width: 100%;
733 width: 100%;
669}
734}
735+.w-5 {
736+ width: 1.25rem;
737+}
670.w-10 {
738.w-10 {
671 width: 2.5rem;
739 width: 2.5rem;
672}
740}
@@ -741,6 +809,22 @@ Ensure the default browser behavior of the `hidden` attribute.
741.transform {
809.transform {
742 transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
810 transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
743}
811}
812+@-webkit-keyframes spin {
813+
814+ to {
815+ transform: rotate(360deg);
816+ }
817+}
818+@keyframes spin {
819+
820+ to {
821+ transform: rotate(360deg);
822+ }
823+}
824+.animate-spin {
825+ -webkit-animation: spin 1s linear infinite;
826+ animation: spin 1s linear infinite;
827+}
744.cursor-pointer {
828.cursor-pointer {
745 cursor: pointer;
829 cursor: pointer;
746}
830}
@@ -765,6 +849,9 @@ Ensure the default browser behavior of the `hidden` attribute.
765.justify-center {
849.justify-center {
766 justify-content: center;
850 justify-content: center;
767}
851}
852+.justify-items-center {
853+ justify-items: center;
854+}
768.gap-5 {
855.gap-5 {
769 gap: 1.25rem;
856 gap: 1.25rem;
770}
857}
@@ -784,6 +871,10 @@ Ensure the default browser behavior of the `hidden` attribute.
784 -moz-column-gap: 0.5rem;
871 -moz-column-gap: 0.5rem;
785 column-gap: 0.5rem;
872 column-gap: 0.5rem;
786}
873}
874+.gap-x-1 {
875+ -moz-column-gap: 0.25rem;
876+ column-gap: 0.25rem;
877+}
787.gap-x-3 {
878.gap-x-3 {
788 -moz-column-gap: 0.75rem;
879 -moz-column-gap: 0.75rem;
789 column-gap: 0.75rem;
880 column-gap: 0.75rem;
@@ -827,6 +918,10 @@ Ensure the default browser behavior of the `hidden` attribute.
827 border-right-width: calc(1px * var(--tw-divide-x-reverse));
918 border-right-width: calc(1px * var(--tw-divide-x-reverse));
828 border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
919 border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
829}
920}
921+.divide-gold > :not([hidden]) ~ :not([hidden]) {
922+ --tw-divide-opacity: 1;
923+ border-color: rgb(252 211 77 / var(--tw-divide-opacity));
924+}
830.justify-self-start {
925.justify-self-start {
831 justify-self: start;
926 justify-self: start;
832}
927}
@@ -851,6 +946,9 @@ Ensure the default browser behavior of the `hidden` attribute.
851.break-all {
946.break-all {
852 word-break: break-all;
947 word-break: break-all;
853}
948}
949+.rounded {
950+ border-radius: 0.25rem;
951+}
854.rounded-md {
952.rounded-md {
855 border-radius: 0.375rem;
953 border-radius: 0.375rem;
856}
954}
@@ -860,12 +958,12 @@ Ensure the default browser behavior of the `hidden` attribute.
860.rounded-full {
958.rounded-full {
861 border-radius: 9999px;
959 border-radius: 9999px;
862}
960}
863−.rounded {
864− border-radius: 0.25rem;
865−}
866.rounded-xl {
961.rounded-xl {
867 border-radius: 0.75rem;
962 border-radius: 0.75rem;
868}
963}
964+.rounded-sm {
965+ border-radius: 0.125rem;
966+}
869.border {
967.border {
870 border-width: 1px;
968 border-width: 1px;
871}
969}
@@ -881,15 +979,15 @@ Ensure the default browser behavior of the `hidden` attribute.
881.border-t {
979.border-t {
882 border-top-width: 1px;
980 border-top-width: 1px;
883}
981}
982+.border-r {
983+ border-right-width: 1px;
984+}
884.border-b {
985.border-b {
885 border-bottom-width: 1px;
986 border-bottom-width: 1px;
886}
987}
887.border-l {
988.border-l {
888 border-left-width: 1px;
989 border-left-width: 1px;
889}
990}
890−.border-r {
891− border-right-width: 1px;
892−}
893.border-gold {
991.border-gold {
894 --tw-border-opacity: 1;
992 --tw-border-opacity: 1;
895 border-color: rgb(252 211 77 / var(--tw-border-opacity));
993 border-color: rgb(252 211 77 / var(--tw-border-opacity));
@@ -914,14 +1012,14 @@ Ensure the default browser behavior of the `hidden` attribute.
914 --tw-border-opacity: 1;
1012 --tw-border-opacity: 1;
915 border-color: rgb(156 163 175 / var(--tw-border-opacity));
1013 border-color: rgb(156 163 175 / var(--tw-border-opacity));
916}
1014}
917−.bg-gold {
918− --tw-bg-opacity: 1;
919− background-color: rgb(252 211 77 / var(--tw-bg-opacity));
920−}
921.bg-navy {
1015.bg-navy {
922 --tw-bg-opacity: 1;
1016 --tw-bg-opacity: 1;
923 background-color: rgb(31 36 60 / var(--tw-bg-opacity));
1017 background-color: rgb(31 36 60 / var(--tw-bg-opacity));
924}
1018}
1019+.bg-gold {
1020+ --tw-bg-opacity: 1;
1021+ background-color: rgb(252 211 77 / var(--tw-bg-opacity));
1022+}
925.bg-white {
1023.bg-white {
926 --tw-bg-opacity: 1;
1024 --tw-bg-opacity: 1;
927 background-color: rgb(255 255 255 / var(--tw-bg-opacity));
1025 background-color: rgb(255 255 255 / var(--tw-bg-opacity));
@@ -938,6 +1036,10 @@ Ensure the default browser behavior of the `hidden` attribute.
938 --tw-bg-opacity: 1;
1036 --tw-bg-opacity: 1;
939 background-color: rgb(23 199 106 / var(--tw-bg-opacity));
1037 background-color: rgb(23 199 106 / var(--tw-bg-opacity));
940}
1038}
1039+.bg-you {
1040+ --tw-bg-opacity: 1;
1041+ background-color: rgb(187 0 17 / var(--tw-bg-opacity));
1042+}
941.bg-orange-gold {
1043.bg-orange-gold {
942 --tw-bg-opacity: 1;
1044 --tw-bg-opacity: 1;
943 background-color: rgb(247 211 146 / var(--tw-bg-opacity));
1045 background-color: rgb(247 211 146 / var(--tw-bg-opacity));
@@ -1035,14 +1137,18 @@ Ensure the default browser behavior of the `hidden` attribute.
1035.p-1 {
1137.p-1 {
1036 padding: 0.25rem;
1138 padding: 0.25rem;
1037}
1139}
1038−.py-0\.5 {
1039− padding-top: 0.125rem;
1040− padding-bottom: 0.125rem;
1041−}
1042.px-2 {
1140.px-2 {
1043 padding-left: 0.5rem;
1141 padding-left: 0.5rem;
1044 padding-right: 0.5rem;
1142 padding-right: 0.5rem;
1045}
1143}
1144+.py-1 {
1145+ padding-top: 0.25rem;
1146+ padding-bottom: 0.25rem;
1147+}
1148+.py-0\.5 {
1149+ padding-top: 0.125rem;
1150+ padding-bottom: 0.125rem;
1151+}
1046.py-0 {
1152.py-0 {
1047 padding-top: 0px;
1153 padding-top: 0px;
1048 padding-bottom: 0px;
1154 padding-bottom: 0px;
@@ -1071,10 +1177,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1071 padding-left: 2rem;
1177 padding-left: 2rem;
1072 padding-right: 2rem;
1178 padding-right: 2rem;
1073}
1179}
1074−.py-1 {
1075− padding-top: 0.25rem;
1076− padding-bottom: 0.25rem;
1077−}
1078.px-0\.5 {
1180.px-0\.5 {
1079 padding-left: 0.125rem;
1181 padding-left: 0.125rem;
1080 padding-right: 0.125rem;
1182 padding-right: 0.125rem;
@@ -1091,6 +1193,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1091 padding-left: 0.25rem;
1193 padding-left: 0.25rem;
1092 padding-right: 0.25rem;
1194 padding-right: 0.25rem;
1093}
1195}
1196+.px-1\.5 {
1197+ padding-left: 0.375rem;
1198+ padding-right: 0.375rem;
1199+}
1094.pt-10 {
1200.pt-10 {
1095 padding-top: 2.5rem;
1201 padding-top: 2.5rem;
1096}
1202}
@@ -1183,6 +1289,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1183 --tw-text-opacity: 1;
1289 --tw-text-opacity: 1;
1184 color: rgb(55 65 81 / var(--tw-text-opacity));
1290 color: rgb(55 65 81 / var(--tw-text-opacity));
1185}
1291}
1292+.text-black {
1293+ --tw-text-opacity: 1;
1294+ color: rgb(0 0 0 / var(--tw-text-opacity));
1295+}
1186.text-green-600 {
1296.text-green-600 {
1187 --tw-text-opacity: 1;
1297 --tw-text-opacity: 1;
1188 color: rgb(22 163 74 / var(--tw-text-opacity));
1298 color: rgb(22 163 74 / var(--tw-text-opacity));
@@ -1229,6 +1339,12 @@ Ensure the default browser behavior of the `hidden` attribute.
1229.opacity-50 {
1339.opacity-50 {
1230 opacity: 0.5;
1340 opacity: 0.5;
1231}
1341}
1342+.opacity-25 {
1343+ opacity: 0.25;
1344+}
1345+.opacity-75 {
1346+ opacity: 0.75;
1347+}
1232.opacity-10 {
1348.opacity-10 {
1233 opacity: 0.1;
1349 opacity: 0.1;
1234}
1350}
@@ -1252,6 +1368,14 @@ Ensure the default browser behavior of the `hidden` attribute.
1252}
1368}
1253@media (min-width: 360px) {
1369@media (min-width: 360px) {
1254
1370
1371+ .\32xs\:-bottom-3\.5 {
1372+ bottom: -0.875rem;
1373+ }
1374+
1375+ .\32xs\:-bottom-3 {
1376+ bottom: -0.75rem;
1377+ }
1378+
1255 .\32xs\:m-8 {
1379 .\32xs\:m-8 {
1256 margin: 2rem;
1380 margin: 2rem;
1257 }
1381 }
@@ -1261,6 +1385,12 @@ Ensure the default browser behavior of the `hidden` attribute.
1261 margin-right: 2rem;
1385 margin-right: 2rem;
1262 }
1386 }
1263
1387
1388+ .\32xs\:space-y-8 > :not([hidden]) ~ :not([hidden]) {
1389+ --tw-space-y-reverse: 0;
1390+ margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse)));
1391+ margin-bottom: calc(2rem * var(--tw-space-y-reverse));
1392+ }
1393+
1264 .\32xs\:p-8 {
1394 .\32xs\:p-8 {
1265 padding: 2rem;
1395 padding: 2rem;
1266 }
1396 }
ui/tailwind.config.cjs+1
@@ -14,6 +14,7 @@ module.exports = {
14 'orange-gold': '#f7d392',
14 'orange-gold': '#f7d392',
15 'l1-blue': '#1792c7',
15 'l1-blue': '#1792c7',
16 'l2-green': '#17c76a',
16 'l2-green': '#17c76a',
17+ 'you': '#b01',
17 'gold-s1': '#e3be45',
18 'gold-s1': '#e3be45',
18 'gold-s2': '#caa93e',
19 'gold-s2': '#caa93e',
19 'gold-s3': '#b09436',
20 'gold-s3': '#b09436',