umanggarg Claude Opus 4.7 commited on
Commit
48ed678
·
1 Parent(s): 8887389

Elevate landing hero: constellation backdrop, cursor glow, unified easing

Browse files

Adds 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>

Files changed (2) hide show
  1. ui/src/App.jsx +17 -4
  2. 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 className="empty-state">
 
 
 
 
 
 
 
 
 
 
 
 
 
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 750ms cubic-bezier(0.16, 1, 0.3, 1) both;
1528
  animation-delay: 0ms;
1529
  }
1530
- .onboarding-steps .onboarding-step:nth-child(2) { animation: onboardIn 560ms cubic-bezier(0.16, 1, 0.3, 1) 120ms both; }
1531
- .onboarding-steps .onboarding-step:nth-child(3) { animation: onboardIn 560ms cubic-bezier(0.16, 1, 0.3, 1) 210ms both; }
1532
- .onboarding-steps .onboarding-step:nth-child(4) { animation: onboardIn 560ms cubic-bezier(0.16, 1, 0.3, 1) 300ms both; }
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 750ms cubic-bezier(0.16, 1, 0.3, 1) both;
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 700ms cubic-bezier(0.16, 1, 0.3, 1) 120ms both;
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: border-color var(--transition), background var(--transition), transform var(--transition), box-shadow var(--transition);
1750
- animation: suggestionIn 800ms cubic-bezier(0.16, 1, 0.3, 1) both;
 
 
 
 
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