baldur 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +21 -0
  4. data/README.md +318 -0
  5. data/TODO.md +6 -0
  6. data/app/assets/javascripts/baldur/controllers/accordion_controller.js +148 -0
  7. data/app/assets/javascripts/baldur/controllers/alert_controller.js +209 -0
  8. data/app/assets/javascripts/baldur/controllers/date_field_controller.js +558 -0
  9. data/app/assets/javascripts/baldur/controllers/details_menu_controller.js +30 -0
  10. data/app/assets/javascripts/baldur/controllers/form_submit_controller.js +7 -0
  11. data/app/assets/javascripts/baldur/controllers/marketing_pricing_controller.js +47 -0
  12. data/app/assets/javascripts/baldur/controllers/marketing_tabs_controller.js +118 -0
  13. data/app/assets/javascripts/baldur/controllers/menu_select_controller.js +401 -0
  14. data/app/assets/javascripts/baldur/controllers/mobile_sidebar_controller.js +13 -0
  15. data/app/assets/javascripts/baldur/controllers/modal_controller.js +149 -0
  16. data/app/assets/javascripts/baldur/controllers/panel_right_controller.js +1 -0
  17. data/app/assets/javascripts/baldur/controllers/panel_secondary_controller.js +129 -0
  18. data/app/assets/javascripts/baldur/controllers/segmented_tabs_controller.js +38 -0
  19. data/app/assets/javascripts/baldur/controllers/sidebar_controller.js +77 -0
  20. data/app/assets/javascripts/baldur/controllers/smooth_scroll_controller.js +29 -0
  21. data/app/assets/javascripts/baldur/controllers/snackbar_controller.js +158 -0
  22. data/app/assets/javascripts/baldur/controllers/table_disclosure_controller.js +46 -0
  23. data/app/assets/javascripts/baldur/controllers/theme_controller.js +90 -0
  24. data/app/assets/javascripts/baldur/controllers/tooltip_controller.js +136 -0
  25. data/app/assets/javascripts/baldur/lib/animation-helpers.js +56 -0
  26. data/app/assets/javascripts/baldur/lib/dom-helpers.js +80 -0
  27. data/app/assets/javascripts/baldur/lib/field-validation-helpers.js +36 -0
  28. data/app/assets/javascripts/baldur/lib/focus-management.js +89 -0
  29. data/app/assets/javascripts/baldur/lib/formatting-helpers.js +100 -0
  30. data/app/assets/javascripts/baldur/lib/lucide.js +20 -0
  31. data/app/assets/javascripts/baldur/lib/snackbar.js +50 -0
  32. data/app/assets/javascripts/baldur/lib/storage-helpers.js +50 -0
  33. data/app/assets/stylesheets/baldur/application/components/alert.css +226 -0
  34. data/app/assets/stylesheets/baldur/application/components/app_bar.css +41 -0
  35. data/app/assets/stylesheets/baldur/application/components/button.css +173 -0
  36. data/app/assets/stylesheets/baldur/application/components/card.css +63 -0
  37. data/app/assets/stylesheets/baldur/application/components/chart.css +40 -0
  38. data/app/assets/stylesheets/baldur/application/components/chip.css +51 -0
  39. data/app/assets/stylesheets/baldur/application/components/dialog.css +81 -0
  40. data/app/assets/stylesheets/baldur/application/components/forms.css +624 -0
  41. data/app/assets/stylesheets/baldur/application/components/layout.css +2 -0
  42. data/app/assets/stylesheets/baldur/application/components/list.css +15 -0
  43. data/app/assets/stylesheets/baldur/application/components/menu.css +300 -0
  44. data/app/assets/stylesheets/baldur/application/components/panel-right.css +1 -0
  45. data/app/assets/stylesheets/baldur/application/components/panel-secondary.css +71 -0
  46. data/app/assets/stylesheets/baldur/application/components/progress.css +84 -0
  47. data/app/assets/stylesheets/baldur/application/components/segmented-buttons.css +117 -0
  48. data/app/assets/stylesheets/baldur/application/components/settings-nav.css +84 -0
  49. data/app/assets/stylesheets/baldur/application/components/sidebar.css +123 -0
  50. data/app/assets/stylesheets/baldur/application/components/snackbar.css +179 -0
  51. data/app/assets/stylesheets/baldur/application/components/stepper.css +124 -0
  52. data/app/assets/stylesheets/baldur/application/components/switch.css +105 -0
  53. data/app/assets/stylesheets/baldur/application/components/table.css +331 -0
  54. data/app/assets/stylesheets/baldur/application/components/timeline.css +184 -0
  55. data/app/assets/stylesheets/baldur/application/components/utilities.css +180 -0
  56. data/app/assets/stylesheets/baldur/application/global.css +125 -0
  57. data/app/assets/stylesheets/baldur/application/marketing/layout.css +36 -0
  58. data/app/assets/stylesheets/baldur/application/motion.css +125 -0
  59. data/app/assets/stylesheets/baldur/application/theme.css +329 -0
  60. data/app/assets/stylesheets/baldur/theme/dark.css +90 -0
  61. data/app/assets/stylesheets/baldur/theme/light.css +82 -0
  62. data/app/assets/stylesheets/baldur.css +27 -0
  63. data/app/assets/stylesheets/baldur_panel_right.css +1 -0
  64. data/app/assets/stylesheets/baldur_panel_secondary.css +1 -0
  65. data/app/assets/tailwind/baldur/engine.css +5 -0
  66. data/app/helpers/baldur/compatibility/ui_aliases.rb +7 -0
  67. data/app/helpers/baldur/marketing_helper.rb +121 -0
  68. data/app/helpers/baldur/optional/auth_page_helper.rb +17 -0
  69. data/app/helpers/baldur/optional/google_auth_helper.rb +16 -0
  70. data/app/helpers/baldur/optional/panel_right_helper.rb +7 -0
  71. data/app/helpers/baldur/optional/panel_secondary_helper.rb +26 -0
  72. data/app/helpers/baldur/render_helper.rb +13 -0
  73. data/app/helpers/baldur/ui_helper.rb +217 -0
  74. data/app/helpers/baldur/ui_helper_feedback.rb +93 -0
  75. data/app/helpers/baldur/ui_helper_forms.rb +230 -0
  76. data/app/helpers/baldur/ui_helper_unavailable.rb +98 -0
  77. data/app/views/baldur/components/_accordion.html.erb +30 -0
  78. data/app/views/baldur/components/_action_row.html.erb +6 -0
  79. data/app/views/baldur/components/_alert.html.erb +61 -0
  80. data/app/views/baldur/components/_badge.html.erb +25 -0
  81. data/app/views/baldur/components/_button.html.erb +81 -0
  82. data/app/views/baldur/components/_card.html.erb +40 -0
  83. data/app/views/baldur/components/_chart_card.html.erb +42 -0
  84. data/app/views/baldur/components/_checkbox.html.erb +27 -0
  85. data/app/views/baldur/components/_date_field.html.erb +43 -0
  86. data/app/views/baldur/components/_google_sign_in_button.html.erb +1 -0
  87. data/app/views/baldur/components/_kebab_menu.html.erb +36 -0
  88. data/app/views/baldur/components/_kpi.html.erb +45 -0
  89. data/app/views/baldur/components/_menu_select.html.erb +78 -0
  90. data/app/views/baldur/components/_modal.html.erb +54 -0
  91. data/app/views/baldur/components/_pagination.html.erb +61 -0
  92. data/app/views/baldur/components/_segmented_buttons.html.erb +51 -0
  93. data/app/views/baldur/components/_settings_nav.html.erb +41 -0
  94. data/app/views/baldur/components/_snackbar.html.erb +42 -0
  95. data/app/views/baldur/components/_snackbar_stack.html.erb +13 -0
  96. data/app/views/baldur/components/_stepper.html.erb +39 -0
  97. data/app/views/baldur/components/_table.html.erb +117 -0
  98. data/app/views/baldur/components/_table_card.html.erb +86 -0
  99. data/app/views/baldur/components/_table_footer.html.erb +68 -0
  100. data/app/views/baldur/components/_text_field.html.erb +33 -0
  101. data/app/views/baldur/components/_tooltip.html.erb +73 -0
  102. data/app/views/baldur/marketing/_cta_banner.html.erb +20 -0
  103. data/app/views/baldur/marketing/_faq_section.html.erb +37 -0
  104. data/app/views/baldur/marketing/_features_section.html.erb +67 -0
  105. data/app/views/baldur/marketing/_footer.html.erb +38 -0
  106. data/app/views/baldur/marketing/_hero_section.html.erb +259 -0
  107. data/app/views/baldur/marketing/_pricing_tables.html.erb +99 -0
  108. data/app/views/baldur/marketing/_testimonials_section.html.erb +80 -0
  109. data/app/views/baldur/marketing/_top_nav.html.erb +28 -0
  110. data/app/views/baldur/optional/_auth_page.html.erb +21 -0
  111. data/app/views/baldur/optional/_google_sign_in_button.html.erb +19 -0
  112. data/app/views/baldur/optional/_panel_right.html.erb +1 -0
  113. data/app/views/baldur/optional/_panel_secondary.html.erb +34 -0
  114. data/baldur.gemspec +30 -0
  115. data/config/importmap.rb +2 -0
  116. data/lib/baldur/configuration.rb +24 -0
  117. data/lib/baldur/engine.rb +10 -0
  118. data/lib/baldur/version.rb +3 -0
  119. data/lib/baldur.rb +17 -0
  120. data/lib/generators/baldur/install/install_generator.rb +113 -0
  121. data/lib/generators/baldur/install/templates/baldur_initializer.rb +19 -0
  122. data/lib/generators/baldur/install/templates/fonts.css +14 -0
  123. data/lib/generators/baldur/install/templates/theme.css +27 -0
  124. data/lib/generators/baldur/install/templates/ui_helper.rb +4 -0
  125. data/lib/generators/baldur/install_google_auth/install_google_auth_generator.rb +15 -0
  126. data/lib/generators/baldur/install_panel_right/install_panel_right_generator.rb +9 -0
  127. data/lib/generators/baldur/install_panel_secondary/install_panel_secondary_generator.rb +21 -0
  128. data/script/verify_host_install +111 -0
  129. data/test/gemspec_test.rb +11 -0
  130. data/test/install_generator_test.rb +35 -0
  131. data/test/install_panel_secondary_generator_test.rb +21 -0
  132. data/test/marketing_helper_test.rb +38 -0
  133. data/test/run_all.rb +3 -0
  134. data/test/test_helper.rb +9 -0
  135. data/test/tmp/install_generator/app/assets/stylesheets/fonts.css +14 -0
  136. data/test/tmp/install_generator/app/assets/stylesheets/theme.css +27 -0
  137. data/test/tmp/install_generator/app/assets/tailwind/application.css +4 -0
  138. data/test/tmp/install_generator/app/helpers/ui_helper.rb +4 -0
  139. data/test/tmp/install_generator/app/javascript/controllers/accordion_controller.js +1 -0
  140. data/test/tmp/install_generator/app/javascript/controllers/date_field_controller.js +1 -0
  141. data/test/tmp/install_generator/app/javascript/controllers/details_menu_controller.js +1 -0
  142. data/test/tmp/install_generator/app/javascript/controllers/form_submit_controller.js +1 -0
  143. data/test/tmp/install_generator/app/javascript/controllers/marketing_pricing_controller.js +1 -0
  144. data/test/tmp/install_generator/app/javascript/controllers/marketing_tabs_controller.js +1 -0
  145. data/test/tmp/install_generator/app/javascript/controllers/menu_select_controller.js +1 -0
  146. data/test/tmp/install_generator/app/javascript/controllers/modal_controller.js +1 -0
  147. data/test/tmp/install_generator/app/javascript/controllers/segmented_tabs_controller.js +1 -0
  148. data/test/tmp/install_generator/app/javascript/controllers/sidebar_controller.js +1 -0
  149. data/test/tmp/install_generator/app/javascript/controllers/smooth_scroll_controller.js +1 -0
  150. data/test/tmp/install_generator/app/javascript/controllers/snackbar_controller.js +1 -0
  151. data/test/tmp/install_generator/app/javascript/controllers/theme_controller.js +1 -0
  152. data/test/tmp/install_generator/app/javascript/controllers/tooltip_controller.js +1 -0
  153. data/test/tmp/install_generator/app/javascript/lib/animation-helpers.js +1 -0
  154. data/test/tmp/install_generator/app/javascript/lib/dom-helpers.js +1 -0
  155. data/test/tmp/install_generator/app/javascript/lib/field-validation-helpers.js +1 -0
  156. data/test/tmp/install_generator/app/javascript/lib/focus-management.js +1 -0
  157. data/test/tmp/install_generator/app/javascript/lib/formatting-helpers.js +1 -0
  158. data/test/tmp/install_generator/app/javascript/lib/snackbar.js +1 -0
  159. data/test/tmp/install_generator/app/javascript/lib/storage-helpers.js +1 -0
  160. data/test/tmp/install_generator/config/initializers/baldur.rb +19 -0
  161. data/test/tmp/install_panel_secondary_generator/app/assets/tailwind/application.css +2 -0
  162. data/test/tmp/install_panel_secondary_generator/app/helpers/panel_secondary_helper.rb +3 -0
  163. data/test/tmp/install_panel_secondary_generator/app/javascript/controllers/panel_secondary_controller.js +1 -0
  164. metadata +259 -0
@@ -0,0 +1,259 @@
1
+ <%
2
+ raise ArgumentError, "Unsupported marketing hero variant" unless variant.to_sym == :solar_system
3
+ hero_style_nonce = content_security_policy_nonce if respond_to?(:content_security_policy_nonce)
4
+ %>
5
+ <style nonce="<%= hero_style_nonce %>">
6
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-callout,
7
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-source,
8
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-callout,
9
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-source {
10
+ position: absolute;
11
+ transform: translate(-50%, -50%);
12
+ }
13
+
14
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-callout,
15
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-callout {
16
+ white-space: nowrap;
17
+ border-radius: 9999px;
18
+ background-color: var(--surface-tonal-2);
19
+ color: var(--color-on-surface);
20
+ font-weight: 500;
21
+ letter-spacing: 0.02em;
22
+ }
23
+
24
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-callout {
25
+ border: 1px solid var(--color-outline);
26
+ padding: 0.5rem 1rem;
27
+ font-size: 0.75rem;
28
+ box-shadow: var(--elev-1);
29
+ }
30
+
31
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-callout {
32
+ border: 1px solid color-mix(in srgb, var(--color-outline) 50%, transparent);
33
+ padding: 0.5rem 0.75rem;
34
+ font-size: 10px;
35
+ text-align: center;
36
+ }
37
+
38
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-callout--1 { left: 22%; top: 28%; }
39
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-callout--2 { left: 50%; top: 0%; }
40
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-callout--3 { left: 80%; top: 32%; }
41
+
42
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-source--1 { left: 18%; top: 12%; }
43
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-source--2 { left: 2%; top: 36%; }
44
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-source--3 { left: 30%; top: 40%; }
45
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-source--4 { left: 41%; top: 16%; }
46
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-source--5 { left: 82%; top: 12%; }
47
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-source--6 { left: 99%; top: 40%; }
48
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-source--7 { left: 72%; top: 42%; }
49
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__desktop-source--8 { left: 70%; top: 20%; }
50
+
51
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-callout--1 { left: 18%; top: 31%; }
52
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-callout--2 { left: 50%; top: 9%; }
53
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-callout--3 { left: 81%; top: 26%; }
54
+
55
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-source--1 { left: 18%; top: 18%; }
56
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-source--2 { left: 10%; top: 43%; }
57
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-source--3 { left: 27%; top: 52%; }
58
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-source--4 { left: 40%; top: 20%; }
59
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-source--5 { left: 82%; top: 18%; }
60
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-source--6 { left: 90%; top: 43%; }
61
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-source--7 { left: 73%; top: 52%; }
62
+ .marketing-hero[data-variant="solar_system"] .marketing-hero__mobile-source--8 { left: 60%; top: 25%; }
63
+ </style>
64
+ <section id="<%= id %>" class="<%= ["marketing-hero bg-surface px-12 sm:px-12", local_assigns[:classes]].compact.join(" ") %>" data-variant="<%= variant %>">
65
+ <div class="mx-auto max-w-6xl pt-24 text-center">
66
+ <h2 class="mx-auto max-w-4xl text-4xl text-on-surface md:text-6xl"><%= headline %></h2>
67
+ <p class="mx-auto mt-6 max-w-3xl text-lg text-soft"><%= body %></p>
68
+ <div class="mt-8 flex flex-col items-center gap-4">
69
+ <div class="flex flex-wrap items-center justify-center gap-4">
70
+ <%= ui_button(**primary_action) %>
71
+ <%= ui_button(**secondary_action) if secondary_action.present? %>
72
+ </div>
73
+ <% if supporting_action.present? %>
74
+ <%= link_to supporting_action.fetch(:href),
75
+ class: "mt-1 inline-flex items-center gap-2 text-sm font-semibold text-primary hover:text-primary",
76
+ data: supporting_action[:data] do %>
77
+ <%= ui_icon(supporting_action[:icon].presence || "play", class_name: "h-4 w-4") %>
78
+ <span><%= supporting_action.fetch(:label) %></span>
79
+ <% end %>
80
+ <% end %>
81
+ </div>
82
+
83
+ <div class="relative mt-8 hidden h-[26rem] overflow-hidden md:block">
84
+ <div class="absolute bottom-[-22rem] left-1/2 h-[44rem] w-[44rem] -translate-x-1/2">
85
+ <div class="pointer-events-none absolute inset-0" aria-hidden="true">
86
+ <div class="absolute inset-[12rem] rounded-full border border-outline"></div>
87
+ <div class="absolute inset-[6rem] rounded-full border border-outline"></div>
88
+ <div class="absolute inset-0 rounded-full border border-outline"></div>
89
+ </div>
90
+
91
+ <% if (callout = Array(callouts)[0]) %>
92
+ <div class="marketing-hero__desktop-callout marketing-hero__desktop-callout--1">
93
+ <%= callout[:label] %>
94
+ </div>
95
+ <% end %>
96
+ <% if (callout = Array(callouts)[1]) %>
97
+ <div class="marketing-hero__desktop-callout marketing-hero__desktop-callout--2">
98
+ <%= callout[:label] %>
99
+ </div>
100
+ <% end %>
101
+ <% if (callout = Array(callouts)[2]) %>
102
+ <div class="marketing-hero__desktop-callout marketing-hero__desktop-callout--3">
103
+ <%= callout[:label] %>
104
+ </div>
105
+ <% end %>
106
+
107
+ <% if (source = Array(orbit_sources)[0]) %>
108
+ <div class="marketing-hero__desktop-source marketing-hero__desktop-source--1">
109
+ <div class="flex h-[4.5rem] w-[4.5rem] items-center justify-center rounded-[22px] border border-outline-variant bg-surface-high p-4 shadow-[var(--elev-1)]">
110
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-9 w-auto object-contain" %>
111
+ </div>
112
+ </div>
113
+ <% end %>
114
+ <% if (source = Array(orbit_sources)[1]) %>
115
+ <div class="marketing-hero__desktop-source marketing-hero__desktop-source--2">
116
+ <div class="flex h-[4.5rem] w-[4.5rem] items-center justify-center rounded-[22px] border border-outline-variant bg-surface-high p-4 shadow-[var(--elev-1)]">
117
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-9 w-auto object-contain" %>
118
+ </div>
119
+ </div>
120
+ <% end %>
121
+ <% if (source = Array(orbit_sources)[2]) %>
122
+ <div class="marketing-hero__desktop-source marketing-hero__desktop-source--3">
123
+ <div class="flex h-[4.5rem] w-[4.5rem] items-center justify-center rounded-[22px] border border-outline-variant bg-surface-high p-4 shadow-[var(--elev-1)]">
124
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-9 w-auto object-contain" %>
125
+ </div>
126
+ </div>
127
+ <% end %>
128
+ <% if (source = Array(orbit_sources)[3]) %>
129
+ <div class="marketing-hero__desktop-source marketing-hero__desktop-source--4">
130
+ <div class="flex h-[4.5rem] w-[4.5rem] items-center justify-center rounded-[22px] border border-outline-variant bg-surface-high p-4 shadow-[var(--elev-1)]">
131
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-9 w-auto object-contain" %>
132
+ </div>
133
+ </div>
134
+ <% end %>
135
+ <% if (source = Array(orbit_sources)[4]) %>
136
+ <div class="marketing-hero__desktop-source marketing-hero__desktop-source--5">
137
+ <div class="flex h-[4.5rem] w-[4.5rem] items-center justify-center rounded-[22px] border border-outline-variant bg-surface-high p-4 shadow-[var(--elev-1)]">
138
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-9 w-auto object-contain" %>
139
+ </div>
140
+ </div>
141
+ <% end %>
142
+ <% if (source = Array(orbit_sources)[5]) %>
143
+ <div class="marketing-hero__desktop-source marketing-hero__desktop-source--6">
144
+ <div class="flex h-[4.5rem] w-[4.5rem] items-center justify-center rounded-[22px] border border-outline-variant bg-surface-high p-4 shadow-[var(--elev-1)]">
145
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-9 w-auto object-contain" %>
146
+ </div>
147
+ </div>
148
+ <% end %>
149
+ <% if (source = Array(orbit_sources)[6]) %>
150
+ <div class="marketing-hero__desktop-source marketing-hero__desktop-source--7">
151
+ <div class="flex h-[4.5rem] w-[4.5rem] items-center justify-center rounded-[22px] border border-outline-variant bg-surface-high p-4 shadow-[var(--elev-1)]">
152
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-9 w-auto object-contain" %>
153
+ </div>
154
+ </div>
155
+ <% end %>
156
+ <% if (source = Array(orbit_sources)[7]) %>
157
+ <div class="marketing-hero__desktop-source marketing-hero__desktop-source--8">
158
+ <div class="flex h-[4.5rem] w-[4.5rem] items-center justify-center rounded-[22px] border border-outline-variant bg-surface-high p-4 shadow-[var(--elev-1)]">
159
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-9 w-auto object-contain" %>
160
+ </div>
161
+ </div>
162
+ <% end %>
163
+
164
+ <div class="absolute left-1/2 top-[48%] flex h-36 w-36 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-outline bg-surface shadow-[var(--elev-2)]">
165
+ <%= image_tag centerpiece_image.fetch(:src), alt: centerpiece_image.fetch(:alt), class: "relative -top-10 h-32 w-32 rounded-[2rem] object-cover" %>
166
+ </div>
167
+ </div>
168
+ </div>
169
+
170
+ <div class="mt-6 md:hidden">
171
+ <div class="relative mx-auto h-[24rem] max-w-sm overflow-hidden">
172
+ <div class="absolute bottom-[-12rem] left-1/2 h-[24rem] w-[24rem] -translate-x-1/2">
173
+ <div class="pointer-events-none absolute inset-0" aria-hidden="true">
174
+ <div class="absolute inset-[7.5rem] rounded-full border border-outline-variant/14"></div>
175
+ <div class="absolute inset-[3.75rem] rounded-full border border-outline-variant/10"></div>
176
+ <div class="absolute inset-0 rounded-full border border-outline-variant/8"></div>
177
+ </div>
178
+
179
+ <% if (callout = Array(callouts)[0]) %>
180
+ <div class="marketing-hero__mobile-callout marketing-hero__mobile-callout--1">
181
+ <%= callout[:label] %>
182
+ </div>
183
+ <% end %>
184
+ <% if (callout = Array(callouts)[1]) %>
185
+ <div class="marketing-hero__mobile-callout marketing-hero__mobile-callout--2">
186
+ <%= callout[:label] %>
187
+ </div>
188
+ <% end %>
189
+ <% if (callout = Array(callouts)[2]) %>
190
+ <div class="marketing-hero__mobile-callout marketing-hero__mobile-callout--3">
191
+ <%= callout[:label] %>
192
+ </div>
193
+ <% end %>
194
+
195
+ <% if (source = Array(orbit_sources)[0]) %>
196
+ <div class="marketing-hero__mobile-source marketing-hero__mobile-source--1">
197
+ <div class="flex h-14 w-14 items-center justify-center rounded-2xl border border-outline-variant/60 bg-surface-high p-3 shadow-[var(--elev-1)]">
198
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-8 w-auto object-contain" %>
199
+ </div>
200
+ </div>
201
+ <% end %>
202
+ <% if (source = Array(orbit_sources)[1]) %>
203
+ <div class="marketing-hero__mobile-source marketing-hero__mobile-source--2">
204
+ <div class="flex h-14 w-14 items-center justify-center rounded-2xl border border-outline-variant/60 bg-surface-high p-3 shadow-[var(--elev-1)]">
205
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-8 w-auto object-contain" %>
206
+ </div>
207
+ </div>
208
+ <% end %>
209
+ <% if (source = Array(orbit_sources)[2]) %>
210
+ <div class="marketing-hero__mobile-source marketing-hero__mobile-source--3">
211
+ <div class="flex h-14 w-14 items-center justify-center rounded-2xl border border-outline-variant/60 bg-surface-high p-3 shadow-[var(--elev-1)]">
212
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-8 w-auto object-contain" %>
213
+ </div>
214
+ </div>
215
+ <% end %>
216
+ <% if (source = Array(orbit_sources)[3]) %>
217
+ <div class="marketing-hero__mobile-source marketing-hero__mobile-source--4">
218
+ <div class="flex h-14 w-14 items-center justify-center rounded-2xl border border-outline-variant/60 bg-surface-high p-3 shadow-[var(--elev-1)]">
219
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-8 w-auto object-contain" %>
220
+ </div>
221
+ </div>
222
+ <% end %>
223
+ <% if (source = Array(orbit_sources)[4]) %>
224
+ <div class="marketing-hero__mobile-source marketing-hero__mobile-source--5">
225
+ <div class="flex h-14 w-14 items-center justify-center rounded-2xl border border-outline-variant/60 bg-surface-high p-3 shadow-[var(--elev-1)]">
226
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-8 w-auto object-contain" %>
227
+ </div>
228
+ </div>
229
+ <% end %>
230
+ <% if (source = Array(orbit_sources)[5]) %>
231
+ <div class="marketing-hero__mobile-source marketing-hero__mobile-source--6">
232
+ <div class="flex h-14 w-14 items-center justify-center rounded-2xl border border-outline-variant/60 bg-surface-high p-3 shadow-[var(--elev-1)]">
233
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-8 w-auto object-contain" %>
234
+ </div>
235
+ </div>
236
+ <% end %>
237
+ <% if (source = Array(orbit_sources)[6]) %>
238
+ <div class="marketing-hero__mobile-source marketing-hero__mobile-source--7">
239
+ <div class="flex h-14 w-14 items-center justify-center rounded-2xl border border-outline-variant/60 bg-surface-high p-3 shadow-[var(--elev-1)]">
240
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-8 w-auto object-contain" %>
241
+ </div>
242
+ </div>
243
+ <% end %>
244
+ <% if (source = Array(orbit_sources)[7]) %>
245
+ <div class="marketing-hero__mobile-source marketing-hero__mobile-source--8">
246
+ <div class="flex h-14 w-14 items-center justify-center rounded-2xl border border-outline-variant/60 bg-surface-high p-3 shadow-[var(--elev-1)]">
247
+ <%= image_tag source.fetch(:asset_path), alt: "#{source.fetch(:name)} logo", class: "max-h-8 w-auto object-contain" %>
248
+ </div>
249
+ </div>
250
+ <% end %>
251
+
252
+ <div class="absolute left-1/2 top-[45%] flex h-28 w-28 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-primary/14 bg-surface shadow-[var(--elev-2)]">
253
+ <%= image_tag centerpiece_image.fetch(:src), alt: centerpiece_image.fetch(:alt), class: "relative -top-1.5 h-[4.5rem] w-[4.5rem] rounded-[1.75rem] object-cover" %>
254
+ </div>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ </section>
@@ -0,0 +1,99 @@
1
+ <%
2
+ resolved_billing_options = Array(billing_options)
3
+ initial_billing = resolved_billing_options.find { |option| option[:selected] }&.dig(:value) || resolved_billing_options.first&.dig(:value) || "monthly"
4
+ resolved_billing_options = resolved_billing_options.map do |option|
5
+ option_data = option.fetch(:data, {}).symbolize_keys
6
+
7
+ option.merge(
8
+ selected: option[:value] == initial_billing,
9
+ data: option_data.except(:action).merge(
10
+ action: ui_marketing_action_string("marketing-pricing#select", option_data[:action]),
11
+ marketing_pricing_target: "billingOption"
12
+ )
13
+ )
14
+ end
15
+ %>
16
+ <section class="<%= ["marketing-pricing-tables bg-surface px-6 py-20 sm:px-12", local_assigns[:classes]].compact.join(" ") %>" data-controller="marketing-pricing" data-marketing-pricing-billing-value="<%= initial_billing %>">
17
+ <div class="mx-auto max-w-6xl">
18
+ <div class="mx-auto max-w-3xl text-center">
19
+ <p class="text-sm font-semibold uppercase tracking-wide text-muted"><%= eyebrow %></p>
20
+ <h1 class="mt-4 text-4xl md:text-6xl"><%= headline %></h1>
21
+ <p class="mt-6 text-lg text-soft"><%= subheadline %></p>
22
+ <div class="mt-8 flex justify-center">
23
+ <%= ui_segmented_buttons(aria_label: "Billing period", items: resolved_billing_options) %>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="mt-12 grid gap-6 lg:grid-cols-3">
28
+ <% Array(plans).each do |plan| %>
29
+ <% monthly = plan.dig(:prices, :monthly) %>
30
+ <% yearly = plan.dig(:prices, :yearly) %>
31
+ <% card_classes = [
32
+ "flex h-full flex-col rounded-[2rem] border p-6",
33
+ plan[:highlight_badge].present? ? "border-primary bg-primary/6 shadow-[var(--elev-2)]" : "border-outline bg-surface-high"
34
+ ].join(" ") %>
35
+ <article
36
+ class="<%= card_classes %>"
37
+ data-marketing-pricing-target="planCard"
38
+ data-plan-key="<%= plan[:key] %>"
39
+ data-monthly-primary="<%= monthly[:primary] %>"
40
+ data-monthly-secondary="<%= monthly[:secondary] %>"
41
+ data-yearly-primary="<%= yearly[:primary] %>"
42
+ data-yearly-secondary="<%= yearly[:secondary] %>"
43
+ >
44
+ <div>
45
+ <% if plan[:highlight_badge].present? %>
46
+ <p class="mb-3 inline-flex rounded-full bg-primary px-3 py-1 text-xs font-semibold text-on-primary"><%= plan[:highlight_badge] %></p>
47
+ <% end %>
48
+ <h2 class="text-2xl font-semibold"><%= plan[:name] %></h2>
49
+ <p class="mt-4 text-4xl font-semibold text-on-surface" data-role="price-primary"><%= monthly[:primary] %></p>
50
+ <p class="mt-2 text-sm text-soft" data-role="price-secondary"><%= monthly[:secondary] %></p>
51
+ <p class="mt-4 text-sm text-soft"><%= plan[:subtitle] %></p>
52
+ </div>
53
+
54
+ <ul class="mt-6 space-y-3 text-sm text-on-surface">
55
+ <% Array(plan[:card_bullets]).each do |bullet| %>
56
+ <li class="flex items-start gap-3">
57
+ <span class="mt-0.5 text-primary"><%= ui_icon("check", class_name: "h-4 w-4") %></span>
58
+ <span><%= bullet %></span>
59
+ </li>
60
+ <% end %>
61
+ </ul>
62
+
63
+ <div class="mt-8 rounded-[1.5rem] bg-surface p-4 text-sm text-soft">
64
+ <p class="font-semibold text-on-surface">Additional usage credits</p>
65
+ <p class="mt-2"><%= plan.dig(:capabilities, :extra_credits) %></p>
66
+ </div>
67
+
68
+ <div class="mt-8 pt-4">
69
+ <%= ui_button(**plan.fetch(:cta)) %>
70
+ </div>
71
+ </article>
72
+ <% end %>
73
+ </div>
74
+
75
+ <div class="mt-12 rounded-[2rem] border border-outline bg-surface-high p-6 lg:p-8">
76
+ <div class="max-w-3xl">
77
+ <h2 class="text-3xl font-semibold"><%= shared_capabilities[:title] %></h2>
78
+ <p class="mt-3 text-sm text-soft"><%= shared_capabilities[:subtitle] %></p>
79
+ </div>
80
+ <ul class="mt-8 space-y-4 text-sm text-soft">
81
+ <% Array(shared_capabilities[:items]).each do |item| %>
82
+ <li class="flex items-start gap-3">
83
+ <span class="mt-0.5 text-primary"><%= ui_icon("check", class_name: "h-4 w-4") %></span>
84
+ <p class="text-base">
85
+ <span class="font-semibold text-on-surface"><%= item[:title] %></span>
86
+ <%= item[:body] %>
87
+ </p>
88
+ </li>
89
+ <% end %>
90
+ </ul>
91
+ </div>
92
+
93
+ <% if fair_use_link.present? %>
94
+ <div class="mt-6 text-center text-sm">
95
+ <%= link_to fair_use_link.fetch(:label), fair_use_link.fetch(:href), class: fair_use_link[:class].presence || "font-semibold text-primary hover:text-primary" %>
96
+ </div>
97
+ <% end %>
98
+ </div>
99
+ </section>
@@ -0,0 +1,80 @@
1
+ <%
2
+ raise ArgumentError, "Unsupported testimonials variant" unless variant.to_sym == :bento
3
+
4
+ cards = Array(items).first(4)
5
+ %>
6
+ <section id="<%= id %>" class="<%= ["marketing-testimonials-section bg-surface px-12 py-16 sm:px-12", local_assigns[:classes]].compact.join(" ") %>" data-variant="<%= variant %>">
7
+ <div class="mx-auto max-w-6xl">
8
+ <h2><%= title %></h2>
9
+ <div class="mt-8 grid gap-6 md:grid-cols-2">
10
+ <% if cards.first.present? %>
11
+ <article class="flex h-full flex-col rounded-3xl border border-outline bg-surface-high p-7 text-left">
12
+ <div class="flex items-start justify-between gap-4">
13
+ <div>
14
+ <p class="text-4xl font-semibold text-on-surface"><%= cards[0][:heading] %></p>
15
+ <p class="mt-1 text-2xl font-medium text-muted"><%= cards[0][:subheading] %></p>
16
+ </div>
17
+ <%= ui_icon("quote", class_name: "h-8 w-8 text-primary") %>
18
+ </div>
19
+ <p class="mt-8 text-lg text-soft"><%= cards[0][:quote] %></p>
20
+ <div class="mt-auto pt-6 text-xs text-muted">
21
+ <p class="font-semibold text-on-surface"><%= cards[0][:name] %></p>
22
+ <p><%= cards[0][:company] %></p>
23
+ </div>
24
+ </article>
25
+ <% end %>
26
+ <div class="grid gap-6">
27
+ <% if cards[1].present? %>
28
+ <article class="flex h-full flex-col rounded-3xl border border-outline-variant bg-primary/10 p-6 text-left">
29
+ <div class="flex items-start justify-between gap-4">
30
+ <div>
31
+ <p class="text-3xl font-semibold text-on-surface"><%= cards[1][:heading] %></p>
32
+ <p class="mt-1 text-xl font-medium text-muted"><%= cards[1][:subheading] %></p>
33
+ </div>
34
+ <%= ui_icon("quote", class_name: "h-6 w-6 text-primary") %>
35
+ </div>
36
+ <p class="mt-4 text-base text-soft"><%= cards[1][:quote] %></p>
37
+ <div class="mt-auto pt-5 text-xs text-muted">
38
+ <p class="font-semibold text-on-surface"><%= cards[1][:name] %></p>
39
+ <p><%= cards[1][:company] %></p>
40
+ </div>
41
+ </article>
42
+ <% end %>
43
+ <div class="grid gap-6 sm:grid-cols-2">
44
+ <% if cards[2].present? %>
45
+ <article class="flex h-full flex-col rounded-3xl border border-outline-variant bg-surface-lowest p-6 text-left">
46
+ <div class="flex items-start justify-between gap-4">
47
+ <div>
48
+ <p class="text-2xl font-semibold text-on-surface"><%= cards[2][:heading] %></p>
49
+ <p class="mt-1 text-lg font-medium text-muted"><%= cards[2][:subheading] %></p>
50
+ </div>
51
+ <%= ui_icon("quote", class_name: "h-6 w-6 text-primary") %>
52
+ </div>
53
+ <p class="mt-4 text-base text-soft"><%= cards[2][:quote] %></p>
54
+ <div class="mt-auto pt-5 text-xs text-muted">
55
+ <p class="font-semibold text-on-surface"><%= cards[2][:name] %></p>
56
+ <p><%= cards[2][:company] %></p>
57
+ </div>
58
+ </article>
59
+ <% end %>
60
+ <% if cards[3].present? %>
61
+ <article class="flex h-full flex-col rounded-3xl border border-outline bg-surface-high p-6 text-left">
62
+ <div class="flex items-start justify-between gap-4">
63
+ <div>
64
+ <p class="text-2xl font-semibold text-on-surface"><%= cards[3][:heading] %></p>
65
+ <p class="mt-1 text-lg font-medium text-muted"><%= cards[3][:subheading] %></p>
66
+ </div>
67
+ <%= ui_icon("quote", class_name: "h-6 w-6 text-primary") %>
68
+ </div>
69
+ <p class="mt-4 text-base text-soft"><%= cards[3][:quote] %></p>
70
+ <div class="mt-auto pt-5 text-xs text-muted">
71
+ <p class="font-semibold text-on-surface"><%= cards[3][:name] %></p>
72
+ <p><%= cards[3][:company] %></p>
73
+ </div>
74
+ </article>
75
+ <% end %>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </section>
@@ -0,0 +1,28 @@
1
+ <%
2
+ resolved_brand = local_assigns.fetch(:brand).symbolize_keys
3
+ resolved_brand[:logo_class] = resolved_brand[:logo_class].presence || "h-8 w-8 rounded-lg"
4
+ resolved_brand[:wordmark_class] = resolved_brand[:wordmark_class].presence || "text-2xl font-bold text-primary"
5
+ brand_markup = content_tag(:span, class: ["brand-lockup", resolved_brand[:wrapper_class], "text-primary"].compact.join(" ")) do
6
+ safe_join(
7
+ [
8
+ image_tag(resolved_brand[:logo_src], alt: resolved_brand[:logo_alt], class: ["brand-lockup__logo", resolved_brand[:logo_class]].join(" ")),
9
+ content_tag(:span, resolved_brand[:wordmark], class: ["brand-lockup__wordmark", resolved_brand[:wordmark_class]].join(" "))
10
+ ]
11
+ )
12
+ end
13
+ brand_markup = link_to(brand_markup, resolved_brand[:href], class: "inline-flex") if resolved_brand[:href].present?
14
+ %>
15
+ <header class="<%= ["marketing-top-nav relative z-50 border-b border-outline-variant bg-surface-high backdrop-blur", local_assigns[:classes]].compact.join(" ") %>">
16
+ <div class="mx-auto flex h-16 max-w-6xl items-center px-12">
17
+ <%= brand_markup %>
18
+ <nav class="ml-auto flex items-center gap-3 text-sm text-soft">
19
+ <% Array(links).each do |link| %>
20
+ <%= link_to link.fetch(:label), link.fetch(:href), class: link[:class] %>
21
+ <% end %>
22
+ <% if theme_toggle.present? %>
23
+ <%= ui_marketing_render_content(theme_toggle) %>
24
+ <% end %>
25
+ <%= ui_button(**primary_action) %>
26
+ </nav>
27
+ </div>
28
+ </header>
@@ -0,0 +1,21 @@
1
+ <%
2
+ resolved_brand_path = local_assigns[:brand_path].presence || "#"
3
+ auth_shell_classes = ["auth-page", "min-h-screen bg-surface-low px-6 py-12", local_assigns[:shell_class]].compact.join(" ")
4
+ auth_card_class = local_assigns[:card_class]
5
+ %>
6
+
7
+ <div class="<%= auth_shell_classes %>">
8
+ <div class="mx-auto max-w-md">
9
+ <div class="mb-6 flex justify-center">
10
+ <%= link_to resolved_brand_path, class: "inline-flex items-center justify-center no-underline" do %>
11
+ <%= render "shared/brand_lockup",
12
+ logo_class: "h-10 w-10 rounded-xl",
13
+ wordmark_class: "text-xl font-semibold text-base-content" %>
14
+ <% end %>
15
+ </div>
16
+
17
+ <%= ui_card(title: title, description: description, classes: auth_card_class || "shadow-sm") do %>
18
+ <%= body %>
19
+ <% end %>
20
+ </div>
21
+ </div>
@@ -0,0 +1,19 @@
1
+ <%
2
+ href = local_assigns[:href] || Baldur.config.default_google_sign_in_path
3
+ label = local_assigns[:label] || "Sign in with Google"
4
+ block = local_assigns[:block].present?
5
+ method = local_assigns[:method]
6
+ data = local_assigns[:data]
7
+ size = local_assigns[:size] || :md
8
+ variant = local_assigns[:variant] || :outlined
9
+ %>
10
+ <%= ui_button(
11
+ label: label,
12
+ href: href,
13
+ method: method,
14
+ data: data,
15
+ variant: variant,
16
+ size: size,
17
+ block: block,
18
+ icon: raw('<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="h-5 w-5"><path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"/><path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"/><path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"/><path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"/><path fill="none" d="M0 0h48v48H0z"/></svg>')
19
+ ) %>
@@ -0,0 +1 @@
1
+ <%= render "baldur/optional/panel_secondary", **local_assigns %>
@@ -0,0 +1,34 @@
1
+ <%
2
+ shell_data = (local_assigns[:shell_data] || {}).deep_dup
3
+ panel_data = (local_assigns[:panel_data] || {}).deep_dup
4
+ trigger_data = (local_assigns[:trigger_data] || {}).deep_dup
5
+ shell_data[:controller] = [shell_data[:controller], "panel-secondary"].compact.join(" ")
6
+ trigger_data[:action] = [trigger_data[:action], "panel-secondary#toggle"].compact.join(" ")
7
+ trigger_data[:panel_secondary_target] = "trigger"
8
+ panel_data[:panel_secondary_target] = "panel"
9
+ %>
10
+ <%= content_tag :div, class: ["panel-secondary-shell", shell_class].compact.join(" "), data: shell_data do %>
11
+ <%= button_tag type: "button",
12
+ class: ["button button--filled button--lg panel-secondary__trigger", trigger_class].compact.join(" "),
13
+ data: trigger_data,
14
+ aria: { controls: id, expanded: false } do %>
15
+ <%= ui_icon(trigger_icon, class_name: "h-5 w-5") %>
16
+ <span><%= trigger_label %></span>
17
+ <% end %>
18
+
19
+ <%= content_tag :aside,
20
+ id: id,
21
+ class: ["panel-secondary", panel_class].compact.join(" "),
22
+ data: panel_data,
23
+ aria: { hidden: true } do %>
24
+ <header class="panel-secondary__header">
25
+ <h3 class="text-lg font-semibold text-on-surface"><%= title %></h3>
26
+ <button type="button" class="icon-button" data-action="panel-secondary#close" aria-label="Close panel">
27
+ <%= ui_icon("x", class_name: "h-5 w-5") %>
28
+ </button>
29
+ </header>
30
+ <div class="panel-secondary__body">
31
+ <%= body %>
32
+ </div>
33
+ <% end %>
34
+ <% end %>
data/baldur.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ require_relative "lib/baldur/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "baldur"
5
+ spec.version = Baldur::VERSION
6
+ spec.authors = [ "Varun Murkar" ]
7
+ spec.email = [ "hello@varunmurkar.com" ]
8
+ spec.summary = "Reusable Rails UI engine for same-stack application interfaces"
9
+ spec.description = "Baldur packages reusable Rails view helpers, components, styles, and Stimulus controllers."
10
+ spec.homepage = "https://github.com/varunmurkar/baldur"
11
+ spec.license = "MIT"
12
+
13
+ spec.files = Dir.chdir(__dir__) do
14
+ Dir[
15
+ "{app,config,lib,script,test}/**/*",
16
+ "README.md",
17
+ "TODO.md",
18
+ "LICENSE",
19
+ "baldur.gemspec",
20
+ "Gemfile"
21
+ ].select { |path| File.file?(path) }
22
+ end
23
+
24
+ spec.required_ruby_version = ">= 3.3.0"
25
+
26
+ spec.add_dependency "importmap-rails"
27
+ spec.add_dependency "lucide-rails"
28
+ spec.add_dependency "rails", ">= 8.1.0"
29
+ spec.add_dependency "tailwindcss-rails"
30
+ end
@@ -0,0 +1,2 @@
1
+ pin_all_from File.expand_path("../app/assets/javascripts/baldur/controllers", __dir__), under: "baldur/controllers"
2
+ pin_all_from File.expand_path("../app/assets/javascripts/baldur/lib", __dir__), under: "baldur/lib"
@@ -0,0 +1,24 @@
1
+ module Baldur
2
+ class Configuration
3
+ attr_accessor :warning_dependency_resolver,
4
+ :dependency_dataset_name_resolver,
5
+ :unavailable_fallback_message,
6
+ :marketing_brand,
7
+ :default_google_sign_in_path,
8
+ :theme_storage_key
9
+
10
+ def initialize
11
+ @warning_dependency_resolver = ->(_warning_keys) { [] }
12
+ @dependency_dataset_name_resolver = ->(dataset_key) { dataset_key.to_s.humanize }
13
+ @unavailable_fallback_message = "Missing metric or raw data required to compute this value."
14
+ @marketing_brand = {
15
+ name: "Brand",
16
+ wordmark: "Brand",
17
+ logo_src: "/icon.png",
18
+ logo_alt: "Brand logo"
19
+ }
20
+ @default_google_sign_in_path = nil
21
+ @theme_storage_key = "baldur.theme"
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ module Baldur
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Baldur
4
+
5
+ initializer "baldur.importmap", before: "importmap" do |app|
6
+ app.config.importmap.paths << Engine.root.join("config/importmap.rb")
7
+ app.config.importmap.cache_sweepers << Engine.root.join("app/assets/javascripts")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Baldur
2
+ VERSION = "0.1.1".freeze
3
+ end
data/lib/baldur.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "rails"
2
+
3
+ require_relative "baldur/version"
4
+ require_relative "baldur/configuration"
5
+ require_relative "baldur/engine"
6
+
7
+ module Baldur
8
+ class << self
9
+ def config
10
+ @config ||= Configuration.new
11
+ end
12
+
13
+ def configure
14
+ yield(config)
15
+ end
16
+ end
17
+ end