plum-cms 0.1.0

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 (178) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +20 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +252 -0
  5. data/app/assets/builds/tailwind.css +2 -0
  6. data/app/assets/images/plum-mark.svg +7 -0
  7. data/app/assets/images/table-needs-logo.svg +83 -0
  8. data/app/assets/javascripts/plum/application.js +15 -0
  9. data/app/assets/stylesheets/application.css +10 -0
  10. data/app/assets/stylesheets/plum/control_panel.css +2 -0
  11. data/app/assets/tailwind/application.css +47 -0
  12. data/app/controllers/plum/application_controller.rb +34 -0
  13. data/app/controllers/plum/cp/assets_controller.rb +84 -0
  14. data/app/controllers/plum/cp/base_controller.rb +45 -0
  15. data/app/controllers/plum/cp/content_types_controller.rb +59 -0
  16. data/app/controllers/plum/cp/dashboard_controller.rb +10 -0
  17. data/app/controllers/plum/cp/entries_controller.rb +240 -0
  18. data/app/controllers/plum/cp/form_definitions_controller.rb +105 -0
  19. data/app/controllers/plum/cp/form_submissions_controller.rb +26 -0
  20. data/app/controllers/plum/cp/globals_controller.rb +84 -0
  21. data/app/controllers/plum/cp/nav_items_controller.rb +68 -0
  22. data/app/controllers/plum/cp/nav_menus_controller.rb +56 -0
  23. data/app/controllers/plum/cp/site_settings_controller.rb +87 -0
  24. data/app/controllers/plum/cp/taxonomies_controller.rb +56 -0
  25. data/app/controllers/plum/cp/terms_controller.rb +55 -0
  26. data/app/controllers/plum/cp/theme_previews_controller.rb +27 -0
  27. data/app/controllers/plum/cp/themes_controller.rb +98 -0
  28. data/app/controllers/plum/form_submissions_controller.rb +44 -0
  29. data/app/controllers/plum/pages_controller.rb +161 -0
  30. data/app/controllers/plum/sessions_controller.rb +25 -0
  31. data/app/controllers/plum/theme_assets_controller.rb +14 -0
  32. data/app/helpers/application_helper.rb +2 -0
  33. data/app/helpers/markdown_helper.rb +21 -0
  34. data/app/helpers/plum/cp/assets_helper.rb +21 -0
  35. data/app/helpers/plum/cp/theme_settings_helper.rb +26 -0
  36. data/app/javascript/application.js +18 -0
  37. data/app/javascript/controllers/application.js +9 -0
  38. data/app/javascript/controllers/index.js +4 -0
  39. data/app/javascript/controllers/plum/blocks_editor_controller.js +275 -0
  40. data/app/javascript/controllers/plum/blueprint_controller.js +120 -0
  41. data/app/javascript/controllers/plum/form_fields_controller.js +84 -0
  42. data/app/javascript/controllers/plum/image_picker_controller.js +143 -0
  43. data/app/javascript/controllers/plum/theme_settings_controller.js +17 -0
  44. data/app/models/plum/application_record.rb +5 -0
  45. data/app/models/plum/asset.rb +69 -0
  46. data/app/models/plum/content_type.rb +35 -0
  47. data/app/models/plum/entry.rb +67 -0
  48. data/app/models/plum/entry_term.rb +8 -0
  49. data/app/models/plum/form_definition.rb +68 -0
  50. data/app/models/plum/form_submission.rb +76 -0
  51. data/app/models/plum/global.rb +24 -0
  52. data/app/models/plum/nav_item.rb +58 -0
  53. data/app/models/plum/nav_menu.rb +24 -0
  54. data/app/models/plum/site.rb +112 -0
  55. data/app/models/plum/site_scoped.rb +17 -0
  56. data/app/models/plum/site_setting.rb +35 -0
  57. data/app/models/plum/taxonomy.rb +24 -0
  58. data/app/models/plum/term.rb +23 -0
  59. data/app/models/plum/user.rb +16 -0
  60. data/app/services/plum/base_blocks.rb +161 -0
  61. data/app/services/plum/block_editor_config.rb +65 -0
  62. data/app/services/plum/block_library.rb +31 -0
  63. data/app/services/plum/builder_renderer.rb +63 -0
  64. data/app/services/plum/field_expander.rb +78 -0
  65. data/app/services/plum/form_renderer.rb +110 -0
  66. data/app/services/plum/liquid_context.rb +335 -0
  67. data/app/services/plum/liquid_filters.rb +58 -0
  68. data/app/services/plum/liquid_renderer.rb +49 -0
  69. data/app/services/plum/theme.rb +160 -0
  70. data/app/services/plum/theme_asset_path.rb +31 -0
  71. data/app/services/plum/theme_package_installer.rb +287 -0
  72. data/app/services/plum/theme_registry.rb +63 -0
  73. data/app/services/plum/theme_resolver.rb +55 -0
  74. data/app/services/plum/theme_settings_params.rb +54 -0
  75. data/app/themes/bagel-boy/assets/screenshot.svg +92 -0
  76. data/app/themes/bagel-boy/assets/theme.css +184 -0
  77. data/app/themes/bagel-boy/blocks/hours.liquid +4 -0
  78. data/app/themes/bagel-boy/blocks/menu_item.liquid +7 -0
  79. data/app/themes/bagel-boy/layouts/base.liquid +64 -0
  80. data/app/themes/bagel-boy/templates/collections/posts.liquid +32 -0
  81. data/app/themes/bagel-boy/templates/entries/_default.liquid +13 -0
  82. data/app/themes/bagel-boy/templates/entries/landing.liquid +4 -0
  83. data/app/themes/bagel-boy/templates/entries/pages.liquid +18 -0
  84. data/app/themes/bagel-boy/templates/entries/posts.liquid +8 -0
  85. data/app/themes/bagel-boy/templates/index.liquid +10 -0
  86. data/app/themes/bagel-boy/templates/search.liquid +28 -0
  87. data/app/themes/bagel-boy/theme.yml +47 -0
  88. data/app/themes/bagel-shop/assets/screenshot.svg +28 -0
  89. data/app/themes/bagel-shop/assets/theme.css +7 -0
  90. data/app/themes/bagel-shop/layouts/base.liquid +507 -0
  91. data/app/themes/bagel-shop/templates/entries/_default.liquid +15 -0
  92. data/app/themes/bagel-shop/templates/entries/pages.liquid +12 -0
  93. data/app/themes/bagel-shop/templates/entries/posts.liquid +19 -0
  94. data/app/themes/bagel-shop/templates/index.liquid +55 -0
  95. data/app/themes/bagel-shop/theme.yml +30 -0
  96. data/app/themes/default/assets/screenshot.svg +24 -0
  97. data/app/themes/default/assets/theme.css +89 -0
  98. data/app/themes/default/layouts/base.liquid +117 -0
  99. data/app/themes/default/templates/collections/_default.liquid +16 -0
  100. data/app/themes/default/templates/collections/posts.liquid +27 -0
  101. data/app/themes/default/templates/entries/_default.liquid +26 -0
  102. data/app/themes/default/templates/entries/landing.liquid +5 -0
  103. data/app/themes/default/templates/entries/pages.liquid +17 -0
  104. data/app/themes/default/templates/entries/posts.liquid +20 -0
  105. data/app/themes/default/templates/index.liquid +22 -0
  106. data/app/themes/default/templates/search.liquid +28 -0
  107. data/app/themes/default/templates/taxonomies/_default.liquid +25 -0
  108. data/app/themes/default/templates/taxonomies/_default_index.liquid +14 -0
  109. data/app/themes/default/theme.yml +23 -0
  110. data/app/views/layouts/plum/cp.html.erb +138 -0
  111. data/app/views/layouts/plum/session.html.erb +43 -0
  112. data/app/views/plum/cp/assets/_form.html.erb +44 -0
  113. data/app/views/plum/cp/assets/edit.html.erb +14 -0
  114. data/app/views/plum/cp/assets/index.html.erb +52 -0
  115. data/app/views/plum/cp/assets/new.html.erb +9 -0
  116. data/app/views/plum/cp/content_types/_form.html.erb +113 -0
  117. data/app/views/plum/cp/content_types/edit.html.erb +8 -0
  118. data/app/views/plum/cp/content_types/index.html.erb +48 -0
  119. data/app/views/plum/cp/content_types/new.html.erb +5 -0
  120. data/app/views/plum/cp/content_types/show.html.erb +59 -0
  121. data/app/views/plum/cp/dashboard/show.html.erb +50 -0
  122. data/app/views/plum/cp/entries/_form.html.erb +196 -0
  123. data/app/views/plum/cp/entries/edit.html.erb +9 -0
  124. data/app/views/plum/cp/entries/index.html.erb +51 -0
  125. data/app/views/plum/cp/entries/new.html.erb +5 -0
  126. data/app/views/plum/cp/form_definitions/_form.html.erb +90 -0
  127. data/app/views/plum/cp/form_definitions/edit.html.erb +11 -0
  128. data/app/views/plum/cp/form_definitions/index.html.erb +43 -0
  129. data/app/views/plum/cp/form_definitions/new.html.erb +5 -0
  130. data/app/views/plum/cp/form_definitions/show.html.erb +70 -0
  131. data/app/views/plum/cp/form_submissions/show.html.erb +20 -0
  132. data/app/views/plum/cp/globals/_form.html.erb +44 -0
  133. data/app/views/plum/cp/globals/edit.html.erb +11 -0
  134. data/app/views/plum/cp/globals/index.html.erb +41 -0
  135. data/app/views/plum/cp/globals/new.html.erb +5 -0
  136. data/app/views/plum/cp/globals/show.html.erb +14 -0
  137. data/app/views/plum/cp/nav_items/_form.html.erb +56 -0
  138. data/app/views/plum/cp/nav_items/edit.html.erb +11 -0
  139. data/app/views/plum/cp/nav_items/new.html.erb +6 -0
  140. data/app/views/plum/cp/nav_menus/_form.html.erb +33 -0
  141. data/app/views/plum/cp/nav_menus/_nav_item.html.erb +24 -0
  142. data/app/views/plum/cp/nav_menus/edit.html.erb +11 -0
  143. data/app/views/plum/cp/nav_menus/index.html.erb +41 -0
  144. data/app/views/plum/cp/nav_menus/new.html.erb +5 -0
  145. data/app/views/plum/cp/nav_menus/show.html.erb +24 -0
  146. data/app/views/plum/cp/shared/_icon.html.erb +39 -0
  147. data/app/views/plum/cp/shared/_image_picker.html.erb +38 -0
  148. data/app/views/plum/cp/site_settings/edit.html.erb +145 -0
  149. data/app/views/plum/cp/site_settings/show.html.erb +62 -0
  150. data/app/views/plum/cp/taxonomies/_form.html.erb +38 -0
  151. data/app/views/plum/cp/taxonomies/edit.html.erb +2 -0
  152. data/app/views/plum/cp/taxonomies/index.html.erb +38 -0
  153. data/app/views/plum/cp/taxonomies/new.html.erb +2 -0
  154. data/app/views/plum/cp/taxonomies/show.html.erb +46 -0
  155. data/app/views/plum/cp/terms/_form.html.erb +30 -0
  156. data/app/views/plum/cp/terms/edit.html.erb +2 -0
  157. data/app/views/plum/cp/terms/new.html.erb +2 -0
  158. data/app/views/plum/cp/themes/index.html.erb +160 -0
  159. data/app/views/plum/form_mailer/submission_notification.html.erb +13 -0
  160. data/app/views/plum/form_mailer/submission_notification.text.erb +7 -0
  161. data/app/views/plum/sessions/new.html.erb +20 -0
  162. data/compat/plum-cms.rb +1 -0
  163. data/config/locales/en.yml +31 -0
  164. data/config/plum_importmap.rb +4 -0
  165. data/config/plum_routes.rb +34 -0
  166. data/db/engine_migrate/20260101000001_create_plum_tables.rb +154 -0
  167. data/lib/generators/plum/install/install_generator.rb +54 -0
  168. data/lib/generators/plum/install/templates/plum_initializer.rb +37 -0
  169. data/lib/plum/configuration.rb +88 -0
  170. data/lib/plum/content_source.rb +45 -0
  171. data/lib/plum/content_source_context.rb +30 -0
  172. data/lib/plum/content_source_registry.rb +120 -0
  173. data/lib/plum/engine.rb +30 -0
  174. data/lib/plum/liquid_tags/form_tag.rb +17 -0
  175. data/lib/plum/version.rb +7 -0
  176. data/lib/plum.rb +11 -0
  177. data/vendor/javascript/lexxy.js +11859 -0
  178. metadata +405 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 164270b5787671af5e77e9d3f2caa722c45e85d22b8b2c92826c5a58f999e088
4
+ data.tar.gz: 278d8f8f0d2bb6106d0e901049def4cb4fba038cda38fbe20bf14ca15906cbe3
5
+ SHA512:
6
+ metadata.gz: f6c8883dfa5d5a1ff3d9b0f6fcd1b6a51db20a61e57014848c0cb10b47810cff108ba1b076f256a801ed5349ff0733a3fca9ce3555af1bc88efaea7b0a9152d1
7
+ data.tar.gz: b818f4afa3d41a69a60c24d3825e00fbe91e95d80bae174349caf82c4f59fd5e577b46676322553ab484a1fb66aa404910d8821a1a41bc21f7a9a1b1f6666425
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-07-31
4
+
5
+ The first public release of Plum, a Rails-native content management engine.
6
+
7
+ ### Included
8
+
9
+ - A Hotwire control panel for entries, content types, navigation, globals,
10
+ taxonomies, forms, assets, themes, and site settings.
11
+ - Structured fields, relationships, reusable blocks, and Lexxy rich text.
12
+ - Portable Liquid themes with public entries, collections, taxonomies, search,
13
+ forms, nested route prefixes, heading anchors, and tables of contents.
14
+ - Standalone and embedded operation with host-owned identity, authorization,
15
+ tenancy, and registered content sources.
16
+ - SQLite-first persistence with PostgreSQL compatibility.
17
+ - Configurable control-panel branding and theme package installation.
18
+
19
+ Plum 0.1 is intentionally pre-1.0. Public APIs may evolve as the project is
20
+ used in more Rails applications and content-first sites.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ben Simmons
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,252 @@
1
+ # Plum
2
+
3
+ Plum is a Rails-native CMS engine with a Hotwire control panel, Liquid themes,
4
+ and site-scoped content. It can run as a standalone Rails app or be mounted in a
5
+ larger Rails app.
6
+
7
+ ## Installation
8
+
9
+ Add Plum to your Gemfile:
10
+
11
+ ```ruby
12
+ gem "plum-cms", "~> 0.1.0"
13
+ ```
14
+
15
+ Then:
16
+
17
+ ```bash
18
+ bundle install
19
+ bin/rails generate plum:install
20
+ bin/rails active_storage:install # if not already installed
21
+ bin/rails db:migrate
22
+ bin/rails server
23
+ ```
24
+
25
+ Visit `/cms` (or your custom mount path) and create or seed an administrator.
26
+
27
+ Plum is pre-1.0 software. Pin the minor version in production and review the
28
+ changelog before upgrading.
29
+
30
+ ### Mount path
31
+
32
+ The default mount path is `/cms`. Change it during install:
33
+
34
+ ```bash
35
+ bin/rails generate plum:install --mount-path=/website
36
+ ```
37
+
38
+ Or mount the engine manually in `config/routes.rb`:
39
+
40
+ ```ruby
41
+ mount Plum::Engine, at: "/website"
42
+ ```
43
+
44
+ ## Embedding in a Host App
45
+
46
+ For apps like Table Needs where users already have accounts:
47
+
48
+ ```ruby
49
+ Plum.configure do |config|
50
+ config.current_site_resolver = ->(controller) { Current.restaurant.plum_site }
51
+ config.current_user_resolver = ->(controller) { Current.user }
52
+ config.authorize_with = :host
53
+ config.host_authorization_resolver = ->(controller) {
54
+ Current.user&.can_manage_website?(Current.restaurant)
55
+ }
56
+ end
57
+ ```
58
+
59
+ In host mode:
60
+ - Plum's login page is bypassed (returns 403 instead of redirect)
61
+ - Plum's role system is skipped — the host app owns authorization
62
+ - `current_user` doesn't need to be a `Plum::User`
63
+
64
+ Each customer gets one `Plum::Site`, typically through the polymorphic `owner`
65
+ association on `plum_sites`.
66
+
67
+ ## White-Label
68
+
69
+ The entire control panel can be rebranded:
70
+
71
+ ```ruby
72
+ Plum.configure do |config|
73
+ # Branding
74
+ config.cp_name = "Table Needs"
75
+ config.cp_subtitle = "Website"
76
+ config.cp_logo_path = "table-needs-logo.svg"
77
+
78
+ # Colors
79
+ config.cp_accent_color = "#2563eb"
80
+ config.cp_sidebar_bg = "#1e293b"
81
+ config.cp_sidebar_header_bg = "#0f172a"
82
+ config.cp_sidebar_text = "#cbd5e1"
83
+ config.cp_sidebar_muted = "#64748b"
84
+
85
+ # Back link to host app
86
+ config.cp_back_url = "/dashboard"
87
+ config.cp_back_label = "← Table Needs"
88
+ end
89
+ ```
90
+
91
+ All buttons, links, focus rings, sidebar, login page, and page titles follow
92
+ the configured brand. Zero Plum branding when white-labeled.
93
+
94
+ ## Content Sources
95
+
96
+ Host apps can inject live application data into Liquid templates without copying
97
+ it into Plum tables:
98
+
99
+ ```ruby
100
+ Plum.configure do |config|
101
+ config.register_content_source :restaurant, ->(context) { context.owner.to_liquid }
102
+ config.register_content_source :menu, "TableNeeds::PlumAdapters::Menu"
103
+ end
104
+ ```
105
+
106
+ Registered sources become top-level Liquid objects:
107
+
108
+ ```liquid
109
+ <h1>{{ restaurant.name }}</h1>
110
+ {% for item in menu.items %}
111
+ <p>{{ item.name }} — {{ item.price }}</p>
112
+ {% endfor %}
113
+ ```
114
+
115
+ This is the foundation for AI grounding — the same registry that makes content
116
+ sources available to templates can feed AI with live merchant data.
117
+
118
+ ## Field Types
119
+
120
+ Blueprint fields define the content model for each content type:
121
+
122
+ | Type | Description |
123
+ |------|-------------|
124
+ | `text` | Single-line text input |
125
+ | `textarea` | Multi-line text |
126
+ | `rich_text` | Lexxy rich text editor (images, formatting, tables) |
127
+ | `number` | Numeric input |
128
+ | `boolean` | Checkbox (true/false) |
129
+ | `date` | Date picker |
130
+ | `select` | Dropdown (requires `options` array) |
131
+ | `checkboxes` | Multi-select checkboxes (requires `options` array) |
132
+ | `color` | Native color picker |
133
+ | `url` | URL input |
134
+ | `image` | Image picker (Active Storage) |
135
+ | `relationship` | Link to another entry (optional `content_type` filter) |
136
+ | `taxonomy` | Term picker from a taxonomy (requires `taxonomy` handle) |
137
+ | `blocks` | Page builder sections |
138
+
139
+ ## Image Transforms
140
+
141
+ Images expose pre-built variant URLs via Active Storage:
142
+
143
+ ```liquid
144
+ <img src="{{ entry.data.hero_image.medium }}" alt="{{ entry.data.hero_image.alt_text }}">
145
+ ```
146
+
147
+ Available sizes: `thumb` (300x300 fill), `small` (640px), `medium` (1200px),
148
+ `large` (2000px), `url` (original). Requires `libvips` — falls back to the
149
+ original URL without it.
150
+
151
+ ## Taxonomies
152
+
153
+ Taxonomies (categories, tags) are managed in the CP under Taxonomies. Add a
154
+ `taxonomy` field to a content type's blueprint to tag entries with terms.
155
+
156
+ ```json
157
+ { "handle": "categories", "type": "taxonomy", "label": "Categories", "taxonomy": "categories" }
158
+ ```
159
+
160
+ Front-end pages are automatic:
161
+ - `/:taxonomy_slug` — lists all terms with entry counts
162
+ - `/:taxonomy_slug/:term_slug` — lists entries tagged with that term (paginated)
163
+
164
+ In templates:
165
+
166
+ ```liquid
167
+ {% for tag in entry.terms.categories %}
168
+ <a href="{{ tag.url }}">{{ tag.name }}</a>
169
+ {% endfor %}
170
+
171
+ {% for term in taxonomies.categories.terms %}
172
+ <a href="{{ term.url }}">{{ term.name }}</a>
173
+ {% endfor %}
174
+ ```
175
+
176
+ ## Collection Pages
177
+
178
+ Visiting `/:content_type_handle` (e.g. `/posts`) renders a paginated index of
179
+ all published entries for that content type. Themes provide collection templates
180
+ at `collections/{handle}.liquid` with a `_default.liquid` fallback.
181
+
182
+ Pagination variables: `pagination.current_page`, `pagination.total_pages`,
183
+ `pagination.previous_url`, `pagination.next_url`.
184
+
185
+ ## Search
186
+
187
+ `/search?q=term` searches entry titles and slugs. Themes provide a
188
+ `search.liquid` template with a search form and results.
189
+
190
+ ## Roles
191
+
192
+ Standalone mode has three roles: `viewer` (read-only CP), `editor` (manage
193
+ content), `admin` (full access including content types, settings, themes,
194
+ taxonomies). In embedded/host mode, roles are skipped entirely.
195
+
196
+ ## Rich Text
197
+
198
+ Rich text fields use [Lexxy](https://basecamp.github.io/lexxy/) (Basecamp's
199
+ editor built on Meta's Lexical). Supports bold, italic, strikethrough, headings,
200
+ lists, links, quotes, code blocks, tables, image uploads, and text colors.
201
+
202
+ Images uploaded through Lexxy use Active Storage Direct Upload and render on the
203
+ front-end as standard `<img>` tags. Text colors are resolved from Lexxy's CSS
204
+ variables to real color values at render time.
205
+
206
+ ## Globals and Navigation
207
+
208
+ Globals are site-wide JSON objects (company info, social links):
209
+
210
+ ```liquid
211
+ {{ globals.company.phone }}
212
+ ```
213
+
214
+ Navigation menus are managed in the CP:
215
+
216
+ ```liquid
217
+ {% for item in nav.main.items %}
218
+ <a href="{{ item.url }}">{{ item.label }}</a>
219
+ {% endfor %}
220
+ ```
221
+
222
+ ## Themes
223
+
224
+ Themes live in `app/themes/{handle}/` with:
225
+ - `theme.yml` — name, settings, block definitions
226
+ - `layouts/base.liquid` — site layout
227
+ - `templates/` — entry, collection, taxonomy, search templates
228
+ - `blocks/{handle}.liquid` — block partials
229
+ - `assets/` — CSS, images
230
+
231
+ Themes can be installed from a zip in the CP.
232
+
233
+ ## Database
234
+
235
+ Plum is SQLite-first but database-agnostic. All JSON is filtered in Ruby (no
236
+ DB-specific SQL), so it runs on PostgreSQL too. Test against Postgres:
237
+
238
+ ```bash
239
+ PLUM_TEST_DB=postgres bin/rails test
240
+ ```
241
+
242
+ ## Development
243
+
244
+ ```bash
245
+ git clone git@github.com:tableneeds/Plum.git && cd Plum
246
+ bin/setup
247
+ bin/rails server
248
+ ```
249
+
250
+ Login: `admin@example.com` / `password`
251
+
252
+ For Bagel Boy demo content: `bin/rails runner "load 'db/seeds/bagel_boy.rb'"`
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.2.1 | 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-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-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking: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}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, 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-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-900:oklch(38.1% .176 304.987);--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-white:#fff;--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--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);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-normal:0em;--tracking-wider:.05em;--leading-tight:1.25;--radius-md:.375rem;--radius-lg:.5rem;--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,ui-sans-serif, system-ui, 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{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{.plum-rich-text-content{outline:none;min-height:14rem}.plum-rich-text-content>*+*{margin-top:.75rem}.plum-rich-text-content h2{font-size:1.25rem;font-weight:700;line-height:1.3}.plum-rich-text-content ul{padding-left:1.5rem;list-style:outside}.plum-rich-text-content ol{padding-left:1.5rem;list-style:decimal}.plum-rich-text-content blockquote{color:#4b5563;border-left:.25rem solid #d1d5db;padding-left:1rem}.plum-rich-text-content a{color:#7c3aed;text-decoration:underline}.plum-rich-text-content p.is-editor-empty:first-child:before{color:#9ca3af;content:attr(data-placeholder);float:left;pointer-events:none;height:0}}@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-y-0{inset-block:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.left-0{left:calc(var(--spacing) * 0)}.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}}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing) * 1)}.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-28{margin-top:calc(var(--spacing) * 28)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.ml-2{margin-left:calc(var(--spacing) * 2)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-16{height:calc(var(--spacing) * 16)}.h-24{height:calc(var(--spacing) * 24)}.h-32{height:calc(var(--spacing) * 32)}.h-44{height:calc(var(--spacing) * 44)}.h-full{height:100%}.min-h-48{min-height:calc(var(--spacing) * 48)}.min-h-full{min-height:100%}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-9{width:calc(var(--spacing) * 9)}.w-12{width:calc(var(--spacing) * 12)}.w-14{width:calc(var(--spacing) * 14)}.w-16{width:calc(var(--spacing) * 16)}.w-32{width:calc(var(--spacing) * 32)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-full{min-width:100%}.flex-1{flex:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.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))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.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:calc(var(--spacing) * 1)}.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(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * 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)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.justify-self-start{justify-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-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-dashed{--tw-border-style:dashed;border-style:dashed}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-green-200{border-color:var(--color-green-200)}.border-purple-500{border-color:var(--color-purple-500)}.border-red-200{border-color:var(--color-red-200)}.border-red-300{border-color:var(--color-red-300)}.border-transparent{border-color:#0000}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.bg-\[\#F6F3EF\]{background-color:#f6f3ef}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-red-50{background-color:var(--color-red-50)}.bg-white{background-color:var(--color-white)}.bg-yellow-100{background-color:var(--color-yellow-100)}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-0{padding:calc(var(--spacing) * 0)}.p-1{padding:calc(var(--spacing) * 1)}.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:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-1{padding-top:calc(var(--spacing) * 1)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-64{padding-left:calc(var(--spacing) * 64)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.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-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-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-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.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-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-purple-600{color:var(--color-purple-600)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.text-yellow-800{color:var(--color-yellow-800)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.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)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.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,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-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:border-purple-400:hover{border-color:var(--color-purple-400)}.hover\:border-purple-500:hover{border-color:var(--color-purple-500)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:text-gray-700:hover{color:var(--color-gray-700)}.hover\:text-purple-900:hover{color:var(--color-purple-900)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-red-900:hover{color:var(--color-red-900)}.hover\:shadow-md:hover{--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)}}.focus\:border-purple-500:focus{border-color:var(--color-purple-500)}.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-purple-500:focus{--tw-ring-color:var(--color-purple-500)}.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\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:opacity-30:disabled{opacity:.3}@media (min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-start-3{grid-column-start:3}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing) * 8)}}}@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-divide-y-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-tracking{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}
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="plum-mark-title">
2
+ <title id="plum-mark-title">Plum CMS mark</title>
3
+ <rect width="64" height="64" rx="14" fill="#241B27"/>
4
+ <path d="M32 10.5v7.5" fill="none" stroke="#F7F2EA" stroke-width="2.8" stroke-linecap="round"/>
5
+ <path d="M20.5 17h23L50 34.5 32 56 14 34.5 20.5 17Z" fill="#8E3E63"/>
6
+ <path d="M27 24h10l3.5 10.3L32 45.5l-8.5-11.2L27 24Z" fill="#F7F2EA" opacity=".14"/>
7
+ </svg>
@@ -0,0 +1,83 @@
1
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
2
+ width="100%" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
3
+ <defs>
4
+ <clipPath id="tn-rounded-clip">
5
+ <rect x="0" y="0" width="512" height="512" rx="88" ry="88" />
6
+ </clipPath>
7
+ </defs>
8
+ <g clip-path="url(#tn-rounded-clip)">
9
+ <path fill="#3F75EE" opacity="1.000000" stroke="none"
10
+ d="
11
+ M343.000000,513.000000
12
+ C228.694443,513.000000 114.888878,513.000000 1.041656,513.000000
13
+ C1.041656,342.402740 1.041656,171.805481 1.041656,1.104111
14
+ C171.555573,1.104111 342.111206,1.104111 512.833435,1.104111
15
+ C512.833435,171.666534 512.833435,342.333252 512.833435,513.000000
16
+ C456.472137,513.000000 399.986053,513.000000 343.000000,513.000000
17
+ M361.727600,374.803467
18
+ C365.093353,378.666077 369.108063,380.071960 374.425720,379.974121
19
+ C393.751709,379.618439 413.088257,379.840363 432.420807,379.822205
20
+ C434.174622,379.820557 435.928314,379.659424 437.633484,379.574768
21
+ C437.633484,298.674988 437.633484,218.403503 437.633484,137.508774
22
+ C435.555359,137.508774 433.769592,137.508759 431.983856,137.508759
23
+ C412.484650,137.508698 392.983368,137.662018 373.487640,137.400925
24
+ C368.604431,137.335510 367.002319,138.395569 367.030518,143.669388
25
+ C367.268585,188.166336 367.166229,232.665100 367.166199,277.163330
26
+ C367.166199,279.049652 367.166199,280.935974 367.166199,283.720551
27
+ C365.277863,282.320465 364.341980,281.700867 363.488739,280.982971
28
+ C346.544037,266.726776 329.574066,252.500153 312.706177,238.153564
29
+ C309.906433,235.772324 307.113861,234.578049 303.318726,234.614212
30
+ C283.821289,234.799973 264.321106,234.700256 244.821884,234.700287
31
+ C242.887024,234.700302 240.952148,234.700302 238.823059,234.700302
32
+ C238.823059,283.359131 238.823059,331.275116 238.823059,379.486359
33
+ C262.023956,379.486359 284.909698,379.486359 308.415039,379.486359
34
+ C308.415039,362.599823 308.415039,346.048462 308.415039,328.348083
35
+ C326.624481,344.215607 343.905762,359.274353 361.727600,374.803467
36
+ M76.070198,169.500000
37
+ C76.070198,182.960419 76.070198,196.420822 76.070198,210.562225
38
+ C98.906937,210.562225 121.291428,210.562225 144.282928,210.562225
39
+ C144.282928,267.286041 144.282928,323.327332 144.282928,379.527649
40
+ C167.807648,379.527649 190.711868,379.527649 214.232285,379.527649
41
+ C214.232285,323.175079 214.232285,267.124512 214.232285,210.475922
42
+ C245.919739,210.475922 276.996582,210.475922 308.383850,210.475922
43
+ C308.383850,185.983521 308.383850,162.093231 308.383850,137.845779
44
+ C230.949844,137.845779 153.743088,137.845779 76.070206,137.845779
45
+ C76.070206,148.243515 76.070206,158.371765 76.070198,169.500000
46
+ z"/>
47
+ <path fill="#FEFEFF" opacity="1.000000" stroke="none"
48
+ d="
49
+ M361.457336,374.568268
50
+ C343.905762,359.274353 326.624481,344.215607 308.415039,328.348083
51
+ C308.415039,346.048462 308.415039,362.599823 308.415039,379.486359
52
+ C284.909698,379.486359 262.023956,379.486359 238.823059,379.486359
53
+ C238.823059,331.275116 238.823059,283.359131 238.823059,234.700302
54
+ C240.952148,234.700302 242.887024,234.700302 244.821884,234.700287
55
+ C264.321106,234.700256 283.821289,234.799973 303.318726,234.614212
56
+ C307.113861,234.578049 309.906433,235.772324 312.706177,238.153564
57
+ C329.574066,252.500153 346.544037,266.726776 363.488739,280.982971
58
+ C364.341980,281.700867 365.277863,282.320465 367.166199,283.720551
59
+ C367.166199,280.935974 367.166199,279.049652 367.166199,277.163330
60
+ C367.166229,232.665100 367.268585,188.166336 367.030518,143.669388
61
+ C367.002319,138.395569 368.604431,137.335510 373.487640,137.400925
62
+ C392.983368,137.662018 412.484650,137.508698 431.983856,137.508759
63
+ C433.769592,137.508759 435.555359,137.508774 437.633484,137.508774
64
+ C437.633484,218.403503 437.633484,298.674988 437.633484,379.574768
65
+ C435.928314,379.659424 434.174622,379.820557 432.420807,379.822205
66
+ C413.088257,379.840363 393.751709,379.618439 374.425720,379.974121
67
+ C369.108063,380.071960 365.093353,378.666077 361.457336,374.568268
68
+ z"/>
69
+ <path fill="#FEFFFF" opacity="1.000000" stroke="none"
70
+ d="
71
+ M76.070206,169.000000
72
+ C76.070206,158.371765 76.070206,148.243515 76.070206,137.845779
73
+ C153.743088,137.845779 230.949844,137.845779 308.383850,137.845779
74
+ C308.383850,162.093231 308.383850,185.983521 308.383850,210.475922
75
+ C276.996582,210.475922 245.919739,210.475922 214.232285,210.475922
76
+ C214.232285,267.124512 214.232285,323.175079 214.232285,379.527649
77
+ C190.711868,379.527649 167.807648,379.527649 144.282928,379.527649
78
+ C144.282928,323.327332 144.282928,267.286041 144.282928,210.562225
79
+ C121.291428,210.562225 98.906937,210.562225 76.070198,210.562225
80
+ C76.070198,196.420822 76.070198,182.960419 76.070206,169.000000
81
+ z"/>
82
+ </g>
83
+ </svg>
@@ -0,0 +1,15 @@
1
+ import "lexxy"
2
+
3
+ document.addEventListener("turbo:submit-start", (event) => {
4
+ event.target.querySelectorAll("lexxy-editor[data-hidden-field]").forEach((editor) => {
5
+ const hidden = document.getElementById(editor.dataset.hiddenField)
6
+ if (hidden) hidden.value = editor.value
7
+ })
8
+ })
9
+
10
+ document.addEventListener("submit", (event) => {
11
+ event.target.querySelectorAll("lexxy-editor[data-hidden-field]").forEach((editor) => {
12
+ const hidden = document.getElementById(editor.dataset.hiddenField)
13
+ if (hidden) hidden.value = editor.value
14
+ })
15
+ })
@@ -0,0 +1,10 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css.
3
+ *
4
+ * With Propshaft, assets are served efficiently without preprocessing steps. You can still include
5
+ * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
6
+ * cascading order, meaning styles declared later in the document or manifest will override earlier ones,
7
+ * depending on specificity.
8
+ *
9
+ * Consider organizing styles into separate files for maintainability.
10
+ */
@@ -0,0 +1,2 @@
1
+ /*! tailwindcss v4.2.1 | 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-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-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking: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-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}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, 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-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-500:oklch(63.7% .237 25.331);--color-red-700:oklch(50.5% .213 27.518);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-900:oklch(38.1% .176 304.987);--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-white:#fff;--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--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);--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-normal:0em;--tracking-wider:.05em;--leading-tight:1.25;--radius-md:.375rem;--radius-lg:.5rem;--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,ui-sans-serif, system-ui, 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{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{.plum-rich-text-content{outline:none;min-height:14rem}.plum-rich-text-content>*+*{margin-top:.75rem}.plum-rich-text-content h2{font-size:1.25rem;font-weight:700;line-height:1.3}.plum-rich-text-content ul{padding-left:1.5rem;list-style:outside}.plum-rich-text-content ol{padding-left:1.5rem;list-style:decimal}.plum-rich-text-content blockquote{color:#4b5563;border-left:.25rem solid #d1d5db;padding-left:1rem}.plum-rich-text-content a{color:#7c3aed;text-decoration:underline}.plum-rich-text-content p.is-editor-empty:first-child:before{color:#9ca3af;content:attr(data-placeholder);float:left;pointer-events:none;height:0}}@layer utilities{.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-y-0{inset-block:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.start\!{inset-inline-start:var(--spacing)!important}.end{inset-inline-end:var(--spacing)}.left-0{left:calc(var(--spacing) * 0)}.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}}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing) * 1)}.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-28{margin-top:calc(var(--spacing) * 28)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.ml-2{margin-left:calc(var(--spacing) * 2)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline\!{display:inline!important}.inline-flex{display:inline-flex}.table{display:table}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-12{height:calc(var(--spacing) * 12)}.h-16{height:calc(var(--spacing) * 16)}.h-44{height:calc(var(--spacing) * 44)}.h-full{height:100%}.min-h-64{min-height:calc(var(--spacing) * 64)}.min-h-full{min-height:100%}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-9{width:calc(var(--spacing) * 9)}.w-12{width:calc(var(--spacing) * 12)}.w-16{width:calc(var(--spacing) * 16)}.w-32{width:calc(var(--spacing) * 32)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-10{min-width:calc(var(--spacing) * 10)}.min-w-full{min-width:100%}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.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:calc(var(--spacing) * 1)}.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-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)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 3) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-x-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-gray-200>:not(:last-child)){border-color:var(--color-gray-200)}.justify-self-start{justify-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-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-dashed{--tw-border-style:dashed;border-style:dashed}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-900{border-color:var(--color-gray-900)}.border-green-200{border-color:var(--color-green-200)}.border-red-200{border-color:var(--color-red-200)}.border-red-300{border-color:var(--color-red-300)}.border-transparent{border-color:#0000}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.bg-\[\#1E1621\]{background-color:#1e1621}.bg-\[\#241B27\]{background-color:#241b27}.bg-\[\#F6F3EF\]{background-color:#f6f3ef}.bg-gray-50{background-color:var(--color-gray-50)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-900{background-color:var(--color-gray-900)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-purple-600{background-color:var(--color-purple-600)}.bg-red-50{background-color:var(--color-red-50)}.bg-white{background-color:var(--color-white)}.bg-white\/10{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.bg-white\/10{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.bg-yellow-100{background-color:var(--color-yellow-100)}.object-cover{object-fit:cover}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.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:calc(var(--spacing) * 1)}.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)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-64{padding-left:calc(var(--spacing) * 64)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.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-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-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-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-normal{--tw-tracking:var(--tracking-normal);letter-spacing:var(--tracking-normal)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#241B27\]{color:#241b27}.text-\[\#A8929F\]{color:#a8929f}.text-\[\#C9B7C2\]{color:#c9b7c2}.text-\[\#D9CBD4\]{color:#d9cbd4}.text-\[\#E6B3CB\]{color:#e6b3cb}.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-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-purple-600{color:var(--color-purple-600)}.text-red-500{color:var(--color-red-500)}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.text-yellow-800{color:var(--color-yellow-800)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.italic\!{font-style:italic!important}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.underline\!{text-decoration-line:underline!important}.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)}.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,)}.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-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media (hover:hover){.hover\:border-purple-400:hover{border-color:var(--color-purple-400)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-purple-700:hover{background-color:var(--color-purple-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab, var(--color-white) 10%, transparent)}}.hover\:text-purple-900:hover{color:var(--color-purple-900)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-white:hover{color:var(--color-white)}.hover\:shadow-md:hover{--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)}}.focus\:border-purple-500:focus{border-color:var(--color-purple-500)}.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-purple-500:focus{--tw-ring-color:var(--color-purple-500)}.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\:outline-none:focus{--tw-outline-style:none;outline-style:none}@media (min-width:40rem){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:col-start-3{grid-column-start:3}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing) * 8)}}}@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-divide-y-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-tracking{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-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}
@@ -0,0 +1,47 @@
1
+ @import "tailwindcss";
2
+
3
+ @layer components {
4
+ .plum-rich-text-content {
5
+ min-height: 14rem;
6
+ outline: none;
7
+ }
8
+
9
+ .plum-rich-text-content > * + * {
10
+ margin-top: 0.75rem;
11
+ }
12
+
13
+ .plum-rich-text-content h2 {
14
+ font-size: 1.25rem;
15
+ font-weight: 700;
16
+ line-height: 1.3;
17
+ }
18
+
19
+ .plum-rich-text-content ul {
20
+ list-style: disc;
21
+ padding-left: 1.5rem;
22
+ }
23
+
24
+ .plum-rich-text-content ol {
25
+ list-style: decimal;
26
+ padding-left: 1.5rem;
27
+ }
28
+
29
+ .plum-rich-text-content blockquote {
30
+ border-left: 0.25rem solid #d1d5db;
31
+ color: #4b5563;
32
+ padding-left: 1rem;
33
+ }
34
+
35
+ .plum-rich-text-content a {
36
+ color: #7c3aed;
37
+ text-decoration: underline;
38
+ }
39
+
40
+ .plum-rich-text-content p.is-editor-empty:first-child::before {
41
+ color: #9ca3af;
42
+ content: attr(data-placeholder);
43
+ float: left;
44
+ height: 0;
45
+ pointer-events: none;
46
+ }
47
+ }
@@ -0,0 +1,34 @@
1
+ module Plum
2
+ class ApplicationController < ::ApplicationController
3
+ helper_method :current_site, :current_user, :current_user_label, :logged_in?, :plum_managed_auth?
4
+
5
+ private
6
+
7
+ def current_site
8
+ @current_site ||= Plum.current_site(self)
9
+ end
10
+
11
+ def current_user
12
+ @current_user ||= Plum.current_user(self)
13
+ end
14
+
15
+ def current_user_label
16
+ user = current_user
17
+ return nil unless user
18
+
19
+ return user.first_name if user.respond_to?(:first_name) && user.first_name.present?
20
+ return user.name if user.respond_to?(:name) && user.name.present?
21
+ return user.email if user.respond_to?(:email) && user.email.present?
22
+
23
+ nil
24
+ end
25
+
26
+ def logged_in?
27
+ current_user.present?
28
+ end
29
+
30
+ def plum_managed_auth?
31
+ Plum.configuration.authorize_with == :plum
32
+ end
33
+ end
34
+ end