Refactor UI components and styles for improved consistency and aesthetics

This commit is contained in:
M1ngdaXie
2026-02-08 12:32:02 -08:00
parent 81855a144e
commit 02908171be
17 changed files with 1024 additions and 832 deletions

View File

@@ -23,20 +23,18 @@ export function CreateButton({ onClick, disabled, icon, children }: CreateButton
onClick={onClick}
disabled={disabled}
className="
bg-pixel-purple-bright
hover:bg-pixel-purple-deep
bg-brand
hover:bg-brand-dark
text-white
border-[3px]
border-pixel-outline
shadow-pixel
hover:shadow-pixel-hover
border
border-border
shadow-card
hover:shadow-float
hover:-translate-y-0.5
hover:-translate-x-0.5
active:translate-y-0.5
active:translate-x-0.5
active:shadow-pixel-active
active:shadow-soft
transition-all
duration-75
duration-150
font-sans
font-semibold
px-6

View File

@@ -2,6 +2,7 @@ import { useNavigate } from 'react-router-dom';
import type { DocumentType } from '@/api/document';
import { Card } from '@/components/ui/card';
import { FileText, KanbanSquare, Trash2 } from 'lucide-react';
import PixelIcon from '@/components/PixelIcon/PixelIcon';
import { Button } from '@/components/ui/button';
interface DocumentCardProps {
@@ -24,15 +25,14 @@ export function DocumentCard({ doc, onDelete, isShared }: DocumentCardProps) {
<Card className="
group
relative
bg-pixel-white
border-[3px]
border-pixel-outline
shadow-pixel-md
hover:shadow-pixel-lg
bg-surface
border
border-border
shadow-card
hover:shadow-float
hover:-translate-y-0.5
hover:-translate-x-0.5
transition-all
duration-100
duration-150
p-6
flex
flex-col
@@ -42,42 +42,32 @@ export function DocumentCard({ doc, onDelete, isShared }: DocumentCardProps) {
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
<div className="
bg-pixel-cyan-bright
p-2
border-[2px]
border-pixel-outline
shadow-pixel-sm
bg-brand-teal
p-2.5
rounded-md
shadow-soft
">
<Icon className="w-6 h-6 text-white" />
<Icon className="w-5 h-5 text-white" />
</div>
<div>
<h3 className="font-pixel text-sm text-pixel-text-primary mb-1">
<h3 className="font-display text-base text-text-primary mb-1">
{doc.name}
</h3>
<span className="font-sans text-xs text-pixel-text-muted">
<span className="font-sans text-xs text-text-muted">
{typeLabel}
</span>
</div>
</div>
{isShared && (
<span className="
bg-pixel-pink-vibrant
text-white
font-sans
text-xs
font-semibold
px-2
py-1
border-[2px]
border-pixel-outline
">
<span className="shared-badge">
<PixelIcon name="gem" size={12} color="hsl(var(--brand-teal))" />
Shared
</span>
)}
</div>
{/* Metadata */}
<div className="font-sans text-xs text-pixel-text-muted">
<div className="font-sans text-xs text-text-muted">
Created {new Date(doc.created_at).toLocaleDateString()}
</div>
@@ -87,17 +77,16 @@ export function DocumentCard({ doc, onDelete, isShared }: DocumentCardProps) {
onClick={handleOpen}
className="
flex-1
bg-pixel-cyan-bright
hover:bg-pixel-purple-bright
bg-brand
hover:bg-brand-dark
text-white
border-[2px]
border-pixel-outline
shadow-pixel-sm
hover:shadow-pixel-hover
border
border-border
shadow-soft
hover:shadow-card
hover:-translate-y-0.5
hover:-translate-x-0.5
transition-all
duration-75
duration-150
font-sans
font-semibold
"
@@ -109,15 +98,14 @@ export function DocumentCard({ doc, onDelete, isShared }: DocumentCardProps) {
onClick={() => onDelete(doc.id)}
variant="outline"
className="
border-[2px]
border-pixel-outline
shadow-pixel-sm
hover:shadow-pixel-hover
border
border-border
shadow-soft
hover:shadow-card
hover:-translate-y-0.5
hover:-translate-x-0.5
hover:bg-red-50
transition-all
duration-75
duration-150
"
>
<Trash2 className="w-4 h-4" />

View File

@@ -1,5 +1,6 @@
import { useAuth } from '@/contexts/AuthContext';
import { Button } from '@/components/ui/button';
import PixelIcon from '@/components/PixelIcon/PixelIcon';
import { LogOut } from 'lucide-react';
function Navbar() {
@@ -9,28 +10,33 @@ function Navbar() {
return (
<nav className="
bg-pixel-white
border-b-[3px]
border-pixel-outline
shadow-pixel-sm
bg-surface
border-b
border-border
shadow-soft
sticky
top-0
z-50
backdrop-blur
">
<div className="max-w-7xl mx-auto px-8 py-4 flex items-center justify-between">
{/* Brand */}
<a
href="/"
className="
font-pixel
text-xl
text-pixel-purple-bright
hover:text-pixel-cyan-bright
font-display
text-lg
text-text-primary
hover:text-brand
transition-colors
duration-200
flex
items-center
gap-2
"
>
Realtime Collab
<PixelIcon name="gem" size={18} color="hsl(var(--brand-teal))" />
DocNest
</a>
{/* User Section */}
@@ -40,15 +46,17 @@ function Navbar() {
src={user.avatar_url}
alt={user.name}
className="
w-10
h-10
border-[3px]
border-pixel-outline
shadow-pixel-sm
w-9
h-9
rounded-full
border
border-border
shadow-soft
object-cover
"
/>
)}
<span className="font-sans font-medium text-pixel-text-primary hidden sm:inline">
<span className="font-sans font-medium text-text-primary hidden sm:inline">
{user.name}
</span>
<Button
@@ -56,14 +64,13 @@ function Navbar() {
variant="outline"
size="sm"
className="
border-[3px]
border-pixel-outline
shadow-pixel-sm
hover:shadow-pixel-hover
border
border-border
shadow-soft
hover:shadow-card
hover:-translate-y-0.5
hover:-translate-x-0.5
transition-all
duration-75
duration-150
font-sans
"
>

View File

@@ -43,45 +43,37 @@ const UserList = ({ awareness }: UserListProps) => {
}, [awareness]);
return (
<div className="
bg-pixel-white
border-[3px]
border-pixel-outline
shadow-pixel-md
p-4
">
<div className="p-2">
{/* Header with online count */}
<div className="
flex
items-center
gap-2
mb-4
pb-3
border-b-[2px]
border-pixel-outline
mb-3
pb-2
border-b
border-border
">
<div className="
w-3
h-3
bg-pixel-green-lime
w-2.5
h-2.5
bg-brand-teal
animate-pulse
border-[2px]
border-pixel-outline
rounded-full
" />
<h4 className="font-pixel text-xs text-pixel-text-primary">
ONLINE
<h4 className="font-display text-xs text-text-secondary uppercase tracking-wide">
Online
</h4>
<span className="
ml-auto
font-sans
text-sm
font-bold
text-pixel-purple-bright
bg-pixel-panel
font-semibold
text-text-primary
bg-surface-muted
px-2
py-1
border-[2px]
border-pixel-outline
py-0.5
rounded-full
">
{users.length}
</span>
@@ -95,7 +87,7 @@ const UserList = ({ awareness }: UserListProps) => {
py-4
font-sans
text-xs
text-pixel-text-muted
text-text-muted
">
No users online
</div>
@@ -108,16 +100,7 @@ const UserList = ({ awareness }: UserListProps) => {
flex
items-center
gap-3
p-2
bg-pixel-panel
border-[2px]
border-pixel-outline
shadow-pixel-sm
hover:shadow-pixel-hover
hover:-translate-y-0.5
hover:-translate-x-0.5
transition-all
duration-75
py-2
"
>
{/* Avatar with online indicator */}
@@ -130,13 +113,14 @@ const UserList = ({ awareness }: UserListProps) => {
className="
w-10
h-10
border-[3px]
border-pixel-outline
shadow-pixel-sm
rounded-full
border
border-border
shadow-soft
object-cover
"
onError={(e) => {
// Fallback to colored square on image error
// Fallback to colored circle on image error
e.currentTarget.style.display = 'none';
const fallback = e.currentTarget.nextElementSibling as HTMLElement;
if (fallback) {
@@ -146,16 +130,15 @@ const UserList = ({ awareness }: UserListProps) => {
/>
{/* Fallback colored square (hidden if avatar loads) */}
<div
className="w-10 h-10 border-[3px] border-pixel-outline shadow-pixel-sm items-center justify-center font-pixel text-xs text-white"
style={{ backgroundColor: user.color, display: 'none' }}
className="w-10 h-10 border border-border shadow-soft items-center justify-center font-display text-xs text-text-secondary rounded-full bg-surface-muted"
style={{ display: 'none' }}
>
{user.name.charAt(0).toUpperCase()}
</div>
</>
) : (
<div
className="w-10 h-10 border-[3px] border-pixel-outline shadow-pixel-sm flex items-center justify-center font-pixel text-xs text-white"
style={{ backgroundColor: user.color }}
className="w-10 h-10 border border-border shadow-soft flex items-center justify-center font-display text-xs text-text-secondary rounded-full bg-surface-muted"
>
{user.name.charAt(0).toUpperCase()}
</div>
@@ -168,10 +151,11 @@ const UserList = ({ awareness }: UserListProps) => {
-right-0.5
w-3
h-3
bg-pixel-green-lime
border-[2px]
border-pixel-white
shadow-pixel-sm
bg-brand-teal
border-2
border-white
shadow-soft
rounded-full
" />
</div>
@@ -180,25 +164,12 @@ const UserList = ({ awareness }: UserListProps) => {
font-sans
text-sm
font-medium
text-pixel-text-primary
text-text-primary
truncate
flex-1
">
{user.name}
</span>
{/* User color indicator (small square) */}
<div
className="
flex-shrink-0
w-4
h-4
border-[2px]
border-pixel-outline
shadow-pixel-sm
"
style={{ backgroundColor: user.color }}
/>
</div>
))
)}

View File

@@ -4,12 +4,13 @@
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
background: rgba(15, 23, 42, 0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.2s ease;
backdrop-filter: blur(2px);
}
@keyframes fadeIn {
@@ -18,11 +19,12 @@
}
.modal-content {
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
background: hsl(var(--surface));
border-radius: var(--radius-lg);
border: 1px solid hsl(var(--border));
box-shadow: var(--shadow-lg);
width: 90%;
max-width: 600px;
max-width: 640px;
max-height: 80vh;
overflow: hidden;
display: flex;
@@ -46,20 +48,54 @@
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid #e2e8f0;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--surface));
}
.modal-header h2 {
margin: 0;
font-size: 20px;
color: #1a202c;
font-size: 18px;
color: hsl(var(--text-primary));
}
.role-banner {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
border-radius: var(--radius-md);
margin: 16px 0;
font-size: 14px;
font-weight: 600;
}
.role-banner.owner {
background: #ecfdf5;
color: #166534;
border: 1px solid #bbf7d0;
}
.role-banner.editor {
background: #fff7ed;
color: #9a3412;
border: 1px solid #fed7aa;
}
.role-banner.viewer {
background: #eff6ff;
color: #1d4ed8;
border: 1px solid #bfdbfe;
}
.role-icon {
font-size: 16px;
}
.close-button {
background: none;
background: transparent;
border: none;
font-size: 28px;
color: #718096;
font-size: 24px;
color: hsl(var(--text-muted));
cursor: pointer;
padding: 0;
width: 32px;
@@ -67,40 +103,43 @@
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
border-radius: 8px;
transition: all 0.2s ease;
}
.close-button:hover {
background: #f7fafc;
color: #2d3748;
background: hsl(var(--surface-muted));
color: hsl(var(--text-primary));
}
.tabs {
display: flex;
border-bottom: 1px solid #e2e8f0;
padding: 0 24px;
display: inline-flex;
gap: 6px;
padding: 10px 16px;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--surface));
}
.tab {
padding: 12px 20px;
background: none;
border: none;
font-size: 14px;
font-weight: 500;
color: #718096;
padding: 8px 14px;
background: transparent;
border: 1px solid transparent;
border-radius: 999px;
font-size: 13px;
font-weight: 600;
color: hsl(var(--text-muted));
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.2s ease;
}
.tab:hover {
color: #2d3748;
color: hsl(var(--text-primary));
}
.tab.active {
color: #667eea;
border-bottom-color: #667eea;
color: white;
background: hsl(var(--primary));
border-color: transparent;
}
.tab-content {
@@ -110,72 +149,78 @@
.message {
padding: 12px 16px;
border-radius: 6px;
border-radius: var(--radius-md);
margin-bottom: 16px;
font-size: 14px;
}
.message.error {
background: #fff5f5;
color: #c53030;
border: 1px solid #feb2b2;
background: #fff1f2;
color: #b91c1c;
border: 1px solid #fecdd3;
}
.message.success {
background: #f0fff4;
color: #22543d;
border: 1px solid #9ae6b4;
background: #ecfdf5;
color: #166534;
border: 1px solid #bbf7d0;
}
.share-form {
display: flex;
gap: 8px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.share-input {
flex: 1;
min-width: 220px;
padding: 10px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-sm);
font-size: 14px;
background: hsl(var(--surface));
}
.share-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
border-color: hsl(var(--primary));
box-shadow: var(--focus-ring);
}
.share-select {
padding: 10px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-sm);
font-size: 14px;
background: white;
background: hsl(var(--surface));
cursor: pointer;
}
.share-select:focus {
outline: none;
border-color: #667eea;
border-color: hsl(var(--primary));
box-shadow: var(--focus-ring);
}
.share-button {
padding: 10px 20px;
background: #667eea;
padding: 10px 18px;
background: hsl(var(--primary));
color: white;
border: none;
border-radius: 6px;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 500;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
box-shadow: var(--shadow-sm);
}
.share-button:hover:not(:disabled) {
background: #5568d3;
background: hsl(var(--primary) / 0.9);
box-shadow: var(--shadow-md);
}
.share-button:disabled {
@@ -186,15 +231,19 @@
.shares-list h3 {
font-size: 14px;
font-weight: 600;
color: #2d3748;
color: hsl(var(--text-primary));
margin: 0 0 12px 0;
}
.empty-state {
padding: 32px;
padding: 28px;
text-align: center;
color: #718096;
color: hsl(var(--text-muted));
font-size: 14px;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.share-item {
@@ -202,9 +251,10 @@
align-items: center;
justify-content: space-between;
padding: 12px;
border: 1px solid #e2e8f0;
border-radius: 8px;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-md);
margin-bottom: 8px;
background: hsl(var(--surface));
}
.share-user {
@@ -214,10 +264,11 @@
}
.share-avatar {
width: 40px;
height: 40px;
width: 36px;
height: 36px;
border-radius: 50%;
object-fit: cover;
border: 1px solid hsl(var(--border));
}
.share-info {
@@ -228,13 +279,13 @@
.share-name {
font-size: 14px;
font-weight: 500;
color: #2d3748;
font-weight: 600;
color: hsl(var(--text-primary));
}
.share-email {
font-size: 13px;
color: #718096;
color: hsl(var(--text-muted));
}
.share-actions {
@@ -244,28 +295,28 @@
}
.permission-badge {
padding: 4px 12px;
background: #edf2f7;
color: #4a5568;
border-radius: 12px;
padding: 4px 10px;
background: hsl(var(--surface-muted));
color: hsl(var(--text-secondary));
border-radius: 999px;
font-size: 12px;
font-weight: 500;
font-weight: 600;
text-transform: capitalize;
}
.remove-button {
padding: 6px 12px;
background: white;
color: #e53e3e;
border: 1px solid #feb2b2;
border-radius: 6px;
color: #b91c1c;
border: 1px solid #fecdd3;
border-radius: var(--radius-sm);
font-size: 13px;
cursor: pointer;
transition: all 0.2s ease;
}
.remove-button:hover:not(:disabled) {
background: #fff5f5;
background: #fff1f2;
}
.remove-button:disabled {
@@ -278,7 +329,7 @@
}
.link-creation p {
color: #4a5568;
color: hsl(var(--text-secondary));
margin-bottom: 20px;
}
@@ -287,6 +338,7 @@
gap: 12px;
justify-content: center;
align-items: center;
flex-wrap: wrap;
}
.link-display {
@@ -296,40 +348,42 @@
}
.link-display > p {
color: #4a5568;
color: hsl(var(--text-secondary));
margin: 0;
}
.link-box {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.link-input {
flex: 1;
min-width: 240px;
padding: 10px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-sm);
font-size: 14px;
background: #f7fafc;
color: #2d3748;
background: hsl(var(--surface-muted));
color: hsl(var(--text-primary));
}
.copy-button {
padding: 10px 20px;
background: #48bb78;
padding: 10px 18px;
background: hsl(var(--secondary));
color: white;
border: none;
border-radius: 6px;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 500;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.copy-button:hover {
background: #38a169;
background: hsl(var(--secondary) / 0.9);
}
.link-meta {
@@ -340,23 +394,23 @@
.link-date {
font-size: 13px;
color: #718096;
color: hsl(var(--text-muted));
}
.revoke-button {
padding: 10px 20px;
background: white;
color: #e53e3e;
border: 1px solid #feb2b2;
border-radius: 6px;
color: #b91c1c;
border: 1px solid #fecdd3;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 500;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.revoke-button:hover:not(:disabled) {
background: #fff5f5;
background: #fff1f2;
}
.revoke-button:disabled {

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react';
import { shareApi } from '../../api/share';
import type { DocumentShareWithUser, ShareLink } from '../../types/share';
import PixelIcon from '../PixelIcon/PixelIcon';
import './ShareModal.css';
interface ShareModalProps {
@@ -152,20 +153,8 @@ function ShareModal({ documentId, onClose, currentPermission, currentRole }: Sha
</div>
{currentRole && (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '12px 16px',
backgroundColor: currentRole === 'owner' ? '#e8f5e9' : currentPermission === 'edit' ? '#fff3e0' : '#e3f2fd',
borderRadius: '8px',
margin: '16px 0',
fontSize: '14px',
fontWeight: '500',
}}
>
<span style={{ fontSize: '18px' }}>
<div className={`role-banner ${currentRole}`}>
<span className="role-icon">
{currentRole === 'owner' ? '👑' : currentPermission === 'edit' ? '✏️' : '👁️'}
</span>
<span>
@@ -222,7 +211,10 @@ function ShareModal({ documentId, onClose, currentPermission, currentRole }: Sha
<div className="shares-list">
<h3>People with access</h3>
{shares.length === 0 ? (
<p className="empty-state">No users have been given access yet.</p>
<div className="empty-state">
<PixelIcon name="gem" size={18} color="hsl(var(--brand-teal))" />
<p>No users have been given access yet.</p>
</div>
) : (
shares.map((share) => (
<div key={share.id} className="share-item">

View File

@@ -4,7 +4,7 @@
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.3);
background: rgba(15, 23, 42, 0.35);
z-index: 1000;
animation: fadeIn 0.2s ease;
}
@@ -27,35 +27,42 @@
position: fixed;
right: 0;
top: 0;
width: 400px;
width: 420px;
height: 100vh;
background: white;
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.15);
background: hsl(var(--surface));
border-left: 1px solid hsl(var(--border));
box-shadow: -10px 0 30px rgba(15, 23, 42, 0.15);
display: flex;
flex-direction: column;
animation: slideIn 0.2s ease;
}
@media (max-width: 640px) {
.version-panel {
width: 100%;
}
}
.version-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid #e2e8f0;
border-bottom: 1px solid hsl(var(--border));
}
.version-panel-header h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #1a202c;
color: hsl(var(--text-primary));
}
.close-button {
background: none;
border: none;
font-size: 28px;
color: #718096;
font-size: 24px;
color: hsl(var(--text-muted));
cursor: pointer;
padding: 0;
width: 32px;
@@ -63,35 +70,37 @@
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
border-radius: 8px;
transition: all 0.2s ease;
}
.close-button:hover {
background: #f7fafc;
color: #2d3748;
background: hsl(var(--surface-muted));
color: hsl(var(--text-primary));
}
.version-panel-actions {
padding: 16px 24px;
border-bottom: 1px solid #e2e8f0;
border-bottom: 1px solid hsl(var(--border));
}
.create-version-btn {
width: 100%;
padding: 10px 16px;
background: #667eea;
background: hsl(var(--primary));
color: white;
border: none;
border-radius: 6px;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 500;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: var(--shadow-sm);
}
.create-version-btn:hover {
background: #5568d3;
background: hsl(var(--primary) / 0.9);
box-shadow: var(--shadow-md);
}
.version-panel-content {
@@ -104,7 +113,14 @@
.empty-state {
text-align: center;
padding: 40px 20px;
color: #718096;
color: hsl(var(--text-muted));
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.empty-state p {
@@ -114,20 +130,20 @@
.empty-state small {
font-size: 13px;
color: #a0aec0;
color: hsl(var(--text-muted));
}
.message {
padding: 12px 16px;
border-radius: 6px;
border-radius: var(--radius-md);
margin-bottom: 16px;
font-size: 14px;
}
.message.error {
background: #fff5f5;
color: #c53030;
border: 1px solid #feb2b2;
background: #fff1f2;
color: #b91c1c;
border: 1px solid #fecdd3;
}
.version-list {
@@ -137,16 +153,17 @@
}
.version-item {
background: #f7fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
background: hsl(var(--surface));
border: 1px solid hsl(var(--border));
border-radius: var(--radius-md);
padding: 14px;
transition: all 0.2s ease;
box-shadow: var(--shadow-sm);
}
.version-item:hover {
border-color: #cbd5e0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border-color: hsl(var(--primary) / 0.25);
box-shadow: var(--shadow-md);
}
.version-header {
@@ -160,23 +177,23 @@
.version-number {
font-weight: 600;
font-size: 14px;
color: #2d3748;
color: hsl(var(--text-primary));
}
.auto-badge {
background: #e8f4fd;
color: #3182ce;
background: hsl(var(--surface-muted));
color: hsl(var(--text-secondary));
padding: 2px 8px;
border-radius: 10px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
}
.version-label {
color: #667eea;
color: hsl(var(--primary));
font-size: 13px;
font-weight: 500;
font-weight: 600;
}
.version-meta {
@@ -184,7 +201,7 @@
align-items: center;
justify-content: space-between;
font-size: 13px;
color: #718096;
color: hsl(var(--text-muted));
margin-bottom: 8px;
}
@@ -199,21 +216,22 @@
height: 20px;
border-radius: 50%;
object-fit: cover;
border: 1px solid hsl(var(--border));
}
.version-time {
color: #a0aec0;
color: hsl(var(--text-muted));
}
.version-preview {
font-size: 13px;
color: #4a5568;
color: hsl(var(--text-secondary));
line-height: 1.5;
margin-bottom: 12px;
padding: 8px;
background: white;
border-radius: 4px;
border: 1px solid #edf2f7;
background: hsl(var(--surface-muted));
border-radius: var(--radius-sm);
border: 1px solid hsl(var(--border));
max-height: 60px;
overflow: hidden;
}
@@ -221,18 +239,18 @@
.restore-btn {
width: 100%;
padding: 8px 12px;
background: white;
color: #667eea;
border: 1px solid #667eea;
border-radius: 6px;
background: hsl(var(--surface));
color: hsl(var(--primary));
border: 1px solid hsl(var(--primary));
border-radius: var(--radius-sm);
font-size: 13px;
font-weight: 500;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.restore-btn:hover:not(:disabled) {
background: #667eea;
background: hsl(var(--primary));
color: white;
}
@@ -247,21 +265,21 @@
justify-content: space-between;
padding: 16px 0;
margin-top: 16px;
border-top: 1px solid #e2e8f0;
border-top: 1px solid hsl(var(--border));
}
.pagination button {
padding: 8px 16px;
background: #f7fafc;
border: 1px solid #e2e8f0;
border-radius: 6px;
background: hsl(var(--surface-muted));
border: 1px solid hsl(var(--border));
border-radius: var(--radius-sm);
font-size: 13px;
cursor: pointer;
transition: all 0.2s ease;
}
.pagination button:hover:not(:disabled) {
background: #edf2f7;
background: hsl(var(--surface));
}
.pagination button:disabled {
@@ -271,7 +289,7 @@
.pagination span {
font-size: 13px;
color: #718096;
color: hsl(var(--text-muted));
}
/* Create Version Modal */
@@ -281,7 +299,7 @@
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
background: rgba(15, 23, 42, 0.5);
display: flex;
align-items: center;
justify-content: center;
@@ -289,39 +307,40 @@
}
.create-modal {
background: white;
border-radius: 12px;
background: hsl(var(--surface));
border-radius: var(--radius-lg);
padding: 24px;
width: 90%;
max-width: 400px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 420px;
border: 1px solid hsl(var(--border));
box-shadow: var(--shadow-lg);
}
.create-modal h3 {
margin: 0 0 8px 0;
font-size: 18px;
color: #1a202c;
color: hsl(var(--text-primary));
}
.create-modal p {
margin: 0 0 20px 0;
font-size: 14px;
color: #718096;
color: hsl(var(--text-secondary));
}
.create-modal label {
display: block;
font-size: 14px;
font-weight: 500;
color: #2d3748;
font-weight: 600;
color: hsl(var(--text-primary));
margin-bottom: 8px;
}
.create-modal input {
width: 100%;
padding: 10px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-sm);
font-size: 14px;
margin-bottom: 20px;
box-sizing: border-box;
@@ -329,8 +348,8 @@
.create-modal input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
border-color: hsl(var(--primary));
box-shadow: var(--focus-ring);
}
.modal-buttons {
@@ -341,31 +360,31 @@
.modal-buttons button {
padding: 10px 20px;
border-radius: 6px;
border-radius: var(--radius-sm);
font-size: 14px;
font-weight: 500;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.modal-buttons button:not(.primary) {
background: #f7fafc;
color: #4a5568;
border: 1px solid #e2e8f0;
background: hsl(var(--surface-muted));
color: hsl(var(--text-secondary));
border: 1px solid hsl(var(--border));
}
.modal-buttons button:not(.primary):hover {
background: #edf2f7;
background: hsl(var(--surface));
}
.modal-buttons button.primary {
background: #667eea;
background: hsl(var(--primary));
color: white;
border: none;
}
.modal-buttons button.primary:hover:not(:disabled) {
background: #5568d3;
background: hsl(var(--primary) / 0.9);
}
.modal-buttons button:disabled {

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react';
import { versionsApi, type DocumentVersion } from '../../api/document';
import * as Y from 'yjs';
import PixelIcon from '../PixelIcon/PixelIcon';
import './VersionHistoryPanel.css';
interface VersionHistoryPanelProps {
@@ -180,6 +181,7 @@ function VersionHistoryPanel({ documentId, ydoc, canEdit, onClose }: VersionHist
<div className="loading-state">Loading versions...</div>
) : versions.length === 0 ? (
<div className="empty-state">
<PixelIcon name="gem" size={18} color="hsl(var(--brand-teal))" />
<p>No versions yet</p>
<small>Versions are created when you save manually or automatically over time</small>
</div>

View File

@@ -1,29 +1,31 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Manrope:wght@500;600;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 232 217 243;
--foreground: 43 27 56;
--background: 220 33% 98%;
--foreground: 222 47% 11%;
--card: 0 0% 100%;
--card-foreground: 43 27 56;
--card-foreground: 222 47% 11%;
--popover: 0 0% 100%;
--popover-foreground: 43 27 56;
--primary: 277 42% 52%;
--popover-foreground: 222 47% 11%;
--primary: 214 89% 52%;
--primary-foreground: 0 0% 100%;
--secondary: 190 100% 50%;
--secondary-foreground: 43 27 56;
--muted: 276 100% 97%;
--muted-foreground: 240 13% 40%;
--accent: 325 100% 71%;
--secondary: 173 80% 40%;
--secondary-foreground: 0 0% 100%;
--muted: 220 16% 96%;
--muted-foreground: 215 16% 47%;
--accent: 188 92% 42%;
--accent-foreground: 0 0% 100%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 100%;
--border: 43 27 56;
--input: 43 27 56;
--ring: 190 100% 50%;
--radius: 0rem;
--border: 215 20% 90%;
--input: 215 20% 90%;
--ring: 214 89% 52%;
--radius: 0.75rem;
}
}
@@ -34,41 +36,62 @@
}
:root {
/* Vibrant Fantasy Color Palette */
--pixel-purple-deep: #4A1B6F;
--pixel-purple-bright: #8B4FB9;
--pixel-pink-vibrant: #FF6EC7;
--pixel-cyan-bright: #00D9FF;
--pixel-orange-warm: #FF8E3C;
--pixel-yellow-gold: #FFD23F;
--pixel-green-lime: #8EF048;
--pixel-green-forest: #3FA54D;
/* Modern SaaS Design Tokens */
--surface: 0 0% 100%;
--surface-muted: 220 16% 96%;
--text-primary: 222 47% 11%;
--text-secondary: 215 25% 27%;
--text-muted: 215 16% 47%;
--brand: 214 89% 52%;
--brand-dark: 221 83% 45%;
--brand-teal: 173 80% 40%;
--brand-teal-dark: 173 80% 32%;
/* UI Backgrounds & Neutrals */
--pixel-bg-dark: #2B1B38;
--pixel-bg-medium: #4A3B5C;
--pixel-bg-light: #E8D9F3;
--pixel-panel: #F5F0FF;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08);
--shadow-md: 0 12px 30px rgba(15, 23, 42, 0.10);
--shadow-lg: 0 20px 50px rgba(15, 23, 42, 0.14);
--focus-ring: 0 0 0 3px rgba(37, 99, 235, 0.25);
--gradient-hero: linear-gradient(120deg, #eef6ff 0%, #f5f7fb 55%, #ffffff 100%);
--gradient-accent: linear-gradient(120deg, #2563eb 0%, #14b8a6 100%);
/* Compatibility aliases (legacy pixel tokens mapped to modern palette) */
--pixel-purple-deep: #1E40AF;
--pixel-purple-bright: #2563EB;
--pixel-pink-vibrant: #38BDF8;
--pixel-cyan-bright: #14B8A6;
--pixel-orange-warm: #F59E0B;
--pixel-yellow-gold: #FBBF24;
--pixel-green-lime: #22C55E;
--pixel-green-forest: #16A34A;
--pixel-bg-dark: #0F172A;
--pixel-bg-medium: #1E293B;
--pixel-bg-light: #F5F7FB;
--pixel-panel: #FFFFFF;
--pixel-white: #FFFFFF;
/* Shadows & Outlines */
--pixel-shadow-dark: #1A0E28;
--pixel-outline: #2B1B38;
--pixel-shadow-dark: rgba(15, 23, 42, 0.2);
--pixel-outline: #E2E8F0;
/* Text Colors */
--pixel-text-primary: #2B1B38;
--pixel-text-secondary: #4A3B5C;
--pixel-text-muted: #8B7B9C;
--pixel-text-primary: #0F172A;
--pixel-text-secondary: #334155;
--pixel-text-muted: #64748B;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: var(--pixel-bg-light);
background-image: url('/pixel-patterns.svg#pixel-checker-subtle');
background: var(--gradient-hero);
color: hsl(var(--foreground));
}
h1, h2, h3, h4, h5 {
font-family: 'Manrope', 'Inter', sans-serif;
}
#root {
@@ -77,54 +100,47 @@
/* Pixel Art Utility Classes */
.pixel-border {
border: 3px solid var(--pixel-outline);
box-shadow:
4px 4px 0 var(--pixel-shadow-dark),
4px 4px 0 3px var(--pixel-outline);
border: 1px solid hsl(var(--border));
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
}
.pixel-card {
border: 3px solid var(--pixel-outline);
box-shadow:
0 0 0 3px var(--pixel-outline),
6px 6px 0 var(--pixel-shadow-dark),
6px 6px 0 3px var(--pixel-outline);
transition: transform 0.1s ease, box-shadow 0.1s ease;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-lg);
box-shadow: var(--shadow-md);
background: hsl(var(--surface));
transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.pixel-card:hover {
transform: translate(-2px, -2px);
box-shadow:
0 0 0 3px var(--pixel-outline),
8px 8px 0 var(--pixel-shadow-dark),
8px 8px 0 3px var(--pixel-outline);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.pixel-button {
border: 3px solid var(--pixel-outline);
box-shadow:
4px 4px 0 var(--pixel-shadow-dark);
transition: transform 0.05s ease, box-shadow 0.05s ease;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-md);
box-shadow: var(--shadow-sm);
transition: transform 0.08s ease, box-shadow 0.08s ease;
cursor: pointer;
}
.pixel-button:hover {
transform: translate(-1px, -1px);
box-shadow:
5px 5px 0 var(--pixel-shadow-dark);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.pixel-button:active {
transform: translate(2px, 2px);
box-shadow:
2px 2px 0 var(--pixel-shadow-dark);
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
/* Focus states for accessibility */
button:focus-visible,
input:focus-visible {
outline: 3px solid var(--pixel-yellow-gold);
outline-offset: 2px;
outline: none;
box-shadow: var(--focus-ring);
}
/* Reduced motion support */
@@ -196,29 +212,29 @@
.create-buttons button {
padding: 0.75rem 1.5rem;
background: var(--pixel-purple-bright);
color: white;
border: 3px solid var(--pixel-outline);
border-radius: 0;
background: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border: 1px solid hsl(var(--border));
border-radius: var(--radius-md);
cursor: pointer;
font-size: 1rem;
font-weight: 600;
box-shadow: 4px 4px 0 var(--pixel-shadow-dark);
transition: transform 0.05s ease, box-shadow 0.05s ease;
box-shadow: var(--shadow-sm);
transition: transform 0.1s ease, box-shadow 0.1s ease, background 0.1s ease;
display: inline-flex;
align-items: center;
justify-content: center;
}
.create-buttons button:hover {
background: var(--pixel-purple-deep);
transform: translate(-1px, -1px);
box-shadow: 5px 5px 0 var(--pixel-shadow-dark);
background: hsl(var(--primary) / 0.9);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.create-buttons button:active {
transform: translate(2px, 2px);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark);
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
.create-buttons button:disabled {
@@ -234,21 +250,17 @@
}
.document-card {
background: var(--pixel-white);
background: hsl(var(--surface));
padding: 1.5rem;
border-radius: 0;
border: 3px solid var(--pixel-outline);
box-shadow:
6px 6px 0 var(--pixel-shadow-dark),
6px 6px 0 3px var(--pixel-outline);
transition: transform 0.1s ease, box-shadow 0.1s ease;
border-radius: var(--radius-lg);
border: 1px solid hsl(var(--border));
box-shadow: var(--shadow-md);
transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.document-card:hover {
transform: translate(-2px, -2px);
box-shadow:
8px 8px 0 var(--pixel-shadow-dark),
8px 8px 0 3px var(--pixel-outline);
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.doc-info h3 {
@@ -256,13 +268,13 @@
}
.doc-type {
color: var(--pixel-text-secondary);
color: hsl(var(--text-secondary));
text-transform: capitalize;
margin-bottom: 0.5rem;
}
.doc-date {
color: var(--pixel-text-muted);
color: hsl(var(--text-muted));
font-size: 0.875rem;
}
@@ -274,37 +286,37 @@
.doc-actions button {
padding: 0.5rem 1rem;
border: 3px solid var(--pixel-outline);
background: var(--pixel-white);
border-radius: 0;
border: 1px solid hsl(var(--border));
background: hsl(var(--surface));
border-radius: var(--radius-sm);
cursor: pointer;
min-width: 44px;
min-height: 44px;
box-shadow: 3px 3px 0 var(--pixel-shadow-dark);
transition: transform 0.05s ease, box-shadow 0.05s ease;
box-shadow: var(--shadow-sm);
transition: transform 0.1s ease, box-shadow 0.1s ease;
display: inline-flex;
align-items: center;
justify-content: center;
}
.doc-actions button:hover {
transform: translate(-1px, -1px);
box-shadow: 4px 4px 0 var(--pixel-shadow-dark);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.doc-actions button:active {
transform: translate(1px, 1px);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark);
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
.doc-actions button:first-child {
background: var(--pixel-cyan-bright);
color: white;
border-color: var(--pixel-outline);
background: hsl(var(--secondary));
color: hsl(var(--secondary-foreground));
border-color: hsl(var(--border));
}
.doc-actions button:first-child:hover {
background: var(--pixel-purple-bright);
background: hsl(var(--primary));
}
/* Editor Page */
@@ -312,6 +324,7 @@
display: flex;
flex-direction: column;
height: 100vh;
background: hsl(var(--background));
}
.page-header {
@@ -319,38 +332,104 @@
justify-content: space-between;
align-items: center;
padding: 1rem;
background: var(--pixel-white);
border-bottom: 3px solid var(--pixel-outline);
background: hsl(var(--surface));
border-bottom: 1px solid hsl(var(--border));
box-shadow: var(--shadow-sm);
}
.page-header button {
padding: 0.5rem 1rem;
background: var(--pixel-panel);
border: 3px solid var(--pixel-outline);
border-radius: 0;
background: hsl(var(--surface));
border: 1px solid hsl(var(--border));
border-radius: var(--radius-sm);
cursor: pointer;
min-width: 44px;
min-height: 44px;
box-shadow: 3px 3px 0 var(--pixel-shadow-dark);
transition: transform 0.05s ease, box-shadow 0.05s ease;
box-shadow: var(--shadow-sm);
transition: transform 0.1s ease, box-shadow 0.1s ease, background 0.1s ease;
}
.page-header button:hover {
background: var(--pixel-bg-light);
transform: translate(-1px, -1px);
box-shadow: 4px 4px 0 var(--pixel-shadow-dark);
background: hsl(var(--surface-muted));
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.page-header button:active {
transform: translate(1px, 1px);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark);
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
.sync-status {
color: var(--pixel-text-secondary);
display: inline-flex;
align-items: center;
gap: 8px;
color: hsl(var(--text-secondary));
font-size: 0.875rem;
}
.sync-dot {
width: 10px;
height: 10px;
border-radius: 3px;
background: hsl(var(--surface-muted));
border: 1px solid hsl(var(--border));
}
.sync-dot.synced {
background: hsl(var(--secondary));
border-color: hsl(var(--secondary));
box-shadow: 0 0 0 2px rgba(20, 184, 166, 0.15);
}
.sync-dot.syncing {
background: hsl(var(--primary));
border-color: hsl(var(--primary));
animation: pulse-dot 1.2s ease-in-out infinite;
}
@keyframes pulse-dot {
0%, 100% { transform: scale(1); opacity: 0.7; }
50% { transform: scale(1.2); opacity: 1; }
}
.view-only-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 999px;
background: hsl(var(--surface));
color: hsl(var(--text-secondary));
font-size: 0.85rem;
font-weight: 600;
border: 1px solid hsl(var(--border));
box-shadow: var(--shadow-sm);
line-height: 1;
}
.view-only-icon {
width: 14px;
height: 14px;
color: hsl(var(--text-muted));
}
.shared-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: hsl(var(--text-secondary));
background: hsl(var(--surface-muted));
border: 1px solid hsl(var(--border));
border-radius: 6px;
box-shadow: 2px 2px 0 rgba(15, 23, 42, 0.08);
}
.page-content {
display: flex;
flex-direction: column; /* Mobile: stack vertically */
@@ -384,9 +463,8 @@
.sidebar {
width: 100%; /* Mobile: full width */
background: var(--pixel-white);
background-image: url('/pixel-patterns.svg#pixel-scanlines');
border-top: 3px solid var(--pixel-outline); /* Mobile: top border */
background: hsl(var(--surface));
border-top: 1px solid hsl(var(--border)); /* Mobile: top border */
border-left: none;
padding: 1rem;
max-height: 200px; /* Mobile: limit height */
@@ -398,18 +476,16 @@
width: 250px; /* Desktop: fixed width */
max-height: none;
border-top: none;
border-left: 3px solid var(--pixel-outline);
border-left: 1px solid hsl(var(--border));
}
}
/* Editor */
.editor-container {
background: var(--pixel-white);
border-radius: 0;
border: 3px solid var(--pixel-outline);
box-shadow:
6px 6px 0 var(--pixel-shadow-dark),
6px 6px 0 3px var(--pixel-outline);
background: hsl(var(--surface));
border-radius: var(--radius-lg);
border: 1px solid hsl(var(--border));
box-shadow: var(--shadow-md);
overflow: hidden;
}
@@ -417,41 +493,39 @@
display: flex;
gap: 0.5rem;
padding: 0.75rem;
background: var(--pixel-panel);
background-image: url('/pixel-patterns.svg#pixel-panel-texture');
border-bottom: 3px solid var(--pixel-outline);
background: hsl(var(--surface-muted));
border-bottom: 1px solid hsl(var(--border));
flex-wrap: wrap;
}
.toolbar button {
padding: 0.5rem 0.75rem;
background: var(--pixel-white);
border: 3px solid var(--pixel-outline);
border-radius: 0;
background: hsl(var(--surface));
border: 1px solid hsl(var(--border));
border-radius: var(--radius-sm);
cursor: pointer;
min-width: 44px;
min-height: 44px;
box-shadow: 3px 3px 0 var(--pixel-shadow-dark);
transition: transform 0.05s ease, box-shadow 0.05s ease;
box-shadow: var(--shadow-sm);
transition: transform 0.1s ease, box-shadow 0.1s ease, background 0.1s ease;
}
.toolbar button:hover {
background: var(--pixel-bg-light);
transform: translate(-1px, -1px);
box-shadow: 4px 4px 0 var(--pixel-shadow-dark);
background: hsl(var(--surface-muted));
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.toolbar button:active {
transform: translate(1px, 1px);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark);
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
.toolbar button.active {
background: var(--pixel-cyan-bright);
color: white;
border-color: var(--pixel-outline);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark);
transform: translate(1px, 1px);
background: hsl(var(--secondary));
color: hsl(var(--secondary-foreground));
border-color: hsl(var(--border));
box-shadow: var(--shadow-sm);
}
.editor-content {
@@ -496,12 +570,13 @@
/* Collaborative Cursors */
.collaboration-cursor__caret {
position: absolute;
border-left: 2px solid;
border-left: 3px solid;
border-right: none;
margin-left: -1px;
margin-right: -1px;
pointer-events: none;
word-break: normal;
box-shadow: 1px 0 0 currentColor;
}
.collaboration-cursor__label {
@@ -515,7 +590,8 @@
user-select: none;
color: #fff;
padding: 2px 6px;
border-radius: 0;
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.15);
white-space: nowrap;
box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.3);
}
@@ -537,14 +613,14 @@
}
.kanban-column {
background: var(--pixel-panel);
background-image: url('/pixel-patterns.svg#pixel-dither-diagonal');
border-radius: 0;
border: 3px solid var(--pixel-outline);
background: hsl(var(--surface));
border-radius: var(--radius-lg);
border: 1px solid hsl(var(--border));
padding: 1rem;
width: 100%; /* Mobile: full width */
min-width: unset;
max-width: unset;
box-shadow: var(--shadow-sm);
}
@media (min-width: 640px) {
@@ -574,22 +650,18 @@
}
.kanban-card {
background: var(--pixel-white);
background: hsl(var(--surface));
padding: 1rem;
border-radius: 0;
border: 3px solid var(--pixel-outline);
box-shadow:
4px 4px 0 var(--pixel-shadow-dark),
4px 4px 0 3px var(--pixel-outline);
border-radius: var(--radius-md);
border: 1px solid hsl(var(--border));
box-shadow: var(--shadow-sm);
cursor: pointer;
transition: transform 0.1s ease, box-shadow 0.1s ease;
transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.kanban-card:hover {
transform: translate(-1px, -1px);
box-shadow:
5px 5px 0 var(--pixel-shadow-dark),
5px 5px 0 3px var(--pixel-outline);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.kanban-card h4 {
@@ -597,38 +669,38 @@
}
.kanban-card p {
color: var(--pixel-text-secondary);
color: hsl(var(--text-secondary));
font-size: 0.875rem;
}
.add-task-btn {
padding: 0.75rem;
background: var(--pixel-white);
border: 3px dashed var(--pixel-outline);
border-radius: 0;
background: hsl(var(--surface));
border: 1px dashed hsl(var(--border));
border-radius: var(--radius-md);
cursor: pointer;
color: var(--pixel-text-secondary);
color: hsl(var(--text-secondary));
}
.add-task-btn:hover {
border-color: var(--pixel-purple-bright);
color: var(--pixel-text-primary);
background: var(--pixel-bg-light);
border-color: hsl(var(--primary));
color: hsl(var(--text-primary));
background: hsl(var(--surface-muted));
}
.add-task-form input {
width: 100%;
padding: 0.75rem;
border: 2px solid var(--pixel-outline);
border-radius: 0;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-sm);
margin-bottom: 0.5rem;
background: var(--pixel-white);
background: hsl(var(--surface));
}
.add-task-form input:focus {
outline: none;
border-color: var(--pixel-cyan-bright);
box-shadow: 0 0 0 2px var(--pixel-cyan-bright);
border-color: hsl(var(--primary));
box-shadow: var(--focus-ring);
}
.form-actions {
@@ -638,28 +710,28 @@
.form-actions button {
padding: 0.5rem 1rem;
border: 3px solid var(--pixel-outline);
border-radius: 0;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-sm);
cursor: pointer;
background: var(--pixel-white);
box-shadow: 3px 3px 0 var(--pixel-shadow-dark);
transition: transform 0.05s ease, box-shadow 0.05s ease;
background: hsl(var(--surface));
box-shadow: var(--shadow-sm);
transition: transform 0.1s ease, box-shadow 0.1s ease, background 0.1s ease;
}
.form-actions button:hover {
transform: translate(-1px, -1px);
box-shadow: 4px 4px 0 var(--pixel-shadow-dark);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.form-actions button:active {
transform: translate(1px, 1px);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark);
transform: translateY(0);
box-shadow: var(--shadow-sm);
}
.form-actions button:first-child {
background: var(--pixel-green-lime);
color: var(--pixel-text-primary);
border-color: var(--pixel-outline);
background: hsl(var(--secondary));
color: hsl(var(--secondary-foreground));
border-color: hsl(var(--border));
}
/* User List */
@@ -682,8 +754,8 @@
.user-color {
width: 12px;
height: 12px;
border-radius: 0;
border: 1px solid var(--pixel-outline);
border-radius: 999px;
border: 1px solid hsl(var(--border));
}
.user-name {
@@ -696,5 +768,5 @@
align-items: center;
height: 100vh;
font-size: 1.25rem;
color: var(--pixel-text-secondary);
color: hsl(var(--text-secondary));
}

View File

@@ -6,6 +6,7 @@ import UserList from "../components/Presence/UserList.tsx";
import ShareModal from "../components/Share/ShareModal.tsx";
import VersionHistoryPanel from "../components/VersionHistory/VersionHistoryPanel.tsx";
import { useYjsDocument } from "../hooks/useYjsDocument.ts";
import { Eye } from "lucide-react";
const EditorPage = () => {
const { id } = useParams<{ id: string }>();
@@ -27,14 +28,11 @@ const EditorPage = () => {
<button onClick={() => navigate("/")}> Back to Home</button>
<div className="header-actions">
{permission !== "edit" && permission !== null && (
<div className="view-only-badge" style={{ display: 'flex', alignItems: 'center', gap: '4px', padding: '4px 12px', backgroundColor: '#f0f0f0', borderRadius: '4px', fontSize: '14px' }}>
<span>👁</span>
<div className="view-only-badge">
<Eye className="view-only-icon" />
<span>View only</span>
</div>
)}
<div className="sync-status">
{synced ? "✓ Synced" : "⟳ Syncing..."}
</div>
{!shareToken && (
<button className="share-btn" onClick={() => setShowShareModal(true)}>
Share

View File

@@ -6,8 +6,8 @@ import { documentsApi } from '@/api/document';
import Navbar from '@/components/Navbar';
import { DocumentCard } from '@/components/Home/DocumentCard';
import { CreateButton } from '@/components/Home/CreateButton';
import FloatingGem from '@/components/PixelSprites/FloatingGem';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import PixelIcon from '@/components/PixelIcon/PixelIcon';
const Home = () => {
const { user } = useAuth();
@@ -64,8 +64,8 @@ const Home = () => {
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-pixel-bg-light">
<div className="font-pixel text-pixel-purple-bright animate-pixel-bounce">
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="text-brand text-base font-medium">
Loading...
</div>
</div>
@@ -75,15 +75,11 @@ const Home = () => {
return (
<>
<Navbar />
<div className="max-w-7xl mx-auto px-8 py-12 relative min-h-screen bg-pixel-bg-light">
{/* Decorative floating gems */}
<FloatingGem position={{ top: '20px', right: '40px' }} delay={0} size={40} />
<FloatingGem position={{ top: '60px', left: '60px' }} delay={1.5} size={32} />
<FloatingGem position={{ bottom: '100px', right: '100px' }} delay={3} size={36} />
<div className="max-w-7xl mx-auto px-8 py-12 relative min-h-screen bg-background">
{/* Page Header */}
<div className="mb-12">
<h1 className="font-pixel text-4xl text-pixel-purple-bright mb-6 tracking-wide">
<h1 className="font-display text-3xl text-text-primary mb-4">
My Workspace
</h1>
@@ -109,10 +105,10 @@ const Home = () => {
{/* Tabbed Interface */}
<Tabs defaultValue="owned" className="w-full">
<TabsList className="
bg-pixel-panel
border-[3px]
border-pixel-outline
shadow-pixel-sm
bg-surface-muted
border
border-border
shadow-soft
p-1
mb-8
">
@@ -121,9 +117,9 @@ const Home = () => {
className="
font-sans
font-semibold
data-[state=active]:bg-pixel-cyan-bright
data-[state=active]:text-white
data-[state=active]:shadow-pixel-sm
data-[state=active]:bg-surface
data-[state=active]:text-text-primary
data-[state=active]:shadow-soft
transition-all
duration-100
"
@@ -135,9 +131,9 @@ const Home = () => {
className="
font-sans
font-semibold
data-[state=active]:bg-pixel-cyan-bright
data-[state=active]:text-white
data-[state=active]:shadow-pixel-sm
data-[state=active]:bg-surface
data-[state=active]:text-text-primary
data-[state=active]:shadow-soft
transition-all
duration-100
"
@@ -150,9 +146,10 @@ const Home = () => {
<TabsContent value="owned">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{ownedDocuments.length === 0 ? (
<p className="col-span-full text-center text-pixel-text-muted font-sans py-12">
No documents yet. Create one to get started!
</p>
<div className="col-span-full flex flex-col items-center gap-3 text-text-muted font-sans py-12">
<PixelIcon name="gem" size={20} color="hsl(var(--brand-teal))" />
<p>No documents yet. Create one to get started!</p>
</div>
) : (
ownedDocuments.map((doc) => (
<DocumentCard
@@ -170,9 +167,10 @@ const Home = () => {
<TabsContent value="shared">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{sharedDocuments.length === 0 ? (
<p className="col-span-full text-center text-pixel-text-muted font-sans py-12">
No shared documents yet.
</p>
<div className="col-span-full flex flex-col items-center gap-3 text-text-muted font-sans py-12">
<PixelIcon name="gem" size={20} color="hsl(var(--brand-teal))" />
<p>No shared documents yet.</p>
</div>
) : (
sharedDocuments.map((doc) => (
<DocumentCard

View File

@@ -24,9 +24,6 @@ const KanbanPage = () => {
<div className="page-header">
<button onClick={() => navigate("/")}> Back to Home</button>
<div className="header-actions">
<div className="sync-status">
{synced ? "✓ Synced" : "⟳ Syncing..."}
</div>
{!shareToken && (
<button className="share-btn" onClick={() => setShowShareModal(true)}>
Share

View File

@@ -2,7 +2,8 @@
.landing-page {
min-height: 100vh;
overflow-x: hidden;
color: hsl(var(--text-primary));
background: hsl(var(--background));
}
/* ========================================
@@ -10,248 +11,313 @@
======================================== */
.landing-hero {
min-height: 100vh;
min-height: 90vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
padding: 2rem;
/* Animated gradient background */
background: linear-gradient(
135deg,
var(--pixel-purple-deep) 0%,
var(--pixel-purple-bright) 40%,
var(--pixel-pink-vibrant) 100%
);
background-size: 200% 200%;
animation: gradient-shift 12s ease infinite;
padding: 6rem 2rem 5rem;
background: var(--gradient-hero);
}
@keyframes gradient-shift {
0%, 100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
.hero-gem svg {
opacity: 0.18;
filter: drop-shadow(0 10px 20px rgba(15, 23, 42, 0.2));
}
.hero-grid {
width: 100%;
max-width: 1200px;
margin: 0 auto;
display: grid;
gap: 3rem;
align-items: center;
}
.hero-content {
text-align: center;
z-index: 10;
max-width: 800px;
text-align: left;
}
.hero-logo {
display: flex;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 16px;
margin-bottom: 2rem;
gap: 12px;
padding: 0.4rem 0.85rem;
border-radius: 999px;
background: hsl(var(--surface));
border: 1px solid hsl(var(--border));
box-shadow: var(--shadow-sm);
margin-bottom: 1.5rem;
}
.hero-brand {
font-size: 3rem;
font-weight: 800;
color: var(--pixel-white);
text-shadow:
4px 4px 0 var(--pixel-shadow-dark),
-1px -1px 0 var(--pixel-shadow-dark),
1px -1px 0 var(--pixel-shadow-dark),
-1px 1px 0 var(--pixel-shadow-dark);
margin: 0;
letter-spacing: -1px;
font-size: 0.95rem;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: hsl(var(--text-secondary));
}
.hero-headline {
font-size: 2.5rem;
font-size: 2.75rem;
font-weight: 700;
color: var(--pixel-white);
margin: 0 0 1.5rem 0;
text-shadow: 2px 2px 0 var(--pixel-shadow-dark);
line-height: 1.2;
color: hsl(var(--text-primary));
margin: 0 0 1.25rem 0;
line-height: 1.1;
}
.hero-tagline {
font-size: 1.25rem;
color: var(--pixel-bg-light);
margin: 0 0 3rem 0;
line-height: 1.6;
opacity: 0.95;
font-size: 1.1rem;
color: hsl(var(--text-secondary));
margin: 0 0 2rem 0;
line-height: 1.7;
}
.hero-login-buttons {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: center;
}
.hero-scroll-hint {
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
color: var(--pixel-white);
opacity: 0.6;
animation: bounce-hint 2s ease-in-out infinite;
.hero-note {
font-size: 0.9rem;
color: hsl(var(--text-muted));
margin: 0;
}
@keyframes bounce-hint {
0%, 100% {
transform: translateX(-50%) translateY(0);
}
50% {
transform: translateX(-50%) translateY(10px);
}
.provider-buttons {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
/* ========================================
Login Buttons
======================================== */
.landing-login-button {
display: flex;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 1rem 2rem;
gap: 10px;
padding: 0.9rem 1.4rem;
font-size: 1rem;
font-weight: 600;
border: 3px solid var(--pixel-outline);
border: 1px solid hsl(var(--border));
border-radius: var(--radius-md);
background: hsl(var(--surface));
color: hsl(var(--text-primary));
cursor: pointer;
transition: transform 0.05s ease, box-shadow 0.05s ease;
min-width: 260px;
box-shadow: var(--shadow-sm);
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
}
.landing-login-button.google {
background: var(--pixel-white);
color: var(--pixel-text-primary);
box-shadow: 4px 4px 0 var(--pixel-shadow-dark);
.landing-login-button:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.landing-login-button.google:hover {
transform: translate(-2px, -2px);
box-shadow: 6px 6px 0 var(--pixel-shadow-dark);
background: var(--pixel-panel);
.landing-login-button.primary {
background: var(--gradient-accent);
color: white;
border: none;
gap: 12px;
}
.landing-login-button.google:active {
transform: translate(2px, 2px);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark);
.landing-login-button.primary:hover {
filter: brightness(0.98);
}
.landing-login-button.github {
background: var(--pixel-bg-dark);
color: var(--pixel-white);
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.5);
}
.landing-login-button.github:hover {
transform: translate(-2px, -2px);
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.5);
background: var(--pixel-bg-medium);
}
.landing-login-button.github:active {
transform: translate(2px, 2px);
box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.5);
.landing-login-button.provider {
background: hsl(var(--surface));
}
.landing-login-button.large {
padding: 1.25rem 2.5rem;
font-size: 1.125rem;
min-width: 300px;
padding: 1rem 2rem;
font-size: 1.05rem;
}
.oauth-icon {
flex-shrink: 0;
}
.hero-mock {
background: hsl(var(--surface));
border: 1px solid hsl(var(--border));
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
overflow: hidden;
}
.mock-topbar {
display: flex;
gap: 6px;
padding: 12px 16px;
border-bottom: 1px solid hsl(var(--border));
background: hsl(var(--surface-muted));
}
.mock-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: hsl(var(--border));
}
.mock-body {
padding: 20px 22px 24px;
display: flex;
flex-direction: column;
gap: 16px;
}
.mock-tabs {
display: flex;
gap: 12px;
font-size: 0.85rem;
color: hsl(var(--text-muted));
}
.mock-tab {
padding-bottom: 6px;
border-bottom: 2px solid transparent;
}
.mock-tab.active {
color: hsl(var(--text-primary));
border-bottom-color: hsl(var(--primary));
font-weight: 600;
}
.mock-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.mock-card {
background: hsl(var(--surface-muted));
border-radius: var(--radius-md);
padding: 14px;
border: 1px solid hsl(var(--border));
}
.mock-line {
height: 8px;
background: hsl(var(--border));
border-radius: 999px;
margin-bottom: 8px;
}
.mock-line.wide {
width: 90%;
}
.mock-line.short {
width: 55%;
margin-bottom: 0;
}
.mock-rows {
display: flex;
flex-direction: column;
gap: 10px;
}
.mock-row {
display: grid;
grid-template-columns: 24px 1fr 80px;
align-items: center;
gap: 10px;
padding: 10px 12px;
border-radius: var(--radius-md);
border: 1px solid hsl(var(--border));
}
.mock-pill {
width: 18px;
height: 18px;
border-radius: 999px;
background: hsl(var(--secondary));
}
/* ========================================
Features Section
======================================== */
.landing-features {
padding: 6rem 2rem;
background: var(--pixel-bg-light);
position: relative;
padding: 5rem 2rem;
background: hsl(var(--background));
}
.section-divider {
display: flex;
align-items: center;
gap: 12px;
padding: 0 2rem;
background: hsl(var(--background));
max-width: 1200px;
margin: 0 auto;
height: 32px;
}
.divider-line {
flex: 1;
height: 1px;
background: hsl(var(--border));
}
.section-title {
text-align: center;
font-size: 2.5rem;
font-size: 2.25rem;
font-weight: 700;
color: var(--pixel-text-primary);
margin: 0 0 4rem 0;
text-shadow: 2px 2px 0 var(--pixel-white);
color: hsl(var(--text-primary));
margin: 0 0 3rem 0;
}
.features-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
gap: 1.75rem;
max-width: 1200px;
margin: 0 auto;
}
.feature-card {
background: var(--pixel-white);
padding: 2.5rem 2rem;
border: 3px solid var(--pixel-outline);
box-shadow:
0 0 0 3px var(--pixel-outline),
6px 6px 0 var(--pixel-shadow-dark),
6px 6px 0 3px var(--pixel-outline);
text-align: center;
transition: transform 0.1s ease, box-shadow 0.1s ease;
opacity: 0;
animation: fade-in-up 0.6s ease forwards;
}
.feature-card:nth-child(1) { animation-delay: 0.1s; }
.feature-card:nth-child(2) { animation-delay: 0.2s; }
.feature-card:nth-child(3) { animation-delay: 0.3s; }
.feature-card:nth-child(4) { animation-delay: 0.4s; }
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
background: hsl(var(--surface));
padding: 2rem;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
text-align: left;
transition: transform 0.12s ease, box-shadow 0.12s ease;
}
.feature-card:hover {
transform: translate(-3px, -3px);
box-shadow:
0 0 0 3px var(--pixel-outline),
9px 9px 0 var(--pixel-shadow-dark),
9px 9px 0 3px var(--pixel-outline);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.feature-icon {
margin-bottom: 1.5rem;
width: 52px;
height: 52px;
border-radius: 14px;
background: hsl(var(--surface-muted));
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.25rem;
border: 1px solid hsl(var(--border));
}
.feature-title {
font-size: 1.25rem;
font-size: 1.15rem;
font-weight: 700;
color: var(--pixel-text-primary);
margin: 0 0 0.75rem 0;
color: hsl(var(--text-primary));
margin: 0 0 0.6rem 0;
}
.feature-description {
font-size: 1rem;
color: var(--pixel-text-secondary);
color: hsl(var(--text-secondary));
margin: 0;
line-height: 1.5;
line-height: 1.6;
}
/* ========================================
@@ -259,44 +325,39 @@
======================================== */
.landing-footer {
padding: 6rem 2rem;
background: var(--pixel-bg-dark);
position: relative;
overflow: hidden;
padding: 4.5rem 2rem;
background: hsl(var(--surface-muted));
border-top: 1px solid hsl(var(--border));
}
.footer-content {
text-align: center;
max-width: 600px;
margin: 0 auto;
position: relative;
z-index: 10;
}
.footer-headline {
font-size: 2rem;
font-size: 1.9rem;
font-weight: 700;
color: var(--pixel-white);
margin: 0 0 1rem 0;
text-shadow: 2px 2px 0 var(--pixel-shadow-dark);
color: hsl(var(--text-primary));
margin: 0 0 0.75rem 0;
}
.footer-tagline {
font-size: 1.125rem;
color: var(--pixel-bg-light);
margin: 0 0 2.5rem 0;
opacity: 0.9;
font-size: 1.05rem;
color: hsl(var(--text-secondary));
margin: 0 0 2rem 0;
}
.footer-login-buttons {
display: flex;
justify-content: center;
margin-bottom: 3rem;
margin-bottom: 2.5rem;
}
.footer-tech {
font-size: 0.875rem;
color: var(--pixel-text-muted);
font-size: 0.9rem;
color: hsl(var(--text-muted));
margin: 0;
}
@@ -304,23 +365,17 @@
Responsive Design
======================================== */
/* Tablet (768px+) */
@media (min-width: 768px) {
.hero-brand {
font-size: 4rem;
}
.hero-headline {
font-size: 3rem;
font-size: 3.25rem;
}
.hero-tagline {
font-size: 1.5rem;
font-size: 1.2rem;
}
.hero-login-buttons {
.provider-buttons {
flex-direction: row;
gap: 1.5rem;
}
.features-grid {
@@ -328,48 +383,23 @@
}
.section-title {
font-size: 3rem;
font-size: 2.5rem;
}
}
/* Desktop (1024px+) */
@media (min-width: 1024px) {
.hero-brand {
font-size: 4.5rem;
}
.hero-headline {
font-size: 3.5rem;
.hero-grid {
grid-template-columns: 1.05fr 0.95fr;
}
.features-grid {
grid-template-columns: repeat(4, 1fr);
}
.feature-card {
padding: 2rem 1.5rem;
}
}
/* Large Desktop (1280px+) */
@media (min-width: 1280px) {
.hero-headline {
font-size: 4rem;
}
}
/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
.landing-hero {
animation: none;
}
.hero-scroll-hint {
animation: none;
}
.landing-login-button,
.feature-card {
animation: none;
opacity: 1;
transition: none;
}
}

View File

@@ -16,43 +16,93 @@ function LandingPage() {
<div className="landing-page">
{/* Hero Section */}
<section className="landing-hero">
<FloatingGem position={{ top: '10%', left: '8%' }} delay={0} size={44} />
<FloatingGem position={{ top: '15%', right: '12%' }} delay={1.5} size={36} />
<FloatingGem position={{ top: '45%', left: '5%' }} delay={2.5} size={28} />
<FloatingGem position={{ bottom: '25%', right: '8%' }} delay={3.5} size={40} />
<FloatingGem position={{ bottom: '15%', left: '15%' }} delay={4} size={32} />
<FloatingGem position={{ top: '60%', right: '20%' }} delay={1} size={24} />
<div className="hero-content">
<div className="hero-logo">
<PixelIcon name="gem" size={56} color="var(--pixel-yellow-gold)" />
<h1 className="hero-brand">DocNest</h1>
</div>
<h2 className="hero-headline">Create Together. In Real-Time.</h2>
<p className="hero-tagline">
Collaborative documents and Kanban boards that sync instantly.
<br />
Work with your team from anywhere, even offline.
</p>
<div className="hero-login-buttons">
<button className="landing-login-button google" onClick={handleGoogleLogin}>
<GoogleIcon />
<span>Sign in with Google</span>
</button>
<button className="landing-login-button github" onClick={handleGitHubLogin}>
<GitHubIcon />
<span>Sign in with GitHub</span>
</button>
</div>
<div className="hero-gem hero-gem-one">
<FloatingGem position={{ top: '12%', left: '8%' }} delay={0} size={28} />
</div>
<div className="hero-gem hero-gem-two">
<FloatingGem position={{ bottom: '18%', right: '10%' }} delay={1.5} size={24} />
</div>
<div className="hero-scroll-hint">
<PixelIcon name="back-arrow" size={24} style={{ transform: 'rotate(-90deg)' }} />
<div className="hero-grid">
<div className="hero-content">
<div className="hero-logo">
<PixelIcon name="gem" size={28} color="hsl(var(--brand-teal))" />
<span className="hero-brand">DocNest</span>
</div>
<h1 className="hero-headline">Create together. In real time.</h1>
<p className="hero-tagline">
Collaborative documents and Kanban boards that sync instantly.
<br />
Work with your team from anywhere, even offline.
</p>
<div className="hero-login-buttons">
<button className="landing-login-button primary" onClick={handleGoogleLogin}>
Get started free
<PixelIcon name="gem" size={16} color="white" />
</button>
<div className="provider-buttons">
<button className="landing-login-button provider" onClick={handleGoogleLogin}>
<GoogleIcon />
<span>Continue with Google</span>
</button>
<button className="landing-login-button provider" onClick={handleGitHubLogin}>
<GitHubIcon />
<span>Continue with GitHub</span>
</button>
</div>
<p className="hero-note">No credit card required.</p>
</div>
</div>
<div className="hero-mock">
<div className="mock-topbar">
<div className="mock-dot" />
<div className="mock-dot" />
<div className="mock-dot" />
</div>
<div className="mock-body">
<div className="mock-tabs">
<span className="mock-tab active">Docs</span>
<span className="mock-tab">Boards</span>
<span className="mock-tab">History</span>
</div>
<div className="mock-grid">
<div className="mock-card">
<div className="mock-line wide" />
<div className="mock-line" />
<div className="mock-line short" />
</div>
<div className="mock-card">
<div className="mock-line wide" />
<div className="mock-line" />
<div className="mock-line short" />
</div>
</div>
<div className="mock-rows">
<div className="mock-row">
<span className="mock-pill" />
<span className="mock-line" />
<span className="mock-line short" />
</div>
<div className="mock-row">
<span className="mock-pill" />
<span className="mock-line" />
<span className="mock-line short" />
</div>
</div>
</div>
</div>
</div>
</section>
<div className="section-divider">
<span className="divider-line" />
<PixelIcon name="gem" size={16} color="hsl(var(--brand-teal))" />
<span className="divider-line" />
</div>
{/* Features Section */}
<section className="landing-features">
<h2 className="section-title">Why DocNest?</h2>
@@ -61,42 +111,44 @@ function LandingPage() {
icon="sync-arrows"
title="Real-Time Collaboration"
description="See changes as they happen. Multiple cursors show who's working where."
color="var(--pixel-cyan-bright)"
color="hsl(var(--brand-teal))"
/>
<FeatureCard
icon="document"
title="Rich Documents"
description="Create formatted documents with headings, lists, and more."
color="var(--pixel-purple-bright)"
color="hsl(var(--brand))"
/>
<FeatureCard
icon="kanban"
title="Kanban Boards"
description="Drag-and-drop task management with real-time updates."
color="var(--pixel-orange-warm)"
color="hsl(var(--accent))"
/>
<FeatureCard
icon="shield"
title="Offline Support"
description="Your work syncs automatically when you're back online."
color="var(--pixel-green-lime)"
color="hsl(var(--secondary))"
/>
</div>
</section>
<div className="section-divider">
<span className="divider-line" />
<PixelIcon name="gem" size={16} color="hsl(var(--brand-teal))" />
<span className="divider-line" />
</div>
{/* Footer CTA */}
<footer className="landing-footer">
<FloatingGem position={{ top: '20%', left: '10%' }} delay={0.5} size={28} />
<FloatingGem position={{ bottom: '30%', right: '12%' }} delay={2} size={32} />
<div className="footer-content">
<h3 className="footer-headline">Ready to collaborate?</h3>
<p className="footer-tagline">Join thousands of teams creating together.</p>
<p className="footer-tagline">Join teams building together in DocNest.</p>
<div className="footer-login-buttons">
<button className="landing-login-button google large" onClick={handleGoogleLogin}>
<GoogleIcon />
<span>Get Started Free</span>
<button className="landing-login-button primary large" onClick={handleGoogleLogin}>
Get started free
</button>
</div>

View File

@@ -3,14 +3,15 @@
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
background: var(--gradient-hero);
padding: 24px;
}
.login-container {
background: white;
border-radius: 16px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
background: hsl(var(--surface));
border-radius: var(--radius-lg);
border: 1px solid hsl(var(--border));
box-shadow: var(--shadow-lg);
padding: 48px 40px;
max-width: 440px;
width: 100%;
@@ -20,20 +21,20 @@
.login-title {
font-size: 32px;
font-weight: 700;
color: #1a202c;
color: hsl(var(--text-primary));
margin: 0 0 8px 0;
}
.login-subtitle {
font-size: 16px;
color: #718096;
color: hsl(var(--text-secondary));
margin: 0 0 32px 0;
}
.login-buttons {
display: flex;
flex-direction: column;
gap: 16px;
gap: 14px;
}
.login-button {
@@ -41,14 +42,22 @@
align-items: center;
justify-content: center;
gap: 12px;
padding: 14px 24px;
font-size: 16px;
padding: 12px 20px;
font-size: 15px;
font-weight: 600;
border: none;
border-radius: 8px;
border: 1px solid hsl(var(--border));
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s ease;
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
width: 100%;
background: hsl(var(--surface));
color: hsl(var(--text-primary));
box-shadow: var(--shadow-sm);
}
.login-button:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.button-icon {
@@ -57,27 +66,20 @@
}
.google-button {
background: #4285f4;
background: var(--gradient-accent);
color: white;
border: none;
}
.google-button:hover {
background: #357ae8;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(66, 133, 244, 0.3);
filter: brightness(0.98);
}
.github-button {
background: #24292e;
color: white;
}
.github-button:hover {
background: #1a1f23;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(36, 41, 46, 0.3);
background: hsl(var(--surface));
}
.login-button:active {
transform: translateY(0);
box-shadow: var(--shadow-sm);
}

View File

@@ -35,8 +35,8 @@ function LoginPage() {
return (
<div className="login-page">
<div className="login-container">
<h1 className="login-title">Realtime Collab</h1>
<p className="login-subtitle">Collaborate in real-time with your team</p>
<h1 className="login-title">DocNest</h1>
<p className="login-subtitle">Collaborate in real time with your team</p>
<div className="login-buttons">
<button
@@ -61,7 +61,7 @@ function LoginPage() {
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
Sign in with Google
Continue with Google
</button>
<button
@@ -74,7 +74,7 @@ function LoginPage() {
d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34-.46-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.87 1.52 2.34 1.07 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.92 0-1.11.38-2 1.03-2.71-.1-.25-.45-1.29.1-2.64 0 0 .84-.27 2.75 1.02.79-.22 1.65-.33 2.5-.33.85 0 1.71.11 2.5.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.35.2 2.39.1 2.64.65.71 1.03 1.6 1.03 2.71 0 3.82-2.34 4.66-4.57 4.91.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2z"
/>
</svg>
Sign in with GitHub
Continue with GitHub
</button>
</div>
</div>

View File

@@ -11,38 +11,50 @@ export default {
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: { DEFAULT: '#8B4FB9', foreground: '#FFFFFF' },
secondary: { DEFAULT: '#00D9FF', foreground: '#2B1B38' },
accent: { DEFAULT: '#FF6EC7', foreground: '#FFFFFF' },
muted: { DEFAULT: '#F5F0FF', foreground: '#4A3B5C' },
primary: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))' },
secondary: { DEFAULT: 'hsl(var(--secondary))', foreground: 'hsl(var(--secondary-foreground))' },
accent: { DEFAULT: 'hsl(var(--accent))', foreground: 'hsl(var(--accent-foreground))' },
muted: { DEFAULT: 'hsl(var(--muted))', foreground: 'hsl(var(--muted-foreground))' },
surface: {
DEFAULT: 'hsl(var(--surface))',
muted: 'hsl(var(--surface-muted))',
},
brand: {
DEFAULT: 'hsl(var(--brand))',
dark: 'hsl(var(--brand-dark))',
teal: 'hsl(var(--brand-teal))',
tealDark: 'hsl(var(--brand-teal-dark))',
},
text: {
primary: 'hsl(var(--text-primary))',
secondary: 'hsl(var(--text-secondary))',
muted: 'hsl(var(--text-muted))',
},
// Custom pixel palette
// Legacy pixel palette aliases (mapped to modern tokens)
pixel: {
purple: { deep: '#4A1B6F', bright: '#8B4FB9' },
cyan: { bright: '#00D9FF' },
pink: { vibrant: '#FF6EC7' },
orange: { warm: '#FF8E3C' },
yellow: { gold: '#FFD23F' },
green: { lime: '#8EF048', forest: '#3FA54D' },
bg: { dark: '#2B1B38', medium: '#4A3B5C', light: '#E8D9F3' },
panel: '#F5F0FF',
purple: { deep: '#1E40AF', bright: '#2563EB' },
cyan: { bright: '#14B8A6' },
pink: { vibrant: '#38BDF8' },
orange: { warm: '#F59E0B' },
yellow: { gold: '#FBBF24' },
green: { lime: '#22C55E', forest: '#16A34A' },
bg: { dark: '#0F172A', medium: '#1E293B', light: '#F5F7FB' },
panel: '#FFFFFF',
white: '#FFFFFF',
shadow: { dark: '#1A0E28' },
outline: '#2B1B38',
text: { primary: '#2B1B38', secondary: '#4A3B5C', muted: '#8B7B9C' },
shadow: { dark: 'rgba(15, 23, 42, 0.2)' },
outline: '#E2E8F0',
text: { primary: '#0F172A', secondary: '#334155', muted: '#64748B' },
},
},
fontFamily: {
pixel: ['"Press Start 2P"', 'cursive'],
sans: ['-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', 'sans-serif'],
display: ['"Manrope"', 'Inter', 'sans-serif'],
sans: ['"Inter"', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', 'sans-serif'],
},
boxShadow: {
'pixel-sm': '3px 3px 0 #1A0E28',
'pixel': '4px 4px 0 #1A0E28, 4px 4px 0 3px #2B1B38',
'pixel-md': '6px 6px 0 #1A0E28, 6px 6px 0 3px #2B1B38',
'pixel-lg': '8px 8px 0 #1A0E28, 8px 8px 0 3px #2B1B38',
'pixel-hover': '5px 5px 0 #1A0E28',
'pixel-active': '2px 2px 0 #1A0E28',
'soft': 'var(--shadow-sm)',
'card': 'var(--shadow-md)',
'float': 'var(--shadow-lg)',
},
keyframes: {
'pixel-float': {
@@ -61,10 +73,10 @@ export default {
'pixel-bounce': 'pixel-bounce 1s ease-in-out infinite',
},
borderRadius: {
DEFAULT: '0',
lg: '0',
md: '0',
sm: '0',
DEFAULT: 'var(--radius-md)',
lg: 'var(--radius-lg)',
md: 'var(--radius-md)',
sm: 'var(--radius-sm)',
},
},
},