Commitc475ee6unknown_key
feat(visual-impact): live AI PR-review hero card + animated bg
feat(visual-impact): live AI PR-review hero card + animated bg User feedback: redesign was reading 'plain' / '1980s'. The hero was type-only — beautiful typography but no product visualization. Adding a CSS-art representation of gluecron's actual product surface so the hero shows the value proposition, not just describes it. landing.tsx - New .landing-hero-visual block right under the hero text. Renders a stylized live-AI-PR-review card showing: - PR header with green presence dot + 'AI reviewing' status pulse - Diff hunk with green +-line highlights for src/billing/checkout.ts - 'claude-bot' AI comment card with gradient avatar + suggestion - Bottom gate row: 3 passing checks + 1 spinning 'AI Review' - Two floating accent badges (spec-to-PR, auto-repair active) bobbing in 3D space around the card to suggest depth and motion. - Card itself has rotateX/Y for subtle 3D tilt, levels on hover. Animations - Hero blobs now drift and breathe (alternating 18s/22s loops) - Status pulse on AI reviewing chip - Spinner on AI Review gate - Float bob on accent badges - Card stagger-in (700ms after page load) PWA cache bumped to v3 so the new visual purges any stale v1/v2 shell.
2 files changed+362−3c475ee69ed5a8ac9fe2b95c9cd4acfde763601f9
2 changed files+362−3
Modifiedsrc/routes/pwa.ts+2−2View fileUnifiedSplit
@@ -64,8 +64,8 @@ pwa.get("/icon.svg", (c) => {
6464 * - other → pass-through (the static CSS is inlined into the HTML, so there's
6565 * no cross-request asset worth caching for v1)
6666 */
67export const SERVICE_WORKER_SRC = `// gluecron service worker — v2 (post Editorial-Technical redesign)
68const CACHE = 'gluecron-shell-v2';
67export const SERVICE_WORKER_SRC = `// gluecron service worker — v3 (Visual Impact pass)
68const CACHE = 'gluecron-shell-v3';
6969const SHELL = ['/', '/manifest.webmanifest', '/icon.svg'];
7070
7171self.addEventListener('install', (e) => {
Modifiedsrc/views/landing.tsx+360−1View fileUnifiedSplit
@@ -102,6 +102,77 @@ export const LandingHero: FC<LandingPageProps> = ({ stats } = {}) => {
102102 </p>
103103 )}
104104 </div>
105
106 {/* ---------- Hero product visual: live AI PR review ---------- */}
107 <div class="landing-hero-visual" aria-hidden="true">
108 <div class="hero-pr-card">
109 <div class="hero-pr-header">
110 <span class="hero-pr-dot" />
111 <span class="hero-pr-title">
112 <span class="hero-pr-num">#247</span>
113 add: stripe checkout flow
114 </span>
115 <span class="hero-pr-status">
116 <span class="hero-pr-status-pulse" />
117 AI reviewing
118 </span>
119 </div>
120 <div class="hero-pr-body">
121 <div class="hero-pr-file">
122 <span class="hero-pr-file-icon">{"●"}</span>
123 <span class="hero-pr-file-name">src/billing/checkout.ts</span>
124 <span class="hero-pr-file-stats">
125 <span class="hero-pr-add">+12</span>
126 <span class="hero-pr-del">{"−3"}</span>
127 </span>
128 </div>
129 <div class="hero-pr-diff">
130 <div class="hero-pr-hunk">@@ -42,6 +42,15 @@ export async function checkout(req)</div>
131 <div class="hero-pr-line-add">+ const session = await stripe.checkout.sessions.create({"{"}</div>
132 <div class="hero-pr-line-add">+ mode: 'subscription',</div>
133 <div class="hero-pr-line-add">+ line_items: [{"{"} price: priceId, quantity: 1 {"}"}],</div>
134 <div class="hero-pr-line-add">+ success_url: `${"$"}{"{"}{"BASE"}{"}"}/done`,</div>
135 <div class="hero-pr-line-add">+ {"}"});</div>
136 </div>
137 </div>
138 <div class="hero-pr-comment">
139 <div class="hero-pr-bot-row">
140 <span class="hero-pr-bot-avatar">{"✨"}</span>
141 <span class="hero-pr-bot-name">claude-bot</span>
142 <span class="hero-pr-bot-meta">commented · just now</span>
143 </div>
144 <p class="hero-pr-bot-text">
145 Logic looks correct. One nit:{" "}
146 <code>success_url</code> should validate session before
147 redirecting — see{" "}
148 <span class="hero-pr-bot-link">stripe-docs/refunds</span>.
149 </p>
150 </div>
151 <div class="hero-pr-gates">
152 <span class="hero-pr-gate hero-pr-gate-pass">
153 {"✓"} GateTest
154 </span>
155 <span class="hero-pr-gate hero-pr-gate-pass">
156 {"✓"} Secret scan
157 </span>
158 <span class="hero-pr-gate hero-pr-gate-pass">
159 {"✓"} Tests · 1234 pass
160 </span>
161 <span class="hero-pr-gate hero-pr-gate-running">
162 <span class="hero-pr-gate-spin" /> AI Review
163 </span>
164 </div>
165 </div>
166 {/* Floating accent badges to suggest depth */}
167 <div class="hero-float hero-float-1">
168 <span class="hero-float-icon">{"✨"}</span>
169 spec-to-PR
170 </div>
171 <div class="hero-float hero-float-2">
172 <span class="hero-float-icon">{"⚡"}</span>
173 auto-repair active
174 </div>
175 </div>
105176 </section>
106177
107178 {/* ---------- Trust strip ---------- */}
@@ -452,10 +523,298 @@ const landingCss = `
452523 /* ---------- Hero ---------- */
453524 .landing-hero {
454525 position: relative;
455 padding: var(--s-20) 0 var(--s-16);
526 padding: var(--s-16) 0 var(--s-20);
456527 text-align: center;
457528 overflow: hidden;
458529 }
530 .landing-hero-blob-1 {
531 animation: hero-blob-drift-1 18s var(--ease, ease) infinite alternate;
532 }
533 .landing-hero-blob-2 {
534 animation: hero-blob-drift-2 22s var(--ease, ease) infinite alternate;
535 }
536 hero-blob-drift-1 {
537 0% { transform: translate(0, 0) scale(1); opacity: 0.55; }
538 100% { transform: translate(8%, 6%) scale(1.18); opacity: 0.75; }
539 }
540 hero-blob-drift-2 {
541 0% { transform: translate(0, 0) scale(1); opacity: 0.40; }
542 100% { transform: translate(-10%, -4%) scale(1.25); opacity: 0.60; }
543 }
544
545 /* ---------- Hero product visual: live AI PR review card ---------- */
546 .landing-hero-visual {
547 position: relative;
548 max-width: 760px;
549 margin: var(--s-12) auto 0;
550 padding: 0 16px;
551 perspective: 1400px;
552 z-index: 2;
553 opacity: 0;
554 animation: hero-visual-in 700ms var(--ease-out-expo, cubic-bezier(0.19, 1, 0.22, 1)) 400ms forwards;
555 }
556 hero-visual-in {
557 from { opacity: 0; transform: translateY(20px); }
558 to { opacity: 1; transform: translateY(0); }
559 }
560 .hero-pr-card {
561 position: relative;
562 background: linear-gradient(180deg, rgba(15,17,26,0.96) 0%, rgba(8,9,15,0.96) 100%);
563 border: 1px solid var(--border-strong);
564 border-radius: var(--r-xl);
565 overflow: hidden;
566 text-align: left;
567 box-shadow:
568 0 30px 80px -20px rgba(0,0,0,0.65),
569 0 0 0 1px rgba(140,109,255,0.18),
570 0 0 60px -10px rgba(140,109,255,0.30);
571 transform: rotateX(2deg) rotateY(-2deg);
572 transition: transform 600ms var(--ease, ease);
573 backdrop-filter: blur(12px);
574 -webkit-backdrop-filter: blur(12px);
575 }
576 .landing-hero-visual:hover .hero-pr-card {
577 transform: rotateX(0deg) rotateY(0deg);
578 }
579 .hero-pr-card::before {
580 content: '';
581 position: absolute;
582 inset: 0;
583 background: linear-gradient(135deg, rgba(140,109,255,0.10), transparent 35%, transparent 65%, rgba(54,197,214,0.08));
584 pointer-events: none;
585 }
586 .hero-pr-header {
587 display: flex;
588 align-items: center;
589 gap: 12px;
590 padding: 14px 18px;
591 border-bottom: 1px solid rgba(255,255,255,0.06);
592 background: rgba(255,255,255,0.025);
593 font-size: 13px;
594 }
595 .hero-pr-dot {
596 width: 10px; height: 10px;
597 border-radius: 50%;
598 background: var(--green);
599 box-shadow: 0 0 10px rgba(52,211,153,0.6);
600 flex-shrink: 0;
601 }
602 .hero-pr-title {
603 color: var(--text-strong);
604 font-weight: 600;
605 flex: 1;
606 overflow: hidden;
607 text-overflow: ellipsis;
608 white-space: nowrap;
609 }
610 .hero-pr-num {
611 color: var(--text-faint);
612 font-family: var(--font-mono);
613 font-weight: 500;
614 margin-right: 8px;
615 }
616 .hero-pr-status {
617 display: inline-flex;
618 align-items: center;
619 gap: 6px;
620 padding: 3px 10px;
621 border-radius: var(--r-full);
622 background: var(--accent-gradient-faint);
623 border: 1px solid rgba(140,109,255,0.30);
624 color: var(--accent);
625 font-family: var(--font-mono);
626 font-size: 11px;
627 letter-spacing: 0.04em;
628 flex-shrink: 0;
629 }
630 .hero-pr-status-pulse {
631 width: 6px; height: 6px;
632 border-radius: 50%;
633 background: var(--accent);
634 box-shadow: 0 0 0 0 rgba(140,109,255,0.6);
635 animation: hero-pulse 1.6s ease-out infinite;
636 }
637 hero-pulse {
638 0% { box-shadow: 0 0 0 0 rgba(140,109,255,0.55); }
639 70% { box-shadow: 0 0 0 8px rgba(140,109,255,0); }
640 100% { box-shadow: 0 0 0 0 rgba(140,109,255,0); }
641 }
642
643 .hero-pr-body {
644 padding: 0;
645 }
646 .hero-pr-file {
647 display: flex;
648 align-items: center;
649 gap: 10px;
650 padding: 10px 18px;
651 border-bottom: 1px solid rgba(255,255,255,0.05);
652 font-family: var(--font-mono);
653 font-size: 12px;
654 background: rgba(255,255,255,0.012);
655 }
656 .hero-pr-file-icon { color: var(--accent-2); }
657 .hero-pr-file-name { color: var(--text); flex: 1; }
658 .hero-pr-file-stats { display: inline-flex; gap: 8px; }
659 .hero-pr-add { color: var(--green); font-weight: 600; }
660 .hero-pr-del { color: var(--red); font-weight: 600; }
661
662 .hero-pr-diff {
663 padding: 12px 18px;
664 font-family: var(--font-mono);
665 font-feature-settings: var(--mono-feat, 'calt');
666 font-size: 12.5px;
667 line-height: 1.7;
668 color: rgba(237,237,242,0.85);
669 overflow-x: auto;
670 }
671 .hero-pr-hunk {
672 color: rgba(140,109,255,0.85);
673 background: rgba(140,109,255,0.06);
674 padding: 2px 8px;
675 margin: 0 -8px 4px;
676 border-radius: 4px;
677 }
678 .hero-pr-line-add {
679 background: rgba(52,211,153,0.08);
680 color: rgba(167,243,208,0.95);
681 padding: 0 8px;
682 margin: 0 -8px;
683 border-left: 2px solid var(--green);
684 padding-left: 8px;
685 }
686
687 .hero-pr-comment {
688 margin: 14px 18px;
689 padding: 14px 16px;
690 background: linear-gradient(135deg, rgba(140,109,255,0.08), rgba(54,197,214,0.05));
691 border: 1px solid rgba(140,109,255,0.25);
692 border-radius: var(--r-md);
693 }
694 .hero-pr-bot-row {
695 display: flex;
696 align-items: center;
697 gap: 8px;
698 margin-bottom: 8px;
699 font-size: 12px;
700 }
701 .hero-pr-bot-avatar {
702 width: 22px; height: 22px;
703 display: inline-flex;
704 align-items: center;
705 justify-content: center;
706 border-radius: 50%;
707 background: var(--accent-gradient);
708 font-size: 11px;
709 box-shadow: 0 0 12px rgba(140,109,255,0.40);
710 }
711 .hero-pr-bot-name {
712 color: var(--text-strong);
713 font-weight: 600;
714 }
715 .hero-pr-bot-meta {
716 color: var(--text-faint);
717 font-family: var(--font-mono);
718 }
719 .hero-pr-bot-text {
720 color: var(--text);
721 font-size: 13px;
722 line-height: 1.55;
723 margin: 0;
724 }
725 .hero-pr-bot-text code {
726 background: rgba(255,255,255,0.06);
727 border: 1px solid rgba(255,255,255,0.10);
728 padding: 1px 6px;
729 border-radius: 4px;
730 font-size: 11.5px;
731 color: var(--accent);
732 }
733 .hero-pr-bot-link { color: var(--accent-2); text-decoration: underline; text-decoration-style: dotted; }
734
735 .hero-pr-gates {
736 display: flex;
737 flex-wrap: wrap;
738 gap: 6px;
739 padding: 12px 18px 16px;
740 border-top: 1px solid rgba(255,255,255,0.06);
741 background: rgba(255,255,255,0.012);
742 }
743 .hero-pr-gate {
744 display: inline-flex;
745 align-items: center;
746 gap: 6px;
747 padding: 4px 10px;
748 border-radius: var(--r-full);
749 font-family: var(--font-mono);
750 font-size: 11px;
751 border: 1px solid;
752 }
753 .hero-pr-gate-pass {
754 color: var(--green);
755 background: rgba(52,211,153,0.08);
756 border-color: rgba(52,211,153,0.30);
757 }
758 .hero-pr-gate-running {
759 color: var(--accent);
760 background: var(--accent-gradient-faint);
761 border-color: rgba(140,109,255,0.40);
762 }
763 .hero-pr-gate-spin {
764 width: 9px; height: 9px;
765 border: 1.5px solid rgba(140,109,255,0.30);
766 border-top-color: var(--accent);
767 border-radius: 50%;
768 animation: hero-spin 800ms linear infinite;
769 }
770 hero-spin {
771 to { transform: rotate(360deg); }
772 }
773
774 /* Floating accent badges around the card */
775 .hero-float {
776 position: absolute;
777 display: inline-flex;
778 align-items: center;
779 gap: 6px;
780 padding: 6px 12px;
781 background: rgba(15,17,26,0.92);
782 border: 1px solid rgba(140,109,255,0.35);
783 border-radius: var(--r-full);
784 font-family: var(--font-mono);
785 font-size: 11px;
786 color: var(--text);
787 box-shadow: 0 12px 24px -8px rgba(0,0,0,0.5), 0 0 18px -4px rgba(140,109,255,0.30);
788 backdrop-filter: blur(8px);
789 -webkit-backdrop-filter: blur(8px);
790 }
791 .hero-float-icon { color: var(--accent); }
792 .hero-float-1 {
793 top: -14px;
794 left: -8px;
795 animation: hero-float-bob-1 5s var(--ease, ease) infinite alternate;
796 }
797 .hero-float-2 {
798 bottom: -14px;
799 right: -8px;
800 animation: hero-float-bob-2 6s var(--ease, ease) infinite alternate;
801 }
802 hero-float-bob-1 {
803 from { transform: translate(0, 0); }
804 to { transform: translate(-8px, -10px); }
805 }
806 hero-float-bob-2 {
807 from { transform: translate(0, 0); }
808 to { transform: translate(8px, 8px); }
809 }
810
811 (max-width: 720px) {
812 .landing-hero-visual { padding: 0 8px; }
813 .hero-pr-card { transform: none; }
814 .hero-pr-title { font-size: 12px; }
815 .hero-pr-diff { font-size: 11px; line-height: 1.6; }
816 .hero-float { display: none; }
817 }
459818 .landing-hero-bg {
460819 position: absolute;
461820 inset: -10% -20%;
462821