feat: redesign starter homepage
This commit is contained in:
Generated
+18
@@ -9,6 +9,7 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.6.0",
|
||||
"@gsap/react": "^2.1.2",
|
||||
"@mastra/core": "^1.50.1",
|
||||
"@supabase/ssr": "^0.12.3",
|
||||
"@supabase/supabase-js": "^2.110.5",
|
||||
@@ -18,6 +19,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.4.0",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"gsap": "^3.15.0",
|
||||
"lucide-react": "^1.24.0",
|
||||
"next": "16.2.10",
|
||||
"pg": "^8.22.0",
|
||||
@@ -1286,6 +1288,16 @@
|
||||
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@gsap/react": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@gsap/react/-/react-2.1.2.tgz",
|
||||
"integrity": "sha512-JqliybO1837UcgH2hVOM4VO+38APk3ECNrsuSM4MuXp+rbf+/2IG2K1YJiqfTcXQHH7XlA0m3ykniFYstfq0Iw==",
|
||||
"license": "SEE LICENSE AT https://gsap.com/standard-license",
|
||||
"peerDependencies": {
|
||||
"gsap": "^3.12.5",
|
||||
"react": ">=17"
|
||||
}
|
||||
},
|
||||
"node_modules/@hono/node-server": {
|
||||
"version": "1.19.14",
|
||||
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
|
||||
@@ -6565,6 +6577,12 @@
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/gsap": {
|
||||
"version": "3.15.0",
|
||||
"resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz",
|
||||
"integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
|
||||
"license": "Standard 'no charge' license: https://gsap.com/standard-license."
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.6.0",
|
||||
"@gsap/react": "^2.1.2",
|
||||
"@mastra/core": "^1.50.1",
|
||||
"@supabase/ssr": "^0.12.3",
|
||||
"@supabase/supabase-js": "^2.110.5",
|
||||
@@ -26,6 +27,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.4.0",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"gsap": "^3.15.0",
|
||||
"lucide-react": "^1.24.0",
|
||||
"next": "16.2.10",
|
||||
"pg": "^8.22.0",
|
||||
|
||||
@@ -785,3 +785,574 @@ input:disabled, select:disabled { color: var(--color-ink-tertiary); background:
|
||||
.conversational-composer-footer { align-items: stretch; flex-direction: column; }
|
||||
.conversational-composer-footer button, .conversational-confirmation button, .conversational-original-question button { width: 100%; }
|
||||
}
|
||||
/* Starter workbench */
|
||||
.welcome {
|
||||
width: min(1040px, 100%);
|
||||
padding: var(--space-8) 0 var(--space-12);
|
||||
}
|
||||
|
||||
.starter-list {
|
||||
width: min(1040px, 100%);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: var(--space-6);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.starter-hero,
|
||||
.product-entrypoints,
|
||||
.starter-themes {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.starter-hero {
|
||||
position: relative;
|
||||
min-height: 220px;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 250px;
|
||||
align-items: center;
|
||||
gap: var(--space-8);
|
||||
padding: var(--space-10);
|
||||
border: 1px solid color-mix(in srgb, var(--color-action) 14%, var(--color-border));
|
||||
border-radius: var(--radius-xl);
|
||||
background: radial-gradient(circle at 82% 36%, rgba(133, 67, 47, .12), transparent 33%), linear-gradient(135deg, #fbfaf7 0%, #f5f1eb 100%);
|
||||
}
|
||||
|
||||
.starter-hero::after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: .2;
|
||||
pointer-events: none;
|
||||
background-image: radial-gradient(rgba(29, 29, 31, .24) .45px, transparent .45px);
|
||||
background-size: 6px 6px;
|
||||
content: "";
|
||||
mask-image: linear-gradient(to right, transparent 8%, #000 72%);
|
||||
}
|
||||
|
||||
.starter-hero-copy {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.starter-greeting {
|
||||
margin: 0 0 var(--space-3);
|
||||
color: var(--color-action);
|
||||
font-size: var(--type-body-sm);
|
||||
font-weight: 600;
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
|
||||
.starter-hero h1 {
|
||||
max-width: 980px;
|
||||
margin: 0;
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(42px, 5.4vw, 68px);
|
||||
font-weight: 400;
|
||||
letter-spacing: -.055em;
|
||||
line-height: .98;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.starter-hero-note {
|
||||
max-width: 590px;
|
||||
margin: var(--space-5) 0 0;
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-body-sm);
|
||||
line-height: 1.7;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.starter-celestial-visual {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 232px;
|
||||
height: 178px;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.starter-orbit {
|
||||
position: absolute;
|
||||
border: 1px solid color-mix(in srgb, var(--color-action) 34%, transparent);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.starter-orbit-outer {
|
||||
inset: 8px 10px;
|
||||
transform: rotate(-12deg);
|
||||
}
|
||||
|
||||
.starter-orbit-inner {
|
||||
inset: 38px 50px;
|
||||
border-color: color-mix(in srgb, var(--color-ink-tertiary) 42%, transparent);
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
.starter-orbit-dot {
|
||||
position: absolute;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-action);
|
||||
box-shadow: 0 0 0 5px color-mix(in srgb, var(--color-action-soft) 72%, transparent);
|
||||
}
|
||||
|
||||
.starter-orbit-dot-primary {
|
||||
top: 26px;
|
||||
right: 37px;
|
||||
}
|
||||
|
||||
.starter-orbit-dot-secondary {
|
||||
bottom: 31px;
|
||||
left: 31px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--color-ink-tertiary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.starter-celestial-mark {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border: 1px solid color-mix(in srgb, var(--color-action) 18%, var(--color-border));
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--color-canvas) 88%, transparent) url("/jyotish-logo.png") center / 32px 32px no-repeat;
|
||||
box-shadow: 0 14px 36px -24px rgba(29, 29, 31, .48);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.product-entrypoints {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--color-canvas);
|
||||
}
|
||||
|
||||
.product-entrypoints small {
|
||||
color: var(--color-ink-secondary);
|
||||
line-height: 1.5;
|
||||
font-size: var(--type-caption);
|
||||
}
|
||||
|
||||
.daily-starlanguage-card,
|
||||
.birth-rectification-card {
|
||||
min-height: 228px;
|
||||
display: grid;
|
||||
grid-template-rows: minmax(0, 1fr) auto;
|
||||
gap: var(--space-6);
|
||||
padding: var(--space-8);
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.birth-rectification-card {
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.product-entrypoint-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: background-color 180ms ease-out;
|
||||
}
|
||||
|
||||
.product-entrypoint-card::after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(112deg, transparent 22%, rgba(255, 255, 255, .54) 46%, transparent 68%);
|
||||
content: "";
|
||||
transform: translateX(-48%);
|
||||
transition: opacity 220ms ease-out, transform 520ms var(--ease-out);
|
||||
}
|
||||
|
||||
.product-entrypoint-hitarea {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: inherit;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.product-entrypoint-card > :not(.product-entrypoint-hitarea) {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover) {
|
||||
border-color: transparent;
|
||||
background: color-mix(in srgb, var(--color-action-soft) 58%, var(--color-canvas));
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover)::after {
|
||||
opacity: .65;
|
||||
transform: translateX(48%);
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:not(:disabled):hover) .product-entrypoint-action {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:focus-visible) {
|
||||
outline: 3px solid color-mix(in srgb, var(--color-focus) 56%, transparent);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
|
||||
.product-entrypoint-card:has(.product-entrypoint-hitarea:disabled) {
|
||||
opacity: .65;
|
||||
}
|
||||
|
||||
.product-entrypoint-hitarea:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.product-entrypoint-copy {
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.product-entrypoint-kicker {
|
||||
display: block;
|
||||
margin-bottom: var(--space-4);
|
||||
color: var(--color-action);
|
||||
font-size: var(--type-overline);
|
||||
font-weight: 600;
|
||||
letter-spacing: .12em;
|
||||
}
|
||||
|
||||
.product-entrypoint-copy h2 {
|
||||
margin: 0;
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(26px, 2.6vw, 34px);
|
||||
font-weight: 400;
|
||||
letter-spacing: -.035em;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
.product-entrypoint-copy p {
|
||||
max-width: 460px;
|
||||
margin: var(--space-3) 0 0;
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-body-sm);
|
||||
line-height: 1.65;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.product-entrypoint-footer {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-5);
|
||||
padding-top: var(--space-5);
|
||||
border-top: 1px solid color-mix(in srgb, var(--color-border) 76%, transparent);
|
||||
}
|
||||
|
||||
.product-entrypoint-footer small {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-entrypoint-action {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-1);
|
||||
color: var(--color-action);
|
||||
font-size: var(--type-caption);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
transition: transform 180ms var(--ease-out);
|
||||
}
|
||||
|
||||
.product-entrypoint-action .starter-arrow {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.starter-themes {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.starter-section-heading {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.starter-section-heading h2 {
|
||||
margin: 0;
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--type-title-lg);
|
||||
font-weight: 400;
|
||||
letter-spacing: -.025em;
|
||||
}
|
||||
|
||||
.starter-section-heading p {
|
||||
margin: 0;
|
||||
color: var(--color-ink-tertiary);
|
||||
font-size: var(--type-caption);
|
||||
}
|
||||
|
||||
.starter-theme-accordion {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--color-canvas-soft);
|
||||
}
|
||||
|
||||
.starter-theme-card {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 170px;
|
||||
flex: 1 1 0;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 20px;
|
||||
align-items: end;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-5);
|
||||
border: 0;
|
||||
border-right: 1px solid var(--color-border);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: background-color 220ms ease-out, flex-grow 360ms var(--ease-out);
|
||||
}
|
||||
|
||||
.starter-theme-card:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.starter-theme-card:not(:disabled):hover,
|
||||
.starter-theme-card:focus-visible {
|
||||
flex-grow: 1.62;
|
||||
background: color-mix(in srgb, var(--color-action-soft) 56%, var(--color-canvas));
|
||||
}
|
||||
|
||||
.starter-theme-accordion:has(.starter-theme-card:not(:disabled):hover) .starter-theme-card:not(:hover),
|
||||
.starter-theme-accordion:has(.starter-theme-card:focus-visible) .starter-theme-card:not(:focus-visible) {
|
||||
flex-grow: .78;
|
||||
}
|
||||
|
||||
.starter-theme-card:not(:disabled):hover .starter-arrow,
|
||||
.starter-theme-card:focus-visible .starter-arrow {
|
||||
color: var(--color-action);
|
||||
transform: translate(2px, -2px);
|
||||
}
|
||||
|
||||
.starter-content {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.starter-content b {
|
||||
color: var(--color-action);
|
||||
font-size: var(--type-body-sm);
|
||||
font-weight: 600;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
.starter-content span {
|
||||
max-width: 245px;
|
||||
overflow: visible;
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--type-title-md);
|
||||
line-height: 1.38;
|
||||
text-overflow: clip;
|
||||
text-wrap: pretty;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.starter-arrow {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
color: var(--color-ink-secondary);
|
||||
transition: color 180ms ease-out, transform 180ms var(--ease-out);
|
||||
}
|
||||
|
||||
.starter-loading {
|
||||
width: min(1040px, 100%);
|
||||
margin-left: 0;
|
||||
padding: var(--space-6);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--color-canvas-soft);
|
||||
color: var(--color-ink-secondary);
|
||||
font-size: var(--type-body-sm);
|
||||
}
|
||||
|
||||
.starter-note {
|
||||
margin: calc(var(--space-2) * -1) 0 0;
|
||||
color: var(--color-ink-secondary);
|
||||
line-height: 1.5;
|
||||
font-size: var(--type-caption);
|
||||
}
|
||||
|
||||
.composer-wrap-starter .composer,
|
||||
.composer-wrap-starter .composer-footer,
|
||||
.composer-wrap-starter .composer-suggestions {
|
||||
width: min(1040px, 100%);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 900px) {
|
||||
.starter-hero {
|
||||
grid-template-columns: minmax(0, 1fr) 180px;
|
||||
padding: var(--space-8);
|
||||
}
|
||||
|
||||
.starter-celestial-visual {
|
||||
width: 180px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.product-entrypoints {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.birth-rectification-card {
|
||||
border-top: 1px solid var(--color-border);
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.starter-theme-accordion {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.starter-theme-card {
|
||||
min-height: 136px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.starter-theme-card:nth-child(2n) {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.starter-theme-card:nth-last-child(-n + 2) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.welcome {
|
||||
padding: var(--space-4) 0 var(--space-8);
|
||||
}
|
||||
|
||||
.starter-list {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.starter-hero {
|
||||
min-height: 0;
|
||||
grid-template-columns: 1fr;
|
||||
padding: var(--space-8) var(--space-6);
|
||||
}
|
||||
|
||||
.starter-hero h1 {
|
||||
font-size: clamp(38px, 11vw, 54px);
|
||||
}
|
||||
|
||||
.starter-celestial-visual {
|
||||
position: absolute;
|
||||
top: var(--space-4);
|
||||
right: calc(var(--space-8) * -1);
|
||||
width: 180px;
|
||||
height: 150px;
|
||||
opacity: .34;
|
||||
}
|
||||
|
||||
.product-entrypoints {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.daily-starlanguage-card,
|
||||
.birth-rectification-card {
|
||||
min-height: 210px;
|
||||
padding: var(--space-6);
|
||||
}
|
||||
|
||||
.birth-rectification-card {
|
||||
border-top: 1px solid var(--color-border);
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.starter-section-heading {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.starter-theme-accordion {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.starter-theme-card {
|
||||
min-height: 116px;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.starter-theme-card:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.starter-theme-accordion:has(.starter-theme-card:not(:disabled):hover) .starter-theme-card:not(:hover),
|
||||
.starter-theme-accordion:has(.starter-theme-card:focus-visible) .starter-theme-card:not(:focus-visible),
|
||||
.starter-theme-card:not(:disabled):hover,
|
||||
.starter-theme-card:focus-visible {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.starter-content span {
|
||||
max-width: 440px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.starter-hero {
|
||||
padding: var(--space-6) var(--space-5);
|
||||
}
|
||||
|
||||
.starter-hero-note {
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
.product-entrypoint-footer {
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.product-entrypoint-action {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.starter-content span {
|
||||
font-size: var(--type-title-sm);
|
||||
}
|
||||
}
|
||||
|
||||
+116
-53
@@ -3,6 +3,8 @@
|
||||
import Link from "next/link";
|
||||
import dynamic from "next/dynamic";
|
||||
import { ArrowUp, ArrowUpRight, Sparkles, Square, X } from "lucide-react";
|
||||
import { useGSAP } from "@gsap/react";
|
||||
import gsap from "gsap";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { FormEvent, KeyboardEvent } from "react";
|
||||
import { AppSidebar } from "@/components/app-sidebar";
|
||||
@@ -71,7 +73,6 @@ import { consultationReportMarkdown } from "@/lib/consultation-report-export";
|
||||
import {
|
||||
OnboardingAuthenticationError,
|
||||
type OnboardingContent,
|
||||
createOnboardingFallbackGreeting,
|
||||
createStartGreeting,
|
||||
isCurrentOnboardingRequest,
|
||||
onboardingProfileFingerprint,
|
||||
@@ -90,6 +91,8 @@ import {
|
||||
} from "@/lib/public-models";
|
||||
import { createBrowserSupabaseClient } from "@/lib/supabase/client";
|
||||
|
||||
gsap.registerPlugin(useGSAP);
|
||||
|
||||
const BirthTimeRectification = dynamic(
|
||||
() => import("@/components/birth-time-rectification").then((module) => module.BirthTimeRectification),
|
||||
{
|
||||
@@ -235,6 +238,12 @@ const dailyStarlanguageCards: DailyStarlanguageCard[] = [
|
||||
{ trend: "适合观察资源流向:时间、注意力、金钱都算。", action: "检查一个正在消耗你的习惯,并给它设上限。", caution: "不要为了短期安心做长期成本高的选择。" },
|
||||
];
|
||||
|
||||
function greetingForHour(hour: number): string {
|
||||
if (hour < 11) return "早上好";
|
||||
if (hour < 18) return "下午好";
|
||||
return "晚上好";
|
||||
}
|
||||
|
||||
const emptyProfile: Profile = {
|
||||
name: "",
|
||||
date: "",
|
||||
@@ -806,6 +815,7 @@ export default function Home() {
|
||||
const closeButton = useRef<HTMLButtonElement>(null);
|
||||
const redeemInput = useRef<HTMLInputElement>(null);
|
||||
const composerInput = useRef<HTMLTextAreaElement>(null);
|
||||
const starterWorkbench = useRef<HTMLDivElement>(null);
|
||||
const pendingConsultation = useRef<PendingConsultation | null>(null);
|
||||
const cancellationRequests = useRef(new Map<string, Promise<void>>());
|
||||
const cancellationFeedbackRequest = useRef<string | null>(null);
|
||||
@@ -974,7 +984,6 @@ export default function Home() {
|
||||
}, [accountId, profile]);
|
||||
|
||||
const profileComplete = isProfileComplete(profile);
|
||||
const birthTimeDisplay = birthTimeDisplayState(profile);
|
||||
const dailyStarlanguage = dailyStarlanguageCard ?? (profileComplete ? buildDailyStarlanguageCard(profile) : null);
|
||||
const onboardingPending = profileComplete && !onboarding && !onboardingError;
|
||||
const currentOnboardingMessage = onboardingJustCompleted
|
||||
@@ -989,6 +998,14 @@ export default function Home() {
|
||||
const shouldStreamOnboarding = !profileComplete;
|
||||
const presetMessageFinished = !shouldStreamOnboarding || presetMessageLength >= currentOnboardingMessage.length;
|
||||
const onboardingCardReady = presetMessageFinished || birthTimeAssessmentPhase !== null;
|
||||
const starterSuggestions = themes.map((theme) => onboarding?.suggestions.find((item) => item.theme === theme.id)
|
||||
?? { theme: theme.id, text: theme.prompt });
|
||||
const starterHomeVisible = profileComplete
|
||||
&& presetMessageFinished
|
||||
&& !onboardingPending
|
||||
&& !rectificationSurfaceOpen
|
||||
&& !activeSession?.messages.length;
|
||||
const daypartGreeting = greetingForHour(new Date().getHours());
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
@@ -2684,6 +2701,39 @@ export default function Home() {
|
||||
continueRectificationQuestion.current(question);
|
||||
}, [rectificationInitialTurn, rectificationLoading, rectificationMutationPending]);
|
||||
|
||||
useGSAP(() => {
|
||||
if (!starterHomeVisible || !starterWorkbench.current) return;
|
||||
const motion = gsap.matchMedia();
|
||||
motion.add("(prefers-reduced-motion: no-preference)", () => {
|
||||
const timeline = gsap.timeline({ defaults: { ease: "power3.out" } });
|
||||
timeline
|
||||
.from(".starter-hero-copy > *", {
|
||||
autoAlpha: 0,
|
||||
duration: 0.62,
|
||||
stagger: 0.07,
|
||||
y: 18,
|
||||
})
|
||||
.from(".starter-celestial-visual", {
|
||||
autoAlpha: 0,
|
||||
duration: 0.76,
|
||||
scale: 0.94,
|
||||
}, 0.08)
|
||||
.from(".product-entrypoint-card", {
|
||||
autoAlpha: 0,
|
||||
duration: 0.54,
|
||||
stagger: 0.08,
|
||||
y: 20,
|
||||
}, 0.2)
|
||||
.from(".starter-theme-card", {
|
||||
autoAlpha: 0,
|
||||
duration: 0.46,
|
||||
stagger: 0.055,
|
||||
y: 14,
|
||||
}, 0.34);
|
||||
});
|
||||
return () => motion.revert();
|
||||
}, { dependencies: [activeSession?.id, starterHomeVisible], scope: starterWorkbench });
|
||||
|
||||
function submit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!profileComplete) {
|
||||
@@ -2839,12 +2889,7 @@ export default function Home() {
|
||||
{profileComplete && onboardingJustCompleted && selectedBirthPlace(profileDraft) && <OnboardingChatMessage role="user" text={selectedBirthPlace(profileDraft)?.label ?? ""} />}
|
||||
{profileComplete && onboardingJustCompleted && <OnboardingChatMessage role="assistant" text={currentOnboardingMessage} streaming length={presetMessageLength} />}
|
||||
</>
|
||||
) : (
|
||||
<OnboardingChatMessage role="assistant" text={onboarding?.greeting
|
||||
|| (onboardingPending
|
||||
? `${profile.name.trim()},稍等一下,我正在准备几个适合开始的问题。`
|
||||
: createOnboardingFallbackGreeting(profile.name))} />
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{!profileComplete && onboardingStep === "birth" && onboardingCardReady && (
|
||||
<div className="onboarding-card-reveal">
|
||||
@@ -2903,9 +2948,24 @@ export default function Home() {
|
||||
{profileComplete && presetMessageFinished && !rectificationSurfaceOpen && (onboardingPending ? (
|
||||
<div className="starter-loading" role="status">正在准备三个入门问题…</div>
|
||||
) : (
|
||||
<div className="starter-list" aria-label="Jyotisha 推荐的初始问题">
|
||||
<div className="product-entrypoints" aria-label="常用占星入口">
|
||||
<article className="daily-starlanguage-card product-entrypoint-card" aria-label="今日星语">
|
||||
<div className="starter-list starter-workbench" ref={starterWorkbench} aria-label="Jyotisha 推荐的初始问题">
|
||||
<section className="starter-hero" aria-labelledby="starter-heading">
|
||||
<div className="starter-hero-copy">
|
||||
<p className="starter-greeting">{daypartGreeting},{profile.name.trim()}。</p>
|
||||
<h1 id="starter-heading">今天想先理清什么?</h1>
|
||||
<p className="starter-hero-note">从此刻最在意的事开始,我会结合你的星盘证据,帮你把问题拆得更清楚。</p>
|
||||
</div>
|
||||
<div className="starter-celestial-visual" aria-hidden="true">
|
||||
<span className="starter-orbit starter-orbit-outer" />
|
||||
<span className="starter-orbit starter-orbit-inner" />
|
||||
<span className="starter-orbit-dot starter-orbit-dot-primary" />
|
||||
<span className="starter-orbit-dot starter-orbit-dot-secondary" />
|
||||
<span className="starter-celestial-mark" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="product-entrypoints" aria-label="常用占星入口">
|
||||
<article className="daily-starlanguage-card product-entrypoint-card" aria-labelledby="daily-starlanguage-title">
|
||||
<button
|
||||
className="product-entrypoint-hitarea"
|
||||
type="button"
|
||||
@@ -2913,20 +2973,17 @@ export default function Home() {
|
||||
disabled={productEntrypointsDisabled}
|
||||
onClick={draftDailyStarlanguageQuestion}
|
||||
/>
|
||||
<div className="daily-starlanguage-heading">
|
||||
<span>今日星语</span>
|
||||
<div className="product-entrypoint-copy">
|
||||
<span className="product-entrypoint-kicker">每日观察</span>
|
||||
<h2 id="daily-starlanguage-title">今日星语</h2>
|
||||
<p>{dailyStarlanguage?.trend}</p>
|
||||
</div>
|
||||
<dl>
|
||||
<div><dt>今日趋势</dt><dd>{dailyStarlanguage?.trend}</dd></div>
|
||||
<div><dt>行动建议</dt><dd>{dailyStarlanguage?.action}</dd></div>
|
||||
<div><dt>今日提醒</dt><dd>{dailyStarlanguage?.caution}</dd></div>
|
||||
</dl>
|
||||
<div className="product-entrypoint-footer">
|
||||
<small>探索性日提示,不是确定预测。</small>
|
||||
<small>{dailyStarlanguage?.action}</small>
|
||||
<span className="product-entrypoint-action" aria-hidden="true">深入看今日 <ArrowUpRight className="starter-arrow" /></span>
|
||||
</div>
|
||||
</article>
|
||||
<article className="birth-rectification-card product-entrypoint-card" aria-label="生时校正">
|
||||
<article className="birth-rectification-card product-entrypoint-card" aria-labelledby="birth-rectification-title">
|
||||
<button
|
||||
className="product-entrypoint-hitarea"
|
||||
type="button"
|
||||
@@ -2934,44 +2991,50 @@ export default function Home() {
|
||||
disabled={productEntrypointsDisabled || rectificationLoading || rectificationMutationPending}
|
||||
onClick={() => void openBirthTimeRectification()}
|
||||
/>
|
||||
<div className="daily-starlanguage-heading">
|
||||
<span>生时校正</span>
|
||||
<div className="product-entrypoint-copy">
|
||||
<span className="product-entrypoint-kicker">出生资料</span>
|
||||
<h2 id="birth-rectification-title">生时校正</h2>
|
||||
<p>{rectificationCardAction === "resume"
|
||||
? "你有一段未完成的校正记录,可以从上次的位置继续。"
|
||||
: "不确定准确出生时间时,可通过已经发生的人生事件逐步缩小范围。"}</p>
|
||||
</div>
|
||||
<dl>
|
||||
{birthTimeDisplay ? (
|
||||
<>
|
||||
<div><dt>{birthTimeDisplay.kind === "candidate" ? "待验证候选时间" : "当前排盘时间"}</dt><dd>{birthTimeDisplay.activeTime}</dd></div>
|
||||
<div><dt>结果状态</dt><dd>{birthTimeDisplay.kind === "candidate" ? `未确认;${birthTimeConsultationOptionsCopy(profile)}` : "已确认"}</dd></div>
|
||||
<div><dt>原始填报</dt><dd>{birthTimeDisplay.reportedLabel}</dd></div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div><dt>填报资料</dt><dd>{formatBirthMoment(profile)}</dd></div>
|
||||
<div><dt>案例状态</dt><dd>{rectificationCardAction === "resume" ? "已有进度,可继续" : "尚未建立进行中的校正案例"}</dd></div>
|
||||
<div><dt>校正方式</dt><dd>星盘差异解释 + 已发生事件验证</dd></div>
|
||||
</>
|
||||
)}
|
||||
</dl>
|
||||
<div className="product-entrypoint-footer">
|
||||
<small>{rectificationCardAction === "resume"
|
||||
? account.rectificationCase?.preservesActiveTime
|
||||
? "新校正进行中;旧确认时间继续有效,继续同一案例不重复收费。"
|
||||
: "继续同一案例不重复收费。"
|
||||
: `固定费用 ${account.rectificationPriceCredits} 点;首轮有效分析生成后收取。`}</small>
|
||||
? "继续同一案例,不重复收费。"
|
||||
: `固定费用 ${account.rectificationPriceCredits} 点;首轮有效分析后收取。`}</small>
|
||||
<span className="product-entrypoint-action" aria-hidden="true">{rectificationCardLabel} <ArrowUpRight className="starter-arrow" /></span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
{(onboarding?.suggestions ?? themes.map((item) => ({ theme: item.id, text: item.prompt }))).map((item) => {
|
||||
const theme = themes.find((candidate) => candidate.id === item.theme);
|
||||
return (
|
||||
<button key={`${item.theme}-${item.text}`} type="button" disabled={!hydrated || Boolean(pendingSessionId) || cancellationPending || !account || !modelCatalog} onClick={() => chooseSuggestedQuestion(item.text, item.theme)}>
|
||||
<span className="starter-content"><b>{theme?.label || "开始"}</b><span>{item.text}</span>{theme && <small>{theme.evidencePreview.join(" / ")} · {theme.claimBoundary}</small>}</span>
|
||||
<ArrowUpRight className="starter-arrow" aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
{onboardingError && <p className="starter-note">Agent 的个性化入门问题暂时不可用,已显示安全的默认问题。</p>}
|
||||
</section>
|
||||
|
||||
<section className="starter-themes" aria-labelledby="starter-themes-heading">
|
||||
<div className="starter-section-heading">
|
||||
<h2 id="starter-themes-heading">或者,从一个主题开始</h2>
|
||||
<p>选择最接近你当下的问题。</p>
|
||||
</div>
|
||||
<div className="starter-theme-accordion">
|
||||
{starterSuggestions.map((item) => {
|
||||
const theme = themes.find((candidate) => candidate.id === item.theme);
|
||||
return (
|
||||
<button
|
||||
className="starter-theme-card"
|
||||
key={`${item.theme}-${item.text}`}
|
||||
type="button"
|
||||
aria-label={`${theme?.label || "开始"}:${item.text}`}
|
||||
disabled={!hydrated || Boolean(pendingSessionId) || cancellationPending || !account || !modelCatalog}
|
||||
onClick={() => chooseSuggestedQuestion(item.text, item.theme)}
|
||||
>
|
||||
<span className="starter-content">
|
||||
<b>{theme?.label || "开始"}</b>
|
||||
<span>{item.text}</span>
|
||||
</span>
|
||||
<ArrowUpRight className="starter-arrow" aria-hidden="true" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
{onboardingError && <p className="starter-note">个性化问题暂时不可用,已显示安全的默认问题。</p>}
|
||||
</div>
|
||||
))}
|
||||
<div ref={conversationEnd} />
|
||||
@@ -3000,7 +3063,7 @@ export default function Home() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!rectificationSurfaceOpen && <div className="composer-wrap">
|
||||
{!rectificationSurfaceOpen && <div className={`composer-wrap ${starterHomeVisible ? "composer-wrap-starter" : ""}`}>
|
||||
{activeSuggestions.length > 0 && (
|
||||
<div className="composer-suggestions" aria-label="推荐继续提问">
|
||||
{activeSuggestions.map((question) => (
|
||||
|
||||
@@ -245,3 +245,21 @@ test("homepage entrypoints use two whole-card native actions", () => {
|
||||
assert.equal(wholeCardActions.length, 2);
|
||||
assert.doesNotMatch(source, /className="daily-starlanguage-heading">[\s\S]{0,180}<button/);
|
||||
});
|
||||
|
||||
test("starter homepage stays editorial and hides technical chart parameters", () => {
|
||||
const source = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
|
||||
const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
||||
const start = source.indexOf('<div className="starter-list starter-workbench"');
|
||||
const end = source.indexOf("{onboardingError &&", start);
|
||||
const starterHomepage = source.slice(start, end);
|
||||
|
||||
assert.ok(start >= 0 && end > start);
|
||||
assert.match(starterHomepage, /className="starter-hero"/);
|
||||
assert.match(starterHomepage, /className="starter-theme-accordion"/);
|
||||
assert.match(starterHomepage, /starterSuggestions\.map/);
|
||||
assert.doesNotMatch(starterHomepage, /evidencePreview|birthTimeDisplay|Vimshottari|D1|D9/);
|
||||
assert.match(source, /const starterSuggestions = themes\.map/);
|
||||
assert.match(source, /composer-wrap-starter/);
|
||||
assert.match(styles, /\/\* Starter workbench \*\/[\s\S]*?\.starter-list \{[\s\S]*?grid-template-columns: minmax\(0, 1fr\);/);
|
||||
assert.match(styles, /\.starter-hero,[\s\S]*?\.product-entrypoints,[\s\S]*?\.starter-themes \{[\s\S]*?width: 100%;/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user