diff --git a/app/app.css b/app/app.css index 7f0cd29..8e6b96d 100644 --- a/app/app.css +++ b/app/app.css @@ -208,3 +208,22 @@ span.ͼ5 { color: var(--foreground, #111827); } /* markdown syntax chars */ details > summary { color: #959595; } .dark details > summary { color: #6b7280; } + +/* ── Typeahead ───────────────────────────────────────────────────────────────*/ + +@keyframes typeahead-in { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } +} +.typeahead-item { + opacity: 0; + animation: typeahead-in 0.2s ease forwards; +} + +@keyframes typeahead-dot { + 0%, 100% { opacity: 0.3; transform: translateY(0); } + 50% { opacity: 1; transform: translateY(-2px); } +} +.typeahead-dot { + animation: typeahead-dot 1s ease infinite; +} diff --git a/app/routes/home.tsx b/app/routes/home.tsx index 8087ca7..f854779 100644 --- a/app/routes/home.tsx +++ b/app/routes/home.tsx @@ -65,6 +65,7 @@ function LoginScreen() { const [suggestions, setSuggestions] = useState([]) const [showSuggestions, setShowSuggestions] = useState(false) const [activeIndex, setActiveIndex] = useState(-1) + const [fetching, setFetching] = useState(false) const debounceRef = useRef | null>(null) useEffect(() => () => { if (debounceRef.current) clearTimeout(debounceRef.current) }, []) @@ -91,8 +92,10 @@ function LoginScreen() { if (!q || q.startsWith('did:')) { setSuggestions([]) setShowSuggestions(false) + setFetching(false) return } + setFetching(true) debounceRef.current = setTimeout(async () => { try { const res = await fetch( @@ -106,6 +109,8 @@ function LoginScreen() { setShowSuggestions(actors.length > 0) } catch { // fail silently — typeahead is non-critical + } finally { + setFetching(false) } }, 300) } @@ -180,12 +185,25 @@ function LoginScreen() { {/* Form */}
- +
+ + {fetching && ( + + {[0, 1, 2].map((j) => ( + + ))} + + )} +
0 && (
    {suggestions.map((actor, i) => (
  • selectSuggestion(actor)} - className={`flex items-center gap-3 px-3 py-2.5 cursor-pointer transition-colors ${ + className={`typeahead-item flex items-center gap-3 px-4 py-3 cursor-pointer border-l-[3px] transition-colors duration-150 ${ i === activeIndex - ? 'bg-[#F0EDE8] dark:bg-[#2E2925]' - : 'hover:bg-[#F5F3F0] dark:hover:bg-[#241F1C]' + ? 'bg-[#F5F1EC] dark:bg-[#231F1C] border-l-[#1A1614] dark:border-l-white' + : 'border-l-transparent hover:bg-[#FAF8F5] dark:hover:bg-[#201D1A] hover:border-l-[#C4BAB0] dark:hover:border-l-[#57504A]' }`} + style={{ animationDelay: `${i * 40}ms` }} > - {actor.avatar ? ( - - ) : ( -
    - )} -
    - {actor.displayName && ( -

    {actor.displayName}

    +
    + {actor.avatar ? ( + + ) : ( +
    + λ +
    + )} +
    +
    + {actor.displayName ? ( + <> +

    {actor.displayName}

    +

    @{actor.handle}

    + + ) : ( +

    @{actor.handle}

    )} -

    @{actor.handle}

  • ))}