/* ========================================================================== DocDoe AI — Frontend Polish & Production Excellence Premium interaction layer for Apple-level UX ========================================================================== */ /* ══════════════════════════════════════════════════════════════════════════ 1. TOUCH RESPONSIVENESS Eliminate 300ms tap delay, optimize touch targets, ensure mobile-first feel ══════════════════════════════════════════════════════════════════════════ */ /* Universal touch optimization - apply to ALL interactive elements */ button, a[href], input[type="button"], input[type="submit"], input[type="reset"], [role="button"], [role="link"], [tabindex]:focus, label, summary, -details > summary { touch-action: manipulation; -webkit-tap-highlight-color: transparent; -webkit-user-select: none; user-select: none; } /* Keep 44px touch targets on compact screens without overriding deliberate desktop control sizes or every Tailwind padding utility in the product. */ @media (max-width: 768px) and (hover: none) { button, [role="button"], [role="tab"], a[href] { min-height: 44px; min-width: 44px; } } /* ══════════════════════════════════════════════════════════════════════════ 2. ACTIVE & HOVER STATES Consistent, immediate feedback for all interactive elements ══════════════════════════════════════════════════════════════════════════ */ /* Base transition for all interactive elements - fast and smooth */ button, a[href], [role="button"], input[type="button"], input[type="submit"], input[type="reset"], [role="tab"] { transition: background-color 0.15s var(--ease-out), border-color 0.15s var(--ease-out), color 0.15s var(--ease-out), transform 0.1s var(--ease-out), box-shadow 0.15s var(--ease-out), opacity 0.15s var(--ease-out); will-change: transform, opacity; } /* Active state - subtle scale for physical feel (98% for press) */ button:active:not(:disabled), [role="button"]:active:not(:disabled), a[href]:active, input[type="button"]:active:not(:disabled), input[type="submit"]:active:not(:disabled), input[type="reset"]:active:not(:disabled), [role="tab"]:active:not(:disabled) { transform: scale(0.98); } /* Hover state - elevate slightly for premium feel */ button:hover:not(:disabled):not(:active), [role="button"]:hover:not(:disabled):not(:active), a[href]:hover:not(:disabled):not(:active), input[type="button"]:hover:not(:disabled):not(:active), input[type="submit"]:hover:not(:disabled):not(:active), input[type="reset"]:hover:not(:disabled):not(:active), [role="tab"]:hover:not(:disabled):not(:active) { transform: translateY(-1px); } /* Focus visible - consistent focus ring for accessibility */ button:focus-visible, a[href]:focus-visible, [role="button"]:focus-visible, input:focus-visible, [role="tab"]:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.12), 0 0 0 1px rgba(255, 255, 255, 0.28); } /* Disabled state - clear and consistent */ button:disabled, [role="button"]:disabled, input:disabled, [role="tab"]:disabled { cursor: not-allowed; opacity: 0.4; transform: none !important; } /* ══════════════════════════════════════════════════════════════════════════ 3. TAP FEEDBACK FOR TOUCH DEVICES Instant visual confirmation on touch ══════════════════════════════════════════════════════════════════════════ */ /* Tap highlight for touch devices */ @media (hover: none) { button:active:not(:disabled), [role="button"]:active:not(:disabled), a[href]:active { background: rgba(255, 255, 255, 0.08) !important; transform: scale(0.96) !important; } } /* Touch ripple effect for primary actions */ .touch-ripple { position: relative; overflow: hidden; } .touch-ripple::after { content: ""; position: absolute; top: 50%; left: 50%; width: 0; height: 0; background: rgba(255, 255, 255, 0.15); border-radius: 50%; transform: translate(-50%, -50%); opacity: 0; transition: width 0.3s ease, height 0.3s ease, opacity 0.3s ease; pointer-events: none; } .touch-ripple:active::after { width: 200%; height: 200%; opacity: 1; } /* ══════════════════════════════════════════════════════════════════════════ 4. LOADING STATES Smooth, skeleton-based loading for content ══════════════════════════════════════════════════════════════════════════ */ /* Loading skeleton base */ .skeleton { background: linear-gradient( 90deg, var(--bg-elevated) 25%, var(--bg-glass) 50%, var(--bg-elevated) 75% ); background-size: 200% 100%; animation: skeleton-loading 1.5s infinite; border-radius: var(--radius-sm); } @keyframes skeleton-loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } /* Text skeleton */ .skeleton-text { height: 1em; margin-bottom: 0.5em; } .skeleton-text.short { width: 60%; } .skeleton-text.medium { width: 80%; } /* Card skeleton */ .skeleton-card { height: 120px; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); } /* Button skeleton */ .skeleton-button { height: 44px; width: 120px; border-radius: var(--radius-sm); } /* Spinner - consistent across app */ .spinner { width: 20px; height: 20px; border: 2px solid transparent; border-top-color: currentColor; border-radius: 50%; animation: spinner-rotate 0.8s linear infinite; display: inline-block; } @keyframes spinner-rotate { to { transform: rotate(360deg); } } /* Loading overlay for containers */ .loading-overlay { position: relative; } .loading-overlay::after { content: ""; position: absolute; inset: 0; background: rgba(0, 0, 0, 0.4); backdrop-filter: blur(4px); border-radius: inherit; display: flex; align-items: center; justify-content: center; } .loading-overlay::before { content: ""; position: absolute; inset: 0; background: inherit; border-radius: inherit; z-index: -1; } /* ══════════════════════════════════════════════════════════════════════════ 5. SCROLL OPTIMIZATION Smooth, jank-free scrolling ══════════════════════════════════════════════════════════════════════════ */ /* Smooth scrolling globally */ html { scroll-behavior: smooth; } /* Scroll container optimization */ * { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.12) transparent; } /* Hide scrollbar on mobile for cleaner look */ @media (max-width: 768px) { *::-webkit-scrollbar { display: none; } * { scrollbar-width: none; } } /* Scrollbar styling for desktop */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.12); border-radius: 3px; border: 2px solid transparent; background-clip: content-box; } ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.22); } /* Prevent scroll jank with contain */ offscreen, [content-visibility="auto"] { contain-intrinsic-size: auto 1000px; } /* ══════════════════════════════════════════════════════════════════════════ 6. MOBILE-SPECIFIC OPTIMIZATIONS Thumb-friendly, compact, efficient ══════════════════════════════════════════════════════════════════════════ */ /* Mobile touch targets - ensure minimum 44px */ @media (max-width: 768px) { button, [role="button"], a[href], [role="tab"], .touch-target { min-height: 44px; min-width: 44px; } /* Larger touch targets for primary actions */ .btn-primary, .btn-hero, .btn-secondary { min-height: 48px; } } /* Mobile card interactions - larger tap area */ @media (max-width: 768px) { .card-touch-area { padding: 4px; margin: -4px; border-radius: inherit; } /* Mobile card active state */ .card-touch-area:active { transform: scale(0.97); opacity: 0.8; } } /* Mobile form inputs - larger tap areas */ @media (max-width: 768px) { input, textarea, select { min-height: 48px; } input[type="checkbox"], input[type="radio"] { min-width: 44px; min-height: 44px; } } /* ══════════════════════════════════════════════════════════════════════════ 7. BLUR OPTIMIZATION Reduce expensive backdrop-filter on mobile ══════════════════════════════════════════════════════════════════════════ */ /* Disable expensive blur on low-end devices */ @media (max-width: 768px) and (hover: none) { *, *::before, *::after { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; } /* Keep each component's own light or dark surface. A blanket dark replacement made light navigation, lesson headers, and sticky actions turn black on touch devices. The existing alpha background remains a sufficient low-cost fallback after blur is disabled above. */ } /* Reduced blur for performance */ @media (forced-colors: active) { *, *::before, *::after { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; } } /* ══════════════════════════════════════════════════════════════════════════ 8. ANIMATION PERFORMANCE Optimized for low-end Android devices ══════════════════════════════════════════════════════════════════════════ */ /* Hardware acceleration for animations */ .gpu-accel { transform: translateZ(0); will-change: transform, opacity; backface-visibility: hidden; perspective: 1000px; } /* Optimized animations - no heavy effects */ @keyframes fade-in-fast { from { opacity: 0; } to { opacity: 1; } } @keyframes slide-up-fast { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .animate-fade-in-fast { animation: fade-in-fast 0.15s var(--ease-out) both; } .animate-slide-up-fast { animation: slide-up-fast 0.2s var(--ease-out) both; } /* Reduced motion preference */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } /* ══════════════════════════════════════════════════════════════════════════ 9. EMPTY STATES Clean, helpful empty states ══════════════════════════════════════════════════════════════════════════ */ .empty-state { text-align: center; padding: 48px 24px; } .empty-state-icon { width: 64px; height: 64px; margin: 0 auto 16px; background: var(--bg-elevated); border-radius: 50%; display: flex; align-items: center; justify-content: center; } .empty-state-title { font-size: 18px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px; } .empty-state-description { font-size: 14px; color: var(--text-secondary); max-width: 300px; margin: 0 auto; } .empty-state-actions { margin-top: 24px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; } /* ══════════════════════════════════════════════════════════════════════════ 10. CARD INTERACTIONS Premium feel for all card components ══════════════════════════════════════════════════════════════════════════ */ /* Card hover - subtle elevation */ .card-hover { border: 1px solid var(--border-subtle); transition: border-color 0.15s var(--ease-out), box-shadow 0.2s var(--ease-out), transform 0.1s var(--ease-out); } .card-hover:hover { border-color: var(--border-medium); box-shadow: var(--shadow-sm); } /* Card active - press effect */ .card-hover:active { transform: scale(0.99); } /* Premium card - glass effect with fallback */ .premium-card { background: var(--bg-surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); transition: background-color 0.2s var(--ease-out), border-color 0.15s var(--ease-out), box-shadow 0.2s var(--ease-out); } .premium-card:hover { background: var(--bg-elevated); border-color: var(--border-medium); box-shadow: var(--shadow-md); } /* ══════════════════════════════════════════════════════════════════════════ 11. FORM ELEMENTS Clean, accessible form controls ══════════════════════════════════════════════════════════════════════════ */ /* Global: kill all native focus styles on form elements. Visual feedback is handled per-component (outer focus-within glow). */ input:focus, textarea:focus, select:focus { outline: none; box-shadow: none; } /* Explicit form inputs (login, settings, onboarding) keep their own focus ring */ .input-field:focus { border-color: var(--accent-primary); box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.08); } /* Input hover state */ input:hover:not(:focus), textarea:hover:not(:focus), select:hover:not(:focus) { border-color: var(--border-medium); } /* Input disabled state */ input:disabled, textarea:disabled, select:disabled { background: var(--bg-glass); border-color: var(--border-subtle); color: var(--text-muted); cursor: not-allowed; } /* Input error state */ input.error, textarea.error, select.error { border-color: var(--accent-red); background: rgba(251, 113, 133, 0.05); } input.error:focus, textarea.error:focus, select.error:focus { box-shadow: 0 0 0 3px rgba(251, 113, 133, 0.15); } /* Label interactions */ label { cursor: pointer; transition: color 0.15s var(--ease-out); } label:hover { color: var(--text-primary); } /* ══════════════════════════════════════════════════════════════════════════ 12. NAVIGATION Smooth, confident navigation ══════════════════════════════════════════════════════════════════════════ */ /* Link transitions */ a { transition: color 0.15s var(--ease-out); } /* Nav item active state */ .nav-item { position: relative; transition: color 0.15s var(--ease-out), background-color 0.15s var(--ease-out); } .nav-item:active { transform: scale(0.97); } /* Nav underline indicator */ .nav-underline { position: absolute; bottom: 0; left: 0; right: 0; height: 2px; background: var(--accent-primary); border-radius: 1px; transform: scaleX(0); transition: transform 0.2s var(--ease-out); } .nav-item.active .nav-underline { transform: scaleX(1); } /* ══════════════════════════════════════════════════════════════════════════ 13. HYDRATION Prevent layout shift and flicker ══════════════════════════════════════════════════════════════════════════ */ /* Prevent layout shift on hydration */ input.caret-transparent, input.caret-transparent:focus { caret-color: transparent; } .hydrate-safe { contain-intrinsic-size: auto 1000px; content-visibility: auto; } /* Skeleton during loading */ [aria-busy="true"] { position: relative; } [aria-busy="true"]::after { content: ""; position: absolute; inset: 0; background: linear-gradient( 90deg, transparent 0%, rgba(255, 255, 255, 0.04) 50%, transparent 100% ); background-size: 200% 100%; animation: skeleton-loading 1.5s infinite; border-radius: inherit; pointer-events: none; } /* Hide content until hydrated */ [aria-live="polite"] { opacity: 1; transition: opacity 0.2s var(--ease-out); } /* ══════════════════════════════════════════════════════════════════════════ 14. BUTTON VARIANTS Consistent button styles ══════════════════════════════════════════════════════════════════════════ */ /* Primary button - consistent style */ .btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 24px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 600; letter-spacing: -0.01em; transition: background-color 0.15s var(--ease-out), border-color 0.15s var(--ease-out), color 0.15s var(--ease-out), transform 0.1s var(--ease-out); min-height: 44px; touch-action: manipulation; } /* DocDoe primary action = white on near-black, matching the signup and composer-send buttons. The old amber (#F59E0B) fought the monochrome brand. */ .btn-primary { background: #ffffff; color: #080808; border: 1px solid rgba(255, 255, 255, 0.16); } .btn-primary:hover:not(:disabled) { background: rgba(255, 255, 255, 0.9); } .btn-primary:active:not(:disabled) { transform: scale(0.98); } .btn-secondary { background: var(--bg-surface); color: var(--text-primary); border: 1px solid var(--border-medium); } .btn-secondary:hover:not(:disabled) { background: var(--bg-elevated); border-color: var(--border-medium); } .btn-ghost { background: transparent; color: var(--text-secondary); border: 1px solid var(--border-subtle); } .btn-ghost:hover:not(:disabled) { background: var(--bg-glass); border-color: var(--border-medium); color: var(--text-primary); } .btn-disabled { cursor: not-allowed; opacity: 0.4; transform: none !important; } /* ══════════════════════════════════════════════════════════════════════════ 15. TYPOGRAPHY REFINEMENTS Better readability and hierarchy ══════════════════════════════════════════════════════════════════════════ */ /* Text balancing for headings */ h1, h2, h3, h4, h5, h6 { text-wrap: balance; } /* Line height adjustments */ p { line-height: 1.6; } /* Selection styling — mono, matching the black/white identity (no legacy amber) */ ::selection { background: rgba(255, 255, 255, 0.18); color: var(--text-primary); } /* Focus visible for all elements */ *:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 2px; } /* Remove default focus outline when using mouse */ *:focus:not(:focus-visible) { outline: none; } /* ══════════════════════════════════════════════════════════════════════════ 16. Z-INDEX MANAGEMENT Consistent layering ══════════════════════════════════════════════════════════════════════════ */ /* Base z-index scale */ .z-dropdown { z-index: 100; } .z-sticky { z-index: 200; } .z-fixed { z-index: 300; } .z-modal-backdrop { z-index: 400; } .z-modal { z-index: 500; } .z-popover { z-index: 600; } .z-tooltip { z-index: 700; } /* ══════════════════════════════════════════════════════════════════════════ 17. FORBIDDEN WORDS REPLACEMENT Replace technical terms with student-friendly language ══════════════════════════════════════════════════════════════════════════ */ /* Hide raw technical error messages */ .error-message { color: var(--accent-red); font-size: 14px; } /* User-friendly error states */ .error-friendly { color: var(--text-secondary); background: var(--bg-elevated); border: 1px solid var(--border-subtle); padding: 12px 16px; border-radius: var(--radius-sm); } /* Loading messages */ .loading-message { color: var(--text-secondary); } /* Success states */ .success-message { color: var(--accent-emerald); background: rgba(16, 185, 129, 0.05); border: 1px solid rgba(16, 185, 129, 0.2); padding: 12px 16px; border-radius: var(--radius-sm); } /* ══════════════════════════════════════════════════════════════════════════ 18. RESPONSIVE TYPE SCALE Better mobile typography ══════════════════════════════════════════════════════════════════════════ */ @media (max-width: 768px) { html { font-size: 15px; } /* Better mobile touch targets */ .mobile-touch { min-height: 48px; padding: 12px 16px; } } /* * Mobile type defaults live in the base layer so any Tailwind utility on the * element wins. Unlayered, these element selectors silently beat every * text-* class on phones (cascade layers: unlayered > layered), flattening * all heading hierarchy across the whole app. */ @layer base { @media (max-width: 768px) { h1 { font-size: 28px; } h2 { font-size: 24px; } h3 { font-size: 20px; } p, li { font-size: 15px; line-height: 1.5; } } } /* ══════════════════════════════════════════════════════════════════════════ 19. SAFE AREA INSETS Proper mobile viewport handling ══════════════════════════════════════════════════════════════════════════ */ /* Safe areas are applied on fixed chrome (bottom nav, drawers, composers). Do not pad the whole body — that fights fixed footers and double-counts insets on iOS. */ /* Full viewport height handling */ .min-h-svh { min-height: 100svh; } .min-h-dvh { min-height: 100dvh; } /* Mobile bottom navigation */ .mobile-nav { padding-bottom: calc(env(safe-area-inset-bottom) + 16px); } /* ══════════════════════════════════════════════════════════════════════════ 20. PRINT STYLES Clean printing for study materials ══════════════════════════════════════════════════════════════════════════ */ @media print { .no-print { display: none !important; } body { background: white; color: black; } .workspace-card, .premium-panel, .glass-panel { background: white; border: 1px solid #e5e7eb; box-shadow: none; } } /* ══════════════════════════════════════════════════════════════════════════ StudyCast / Discuss player — transcript scroller polish Dedicated thin, high-quality scrollbar for the live highlighted transcript in the floating premium player (current things perfection). ══════════════════════════════════════════════════════════════════════════ */ .custom-scroll { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.18) transparent; } .custom-scroll::-webkit-scrollbar { width: 5px; } .custom-scroll::-webkit-scrollbar-track { background: transparent; } .custom-scroll::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); border-radius: 4px; } .custom-scroll::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.28); }