+2
-1
.gitignore
+2
-1
.gitignore
+1
-2
AGENTS.md
+1
-2
AGENTS.md
···
195
195
| Variable | Default | Description |
196
196
| --------------------------- | --------------------------------- | ---------------------------------- |
197
197
| `PORT` | 18910 | HTTP server port |
198
-
| `SERVER_PUBLIC_URL` | - | Public URL for reverse proxy |
198
+
| `SERVER_PUBLIC_URL` | - | Public URL for reverse proxy (enables secure cookies when HTTPS) |
199
199
| `ARABICA_DB_PATH` | ~/.local/share/arabica/arabica.db | BoltDB path (sessions, registry) |
200
200
| `ARABICA_FEED_INDEX_PATH` | ~/.local/share/arabica/feed-index.db | Firehose index BoltDB path |
201
201
| `ARABICA_PROFILE_CACHE_TTL` | 1h | Profile cache duration |
202
-
| `SECURE_COOKIES` | false | Set true for HTTPS |
203
202
| `LOG_LEVEL` | info | debug/info/warn/error |
204
203
| `LOG_FORMAT` | console | console/json |
205
204
+15
-13
BACKLOG.md
+15
-13
BACKLOG.md
···
5
5
6
6
---
7
7
8
+
## Far Future Considerations
9
+
10
+
- Pivot to full svelte-kit?
11
+
12
+
- Maybe swap from boltdb to sqlite
13
+
- Use the non-cgo library
14
+
- Is there a compelling reason to do this?
15
+
- Might be good as a sort of witness-cache type thing
16
+
8
17
## Features
9
18
10
19
1. LARGE: complete record styling refactor that changes from table-style to more mobile-friendly style
···
29
38
30
39
- Add my custom iosevka font as default font
31
40
32
-
## Far Future Considerations
33
-
34
-
- Pivot to full svelte-kit?
35
-
36
-
- Maybe swap from boltdb to sqlite
37
-
- Use the non-cgo library
41
+
- Improve caching of profile pictures, tangled.sh apparently does this really well
42
+
- Something with a cloudflare cdn
43
+
- Might be able to just save to the db when backfilling a profile's records
44
+
- NOTE: requires research into existing solustions (whatever tangled does is probably good)
38
45
39
46
## Fixes
40
47
···
42
49
43
50
- Backfill on startup should be cache invalidated if time since last backfill exceeds some amount (set in code/env var maybe?)
44
51
45
-
- Fix always using celcius for units, use settings (future state) or infer from number (maybe also future state)
46
-
47
52
- Make rating color nicer, but on white background for selector on new/edit brew page
48
53
49
-
- Refactor: remove the `SECURE_COOKIES` env var, it should be unecessary
50
-
- For dev, we should know its running in dev mode by checking the root url env var I think?
51
-
- This just adds noise and feels like an antipattern
54
+
- Profile page should show more details, and allow brew entries to take up more vertical space
52
55
53
-
- Fix styling of manage records page to use rounded tables like everything else
54
-
- Should also use tab selectors the same way as the profile uses
56
+
- Show "view" button on brews in profile page
+1
-2
CLAUDE.md
+1
-2
CLAUDE.md
···
160
160
| Variable | Default | Description |
161
161
| --------------------------- | --------------------------------- | ---------------------------------- |
162
162
| `PORT` | 18910 | HTTP server port |
163
-
| `SERVER_PUBLIC_URL` | - | Public URL for reverse proxy |
163
+
| `SERVER_PUBLIC_URL` | - | Public URL for reverse proxy (enables secure cookies when HTTPS) |
164
164
| `ARABICA_DB_PATH` | ~/.local/share/arabica/arabica.db | BoltDB path (sessions, registry) |
165
165
| `ARABICA_FEED_INDEX_PATH` | ~/.local/share/arabica/feed-index.db | Firehose index BoltDB path |
166
166
| `ARABICA_PROFILE_CACHE_TTL` | 1h | Profile cache duration |
167
-
| `SECURE_COOKIES` | false | Set true for HTTPS |
168
167
| `LOG_LEVEL` | info | debug/info/warn/error |
169
168
| `LOG_FORMAT` | console | console/json |
170
169
+2
-4
README.md
+2
-4
README.md
···
50
50
```yaml
51
51
environment:
52
52
- SERVER_PUBLIC_URL=https://arabica.example.com
53
-
- SECURE_COOKIES=true
54
53
```
55
54
56
55
## Configuration
···
62
61
### Environment Variables
63
62
64
63
- `PORT` - Server port (default: 18910)
65
-
- `SERVER_PUBLIC_URL` - Public URL for reverse proxy deployments (e.g., https://arabica.example.com)
64
+
- `SERVER_PUBLIC_URL` - Public URL for reverse proxy deployments (e.g., https://arabica.example.com). When set to an HTTPS URL, secure cookies are automatically enabled.
66
65
- `ARABICA_DB_PATH` - BoltDB path (default: ~/.local/share/arabica/arabica.db)
67
66
- `ARABICA_FEED_INDEX_PATH` - Firehose index BoltDB path (default: ~/.local/share/arabica/feed-index.db)
68
67
- `ARABICA_PROFILE_CACHE_TTL` - Profile cache duration (default: 1h)
69
68
- `OAUTH_CLIENT_ID` - OAuth client ID (optional, uses localhost mode if not set)
70
69
- `OAUTH_REDIRECT_URI` - OAuth redirect URI (optional)
71
-
- `SECURE_COOKIES` - Set to true for HTTPS (default: false)
72
70
- `LOG_LEVEL` - Logging level: debug, info, warn, error (default: info)
73
71
- `LOG_FORMAT` - Log format: console, json (default: console)
74
72
···
108
106
```bash
109
107
# Example with nginx reverse proxy
110
108
SERVER_PUBLIC_URL=https://arabica.example.com
111
-
SECURE_COOKIES=true
112
109
PORT=18910
113
110
114
111
# The server listens on localhost:18910
115
112
# But OAuth callbacks use https://arabica.example.com/oauth/callback
113
+
# Secure cookies are automatically enabled when SERVER_PUBLIC_URL uses HTTPS
116
114
```
117
115
118
116
The `SERVER_PUBLIC_URL` is used for OAuth client metadata and callback URLs, ensuring the AT Protocol OAuth flow works correctly when the server is accessed via a different URL than it's running on.
+4
-3
cmd/arabica-server/main.go
+4
-3
cmd/arabica-server/main.go
···
276
276
defer stopCacheCleanup()
277
277
log.Info().Msg("Session cache initialized with background cleanup")
278
278
279
-
// Determine if we should use secure cookies (default: false for development)
280
-
// Set SECURE_COOKIES=true in production with HTTPS
281
-
secureCookies := os.Getenv("SECURE_COOKIES") == "true"
279
+
// Determine if we should use secure cookies based on the public URL scheme
280
+
// If the public URL uses HTTPS, we automatically set the Secure flag on cookies
281
+
// For local development (no SERVER_PUBLIC_URL set), secure cookies are disabled
282
+
secureCookies := strings.HasPrefix(publicURL, "https://")
282
283
283
284
// Initialize handlers with all dependencies via constructor injection
284
285
h := handlers.NewHandler(
+1
-1
.env.example
deploy/.env.example
+1
-1
.env.example
deploy/.env.example
-1
deploy/compose.yml
-1
deploy/compose.yml
+1
-2
docs/deploy.md
+1
-2
docs/deploy.md
···
127
127
| `ACME_EMAIL` | (empty) | Email for Let's Encrypt |
128
128
| `LOG_LEVEL` | info | debug/info/warn/error |
129
129
| `LOG_FORMAT` | json | console/json |
130
-
| `SERVER_PUBLIC_URL` | https://${DOMAIN} | Override public URL |
131
-
| `SECURE_COOKIES` | true | Use secure cookies |
130
+
| `SERVER_PUBLIC_URL` | https://${DOMAIN} | Override public URL (enables secure cookies when HTTPS) |
132
131
133
132
## Support
134
133
+5
-3
docs/nix-install.md
+5
-3
docs/nix-install.md
···
10
10
11
11
services.arabica = {
12
12
enable = true;
13
-
port = 18910;
13
+
publicUrl = "https://arabica.example.com";
14
+
settings = {
15
+
port = 18910;
16
+
logLevel = "info";
17
+
};
14
18
dataDir = "/var/lib/arabica";
15
-
logLevel = "info";
16
-
secureCookies = false; # Set true if behind HTTPS proxy
17
19
};
18
20
}
19
21
```
+21
frontend/src/routes/Manage.svelte
+21
frontend/src/routes/Manage.svelte
···
348
348
class="px-4 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider"
349
349
>馃彮 Roaster</th
350
350
>
351
+
<th
352
+
class="px-4 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider"
353
+
>馃摑 Description</th
354
+
>
351
355
<th
352
356
class="px-4 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider"
353
357
>Actions</th
···
369
373
<td class="px-4 py-3 text-sm text-brown-900"
370
374
>{bean.roaster?.name || "-"}</td
371
375
>
376
+
<td class="px-4 py-3 text-sm text-brown-700 italic max-w-xs"
377
+
>{bean.description || "-"}</td
378
+
>
372
379
<td class="px-4 py-3 text-sm space-x-2">
373
380
<button
374
381
on:click={() => editBean(bean)}
···
513
520
class="px-4 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider"
514
521
>馃拵 Burr Type</th
515
522
>
523
+
<th
524
+
class="px-4 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider"
525
+
>馃摑 Notes</th
526
+
>
516
527
<th
517
528
class="px-4 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider"
518
529
>Actions</th
···
531
542
<td class="px-4 py-3 text-sm text-brown-900"
532
543
>{grinder.burr_type || "-"}</td
533
544
>
545
+
<td class="px-4 py-3 text-sm text-brown-700 italic max-w-xs"
546
+
>{grinder.notes || "-"}</td
547
+
>
534
548
<td class="px-4 py-3 text-sm space-x-2">
535
549
<button
536
550
on:click={() => editGrinder(grinder)}
···
585
599
class="px-4 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider"
586
600
>馃敡 Type</th
587
601
>
602
+
<th
603
+
class="px-4 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider"
604
+
>馃摑 Description</th
605
+
>
588
606
<th
589
607
class="px-4 py-3 text-left text-xs font-medium text-brown-900 uppercase tracking-wider"
590
608
>Actions</th
···
600
618
<td class="px-4 py-3 text-sm text-brown-900"
601
619
>{brewer.brewer_type || "-"}</td
602
620
>
621
+
<td class="px-4 py-3 text-sm text-brown-700 italic max-w-xs"
622
+
>{brewer.description || "-"}</td
623
+
>
603
624
<td class="px-4 py-3 text-sm space-x-2">
604
625
<button
605
626
on:click={() => editBrewer(brewer)}
+334
-1
frontend/src/routes/Profile.svelte
+334
-1
frontend/src/routes/Profile.svelte
···
2
2
import { onMount } from "svelte";
3
3
import { api } from "../lib/api.js";
4
4
import { navigate } from "../lib/router.js";
5
+
import Modal from "../components/Modal.svelte";
5
6
6
7
export let actor;
7
8
···
17
18
18
19
let activeTab = "brews";
19
20
21
+
// Modal states
22
+
let showBeanModal = false;
23
+
let showRoasterModal = false;
24
+
let showGrinderModal = false;
25
+
let showBrewerModal = false;
26
+
27
+
// Forms
28
+
let beanForm = {
29
+
name: "",
30
+
origin: "",
31
+
roast_level: "",
32
+
process: "",
33
+
description: "",
34
+
roaster_rkey: "",
35
+
};
36
+
let roasterForm = { name: "", location: "", website: "", description: "" };
37
+
let grinderForm = { name: "", grinder_type: "", burr_type: "", notes: "" };
38
+
let brewerForm = { name: "", brewer_type: "", description: "" };
39
+
20
40
onMount(async () => {
41
+
await loadProfile();
42
+
});
43
+
44
+
async function loadProfile() {
21
45
try {
22
46
const data = await api.get(`/api/profile-json/${actor}`);
23
47
profile = data.profile;
···
35
59
} finally {
36
60
loading = false;
37
61
}
38
-
});
62
+
}
39
63
40
64
function formatDate(dateStr) {
41
65
const date = new Date(dateStr);
···
45
69
day: "numeric",
46
70
});
47
71
}
72
+
73
+
// Bean handlers
74
+
function addBean() {
75
+
beanForm = {
76
+
name: "",
77
+
origin: "",
78
+
roast_level: "",
79
+
process: "",
80
+
description: "",
81
+
roaster_rkey: "",
82
+
};
83
+
showBeanModal = true;
84
+
}
85
+
86
+
async function saveBean() {
87
+
try {
88
+
await api.post("/api/beans", beanForm);
89
+
await loadProfile();
90
+
showBeanModal = false;
91
+
} catch (err) {
92
+
console.error("Bean save error:", err);
93
+
alert("Failed to save bean: " + err.message);
94
+
}
95
+
}
96
+
97
+
// Roaster handlers
98
+
function addRoaster() {
99
+
roasterForm = { name: "", location: "", website: "", description: "" };
100
+
showRoasterModal = true;
101
+
}
102
+
103
+
async function saveRoaster() {
104
+
try {
105
+
await api.post("/api/roasters", roasterForm);
106
+
await loadProfile();
107
+
showRoasterModal = false;
108
+
} catch (err) {
109
+
alert("Failed to save roaster: " + err.message);
110
+
}
111
+
}
112
+
113
+
// Grinder handlers
114
+
function addGrinder() {
115
+
grinderForm = { name: "", grinder_type: "", burr_type: "", notes: "" };
116
+
showGrinderModal = true;
117
+
}
118
+
119
+
async function saveGrinder() {
120
+
try {
121
+
await api.post("/api/grinders", grinderForm);
122
+
await loadProfile();
123
+
showGrinderModal = false;
124
+
} catch (err) {
125
+
alert("Failed to save grinder: " + err.message);
126
+
}
127
+
}
128
+
129
+
// Brewer handlers
130
+
function addBrewer() {
131
+
brewerForm = { name: "", brewer_type: "", description: "" };
132
+
showBrewerModal = true;
133
+
}
134
+
135
+
async function saveBrewer() {
136
+
try {
137
+
await api.post("/api/brewers", brewerForm);
138
+
await loadProfile();
139
+
showBrewerModal = false;
140
+
} catch (err) {
141
+
alert("Failed to save brewer: " + err.message);
142
+
}
143
+
}
48
144
</script>
49
145
50
146
<svelte:head>
···
173
269
class="bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl shadow-xl p-8 text-center border border-brown-300"
174
270
>
175
271
<p class="text-brown-800 text-lg font-medium">No brews yet.</p>
272
+
{#if isOwnProfile}
273
+
<div class="mt-4">
274
+
<button
275
+
on:click={() => navigate("/brews/new")}
276
+
class="bg-brown-700 text-white px-6 py-2 rounded-lg hover:bg-brown-800 font-medium transition-colors"
277
+
>
278
+
+ Add Brew
279
+
</button>
280
+
</div>
281
+
{/if}
176
282
</div>
177
283
{:else}
178
284
<div
···
235
341
</tbody>
236
342
</table>
237
343
</div>
344
+
{#if isOwnProfile}
345
+
<div class="mt-4 text-center">
346
+
<button
347
+
on:click={() => navigate("/brews/new")}
348
+
class="bg-brown-700 text-white px-6 py-2 rounded-lg hover:bg-brown-800 font-medium transition-colors"
349
+
>
350
+
+ Add Brew
351
+
</button>
352
+
</div>
353
+
{/if}
238
354
{/if}
239
355
{:else if activeTab === "beans"}
240
356
<div class="space-y-6">
···
302
418
</tbody>
303
419
</table>
304
420
</div>
421
+
{#if isOwnProfile}
422
+
<div class="mt-4 text-center">
423
+
<button
424
+
on:click={addBean}
425
+
class="bg-brown-700 text-white px-6 py-2 rounded-lg hover:bg-brown-800 font-medium transition-colors"
426
+
>
427
+
+ Add Bean
428
+
</button>
429
+
</div>
430
+
{/if}
305
431
</div>
306
432
{/if}
307
433
···
357
483
</tbody>
358
484
</table>
359
485
</div>
486
+
{#if isOwnProfile}
487
+
<div class="mt-4 text-center">
488
+
<button
489
+
on:click={addRoaster}
490
+
class="bg-brown-700 text-white px-6 py-2 rounded-lg hover:bg-brown-800 font-medium transition-colors"
491
+
>
492
+
+ Add Roaster
493
+
</button>
494
+
</div>
495
+
{/if}
360
496
</div>
361
497
{/if}
362
498
···
365
501
class="bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl shadow-xl p-8 text-center text-brown-800 border border-brown-300"
366
502
>
367
503
<p class="font-medium">No beans or roasters yet.</p>
504
+
{#if isOwnProfile}
505
+
<div class="mt-4 flex gap-4 justify-center">
506
+
<button
507
+
on:click={addBean}
508
+
class="bg-brown-700 text-white px-6 py-2 rounded-lg hover:bg-brown-800 font-medium transition-colors"
509
+
>
510
+
+ Add Bean
511
+
</button>
512
+
<button
513
+
on:click={addRoaster}
514
+
class="bg-brown-700 text-white px-6 py-2 rounded-lg hover:bg-brown-800 font-medium transition-colors"
515
+
>
516
+
+ Add Roaster
517
+
</button>
518
+
</div>
519
+
{/if}
368
520
</div>
369
521
{/if}
370
522
</div>
···
420
572
</tbody>
421
573
</table>
422
574
</div>
575
+
{#if isOwnProfile}
576
+
<div class="mt-4 text-center">
577
+
<button
578
+
on:click={addGrinder}
579
+
class="bg-brown-700 text-white px-6 py-2 rounded-lg hover:bg-brown-800 font-medium transition-colors"
580
+
>
581
+
+ Add Grinder
582
+
</button>
583
+
</div>
584
+
{/if}
423
585
</div>
424
586
{/if}
425
587
···
466
628
</tbody>
467
629
</table>
468
630
</div>
631
+
{#if isOwnProfile}
632
+
<div class="mt-4 text-center">
633
+
<button
634
+
on:click={addBrewer}
635
+
class="bg-brown-700 text-white px-6 py-2 rounded-lg hover:bg-brown-800 font-medium transition-colors"
636
+
>
637
+
+ Add Brewer
638
+
</button>
639
+
</div>
640
+
{/if}
469
641
</div>
470
642
{/if}
471
643
···
474
646
class="bg-gradient-to-br from-brown-100 to-brown-200 rounded-xl shadow-xl p-8 text-center text-brown-800 border border-brown-300"
475
647
>
476
648
<p class="font-medium">No gear added yet.</p>
649
+
{#if isOwnProfile}
650
+
<div class="mt-4 flex gap-4 justify-center">
651
+
<button
652
+
on:click={addGrinder}
653
+
class="bg-brown-700 text-white px-6 py-2 rounded-lg hover:bg-brown-800 font-medium transition-colors"
654
+
>
655
+
+ Add Grinder
656
+
</button>
657
+
<button
658
+
on:click={addBrewer}
659
+
class="bg-brown-700 text-white px-6 py-2 rounded-lg hover:bg-brown-800 font-medium transition-colors"
660
+
>
661
+
+ Add Brewer
662
+
</button>
663
+
</div>
664
+
{/if}
477
665
</div>
478
666
{/if}
479
667
</div>
···
481
669
</div>
482
670
{/if}
483
671
</div>
672
+
673
+
<!-- Modals -->
674
+
{#if isOwnProfile}
675
+
<Modal
676
+
bind:isOpen={showBeanModal}
677
+
title="Add Bean"
678
+
onSave={saveBean}
679
+
onCancel={() => (showBeanModal = false)}
680
+
>
681
+
<input
682
+
type="text"
683
+
bind:value={beanForm.name}
684
+
placeholder="Name *"
685
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
686
+
/>
687
+
<input
688
+
type="text"
689
+
bind:value={beanForm.origin}
690
+
placeholder="Origin *"
691
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
692
+
/>
693
+
<select
694
+
bind:value={beanForm.roaster_rkey}
695
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
696
+
>
697
+
<option value="">Select Roaster (Optional)</option>
698
+
{#each roasters as roaster}
699
+
<option value={roaster.rkey}>{roaster.name}</option>
700
+
{/each}
701
+
</select>
702
+
<select
703
+
bind:value={beanForm.roast_level}
704
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
705
+
>
706
+
<option value="">Select Roast Level (Optional)</option>
707
+
<option value="Ultra-Light">Ultra-Light</option>
708
+
<option value="Light">Light</option>
709
+
<option value="Medium-Light">Medium-Light</option>
710
+
<option value="Medium">Medium</option>
711
+
<option value="Medium-Dark">Medium-Dark</option>
712
+
<option value="Dark">Dark</option>
713
+
</select>
714
+
<input
715
+
type="text"
716
+
bind:value={beanForm.process}
717
+
placeholder="Process (e.g. Washed, Natural, Honey)"
718
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
719
+
/>
720
+
<textarea
721
+
bind:value={beanForm.description}
722
+
placeholder="Description"
723
+
rows="3"
724
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
725
+
></textarea>
726
+
</Modal>
727
+
728
+
<Modal
729
+
bind:isOpen={showRoasterModal}
730
+
title="Add Roaster"
731
+
onSave={saveRoaster}
732
+
onCancel={() => (showRoasterModal = false)}
733
+
>
734
+
<input
735
+
type="text"
736
+
bind:value={roasterForm.name}
737
+
placeholder="Name *"
738
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
739
+
/>
740
+
<input
741
+
type="text"
742
+
bind:value={roasterForm.location}
743
+
placeholder="Location"
744
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
745
+
/>
746
+
<input
747
+
type="url"
748
+
bind:value={roasterForm.website}
749
+
placeholder="Website"
750
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
751
+
/>
752
+
</Modal>
753
+
754
+
<Modal
755
+
bind:isOpen={showGrinderModal}
756
+
title="Add Grinder"
757
+
onSave={saveGrinder}
758
+
onCancel={() => (showGrinderModal = false)}
759
+
>
760
+
<input
761
+
type="text"
762
+
bind:value={grinderForm.name}
763
+
placeholder="Name *"
764
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
765
+
/>
766
+
<select
767
+
bind:value={grinderForm.grinder_type}
768
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
769
+
>
770
+
<option value="">Select Grinder Type *</option>
771
+
<option value="Hand">Hand</option>
772
+
<option value="Electric">Electric</option>
773
+
<option value="Portable Electric">Portable Electric</option>
774
+
</select>
775
+
<select
776
+
bind:value={grinderForm.burr_type}
777
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
778
+
>
779
+
<option value="">Select Burr Type (Optional)</option>
780
+
<option value="Conical">Conical</option>
781
+
<option value="Flat">Flat</option>
782
+
</select>
783
+
<textarea
784
+
bind:value={grinderForm.notes}
785
+
placeholder="Notes"
786
+
rows="3"
787
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
788
+
></textarea>
789
+
</Modal>
790
+
791
+
<Modal
792
+
bind:isOpen={showBrewerModal}
793
+
title="Add Brewer"
794
+
onSave={saveBrewer}
795
+
onCancel={() => (showBrewerModal = false)}
796
+
>
797
+
<input
798
+
type="text"
799
+
bind:value={brewerForm.name}
800
+
placeholder="Name *"
801
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
802
+
/>
803
+
<input
804
+
type="text"
805
+
bind:value={brewerForm.brewer_type}
806
+
placeholder="Type (e.g., Pour-Over, Immersion, Espresso)"
807
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
808
+
/>
809
+
<textarea
810
+
bind:value={brewerForm.description}
811
+
placeholder="Description"
812
+
rows="3"
813
+
class="w-full rounded-lg border-2 border-brown-300 bg-white shadow-sm py-2 px-3 focus:border-brown-600 focus:ring-brown-600"
814
+
></textarea>
815
+
</Modal>
816
+
{/if}
+1
-1
justfile
+1
-1
justfile
···
2
2
@LOG_LEVEL=debug LOG_FORMAT=console go run cmd/arabica-server/main.go -known-dids known-dids.txt
3
3
4
4
run-production:
5
-
@LOG_FORMAT=json SECURE_COOKIES=true go run cmd/arabica-server/main.go
5
+
@LOG_FORMAT=json SERVER_PUBLIC_URL=https://arabica.example.com go run cmd/arabica-server/main.go
6
6
7
7
test:
8
8
@go test ./... -cover -coverprofile=cover.out
+10
-29
module.nix
+10
-29
module.nix
···
26
26
};
27
27
28
28
logFormat = lib.mkOption {
29
-
type = lib.types.enum [ "pretty" "json" ];
29
+
type = lib.types.enum [ "console" "json" ];
30
30
default = "json";
31
31
description =
32
32
"Log format. Use 'json' for production, 'pretty' for development.";
33
33
};
34
-
35
-
secureCookies = lib.mkOption {
36
-
type = lib.types.bool;
37
-
default = true;
38
-
description =
39
-
"Whether to set the Secure flag on cookies. Should be true when using HTTPS.";
40
-
};
41
34
};
42
35
43
-
oauth = {
44
-
clientId = lib.mkOption {
45
-
type = lib.types.str;
46
-
description = ''
47
-
OAuth client ID. This should be the URL to your client-metadata.json endpoint.
48
-
For example: https://arabica.example.com/client-metadata.json
49
-
'';
50
-
example = "https://arabica.example.com/client-metadata.json";
51
-
};
52
-
53
-
redirectUri = lib.mkOption {
54
-
type = lib.types.str;
55
-
description = ''
56
-
OAuth redirect URI. This is where users are redirected after authentication.
57
-
For example: https://arabica.example.com/oauth/callback
58
-
'';
59
-
example = "https://arabica.example.com/oauth/callback";
60
-
};
36
+
publicUrl = lib.mkOption {
37
+
type = lib.types.str;
38
+
description = ''
39
+
Public URL where the arabica service is accessible.
40
+
This is used for OAuth configuration and automatically enables secure cookies when using HTTPS.
41
+
For example: https://arabica.example.com
42
+
'';
43
+
example = "https://arabica.example.com";
61
44
};
62
45
63
46
dataDir = lib.mkOption {
···
133
116
PORT = toString cfg.settings.port;
134
117
LOG_LEVEL = cfg.settings.logLevel;
135
118
LOG_FORMAT = cfg.settings.logFormat;
136
-
SECURE_COOKIES = lib.boolToString cfg.settings.secureCookies;
137
-
OAUTH_CLIENT_ID = cfg.oauth.clientId;
138
-
OAUTH_REDIRECT_URI = cfg.oauth.redirectUri;
119
+
SERVER_PUBLIC_URL = cfg.publicUrl;
139
120
ARABICA_DB_PATH = "${cfg.dataDir}/arabica.db";
140
121
};
141
122
};
+1
-1
static/app/index.html
+1
-1
static/app/index.html
···
35
35
<!-- Web Manifest -->
36
36
<link rel="manifest" href="/static/manifest.json" />
37
37
<meta name="theme-color" content="#78350f" />
38
-
<script type="module" crossorigin src="/static/app/assets/index-BicdtNgC.js"></script>
38
+
<script type="module" crossorigin src="/static/app/assets/index-CfVnAkmR.js"></script>
39
39
<link rel="stylesheet" crossorigin href="/static/app/assets/index-DUcERGgO.css">
40
40
</head>
41
41
<body class="bg-brown-50 text-brown-900 min-h-screen">
+1
-1
static/css/output.css
+1
-1
static/css/output.css
···
1
-
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}button,input[type=button],input[type=submit]{min-height:44px;min-width:44px}@media (max-width:768px){input,select,textarea{font-size:16px}}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.right-0{right:0}.top-0{top:0}.z-10{z-index:10}.z-50{z-index:50}.m-1{margin:.25rem}.-mx-3{margin-left:-.75rem;margin-right:-.75rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-12{margin-top:3rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-2{height:.5rem}.h-20{height:5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.max-h-60{max-height:15rem}.min-h-screen{min-height:100vh}.w-1\/2{width:50%}.w-1\/4{width:25%}.w-1\/6{width:16.666667%}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.w-20{width:5rem}.w-3\/4{width:75%}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[50px\]{min-width:50px}.min-w-full{min-width:100%}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-none{max-width:none}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.rotate-180{--tw-rotate:180deg}.rotate-180,.transform{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))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-0\.5{row-gap:.125rem}.gap-y-1{row-gap:.25rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.5rem*var(--tw-space-y-reverse));margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-brown-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(234 221 215/var(--tw-divide-opacity,1))}.divide-brown-300>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(224 206 199/var(--tw-divide-opacity,1))}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-brown-100{--tw-border-opacity:1;border-color:rgb(242 232 229/var(--tw-border-opacity,1))}.border-brown-200{--tw-border-opacity:1;border-color:rgb(234 221 215/var(--tw-border-opacity,1))}.border-brown-300{--tw-border-opacity:1;border-color:rgb(224 206 199/var(--tw-border-opacity,1))}.border-brown-600{--tw-border-opacity:1;border-color:rgb(127 85 57/var(--tw-border-opacity,1))}.border-brown-700{--tw-border-opacity:1;border-color:rgb(107 68 35/var(--tw-border-opacity,1))}.border-brown-800{--tw-border-opacity:1;border-color:rgb(74 44 42/var(--tw-border-opacity,1))}.border-brown-900{--tw-border-opacity:1;border-color:rgb(61 35 25/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-red-200{--tw-border-opacity:1;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity,1))}.bg-amber-100{--tw-bg-opacity:1;background-color:rgb(254 243 199/var(--tw-bg-opacity,1))}.bg-amber-400{--tw-bg-opacity:1;background-color:rgb(251 191 36/var(--tw-bg-opacity,1))}.bg-black\/40{background-color:rgba(0,0,0,.4)}.bg-brown-200{--tw-bg-opacity:1;background-color:rgb(234 221 215/var(--tw-bg-opacity,1))}.bg-brown-200\/80{background-color:hsla(19,31%,88%,.8)}.bg-brown-300{--tw-bg-opacity:1;background-color:rgb(224 206 199/var(--tw-bg-opacity,1))}.bg-brown-50{--tw-bg-opacity:1;background-color:rgb(253 248 246/var(--tw-bg-opacity,1))}.bg-brown-50\/60{background-color:hsla(17,64%,98%,.6)}.bg-brown-600{--tw-bg-opacity:1;background-color:rgb(127 85 57/var(--tw-bg-opacity,1))}.bg-brown-700{--tw-bg-opacity:1;background-color:rgb(107 68 35/var(--tw-bg-opacity,1))}.bg-brown-800{--tw-bg-opacity:1;background-color:rgb(74 44 42/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-white\/60{background-color:hsla(0,0%,100%,.6)}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-amber-50{--tw-gradient-from:#fffbeb var(--tw-gradient-from-position);--tw-gradient-to:rgba(255,251,235,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-brown-100{--tw-gradient-from:#f2e8e5 var(--tw-gradient-from-position);--tw-gradient-to:hsla(14,33%,92%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-brown-50{--tw-gradient-from:#fdf8f6 var(--tw-gradient-from-position);--tw-gradient-to:hsla(17,64%,98%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-brown-500{--tw-gradient-from:#bfa094 var(--tw-gradient-from-position);--tw-gradient-to:hsla(17,25%,66%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-brown-700{--tw-gradient-from:#6b4423 var(--tw-gradient-from-position);--tw-gradient-to:rgba(107,68,35,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-brown-800{--tw-gradient-from:#4a2c2a var(--tw-gradient-from-position);--tw-gradient-to:rgba(74,44,42,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-brown-100{--tw-gradient-to:#f2e8e5 var(--tw-gradient-to-position)}.to-brown-200{--tw-gradient-to:#eaddd7 var(--tw-gradient-to-position)}.to-brown-600{--tw-gradient-to:#7f5539 var(--tw-gradient-to-position)}.to-brown-800{--tw-gradient-to:#4a2c2a var(--tw-gradient-to-position)}.to-brown-900{--tw-gradient-to:#3d2319 var(--tw-gradient-to-position)}.object-cover{-o-object-fit:cover;object-fit:cover}.p-12{padding:3rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-bottom:.125rem;padding-top:.125rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-12{padding-bottom:3rem;padding-top:3rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.py-8{padding-bottom:2rem;padding-top:2rem}.pl-3{padding-left:.75rem}.pt-1{padding-top:.25rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-6xl{font-size:3.75rem;line-height:1}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-relaxed{line-height:1.625}.tracking-wider{letter-spacing:.05em}.text-amber-900{--tw-text-opacity:1;color:rgb(120 53 15/var(--tw-text-opacity,1))}.text-brown-100{--tw-text-opacity:1;color:rgb(242 232 229/var(--tw-text-opacity,1))}.text-brown-300{--tw-text-opacity:1;color:rgb(224 206 199/var(--tw-text-opacity,1))}.text-brown-400{--tw-text-opacity:1;color:rgb(210 186 176/var(--tw-text-opacity,1))}.text-brown-500{--tw-text-opacity:1;color:rgb(191 160 148/var(--tw-text-opacity,1))}.text-brown-600{--tw-text-opacity:1;color:rgb(127 85 57/var(--tw-text-opacity,1))}.text-brown-700{--tw-text-opacity:1;color:rgb(107 68 35/var(--tw-text-opacity,1))}.text-brown-800{--tw-text-opacity:1;color:rgb(74 44 42/var(--tw-text-opacity,1))}.text-brown-900{--tw-text-opacity:1;color:rgb(61 35 25/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.accent-brown-700{accent-color:#6b4423}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-md,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-brown-500{--tw-ring-opacity:1;--tw-ring-color:rgb(191 160 148/var(--tw-ring-opacity,1))}.ring-brown-600{--tw-ring-opacity:1;--tw-ring-color:rgb(127 85 57/var(--tw-ring-opacity,1))}.filter{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)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-shadow{transition-duration:.15s;transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.htmx-swapping{opacity:0;transition:opacity .3s ease-out}.hover\:bg-brown-100:hover{--tw-bg-opacity:1;background-color:rgb(242 232 229/var(--tw-bg-opacity,1))}.hover\:bg-brown-100\/60:hover{background-color:hsla(14,33%,92%,.6)}.hover\:bg-brown-300:hover{--tw-bg-opacity:1;background-color:rgb(224 206 199/var(--tw-bg-opacity,1))}.hover\:bg-brown-400:hover{--tw-bg-opacity:1;background-color:rgb(210 186 176/var(--tw-bg-opacity,1))}.hover\:bg-brown-50:hover{--tw-bg-opacity:1;background-color:rgb(253 248 246/var(--tw-bg-opacity,1))}.hover\:bg-brown-800:hover{--tw-bg-opacity:1;background-color:rgb(74 44 42/var(--tw-bg-opacity,1))}.hover\:bg-gray-300:hover{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity,1))}.hover\:from-brown-600:hover{--tw-gradient-from:#7f5539 var(--tw-gradient-from-position);--tw-gradient-to:rgba(127,85,57,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.hover\:from-brown-800:hover{--tw-gradient-from:#4a2c2a var(--tw-gradient-from-position);--tw-gradient-to:rgba(74,44,42,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.hover\:to-brown-700:hover{--tw-gradient-to:#6b4423 var(--tw-gradient-to-position)}.hover\:to-brown-900:hover{--tw-gradient-to:#3d2319 var(--tw-gradient-to-position)}.hover\:text-brown-700:hover{--tw-text-opacity:1;color:rgb(107 68 35/var(--tw-text-opacity,1))}.hover\:text-brown-800:hover{--tw-text-opacity:1;color:rgb(74 44 42/var(--tw-text-opacity,1))}.hover\:text-brown-900:hover{--tw-text-opacity:1;color:rgb(61 35 25/var(--tw-text-opacity,1))}.hover\:text-red-800:hover{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-80:hover{opacity:.8}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.hover\:shadow-lg:hover,.hover\:shadow-xl:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hover\:shadow-xl:hover{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.hover\:ring-2:hover{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.hover\:ring-brown-600:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(127 85 57/var(--tw-ring-opacity,1))}.focus\:border-brown-600:focus{--tw-border-opacity:1;border-color:rgb(127 85 57/var(--tw-border-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-brown-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(127 85 57/var(--tw-ring-opacity,1))}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:640px){.sm\:inline{display:inline}}@media (min-width:768px){.md\:mx-0{margin-left:0;margin-right:0}.md\:mb-6{margin-bottom:1.5rem}.md\:line-clamp-2{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2}.md\:flex{display:flex}.md\:hidden{display:none}.md\:w-20{width:5rem}.md\:min-w-\[60px\]{min-width:60px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:flex-col{flex-direction:column}.md\:items-start{align-items:flex-start}.md\:items-end{align-items:flex-end}.md\:justify-between{justify-content:space-between}.md\:gap-4{gap:1rem}.md\:space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.5rem*var(--tw-space-y-reverse));margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)))}.md\:p-3{padding:.75rem}.md\:p-4{padding:1rem}.md\:p-5{padding:1.25rem}.md\:p-6{padding:1.5rem}.md\:p-8{padding:2rem}.md\:px-4{padding-left:1rem;padding-right:1rem}.md\:px-6{padding-left:1.5rem;padding-right:1.5rem}.md\:py-3{padding-bottom:.75rem;padding-top:.75rem}.md\:py-8{padding-bottom:2rem;padding-top:2rem}.md\:text-3xl{font-size:1.875rem;line-height:2.25rem}.md\:text-base{font-size:1rem;line-height:1.5rem}.md\:text-sm{font-size:.875rem;line-height:1.25rem}}
1
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-tap-highlight-color:transparent}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-feature-settings:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}button,input[type=button],input[type=submit]{min-height:44px;min-width:44px}@media (max-width:768px){input,select,textarea{font-size:16px}}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.right-0{right:0}.top-0{top:0}.z-10{z-index:10}.z-50{z-index:50}.m-1{margin:.25rem}.-mx-3{margin-left:-.75rem;margin-right:-.75rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-12{margin-top:3rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-2{height:.5rem}.h-20{height:5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.max-h-60{max-height:15rem}.min-h-screen{min-height:100vh}.w-1\/2{width:50%}.w-1\/4{width:25%}.w-1\/6{width:16.666667%}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.w-20{width:5rem}.w-3\/4{width:75%}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[50px\]{min-width:50px}.min-w-full{min-width:100%}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-none{max-width:none}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.rotate-180{--tw-rotate:180deg}.rotate-180,.transform{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))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-0\.5{row-gap:.125rem}.gap-y-1{row-gap:.25rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.5rem*var(--tw-space-y-reverse));margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-brown-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(234 221 215/var(--tw-divide-opacity,1))}.divide-brown-300>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(224 206 199/var(--tw-divide-opacity,1))}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l-2{border-left-width:2px}.border-t{border-top-width:1px}.border-brown-100{--tw-border-opacity:1;border-color:rgb(242 232 229/var(--tw-border-opacity,1))}.border-brown-200{--tw-border-opacity:1;border-color:rgb(234 221 215/var(--tw-border-opacity,1))}.border-brown-300{--tw-border-opacity:1;border-color:rgb(224 206 199/var(--tw-border-opacity,1))}.border-brown-600{--tw-border-opacity:1;border-color:rgb(127 85 57/var(--tw-border-opacity,1))}.border-brown-700{--tw-border-opacity:1;border-color:rgb(107 68 35/var(--tw-border-opacity,1))}.border-brown-800{--tw-border-opacity:1;border-color:rgb(74 44 42/var(--tw-border-opacity,1))}.border-brown-900{--tw-border-opacity:1;border-color:rgb(61 35 25/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-red-200{--tw-border-opacity:1;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity,1))}.bg-amber-100{--tw-bg-opacity:1;background-color:rgb(254 243 199/var(--tw-bg-opacity,1))}.bg-amber-400{--tw-bg-opacity:1;background-color:rgb(251 191 36/var(--tw-bg-opacity,1))}.bg-black\/40{background-color:rgba(0,0,0,.4)}.bg-brown-200{--tw-bg-opacity:1;background-color:rgb(234 221 215/var(--tw-bg-opacity,1))}.bg-brown-200\/80{background-color:hsla(19,31%,88%,.8)}.bg-brown-300{--tw-bg-opacity:1;background-color:rgb(224 206 199/var(--tw-bg-opacity,1))}.bg-brown-50{--tw-bg-opacity:1;background-color:rgb(253 248 246/var(--tw-bg-opacity,1))}.bg-brown-50\/60{background-color:hsla(17,64%,98%,.6)}.bg-brown-600{--tw-bg-opacity:1;background-color:rgb(127 85 57/var(--tw-bg-opacity,1))}.bg-brown-700{--tw-bg-opacity:1;background-color:rgb(107 68 35/var(--tw-bg-opacity,1))}.bg-brown-800{--tw-bg-opacity:1;background-color:rgb(74 44 42/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-white\/60{background-color:hsla(0,0%,100%,.6)}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-amber-50{--tw-gradient-from:#fffbeb var(--tw-gradient-from-position);--tw-gradient-to:rgba(255,251,235,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-brown-100{--tw-gradient-from:#f2e8e5 var(--tw-gradient-from-position);--tw-gradient-to:hsla(14,33%,92%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-brown-50{--tw-gradient-from:#fdf8f6 var(--tw-gradient-from-position);--tw-gradient-to:hsla(17,64%,98%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-brown-500{--tw-gradient-from:#bfa094 var(--tw-gradient-from-position);--tw-gradient-to:hsla(17,25%,66%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-brown-700{--tw-gradient-from:#6b4423 var(--tw-gradient-from-position);--tw-gradient-to:rgba(107,68,35,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-brown-800{--tw-gradient-from:#4a2c2a var(--tw-gradient-from-position);--tw-gradient-to:rgba(74,44,42,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-brown-100{--tw-gradient-to:#f2e8e5 var(--tw-gradient-to-position)}.to-brown-200{--tw-gradient-to:#eaddd7 var(--tw-gradient-to-position)}.to-brown-600{--tw-gradient-to:#7f5539 var(--tw-gradient-to-position)}.to-brown-800{--tw-gradient-to:#4a2c2a var(--tw-gradient-to-position)}.to-brown-900{--tw-gradient-to:#3d2319 var(--tw-gradient-to-position)}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-12{padding:3rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-bottom:.125rem;padding-top:.125rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-12{padding-bottom:3rem;padding-top:3rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.py-8{padding-bottom:2rem;padding-top:2rem}.pl-3{padding-left:.75rem}.pt-1{padding-top:.25rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-6xl{font-size:3.75rem;line-height:1}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.italic{font-style:italic}.leading-relaxed{line-height:1.625}.tracking-wider{letter-spacing:.05em}.text-amber-900{--tw-text-opacity:1;color:rgb(120 53 15/var(--tw-text-opacity,1))}.text-brown-100{--tw-text-opacity:1;color:rgb(242 232 229/var(--tw-text-opacity,1))}.text-brown-300{--tw-text-opacity:1;color:rgb(224 206 199/var(--tw-text-opacity,1))}.text-brown-400{--tw-text-opacity:1;color:rgb(210 186 176/var(--tw-text-opacity,1))}.text-brown-500{--tw-text-opacity:1;color:rgb(191 160 148/var(--tw-text-opacity,1))}.text-brown-600{--tw-text-opacity:1;color:rgb(127 85 57/var(--tw-text-opacity,1))}.text-brown-700{--tw-text-opacity:1;color:rgb(107 68 35/var(--tw-text-opacity,1))}.text-brown-800{--tw-text-opacity:1;color:rgb(74 44 42/var(--tw-text-opacity,1))}.text-brown-900{--tw-text-opacity:1;color:rgb(61 35 25/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.accent-brown-700{accent-color:#6b4423}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-md,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-brown-500{--tw-ring-opacity:1;--tw-ring-color:rgb(191 160 148/var(--tw-ring-opacity,1))}.ring-brown-600{--tw-ring-opacity:1;--tw-ring-color:rgb(127 85 57/var(--tw-ring-opacity,1))}.filter{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)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-shadow{transition-duration:.15s;transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.htmx-swapping{opacity:0;transition:opacity .3s ease-out}.hover\:bg-brown-100:hover{--tw-bg-opacity:1;background-color:rgb(242 232 229/var(--tw-bg-opacity,1))}.hover\:bg-brown-100\/60:hover{background-color:hsla(14,33%,92%,.6)}.hover\:bg-brown-300:hover{--tw-bg-opacity:1;background-color:rgb(224 206 199/var(--tw-bg-opacity,1))}.hover\:bg-brown-400:hover{--tw-bg-opacity:1;background-color:rgb(210 186 176/var(--tw-bg-opacity,1))}.hover\:bg-brown-50:hover{--tw-bg-opacity:1;background-color:rgb(253 248 246/var(--tw-bg-opacity,1))}.hover\:bg-brown-800:hover{--tw-bg-opacity:1;background-color:rgb(74 44 42/var(--tw-bg-opacity,1))}.hover\:bg-gray-300:hover{--tw-bg-opacity:1;background-color:rgb(209 213 219/var(--tw-bg-opacity,1))}.hover\:from-brown-600:hover{--tw-gradient-from:#7f5539 var(--tw-gradient-from-position);--tw-gradient-to:rgba(127,85,57,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.hover\:from-brown-800:hover{--tw-gradient-from:#4a2c2a var(--tw-gradient-from-position);--tw-gradient-to:rgba(74,44,42,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.hover\:to-brown-700:hover{--tw-gradient-to:#6b4423 var(--tw-gradient-to-position)}.hover\:to-brown-900:hover{--tw-gradient-to:#3d2319 var(--tw-gradient-to-position)}.hover\:text-brown-700:hover{--tw-text-opacity:1;color:rgb(107 68 35/var(--tw-text-opacity,1))}.hover\:text-brown-800:hover{--tw-text-opacity:1;color:rgb(74 44 42/var(--tw-text-opacity,1))}.hover\:text-brown-900:hover{--tw-text-opacity:1;color:rgb(61 35 25/var(--tw-text-opacity,1))}.hover\:text-red-800:hover{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-80:hover{opacity:.8}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.hover\:shadow-lg:hover,.hover\:shadow-xl:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hover\:shadow-xl:hover{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.hover\:ring-2:hover{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.hover\:ring-brown-600:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(127 85 57/var(--tw-ring-opacity,1))}.focus\:border-brown-600:focus{--tw-border-opacity:1;border-color:rgb(127 85 57/var(--tw-border-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-brown-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(127 85 57/var(--tw-ring-opacity,1))}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:640px){.sm\:inline{display:inline}}@media (min-width:768px){.md\:mx-0{margin-left:0;margin-right:0}.md\:mb-6{margin-bottom:1.5rem}.md\:line-clamp-2{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2}.md\:flex{display:flex}.md\:hidden{display:none}.md\:w-20{width:5rem}.md\:min-w-\[60px\]{min-width:60px}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:flex-col{flex-direction:column}.md\:items-start{align-items:flex-start}.md\:items-end{align-items:flex-end}.md\:justify-between{justify-content:space-between}.md\:gap-4{gap:1rem}.md\:space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.5rem*var(--tw-space-y-reverse));margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)))}.md\:p-3{padding:.75rem}.md\:p-4{padding:1rem}.md\:p-5{padding:1.25rem}.md\:p-6{padding:1.5rem}.md\:p-8{padding:2rem}.md\:px-4{padding-left:1rem;padding-right:1rem}.md\:px-6{padding-left:1.5rem;padding-right:1.5rem}.md\:py-3{padding-bottom:.75rem;padding-top:.75rem}.md\:py-8{padding-bottom:2rem;padding-top:2rem}.md\:text-3xl{font-size:1.875rem;line-height:2.25rem}.md\:text-base{font-size:1rem;line-height:1.5rem}.md\:text-sm{font-size:.875rem;line-height:1.25rem}}
History
1 round
0 comments
pdewey.com
submitted
#0
1 commit
expand
collapse
feat: manage page styling improvements
expand 0 comments
closed without merging