Spaces:
Running
Running
Elevate landing hero: constellation backdrop, cursor glow, unified easing
Browse filesAdds a .constellation-bg primitive — a faint dot lattice that drifts with
the cursor via the same --mx/--my channel as .has-cursor-glow, so one
mousemove handler feeds both the spotlight and the parallax. The dots
evoke map coordinates, which fits Cartographer's premise.
Applied to all three empty-state branches (landing, all-repos, repo
selected). Custom properties inherit down the tree, so the cursor glow
on .empty-state drives parallax on the inner .constellation-bg without
needing a second handler.
Also migrated onboarding and suggest entrance animations + suggestion-btn
hover transitions to the existing --dur-*/--ease-spring* tokens so every
motion in the app shares one curve family.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ui/src/App.jsx +17 -4
- ui/src/index.css +48 -8
ui/src/App.jsx
CHANGED
|
@@ -757,10 +757,23 @@ export default function App() {
|
|
| 757 |
{!showReadme && view === "chat" && (
|
| 758 |
<>
|
| 759 |
{messages.length === 0 ? (
|
| 760 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
{activeRepo === "all" && repos.length > 0 ? (
|
| 762 |
// "All repos" explicitly selected with repos indexed — cross-repo query mode
|
| 763 |
-
<div className="suggest-state">
|
| 764 |
<h2>Ask across all repos</h2>
|
| 765 |
<p>Searching <strong>{repos.length} indexed repos</strong> at once — {repos.map(r => r.slug.split("/")[1]).join(", ")}. Results show which repo each source comes from.</p>
|
| 766 |
<div className="suggestions">
|
|
@@ -789,7 +802,7 @@ export default function App() {
|
|
| 789 |
</div>
|
| 790 |
) : !activeRepo || activeRepo === "all" ? (
|
| 791 |
// No repo selected yet (landing), or All repos selected but nothing indexed
|
| 792 |
-
<div className="onboarding-steps">
|
| 793 |
<div className="onboarding-header">
|
| 794 |
<svg width="72" height="72" viewBox="0 0 24 24" fill="none"
|
| 795 |
style={{ marginBottom: 12, filter: "drop-shadow(0 0 8px rgba(91,143,249,0.70))" }}>
|
|
@@ -827,7 +840,7 @@ export default function App() {
|
|
| 827 |
</div>
|
| 828 |
) : (
|
| 829 |
// Repo selected — show mode-aware suggestions + feature discovery cards
|
| 830 |
-
<div className="suggest-state">
|
| 831 |
<h2>How does {activeRepo.split("/")[1]} work?</h2>
|
| 832 |
|
| 833 |
|
|
|
|
| 757 |
{!showReadme && view === "chat" && (
|
| 758 |
<>
|
| 759 |
{messages.length === 0 ? (
|
| 760 |
+
<div
|
| 761 |
+
className="empty-state has-cursor-glow"
|
| 762 |
+
// Shared --mx/--my channel: one mousemove feeds both the
|
| 763 |
+
// glow pseudo and the constellation parallax. Percentages
|
| 764 |
+
// are used so the transforms are resolution-independent.
|
| 765 |
+
onMouseMove={(e) => {
|
| 766 |
+
const r = e.currentTarget.getBoundingClientRect();
|
| 767 |
+
const mx = ((e.clientX - r.left) / r.width) * 100;
|
| 768 |
+
const my = ((e.clientY - r.top) / r.height) * 100;
|
| 769 |
+
e.currentTarget.style.setProperty("--mx", `${mx}%`);
|
| 770 |
+
e.currentTarget.style.setProperty("--my", `${my}%`);
|
| 771 |
+
}}
|
| 772 |
+
style={{ "--glow-size": "640px", "--glow-intensity": "6%" }}
|
| 773 |
+
>
|
| 774 |
{activeRepo === "all" && repos.length > 0 ? (
|
| 775 |
// "All repos" explicitly selected with repos indexed — cross-repo query mode
|
| 776 |
+
<div className="suggest-state constellation-bg">
|
| 777 |
<h2>Ask across all repos</h2>
|
| 778 |
<p>Searching <strong>{repos.length} indexed repos</strong> at once — {repos.map(r => r.slug.split("/")[1]).join(", ")}. Results show which repo each source comes from.</p>
|
| 779 |
<div className="suggestions">
|
|
|
|
| 802 |
</div>
|
| 803 |
) : !activeRepo || activeRepo === "all" ? (
|
| 804 |
// No repo selected yet (landing), or All repos selected but nothing indexed
|
| 805 |
+
<div className="onboarding-steps constellation-bg">
|
| 806 |
<div className="onboarding-header">
|
| 807 |
<svg width="72" height="72" viewBox="0 0 24 24" fill="none"
|
| 808 |
style={{ marginBottom: 12, filter: "drop-shadow(0 0 8px rgba(91,143,249,0.70))" }}>
|
|
|
|
| 840 |
</div>
|
| 841 |
) : (
|
| 842 |
// Repo selected — show mode-aware suggestions + feature discovery cards
|
| 843 |
+
<div className="suggest-state constellation-bg">
|
| 844 |
<h2>How does {activeRepo.split("/")[1]} work?</h2>
|
| 845 |
|
| 846 |
|
ui/src/index.css
CHANGED
|
@@ -309,6 +309,42 @@ body::before {
|
|
| 309 |
to { opacity: 1; transform: translateY(0); filter: blur(0); }
|
| 310 |
}
|
| 311 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
/* Respect the user. Disable motion where requested. */
|
| 313 |
@media (prefers-reduced-motion: reduce) {
|
| 314 |
*, *::before, *::after {
|
|
@@ -1524,12 +1560,12 @@ textarea:focus-visible {
|
|
| 1524 |
to { opacity: 1; transform: translateY(0); }
|
| 1525 |
}
|
| 1526 |
.onboarding-header {
|
| 1527 |
-
animation: onboardIn
|
| 1528 |
animation-delay: 0ms;
|
| 1529 |
}
|
| 1530 |
-
.onboarding-steps .onboarding-step:nth-child(2) { animation: onboardIn
|
| 1531 |
-
.onboarding-steps .onboarding-step:nth-child(3) { animation: onboardIn
|
| 1532 |
-
.onboarding-steps .onboarding-step:nth-child(4) { animation: onboardIn
|
| 1533 |
|
| 1534 |
/* Header for onboarding section */
|
| 1535 |
.onboarding-header {
|
|
@@ -1701,7 +1737,7 @@ textarea:focus-visible {
|
|
| 1701 |
/* Suggest state */
|
| 1702 |
.suggest-state { max-width: 620px; width: 100%; text-align: center; padding-top: 16px; padding-bottom: 24px; }
|
| 1703 |
.suggest-state h2 {
|
| 1704 |
-
animation: onboardIn
|
| 1705 |
font-size: 48px;
|
| 1706 |
font-weight: 300;
|
| 1707 |
font-family: var(--serif);
|
|
@@ -1712,7 +1748,7 @@ textarea:focus-visible {
|
|
| 1712 |
text-shadow: 0 0 80px rgba(91,143,249,0.20);
|
| 1713 |
}
|
| 1714 |
.suggest-state > p {
|
| 1715 |
-
animation: onboardIn
|
| 1716 |
font-size: 13.5px;
|
| 1717 |
color: var(--text-2);
|
| 1718 |
margin-bottom: 20px;
|
|
@@ -1746,8 +1782,12 @@ textarea:focus-visible {
|
|
| 1746 |
font-family: var(--sans);
|
| 1747 |
padding: 13px 14px 13px 14px;
|
| 1748 |
text-align: left;
|
| 1749 |
-
transition:
|
| 1750 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1751 |
box-shadow: 0 1px 3px rgba(0,0,0,0.25), 0 4px 16px rgba(0,0,0,0.15);
|
| 1752 |
}
|
| 1753 |
|
|
|
|
| 309 |
to { opacity: 1; transform: translateY(0); filter: blur(0); }
|
| 310 |
}
|
| 311 |
|
| 312 |
+
/* .constellation-bg — a dotted lattice that drifts subtly with the cursor.
|
| 313 |
+
A thematic backdrop for the Cartographer landing hero: tiny "coordinate"
|
| 314 |
+
points evoke the act of mapping. Host element must also set --mx/--my via
|
| 315 |
+
onMouseMove (same contract as .has-cursor-glow — reuse the same handler). */
|
| 316 |
+
.constellation-bg {
|
| 317 |
+
position: relative;
|
| 318 |
+
isolation: isolate;
|
| 319 |
+
/* --mx / --my are intentionally NOT declared here so they inherit from a
|
| 320 |
+
has-cursor-glow ancestor. Declaring them locally would reset parallax
|
| 321 |
+
to centred every time. The calc() fallback covers the no-ancestor case. */
|
| 322 |
+
}
|
| 323 |
+
.constellation-bg::before {
|
| 324 |
+
content: "";
|
| 325 |
+
position: absolute;
|
| 326 |
+
inset: -40px; /* bleed past edges so parallax never reveals an edge */
|
| 327 |
+
background-image:
|
| 328 |
+
radial-gradient(rgba(180, 200, 255, 0.18) 1px, transparent 1.2px),
|
| 329 |
+
radial-gradient(rgba(180, 200, 255, 0.08) 1px, transparent 1.2px);
|
| 330 |
+
background-size: 44px 44px, 88px 88px;
|
| 331 |
+
background-position: 0 0, 22px 22px;
|
| 332 |
+
mask-image: radial-gradient(ellipse at 50% 45%, black 0%, black 30%, transparent 75%);
|
| 333 |
+
-webkit-mask-image: radial-gradient(ellipse at 50% 45%, black 0%, black 30%, transparent 75%);
|
| 334 |
+
pointer-events: none;
|
| 335 |
+
z-index: 0;
|
| 336 |
+
/* Cursor parallax — map 0..100% onto a ±12px offset. calc keeps it smooth
|
| 337 |
+
with no JS animation loop; the paint pipeline handles interpolation. */
|
| 338 |
+
transform: translate3d(
|
| 339 |
+
calc((var(--mx, 50%) - 50%) * -0.12),
|
| 340 |
+
calc((var(--my, 50%) - 50%) * -0.12),
|
| 341 |
+
0
|
| 342 |
+
);
|
| 343 |
+
transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
|
| 344 |
+
opacity: 0.9;
|
| 345 |
+
}
|
| 346 |
+
.constellation-bg > * { position: relative; z-index: 1; }
|
| 347 |
+
|
| 348 |
/* Respect the user. Disable motion where requested. */
|
| 349 |
@media (prefers-reduced-motion: reduce) {
|
| 350 |
*, *::before, *::after {
|
|
|
|
| 1560 |
to { opacity: 1; transform: translateY(0); }
|
| 1561 |
}
|
| 1562 |
.onboarding-header {
|
| 1563 |
+
animation: onboardIn var(--dur-slow) var(--ease-spring) both;
|
| 1564 |
animation-delay: 0ms;
|
| 1565 |
}
|
| 1566 |
+
.onboarding-steps .onboarding-step:nth-child(2) { animation: onboardIn var(--dur-medium) var(--ease-spring) 120ms both; }
|
| 1567 |
+
.onboarding-steps .onboarding-step:nth-child(3) { animation: onboardIn var(--dur-medium) var(--ease-spring) 210ms both; }
|
| 1568 |
+
.onboarding-steps .onboarding-step:nth-child(4) { animation: onboardIn var(--dur-medium) var(--ease-spring) 300ms both; }
|
| 1569 |
|
| 1570 |
/* Header for onboarding section */
|
| 1571 |
.onboarding-header {
|
|
|
|
| 1737 |
/* Suggest state */
|
| 1738 |
.suggest-state { max-width: 620px; width: 100%; text-align: center; padding-top: 16px; padding-bottom: 24px; }
|
| 1739 |
.suggest-state h2 {
|
| 1740 |
+
animation: onboardIn var(--dur-slow) var(--ease-spring) both;
|
| 1741 |
font-size: 48px;
|
| 1742 |
font-weight: 300;
|
| 1743 |
font-family: var(--serif);
|
|
|
|
| 1748 |
text-shadow: 0 0 80px rgba(91,143,249,0.20);
|
| 1749 |
}
|
| 1750 |
.suggest-state > p {
|
| 1751 |
+
animation: onboardIn var(--dur-medium) var(--ease-spring) 120ms both;
|
| 1752 |
font-size: 13.5px;
|
| 1753 |
color: var(--text-2);
|
| 1754 |
margin-bottom: 20px;
|
|
|
|
| 1782 |
font-family: var(--sans);
|
| 1783 |
padding: 13px 14px 13px 14px;
|
| 1784 |
text-align: left;
|
| 1785 |
+
transition:
|
| 1786 |
+
border-color var(--dur-fast) var(--ease-spring),
|
| 1787 |
+
background var(--dur-fast) var(--ease-spring),
|
| 1788 |
+
transform var(--dur-fast) var(--ease-spring-snap),
|
| 1789 |
+
box-shadow var(--dur-fast) var(--ease-spring-snap);
|
| 1790 |
+
animation: suggestionIn var(--dur-slow) var(--ease-spring) both;
|
| 1791 |
box-shadow: 0 1px 3px rgba(0,0,0,0.25), 0 4px 16px rgba(0,0,0,0.15);
|
| 1792 |
}
|
| 1793 |
|