diff --git a/web/app/globals.css b/web/app/globals.css index 7540834..a7f3c04 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -1,15 +1,15 @@ :root { - --ink: #090f0e; - --jade: #0d1b18; - --jade-2: #122722; - --paper: #f4ead4; - --paper-dim: #c7b999; - --gold: #d6a958; - --gold-soft: rgba(214, 169, 88, 0.18); - --cyan: #34e0bd; - --cyan-soft: rgba(52, 224, 189, 0.16); - --line: rgba(244, 234, 212, 0.13); - --danger: #ff7a5f; + --ink: #080d0c; + --jade: #101d19; + --jade-2: #172923; + --paper: #f5ecd8; + --paper-dim: #c8b99a; + --gold: #d5a855; + --gold-soft: rgba(213, 168, 85, 0.16); + --cyan: #36d8bd; + --cyan-soft: rgba(54, 216, 189, 0.13); + --line: rgba(245, 236, 216, 0.12); + --danger: #ff856d; } * { @@ -26,9 +26,9 @@ body { min-height: 100vh; font-family: "Songti SC", "Noto Serif SC", "STSong", ui-serif, Georgia, serif; background: - radial-gradient(circle at 18% 4%, rgba(214, 169, 88, 0.14), transparent 28rem), - radial-gradient(circle at 82% 12%, rgba(52, 224, 189, 0.12), transparent 24rem), - linear-gradient(135deg, rgba(255, 255, 255, 0.035) 0 1px, transparent 1px 32px), + radial-gradient(circle at 14% 0%, rgba(213, 168, 85, 0.13), transparent 26rem), + radial-gradient(circle at 82% 8%, rgba(54, 216, 189, 0.11), transparent 24rem), + linear-gradient(135deg, rgba(245, 236, 216, 0.035) 0 1px, transparent 1px 34px), var(--ink); } @@ -41,69 +41,6 @@ button { cursor: pointer; } -.shell { - width: min(1180px, calc(100% - 40px)); - margin: 0 auto; - padding: 36px 0 80px; -} - -.hero { - position: relative; - display: grid; - grid-template-columns: 1fr minmax(260px, 420px); - gap: 38px; - min-height: 430px; - padding: 34px; - border: 1px solid var(--line); - border-radius: 28px; - overflow: hidden; - background: - linear-gradient(130deg, rgba(13, 27, 24, 0.96), rgba(9, 15, 14, 0.78)), - radial-gradient(circle at 70% 50%, rgba(52, 224, 189, 0.14), transparent 20rem); - box-shadow: 0 24px 80px rgba(0, 0, 0, 0.3); -} - -.hero::after { - content: ""; - position: absolute; - inset: 22px; - border: 1px solid rgba(214, 169, 88, 0.16); - border-radius: 22px; - pointer-events: none; -} - -.brand { - display: flex; - align-items: center; - gap: 16px; - position: relative; - z-index: 1; -} - -.seal { - display: grid; - width: 58px; - height: 58px; - place-items: center; - border: 1px solid var(--gold); - border-radius: 50%; - color: var(--gold); - font-size: 30px; - box-shadow: 0 0 36px var(--gold-soft); -} - -.kicker, -.eyebrow, -.section-label { - margin: 0; - color: var(--gold); - font-family: ui-sans-serif, system-ui, sans-serif; - font-size: 12px; - font-weight: 700; - letter-spacing: 0.14em; - text-transform: uppercase; -} - h1, h2, h3, @@ -112,86 +49,264 @@ p { margin: 0; } -h1 { - font-size: clamp(34px, 5vw, 64px); +.shell { + width: min(1120px, calc(100% - 40px)); + min-height: 100vh; + margin: 0 auto; + padding: 24px 0 96px; +} + +.app-topbar { + position: sticky; + top: 0; + z-index: 20; + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 0 18px; + backdrop-filter: blur(18px); +} + +.brand { + display: flex; + align-items: center; + gap: 12px; +} + +.compact-button { + border: 0; + color: var(--paper); + text-align: left; + background: transparent; +} + +.seal { + display: grid; + width: 48px; + height: 48px; + place-items: center; + border: 1px solid var(--gold); + border-radius: 50%; + color: var(--gold); + font-size: 24px; + box-shadow: 0 0 30px var(--gold-soft); +} + +.brand small, +.kicker, +.eyebrow, +.section-label { + display: block; + color: var(--gold); + font-family: ui-sans-serif, system-ui, sans-serif; + font-size: 11px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.brand strong { + display: block; + margin-top: 2px; + font-size: 22px; letter-spacing: 0.08em; } -.hero-copy { - align-self: end; - position: relative; - z-index: 1; - max-width: 680px; +.desktop-nav { + display: flex; + gap: 8px; + padding: 6px; + border: 1px solid var(--line); + border-radius: 999px; + background: rgba(8, 13, 12, 0.6); } -.hero-copy h2 { - margin-top: 18px; - font-size: clamp(36px, 6vw, 82px); +.desktop-nav button { + min-width: 78px; + min-height: 38px; + border: 0; + border-radius: 999px; + color: var(--paper-dim); + background: transparent; +} + +.desktop-nav .active { + color: #160f07; + background: linear-gradient(135deg, #f0d38c, var(--gold)); +} + +.job-banner { + position: sticky; + top: 78px; + z-index: 19; + display: flex; + align-items: center; + gap: 12px; + width: 100%; + margin-bottom: 18px; + padding: 14px 16px; + border: 1px solid rgba(54, 216, 189, 0.28); + border-radius: 16px; + color: var(--paper); + text-align: left; + background: rgba(17, 36, 31, 0.94); + box-shadow: 0 14px 42px rgba(0, 0, 0, 0.24); +} + +.job-banner span:nth-child(2) { + flex: 1; + color: var(--paper-dim); +} + +.job-banner strong { + color: var(--cyan); + white-space: nowrap; +} + +.pulse-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--cyan); + box-shadow: 0 0 0 0 rgba(54, 216, 189, 0.55); + animation: pulse 1.4s infinite; +} + +.home-screen { + display: grid; + grid-template-columns: minmax(0, 0.95fr) minmax(420px, 1.05fr); + gap: 28px; + align-items: end; + min-height: calc(100vh - 150px); + padding: 38px; + border: 1px solid var(--line); + border-radius: 28px; + background: + linear-gradient(135deg, rgba(16, 29, 25, 0.96), rgba(8, 13, 12, 0.82)), + repeating-radial-gradient(circle at 82% 34%, rgba(213, 168, 85, 0.12) 0 1px, transparent 1px 26px); + box-shadow: 0 28px 90px rgba(0, 0, 0, 0.32); +} + +.hero-copy { + max-width: 620px; +} + +.hero-copy h1 { + margin-top: 16px; + font-size: clamp(46px, 7vw, 88px); line-height: 1.05; - letter-spacing: 0.02em; + letter-spacing: 0.03em; } .hero-copy p:last-child { - max-width: 620px; - margin-top: 22px; + margin-top: 20px; color: var(--paper-dim); font-size: 18px; line-height: 1.8; } -.hero-orbit { - position: relative; - z-index: 1; - align-self: center; - aspect-ratio: 1; - border: 1px solid rgba(52, 224, 189, 0.32); - border-radius: 50%; - background: - radial-gradient(circle, rgba(52, 224, 189, 0.22), transparent 46%), - repeating-radial-gradient(circle, rgba(214, 169, 88, 0.16) 0 1px, transparent 1px 26px); +.service-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 14px; + align-self: stretch; } -.palm-mark { - position: absolute; - inset: 15%; +.service-card { display: grid; - place-items: center; - border: 1px solid rgba(214, 169, 88, 0.42); - border-radius: 50%; + min-height: 260px; + padding: 20px; + border: 1px solid rgba(213, 168, 85, 0.22); + border-radius: 22px; + color: var(--paper); + text-align: left; + background: + linear-gradient(180deg, rgba(213, 168, 85, 0.12), rgba(54, 216, 189, 0.05)), + rgba(8, 13, 12, 0.42); +} + +.service-card span { + justify-self: start; + align-self: start; + padding: 6px 10px; + border-radius: 999px; color: var(--cyan); - font-size: clamp(80px, 12vw, 150px); - text-shadow: 0 0 32px rgba(52, 224, 189, 0.42); + font: 700 12px ui-sans-serif, system-ui, sans-serif; + background: var(--cyan-soft); +} + +.service-card strong { + align-self: end; + color: var(--gold); + font-size: 44px; + line-height: 1; +} + +.service-card em { + color: var(--paper); + font-size: 20px; + font-style: normal; +} + +.service-card small { + color: var(--paper-dim); + font-size: 14px; + line-height: 1.65; +} + +.service-card.muted { + opacity: 0.68; +} + +.quick-row { + display: flex; + grid-column: 1 / -1; + gap: 12px; } .workspace { display: grid; - grid-template-columns: minmax(0, 1.3fr) minmax(320px, 0.7fr); - gap: 22px; - margin-top: 22px; + grid-template-columns: minmax(0, 1fr) 340px; + gap: 18px; } .upload-card, -.archive-card, -.report-panel { +.side-panel, +.archive-screen, +.report-panel, +.mini-card { border: 1px solid var(--line); border-radius: 24px; - background: rgba(13, 27, 24, 0.82); - box-shadow: 0 18px 54px rgba(0, 0, 0, 0.22); + background: rgba(16, 29, 25, 0.86); + box-shadow: 0 18px 54px rgba(0, 0, 0, 0.24); } .upload-card, -.archive-card { - padding: 26px; +.archive-screen, +.report-panel, +.mini-card { + padding: 24px; } -.upload-card h3, +.side-panel { + display: grid; + gap: 14px; + align-content: start; + border: 0; + background: transparent; + box-shadow: none; +} + +.upload-card h2, +.archive-head h2, .report-panel h3 { margin-top: 10px; font-size: 34px; } -.upload-card > p { - margin-top: 12px; +.upload-card > p, +.mini-card p { + margin-top: 10px; color: var(--paper-dim); line-height: 1.75; } @@ -199,14 +314,14 @@ h1 { .drop-zone { position: relative; display: grid; - min-height: 310px; - margin-top: 22px; + min-height: 360px; + margin-top: 20px; place-items: center; - border: 1px dashed rgba(214, 169, 88, 0.5); + border: 1px dashed rgba(213, 168, 85, 0.5); border-radius: 22px; background: - linear-gradient(135deg, rgba(214, 169, 88, 0.08), transparent), - rgba(9, 15, 14, 0.48); + linear-gradient(135deg, rgba(213, 168, 85, 0.08), rgba(54, 216, 189, 0.04)), + rgba(8, 13, 12, 0.54); overflow: hidden; } @@ -218,31 +333,43 @@ h1 { } .drop-zone span { + display: grid; + gap: 8px; + text-align: center; +} + +.drop-zone strong { color: var(--gold); - font-size: 20px; + font-size: 24px; +} + +.drop-zone small { + color: var(--paper-dim); + font: 600 13px ui-sans-serif, system-ui, sans-serif; } .drop-zone img { width: 100%; height: 100%; - max-height: 420px; + max-height: 460px; object-fit: cover; } .segmented { display: flex; gap: 10px; - margin-top: 18px; + margin-top: 16px; } .segmented button, .primary-action, +.ghost-action, .delete-action { min-height: 46px; border: 1px solid var(--line); border-radius: 999px; color: var(--paper); - background: rgba(255, 255, 255, 0.04); + background: rgba(245, 236, 216, 0.045); } .segmented button { @@ -251,45 +378,55 @@ h1 { .segmented .active, .primary-action { - border-color: rgba(214, 169, 88, 0.8); - color: #1a1208; - background: linear-gradient(135deg, #f1d28c, var(--gold)); + border-color: rgba(213, 168, 85, 0.82); + color: #180f06; + font-weight: 700; + background: linear-gradient(135deg, #f0d38c, var(--gold)); } -.primary-action { +.primary-action, +.ghost-action { + padding: 0 22px; +} + +.upload-card .primary-action { width: 100%; - margin-top: 18px; - font-weight: 700; + margin-top: 16px; } .primary-action:disabled { - opacity: 0.7; + opacity: 0.72; cursor: wait; } +.ghost-action { + border-color: rgba(54, 216, 189, 0.24); + color: var(--cyan); +} + .error { margin-top: 14px; color: var(--danger); + line-height: 1.6; } .stats { display: grid; grid-template-columns: 1fr 1fr; - gap: 12px; - margin-top: 18px; + gap: 10px; } .stat { - padding: 18px; + padding: 16px; border: 1px solid var(--line); border-radius: 18px; - background: rgba(255, 255, 255, 0.035); + background: rgba(245, 236, 216, 0.04); } .stat strong { display: block; color: var(--cyan); - font-size: 34px; + font-size: 32px; } .stat span, @@ -297,35 +434,51 @@ h1 { color: var(--paper-dim); } +.archive-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 18px; +} + .report-list { display: grid; gap: 12px; - margin-top: 18px; } .archive-item { display: flex; justify-content: space-between; - gap: 16px; + gap: 18px; width: 100%; padding: 16px; border: 1px solid var(--line); border-radius: 18px; color: var(--paper); text-align: left; - background: rgba(9, 15, 14, 0.44); + background: rgba(8, 13, 12, 0.44); } .archive-item strong, -.archive-item small { +.archive-item small, +.archive-item b { display: block; } .archive-item small { - margin-top: 6px; + margin-top: 5px; color: var(--paper-dim); } +.archive-item b { + max-width: 760px; + margin-top: 8px; + color: rgba(245, 236, 216, 0.76); + font-weight: 400; + line-height: 1.55; +} + .archive-item em { flex: 0 0 auto; color: var(--cyan); @@ -334,40 +487,38 @@ h1 { } .report-panel { - margin-top: 22px; - padding: 28px; + margin-top: 18px; } .report-hero { display: grid; - grid-template-columns: 1fr 180px; - gap: 24px; - padding: 26px; - border: 1px solid rgba(214, 169, 88, 0.22); - border-radius: 22px; - background: linear-gradient(135deg, rgba(214, 169, 88, 0.1), rgba(52, 224, 189, 0.06)); + grid-template-columns: 1fr 160px; + gap: 22px; + padding: 22px; + border: 1px solid rgba(213, 168, 85, 0.22); + border-radius: 20px; + background: linear-gradient(135deg, rgba(213, 168, 85, 0.1), rgba(54, 216, 189, 0.06)); } .report-hero p:not(.section-label) { - margin-top: 16px; + margin-top: 14px; color: var(--paper-dim); - font-size: 18px; - line-height: 1.85; + font-size: 17px; + line-height: 1.8; } .score { display: grid; place-content: center; - border: 1px solid rgba(52, 224, 189, 0.3); - border-radius: 50%; aspect-ratio: 1; + border: 1px solid rgba(54, 216, 189, 0.3); + border-radius: 50%; color: var(--cyan); text-align: center; - box-shadow: inset 0 0 40px rgba(52, 224, 189, 0.09); } .score strong { - font-size: 64px; + font-size: 56px; line-height: 1; } @@ -379,14 +530,14 @@ h1 { .observations { display: flex; flex-wrap: wrap; - gap: 10px; - margin-top: 18px; + gap: 9px; + margin-top: 16px; } .keywords span, .observations span { - padding: 8px 13px; - border: 1px solid rgba(52, 224, 189, 0.24); + padding: 7px 12px; + border: 1px solid rgba(54, 216, 189, 0.24); border-radius: 999px; color: var(--cyan); background: var(--cyan-soft); @@ -395,34 +546,34 @@ h1 { .dimension-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 16px; - margin-top: 22px; + gap: 14px; + margin-top: 18px; } .dimension-card, .summary-list { border: 1px solid var(--line); - border-radius: 20px; - background: rgba(9, 15, 14, 0.38); + border-radius: 18px; + background: rgba(8, 13, 12, 0.38); } .dimension-card { - padding: 20px; + padding: 18px; } .dimension-head { display: grid; grid-template-columns: auto 1fr auto; align-items: center; - gap: 12px; + gap: 10px; } .dimension-head span { - color: rgba(244, 234, 212, 0.4); + color: rgba(245, 236, 216, 0.42); } .dimension-head strong { - font-size: 22px; + font-size: 21px; } .dimension-head em { @@ -431,28 +582,29 @@ h1 { } .dimension-card p { - margin-top: 14px; + margin-top: 12px; color: var(--paper-dim); - line-height: 1.75; + line-height: 1.72; } .advice { - margin-top: 16px; - padding: 14px; - border-radius: 16px; + margin-top: 14px; + padding: 13px; + border-radius: 14px; color: #f0d9a6; + line-height: 1.65; background: var(--gold-soft); } .summary-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 16px; - margin-top: 18px; + gap: 14px; + margin-top: 16px; } .summary-list { - padding: 18px; + padding: 16px; } .summary-list h4 { @@ -461,14 +613,14 @@ h1 { } .summary-list p { - margin-top: 12px; + margin-top: 10px; color: var(--paper-dim); line-height: 1.65; } .disclaimer { - margin-top: 18px; - color: rgba(244, 234, 212, 0.56); + margin-top: 16px; + color: rgba(245, 236, 216, 0.56); line-height: 1.7; } @@ -478,130 +630,187 @@ h1 { color: var(--danger); } -@media (max-width: 860px) { - .shell { - width: min(100% - 24px, 1180px); - padding-top: 16px; - } +.mobile-tabbar { + display: none; +} - .hero, +@keyframes pulse { + 70% { + box-shadow: 0 0 0 10px rgba(54, 216, 189, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(54, 216, 189, 0); + } +} + +@media (max-width: 920px) { + .home-screen, .workspace, .report-hero, .dimension-grid, - .summary-grid, .summary-grid { grid-template-columns: 1fr; } - .hero { + .home-screen { min-height: auto; - padding: 24px; } - .hero-orbit { - max-width: 300px; - margin: 0 auto; - } - - .report-hero { - padding: 20px; + .service-grid { + grid-template-columns: repeat(3, minmax(180px, 1fr)); + overflow-x: auto; + padding-bottom: 4px; } .score { - width: 160px; + width: 150px; } } @media (max-width: 640px) { body { background: - radial-gradient(circle at 20% 0%, rgba(214, 169, 88, 0.14), transparent 18rem), - radial-gradient(circle at 88% 6%, rgba(52, 224, 189, 0.1), transparent 16rem), + radial-gradient(circle at 16% 0%, rgba(213, 168, 85, 0.12), transparent 16rem), + radial-gradient(circle at 86% 4%, rgba(54, 216, 189, 0.1), transparent 14rem), var(--ink); } .shell { width: 100%; - padding: 0 12px 48px; + padding: 0 12px 82px; } - .hero { - border-left: 0; - border-right: 0; - border-radius: 0 0 24px 24px; - gap: 22px; - padding: 20px 16px 22px; + .app-topbar { + padding: 12px 4px; } - .hero::after { - inset: 12px; - border-radius: 18px; - } - - .brand { - gap: 12px; + .desktop-nav { + display: none; } .seal { - width: 48px; - height: 48px; - font-size: 24px; + width: 42px; + height: 42px; + font-size: 20px; } - h1 { - font-size: 34px; + .brand strong { + font-size: 19px; } - .hero-copy h2 { - margin-top: 12px; - font-size: clamp(34px, 11vw, 48px); + .job-banner { + top: 66px; + margin: 0 0 12px; + padding: 12px; + border-radius: 14px; + font-size: 13px; + } + + .job-banner strong { + display: none; + } + + .home-screen { + display: flex; + min-height: calc(100vh - 154px); + flex-direction: column; + justify-content: space-between; + gap: 18px; + padding: 22px 16px; + border-radius: 24px; + } + + .hero-copy h1 { + margin-top: 14px; + font-size: 42px; line-height: 1.12; } .hero-copy p:last-child { margin-top: 14px; font-size: 15px; - line-height: 1.7; + line-height: 1.72; } - .hero-orbit { - max-width: 220px; + .service-grid { + display: grid; + grid-template-columns: 1fr; + gap: 10px; + overflow: visible; } - .palm-mark { - font-size: 82px; + .service-card { + min-height: auto; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 12px; + padding: 15px; + border-radius: 18px; + } + + .service-card span { + grid-column: 3; + grid-row: 1 / 3; + } + + .service-card strong { + grid-column: 1; + grid-row: 1 / 3; + font-size: 30px; + } + + .service-card em, + .service-card small { + grid-column: 2; + } + + .service-card small { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + } + + .quick-row { + display: grid; + grid-template-columns: 1fr 1fr; } .workspace { - gap: 14px; - margin-top: 14px; - padding: 0 12px; + gap: 12px; } .upload-card, - .archive-card, - .report-panel { + .archive-screen, + .report-panel, + .mini-card { + padding: 16px; border-radius: 20px; - padding: 18px; } - .upload-card h3, + .side-panel { + gap: 12px; + } + + .upload-card h2, + .archive-head h2, .report-panel h3 { - font-size: 27px; + font-size: 28px; } - .upload-card > p { + .upload-card > p, + .mini-card p { font-size: 14px; } .drop-zone { - min-height: 220px; + min-height: 250px; margin-top: 16px; border-radius: 18px; } .drop-zone img { - max-height: 280px; + max-height: 320px; } .segmented { @@ -610,30 +819,27 @@ h1 { .segmented button, .primary-action, + .ghost-action, .delete-action { min-height: 44px; font-size: 14px; } - .stats { - gap: 10px; + .archive-head { + align-items: flex-start; } - .stat { - padding: 14px; - } - - .stat strong { - font-size: 28px; + .archive-head .ghost-action { + padding: 0 14px; + white-space: nowrap; } .archive-item { - align-items: flex-start; padding: 14px; } - .archive-item strong { - font-size: 15px; + .archive-item b { + display: none; } .archive-item small, @@ -642,41 +848,31 @@ h1 { } .report-panel { - margin: 14px 12px 0; + margin-top: 12px; } .report-hero { - gap: 18px; + gap: 16px; + padding: 16px; + border-radius: 18px; } .report-hero p:not(.section-label) { font-size: 15px; - line-height: 1.75; + line-height: 1.72; } .score { - width: 132px; - justify-self: start; + width: 126px; } .score strong { - font-size: 48px; - } - - .keywords span, - .observations span { - padding: 7px 11px; - font-size: 13px; - } - - .dimension-grid { - gap: 12px; + font-size: 46px; } .dimension-card, .summary-list { - padding: 16px; - border-radius: 18px; + padding: 15px; } .dimension-head { @@ -689,15 +885,50 @@ h1 { font-size: 13px; } - .dimension-head strong { - font-size: 20px; - } - .dimension-card p, .summary-list p, .advice, .disclaimer { font-size: 14px; - line-height: 1.72; + line-height: 1.7; + } + + .mobile-tabbar { + position: fixed; + right: 12px; + bottom: 12px; + left: 12px; + z-index: 30; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 6px; + padding: 8px; + border: 1px solid var(--line); + border-radius: 22px; + background: rgba(8, 13, 12, 0.92); + box-shadow: 0 18px 46px rgba(0, 0, 0, 0.36); + backdrop-filter: blur(18px); + } + + .mobile-tabbar button { + display: grid; + gap: 2px; + min-height: 48px; + place-items: center; + border: 0; + border-radius: 16px; + color: var(--paper-dim); + background: transparent; + font: 700 12px ui-sans-serif, system-ui, sans-serif; + } + + .mobile-tabbar span { + font-family: "Songti SC", "STSong", ui-serif, Georgia, serif; + font-size: 18px; + } + + .mobile-tabbar .active { + color: #160f07; + background: linear-gradient(135deg, #f0d38c, var(--gold)); } } diff --git a/web/components/PalmWebApp.tsx b/web/components/PalmWebApp.tsx index 8b4598a..d1544d5 100644 --- a/web/components/PalmWebApp.tsx +++ b/web/components/PalmWebApp.tsx @@ -25,6 +25,30 @@ const statusText: Record = { failed: "失败", }; +const services = [ + { + id: "palm", + name: "手相", + title: "掌心解读", + description: "上传掌心照片,生成生活、学习、事业与关系提醒。", + status: "已开放", + }, + { + id: "face", + name: "面相", + title: "气色与五官", + description: "未来支持面部特征与状态观察,适合做日常运势入口。", + status: "规划中", + }, + { + id: "bazi", + name: "八字", + title: "生辰格局", + description: "未来支持出生信息推演,沉淀长期个人档案。", + status: "规划中", + }, +]; + export default function PalmWebApp() { const [ready, setReady] = useState(false); const [file, setFile] = useState(null); @@ -34,6 +58,8 @@ export default function PalmWebApp() { const [error, setError] = useState(""); const [reports, setReports] = useState([]); const [activeReport, setActiveReport] = useState(null); + const [activeView, setActiveView] = useState<"home" | "palm" | "archive">("home"); + const [activeJobId, setActiveJobId] = useState(""); useEffect(() => { ensureToken() @@ -45,7 +71,15 @@ export default function PalmWebApp() { }); }, []); + useEffect(() => { + return () => { + if (preview) URL.revokeObjectURL(preview); + }; + }, [preview]); + const completedReports = reports.filter((item) => item.status === "completed").length; + const latestReport = reports[0]; + const hasActiveJob = Boolean(activeJobId); function onPickFile(event: ChangeEvent) { const selected = event.target.files?.[0]; @@ -70,16 +104,22 @@ export default function PalmWebApp() { setError(""); try { const upload = await uploadPalmImage(file); - setBusyText("先生正在解读掌纹..."); const report = await apiFetch("/reports", { method: "POST", body: JSON.stringify({ image_id: upload.image_id, hand_side: handSide }), }); - await pollReport(report.id); + setActiveReport(report); + setActiveJobId(report.id); + setBusyText(""); + setFile(null); + setPreview(""); + void pollReport(report.id) + .then(() => loadReports()) + .catch((err) => setError(err instanceof Error ? err.message : "报告生成失败")) + .finally(() => setActiveJobId("")); await loadReports(); } catch (err) { setError(err instanceof Error ? err.message : "生成失败"); - } finally { setBusyText(""); } } @@ -110,64 +150,127 @@ export default function PalmWebApp() { return (
-
-
- -
-

CYBER MISTER

-

赛博先生

+
+ + +
+ + {hasActiveJob ? ( + + ) : null} + + {activeView === "home" ? ( +
+
+

AI 玄学档案 · 娱乐占卜 · 自我反思

+

把日常困惑,交给先生慢慢看。

+

从手相开始,逐步扩展到面相、八字与个人长期档案。每一次解读都更贴近生活、学习、事业与关系。

-
-
-

AI 手相报告 · 娱乐占卜 · 自我反思

-

把掌心里的线索,翻译成更贴近日常的提醒。

-

- 上传一张清晰掌心照片,生成面向生活、学习、事业与关系的手相报告。高级东方玄学的仪式感,配上现代 AI 的分析速度。 -

-
- -
-
-
-
01 · 请先生看掌
-

上传掌心照片

-

掌心完整入镜、光线充足、纹路清晰。左右手不知道也没关系。

- - - -
- {(["left", "right", "unknown"] as HandSide[]).map((side) => ( - ))}
- - {error ?

{error}

: null} -
+
+ + +
+
+ ) : null} -
-
02 · 解读档案
-
- - + {activeView === "palm" ? ( +
+
+
PALM READING
+

上传掌心照片

+

掌心完整入镜、光线充足、纹路清晰。左右手不确定可以选“不确定”。

+ + + +
+ {(["left", "right", "unknown"] as HandSide[]).map((side) => ( + + ))} +
+ + + {error ?

{error}

: null} +
+ + +
+ ) : null} + + {activeView === "archive" ? ( +
+
+
+

MISTER ARCHIVE

+

个人玄学档案

+
+
{reports.length ? ( - reports.slice(0, 6).map((item) => ( + reports.map((item) => ( @@ -176,8 +279,8 @@ export default function PalmWebApp() {

暂无档案。生成第一份报告后会出现在这里。

)}
-
- + + ) : null} {activeReport ? ( deleteReport(activeReport.id)} /> ) : null} + +
); }