@osimart/create-storefront 0.0.0-stage → 1.0.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 (206) hide show
  1. package/README.md +80 -2
  2. package/THEME_DEV_API.md +80 -0
  3. package/app/app.vue +154 -0
  4. package/app/assets/css/osi-controls.css +824 -0
  5. package/app/components/SectionRenderer.vue +46 -0
  6. package/app/components/builder/BuilderOverlay.vue +582 -0
  7. package/app/components/checkout/ExtraFees.vue +329 -0
  8. package/app/components/checkout/PaymentHandoff.vue +388 -0
  9. package/app/components/common/Banner.vue +35 -0
  10. package/app/components/common/ConfirmDeleteModal.vue +117 -0
  11. package/app/components/common/DropdownSelect.vue +432 -0
  12. package/app/components/common/Loader.vue +32 -0
  13. package/app/components/common/OsiField.vue +218 -0
  14. package/app/components/common/PhoneNumInput.vue +434 -0
  15. package/app/composables/UseBodyScrollLock.js +122 -0
  16. package/app/composables/UseThemeGroup.js +575 -0
  17. package/app/composables/useAddToCart.js +87 -0
  18. package/app/composables/useAnalytics.js +15 -0
  19. package/app/composables/useAnnouncements.js +29 -0
  20. package/app/composables/useApiErrorToast.js +104 -0
  21. package/app/composables/useAuth.js +99 -0
  22. package/app/composables/useBanners.js +25 -0
  23. package/app/composables/useBuilderPreview.js +320 -0
  24. package/app/composables/useButtonStyle.js +107 -0
  25. package/app/composables/useCarousel.js +160 -0
  26. package/app/composables/useCart.js +109 -0
  27. package/app/composables/useCheckout.js +76 -0
  28. package/app/composables/useClassificationItems.js +269 -0
  29. package/app/composables/useClassificationPage.js +545 -0
  30. package/app/composables/useClassificationUrl.js +63 -0
  31. package/app/composables/useColorScheme.js +44 -0
  32. package/app/composables/useContactForm.js +53 -0
  33. package/app/composables/useContentTree.js +770 -0
  34. package/app/composables/useDragScroll.js +73 -0
  35. package/app/composables/useFooterSocials.js +107 -0
  36. package/app/composables/useInputStyle.js +45 -0
  37. package/app/composables/useLayoutShell.js +114 -0
  38. package/app/composables/useLazyVideo.js +36 -0
  39. package/app/composables/useMediaTypes.js +66 -0
  40. package/app/composables/useMediaUrl.js +71 -0
  41. package/app/composables/useMenu.js +100 -0
  42. package/app/composables/useNavStyle.js +219 -0
  43. package/app/composables/usePlaceholders.js +294 -0
  44. package/app/composables/usePolicies.js +26 -0
  45. package/app/composables/useProductCardMedia.js +135 -0
  46. package/app/composables/useProductCardStyle.js +132 -0
  47. package/app/composables/useProductDetail.js +982 -0
  48. package/app/composables/useProductGallery.js +166 -0
  49. package/app/composables/useProductItems.js +270 -0
  50. package/app/composables/useProductUrl.js +60 -0
  51. package/app/composables/useProductVariants.js +210 -0
  52. package/app/composables/useQuickShop.js +115 -0
  53. package/app/composables/useRelatedProducts.js +186 -0
  54. package/app/composables/useRenderContext.js +45 -0
  55. package/app/composables/useScopedCss.js +106 -0
  56. package/app/composables/useSearch.js +35 -0
  57. package/app/composables/useSectionData.js +462 -0
  58. package/app/composables/useSectionShell.js +113 -0
  59. package/app/composables/useSidebar.js +69 -0
  60. package/app/composables/useStoreData.js +57 -0
  61. package/app/composables/useStoreInit.js +52 -0
  62. package/app/composables/useStorefront.js +239 -0
  63. package/app/composables/useT.js +21 -0
  64. package/app/composables/useTextStyles.js +84 -0
  65. package/app/composables/useThemeControls.js +34 -0
  66. package/app/composables/useThemeSeo.js +91 -0
  67. package/app/composables/useThemeTokens.js +171 -0
  68. package/app/composables/useWishlist.js +66 -0
  69. package/app/logic/components/AnnouncementBar.js +118 -0
  70. package/app/logic/components/CardGrid.js +125 -0
  71. package/app/logic/components/CardList.js +98 -0
  72. package/app/logic/components/CartDrawer.js +273 -0
  73. package/app/logic/components/CartItem.js +31 -0
  74. package/app/logic/components/ClassificationBento.js +6 -0
  75. package/app/logic/components/ClassificationCard.js +6 -0
  76. package/app/logic/components/ClassificationCarousel.js +115 -0
  77. package/app/logic/components/ClassificationEditorial.js +41 -0
  78. package/app/logic/components/ClassificationFeatured.js +57 -0
  79. package/app/logic/components/ClassificationGrid.js +6 -0
  80. package/app/logic/components/ClassificationNav.js +61 -0
  81. package/app/logic/components/ClassificationPart.js +72 -0
  82. package/app/logic/components/FooterBlock.js +91 -0
  83. package/app/logic/components/FooterSocials.js +60 -0
  84. package/app/logic/components/HeaderDropdownList.js +29 -0
  85. package/app/logic/components/HeaderIcon.js +68 -0
  86. package/app/logic/components/HeaderLogo.js +39 -0
  87. package/app/logic/components/HeaderNav.js +268 -0
  88. package/app/logic/components/MobileMenu.js +280 -0
  89. package/app/logic/components/ProductBuyButtons.js +66 -0
  90. package/app/logic/components/ProductCard.js +160 -0
  91. package/app/logic/components/ProductFilters.js +37 -0
  92. package/app/logic/components/ProductGallery.js +19 -0
  93. package/app/logic/components/ProductInfoBlock.js +411 -0
  94. package/app/logic/components/ProductLightbox.js +12 -0
  95. package/app/logic/components/ProductQuantity.js +31 -0
  96. package/app/logic/components/ProductVariantPicker.js +125 -0
  97. package/app/logic/components/QuickBuy.js +194 -0
  98. package/app/logic/components/SearchOverlay.js +41 -0
  99. package/app/logic/components/SidebarMenuItem.js +39 -0
  100. package/app/logic/components/SiteFooter.js +476 -0
  101. package/app/logic/components/SiteHeader.js +266 -0
  102. package/app/logic/components/WhatsAppButton.js +75 -0
  103. package/app/logic/layouts/default.js +66 -0
  104. package/app/logic/sections/AnnouncementBarSection.js +6 -0
  105. package/app/logic/sections/BlockCarousel.js +103 -0
  106. package/app/logic/sections/ClassificationListSection.js +40 -0
  107. package/app/logic/sections/ContactFormSection.js +294 -0
  108. package/app/logic/sections/ContactInfoSection.js +419 -0
  109. package/app/logic/sections/ContentNode.js +189 -0
  110. package/app/logic/sections/CountdownSection.js +254 -0
  111. package/app/logic/sections/CustomHtmlSection.js +51 -0
  112. package/app/logic/sections/DividerSection.js +31 -0
  113. package/app/logic/sections/FaqSection.js +53 -0
  114. package/app/logic/sections/FeatureListSection.js +6 -0
  115. package/app/logic/sections/FeaturedProductSection.js +40 -0
  116. package/app/logic/sections/FooterSection.js +16 -0
  117. package/app/logic/sections/FooterUtilitiesSection.js +24 -0
  118. package/app/logic/sections/GallerySection.js +232 -0
  119. package/app/logic/sections/HeaderSection.js +24 -0
  120. package/app/logic/sections/HeroSection.js +24 -0
  121. package/app/logic/sections/LogoListSection.js +6 -0
  122. package/app/logic/sections/MainClassificationSection.js +473 -0
  123. package/app/logic/sections/MainProductSection.js +211 -0
  124. package/app/logic/sections/MainSearchSection.js +6 -0
  125. package/app/logic/sections/MapSection.js +92 -0
  126. package/app/logic/sections/MediaContentSection.js +38 -0
  127. package/app/logic/sections/MediaWithTextSection.js +238 -0
  128. package/app/logic/sections/NewsletterSection.js +126 -0
  129. package/app/logic/sections/ProductHighlightSection.js +201 -0
  130. package/app/logic/sections/ProductListSection.js +45 -0
  131. package/app/logic/sections/ProductRecommendationsSection.js +113 -0
  132. package/app/logic/sections/RichTextSection.js +6 -0
  133. package/app/logic/sections/ScrollingTextSection.js +69 -0
  134. package/app/logic/sections/SearchHeaderSection.js +22 -0
  135. package/app/logic/sections/SlideshowSection.js +118 -0
  136. package/app/logic/sections/SpacerSection.js +37 -0
  137. package/app/logic/sections/SplitShowcaseSection.js +6 -0
  138. package/app/logic/sections/TestimonialListSection.js +6 -0
  139. package/app/logic/sections/VideoSection.js +235 -0
  140. package/app/pages/[category]/[product].vue +196 -0
  141. package/app/pages/__builder-preview.vue +38 -0
  142. package/app/pages/categories/[slug].vue +208 -0
  143. package/app/pages/checkout.vue +1972 -0
  144. package/app/pages/forgot-password.vue +115 -0
  145. package/app/pages/index.vue +64 -0
  146. package/app/pages/login.vue +585 -0
  147. package/app/pages/order-detail.vue +442 -0
  148. package/app/pages/pages/[handle].vue +58 -0
  149. package/app/pages/privacy-policy.vue +46 -0
  150. package/app/pages/resetpassword.vue +138 -0
  151. package/app/pages/resetverificationcode.vue +163 -0
  152. package/app/pages/result.vue +203 -0
  153. package/app/pages/return-policy.vue +50 -0
  154. package/app/pages/search.vue +100 -0
  155. package/app/pages/shipping-policy.vue +46 -0
  156. package/app/pages/user-profile/components/AddAddressModal.vue +452 -0
  157. package/app/pages/user-profile/components/Whishlist.vue +137 -0
  158. package/app/pages/user-profile/components/left-bar.vue +233 -0
  159. package/app/pages/user-profile/components/orders.vue +162 -0
  160. package/app/pages/user-profile/components/profile-details.vue +566 -0
  161. package/app/pages/user-profile/components/settings.vue +194 -0
  162. package/app/pages/user-profile/index.vue +252 -0
  163. package/app/pages/verificationcode.vue +188 -0
  164. package/app/plugins/analytics.client.js +99 -0
  165. package/app/plugins/geo.ts +24 -0
  166. package/app/plugins/reveal.client.js +53 -0
  167. package/app/plugins/toast.js +10 -0
  168. package/app/plugins/vue-query.ts +5 -0
  169. package/app/services/ApiServices.js +74 -0
  170. package/app/services/AuthBus.js +162 -0
  171. package/app/services/Authentication/AuthenticationService.js +133 -0
  172. package/app/services/Authentication/TokenService.js +62 -0
  173. package/app/services/CartServices.js +91 -0
  174. package/app/services/CartToken.js +26 -0
  175. package/app/services/CategoryServices.js +54 -0
  176. package/app/services/CheckoutServices.js +113 -0
  177. package/app/services/HomeServices.js +133 -0
  178. package/app/services/NewsletterServices.js +29 -0
  179. package/app/services/PaymentServices.js +71 -0
  180. package/app/services/PoliciesServices.js +16 -0
  181. package/app/services/ProductServices.js +79 -0
  182. package/app/services/SearchServices.js +18 -0
  183. package/app/services/UserServices.js +105 -0
  184. package/app/services/UtilsServices.js +46 -0
  185. package/app/services/WishlistServices.js +51 -0
  186. package/app/services/storefront.service.js +57 -0
  187. package/app/stores/userStore.js +13 -0
  188. package/app/theme/categoryTree.js +68 -0
  189. package/app/theme/controls.js +88 -0
  190. package/app/theme/registry.js +56 -0
  191. package/app/utils/analytics.js +864 -0
  192. package/app/utils/device.js +50 -0
  193. package/app/utils/eventBus.js +24 -0
  194. package/app/utils/icon-presets.js +24 -0
  195. package/app/utils/productOffers.js +118 -0
  196. package/app/utils/requestCache.js +36 -0
  197. package/app/utils/scrollToError.js +59 -0
  198. package/app/utils/toPlainError.js +30 -0
  199. package/bin/osimart-theme.mjs +259 -0
  200. package/nuxt/osi-sections.ts +56 -0
  201. package/nuxt.config.ts +60 -0
  202. package/package.json +43 -3
  203. package/public/css/input.css +29 -0
  204. package/public/css/output.css +2716 -0
  205. package/public/favicon-32x32.png +0 -0
  206. package/public/robots.txt +2 -0
@@ -0,0 +1,824 @@
1
+ /* =========================================================================
2
+ Storefront controls — buttons and form fields.
3
+
4
+ One stylesheet for every button and field outside the builder sections'
5
+ own bespoke markup: cart drawer, login/register, password flows, checkout,
6
+ account pages, modals, search, and any section that opts in with the
7
+ classes below.
8
+
9
+ Resolution order, for every property:
10
+
11
+ --btn-* / --in-* this one control (useButtonStyle / useInputStyle)
12
+ --osi-button-* / -input-* the theme (useThemeTokens)
13
+ --osi-btn-bg, --osi-border the colour scheme (useColorScheme)
14
+ literal last resort
15
+
16
+ Every rule is prefixed with :is(.osi-root, body). That's deliberate: it lifts
17
+ these rules above a single Tailwind utility (bg-*, border-*, rounded-*) and
18
+ above a component's own one-class scoped rule, so adding `osi-btn` to an
19
+ element is enough — no !important, no hunting down every leftover utility.
20
+
21
+ `body` is in the prefix because overlays (cart drawer, search, mobile menu…)
22
+ are <Teleport>ed to <body>, i.e. OUTSIDE .osi-root. app.vue puts the
23
+ osi-btns--* / osi-inputs--* / osi-labels--* classes on <body> too, so a
24
+ teleported control resolves exactly like one on a page. :is() takes the
25
+ specificity of its most specific argument (a class), so this is the same
26
+ weight as the old bare .osi-root prefix.
27
+
28
+ Internal variables start with --_ and are private to this file.
29
+ ========================================================================= */
30
+
31
+ /* ------------------------------------------------------------------ BUTTONS */
32
+
33
+ :is(.osi-root, body) .osi-btn {
34
+ /* The two colours a button is made of. */
35
+ --_color: var(--btn-bg, var(--osi-button-bg, var(--osi-btn-bg, #121212)));
36
+ --_on-color: var(--btn-fg, var(--osi-button-fg, var(--osi-btn-text, #ffffff)));
37
+
38
+ /* Resting paint for the current style (SOLID here; modes below replace it). */
39
+ --_pbg: var(--_color);
40
+ --_pfg: var(--_on-color);
41
+ --_pbd: var(--btn-border-color, var(--osi-button-border-color, var(--_color)));
42
+
43
+ /* Where INVERT goes on hover, per style. */
44
+ --_ibg: transparent;
45
+ --_ifg: var(--_color);
46
+ --_ibd: var(--_color);
47
+
48
+ /* What is actually painted. Hover rules only ever touch these three. */
49
+ --_xbg: var(--_pbg);
50
+ --_xfg: var(--_pfg);
51
+ --_xbd: var(--_pbd);
52
+
53
+ --_pady: var(--btn-pad-y, var(--osi-button-pad-y, 12px));
54
+ --_padx: var(--btn-pad-x, var(--osi-button-pad-x, 28px));
55
+ --_scale: 1;
56
+ --_padk: 1; /* 0 for UNDERLINE / TEXT, which have no box to pad */
57
+
58
+ display: inline-flex;
59
+ align-items: center;
60
+ justify-content: center;
61
+ gap: 0.5em;
62
+ box-sizing: border-box;
63
+ max-width: 100%;
64
+ padding: calc(var(--_pady) * var(--_scale) * var(--_padk)) calc(var(--_padx) * var(--_scale) * var(--_padk));
65
+ background: var(--_xbg);
66
+ color: var(--_xfg);
67
+ border: var(--btn-border-w, var(--osi-button-border-w, 1px)) solid var(--_xbd);
68
+ border-radius: var(--btn-radius, var(--osi-button-radius, 4px));
69
+ box-shadow: var(--btn-shadow, var(--osi-button-shadow, none));
70
+ font-family: var(--btn-ff, var(--osi-button-ff, inherit));
71
+ font-size: calc(var(--btn-fs, var(--osi-button-fs, 14px)) * (0.5 + var(--_scale) / 2));
72
+ font-weight: var(--btn-fw, var(--osi-button-fw, 500));
73
+ letter-spacing: var(--btn-ls, var(--osi-button-ls, normal));
74
+ text-transform: var(--btn-tt, var(--osi-button-tt, none));
75
+ line-height: 1.2;
76
+ text-align: center;
77
+ text-decoration: none;
78
+ white-space: nowrap;
79
+ cursor: pointer;
80
+ -webkit-appearance: none;
81
+ appearance: none;
82
+ -webkit-tap-highlight-color: transparent;
83
+ transition:
84
+ background-color 0.2s ease,
85
+ color 0.2s ease,
86
+ border-color 0.2s ease,
87
+ box-shadow 0.2s ease,
88
+ opacity 0.2s ease,
89
+ filter 0.2s ease,
90
+ transform 0.2s ease;
91
+ }
92
+ :is(.osi-root, body) .osi-btn:focus-visible {
93
+ outline: 2px solid var(--_color);
94
+ outline-offset: 2px;
95
+ }
96
+ :is(.osi-root, body) .osi-btn:disabled,
97
+ :is(.osi-root, body) .osi-btn[aria-disabled="true"] {
98
+ opacity: 0.55;
99
+ cursor: not-allowed;
100
+ transform: none;
101
+ filter: none;
102
+ }
103
+
104
+ /* ---- style: theme default (root class) */
105
+
106
+ :is(.osi-root, body).osi-btns--outline .osi-btn {
107
+ --_pbg: transparent;
108
+ --_pfg: var(--btn-fg, var(--osi-button-fg, var(--_color)));
109
+ --_pbd: var(--btn-border-color, var(--osi-button-border-color, var(--_pfg)));
110
+ --_ibg: var(--_pfg);
111
+ --_ifg: var(--osi-bg, #ffffff);
112
+ --_ibd: var(--_pfg);
113
+ }
114
+ :is(.osi-root, body).osi-btns--underline .osi-btn,
115
+ :is(.osi-root, body).osi-btns--text .osi-btn {
116
+ --_pbg: transparent;
117
+ --_pfg: var(--btn-fg, var(--osi-button-fg, var(--_color)));
118
+ --_pbd: transparent;
119
+ --_ibg: transparent;
120
+ --_ifg: var(--_pfg);
121
+ --_ibd: transparent;
122
+ --_padk: 0;
123
+ box-shadow: none;
124
+ }
125
+ :is(.osi-root, body).osi-btns--underline .osi-btn {
126
+ text-decoration: underline;
127
+ text-underline-offset: 0.3em;
128
+ }
129
+
130
+ /* ---- style: this button's own choice (element class) — after the root
131
+ rules on purpose, same specificity, so the element wins. */
132
+
133
+ :is(.osi-root, body) .osi-btn.osi-btn--solid {
134
+ --_pbg: var(--_color);
135
+ --_pfg: var(--_on-color);
136
+ --_pbd: var(--btn-border-color, var(--osi-button-border-color, var(--_color)));
137
+ --_ibg: transparent;
138
+ --_ifg: var(--_color);
139
+ --_ibd: var(--_color);
140
+ --_padk: 1;
141
+ text-decoration: none;
142
+ box-shadow: var(--btn-shadow, var(--osi-button-shadow, none));
143
+ }
144
+ :is(.osi-root, body) .osi-btn.osi-btn--outline {
145
+ --_pbg: transparent;
146
+ --_pfg: var(--btn-fg, var(--osi-button-fg, var(--_color)));
147
+ --_pbd: var(--btn-border-color, var(--osi-button-border-color, var(--_pfg)));
148
+ --_ibg: var(--_pfg);
149
+ --_ifg: var(--osi-bg, #ffffff);
150
+ --_ibd: var(--_pfg);
151
+ --_padk: 1;
152
+ text-decoration: none;
153
+ box-shadow: var(--btn-shadow, var(--osi-button-shadow, none));
154
+ }
155
+ :is(.osi-root, body) .osi-btn.osi-btn--underline,
156
+ :is(.osi-root, body) .osi-btn.osi-btn--text {
157
+ --_pbg: transparent;
158
+ --_pfg: var(--btn-fg, var(--osi-button-fg, var(--_color)));
159
+ --_pbd: transparent;
160
+ --_ibg: transparent;
161
+ --_ifg: var(--_pfg);
162
+ --_ibd: transparent;
163
+ --_padk: 0;
164
+ box-shadow: none;
165
+ }
166
+ :is(.osi-root, body) .osi-btn.osi-btn--underline {
167
+ text-decoration: underline;
168
+ text-underline-offset: 0.3em;
169
+ }
170
+ :is(.osi-root, body) .osi-btn.osi-btn--text {
171
+ text-decoration: none;
172
+ }
173
+
174
+ /* ---- secondary: the quieter action next to a primary one (Apply, Cancel,
175
+ Continue shopping). Shares the theme's shape, has its own colours. */
176
+
177
+ :is(.osi-root, body) .osi-btn.osi-btn--secondary {
178
+ --_pbg: var(--osi-button2-bg, transparent);
179
+ --_pfg: var(--osi-button2-fg, var(--osi-button-bg, var(--osi-btn-bg, currentColor)));
180
+ --_pbd: var(--osi-button2-border-color, var(--_pfg));
181
+ --_ibg: var(--_pfg);
182
+ --_ifg: var(--osi-bg, #ffffff);
183
+ --_ibd: var(--_pfg);
184
+ text-decoration: none;
185
+ }
186
+
187
+ /* ---- danger: destructive confirmations. Pair with osi-btn--solid so a
188
+ theme set to OUTLINE/TEXT still gets an unmistakable button. */
189
+
190
+ :is(.osi-root, body) .osi-btn.osi-btn--danger {
191
+ --btn-bg: var(--osi-input-error, #dc2626);
192
+ --btn-fg: #ffffff;
193
+ --btn-border-color: var(--osi-input-error, #dc2626);
194
+ }
195
+
196
+ /* ---- sizes and width */
197
+
198
+ :is(.osi-root, body) .osi-btn.osi-btn--sm {
199
+ --_scale: 0.6;
200
+ }
201
+ :is(.osi-root, body) .osi-btn.osi-btn--lg {
202
+ --_scale: 1.3;
203
+ }
204
+ :is(.osi-root, body) .osi-btn.osi-btn--block {
205
+ display: flex;
206
+ width: 100%;
207
+ }
208
+ :is(.osi-root, body) .osi-btn.osi-btn--icon {
209
+ --_padx: var(--_pady);
210
+ }
211
+
212
+ /* ---- hover. Explicit hover colours win over whatever the effect does. */
213
+
214
+ :is(.osi-root, body) .osi-btn:not(:disabled):hover {
215
+ --_xbg: var(--btn-hover-bg, var(--osi-button-hover-bg, var(--_ebg, var(--_pbg))));
216
+ --_xfg: var(--btn-hover-fg, var(--osi-button-hover-fg, var(--_efg, var(--_pfg))));
217
+ --_xbd: var(--osi-button-hover-border-color, var(--_ebd, var(--_pbd)));
218
+ }
219
+
220
+ /* root effect */
221
+ :is(.osi-root, body).osi-btns-hover--fade .osi-btn:not(:disabled):hover {
222
+ opacity: 0.85;
223
+ }
224
+ :is(.osi-root, body).osi-btns-hover--darken .osi-btn:not(:disabled):hover {
225
+ filter: brightness(0.9);
226
+ }
227
+ :is(.osi-root, body).osi-btns-hover--lift .osi-btn:not(:disabled):hover {
228
+ transform: translateY(-1px);
229
+ box-shadow: var(--osi-button-hover-shadow, 0 6px 14px -4px rgba(0, 0, 0, 0.25));
230
+ }
231
+ :is(.osi-root, body).osi-btns-hover--invert .osi-btn:not(:disabled):hover {
232
+ --_ebg: var(--_ibg);
233
+ --_efg: var(--_ifg);
234
+ --_ebd: var(--_ibd);
235
+ }
236
+
237
+ /* element effect — each one resets what the others set */
238
+ :is(.osi-root, body) .osi-btn[class*="osi-btn-hover--"]:not(:disabled):hover {
239
+ --_ebg: initial;
240
+ --_efg: initial;
241
+ --_ebd: initial;
242
+ opacity: 1;
243
+ filter: none;
244
+ transform: none;
245
+ }
246
+ :is(.osi-root, body) .osi-btn.osi-btn-hover--fade:not(:disabled):hover {
247
+ opacity: 0.85;
248
+ }
249
+ :is(.osi-root, body) .osi-btn.osi-btn-hover--darken:not(:disabled):hover {
250
+ filter: brightness(0.9);
251
+ }
252
+ :is(.osi-root, body) .osi-btn.osi-btn-hover--lift:not(:disabled):hover {
253
+ transform: translateY(-1px);
254
+ box-shadow: var(--osi-button-hover-shadow, 0 6px 14px -4px rgba(0, 0, 0, 0.25));
255
+ }
256
+ :is(.osi-root, body) .osi-btn.osi-btn-hover--invert:not(:disabled):hover {
257
+ --_ebg: var(--_ibg);
258
+ --_efg: var(--_ifg);
259
+ --_ebd: var(--_ibd);
260
+ }
261
+
262
+ @media (max-width: 639px) {
263
+ :is(.osi-root, body) .osi-btn {
264
+ --_pady: var(--btn-pad-y, var(--osi-button-pad-y-mobile, var(--osi-button-pad-y, 12px)));
265
+ --_padx: var(--btn-pad-x, var(--osi-button-pad-x-mobile, var(--osi-button-pad-x, 28px)));
266
+ }
267
+ }
268
+
269
+ @media (prefers-reduced-motion: reduce) {
270
+ :is(.osi-root, body) .osi-btn {
271
+ transition: none;
272
+ }
273
+ :is(.osi-root, body) .osi-btn:hover {
274
+ transform: none !important;
275
+ }
276
+ }
277
+
278
+ /* A spinner that inherits the button's text colour. */
279
+ :is(.osi-root, body) .osi-btn-spinner {
280
+ display: inline-block;
281
+ width: 1em;
282
+ height: 1em;
283
+ border: 2px solid currentColor;
284
+ border-right-color: transparent;
285
+ border-radius: 999px;
286
+ animation: osi-btn-spin 0.7s linear infinite;
287
+ }
288
+ @keyframes osi-btn-spin {
289
+ to {
290
+ transform: rotate(360deg);
291
+ }
292
+ }
293
+
294
+ /* ------------------------------------------------------------------- FIELDS */
295
+
296
+ :is(.osi-root, body) .osi-input {
297
+ --_in-bg: var(--in-bg, var(--osi-input-bg, transparent));
298
+ --_in-border: var(--in-border-color, var(--osi-input-border-color, var(--osi-border, #d1d5db)));
299
+ --_in-focus: var(--in-focus, var(--osi-input-focus, currentColor));
300
+ --_in-error: var(--in-error, var(--osi-input-error, #dc2626));
301
+ --_in-pady: var(--in-pad-y, var(--osi-input-pad-y, 10px));
302
+ --_in-padx: var(--in-pad-x, var(--osi-input-pad-x, 12px));
303
+ --_in-fs: var(--in-fs, var(--osi-input-fs, 15px));
304
+ --_in-xk: 1; /* 0 for UNDERLINE: no box, so no side padding */
305
+
306
+ display: block;
307
+ width: 100%;
308
+ min-width: 0;
309
+ box-sizing: border-box;
310
+ padding: var(--_in-pady) calc(var(--_in-padx) * var(--_in-xk));
311
+ background: var(--_in-bg);
312
+ color: var(--in-fg, var(--osi-input-fg, inherit));
313
+ border: var(--in-border-w, var(--osi-input-border-w, 1px)) solid var(--_in-border);
314
+ border-radius: var(--in-radius, var(--osi-input-radius, 4px));
315
+ box-shadow: var(--in-shadow, var(--osi-input-shadow, none));
316
+ font-family: inherit;
317
+ font-size: var(--_in-fs);
318
+ font-weight: 400;
319
+ line-height: 1.4;
320
+ text-align: start;
321
+ outline: none;
322
+ -webkit-appearance: none;
323
+ appearance: none;
324
+ transition: border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
325
+ }
326
+ :is(.osi-root, body) .osi-input::placeholder {
327
+ color: var(--in-placeholder, var(--osi-input-placeholder, currentColor));
328
+ opacity: 0.5;
329
+ }
330
+ :is(.osi-root, body) .osi-input:focus,
331
+ :is(.osi-root, body) .osi-input:focus-within,
332
+ :is(.osi-root, body) .osi-input:focus-visible {
333
+ border-color: var(--_in-focus);
334
+ outline: none;
335
+ }
336
+ :is(.osi-root, body) .osi-input:disabled {
337
+ opacity: 0.6;
338
+ cursor: not-allowed;
339
+ }
340
+ :is(.osi-root, body) .osi-input[aria-invalid="true"],
341
+ :is(.osi-root, body) .osi-input.is-invalid,
342
+ :is(.osi-root, body) .osi-field--error .osi-input {
343
+ border-color: var(--_in-error);
344
+ }
345
+ :is(.osi-root, body) textarea.osi-input {
346
+ resize: vertical;
347
+ min-height: 6em;
348
+ }
349
+ :is(.osi-root, body) select.osi-input,
350
+ :is(.osi-root, body) .osi-input.osi-select-trigger {
351
+ display: flex;
352
+ align-items: center;
353
+ justify-content: space-between;
354
+ gap: 0.5rem;
355
+ cursor: pointer;
356
+ /* A button has no intrinsic line box; this keeps it the same height as an
357
+ input with the same padding. */
358
+ min-height: calc(var(--_in-fs) * 1.4 + var(--_in-pady) * 2 + var(--in-border-w, var(--osi-input-border-w, 1px)) * 2);
359
+ }
360
+ :is(.osi-root, body) .osi-input:-webkit-autofill {
361
+ -webkit-box-shadow: 0 0 0 100px var(--in-bg, var(--osi-input-bg, var(--osi-bg, #ffffff))) inset;
362
+ -webkit-text-fill-color: var(--in-fg, var(--osi-input-fg, var(--osi-text, #121212)));
363
+ }
364
+
365
+ /* ---- field style: theme default (root) */
366
+
367
+ :is(.osi-root, body).osi-inputs--filled .osi-input {
368
+ --_in-bg: var(--in-bg, var(--osi-input-bg, color-mix(in srgb, currentColor 6%, transparent)));
369
+ --_in-border: var(--in-border-color, var(--osi-input-border-color, transparent));
370
+ }
371
+ :is(.osi-root, body).osi-inputs--underline .osi-input {
372
+ --_in-bg: var(--in-bg, var(--osi-input-bg, transparent));
373
+ --_in-xk: 0;
374
+ border-width: 0 0 var(--in-border-w, var(--osi-input-border-w, 1px));
375
+ border-radius: 0;
376
+ box-shadow: none;
377
+ }
378
+
379
+ /* ---- field style: element override (after, same specificity) */
380
+
381
+ :is(.osi-root, body) .osi-input.osi-input--outline {
382
+ --_in-bg: var(--in-bg, var(--osi-input-bg, transparent));
383
+ --_in-border: var(--in-border-color, var(--osi-input-border-color, var(--osi-border, #d1d5db)));
384
+ --_in-xk: 1;
385
+ border-width: var(--in-border-w, var(--osi-input-border-w, 1px));
386
+ border-radius: var(--in-radius, var(--osi-input-radius, 4px));
387
+ box-shadow: var(--in-shadow, var(--osi-input-shadow, none));
388
+ }
389
+ :is(.osi-root, body) .osi-input.osi-input--filled {
390
+ --_in-bg: var(--in-bg, var(--osi-input-bg, color-mix(in srgb, currentColor 6%, transparent)));
391
+ --_in-border: var(--in-border-color, var(--osi-input-border-color, transparent));
392
+ --_in-xk: 1;
393
+ border-width: var(--in-border-w, var(--osi-input-border-w, 1px));
394
+ border-radius: var(--in-radius, var(--osi-input-radius, 4px));
395
+ box-shadow: var(--in-shadow, var(--osi-input-shadow, none));
396
+ }
397
+ :is(.osi-root, body) .osi-input.osi-input--underline {
398
+ --_in-bg: var(--in-bg, var(--osi-input-bg, transparent));
399
+ --_in-xk: 0;
400
+ border-width: 0 0 var(--in-border-w, var(--osi-input-border-w, 1px));
401
+ border-radius: 0;
402
+ box-shadow: none;
403
+ }
404
+
405
+ @media (max-width: 639px) {
406
+ :is(.osi-root, body) .osi-input {
407
+ --_in-pady: var(--in-pad-y-mobile, var(--in-pad-y, var(--osi-input-pad-y-mobile, var(--osi-input-pad-y, 10px))));
408
+ --_in-padx: var(--in-pad-x-mobile, var(--in-pad-x, var(--osi-input-pad-x-mobile, var(--osi-input-pad-x, 12px))));
409
+ /* 16px minimum or iOS Safari zooms the page on focus. */
410
+ --_in-fs: max(var(--in-fs, var(--osi-input-fs-mobile, var(--osi-input-fs, 15px))), 16px);
411
+ }
412
+ }
413
+
414
+ /* ------------------------------------------------------ LABELLED FIELD
415
+ Same DOM shape and behaviour as CONTACT_FORM's fields:
416
+
417
+ .osi-field
418
+ .osi-field-control (position: relative)
419
+ <input class="osi-input">
420
+ <label class="osi-field-label">
421
+ .osi-field-suffix? (password eye, etc.)
422
+ .osi-field-error?
423
+
424
+ The label comes AFTER the input so `input:focus ~ label` can float it;
425
+ `order: -1` puts it back on top in ABOVE mode.
426
+
427
+ The label style and field style are classes on .osi-field
428
+ (osi-field--label-*, osi-field--input-*), resolved by OsiField.vue from
429
+ the component's props or the theme.
430
+ ------------------------------------------------------------------------ */
431
+
432
+ :is(.osi-root, body) .osi-field {
433
+ --_f-pady: var(--in-pad-y, var(--osi-input-pad-y, 10px));
434
+ --_f-padx: var(--in-pad-x, var(--osi-input-pad-x, 12px));
435
+ --_f-fs: var(--in-fs, var(--osi-input-fs, 15px));
436
+ --_f-xk: 1;
437
+ --_float-fs: 0.6875rem;
438
+ --_float-gap: 0.3rem;
439
+
440
+ display: flex;
441
+ flex-direction: column;
442
+ gap: 0.375rem;
443
+ min-width: 0;
444
+ text-align: start;
445
+ }
446
+ :is(.osi-root, body) .osi-field-control {
447
+ position: relative;
448
+ display: flex;
449
+ flex-direction: column;
450
+ gap: 0.375rem;
451
+ }
452
+ :is(.osi-root, body) .osi-field-label {
453
+ font-size: var(--osi-label-fs, 0.75rem);
454
+ font-weight: var(--osi-label-fw, 500);
455
+ letter-spacing: var(--osi-label-ls, 0.02em);
456
+ text-transform: var(--osi-label-tt, none);
457
+ color: var(--osi-label-color, inherit);
458
+ opacity: 0.8;
459
+ margin: 0;
460
+ }
461
+ :is(.osi-root, body) .osi-field-required {
462
+ margin-inline-start: 0.125em;
463
+ }
464
+ :is(.osi-root, body) .osi-field-error {
465
+ margin: 0;
466
+ font-size: 0.75rem;
467
+ color: var(--in-error, var(--osi-input-error, #dc2626));
468
+ }
469
+ :is(.osi-root, body) .osi-field-hint {
470
+ margin: 0;
471
+ font-size: 0.75rem;
472
+ opacity: 0.65;
473
+ }
474
+ :is(.osi-root, body) .osi-field-suffix {
475
+ position: absolute;
476
+ inset-inline-end: calc(var(--_f-padx) * var(--_f-xk) + 0.25rem);
477
+ top: 50%;
478
+ transform: translateY(-50%);
479
+ display: flex;
480
+ align-items: center;
481
+ color: inherit;
482
+ opacity: 0.6;
483
+ z-index: 1;
484
+ }
485
+ :is(.osi-root, body) .osi-field--suffix .osi-input {
486
+ padding-inline-end: calc(var(--_f-padx) * var(--_f-xk) + 2rem);
487
+ }
488
+ :is(.osi-root, body) .osi-field--input-underline {
489
+ --_f-xk: 0;
490
+ }
491
+ :is(.osi-root, body) .osi-field--input-underline .osi-field-suffix {
492
+ inset-inline-end: 0;
493
+ }
494
+
495
+ /* above */
496
+ :is(.osi-root, body) .osi-field--label-above .osi-field-label {
497
+ order: -1;
498
+ }
499
+
500
+ /* inside only — kept for screen readers and the `for` association */
501
+ :is(.osi-root, body) .osi-field--label-placeholder .osi-field-label {
502
+ position: absolute;
503
+ width: 1px;
504
+ height: 1px;
505
+ margin: -1px;
506
+ padding: 0;
507
+ overflow: hidden;
508
+ clip: rect(0 0 0 0);
509
+ white-space: nowrap;
510
+ border: 0;
511
+ }
512
+
513
+ /* floating
514
+ Same behaviour as CONTACT_FORM: at rest the label sits centred on the
515
+ field's text line; on focus / with a value it rises onto the top border
516
+ (outline) or to the top inside the box (filled, underline).
517
+
518
+ Positions are measured against the input itself — the control box is
519
+ exactly the input's size — so a stray global `input { height }` or
520
+ `label { … }` rule can't push the label off-centre. The doubled class
521
+ lifts these above any single-class or element rule from elsewhere. */
522
+ :is(.osi-root, body) .osi-field.osi-field--label-floating .osi-field-control {
523
+ position: relative;
524
+ display: block;
525
+ gap: 0;
526
+ }
527
+ :is(.osi-root, body) .osi-field.osi-field--label-floating .osi-field-control > .osi-input {
528
+ margin: 0;
529
+ height: auto;
530
+ }
531
+ :is(.osi-root, body) .osi-field.osi-field--label-floating .osi-field-label.osi-field-label {
532
+ position: absolute;
533
+ display: block;
534
+ inset-inline-start: calc(var(--_f-padx) * var(--_f-xk));
535
+ inset-inline-end: auto;
536
+ top: 50%;
537
+ bottom: auto;
538
+ transform: translateY(-50%);
539
+ width: auto;
540
+ max-width: none;
541
+ margin: 0;
542
+ padding: 0 0.25rem;
543
+ font-size: var(--_f-fs);
544
+ font-weight: 400;
545
+ letter-spacing: normal;
546
+ text-transform: none;
547
+ line-height: 1.4;
548
+ white-space: nowrap;
549
+ background: transparent;
550
+ pointer-events: none;
551
+ opacity: 0.6;
552
+ z-index: 1;
553
+ transition: top 0.18s ease, transform 0.18s ease, font-size 0.18s ease,
554
+ opacity 0.18s ease, color 0.18s ease, background-color 0.18s ease;
555
+ }
556
+
557
+ /* floated — outline: onto the border, with a chip so the line doesn't
558
+ strike through the text */
559
+ :is(.osi-root, body) .osi-field.osi-field--label-floating .osi-input:focus ~ .osi-field-label.osi-field-label,
560
+ :is(.osi-root, body) .osi-field.osi-field--label-floating .osi-input:not(:placeholder-shown) ~ .osi-field-label.osi-field-label,
561
+ :is(.osi-root, body) .osi-field.osi-field--label-floating .osi-input:autofill ~ .osi-field-label.osi-field-label,
562
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--floated .osi-field-label.osi-field-label {
563
+ top: 0;
564
+ transform: translateY(-50%);
565
+ font-size: var(--_float-fs);
566
+ letter-spacing: 0.04em;
567
+ line-height: 1;
568
+ opacity: 1;
569
+ background: var(--osi-field-chip, var(--in-bg, var(--osi-input-bg, var(--osi-bg, #ffffff))));
570
+ }
571
+ /* Separate rule: an engine that doesn't know one of these pseudo-classes
572
+ would otherwise drop the whole list above. */
573
+ :is(.osi-root, body) .osi-field.osi-field--label-floating .osi-input:-webkit-autofill ~ .osi-field-label.osi-field-label,
574
+ :is(.osi-root, body) .osi-field.osi-field--label-floating .osi-input:focus-within ~ .osi-field-label.osi-field-label {
575
+ top: 0;
576
+ transform: translateY(-50%);
577
+ font-size: var(--_float-fs);
578
+ letter-spacing: 0.04em;
579
+ line-height: 1;
580
+ opacity: 1;
581
+ background: var(--osi-field-chip, var(--in-bg, var(--osi-input-bg, var(--osi-bg, #ffffff))));
582
+ }
583
+ :is(.osi-root, body) .osi-field.osi-field--label-floating .osi-input:focus ~ .osi-field-label.osi-field-label {
584
+ color: var(--in-focus, var(--osi-input-focus, inherit));
585
+ }
586
+
587
+ /* filled / underline: no outline to notch, so the label rises inside the box
588
+ and the box grows by the floated label's height to make room. */
589
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-filled .osi-field-control > .osi-input,
590
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-underline .osi-field-control > .osi-input {
591
+ padding-top: calc(var(--_f-pady) + var(--_float-fs));
592
+ }
593
+ /* At rest the label is centred in the (taller) box, same as outline. */
594
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-filled .osi-field-label.osi-field-label,
595
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-underline .osi-field-label.osi-field-label {
596
+ top: 50%;
597
+ }
598
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-filled .osi-input:focus ~ .osi-field-label.osi-field-label,
599
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-filled .osi-input:not(:placeholder-shown) ~ .osi-field-label.osi-field-label,
600
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-filled .osi-input:autofill ~ .osi-field-label.osi-field-label,
601
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-filled.osi-field--floated .osi-field-label.osi-field-label,
602
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-underline .osi-input:focus ~ .osi-field-label.osi-field-label,
603
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-underline .osi-input:not(:placeholder-shown) ~ .osi-field-label.osi-field-label,
604
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-underline .osi-input:autofill ~ .osi-field-label.osi-field-label,
605
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-underline.osi-field--floated .osi-field-label.osi-field-label {
606
+ top: var(--_float-gap);
607
+ transform: none;
608
+ background: transparent;
609
+ }
610
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-filled .osi-input:-webkit-autofill ~ .osi-field-label.osi-field-label,
611
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-underline .osi-input:-webkit-autofill ~ .osi-field-label.osi-field-label {
612
+ top: var(--_float-gap);
613
+ transform: none;
614
+ background: transparent;
615
+ }
616
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--input-underline .osi-field-label.osi-field-label {
617
+ inset-inline-start: 0;
618
+ padding-inline: 0;
619
+ }
620
+
621
+ /* textarea: the first line is at the top, not the middle */
622
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--area .osi-field-label.osi-field-label {
623
+ top: calc(var(--_f-pady) + var(--in-border-w, var(--osi-input-border-w, 1px)));
624
+ transform: none;
625
+ }
626
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--area.osi-field--input-filled .osi-field-label.osi-field-label,
627
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--area.osi-field--input-underline .osi-field-label.osi-field-label {
628
+ top: calc(var(--_f-pady) + var(--_float-fs));
629
+ }
630
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--area .osi-input:focus ~ .osi-field-label.osi-field-label,
631
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--area .osi-input:not(:placeholder-shown) ~ .osi-field-label.osi-field-label {
632
+ top: 0;
633
+ transform: translateY(-50%);
634
+ }
635
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--area.osi-field--input-filled .osi-input:focus ~ .osi-field-label.osi-field-label,
636
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--area.osi-field--input-filled .osi-input:not(:placeholder-shown) ~ .osi-field-label.osi-field-label,
637
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--area.osi-field--input-underline .osi-input:focus ~ .osi-field-label.osi-field-label,
638
+ :is(.osi-root, body) .osi-field.osi-field--label-floating.osi-field--area.osi-field--input-underline .osi-input:not(:placeholder-shown) ~ .osi-field-label.osi-field-label {
639
+ top: var(--_float-gap);
640
+ transform: none;
641
+ }
642
+
643
+ @media (max-width: 639px) {
644
+ :is(.osi-root, body) .osi-field {
645
+ --_f-pady: var(--in-pad-y-mobile, var(--in-pad-y, var(--osi-input-pad-y-mobile, var(--osi-input-pad-y, 10px))));
646
+ --_f-padx: var(--in-pad-x-mobile, var(--in-pad-x, var(--osi-input-pad-x-mobile, var(--osi-input-pad-x, 12px))));
647
+ --_f-fs: max(var(--in-fs, var(--osi-input-fs-mobile, var(--osi-input-fs, 15px))), 16px);
648
+ }
649
+ }
650
+
651
+ @media (prefers-reduced-motion: reduce) {
652
+ :is(.osi-root, body) .osi-field-label {
653
+ transition: none !important;
654
+ }
655
+ }
656
+
657
+ /* ------------------------------------------------------------ PHONE FIELD
658
+ PhoneNumInput's `theme` variant: a country button and a number input,
659
+ both `.osi-input`, joined into one visual box. */
660
+
661
+ :is(.osi-root, body) .osi-phone {
662
+ display: flex;
663
+ align-items: stretch;
664
+ width: 100%;
665
+ min-width: 0;
666
+ }
667
+ :is(.osi-root, body) .osi-phone .osi-phone-code {
668
+ flex: none;
669
+ width: auto;
670
+ display: flex;
671
+ align-items: center;
672
+ gap: 0.375rem;
673
+ border-start-end-radius: 0;
674
+ border-end-end-radius: 0;
675
+ border-inline-end-width: 0;
676
+ cursor: pointer;
677
+ min-height: 100%;
678
+ }
679
+ :is(.osi-root, body) .osi-phone .osi-phone-number {
680
+ flex: 1;
681
+ border-start-start-radius: 0;
682
+ border-end-start-radius: 0;
683
+ }
684
+ :is(.osi-root, body) .osi-field--input-underline .osi-phone .osi-phone-code,
685
+ :is(.osi-root, body).osi-inputs--underline .osi-phone .osi-phone-code {
686
+ padding-inline-end: 0.75rem;
687
+ border-inline-end-width: 0;
688
+ }
689
+ :is(.osi-root, body) .osi-field--input-underline .osi-phone .osi-phone-number,
690
+ :is(.osi-root, body).osi-inputs--underline .osi-phone .osi-phone-number {
691
+ padding-inline-start: 0.5rem;
692
+ }
693
+ /* Filled: two fills side by side read as two boxes; a hairline splits them. */
694
+ :is(.osi-root, body) .osi-field--input-filled .osi-phone .osi-phone-code {
695
+ border-inline-end: 1px solid color-mix(in srgb, currentColor 12%, transparent);
696
+ }
697
+ :is(.osi-root, body) .osi-phone .osi-phone-code img {
698
+ width: 1.25rem;
699
+ height: 1.25rem;
700
+ object-fit: contain;
701
+ }
702
+ :is(.osi-root, body) .osi-phone .osi-phone-code svg {
703
+ width: 0.875rem;
704
+ height: 0.875rem;
705
+ opacity: 0.7;
706
+ }
707
+
708
+ /* ------------------------------------------------------------ OTP DIGITS */
709
+
710
+ :is(.osi-root, body) .osi-input.osi-otp {
711
+ width: 3rem;
712
+ height: 3rem;
713
+ padding: 0;
714
+ text-align: center;
715
+ font-size: 1.25rem;
716
+ }
717
+
718
+ /* ------------------------------------------------------------ AUTH CARD
719
+ The login / register / password pages share one card. It paints with the
720
+ default scheme's background so a floating label's chip matches it. */
721
+
722
+ :is(.osi-root, body) .osi-auth-card {
723
+ --osi-field-chip: var(--osi-bg, #ffffff);
724
+ width: 100%;
725
+ max-width: 550px;
726
+ box-sizing: border-box;
727
+ padding: 1.25rem;
728
+ background: var(--osi-bg, #ffffff);
729
+ color: var(--osi-text, inherit);
730
+ border: 1px solid var(--osi-border, #e5e5e5);
731
+ border-radius: var(--osi-button-radius, 4px);
732
+ }
733
+ @media (min-width: 640px) {
734
+ :is(.osi-root, body) .osi-auth-card {
735
+ padding: 2.25rem;
736
+ }
737
+ }
738
+ :is(.osi-root, body) .osi-auth-stack {
739
+ display: flex;
740
+ flex-direction: column;
741
+ gap: var(--osi-input-gap, 1rem);
742
+ }
743
+ :is(.osi-root, body) .osi-auth-row {
744
+ display: grid;
745
+ grid-template-columns: repeat(2, minmax(0, 1fr));
746
+ gap: var(--osi-input-gap, 1rem);
747
+ }
748
+ @media (max-width: 479px) {
749
+ :is(.osi-root, body) .osi-auth-row {
750
+ grid-template-columns: minmax(0, 1fr);
751
+ }
752
+ }
753
+
754
+ /* Segmented Login / Register switch — it's a pair of buttons, so it takes the
755
+ theme's radius and border width. */
756
+ :is(.osi-root, body) .osi-segmented {
757
+ display: grid;
758
+ grid-template-columns: 1fr 1fr;
759
+ gap: 0.25rem;
760
+ padding: 0.25rem;
761
+ border: var(--osi-button-border-w, 1px) solid var(--osi-border, #e5e5e5);
762
+ border-radius: calc(var(--osi-button-radius, 4px) + 0.25rem);
763
+ }
764
+ :is(.osi-root, body) .osi-segmented > .osi-btn {
765
+ width: 100%;
766
+ }
767
+ :is(.osi-root, body) .osi-segmented > .osi-btn:not(.is-active) {
768
+ --_pbg: transparent;
769
+ --_pfg: inherit;
770
+ --_pbd: transparent;
771
+ --_ibg: transparent;
772
+ --_ifg: inherit;
773
+ --_ibd: transparent;
774
+ box-shadow: none;
775
+ opacity: 0.55;
776
+ }
777
+
778
+ /* ------------------------------------------------------------ SPINNER */
779
+
780
+ :is(.osi-root, body) .osi-loader-row {
781
+ display: flex;
782
+ justify-content: center;
783
+ align-items: center;
784
+ padding: 0.75rem;
785
+ }
786
+
787
+ /* ------------------------------------------------------------ ANIMATIONS
788
+ Theme → Animations. Root classes come from useThemeControls. */
789
+
790
+ /* Reveal sections on scroll (plugins/reveal.client.js adds .is-revealed).
791
+ The first section is never hidden: it's what the visitor lands on. */
792
+ .osi-root.osi-reveal .osi-sections > .osi-section:not(:first-child):not(.is-revealed) {
793
+ opacity: 0;
794
+ transform: translateY(24px);
795
+ }
796
+ .osi-root.osi-reveal .osi-section {
797
+ transition: opacity 0.7s ease, transform 0.7s cubic-bezier(0.22, 0.61, 0.36, 1);
798
+ }
799
+
800
+ /* Hover effect on cards and linked images across sections. */
801
+ .osi-root.osi-hover--lift .osi-card,
802
+ .osi-root.osi-hover--lift .osi-cc,
803
+ .osi-root.osi-hover--lift .osi-ig-card {
804
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
805
+ }
806
+ .osi-root.osi-hover--lift .osi-card:hover,
807
+ .osi-root.osi-hover--lift .osi-cc:hover,
808
+ .osi-root.osi-hover--lift .osi-ig-card:hover {
809
+ transform: translateY(-4px);
810
+ box-shadow: 0 14px 30px -14px rgb(0 0 0 / 0.3);
811
+ }
812
+
813
+ @media (prefers-reduced-motion: reduce) {
814
+ .osi-root.osi-reveal .osi-section {
815
+ opacity: 1 !important;
816
+ transform: none !important;
817
+ transition: none;
818
+ }
819
+ .osi-root.osi-hover--lift .osi-card:hover,
820
+ .osi-root.osi-hover--lift .osi-cc:hover,
821
+ .osi-root.osi-hover--lift .osi-ig-card:hover {
822
+ transform: none;
823
+ }
824
+ }