Skip to content

Commit 0e3aa7d

Browse files
hyperb1issclaude
andcommitted
fix: resolve hero orphan text, theme picker layout shift, and editor spacing
Hero headline "made stunning." now renders as a deliberate second line via block span instead of wrapping mid-phrase. Theme selector converted from in-flow accordion to an absolutely-positioned floating overlay with click-outside dismiss, eliminating the 400px+ layout shift. Editor section vertical padding tightened for better visual flow. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ea8d880 commit 0e3aa7d

4 files changed

Lines changed: 147 additions & 121 deletions

File tree

web/src/app/globals.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,17 @@ body {
129129
}
130130
}
131131

132+
@keyframes dropIn {
133+
from {
134+
opacity: 0;
135+
transform: translateY(-8px);
136+
}
137+
to {
138+
opacity: 1;
139+
transform: translateY(0);
140+
}
141+
}
142+
132143
/* ═══════════════════════════════════════════════
133144
Utility Classes
134145
═══════════════════════════════════════════════ */
@@ -137,6 +148,10 @@ body {
137148
animation: neonGlow 2s ease-in-out infinite alternate;
138149
}
139150

151+
.animate-drop-in {
152+
animation: dropIn 0.2s ease-out;
153+
}
154+
140155
.gradient-text {
141156
background: linear-gradient(135deg, var(--sc-purple), var(--sc-pink-soft), var(--sc-cyan));
142157
-webkit-background-clip: text;

web/src/components/editor.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ export function Editor() {
177177
}, []);
178178

179179
return (
180-
<section id="editor" className="mx-auto max-w-7xl px-6 py-20">
181-
<div className="mb-10 text-center">
180+
<section id="editor" className="mx-auto max-w-7xl px-6 py-16">
181+
<div className="mb-8 text-center">
182182
<h2 className="mb-3 text-3xl font-bold tracking-tight md:text-4xl">
183183
<span className="gradient-text">Live Editor</span>
184184
</h2>

web/src/components/hero.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function Hero() {
1616
</div>
1717

1818
<h1 className="mb-6 text-5xl font-extrabold leading-[1.1] tracking-tight md:text-7xl">
19-
Markdown to PDF, <span className="gradient-text-shimmer">made stunning.</span>
19+
Markdown to PDF, <span className="gradient-text-shimmer block">made stunning.</span>
2020
</h1>
2121

2222
<p className="mx-auto mb-10 max-w-2xl text-lg leading-relaxed text-sc-fg-muted md:text-xl">

web/src/components/theme-selector.tsx

Lines changed: 129 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,22 @@ export function ThemeSelector({ activeTheme, onSelect, disabled }: ThemeSelector
521521
return () => window.removeEventListener('keydown', handler);
522522
}, [expanded]);
523523

524+
// Close on click outside
525+
useEffect(() => {
526+
if (!expanded) return;
527+
const handler = (e: MouseEvent) => {
528+
if (panelRef.current && !panelRef.current.contains(e.target as Node)) {
529+
setExpanded(false);
530+
}
531+
};
532+
// Use setTimeout so the opening click doesn't immediately close it
533+
const t = setTimeout(() => window.addEventListener('mousedown', handler), 0);
534+
return () => {
535+
clearTimeout(t);
536+
window.removeEventListener('mousedown', handler);
537+
};
538+
}, [expanded]);
539+
524540
// Filter themes
525541
const filteredThemes = useMemo(() => {
526542
let result = THEMES;
@@ -563,7 +579,7 @@ export function ThemeSelector({ activeTheme, onSelect, disabled }: ThemeSelector
563579
);
564580

565581
return (
566-
<div className="mb-6">
582+
<div className="relative mb-6">
567583
{/* ── Trigger Row ─────────────────────────────────────── */}
568584
<div className="flex items-center justify-center gap-3">
569585
{/* Current theme pill */}
@@ -613,124 +629,105 @@ export function ThemeSelector({ activeTheme, onSelect, disabled }: ThemeSelector
613629
</button>
614630
</div>
615631

616-
{/* ── Expanded Panel ──────────────────────────────────── */}
617-
<div
618-
className="grid transition-[grid-template-rows] duration-300 ease-out"
619-
style={{ gridTemplateRows: expanded ? '1fr' : '0fr' }}
620-
>
621-
<div className="overflow-hidden">
622-
<div ref={panelRef} className="pt-5">
623-
<div className="rounded-2xl bg-sc-bg-dark/80 p-4 ring-1 ring-white/[0.06] backdrop-blur-sm">
624-
{/* Search */}
625-
<div className="relative mb-3">
626-
<svg
627-
aria-hidden="true"
628-
className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-sc-fg-dim"
629-
fill="none"
630-
viewBox="0 0 24 24"
631-
stroke="currentColor"
632-
strokeWidth={2}
633-
>
634-
<path
635-
strokeLinecap="round"
636-
strokeLinejoin="round"
637-
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
638-
/>
639-
</svg>
640-
<input
641-
ref={searchRef}
642-
type="text"
643-
placeholder="Search themes..."
644-
value={search}
645-
onChange={e => setSearch(e.target.value)}
646-
className="w-full rounded-xl bg-sc-bg/80 py-2.5 pl-10 pr-4 text-sm text-sc-fg placeholder:text-sc-fg-dim ring-1 ring-white/[0.06] transition-all focus:outline-none focus:ring-sc-purple/40 focus:shadow-[0_0_20px_rgba(225,53,255,0.08)]"
632+
{/* ── Floating Overlay Panel ────────────────────────── */}
633+
{expanded && (
634+
<div ref={panelRef} className="absolute left-0 right-0 top-full z-40 pt-3 animate-drop-in">
635+
<div className="rounded-2xl bg-sc-bg-dark/95 p-4 shadow-[0_16px_48px_rgba(0,0,0,0.4)] ring-1 ring-white/[0.08] backdrop-blur-xl">
636+
{/* Search */}
637+
<div className="relative mb-3">
638+
<svg
639+
aria-hidden="true"
640+
className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-sc-fg-dim"
641+
fill="none"
642+
viewBox="0 0 24 24"
643+
stroke="currentColor"
644+
strokeWidth={2}
645+
>
646+
<path
647+
strokeLinecap="round"
648+
strokeLinejoin="round"
649+
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
647650
/>
648-
{search && (
649-
<button
650-
type="button"
651-
onClick={() => setSearch('')}
652-
className="absolute right-3 top-1/2 -translate-y-1/2 text-sc-fg-dim hover:text-sc-fg"
653-
>
654-
<svg
655-
aria-hidden="true"
656-
className="h-4 w-4"
657-
fill="none"
658-
viewBox="0 0 24 24"
659-
stroke="currentColor"
660-
strokeWidth={2}
661-
>
662-
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
663-
</svg>
664-
</button>
665-
)}
666-
</div>
667-
668-
{/* Family filter pills */}
669-
<div className="mb-3 flex flex-wrap gap-1.5">
651+
</svg>
652+
<input
653+
ref={searchRef}
654+
type="text"
655+
placeholder="Search themes..."
656+
value={search}
657+
onChange={e => setSearch(e.target.value)}
658+
className="w-full rounded-xl bg-sc-bg/80 py-2.5 pl-10 pr-4 text-sm text-sc-fg placeholder:text-sc-fg-dim ring-1 ring-white/[0.06] transition-all focus:outline-none focus:ring-sc-purple/40 focus:shadow-[0_0_20px_rgba(225,53,255,0.08)]"
659+
/>
660+
{search && (
670661
<button
671662
type="button"
672-
onClick={() => setActiveFamily(null)}
673-
className={`rounded-lg px-2.5 py-1 text-xs font-medium transition-all ${
674-
activeFamily === null
675-
? 'bg-sc-purple/15 text-sc-purple ring-1 ring-sc-purple/30'
676-
: 'text-sc-fg-dim hover:bg-sc-bg-highlight hover:text-sc-fg'
677-
}`}
663+
onClick={() => setSearch('')}
664+
className="absolute right-3 top-1/2 -translate-y-1/2 text-sc-fg-dim hover:text-sc-fg"
678665
>
679-
All ({THEMES.length})
666+
<svg
667+
aria-hidden="true"
668+
className="h-4 w-4"
669+
fill="none"
670+
viewBox="0 0 24 24"
671+
stroke="currentColor"
672+
strokeWidth={2}
673+
>
674+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
675+
</svg>
680676
</button>
681-
{FAMILIES.map(fam => {
682-
const count = THEMES.filter(t => t.family === fam.id).length;
683-
return (
684-
<button
685-
type="button"
686-
key={fam.id}
687-
onClick={() => setActiveFamily(f => (f === fam.id ? null : fam.id))}
688-
className={`rounded-lg px-2.5 py-1 text-xs font-medium transition-all ${
689-
activeFamily === fam.id
690-
? 'bg-sc-purple/15 text-sc-purple ring-1 ring-sc-purple/30'
691-
: 'text-sc-fg-dim hover:bg-sc-bg-highlight hover:text-sc-fg'
692-
}`}
693-
>
694-
{fam.label} ({count})
695-
</button>
696-
);
697-
})}
698-
</div>
677+
)}
678+
</div>
699679

700-
{/* Theme grid */}
701-
<div className="editor-scrollbar max-h-[360px] overflow-y-auto pr-1">
702-
{filteredThemes.length === 0 && (
703-
<div className="py-8 text-center text-sm text-sc-fg-dim">
704-
No themes match &ldquo;{search}&rdquo;
705-
</div>
706-
)}
680+
{/* Family filter pills */}
681+
<div className="mb-3 flex flex-wrap gap-1.5">
682+
<button
683+
type="button"
684+
onClick={() => setActiveFamily(null)}
685+
className={`rounded-lg px-2.5 py-1 text-xs font-medium transition-all ${
686+
activeFamily === null
687+
? 'bg-sc-purple/15 text-sc-purple ring-1 ring-sc-purple/30'
688+
: 'text-sc-fg-dim hover:bg-sc-bg-highlight hover:text-sc-fg'
689+
}`}
690+
>
691+
All ({THEMES.length})
692+
</button>
693+
{FAMILIES.map(fam => {
694+
const count = THEMES.filter(t => t.family === fam.id).length;
695+
return (
696+
<button
697+
type="button"
698+
key={fam.id}
699+
onClick={() => setActiveFamily(f => (f === fam.id ? null : fam.id))}
700+
className={`rounded-lg px-2.5 py-1 text-xs font-medium transition-all ${
701+
activeFamily === fam.id
702+
? 'bg-sc-purple/15 text-sc-purple ring-1 ring-sc-purple/30'
703+
: 'text-sc-fg-dim hover:bg-sc-bg-highlight hover:text-sc-fg'
704+
}`}
705+
>
706+
{fam.label} ({count})
707+
</button>
708+
);
709+
})}
710+
</div>
707711

708-
{/* Grouped view (when no filter active) */}
709-
{groupedThemes?.map(({ family, themes }) => (
710-
<div key={family.id} className="mb-4 last:mb-0">
711-
<div className="mb-2 flex items-center gap-2 px-1">
712-
<h4 className="text-xs font-semibold uppercase tracking-widest text-sc-fg-dim">
713-
{family.label}
714-
</h4>
715-
<div className="h-px flex-1 bg-white/[0.04]" />
716-
</div>
717-
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
718-
{themes.map(theme => (
719-
<ThemeCard
720-
key={theme.id}
721-
theme={theme}
722-
isActive={activeTheme === theme.id}
723-
onSelect={() => handleSelect(theme.id)}
724-
/>
725-
))}
726-
</div>
727-
</div>
728-
))}
712+
{/* Theme grid */}
713+
<div className="editor-scrollbar max-h-[360px] overflow-y-auto pr-1">
714+
{filteredThemes.length === 0 && (
715+
<div className="py-8 text-center text-sm text-sc-fg-dim">
716+
No themes match &ldquo;{search}&rdquo;
717+
</div>
718+
)}
729719

730-
{/* Flat view (when searching or family selected) */}
731-
{!groupedThemes && (
720+
{/* Grouped view (when no filter active) */}
721+
{groupedThemes?.map(({ family, themes }) => (
722+
<div key={family.id} className="mb-4 last:mb-0">
723+
<div className="mb-2 flex items-center gap-2 px-1">
724+
<h4 className="text-xs font-semibold uppercase tracking-widest text-sc-fg-dim">
725+
{family.label}
726+
</h4>
727+
<div className="h-px flex-1 bg-white/[0.04]" />
728+
</div>
732729
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
733-
{filteredThemes.map(theme => (
730+
{themes.map(theme => (
734731
<ThemeCard
735732
key={theme.id}
736733
theme={theme}
@@ -739,19 +736,33 @@ export function ThemeSelector({ activeTheme, onSelect, disabled }: ThemeSelector
739736
/>
740737
))}
741738
</div>
742-
)}
743-
</div>
744-
745-
{/* Footer count */}
746-
{(search || activeFamily) && filteredThemes.length > 0 && (
747-
<div className="mt-2 text-center text-xs text-sc-fg-dim">
748-
{filteredThemes.length} of {THEMES.length} themes
739+
</div>
740+
))}
741+
742+
{/* Flat view (when searching or family selected) */}
743+
{!groupedThemes && (
744+
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
745+
{filteredThemes.map(theme => (
746+
<ThemeCard
747+
key={theme.id}
748+
theme={theme}
749+
isActive={activeTheme === theme.id}
750+
onSelect={() => handleSelect(theme.id)}
751+
/>
752+
))}
749753
</div>
750754
)}
751755
</div>
756+
757+
{/* Footer count */}
758+
{(search || activeFamily) && filteredThemes.length > 0 && (
759+
<div className="mt-2 text-center text-xs text-sc-fg-dim">
760+
{filteredThemes.length} of {THEMES.length} themes
761+
</div>
762+
)}
752763
</div>
753764
</div>
754-
</div>
765+
)}
755766
</div>
756767
);
757768
}

0 commit comments

Comments
 (0)