DocDoeAI / src /app /polish.css
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
28 kB
/* ==========================================================================
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);
}