@nyuccl/smile 0.2.0-beta.38 → 0.2.0-beta.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/builtins/ExpView.vue +1 -1
  3. package/dist/runtime/components/builtins/InformedConsentModal.vue +2 -2
  4. package/dist/runtime/components/builtins/InformedConsentView.vue +2 -2
  5. package/dist/runtime/components/builtins/InstructionsView.vue +1 -1
  6. package/dist/runtime/components/builtins/LocalDevBanner.vue +1 -1
  7. package/dist/runtime/components/builtins/MTurkRecruitView.vue +3 -3
  8. package/dist/runtime/components/builtins/StudyPreviewText.vue +7 -7
  9. package/dist/runtime/components/builtins/ThanksView.vue +3 -3
  10. package/dist/runtime/components/builtins/WithdrawModal.vue +6 -6
  11. package/dist/runtime/components/dev/RecruitmentChooserView.vue +19 -2
  12. package/dist/runtime/components/dev/ResponsiveDeviceContainer.vue +10 -4
  13. package/dist/runtime/components/dev/SmileDevPresentationView.vue +1 -1
  14. package/dist/runtime/components/dev/console/ConfigList.vue +6 -5
  15. package/dist/runtime/components/dev/console/ConsoleConfigPanel.vue +17 -1
  16. package/dist/runtime/components/dev/console/ConsoleLogPanel.vue +4 -4
  17. package/dist/runtime/components/dev/console/DatabaseList.vue +1 -1
  18. package/dist/runtime/components/dev/menu/DevConfigPanel.vue +1 -1
  19. package/dist/runtime/components/dev/menu/SmileDevAppMenu.vue +1 -1
  20. package/dist/runtime/components/dev/navbar/DatabaseButtonGroup.vue +2 -2
  21. package/dist/runtime/components/dev/navbar/KeyCommandNotification.vue +1 -1
  22. package/dist/runtime/components/dev/navbar/SmileDevNavBar.vue +2 -2
  23. package/dist/runtime/components/dev/presentation/PresentationNavBar.vue +2 -2
  24. package/dist/runtime/components/dev/sidebar/AppProgressPanel.vue +18 -18
  25. package/dist/runtime/components/dev/sidebar/ConfigurationVariablesPanel.vue +1 -1
  26. package/dist/runtime/components/dev/sidebar/DatabaseStatusInfoPanel.vue +26 -7
  27. package/dist/runtime/components/dev/sidebar/RandomizationSidebarPanel.vue +4 -2
  28. package/dist/runtime/components/dev/sidebar/StepDataViewer.vue +1 -1
  29. package/dist/runtime/components/dev/sidebar/StepExplorerPanel.vue +5 -2
  30. package/dist/runtime/components/dev/sidebar/StepNode.vue +4 -3
  31. package/dist/runtime/components/layouts/ConstrainedTaskWindow.vue +1 -1
  32. package/dist/runtime/components/ui/badge/index.d.ts +1 -1
  33. package/dist/runtime/components/ui/button/index.d.ts +2 -2
  34. package/dist/runtime/components/ui/button-group/ButtonGroup.d.vue.ts +1 -1
  35. package/dist/runtime/components/ui/button-group/ButtonGroup.vue.d.ts +1 -1
  36. package/dist/runtime/components/ui/checkbox/Checkbox.d.vue.ts +2 -2
  37. package/dist/runtime/components/ui/checkbox/Checkbox.vue.d.ts +2 -2
  38. package/dist/runtime/components/ui/checkbox/index.d.ts +1 -1
  39. package/dist/runtime/components/ui/dropdown-menu/DropdownMenuItem.d.vue.ts +1 -1
  40. package/dist/runtime/components/ui/dropdown-menu/DropdownMenuItem.vue.d.ts +1 -1
  41. package/dist/runtime/components/ui/multiselect/MultiSelect.d.vue.ts +1 -1
  42. package/dist/runtime/components/ui/multiselect/MultiSelect.vue.d.ts +1 -1
  43. package/dist/runtime/components/ui/resizable/ResizablePanel.d.vue.ts +1 -1
  44. package/dist/runtime/components/ui/resizable/ResizablePanel.vue.d.ts +1 -1
  45. package/dist/runtime/components/ui/select/Select.d.vue.ts +1 -1
  46. package/dist/runtime/components/ui/select/Select.vue.d.ts +1 -1
  47. package/dist/runtime/components/ui/sheet/SheetContent.d.vue.ts +1 -1
  48. package/dist/runtime/components/ui/sheet/SheetContent.vue.d.ts +1 -1
  49. package/dist/runtime/components/ui/sidebar/Sidebar.d.vue.ts +1 -1
  50. package/dist/runtime/components/ui/sidebar/Sidebar.vue.d.ts +1 -1
  51. package/dist/runtime/components/ui/sidebar/SidebarMenuButton.d.vue.ts +1 -1
  52. package/dist/runtime/components/ui/sidebar/SidebarMenuButton.vue.d.ts +1 -1
  53. package/dist/runtime/components/ui/sidebar/SidebarMenuButtonChild.d.vue.ts +1 -1
  54. package/dist/runtime/components/ui/sidebar/SidebarMenuButtonChild.vue.d.ts +1 -1
  55. package/dist/runtime/components/ui/sidebar/index.d.ts +1 -1
  56. package/dist/runtime/components/ui/switch/Switch.d.vue.ts +2 -2
  57. package/dist/runtime/components/ui/switch/Switch.vue.d.ts +2 -2
  58. package/dist/runtime/components/ui/switch/index.d.ts +1 -1
  59. package/dist/runtime/components/ui/tags-input/TagsInput.d.vue.ts +1 -1
  60. package/dist/runtime/components/ui/tags-input/TagsInput.vue.d.ts +1 -1
  61. package/dist/runtime/components/ui/toggle/Toggle.d.vue.ts +2 -2
  62. package/dist/runtime/components/ui/toggle/Toggle.vue.d.ts +2 -2
  63. package/dist/runtime/components/ui/toggle/index.d.ts +1 -1
  64. package/dist/runtime/components/ui/toggle-group/ToggleGroup.d.vue.ts +1 -1
  65. package/dist/runtime/components/ui/toggle-group/ToggleGroup.vue.d.ts +1 -1
  66. package/dist/runtime/composables/useViewAPI.d.ts +1 -0
  67. package/dist/runtime/composables/useViewAPI.js +20 -4
  68. package/dist/runtime/composables/useWindowSizer.d.ts +14 -0
  69. package/dist/runtime/composables/useWindowSizer.js +48 -0
  70. package/dist/runtime/core/config.d.ts +0 -16
  71. package/dist/runtime/core/config.js +0 -13
  72. package/dist/runtime/core/stepper/Stepper.d.ts +13 -10
  73. package/dist/runtime/core/stepper/Stepper.js +69 -52
  74. package/dist/runtime/core/stepper/StepperSerializer.js +32 -14
  75. package/dist/runtime/css/main.css +1 -1
  76. package/dist/runtime/layouts/development.vue +30 -5
  77. package/dist/runtime/layouts/experiment.d.vue.ts +7 -10
  78. package/dist/runtime/layouts/experiment.vue +12 -0
  79. package/dist/runtime/layouts/experiment.vue.d.ts +7 -10
  80. package/dist/runtime/layouts/presentation.vue +22 -3
  81. package/dist/runtime/pages/dev-login.vue +1 -1
  82. package/dist/runtime/server/api/db-info.get.d.ts +5 -0
  83. package/dist/runtime/server/api/db-info.get.js +10 -0
  84. package/dist/runtime/stores/smilestore.d.ts +0 -1014
  85. package/dist/runtime/utils/utils.js +7 -0
  86. package/package.json +2 -2
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nyuccl/smile",
3
3
  "configKey": "smile",
4
- "version": "0.2.0-beta.38",
4
+ "version": "0.2.0-beta.41",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -24,7 +24,7 @@ function finish() {
24
24
  </h1>
25
25
 
26
26
  <!-- Visual separator -->
27
- <hr class="border-gray-300 my-4">
27
+ <hr class="border-muted-foreground/30 my-4">
28
28
 
29
29
  <!-- Navigation controls -->
30
30
  <div class="flex justify-end">
@@ -46,7 +46,7 @@ onUnmounted(() => {
46
46
 
47
47
  <!-- Close button in top-right corner -->
48
48
  <Button
49
- class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 p-2 rounded-full hover:bg-gray-100 transition-colors"
49
+ class="absolute top-4 right-4 text-muted-foreground hover:text-foreground p-2 rounded-full hover:bg-muted transition-colors"
50
50
  aria-label="close"
51
51
  @click="$emit('toggleConsent')"
52
52
  >
@@ -65,7 +65,7 @@ onUnmounted(() => {
65
65
  </template>
66
66
  <!-- Error fallback if consent text component is not available -->
67
67
  <template v-else>
68
- <div class="rounded-md border border-red-200 bg-red-50 p-4 text-sm text-red-800">
68
+ <div class="rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive">
69
69
  Error: Unable to load consent form text. Please contact the study administrator.
70
70
  </div>
71
71
  </template>
@@ -63,7 +63,7 @@ onBeforeUnmount(() => {
63
63
  read the consent form (you can scroll the page).
64
64
  </p>
65
65
 
66
- <div class="border-t border-gray-200 my-4" />
66
+ <div class="border-t border-border my-4" />
67
67
 
68
68
  <div class="flex items-center space-x-2 mb-4">
69
69
  <Switch
@@ -84,7 +84,7 @@ onBeforeUnmount(() => {
84
84
  <div class="hidden">
85
85
  <Label
86
86
  for="your_name"
87
- class="text-sm font-medium text-gray-700 mb-2 block"
87
+ class="text-sm font-medium text-foreground mb-2 block"
88
88
  >
89
89
  Required! Please enter your name:
90
90
  </Label>
@@ -43,7 +43,7 @@ function finish(_goto) {
43
43
  </p>
44
44
 
45
45
  <!-- Visual separator -->
46
- <hr class="border-gray-300 my-4">
46
+ <hr class="border-muted-foreground/30 my-4">
47
47
 
48
48
  <!-- Navigation button container -->
49
49
  <div class="flex justify-end">
@@ -12,5 +12,5 @@ const isDev = import.meta.dev;
12
12
  </template>
13
13
 
14
14
  <style scoped>
15
- .local-dev-banner{background:repeating-linear-gradient(-45deg,#facc15,#facc15 10px,#f0c000 0,#f0c000 20px);color:#1a1a1a;flex-shrink:0;font-size:10px;font-weight:700;letter-spacing:.08em;padding:3px 0;text-align:center;width:100%}.dark .local-dev-banner{background:repeating-linear-gradient(-45deg,#555,#555 10px,#4a4a4a 0,#4a4a4a 20px);color:#ccc}
15
+ .local-dev-banner{background:repeating-linear-gradient(-45deg,var(--dev-banner-stripe-1),var(--dev-banner-stripe-1) 10px,var(--dev-banner-stripe-2) 10px,var(--dev-banner-stripe-2) 20px);color:var(--dev-banner-text);flex-shrink:0;font-size:10px;font-weight:700;letter-spacing:.08em;padding:3px 0;text-align:center;width:100%}
16
16
  </style>
@@ -82,7 +82,7 @@ function submitForm(event) {
82
82
  </p>
83
83
 
84
84
  <!-- Visual separator -->
85
- <hr class="border-gray-300 my-4">
85
+ <hr class="border-muted-foreground/30 my-4">
86
86
 
87
87
  <!-- Completion code submission form -->
88
88
  <form
@@ -102,11 +102,11 @@ function submitForm(event) {
102
102
  v-model="completionCode"
103
103
  name="completioncode"
104
104
  placeholder="Paste your completion code here"
105
- :class="{ 'border-red-500': formError }"
105
+ :class="{ 'border-destructive': formError }"
106
106
  />
107
107
  <p
108
108
  v-if="formError"
109
- class="text-red-500 text-sm mt-1"
109
+ class="text-destructive text-sm mt-1"
110
110
  >
111
111
  {{ formError }}
112
112
  </p>
@@ -39,7 +39,7 @@ const _props = defineProps({
39
39
  </p>
40
40
 
41
41
  <!-- Visual separator -->
42
- <hr class="border-gray-300 my-4">
42
+ <hr class="border-muted-foreground/30 my-4">
43
43
 
44
44
  <!-- Study summary -->
45
45
  <p>
@@ -58,9 +58,9 @@ const _props = defineProps({
58
58
  >
59
59
 
60
60
  <!-- Lab information card -->
61
- <article class="border border-gray-300 rounded-lg shadow-lg">
61
+ <article class="border border-border rounded-lg shadow-lg">
62
62
  <!-- Card header -->
63
- <div class="bg-gray-100 px-3 py-2 text-xs font-medium border-b border-gray-300 rounded-t-lg">
63
+ <div class="bg-muted px-3 py-2 text-xs font-medium border-b border-border rounded-t-lg">
64
64
  <p><Sparkles class="inline" />&nbsp;&nbsp;Who are we?</p>
65
65
  </div>
66
66
 
@@ -71,26 +71,26 @@ const _props = defineProps({
71
71
  <a
72
72
  href="http://gureckislab.org"
73
73
  target="_blank"
74
- class="text-green-500 hover:text-green-600"
74
+ class="text-link-button-foreground hover:text-link-button-foreground/80"
75
75
  >Computation and Cognition Lab</a>
76
76
  at New York University under the direction to
77
77
  <a
78
78
  href="http://todd.gureckislab.org"
79
79
  target="_blank"
80
- class="text-green-500 hover:text-green-600"
80
+ class="text-link-button-foreground hover:text-link-button-foreground/80"
81
81
  >Dr. Todd Gureckis</a>. Our lab uses games and other fun tasks to study human intellience. Our research is funded in the public
82
82
  interest by the United States
83
83
  <a
84
84
  href="https://nsf.gov"
85
85
  target="_new"
86
- class="text-green-500 hover:text-green-600"
86
+ class="text-link-button-foreground hover:text-link-button-foreground/80"
87
87
  >National Science Foundation</a>
88
88
  among other organizations and non-profit foundations and we publish our work, code, and results for the
89
89
  public. We very much appreciate research participants in our studies and have a public
90
90
  <a
91
91
  href="http://gureckislab.org/research-code-of-ethics.html"
92
92
  target="_new"
93
- class="text-green-500 hover:text-green-600"
93
+ class="text-link-button-foreground hover:text-link-button-foreground/80"
94
94
  >code of ethics</a>
95
95
  we strive to uphold in all our studies.
96
96
  </div>
@@ -286,7 +286,7 @@ onMounted(() => {
286
286
  <Button
287
287
  variant="default"
288
288
  as="a"
289
- :href="`${api.config.sona.url}/webstudy_credit.aspx?experiment_id=${api.config.sona.experimentId}&credit_token=${api.config.sona.creditToken}&survey_code=${api.store.private.recruitmentInfo.survey_code}`"
289
+ :href="`${api.store.private.recruitmentInfo.url}/webstudy_credit.aspx?experiment_id=${api.store.private.recruitmentInfo.experimentId}&credit_token=${api.store.private.recruitmentInfo.creditToken}&survey_code=${api.store.private.recruitmentInfo.survey_code}`"
290
290
  >
291
291
  Return to SONA for credit
292
292
  <ArrowRight />
@@ -327,7 +327,7 @@ onMounted(() => {
327
327
  <Button
328
328
  variant="default"
329
329
  as="a"
330
- :href="`${api.config.sonaPaid.url}/webstudy_credit.aspx?experiment_id=${api.config.sonaPaid.experimentId}&credit_token=${api.config.sonaPaid.creditToken}&survey_code=${api.store.private.recruitmentInfo.survey_code}`"
330
+ :href="`${api.store.private.recruitmentInfo.url}/webstudy_credit.aspx?experiment_id=${api.store.private.recruitmentInfo.experimentId}&credit_token=${api.store.private.recruitmentInfo.creditToken}&survey_code=${api.store.private.recruitmentInfo.survey_code}`"
331
331
  >
332
332
  Return to SONA for payment
333
333
  <ArrowRight />
@@ -368,7 +368,7 @@ onMounted(() => {
368
368
  <Button
369
369
  variant="default"
370
370
  as="a"
371
- :href="`${api.config.spark.completionUrl}/${api.store.private.recruitmentInfo.subject_ID}`"
371
+ :href="`${api.store.private.recruitmentInfo.completionUrl}/${api.store.private.recruitmentInfo.subject_ID}`"
372
372
  >
373
373
  Return to SPARK
374
374
  <ArrowRight />
@@ -70,7 +70,7 @@ onUnmounted(() => {
70
70
 
71
71
  <!-- Close button -->
72
72
  <Button
73
- class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 p-2 rounded-full hover:bg-gray-100 transition-colors"
73
+ class="absolute top-4 right-4 text-muted-foreground hover:text-foreground p-2 rounded-full hover:bg-muted transition-colors"
74
74
  aria-label="close"
75
75
  @click="$emit('toggleWithdraw')"
76
76
  >
@@ -85,7 +85,7 @@ onUnmounted(() => {
85
85
  <!-- Modal header with title -->
86
86
  <template #title>
87
87
  <h3 class="text-3xl font-semibold">
88
- <LogOut class="text-red-500 inline-block mr-2" />Withdraw from study?
88
+ <LogOut class="text-destructive inline-block mr-2" />Withdraw from study?
89
89
  </h3>
90
90
  </template>
91
91
 
@@ -116,7 +116,7 @@ onUnmounted(() => {
116
116
  <Checkbox
117
117
  :id="`withdraw-option-${index}`"
118
118
  :checked="forminfo.reason_select.includes(option)"
119
- class="border-gray-600 data-[state=checked]:bg-gray-800 data-[state=checked]:border-gray-800"
119
+ class="border-input data-[state=checked]:bg-primary data-[state=checked]:border-primary"
120
120
  @update:checked="
121
121
  (checked) => {
122
122
  if (checked) {
@@ -141,7 +141,7 @@ onUnmounted(() => {
141
141
  </Label>
142
142
  </div>
143
143
  </div>
144
- <p class="text-xs text-gray-500">
144
+ <p class="text-xs text-muted-foreground">
145
145
  Select all that apply.
146
146
  </p>
147
147
  </div>
@@ -159,7 +159,7 @@ onUnmounted(() => {
159
159
  class="w-full bg-background dark:bg-background text-base resize-vertical"
160
160
  rows="4"
161
161
  />
162
- <p class="text-xs text-gray-500">
162
+ <p class="text-xs text-muted-foreground">
163
163
  Please let us know any additional information you would like to share.
164
164
  </p>
165
165
  </div>
@@ -177,7 +177,7 @@ onUnmounted(() => {
177
177
  placeholder="participant@gmail.com"
178
178
  class="w-full text-base bg-background dark:bg-background"
179
179
  />
180
- <p class="text-xs text-gray-500">
180
+ <p class="text-xs text-muted-foreground">
181
181
  Please enter your email address so we can follow up with you. This is optional and we otherwise will
182
182
  try to figure out how to reach you. However, this can help avoid any potential problems. Feel free to
183
183
  use an anonymized email like your Prolific contact email, Hide my email (Apple), or create a free
@@ -6,6 +6,13 @@ import useSmileStore from "../../stores/smilestore";
6
6
  const api = useViewAPI();
7
7
  const store = useSmileStore();
8
8
  const config = store.config;
9
+ const dbInfo = ref(null);
10
+ if (import.meta.client) {
11
+ fetch("/api/db-info").then((r) => r.json()).then((data) => {
12
+ dbInfo.value = data;
13
+ }).catch(() => {
14
+ });
15
+ }
9
16
  const selectedService = ref(null);
10
17
  const baseUrl = computed(() => {
11
18
  if (config.deployURL) return config.deployURL.replace(/\/$/, "");
@@ -90,7 +97,7 @@ const services = {
90
97
  studyUrl: computed(() => `${baseUrl.value}/mturk`),
91
98
  setupSteps: [
92
99
  `You'll need the <strong>AWS CLI</strong> or a tool like <a href="https://github.com/Mechanical-Turk-Requester" target="_blank" class="underline font-medium">Boto3 (Python)</a> to create ExternalQuestion HITs.`,
93
- 'Create an ExternalQuestion XML pointing to the MTurk URL shown below: <pre class="mt-1 p-2 bg-gray-100 rounded text-xs overflow-x-auto">&lt;ExternalQuestion xmlns="..."&gt;\n &lt;ExternalURL&gt;YOUR_MTURK_URL&lt;/ExternalURL&gt;\n &lt;FrameHeight&gt;800&lt;/FrameHeight&gt;\n&lt;/ExternalQuestion&gt;</pre>',
100
+ 'Create an ExternalQuestion XML pointing to the MTurk URL shown below: <pre class="mt-1 p-2 bg-muted rounded text-xs overflow-x-auto">&lt;ExternalQuestion xmlns="..."&gt;\n &lt;ExternalURL&gt;YOUR_MTURK_URL&lt;/ExternalURL&gt;\n &lt;FrameHeight&gt;800&lt;/FrameHeight&gt;\n&lt;/ExternalQuestion&gt;</pre>',
94
101
  "Create the HIT using <code>create_hit_type</code> and <code>create_hit_with_hit_type</code> (or equivalent).",
95
102
  "SMILE handles the MTurk form submission automatically \u2014 when participants finish, the Thanks page posts their completion to <code>turkSubmitTo</code>.",
96
103
  "For testing, use the <strong>MTurk Sandbox</strong> (<code>https://workersandbox.mturk.com</code>) before publishing to production.",
@@ -380,6 +387,16 @@ const currentService = computed(() => services[selectedService.value]);
380
387
  </button>
381
388
  </dd>
382
389
 
390
+ <template v-if="dbInfo">
391
+ <dt class="text-muted-foreground/60">
392
+ Database
393
+ </dt>
394
+ <dd class="font-mono flex items-center gap-1">
395
+ <span class="text-xs">{{ dbInfo.type }}</span>
396
+ <code class="text-xs text-muted-foreground">{{ dbInfo.url }}</code>
397
+ </dd>
398
+ </template>
399
+
383
400
  <template v-if="config.github?.repoName">
384
401
  <dt class="text-muted-foreground/60">
385
402
  Repo
@@ -545,5 +562,5 @@ const currentService = computed(() => services[selectedService.value]);
545
562
  </style>
546
563
 
547
564
  <style scoped>
548
- .is-blue{background-color:#66d1ff;color:#393939}.is-red{background-color:#ff6685;color:#393939}.is-light-yellow{background-color:#f5d58a;color:#393939}.is-yellow{background-color:#ffb70f;color:#393939}.is-pink{background-color:pink;color:#393939}.is-green{background-color:#5be4a6;color:#393939}.is-orange{background-color:#ffb366;color:#393939}.is-teal{background-color:#66d9cc;color:#393939}.is-coral{background-color:#ff8c82;color:#393939}.is-purple{background-color:#ebaeff;border:none;color:#393939}@media screen and (max-width:599px){.text-4xl{font-size:1.5em}}@media screen and (max-width:418px){.text-4xl{display:none}}
565
+ .is-blue{background-color:var(--recruit-blue)}.is-blue,.is-red{color:var(--recruit-text)}.is-red{background-color:var(--recruit-red)}.is-light-yellow{background-color:var(--recruit-yellow-light);color:var(--recruit-text)}.is-yellow{background-color:var(--recruit-yellow)}.is-pink,.is-yellow{color:var(--recruit-text)}.is-pink{background-color:var(--recruit-pink)}.is-green{background-color:var(--recruit-green)}.is-green,.is-orange{color:var(--recruit-text)}.is-orange{background-color:var(--recruit-orange)}.is-teal{background-color:var(--recruit-teal)}.is-coral,.is-teal{color:var(--recruit-text)}.is-coral{background-color:var(--recruit-coral)}.is-purple{background-color:var(--recruit-purple);border:none;color:var(--recruit-text)}@media screen and (max-width:599px){.text-4xl{font-size:1.5em}}@media screen and (max-width:418px){.text-4xl{display:none}}
549
566
  </style>
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
2
  import { Maximize, Moon, RotateCcw, Sun, SunMoon } from "lucide-vue-next";
3
- import { ref, computed } from "vue";
3
+ import { ref, computed, watch } from "vue";
4
4
  import ResponsiveDeviceSelect from "./menu/ResponsiveDeviceSelect.vue";
5
5
  import { useElementSize } from "@vueuse/core";
6
6
  import { devicePresets } from "./devicePresets.js";
@@ -8,7 +8,13 @@ import useAPI from "../../composables/useAPI";
8
8
  import { useSmileColorMode } from "../../composables/useColorMode";
9
9
  const api = useAPI();
10
10
  const fullScreenDiv = ref(null);
11
- const { width: _fullScreenWidth, height: _fullScreenHeight } = useElementSize(fullScreenDiv);
11
+ const { width: fullScreenWidth, height: fullScreenHeight } = useElementSize(fullScreenDiv);
12
+ watch([fullScreenWidth, fullScreenHeight], ([w, h]) => {
13
+ if (api.store.dev.isFullscreen && w > 0 && h > 0) {
14
+ api.store.dev.deviceWidth = Math.round(w);
15
+ api.store.dev.deviceHeight = Math.round(h);
16
+ }
17
+ });
12
18
  const {
13
19
  state: experimentColorMode,
14
20
  mode: experimentColorModeRaw,
@@ -126,7 +132,7 @@ const colorMode = computed(() => {
126
132
  size="xs"
127
133
  @click="toggleRotation"
128
134
  >
129
- <RotateCcw :class="{ 'text-blue-400': api.store.dev.isRotated }" />
135
+ <RotateCcw :class="{ 'text-link-button-foreground': api.store.dev.isRotated }" />
130
136
  </Button>
131
137
  </TooltipTrigger>
132
138
  <TooltipContent side="bottom">
@@ -214,5 +220,5 @@ const colorMode = computed(() => {
214
220
  </template>
215
221
 
216
222
  <style scoped>
217
- .device-container-wrapper,.fullscreen-container{height:100%;position:relative;width:100%}.device-container-wrapper{background-image:linear-gradient(color-mix(in srgb,var(--border) 70%,transparent) 1px,transparent 1px),linear-gradient(90deg,color-mix(in srgb,var(--border) 70%,transparent) 1px,transparent 1px);background-position:-1px 0;background-size:20px 20px;container-type:inline-size;max-width:100%;min-height:100%;overflow-x:auto;overflow-y:auto}.device-content-wrapper{align-items:center;background-color:var(--test-background);display:flex;flex-direction:column;justify-content:flex-start;padding:20px}@container (min-width: 800px){.device-content-wrapper{align-items:center;padding:20px}.device-wrapper{justify-content:center;margin-left:15px}}@container (max-width: 799px){.device-content-wrapper{align-items:flex-start;margin-left:0}.device-wrapper{justify-content:flex-start}.device-info,.device-wrapper{margin-left:15px}}.device-info{background-color:var(--background);border:1px solid var(--border);border-radius:10px;font-family:monospace;font-size:14px;margin-bottom:10px;padding:5px;text-align:center}.device-controls{align-items:center;display:flex;flex-direction:row;gap:8px}.device-controls>*{align-items:center;display:flex;gap:16px}.device-dimensions{color:var(--muted-foreground);font-family:monospace;font-size:.8em}.device-controls :deep(.select-small){font-size:12px!important;height:28px!important;padding:4px 8px!important}.device-controls :deep(.select-small svg){height:12px!important;width:12px!important}.device-wrapper{align-items:center;background-color:var(--background);display:flex;justify-content:flex-start;position:relative}.device-container{border:2px solid var(--border);border-radius:12px;box-shadow:0 6px 20px rgba(0,0,0,.15),0 3px 8px rgba(0,0,0,.1);display:inline-block;height:var(--device-height);overflow-x:auto;overflow-y:auto;width:var(--device-width)}.resize-handle{background:#8f8f8f;border-radius:20px;box-shadow:0 2px 4px rgba(0,0,0,.2);position:absolute;transform-origin:center;transition:all .2s}.resize-handle:hover{background:#90b7e5;transform:scale(1.1)}.resize-handle:active{background:#90b7e5;transform:scale(1.05)}.resize-handle-left{cursor:ew-resize;height:40px;left:-15px;top:50%;transform:translateY(-50%);width:8px}.resize-handle-left:hover{transform:translateY(-50%) scale(1.1)}.resize-handle-left:active{transform:translateY(-50%) scale(1.05)}.resize-handle-right{cursor:ew-resize;height:40px;right:-15px;top:50%;transform:translateY(-50%);width:8px}.resize-handle-right:hover{transform:translateY(-50%) scale(1.1)}.resize-handle-right:active{transform:translateY(-50%) scale(1.05)}.resize-handle-bottom{bottom:-15px;cursor:ns-resize;height:8px;left:50%;transform:translateX(-50%);width:40px}.resize-handle-bottom:hover{transform:translateX(-50%) scale(1.1)}.resize-handle-bottom:active{transform:translateX(-50%) scale(1.05)}
223
+ .device-container-wrapper,.fullscreen-container{height:100%;position:relative;width:100%}.device-container-wrapper{background-image:linear-gradient(color-mix(in srgb,var(--border) 70%,transparent) 1px,transparent 1px),linear-gradient(90deg,color-mix(in srgb,var(--border) 70%,transparent) 1px,transparent 1px);background-position:-1px 0;background-size:20px 20px;container-type:inline-size;max-width:100%;min-height:100%;overflow-x:auto;overflow-y:auto}.device-content-wrapper{align-items:center;background-color:var(--test-background);display:flex;flex-direction:column;justify-content:flex-start;padding:20px}@container (min-width: 800px){.device-content-wrapper{align-items:center;padding:20px}.device-wrapper{justify-content:center;margin-left:15px}}@container (max-width: 799px){.device-content-wrapper{align-items:flex-start;margin-left:0}.device-wrapper{justify-content:flex-start}.device-info,.device-wrapper{margin-left:15px}}.device-info{background-color:var(--background);border:1px solid var(--border);border-radius:10px;font-family:monospace;font-size:14px;margin-bottom:10px;padding:5px;text-align:center}.device-controls{align-items:center;display:flex;flex-direction:row;gap:8px}.device-controls>*{align-items:center;display:flex;gap:16px}.device-dimensions{color:var(--muted-foreground);font-family:monospace;font-size:.8em}.device-controls :deep(.select-small){font-size:12px!important;height:28px!important;padding:4px 8px!important}.device-controls :deep(.select-small svg){height:12px!important;width:12px!important}.device-wrapper{align-items:center;background-color:var(--background);display:flex;justify-content:flex-start;position:relative}.device-container{border:2px solid var(--border);border-radius:12px;box-shadow:0 6px 20px rgba(0,0,0,.15),0 3px 8px rgba(0,0,0,.1);display:inline-block;height:var(--device-height);overflow-x:auto;overflow-y:auto;width:var(--device-width)}.resize-handle{background:var(--resize-handle);border-radius:20px;box-shadow:0 2px 4px rgba(0,0,0,.2);position:absolute;transform-origin:center;transition:all .2s}.resize-handle:hover{background:var(--resize-handle-hover);transform:scale(1.1)}.resize-handle:active{background:var(--resize-handle-hover);transform:scale(1.05)}.resize-handle-left{cursor:ew-resize;height:40px;left:-15px;top:50%;transform:translateY(-50%);width:8px}.resize-handle-left:hover{transform:translateY(-50%) scale(1.1)}.resize-handle-left:active{transform:translateY(-50%) scale(1.05)}.resize-handle-right{cursor:ew-resize;height:40px;right:-15px;top:50%;transform:translateY(-50%);width:8px}.resize-handle-right:hover{transform:translateY(-50%) scale(1.1)}.resize-handle-right:active{transform:translateY(-50%) scale(1.05)}.resize-handle-bottom{bottom:-15px;cursor:ns-resize;height:8px;left:50%;transform:translateX(-50%);width:40px}.resize-handle-bottom:hover{transform:translateX(-50%) scale(1.1)}.resize-handle-bottom:active{transform:translateX(-50%) scale(1.05)}
218
224
  </style>
@@ -79,5 +79,5 @@ onMounted(() => {
79
79
  </template>
80
80
 
81
81
  <style scoped>
82
- .presentation-container{display:flex;flex:1;flex-direction:column;height:100%;width:100%}.toolbar{background-color:var(--dev-bar-bg);height:36px;margin-bottom:auto;margin-top:auto;width:full}.content-wrapper{display:flex;flex:1;overflow:hidden;width:100%}.content-and-console{flex-direction:column;overflow:hidden}.content-and-console,.main-content{display:flex;flex:1;min-height:0;min-width:0}.main-content{flex-direction:column;overflow-x:auto;overflow-y:auto}.loading-container{align-items:center;display:flex;flex-direction:column;height:100%;justify-content:center;min-height:200px}.loading-spinner{animation:spin 1s linear infinite;border:4px solid #f3f3f3;border-radius:50%;border-top-color:#3498db;height:40px;margin-bottom:16px;width:40px}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}
82
+ .presentation-container{display:flex;flex:1;flex-direction:column;height:100%;width:100%}.toolbar{background-color:var(--dev-bar-bg);height:36px;margin-bottom:auto;margin-top:auto;width:full}.content-wrapper{display:flex;flex:1;overflow:hidden;width:100%}.content-and-console{flex-direction:column;overflow:hidden}.content-and-console,.main-content{display:flex;flex:1;min-height:0;min-width:0}.main-content{flex-direction:column;overflow-x:auto;overflow-y:auto}.loading-container{align-items:center;display:flex;flex-direction:column;height:100%;justify-content:center;min-height:200px}.loading-spinner{animation:spin 1s linear infinite;border-top:4px solid var(--spinner-track);border:4px solid var(--spinner-track);border-radius:50%;border-top-color:var(--spinner-active);height:40px;margin-bottom:16px;width:40px}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}
83
83
  </style>
@@ -18,7 +18,7 @@ const data_field = computed(() => {
18
18
  const pieces = props.data.split(".");
19
19
  let view_data = api.all_config;
20
20
  for (let i = 0; i < pieces.length; i++) {
21
- if (view_data[pieces[i]]) {
21
+ if (view_data != null && typeof view_data === "object" && pieces[i] in view_data) {
22
22
  view_data = view_data[pieces[i]];
23
23
  }
24
24
  }
@@ -28,9 +28,10 @@ const data_field = computed(() => {
28
28
  }
29
29
  });
30
30
  function truncateText(text, maxLength = 30) {
31
- if (!text) return text;
32
- if (text.length <= maxLength) return text;
33
- return text.substring(0, maxLength) + "...";
31
+ if (text === null || text === void 0) return text;
32
+ const str = String(text);
33
+ if (str.length <= maxLength) return str;
34
+ return str.substring(0, maxLength) + "...";
34
35
  }
35
36
  function option_selected(option) {
36
37
  emit("selected", option);
@@ -104,5 +105,5 @@ function option_selected(option) {
104
105
  </template>
105
106
 
106
107
  <style scoped>
107
- .overflow-y-auto::-webkit-scrollbar{width:6px}.overflow-y-auto::-webkit-scrollbar-track{background:var(--scrollbar-track);border-radius:3px}.overflow-y-auto::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:3px}.overflow-y-auto::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}.item-key{color:#0baac3;font-weight:400}.primitive-item .item-value{color:#dda814;font-weight:400}.object-item .item-key,.object-item .item-value{color:#0baac3;font-weight:400}.config-list-container .bg-blue-100{background-color:var(--accent)}.config-list-container .text-blue-800{color:var(--accent-foreground)}.config-list-container .border-blue-200{border-color:var(--accent)}
108
+ .overflow-y-auto::-webkit-scrollbar{width:6px}.overflow-y-auto::-webkit-scrollbar-track{background:var(--scrollbar-track);border-radius:3px}.overflow-y-auto::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:3px}.overflow-y-auto::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}.item-key{color:var(--data-key-color);font-weight:400}.primitive-item .item-value{color:var(--data-value-color);font-weight:400}.object-item .item-key,.object-item .item-value{color:var(--data-key-color);font-weight:400}
108
109
  </style>
@@ -51,6 +51,22 @@ function panel2_select(option) {
51
51
  save_path();
52
52
  }
53
53
  function panel3_select(option) {
54
+ const currentPath = panel_path(0);
55
+ if (currentPath) {
56
+ const pieces = currentPath.split(".");
57
+ let view_data = api.all_config;
58
+ for (let i = 0; i < pieces.length; i++) {
59
+ if (view_data != null && typeof view_data === "object" && pieces[i] in view_data) {
60
+ view_data = view_data[pieces[i]];
61
+ }
62
+ }
63
+ if (view_data != null && typeof view_data === "object" && option in view_data) {
64
+ const child = view_data[option];
65
+ if (child == null || typeof child !== "object") {
66
+ return;
67
+ }
68
+ }
69
+ }
54
70
  browse_panels.path.push(String(option));
55
71
  save_path();
56
72
  }
@@ -105,7 +121,7 @@ function _resetDevState() {
105
121
  Read more about configuration options
106
122
  <a
107
123
  href="https://smile.gureckislab.org/configuration.html"
108
- class="text-blue-600 underline ml-1"
124
+ class="text-link-button-foreground underline ml-1"
109
125
  >in the docs</a>.
110
126
  </div>
111
127
  </div>
@@ -32,13 +32,13 @@ function getBgClass(msg) {
32
32
  case "log":
33
33
  return "bg-background";
34
34
  case "warn":
35
- return "bg-yellow-100";
35
+ return "log-bg-warn";
36
36
  case "error":
37
- return "bg-red-100";
37
+ return "log-bg-error";
38
38
  case "debug":
39
39
  return "bg-muted text-muted-foreground";
40
40
  case "success":
41
- return "bg-green-100";
41
+ return "log-bg-success";
42
42
  default:
43
43
  return "bg-background";
44
44
  }
@@ -268,5 +268,5 @@ function getBgClass(msg) {
268
268
  </template>
269
269
 
270
270
  <style scoped>
271
- .overflow-y-scroll::-webkit-scrollbar{width:6px}.overflow-y-scroll::-webkit-scrollbar-track{background:var(--scrollbar-track);border-radius:3px}.overflow-y-scroll::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:3px}.overflow-y-scroll::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}.bg-background{background-color:var(--background)}.bg-muted{background-color:var(--muted)}.text-muted-foreground{color:var(--muted-foreground)}.border-dev-lines{border-color:var(--dev-lines)}.bg-yellow-100{background-color:var(--log-yellow)}.bg-red-100{background-color:var(--log-red)}.bg-green-100{background-color:var(--log-green)}
271
+ .overflow-y-scroll::-webkit-scrollbar{width:6px}.overflow-y-scroll::-webkit-scrollbar-track{background:var(--scrollbar-track);border-radius:3px}.overflow-y-scroll::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:3px}.overflow-y-scroll::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}.bg-background{background-color:var(--background)}.bg-muted{background-color:var(--muted)}.text-muted-foreground{color:var(--muted-foreground)}.border-dev-lines{border-color:var(--dev-lines)}.log-bg-warn{background-color:var(--log-yellow)}.log-bg-error{background-color:var(--log-red)}.log-bg-success{background-color:var(--log-green)}
272
272
  </style>
@@ -108,5 +108,5 @@ function option_selected(option) {
108
108
  </template>
109
109
 
110
110
  <style scoped>
111
- .overflow-y-auto::-webkit-scrollbar{width:6px}.overflow-y-auto::-webkit-scrollbar-track{background:var(--scrollbar-track);border-radius:3px}.overflow-y-auto::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:3px}.overflow-y-auto::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}.item-key{color:#0baac3;font-weight:400}.primitive-item .item-value{color:#dda814;font-weight:400}.object-item .item-key,.object-item .item-value{color:#0baac3;font-weight:400}.database-list-container .bg-blue-100{background-color:var(--accent)}.database-list-container .text-blue-800{color:var(--accent-foreground)}.database-list-container .border-blue-200{border-color:var(--accent)}
111
+ .overflow-y-auto::-webkit-scrollbar{width:6px}.overflow-y-auto::-webkit-scrollbar-track{background:var(--scrollbar-track);border-radius:3px}.overflow-y-auto::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:3px}.overflow-y-auto::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}.item-key{color:var(--data-key-color);font-weight:400}.primitive-item .item-value{color:var(--data-value-color);font-weight:400}.object-item .item-key,.object-item .item-value{color:var(--data-key-color);font-weight:400}
112
112
  </style>
@@ -154,7 +154,7 @@ function resetDevState() {
154
154
  size="xs"
155
155
  @click="toggleRotation"
156
156
  >
157
- <RotateCcw :class="{ 'text-blue-400': api.store.dev.isRotated }" />
157
+ <RotateCcw :class="{ 'text-link-button-foreground': api.store.dev.isRotated }" />
158
158
  </Button>
159
159
  </TooltipTrigger>
160
160
  <TooltipContent side="bottom">
@@ -183,5 +183,5 @@ const isDarkMode = computed({
183
183
  </template>
184
184
 
185
185
  <style scoped>
186
- .mainbutton{background-color:#afdaec}.dark .mainbutton{background-color:#c0f0a3;color:#000}
186
+ .mainbutton{background-color:var(--dev-brand-bg);color:var(--dev-brand-text)}
187
187
  </style>
@@ -58,8 +58,8 @@ const database_tooltip = computed(() => {
58
58
  :percentage="Math.round(api.store.localState.approxDataSize / 1048576) * 100"
59
59
  :size="12"
60
60
  :stroke-width="40"
61
- slicecolor="#aaa"
62
- basecolor="#aaa"
61
+ slicecolor="var(--muted-foreground)"
62
+ basecolor="var(--muted-foreground)"
63
63
  />
64
64
  </div>
65
65
  </template>
@@ -54,5 +54,5 @@ const _props = defineProps({
54
54
  </template>
55
55
 
56
56
  <style scoped>
57
- .notification{background:var(--background);border:1px solid var(--border);border-radius:8px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06),0 0 0 1px rgba(0,0,0,.05);left:60px;max-width:300px;overflow:hidden;position:fixed;top:50px;z-index:9999}.error-notification{background:#fef2f2;border:1px solid #fecaca}.notification-content{background:linear-gradient(135deg,var(--background) 0,var(--muted) 100%);padding:12px 16px}.error-content{background:linear-gradient(135deg,#fef2f2,#fee2e2)}.command-badge{align-items:center;display:flex;margin-bottom:8px}.command-key{background:var(--primary);border:1px solid var(--primary);border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.1);color:var(--primary-foreground);font-family:SF Mono,Monaco,Cascadia Code,Roboto Mono,Consolas,Courier New,monospace;font-size:.875rem;font-weight:600;letter-spacing:.025em;padding:4px 8px}.error-key{background:#ef4444;border:1px solid #ef4444;color:#fef2f2}.action-text{color:var(--foreground);font-size:.875rem;font-weight:500;line-height:1.4}.error-text{color:#ef4444}.notification-arrow{background:var(--background);border-left:1px solid var(--border);border-top:1px solid var(--border);height:12px;left:20px;position:absolute;top:-6px;transform:rotate(45deg);width:12px}.error-arrow{background:#fef2f2;border-left:1px solid #fecaca;border-top:1px solid #fecaca}.slide-up-enter-active,.slide-up-leave-active{transition:all .3s cubic-bezier(.4,0,.2,1)}.slide-up-enter-from,.slide-up-leave-to{opacity:0;transform:translateX(-100%) translateY(-10px)}@media (prefers-color-scheme:dark){.notification{box-shadow:0 4px 6px -1px rgba(0,0,0,.3),0 2px 4px -1px rgba(0,0,0,.2),0 0 0 1px hsla(0,0%,100%,.1)}}
57
+ .notification{background:var(--background);border:1px solid var(--border);border-radius:8px;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06),0 0 0 1px rgba(0,0,0,.05);left:60px;max-width:300px;overflow:hidden;position:fixed;top:50px;z-index:9999}.error-notification{background:var(--error-notif-bg);border:1px solid var(--error-notif-border)}.notification-content{background:linear-gradient(135deg,var(--background) 0,var(--muted) 100%);padding:12px 16px}.error-content{background:linear-gradient(135deg,var(--error-notif-bg) 0,var(--error-notif-bg-end) 100%)}.command-badge{align-items:center;display:flex;margin-bottom:8px}.command-key{background:var(--primary);border:1px solid var(--primary);border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.1);color:var(--primary-foreground);font-family:SF Mono,Monaco,Cascadia Code,Roboto Mono,Consolas,Courier New,monospace;font-size:.875rem;font-weight:600;letter-spacing:.025em;padding:4px 8px}.error-key{background:var(--error-notif-text);border:1px solid var(--error-notif-text);color:var(--error-notif-bg)}.action-text{color:var(--foreground);font-size:.875rem;font-weight:500;line-height:1.4}.error-text{color:var(--error-notif-text)}.notification-arrow{background:var(--background);border-left:1px solid var(--border);border-top:1px solid var(--border);height:12px;left:20px;position:absolute;top:-6px;transform:rotate(45deg);width:12px}.error-arrow{background:var(--error-notif-bg);border-left:1px solid var(--error-notif-border);border-top:1px solid var(--error-notif-border)}.slide-up-enter-active,.slide-up-leave-active{transition:all .3s cubic-bezier(.4,0,.2,1)}.slide-up-enter-from,.slide-up-leave-to{opacity:0;transform:translateX(-100%) translateY(-10px)}@media (prefers-color-scheme:dark){.notification{box-shadow:0 4px 6px -1px rgba(0,0,0,.3),0 2px 4px -1px rgba(0,0,0,.2),0 0 0 1px hsla(0,0%,100%,.1)}}
58
58
  </style>
@@ -165,7 +165,7 @@ onKeyDown((e) => {
165
165
  <!-- Middle section - Centered content area -->
166
166
  <div class="flex items-center justify-center flex-1 min-w-0 px-2 py-1 rounded">
167
167
  <div class="flex items-center space-x-2">
168
- <div class="text-xs text-gray-600">
168
+ <div class="text-xs text-muted-foreground">
169
169
  <!-- Middle content placeholder -->
170
170
  </div>
171
171
  </div>
@@ -173,7 +173,7 @@ onKeyDown((e) => {
173
173
 
174
174
  <!-- Right section - Control buttons -->
175
175
  <div class="flex items-center flex-shrink-0 px-1 py-1 rounded">
176
- <div class="flex items-center space-x-2.5 border-gray-300 pl-4 rounded-l">
176
+ <div class="flex items-center space-x-2.5 border-border pl-4 rounded-l">
177
177
  <!-- Logout button (only when authenticated) -->
178
178
  <div class="flex items-center">
179
179
  <LogoutButton />
@@ -35,7 +35,7 @@ defineProps({
35
35
  <div class="flex items-center justify-center flex-1 min-w-0 px-2 py-1 rounded">
36
36
  <div class="flex items-center space-x-2">
37
37
  <!-- Middle content placeholder -->
38
- <div class="text-xs text-gray-600">
38
+ <div class="text-xs text-muted-foreground">
39
39
  <!-- Middle content placeholder -->
40
40
  </div>
41
41
  </div>
@@ -43,7 +43,7 @@ defineProps({
43
43
 
44
44
  <!-- Right section - Control buttons -->
45
45
  <div class="flex items-center flex-shrink-0 px-1 py-1 rounded">
46
- <div class="flex items-center space-x-2.5 border-gray-300 pl-4 rounded-l">
46
+ <div class="flex items-center space-x-2.5 border-border pl-4 rounded-l">
47
47
  <!-- Logout button (only when authenticated) -->
48
48
  <div class="flex items-center">
49
49
  <LogoutButton />