tramway 3.1.2.8 → 4.0.1.1
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.
- checksums.yaml +4 -4
- data/README.md +134 -18
- data/app/assets/builds/tailwind.css +2 -0
- data/app/assets/javascripts/tramway/tramway.js +202 -1
- data/app/assets/stylesheets/font-awesome.css +2337 -0
- data/app/assets/tailwind/application.css +29 -0
- data/app/components/tramway/chat_component.html.haml +1 -1
- data/app/components/tramway/containers/main_component.rb +1 -3
- data/app/components/tramway/containers/narrow_component.rb +5 -4
- data/app/components/tramway/form/builder.rb +1 -2
- data/app/components/tramway/grid/card_component.html.haml +2 -0
- data/app/components/tramway/grid/card_component.rb +37 -0
- data/app/components/tramway/grid_component.html.haml +18 -0
- data/app/components/tramway/grid_component.rb +76 -0
- data/app/components/tramway/native_text_component.rb +1 -2
- data/app/components/tramway/nav/item_component.rb +4 -2
- data/app/components/tramway/navbar_component.html.haml +131 -102
- data/app/components/tramway/navbar_component.rb +84 -2
- data/app/components/tramway/table/cell_component.rb +9 -1
- data/app/components/tramway/table/header_component.rb +23 -2
- data/app/components/tramway/table/row_component.rb +27 -10
- data/app/components/tramway/table_component.rb +28 -1
- data/app/controllers/tramway/entities_controller.rb +13 -6
- data/app/views/tramway/entities/_list.html.haml +2 -1
- data/app/views/tramway/layouts/application.html.haml +8 -6
- data/config/routes.rb +1 -2
- data/config/tailwind.config.js +57 -0
- data/lib/generators/tramway/install/font_awesome_helpers.rb +39 -0
- data/lib/generators/tramway/install/install_generator.rb +133 -66
- data/lib/generators/tramway/install/layout_helpers.rb +95 -0
- data/lib/tramway/configs/entities/page.rb +1 -0
- data/lib/tramway/engine.rb +12 -0
- data/lib/tramway/errors.rb +45 -0
- data/lib/tramway/forms/validations.rb +1 -2
- data/lib/tramway/helpers/navbar_helper.rb +3 -1
- data/lib/tramway/helpers/table_helper.rb +27 -0
- data/lib/tramway/helpers/views_helper.rb +26 -2
- data/lib/tramway/pg_searchable.rb +62 -0
- data/lib/tramway/searchable.rb +8 -26
- data/lib/tramway/version.rb +1 -1
- metadata +44 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c7d9ecf679835522a59184b0ffdbc6edf5cfa66aa5f1126429828497b3115e12
|
|
4
|
+
data.tar.gz: efa55f1a6971b64000f41f387b6a85b7d41756b4c5240c35ce3f283f6f878f4c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67ac2fd8508f6a2d172638f3ac7f808721e5d4c1aa24f13e247238196c1fc90cdc5ddc1e0f7d5aa41cf13c1e47d0fa0d118d807f073afa1372ebc83b9b0f873b
|
|
7
|
+
data.tar.gz: 2bd0d71dbade50601a643f2c6c3567b00440d2104db60f53fc0f1a3937c73c77fde8509a8cddea9b68c908dfe92214c036e6b14bee02919c9a1a0b2f4ef5b1be
|
data/README.md
CHANGED
|
@@ -20,6 +20,7 @@ Codex instruction that points agents to the Tramway skill for Tramway-native cod
|
|
|
20
20
|
* [Tramway Tooltip](https://github.com/Purple-Magic/tramway#tramway-tooltip)
|
|
21
21
|
* [Tramway Chat](https://github.com/Purple-Magic/tramway#tramway-chat)
|
|
22
22
|
* [Tramway Table Component](https://github.com/Purple-Magic/tramway#tramway-table-component)
|
|
23
|
+
* [Tramway Grid](https://github.com/Purple-Magic/tramway#tramway-grid)
|
|
23
24
|
* [Tailwind-styled forms](https://github.com/Purple-Magic/tramway#tailwind-styled-forms)
|
|
24
25
|
* [Stimulus-based inputs](https://github.com/Purple-Magic/tramway#stimulus-based-inputs)
|
|
25
26
|
* [Tailwind-styled pagination](https://github.com/Purple-Magic/tramway?tab=readme-ov-file#tailwind-styled-pagination-for-kaminari)
|
|
@@ -29,14 +30,14 @@ Codex instruction that points agents to the Tramway skill for Tramway-native cod
|
|
|
29
30
|
|
|
30
31
|
Tramway is actively verified against the following Ruby and Rails versions.
|
|
31
32
|
|
|
32
|
-
| Ruby \ Rails |
|
|
33
|
-
| ------------- |
|
|
34
|
-
| 3.2 | ✅
|
|
35
|
-
| 3.3 | ✅
|
|
36
|
-
| 3.4 | ✅
|
|
37
|
-
| 4.0 | ✅
|
|
33
|
+
| Ruby \ Rails | 8.0 | 8.1 |
|
|
34
|
+
| ------------- | --- | --- |
|
|
35
|
+
| 3.2 | ✅ | ✅ |
|
|
36
|
+
| 3.3 | ✅ | ✅ |
|
|
37
|
+
| 3.4 | ✅ | ✅ |
|
|
38
|
+
| 4.0 | ✅ | ✅ |
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
Rails 7 is no longer supported. See the [announcement](https://rubyonrails.org/2025/10/29/new-rails-releases-and-end-of-support-announcement) for details.
|
|
40
41
|
|
|
41
42
|
## Installation
|
|
42
43
|
Add this line to your application's Gemfile:
|
|
@@ -218,8 +219,47 @@ Tramway.configure do |config|
|
|
|
218
219
|
end
|
|
219
220
|
```
|
|
220
221
|
|
|
221
|
-
When search is enabled,
|
|
222
|
-
|
|
222
|
+
When search is enabled, a text input and a search icon button appear at the top of the index page. Tramway uses
|
|
223
|
+
`Model.search(query)` if you define it (e.g. a `pg_search_scope` or any custom scope named `search`). If not, it falls back
|
|
224
|
+
to `Model.tramway_search(query)`, which searches across all string/text columns of the model using the [`pg_search`](https://github.com/Casecommons/pg_search)
|
|
225
|
+
gem, and logs a warning. The fallback is generic and not tailored to your data structure, so it is not intended for
|
|
226
|
+
long-term use and may be slow or not scalable.
|
|
227
|
+
|
|
228
|
+
`pg_search` requires PostgreSQL's full-text search functions. If your application's database is not PostgreSQL, calling
|
|
229
|
+
the fallback raises `Tramway::Errors::UnsupportedDatabaseAdapterError` with an explanation and a suggestion to define your
|
|
230
|
+
own `Model.search(query)` scope instead.
|
|
231
|
+
|
|
232
|
+
`bin/rails g tramway:install` adds `gem "pg_search"` to your Gemfile automatically (like Tramway's other dependencies), so
|
|
233
|
+
run it (and `bundle install`) if you see the fallback raise `Tramway::Errors::MissingGemError`. That error also fires if
|
|
234
|
+
`pg_search` can't otherwise be loaded, and explains how to fix it or define your own `Model.search(query)` scope.
|
|
235
|
+
|
|
236
|
+
**includes**
|
|
237
|
+
|
|
238
|
+
If a decorator's `index_attributes` reads an association (e.g. `object.comments.map(&:text)`), each row triggers its own
|
|
239
|
+
query unless the association is eager-loaded. Set `includes` on the `:index` page entry to preload it, using the same
|
|
240
|
+
shape Rails' `.includes` accepts (a symbol, array, or nested hash):
|
|
241
|
+
|
|
242
|
+
*config/initializers/tramway.rb*
|
|
243
|
+
```ruby
|
|
244
|
+
Tramway.configure do |config|
|
|
245
|
+
config.entities = [
|
|
246
|
+
{
|
|
247
|
+
name: :campaign,
|
|
248
|
+
namespace: :admin,
|
|
249
|
+
pages: [
|
|
250
|
+
{
|
|
251
|
+
action: :index,
|
|
252
|
+
includes: [:user, :comments]
|
|
253
|
+
}
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
end
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
In this example, Tramway calls `Campaign.includes(:user, :comments)` before applying `scope`/`search`, so accessing
|
|
261
|
+
`campaign.user` or `campaign.comments` in the decorator's `index_attributes` does not issue an extra query per row.
|
|
262
|
+
Omitting `includes` produces identical behavior to before this option existed.
|
|
223
263
|
|
|
224
264
|
**show page**
|
|
225
265
|
|
|
@@ -920,10 +960,54 @@ background:
|
|
|
920
960
|
with_entities: Show Tramway CRUD index page links to navbar. Default: true
|
|
921
961
|
```
|
|
922
962
|
|
|
963
|
+
When you use the default vertical navbar layout, it renders as a fixed left sidebar on desktop. The icon-only
|
|
964
|
+
sidebar toggle collapses the whole sidebar and keeps the main content offset in sync, so the page shifts between
|
|
965
|
+
`md:pl-72` and `md:pl-24` as the sidebar changes width.
|
|
966
|
+
|
|
967
|
+
The collapse/expand motion is tuned per element rather than a blanket `transition-all`: the sidebar width and the
|
|
968
|
+
main content's `padding-left` each transition only that one property (`transition-[width]` /
|
|
969
|
+
`transition-[padding-left]`) over 300ms on the `ease-in-out-strong` curve (`cubic-bezier(0.77, 0, 0.175, 1)`,
|
|
970
|
+
registered in `config/tailwind.config.js`), since both are on-screen movement. The header and nav-item list fade
|
|
971
|
+
with `transition-opacity` on the `ease-out-strong` curve (`cubic-bezier(0.23, 1, 0.32, 1)`), matching that they're
|
|
972
|
+
entering/exiting rather than moving. The toggle chevron no longer swaps between `fa-chevron-left` and
|
|
973
|
+
`fa-chevron-right` — it stays a single `fa-chevron-left` icon that rotates 180° (`transition-transform duration-200
|
|
974
|
+
ease-in-out-strong`) to indicate state instead of snapping instantly. All of the above respect
|
|
975
|
+
`prefers-reduced-motion` via `motion-reduce:duration-0` on the movement/rotation transitions; the opacity fade is
|
|
976
|
+
left intact since it aids comprehension without moving anything.
|
|
977
|
+
|
|
978
|
+
Tramway now ships with Font Awesome loaded through the engine, and the vertical navbar collapse control uses a
|
|
979
|
+
single `fa-chevron-left` icon for both states (rotated when collapsed).
|
|
980
|
+
Tramway ships a plain `font-awesome.css` asset for Propshaft compatibility and adds Font Awesome's font
|
|
981
|
+
directory to the host app asset load path, so `stylesheet_link_tag "font-awesome"` resolves without extra
|
|
982
|
+
app-side asset configuration.
|
|
983
|
+
On mobile, `tramway_navbar` always renders the same top navbar regardless of `direction:`. Desktop `:horizontal`
|
|
984
|
+
renders the classic top navbar, and desktop `:vertical` renders the collapsible sidebar.
|
|
985
|
+
When `tramway_navbar` renders before `tramway_main_container`, the main container helper keeps the vertical
|
|
986
|
+
sidebar offset in sync automatically and removes it for horizontal pages.
|
|
987
|
+
|
|
988
|
+
The collapsed/expanded state of the vertical sidebar persists across page navigations. The Stimulus controller
|
|
989
|
+
saves the state to the browser's `localStorage` (key `tramway-navbar-expanded`) whenever the toggle button is
|
|
990
|
+
used, and reads it back on every page load, so collapsing the sidebar on one page keeps it collapsed everywhere
|
|
991
|
+
else until it's expanded again. This is per-browser state: it isn't synced across devices or persisted server-side,
|
|
992
|
+
and if `localStorage` is unavailable (e.g. private browsing with storage blocked), the sidebar simply falls back
|
|
993
|
+
to always starting expanded.
|
|
994
|
+
|
|
995
|
+
Since the server always renders the sidebar expanded (it has no way to know the browser's stored preference) and
|
|
996
|
+
the Stimulus controller only applies the collapsed classes once it connects, `tramway_navbar` also renders a small
|
|
997
|
+
blocking inline `<script>` alongside its markup. It reads `localStorage` synchronously, before the browser paints,
|
|
998
|
+
and — if the sidebar was left collapsed — sets a `data-tramway-navbar-expanded="false"` attribute on `<html>`. A
|
|
999
|
+
matching `@media (min-width: 768px)` CSS block (rendered alongside the navbar) keys off that attribute to apply the
|
|
1000
|
+
collapsed width, main-container padding, and faded header/menu immediately, before any JavaScript module has had a
|
|
1001
|
+
chance to load and run. This is what prevents the sidebar from flashing expanded and then snapping to collapsed on
|
|
1002
|
+
page load. The Stimulus controller keeps that `<html>` attribute in sync afterwards (on load and on every toggle),
|
|
1003
|
+
so this pre-paint styling and the controller's own classes never fight each other.
|
|
1004
|
+
|
|
923
1005
|
**NOTE:** `tramway_navbar` method called without arguments and block of code will render only [Tramway CRUD](https://github.com/Purple-Magic/tramway#tramway-crud) links on the left.
|
|
924
1006
|
|
|
925
1007
|
In case you want to hide entity links you can pass `with_entities: false`.
|
|
926
1008
|
|
|
1009
|
+
The default `:vertical` navbar now renders as a fixed left sidebar on desktop screens. If your page content uses a full-width layout, wrap it with `tramway_main_container class: 'md:pl-72'` so the sidebar has room.
|
|
1010
|
+
|
|
927
1011
|
```erb
|
|
928
1012
|
<% if current_user.present? %>
|
|
929
1013
|
<%= tramway_navbar title: 'WaiWai' do |nav| %>
|
|
@@ -1134,17 +1218,23 @@ implementations.
|
|
|
1134
1218
|
<% end %>
|
|
1135
1219
|
```
|
|
1136
1220
|
|
|
1137
|
-
`tramway_table` accepts the same
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1221
|
+
`tramway_table` accepts an optional `size:` argument plus the same `options` hash as `Tramway::TableComponent`. Supported
|
|
1222
|
+
sizes are `:small`, `:medium`, and `:large`. The default is `:medium`, and it renders exactly like the current table
|
|
1223
|
+
implementation. The `options` hash is forwarded as HTML attributes, so you can pass things like `id`, `data` attributes, or
|
|
1224
|
+
additional classes. The table itself carries the default dark background (`bg-zinc-950`), so you can override it directly
|
|
1225
|
+
with a utility like `class: '!bg-red-500'`. If you do not supply your own width utility (e.g. a class that starts with `w-`),
|
|
1226
|
+
the component automatically appends `w-full` to keep the table responsive.
|
|
1227
|
+
|
|
1228
|
+
`tramway_row`, `tramway_header`, and `tramway_cell` automatically follow the active table size, so a single `size:` on
|
|
1229
|
+
`tramway_table` controls the whole table.
|
|
1141
1230
|
|
|
1142
|
-
Use the optional `href:` argument on `tramway_row` to turn an entire row into a link. Linked rows
|
|
1143
|
-
(`cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700`) to indicate
|
|
1231
|
+
Use the optional `href:` argument on `tramway_row` to turn an entire row into a link. Linked rows keep their transparent
|
|
1232
|
+
background and gain pointer and hover styles (`cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700`) to indicate
|
|
1233
|
+
interactivity.
|
|
1144
1234
|
|
|
1145
1235
|
```erb
|
|
1146
|
-
<%= tramway_table class: 'max-w-3xl border border-gray-200', data: { controller: 'table' } do %>
|
|
1147
|
-
<%= tramway_header
|
|
1236
|
+
<%= tramway_table size: :large, class: 'max-w-3xl border border-gray-200', data: { controller: 'table' } do %>
|
|
1237
|
+
<%= tramway_header headers: ['Name', 'Email'] %>
|
|
1148
1238
|
|
|
1149
1239
|
<%= tramway_row href: user_path(user) do %>
|
|
1150
1240
|
<%= tramway_cell do %>
|
|
@@ -1171,6 +1261,32 @@ details panel. Pass `preview: false` when you want a row without the preview pan
|
|
|
1171
1261
|
<% end %>
|
|
1172
1262
|
```
|
|
1173
1263
|
|
|
1264
|
+
### Tramway Grid
|
|
1265
|
+
|
|
1266
|
+
Use `tramway_grid` when you want dashboard-style layouts with cards that span multiple rows and columns while keeping the
|
|
1267
|
+
existing Tramway dark zinc styling. The grid accepts `rows:` and `columns:`, and it also accepts `outline: true` when you
|
|
1268
|
+
want to show separators between cells. Those separators are drawn in the gap between grid cells, so they stay centered
|
|
1269
|
+
when cards have content. Cards use `size: [rows, columns]`, with responsive spans on desktop and stacked cards on smaller
|
|
1270
|
+
screens. Each grid cell is rendered as a real `20em x 20em` box, and the grid always builds the exact `rows x columns`
|
|
1271
|
+
matrix even when the slot is empty. Cards participate in the same grid, so `size: [1, 1]` occupies exactly one cell.
|
|
1272
|
+
|
|
1273
|
+
```haml
|
|
1274
|
+
= tramway_grid rows: 8, columns: 8, outline: true do
|
|
1275
|
+
= tramway_card size: [1, 2] do
|
|
1276
|
+
.text-sm.font-medium.text-zinc-400 Revenue
|
|
1277
|
+
.mt-2.text-3xl.font-semibold.text-zinc-50 $128,240
|
|
1278
|
+
|
|
1279
|
+
= tramway_card size: [2, 3] do
|
|
1280
|
+
.text-sm.font-medium.text-zinc-400 Activity
|
|
1281
|
+
.mt-4.space-y-2
|
|
1282
|
+
.flex.items-center.justify-between
|
|
1283
|
+
%span.text-zinc-300 Orders
|
|
1284
|
+
%span.text-zinc-50 48
|
|
1285
|
+
.flex.items-center.justify-between
|
|
1286
|
+
%span.text-zinc-300 Refunds
|
|
1287
|
+
%span.text-zinc-50 3
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1174
1290
|
`tramway_cell` also forwards any HTML options to the underlying cell wrapper. Use this when you need per-cell classes,
|
|
1175
1291
|
data attributes, ids, or other attributes:
|
|
1176
1292
|
|
|
@@ -1598,7 +1714,7 @@ end
|
|
|
1598
1714
|
|
|
1599
1715
|
## Contributing
|
|
1600
1716
|
|
|
1601
|
-
|
|
1717
|
+
`lefthook` is pinned in [.tool-versions](/home/kalashnikovisme/projects/tramway/.tool-versions) so `mise` can resolve the shim used by `make install`.
|
|
1602
1718
|
|
|
1603
1719
|
```
|
|
1604
1720
|
make install
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-400:oklch(75% .183 55.934);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-400:oklch(79.2% .209 151.711);--color-green-600:oklch(62.7% .194 149.214);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-teal-100:oklch(95.3% .051 180.801);--color-teal-200:oklch(91% .096 180.426);--color-teal-700:oklch(51.1% .096 186.391);--color-teal-800:oklch(43.7% .078 188.216);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-zinc-50:oklch(98.5% 0 none);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-lg:32rem;--container-3xl:48rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--leading-tight:1.25;--leading-snug:1.375;--radius-sm:.25rem;--radius-md:.375rem;--radius-xl:.75rem;--radius-2xl:1rem;--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}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;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{inset-inline:0}.inset-y-0{inset-block:0}.top-0{top:0}.top-4{top:calc(var(--spacing) * 4)}.right-0{right:0}.right-4{right:calc(var(--spacing) * 4)}.right-6{right:calc(var(--spacing) * 6)}.bottom-0{bottom:0}.bottom-full{bottom:100%}.left-0{left:0}.left-1\/2{left:50%}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-1{margin:var(--spacing)}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.my-2{margin-block:calc(var(--spacing) * 2)}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-8{margin-top:calc(var(--spacing) * 8)}.mt-10{margin-top:calc(var(--spacing) * 10)}.mt-auto{margin-top:auto}.mb-0{margin-bottom:0}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-4{margin-left:calc(var(--spacing) * 4)}.box-border{box-sizing:border-box}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-1\/2{height:50%}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-15{height:calc(var(--spacing) * 15)}.h-dvh{height:100dvh}.h-fit{height:fit-content}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-full{max-height:100%}.min-h-0{min-height:0}.min-h-2{min-height:calc(var(--spacing) * 2)}.min-h-4{min-height:calc(var(--spacing) * 4)}.min-h-6{min-height:calc(var(--spacing) * 6)}.min-h-8{min-height:calc(var(--spacing) * 8)}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-12{min-height:calc(var(--spacing) * 12)}.min-h-15{min-height:calc(var(--spacing) * 15)}.min-h-40{min-height:calc(var(--spacing) * 40)}.min-h-60{min-height:calc(var(--spacing) * 60)}.min-h-dvh{min-height:100dvh}.w-3{width:calc(var(--spacing) * 3)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-72{width:calc(var(--spacing) * 72)}.w-fit{width:fit-content}.w-full{width:100%}.w-max{width:max-content}.w-px{width:1px}.w-screen{width:100vw}.max-w-3xl{max-width:var(--container-3xl)}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.max-w-none{max-width:none}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:0}.min-w-2{min-width:calc(var(--spacing) * 2)}.min-w-4{min-width:calc(var(--spacing) * 4)}.min-w-6{min-width:calc(var(--spacing) * 6)}.min-w-40{min-width:calc(var(--spacing) * 40)}.min-w-96{min-width:calc(var(--spacing) * 96)}.flex-1{flex:1}.flex-auto{flex:auto}.flex-initial{flex:0 auto}.flex-none{flex:none}.shrink-0{flex-shrink:0}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.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))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.grid-cols-9{grid-template-columns:repeat(9,minmax(0,1fr))}.grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-1{column-gap:var(--spacing)}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-t-xl{border-top-left-radius:var(--radius-xl);border-top-right-radius:var(--radius-xl)}.rounded-tl-md{border-top-left-radius:var(--radius-md)}.rounded-tr-md{border-top-right-radius:var(--radius-md)}.rounded-b-none{border-bottom-right-radius:0;border-bottom-left-radius:0}.rounded-b-xl{border-bottom-right-radius:var(--radius-xl);border-bottom-left-radius:var(--radius-xl)}.rounded-bl-2xl{border-bottom-left-radius:var(--radius-2xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dotted{--tw-border-style:dotted;border-style:dotted}.border-blue-800{border-color:var(--color-blue-800)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-800{border-color:var(--color-gray-800)}.border-green-800{border-color:var(--color-green-800)}.border-indigo-800{border-color:var(--color-indigo-800)}.border-orange-800{border-color:var(--color-orange-800)}.border-red-600{border-color:var(--color-red-600)}.border-red-800{border-color:var(--color-red-800)}.border-teal-200{border-color:var(--color-teal-200)}.border-teal-700{border-color:var(--color-teal-700)}.border-transparent{border-color:#0000}.border-violet-800{border-color:var(--color-violet-800)}.border-white{border-color:var(--color-white)}.border-yellow-800{border-color:var(--color-yellow-800)}.border-zinc-50{border-color:var(--color-zinc-50)}.border-zinc-100\/60{border-color:#f4f4f599}@supports (color:color-mix(in lab, red, red)){.border-zinc-100\/60{border-color:color-mix(in oklab, var(--color-zinc-100) 60%, transparent)}}.border-zinc-700{border-color:var(--color-zinc-700)}.border-zinc-700\/50{border-color:#3f3f4680}@supports (color:color-mix(in lab, red, red)){.border-zinc-700\/50{border-color:color-mix(in oklab, var(--color-zinc-700) 50%, transparent)}}.border-zinc-800{border-color:var(--color-zinc-800)}.\!bg-red-500{background-color:var(--color-red-500)!important}.\!bg-zinc-950{background-color:var(--color-zinc-950)!important}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-blue-900\/30{background-color:#1c398e4d}@supports (color:color-mix(in lab, red, red)){.bg-blue-900\/30{background-color:color-mix(in oklab, var(--color-blue-900) 30%, transparent)}}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-800\/60{background-color:#1e293999}@supports (color:color-mix(in lab, red, red)){.bg-gray-800\/60{background-color:color-mix(in oklab, var(--color-gray-800) 60%, transparent)}}.bg-gray-900{background-color:var(--color-gray-900)}.bg-gray-900\/30{background-color:#1018284d}@supports (color:color-mix(in lab, red, red)){.bg-gray-900\/30{background-color:color-mix(in oklab, var(--color-gray-900) 30%, transparent)}}.bg-green-600{background-color:var(--color-green-600)}.bg-green-900\/30{background-color:#0d542b4d}@supports (color:color-mix(in lab, red, red)){.bg-green-900\/30{background-color:color-mix(in oklab, var(--color-green-900) 30%, transparent)}}.bg-indigo-900\/30{background-color:#312c854d}@supports (color:color-mix(in lab, red, red)){.bg-indigo-900\/30{background-color:color-mix(in oklab, var(--color-indigo-900) 30%, transparent)}}.bg-orange-400{background-color:var(--color-orange-400)}.bg-orange-900\/30{background-color:#7e2a0c4d}@supports (color:color-mix(in lab, red, red)){.bg-orange-900\/30{background-color:color-mix(in oklab, var(--color-orange-900) 30%, transparent)}}.bg-red-100{background-color:var(--color-red-100)}.bg-red-900\/30{background-color:#82181a4d}@supports (color:color-mix(in lab, red, red)){.bg-red-900\/30{background-color:color-mix(in oklab, var(--color-red-900) 30%, transparent)}}.bg-teal-100{background-color:var(--color-teal-100)}.bg-transparent{background-color:#0000}.bg-violet-900\/30{background-color:#4d179a4d}@supports (color:color-mix(in lab, red, red)){.bg-violet-900\/30{background-color:color-mix(in oklab, var(--color-violet-900) 30%, transparent)}}.bg-white{background-color:var(--color-white)}.bg-yellow-900\/30{background-color:#733e0a4d}@supports (color:color-mix(in lab, red, red)){.bg-yellow-900\/30{background-color:color-mix(in oklab, var(--color-yellow-900) 30%, transparent)}}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-800{background-color:var(--color-zinc-800)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-900\/30{background-color:#18181b4d}@supports (color:color-mix(in lab, red, red)){.bg-zinc-900\/30{background-color:color-mix(in oklab, var(--color-zinc-900) 30%, transparent)}}.bg-zinc-900\/80{background-color:#18181bcc}@supports (color:color-mix(in lab, red, red)){.bg-zinc-900\/80{background-color:color-mix(in oklab, var(--color-zinc-900) 80%, transparent)}}.bg-zinc-950{background-color:var(--color-zinc-950)}.bg-zinc-950\/80{background-color:#09090bcc}@supports (color:color-mix(in lab, red, red)){.bg-zinc-950\/80{background-color:color-mix(in oklab, var(--color-zinc-950) 80%, transparent)}}.bg-zinc-950\/95{background-color:#09090bf2}@supports (color:color-mix(in lab, red, red)){.bg-zinc-950\/95{background-color:color-mix(in oklab, var(--color-zinc-950) 95%, transparent)}}.p-1{padding:var(--spacing)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pt-6{padding-top:calc(var(--spacing) * 6)}.pt-16{padding-top:calc(var(--spacing) * 16)}.pb-1{padding-bottom:var(--spacing)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.leading-7{--tw-leading:calc(var(--spacing) * 7);line-height:calc(var(--spacing) * 7)}.leading-none{--tw-leading:1;line-height:1}.leading-snug{--tw-leading:var(--leading-snug);line-height:var(--leading-snug)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.\!text-zinc-50{color:var(--color-zinc-50)!important}.text-blue-100\/90{color:#dbeafee6}@supports (color:color-mix(in lab, red, red)){.text-blue-100\/90{color:color-mix(in oklab, var(--color-blue-100) 90%, transparent)}}.text-blue-400{color:var(--color-blue-400)}.text-blue-800{color:var(--color-blue-800)}.text-current{color:currentColor}.text-gray-100{color:var(--color-gray-100)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-green-400{color:var(--color-green-400)}.text-green-800{color:var(--color-green-800)}.text-indigo-400{color:var(--color-indigo-400)}.text-indigo-800{color:var(--color-indigo-800)}.text-orange-400{color:var(--color-orange-400)}.text-orange-800{color:var(--color-orange-800)}.text-red-200{color:var(--color-red-200)}.text-red-400{color:var(--color-red-400)}.text-red-800{color:var(--color-red-800)}.text-teal-800{color:var(--color-teal-800)}.text-violet-400{color:var(--color-violet-400)}.text-violet-800{color:var(--color-violet-800)}.text-white{color:var(--color-white)}.text-yellow-400{color:var(--color-yellow-400)}.text-yellow-800{color:var(--color-yellow-800)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-200{color:var(--color-zinc-200)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-950{color:var(--color-zinc-950)}.capitalize{text-transform:capitalize}.italic{font-style:italic}.underline{text-decoration-line:underline}.placeholder-gray-400::placeholder{color:var(--color-gray-400)}.placeholder-gray-500::placeholder{color:var(--color-gray-500)}.opacity-0{opacity:0}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-inner{--tw-shadow:inset 0 2px 4px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring,.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-gray-200{--tw-ring-color:var(--color-gray-200)}.ring-gray-700{--tw-ring-color:var(--color-gray-700)}.ring-zinc-800{--tw-ring-color:var(--color-zinc-800)}.ring-offset-zinc-950{--tw-ring-offset-color:var(--color-zinc-950)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.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-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}@media (hover:hover){.group-hover\:visible:is(:where(.group):hover *){visibility:visible}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}.peer-hover\:visible:is(:where(.peer):hover~*){visibility:visible}.peer-hover\:opacity-100:is(:where(.peer):hover~*){opacity:1}}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-70:is(:where(.peer):disabled~*){opacity:.7}.marker\:hidden ::marker{display:none}.marker\:hidden::marker{display:none}.marker\:hidden ::-webkit-details-marker{display:none}.marker\:hidden::-webkit-details-marker{display:none}.placeholder\:text-gray-400::placeholder{color:var(--color-gray-400)}.placeholder\:text-zinc-500::placeholder{color:var(--color-zinc-500)}.first\:block:first-child{display:block}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.open\:block:is([open],:popover-open,:open){display:block}@media (hover:hover){.hover\:border-gray-300:hover{border-color:var(--color-gray-300)}.hover\:bg-blue-400:hover{background-color:var(--color-blue-400)}.hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.hover\:bg-blue-900:hover{background-color:var(--color-blue-900)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-300:hover{background-color:var(--color-gray-300)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-gray-900:hover{background-color:var(--color-gray-900)}.hover\:bg-green-400:hover{background-color:var(--color-green-400)}.hover\:bg-green-900:hover{background-color:var(--color-green-900)}.hover\:bg-indigo-900:hover{background-color:var(--color-indigo-900)}.hover\:bg-orange-200:hover{background-color:var(--color-orange-200)}.hover\:bg-orange-900:hover{background-color:var(--color-orange-900)}.hover\:bg-red-300:hover{background-color:var(--color-red-300)}.hover\:bg-red-900:hover{background-color:var(--color-red-900)}.hover\:bg-violet-900:hover{background-color:var(--color-violet-900)}.hover\:bg-yellow-900:hover{background-color:var(--color-yellow-900)}.hover\:bg-zinc-200:hover{background-color:var(--color-zinc-200)}.hover\:bg-zinc-800:hover{background-color:var(--color-zinc-800)}.hover\:bg-zinc-800\/80:hover{background-color:#27272acc}@supports (color:color-mix(in lab, red, red)){.hover\:bg-zinc-800\/80:hover{background-color:color-mix(in oklab, var(--color-zinc-800) 80%, transparent)}}.hover\:bg-zinc-900:hover{background-color:var(--color-zinc-900)}.hover\:text-gray-600:hover{color:var(--color-gray-600)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-zinc-50:hover{color:var(--color-zinc-50)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-blue-400:focus{border-color:var(--color-blue-400)}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:border-gray-300:focus{border-color:var(--color-gray-300)}.focus\:ring-0:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-blue-200:focus{--tw-ring-color:var(--color-blue-200)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-blue-500\/30:focus{--tw-ring-color:#3080ff4d}@supports (color:color-mix(in lab, red, red)){.focus\:ring-blue-500\/30:focus{--tw-ring-color:color-mix(in oklab, var(--color-blue-500) 30%, transparent)}}.focus\:ring-gray-300:focus{--tw-ring-color:var(--color-gray-300)}.focus\:ring-gray-600:focus{--tw-ring-color:var(--color-gray-600)}.focus\:ring-zinc-400:focus{--tw-ring-color:var(--color-zinc-400)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-zinc-950:focus{--tw-ring-offset-color:var(--color-zinc-950)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:ring-0:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-zinc-300:focus-visible{--tw-ring-color:var(--color-zinc-300)}.focus-visible\:ring-zinc-400:focus-visible{--tw-ring-color:var(--color-zinc-400)}.focus-visible\:ring-offset-0:focus-visible{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-zinc-950:focus-visible{--tw-ring-offset-color:var(--color-zinc-950)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-gray-100:disabled{background-color:var(--color-gray-100)}.disabled\:bg-gray-200:disabled{background-color:var(--color-gray-200)}.disabled\:text-gray-400:disabled{color:var(--color-gray-400)}.disabled\:text-gray-500:disabled{color:var(--color-gray-500)}.disabled\:opacity-50:disabled{opacity:.5}.data-\[state\=checked\]\:border-zinc-50[data-state=checked]{border-color:var(--color-zinc-50)}.data-\[state\=checked\]\:bg-zinc-50[data-state=checked]{background-color:var(--color-zinc-50)}.data-\[state\=checked\]\:text-zinc-950[data-state=checked]{color:var(--color-zinc-950)}@supports ((-webkit-backdrop-filter:var(--tw)) or (backdrop-filter:var(--tw))){.supports-\[backdrop-filter\]\:bg-zinc-950\/80{background-color:#09090bcc}@supports (color:color-mix(in lab, red, red)){.supports-\[backdrop-filter\]\:bg-zinc-950\/80{background-color:color-mix(in oklab, var(--color-zinc-950) 80%, transparent)}}}@media (min-width:40rem){.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:px-3{padding-inline:calc(var(--spacing) * 3)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}.sm\:py-2{padding-block:calc(var(--spacing) * 2)}.sm\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.sm\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@media (min-width:48rem){.md\:fixed{position:fixed}.md\:top-0{top:0}.md\:left-0{left:0}.md\:left-1\/2{left:50%}.md\:left-auto{left:auto}.md\:z-40{z-index:40}.md\:mt-8{margin-top:calc(var(--spacing) * 8)}.md\:block{display:block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:h-dvh{height:100dvh}.md\:w-24{width:calc(var(--spacing) * 24)}.md\:w-72{width:calc(var(--spacing) * 72)}.md\:-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.md\:translate-x-0{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.md\:grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.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-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.md\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.md\:grid-cols-9{grid-template-columns:repeat(9,minmax(0,1fr))}.md\:grid-cols-10{grid-template-columns:repeat(10,minmax(0,1fr))}.md\:flex-col{flex-direction:column}.md\:justify-between{justify-content:space-between}.md\:gap-1{gap:var(--spacing)}:where(.md\:space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.md\:overflow-hidden{overflow:hidden}.md\:rounded-full{border-radius:3.40282e38px}.md\:rounded-xl{border-radius:var(--radius-xl)}.md\:border{border-style:var(--tw-border-style);border-width:1px}.md\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.md\:border-zinc-800{border-color:var(--color-zinc-800)}.md\:bg-zinc-950{background-color:var(--color-zinc-950)}.md\:p-4{padding:calc(var(--spacing) * 4)}.md\:p-6{padding:calc(var(--spacing) * 6)}.md\:px-4{padding-inline:calc(var(--spacing) * 4)}.md\:py-3{padding-block:calc(var(--spacing) * 3)}.md\:pt-2{padding-top:calc(var(--spacing) * 2)}.md\:pr-72{padding-right:calc(var(--spacing) * 72)}.md\:pl-24{padding-left:calc(var(--spacing) * 24)}.md\:pl-72{padding-left:calc(var(--spacing) * 72)}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.md\: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,)}.md\:transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.md\:duration-300{--tw-duration:.3s;transition-duration:.3s}.md\:ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}}@media (min-width:64rem){.lg\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}.rtl\:text-right:where(:dir(rtl),[dir=rtl],[dir=rtl] *){text-align:right}@media (prefers-color-scheme:dark){@media (hover:hover){.dark\:hover\:bg-gray-700:hover{background-color:var(--color-gray-700)}}}.tramway-scrollbar{scrollbar-width:thin;scrollbar-color:#52525be6 #09090beb}.tramway-scrollbar::-webkit-scrollbar{width:.5rem}.tramway-scrollbar::-webkit-scrollbar-track{background:#09090beb}.tramway-scrollbar::-webkit-scrollbar-thumb{background:#52525be6;border-radius:9999px}.tramway-scrollbar::-webkit-scrollbar-thumb:hover{background:#71717af2}.tramway-scrollbar::-webkit-scrollbar-corner{background:0 0}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}
|
|
@@ -455,4 +455,205 @@ class Tooltip extends Controller {
|
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
-
|
|
458
|
+
class Navbar extends Controller {
|
|
459
|
+
connect() {
|
|
460
|
+
this.root = document.documentElement
|
|
461
|
+
this.desktopBreakpoint = 768
|
|
462
|
+
this.menuHiddenClass = 'hidden'
|
|
463
|
+
this.menuOffscreenClass = '-translate-x-full'
|
|
464
|
+
this.rootLockClass = 'overflow-hidden'
|
|
465
|
+
this.desktopNavbarExpandedWidthClass = 'md:w-72'
|
|
466
|
+
this.desktopNavbarCollapsedWidthClass = 'md:w-24'
|
|
467
|
+
this.desktopMainExpandedPaddingClass = 'md:pl-72'
|
|
468
|
+
this.desktopMainCollapsedPaddingClass = 'md:pl-24'
|
|
469
|
+
this.hiddenInteractionClasses = ['opacity-0', 'pointer-events-none']
|
|
470
|
+
this.expandedStorageKey = 'tramway-navbar-expanded'
|
|
471
|
+
|
|
472
|
+
this.desktopNavbar = this.element
|
|
473
|
+
this.desktopHeader = document.getElementById('desktop-navbar-header')
|
|
474
|
+
this.desktopMenu = document.getElementById('desktop-navbar-content')
|
|
475
|
+
this.desktopMainContainer = document.getElementById('tramway-main-container')
|
|
476
|
+
this.desktopToggleButton = document.getElementById('desktop-navbar-toggle-button')
|
|
477
|
+
this.desktopToggleIcon = document.getElementById('desktop-navbar-toggle-icon')
|
|
478
|
+
this.desktopToggleWrapper = document.getElementById('desktop-navbar-toggle-wrapper')
|
|
479
|
+
this.mobileButton = document.getElementById('mobile-menu-button')
|
|
480
|
+
this.mobileCloseButton = document.getElementById('mobile-menu-close-button')
|
|
481
|
+
this.mobileMenu = document.getElementById('mobile-menu')
|
|
482
|
+
|
|
483
|
+
this.handleMobileToggle = this.toggleMobileMenu.bind(this)
|
|
484
|
+
this.handleMobileClose = this.closeMobileMenu.bind(this)
|
|
485
|
+
this.handleBeforeCache = this.handleBeforeCache.bind(this)
|
|
486
|
+
this.handleResize = this.handleResize.bind(this)
|
|
487
|
+
this.handleDesktopToggle = this.toggleDesktopExpanded.bind(this)
|
|
488
|
+
|
|
489
|
+
this.bindMobileControls()
|
|
490
|
+
this.bindDesktopControls()
|
|
491
|
+
document.addEventListener('turbo:before-cache', this.handleBeforeCache)
|
|
492
|
+
window.addEventListener('resize', this.handleResize)
|
|
493
|
+
|
|
494
|
+
this.syncDesktopExpandedState()
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
readStoredExpanded() {
|
|
498
|
+
try {
|
|
499
|
+
return window.localStorage.getItem(this.expandedStorageKey)
|
|
500
|
+
} catch {
|
|
501
|
+
return null
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
writeStoredExpanded(expanded) {
|
|
506
|
+
try {
|
|
507
|
+
window.localStorage.setItem(this.expandedStorageKey, expanded ? 'true' : 'false')
|
|
508
|
+
} catch {
|
|
509
|
+
/* localStorage unavailable (e.g. private mode) — state simply won't persist */
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
disconnect() {
|
|
514
|
+
document.removeEventListener('turbo:before-cache', this.handleBeforeCache)
|
|
515
|
+
window.removeEventListener('resize', this.handleResize)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
bindMobileControls() {
|
|
519
|
+
if (this.mobileButton) {
|
|
520
|
+
this.mobileButton.addEventListener('click', this.handleMobileToggle)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (this.mobileCloseButton) {
|
|
524
|
+
this.mobileCloseButton.addEventListener('click', this.handleMobileClose)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (this.mobileMenu) {
|
|
528
|
+
this.mobileMenu.querySelectorAll('a').forEach((link) => {
|
|
529
|
+
link.addEventListener('click', () => this.closeMobileMenu())
|
|
530
|
+
})
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
bindDesktopControls() {
|
|
535
|
+
if (this.isVertical() && this.desktopToggleButton) {
|
|
536
|
+
this.desktopToggleButton.addEventListener('click', this.handleDesktopToggle)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
handleBeforeCache() {
|
|
541
|
+
this.closeMobileMenu({ hideImmediately: true })
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
handleResize() {
|
|
545
|
+
if (window.innerWidth >= 640) {
|
|
546
|
+
this.closeMobileMenu({ hideImmediately: true })
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (this.isDesktopViewport()) {
|
|
550
|
+
this.syncDesktopExpandedState()
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
toggleMobileMenu() {
|
|
555
|
+
if (!this.mobileMenu) return
|
|
556
|
+
|
|
557
|
+
if (this.mobileMenu.classList.contains(this.menuHiddenClass)) {
|
|
558
|
+
this.openMobileMenu()
|
|
559
|
+
return
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
this.closeMobileMenu()
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
openMobileMenu() {
|
|
566
|
+
if (!this.mobileMenu) return
|
|
567
|
+
|
|
568
|
+
this.mobileMenu.classList.remove(this.menuHiddenClass)
|
|
569
|
+
window.requestAnimationFrame(() => {
|
|
570
|
+
this.mobileMenu.classList.remove(this.menuOffscreenClass)
|
|
571
|
+
this.root.classList.add(this.rootLockClass)
|
|
572
|
+
})
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
closeMobileMenu({ hideImmediately = false } = {}) {
|
|
576
|
+
if (!this.mobileMenu) {
|
|
577
|
+
this.root.classList.remove(this.rootLockClass)
|
|
578
|
+
return
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
this.mobileMenu.classList.add(this.menuOffscreenClass)
|
|
582
|
+
this.root.classList.remove(this.rootLockClass)
|
|
583
|
+
|
|
584
|
+
if (hideImmediately) {
|
|
585
|
+
this.mobileMenu.classList.add(this.menuHiddenClass)
|
|
586
|
+
return
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
this.mobileMenu.addEventListener(
|
|
590
|
+
'transitionend',
|
|
591
|
+
() => this.mobileMenu.classList.add(this.menuHiddenClass),
|
|
592
|
+
{ once: true }
|
|
593
|
+
)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
toggleDesktopExpanded() {
|
|
597
|
+
this.setDesktopExpanded(this.desktopNavbar.dataset.expanded === 'false', { persist: true })
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
syncDesktopExpandedState() {
|
|
601
|
+
const storedExpanded = this.readStoredExpanded()
|
|
602
|
+
const expanded = storedExpanded === null ? this.desktopNavbar.dataset.expanded !== 'false' : storedExpanded !== 'false'
|
|
603
|
+
|
|
604
|
+
this.setDesktopExpanded(expanded)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
setDesktopExpanded(expanded, { persist = false } = {}) {
|
|
608
|
+
if (!this.isVertical() || !this.desktopNavbar || !this.desktopHeader || !this.desktopMenu || !this.desktopMainContainer || !this.desktopToggleButton || !this.desktopToggleWrapper) {
|
|
609
|
+
return
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (persist) {
|
|
613
|
+
this.writeStoredExpanded(expanded)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (expanded) {
|
|
617
|
+
document.documentElement.removeAttribute('data-tramway-navbar-expanded')
|
|
618
|
+
} else {
|
|
619
|
+
document.documentElement.setAttribute('data-tramway-navbar-expanded', 'false')
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
this.desktopNavbar.classList.toggle(this.desktopNavbarExpandedWidthClass, expanded)
|
|
623
|
+
this.desktopNavbar.classList.toggle(this.desktopNavbarCollapsedWidthClass, !expanded)
|
|
624
|
+
this.desktopNavbar.dataset.expanded = expanded ? 'true' : 'false'
|
|
625
|
+
|
|
626
|
+
this.desktopMainContainer.classList.toggle(this.desktopMainExpandedPaddingClass, expanded)
|
|
627
|
+
this.desktopMainContainer.classList.toggle(this.desktopMainCollapsedPaddingClass, !expanded)
|
|
628
|
+
|
|
629
|
+
this.hiddenInteractionClasses.forEach((className) => {
|
|
630
|
+
this.desktopHeader.classList.toggle(className, !expanded)
|
|
631
|
+
this.desktopMenu.classList.toggle(className, !expanded)
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
this.desktopHeader.toggleAttribute('inert', !expanded)
|
|
635
|
+
this.desktopMenu.toggleAttribute('inert', !expanded)
|
|
636
|
+
this.desktopHeader.setAttribute('aria-hidden', expanded ? 'false' : 'true')
|
|
637
|
+
this.desktopMenu.setAttribute('aria-hidden', expanded ? 'false' : 'true')
|
|
638
|
+
|
|
639
|
+
this.desktopToggleWrapper.classList.toggle('justify-end', expanded)
|
|
640
|
+
this.desktopToggleWrapper.classList.toggle('justify-center', !expanded)
|
|
641
|
+
if (this.desktopToggleIcon) {
|
|
642
|
+
this.desktopToggleIcon.classList.toggle('rotate-180', !expanded)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
this.desktopToggleButton.setAttribute('aria-label', expanded ? 'Collapse sidebar' : 'Expand sidebar')
|
|
646
|
+
|
|
647
|
+
this.desktopToggleButton.setAttribute('aria-expanded', expanded ? 'true' : 'false')
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
isVertical() {
|
|
651
|
+
return this.element.dataset.direction === 'vertical'
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
isDesktopViewport() {
|
|
655
|
+
return this.isVertical() && window.innerWidth >= this.desktopBreakpoint
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
export { TramwaySelect, TableRowPreview, UiCheckbox, Tooltip, Navbar }
|