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} onClick={onClick}
disabled={disabled} disabled={disabled}
className=" className="
bg-pixel-purple-bright bg-brand
hover:bg-pixel-purple-deep hover:bg-brand-dark
text-white text-white
border-[3px] border
border-pixel-outline border-border
shadow-pixel shadow-card
hover:shadow-pixel-hover hover:shadow-float
hover:-translate-y-0.5 hover:-translate-y-0.5
hover:-translate-x-0.5
active:translate-y-0.5 active:translate-y-0.5
active:translate-x-0.5 active:shadow-soft
active:shadow-pixel-active
transition-all transition-all
duration-75 duration-150
font-sans font-sans
font-semibold font-semibold
px-6 px-6

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { shareApi } from '../../api/share'; import { shareApi } from '../../api/share';
import type { DocumentShareWithUser, ShareLink } from '../../types/share'; import type { DocumentShareWithUser, ShareLink } from '../../types/share';
import PixelIcon from '../PixelIcon/PixelIcon';
import './ShareModal.css'; import './ShareModal.css';
interface ShareModalProps { interface ShareModalProps {
@@ -152,20 +153,8 @@ function ShareModal({ documentId, onClose, currentPermission, currentRole }: Sha
</div> </div>
{currentRole && ( {currentRole && (
<div <div className={`role-banner ${currentRole}`}>
style={{ <span className="role-icon">
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' }}>
{currentRole === 'owner' ? '👑' : currentPermission === 'edit' ? '✏️' : '👁️'} {currentRole === 'owner' ? '👑' : currentPermission === 'edit' ? '✏️' : '👁️'}
</span> </span>
<span> <span>
@@ -222,7 +211,10 @@ function ShareModal({ documentId, onClose, currentPermission, currentRole }: Sha
<div className="shares-list"> <div className="shares-list">
<h3>People with access</h3> <h3>People with access</h3>
{shares.length === 0 ? ( {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) => ( shares.map((share) => (
<div key={share.id} className="share-item"> <div key={share.id} className="share-item">

View File

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

View File

@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { versionsApi, type DocumentVersion } from '../../api/document'; import { versionsApi, type DocumentVersion } from '../../api/document';
import * as Y from 'yjs'; import * as Y from 'yjs';
import PixelIcon from '../PixelIcon/PixelIcon';
import './VersionHistoryPanel.css'; import './VersionHistoryPanel.css';
interface VersionHistoryPanelProps { interface VersionHistoryPanelProps {
@@ -180,6 +181,7 @@ function VersionHistoryPanel({ documentId, ydoc, canEdit, onClose }: VersionHist
<div className="loading-state">Loading versions...</div> <div className="loading-state">Loading versions...</div>
) : versions.length === 0 ? ( ) : versions.length === 0 ? (
<div className="empty-state"> <div className="empty-state">
<PixelIcon name="gem" size={18} color="hsl(var(--brand-teal))" />
<p>No versions yet</p> <p>No versions yet</p>
<small>Versions are created when you save manually or automatically over time</small> <small>Versions are created when you save manually or automatically over time</small>
</div> </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 base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root { :root {
--background: 232 217 243; --background: 220 33% 98%;
--foreground: 43 27 56; --foreground: 222 47% 11%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 43 27 56; --card-foreground: 222 47% 11%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 43 27 56; --popover-foreground: 222 47% 11%;
--primary: 277 42% 52%; --primary: 214 89% 52%;
--primary-foreground: 0 0% 100%; --primary-foreground: 0 0% 100%;
--secondary: 190 100% 50%; --secondary: 173 80% 40%;
--secondary-foreground: 43 27 56; --secondary-foreground: 0 0% 100%;
--muted: 276 100% 97%; --muted: 220 16% 96%;
--muted-foreground: 240 13% 40%; --muted-foreground: 215 16% 47%;
--accent: 325 100% 71%; --accent: 188 92% 42%;
--accent-foreground: 0 0% 100%; --accent-foreground: 0 0% 100%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 100%; --destructive-foreground: 0 0% 100%;
--border: 43 27 56; --border: 215 20% 90%;
--input: 43 27 56; --input: 215 20% 90%;
--ring: 190 100% 50%; --ring: 214 89% 52%;
--radius: 0rem; --radius: 0.75rem;
} }
} }
@@ -34,41 +36,62 @@
} }
:root { :root {
/* Vibrant Fantasy Color Palette */ /* Modern SaaS Design Tokens */
--pixel-purple-deep: #4A1B6F; --surface: 0 0% 100%;
--pixel-purple-bright: #8B4FB9; --surface-muted: 220 16% 96%;
--pixel-pink-vibrant: #FF6EC7; --text-primary: 222 47% 11%;
--pixel-cyan-bright: #00D9FF; --text-secondary: 215 25% 27%;
--pixel-orange-warm: #FF8E3C; --text-muted: 215 16% 47%;
--pixel-yellow-gold: #FFD23F; --brand: 214 89% 52%;
--pixel-green-lime: #8EF048; --brand-dark: 221 83% 45%;
--pixel-green-forest: #3FA54D; --brand-teal: 173 80% 40%;
--brand-teal-dark: 173 80% 32%;
/* UI Backgrounds & Neutrals */ --radius-sm: 8px;
--pixel-bg-dark: #2B1B38; --radius-md: 12px;
--pixel-bg-medium: #4A3B5C; --radius-lg: 16px;
--pixel-bg-light: #E8D9F3; --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08);
--pixel-panel: #F5F0FF; --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; --pixel-white: #FFFFFF;
/* Shadows & Outlines */ --pixel-shadow-dark: rgba(15, 23, 42, 0.2);
--pixel-shadow-dark: #1A0E28; --pixel-outline: #E2E8F0;
--pixel-outline: #2B1B38;
/* Text Colors */ --pixel-text-primary: #0F172A;
--pixel-text-primary: #2B1B38; --pixel-text-secondary: #334155;
--pixel-text-secondary: #4A3B5C; --pixel-text-muted: #64748B;
--pixel-text-muted: #8B7B9C;
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
background: var(--pixel-bg-light); background: var(--gradient-hero);
background-image: url('/pixel-patterns.svg#pixel-checker-subtle'); color: hsl(var(--foreground));
}
h1, h2, h3, h4, h5 {
font-family: 'Manrope', 'Inter', sans-serif;
} }
#root { #root {
@@ -77,54 +100,47 @@
/* Pixel Art Utility Classes */ /* Pixel Art Utility Classes */
.pixel-border { .pixel-border {
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
box-shadow: border-radius: var(--radius-md);
4px 4px 0 var(--pixel-shadow-dark), box-shadow: var(--shadow-sm);
4px 4px 0 3px var(--pixel-outline);
} }
.pixel-card { .pixel-card {
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
box-shadow: border-radius: var(--radius-lg);
0 0 0 3px var(--pixel-outline), box-shadow: var(--shadow-md);
6px 6px 0 var(--pixel-shadow-dark), background: hsl(var(--surface));
6px 6px 0 3px var(--pixel-outline); transition: transform 0.12s ease, box-shadow 0.12s ease;
transition: transform 0.1s ease, box-shadow 0.1s ease;
} }
.pixel-card:hover { .pixel-card:hover {
transform: translate(-2px, -2px); transform: translateY(-2px);
box-shadow: box-shadow: var(--shadow-lg);
0 0 0 3px var(--pixel-outline),
8px 8px 0 var(--pixel-shadow-dark),
8px 8px 0 3px var(--pixel-outline);
} }
.pixel-button { .pixel-button {
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
box-shadow: border-radius: var(--radius-md);
4px 4px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
transition: transform 0.05s ease, box-shadow 0.05s ease; transition: transform 0.08s ease, box-shadow 0.08s ease;
cursor: pointer; cursor: pointer;
} }
.pixel-button:hover { .pixel-button:hover {
transform: translate(-1px, -1px); transform: translateY(-1px);
box-shadow: box-shadow: var(--shadow-md);
5px 5px 0 var(--pixel-shadow-dark);
} }
.pixel-button:active { .pixel-button:active {
transform: translate(2px, 2px); transform: translateY(0);
box-shadow: box-shadow: var(--shadow-sm);
2px 2px 0 var(--pixel-shadow-dark);
} }
/* Focus states for accessibility */ /* Focus states for accessibility */
button:focus-visible, button:focus-visible,
input:focus-visible { input:focus-visible {
outline: 3px solid var(--pixel-yellow-gold); outline: none;
outline-offset: 2px; box-shadow: var(--focus-ring);
} }
/* Reduced motion support */ /* Reduced motion support */
@@ -196,29 +212,29 @@
.create-buttons button { .create-buttons button {
padding: 0.75rem 1.5rem; padding: 0.75rem 1.5rem;
background: var(--pixel-purple-bright); background: hsl(var(--primary));
color: white; color: hsl(var(--primary-foreground));
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
border-radius: 0; border-radius: var(--radius-md);
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
box-shadow: 4px 4px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
transition: transform 0.05s ease, box-shadow 0.05s ease; transition: transform 0.1s ease, box-shadow 0.1s ease, background 0.1s ease;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.create-buttons button:hover { .create-buttons button:hover {
background: var(--pixel-purple-deep); background: hsl(var(--primary) / 0.9);
transform: translate(-1px, -1px); transform: translateY(-1px);
box-shadow: 5px 5px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-md);
} }
.create-buttons button:active { .create-buttons button:active {
transform: translate(2px, 2px); transform: translateY(0);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
} }
.create-buttons button:disabled { .create-buttons button:disabled {
@@ -234,21 +250,17 @@
} }
.document-card { .document-card {
background: var(--pixel-white); background: hsl(var(--surface));
padding: 1.5rem; padding: 1.5rem;
border-radius: 0; border-radius: var(--radius-lg);
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
box-shadow: box-shadow: var(--shadow-md);
6px 6px 0 var(--pixel-shadow-dark), transition: transform 0.12s ease, box-shadow 0.12s ease;
6px 6px 0 3px var(--pixel-outline);
transition: transform 0.1s ease, box-shadow 0.1s ease;
} }
.document-card:hover { .document-card:hover {
transform: translate(-2px, -2px); transform: translateY(-2px);
box-shadow: box-shadow: var(--shadow-lg);
8px 8px 0 var(--pixel-shadow-dark),
8px 8px 0 3px var(--pixel-outline);
} }
.doc-info h3 { .doc-info h3 {
@@ -256,13 +268,13 @@
} }
.doc-type { .doc-type {
color: var(--pixel-text-secondary); color: hsl(var(--text-secondary));
text-transform: capitalize; text-transform: capitalize;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.doc-date { .doc-date {
color: var(--pixel-text-muted); color: hsl(var(--text-muted));
font-size: 0.875rem; font-size: 0.875rem;
} }
@@ -274,37 +286,37 @@
.doc-actions button { .doc-actions button {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
background: var(--pixel-white); background: hsl(var(--surface));
border-radius: 0; border-radius: var(--radius-sm);
cursor: pointer; cursor: pointer;
min-width: 44px; min-width: 44px;
min-height: 44px; min-height: 44px;
box-shadow: 3px 3px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
transition: transform 0.05s ease, box-shadow 0.05s ease; transition: transform 0.1s ease, box-shadow 0.1s ease;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.doc-actions button:hover { .doc-actions button:hover {
transform: translate(-1px, -1px); transform: translateY(-1px);
box-shadow: 4px 4px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-md);
} }
.doc-actions button:active { .doc-actions button:active {
transform: translate(1px, 1px); transform: translateY(0);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
} }
.doc-actions button:first-child { .doc-actions button:first-child {
background: var(--pixel-cyan-bright); background: hsl(var(--secondary));
color: white; color: hsl(var(--secondary-foreground));
border-color: var(--pixel-outline); border-color: hsl(var(--border));
} }
.doc-actions button:first-child:hover { .doc-actions button:first-child:hover {
background: var(--pixel-purple-bright); background: hsl(var(--primary));
} }
/* Editor Page */ /* Editor Page */
@@ -312,6 +324,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; height: 100vh;
background: hsl(var(--background));
} }
.page-header { .page-header {
@@ -319,38 +332,104 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 1rem; padding: 1rem;
background: var(--pixel-white); background: hsl(var(--surface));
border-bottom: 3px solid var(--pixel-outline); border-bottom: 1px solid hsl(var(--border));
box-shadow: var(--shadow-sm);
} }
.page-header button { .page-header button {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
background: var(--pixel-panel); background: hsl(var(--surface));
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
border-radius: 0; border-radius: var(--radius-sm);
cursor: pointer; cursor: pointer;
min-width: 44px; min-width: 44px;
min-height: 44px; min-height: 44px;
box-shadow: 3px 3px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
transition: transform 0.05s ease, box-shadow 0.05s ease; transition: transform 0.1s ease, box-shadow 0.1s ease, background 0.1s ease;
} }
.page-header button:hover { .page-header button:hover {
background: var(--pixel-bg-light); background: hsl(var(--surface-muted));
transform: translate(-1px, -1px); transform: translateY(-1px);
box-shadow: 4px 4px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-md);
} }
.page-header button:active { .page-header button:active {
transform: translate(1px, 1px); transform: translateY(0);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
} }
.sync-status { .sync-status {
color: var(--pixel-text-secondary); display: inline-flex;
align-items: center;
gap: 8px;
color: hsl(var(--text-secondary));
font-size: 0.875rem; 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 { .page-content {
display: flex; display: flex;
flex-direction: column; /* Mobile: stack vertically */ flex-direction: column; /* Mobile: stack vertically */
@@ -384,9 +463,8 @@
.sidebar { .sidebar {
width: 100%; /* Mobile: full width */ width: 100%; /* Mobile: full width */
background: var(--pixel-white); background: hsl(var(--surface));
background-image: url('/pixel-patterns.svg#pixel-scanlines'); border-top: 1px solid hsl(var(--border)); /* Mobile: top border */
border-top: 3px solid var(--pixel-outline); /* Mobile: top border */
border-left: none; border-left: none;
padding: 1rem; padding: 1rem;
max-height: 200px; /* Mobile: limit height */ max-height: 200px; /* Mobile: limit height */
@@ -398,18 +476,16 @@
width: 250px; /* Desktop: fixed width */ width: 250px; /* Desktop: fixed width */
max-height: none; max-height: none;
border-top: none; border-top: none;
border-left: 3px solid var(--pixel-outline); border-left: 1px solid hsl(var(--border));
} }
} }
/* Editor */ /* Editor */
.editor-container { .editor-container {
background: var(--pixel-white); background: hsl(var(--surface));
border-radius: 0; border-radius: var(--radius-lg);
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
box-shadow: box-shadow: var(--shadow-md);
6px 6px 0 var(--pixel-shadow-dark),
6px 6px 0 3px var(--pixel-outline);
overflow: hidden; overflow: hidden;
} }
@@ -417,41 +493,39 @@
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
padding: 0.75rem; padding: 0.75rem;
background: var(--pixel-panel); background: hsl(var(--surface-muted));
background-image: url('/pixel-patterns.svg#pixel-panel-texture'); border-bottom: 1px solid hsl(var(--border));
border-bottom: 3px solid var(--pixel-outline);
flex-wrap: wrap; flex-wrap: wrap;
} }
.toolbar button { .toolbar button {
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
background: var(--pixel-white); background: hsl(var(--surface));
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
border-radius: 0; border-radius: var(--radius-sm);
cursor: pointer; cursor: pointer;
min-width: 44px; min-width: 44px;
min-height: 44px; min-height: 44px;
box-shadow: 3px 3px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
transition: transform 0.05s ease, box-shadow 0.05s ease; transition: transform 0.1s ease, box-shadow 0.1s ease, background 0.1s ease;
} }
.toolbar button:hover { .toolbar button:hover {
background: var(--pixel-bg-light); background: hsl(var(--surface-muted));
transform: translate(-1px, -1px); transform: translateY(-1px);
box-shadow: 4px 4px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-md);
} }
.toolbar button:active { .toolbar button:active {
transform: translate(1px, 1px); transform: translateY(0);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
} }
.toolbar button.active { .toolbar button.active {
background: var(--pixel-cyan-bright); background: hsl(var(--secondary));
color: white; color: hsl(var(--secondary-foreground));
border-color: var(--pixel-outline); border-color: hsl(var(--border));
box-shadow: 2px 2px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
transform: translate(1px, 1px);
} }
.editor-content { .editor-content {
@@ -496,12 +570,13 @@
/* Collaborative Cursors */ /* Collaborative Cursors */
.collaboration-cursor__caret { .collaboration-cursor__caret {
position: absolute; position: absolute;
border-left: 2px solid; border-left: 3px solid;
border-right: none; border-right: none;
margin-left: -1px; margin-left: -1px;
margin-right: -1px; margin-right: -1px;
pointer-events: none; pointer-events: none;
word-break: normal; word-break: normal;
box-shadow: 1px 0 0 currentColor;
} }
.collaboration-cursor__label { .collaboration-cursor__label {
@@ -515,7 +590,8 @@
user-select: none; user-select: none;
color: #fff; color: #fff;
padding: 2px 6px; padding: 2px 6px;
border-radius: 0; border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.15);
white-space: nowrap; white-space: nowrap;
box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.3); box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.3);
} }
@@ -537,14 +613,14 @@
} }
.kanban-column { .kanban-column {
background: var(--pixel-panel); background: hsl(var(--surface));
background-image: url('/pixel-patterns.svg#pixel-dither-diagonal'); border-radius: var(--radius-lg);
border-radius: 0; border: 1px solid hsl(var(--border));
border: 3px solid var(--pixel-outline);
padding: 1rem; padding: 1rem;
width: 100%; /* Mobile: full width */ width: 100%; /* Mobile: full width */
min-width: unset; min-width: unset;
max-width: unset; max-width: unset;
box-shadow: var(--shadow-sm);
} }
@media (min-width: 640px) { @media (min-width: 640px) {
@@ -574,22 +650,18 @@
} }
.kanban-card { .kanban-card {
background: var(--pixel-white); background: hsl(var(--surface));
padding: 1rem; padding: 1rem;
border-radius: 0; border-radius: var(--radius-md);
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
box-shadow: box-shadow: var(--shadow-sm);
4px 4px 0 var(--pixel-shadow-dark),
4px 4px 0 3px var(--pixel-outline);
cursor: pointer; 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 { .kanban-card:hover {
transform: translate(-1px, -1px); transform: translateY(-1px);
box-shadow: box-shadow: var(--shadow-md);
5px 5px 0 var(--pixel-shadow-dark),
5px 5px 0 3px var(--pixel-outline);
} }
.kanban-card h4 { .kanban-card h4 {
@@ -597,38 +669,38 @@
} }
.kanban-card p { .kanban-card p {
color: var(--pixel-text-secondary); color: hsl(var(--text-secondary));
font-size: 0.875rem; font-size: 0.875rem;
} }
.add-task-btn { .add-task-btn {
padding: 0.75rem; padding: 0.75rem;
background: var(--pixel-white); background: hsl(var(--surface));
border: 3px dashed var(--pixel-outline); border: 1px dashed hsl(var(--border));
border-radius: 0; border-radius: var(--radius-md);
cursor: pointer; cursor: pointer;
color: var(--pixel-text-secondary); color: hsl(var(--text-secondary));
} }
.add-task-btn:hover { .add-task-btn:hover {
border-color: var(--pixel-purple-bright); border-color: hsl(var(--primary));
color: var(--pixel-text-primary); color: hsl(var(--text-primary));
background: var(--pixel-bg-light); background: hsl(var(--surface-muted));
} }
.add-task-form input { .add-task-form input {
width: 100%; width: 100%;
padding: 0.75rem; padding: 0.75rem;
border: 2px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
border-radius: 0; border-radius: var(--radius-sm);
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
background: var(--pixel-white); background: hsl(var(--surface));
} }
.add-task-form input:focus { .add-task-form input:focus {
outline: none; outline: none;
border-color: var(--pixel-cyan-bright); border-color: hsl(var(--primary));
box-shadow: 0 0 0 2px var(--pixel-cyan-bright); box-shadow: var(--focus-ring);
} }
.form-actions { .form-actions {
@@ -638,28 +710,28 @@
.form-actions button { .form-actions button {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border: 3px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
border-radius: 0; border-radius: var(--radius-sm);
cursor: pointer; cursor: pointer;
background: var(--pixel-white); background: hsl(var(--surface));
box-shadow: 3px 3px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
transition: transform 0.05s ease, box-shadow 0.05s ease; transition: transform 0.1s ease, box-shadow 0.1s ease, background 0.1s ease;
} }
.form-actions button:hover { .form-actions button:hover {
transform: translate(-1px, -1px); transform: translateY(-1px);
box-shadow: 4px 4px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-md);
} }
.form-actions button:active { .form-actions button:active {
transform: translate(1px, 1px); transform: translateY(0);
box-shadow: 2px 2px 0 var(--pixel-shadow-dark); box-shadow: var(--shadow-sm);
} }
.form-actions button:first-child { .form-actions button:first-child {
background: var(--pixel-green-lime); background: hsl(var(--secondary));
color: var(--pixel-text-primary); color: hsl(var(--secondary-foreground));
border-color: var(--pixel-outline); border-color: hsl(var(--border));
} }
/* User List */ /* User List */
@@ -682,8 +754,8 @@
.user-color { .user-color {
width: 12px; width: 12px;
height: 12px; height: 12px;
border-radius: 0; border-radius: 999px;
border: 1px solid var(--pixel-outline); border: 1px solid hsl(var(--border));
} }
.user-name { .user-name {
@@ -696,5 +768,5 @@
align-items: center; align-items: center;
height: 100vh; height: 100vh;
font-size: 1.25rem; 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 ShareModal from "../components/Share/ShareModal.tsx";
import VersionHistoryPanel from "../components/VersionHistory/VersionHistoryPanel.tsx"; import VersionHistoryPanel from "../components/VersionHistory/VersionHistoryPanel.tsx";
import { useYjsDocument } from "../hooks/useYjsDocument.ts"; import { useYjsDocument } from "../hooks/useYjsDocument.ts";
import { Eye } from "lucide-react";
const EditorPage = () => { const EditorPage = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
@@ -27,14 +28,11 @@ const EditorPage = () => {
<button onClick={() => navigate("/")}> Back to Home</button> <button onClick={() => navigate("/")}> Back to Home</button>
<div className="header-actions"> <div className="header-actions">
{permission !== "edit" && permission !== null && ( {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' }}> <div className="view-only-badge">
<span>👁</span> <Eye className="view-only-icon" />
<span>View only</span> <span>View only</span>
</div> </div>
)} )}
<div className="sync-status">
{synced ? "✓ Synced" : "⟳ Syncing..."}
</div>
{!shareToken && ( {!shareToken && (
<button className="share-btn" onClick={() => setShowShareModal(true)}> <button className="share-btn" onClick={() => setShowShareModal(true)}>
Share Share

View File

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

View File

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

View File

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

View File

@@ -16,43 +16,93 @@ function LandingPage() {
<div className="landing-page"> <div className="landing-page">
{/* Hero Section */} {/* Hero Section */}
<section className="landing-hero"> <section className="landing-hero">
<FloatingGem position={{ top: '10%', left: '8%' }} delay={0} size={44} /> <div className="hero-gem hero-gem-one">
<FloatingGem position={{ top: '15%', right: '12%' }} delay={1.5} size={36} /> <FloatingGem position={{ top: '12%', left: '8%' }} delay={0} size={28} />
<FloatingGem position={{ top: '45%', left: '5%' }} delay={2.5} size={28} /> </div>
<FloatingGem position={{ bottom: '25%', right: '8%' }} delay={3.5} size={40} /> <div className="hero-gem hero-gem-two">
<FloatingGem position={{ bottom: '15%', left: '15%' }} delay={4} size={32} /> <FloatingGem position={{ bottom: '18%', right: '10%' }} delay={1.5} size={24} />
<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> </div>
<div className="hero-scroll-hint"> <div className="hero-grid">
<PixelIcon name="back-arrow" size={24} style={{ transform: 'rotate(-90deg)' }} /> <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> </div>
</section> </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 */} {/* Features Section */}
<section className="landing-features"> <section className="landing-features">
<h2 className="section-title">Why DocNest?</h2> <h2 className="section-title">Why DocNest?</h2>
@@ -61,42 +111,44 @@ function LandingPage() {
icon="sync-arrows" icon="sync-arrows"
title="Real-Time Collaboration" title="Real-Time Collaboration"
description="See changes as they happen. Multiple cursors show who's working where." description="See changes as they happen. Multiple cursors show who's working where."
color="var(--pixel-cyan-bright)" color="hsl(var(--brand-teal))"
/> />
<FeatureCard <FeatureCard
icon="document" icon="document"
title="Rich Documents" title="Rich Documents"
description="Create formatted documents with headings, lists, and more." description="Create formatted documents with headings, lists, and more."
color="var(--pixel-purple-bright)" color="hsl(var(--brand))"
/> />
<FeatureCard <FeatureCard
icon="kanban" icon="kanban"
title="Kanban Boards" title="Kanban Boards"
description="Drag-and-drop task management with real-time updates." description="Drag-and-drop task management with real-time updates."
color="var(--pixel-orange-warm)" color="hsl(var(--accent))"
/> />
<FeatureCard <FeatureCard
icon="shield" icon="shield"
title="Offline Support" title="Offline Support"
description="Your work syncs automatically when you're back online." description="Your work syncs automatically when you're back online."
color="var(--pixel-green-lime)" color="hsl(var(--secondary))"
/> />
</div> </div>
</section> </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 CTA */}
<footer className="landing-footer"> <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"> <div className="footer-content">
<h3 className="footer-headline">Ready to collaborate?</h3> <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"> <div className="footer-login-buttons">
<button className="landing-login-button google large" onClick={handleGoogleLogin}> <button className="landing-login-button primary large" onClick={handleGoogleLogin}>
<GoogleIcon /> Get started free
<span>Get Started Free</span>
</button> </button>
</div> </div>

View File

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

View File

@@ -35,8 +35,8 @@ function LoginPage() {
return ( return (
<div className="login-page"> <div className="login-page">
<div className="login-container"> <div className="login-container">
<h1 className="login-title">Realtime Collab</h1> <h1 className="login-title">DocNest</h1>
<p className="login-subtitle">Collaborate in real-time with your team</p> <p className="login-subtitle">Collaborate in real time with your team</p>
<div className="login-buttons"> <div className="login-buttons">
<button <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" 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> </svg>
Sign in with Google Continue with Google
</button> </button>
<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" 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> </svg>
Sign in with GitHub Continue with GitHub
</button> </button>
</div> </div>
</div> </div>

View File

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