Add desk lock mode, web bookmark card type, sound default, and buried visual effects

This commit is contained in:
airent106 committed 2026-10-13 09:53:26 -06:00
1 parent 15fbe1db88
commit 19eb968f86
7 files changed
+493 -187

No files matched your search

+132 -16
View File
@@ -2,12 +2,13 @@ import React, { useState } from 'react';
import { CardItem } from '../types';
import { getShadowStyle } from '../utils/lighting';
import { Fastener, Stamp, PhotoCorners } from './Fastener';
import { Pencil } from 'lucide-react';
import { Pencil, Globe, ExternalLink } from 'lucide-react';
interface PaperCardProps {
card: CardItem;
lightAngle: number;
isDragging: boolean;
isLocked?: boolean;
onPointerDown: (e: React.PointerEvent, card: CardItem) => void;
onDoubleClick: (card: CardItem) => void;
onToggleTodo?: (cardId: string, index: number) => void;
@@ -17,6 +18,7 @@ export const PaperCard: React.FC<PaperCardProps> = ({
card,
lightAngle,
isDragging,
isLocked = false,
onPointerDown,
onDoubleClick,
onToggleTodo
@@ -34,9 +36,9 @@ export const PaperCard: React.FC<PaperCardProps> = ({
return (
<div
id={card.id}
className={`absolute cursor-grab active:cursor-grabbing select-none transition-transform will-change-transform ${
isDragging ? 'z-50 scale-[1.03]' : ''
}`}
className={`absolute select-none transition-transform will-change-transform ${
isLocked ? 'cursor-default' : 'cursor-grab active:cursor-grabbing'
} ${isDragging ? 'z-50 scale-[1.03]' : ''}`}
style={{
left: `${card.x}px`,
top: `${card.y}px`,
@@ -48,6 +50,12 @@ export const PaperCard: React.FC<PaperCardProps> = ({
}}
onPointerDown={(e) => onPointerDown(e, card)}
onDoubleClick={() => onDoubleClick(card)}
onClick={(e) => {
if (isLocked && card.type === 'bookmark' && card.url) {
e.stopPropagation();
window.open(card.url.startsWith('http') ? card.url : `https://${card.url}`, '_blank', 'noopener,noreferrer');
}
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
@@ -186,27 +194,36 @@ export const PaperCard: React.FC<PaperCardProps> = ({
{/* 3. KRAFT LUGGAGE / PARCEL TAG */}
{card.type === 'tag' && (
<div
className="p-4 flex flex-col items-center justify-between text-neutral-800 relative [clip-path:polygon(20%_0%,80%_0%,100%_15%,100%_100%,0%_100%,0%_15%)]"
className="p-3.5 pb-4 flex flex-col items-center justify-between text-neutral-800 relative [clip-path:polygon(20%_0%,80%_0%,100%_15%,100%_100%,0%_100%,0%_15%)]"
style={{ backgroundColor: card.color || '#CBB296', minHeight: `${card.height}px` }}
>
{/* Tag string & brass eyelet */}
<div className="w-5 h-5 rounded-full border-2 border-amber-900/60 bg-amber-100/50 shadow-inner mt-1 mb-3 flex items-center justify-center">
<div className="w-5 h-5 rounded-full border-2 border-amber-900/60 bg-amber-100/50 shadow-inner mt-0.5 mb-2 flex items-center justify-center shrink-0">
<div className="w-2.5 h-2.5 rounded-full bg-neutral-900/80 shadow" />
</div>
<div className="text-center w-full my-auto space-y-2">
<div className="text-xs font-mono font-bold tracking-widest text-neutral-700 uppercase">
{card.tagNumber || '#1828'}
</div>
<div className="text-xl font-bold tracking-wider font-courier uppercase text-neutral-900">
{card.subtitle || 'fragile'}
</div>
<div className="text-xs font-mono text-neutral-600">
{card.date || 'Sept 4'}
<div className="text-center w-full my-auto space-y-1 px-1">
<div className="text-[10px] font-mono font-bold tracking-widest text-neutral-700/80 uppercase">
{card.tagNumber || card.subtitle || '#TAG'}
</div>
{card.title && (
<div className={`text-sm font-bold tracking-wide uppercase text-neutral-900 ${card.fontFamily}`}>
{card.title}
</div>
)}
{card.content && (
<div className={`text-xs text-neutral-900/90 leading-snug whitespace-pre-line py-1.5 border-y border-amber-900/20 my-1 ${card.fontFamily}`}>
{card.content}
</div>
)}
{card.date && (
<div className="text-[10px] font-mono text-neutral-700">
{card.date}
</div>
)}
</div>
<div className="w-full text-center border-t border-amber-900/20 pt-2 text-[10px] tracking-widest uppercase font-mono text-neutral-600">
<div className="w-full text-center border-t border-amber-900/20 pt-1.5 text-[9px] tracking-widest uppercase font-mono text-neutral-700 shrink-0">
PRIORITY PARCEL
</div>
</div>
@@ -415,6 +432,105 @@ export const PaperCard: React.FC<PaperCardProps> = ({
</div>
</div>
)}
{/* 9. BOOKMARK / WEB PREVIEW CARD */}
{card.type === 'bookmark' && (
<div className="p-3 bg-[#FAF8F5] text-neutral-900 flex flex-col justify-between h-full rounded border border-neutral-300/80 shadow-xs">
{/* Header: Favicon + Domain + External Link */}
<div className="flex items-center justify-between gap-1.5 pb-1.5 mb-1.5 border-b border-neutral-200/90">
<div className="flex items-center gap-1.5 overflow-hidden">
{card.faviconUrl ? (
<img src={card.faviconUrl} alt="" className="w-3.5 h-3.5 rounded-xs shrink-0" />
) : (
<Globe size={13} className="text-neutral-500 shrink-0" />
)}
<span className="font-mono text-[10px] text-neutral-600 truncate font-semibold">
{card.url ? (() => {
try {
return new URL(card.url.startsWith('http') ? card.url : `https://${card.url}`).hostname.replace('www.', '');
} catch {
return 'link';
}
})() : 'bookmark'}
</span>
</div>
{card.url && (
<a
href={card.url.startsWith('http') ? card.url : `https://${card.url}`}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="p-1 rounded bg-neutral-200/60 hover:bg-amber-400 hover:text-black text-neutral-600 transition shrink-0"
title="Open link in new window"
>
<ExternalLink size={11} />
</a>
)}
</div>
{/* Thumbnail Preview Image */}
<div
className="w-full aspect-[16/10] bg-neutral-200 rounded overflow-hidden relative shadow-inner mb-2 cursor-pointer group"
onClick={(e) => {
if (card.url) {
e.stopPropagation();
window.open(card.url.startsWith('http') ? card.url : `https://${card.url}`, '_blank', 'noopener,noreferrer');
}
}}
>
{card.imageUrl ? (
<img
src={card.imageUrl}
alt={card.title || 'Web preview'}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-200"
loading="lazy"
/>
) : (
<div className="w-full h-full flex flex-col items-center justify-center text-neutral-400 gap-1 bg-neutral-100">
<Globe size={18} className="text-neutral-400" />
<span className="text-[10px] font-mono">Web Snapshot</span>
</div>
)}
{/* Hover visit pill */}
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/15 transition flex items-center justify-center">
<span className="opacity-0 group-hover:opacity-100 bg-black/80 text-white text-[10px] font-mono px-2 py-0.5 rounded shadow transition flex items-center gap-1">
<ExternalLink size={10} /> Visit
</span>
</div>
</div>
{/* Title & Description */}
<div className="flex-1 flex flex-col justify-between">
<div>
<h4
className="font-bold text-xs leading-snug line-clamp-2 text-neutral-900 cursor-pointer hover:text-amber-600 transition"
onClick={(e) => {
if (card.url) {
e.stopPropagation();
window.open(card.url.startsWith('http') ? card.url : `https://${card.url}`, '_blank', 'noopener,noreferrer');
}
}}
>
{card.title || card.url || 'Web Bookmark'}
</h4>
{card.content && (
<p className="text-[11px] text-neutral-600 line-clamp-2 mt-1 font-sans">
{card.content}
</p>
)}
</div>
{card.url && (
<div className="mt-2 pt-1.5 border-t border-neutral-200/80 flex items-center justify-between text-[10px] text-neutral-500 font-mono">
<span className="truncate max-w-[130px]">{card.url}</span>
<span className="text-amber-600 font-bold flex items-center gap-0.5">
↗
</span>
</div>
)}
</div>
</div>
)}
</div>
</div>
);