style(app): normalize tailwind utility syntax
This commit is contained in:
@@ -17,7 +17,7 @@ describe("shared Tailwind components", () => {
|
|||||||
expect(linkHtml).toContain('href="/chat"');
|
expect(linkHtml).toContain('href="/chat"');
|
||||||
expect(linkHtml).toContain('aria-label="Back to chat"');
|
expect(linkHtml).toContain('aria-label="Back to chat"');
|
||||||
expect(linkHtml).toContain("inline-flex");
|
expect(linkHtml).toContain("inline-flex");
|
||||||
expect(linkHtml).toContain("size-[var(--responsive-icon-button-size,42px)]");
|
expect(linkHtml).toContain("size-(--responsive-icon-button-size,42px)");
|
||||||
expect(linkHtml).toContain("hover:bg-white");
|
expect(linkHtml).toContain("hover:bg-white");
|
||||||
expect(actionHtml).toContain("<button");
|
expect(actionHtml).toContain("<button");
|
||||||
expect(actionHtml).toContain("size-8");
|
expect(actionHtml).toContain("size-8");
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ type BackButtonActionProps = BackButtonBaseProps & {
|
|||||||
export type BackButtonProps = BackButtonLinkProps | BackButtonActionProps;
|
export type BackButtonProps = BackButtonLinkProps | BackButtonActionProps;
|
||||||
|
|
||||||
const BASE_CLASS_NAME =
|
const BASE_CLASS_NAME =
|
||||||
"inline-flex cursor-pointer items-center justify-center rounded-full border-0 p-0 text-[var(--color-auth-text-primary)] no-underline transition-[background,box-shadow,transform] duration-150 ease-out focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]";
|
"inline-flex cursor-pointer items-center justify-center rounded-full border-0 p-0 text-(--color-auth-text-primary) no-underline transition-[background,box-shadow,transform] duration-150 ease-out focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]";
|
||||||
|
|
||||||
const VARIANT_CLASS_NAMES = {
|
const VARIANT_CLASS_NAMES = {
|
||||||
floating:
|
floating:
|
||||||
"absolute left-[calc(var(--spacing-xl,20px)+var(--app-safe-left,0px))] top-[calc(var(--spacing-xl,20px)+var(--app-safe-top,0px))] z-[2] size-[var(--back-button-size)] bg-[var(--color-auth-surface)] pr-[2px] shadow-[0_2px_8px_rgba(0,0,0,0.1)] hover:bg-[rgba(0,0,0,0.04)] active:scale-[0.96]",
|
"absolute left-[calc(var(--spacing-xl,20px)+var(--app-safe-left,0px))] top-[calc(var(--spacing-xl,20px)+var(--app-safe-top,0px))] z-2 size-(--back-button-size) bg-(--color-auth-surface) pr-[2px] shadow-[0_2px_8px_rgba(0,0,0,0.1)] hover:bg-[rgba(0,0,0,0.04)] active:scale-96",
|
||||||
soft:
|
soft:
|
||||||
"size-[var(--responsive-icon-button-size,42px)] border border-[rgba(25,19,22,0.08)] bg-[rgba(255,255,255,0.82)] text-[#21191d] shadow-[0_10px_22px_rgba(50,30,40,0.08)] hover:-translate-y-px hover:bg-white hover:shadow-[0_14px_26px_rgba(50,30,40,0.12)] active:translate-y-px",
|
"size-(--responsive-icon-button-size,42px) border border-[rgba(25,19,22,0.08)] bg-[rgba(255,255,255,0.82)] text-[#21191d] shadow-[0_10px_22px_rgba(50,30,40,0.08)] hover:-translate-y-px hover:bg-white hover:shadow-[0_14px_26px_rgba(50,30,40,0.12)] active:translate-y-px",
|
||||||
ghost:
|
ghost:
|
||||||
"size-8 bg-transparent text-black hover:opacity-70 active:scale-[0.96]",
|
"size-8 bg-transparent text-black hover:opacity-70 active:scale-96",
|
||||||
} satisfies Record<NonNullable<BackButtonBaseProps["variant"]>, string>;
|
} satisfies Record<NonNullable<BackButtonBaseProps["variant"]>, string>;
|
||||||
|
|
||||||
export function BackButton({
|
export function BackButton({
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function CharacterAvatar({
|
|||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={[
|
className={[
|
||||||
"inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-[var(--color-avatar-border,#fbf3f5)]",
|
"inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-(--color-avatar-border,#fbf3f5)",
|
||||||
className,
|
className,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ describe("core Tailwind components", () => {
|
|||||||
expect(checkedHtml).toContain('role="checkbox"');
|
expect(checkedHtml).toContain('role="checkbox"');
|
||||||
expect(checkedHtml).toContain('aria-checked="true"');
|
expect(checkedHtml).toContain('aria-checked="true"');
|
||||||
expect(checkedHtml).toContain('aria-label="Accept terms"');
|
expect(checkedHtml).toContain('aria-label="Accept terms"');
|
||||||
expect(checkedHtml).toContain("size-[var(--auth-legal-checkbox-size)]");
|
expect(checkedHtml).toContain("size-(--auth-legal-checkbox-size)");
|
||||||
expect(checkedHtml).toContain(
|
expect(checkedHtml).toContain(
|
||||||
"size-[var(--auth-legal-checkbox-inner-size)]",
|
"size-(--auth-legal-checkbox-inner-size)",
|
||||||
);
|
);
|
||||||
expect(labeledHtml).toContain("<label");
|
expect(labeledHtml).toContain("<label");
|
||||||
expect(labeledHtml).toContain("Keep me signed in");
|
expect(labeledHtml).toContain("Keep me signed in");
|
||||||
expect(labeledHtml).toContain("gap-[var(--spacing-sm)]");
|
expect(labeledHtml).toContain("gap-(--spacing-sm)");
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function Checkbox({
|
|||||||
ariaLabel,
|
ariaLabel,
|
||||||
}: CheckboxProps) {
|
}: CheckboxProps) {
|
||||||
const checkboxClassName = [
|
const checkboxClassName = [
|
||||||
"mt-0.5 inline-flex size-[var(--auth-legal-checkbox-size)] shrink-0 cursor-pointer items-center justify-center rounded-full border border-[var(--color-auth-surface)] bg-[var(--color-auth-surface)] p-0 shadow-[0_0_0_1px_rgba(0,0,0,0.06)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent)] disabled:cursor-not-allowed disabled:opacity-50",
|
"mt-0.5 inline-flex size-(--auth-legal-checkbox-size) shrink-0 cursor-pointer items-center justify-center rounded-full border border-(--color-auth-surface) bg-(--color-auth-surface) p-0 shadow-[0_0_0_1px_rgba(0,0,0,0.06)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--color-accent) disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
].join(" ");
|
].join(" ");
|
||||||
const button = (
|
const button = (
|
||||||
<button
|
<button
|
||||||
@@ -42,7 +42,7 @@ export function Checkbox({
|
|||||||
className={checkboxClassName}
|
className={checkboxClassName}
|
||||||
>
|
>
|
||||||
{checked ? (
|
{checked ? (
|
||||||
<span className="block size-[var(--auth-legal-checkbox-inner-size)] rounded-full bg-[var(--color-auth-legal-check)]" />
|
<span className="block size-(--auth-legal-checkbox-inner-size) rounded-full bg-(--color-auth-legal-check)" />
|
||||||
) : null}
|
) : null}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
@@ -58,14 +58,14 @@ export function Checkbox({
|
|||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className={[
|
className={[
|
||||||
"flex w-full cursor-pointer items-start gap-[var(--spacing-sm)]",
|
"flex w-full cursor-pointer items-start gap-(--spacing-sm)",
|
||||||
className,
|
className,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" ")}
|
.join(" ")}
|
||||||
>
|
>
|
||||||
{button}
|
{button}
|
||||||
<span className="flex-auto text-left text-[var(--font-size-sm)] leading-[1.4] text-[var(--color-auth-text-primary)]">
|
<span className="flex-auto text-left text-(length:--font-size-sm) leading-[1.4] text-(--color-auth-text-primary)">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function LoadingIndicator({
|
|||||||
style={style}
|
style={style}
|
||||||
/>
|
/>
|
||||||
{label ? (
|
{label ? (
|
||||||
<span className="text-[var(--font-size-sm)] text-text-secondary">
|
<span className="text-(length:--font-size-sm) text-text-secondary">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function UserMessageAvatar({
|
|||||||
size = 43,
|
size = 43,
|
||||||
}: UserMessageAvatarProps) {
|
}: UserMessageAvatarProps) {
|
||||||
const avatarClassName = [
|
const avatarClassName = [
|
||||||
"flex shrink-0 items-center justify-center overflow-hidden rounded-full border-2 border-[var(--color-avatar-border,#fbf3f5)] bg-[var(--color-avatar-border,#fbf3f5)] shadow-[var(--shadow-input-box,0_1px_2px_rgba(0,0,0,0.1))]",
|
"flex shrink-0 items-center justify-center overflow-hidden rounded-full border-2 border-(--color-avatar-border,#fbf3f5) bg-(--color-avatar-border,#fbf3f5) shadow-(--shadow-input-box,0_1px_2px_rgba(0,0,0,0.1))",
|
||||||
className,
|
className,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ export function AuthScreen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileShell>
|
<MobileShell>
|
||||||
<div className="relative flex min-h-[var(--app-viewport-height,100dvh)] w-full flex-1 flex-col overflow-hidden bg-[#fbf1f2]">
|
<div className="relative flex min-h-(--app-viewport-height,100dvh) w-full flex-1 flex-col overflow-hidden bg-[#fbf1f2]">
|
||||||
<AuthBackground />
|
<AuthBackground />
|
||||||
<div
|
<div
|
||||||
className="relative z-[2] flex flex-auto flex-col justify-center"
|
className="relative z-2 flex flex-auto flex-col justify-center"
|
||||||
style={{
|
style={{
|
||||||
padding:
|
padding:
|
||||||
"calc(var(--page-padding-y, var(--spacing-lg)) + var(--app-safe-top, 0px)) calc(var(--page-padding-x, var(--spacing-lg)) + var(--app-safe-right, 0px)) calc(var(--page-padding-y, var(--spacing-lg)) + var(--app-safe-bottom, 0px)) calc(var(--page-padding-x, var(--spacing-lg)) + var(--app-safe-left, 0px))",
|
"calc(var(--page-padding-y, var(--spacing-lg)) + var(--app-safe-top, 0px)) calc(var(--page-padding-x, var(--spacing-lg)) + var(--app-safe-right, 0px)) calc(var(--page-padding-y, var(--spacing-lg)) + var(--app-safe-bottom, 0px)) calc(var(--page-padding-x, var(--spacing-lg)) + var(--app-safe-left, 0px))",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ describe("auth Tailwind components", () => {
|
|||||||
<AuthPrimaryButton isLoading>Continue</AuthPrimaryButton>,
|
<AuthPrimaryButton isLoading>Continue</AuthPrimaryButton>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain("min-h-[var(--auth-field-height)]");
|
expect(html).toContain("min-h-(--auth-field-height)");
|
||||||
expect(html).toContain("bg-[linear-gradient");
|
expect(html).toContain("bg-[linear-gradient");
|
||||||
expect(html).toContain("animate-spin");
|
expect(html).toContain("animate-spin");
|
||||||
expect(html).toContain("Continue");
|
expect(html).toContain("Continue");
|
||||||
@@ -80,7 +80,7 @@ describe("auth Tailwind components", () => {
|
|||||||
<AuthSocialButton icon={<span>G</span>}>Continue</AuthSocialButton>,
|
<AuthSocialButton icon={<span>G</span>}>Continue</AuthSocialButton>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain("min-h-[var(--auth-social-button-height)]");
|
expect(html).toContain("min-h-(--auth-social-button-height)");
|
||||||
expect(html).toContain("border-auth-border");
|
expect(html).toContain("border-auth-border");
|
||||||
expect(html).toContain("text-auth-text-primary");
|
expect(html).toContain("text-auth-text-primary");
|
||||||
expect(html).toContain("Continue");
|
expect(html).toContain("Continue");
|
||||||
@@ -108,7 +108,7 @@ describe("auth Tailwind components", () => {
|
|||||||
const html = renderToStaticMarkup(<AuthLegalText />);
|
const html = renderToStaticMarkup(<AuthLegalText />);
|
||||||
|
|
||||||
expect(html).toContain('aria-label="Agree to terms"');
|
expect(html).toContain('aria-label="Agree to terms"');
|
||||||
expect(html).toContain("size-[var(--auth-legal-checkbox-size)]");
|
expect(html).toContain("size-(--auth-legal-checkbox-size)");
|
||||||
expect(html).toContain("bg-auth-legal-check");
|
expect(html).toContain("bg-auth-legal-check");
|
||||||
expect(html).toContain("Privacy Agreement");
|
expect(html).toContain("Privacy Agreement");
|
||||||
expect(html).toContain("Terms of User");
|
expect(html).toContain("Terms of User");
|
||||||
@@ -125,7 +125,7 @@ describe("auth Tailwind components", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain("min-h-[var(--auth-field-height)]");
|
expect(html).toContain("min-h-(--auth-field-height)");
|
||||||
expect(html).toContain("placeholder:text-auth-input-placeholder");
|
expect(html).toContain("placeholder:text-auth-input-placeholder");
|
||||||
expect(html).toContain("text-auth-text-primary");
|
expect(html).toContain("text-auth-text-primary");
|
||||||
expect(html).toContain("Invalid email");
|
expect(html).toContain("Invalid email");
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export function AuthEmailPanel({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="self-center border-0 bg-transparent pt-md text-[var(--responsive-body)] text-auth-text-primary underline underline-offset-[3px] transition-opacity duration-150 hover:opacity-80"
|
className="self-center border-0 bg-transparent pt-md text-(length:--responsive-body) text-auth-text-primary underline underline-offset-[3px] transition-opacity duration-150 hover:opacity-80"
|
||||||
onClick={() => setMode((m) => (m === "login" ? "register" : "login"))}
|
onClick={() => setMode((m) => (m === "login" ? "register" : "login"))}
|
||||||
>
|
>
|
||||||
{mode === "login"
|
{mode === "login"
|
||||||
@@ -93,13 +93,13 @@ export function AuthEmailPanel({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="self-center border-0 bg-transparent py-md text-[var(--responsive-body)] text-auth-text-primary underline underline-offset-[3px] transition-opacity duration-150 hover:opacity-80"
|
className="self-center border-0 bg-transparent py-md text-(length:--responsive-body) text-auth-text-primary underline underline-offset-[3px] transition-opacity duration-150 hover:opacity-80"
|
||||||
onClick={() => setShowOptions(true)}
|
onClick={() => setShowOptions(true)}
|
||||||
>
|
>
|
||||||
Other Sign In Options
|
Other Sign In Options
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="min-h-[var(--page-section-gap,var(--spacing-lg))] flex-[1_1_0]" />
|
<div className="min-h-(--page-section-gap,var(--spacing-lg)) flex-[1_1_0]" />
|
||||||
<AuthLegalText />
|
<AuthLegalText />
|
||||||
|
|
||||||
<AuthOtherOptionsDialog
|
<AuthOtherOptionsDialog
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function AuthErrorMessage({ message }: { message: string | null }) {
|
|||||||
if (!message) return null;
|
if (!message) return null;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-full bg-transparent p-0 text-center text-[var(--responsive-caption)] leading-[1.4] text-error"
|
className="w-full bg-transparent p-0 text-center text-(length:--responsive-caption) leading-[1.4] text-error"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
{message}
|
{message}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function AuthFacebookPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-0 w-full flex-auto flex-col items-stretch">
|
<div className="flex min-h-0 w-full flex-auto flex-col items-stretch">
|
||||||
<div className="min-h-[var(--page-section-gap,var(--spacing-lg))] flex-[1_1_0]" />
|
<div className="min-h-(--page-section-gap,var(--spacing-lg)) flex-[1_1_0]" />
|
||||||
<Image
|
<Image
|
||||||
src="/images/auth/ic-logo-login.png"
|
src="/images/auth/ic-logo-login.png"
|
||||||
alt="Cozsweet"
|
alt="Cozsweet"
|
||||||
@@ -59,17 +59,17 @@ export function AuthFacebookPanel({
|
|||||||
style={{ width: "auto", height: "auto" }}
|
style={{ width: "auto", height: "auto" }}
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
<div className="min-h-[var(--page-section-gap,var(--spacing-lg))] flex-[1_1_0]" />
|
<div className="min-h-(--page-section-gap,var(--spacing-lg)) flex-[1_1_0]" />
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="mt-md inline-flex min-h-[var(--auth-field-height)] w-full cursor-pointer items-center justify-center gap-md rounded-full border-0 bg-auth-surface px-lg text-[var(--responsive-body)] font-semibold text-auth-text-primary shadow-[0_2px_6px_rgba(0,0,0,0.06)] transition-[filter,transform,opacity] duration-150 enabled:hover:brightness-[0.97] enabled:active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-60"
|
className="mt-md inline-flex min-h-(--auth-field-height) w-full cursor-pointer items-center justify-center gap-md rounded-full border-0 bg-auth-surface px-lg text-(length:--responsive-body) font-semibold text-auth-text-primary shadow-[0_2px_6px_rgba(0,0,0,0.06)] transition-[filter,transform,opacity] duration-150 enabled:hover:brightness-[0.97] enabled:active:scale-98 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
onClick={() => handleFacebook()}
|
onClick={() => handleFacebook()}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<span
|
<span
|
||||||
className="inline-block size-[var(--responsive-spinner-size,18px)] flex-none animate-spin rounded-full border-2 border-solid border-facebook-blue border-r-transparent border-b-transparent border-l-transparent"
|
className="inline-block size-(--responsive-spinner-size,18px) flex-none animate-spin rounded-full border-2 border-solid border-facebook-blue border-r-transparent border-b-transparent border-l-transparent"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -87,13 +87,13 @@ export function AuthFacebookPanel({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="self-center border-0 bg-transparent py-md text-[var(--responsive-body)] text-auth-text-primary underline underline-offset-[3px] transition-opacity duration-150 hover:opacity-80"
|
className="self-center border-0 bg-transparent py-md text-(length:--responsive-body) text-auth-text-primary underline underline-offset-[3px] transition-opacity duration-150 hover:opacity-80"
|
||||||
onClick={() => setShowOptions(true)}
|
onClick={() => setShowOptions(true)}
|
||||||
>
|
>
|
||||||
Other Sign In Options
|
Other Sign In Options
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="min-h-[var(--page-section-gap,var(--spacing-lg))] flex-[1_1_0]" />
|
<div className="min-h-(--page-section-gap,var(--spacing-lg)) flex-[1_1_0]" />
|
||||||
|
|
||||||
<AuthErrorMessage message={errorMessage} />
|
<AuthErrorMessage message={errorMessage} />
|
||||||
<AuthLegalText />
|
<AuthLegalText />
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ export function AuthLegalText() {
|
|||||||
<div className="flex w-full items-start gap-sm">
|
<div className="flex w-full items-start gap-sm">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="mt-0.5 inline-flex size-[var(--auth-legal-checkbox-size)] flex-none cursor-pointer items-center justify-center rounded-full border border-auth-surface bg-auth-surface p-0 shadow-[0_0_0_1px_rgba(0,0,0,0.06)]"
|
className="mt-0.5 inline-flex size-(--auth-legal-checkbox-size) flex-none cursor-pointer items-center justify-center rounded-full border border-auth-surface bg-auth-surface p-0 shadow-[0_0_0_1px_rgba(0,0,0,0.06)]"
|
||||||
onClick={() => setChecked((c) => !c)}
|
onClick={() => setChecked((c) => !c)}
|
||||||
aria-label="Agree to terms"
|
aria-label="Agree to terms"
|
||||||
aria-pressed={checked}
|
aria-pressed={checked}
|
||||||
>
|
>
|
||||||
{checked ? (
|
{checked ? (
|
||||||
<span className="block size-[var(--auth-legal-checkbox-inner-size)] rounded-full bg-auth-legal-check" />
|
<span className="block size-(--auth-legal-checkbox-inner-size) rounded-full bg-auth-legal-check" />
|
||||||
) : null}
|
) : null}
|
||||||
</button>
|
</button>
|
||||||
<p className="m-0 flex-auto text-left text-[var(--responsive-caption)] leading-[1.4] text-auth-text-primary">
|
<p className="m-0 flex-auto text-left text-(length:--responsive-caption) leading-[1.4] text-auth-text-primary">
|
||||||
I agree to the Cozsweet {" "}
|
I agree to the Cozsweet {" "}
|
||||||
<a
|
<a
|
||||||
href={AppConstants.privacyPolicyUrl}
|
href={AppConstants.privacyPolicyUrl}
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ export function AuthOtherOptionsDialog({
|
|||||||
style={{ paddingBottom: "calc(var(--spacing-xxl) + var(--app-safe-bottom, 0px))" }}
|
style={{ paddingBottom: "calc(var(--spacing-xxl) + var(--app-safe-bottom, 0px))" }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mb-sm h-[var(--auth-drag-handle-height)] w-[var(--auth-drag-handle-width)] self-center rounded-[2px] bg-auth-drag-handle"
|
className="mb-sm h-(--auth-drag-handle-height) w-(--auth-drag-handle-width) self-center rounded-[2px] bg-auth-drag-handle"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<h2 className="my-sm text-center text-[var(--responsive-page-title)] font-bold text-auth-text-primary">
|
<h2 className="my-sm text-center text-(length:--responsive-page-title) font-bold text-auth-text-primary">
|
||||||
Other Sign-in Options
|
Other Sign-in Options
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ export function AuthOtherOptionsDialog({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="self-center border-0 bg-transparent py-md text-[var(--responsive-body)] font-medium text-auth-text-primary no-underline hover:underline"
|
className="self-center border-0 bg-transparent py-md text-(length:--responsive-body) font-medium text-auth-text-primary no-underline hover:underline"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function AuthPrimaryButton({
|
|||||||
type={type}
|
type={type}
|
||||||
disabled={disabled || isLoading}
|
disabled={disabled || isLoading}
|
||||||
className={[
|
className={[
|
||||||
"inline-flex min-h-[var(--auth-field-height)] w-full cursor-pointer items-center justify-center gap-sm rounded-full border-0 bg-[linear-gradient(90deg,var(--color-auth-primary-gradient-start),var(--color-auth-primary-gradient-end))] px-lg text-[var(--responsive-body)] font-bold text-white shadow-[0_3px_5px_var(--color-auth-primary-button-shadow)] transition-[filter,transform,opacity] duration-150 active:enabled:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-70",
|
"inline-flex min-h-(--auth-field-height) w-full cursor-pointer items-center justify-center gap-sm rounded-full border-0 bg-[linear-gradient(90deg,var(--color-auth-primary-gradient-start),var(--color-auth-primary-gradient-end))] px-lg text-(length:--responsive-body) font-bold text-white shadow-[0_3px_5px_var(--color-auth-primary-button-shadow)] transition-[filter,transform,opacity] duration-150 active:enabled:scale-98 disabled:cursor-not-allowed disabled:opacity-70",
|
||||||
className,
|
className,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@@ -41,7 +41,7 @@ export function AuthPrimaryButton({
|
|||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<span
|
<span
|
||||||
className="inline-block size-[var(--responsive-spinner-size,18px)] animate-spin rounded-full border-2 border-solid border-white border-r-transparent border-b-transparent border-l-transparent"
|
className="inline-block size-(--responsive-spinner-size,18px) animate-spin rounded-full border-2 border-solid border-white border-r-transparent border-b-transparent border-l-transparent"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function AuthSocialButton({
|
|||||||
type={type}
|
type={type}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={[
|
className={[
|
||||||
"inline-flex min-h-[var(--auth-social-button-height)] w-full cursor-pointer items-center justify-center gap-md rounded-full border border-auth-border bg-auth-surface px-lg text-[var(--responsive-body)] font-medium text-auth-text-primary transition-[background,opacity] duration-150 enabled:hover:bg-[rgba(0,0,0,0.02)] disabled:cursor-not-allowed disabled:opacity-50",
|
"inline-flex min-h-(--auth-social-button-height) w-full cursor-pointer items-center justify-center gap-md rounded-full border border-auth-border bg-auth-surface px-lg text-(length:--responsive-body) font-medium text-auth-text-primary transition-[background,opacity] duration-150 enabled:hover:bg-[rgba(0,0,0,0.02)] disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function AuthSocialButtons({
|
|||||||
googleSlot,
|
googleSlot,
|
||||||
}: AuthSocialButtonsProps) {
|
}: AuthSocialButtonsProps) {
|
||||||
const buttonClassName =
|
const buttonClassName =
|
||||||
"inline-flex min-h-[var(--button-height)] w-full cursor-pointer items-center justify-center gap-sm rounded-full border border-chat-input-border bg-[rgba(255,255,255,0.04)] px-lg text-[var(--responsive-body)] font-medium text-text-primary transition-colors duration-150 enabled:hover:bg-[rgba(255,255,255,0.08)] disabled:cursor-not-allowed disabled:opacity-50";
|
"inline-flex min-h-(--button-height) w-full cursor-pointer items-center justify-center gap-sm rounded-full border border-chat-input-border bg-[rgba(255,255,255,0.04)] px-lg text-(length:--responsive-body) font-medium text-text-primary transition-colors duration-150 enabled:hover:bg-[rgba(255,255,255,0.08)] disabled:cursor-not-allowed disabled:opacity-50";
|
||||||
const iconClassName = "inline-flex size-5 items-center justify-center";
|
const iconClassName = "inline-flex size-5 items-center justify-center";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -47,7 +47,7 @@ export function AuthSocialButtons({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{googleSlot ? (
|
{googleSlot ? (
|
||||||
<div className="flex min-h-[var(--button-height)] w-full justify-center">
|
<div className="flex min-h-(--button-height) w-full justify-center">
|
||||||
{googleSlot}
|
{googleSlot}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const AuthTextField = forwardRef<HTMLInputElement, AuthTextFieldProps>(
|
|||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const inputClassName = [
|
const inputClassName = [
|
||||||
"min-h-[var(--auth-field-height)] w-full rounded-full border-0 bg-auth-surface py-0 pl-xxl pr-lg text-[var(--responsive-body)] text-auth-text-primary shadow-[0_3px_5px_var(--color-auth-input-shadow)] outline-none transition-shadow duration-150 placeholder:text-auth-input-placeholder focus:shadow-[0_3px_5px_var(--color-auth-input-shadow),0_0_0_2px_var(--color-accent)] disabled:cursor-not-allowed disabled:opacity-70",
|
"min-h-(--auth-field-height) w-full rounded-full border-0 bg-auth-surface py-0 pl-xxl pr-lg text-(length:--responsive-body) text-auth-text-primary shadow-[0_3px_5px_var(--color-auth-input-shadow)] outline-none transition-shadow duration-150 placeholder:text-auth-input-placeholder focus:shadow-[0_3px_5px_var(--color-auth-input-shadow),0_0_0_2px_var(--color-accent)] disabled:cursor-not-allowed disabled:opacity-70",
|
||||||
errorMessage
|
errorMessage
|
||||||
? "shadow-[0_3px_5px_rgba(255,77,79,0.18),0_0_0_1px_var(--color-error)] focus:shadow-[0_3px_5px_rgba(255,77,79,0.18),0_0_0_2px_var(--color-error)]"
|
? "shadow-[0_3px_5px_rgba(255,77,79,0.18),0_0_0_1px_var(--color-error)] focus:shadow-[0_3px_5px_rgba(255,77,79,0.18),0_0_0_2px_var(--color-error)]"
|
||||||
: "",
|
: "",
|
||||||
@@ -54,7 +54,7 @@ export const AuthTextField = forwardRef<HTMLInputElement, AuthTextFieldProps>(
|
|||||||
return (
|
return (
|
||||||
<label className="flex w-full flex-col gap-xs">
|
<label className="flex w-full flex-col gap-xs">
|
||||||
{label ? (
|
{label ? (
|
||||||
<span className="text-[var(--responsive-caption)] text-text-secondary">
|
<span className="text-(length:--responsive-caption) text-text-secondary">
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -79,7 +79,7 @@ export const AuthTextField = forwardRef<HTMLInputElement, AuthTextFieldProps>(
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{errorMessage ? (
|
{errorMessage ? (
|
||||||
<span className="text-[var(--responsive-caption)] text-error">
|
<span className="text-(length:--responsive-caption) text-error">
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ describe("chat Tailwind components", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(aiHtml).toContain('aria-label="AI avatar"');
|
expect(aiHtml).toContain('aria-label="AI avatar"');
|
||||||
expect(aiHtml).toContain("size-[var(--chat-avatar-size,43px)]");
|
expect(aiHtml).toContain("size-(--chat-avatar-size,43px)");
|
||||||
expect(aiHtml).toContain("size-full object-cover");
|
expect(aiHtml).toContain("size-full object-cover");
|
||||||
expect(aiHtml).toContain("%2Fimages%2Fchat%2Fpic-chat-elio.png");
|
expect(aiHtml).toContain("%2Fimages%2Fchat%2Fpic-chat-elio.png");
|
||||||
expect(userHtml).toContain('aria-label="User avatar"');
|
expect(userHtml).toContain('aria-label="User avatar"');
|
||||||
@@ -97,7 +97,7 @@ describe("chat Tailwind components", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(activeHtml).toContain('aria-label="Send message"');
|
expect(activeHtml).toContain('aria-label="Send message"');
|
||||||
expect(activeHtml).toContain("size-[var(--chat-send-button-size,40px)]");
|
expect(activeHtml).toContain("size-(--chat-send-button-size,40px)");
|
||||||
expect(activeHtml).toContain(
|
expect(activeHtml).toContain(
|
||||||
"bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]",
|
"bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]",
|
||||||
);
|
);
|
||||||
@@ -124,7 +124,7 @@ describe("chat Tailwind components", () => {
|
|||||||
expect(openableHtml).toContain("rounded-tl-none");
|
expect(openableHtml).toContain("rounded-tl-none");
|
||||||
expect(openableHtml).toContain("block h-auto w-full object-cover");
|
expect(openableHtml).toContain("block h-auto w-full object-cover");
|
||||||
expect(openableHtml).toContain("data:image/png;base64,/chat-image.png");
|
expect(openableHtml).toContain("data:image/png;base64,/chat-image.png");
|
||||||
expect(paywalledHtml).toContain("scale-[1.04] blur-[8px]");
|
expect(paywalledHtml).toContain("scale-104 blur-sm");
|
||||||
expect(paywalledHtml).toContain(
|
expect(paywalledHtml).toContain(
|
||||||
"bg-[linear-gradient(180deg,rgba(255,255,255,0.04)_0%,rgba(255,255,255,0.14)_100%)]",
|
"bg-[linear-gradient(180deg,rgba(255,255,255,0.04)_0%,rgba(255,255,255,0.14)_100%)]",
|
||||||
);
|
);
|
||||||
@@ -149,7 +149,7 @@ describe("chat Tailwind components", () => {
|
|||||||
expect(html).toContain("Credits keep the conversation going.");
|
expect(html).toContain("Credits keep the conversation going.");
|
||||||
expect(html).toContain('aria-label="Top up"');
|
expect(html).toContain('aria-label="Top up"');
|
||||||
expect(html).toContain("bg-[linear-gradient(135deg,#f96ade_0%,#f657a0_100%)]");
|
expect(html).toContain("bg-[linear-gradient(135deg,#f96ade_0%,#f657a0_100%)]");
|
||||||
expect(html).toContain("active:scale-[0.98]");
|
expect(html).toContain("active:scale-98");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders ChatHeader guest and member branches", () => {
|
it("renders ChatHeader guest and member branches", () => {
|
||||||
@@ -159,12 +159,12 @@ describe("chat Tailwind components", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(guestHtml).toContain('aria-label="Sign up to unlock more features"');
|
expect(guestHtml).toContain('aria-label="Sign up to unlock more features"');
|
||||||
expect(guestHtml).toContain("bg-[var(--color-accent,#f84d96)]");
|
expect(guestHtml).toContain("bg-(--color-accent,#f84d96)");
|
||||||
expect(guestHtml).toContain("size-[var(--icon-size-sm,16px)]");
|
expect(guestHtml).toContain("size-(--icon-size-sm,16px)");
|
||||||
expect(memberHtml).toContain("Offer");
|
expect(memberHtml).toContain("Offer");
|
||||||
expect(memberHtml).toContain('aria-label="Menu"');
|
expect(memberHtml).toContain('aria-label="Menu"');
|
||||||
expect(memberHtml).toContain("bg-[rgba(13,11,20,0.7)]");
|
expect(memberHtml).toContain("bg-[rgba(13,11,20,0.7)]");
|
||||||
expect(memberHtml).toContain("size-[var(--icon-size-xl,32px)]");
|
expect(memberHtml).toContain("size-(--icon-size-xl,32px)");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders ChatInputTextField with textarea utilities", () => {
|
it("renders ChatInputTextField with textarea utilities", () => {
|
||||||
@@ -179,10 +179,10 @@ describe("chat Tailwind components", () => {
|
|||||||
|
|
||||||
expect(html).toContain("<textarea");
|
expect(html).toContain("<textarea");
|
||||||
expect(html).toContain('aria-label="Message"');
|
expect(html).toContain('aria-label="Message"');
|
||||||
expect(html).toContain("min-h-[var(--chat-send-button-size,40px)]");
|
expect(html).toContain("min-h-(--chat-send-button-size,40px)");
|
||||||
expect(html).toContain("max-h-[min(30vh,120px)]");
|
expect(html).toContain("max-h-[min(30vh,120px)]");
|
||||||
expect(html).toContain("caret-[var(--color-accent,#f84d96)]");
|
expect(html).toContain("caret-(--color-accent,#f84d96)");
|
||||||
expect(html).toContain("placeholder:text-[var(--color-text-hint,#757575)]");
|
expect(html).toContain("placeholder:text-(--color-text-hint,#757575)");
|
||||||
expect(html).toContain("Message Elio");
|
expect(html).toContain("Message Elio");
|
||||||
expect(html).toContain("Hello");
|
expect(html).toContain("Hello");
|
||||||
});
|
});
|
||||||
@@ -202,8 +202,8 @@ describe("chat Tailwind components", () => {
|
|||||||
expect(html).toContain('aria-expanded="true"');
|
expect(html).toContain('aria-expanded="true"');
|
||||||
expect(html).toContain('aria-label="Open elsewhere. Better experience"');
|
expect(html).toContain('aria-label="Open elsewhere. Better experience"');
|
||||||
expect(html).toContain("grid-cols-[auto_minmax(0,1fr)]");
|
expect(html).toContain("grid-cols-[auto_minmax(0,1fr)]");
|
||||||
expect(html).toContain("backdrop-blur-[12px]");
|
expect(html).toContain("backdrop-blur-md");
|
||||||
expect(html).toContain("hover:opacity-[0.94]");
|
expect(html).toContain("hover:opacity-94");
|
||||||
expect(html).toContain("Open elsewhere");
|
expect(html).toContain("Open elsewhere");
|
||||||
expect(html).toContain("Better experience");
|
expect(html).toContain("Better experience");
|
||||||
});
|
});
|
||||||
@@ -217,10 +217,10 @@ describe("chat Tailwind components", () => {
|
|||||||
expect(html).toContain('aria-modal="true"');
|
expect(html).toContain('aria-modal="true"');
|
||||||
expect(html).toContain('aria-labelledby="pwa-dialog-title"');
|
expect(html).toContain('aria-labelledby="pwa-dialog-title"');
|
||||||
expect(html).toContain("fixed inset-0");
|
expect(html).toContain("fixed inset-0");
|
||||||
expect(html).toContain("max-w-[var(--pwa-install-dialog-max-width,360px)]");
|
expect(html).toContain("max-w-(--pwa-install-dialog-max-width,360px)");
|
||||||
expect(html).toContain("size-[var(--icon-size-86,86px)]");
|
expect(html).toContain("size-(--icon-size-86,86px)");
|
||||||
expect(html).toContain("min-h-[var(--pwa-button-height,44px)]");
|
expect(html).toContain("min-h-(--pwa-button-height,44px)");
|
||||||
expect(html).toContain("text-[var(--color-pwa-button-text,#ffffff)]");
|
expect(html).toContain("text-(--color-pwa-button-text,#ffffff)");
|
||||||
expect(html).toContain("%2Fimages%2Ficons%2Ficon-add-to-home.png");
|
expect(html).toContain("%2Fimages%2Ficons%2Ficon-add-to-home.png");
|
||||||
expect(html).toContain("Add to Home Screen");
|
expect(html).toContain("Add to Home Screen");
|
||||||
expect(html).toContain("Cancel");
|
expect(html).toContain("Cancel");
|
||||||
@@ -251,12 +251,12 @@ describe("chat Tailwind components", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain('aria-labelledby="history-unlock-title"');
|
expect(html).toContain('aria-labelledby="history-unlock-title"');
|
||||||
expect(html).toContain("z-[200]");
|
expect(html).toContain("z-200");
|
||||||
expect(html).toContain("Unlock previous messages?");
|
expect(html).toContain("Unlock previous messages?");
|
||||||
expect(html).toContain("We found 3 locked messages");
|
expect(html).toContain("We found 3 locked messages");
|
||||||
expect(html).toContain("Could not unlock history.");
|
expect(html).toContain("Could not unlock history.");
|
||||||
expect(html).toContain("Unlocking...");
|
expect(html).toContain("Unlocking...");
|
||||||
expect(html).toContain("text-[var(--color-pwa-button-text,#ffffff)]");
|
expect(html).toContain("text-(--color-pwa-button-text,#ffffff)");
|
||||||
expect(html).toContain("disabled");
|
expect(html).toContain("disabled");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -286,12 +286,12 @@ describe("chat Tailwind components", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain('aria-labelledby="insufficient-credits-title"');
|
expect(html).toContain('aria-labelledby="insufficient-credits-title"');
|
||||||
expect(html).toContain("z-[220]");
|
expect(html).toContain("z-220");
|
||||||
expect(html).toContain("Not enough credits");
|
expect(html).toContain("Not enough credits");
|
||||||
expect(html).toContain("Balance");
|
expect(html).toContain("Balance");
|
||||||
expect(html).toContain("Required");
|
expect(html).toContain("Required");
|
||||||
expect(html).toContain("Still needed");
|
expect(html).toContain("Still needed");
|
||||||
expect(html).toContain("text-[var(--color-pwa-button-text,#ffffff)]");
|
expect(html).toContain("text-(--color-pwa-button-text,#ffffff)");
|
||||||
expect(html).toContain("bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)]");
|
expect(html).toContain("bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)]");
|
||||||
expect(html).toContain("Top up now");
|
expect(html).toContain("Top up now");
|
||||||
});
|
});
|
||||||
@@ -318,7 +318,7 @@ describe("chat Tailwind components", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(html).toContain('aria-labelledby="voice-unlock-options-title"');
|
expect(html).toContain('aria-labelledby="voice-unlock-options-title"');
|
||||||
expect(html).toContain("z-[72]");
|
expect(html).toContain("z-72");
|
||||||
expect(html).toContain("Unlock voice message");
|
expect(html).toContain("Unlock voice message");
|
||||||
expect(html).toContain("购买语音包");
|
expect(html).toContain("购买语音包");
|
||||||
expect(html).toContain("开通 VIP");
|
expect(html).toContain("开通 VIP");
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const DEFAULT_TITLE = "Open in external browser";
|
|||||||
const DEFAULT_DESCRIPTION = "Find me more easily";
|
const DEFAULT_DESCRIPTION = "Find me more easily";
|
||||||
const DEFAULT_AUTO_COLLAPSE_DELAY_MS = 3500;
|
const DEFAULT_AUTO_COLLAPSE_DELAY_MS = 3500;
|
||||||
const OVERLAY_BASE_CLASS_NAME =
|
const OVERLAY_BASE_CLASS_NAME =
|
||||||
"pointer-events-auto absolute right-[var(--spacing-2,8px)] top-[var(--spacing-2,8px)] z-20 box-border min-h-12 max-w-[min(74vw,260px)] cursor-pointer border border-[rgba(255,255,255,0.18)] bg-[linear-gradient(135deg,rgba(255,255,255,0.14),rgba(255,255,255,0.04)),var(--color-blur-background,rgba(13,11,20,0.86))] text-white shadow-[0_12px_28px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.16)] backdrop-blur-[12px] backdrop-saturate-[1.2] transition-[border-color,box-shadow,opacity,padding,transform] duration-[180ms] [-webkit-tap-highlight-color:transparent] hover:-translate-y-px hover:border-[rgba(255,255,255,0.28)] hover:opacity-[0.94] hover:shadow-[0_14px_32px_rgba(0,0,0,0.28),inset_0_1px_0_rgba(255,255,255,0.18)] active:translate-y-px active:scale-[0.99] active:opacity-[0.88] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[rgba(255,255,255,0.88)]";
|
"pointer-events-auto absolute right-(--spacing-2,8px) top-(--spacing-2,8px) z-20 box-border min-h-12 max-w-[min(74vw,260px)] cursor-pointer border border-[rgba(255,255,255,0.18)] bg-[linear-gradient(135deg,rgba(255,255,255,0.14),rgba(255,255,255,0.04)),var(--color-blur-background,rgba(13,11,20,0.86))] text-white shadow-[0_12px_28px_rgba(0,0,0,0.24),inset_0_1px_0_rgba(255,255,255,0.16)] backdrop-blur-md backdrop-saturate-120 transition-[border-color,box-shadow,opacity,padding,transform] duration-180 [-webkit-tap-highlight-color:transparent] hover:-translate-y-px hover:border-[rgba(255,255,255,0.28)] hover:opacity-94 hover:shadow-[0_14px_32px_rgba(0,0,0,0.28),inset_0_1px_0_rgba(255,255,255,0.18)] active:translate-y-px active:scale-99 active:opacity-88 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-[rgba(255,255,255,0.88)]";
|
||||||
const OVERLAY_EXPANDED_CLASS_NAME =
|
const OVERLAY_EXPANDED_CLASS_NAME =
|
||||||
"grid w-fit grid-cols-[auto_minmax(0,1fr)] items-center gap-2 rounded-[18px] px-2.5 py-2";
|
"grid w-fit grid-cols-[auto_minmax(0,1fr)] items-center gap-2 rounded-[18px] px-2.5 py-2";
|
||||||
const OVERLAY_COLLAPSED_CLASS_NAME =
|
const OVERLAY_COLLAPSED_CLASS_NAME =
|
||||||
@@ -90,7 +90,7 @@ export function BrowserHintOverlay({
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className="inline-flex size-[30px] items-center justify-center rounded-full bg-[rgba(255,255,255,0.15)] text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.16)] transition-[background,transform] duration-[180ms]"
|
className="inline-flex size-[30px] items-center justify-center rounded-full bg-[rgba(255,255,255,0.15)] text-white shadow-[inset_0_1px_0_rgba(255,255,255,0.16)] transition-[background,transform] duration-180"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<ExternalLink size={15} strokeWidth={2.4} />
|
<ExternalLink size={15} strokeWidth={2.4} />
|
||||||
@@ -103,7 +103,7 @@ export function BrowserHintOverlay({
|
|||||||
}
|
}
|
||||||
aria-hidden={!isExpanded}
|
aria-hidden={!isExpanded}
|
||||||
>
|
>
|
||||||
<span className="break-words text-[var(--font-size-sm,12px)] font-extrabold leading-[1.25] text-white">
|
<span className="break-words text-(length:--font-size-sm,12px) font-extrabold leading-[1.25] text-white">
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
<span className="break-words text-[11px] font-semibold leading-[1.2] text-[rgba(255,255,255,0.72)]">
|
<span className="break-words text-[11px] font-semibold leading-[1.2] text-[rgba(255,255,255,0.72)]">
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ export function ChatHeader({ isGuest, offerBanner }: ChatHeaderProps) {
|
|||||||
<header className="flex shrink-0 flex-col items-stretch bg-transparent p-0">
|
<header className="flex shrink-0 flex-col items-stretch bg-transparent p-0">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex w-full cursor-pointer items-center justify-center gap-[var(--spacing-sm,8px)] border-0 bg-[var(--color-accent,#f84d96)] px-[var(--spacing-md,12px)] py-[var(--spacing-sm,8px)] text-center text-[var(--responsive-caption,var(--font-size-sm,12px))] font-medium text-white"
|
className="flex w-full cursor-pointer items-center justify-center gap-(--spacing-sm,8px) border-0 bg-(--color-accent,#f84d96) px-(--spacing-md,12px) py-(--spacing-sm,8px) text-center text-(length:--responsive-caption,var(--font-size-sm,12px)) font-medium text-white"
|
||||||
onClick={() => navigator.openAuth(ROUTES.chat)}
|
onClick={() => navigator.openAuth(ROUTES.chat)}
|
||||||
aria-label="Sign up to unlock more features"
|
aria-label="Sign up to unlock more features"
|
||||||
>
|
>
|
||||||
<Lock
|
<Lock
|
||||||
className="size-[var(--icon-size-sm,16px)]"
|
className="size-(--icon-size-sm,16px)"
|
||||||
size={16}
|
size={16}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
@@ -43,16 +43,16 @@ export function ChatHeader({ isGuest, offerBanner }: ChatHeaderProps) {
|
|||||||
<header className="flex shrink-0 flex-col items-stretch bg-transparent p-0">
|
<header className="flex shrink-0 flex-col items-stretch bg-transparent p-0">
|
||||||
{offerBanner}
|
{offerBanner}
|
||||||
|
|
||||||
<div className="ml-[var(--spacing-md,12px)] flex items-center justify-between px-[var(--spacing-md,12px)] py-[var(--spacing-sm,8px)]">
|
<div className="ml-(--spacing-md,12px) flex items-center justify-between px-(--spacing-md,12px) py-(--spacing-sm,8px)">
|
||||||
{/* 菜单按钮(点击 → push 到 /sidebar,与 Dart MenuButton 一致) */}
|
{/* 菜单按钮(点击 → push 到 /sidebar,与 Dart MenuButton 一致) */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex cursor-pointer items-center justify-center rounded-[var(--radius-full,999px)] border-0 bg-[rgba(13,11,20,0.7)] p-[var(--spacing-sm,8px)] text-[var(--color-text-primary,#fff)]"
|
className="flex cursor-pointer items-center justify-center rounded-(--radius-full,999px) border-0 bg-[rgba(13,11,20,0.7)] p-(--spacing-sm,8px) text-(--color-text-primary,#fff)"
|
||||||
onClick={() => navigator.push(ROUTES.sidebar)}
|
onClick={() => navigator.push(ROUTES.sidebar)}
|
||||||
aria-label="Menu"
|
aria-label="Menu"
|
||||||
>
|
>
|
||||||
<Menu
|
<Menu
|
||||||
className="size-[var(--icon-size-xl,32px)]"
|
className="size-(--icon-size-xl,32px)"
|
||||||
size={24}
|
size={24}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const ChatInputTextField = forwardRef<
|
|||||||
<div className="flex min-w-0 flex-auto items-center rounded-full bg-white px-(--spacing-lg,16px)">
|
<div className="flex min-w-0 flex-auto items-center rounded-full bg-white px-(--spacing-lg,16px)">
|
||||||
<textarea
|
<textarea
|
||||||
ref={innerRef}
|
ref={innerRef}
|
||||||
className="min-h-[var(--chat-send-button-size,40px)] max-h-[min(30vh,120px)] w-full min-w-0 flex-auto resize-none border-0 bg-transparent pb-0 pl-0 pr-0 pt-[clamp(5px,1.111vw,6px)] font-[inherit] text-[16px] leading-[clamp(22px,4.444vw,24px)] text-[var(--color-text-foreground,#000)] caret-[var(--color-accent,#f84d96)] outline-none placeholder:text-[var(--color-text-hint,#757575)]"
|
className="min-h-(--chat-send-button-size,40px) max-h-[min(30vh,120px)] w-full min-w-0 flex-auto resize-none border-0 bg-transparent pb-0 pl-0 pr-0 pt-[clamp(5px,1.111vw,6px)] font-[inherit] text-[16px] leading-[clamp(22px,4.444vw,24px)] text-(--color-text-foreground,#000) caret-(--color-accent,#f84d96) outline-none placeholder:text-(--color-text-hint,#757575)"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e: FormEvent<HTMLTextAreaElement>) =>
|
onChange={(e: FormEvent<HTMLTextAreaElement>) =>
|
||||||
onChange(e.currentTarget.value)
|
onChange(e.currentTarget.value)
|
||||||
|
|||||||
@@ -49,12 +49,12 @@ export function ChatInsufficientCreditsBanner({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="mx-[var(--chat-inline-padding,16px)] mb-[var(--page-section-gap,16px)] shrink-0 rounded-[var(--responsive-card-radius-sm,24px)] bg-[linear-gradient(135deg,#f96ade_0%,#f657a0_100%)] px-[var(--responsive-card-padding,16px)] py-[var(--page-section-gap-lg,24px)] text-center shadow-[0_4px_16px_rgba(248,77,150,0.25)]"
|
className="mx-(--chat-inline-padding,16px) mb-(--page-section-gap,16px) shrink-0 rounded-(--responsive-card-radius-sm,24px) bg-[linear-gradient(135deg,#f96ade_0%,#f657a0_100%)] px-(--responsive-card-padding,16px) py-(--page-section-gap-lg,24px) text-center shadow-[0_4px_16px_rgba(248,77,150,0.25)]"
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
data-testid="chat-insufficient-credits-banner"
|
data-testid="chat-insufficient-credits-banner"
|
||||||
>
|
>
|
||||||
<p className="m-0 mb-[var(--spacing-sm,8px)] text-[var(--responsive-card-title,18px)] font-semibold leading-[1.4] text-white opacity-95">
|
<p className="m-0 mb-(--spacing-sm,8px) text-(length:--responsive-card-title,18px) font-semibold leading-[1.4] text-white opacity-95">
|
||||||
{titleLines.map((line, index) => (
|
{titleLines.map((line, index) => (
|
||||||
<span key={`${line}-${index}`}>
|
<span key={`${line}-${index}`}>
|
||||||
{line}
|
{line}
|
||||||
@@ -63,13 +63,13 @@ export function ChatInsufficientCreditsBanner({
|
|||||||
))}
|
))}
|
||||||
</p>
|
</p>
|
||||||
{description ? (
|
{description ? (
|
||||||
<p className="mx-auto mb-[var(--spacing-lg,16px)] mt-0 max-w-[min(100%,360px)] text-[var(--responsive-caption,14px)] font-semibold leading-[1.4] text-[rgba(255,255,255,0.88)]">
|
<p className="mx-auto mb-(--spacing-lg,16px) mt-0 max-w-[min(100%,360px)] text-(length:--responsive-caption,14px) font-semibold leading-[1.4] text-[rgba(255,255,255,0.88)]">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="min-h-[var(--responsive-control-height,48px)] w-full cursor-pointer rounded-full border-0 bg-[linear-gradient(135deg,#ffb8e0_0%,#f57ec0_100%)] px-[var(--responsive-card-padding-lg,24px)] text-[var(--responsive-card-title,18px)] font-bold text-white shadow-[0_2px_8px_rgba(0,0,0,0.15)] transition-[box-shadow,transform] duration-150 hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)] active:scale-[0.98]"
|
className="min-h-(--responsive-control-height,48px) w-full cursor-pointer rounded-full border-0 bg-[linear-gradient(135deg,#ffb8e0_0%,#f57ec0_100%)] px-(--responsive-card-padding-lg,24px) text-(length:--responsive-card-title,18px) font-bold text-white shadow-[0_2px_8px_rgba(0,0,0,0.15)] transition-[box-shadow,transform] duration-150 hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)] active:scale-98"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
aria-label={ctaLabel}
|
aria-label={ctaLabel}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function ChatSendButton({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={[
|
className={[
|
||||||
"flex aspect-square size-[var(--chat-send-button-size,40px)] shrink-0 cursor-pointer items-center justify-center rounded-full border-0 bg-[var(--color-button-gradient-end,#fc69df)] text-white transition-[background,transform] duration-200 disabled:cursor-not-allowed disabled:opacity-40 focus-visible:bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]",
|
"flex aspect-square size-(--chat-send-button-size,40px) shrink-0 cursor-pointer items-center justify-center rounded-full border-0 bg-(--color-button-gradient-end,#fc69df) text-white transition-[background,transform] duration-200 disabled:cursor-not-allowed disabled:opacity-40 focus-visible:bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]",
|
||||||
isActive
|
isActive
|
||||||
? "bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]"
|
? "bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]"
|
||||||
: "bg-[#f8a8ce] text-[rgba(255,255,255,0.88)] shadow-none",
|
: "bg-[#f8a8ce] text-[rgba(255,255,255,0.88)] shadow-none",
|
||||||
@@ -49,7 +49,7 @@ export function ChatSendButton({
|
|||||||
aria-label="Send message"
|
aria-label="Send message"
|
||||||
>
|
>
|
||||||
<ArrowUp
|
<ArrowUp
|
||||||
className="size-[var(--icon-size-xl,24px)] text-[length:var(--icon-size-xl,24px)] leading-none"
|
className="size-(--icon-size-xl,24px) text-(length:--icon-size-xl,24px) leading-none"
|
||||||
size={24}
|
size={24}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -21,31 +21,31 @@ export function HistoryUnlockDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-[200] flex items-center justify-center bg-[rgba(0,0,0,0.5)] pb-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-bottom,0px))] pl-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-left,0px))] pr-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-right,0px))] pt-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-top,0px))]"
|
className="fixed inset-0 z-200 flex items-center justify-center bg-[rgba(0,0,0,0.5)] pb-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-bottom,0px))] pl-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-left,0px))] pr-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-right,0px))] pt-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-top,0px))]"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="history-unlock-title"
|
aria-labelledby="history-unlock-title"
|
||||||
>
|
>
|
||||||
<div className="w-full max-w-[var(--pwa-install-dialog-max-width,360px)] rounded-[var(--responsive-card-radius,40px)] bg-[var(--color-page-background,#ffffff)] px-[var(--responsive-card-padding,16px)] pb-[var(--responsive-card-padding,16px)] pt-[var(--responsive-card-padding-lg,24px)] text-center shadow-[0_10px_20px_rgba(0,0,0,0.1)]">
|
<div className="w-full max-w-(--pwa-install-dialog-max-width,360px) rounded-(--responsive-card-radius,40px) bg-(--color-page-background,#ffffff) px-(--responsive-card-padding,16px) pb-(--responsive-card-padding,16px) pt-(--responsive-card-padding-lg,24px) text-center shadow-[0_10px_20px_rgba(0,0,0,0.1)]">
|
||||||
<h2
|
<h2
|
||||||
id="history-unlock-title"
|
id="history-unlock-title"
|
||||||
className="m-0 mb-2.5 text-[var(--responsive-page-title,var(--font-size-22,22px))] font-bold leading-[1.2] text-[var(--color-text-foreground,#171717)]"
|
className="m-0 mb-2.5 text-(length:--responsive-page-title,var(--font-size-22,22px)) font-bold leading-[1.2] text-(--color-text-foreground,#171717)"
|
||||||
>
|
>
|
||||||
Unlock previous messages?
|
Unlock previous messages?
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mx-[var(--responsive-card-padding-lg,24px)] mb-[var(--spacing-lg,16px)] mt-0 text-left text-[var(--responsive-body,var(--font-size-lg,16px))] leading-[1.5] text-[#393939]">
|
<p className="mx-(--responsive-card-padding-lg,24px) mb-(--spacing-lg,16px) mt-0 text-left text-(length:--responsive-body,var(--font-size-lg,16px)) leading-[1.5] text-[#393939]">
|
||||||
We found {lockedCount} locked messages in your chat history. You can
|
We found {lockedCount} locked messages in your chat history. You can
|
||||||
unlock them now to continue the conversation with full context.
|
unlock them now to continue the conversation with full context.
|
||||||
</p>
|
</p>
|
||||||
{errorMessage ? (
|
{errorMessage ? (
|
||||||
<p className="mx-[var(--responsive-card-padding-lg,24px)] mb-[var(--spacing-lg,16px)] mt-0 text-left text-[var(--responsive-caption,var(--font-size-md,14px))] leading-[1.4] text-[#c0364c]">
|
<p className="mx-(--responsive-card-padding-lg,24px) mb-(--spacing-lg,16px) mt-0 text-left text-(length:--responsive-caption,var(--font-size-md,14px)) leading-[1.4] text-[#c0364c]">
|
||||||
{errorMessage}
|
{errorMessage}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="flex w-full gap-[var(--spacing-md,12px)]">
|
<div className="flex w-full gap-(--spacing-md,12px)">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex min-h-[var(--pwa-button-height,44px)] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[var(--color-text-secondary,#9e9e9e)] text-[var(--responsive-body,var(--font-size-lg,16px))] font-semibold text-[var(--color-pwa-button-text,#ffffff)] disabled:cursor-not-allowed disabled:opacity-72"
|
className="flex min-h-(--pwa-button-height,44px) flex-auto cursor-pointer items-center justify-center rounded-(--radius-bottom-sheet,28px) border-0 bg-(--color-text-secondary,#9e9e9e) text-(length:--responsive-body,var(--font-size-lg,16px)) font-semibold text-(--color-pwa-button-text,#ffffff) disabled:cursor-not-allowed disabled:opacity-72"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
@@ -53,7 +53,7 @@ export function HistoryUnlockDialog({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex min-h-[var(--pwa-button-height,44px)] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[linear-gradient(var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))] text-[var(--responsive-body,var(--font-size-lg,16px))] font-semibold text-[var(--color-pwa-button-text,#ffffff)] disabled:cursor-not-allowed disabled:opacity-72"
|
className="flex min-h-(--pwa-button-height,44px) flex-auto cursor-pointer items-center justify-center rounded-(--radius-bottom-sheet,28px) border-0 bg-[linear-gradient(var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))] text-(length:--responsive-body,var(--font-size-lg,16px)) font-semibold text-(--color-pwa-button-text,#ffffff) disabled:cursor-not-allowed disabled:opacity-72"
|
||||||
onClick={onConfirm}
|
onClick={onConfirm}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function ImageBubble({
|
|||||||
const canOpen = Boolean(messageId && onOpenImage);
|
const canOpen = Boolean(messageId && onOpenImage);
|
||||||
const imageClassName = [
|
const imageClassName = [
|
||||||
"block h-auto w-full object-cover",
|
"block h-auto w-full object-cover",
|
||||||
imagePaywalled ? "scale-[1.04] blur-[8px]" : "",
|
imagePaywalled ? "scale-104 blur-sm" : "",
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" ");
|
.join(" ");
|
||||||
@@ -38,7 +38,7 @@ export function ImageBubble({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative max-h-[var(--chat-media-size,220px)] max-w-[var(--chat-media-size,220px)] cursor-pointer overflow-hidden rounded-[var(--radius-lg,12px)] rounded-tl-none bg-[var(--color-bubble-background,#fff)]"
|
className="relative max-h-(--chat-media-size,220px) max-w-(--chat-media-size,220px) cursor-pointer overflow-hidden rounded-(--radius-lg,12px) rounded-tl-none bg-(--color-bubble-background,#fff)"
|
||||||
onClick={openImage}
|
onClick={openImage}
|
||||||
role={canOpen ? "button" : undefined}
|
role={canOpen ? "button" : undefined}
|
||||||
tabIndex={canOpen ? 0 : undefined}
|
tabIndex={canOpen ? 0 : undefined}
|
||||||
@@ -55,7 +55,7 @@ export function ImageBubble({
|
|||||||
messageId={messageId}
|
messageId={messageId}
|
||||||
remoteUrl={imageUrl}
|
remoteUrl={imageUrl}
|
||||||
className={imageClassName}
|
className={imageClassName}
|
||||||
errorClassName="flex size-[var(--chat-media-size,220px)] items-center justify-center bg-[var(--color-bubble-background,#fff)] text-[var(--icon-size-xl,24px)] text-[var(--color-text-secondary,#9e9e9e)]"
|
errorClassName="flex size-(--chat-media-size,220px) items-center justify-center bg-(--color-bubble-background,#fff) text-(length:--icon-size-xl,24px) text-(--color-text-secondary,#9e9e9e)"
|
||||||
width={240}
|
width={240}
|
||||||
height={240}
|
height={240}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -23,56 +23,56 @@ export function InsufficientCreditsDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-[220] flex items-center justify-center bg-[rgba(0,0,0,0.52)] pb-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-bottom,0px))] pl-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-left,0px))] pr-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-right,0px))] pt-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-top,0px))]"
|
className="fixed inset-0 z-220 flex items-center justify-center bg-[rgba(0,0,0,0.52)] pb-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-bottom,0px))] pl-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-left,0px))] pr-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-right,0px))] pt-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-top,0px))]"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="insufficient-credits-title"
|
aria-labelledby="insufficient-credits-title"
|
||||||
>
|
>
|
||||||
<div className="w-full max-w-[var(--pwa-install-dialog-max-width,360px)] rounded-[var(--responsive-card-radius,36px)] border border-[rgba(246,87,160,0.16)] bg-[var(--color-page-background,#ffffff)] px-[var(--responsive-card-padding,18px)] pb-[var(--responsive-card-padding,18px)] pt-[var(--responsive-card-padding-lg,24px)] text-center shadow-[0_18px_46px_rgba(38,18,31,0.18)]">
|
<div className="w-full max-w-(--pwa-install-dialog-max-width,360px) rounded-(--responsive-card-radius,36px) border border-[rgba(246,87,160,0.16)] bg-(--color-page-background,#ffffff) px-(--responsive-card-padding,18px) pb-(--responsive-card-padding,18px) pt-(--responsive-card-padding-lg,24px) text-center shadow-[0_18px_46px_rgba(38,18,31,0.18)]">
|
||||||
<h2
|
<h2
|
||||||
id="insufficient-credits-title"
|
id="insufficient-credits-title"
|
||||||
className="m-0 mb-2.5 text-[var(--responsive-page-title,var(--font-size-22,22px))] font-bold leading-[1.2] text-[var(--color-text-foreground,#171717)]"
|
className="m-0 mb-2.5 text-(length:--responsive-page-title,var(--font-size-22,22px)) font-bold leading-[1.2] text-(--color-text-foreground,#171717)"
|
||||||
>
|
>
|
||||||
Not enough credits
|
Not enough credits
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mx-[var(--spacing-md,12px)] mb-[var(--page-section-gap,18px)] mt-0 text-[var(--responsive-body,var(--font-size-lg,16px))] leading-[1.5] text-[#393939]">
|
<p className="mx-(--spacing-md,12px) mb-(--page-section-gap,18px) mt-0 text-(length:--responsive-body,var(--font-size-lg,16px)) leading-[1.5] text-[#393939]">
|
||||||
Top up your credits to unlock this message and keep the moment going.
|
Top up your credits to unlock this message and keep the moment going.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{showCreditDetail ? (
|
{showCreditDetail ? (
|
||||||
<dl className="mb-[var(--page-section-gap,18px)] grid gap-[var(--spacing-sm,8px)] rounded-[var(--responsive-card-radius-sm,22px)] bg-[#fff4f9] p-[var(--spacing-md,12px)]">
|
<dl className="mb-(--page-section-gap,18px) grid gap-(--spacing-sm,8px) rounded-(--responsive-card-radius-sm,22px) bg-[#fff4f9] p-(--spacing-md,12px)">
|
||||||
<div className="flex items-center justify-between gap-[var(--spacing-md,12px)] text-[var(--responsive-caption,var(--font-size-md,14px))] leading-[1.3] text-[#4a3b43]">
|
<div className="flex items-center justify-between gap-(--spacing-md,12px) text-(length:--responsive-caption,var(--font-size-md,14px)) leading-[1.3] text-[#4a3b43]">
|
||||||
<dt className="m-0 font-medium">Balance</dt>
|
<dt className="m-0 font-medium">Balance</dt>
|
||||||
<dd className="m-0 text-[var(--responsive-body,var(--font-size-lg,16px))] font-extrabold text-[#f657a0]">
|
<dd className="m-0 text-(length:--responsive-body,var(--font-size-lg,16px)) font-extrabold text-[#f657a0]">
|
||||||
{creditBalance}
|
{creditBalance}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between gap-[var(--spacing-md,12px)] text-[var(--responsive-caption,var(--font-size-md,14px))] leading-[1.3] text-[#4a3b43]">
|
<div className="flex items-center justify-between gap-(--spacing-md,12px) text-(length:--responsive-caption,var(--font-size-md,14px)) leading-[1.3] text-[#4a3b43]">
|
||||||
<dt className="m-0 font-medium">Required</dt>
|
<dt className="m-0 font-medium">Required</dt>
|
||||||
<dd className="m-0 text-[var(--responsive-body,var(--font-size-lg,16px))] font-extrabold text-[#f657a0]">
|
<dd className="m-0 text-(length:--responsive-body,var(--font-size-lg,16px)) font-extrabold text-[#f657a0]">
|
||||||
{requiredCredits}
|
{requiredCredits}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between gap-[var(--spacing-md,12px)] text-[var(--responsive-caption,var(--font-size-md,14px))] leading-[1.3] text-[#4a3b43]">
|
<div className="flex items-center justify-between gap-(--spacing-md,12px) text-(length:--responsive-caption,var(--font-size-md,14px)) leading-[1.3] text-[#4a3b43]">
|
||||||
<dt className="m-0 font-medium">Still needed</dt>
|
<dt className="m-0 font-medium">Still needed</dt>
|
||||||
<dd className="m-0 text-[var(--responsive-body,var(--font-size-lg,16px))] font-extrabold text-[#f657a0]">
|
<dd className="m-0 text-(length:--responsive-body,var(--font-size-lg,16px)) font-extrabold text-[#f657a0]">
|
||||||
{shortfallCredits}
|
{shortfallCredits}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className="flex w-full gap-[var(--spacing-md,12px)]">
|
<div className="flex w-full gap-(--spacing-md,12px)">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex min-h-[var(--pwa-button-height,44px)] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[var(--color-text-secondary,#9e9e9e)] text-[var(--responsive-body,var(--font-size-lg,16px))] font-bold text-[var(--color-pwa-button-text,#ffffff)] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
className="flex min-h-(--pwa-button-height,44px) flex-auto cursor-pointer items-center justify-center rounded-(--radius-bottom-sheet,28px) border-0 bg-(--color-text-secondary,#9e9e9e) text-(length:--responsive-body,var(--font-size-lg,16px)) font-bold text-(--color-pwa-button-text,#ffffff) focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Later
|
Later
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex min-h-[var(--pwa-button-height,44px)] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)] text-[var(--responsive-body,var(--font-size-lg,16px))] font-bold text-[var(--color-pwa-button-text,#ffffff)] shadow-[0_6px_14px_rgba(246,87,160,0.28)] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
className="flex min-h-(--pwa-button-height,44px) flex-auto cursor-pointer items-center justify-center rounded-(--radius-bottom-sheet,28px) border-0 bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)] text-(length:--responsive-body,var(--font-size-lg,16px)) font-bold text-(--color-pwa-button-text,#ffffff) shadow-[0_6px_14px_rgba(246,87,160,0.28)] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
||||||
onClick={onConfirm}
|
onClick={onConfirm}
|
||||||
>
|
>
|
||||||
Top up now
|
Top up now
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function LockedImageMessageCard({
|
|||||||
}: LockedImageMessageCardProps) {
|
}: LockedImageMessageCardProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-[min(72vw,280px)] overflow-hidden rounded-[var(--responsive-card-radius-sm,18px)] rounded-tl-none border border-[rgba(246,87,160,0.2)] bg-white shadow-[0_4px_14px_rgba(246,87,160,0.14)]"
|
className="w-[min(72vw,280px)] overflow-hidden rounded-(--responsive-card-radius-sm,18px) rounded-tl-none border border-[rgba(246,87,160,0.2)] bg-white shadow-[0_4px_14px_rgba(246,87,160,0.14)]"
|
||||||
role="group"
|
role="group"
|
||||||
aria-label="Locked private image"
|
aria-label="Locked private image"
|
||||||
>
|
>
|
||||||
@@ -25,15 +25,15 @@ export function LockedImageMessageCard({
|
|||||||
<LockKeyhole size={19} aria-hidden="true" />
|
<LockKeyhole size={19} aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-[var(--responsive-card-padding,14px)]">
|
<div className="p-(--responsive-card-padding,14px)">
|
||||||
<p className="m-0 text-[var(--responsive-body,14px)] leading-[1.4] text-[#3c3b3b]">
|
<p className="m-0 text-(length:--responsive-body,14px) leading-[1.4] text-[#3c3b3b]">
|
||||||
{hint && hint.length > 0
|
{hint && hint.length > 0
|
||||||
? hint
|
? hint
|
||||||
: "Elio sent you a locked image."}
|
: "Elio sent you a locked image."}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="mt-[var(--spacing-md,12px)] w-full cursor-pointer rounded-full border-0 bg-[linear-gradient(90deg,#ff67e0,#ff52a2)] px-[var(--spacing-md,12px)] py-[clamp(9px,1.852vw,10px)] text-[var(--responsive-body,14px)] font-bold text-white disabled:cursor-not-allowed disabled:opacity-65 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
className="mt-(--spacing-md,12px) w-full cursor-pointer rounded-full border-0 bg-[linear-gradient(90deg,#ff67e0,#ff52a2)] px-(--spacing-md,12px) py-[clamp(9px,1.852vw,10px)] text-(length:--responsive-body,14px) font-bold text-white disabled:cursor-not-allowed disabled:opacity-65 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
||||||
disabled={isUnlocking || !onUnlock}
|
disabled={isUnlocking || !onUnlock}
|
||||||
onClick={onUnlock}
|
onClick={onUnlock}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface MessageAvatarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AVATAR_CLASS_NAME =
|
const AVATAR_CLASS_NAME =
|
||||||
"flex size-[var(--chat-avatar-size,43px)] shrink-0 items-center justify-center overflow-hidden rounded-full border-2 border-[var(--color-avatar-border,#fbf3f5)] bg-[var(--color-avatar-border,#fbf3f5)] shadow-[var(--shadow-input-box,0_1px_2px_rgba(0,0,0,0.1))]";
|
"flex size-(--chat-avatar-size,43px) shrink-0 items-center justify-center overflow-hidden rounded-full border-2 border-(--color-avatar-border,#fbf3f5) bg-(--color-avatar-border,#fbf3f5) shadow-(--shadow-input-box,0_1px_2px_rgba(0,0,0,0.1))";
|
||||||
|
|
||||||
export function MessageAvatar({ isFromAI, userAvatarUrl }: MessageAvatarProps) {
|
export function MessageAvatar({ isFromAI, userAvatarUrl }: MessageAvatarProps) {
|
||||||
if (isFromAI) {
|
if (isFromAI) {
|
||||||
|
|||||||
@@ -15,24 +15,24 @@ export function PrivateMessageCard({
|
|||||||
}: PrivateMessageCardProps) {
|
}: PrivateMessageCardProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="max-w-[min(72vw,280px)] rounded-[var(--responsive-card-radius-sm,18px)] rounded-tl-none border border-[rgba(246,87,160,0.2)] bg-[linear-gradient(180deg,rgba(255,244,248,0.95),rgba(255,255,255,0.95)),#ffffff] p-[var(--responsive-card-padding,14px)] text-[#3c3b3b] shadow-[0_4px_12px_rgba(246,87,160,0.12)]"
|
className="max-w-[min(72vw,280px)] rounded-(--responsive-card-radius-sm,18px) rounded-tl-none border border-[rgba(246,87,160,0.2)] bg-[linear-gradient(180deg,rgba(255,244,248,0.95),rgba(255,255,255,0.95)),#ffffff] p-(--responsive-card-padding,14px) text-[#3c3b3b] shadow-[0_4px_12px_rgba(246,87,160,0.12)]"
|
||||||
role="group"
|
role="group"
|
||||||
aria-label="Locked private message"
|
aria-label="Locked private message"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="mb-[clamp(8px,1.852vw,10px)] flex size-[var(--responsive-icon-button-size,42px)] items-center justify-center rounded-full bg-[linear-gradient(135deg,#ff8fc7_0%,#f657a0_100%)] text-white"
|
className="mb-[clamp(8px,1.852vw,10px)] flex size-(--responsive-icon-button-size,42px) items-center justify-center rounded-full bg-[linear-gradient(135deg,#ff8fc7_0%,#f657a0_100%)] text-white"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<LockKeyhole className="block" size={22} />
|
<LockKeyhole className="block" size={22} />
|
||||||
</div>
|
</div>
|
||||||
<p className="m-0 text-[var(--responsive-body,14px)] leading-[1.4] text-[#3c3b3b]">
|
<p className="m-0 text-(length:--responsive-body,14px) leading-[1.4] text-[#3c3b3b]">
|
||||||
{hint && hint.length > 0
|
{hint && hint.length > 0
|
||||||
? hint
|
? hint
|
||||||
: "Elio has a private message for you."}
|
: "Elio has a private message for you."}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="mt-[var(--spacing-md,12px)] w-full cursor-pointer rounded-full border-0 bg-[linear-gradient(90deg,#ff67e0,#ff52a2)] px-[var(--spacing-md,12px)] py-[clamp(9px,1.852vw,10px)] text-[var(--responsive-body,14px)] font-bold text-white disabled:cursor-not-allowed disabled:opacity-65 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
className="mt-(--spacing-md,12px) w-full cursor-pointer rounded-full border-0 bg-[linear-gradient(90deg,#ff67e0,#ff52a2)] px-(--spacing-md,12px) py-[clamp(9px,1.852vw,10px)] text-(length:--responsive-body,14px) font-bold text-white disabled:cursor-not-allowed disabled:opacity-65 focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
||||||
disabled={isUnlocking || !onUnlock}
|
disabled={isUnlocking || !onUnlock}
|
||||||
onClick={onUnlock}
|
onClick={onUnlock}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ export function PwaInstallDialog({ onClose, onInstall }: PwaInstallDialogProps)
|
|||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="pwa-dialog-title"
|
aria-labelledby="pwa-dialog-title"
|
||||||
>
|
>
|
||||||
<div className="flex w-full max-w-[var(--pwa-install-dialog-max-width,360px)] flex-col items-center rounded-[var(--responsive-card-radius,40px)] bg-[var(--color-page-background,#ffffff)] px-[var(--responsive-card-padding,16px)] pb-[var(--responsive-card-padding,16px)] pt-[var(--responsive-card-padding-lg,20px)] text-center shadow-[0_10px_20px_rgba(0,0,0,0.1)]">
|
<div className="flex w-full max-w-(--pwa-install-dialog-max-width,360px) flex-col items-center rounded-(--responsive-card-radius,40px) bg-(--color-page-background,#ffffff) px-(--responsive-card-padding,16px) pb-(--responsive-card-padding,16px) pt-(--responsive-card-padding-lg,20px) text-center shadow-[0_10px_20px_rgba(0,0,0,0.1)]">
|
||||||
<Image
|
<Image
|
||||||
className="mb-[var(--spacing-lg,16px)] size-[var(--icon-size-86,86px)] object-contain"
|
className="mb-(--spacing-lg,16px) size-(--icon-size-86,86px) object-contain"
|
||||||
src="/images/icons/icon-add-to-home.png"
|
src="/images/icons/icon-add-to-home.png"
|
||||||
alt=""
|
alt=""
|
||||||
width={86}
|
width={86}
|
||||||
@@ -45,24 +45,24 @@ export function PwaInstallDialog({ onClose, onInstall }: PwaInstallDialogProps)
|
|||||||
/>
|
/>
|
||||||
<h2
|
<h2
|
||||||
id="pwa-dialog-title"
|
id="pwa-dialog-title"
|
||||||
className="m-0 mb-[var(--spacing-sm,8px)] text-[var(--responsive-page-title,var(--font-size-22,22px))] font-bold leading-[1.2] text-[var(--color-text-foreground,#171717)]"
|
className="m-0 mb-(--spacing-sm,8px) text-(length:--responsive-page-title,var(--font-size-22,22px)) font-bold leading-[1.2] text-(--color-text-foreground,#171717)"
|
||||||
>
|
>
|
||||||
Add to Home Screen
|
Add to Home Screen
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mx-[var(--responsive-card-padding-lg,36px)] mb-[var(--page-section-gap-lg,24px)] mt-0 text-[var(--responsive-body,var(--font-size-lg,16px))] leading-[1.5] text-[#393939]">
|
<p className="mx-(--responsive-card-padding-lg,36px) mb-(--page-section-gap-lg,24px) mt-0 text-(length:--responsive-body,var(--font-size-lg,16px)) leading-[1.5] text-[#393939]">
|
||||||
Add CozSweet to Home Screen{"\n"}for the best experience
|
Add CozSweet to Home Screen{"\n"}for the best experience
|
||||||
</p>
|
</p>
|
||||||
<div className="flex w-full gap-[var(--spacing-md,12px)]">
|
<div className="flex w-full gap-(--spacing-md,12px)">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex min-h-[var(--pwa-button-height,44px)] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[var(--color-text-secondary,#9e9e9e)] text-[var(--responsive-body,var(--font-size-lg,16px))] font-semibold text-[var(--color-pwa-button-text,#ffffff)]"
|
className="flex min-h-(--pwa-button-height,44px) flex-auto cursor-pointer items-center justify-center rounded-(--radius-bottom-sheet,28px) border-0 bg-(--color-text-secondary,#9e9e9e) text-(length:--responsive-body,var(--font-size-lg,16px)) font-semibold text-(--color-pwa-button-text,#ffffff)"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex min-h-[var(--pwa-button-height,44px)] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[linear-gradient(var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))] text-[var(--responsive-body,var(--font-size-lg,16px))] font-semibold text-[var(--color-pwa-button-text,#ffffff)]"
|
className="flex min-h-(--pwa-button-height,44px) flex-auto cursor-pointer items-center justify-center rounded-(--radius-bottom-sheet,28px) border-0 bg-[linear-gradient(var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))] text-(length:--responsive-body,var(--font-size-lg,16px)) font-semibold text-(--color-pwa-button-text,#ffffff)"
|
||||||
onClick={handleInstall}
|
onClick={handleInstall}
|
||||||
>
|
>
|
||||||
OK
|
OK
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export interface TextBubbleProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BASE_CLASS_NAME =
|
const BASE_CLASS_NAME =
|
||||||
"w-full self-stretch whitespace-pre-wrap break-words rounded-[var(--radius-xxl,24px)] px-[var(--spacing-4,16px)] py-[var(--spacing-3,12px)] text-[length:var(--chat-message-font-size,var(--responsive-body,16px))] leading-[var(--chat-message-line-height,1.5)] shadow-[var(--shadow-input-box,0_1px_2px_rgba(0,0,0,0.1))]";
|
"w-full self-stretch whitespace-pre-wrap break-words rounded-(--radius-xxl,24px) px-(--spacing-4,16px) py-(--spacing-3,12px) text-(length:--chat-message-font-size,var(--responsive-body,16px)) leading-(--chat-message-line-height,1.5) shadow-(--shadow-input-box,0_1px_2px_rgba(0,0,0,0.1))";
|
||||||
|
|
||||||
const AI_CLASS_NAME =
|
const AI_CLASS_NAME =
|
||||||
"rounded-tl-none bg-white text-[var(--color-text-foreground,#000)]";
|
"rounded-tl-none bg-white text-(--color-text-foreground,#000)";
|
||||||
|
|
||||||
const USER_CLASS_NAME =
|
const USER_CLASS_NAME =
|
||||||
"rounded-tr-none bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))] text-white";
|
"rounded-tr-none bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))] text-white";
|
||||||
|
|||||||
@@ -17,37 +17,37 @@ export function VoiceUnlockOptionsDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-[72] flex items-center justify-center bg-[rgba(0,0,0,0.5)] pb-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-bottom,0px))] pl-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-left,0px))] pr-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-right,0px))] pt-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-top,0px))]"
|
className="fixed inset-0 z-72 flex items-center justify-center bg-[rgba(0,0,0,0.5)] pb-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-bottom,0px))] pl-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-left,0px))] pr-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-right,0px))] pt-[calc(var(--dialog-safe-margin,16px)+var(--app-safe-top,0px))]"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="voice-unlock-options-title"
|
aria-labelledby="voice-unlock-options-title"
|
||||||
>
|
>
|
||||||
<div className="w-full max-w-[var(--dialog-max-width,380px)] rounded-[var(--responsive-card-radius,36px)] bg-[var(--color-page-background,#ffffff)] px-[var(--responsive-card-padding,18px)] pb-[var(--responsive-card-padding,18px)] pt-[var(--responsive-card-padding-lg,24px)] shadow-[0_18px_40px_rgba(0,0,0,0.16)]">
|
<div className="w-full max-w-(--dialog-max-width,380px) rounded-(--responsive-card-radius,36px) bg-(--color-page-background,#ffffff) px-(--responsive-card-padding,18px) pb-(--responsive-card-padding,18px) pt-(--responsive-card-padding-lg,24px) shadow-[0_18px_40px_rgba(0,0,0,0.16)]">
|
||||||
<h2
|
<h2
|
||||||
id="voice-unlock-options-title"
|
id="voice-unlock-options-title"
|
||||||
className="m-0 mb-[clamp(7px,1.481vw,8px)] text-center text-[var(--responsive-page-title,var(--font-size-22,22px))] font-bold leading-[1.2] text-[var(--color-text-foreground,#171717)]"
|
className="m-0 mb-[clamp(7px,1.481vw,8px)] text-center text-(length:--responsive-page-title,var(--font-size-22,22px)) font-bold leading-[1.2] text-(--color-text-foreground,#171717)"
|
||||||
>
|
>
|
||||||
Unlock voice message
|
Unlock voice message
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mx-[var(--spacing-md,12px)] mb-[var(--page-section-gap-lg,20px)] mt-0 text-center text-[var(--responsive-body,var(--font-size-md,14px))] leading-[1.5] text-[#393939]">
|
<p className="mx-(--spacing-md,12px) mb-(--page-section-gap-lg,20px) mt-0 text-center text-(length:--responsive-body,var(--font-size-md,14px)) leading-[1.5] text-[#393939]">
|
||||||
Choose how you would like to unlock this voice message.
|
Choose how you would like to unlock this voice message.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col gap-[var(--spacing-md,12px)]">
|
<div className="flex flex-col gap-(--spacing-md,12px)">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex min-h-[var(--responsive-control-height,48px)] w-full cursor-pointer flex-col items-center justify-center gap-[var(--responsive-inline-gap-xs,4px)] rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)] px-[var(--responsive-card-padding,16px)] py-[clamp(9px,1.852vw,10px)] text-[var(--responsive-body,var(--font-size-lg,16px))] font-bold text-white shadow-[0_5px_7px_rgba(247,89,168,0.24)] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
className="flex min-h-(--responsive-control-height,48px) w-full cursor-pointer flex-col items-center justify-center gap-(--responsive-inline-gap-xs,4px) rounded-(--radius-bottom-sheet,28px) border-0 bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)] px-(--responsive-card-padding,16px) py-[clamp(9px,1.852vw,10px)] text-(length:--responsive-body,var(--font-size-lg,16px)) font-bold text-white shadow-[0_5px_7px_rgba(247,89,168,0.24)] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
||||||
onClick={onBuyVoicePackage}
|
onClick={onBuyVoicePackage}
|
||||||
>
|
>
|
||||||
购买语音包
|
购买语音包
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="flex min-h-[var(--responsive-control-height,48px)] w-full cursor-pointer flex-col items-center justify-center gap-[var(--responsive-inline-gap-xs,4px)] rounded-[var(--radius-bottom-sheet,28px)] border border-transparent bg-[linear-gradient(#ffffff,#ffffff)_padding-box,linear-gradient(90deg,#ff67e0,#ff52a2)_border-box] px-[var(--responsive-card-padding,16px)] py-[clamp(9px,1.852vw,10px)] text-[var(--responsive-body,var(--font-size-lg,16px))] font-bold text-[#f657a0] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
className="flex min-h-(--responsive-control-height,48px) w-full cursor-pointer flex-col items-center justify-center gap-(--responsive-inline-gap-xs,4px) rounded-(--radius-bottom-sheet,28px) border border-transparent bg-[linear-gradient(#ffffff,#ffffff)_padding-box,linear-gradient(90deg,#ff67e0,#ff52a2)_border-box] px-(--responsive-card-padding,16px) py-[clamp(9px,1.852vw,10px)] text-(length:--responsive-body,var(--font-size-lg,16px)) font-bold text-[#f657a0] focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
||||||
onClick={onActivateVip}
|
onClick={onActivateVip}
|
||||||
>
|
>
|
||||||
<span className="leading-[1.2]">开通 VIP</span>
|
<span className="leading-[1.2]">开通 VIP</span>
|
||||||
<span className="text-[var(--responsive-caption,13px)] font-medium leading-[1.25] text-[#3c3b3b]">
|
<span className="text-(length:--responsive-caption,13px) font-medium leading-[1.25] text-[#3c3b3b]">
|
||||||
免费10条语言消息+无限畅聊
|
免费10条语言消息+无限畅聊
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -55,7 +55,7 @@ export function VoiceUnlockOptionsDialog({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="mt-[clamp(12px,2.593vw,14px)] min-h-[var(--responsive-control-height,48px)] w-full cursor-pointer rounded-[var(--radius-bottom-sheet,28px)] border-0 bg-[var(--color-text-secondary,#9e9e9e)] text-[var(--responsive-body,var(--font-size-lg,16px))] font-bold text-white focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
className="mt-[clamp(12px,2.593vw,14px)] min-h-(--responsive-control-height,48px) w-full cursor-pointer rounded-(--radius-bottom-sheet,28px) border-0 bg-(--color-text-secondary,#9e9e9e) text-(length:--responsive-body,var(--font-size-lg,16px)) font-bold text-white focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#f657a0]"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ describe("splash Tailwind components", () => {
|
|||||||
it("renders SplashLogo with Tailwind wrapper and image classes", () => {
|
it("renders SplashLogo with Tailwind wrapper and image classes", () => {
|
||||||
const html = renderToStaticMarkup(<SplashLogo />);
|
const html = renderToStaticMarkup(<SplashLogo />);
|
||||||
|
|
||||||
expect(html).toContain("z-[2]");
|
expect(html).toContain("z-2");
|
||||||
expect(html).toContain("inline-flex");
|
expect(html).toContain("inline-flex");
|
||||||
expect(html).toContain("block h-auto w-auto");
|
expect(html).toContain("block h-auto w-auto");
|
||||||
expect(html).toContain("%2Fimages%2Fsplash%2Fic-logo-home.png");
|
expect(html).toContain("%2Fimages%2Fsplash%2Fic-logo-home.png");
|
||||||
@@ -43,7 +43,7 @@ describe("splash Tailwind components", () => {
|
|||||||
const html = renderToStaticMarkup(<SplashContent />);
|
const html = renderToStaticMarkup(<SplashContent />);
|
||||||
|
|
||||||
expect(html).toContain("flex flex-col gap-md");
|
expect(html).toContain("flex flex-col gap-md");
|
||||||
expect(html).toContain("font-[var(--font-athelas)]");
|
expect(html).toContain("font-(family-name:--font-athelas)");
|
||||||
expect(html).toContain("whitespace-pre-line");
|
expect(html).toContain("whitespace-pre-line");
|
||||||
expect(html).toContain("Welcome to my secret hideout");
|
expect(html).toContain("Welcome to my secret hideout");
|
||||||
});
|
});
|
||||||
@@ -67,8 +67,8 @@ describe("splash Tailwind components", () => {
|
|||||||
<SplashButton onStartChat={() => undefined} />,
|
<SplashButton onStartChat={() => undefined} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(readyHtml).toContain("z-[2]");
|
expect(readyHtml).toContain("z-2");
|
||||||
expect(readyHtml).toContain("max-w-[480px]");
|
expect(readyHtml).toContain("max-w-120");
|
||||||
expect(readyHtml).toContain("bg-[linear-gradient(to_right");
|
expect(readyHtml).toContain("bg-[linear-gradient(to_right");
|
||||||
expect(readyHtml).toContain("Start Chatting");
|
expect(readyHtml).toContain("Start Chatting");
|
||||||
expect(loadingHtml).toContain("disabled");
|
expect(loadingHtml).toContain("disabled");
|
||||||
|
|||||||
@@ -14,17 +14,17 @@ export function SplashButton({ onStartChat }: SplashButtonProps) {
|
|||||||
const isLoading = !state.hasInitialized || state.isLoading;
|
const isLoading = !state.hasInitialized || state.isLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="z-[2] flex w-full flex-row items-center justify-center gap-[clamp(var(--spacing-md,12px),4.815vw,var(--spacing-26,26px))] px-[clamp(var(--spacing-sm,8px),2.963vw,var(--spacing-lg,16px))]">
|
<div className="z-2 flex w-full flex-row items-center justify-center gap-[clamp(var(--spacing-md,12px),4.815vw,var(--spacing-26,26px))] px-[clamp(var(--spacing-sm,8px),2.963vw,var(--spacing-lg,16px))]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onStartChat}
|
onClick={onStartChat}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className="inline-flex min-h-[var(--responsive-control-height,48px)] w-full max-w-[480px] flex-auto cursor-pointer items-center justify-center rounded-[var(--radius-full,999px)] border-0 bg-[linear-gradient(to_right,var(--color-button-gradient-start),var(--color-button-gradient-end))] px-[clamp(var(--spacing-lg,16px),5.556vw,30px)] font-bold italic text-white shadow-[0_0_10px_rgba(248,89,168,0.3)] disabled:cursor-not-allowed disabled:opacity-70"
|
className="inline-flex min-h-(--responsive-control-height,48px) w-full max-w-120 flex-auto cursor-pointer items-center justify-center rounded-(--radius-full,999px) border-0 bg-[linear-gradient(to_right,var(--color-button-gradient-start),var(--color-button-gradient-end))] px-[clamp(var(--spacing-lg,16px),5.556vw,30px)] font-bold italic text-white shadow-[0_0_10px_rgba(248,89,168,0.3)] disabled:cursor-not-allowed disabled:opacity-70"
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<LoadingIndicator color="#ffffff" />
|
<LoadingIndicator color="#ffffff" />
|
||||||
) : (
|
) : (
|
||||||
<span className="whitespace-nowrap text-[var(--responsive-section-title,var(--font-size-xxl))] font-bold text-white">
|
<span className="whitespace-nowrap text-(length:--responsive-section-title,var(--font-size-xxl)) font-bold text-white">
|
||||||
Start Chatting
|
Start Chatting
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ const APOSTROPHE = "'";
|
|||||||
|
|
||||||
export function SplashContent() {
|
export function SplashContent() {
|
||||||
return (
|
return (
|
||||||
<div className="z-[2] flex flex-col gap-md">
|
<div className="z-2 flex flex-col gap-md">
|
||||||
<h1 className="m-0 whitespace-pre-line text-left font-[var(--font-athelas)] text-[clamp(28px,7.8vw,30px)] font-bold italic leading-[1.28] text-white">
|
<h1 className="m-0 whitespace-pre-line text-left font-(family-name:--font-athelas) text-[clamp(28px,7.8vw,30px)] font-bold italic leading-[1.28] text-white">
|
||||||
Welcome to my secret hideout~
|
Welcome to my secret hideout~
|
||||||
{"\n"}It{APOSTROPHE}s just the two of us now.
|
{"\n"}It{APOSTROPHE}s just the two of us now.
|
||||||
{"\n"}Feel free to whisper your
|
{"\n"}Feel free to whisper your
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Image from "next/image";
|
|||||||
|
|
||||||
export function SplashLogo() {
|
export function SplashLogo() {
|
||||||
return (
|
return (
|
||||||
<div className="z-[2] inline-flex">
|
<div className="z-2 inline-flex">
|
||||||
<Image
|
<Image
|
||||||
src="/images/splash/ic-logo-home.png"
|
src="/images/splash/ic-logo-home.png"
|
||||||
alt="cozsweet"
|
alt="cozsweet"
|
||||||
|
|||||||
Reference in New Issue
Block a user