django-admin-react 0.2.0a7__tar.gz → 0.2.0a8__tar.gz

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 (52) hide show
  1. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/PKG-INFO +28 -13
  2. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/README.md +27 -12
  3. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/inlines.py +23 -1
  4. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/detail.py +35 -0
  5. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/list.py +31 -6
  6. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/conf.py +23 -10
  7. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/static/admin_react/.vite/manifest.json +2 -2
  8. django_admin_react-0.2.0a8/django_admin_react/static/admin_react/assets/index-BSzI7RU6.css +1 -0
  9. django_admin_react-0.2.0a8/django_admin_react/static/admin_react/assets/index-CxlHfz-w.js +8 -0
  10. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/static/admin_react/index.html +2 -2
  11. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/templates/admin_react/index.html +14 -3
  12. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/views.py +74 -3
  13. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/pyproject.toml +1 -1
  14. django_admin_react-0.2.0a7/django_admin_react/static/admin_react/assets/index-BK8mlqvA.js +0 -8
  15. django_admin_react-0.2.0a7/django_admin_react/static/admin_react/assets/index-BVqO3W_r.css +0 -1
  16. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/LICENSE +0 -0
  17. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/README.md +0 -0
  18. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/__init__.py +0 -0
  19. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/README.md +0 -0
  20. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/__init__.py +0 -0
  21. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/dates.py +0 -0
  22. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/filters.py +0 -0
  23. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/inlines_write.py +0 -0
  24. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/panels.py +0 -0
  25. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/permissions.py +0 -0
  26. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/registry.py +0 -0
  27. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/serializers.py +0 -0
  28. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/urls.py +0 -0
  29. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/README.md +0 -0
  30. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/__init__.py +0 -0
  31. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/actions.py +0 -0
  32. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/auth.py +0 -0
  33. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/autocomplete.py +0 -0
  34. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/bulk.py +0 -0
  35. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/create.py +0 -0
  36. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/create_form.py +0 -0
  37. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/delete_preview.py +0 -0
  38. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/destroy.py +0 -0
  39. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/history.py +0 -0
  40. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/password.py +0 -0
  41. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/registry.py +0 -0
  42. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/schema.py +0 -0
  43. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/views/update.py +0 -0
  44. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/api/writes.py +0 -0
  45. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/apps.py +0 -0
  46. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/audit.py +0 -0
  47. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/pwa.py +0 -0
  48. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/templates/README.md +0 -0
  49. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/templates/admin_react/README.md +0 -0
  50. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/templates/admin_react/login.html +0 -0
  51. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/templates/admin_react/sw.js +0 -0
  52. {django_admin_react-0.2.0a7 → django_admin_react-0.2.0a8}/django_admin_react/urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: django-admin-react
3
- Version: 0.2.0a7
3
+ Version: 0.2.0a8
4
4
  Summary: A drop-in React single-page admin for Django, driven entirely by ModelAdmin.
5
5
  License: MIT
6
6
  Keywords: django,admin,react,spa,tailwind
@@ -142,16 +142,19 @@ All settings are optional. Defaults shown:
142
142
  ```python
143
143
  DJANGO_ADMIN_REACT = {
144
144
  "ADMIN_SITE": "django.contrib.admin.site", # dotted path to AdminSite instance
145
- "DEFAULT_PAGE_SIZE": 25,
145
+ "DEFAULT_PAGE_SIZE": 25, # fallback only; the list page size derives
146
+ # from ModelAdmin.list_per_page (Django parity).
146
147
  "MAX_PAGE_SIZE": 200,
147
148
  "ENABLE_PROFILING": False,
148
149
 
149
- # Branding — rendered server-side into the SPA shell, so the
150
- # consumer's title + favicon are present on first paint (no FOUC).
151
- "BRAND_TITLE": None, # str | None sidebar header + browser tab.
152
- "BRAND_LOGO_URL": None, # str | None used as the favicon and
153
- # the sidebar logo. Absolute URL or a
154
- # path under your STATIC_URL.
150
+ # Branding — all optional. The defaults derive from your AdminSite
151
+ # (site_header / site_title / site_logo), so if you already branded
152
+ # the HTML admin you need nothing here. Rendered server-side into the
153
+ # SPA shell, so title + favicon are present on first paint (no FOUC).
154
+ "BRAND_TITLE": None, # str | None override for BOTH brand strings.
155
+ "BRAND_LOGO_URL": None, # str | None — favicon + sidebar logo;
156
+ # falls back to AdminSite.site_logo. Absolute
157
+ # URL or a path under your STATIC_URL.
155
158
  "PRIMARY_COLOR": "#2563eb", # accent for primary buttons, links, and
156
159
  # active states. Hex only (validated);
157
160
  # injected as the --dar-primary CSS var, so
@@ -161,16 +164,28 @@ DJANGO_ADMIN_REACT = {
161
164
 
162
165
  #### Branding (`BRAND_TITLE` + `BRAND_LOGO_URL`)
163
166
 
164
- Both default to `None`. Resolution order for the title:
167
+ Both default to `None` and **derive from your `AdminSite`**, mirroring
168
+ Django admin — so if you already customised the HTML admin's branding,
169
+ you need no settings here at all.
170
+
171
+ **Sidebar header** resolution:
165
172
 
166
173
  1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
167
- 2. `<your AdminSite>.site_header` — if you already set `site_header`
168
- on a custom `AdminSite`, the SPA reuses it automatically. No need
169
- to repeat yourself.
174
+ 2. `<your AdminSite>.site_header` — reused automatically.
170
175
  3. `"Django Admin"` — last-resort fallback.
171
176
 
177
+ **Browser-tab `<title>`** resolution (Django uses `site_title` for the
178
+ tab, `site_header` for the on-page header):
179
+
180
+ 1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
181
+ 2. `<your AdminSite>.site_title` — Django's tab-title source.
182
+ 3. `<your AdminSite>.site_header` — fallback.
183
+ 4. `"Django Admin"` — last-resort fallback.
184
+
172
185
  `BRAND_LOGO_URL` accepts either an absolute URL or a path the browser
173
- can resolve under your `STATIC_URL`. It is used both as the favicon
186
+ can resolve under your `STATIC_URL`. When unset, a `site_logo` attribute
187
+ on your `AdminSite` is used (Django has no logo by default, so set it as
188
+ a constant on your custom site). It is used both as the favicon
174
189
  (`<link rel="icon">` in the SPA shell) and as the small logo next to
175
190
  the brand title in the sidebar.
176
191
 
@@ -111,16 +111,19 @@ All settings are optional. Defaults shown:
111
111
  ```python
112
112
  DJANGO_ADMIN_REACT = {
113
113
  "ADMIN_SITE": "django.contrib.admin.site", # dotted path to AdminSite instance
114
- "DEFAULT_PAGE_SIZE": 25,
114
+ "DEFAULT_PAGE_SIZE": 25, # fallback only; the list page size derives
115
+ # from ModelAdmin.list_per_page (Django parity).
115
116
  "MAX_PAGE_SIZE": 200,
116
117
  "ENABLE_PROFILING": False,
117
118
 
118
- # Branding — rendered server-side into the SPA shell, so the
119
- # consumer's title + favicon are present on first paint (no FOUC).
120
- "BRAND_TITLE": None, # str | None sidebar header + browser tab.
121
- "BRAND_LOGO_URL": None, # str | None used as the favicon and
122
- # the sidebar logo. Absolute URL or a
123
- # path under your STATIC_URL.
119
+ # Branding — all optional. The defaults derive from your AdminSite
120
+ # (site_header / site_title / site_logo), so if you already branded
121
+ # the HTML admin you need nothing here. Rendered server-side into the
122
+ # SPA shell, so title + favicon are present on first paint (no FOUC).
123
+ "BRAND_TITLE": None, # str | None override for BOTH brand strings.
124
+ "BRAND_LOGO_URL": None, # str | None — favicon + sidebar logo;
125
+ # falls back to AdminSite.site_logo. Absolute
126
+ # URL or a path under your STATIC_URL.
124
127
  "PRIMARY_COLOR": "#2563eb", # accent for primary buttons, links, and
125
128
  # active states. Hex only (validated);
126
129
  # injected as the --dar-primary CSS var, so
@@ -130,16 +133,28 @@ DJANGO_ADMIN_REACT = {
130
133
 
131
134
  #### Branding (`BRAND_TITLE` + `BRAND_LOGO_URL`)
132
135
 
133
- Both default to `None`. Resolution order for the title:
136
+ Both default to `None` and **derive from your `AdminSite`**, mirroring
137
+ Django admin — so if you already customised the HTML admin's branding,
138
+ you need no settings here at all.
139
+
140
+ **Sidebar header** resolution:
134
141
 
135
142
  1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
136
- 2. `<your AdminSite>.site_header` — if you already set `site_header`
137
- on a custom `AdminSite`, the SPA reuses it automatically. No need
138
- to repeat yourself.
143
+ 2. `<your AdminSite>.site_header` — reused automatically.
139
144
  3. `"Django Admin"` — last-resort fallback.
140
145
 
146
+ **Browser-tab `<title>`** resolution (Django uses `site_title` for the
147
+ tab, `site_header` for the on-page header):
148
+
149
+ 1. `DJANGO_ADMIN_REACT["BRAND_TITLE"]` — explicit override.
150
+ 2. `<your AdminSite>.site_title` — Django's tab-title source.
151
+ 3. `<your AdminSite>.site_header` — fallback.
152
+ 4. `"Django Admin"` — last-resort fallback.
153
+
141
154
  `BRAND_LOGO_URL` accepts either an absolute URL or a path the browser
142
- can resolve under your `STATIC_URL`. It is used both as the favicon
155
+ can resolve under your `STATIC_URL`. When unset, a `site_logo` attribute
156
+ on your `AdminSite` is used (Django has no logo by default, so set it as
157
+ a constant on your custom site). It is used both as the favicon
143
158
  (`<link rel="icon">` in the SPA shell) and as the small logo next to
144
159
  the brand title in the sidebar.
145
160
 
@@ -26,6 +26,7 @@ from typing import Any
26
26
 
27
27
  from django.contrib.admin.options import InlineModelAdmin
28
28
  from django.contrib.admin.options import ModelAdmin
29
+ from django.contrib.admin.options import TabularInline
29
30
  from django.contrib.admin.utils import label_for_field
30
31
  from django.contrib.admin.utils import lookup_field
31
32
  from django.db.models import ForeignKey
@@ -120,6 +121,21 @@ def _show_change_link_allowed(
120
121
  return bool(target_admin.has_view_permission(request))
121
122
 
122
123
 
124
+ def _inline_kind(inline: InlineModelAdmin) -> str:
125
+ """Tabular vs stacked layout hint for the SPA.
126
+
127
+ Classified by the inline's **base class** (``admin.TabularInline``),
128
+ not its subclass *name*. The previous ``"Tabular" in
129
+ type(inline).__name__`` check mis-classified the common real-world
130
+ ``class BookInline(admin.TabularInline)`` (no "Tabular" in the name)
131
+ as stacked, so a tabular inline rendered as a card list (#417).
132
+ ``StackedInline`` — and a bare ``InlineModelAdmin`` — fall through to
133
+ the stacked layout, matching Django's own template selection
134
+ (``TabularInline`` is the only base that renders a table).
135
+ """
136
+ return "tabular" if isinstance(inline, TabularInline) else "stacked"
137
+
138
+
123
139
  def _spec_for_inline(
124
140
  inline: InlineModelAdmin,
125
141
  parent: Model,
@@ -146,7 +162,7 @@ def _spec_for_inline(
146
162
  can_change = bool(inline.has_change_permission(request, parent))
147
163
  can_delete = bool(inline.has_delete_permission(request, parent))
148
164
 
149
- kind = "tabular" if "Tabular" in type(inline).__name__ else "stacked"
165
+ kind = _inline_kind(inline)
150
166
 
151
167
  visible_fields = _visible_inline_fields(inline, parent, request)
152
168
  fields_meta = _fields_meta(inline, child_model, visible_fields, request)
@@ -160,6 +176,12 @@ def _spec_for_inline(
160
176
  "label": str(meta.verbose_name_plural),
161
177
  "kind": kind,
162
178
  "fk_name": fk_name,
179
+ # The child's pk field name (#418): when the pk is an explicit,
180
+ # non-auto field (e.g. a UUIDField) it shows up as an inline
181
+ # column, and the SPA must render it without ellipsis — the row's
182
+ # identity must stay fully readable/copyable (mirrors the list
183
+ # ``pk_field`` / #360).
184
+ "pk_field": meta.pk.name,
163
185
  "child": {"app_label": meta.app_label, "model_name": meta.model_name},
164
186
  "extra": int(getattr(inline, "extra", 0)),
165
187
  "min_num": getattr(inline, "min_num", None),
@@ -26,6 +26,8 @@ from django.db.models import FileField
26
26
  from django.db.models import ForeignKey
27
27
  from django.db.models import ManyToManyField
28
28
  from django.db.models import Model
29
+ from django.forms.widgets import Textarea
30
+ from django.forms.widgets import TextInput
29
31
  from django.http import HttpRequest
30
32
  from django.http import HttpResponse
31
33
  from django.http import JsonResponse
@@ -358,9 +360,42 @@ def _descriptor_for(
358
360
  # relations). ``elif`` so ``radio_fields`` wins if a field is in both.
359
361
  elif name in (getattr(model_admin, "raw_id_fields", None) or ()):
360
362
  descriptor["widget"] = "raw_id"
363
+ # formfield_overrides (#446): the bound form field's widget already
364
+ # reflects the admin's ``formfield_overrides`` /
365
+ # ``formfield_for_dbfield`` — Django applied them in ``get_form``.
366
+ # Honour the one override the SPA can act on with the existing type
367
+ # vocabulary: a single-line string promoted to a ``Textarea`` becomes
368
+ # the multi-line ``text`` type (rendered as a ``<textarea>``), and a
369
+ # multi-line ``text`` forced to a single-line ``TextInput`` collapses
370
+ # back to ``string``. Other widget overrides (date pickers, FK
371
+ # autocomplete) the SPA already renders from the field type. Choice
372
+ # fields are untouched — their ``choice`` type wins above.
373
+ _apply_widget_override(descriptor, form_field)
361
374
  return descriptor
362
375
 
363
376
 
377
+ def _apply_widget_override(descriptor: dict[str, Any], form_field: Any) -> None:
378
+ """Reconcile the descriptor type with the bound form field's widget.
379
+
380
+ Reuses the form widget (source of truth) so ``formfield_overrides``
381
+ has a visible effect, mapping only to the existing ``string`` /
382
+ ``text`` vocabulary so no new wire type is introduced (#446).
383
+ """
384
+ if form_field is None:
385
+ return
386
+ widget = getattr(form_field, "widget", None)
387
+ if widget is None:
388
+ return
389
+ if descriptor["type"] == "string" and isinstance(widget, Textarea):
390
+ descriptor["type"] = "text"
391
+ elif (
392
+ descriptor["type"] == "text"
393
+ and isinstance(widget, TextInput)
394
+ and not isinstance(widget, Textarea)
395
+ ):
396
+ descriptor["type"] = "string"
397
+
398
+
364
399
  def _readonly_callable_descriptor(
365
400
  model_admin: ModelAdmin,
366
401
  model: type[Model],
@@ -157,7 +157,9 @@ class ListView(View):
157
157
  page = 1
158
158
  page_size = max(total, 1)
159
159
  else:
160
- page_size = _clamp_page_size(request.GET.get("page_size"))
160
+ page_size = _clamp_page_size(
161
+ request.GET.get("page_size"), _default_page_size(model_admin)
162
+ )
161
163
  page = _clamp_page(request.GET.get("page"))
162
164
  start = (page - 1) * page_size
163
165
  end = start + page_size
@@ -253,14 +255,37 @@ def _clamp_page(raw: str | None) -> int:
253
255
  return max(1, n)
254
256
 
255
257
 
256
- def _clamp_page_size(raw: str | None) -> int:
258
+ def _default_page_size(model_admin: ModelAdmin) -> int:
259
+ """The default page size when the client sends no ``?page_size=`` (#281).
260
+
261
+ Derived from ``ModelAdmin.list_per_page`` so the source of truth is the
262
+ admin (Rule #1), matching Django's changelist — a consumer who set
263
+ ``list_per_page`` for the HTML admin gets the same page size in the SPA
264
+ with no extra setting. Falls back to ``conf.DEFAULT_PAGE_SIZE`` only when
265
+ ``list_per_page`` is missing/invalid, and is capped at ``MAX_PAGE_SIZE``
266
+ so the per-request DoS ceiling still holds (a consumer wanting a bigger
267
+ default raises ``MAX_PAGE_SIZE`` too).
268
+ """
269
+ fallback = int(conf.DEFAULT_PAGE_SIZE)
270
+ try:
271
+ n = int(getattr(model_admin, "list_per_page", fallback))
272
+ except (TypeError, ValueError):
273
+ n = fallback
274
+ if n < 1:
275
+ n = fallback
276
+ return min(n, int(conf.MAX_PAGE_SIZE))
277
+
278
+
279
+ def _clamp_page_size(raw: str | None, default: int) -> int:
257
280
  """Parse ``?page_size=`` and clamp to ``[1, conf.MAX_PAGE_SIZE]``.
258
281
 
259
- The clamp is a denial-of-service guard: without an upper bound a
260
- client could pass ``?page_size=10_000_000`` and force the
261
- database to materialise ten million rows.
282
+ ``default`` (the model's ``list_per_page``-derived size, see
283
+ ``_default_page_size``) is used when the param is absent or invalid.
284
+
285
+ The upper clamp is a denial-of-service guard: without it a client
286
+ could pass ``?page_size=10_000_000`` and force the database to
287
+ materialise ten million rows.
262
288
  """
263
- default = int(conf.DEFAULT_PAGE_SIZE)
264
289
  maximum = int(conf.MAX_PAGE_SIZE)
265
290
  try:
266
291
  n = int(raw) if raw is not None else default
@@ -22,6 +22,12 @@ from django.conf import settings as django_settings
22
22
 
23
23
  DEFAULTS: dict[str, Any] = {
24
24
  "ADMIN_SITE": "django.contrib.admin.site",
25
+ # The list page size derives from the model's
26
+ # ``ModelAdmin.list_per_page`` (Django's changelist source of truth,
27
+ # Rule #1 / #281), so the SPA pages like the HTML admin with no extra
28
+ # setting. ``DEFAULT_PAGE_SIZE`` is the fallback only when
29
+ # ``list_per_page`` is missing / invalid. ``MAX_PAGE_SIZE`` always caps
30
+ # ``?page_size`` (a DoS guard).
25
31
  "DEFAULT_PAGE_SIZE": 25,
26
32
  "MAX_PAGE_SIZE": 200,
27
33
  "ENABLE_PROFILING": False,
@@ -29,16 +35,23 @@ DEFAULTS: dict[str, Any] = {
29
35
  # rendered server-side into the SPA index template so the SPA
30
36
  # picks them up on first paint (no FOUC).
31
37
  #
32
- # ``BRAND_TITLE`` — string shown in the sidebar header *and*
33
- # the browser tab title. ``None`` (default)
34
- # falls back to the configured AdminSite's
35
- # ``site_header`` if set, else the package
36
- # name. Plain text; no HTML.
37
- # ``BRAND_LOGO_URL`` — URL to a square logo / favicon. Written
38
- # into the SPA's ``<link rel="icon">``.
39
- # ``None`` (default) keeps the no-op
40
- # ``data:,`` placeholder. Either an absolute
41
- # URL or a path under your ``STATIC_URL``.
38
+ # ``BRAND_TITLE`` — optional override for *both* the sidebar
39
+ # header and the browser-tab title. ``None``
40
+ # (default) derives them from the AdminSite,
41
+ # mirroring Django admin: ``site_header``
42
+ # sidebar header, ``site_title`` tab title
43
+ # (falling back to ``site_header``), else the
44
+ # package name (#281). A consumer who already
45
+ # set ``site_header`` / ``site_title`` on their
46
+ # ``AdminSite`` needs no branding setting at all.
47
+ # Plain text; no HTML.
48
+ # ``BRAND_LOGO_URL`` — optional override for the logo / favicon URL,
49
+ # written into the SPA's ``<link rel="icon">``.
50
+ # ``None`` (default) reads ``site_logo`` off the
51
+ # configured ``AdminSite`` if the consumer set
52
+ # that attribute (#281), else keeps the no-op
53
+ # ``data:,`` placeholder. Either an absolute URL
54
+ # or a path under your ``STATIC_URL``.
42
55
  "BRAND_TITLE": None,
43
56
  "BRAND_LOGO_URL": None,
44
57
  # ``PRIMARY_COLOR`` — the accent color for primary buttons, links, and
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "index.html": {
3
- "file": "assets/index-BK8mlqvA.js",
3
+ "file": "assets/index-CxlHfz-w.js",
4
4
  "name": "index",
5
5
  "src": "index.html",
6
6
  "isEntry": true,
7
7
  "css": [
8
- "assets/index-BVqO3W_r.css"
8
+ "assets/index-BSzI7RU6.css"
9
9
  ]
10
10
  }
11
11
  }
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}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;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media(min-width:640px){.container{max-width:640px}}@media(min-width:768px){.container{max-width:768px}}@media(min-width:1024px){.container{max-width:1024px}}@media(min-width:1280px){.container{max-width:1280px}}@media(min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.inset-x-0{left:0;right:0}.inset-y-0{top:0;bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-2{top:.5rem}.isolate{isolation:isolate}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[200\]{z-index:200}.col-span-2{grid-column:span 2 / span 2}.m-6{margin:1.5rem}.-ml-2{margin-left:-.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-0\.5{margin-left:.125rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.block{display:block}.inline-block{display:inline-block}.\!inline{display:inline!important}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-10{height:2.5rem}.h-14{height:3.5rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-9{height:2.25rem}.h-full{height:100%}.max-h-48{max-height:12rem}.max-h-60{max-height:15rem}.max-h-72{max-height:18rem}.max-h-\[85vh\]{max-height:85vh}.min-h-screen{min-height:100vh}.w-1\.5{width:.375rem}.w-10{width:2.5rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-28{width:7rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4{width:1rem}.w-40{width:10rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-72{width:18rem}.w-80{width:20rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-48{min-width:12rem}.min-w-\[14rem\]{min-width:14rem}.min-w-full{min-width:100%}.max-w-\[12rem\]{max-width:12rem}.max-w-\[16rem\]{max-width:16rem}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-full{max-width:100%}.max-w-prose{max-width:65ch}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.table-fixed{table-layout:fixed}.-translate-x-full{--tw-translate-x: -100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-rotate-90{--tw-rotate: -90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-col-resize{cursor:col-resize}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(243 244 246 / var(--tw-divide-opacity, 1))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(229 231 235 / var(--tw-divide-opacity, 1))}.divide-gray-800>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(31 41 55 / var(--tw-divide-opacity, 1))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-t-xl{border-top-left-radius:.75rem;border-top-right-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-amber-300{--tw-border-opacity: 1;border-color:rgb(252 211 77 / var(--tw-border-opacity, 1))}.border-blue-300{--tw-border-opacity: 1;border-color:rgb(147 197 253 / var(--tw-border-opacity, 1))}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(37 99 235 / var(--tw-border-opacity, 1))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-green-300{--tw-border-opacity: 1;border-color:rgb(134 239 172 / var(--tw-border-opacity, 1))}.border-primary{border-color:var(--dar-primary, #2563eb)}.border-red-300{--tw-border-opacity: 1;border-color:rgb(252 165 165 / var(--tw-border-opacity, 1))}.border-red-400{--tw-border-opacity: 1;border-color:rgb(248 113 113 / var(--tw-border-opacity, 1))}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-600{--tw-border-opacity: 1;border-color:rgb(220 38 38 / var(--tw-border-opacity, 1))}.border-transparent{border-color:transparent}.bg-amber-50{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.bg-black\/50{background-color:#00000080}.bg-blue-50{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-400{--tw-bg-opacity: 1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-primary{background-color:var(--dar-primary, #2563eb)}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.fill-amber-400{fill:#fbbf24}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pl-5{padding-left:1.25rem}.pr-3{padding-right:.75rem}.pt-1{padding-top:.25rem}.pt-20{padding-top:5rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.text-2xl{font-size:1.5rem;line-height:2rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.tracking-wide{letter-spacing:.025em}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-amber-700{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.text-amber-800{--tw-text-opacity: 1;color:rgb(146 64 14 / var(--tw-text-opacity, 1))}.text-blue-700{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity: 1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-inherit{color:inherit}.text-primary{color:var(--dar-primary, #2563eb)}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity: 1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.no-underline{text-decoration-line:none}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.placeholder-gray-500::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.placeholder-gray-500::placeholder{--tw-placeholder-opacity: 1;color:rgb(107 114 128 / var(--tw-placeholder-opacity, 1))}.opacity-0{opacity:0}.opacity-25{opacity:.25}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.blur{--tw-blur: blur(8px);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)}.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)}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}html,body,#root{height:100%}*{scrollbar-width:none;-ms-overflow-style:none}*::-webkit-scrollbar{display:none}.dark body{background-color:#0b1020;color:#e5e7eb}.dark .bg-white{background-color:#111827!important}.dark .bg-gray-50{background-color:#0b1020!important}.dark .bg-gray-200{background-color:#1f2937!important}.dark .text-gray-900,.dark .text-gray-800{color:#f3f4f6!important}.dark .text-gray-700,.dark .text-gray-600,.dark .text-gray-500{color:#9ca3af!important}.border-gray-200,.border-gray-300{border-color:#e5e7eb}.dark .border-gray-200,.dark .border-gray-300,.dark .border-gray-100{border-color:#2b3544!important}.dark .divide-gray-100>:not([hidden])~:not([hidden]),.dark .divide-gray-200>:not([hidden])~:not([hidden]){border-color:#1f2937!important}.dark .hover\:bg-gray-50:hover,.dark .hover\:bg-gray-100:hover{background-color:#1f2937!important}.dark input:not([type=checkbox],[type=radio],[type=range]),.dark select,.dark textarea{background-color:#111827!important;color:#e5e7eb!important;border-color:#2b3544!important}.dark input::-moz-placeholder,.dark textarea::-moz-placeholder{color:#6b7280!important}.dark input::placeholder,.dark textarea::placeholder{color:#6b7280!important}.dark .bg-blue-50{background-color:#1e293b!important}.dark .text-blue-700{color:#93c5fd!important}.dark .bg-red-50{background-color:#3f1d1d!important}.dark .border-red-300,.dark .border-red-400{border-color:#7f1d1d!important}.dark .text-red-700,.dark .text-red-600{color:#fca5a5!important}.dark .bg-green-50{background-color:#14321f!important}.dark .text-green-700,.dark .text-green-600{color:#86efac!important}.dark .bg-amber-50{background-color:#3a2e12!important}.dark .border-amber-300{border-color:#92400e!important}.dark .text-amber-800,.dark .text-amber-700{color:#fcd34d!important}.dark .bg-gray-100{background-color:#1f2937!important}table td>div>span,dl dd>span{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#6b7280}.dark table td>div>span,.dark dl dd>span{color:#9ca3af}table td>div>span a,dl dd>span a{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--dar-primary, #4f46e5)}table td>div>span a:hover,dl dd>span a:hover{text-decoration:underline}.dark table td>div>span a,.dark dl dd>span a{color:color-mix(in srgb,var(--dar-primary, #4f46e5) 65%,white)}.dark .text-primary{color:color-mix(in srgb,var(--dar-primary, #2563eb) 65%,white)}.first\:pt-0:first-child{padding-top:0}.hover\:border-gray-200:hover{--tw-border-opacity: 1;border-color:rgb(229 231 235 / var(--tw-border-opacity, 1))}.hover\:bg-blue-400\/60:hover{background-color:#60a5fa99}.hover\:bg-blue-50:hover{--tw-bg-opacity: 1;background-color:rgb(239 246 255 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-700:hover{--tw-bg-opacity: 1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800:hover{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.hover\:bg-red-700:hover{--tw-bg-opacity: 1;background-color:rgb(185 28 28 / var(--tw-bg-opacity, 1))}.hover\:text-amber-500:hover{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.hover\:text-gray-600:hover{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.hover\:text-gray-800:hover{--tw-text-opacity: 1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.hover\:text-red-600:hover{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:no-underline:hover{text-decoration-line:none}.hover\:opacity-90:hover{opacity:.9}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-gray-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity, 1))}.focus\:ring-red-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1))}.focus-visible\:border-blue-500:focus-visible{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-1:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-inset:focus-visible{--tw-ring-inset: inset}.focus-visible\:ring-blue-500:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-gray-400:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(156 163 175 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-gray-500:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width: 1px}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:opacity-100{opacity:1}@media(min-width:640px){.sm\:max-w-md{max-width:28rem}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-start{align-items:flex-start}.sm\:items-center{align-items:center}.sm\:justify-between{justify-content:space-between}.sm\:gap-4{gap:1rem}.sm\:rounded-xl{border-radius:.75rem}}@media(min-width:1024px){.lg\:static{position:static}.lg\:z-auto{z-index:auto}.lg\:hidden{display:none}.lg\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:pt-6{padding-top:1.5rem}}