Add feature shortcuts to header, remove AI branding, reorganize app categories

- Add Open Notebook, Unlock Article, and Transcribe buttons to header
- Move search bar to center of header across all pages
- Rename "AI Notebook" to "Open Notebook" throughout
- Support ?type= query param on /notes/new for deep-linking
- Reorganize AppSwitcher categories (Creating, Sharing, Observing)
- Move rTube and rSwag to Creating category
- Update EcosystemFooter link order to match new categories
- Update header bg to bg-slate-900/85
- Add key emojis to UserMenu sign-in states

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-02-25 13:36:28 -08:00
parent 5cc17e05e7
commit cce178671b
10 changed files with 86 additions and 48 deletions

View File

@ -208,7 +208,7 @@ export default function NotebookDetailPage() {
: 'text-slate-400 hover:text-white'
}`}
>
AI Notebook
Open Notebook
</button>
</div>

View File

@ -3,7 +3,6 @@
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { NotebookCard } from '@/components/NotebookCard';
import { SearchBar } from '@/components/SearchBar';
import { Header } from '@/components/Header';
interface NotebookData {
@ -32,25 +31,15 @@ export default function NotebooksPage() {
<Header
breadcrumbs={[{ label: 'Notebooks' }]}
actions={
<>
<div className="hidden md:block w-64">
<SearchBar />
</div>
<Link
href="/notebooks/new"
className="px-3 md:px-4 py-2 bg-amber-500 hover:bg-amber-400 text-black text-sm font-medium rounded-lg transition-colors"
>
<span className="hidden sm:inline">New Notebook</span>
<svg className="w-4 h-4 sm:hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>
</Link>
</>
<Link
href="/notebooks/new"
className="px-3 md:px-4 py-2 bg-amber-500 hover:bg-amber-400 text-black text-sm font-medium rounded-lg transition-colors"
>
<span className="hidden sm:inline">New Notebook</span>
<svg className="w-4 h-4 sm:hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>
</Link>
}
/>
{/* Mobile search */}
<div className="md:hidden px-4 py-3 border-b border-slate-800">
<SearchBar />
</div>
<main className="max-w-6xl mx-auto px-4 md:px-6 py-6 md:py-8">
{loading ? (
<div className="flex items-center justify-center py-20">

View File

@ -42,11 +42,14 @@ function NewNoteForm() {
const router = useRouter();
const searchParams = useSearchParams();
const preselectedNotebook = searchParams.get('notebookId');
const preselectedType = searchParams.get('type');
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [bodyJson, setBodyJson] = useState<object | null>(null);
const [type, setType] = useState('NOTE');
const [type, setType] = useState(
NOTE_TYPES.some((t) => t.value === preselectedType) ? preselectedType! : 'NOTE'
);
const [url, setUrl] = useState('');
const [language, setLanguage] = useState('');
const [tags, setTags] = useState('');

View File

@ -3,7 +3,6 @@
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { NotebookCard } from '@/components/NotebookCard';
import { SearchBar } from '@/components/SearchBar';
import { EcosystemFooter } from '@/components/EcosystemFooter';
import { TranscriptionDemo } from '@/components/TranscriptionDemo';
@ -30,11 +29,6 @@ export default function HomePage() {
return (
<div className="min-h-screen bg-[#0a0a0a]">
{/* Mobile search */}
<div className="md:hidden px-4 py-3 border-b border-slate-800">
<SearchBar />
</div>
{/* Hero */}
<section className="py-12 md:py-20 px-4 md:px-6">
<div className="max-w-4xl mx-auto text-center">

View File

@ -713,7 +713,7 @@ export default function VoicePage() {
return (
<div className="min-h-screen bg-[#0a0a0a] flex flex-col">
{/* Header */}
<header className="border-b border-slate-800 backdrop-blur-sm bg-[#0a0a0a]/90 sticky top-0 z-50">
<header className="border-b border-slate-800 backdrop-blur-sm bg-slate-900/85 sticky top-0 z-50">
<div className="px-4 py-3 flex items-center justify-between">
<div className="flex items-center gap-1">
<AppSwitcher current="notes" />

View File

@ -17,6 +17,8 @@ const MODULES: AppModule[] = [
{ id: 'space', name: 'rSpace', badge: 'rS', color: 'bg-teal-300', emoji: '🎨', description: 'Real-time collaborative canvas', domain: 'rspace.online' },
{ id: 'notes', name: 'rNotes', badge: 'rN', color: 'bg-amber-300', emoji: '📝', description: 'Group note-taking & knowledge capture', domain: 'rnotes.online' },
{ id: 'pubs', name: 'rPubs', badge: 'rP', color: 'bg-rose-300', emoji: '📖', description: 'Collaborative publishing platform', domain: 'rpubs.online' },
{ id: 'tube', name: 'rTube', badge: 'rTu', color: 'bg-pink-300', emoji: '🎬', description: 'Community video platform', domain: 'rtube.online' },
{ id: 'swag', name: 'rSwag', badge: 'rSw', color: 'bg-red-200', emoji: '👕', description: 'Community merch & swag store', domain: 'rswag.online' },
// Planning
{ id: 'cal', name: 'rCal', badge: 'rC', color: 'bg-sky-300', emoji: '📅', description: 'Collaborative scheduling & events', domain: 'rcal.online' },
{ id: 'trips', name: 'rTrips', badge: 'rT', color: 'bg-emerald-300', emoji: '✈️', description: 'Group travel planning in real time', domain: 'rtrips.online' },
@ -34,13 +36,12 @@ const MODULES: AppModule[] = [
{ id: 'wallet', name: 'rWallet', badge: 'rW', color: 'bg-yellow-300', emoji: '💰', description: 'Multi-chain crypto wallet', domain: 'rwallet.online' },
{ id: 'cart', name: 'rCart', badge: 'rCt', color: 'bg-orange-300', emoji: '🛒', description: 'Group commerce & shared shopping', domain: 'rcart.online' },
{ id: 'auctions', name: 'rAuctions', badge: 'rA', color: 'bg-red-300', emoji: '🔨', description: 'Live auction platform', domain: 'rauctions.online' },
{ id: 'swag', name: 'rSwag', badge: 'rSw', color: 'bg-red-200', emoji: '👕', description: 'Community merch & swag store', domain: 'rswag.online' },
// Social & Media
{ id: 'photos', name: 'rPhotos', badge: 'rPh', color: 'bg-pink-200', emoji: '📸', description: 'Shared community photo albums', domain: 'rphotos.online' },
{ id: 'tube', name: 'rTube', badge: 'rTu', color: 'bg-pink-300', emoji: '🎬', description: 'Group video platform', domain: 'rtube.online' },
// Sharing
{ id: 'photos', name: 'rPhotos', badge: 'rPh', color: 'bg-pink-200', emoji: '📸', description: 'Community photo commons', domain: 'rphotos.online' },
{ id: 'network', name: 'rNetwork', badge: 'rNe', color: 'bg-blue-300', emoji: '🕸️', description: 'Community network & social graph', domain: 'rnetwork.online' },
{ id: 'socials', name: 'rSocials', badge: 'rSo', color: 'bg-sky-200', emoji: '📢', description: 'Social media management', domain: 'rsocials.online' },
{ id: 'files', name: 'rFiles', badge: 'rFi', color: 'bg-cyan-300', emoji: '📁', description: 'Collaborative file storage', domain: 'rfiles.online' },
{ id: 'socials', name: 'rSocials', badge: 'rSo', color: 'bg-sky-200', emoji: '📢', description: 'Social media management', domain: 'rsocials.online' },
// Observing
{ id: 'data', name: 'rData', badge: 'rD', color: 'bg-purple-300', emoji: '📊', description: 'Analytics & insights dashboard', domain: 'rdata.online' },
// Work & Productivity
{ id: 'work', name: 'rWork', badge: 'rWo', color: 'bg-slate-300', emoji: '📋', description: 'Project & task management', domain: 'rwork.online' },
@ -53,6 +54,8 @@ const MODULE_CATEGORIES: Record<string, string> = {
space: 'Creating',
notes: 'Creating',
pubs: 'Creating',
tube: 'Creating',
swag: 'Creating',
cal: 'Planning',
trips: 'Planning',
maps: 'Planning',
@ -66,13 +69,11 @@ const MODULE_CATEGORIES: Record<string, string> = {
wallet: 'Funding & Commerce',
cart: 'Funding & Commerce',
auctions: 'Funding & Commerce',
swag: 'Funding & Commerce',
photos: 'Social & Media',
tube: 'Social & Media',
network: 'Social & Media',
socials: 'Social & Media',
files: 'Social & Media',
data: 'Social & Media',
photos: 'Sharing',
network: 'Sharing',
files: 'Sharing',
socials: 'Sharing',
data: 'Observing',
work: 'Work & Productivity',
ids: 'Identity & Infrastructure',
stack: 'Identity & Infrastructure',
@ -84,7 +85,8 @@ const CATEGORY_ORDER = [
'Communicating',
'Deciding',
'Funding & Commerce',
'Social & Media',
'Sharing',
'Observing',
'Work & Productivity',
'Identity & Infrastructure',
];

View File

@ -1,30 +1,39 @@
'use client';
const FOOTER_LINKS = [
// Creating
{ name: 'rSpace', href: 'https://rspace.online' },
{ name: 'rNotes', href: 'https://rnotes.online' },
{ name: 'rPubs', href: 'https://rpubs.online' },
{ name: 'rTube', href: 'https://rtube.online' },
{ name: 'rSwag', href: 'https://rswag.online' },
// Planning
{ name: 'rCal', href: 'https://rcal.online' },
{ name: 'rTrips', href: 'https://rtrips.online' },
{ name: 'rMaps', href: 'https://rmaps.online' },
// Communicating
{ name: 'rChats', href: 'https://rchats.online' },
{ name: 'rInbox', href: 'https://rinbox.online' },
{ name: 'rMail', href: 'https://rmail.online' },
{ name: 'rForum', href: 'https://rforum.online' },
// Deciding
{ name: 'rChoices', href: 'https://rchoices.online' },
{ name: 'rVote', href: 'https://rvote.online' },
// Funding & Commerce
{ name: 'rFunds', href: 'https://rfunds.online' },
{ name: 'rWallet', href: 'https://rwallet.online' },
{ name: 'rCart', href: 'https://rcart.online' },
{ name: 'rAuctions', href: 'https://rauctions.online' },
{ name: 'rSwag', href: 'https://rswag.online' },
// Sharing
{ name: 'rPhotos', href: 'https://rphotos.online' },
{ name: 'rTube', href: 'https://rtube.online' },
{ name: 'rNetwork', href: 'https://rnetwork.online' },
{ name: 'rSocials', href: 'https://rsocials.online' },
{ name: 'rFiles', href: 'https://rfiles.online' },
{ name: 'rSocials', href: 'https://rsocials.online' },
// Observing
{ name: 'rData', href: 'https://rdata.online' },
// Work & Productivity
{ name: 'rWork', href: 'https://rwork.online' },
// Identity & Infrastructure
{ name: 'rIDs', href: 'https://ridentity.online' },
{ name: 'rStack', href: 'https://rstack.online' },
];

View File

@ -1,7 +1,9 @@
'use client';
import Link from 'next/link';
import { AppSwitcher } from './AppSwitcher';
import { SpaceSwitcher } from './SpaceSwitcher';
import { SearchBar } from './SearchBar';
import { UserMenu } from './UserMenu';
export interface BreadcrumbItem {
@ -22,7 +24,7 @@ interface HeaderProps {
export function Header({ current = 'notes', breadcrumbs, actions, maxWidth = 'max-w-6xl' }: HeaderProps) {
return (
<nav className="border-b border-slate-800 backdrop-blur-sm bg-[#0a0a0a]/90 sticky top-0 z-50">
<nav className="border-b border-slate-800 backdrop-blur-sm bg-slate-900/85 sticky top-0 z-50">
<div className={`${maxWidth} mx-auto px-4 md:px-6 py-3 flex items-center justify-between gap-2`}>
{/* Left: App switcher + Space switcher + Breadcrumbs */}
<div className="flex items-center gap-1 min-w-0">
@ -49,6 +51,45 @@ export function Header({ current = 'notes', breadcrumbs, actions, maxWidth = 'ma
)}
</div>
{/* Center: Search */}
<div className="hidden md:block flex-1 max-w-md mx-4">
<SearchBar />
</div>
{/* Feature shortcuts */}
<div className="hidden lg:flex items-center gap-1.5 flex-shrink-0">
<Link
href="/opennotebook"
className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-slate-400 hover:text-amber-400 hover:bg-amber-500/10 rounded-lg transition-colors"
title="Open Notebook"
>
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
Open Notebook
</Link>
<Link
href="/notes/new?type=CLIP"
className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-slate-400 hover:text-green-400 hover:bg-green-500/10 rounded-lg transition-colors"
title="Unlock a paywalled article"
>
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z" />
</svg>
Unlock Article
</Link>
<Link
href="/notes/new?type=AUDIO"
className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-slate-400 hover:text-violet-400 hover:bg-violet-500/10 rounded-lg transition-colors"
title="Record and transcribe a voice note"
>
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
</svg>
Transcribe
</Link>
</div>
{/* Right: Actions + UserMenu */}
<div className="flex items-center gap-2 md:gap-3 flex-shrink-0">
{actions}

View File

@ -19,7 +19,7 @@ export function OpenNotebookEmbed({ className = '' }: OpenNotebookEmbedProps) {
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
<span className="text-sm text-slate-400">Loading AI Notebook...</span>
<span className="text-sm text-slate-400">Loading Notebook...</span>
</div>
</div>
)}
@ -37,7 +37,7 @@ export function OpenNotebookEmbed({ className = '' }: OpenNotebookEmbedProps) {
src={notebookUrl}
className="w-full h-full border-0"
allow="clipboard-write; clipboard-read"
title="AI Notebook — OpenNotebook"
title="OpenNotebook"
onLoad={() => setLoading(false)}
/>
</div>

View File

@ -47,7 +47,7 @@ export function UserMenu() {
href="https://auth.ridentity.online"
className="px-3 py-1.5 text-sm bg-cyan-500 hover:bg-cyan-400 text-black font-medium rounded-lg transition-colors no-underline"
>
Sign In
🔑 Sign In
</a>
);
}
@ -63,7 +63,7 @@ export function UserMenu() {
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-cyan-400 to-violet-500 flex items-center justify-center text-xs font-bold text-white flex-shrink-0">
{(user.username || 'U')[0].toUpperCase()}
</div>
<span className="text-sm text-slate-300 hidden sm:inline">{displayName}</span>
<span className="text-sm text-slate-300 hidden sm:inline">🔐 {displayName}</span>
<span className="text-[0.7em] text-slate-500 hidden sm:inline">&#9662;</span>
</button>