Spaces:
Runtime error
Runtime error
| @import url('https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;600;700&display=swap'); | |
| @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:wght@400;500;600;700&display=swap'); | |
| :root { | |
| font-family: 'Plus Jakarta Sans', system-ui, Avenir, Helvetica, Arial, sans-serif; | |
| line-height: 1.6; | |
| font-weight: 400; | |
| } | |
| html[lang='he'], | |
| html[dir='rtl'] { | |
| font-family: 'Heebo', 'Rubik', 'Noto Sans Hebrew', 'Segoe UI', Arial, sans-serif; | |
| } | |
| html[lang='ar'] { | |
| font-family: 'Noto Sans Arabic', 'Cairo', 'Tajawal', 'Segoe UI', Arial, sans-serif; | |
| } | |
| :root { | |
| color-scheme: light dark; | |
| color: var(--text-primary); | |
| background-color: var(--bg-light); | |
| font-synthesis: none; | |
| text-rendering: optimizeLegibility; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| } | |
| a { | |
| font-weight: 500; | |
| text-decoration: inherit; | |
| } | |
| body { | |
| margin: 0; | |
| min-width: 320px; | |
| min-height: 100vh; | |
| } | |
| h1 { | |
| font-size: 1.875rem; | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| margin: 0; | |
| letter-spacing: -0.025em; | |
| } | |
| /* h2 is a real heading again — the old global made EVERY h2 a small uppercase eyebrow, which | |
| forced pages to keep resetting it (and left section titles smaller than body text). The eyebrow | |
| look is now opt-in via .eyebrow for the section/card labels that actually want it. */ | |
| h2 { | |
| font-size: 1.25rem; | |
| font-weight: 700; | |
| color: var(--text-primary); | |
| margin: 0; | |
| } | |
| .eyebrow { | |
| font-size: 0.8125rem; | |
| font-weight: 600; | |
| color: var(--text-secondary); | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin: 0; | |
| } | |
| h3 { | |
| font-size: 1rem; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| margin: 0; | |
| } | |
| button { | |
| border-radius: 8px; | |
| border: 1px solid transparent; | |
| padding: 0.6em 1.2em; | |
| font-size: 1em; | |
| font-weight: 500; | |
| font-family: inherit; | |
| cursor: pointer; | |
| transition: border-color 0.25s; | |
| } | |
| /* Visible keyboard focus — cross-browser and keyboard-only, so the ring guides Tab navigation | |
| without flashing on every mouse click. */ | |
| button:focus-visible, | |
| a:focus-visible, | |
| [tabindex]:focus-visible { | |
| outline: 2px solid var(--primary, #25d366); | |
| outline-offset: 2px; | |
| } | |
| button:focus:not(:focus-visible) { | |
| outline: none; | |
| } | |
| html { | |
| /* Root/overscroll background follows the app theme token, so an explicit dark theme on a | |
| light-OS device doesn't reveal a white <html> background above/below the body (overscroll). */ | |
| background-color: var(--bg-light); | |
| } | |
| @media (prefers-color-scheme: light) { | |
| /* Guarded with :not([data-theme='dark']): when the OS prefers light but the user explicitly | |
| picked the dark theme, the app's own choice must win (otherwise the root is force-whitened). */ | |
| :root:not([data-theme='dark']) { | |
| color: #213547; | |
| background-color: #ffffff; | |
| } | |
| } | |
| /* Global select/dropdown styling */ | |
| select { | |
| appearance: none; | |
| -webkit-appearance: none; | |
| -moz-appearance: none; | |
| width: 100%; | |
| padding: 0.75rem 2.5rem 0.75rem 1rem; | |
| font-size: 0.9375rem; | |
| font-family: inherit; | |
| color: var(--text-primary); | |
| background-color: var(--bg-white, #fff); | |
| background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2364748B' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); | |
| background-repeat: no-repeat; | |
| background-position: right 0.75rem center; | |
| background-size: 16px; | |
| border: 1px solid var(--border, #e2e8f0); | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| select:hover { | |
| border-color: var(--primary, #25d366); | |
| } | |
| select:focus { | |
| outline: none; | |
| border-color: var(--primary, #25d366); | |
| box-shadow: 0 0 0 3px rgba(37, 211, 102, 0.1); | |
| } | |
| select:disabled { | |
| background-color: var(--bg-light, #f8fafc); | |
| color: var(--text-muted, #94a3b8); | |
| cursor: not-allowed; | |
| } | |
| /* GLOBAL MODAL STYLES */ | |
| /* Modal Overlay */ | |
| .modal-overlay { | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0, 0, 0, 0.5); | |
| backdrop-filter: blur(4px); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 1000; | |
| animation: fadeIn 0.2s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| } | |
| to { | |
| opacity: 1; | |
| } | |
| } | |
| /* Modal Card */ | |
| .modal { | |
| background: var(--bg-white, #fff); | |
| border-radius: 16px; | |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); | |
| width: 90%; | |
| max-width: 480px; | |
| animation: slideUp 0.3s ease; | |
| /* Cap height and lay out as a flex column so the body scrolls while the header and footer stay | |
| pinned — config-heavy modals no longer push the title/Save button off-screen. */ | |
| display: flex; | |
| flex-direction: column; | |
| max-height: 90vh; | |
| overflow: hidden; | |
| } | |
| /* The install-plugin modal holds the catalog list — a bit wider on desktop for comfortable reading. | |
| The responsive rules below (<=768px: 95% width; <=480px: full-width bottom sheet) still apply, so | |
| it stays responsive on small screens. Lives here (global modal styles) rather than in a page CSS | |
| so it isn't subject to the page-root scoping rule. */ | |
| .modal.install-modal { | |
| max-width: 680px; | |
| } | |
| @keyframes slideUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .modal-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 1.5rem 1.5rem 1rem; | |
| border-bottom: 1px solid var(--border, #e2e8f0); | |
| flex-shrink: 0; | |
| } | |
| .modal-header h2 { | |
| font-size: 1.25rem; | |
| font-weight: 700; | |
| color: var(--text-primary, #1e293b); | |
| margin: 0; | |
| text-transform: none; | |
| letter-spacing: normal; | |
| } | |
| .modal-body { | |
| padding: 1.5rem; | |
| /* Scroll the body (not the whole card) so the pinned header/footer stay visible. */ | |
| overflow-y: auto; | |
| flex: 1 1 auto; | |
| } | |
| .modal-body > label { | |
| display: block; | |
| font-size: 0.875rem; | |
| font-weight: 600; | |
| color: var(--text-secondary, #64748b); | |
| margin-bottom: 0.5rem; | |
| } | |
| .modal-body input { | |
| width: 100%; | |
| padding: 0.875rem 1rem; | |
| border: 1px solid var(--border, #e2e8f0); | |
| border-radius: 8px; | |
| font-size: 0.9375rem; | |
| background: var(--bg-light, #f8fafc); | |
| transition: all 0.2s; | |
| box-sizing: border-box; | |
| } | |
| .modal-body input:focus { | |
| outline: none; | |
| border-color: var(--primary, #25d366); | |
| background: var(--bg-white, #fff); | |
| box-shadow: 0 0 0 3px rgba(37, 211, 102, 0.1); | |
| } | |
| /* The rule above is meant for TEXT inputs. Radios/checkboxes would otherwise stretch to width:100% | |
| and shove their labels far to the right; reset them back to their native box. The :not() excludes | |
| the visually-hidden toggle-switch checkbox (it must stay width:0) so this never ties with the | |
| per-page `.toggle-switch input { width:0 }` rule on specificity. */ | |
| .modal-body input[type='radio']:not(.toggle-switch input), | |
| .modal-body input[type='checkbox']:not(.toggle-switch input) { | |
| width: auto; | |
| padding: 0; | |
| border: none; | |
| border-radius: 0; | |
| background: none; | |
| box-shadow: none; | |
| flex: none; | |
| } | |
| .modal-footer { | |
| display: flex; | |
| justify-content: flex-end; | |
| gap: 0.75rem; | |
| padding: 1rem 1.5rem 1.5rem; | |
| flex-shrink: 0; | |
| } | |
| /* Icon Button (modal close X) */ | |
| .btn-icon { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 36px; | |
| height: 36px; | |
| background: var(--bg-light, #f8fafc); | |
| border: 1px solid var(--border, #e2e8f0); | |
| border-radius: 8px; | |
| cursor: pointer; | |
| color: var(--text-secondary, #64748b); | |
| transition: all 0.2s; | |
| flex-shrink: 0; | |
| padding: 0; | |
| } | |
| .btn-icon svg { | |
| width: 20px; | |
| height: 20px; | |
| stroke: var(--text-secondary, #64748b); | |
| } | |
| .btn-icon:hover { | |
| background: rgba(239, 68, 68, 0.12); | |
| border-color: rgba(239, 68, 68, 0.3); | |
| color: var(--error); | |
| } | |
| .btn-icon:hover svg { | |
| stroke: var(--error); | |
| } | |
| /* Secondary Button */ | |
| .btn-secondary { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| padding: 0.75rem 1.25rem; | |
| background: var(--bg-light, #f8fafc); | |
| color: var(--text-secondary, #64748b); | |
| border: 1px solid var(--border, #e2e8f0); | |
| border-radius: 8px; | |
| font-size: 0.9375rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .btn-secondary:hover:not(:disabled) { | |
| background: var(--bg-white, #fff); | |
| border-color: var(--text-muted, #94a3b8); | |
| color: var(--text-primary, #1e293b); | |
| } | |
| .btn-secondary:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| /* Primary Button — canonical definition; page-scoped copies were consolidated here so the | |
| primary CTA looks identical on every page (padding/radius/disabled used to drift per page). */ | |
| .btn-primary { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| padding: 0.75rem 1.25rem; | |
| background: var(--primary, #25d366); | |
| color: #fff; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 0.9375rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .btn-primary:hover:not(:disabled) { | |
| background: var(--primary-hover, #1da851); | |
| } | |
| .btn-primary:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| /* Danger Button — canonical definition (page copies also used two different base reds). */ | |
| .btn-danger { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| padding: 0.75rem 1.25rem; | |
| background: var(--error, #ef4444); | |
| color: #fff; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 0.9375rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .btn-danger:hover:not(:disabled) { | |
| background: #b91c1c; | |
| } | |
| .btn-danger:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| /* Spin Animation */ | |
| @keyframes spin { | |
| from { | |
| transform: rotate(0deg); | |
| } | |
| to { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| .animate-spin { | |
| animation: spin 1s linear infinite; | |
| } | |
| /* Code element styling */ | |
| code { | |
| background: var(--bg-light, #f1f5f9); | |
| padding: 0.125rem 0.375rem; | |
| border-radius: 4px; | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; | |
| font-size: 0.875em; | |
| color: var(--text-primary, #1e293b); | |
| } | |
| /* RTL / Hebrew direction support */ | |
| /* Keep code, technical identifiers, and numbers LTR even inside RTL containers */ | |
| [dir='rtl'] code, | |
| [dir='rtl'] .mono, | |
| [dir='rtl'] pre { | |
| direction: ltr; | |
| text-align: left; | |
| unicode-bidi: embed; | |
| } | |
| /* Modal footer: actions should align to the start of the writing direction */ | |
| [dir='rtl'] .modal-footer { | |
| justify-content: flex-end; | |
| flex-direction: row-reverse; | |
| } | |
| /* Select dropdown caret on the opposite side */ | |
| [dir='rtl'] select { | |
| padding: 0.75rem 1rem 0.75rem 2.5rem; | |
| background-position: left 0.75rem center; | |
| } | |
| /* Inputs in modals: text aligns naturally */ | |
| [dir='rtl'] .modal-body input, | |
| [dir='rtl'] .modal-body textarea, | |
| [dir='rtl'] input[type='text'], | |
| [dir='rtl'] input[type='url'], | |
| [dir='rtl'] input[type='password'], | |
| [dir='rtl'] input[type='number'], | |
| [dir='rtl'] textarea { | |
| text-align: right; | |
| } | |
| /* URLs, emails, phones — force LTR even inside RTL */ | |
| [dir='rtl'] input[type='url'], | |
| [dir='rtl'] input[type='email'], | |
| [dir='rtl'] input[type='tel'] { | |
| direction: ltr; | |
| text-align: left; | |
| } | |
| /* Toast container — slide in from the opposite side */ | |
| [dir='rtl'] .toast-container { | |
| right: auto; | |
| left: 1rem; | |
| } | |
| /* Mobile Responsive - Global */ | |
| @media (max-width: 768px) { | |
| h1 { | |
| font-size: 1.5rem; | |
| } | |
| .modal { | |
| width: 95%; | |
| max-width: none; | |
| margin: 1rem; | |
| border-radius: 12px; | |
| } | |
| .modal-header { | |
| padding: 1.25rem 1.25rem 0.875rem; | |
| } | |
| .modal-header h2 { | |
| font-size: 1.125rem; | |
| } | |
| .modal-body { | |
| padding: 1.25rem; | |
| } | |
| .modal-footer { | |
| padding: 0.875rem 1.25rem 1.25rem; | |
| } | |
| select { | |
| padding: 0.625rem 2.25rem 0.625rem 0.875rem; | |
| font-size: 0.875rem; | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| h1 { | |
| font-size: 1.25rem; | |
| } | |
| .modal-overlay { | |
| align-items: flex-end; | |
| } | |
| .modal { | |
| width: 100%; | |
| margin: 0; | |
| border-radius: 16px 16px 0 0; | |
| /* max-height + body scroll already come from the base .modal rule; the bottom-sheet only | |
| changes width/anchoring. (Scrolling lives on .modal-body to avoid a double scrollbar.) */ | |
| } | |
| } | |