tangled
alpha
login
or
join now
desertthunder.dev
/
inkfinite
web based infinite canvas
2
fork
atom
overview
issues
pulls
pipelines
feat: improve spacing and add dark mode toggle
desertthunder.dev
1 month ago
ae8848bb
530ae13c
+156
-69
7 changed files
expand all
collapse all
unified
split
apps
web
src
lib
assets
moon.svg
sun.svg
components
Icon.svelte
StatusBar.svelte
Toolbar.svelte
theme.svelte.ts
routes
+layout.svelte
+1
apps/web/src/lib/assets/moon.svg
···
1
1
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-moon"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
+1
apps/web/src/lib/assets/sun.svg
···
1
1
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sun"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>
+5
-1
apps/web/src/lib/components/Icon.svelte
···
2
2
import CloseIcon from '$lib/assets/close.svg?raw';
3
3
import FolderIcon from '$lib/assets/folder.svg?raw';
4
4
import InfoCircleIcon from '$lib/assets/info-circle.svg?raw';
5
5
+
import MoonIcon from '$lib/assets/moon.svg?raw';
5
6
import PencilIcon from '$lib/assets/pencil.svg?raw';
7
7
+
import SunIcon from '$lib/assets/sun.svg?raw';
6
8
import TrashIcon from '$lib/assets/trash.svg?raw';
7
9
8
8
-
export type IconName = 'close' | 'folder' | 'info-circle' | 'pencil' | 'trash';
10
10
+
export type IconName = 'close' | 'folder' | 'info-circle' | 'moon' | 'pencil' | 'sun' | 'trash';
9
11
10
12
type Props = { name: IconName; size?: number; color?: string };
11
13
···
15
17
close: CloseIcon,
16
18
folder: FolderIcon,
17
19
'info-circle': InfoCircleIcon,
20
20
+
moon: MoonIcon,
18
21
pencil: PencilIcon,
22
22
+
sun: SunIcon,
19
23
trash: TrashIcon
20
24
};
21
25
+15
-8
apps/web/src/lib/components/StatusBar.svelte
···
176
176
.status-bar {
177
177
display: grid;
178
178
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
179
179
-
gap: 1rem;
180
180
-
padding: 0.5rem 1rem;
179
179
+
gap: 1.5rem;
180
180
+
padding: 0.75rem 1.5rem;
181
181
background: var(--surface-elevated);
182
182
border-top: 1px solid var(--border);
183
183
font-size: 0.75rem;
184
184
align-items: center;
185
185
-
min-height: 40px;
185
185
+
min-height: 48px;
186
186
}
187
187
188
188
.status-bar__section {
189
189
display: flex;
190
190
flex-direction: row;
191
191
align-items: center;
192
192
-
gap: 0.5rem;
192
192
+
gap: 0.75rem;
193
193
position: relative;
194
194
}
195
195
196
196
.status-bar__toggle-row {
197
197
display: flex;
198
198
-
gap: 1rem;
198
198
+
gap: 1.25rem;
199
199
}
200
200
201
201
.status-bar__toggle {
202
202
display: flex;
203
203
align-items: center;
204
204
-
gap: 0.25rem;
204
204
+
gap: 0.375rem;
205
205
font-size: 0.75rem;
206
206
color: var(--text);
207
207
}
···
209
209
.status-bar__toggle input {
210
210
margin: 0;
211
211
cursor: pointer;
212
212
+
opacity: 0.8;
212
213
}
214
214
+
215
215
+
.status-bar__toggle:hover input {
216
216
+
opacity: 1;
217
217
+
}
213
218
214
219
.status-bar__toggle input:focus {
215
220
outline: 2px solid var(--accent);
···
217
222
}
218
223
219
224
.status-bar__label {
220
220
-
font-size: 0.75rem;
225
225
+
font-size: 0.6875rem;
221
226
color: var(--text-muted);
222
227
text-transform: uppercase;
223
223
-
letter-spacing: 0.05em;
228
228
+
letter-spacing: 0.075em;
229
229
+
font-weight: 600;
224
230
}
225
231
226
232
.status-bar__value {
227
233
font-weight: 500;
228
234
color: var(--text);
235
235
+
font-variant-numeric: tabular-nums;
229
236
}
230
237
231
238
.status-bar__value--error {
+87
-60
apps/web/src/lib/components/Toolbar.svelte
···
10
10
} from '$lib/constants';
11
11
import type { Platform } from '$lib/platform';
12
12
import type { BrushSettings, BrushStore } from '$lib/status';
13
13
+
import { themeStore } from '$lib/theme.svelte';
13
14
import type {
14
15
ArrowShape,
15
16
BoardMeta,
···
608
609
</div>
609
610
610
611
<div class="toolbar__info-actions">
612
612
+
<button
613
613
+
class="toolbar__info"
614
614
+
onclick={() => themeStore.toggle()}
615
615
+
aria-label="Toggle Dark Mode"
616
616
+
title="Toggle Dark Mode">
617
617
+
<Icon name={themeStore.current === 'dark' ? 'sun' : 'moon'} size={16} />
618
618
+
<span class="toolbar__info-label">{themeStore.current === 'dark' ? 'Light' : 'Dark'}</span>
619
619
+
</button>
611
620
{#if platform === 'web' && onOpenBrowser}
612
621
<button class="toolbar__info" onclick={onOpenBrowser} aria-label="Browse boards">
613
622
<Icon name="folder" size={16} />
···
670
679
<style>
671
680
.toolbar {
672
681
display: flex;
673
673
-
gap: 0.5rem;
674
674
-
padding: 0.75rem;
682
682
+
gap: 0.75rem;
683
683
+
padding: 0.75rem 1rem;
675
684
background: var(--surface-elevated);
676
685
border-bottom: 1px solid var(--border);
677
686
align-items: center;
687
687
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
688
688
+
z-index: 10;
689
689
+
position: relative;
678
690
}
679
691
692
692
+
.toolbar__brand {
693
693
+
display: flex;
694
694
+
align-items: center;
695
695
+
gap: 0.75rem;
696
696
+
margin-right: 1.5rem;
697
697
+
}
698
698
+
699
699
+
.toolbar__logo img {
700
700
+
width: 32px;
701
701
+
height: 32px;
702
702
+
}
703
703
+
704
704
+
.toolbar__name {
705
705
+
font-weight: 600;
706
706
+
font-size: 1.125rem;
707
707
+
letter-spacing: -0.025em;
708
708
+
color: var(--text);
709
709
+
}
710
710
+
711
711
+
.toolbar__tagline {
712
712
+
font-size: 0.75rem;
713
713
+
color: var(--text-muted);
714
714
+
font-weight: 500;
715
715
+
}
716
716
+
680
717
.toolbar__tool-button {
681
718
display: flex;
682
719
flex-direction: column;
683
720
align-items: center;
684
684
-
gap: 0.25rem;
685
685
-
padding: 0.5rem 0.75rem;
686
686
-
border: 1px solid var(--border);
687
687
-
border-radius: 0.25rem;
688
688
-
background: var(--surface);
689
689
-
color: var(--text);
721
721
+
gap: 0.375rem;
722
722
+
padding: 0.625rem 0.875rem;
723
723
+
border: 1px solid transparent;
724
724
+
border-radius: 0.5rem;
725
725
+
background: transparent;
726
726
+
color: var(--text-muted);
690
727
cursor: pointer;
691
691
-
transition: all 0.2s;
692
692
-
min-width: 60px;
728
728
+
transition: all 0.2s ease;
729
729
+
min-width: 68px;
693
730
}
694
731
695
732
.toolbar__tool-button:hover {
696
696
-
background: var(--surface-elevated);
697
697
-
border-color: var(--text-muted);
733
733
+
background: var(--bg-tertiary);
734
734
+
color: var(--text);
698
735
}
699
736
700
737
.toolbar__tool-button:focus {
···
706
743
.tool-button.active {
707
744
background: var(--accent);
708
745
color: var(--surface);
709
709
-
border-color: var(--accent-hover);
746
746
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
710
747
}
711
748
712
749
.toolbar__tool-icon {
713
713
-
font-size: 1.5rem;
750
750
+
font-size: 1.25rem;
714
751
line-height: 1;
715
752
}
716
753
717
754
.toolbar__tool-label {
718
755
font-size: 0.75rem;
756
756
+
font-weight: 500;
719
757
line-height: 1;
720
758
white-space: nowrap;
721
759
}
···
723
761
.toolbar__divider {
724
762
width: 1px;
725
763
background-color: var(--border);
726
726
-
margin: 0 8px;
727
727
-
height: 40px;
728
728
-
}
729
729
-
730
730
-
.toolbar__colors {
731
731
-
display: flex;
732
732
-
gap: 0.75rem;
733
733
-
align-items: center;
764
764
+
margin: 0 1rem;
765
765
+
height: 48px;
734
766
}
735
767
736
736
-
.toolbar__color-control {
737
737
-
display: flex;
738
738
-
flex-direction: column;
739
739
-
gap: 0.25rem;
740
740
-
font-size: 0.75rem;
741
741
-
color: var(--text-muted);
742
742
-
}
743
743
-
744
744
-
.toolbar__color-control input[type='color'] {
745
745
-
width: 40px;
746
746
-
height: 30px;
747
747
-
border: 1px solid var(--border);
748
748
-
border-radius: 6px;
749
749
-
padding: 0;
750
750
-
background: transparent;
751
751
-
cursor: pointer;
752
752
-
}
753
753
-
754
754
-
.toolbar__color-control input[type='color']:disabled {
755
755
-
opacity: 0.4;
756
756
-
cursor: not-allowed;
757
757
-
}
768
768
+
.toolbar__info {
769
769
+
display: flex;
770
770
+
align-items: center;
771
771
+
gap: 0.5rem;
772
772
+
padding: 0.5rem 0.75rem;
773
773
+
border-radius: 0.375rem;
774
774
+
background: transparent;
775
775
+
border: none;
776
776
+
color: var(--text-muted);
777
777
+
cursor: pointer;
778
778
+
transition: color 0.2s;
779
779
+
font-size: 0.875rem;
780
780
+
}
781
781
+
782
782
+
.toolbar__info:hover {
783
783
+
background: var(--bg-tertiary);
784
784
+
color: var(--text);
785
785
+
}
758
786
759
787
.toolbar__zoom,
760
788
.toolbar__export {
···
766
794
border: 1px solid var(--border);
767
795
background: var(--surface);
768
796
color: var(--text);
769
769
-
padding: 0.5rem 0.75rem;
770
770
-
border-radius: 0.25rem;
797
797
+
padding: 0.5rem 1rem;
798
798
+
border-radius: 0.375rem;
771
799
cursor: pointer;
772
772
-
font-size: 13px;
773
773
-
min-width: 60px;
800
800
+
font-size: 0.875rem;
801
801
+
font-weight: 500;
802
802
+
min-width: 72px;
803
803
+
transition: all 0.2s;
774
804
}
775
805
776
806
.toolbar__zoom-button:hover,
777
807
.toolbar__export-button:hover {
778
778
-
background: var(--surface-elevated);
779
779
-
}
780
780
-
781
781
-
.toolbar__zoom-button:focus,
782
782
-
.toolbar__export-button:focus {
783
783
-
outline: 2px solid var(--accent);
784
784
-
outline-offset: 2px;
808
808
+
background: var(--bg-tertiary);
809
809
+
border-color: var(--text-muted);
785
810
}
786
811
787
812
.toolbar__zoom-menu,
788
813
.toolbar__export-menu {
789
814
position: absolute;
790
790
-
top: calc(100% + 4px);
815
815
+
top: calc(100% + 8px);
791
816
left: 0;
792
792
-
background: var(--surface);
817
817
+
background: var(--surface-elevated);
793
818
color: var(--text);
794
819
border: 1px solid var(--border);
795
795
-
border-radius: 6px;
796
796
-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
797
797
-
padding: 8px;
820
820
+
border-radius: 0.5rem;
821
821
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
822
822
+
padding: 0.5rem;
798
823
display: flex;
799
824
flex-direction: column;
800
825
gap: 0.25rem;
826
826
+
min-width: 160px;
827
827
+
z-index: 20;
801
828
z-index: 10;
802
829
min-width: 150px;
803
830
}
+44
apps/web/src/lib/theme.svelte.ts
···
1
1
+
import { browser } from "$app/environment";
2
2
+
3
3
+
export type Theme = "light" | "dark";
4
4
+
5
5
+
export function createThemeStore() {
6
6
+
let theme = $state<Theme>("dark");
7
7
+
8
8
+
if (browser) {
9
9
+
const stored = localStorage.getItem("theme") as Theme | null;
10
10
+
if (stored === "light" || stored === "dark") {
11
11
+
theme = stored;
12
12
+
} else {
13
13
+
theme = "dark";
14
14
+
localStorage.setItem("theme", "dark");
15
15
+
}
16
16
+
document.documentElement.setAttribute("data-theme", theme);
17
17
+
}
18
18
+
19
19
+
function toggle() {
20
20
+
theme = theme === "dark" ? "light" : "dark";
21
21
+
if (browser) {
22
22
+
localStorage.setItem("theme", theme);
23
23
+
document.documentElement.setAttribute("data-theme", theme);
24
24
+
}
25
25
+
}
26
26
+
27
27
+
function set(newTheme: Theme) {
28
28
+
theme = newTheme;
29
29
+
if (browser) {
30
30
+
localStorage.setItem("theme", theme);
31
31
+
document.documentElement.setAttribute("data-theme", theme);
32
32
+
}
33
33
+
}
34
34
+
35
35
+
return {
36
36
+
get current() {
37
37
+
return theme;
38
38
+
},
39
39
+
toggle,
40
40
+
set,
41
41
+
};
42
42
+
}
43
43
+
44
44
+
export const themeStore = createThemeStore();
+3
apps/web/src/routes/+layout.svelte
···
1
1
<script lang="ts">
2
2
import favicon from '$lib/assets/favicon.svg';
3
3
+
import { themeStore } from '$lib/theme.svelte';
3
4
import '../app.css';
4
5
5
6
let { children } = $props();
7
7
+
8
8
+
const _ = themeStore;
6
9
</script>
7
10
8
11
<svelte:head>