on main help sign in
i'm the true sigilante
262ca0eJohn Hyde
ui/src/components/HomeScreenTiles.svelte+3−3
@@ -17,21 +17,21 @@
17</script>
17</script>
18
18
19<div class="pt-10 max-w-md w-full flex flex-wrap gap-5 justify-center">
19<div class="pt-10 max-w-md w-full flex flex-wrap gap-5 justify-center">
20− <div class="rounded-lg bg-white flex-grow max-w-xs">
20+ <div class="rounded-lg bg-white grow max-w-xs">
21 <DocLink modal path="astrolabe">
21 <DocLink modal path="astrolabe">
22 <div class="p-5 text-xl text-center">
22 <div class="p-5 text-xl text-center">
23 About Astrolabe
23 About Astrolabe
24 </div>
24 </div>
25 </DocLink>
25 </DocLink>
26 </div>
26 </div>
27− <div class="rounded-lg bg-white flex-grow max-w-xs">
27+ <div class="rounded-lg bg-white grow max-w-xs">
28 <DocLink modal path="">
28 <DocLink modal path="">
29 <div class="p-5 text-xl text-center">
29 <div class="p-5 text-xl text-center">
30 About Urbit IDs
30 About Urbit IDs
31 </div>
31 </div>
32 </DocLink>
32 </DocLink>
33 </div>
33 </div>
34− <a class="rounded-lg bg-navy border border-gold flex-grow max-w-s" use:link href="/chart">
34+ <a class="rounded-lg bg-navy border border-gold grow max-w-s" use:link href="/chart">
35 <p class="p-5 text-xl text-center text-white">
35 <p class="p-5 text-xl text-center text-white">
36 <br/>
36 <br/>
37 Star Chart
37 Star Chart
ui/src/components/ShipListing.svelte+1−1
@@ -25,7 +25,7 @@ on:click={handleClick}
25on:click
25on:click
26>
26>
27 <div class="flex flex-wrap p-4 items-center">
27 <div class="flex flex-wrap p-4 items-center">
28− <Sigil {patp} size={48} />
28+ <Sigil {patp} size={48} useNew />
29 <!-- {#if ship.avatar}
29 <!-- {#if ship.avatar}
30 <img src={ship.avatar} class="h-12 w-12 mr-4" />
30 <img src={ship.avatar} class="h-12 w-12 mr-4" />
31 {/if} -->
31 {/if} -->
ui/src/components/ShipView.svelte+1−1
@@ -181,7 +181,7 @@
181<div class="p-8 max-w-md w-full rounded-lg bg-white flex flex-col md:max-w-2xl">
181<div class="p-8 max-w-md w-full rounded-lg bg-white flex flex-col md:max-w-2xl">
182 <div class="flex flex-col items-center space-y-4 md:flex-row md:items-start md:space-x-8 md:space-y-0">
182 <div class="flex flex-col items-center space-y-4 md:flex-row md:items-start md:space-x-8 md:space-y-0">
183 <div class="w-full max-w-[256px] md:min-w-[256px] flex-shrink-0">
183 <div class="w-full max-w-[256px] md:min-w-[256px] flex-shrink-0">
184− <Sigil patp={patp} size={256} />
184+ <Sigil patp={patp} size={256} useNew />
185 </div>
185 </div>
186 <div class="grow w-full min-w-0">
186 <div class="grow w-full min-w-0">
187 <h2 class="text-2xl text-center">
187 <h2 class="text-2xl text-center">
ui/src/components/Sigil.svelte+16−6
@@ -2,6 +2,7 @@
2 import { clan } from 'urbit-ob';
2 import { clan } from 'urbit-ob';
3 import { sigil as sigil1, stringRenderer as sr1 } from '@tlon/sigil-js';
3 import { sigil as sigil1, stringRenderer as sr1 } from '@tlon/sigil-js';
4 import { sigil as sigil2, stringRenderer as sr2 } from '@johnhyde/sigil-js';
4 import { sigil as sigil2, stringRenderer as sr2 } from '@johnhyde/sigil-js';
5+ import { sigilScalingFunction } from 'lib/sigil';
5 import moonPng from '../assets/moon.png';
6 import moonPng from '../assets/moon.png';
6 import cometSvg from '../assets/comet.svg';
7 import cometSvg from '../assets/comet.svg';
7
8
@@ -20,15 +21,24 @@
20 if (displaySigil) {
21 if (displaySigil) {
21 let sigil = sigil1;
22 let sigil = sigil1;
22 let sr = sr1;
23 let sr = sr1;
24+ let svgAST;
23 if (useNew) {
25 if (useNew) {
24 sigil = sigil2;
26 sigil = sigil2;
25 sr = sr2;
27 sr = sr2;
28+ svgAST = sigil({
29+ patp: patp,
30+ size,
31+ colors: [bgColor, fgColor],
32+ autoScaleStrokes: true,
33+ strokeScalingFunctionV2: sigilScalingFunction,
34+ });
35+ } else {
36+ svgAST = sigil({
37+ patp: patp,
38+ size,
39+ colors: [bgColor, fgColor],
40+ });
26 }
41 }
27− const svgAST = sigil({
28− patp: patp,
29− size,
30− colors: [bgColor, fgColor],
31− });
32 svgAST.attributes.preserveAspectRatio = 'xMidYMin slice';
42 svgAST.attributes.preserveAspectRatio = 'xMidYMin slice';
33 svgString = sr(svgAST);
43 svgString = sr(svgAST);
34 if (altBgColor) {
44 if (altBgColor) {
@@ -42,7 +52,7 @@
42 }
52 }
43</script>
53</script>
44
54
45−<div class="flex-grow" style:max-width="{size}px">
55+<div class="grow" style:max-width="{size}px">
46 {#if displaySigil}
56 {#if displaySigil}
47 {@html svgString}
57 {@html svgString}
48 {:else}
58 {:else}
ui/src/components/docs/Doc.svelte+1−1
@@ -26,7 +26,7 @@
26 {:else}
26 {:else}
27 <div class="w-7 h-7" />
27 <div class="w-7 h-7" />
28 {/if}
28 {/if}
29− <div class="flex-grow text-center text-xl">
29+ <div class="grow text-center text-xl">
30 Help
30 Help
31 </div>
31 </div>
32 <CloseButton on:click={close} />
32 <CloseButton on:click={close} />
ui/src/components/search/SearchBar.svelte+35−11
@@ -1,15 +1,20 @@
1<script lang="ts">
1<script lang="ts">
2+ import SigilInput from './sigil/SigilInput.svelte';
3+
2
4
3 import { SearchAnalysis } from 'lib/id';
5 import { SearchAnalysis } from 'lib/id';
6+ import { SigilQuery } from 'lib/sigil';
4
7
5 export let patp: string = undefined;
8 export let patp: string = undefined;
6 export let searchQuery: RegExp = /~der/;
9 export let searchQuery: RegExp = /~der/;
7 export let analysis: SearchAnalysis = new SearchAnalysis();
10 export let analysis: SearchAnalysis = new SearchAnalysis();
8 export let search = '';
11 export let search = '';
12+ export let sigilQuery: SigilQuery = new SigilQuery('planet');
9 export let pauseAnalysis: boolean = false;
13 export let pauseAnalysis: boolean = false;
10− let debouncedSearch = search;
11−
12 export let placeholderText = "Search by @p or #. '*' matches any character";
14 export let placeholderText = "Search by @p or #. '*' matches any character";
15+
16+ let debouncedSearch = search;
17+ let showSigilInput: boolean = false;
13
18
14 $: {
19 $: {
15 if (!pauseAnalysis) {
20 if (!pauseAnalysis) {
@@ -34,16 +39,35 @@
34 debouncedSearch = search;
39 debouncedSearch = search;
35 }, 300);
40 }, 300);
36 }
41 }
42+
43+ function openSigilInput() {
44+ showSigilInput = true;
45+ }
46+
47+ function closeSigilInput() {
48+ showSigilInput = false;
49+ }
37</script>
50</script>
38
51
39−<div class="relative max-w-md rounded-full w-full">
52+<div class="relative max-w-md w-full">
53+ {#if showSigilInput}
54+ <div class="rounded-2xl bg-white overflow-hidden">
55+ <SigilInput bind:sigilQuery></SigilInput>
56+ </div>
57+ <button
58+ on:click={closeSigilInput}
59+ class="absolute top-2 right-2 w-20 h-6 rounded-full bg-black transition-opacity opacity-10 hover:opacity-40"
60+ />
61+ {:else}
40− <input type="text" class="p-4 py-2 w-full rounded-full"
62+ <input type="text" class="p-4 py-2 w-full rounded-full"
41− spellcheck="false"
63+ spellcheck="false"
42− placeholder={placeholderText}
64+ placeholder={placeholderText}
43− bind:value={search}
65+ bind:value={search}
44− autofocus
66+ autofocus
67+ />
68+ <button
69+ on:click={openSigilInput}
70+ class="absolute top-1 right-1 w-8 h-8 rounded-full bg-black transition-opacity opacity-10 hover:opacity-40"
45 />
71 />
46− <button
72+ {/if}
47− class="absolute right-0 w-10 h-10 rounded-full bg-black transition-opacity opacity-10 hover:opacity-40"
48− />
49</div>
73</div>
ui/src/components/search/sigil/ClanSelect.sveltenew file+41
@@ -0,0 +1,41 @@
1+<script lang="ts">
2+ import ClanSelectOption from './ClanSelectOption.svelte';
3+
4+ import { capitalize } from 'lib/utils';
5+ import { CLANS, Clan } from 'types/sigil';
6+
7+ // const CLANS = ['galaxy', 'star', 'planet'] as const;
8+ // const ClanTuple = typeof CLANS;
9+ // type Clan = typeof ClanTuple[number];
10+
11+ export let sigilSearch = {};
12+ export let clan: Clan = 'planet';
13+
14+ let dropdownOpen = false;
15+
16+ function selectClan(itemClan: typeof clan) {
17+ clan = itemClan;
18+ dropdownOpen = false;
19+ }
20+</script>
21+
22+<div class="text-xl text-center bg-white">
23+ <p
24+ on:click={() => dropdownOpen = true}
25+ class=" py-2"
26+ >
27+ {capitalize(clan)}
28+ <span class="font-mono">▼</span>
29+ </p>
30+ {#if dropdownOpen}
31+ <div class="absolute top-1 w-full flex flex-col mx-auto divide-y bg-white">
32+ <h3 class="py-2" on:click={() => dropdownOpen = false}>
33+ Select Sigil Type
34+ </h3>
35+ {#each CLANS as itemClan}
36+ <ClanSelectOption clan={itemClan} on:click={() => selectClan(itemClan)} />
37+ {/each}
38+ </div>
39+ {/if}
40+</div>
41+
ui/src/components/search/sigil/ClanSelectOption.sveltenew file+17
@@ -0,0 +1,17 @@
1+<script lang="ts">
2+ import { capitalize } from 'lib/utils';
3+ import MiniFakeSigil from './MiniFakeSigil.svelte';
4+
5+ export let sigilSearch = {};
6+ export let clan: ('galaxy' | 'star' | 'planet') = 'planet';
7+
8+ let dropdownOpen = true;
9+</script>
10+
11+<div class="flex items-center justify-center gap-2 py-2" on:click>
12+ <h3>
13+ {capitalize(clan)}:
14+ </h3>
15+ <MiniFakeSigil {clan} />
16+</div>
17+
ui/src/components/search/sigil/GeonSelector.sveltenew file+29
@@ -0,0 +1,29 @@
1+<script lang="ts">
2+ import Symbol from './Symbol.svelte';
3+
4+ import type { PartType, SymbolQuery } from 'types/sigil';
5+ import { GEONS } from 'types/sigil';
6+
7+ export let symbolQuery: SymbolQuery;
8+ export let inputMode: PartType;
9+
10+ let size = 48;
11+
12+ function setGeon(geon) {
13+ symbolQuery.geon = geon;
14+ inputMode = undefined;
15+ }
16+</script>
17+
18+<div on:click={() => inputMode = undefined}>
19+ done
20+</div>
21+<div on:click={() => setGeon(undefined)}>
22+ clear
23+</div>
24+{#each GEONS as geon}
25+ <div style:width="{size}px" on:click={() => setGeon(geon)}>
26+ <Symbol components={[geon]} {size} />
27+ </div>
28+{/each}
29+
ui/src/components/search/sigil/MiniFakeSigil.sveltenew file+25
@@ -0,0 +1,25 @@
1+<script lang="ts">
2+ import { sigilFromSymbols, stringRenderer as sr, symbolFromDef } from '@johnhyde/sigil-js';
3+
4+ export let clan: ('galaxy' | 'star' | 'planet') = 'planet';
5+
6+ let size = 32;
7+ let squareDef = ['gci'];
8+ let square = symbolFromDef(squareDef);
9+ let sigilProps = {
10+ size,
11+ renderer: sr,
12+ colors: ['white', 'black'],
13+ icon: true,
14+ }
15+ let sigils = {
16+ galaxy: sigilFromSymbols([square], sigilProps),
17+ star: sigilFromSymbols([square, square], sigilProps),
18+ planet: sigilFromSymbols([square, square, square, square], sigilProps),
19+ }
20+ $: sigilWidth = (clan === 'galaxy' ? size / 2 : size);
21+</script>
22+
23+<div style:width="{sigilWidth}px">
24+ {@html sigils[clan]}
25+</div>
ui/src/components/search/sigil/SigilInput.sveltenew file+56
@@ -0,0 +1,56 @@
1+<script lang="ts">
2+ import SymbolInputModeSelector from './SymbolInputModeSelector.svelte';
3+ import SymbolInput from './SymbolInput.svelte';
4+ import ClanSelect from './ClanSelect.svelte';
5+
6+ import type { PartType } from 'types/sigil';
7+ import { SigilQuery } from 'lib/sigil';
8+
9+ export let sigilQuery: SigilQuery;
10+ // export let clan: ('galaxy' | 'star' | 'planet') = 'planet';
11+ let focusedSymbolIndex: number = 0;
12+ let inputMode: PartType = undefined;
13+
14+ // let symbols: SymbolQuery = [];
15+
16+ $: gridClasses = (sigilQuery.clan === 'galaxy' ? 'w-[50%]' : 'grid grid-cols-2 items-center');
17+
18+ $: focusedSymbol = sigilQuery.activeSymbols[focusedSymbolIndex];
19+ $: {
20+ const numActiveSymbols = sigilQuery.activeSymbols.length;
21+ if (focusedSymbolIndex >= numActiveSymbols) {
22+ focusedSymbolIndex = numActiveSymbols - 1;
23+ }
24+ }
25+ $: {
26+ if (!focusedSymbol.geon) {
27+ inputMode = 'geon';
28+ }
29+ }
30+ // function updateSigilQuery() {
31+ // sigilQuery.setSymbol(focusedSymbol, focusedSymbolIndex);
32+ // }
33+ // $: focusedSymbol, updateSigilQuery();
34+</script>
35+
36+<div class="">
37+ <ClanSelect bind:clan={sigilQuery.clan} />
38+ <div class="flex flex-col xs:flex-row">
39+ <div class="grow-[2] mx-auto w-full xs:m-0 xs:max-w-[18rem]">
40+ <div class={`max-w-[18rem] mx-auto p-4 ${gridClasses} xs:rounded-bl-2xl`}>
41+ {#each sigilQuery.activeSymbols as symbol, index}
42+ <SymbolInput
43+ bind:symbolQuery={symbol}
44+ focused={index === focusedSymbolIndex}
45+ on:click={() => focusedSymbolIndex = index}
46+ />
47+ {/each}
48+ </div>
49+ </div>
50+ <div class="bg-green-100 grow rounded-b-2xl xs:rounded-bl-none">
51+ {inputMode}
52+ <SymbolInputModeSelector bind:symbolQuery={focusedSymbol} bind:inputMode />
53+ </div>
54+ </div>
55+ {sigilQuery.string}
56+</div>
ui/src/components/search/sigil/Symbol.sveltenew file+41
@@ -0,0 +1,41 @@
1+<script lang="ts">
2+ import { sigilFromSymbols, stringRenderer as sr, symbolFromDef } from '@johnhyde/sigil-js';
3+ import { sigilScalingFunction } from 'lib/sigil';
4+
5+ export let components: string[];
6+ export let size: number;
7+ export let fgColor: string = 'black';
8+ export let bgColor: string = 'white';
9+ export let altBgColor: string = undefined;
10+ export let inverted: boolean = false;
11+ let svgString: any;
12+
13+ $: {
14+ const symbol = symbolFromDef(components);
15+ let colors = [bgColor, fgColor];
16+ let tempAltBgColor = altBgColor;
17+ if (inverted) {
18+ colors = [fgColor, bgColor];
19+ if (!tempAltBgColor) tempAltBgColor = bgColor;
20+ }
21+ const svgAST = sigilFromSymbols(
22+ [symbol],
23+ {
24+ size,
25+ colors,
26+ margin: false,
27+ autoScaleStrokes: true,
28+ strokeScalingFunctionV2: sigilScalingFunction,
29+ },
30+ );
31+ svgAST.attributes.preserveAspectRatio = 'xMidYMin slice';
32+ svgString = sr(svgAST);
33+ if (tempAltBgColor) {
34+ svgString = svgString.replace(/rect fill="[^"]+"/, `rect fill="${tempAltBgColor}"`);
35+ }
36+ }
37+</script>
38+
39+<div class="aspect-square" style:max-width="{size}px" on:click>
40+ {@html svgString}
41+</div>
ui/src/components/search/sigil/SymbolInput.sveltenew file+20
@@ -0,0 +1,20 @@
1+<script lang="ts">
2+ import Symbol from './Symbol.svelte';
3+
4+ import type { SymbolQuery } from 'types/sigil';
5+
6+ export let symbolQuery: SymbolQuery;
7+ export let focused: boolean = false;
8+ let size = 128;
9+
10+ $: focusedClasses = focused ? 'border border-gold-s1 border-2' : '';
11+</script>
12+
13+<div class="aspect-square flex {focusedClasses}" style:max-width="{size}px" on:click>
14+ {#if symbolQuery.components.length > 0}
15+ <Symbol components={symbolQuery.components} {size} inverted={!symbolQuery.geon} />
16+ {symbolQuery.geon}
17+ {:else}
18+ <div class="m-1 rounded-sm border-4 border-gray-500 grow"></div>
19+ {/if}
20+</div>
ui/src/components/search/sigil/SymbolInputModeSelector.sveltenew file+27
@@ -0,0 +1,27 @@
1+<script lang="ts">
2+ import GeonSelector from './GeonSelector.svelte';
3+ import SymbolInputModeSelectorButton from './SymbolInputModeSelectorButton.svelte';
4+
5+ import Symbol from './Symbol.svelte';
6+
7+ import type { PartType, SymbolQuery } from 'types/sigil';
8+ import { PART_TYPES } from 'types/sigil';
9+
10+ export let symbolQuery: SymbolQuery;
11+ export let inputMode: PartType;
12+
13+ let size = 48;
14+</script>
15+
16+<div class="grid grid-rows-2 grid-flow-col xs:grid-cols-2 xs:grid-flow-row place-content-center justify-center">
17+ {#if inputMode === 'geon'}
18+ <GeonSelector bind:symbolQuery bind:inputMode />
19+ {:else}
20+ {#each PART_TYPES as partType}
21+ <SymbolInputModeSelectorButton {partType} focused={inputMode === partType}
22+ on:click={() => inputMode = partType}
23+ />
24+ {/each}
25+ {/if}
26+</div>
27+
ui/src/components/search/sigil/SymbolInputModeSelectorButton.sveltenew file+38
@@ -0,0 +1,38 @@
1+<script lang="ts">
2+ import Symbol from './Symbol.svelte';
3+
4+ import type { PartType, SymbolQuery } from 'types/sigil';
5+ import { PART_TYPES } from 'types/sigil';
6+
7+ export let partType: PartType;
8+ export let focused: boolean = false;
9+
10+ let size = 48;
11+ $: focusedClasses = focused ? 'border border-gold-s1 border-2' : '';
12+</script>
13+
14+<div class="border-2 w-[48px] {focusedClasses}" on:click>
15+ {#if partType === 'geon'}
16+ <Symbol components={['ghb']} {size} />
17+ {:else if partType === 'line'}
18+ <Symbol components={['lvf', 'lvfl', 'lvfr', 'lhf', 'lhft', 'lhfb', 'lff', 'lbf']} {size} inverted />
19+ {:else if partType === 'arc'}
20+ <Symbol components={['abl202', 'abl402', 'abl602', 'abl802' ]} {size} inverted />
21+ {:else if partType === 'ring'}
22+ <Symbol components={['rm1', 'rm2', 'rm3']} {size} inverted />
23+ {:else if partType === 'circle'}
24+ <Symbol components={[
25+ // 'ctr1mb', 'cbr1mb', 'cbl1mb', 'ctl1mb',
26+ 'cbr3mb', 'ctl3mb',
27+ 'cmmb',
28+ 'ct2mb', 'cl2mb', 'cb2mb', 'cr2mb',
29+ ]} {size} inverted />
30+ {:else if partType === 'donut'}
31+ <Symbol components={['dbl', /*'dl',*/ 'dm', 'dtl', 'dtr']} {size} inverted />
32+ {:else if partType === 'bezier'}
33+ <Symbol components={['b1l1', 'b1l2', 'b1l3']} {size} inverted />
34+ {:else}
35+ not {partType}
36+ {/if}
37+</div>
38+
ui/src/lib/sigil.tsnew file+55
@@ -0,0 +1,55 @@
1+import type { Clan } from 'types/sigil';
2+import { SymbolQuery } from 'types/sigil';
3+
4+export class SigilQuery {
5+ clan: Clan;
6+ symbols: SymbolQuery[];
7+
8+ constructor(clan: Clan = 'planet', symbols = []) {
9+ this.clan = clan;
10+ this.symbols = symbols;
11+ this.padSymbols();
12+ }
13+
14+ padSymbols() {
15+ if (this.symbols.length === 1) {
16+ this.symbols.unshift(new SymbolQuery());
17+ }
18+ while (this.symbols.length < 4) {
19+ this.symbols.push(new SymbolQuery());
20+ }
21+ this.symbols = this.symbols.slice(0, 4);
22+ this.symbols.forEach((symbol, i) => {
23+ if (i % 2 === 0) {
24+ symbol.symbolType = 'prefix';
25+ // symbol.components = ['gci', 'abr622']
26+ } else {
27+ symbol.symbolType = 'suffix';
28+ symbol.components = ['gsq', 'abl202']
29+ }
30+ });
31+ this.symbols = this.symbols;
32+ }
33+
34+ get activeSymbols() {
35+ if (this.clan === 'galaxy')
36+ return [this.symbols[1]];
37+ if (this.clan === 'star')
38+ return this.symbols.slice(0, 2);
39+ return this.symbols;
40+ }
41+
42+ get string(): string {
43+ return this.symbols.map((symbol) => symbol.components.join()).join(' | ')
44+ }
45+
46+ setSymbol(symbol: SymbolQuery, activeSymbolIndex: number) {
47+ if (activeSymbolIndex >= this.activeSymbols.length)
48+ throw new Error(`Symbol index ${activeSymbolIndex} out of range for clan: ${this.clan}`);
49+ this.activeSymbols[activeSymbolIndex] = symbol;
50+ }
51+}
52+
53+export function sigilScalingFunction(size: number): number {
54+ return 3.5 * Math.sqrt(51 / size);
55+}
ui/src/lib/utils.ts+4
@@ -41,3 +41,7 @@ export function toggleStoreKey(store, key) {
41 [key]: !store[key],
41 [key]: !store[key],
42 }));
42 }));
43}
43}
44+
45+export function capitalize(string: string): string {
46+ return string.charAt(0).toUpperCase() + string.slice(1);
47+}
ui/src/styles/tailwind-output.css+248−16
@@ -532,8 +532,23 @@ Ensure the default browser behavior of the `hidden` attribute.
532.-bottom-3 {
532.-bottom-3 {
533 bottom: -0.75rem;
533 bottom: -0.75rem;
534}
534}
535−.z-50 {
535+.top-1 {
536− z-index: 50;
536+ top: 0.25rem;
537+}
538+.right-1 {
539+ right: 0.25rem;
540+}
541+.top-2 {
542+ top: 0.5rem;
543+}
544+.right-2 {
545+ right: 0.5rem;
546+}
547+.right-4 {
548+ right: 1rem;
549+}
550+.right-3 {
551+ right: 0.75rem;
537}
552}
538.z-20 {
553.z-20 {
539 z-index: 20;
554 z-index: 20;
@@ -541,6 +556,9 @@ Ensure the default browser behavior of the `hidden` attribute.
541.z-10 {
556.z-10 {
542 z-index: 10;
557 z-index: 10;
543}
558}
559+.z-50 {
560+ z-index: 50;
561+}
544.m-4 {
562.m-4 {
545 margin: 1rem;
563 margin: 1rem;
546}
564}
@@ -553,9 +571,11 @@ Ensure the default browser behavior of the `hidden` attribute.
553.-m-2 {
571.-m-2 {
554 margin: -0.5rem;
572 margin: -0.5rem;
555}
573}
556−.mx-4 {
574+.m-auto {
557− margin-left: 1rem;
575+ margin: auto;
576+}
577+.m-1 {
558− margin-right: 1rem;
578+ margin: 0.25rem;
559}
579}
560.mx-auto {
580.mx-auto {
561 margin-left: auto;
581 margin-left: auto;
@@ -573,6 +593,10 @@ Ensure the default browser behavior of the `hidden` attribute.
573 margin-top: 0.75rem;
593 margin-top: 0.75rem;
574 margin-bottom: 0.75rem;
594 margin-bottom: 0.75rem;
575}
595}
596+.mx-4 {
597+ margin-left: 1rem;
598+ margin-right: 1rem;
599+}
576.mx-8 {
600.mx-8 {
577 margin-left: 2rem;
601 margin-left: 2rem;
578 margin-right: 2rem;
602 margin-right: 2rem;
@@ -657,6 +681,9 @@ Ensure the default browser behavior of the `hidden` attribute.
657.mr-3 {
681.mr-3 {
658 margin-right: 0.75rem;
682 margin-right: 0.75rem;
659}
683}
684+.ml-auto {
685+ margin-left: auto;
686+}
660.-mt-0\.5 {
687.-mt-0\.5 {
661 margin-top: -0.125rem;
688 margin-top: -0.125rem;
662}
689}
@@ -684,6 +711,9 @@ Ensure the default browser behavior of the `hidden` attribute.
684.hidden {
711.hidden {
685 display: none;
712 display: none;
686}
713}
714+.aspect-square {
715+ aspect-ratio: 1 / 1;
716+}
687.h-full {
717.h-full {
688 height: 100%;
718 height: 100%;
689}
719}
@@ -723,6 +753,24 @@ Ensure the default browser behavior of the `hidden` attribute.
723.h-24 {
753.h-24 {
724 height: 6rem;
754 height: 6rem;
725}
755}
756+.h-20 {
757+ height: 5rem;
758+}
759+.h-48 {
760+ height: 12rem;
761+}
762+.h-64 {
763+ height: 16rem;
764+}
765+.h-72 {
766+ height: 18rem;
767+}
768+.h-16 {
769+ height: 4rem;
770+}
771+.h-auto {
772+ height: auto;
773+}
726.min-h-\[20px\] {
774.min-h-\[20px\] {
727 min-height: 20px;
775 min-height: 20px;
728}
776}
@@ -732,6 +780,9 @@ Ensure the default browser behavior of the `hidden` attribute.
732.min-h-0 {
780.min-h-0 {
733 min-height: 0px;
781 min-height: 0px;
734}
782}
783+.min-h-\[20rem\] {
784+ min-height: 20rem;
785+}
735.w-full {
786.w-full {
736 width: 100%;
787 width: 100%;
737}
788}
@@ -765,6 +816,36 @@ Ensure the default browser behavior of the `hidden` attribute.
765.w-6 {
816.w-6 {
766 width: 1.5rem;
817 width: 1.5rem;
767}
818}
819+.w-48 {
820+ width: 12rem;
821+}
822+.w-24 {
823+ width: 6rem;
824+}
825+.w-20 {
826+ width: 5rem;
827+}
828+.w-8 {
829+ width: 2rem;
830+}
831+.w-64 {
832+ width: 16rem;
833+}
834+.w-72 {
835+ width: 18rem;
836+}
837+.w-\[50\%\] {
838+ width: 50%;
839+}
840+.w-\[18rem\] {
841+ width: 18rem;
842+}
843+.w-16 {
844+ width: 4rem;
845+}
846+.w-\[48px\] {
847+ width: 48px;
848+}
768.min-w-0 {
849.min-w-0 {
769 min-width: 0px;
850 min-width: 0px;
770}
851}
@@ -786,6 +867,9 @@ Ensure the default browser behavior of the `hidden` attribute.
786.max-w-2xl {
867.max-w-2xl {
787 max-width: 42rem;
868 max-width: 42rem;
788}
869}
870+.max-w-\[18rem\] {
871+ max-width: 18rem;
872+}
789.flex-shrink-0 {
873.flex-shrink-0 {
790 flex-shrink: 0;
874 flex-shrink: 0;
791}
875}
@@ -801,6 +885,9 @@ Ensure the default browser behavior of the `hidden` attribute.
801.grow {
885.grow {
802 flex-grow: 1;
886 flex-grow: 1;
803}
887}
888+.grow-\[2\] {
889+ flex-grow: 2;
890+}
804.border-collapse {
891.border-collapse {
805 border-collapse: collapse;
892 border-collapse: collapse;
806}
893}
@@ -837,21 +924,45 @@ Ensure the default browser behavior of the `hidden` attribute.
837.resize {
924.resize {
838 resize: both;
925 resize: both;
839}
926}
927+.grid-flow-col {
928+ grid-auto-flow: column;
929+}
840.grid-cols-3 {
930.grid-cols-3 {
841 grid-template-columns: repeat(3, minmax(0, 1fr));
931 grid-template-columns: repeat(3, minmax(0, 1fr));
842}
932}
933+.grid-cols-2 {
934+ grid-template-columns: repeat(2, minmax(0, 1fr));
935+}
936+.grid-cols-1 {
937+ grid-template-columns: repeat(1, minmax(0, 1fr));
938+}
939+.grid-rows-2 {
940+ grid-template-rows: repeat(2, minmax(0, 1fr));
941+}
942+.grid-rows-none {
943+ grid-template-rows: none;
944+}
843.flex-col {
945.flex-col {
844 flex-direction: column;
946 flex-direction: column;
845}
947}
846.flex-wrap {
948.flex-wrap {
847 flex-wrap: wrap;
949 flex-wrap: wrap;
848}
950}
951+.place-content-center {
952+ place-content: center;
953+}
954+.place-items-stretch {
955+ place-items: stretch;
956+}
849.content-center {
957.content-center {
850 align-content: center;
958 align-content: center;
851}
959}
852.items-center {
960.items-center {
853 align-items: center;
961 align-items: center;
854}
962}
963+.justify-end {
964+ justify-content: flex-end;
965+}
855.justify-center {
966.justify-center {
856 justify-content: center;
967 justify-content: center;
857}
968}
@@ -928,6 +1039,9 @@ Ensure the default browser behavior of the `hidden` attribute.
928 --tw-divide-opacity: 1;
1039 --tw-divide-opacity: 1;
929 border-color: rgb(252 211 77 / var(--tw-divide-opacity));
1040 border-color: rgb(252 211 77 / var(--tw-divide-opacity));
930}
1041}
1042+.self-center {
1043+ align-self: center;
1044+}
931.justify-self-start {
1045.justify-self-start {
932 justify-self: start;
1046 justify-self: start;
933}
1047}
@@ -970,6 +1084,26 @@ Ensure the default browser behavior of the `hidden` attribute.
970.rounded-sm {
1084.rounded-sm {
971 border-radius: 0.125rem;
1085 border-radius: 0.125rem;
972}
1086}
1087+.rounded-2xl {
1088+ border-radius: 1rem;
1089+}
1090+.rounded-3xl {
1091+ border-radius: 1.5rem;
1092+}
1093+.rounded-b-2xl {
1094+ border-bottom-right-radius: 1rem;
1095+ border-bottom-left-radius: 1rem;
1096+}
1097+.rounded-t-2xl {
1098+ border-top-left-radius: 1rem;
1099+ border-top-right-radius: 1rem;
1100+}
1101+.rounded-br-2xl {
1102+ border-bottom-right-radius: 1rem;
1103+}
1104+.rounded-bl-none {
1105+ border-bottom-left-radius: 0px;
1106+}
973.border {
1107.border {
974 border-width: 1px;
1108 border-width: 1px;
975}
1109}
@@ -979,6 +1113,9 @@ Ensure the default browser behavior of the `hidden` attribute.
979.border-8 {
1113.border-8 {
980 border-width: 8px;
1114 border-width: 8px;
981}
1115}
1116+.border-2 {
1117+ border-width: 2px;
1118+}
982.border-l-8 {
1119.border-l-8 {
983 border-left-width: 8px;
1120 border-left-width: 8px;
984}
1121}
@@ -1018,6 +1155,30 @@ Ensure the default browser behavior of the `hidden` attribute.
1018 --tw-border-opacity: 1;
1155 --tw-border-opacity: 1;
1019 border-color: rgb(156 163 175 / var(--tw-border-opacity));
1156 border-color: rgb(156 163 175 / var(--tw-border-opacity));
1020}
1157}
1158+.border-red-500 {
1159+ --tw-border-opacity: 1;
1160+ border-color: rgb(239 68 68 / var(--tw-border-opacity));
1161+}
1162+.border-gold-s5 {
1163+ --tw-border-opacity: 1;
1164+ border-color: rgb(126 106 39 / var(--tw-border-opacity));
1165+}
1166+.border-gold-s2 {
1167+ --tw-border-opacity: 1;
1168+ border-color: rgb(202 169 62 / var(--tw-border-opacity));
1169+}
1170+.border-gold-s1 {
1171+ --tw-border-opacity: 1;
1172+ border-color: rgb(227 190 69 / var(--tw-border-opacity));
1173+}
1174+.border-gray-800 {
1175+ --tw-border-opacity: 1;
1176+ border-color: rgb(31 41 55 / var(--tw-border-opacity));
1177+}
1178+.border-gray-500 {
1179+ --tw-border-opacity: 1;
1180+ border-color: rgb(107 114 128 / var(--tw-border-opacity));
1181+}
1021.bg-navy {
1182.bg-navy {
1022 --tw-bg-opacity: 1;
1183 --tw-bg-opacity: 1;
1023 background-color: rgb(31 36 60 / var(--tw-bg-opacity));
1184 background-color: rgb(31 36 60 / var(--tw-bg-opacity));
@@ -1122,6 +1283,25 @@ Ensure the default browser behavior of the `hidden` attribute.
1122 --tw-bg-opacity: 1;
1283 --tw-bg-opacity: 1;
1123 background-color: rgb(227 190 69 / var(--tw-bg-opacity));
1284 background-color: rgb(227 190 69 / var(--tw-bg-opacity));
1124}
1285}
1286+.bg-blue-800 {
1287+ --tw-bg-opacity: 1;
1288+ background-color: rgb(30 64 175 / var(--tw-bg-opacity));
1289+}
1290+.bg-green-900 {
1291+ --tw-bg-opacity: 1;
1292+ background-color: rgb(20 83 45 / var(--tw-bg-opacity));
1293+}
1294+.bg-green-100 {
1295+ --tw-bg-opacity: 1;
1296+ background-color: rgb(220 252 231 / var(--tw-bg-opacity));
1297+}
1298+.bg-blue-200 {
1299+ --tw-bg-opacity: 1;
1300+ background-color: rgb(191 219 254 / var(--tw-bg-opacity));
1301+}
1302+.fill-transparent {
1303+ fill: transparent;
1304+}
1125.p-5 {
1305.p-5 {
1126 padding: 1.25rem;
1306 padding: 1.25rem;
1127}
1307}
@@ -1159,10 +1339,6 @@ Ensure the default browser behavior of the `hidden` attribute.
1159 padding-top: 0px;
1339 padding-top: 0px;
1160 padding-bottom: 0px;
1340 padding-bottom: 0px;
1161}
1341}
1162−.py-4 {
1163− padding-top: 1rem;
1164− padding-bottom: 1rem;
1165−}
1166.px-4 {
1342.px-4 {
1167 padding-left: 1rem;
1343 padding-left: 1rem;
1168 padding-right: 1rem;
1344 padding-right: 1rem;
@@ -1175,6 +1351,10 @@ Ensure the default browser behavior of the `hidden` attribute.
1175 padding-left: 0px;
1351 padding-left: 0px;
1176 padding-right: 0px;
1352 padding-right: 0px;
1177}
1353}
1354+.py-4 {
1355+ padding-top: 1rem;
1356+ padding-bottom: 1rem;
1357+}
1178.py-2 {
1358.py-2 {
1179 padding-top: 0.5rem;
1359 padding-top: 0.5rem;
1180 padding-bottom: 0.5rem;
1360 padding-bottom: 0.5rem;
@@ -1278,6 +1458,9 @@ Ensure the default browser behavior of the `hidden` attribute.
1278.font-bold {
1458.font-bold {
1279 font-weight: 700;
1459 font-weight: 700;
1280}
1460}
1461+.capitalize {
1462+ text-transform: capitalize;
1463+}
1281.leading-4 {
1464.leading-4 {
1282 line-height: 1rem;
1465 line-height: 1rem;
1283}
1466}
@@ -1342,21 +1525,26 @@ Ensure the default browser behavior of the `hidden` attribute.
1342.underline-offset-2 {
1525.underline-offset-2 {
1343 text-underline-offset: 2px;
1526 text-underline-offset: 2px;
1344}
1527}
1345−.opacity-50 {
1346− opacity: 0.5;
1347−}
1348.opacity-25 {
1528.opacity-25 {
1349 opacity: 0.25;
1529 opacity: 0.25;
1350}
1530}
1351.opacity-75 {
1531.opacity-75 {
1352 opacity: 0.75;
1532 opacity: 0.75;
1353}
1533}
1534+.opacity-50 {
1535+ opacity: 0.5;
1536+}
1354.opacity-10 {
1537.opacity-10 {
1355 opacity: 0.1;
1538 opacity: 0.1;
1356}
1539}
1357.outline {
1540.outline {
1358 outline-style: solid;
1541 outline-style: solid;
1359}
1542}
1543+.ring {
1544+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1545+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
1546+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
1547+}
1360.filter {
1548.filter {
1361 filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1549 filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1362}
1550}
@@ -1365,6 +1553,16 @@ Ensure the default browser behavior of the `hidden` attribute.
1365 transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1553 transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1366 transition-duration: 150ms;
1554 transition-duration: 150ms;
1367}
1555}
1556+.transition-transform {
1557+ transition-property: transform;
1558+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1559+ transition-duration: 150ms;
1560+}
1561+.transition-all {
1562+ transition-property: all;
1563+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1564+ transition-duration: 150ms;
1565+}
1368.hover\:bg-gray-200:hover {
1566.hover\:bg-gray-200:hover {
1369 --tw-bg-opacity: 1;
1567 --tw-bg-opacity: 1;
1370 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
1568 background-color: rgb(229 231 235 / var(--tw-bg-opacity));
@@ -1406,6 +1604,40 @@ Ensure the default browser behavior of the `hidden` attribute.
1406 padding-bottom: 2rem;
1604 padding-bottom: 2rem;
1407 }
1605 }
1408}
1606}
1607+@media (min-width: 475px) {
1608+
1609+ .xs\:m-0 {
1610+ margin: 0px;
1611+ }
1612+
1613+ .xs\:max-w-\[18rem\] {
1614+ max-width: 18rem;
1615+ }
1616+
1617+ .xs\:grid-flow-row {
1618+ grid-auto-flow: row;
1619+ }
1620+
1621+ .xs\:grid-cols-2 {
1622+ grid-template-columns: repeat(2, minmax(0, 1fr));
1623+ }
1624+
1625+ .xs\:grid-rows-none {
1626+ grid-template-rows: none;
1627+ }
1628+
1629+ .xs\:flex-row {
1630+ flex-direction: row;
1631+ }
1632+
1633+ .xs\:rounded-bl-2xl {
1634+ border-bottom-left-radius: 1rem;
1635+ }
1636+
1637+ .xs\:rounded-bl-none {
1638+ border-bottom-left-radius: 0px;
1639+ }
1640+}
1409@media (min-width: 768px) {
1641@media (min-width: 768px) {
1410
1642
1411 .md\:mx-auto {
1643 .md\:mx-auto {
@@ -1417,14 +1649,14 @@ Ensure the default browser behavior of the `hidden` attribute.
1417 min-width: 256px;
1649 min-width: 256px;
1418 }
1650 }
1419
1651
1420− .md\:max-w-xl {
1421− max-width: 36rem;
1422− }
1423−
1424 .md\:max-w-2xl {
1652 .md\:max-w-2xl {
1425 max-width: 42rem;
1653 max-width: 42rem;
1426 }
1654 }
1427
1655
1656+ .md\:max-w-xl {
1657+ max-width: 36rem;
1658+ }
1659+
1428 .md\:flex-row {
1660 .md\:flex-row {
1429 flex-direction: row;
1661 flex-direction: row;
1430 }
1662 }
ui/src/types/sigil.tsnew file+30
@@ -0,0 +1,30 @@
1+export const CLANS = ['galaxy', 'star', 'planet'] as const;
2+export type Clan = typeof CLANS[number];
3+
4+export const PART_TYPES = ['geon', 'line', 'arc', 'ring', 'circle', 'donut', 'bezier'] as const;
5+export type PartType = typeof PART_TYPES[number];
6+
7+export const GEONS = ['gci', 'gsq', 'ghl', 'ghr', 'ght', 'ghb', 'gq1', 'gq2', 'gq3', 'gq4', 'gfs', 'gbs'];
8+export type Geon = typeof GEONS[number];
9+
10+export type SymbolType = ('prefix' | 'suffix');
11+
12+export class SymbolQuery {
13+ symbolType: SymbolType;
14+ components: string[];
15+
16+ constructor(symbolType: SymbolType = 'suffix', components = []) {
17+ this.symbolType = symbolType;
18+ this.components = components;
19+ }
20+
21+ set geon(geon: Geon) {
22+ this.components = this.components.filter(c => c[0] !== 'g');
23+ if (geon)
24+ this.components = [geon, ...this.components];
25+ }
26+
27+ get geon(): Geon {
28+ return this.components.find(c => c[0] === 'g');
29+ }
30+}