Browse Source
Refactor video pages to use a shared VideoStepScreen component; streamline UI components and improve code organization
master
Refactor video pages to use a shared VideoStepScreen component; streamline UI components and improve code organization
master
13 changed files with 1404 additions and 1089 deletions
-
165src/app/details/[section]/detail-section-client.tsx
-
2src/app/details/[section]/page.tsx
-
49src/app/details/complete/page.tsx
-
166src/app/details/page.tsx
-
355src/app/globals.css
-
24src/app/intro/page.tsx
-
62src/app/page.tsx
-
176src/app/questions/page.tsx
-
184src/app/rules/page.tsx
-
236src/app/video-2/page.tsx
-
236src/app/video/page.tsx
-
202src/components/screens/video-step-screen.tsx
-
214src/components/ui/fabric-mobile.tsx
@ -1,37 +1,46 @@ |
|||||
import Link from "next/link"; |
import Link from "next/link"; |
||||
|
import { |
||||
|
CheckIcon, |
||||
|
FabricCard, |
||||
|
FabricPill, |
||||
|
FabricScreen, |
||||
|
FabricStatusBar, |
||||
|
fabricSecondaryButtonClass, |
||||
|
} from "@/components/ui/fabric-mobile"; |
||||
|
|
||||
export default function DetailedQuestionsCompletePage() { |
export default function DetailedQuestionsCompletePage() { |
||||
return ( |
return ( |
||||
<main className="flex min-h-screen items-center justify-center bg-[#444446] p-2 sm:p-4"> |
|
||||
<section className="relative aspect-[375/813] w-full max-w-[375px] overflow-hidden rounded-[24px] bg-[#FCF8F7] shadow-[0_30px_70px_rgba(0,0,0,0.34)]"> |
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top,#FFBDD2_0%,rgba(255,189,210,0.24)_14%,rgba(255,255,255,0)_36%),linear-gradient(180deg,#FFFDFD_0%,#FAF4F7_100%)]" /> |
|
||||
|
<FabricScreen contentClassName="justify-center"> |
||||
|
<FabricStatusBar /> |
||||
|
|
||||
<div className="relative z-10 flex h-full flex-col items-center justify-center px-7 text-center"> |
|
||||
<div className="flex h-24 w-24 items-center justify-center rounded-full bg-linear-to-r from-[#F04C99] to-[#FF8575] text-[34px] font-semibold text-white shadow-[0_24px_44px_rgba(240,76,153,0.25)]"> |
|
||||
✓ |
|
||||
|
<div className="mt-auto flex flex-col items-center text-center"> |
||||
|
<div className="flex h-24 w-24 items-center justify-center rounded-full bg-[linear-gradient(135deg,#AF5568_0%,#D6765C_100%)] text-white shadow-[0_24px_44px_rgba(175,85,104,0.25)]"> |
||||
|
<CheckIcon /> |
||||
</div> |
</div> |
||||
|
|
||||
<h1 |
|
||||
className="mt-10 text-[32px] font-semibold leading-[1.15] text-[#2E2327]" |
|
||||
style={{ fontFamily: "Georgia, Times New Roman, serif" }} |
|
||||
> |
|
||||
|
<FabricPill className="mt-6">All sections complete</FabricPill> |
||||
|
|
||||
|
<h1 className="fabric-display mt-6 text-[34px] leading-[1.06] text-[#2E2327]"> |
||||
Your profile details are complete |
Your profile details are complete |
||||
</h1> |
</h1> |
||||
|
|
||||
<p className="mt-5 max-w-[280px] text-[16px] leading-8 text-[#665D63]"> |
|
||||
We have received all of the required information. Our review may |
|
||||
take a little time, and we will notify you as soon as there is an |
|
||||
update. |
|
||||
|
<p className="mt-5 max-w-[290px] text-[16px] leading-8 text-[#665D63]"> |
||||
|
We have received all of the required information. Our review may take |
||||
|
a little time, and we will notify you as soon as there is an update. |
||||
|
</p> |
||||
|
|
||||
|
<FabricCard className="mt-8 w-full px-5 py-5 text-left"> |
||||
|
<p className="fabric-kicker">What happens next</p> |
||||
|
<p className="mt-3 text-[14px] leading-7 text-[#665953]"> |
||||
|
Your answers stay attached to this intake flow, and the team can now |
||||
|
review the completed profile as one unified submission. |
||||
</p> |
</p> |
||||
|
</FabricCard> |
||||
|
|
||||
<Link |
|
||||
className="mt-12 flex h-[48px] w-full max-w-[290px] items-center justify-center rounded-[14px] bg-[#242424] text-[18px] font-semibold text-white shadow-[0_14px_30px_rgba(0,0,0,0.18)]" |
|
||||
href="/" |
|
||||
> |
|
||||
|
<Link className={`${fabricSecondaryButtonClass} mt-8`} href="/"> |
||||
Return Home |
Return Home |
||||
</Link> |
</Link> |
||||
</div> |
</div> |
||||
</section> |
|
||||
</main> |
|
||||
|
</FabricScreen> |
||||
); |
); |
||||
} |
} |
||||
@ -1,5 +1,360 @@ |
|||||
@import "tailwindcss"; |
@import "tailwindcss"; |
||||
|
|
||||
|
:root { |
||||
|
--fabric-display: |
||||
|
"Iowan Old Style", "Palatino Linotype", "Book Antiqua", Georgia, serif; |
||||
|
--fabric-body: "Aptos", "Trebuchet MS", "Segoe UI", sans-serif; |
||||
|
--fabric-ink: #30211d; |
||||
|
--fabric-muted: #6f5e58; |
||||
|
--fabric-rose: #af5568; |
||||
|
--fabric-rust: #d6765c; |
||||
|
--fabric-paper: #fff9f4; |
||||
|
--fabric-paper-soft: #fff4ed; |
||||
|
--fabric-shell: #f2dfd0; |
||||
|
--fabric-stroke: #e4d0c4; |
||||
|
} |
||||
|
|
||||
body { |
body { |
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; |
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; |
||||
} |
} |
||||
|
|
||||
|
.fabric-body { |
||||
|
color: var(--fabric-ink); |
||||
|
font-family: var(--fabric-body); |
||||
|
} |
||||
|
|
||||
|
.fabric-display { |
||||
|
font-family: var(--fabric-display); |
||||
|
} |
||||
|
|
||||
|
.fabric-stage { |
||||
|
align-items: center; |
||||
|
background: radial-gradient( |
||||
|
circle at top, |
||||
|
#6d5b58 0%, |
||||
|
#514544 42%, |
||||
|
#392f31 100% |
||||
|
); |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
min-height: 100vh; |
||||
|
padding: 0.75rem; |
||||
|
} |
||||
|
|
||||
|
.fabric-phone { |
||||
|
aspect-ratio: 375 / 813; |
||||
|
background: linear-gradient(180deg, #f8eee5 0%, #f0ddcf 100%); |
||||
|
border: 1px solid rgb(255 255 255 / 15%); |
||||
|
border-radius: 32px; |
||||
|
box-shadow: 0 32px 90px rgb(25 12 14 / 42%); |
||||
|
max-width: 390px; |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.fabric-phone::before { |
||||
|
background: |
||||
|
radial-gradient( |
||||
|
circle at 15% 0%, |
||||
|
rgb(255 255 255 / 82%) 0%, |
||||
|
rgb(255 255 255 / 0%) 28% |
||||
|
), |
||||
|
radial-gradient( |
||||
|
circle at 100% 85%, |
||||
|
rgb(212 137 110 / 18%) 0%, |
||||
|
rgb(212 137 110 / 0%) 28% |
||||
|
), |
||||
|
linear-gradient( |
||||
|
180deg, |
||||
|
rgb(255 255 255 / 40%) 0%, |
||||
|
rgb(255 255 255 / 0%) 30%, |
||||
|
rgb(157 107 90 / 8%) 100% |
||||
|
); |
||||
|
content: ""; |
||||
|
inset: 0; |
||||
|
position: absolute; |
||||
|
} |
||||
|
|
||||
|
.fabric-phone::after { |
||||
|
background-image: |
||||
|
repeating-linear-gradient( |
||||
|
0deg, |
||||
|
rgb(157 117 99 / 18%) 0 1px, |
||||
|
transparent 1px 12px |
||||
|
), |
||||
|
repeating-linear-gradient( |
||||
|
90deg, |
||||
|
rgb(255 255 255 / 28%) 0 1px, |
||||
|
transparent 1px 16px |
||||
|
); |
||||
|
content: ""; |
||||
|
inset: 0; |
||||
|
mix-blend-mode: soft-light; |
||||
|
opacity: 0.24; |
||||
|
position: absolute; |
||||
|
} |
||||
|
|
||||
|
.fabric-screen { |
||||
|
background: linear-gradient( |
||||
|
180deg, |
||||
|
rgb(255 252 248 / 72%) 0%, |
||||
|
rgb(246 236 226 / 62%) 100% |
||||
|
); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100%; |
||||
|
padding: 1.25rem; |
||||
|
position: relative; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
.fabric-status { |
||||
|
align-items: center; |
||||
|
color: var(--fabric-ink); |
||||
|
display: flex; |
||||
|
font-size: 0.875rem; |
||||
|
font-weight: 600; |
||||
|
justify-content: space-between; |
||||
|
letter-spacing: 0.01em; |
||||
|
padding: 0 0.4rem; |
||||
|
} |
||||
|
|
||||
|
.fabric-signal { |
||||
|
align-items: center; |
||||
|
display: flex; |
||||
|
gap: 0.35rem; |
||||
|
opacity: 0.85; |
||||
|
} |
||||
|
|
||||
|
.fabric-signal-bar { |
||||
|
border: 1px solid currentcolor; |
||||
|
border-radius: 0.3rem; |
||||
|
display: block; |
||||
|
height: 0.45rem; |
||||
|
width: 1.15rem; |
||||
|
} |
||||
|
|
||||
|
.fabric-signal-dot { |
||||
|
background: currentcolor; |
||||
|
border-radius: 999px; |
||||
|
display: block; |
||||
|
height: 0.45rem; |
||||
|
width: 0.45rem; |
||||
|
} |
||||
|
|
||||
|
.fabric-nav-button { |
||||
|
align-items: center; |
||||
|
backdrop-filter: blur(8px); |
||||
|
background: linear-gradient( |
||||
|
180deg, |
||||
|
rgb(255 255 255 / 92%) 0%, |
||||
|
rgb(255 247 243 / 78%) 100% |
||||
|
); |
||||
|
border: 1px solid rgb(255 255 255 / 72%); |
||||
|
border-radius: 1.15rem; |
||||
|
box-shadow: 0 14px 28px rgb(79 48 38 / 12%); |
||||
|
color: var(--fabric-ink); |
||||
|
display: flex; |
||||
|
height: 2.85rem; |
||||
|
justify-content: center; |
||||
|
transition: |
||||
|
box-shadow 180ms ease, |
||||
|
opacity 180ms ease, |
||||
|
transform 180ms ease; |
||||
|
width: 2.85rem; |
||||
|
} |
||||
|
|
||||
|
.fabric-nav-button:hover { |
||||
|
box-shadow: 0 18px 32px rgb(79 48 38 / 16%); |
||||
|
transform: translateY(-1px); |
||||
|
} |
||||
|
|
||||
|
.fabric-nav-button:disabled { |
||||
|
box-shadow: none; |
||||
|
opacity: 0.38; |
||||
|
transform: none; |
||||
|
} |
||||
|
|
||||
|
.fabric-card { |
||||
|
backdrop-filter: blur(8px); |
||||
|
background: linear-gradient( |
||||
|
180deg, |
||||
|
rgb(255 255 255 / 94%) 0%, |
||||
|
rgb(255 249 244 / 84%) 100% |
||||
|
); |
||||
|
border: 1px solid rgb(255 255 255 / 70%); |
||||
|
border-radius: 1.75rem; |
||||
|
box-shadow: 0 20px 45px rgb(99 63 50 / 13%); |
||||
|
} |
||||
|
|
||||
|
.fabric-muted-panel { |
||||
|
background: rgb(251 244 239 / 80%); |
||||
|
border: 1px solid rgb(228 208 196 / 80%); |
||||
|
border-radius: 1.35rem; |
||||
|
box-shadow: inset 0 1px 0 rgb(255 255 255 / 55%); |
||||
|
} |
||||
|
|
||||
|
.fabric-kicker { |
||||
|
color: #a05d63; |
||||
|
font-size: 0.72rem; |
||||
|
font-weight: 700; |
||||
|
letter-spacing: 0.22em; |
||||
|
text-transform: uppercase; |
||||
|
} |
||||
|
|
||||
|
.fabric-pill { |
||||
|
align-items: center; |
||||
|
background: #f6e4dd; |
||||
|
border-radius: 999px; |
||||
|
color: #99535b; |
||||
|
display: inline-flex; |
||||
|
font-size: 0.75rem; |
||||
|
font-weight: 700; |
||||
|
gap: 0.35rem; |
||||
|
letter-spacing: 0.08em; |
||||
|
padding: 0.45rem 0.85rem; |
||||
|
text-transform: uppercase; |
||||
|
} |
||||
|
|
||||
|
.fabric-progress-track { |
||||
|
background: #ead4ca; |
||||
|
border-radius: 999px; |
||||
|
height: 0.55rem; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.fabric-progress-fill { |
||||
|
background: linear-gradient( |
||||
|
135deg, |
||||
|
var(--fabric-rose) 0%, |
||||
|
var(--fabric-rust) 100% |
||||
|
); |
||||
|
border-radius: 999px; |
||||
|
box-shadow: 0 4px 12px rgb(175 85 104 / 32%); |
||||
|
height: 100%; |
||||
|
transition: width 260ms ease; |
||||
|
} |
||||
|
|
||||
|
.fabric-input { |
||||
|
background: linear-gradient(180deg, #fffcfa 0%, #fff7f1 100%); |
||||
|
border: 1px solid var(--fabric-stroke); |
||||
|
border-radius: 1.25rem; |
||||
|
box-shadow: |
||||
|
inset 0 1px 0 rgb(255 255 255 / 85%), |
||||
|
0 10px 24px rgb(121 84 70 / 6%); |
||||
|
color: var(--fabric-ink); |
||||
|
font-family: var(--fabric-body); |
||||
|
font-size: 1rem; |
||||
|
min-height: 3.6rem; |
||||
|
outline: none; |
||||
|
padding: 0 1rem; |
||||
|
transition: |
||||
|
border-color 180ms ease, |
||||
|
box-shadow 180ms ease, |
||||
|
transform 180ms ease; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.fabric-input::placeholder { |
||||
|
color: #9e8a83; |
||||
|
} |
||||
|
|
||||
|
.fabric-input:focus, |
||||
|
.fabric-textarea:focus { |
||||
|
border-color: var(--fabric-rose); |
||||
|
box-shadow: |
||||
|
inset 0 1px 0 rgb(255 255 255 / 85%), |
||||
|
0 0 0 4px rgb(245 213 205 / 60%), |
||||
|
0 12px 28px rgb(121 84 70 / 8%); |
||||
|
} |
||||
|
|
||||
|
.fabric-textarea { |
||||
|
line-height: 1.7; |
||||
|
min-height: 9.5rem; |
||||
|
padding: 1rem; |
||||
|
resize: none; |
||||
|
} |
||||
|
|
||||
|
.fabric-primary-button, |
||||
|
.fabric-secondary-button { |
||||
|
align-items: center; |
||||
|
border-radius: 1rem; |
||||
|
display: flex; |
||||
|
font-family: var(--fabric-body); |
||||
|
font-size: 1.05rem; |
||||
|
font-weight: 700; |
||||
|
justify-content: center; |
||||
|
min-height: 3.25rem; |
||||
|
transition: |
||||
|
box-shadow 180ms ease, |
||||
|
filter 180ms ease, |
||||
|
opacity 180ms ease, |
||||
|
transform 180ms ease; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.fabric-primary-button { |
||||
|
background: linear-gradient( |
||||
|
135deg, |
||||
|
var(--fabric-rose) 0%, |
||||
|
var(--fabric-rust) 100% |
||||
|
); |
||||
|
box-shadow: 0 18px 36px rgb(175 85 104 / 28%); |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.fabric-primary-button:hover, |
||||
|
.fabric-secondary-button:hover { |
||||
|
transform: translateY(-1px); |
||||
|
} |
||||
|
|
||||
|
.fabric-primary-button:disabled, |
||||
|
.fabric-secondary-button:disabled { |
||||
|
box-shadow: none; |
||||
|
filter: saturate(0.7); |
||||
|
opacity: 0.52; |
||||
|
transform: none; |
||||
|
} |
||||
|
|
||||
|
.fabric-secondary-button { |
||||
|
background: #2f241f; |
||||
|
box-shadow: 0 14px 30px rgb(47 36 31 / 20%); |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.fabric-link-disabled { |
||||
|
filter: saturate(0.7); |
||||
|
opacity: 0.48; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.fabric-scroll { |
||||
|
scrollbar-width: none; |
||||
|
} |
||||
|
|
||||
|
.fabric-scroll::-webkit-scrollbar { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.fabric-accent-dot { |
||||
|
background: linear-gradient( |
||||
|
135deg, |
||||
|
var(--fabric-rose) 0%, |
||||
|
var(--fabric-rust) 100% |
||||
|
); |
||||
|
border-radius: 999px; |
||||
|
box-shadow: 0 0 0 6px rgb(245 213 205 / 50%); |
||||
|
height: 0.55rem; |
||||
|
width: 0.55rem; |
||||
|
} |
||||
|
|
||||
|
.fabric-divider { |
||||
|
background: linear-gradient( |
||||
|
90deg, |
||||
|
rgb(190 160 146 / 0%) 0%, |
||||
|
rgb(190 160 146 / 70%) 50%, |
||||
|
rgb(190 160 146 / 0%) 100% |
||||
|
); |
||||
|
height: 1px; |
||||
|
} |
||||
@ -1,237 +1,11 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import Image from "next/image"; |
|
||||
import Link from "next/link"; |
|
||||
import { useEffect, useState } from "react"; |
|
||||
|
|
||||
function BackIcon() { |
|
||||
return ( |
|
||||
<svg |
|
||||
aria-hidden="true" |
|
||||
fill="none" |
|
||||
height="18" |
|
||||
viewBox="0 0 18 18" |
|
||||
width="18" |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
> |
|
||||
<path |
|
||||
d="M10.5 4.5 6 9l4.5 4.5" |
|
||||
stroke="currentColor" |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth="1.8" |
|
||||
/> |
|
||||
</svg> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
function HistoryIcon() { |
|
||||
return ( |
|
||||
<svg |
|
||||
aria-hidden="true" |
|
||||
fill="none" |
|
||||
height="18" |
|
||||
viewBox="0 0 18 18" |
|
||||
width="18" |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
> |
|
||||
<path |
|
||||
d="M3.75 8.25a5.25 5.25 0 1 1 1.539 3.711" |
|
||||
stroke="currentColor" |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth="1.6" |
|
||||
/> |
|
||||
<path |
|
||||
d="M3.75 4.5v3.75H7.5" |
|
||||
stroke="currentColor" |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth="1.6" |
|
||||
/> |
|
||||
</svg> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
function PlayIcon() { |
|
||||
return ( |
|
||||
<svg |
|
||||
aria-hidden="true" |
|
||||
fill="none" |
|
||||
height="28" |
|
||||
viewBox="0 0 28 28" |
|
||||
width="28" |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
> |
|
||||
<path d="M10 8.75 20 14l-10 5.25V8.75Z" fill="white" /> |
|
||||
</svg> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
const metrics = [ |
|
||||
{ label: "marriage applicants", value: "120" }, |
|
||||
{ label: "Successful marriage", value: "120" }, |
|
||||
]; |
|
||||
|
|
||||
const mockVideoDuration = 12; |
|
||||
|
import VideoStepScreen from "@/components/screens/video-step-screen"; |
||||
|
|
||||
export default function VideoPageTwo() { |
export default function VideoPageTwo() { |
||||
const [isPlaying, setIsPlaying] = useState(false); |
|
||||
const [watchedSeconds, setWatchedSeconds] = useState(0); |
|
||||
const isCompleted = watchedSeconds >= mockVideoDuration; |
|
||||
const progressPercent = Math.min( |
|
||||
(watchedSeconds / mockVideoDuration) * 100, |
|
||||
100, |
|
||||
); |
|
||||
|
|
||||
useEffect(() => { |
|
||||
if (!isPlaying || isCompleted) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const timer = window.setInterval(() => { |
|
||||
setWatchedSeconds((currentValue) => { |
|
||||
const nextValue = Math.min(currentValue + 1, mockVideoDuration); |
|
||||
|
|
||||
if (nextValue >= mockVideoDuration) { |
|
||||
setIsPlaying(false); |
|
||||
} |
|
||||
|
|
||||
return nextValue; |
|
||||
}); |
|
||||
}, 1000); |
|
||||
|
|
||||
return () => window.clearInterval(timer); |
|
||||
}, [isCompleted, isPlaying]); |
|
||||
|
|
||||
const handlePlay = () => { |
|
||||
if (isCompleted) { |
|
||||
setWatchedSeconds(0); |
|
||||
} |
|
||||
|
|
||||
setIsPlaying((currentValue) => !currentValue || isCompleted); |
|
||||
}; |
|
||||
|
|
||||
return ( |
return ( |
||||
<main className="flex min-h-screen items-center justify-center bg-[#444446] p-2 sm:p-4"> |
|
||||
<section className="relative aspect-[375/813] w-full max-w-[375px] overflow-hidden rounded-[24px] bg-[#FCF8F7] shadow-[0_30px_70px_rgba(0,0,0,0.34)]"> |
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top,#FF9BB8_0%,rgba(255,155,184,0.32)_14%,rgba(255,255,255,0)_34%),linear-gradient(180deg,rgba(255,255,255,0.72)_0%,rgba(255,255,255,0.98)_34%,#FCF8F7_100%)]" /> |
|
||||
<div className="absolute inset-0 opacity-40 [background-image:linear-gradient(45deg,rgba(226,201,206,0.28)_25%,transparent_25%),linear-gradient(-45deg,rgba(226,201,206,0.28)_25%,transparent_25%),linear-gradient(45deg,transparent_75%,rgba(226,201,206,0.22)_75%),linear-gradient(-45deg,transparent_75%,rgba(226,201,206,0.22)_75%)] [background-position:0_0,0_8px,8px_-8px,-8px_0] [background-size:16px_16px]" /> |
|
||||
|
|
||||
<div className="relative z-10 flex h-full flex-col px-4 pb-4 pt-5"> |
|
||||
<div className="flex items-center justify-between px-2 text-[#1C1C1E]"> |
|
||||
<span className="text-[14px] font-semibold">9:41</span> |
|
||||
<div className="flex items-center gap-1.5"> |
|
||||
<span className="block h-[7px] w-[18px] rounded-[3px] border border-current opacity-85" /> |
|
||||
<span className="block h-[7px] w-[7px] rounded-full bg-current opacity-85" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div className="mt-4 flex items-center justify-between px-1"> |
|
||||
<Link |
|
||||
aria-label="Back to rules" |
|
||||
className="flex h-11 w-11 items-center justify-center rounded-full bg-white text-[#202020] shadow-[0_10px_24px_rgba(0,0,0,0.08)]" |
|
||||
href="/rules" |
|
||||
> |
|
||||
<BackIcon /> |
|
||||
</Link> |
|
||||
|
|
||||
<h1 |
|
||||
className="text-[20px] font-semibold text-[#2A1D1E]" |
|
||||
style={{ fontFamily: "Georgia, Times New Roman, serif" }} |
|
||||
> |
|
||||
Habib Marriage |
|
||||
</h1> |
|
||||
|
|
||||
<button |
|
||||
aria-label="Open watch history" |
|
||||
className="flex h-11 w-11 items-center justify-center rounded-full bg-white text-[#202020] shadow-[0_10px_24px_rgba(0,0,0,0.08)]" |
|
||||
type="button" |
|
||||
> |
|
||||
<HistoryIcon /> |
|
||||
</button> |
|
||||
</div> |
|
||||
|
|
||||
<div className="mt-[110px] px-1"> |
|
||||
<div className="relative overflow-hidden rounded-[20px] shadow-[0_18px_40px_rgba(0,0,0,0.22)]"> |
|
||||
<Image |
|
||||
alt="Video preview for Habib Marriage" |
|
||||
height={420} |
|
||||
priority |
|
||||
sizes="(max-width: 375px) 100vw, 320px" |
|
||||
src="/assets/images/Rectangle 3077.png" |
|
||||
width={640} |
|
||||
/> |
|
||||
<div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(6,8,14,0.08)_0%,rgba(6,8,14,0.44)_100%)]" /> |
|
||||
<div className="absolute inset-x-0 top-0 h-16 bg-[linear-gradient(180deg,rgba(6,8,14,0.38)_0%,rgba(6,8,14,0)_100%)] px-4 py-3 text-white"> |
|
||||
<div className="flex items-center justify-between text-[11px] font-medium uppercase tracking-[0.16em]"> |
|
||||
<span>Mock video</span> |
|
||||
<span>{watchedSeconds}s / 12s</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className="absolute inset-x-4 bottom-4"> |
|
||||
<div className="h-1.5 overflow-hidden rounded-full bg-white/30"> |
|
||||
<div |
|
||||
className="h-full rounded-full bg-[#F04C99] transition-[width] duration-700" |
|
||||
style={{ width: `${progressPercent}%` }} |
|
||||
|
<VideoStepScreen |
||||
|
backHref="/rules" |
||||
|
nextHref="/questions" |
||||
|
stepLabel="Step 3" |
||||
/> |
/> |
||||
</div> |
|
||||
</div> |
|
||||
<button |
|
||||
aria-label={ |
|
||||
isCompleted |
|
||||
? "Replay mock video" |
|
||||
: isPlaying |
|
||||
? "Pause mock video" |
|
||||
: "Play mock video" |
|
||||
} |
|
||||
className="absolute left-1/2 top-1/2 flex h-20 w-20 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-[#BF2F2F] shadow-[0_18px_35px_rgba(191,47,47,0.45)]" |
|
||||
onClick={handlePlay} |
|
||||
type="button" |
|
||||
> |
|
||||
<PlayIcon /> |
|
||||
</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<p className="mt-4 px-2 text-center text-[12px] font-medium text-[#6D6268]"> |
|
||||
Watch the full mock video to unlock the next step. |
|
||||
</p> |
|
||||
|
|
||||
<div className="mt-[102px] grid grid-cols-2 gap-4 px-4 text-center text-[#242424]"> |
|
||||
{metrics.map((metric) => ( |
|
||||
<div key={metric.label}> |
|
||||
<p className="text-[20px] font-semibold">{metric.value}</p> |
|
||||
<p className="mt-2 text-[13px] font-semibold leading-5"> |
|
||||
{metric.label} |
|
||||
</p> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
|
|
||||
<div className="mt-auto space-y-3"> |
|
||||
<button |
|
||||
className="h-[44px] w-full rounded-[14px] bg-linear-to-r from-[#F04C99] to-[#FF8575] text-[18px] font-semibold text-white shadow-[0_16px_36px_rgba(240,76,153,0.28)]" |
|
||||
type="button" |
|
||||
> |
|
||||
Information recording |
|
||||
</button> |
|
||||
|
|
||||
<Link |
|
||||
aria-disabled={!isCompleted} |
|
||||
className={`flex h-[44px] items-center justify-center rounded-[14px] text-[18px] font-semibold transition-opacity ${ |
|
||||
isCompleted |
|
||||
? "bg-[#242424] text-white shadow-[0_14px_30px_rgba(0,0,0,0.18)]" |
|
||||
: "pointer-events-none bg-[#D9D2D5] text-[#857D82] opacity-80" |
|
||||
}`}
|
|
||||
href={isCompleted ? "/questions" : "/video-2"} |
|
||||
tabIndex={isCompleted ? 0 : -1} |
|
||||
> |
|
||||
Next |
|
||||
</Link> |
|
||||
</div> |
|
||||
</div> |
|
||||
</section> |
|
||||
</main> |
|
||||
); |
); |
||||
} |
} |
||||
@ -1,237 +1,5 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import Image from "next/image"; |
|
||||
import Link from "next/link"; |
|
||||
import { useEffect, useState } from "react"; |
|
||||
|
|
||||
function BackIcon() { |
|
||||
return ( |
|
||||
<svg |
|
||||
aria-hidden="true" |
|
||||
fill="none" |
|
||||
height="18" |
|
||||
viewBox="0 0 18 18" |
|
||||
width="18" |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
> |
|
||||
<path |
|
||||
d="M10.5 4.5 6 9l4.5 4.5" |
|
||||
stroke="currentColor" |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth="1.8" |
|
||||
/> |
|
||||
</svg> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
function HistoryIcon() { |
|
||||
return ( |
|
||||
<svg |
|
||||
aria-hidden="true" |
|
||||
fill="none" |
|
||||
height="18" |
|
||||
viewBox="0 0 18 18" |
|
||||
width="18" |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
> |
|
||||
<path |
|
||||
d="M3.75 8.25a5.25 5.25 0 1 1 1.539 3.711" |
|
||||
stroke="currentColor" |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth="1.6" |
|
||||
/> |
|
||||
<path |
|
||||
d="M3.75 4.5v3.75H7.5" |
|
||||
stroke="currentColor" |
|
||||
strokeLinecap="round" |
|
||||
strokeLinejoin="round" |
|
||||
strokeWidth="1.6" |
|
||||
/> |
|
||||
</svg> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
function PlayIcon() { |
|
||||
return ( |
|
||||
<svg |
|
||||
aria-hidden="true" |
|
||||
fill="none" |
|
||||
height="28" |
|
||||
viewBox="0 0 28 28" |
|
||||
width="28" |
|
||||
xmlns="http://www.w3.org/2000/svg" |
|
||||
> |
|
||||
<path d="M10 8.75 20 14l-10 5.25V8.75Z" fill="white" /> |
|
||||
</svg> |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
const metrics = [ |
|
||||
{ label: "marriage applicants", value: "120" }, |
|
||||
{ label: "Successful marriage", value: "120" }, |
|
||||
]; |
|
||||
|
|
||||
const mockVideoDuration = 12; |
|
||||
|
import VideoStepScreen from "@/components/screens/video-step-screen"; |
||||
|
|
||||
export default function VideoPage() { |
export default function VideoPage() { |
||||
const [isPlaying, setIsPlaying] = useState(false); |
|
||||
const [watchedSeconds, setWatchedSeconds] = useState(0); |
|
||||
const isCompleted = watchedSeconds >= mockVideoDuration; |
|
||||
const progressPercent = Math.min( |
|
||||
(watchedSeconds / mockVideoDuration) * 100, |
|
||||
100, |
|
||||
); |
|
||||
|
|
||||
useEffect(() => { |
|
||||
if (!isPlaying || isCompleted) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
const timer = window.setInterval(() => { |
|
||||
setWatchedSeconds((currentValue) => { |
|
||||
const nextValue = Math.min(currentValue + 1, mockVideoDuration); |
|
||||
|
|
||||
if (nextValue >= mockVideoDuration) { |
|
||||
setIsPlaying(false); |
|
||||
} |
|
||||
|
|
||||
return nextValue; |
|
||||
}); |
|
||||
}, 1000); |
|
||||
|
|
||||
return () => window.clearInterval(timer); |
|
||||
}, [isCompleted, isPlaying]); |
|
||||
|
|
||||
const handlePlay = () => { |
|
||||
if (isCompleted) { |
|
||||
setWatchedSeconds(0); |
|
||||
} |
|
||||
|
|
||||
setIsPlaying((currentValue) => !currentValue || isCompleted); |
|
||||
}; |
|
||||
|
|
||||
return ( |
|
||||
<main className="flex min-h-screen items-center justify-center bg-[#444446] p-2 sm:p-4"> |
|
||||
<section className="relative aspect-[375/813] w-full max-w-[375px] overflow-hidden rounded-[24px] bg-[#FCF8F7] shadow-[0_30px_70px_rgba(0,0,0,0.34)]"> |
|
||||
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top,#FF9BB8_0%,rgba(255,155,184,0.32)_14%,rgba(255,255,255,0)_34%),linear-gradient(180deg,rgba(255,255,255,0.72)_0%,rgba(255,255,255,0.98)_34%,#FCF8F7_100%)]" /> |
|
||||
<div className="absolute inset-0 opacity-40 [background-image:linear-gradient(45deg,rgba(226,201,206,0.28)_25%,transparent_25%),linear-gradient(-45deg,rgba(226,201,206,0.28)_25%,transparent_25%),linear-gradient(45deg,transparent_75%,rgba(226,201,206,0.22)_75%),linear-gradient(-45deg,transparent_75%,rgba(226,201,206,0.22)_75%)] [background-position:0_0,0_8px,8px_-8px,-8px_0] [background-size:16px_16px]" /> |
|
||||
|
|
||||
<div className="relative z-10 flex h-full flex-col px-4 pb-4 pt-5"> |
|
||||
<div className="flex items-center justify-between px-2 text-[#1C1C1E]"> |
|
||||
<span className="text-[14px] font-semibold">9:41</span> |
|
||||
<div className="flex items-center gap-1.5"> |
|
||||
<span className="block h-[7px] w-[18px] rounded-[3px] border border-current opacity-85" /> |
|
||||
<span className="block h-[7px] w-[7px] rounded-full bg-current opacity-85" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div className="mt-4 flex items-center justify-between px-1"> |
|
||||
<Link |
|
||||
aria-label="Back to home" |
|
||||
className="flex h-11 w-11 items-center justify-center rounded-full bg-white text-[#202020] shadow-[0_10px_24px_rgba(0,0,0,0.08)]" |
|
||||
href="/" |
|
||||
> |
|
||||
<BackIcon /> |
|
||||
</Link> |
|
||||
|
|
||||
<h1 |
|
||||
className="text-[20px] font-semibold text-[#2A1D1E]" |
|
||||
style={{ fontFamily: "Georgia, Times New Roman, serif" }} |
|
||||
> |
|
||||
Habib Marriage |
|
||||
</h1> |
|
||||
|
|
||||
<button |
|
||||
aria-label="Open watch history" |
|
||||
className="flex h-11 w-11 items-center justify-center rounded-full bg-white text-[#202020] shadow-[0_10px_24px_rgba(0,0,0,0.08)]" |
|
||||
type="button" |
|
||||
> |
|
||||
<HistoryIcon /> |
|
||||
</button> |
|
||||
</div> |
|
||||
|
|
||||
<div className="mt-[110px] px-1"> |
|
||||
<div className="relative overflow-hidden rounded-[20px] shadow-[0_18px_40px_rgba(0,0,0,0.22)]"> |
|
||||
<Image |
|
||||
alt="Video preview for Habib Marriage" |
|
||||
height={420} |
|
||||
priority |
|
||||
sizes="(max-width: 375px) 100vw, 320px" |
|
||||
src="/assets/images/Rectangle 3077.png" |
|
||||
width={640} |
|
||||
/> |
|
||||
<div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(6,8,14,0.08)_0%,rgba(6,8,14,0.44)_100%)]" /> |
|
||||
<div className="absolute inset-x-0 top-0 h-16 bg-[linear-gradient(180deg,rgba(6,8,14,0.38)_0%,rgba(6,8,14,0)_100%)] px-4 py-3 text-white"> |
|
||||
<div className="flex items-center justify-between text-[11px] font-medium uppercase tracking-[0.16em]"> |
|
||||
<span>Mock video</span> |
|
||||
<span>{watchedSeconds}s / 12s</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
<div className="absolute inset-x-4 bottom-4"> |
|
||||
<div className="h-1.5 overflow-hidden rounded-full bg-white/30"> |
|
||||
<div |
|
||||
className="h-full rounded-full bg-[#F04C99] transition-[width] duration-700" |
|
||||
style={{ width: `${progressPercent}%` }} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
<button |
|
||||
aria-label={ |
|
||||
isCompleted |
|
||||
? "Replay mock video" |
|
||||
: isPlaying |
|
||||
? "Pause mock video" |
|
||||
: "Play mock video" |
|
||||
} |
|
||||
className="absolute left-1/2 top-1/2 flex h-20 w-20 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-[#BF2F2F] shadow-[0_18px_35px_rgba(191,47,47,0.45)]" |
|
||||
onClick={handlePlay} |
|
||||
type="button" |
|
||||
> |
|
||||
<PlayIcon /> |
|
||||
</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<p className="mt-4 px-2 text-center text-[12px] font-medium text-[#6D6268]"> |
|
||||
Watch the full mock video to unlock the next step. |
|
||||
</p> |
|
||||
|
|
||||
<div className="mt-[102px] grid grid-cols-2 gap-4 px-4 text-center text-[#242424]"> |
|
||||
{metrics.map((metric) => ( |
|
||||
<div key={metric.label}> |
|
||||
<p className="text-[20px] font-semibold">{metric.value}</p> |
|
||||
<p className="mt-2 text-[13px] font-semibold leading-5"> |
|
||||
{metric.label} |
|
||||
</p> |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
|
|
||||
<div className="mt-auto space-y-3"> |
|
||||
<button |
|
||||
className="h-[44px] w-full rounded-[14px] bg-linear-to-r from-[#F04C99] to-[#FF8575] text-[18px] font-semibold text-white shadow-[0_16px_36px_rgba(240,76,153,0.28)]" |
|
||||
type="button" |
|
||||
> |
|
||||
Information recording |
|
||||
</button> |
|
||||
|
|
||||
<Link |
|
||||
aria-disabled={!isCompleted} |
|
||||
className={`flex h-[44px] items-center justify-center rounded-[14px] text-[18px] font-semibold transition-opacity ${ |
|
||||
isCompleted |
|
||||
? "bg-[#242424] text-white shadow-[0_14px_30px_rgba(0,0,0,0.18)]" |
|
||||
: "pointer-events-none bg-[#D9D2D5] text-[#857D82] opacity-80" |
|
||||
}`}
|
|
||||
href={isCompleted ? "/rules" : "/video"} |
|
||||
tabIndex={isCompleted ? 0 : -1} |
|
||||
> |
|
||||
Next |
|
||||
</Link> |
|
||||
</div> |
|
||||
</div> |
|
||||
</section> |
|
||||
</main> |
|
||||
); |
|
||||
|
return <VideoStepScreen backHref="/" nextHref="/rules" stepLabel="Step 1" />; |
||||
} |
} |
||||
@ -0,0 +1,202 @@ |
|||||
|
"use client"; |
||||
|
|
||||
|
import Image from "next/image"; |
||||
|
import Link from "next/link"; |
||||
|
import { useEffect, useState } from "react"; |
||||
|
import { |
||||
|
BackIcon, |
||||
|
FabricCard, |
||||
|
FabricIconButton, |
||||
|
FabricIconLink, |
||||
|
FabricPill, |
||||
|
FabricProgress, |
||||
|
FabricScreen, |
||||
|
FabricStatusBar, |
||||
|
fabricDisabledLinkClass, |
||||
|
fabricMutedPanelClass, |
||||
|
fabricSecondaryButtonClass, |
||||
|
HistoryIcon, |
||||
|
PlayIcon, |
||||
|
} from "@/components/ui/fabric-mobile"; |
||||
|
|
||||
|
const metrics = [ |
||||
|
{ label: "Marriage applicants", value: "120" }, |
||||
|
{ label: "Successful marriages", value: "120" }, |
||||
|
] as const; |
||||
|
|
||||
|
const mockVideoDuration = 12; |
||||
|
|
||||
|
export default function VideoStepScreen({ |
||||
|
backHref, |
||||
|
nextHref, |
||||
|
stepLabel, |
||||
|
}: { |
||||
|
backHref: string; |
||||
|
nextHref: string; |
||||
|
stepLabel: string; |
||||
|
}) { |
||||
|
const [isPlaying, setIsPlaying] = useState(false); |
||||
|
const [watchedSeconds, setWatchedSeconds] = useState(0); |
||||
|
const isCompleted = watchedSeconds >= mockVideoDuration; |
||||
|
const progressPercent = Math.min( |
||||
|
(watchedSeconds / mockVideoDuration) * 100, |
||||
|
100, |
||||
|
); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (!isPlaying || isCompleted) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const timer = window.setInterval(() => { |
||||
|
setWatchedSeconds((currentValue) => { |
||||
|
const nextValue = Math.min(currentValue + 1, mockVideoDuration); |
||||
|
|
||||
|
if (nextValue >= mockVideoDuration) { |
||||
|
setIsPlaying(false); |
||||
|
} |
||||
|
|
||||
|
return nextValue; |
||||
|
}); |
||||
|
}, 1000); |
||||
|
|
||||
|
return () => window.clearInterval(timer); |
||||
|
}, [isCompleted, isPlaying]); |
||||
|
|
||||
|
const handlePlay = () => { |
||||
|
if (isCompleted) { |
||||
|
setWatchedSeconds(0); |
||||
|
} |
||||
|
|
||||
|
setIsPlaying((currentValue) => !currentValue || isCompleted); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<FabricScreen> |
||||
|
<FabricStatusBar /> |
||||
|
|
||||
|
<div className="mt-5 flex items-center justify-between"> |
||||
|
<FabricIconLink aria-label="Go back" href={backHref}> |
||||
|
<BackIcon /> |
||||
|
</FabricIconLink> |
||||
|
|
||||
|
<div className="text-center"> |
||||
|
<p className="fabric-kicker">Guided intake</p> |
||||
|
<h1 className="fabric-display mt-2 text-[28px] leading-none text-[#2E211E]"> |
||||
|
Habib Marriage |
||||
|
</h1> |
||||
|
</div> |
||||
|
|
||||
|
<FabricIconButton aria-label="Open watch history"> |
||||
|
<HistoryIcon /> |
||||
|
</FabricIconButton> |
||||
|
</div> |
||||
|
|
||||
|
<FabricCard className="mt-6 px-5 py-5"> |
||||
|
<div className="flex items-start justify-between gap-4"> |
||||
|
<div> |
||||
|
<p className="fabric-kicker">Orientation video</p> |
||||
|
<h2 className="fabric-display mt-3 text-[30px] leading-[1.02] text-[#2E211E]"> |
||||
|
Watch this short introduction before continuing |
||||
|
</h2> |
||||
|
</div> |
||||
|
<FabricPill className="shrink-0">{stepLabel}</FabricPill> |
||||
|
</div> |
||||
|
|
||||
|
<p className="mt-4 text-[14px] leading-7 text-[#6E5E58]"> |
||||
|
This preview explains the tone of the platform and unlocks the next |
||||
|
step once the full clip has been watched. |
||||
|
</p> |
||||
|
</FabricCard> |
||||
|
|
||||
|
<div className="mt-5 px-1"> |
||||
|
<div className="relative overflow-hidden rounded-[30px] border border-white/65 bg-[#241914] shadow-[0_24px_48px_rgba(56,31,27,0.28)]"> |
||||
|
<Image |
||||
|
alt="Video preview for Habib Marriage" |
||||
|
height={420} |
||||
|
priority |
||||
|
sizes="(max-width: 390px) 100vw, 340px" |
||||
|
src="/assets/images/Rectangle 3077.png" |
||||
|
width={640} |
||||
|
/> |
||||
|
<div className="absolute inset-0 bg-[linear-gradient(180deg,rgba(18,12,10,0.12)_0%,rgba(18,12,10,0.52)_100%)]" /> |
||||
|
|
||||
|
<div className="absolute inset-x-0 top-0 px-5 py-4 text-white"> |
||||
|
<div className="flex items-center justify-between"> |
||||
|
<FabricPill className="bg-white/18 text-white backdrop-blur"> |
||||
|
12s walkthrough |
||||
|
</FabricPill> |
||||
|
<span className="text-[12px] font-semibold uppercase tracking-[0.18em] text-white/82"> |
||||
|
{watchedSeconds}s / {mockVideoDuration}s |
||||
|
</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div className="absolute inset-x-5 bottom-5"> |
||||
|
<FabricProgress |
||||
|
className="h-2 bg-white/25" |
||||
|
fillClassName="bg-[linear-gradient(135deg,#E68C79_0%,#F7C29A_100%)] shadow-none" |
||||
|
value={progressPercent} |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<button |
||||
|
aria-label={ |
||||
|
isCompleted |
||||
|
? "Replay the introduction" |
||||
|
: isPlaying |
||||
|
? "Pause the introduction" |
||||
|
: "Play the introduction" |
||||
|
} |
||||
|
className="absolute left-1/2 top-1/2 flex h-[88px] w-[88px] -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-white/28 bg-[radial-gradient(circle_at_top,#C16777_0%,#A14556_48%,#7E2F3B_100%)] text-white shadow-[0_20px_42px_rgba(126,47,59,0.42)] backdrop-blur" |
||||
|
onClick={handlePlay} |
||||
|
type="button" |
||||
|
> |
||||
|
<PlayIcon /> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-4 grid grid-cols-2 gap-3 px-1"> |
||||
|
{metrics.map((metric) => ( |
||||
|
<FabricCard className="px-4 py-4 text-center" key={metric.label}> |
||||
|
<p className="fabric-display text-[28px] leading-none text-[#2F221E]"> |
||||
|
{metric.value} |
||||
|
</p> |
||||
|
<p className="mt-3 text-[12px] font-semibold uppercase tracking-[0.14em] text-[#7A655D]"> |
||||
|
{metric.label} |
||||
|
</p> |
||||
|
</FabricCard> |
||||
|
))} |
||||
|
</div> |
||||
|
|
||||
|
<div className="mt-auto space-y-3"> |
||||
|
<div className={`${fabricMutedPanelClass} px-4 py-4`}> |
||||
|
<div className="flex items-start gap-3"> |
||||
|
<span className="fabric-accent-dot mt-1 shrink-0" /> |
||||
|
<div> |
||||
|
<p className="text-[14px] font-semibold text-[#47302A]"> |
||||
|
Watch until the end to unlock the next screen. |
||||
|
</p> |
||||
|
<p className="mt-1 text-[13px] leading-6 text-[#7A665F]"> |
||||
|
Your progress bar fills as the preview runs. Replay is available |
||||
|
after completion. |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<Link |
||||
|
aria-disabled={!isCompleted} |
||||
|
className={`${fabricSecondaryButtonClass} ${ |
||||
|
!isCompleted ? fabricDisabledLinkClass : "" |
||||
|
}`}
|
||||
|
href={nextHref} |
||||
|
tabIndex={isCompleted ? 0 : -1} |
||||
|
> |
||||
|
Next |
||||
|
</Link> |
||||
|
</div> |
||||
|
</FabricScreen> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,214 @@ |
|||||
|
import Link from "next/link"; |
||||
|
import type { |
||||
|
ButtonHTMLAttributes, |
||||
|
ComponentPropsWithoutRef, |
||||
|
ReactNode, |
||||
|
} from "react"; |
||||
|
|
||||
|
function mergeClasses(...classes: Array<string | false | null | undefined>) { |
||||
|
return classes.filter(Boolean).join(" "); |
||||
|
} |
||||
|
|
||||
|
export function FabricScreen({ |
||||
|
children, |
||||
|
className, |
||||
|
contentClassName, |
||||
|
}: { |
||||
|
children: ReactNode; |
||||
|
className?: string; |
||||
|
contentClassName?: string; |
||||
|
}) { |
||||
|
return ( |
||||
|
<main className="fabric-stage fabric-body"> |
||||
|
<section className={mergeClasses("fabric-phone", className)}> |
||||
|
<div className={mergeClasses("fabric-screen", contentClassName)}> |
||||
|
{children} |
||||
|
</div> |
||||
|
</section> |
||||
|
</main> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function FabricStatusBar() { |
||||
|
return ( |
||||
|
<div className="fabric-status"> |
||||
|
<span>9:41</span> |
||||
|
<div className="fabric-signal"> |
||||
|
<span className="fabric-signal-bar" /> |
||||
|
<span className="fabric-signal-dot" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function FabricCard({ |
||||
|
children, |
||||
|
className, |
||||
|
}: { |
||||
|
children: ReactNode; |
||||
|
className?: string; |
||||
|
}) { |
||||
|
return ( |
||||
|
<div className={mergeClasses("fabric-card", className)}>{children}</div> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function FabricPill({ |
||||
|
children, |
||||
|
className, |
||||
|
}: { |
||||
|
children: ReactNode; |
||||
|
className?: string; |
||||
|
}) { |
||||
|
return ( |
||||
|
<span className={mergeClasses("fabric-pill", className)}>{children}</span> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function FabricProgress({ |
||||
|
className, |
||||
|
fillClassName, |
||||
|
value, |
||||
|
}: { |
||||
|
className?: string; |
||||
|
fillClassName?: string; |
||||
|
value: number; |
||||
|
}) { |
||||
|
const clampedValue = Math.max(0, Math.min(value, 100)); |
||||
|
|
||||
|
return ( |
||||
|
<div className={mergeClasses("fabric-progress-track", className)}> |
||||
|
<div |
||||
|
className={mergeClasses("fabric-progress-fill", fillClassName)} |
||||
|
style={{ width: `${clampedValue}%` }} |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function FabricIconButton({ |
||||
|
children, |
||||
|
className, |
||||
|
type = "button", |
||||
|
...props |
||||
|
}: ButtonHTMLAttributes<HTMLButtonElement> & { |
||||
|
children: ReactNode; |
||||
|
className?: string; |
||||
|
}) { |
||||
|
return ( |
||||
|
<button |
||||
|
className={mergeClasses("fabric-nav-button", className)} |
||||
|
type={type} |
||||
|
{...props} |
||||
|
> |
||||
|
{children} |
||||
|
</button> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function FabricIconLink({ |
||||
|
children, |
||||
|
className, |
||||
|
...props |
||||
|
}: ComponentPropsWithoutRef<typeof Link> & { |
||||
|
children: ReactNode; |
||||
|
className?: string; |
||||
|
}) { |
||||
|
return ( |
||||
|
<Link className={mergeClasses("fabric-nav-button", className)} {...props}> |
||||
|
{children} |
||||
|
</Link> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function BackIcon() { |
||||
|
return ( |
||||
|
<svg |
||||
|
aria-hidden="true" |
||||
|
fill="none" |
||||
|
height="18" |
||||
|
viewBox="0 0 18 18" |
||||
|
width="18" |
||||
|
xmlns="http://www.w3.org/2000/svg" |
||||
|
> |
||||
|
<path |
||||
|
d="M10.5 4.5 6 9l4.5 4.5" |
||||
|
stroke="currentColor" |
||||
|
strokeLinecap="round" |
||||
|
strokeLinejoin="round" |
||||
|
strokeWidth="1.8" |
||||
|
/> |
||||
|
</svg> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function HistoryIcon() { |
||||
|
return ( |
||||
|
<svg |
||||
|
aria-hidden="true" |
||||
|
fill="none" |
||||
|
height="18" |
||||
|
viewBox="0 0 18 18" |
||||
|
width="18" |
||||
|
xmlns="http://www.w3.org/2000/svg" |
||||
|
> |
||||
|
<path |
||||
|
d="M3.75 8.25a5.25 5.25 0 1 1 1.539 3.711" |
||||
|
stroke="currentColor" |
||||
|
strokeLinecap="round" |
||||
|
strokeLinejoin="round" |
||||
|
strokeWidth="1.6" |
||||
|
/> |
||||
|
<path |
||||
|
d="M3.75 4.5v3.75H7.5" |
||||
|
stroke="currentColor" |
||||
|
strokeLinecap="round" |
||||
|
strokeLinejoin="round" |
||||
|
strokeWidth="1.6" |
||||
|
/> |
||||
|
</svg> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function PlayIcon() { |
||||
|
return ( |
||||
|
<svg |
||||
|
aria-hidden="true" |
||||
|
fill="none" |
||||
|
height="28" |
||||
|
viewBox="0 0 28 28" |
||||
|
width="28" |
||||
|
xmlns="http://www.w3.org/2000/svg" |
||||
|
> |
||||
|
<path d="M10 8.75 20 14l-10 5.25V8.75Z" fill="white" /> |
||||
|
</svg> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function CheckIcon() { |
||||
|
return ( |
||||
|
<svg |
||||
|
aria-hidden="true" |
||||
|
fill="none" |
||||
|
height="28" |
||||
|
viewBox="0 0 28 28" |
||||
|
width="28" |
||||
|
xmlns="http://www.w3.org/2000/svg" |
||||
|
> |
||||
|
<path |
||||
|
d="m7.5 14.5 4.25 4.25 8.75-9" |
||||
|
stroke="currentColor" |
||||
|
strokeLinecap="round" |
||||
|
strokeLinejoin="round" |
||||
|
strokeWidth="2.2" |
||||
|
/> |
||||
|
</svg> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export const fabricInputClass = "fabric-input"; |
||||
|
export const fabricTextareaClass = "fabric-input fabric-textarea"; |
||||
|
export const fabricPrimaryButtonClass = "fabric-primary-button"; |
||||
|
export const fabricSecondaryButtonClass = "fabric-secondary-button"; |
||||
|
export const fabricMutedPanelClass = "fabric-muted-panel"; |
||||
|
export const fabricDisabledLinkClass = "fabric-link-disabled"; |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue