mirror of
https://github.com/documenso/documenso.git
synced 2025-11-10 04:22:32 +10:00
feat: single-player-mode-polish (#435)
This commit is contained in:
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
|
|
||||||
import { Footer } from '~/components/(marketing)/footer';
|
import { Footer } from '~/components/(marketing)/footer';
|
||||||
@ -13,6 +15,7 @@ export type MarketingLayoutProps = {
|
|||||||
|
|
||||||
export default function MarketingLayout({ children }: MarketingLayoutProps) {
|
export default function MarketingLayout({ children }: MarketingLayoutProps) {
|
||||||
const [scrollY, setScrollY] = useState(0);
|
const [scrollY, setScrollY] = useState(0);
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onScroll = () => {
|
const onScroll = () => {
|
||||||
@ -25,7 +28,11 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative max-w-[100vw] overflow-y-auto overflow-x-hidden pt-20 md:pt-28">
|
<div
|
||||||
|
className={cn('relative max-w-[100vw] pt-20 md:pt-28', {
|
||||||
|
'overflow-y-auto overflow-x-hidden': pathname !== '/singleplayer',
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={cn('fixed left-0 top-0 z-50 w-full bg-transparent', {
|
className={cn('fixed left-0 top-0 z-50 w-full bg-transparent', {
|
||||||
'bg-background/50 backdrop-blur-md': scrollY > 5,
|
'bg-background/50 backdrop-blur-md': scrollY > 5,
|
||||||
|
|||||||
@ -130,7 +130,7 @@ export default function SinglePlayerModePage() {
|
|||||||
signer: data.email,
|
signer: data.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
router.push(`/single-player-mode/${documentToken}/success`);
|
router.push(`/singleplayer/${documentToken}/success`);
|
||||||
} catch {
|
} catch {
|
||||||
toast({
|
toast({
|
||||||
title: 'Something went wrong',
|
title: 'Something went wrong',
|
||||||
@ -23,7 +23,7 @@ const SOCIAL_LINKS = [
|
|||||||
|
|
||||||
const FOOTER_LINKS = [
|
const FOOTER_LINKS = [
|
||||||
{ href: '/pricing', text: 'Pricing' },
|
{ href: '/pricing', text: 'Pricing' },
|
||||||
{ href: '/single-player-mode', text: 'Single Player Mode' },
|
{ href: '/singleplayer', text: 'Singleplayer' },
|
||||||
{ href: '/blog', text: 'Blog' },
|
{ href: '/blog', text: 'Blog' },
|
||||||
{ href: '/open', text: 'Open' },
|
{ href: '/open', text: 'Open' },
|
||||||
{ href: 'https://shop.documenso.com', text: 'Shop', target: '_blank' },
|
{ href: 'https://shop.documenso.com', text: 'Shop', target: '_blank' },
|
||||||
|
|||||||
@ -35,7 +35,7 @@ export const Header = ({ className, ...props }: HeaderProps) => {
|
|||||||
|
|
||||||
{isSinglePlayerModeMarketingEnabled && (
|
{isSinglePlayerModeMarketingEnabled && (
|
||||||
<Link
|
<Link
|
||||||
href="/single-player-mode"
|
href="/singleplayer"
|
||||||
className="bg-primary dark:text-background rounded-full px-2 py-1 text-xs font-semibold sm:px-3"
|
className="bg-primary dark:text-background rounded-full px-2 py-1 text-xs font-semibold sm:px-3"
|
||||||
>
|
>
|
||||||
Try now!
|
Try now!
|
||||||
|
|||||||
@ -134,9 +134,9 @@ export const Hero = ({ className, ...props }: HeroProps) => {
|
|||||||
variants={HeroTitleVariants}
|
variants={HeroTitleVariants}
|
||||||
initial="initial"
|
initial="initial"
|
||||||
animate="animate"
|
animate="animate"
|
||||||
className="border-primary bg-background hover:bg-muted mx-auto mt-8 w-60 rounded-xl border transition duration-300"
|
className="border-primary bg-background hover:bg-muted mx-auto mt-8 w-60 rounded-xl border transition-colors duration-300"
|
||||||
>
|
>
|
||||||
<Link href="/single-player-mode" className="block px-4 py-2 text-center">
|
<Link href="/singleplayer" className="block px-4 py-2 text-center">
|
||||||
<h2 className="text-muted-foreground text-xs font-semibold">
|
<h2 className="text-muted-foreground text-xs font-semibold">
|
||||||
Introducing Single Player Mode
|
Introducing Single Player Mode
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@ -17,8 +17,8 @@ export type MobileNavigationProps = {
|
|||||||
|
|
||||||
export const MENU_NAVIGATION_LINKS = [
|
export const MENU_NAVIGATION_LINKS = [
|
||||||
{
|
{
|
||||||
href: '/single-player-mode',
|
href: '/singleplayer',
|
||||||
text: 'Single Player Mode',
|
text: 'Singleplayer',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
href: '/blog',
|
href: '/blog',
|
||||||
|
|||||||
@ -60,26 +60,17 @@ export const calculateTextScaleSize = (
|
|||||||
*/
|
*/
|
||||||
export function useElementScaleSize(
|
export function useElementScaleSize(
|
||||||
container: { width: number; height: number },
|
container: { width: number; height: number },
|
||||||
child: RefObject<HTMLElement | null>,
|
text: string,
|
||||||
fontSize: number,
|
fontSize: number,
|
||||||
fontFamily: string,
|
fontFamily: string,
|
||||||
) {
|
) {
|
||||||
const [scalingFactor, setScalingFactor] = useState(1);
|
const [scalingFactor, setScalingFactor] = useState(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!child.current) {
|
const scaleSize = calculateTextScaleSize(container, text, `${fontSize}px`, fontFamily);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const scaleSize = calculateTextScaleSize(
|
|
||||||
container,
|
|
||||||
child.current.innerText,
|
|
||||||
`${fontSize}px`,
|
|
||||||
fontFamily,
|
|
||||||
);
|
|
||||||
|
|
||||||
setScalingFactor(scaleSize);
|
setScalingFactor(scaleSize);
|
||||||
}, [child, container, fontFamily, fontSize]);
|
}, [text, container, fontFamily, fontSize]);
|
||||||
|
|
||||||
return scalingFactor;
|
return scalingFactor;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign
|
|||||||
const sheenGradient = useMotionTemplate`linear-gradient(
|
const sheenGradient = useMotionTemplate`linear-gradient(
|
||||||
30deg,
|
30deg,
|
||||||
transparent,
|
transparent,
|
||||||
rgba(var(--sheen-color) / ${trackMouse ? sheenOpacity : 0}) ${sheenPosition}%,
|
rgba(var(--sheen-color) / ${sheenOpacity}) ${sheenPosition}%,
|
||||||
transparent)`;
|
transparent)`;
|
||||||
|
|
||||||
const cardRef = useRef<HTMLDivElement>(null);
|
const cardRef = useRef<HTMLDivElement>(null);
|
||||||
@ -98,10 +98,12 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign
|
|||||||
void animate(cardX, 0, { duration: 2, ease: 'backInOut' });
|
void animate(cardX, 0, { duration: 2, ease: 'backInOut' });
|
||||||
void animate(cardY, 0, { duration: 2, ease: 'backInOut' });
|
void animate(cardY, 0, { duration: 2, ease: 'backInOut' });
|
||||||
|
|
||||||
|
void animate(sheenOpacity, 0, { duration: 2, ease: 'backInOut' });
|
||||||
|
|
||||||
setTrackMouse(false);
|
setTrackMouse(false);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
},
|
},
|
||||||
[cardX, cardY, cardCenterPosition, trackMouse],
|
[cardX, cardY, cardCenterPosition, trackMouse, sheenOpacity],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -126,7 +128,6 @@ export const SigningCard3D = ({ className, name, signingCelebrationImage }: Sign
|
|||||||
transformStyle: 'preserve-3d',
|
transformStyle: 'preserve-3d',
|
||||||
rotateX,
|
rotateX,
|
||||||
rotateY,
|
rotateY,
|
||||||
// willChange: 'transform background-image',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SigningCardContent className="bg-transparent" name={name} />
|
<SigningCardContent className="bg-transparent" name={name} />
|
||||||
|
|||||||
@ -70,25 +70,23 @@ export function SinglePlayerModeSignatureField({
|
|||||||
throw new Error('Invalid field type');
|
throw new Error('Invalid field type');
|
||||||
}
|
}
|
||||||
|
|
||||||
const $paragraphEl = useRef<HTMLParagraphElement>(null);
|
|
||||||
|
|
||||||
const { height, width } = useFieldPageCoords(field);
|
const { height, width } = useFieldPageCoords(field);
|
||||||
|
|
||||||
|
const insertedBase64Signature = field.inserted && field.Signature?.signatureImageAsBase64;
|
||||||
|
const insertedTypeSignature = field.inserted && field.Signature?.typedSignature;
|
||||||
|
|
||||||
const scalingFactor = useElementScaleSize(
|
const scalingFactor = useElementScaleSize(
|
||||||
{
|
{
|
||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
},
|
},
|
||||||
$paragraphEl,
|
insertedTypeSignature || '',
|
||||||
maxFontSize,
|
maxFontSize,
|
||||||
fontVariableValue,
|
fontVariableValue,
|
||||||
);
|
);
|
||||||
|
|
||||||
const fontSize = maxFontSize * scalingFactor;
|
const fontSize = maxFontSize * scalingFactor;
|
||||||
|
|
||||||
const insertedBase64Signature = field.inserted && field.Signature?.signatureImageAsBase64;
|
|
||||||
const insertedTypeSignature = field.inserted && field.Signature?.typedSignature;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SinglePlayerModeFieldCardContainer field={field}>
|
<SinglePlayerModeFieldCardContainer field={field}>
|
||||||
{insertedBase64Signature ? (
|
{insertedBase64Signature ? (
|
||||||
@ -99,7 +97,6 @@ export function SinglePlayerModeSignatureField({
|
|||||||
/>
|
/>
|
||||||
) : insertedTypeSignature ? (
|
) : insertedTypeSignature ? (
|
||||||
<p
|
<p
|
||||||
ref={$paragraphEl}
|
|
||||||
style={{
|
style={{
|
||||||
fontSize: `clamp(${minFontSize}px, ${fontSize}px, ${maxFontSize}px)`,
|
fontSize: `clamp(${minFontSize}px, ${fontSize}px, ${maxFontSize}px)`,
|
||||||
fontFamily: `var(${fontVariable})`,
|
fontFamily: `var(${fontVariable})`,
|
||||||
@ -145,7 +142,7 @@ export function SinglePlayerModeCustomTextField({
|
|||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
},
|
},
|
||||||
$paragraphEl,
|
field.customText,
|
||||||
maxFontSize,
|
maxFontSize,
|
||||||
fontVariableValue,
|
fontVariableValue,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user