Coffee journaling on ATProto (alpha) alpha.arabica.social
coffee

feat: manage page styling improvements #7

closed opened by pdewey.com targeting main from refactor-svelte
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:hm5f3dnm6jdhrc55qp2npdja/sh.tangled.repo.pull/3mdercwj6qp22
+400 -65
Diff #0
+2 -1
.gitignore
··· 6 6 *.dll 7 7 *.so 8 8 *.dylib 9 + /arabica-server 9 10 10 11 11 12 # Test binary ··· 15 16 *.out 16 17 17 18 # Generated files 18 - web/static/css/output.css 19 + static/css/output.css 19 20 20 21 # Database 21 22 *.db
+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
··· 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
··· 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
··· 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
··· 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
··· 7 7 8 8 LOG_LEVEL=info 9 9 LOG_FORMAT=json 10 + # SERVER_PUBLIC_URL with HTTPS automatically enables secure cookies 10 11 SERVER_PUBLIC_URL=https://${DOMAIN} 11 - SECURE_COOKIES=true
-1
deploy/compose.yml
··· 28 28 - LOG_LEVEL=${LOG_LEVEL:-info} 29 29 - LOG_FORMAT=${LOG_FORMAT:-json} 30 30 - SERVER_PUBLIC_URL=${SERVER_PUBLIC_URL:-https://${DOMAIN}} 31 - - SECURE_COOKIES=true 32 31 restart: unless-stopped 33 32 34 33 volumes:
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 - *,: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
sign up or login to add to the discussion
pdewey.com submitted #0
1 commit
expand
feat: manage page styling improvements
expand 0 comments
closed without merging