From: Danilo M. Date: Thu, 16 Apr 2026 14:53:21 +0000 (+0200) Subject: feat: add form component styles (inputs, textarea, checkbox, radio) X-Git-Tag: release_22042026-1342~185 X-Git-Url: https://git.danix.xyz/?a=commitdiff_plain;h=9877591a86cb66917c35d5b58190270061d7261a;p=danix.xyz-2.git feat: add form component styles (inputs, textarea, checkbox, radio) --- diff --git a/themes/danix-xyz-hacker/assets/css/main.css b/themes/danix-xyz-hacker/assets/css/main.css index 9e5c86a..19584bf 100644 --- a/themes/danix-xyz-hacker/assets/css/main.css +++ b/themes/danix-xyz-hacker/assets/css/main.css @@ -595,3 +595,477 @@ article.border.border-border\/30.rounded-lg.overflow-hidden.group.bg-bg { border-color: var(--border); box-shadow: 0 0 20px var(--accent-glow); } + +/* ============================================ + FORM COMPONENTS (Week 4) + ============================================ */ + +/* Form input base styles */ +.form-input, +.form-textarea, +.form-select { + @apply w-full px-4 py-2 rounded border border-border bg-bg2 text-text font-body transition-all duration-200; +} + +/* Input placeholder styling */ +.form-input::placeholder, +.form-textarea::placeholder { + color: var(--text-dim); + opacity: 0.7; +} + +/* Input focus state */ +.form-input:focus, +.form-textarea:focus, +.form-select:focus { + @apply outline-none border-accent ring-2 ring-accent ring-offset-2; + ring-offset-color: var(--bg); + border-color: var(--accent); +} + +/* Input invalid/error state */ +.form-input:invalid, +.form-textarea:invalid, +.form-select:invalid, +.form-input.error, +.form-textarea.error, +.form-select.error { + @apply border-red-500 ring-red-500; +} + +.form-input:invalid:focus, +.form-textarea:invalid:focus, +.form-select:invalid:focus { + @apply ring-red-500 border-red-500; + ring-offset-color: var(--bg); +} + +/* Input disabled state */ +.form-input:disabled, +.form-textarea:disabled, +.form-select:disabled { + @apply opacity-50 cursor-not-allowed bg-muted; +} + +/* Textarea specific */ +.form-textarea { + @apply min-h-32; + resize: vertical; +} + +.form-textarea.auto-expand { + resize: none; + overflow-y: hidden; +} + +/* Select dropdown */ +.form-select { + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23a855f7' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.75rem center; + padding-right: 2.5rem; +} + +html.theme-light .form-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239333ea' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); +} + +/* Checkbox and radio button base */ +.form-checkbox, +.form-radio { + @apply w-5 h-5 cursor-pointer accent-accent transition-all duration-200; + appearance: none; + border: 2px solid var(--border); + border-radius: 0.375rem; + flex-shrink: 0; +} + +.form-radio { + border-radius: 50%; +} + +/* Checkbox/radio focus state */ +.form-checkbox:focus-visible, +.form-radio:focus-visible { + @apply outline-none ring-2 ring-accent ring-offset-2; + ring-offset-color: var(--bg); +} + +/* Checkbox/radio checked state */ +.form-checkbox:checked, +.form-radio:checked { + background-color: var(--accent); + border-color: var(--accent); + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; +} + +.form-radio:checked { + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3.5'/%3E%3C/svg%3E"); +} + +/* Checkbox/radio disabled state */ +.form-checkbox:disabled, +.form-radio:disabled { + @apply opacity-50 cursor-not-allowed; + border-color: var(--muted); +} + +/* Form group layout */ +.form-group { + @apply space-y-2; +} + +.form-group label { + @apply block text-sm font-semibold text-text; +} + +.form-group.required label::after { + content: ' *'; + color: #ef4444; +} + +.form-group-input { + @apply relative; +} + +.form-group-help { + @apply text-xs text-text-dim; +} + +.form-group.error .form-group-help { + @apply text-red-500; +} + +.form-error { + @apply text-sm text-red-500 mt-1; +} + +/* Form layout utilities */ +.form-row { + @apply flex flex-col md:flex-row gap-4; +} + +.form-row > .form-group { + @apply flex-1; +} + +.form-inline { + @apply flex flex-col sm:flex-row items-end gap-4; +} + +.form-inline .form-group { + @apply flex-1; +} + +.form-inline .btn { + @apply h-10; +} + +/* Character count indicator */ +.form-char-count { + @apply text-xs text-text-dim text-right; +} + +.form-char-count.warning { + @apply text-amber-500; +} + +.form-char-count.error { + @apply text-red-500; +} + +/* ============================================ + MODAL COMPONENTS (Week 4) + ============================================ */ + +/* Modal backdrop */ +.modal-backdrop { + @apply fixed inset-0 bg-black/50 opacity-0 invisible transition-all duration-300 z-40; + backdrop-filter: blur(2px); +} + +.modal-backdrop.active { + @apply opacity-100 visible; +} + +/* Modal container */ +.modal { + @apply fixed inset-0 flex items-center justify-center opacity-0 invisible transition-all duration-300 z-50 p-4; + pointer-events: none; +} + +.modal.active { + @apply opacity-100 visible; + pointer-events: auto; +} + +.modal.active .modal-backdrop { + @apply opacity-100 visible; + pointer-events: auto; +} + +/* Modal content box */ +.modal-content { + @apply bg-bg2 border border-border rounded-lg shadow-2xl max-w-lg w-full max-h-[90vh] flex flex-col; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + animation: modalSlideUp 0.3s ease-out; +} + +@keyframes modalSlideUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* Modal header */ +.modal-header { + @apply flex items-start justify-between gap-4 p-6 border-b border-border; +} + +.modal-title { + @apply text-xl font-bold text-text; +} + +.modal-close { + @apply flex items-center justify-center w-8 h-8 rounded hover:bg-surface cursor-pointer transition-colors; +} + +.modal-close::before, +.modal-close::after { + content: ''; + @apply absolute w-5 h-0.5 bg-text-dim; +} + +.modal-close::before { + transform: rotate(45deg); +} + +.modal-close::after { + transform: rotate(-45deg); +} + +.modal-close:hover::before, +.modal-close:hover::after { + @apply bg-accent; +} + +/* Modal body */ +.modal-body { + @apply flex-1 overflow-y-auto p-6 space-y-4; +} + +/* Modal footer */ +.modal-footer { + @apply flex items-center justify-end gap-3 p-6 border-t border-border; +} + +/* Modal sizes */ +.modal-content.modal-sm { + @apply max-w-sm; +} + +.modal-content.modal-md { + @apply max-w-md; +} + +.modal-content.modal-lg { + @apply max-w-2xl; +} + +/* Modal variants */ +.modal-content.modal-alert { + @apply max-w-sm; +} + +.modal-content.modal-confirm { + @apply max-w-sm; +} + +/* Modal button styling */ +.modal-footer .btn { + @apply min-w-[100px]; +} + +/* ============================================ + INTERACTIVE PATTERNS (Week 4) + ============================================ */ + +/* Loading spinner */ +.spinner { + @apply inline-block; + width: 1rem; + height: 1rem; + border: 2px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.spinner-sm { + width: 0.75rem; + height: 0.75rem; + border-width: 1.5px; +} + +.spinner-lg { + width: 1.5rem; + height: 1.5rem; + border-width: 3px; +} + +/* Button with spinner */ +.btn:disabled .spinner { + @apply inline-block mr-2; +} + +/* Toast notification container */ +.toast-container { + @apply fixed bottom-0 right-0 p-4 space-y-3 max-w-sm z-50; +} + +@media (max-width: 640px) { + .toast-container { + @apply left-0 right-0 px-4; + } +} + +/* Toast base */ +.toast { + @apply flex items-start gap-3 p-4 rounded-lg border border-border shadow-lg; + animation: slideInUp 0.3s ease-out; + background-color: var(--bg2); + color: var(--text); +} + +@keyframes slideInUp { + from { + transform: translateY(100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* Toast variants */ +.toast-success { + border-color: #10b981; + background-color: rgba(16, 185, 129, 0.1); +} + +.toast-success::before { + content: '✓'; + color: #10b981; + font-weight: bold; + flex-shrink: 0; +} + +.toast-error { + border-color: #ef4444; + background-color: rgba(239, 68, 68, 0.1); +} + +.toast-error::before { + content: '✕'; + color: #ef4444; + font-weight: bold; + flex-shrink: 0; +} + +.toast-info { + border-color: #3b82f6; + background-color: rgba(59, 130, 246, 0.1); +} + +.toast-info::before { + content: 'ℹ'; + color: #3b82f6; + flex-shrink: 0; +} + +.toast-warning { + border-color: #f59e0b; + background-color: rgba(245, 158, 11, 0.1); +} + +.toast-warning::before { + content: '⚠'; + color: #f59e0b; + flex-shrink: 0; +} + +.toast-close { + @apply ml-auto flex-shrink-0 w-6 h-6 flex items-center justify-center cursor-pointer hover:bg-surface rounded transition-colors; +} + +/* Tooltip */ +.tooltip { + @apply relative inline-block; +} + +.tooltip-text { + @apply absolute bg-bg2 text-text-dim text-xs rounded px-2 py-1 whitespace-nowrap pointer-events-none opacity-0 invisible transition-all duration-200; + z-index: 50; + bottom: 125%; + left: 50%; + transform: translateX(-50%); +} + +.tooltip:hover .tooltip-text { + @apply opacity-100 visible; +} + +.tooltip-text::after { + content: ''; + @apply absolute w-2 h-2 bg-bg2; + bottom: -4px; + left: 50%; + transform: translateX(-50%) rotate(45deg); +} + +/* Tooltip directions */ +.tooltip-bottom .tooltip-text { + @apply top-[125%] bottom-auto; +} + +.tooltip-bottom .tooltip-text::after { + @apply top-[-4px] bottom-auto; + transform: translateX(-50%) rotate(225deg); +} + +.tooltip-left .tooltip-text { + @apply left-auto right-[125%]; + transform: none; +} + +.tooltip-left .tooltip-text::after { + @apply left-auto right-[-4px]; + transform: rotate(135deg); +} + +.tooltip-right .tooltip-text { + @apply left-[125%]; + transform: none; +} + +.tooltip-right .tooltip-text::after { + @apply left-[-4px] right-auto; + transform: rotate(315deg); +} diff --git a/themes/danix-xyz-hacker/assets/css/main.min.css b/themes/danix-xyz-hacker/assets/css/main.min.css index eead573..cf5343a 100644 --- a/themes/danix-xyz-hacker/assets/css/main.min.css +++ b/themes/danix-xyz-hacker/assets/css/main.min.css @@ -2423,6 +2423,790 @@ article.border.border-border\/30.rounded-lg.overflow-hidden.group.bg-bg { box-shadow: 0 0 20px var(--accent-glow); } +/* ============================================ + FORM COMPONENTS (Week 4) + ============================================ */ + +/* Form input base styles */ + +.form-input, +.form-textarea, +.form-select { + width: 100%; + border-radius: 0.25rem; + border-width: 1px; + border-color: var(--border); + background-color: var(--bg2); + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + font-family: IBM Plex Sans, sans-serif; + color: var(--text); + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; +} + +article.form-input.border-border\/30.rounded-lg.overflow-hidden.group.bg-bg,article +.form-textarea.border-border\/30.rounded-lg.overflow-hidden.group.bg-bg,article +.form-select.border-border\/30.rounded-lg.overflow-hidden.group.bg-bg { + border-color: var(--border); + box-shadow: 0 0 20px var(--accent-glow); +} + +/* Input placeholder styling */ + +.form-input::-moz-placeholder, .form-textarea::-moz-placeholder { + color: var(--text-dim); + opacity: 0.7; +} + +.form-input::placeholder, +.form-textarea::placeholder { + color: var(--text-dim); + opacity: 0.7; +} + +/* Input focus state */ + +.form-input:focus, +.form-textarea:focus, +.form-select:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + --tw-ring-color: var(--accent); + --tw-ring-offset-width: 2px; + ring-offset-color: var(--bg); + border-color: var(--accent); +} + +/* Input invalid/error state */ + +.form-input:invalid, +.form-textarea:invalid, +.form-select:invalid, +.form-input.error, +.form-textarea.error, +.form-select.error { + --tw-border-opacity: 1; + border-color: rgb(239 68 68 / var(--tw-border-opacity, 1)); + --tw-ring-opacity: 1; + --tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1)); +} + +.form-input:invalid:focus, +.form-textarea:invalid:focus, +.form-select:invalid:focus { + --tw-border-opacity: 1; + border-color: rgb(239 68 68 / var(--tw-border-opacity, 1)); + --tw-ring-opacity: 1; + --tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity, 1)); + ring-offset-color: var(--bg); +} + +/* Input disabled state */ + +.form-input:disabled, +.form-textarea:disabled, +.form-select:disabled { + cursor: not-allowed; + background-color: var(--muted); + opacity: 0.5; +} + +/* Textarea specific */ + +.form-textarea { + min-height: 8rem; + resize: vertical; +} + +.form-textarea.auto-expand { + resize: none; + overflow-y: hidden; +} + +/* Select dropdown */ + +.form-select { + cursor: pointer; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23a855f7' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.75rem center; + padding-right: 2.5rem; +} + +html.theme-light .form-select { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239333ea' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); +} + +/* Checkbox and radio button base */ + +.form-checkbox, +.form-radio { + height: 1.25rem; + width: 1.25rem; + cursor: pointer; + accent-color: var(--accent); + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border: 2px solid var(--border); + border-radius: 0.375rem; + flex-shrink: 0; +} + +.form-radio { + border-radius: 50%; +} + +/* Checkbox/radio focus state */ + +.form-checkbox:focus-visible, +.form-radio:focus-visible { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + --tw-ring-color: var(--accent); + --tw-ring-offset-width: 2px; + ring-offset-color: var(--bg); +} + +/* Checkbox/radio checked state */ + +.form-checkbox:checked, +.form-radio:checked { + background-color: var(--accent); + border-color: var(--accent); + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; +} + +.form-radio:checked { + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='8' cy='8' r='3.5'/%3E%3C/svg%3E"); +} + +/* Checkbox/radio disabled state */ + +.form-checkbox:disabled, +.form-radio:disabled { + cursor: not-allowed; + opacity: 0.5; + border-color: var(--muted); +} + +/* Form group layout */ + +.form-group > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.form-group label { + display: block; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 600; + color: var(--text); +} + +.form-group.required label::after { + content: ' *'; + color: #ef4444; +} + +.form-group-input { + position: relative; +} + +.form-group-help { + font-size: 0.75rem; + line-height: 1rem; + color: var(--text-dim); +} + +.form-group.error .form-group-help { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity, 1)); +} + +.form-error { + margin-top: 0.25rem; + font-size: 0.875rem; + line-height: 1.25rem; + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity, 1)); +} + +/* Form layout utilities */ + +.form-row { + display: flex; + flex-direction: column; + gap: 1rem; +} + +@media (min-width: 768px) { + .form-row { + flex-direction: row; + } +} + +.form-row > .form-group { + flex: 1 1 0%; +} + +.form-inline { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 1rem; +} + +@media (min-width: 640px) { + .form-inline { + flex-direction: row; + } +} + +.form-inline .form-group { + flex: 1 1 0%; +} + +.form-inline .btn { + height: 2.5rem; +} + +/* Character count indicator */ + +.form-char-count { + text-align: right; + font-size: 0.75rem; + line-height: 1rem; + color: var(--text-dim); +} + +.form-char-count.warning { + --tw-text-opacity: 1; + color: rgb(245 158 11 / var(--tw-text-opacity, 1)); +} + +.form-char-count.error { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity, 1)); +} + +/* ============================================ + MODAL COMPONENTS (Week 4) + ============================================ */ + +/* Modal backdrop */ + +.modal-backdrop { + visibility: hidden; + position: fixed; + inset: 0px; + z-index: 40; + background-color: rgb(0 0 0 / 0.5); + opacity: 0; + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 300ms; + -webkit-backdrop-filter: blur(2px); + backdrop-filter: blur(2px); +} + +.modal-backdrop.active { + visibility: visible; + opacity: 1; +} + +/* Modal container */ + +.modal { + visibility: hidden; + position: fixed; + inset: 0px; + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; + opacity: 0; + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 300ms; + pointer-events: none; +} + +.modal.active { + visibility: visible; + opacity: 1; + pointer-events: auto; +} + +.modal.active .modal-backdrop { + visibility: visible; + opacity: 1; + pointer-events: auto; +} + +/* Modal content box */ + +.modal-content { + display: flex; + max-height: 90vh; + width: 100%; + max-width: 32rem; + flex-direction: column; + border-radius: 0.5rem; + border-width: 1px; + border-color: var(--border); + background-color: var(--bg2); + --tw-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); + --tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +article.modal-content.border-border\/30.rounded-lg.overflow-hidden.group.bg-bg { + border-color: var(--border); + box-shadow: 0 0 20px var(--accent-glow); +} + +article.border.border-border\/30.modal-content.overflow-hidden.group.bg-bg { + border-color: var(--border); + box-shadow: 0 0 20px var(--accent-glow); +} + +.modal-content { + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + animation: modalSlideUp 0.3s ease-out; +} + +@keyframes modalSlideUp { + from { + transform: translateY(20px); + opacity: 0; + } + + to { + transform: translateY(0); + opacity: 1; + } +} + +/* Modal header */ + +.modal-header { + border-color: var(--border); +} + +.frosted-bar.modal-header { + border-color: var(--border); +} + +.modal-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + border-bottom-width: 1px; + border-color: var(--border); + padding: 1.5rem; +} + +.modal-title { + font-size: 1.25rem; + line-height: 1.75rem; + font-weight: 700; + color: var(--text); +} + +.modal-close { + display: flex; + height: 2rem; + width: 2rem; + cursor: pointer; + align-items: center; + justify-content: center; + border-radius: 0.25rem; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.modal-close:hover { + background-color: var(--surface); +} + +.modal-close::before, +.modal-close::after { + content: ''; + position: absolute; + height: 0.125rem; + width: 1.25rem; + background-color: var(--text-dim); +} + +.modal-close::before { + transform: rotate(45deg); +} + +.modal-close::after { + transform: rotate(-45deg); +} + +.modal-close:hover::before, +.modal-close:hover::after { + background-color: var(--accent); +} + +/* Modal body */ + +.modal-body { + flex: 1 1 0%; +} + +.modal-body > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.modal-body { + overflow-y: auto; + padding: 1.5rem; +} + +/* Modal footer */ + +.modal-footer { + border-color: var(--border); +} + + + .frosted-bar.modal-footer { + border-color: var(--border); +} + +.modal-footer { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 0.75rem; + border-top-width: 1px; + border-color: var(--border); + padding: 1.5rem; +} + +/* Modal sizes */ + +.modal-content.modal-sm { + max-width: 24rem; +} + +.modal-content.modal-md { + max-width: 28rem; +} + +.modal-content.modal-lg { + max-width: 42rem; +} + +/* Modal variants */ + +.modal-content.modal-alert { + max-width: 24rem; +} + +.modal-content.modal-confirm { + max-width: 24rem; +} + +/* Modal button styling */ + +.modal-footer .btn { + min-width: 100px; +} + +/* ============================================ + INTERACTIVE PATTERNS (Week 4) + ============================================ */ + +/* Loading spinner */ + +.spinner { + display: inline-block; + width: 1rem; + height: 1rem; + border: 2px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.spinner-sm { + width: 0.75rem; + height: 0.75rem; + border-width: 1.5px; +} + +.spinner-lg { + width: 1.5rem; + height: 1.5rem; + border-width: 3px; +} + +/* Button with spinner */ + +.btn:disabled .spinner { + margin-right: 0.5rem; + display: inline-block; +} + +/* Toast notification container */ + +.toast-container { + position: fixed; + bottom: 0px; + right: 0px; + z-index: 50; + max-width: 24rem; +} + +.toast-container > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.75rem * var(--tw-space-y-reverse)); +} + +.toast-container { + padding: 1rem; +} + +@media (max-width: 640px) { + .toast-container { + left: 0px; + right: 0px; + padding-left: 1rem; + padding-right: 1rem; + } +} + +/* Toast base */ + +.toast { + display: flex; + align-items: flex-start; + gap: 0.75rem; + border-radius: 0.5rem; + border-width: 1px; + border-color: var(--border); + padding: 1rem; + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +article.border.border-border\/30.toast.overflow-hidden.group.bg-bg { + border-color: var(--border); + box-shadow: 0 0 20px var(--accent-glow); +} + +article.toast.border-border\/30.rounded-lg.overflow-hidden.group.bg-bg { + border-color: var(--border); + box-shadow: 0 0 20px var(--accent-glow); +} + +.toast { + animation: slideInUp 0.3s ease-out; + background-color: var(--bg2); + color: var(--text); +} + +@keyframes slideInUp { + from { + transform: translateY(100%); + opacity: 0; + } + + to { + transform: translateY(0); + opacity: 1; + } +} + +/* Toast variants */ + +.toast-success { + border-color: #10b981; + background-color: rgba(16, 185, 129, 0.1); +} + +.toast-success::before { + content: '✓'; + color: #10b981; + font-weight: bold; + flex-shrink: 0; +} + +.toast-error { + border-color: #ef4444; + background-color: rgba(239, 68, 68, 0.1); +} + +.toast-error::before { + content: '✕'; + color: #ef4444; + font-weight: bold; + flex-shrink: 0; +} + +.toast-info { + border-color: #3b82f6; + background-color: rgba(59, 130, 246, 0.1); +} + +.toast-info::before { + content: 'ℹ'; + color: #3b82f6; + flex-shrink: 0; +} + +.toast-warning { + border-color: #f59e0b; + background-color: rgba(245, 158, 11, 0.1); +} + +.toast-warning::before { + content: '⚠'; + color: #f59e0b; + flex-shrink: 0; +} + +.toast-close { + margin-left: auto; + display: flex; + height: 1.5rem; + width: 1.5rem; + flex-shrink: 0; + cursor: pointer; + align-items: center; + justify-content: center; + border-radius: 0.25rem; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.toast-close:hover { + background-color: var(--surface); +} + +/* Tooltip */ + +.tooltip { + position: relative; + display: inline-block; +} + +.tooltip-text { + pointer-events: none; + visibility: hidden; + position: absolute; + white-space: nowrap; + border-radius: 0.25rem; + background-color: var(--bg2); + padding-left: 0.5rem; + padding-right: 0.5rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + font-size: 0.75rem; + line-height: 1rem; + color: var(--text-dim); + opacity: 0; + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 200ms; + z-index: 50; + bottom: 125%; + left: 50%; + transform: translateX(-50%); +} + +.tooltip:hover .tooltip-text { + visibility: visible; + opacity: 1; +} + +.tooltip-text::after { + content: ''; + position: absolute; + height: 0.5rem; + width: 0.5rem; + background-color: var(--bg2); + bottom: -4px; + left: 50%; + transform: translateX(-50%) rotate(45deg); +} + +/* Tooltip directions */ + +.tooltip-bottom .tooltip-text { + top: 125%; + bottom: auto; +} + +.tooltip-bottom .tooltip-text::after { + top: -4px; + bottom: auto; + transform: translateX(-50%) rotate(225deg); +} + +.tooltip-left .tooltip-text { + left: auto; + right: 125%; + transform: none; +} + +.tooltip-left .tooltip-text::after { + left: auto; + right: -4px; + transform: rotate(135deg); +} + +.tooltip-right .tooltip-text { + left: 125%; + transform: none; +} + +.tooltip-right .tooltip-text::after { + left: -4px; + right: auto; + transform: rotate(315deg); +} + .hover\:bg-surface:hover { background-color: var(--surface); }