When offline, hide Files and Admin from the bottom nav and display "Offline Mode" next to the status dot. The SW offline fallback page also only shows Capture and Queue nav items. Online-only pages (browser, admin, reviewer) are never cached and go straight to the offline fallback when the network is unavailable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
776 lines
14 KiB
CSS
776 lines
14 KiB
CSS
/* NextSnap - Mobile-first responsive styles */
|
|
|
|
:root {
|
|
--bg-primary: #1a1a2e;
|
|
--bg-secondary: #16213e;
|
|
--bg-tertiary: #0f1729;
|
|
--text-primary: #ffffff;
|
|
--text-secondary: #a0a0a0;
|
|
--accent: #4a90e2;
|
|
--success: #4caf50;
|
|
--warning: #ff9800;
|
|
--error: #f44336;
|
|
--offline: #757575;
|
|
}
|
|
|
|
@media (prefers-color-scheme: light) {
|
|
:root {
|
|
--bg-primary: #ffffff;
|
|
--bg-secondary: #f5f5f5;
|
|
--bg-tertiary: #e0e0e0;
|
|
--text-primary: #212121;
|
|
--text-secondary: #757575;
|
|
}
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
'Helvetica Neue', Arial, sans-serif;
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
line-height: 1.6;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
#app {
|
|
max-width: 480px;
|
|
margin: 0 auto;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* Utility classes */
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
|
|
.container {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.btn {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 1rem;
|
|
font-size: 1rem;
|
|
font-weight: 600;
|
|
border: none;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
text-align: center;
|
|
transition: all 0.2s;
|
|
min-height: 48px;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: var(--accent);
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:active {
|
|
opacity: 0.8;
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
/* Top Bar */
|
|
.top-bar {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: var(--bg-secondary);
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
z-index: 1000;
|
|
height: 56px;
|
|
}
|
|
|
|
.top-bar-content {
|
|
max-width: 480px;
|
|
margin: 0 auto;
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
.app-title {
|
|
font-size: 1.25rem;
|
|
font-weight: 700;
|
|
margin: 0;
|
|
}
|
|
|
|
.top-bar-indicators {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
/* Connectivity Indicator */
|
|
.connectivity-indicator {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
position: relative;
|
|
}
|
|
|
|
.connectivity-indicator.online {
|
|
background: var(--success);
|
|
box-shadow: 0 0 8px var(--success);
|
|
}
|
|
|
|
.connectivity-indicator.offline {
|
|
background: var(--offline);
|
|
}
|
|
|
|
.connectivity-indicator.syncing {
|
|
background: var(--warning);
|
|
animation: pulse 1.5s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
opacity: 0.5;
|
|
transform: scale(1.2);
|
|
}
|
|
}
|
|
|
|
/* Pending Count Badge */
|
|
.pending-count {
|
|
background: var(--error);
|
|
color: white;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 12px;
|
|
min-width: 24px;
|
|
text-align: center;
|
|
}
|
|
|
|
/* Main Content - Account for top bar and bottom nav */
|
|
#app {
|
|
padding-top: 56px;
|
|
padding-bottom: 70px;
|
|
}
|
|
|
|
/* Bottom Navigation */
|
|
.bottom-nav {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: var(--bg-secondary);
|
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
display: flex;
|
|
justify-content: space-around;
|
|
z-index: 1000;
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
}
|
|
|
|
.nav-item {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 0.5rem 0;
|
|
text-decoration: none;
|
|
color: var(--text-secondary);
|
|
transition: color 0.2s;
|
|
min-height: 56px;
|
|
justify-content: center;
|
|
}
|
|
|
|
.nav-item:active {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.nav-item.active {
|
|
color: var(--accent);
|
|
}
|
|
|
|
.nav-icon {
|
|
font-size: 1.5rem;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.nav-label {
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Responsive adjustments for larger screens */
|
|
@media (min-width: 480px) {
|
|
.top-bar-content,
|
|
.bottom-nav {
|
|
max-width: 480px;
|
|
margin: 0 auto;
|
|
}
|
|
}
|
|
|
|
/* Queue-specific improvements */
|
|
.btn-outline:active:not(:disabled) {
|
|
opacity: 0.8;
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
.btn-outline:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Scrollbar styling for webkit browsers */
|
|
.queue-list::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
.queue-list::-webkit-scrollbar-track {
|
|
background: var(--bg-tertiary);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.queue-list::-webkit-scrollbar-thumb {
|
|
background: var(--text-secondary);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.queue-list::-webkit-scrollbar-thumb:hover {
|
|
background: var(--accent);
|
|
}
|
|
|
|
/* Loading animation */
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.btn-icon.spinning {
|
|
display: inline-block;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
/* ============================================
|
|
POLISH & ANIMATIONS - Module 14
|
|
============================================ */
|
|
|
|
/* Smooth page transitions */
|
|
#app {
|
|
animation: fadeIn 0.3s ease-in;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* Button interactions */
|
|
.btn {
|
|
position: relative;
|
|
overflow: hidden;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.btn:hover:not(:disabled) {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.btn:active:not(:disabled) {
|
|
transform: translateY(0);
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
/* Ripple effect for buttons */
|
|
.btn::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 0;
|
|
height: 0;
|
|
border-radius: 50%;
|
|
background: rgba(255, 255, 255, 0.3);
|
|
transform: translate(-50%, -50%);
|
|
transition: width 0.6s, height 0.6s;
|
|
}
|
|
|
|
.btn:active::before {
|
|
width: 300px;
|
|
height: 300px;
|
|
}
|
|
|
|
/* Enhanced connectivity indicator pulse */
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
box-shadow: 0 0 0 0 var(--warning);
|
|
}
|
|
50% {
|
|
opacity: 0.8;
|
|
transform: scale(1.1);
|
|
box-shadow: 0 0 0 8px transparent;
|
|
}
|
|
}
|
|
|
|
/* Smooth connectivity state transitions */
|
|
.connectivity-indicator {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
/* Loading spinner enhancement */
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Skeleton loader for content */
|
|
@keyframes shimmer {
|
|
0% {
|
|
background-position: -468px 0;
|
|
}
|
|
100% {
|
|
background-position: 468px 0;
|
|
}
|
|
}
|
|
|
|
.skeleton {
|
|
background: linear-gradient(
|
|
90deg,
|
|
var(--bg-secondary) 0px,
|
|
rgba(255, 255, 255, 0.1) 40px,
|
|
var(--bg-secondary) 80px
|
|
);
|
|
background-size: 800px 100px;
|
|
animation: shimmer 2s infinite;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* Form input enhancements */
|
|
input:not([type="file"]),
|
|
textarea,
|
|
select {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
input:focus:not([type="file"]),
|
|
textarea:focus,
|
|
select:focus {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(74, 144, 226, 0.2);
|
|
}
|
|
|
|
/* Card hover effects */
|
|
.queue-item,
|
|
.thumbnail,
|
|
.file-item {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.queue-item:hover,
|
|
.thumbnail:hover,
|
|
.file-item:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
/* Toast notification slide-in */
|
|
.toast {
|
|
animation: slideUp 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes slideUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translate(-50%, 20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translate(-50%, 0);
|
|
}
|
|
}
|
|
|
|
/* Modal fade-in */
|
|
.modal {
|
|
animation: modalFadeIn 0.2s ease-out;
|
|
}
|
|
|
|
@keyframes modalFadeIn {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.modal-content {
|
|
animation: modalSlideUp 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes modalSlideUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(30px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* Navigation item active state */
|
|
.nav-item {
|
|
position: relative;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.nav-item::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 50%;
|
|
width: 0;
|
|
height: 2px;
|
|
background: var(--accent);
|
|
transform: translateX(-50%);
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
.nav-item.active::after,
|
|
.nav-item:hover::after {
|
|
width: 80%;
|
|
}
|
|
|
|
/* Focus visible for accessibility */
|
|
*:focus-visible {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Improve button focus states */
|
|
button:focus-visible,
|
|
.btn:focus-visible {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Smooth scroll behavior */
|
|
html {
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
/* Loading bar at top of page */
|
|
.loading-bar {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 3px;
|
|
background: var(--accent);
|
|
transform-origin: left;
|
|
animation: loadingBar 1s ease-in-out infinite;
|
|
z-index: 9999;
|
|
}
|
|
|
|
@keyframes loadingBar {
|
|
0% {
|
|
transform: scaleX(0);
|
|
}
|
|
50% {
|
|
transform: scaleX(0.5);
|
|
}
|
|
100% {
|
|
transform: scaleX(1);
|
|
}
|
|
}
|
|
|
|
/* Status badge animations */
|
|
.badge,
|
|
.status-badge {
|
|
animation: badgeFadeIn 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes badgeFadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.8);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
/* Image fade-in on load */
|
|
img {
|
|
animation: imageFadeIn 0.4s ease-out;
|
|
}
|
|
|
|
@keyframes imageFadeIn {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
/* Thumbnail grid stagger animation */
|
|
.photo-thumbnails .thumbnail {
|
|
animation: staggerFadeIn 0.4s ease-out backwards;
|
|
}
|
|
|
|
.photo-thumbnails .thumbnail:nth-child(1) { animation-delay: 0.05s; }
|
|
.photo-thumbnails .thumbnail:nth-child(2) { animation-delay: 0.1s; }
|
|
.photo-thumbnails .thumbnail:nth-child(3) { animation-delay: 0.15s; }
|
|
.photo-thumbnails .thumbnail:nth-child(4) { animation-delay: 0.2s; }
|
|
.photo-thumbnails .thumbnail:nth-child(5) { animation-delay: 0.25s; }
|
|
|
|
@keyframes staggerFadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* Enhanced empty state */
|
|
.empty-state {
|
|
animation: emptyStateFadeIn 0.5s ease-out;
|
|
}
|
|
|
|
@keyframes emptyStateFadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.95);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
/* Pending count badge bounce */
|
|
.pending-count {
|
|
animation: badgeBounce 0.5s ease-out;
|
|
}
|
|
|
|
@keyframes badgeBounce {
|
|
0% {
|
|
transform: scale(0);
|
|
}
|
|
50% {
|
|
transform: scale(1.2);
|
|
}
|
|
100% {
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
/* Enhanced spinner */
|
|
.spinner,
|
|
.spinner-small {
|
|
animation: spin 1s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
}
|
|
|
|
/* Delete button shake on hover */
|
|
.queue-item-delete:hover,
|
|
.btn-danger:hover {
|
|
animation: shake 0.5s;
|
|
}
|
|
|
|
@keyframes shake {
|
|
0%, 100% { transform: translateX(0); }
|
|
25% { transform: translateX(-2px); }
|
|
75% { transform: translateX(2px); }
|
|
}
|
|
|
|
/* Success checkmark animation */
|
|
@keyframes checkmark {
|
|
0% {
|
|
transform: scale(0) rotate(0deg);
|
|
}
|
|
50% {
|
|
transform: scale(1.2) rotate(180deg);
|
|
}
|
|
100% {
|
|
transform: scale(1) rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.success-icon {
|
|
animation: checkmark 0.6s ease-out;
|
|
}
|
|
|
|
/* Table row hover effect */
|
|
.user-table tr {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.user-table tr:hover {
|
|
transform: scale(1.01);
|
|
}
|
|
|
|
/* Form validation feedback */
|
|
input:invalid:not(:placeholder-shown) {
|
|
border-color: var(--error);
|
|
animation: inputShake 0.3s;
|
|
}
|
|
|
|
input:valid:not(:placeholder-shown) {
|
|
border-color: var(--success);
|
|
}
|
|
|
|
@keyframes inputShake {
|
|
0%, 100% { transform: translateX(0); }
|
|
25% { transform: translateX(-5px); }
|
|
75% { transform: translateX(5px); }
|
|
}
|
|
|
|
/* Reduced motion for accessibility */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
}
|
|
}
|
|
|
|
/* Dark mode enhancements */
|
|
@media (prefers-color-scheme: dark) {
|
|
.btn:hover:not(:disabled) {
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
input:focus:not([type="file"]),
|
|
textarea:focus,
|
|
select:focus {
|
|
box-shadow: 0 4px 12px rgba(74, 144, 226, 0.3);
|
|
}
|
|
}
|
|
|
|
/* Progress bar for uploads */
|
|
.upload-progress {
|
|
position: relative;
|
|
height: 4px;
|
|
background: var(--bg-tertiary);
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.upload-progress-bar {
|
|
height: 100%;
|
|
background: var(--accent);
|
|
transition: width 0.3s ease;
|
|
position: relative;
|
|
}
|
|
|
|
.upload-progress-bar::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: linear-gradient(
|
|
90deg,
|
|
transparent,
|
|
rgba(255, 255, 255, 0.3),
|
|
transparent
|
|
);
|
|
animation: shimmer 1.5s infinite;
|
|
}
|
|
|
|
/* Improved disabled state */
|
|
button:disabled,
|
|
.btn:disabled,
|
|
input:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.5;
|
|
filter: grayscale(50%);
|
|
}
|
|
|
|
/* Connection status pulse enhancement */
|
|
.connectivity-indicator.online {
|
|
animation: successPulse 2s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes successPulse {
|
|
0%, 100% {
|
|
box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7);
|
|
}
|
|
50% {
|
|
box-shadow: 0 0 0 8px rgba(76, 175, 80, 0);
|
|
}
|
|
}
|
|
|
|
/* Error state pulse */
|
|
.connectivity-indicator.offline {
|
|
animation: errorPulse 2s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes errorPulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
/* Micro-interaction for clickable items */
|
|
[role="button"],
|
|
.clickable {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
[role="button"]:active,
|
|
.clickable:active {
|
|
transform: scale(0.97);
|
|
}
|
|
|
|
/* Smooth color transitions */
|
|
* {
|
|
transition-property: color, background-color, border-color;
|
|
transition-duration: 0.2s;
|
|
transition-timing-function: ease;
|
|
}
|
|
|
|
/* Override for elements that shouldn't have color transitions */
|
|
button,
|
|
.btn,
|
|
input,
|
|
textarea,
|
|
select {
|
|
transition-property: all;
|
|
}
|
|
|
|
/* Offline mode label */
|
|
.offline-label {
|
|
font-size: 0.75rem;
|
|
color: var(--offline, #888);
|
|
font-weight: 600;
|
|
}
|