Added logo animation to login screen, initial work

This commit is contained in:
2026-02-15 13:49:15 +00:00
parent 7fbf1dcb95
commit 83b941262e
16 changed files with 506 additions and 76 deletions
+3 -1
View File
@@ -29,7 +29,9 @@
"Bash(npx -y serve -l 3333 .)",
"Bash(npx serve:*)",
"Bash(timeout /t 3 /nobreak)",
"Bash(jq:*)"
"Bash(jq:*)",
"Bash(git stash:*)",
"Bash(npx tsc:*)"
]
}
}
+8
View File
@@ -0,0 +1,8 @@
<svg width="200" height="450" viewBox="0 0 200 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="450" rx="100" fill="#E38B16"/>
<g transform="translate(50, 100) scale(1.2)">
<path d="M10 0 L50 30 L10 60" stroke="white" stroke-width="10" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<line x1="55" y1="65" x2="85" y2="65" stroke="white" stroke-width="10" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<svg width="200" height="450" viewBox="0 0 200 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="450" rx="100" fill="#109E6C"/>
<g transform="translate(45, 100) scale(1)">
<rect x="0" y="60" width="20" height="40" fill="white"/>
<rect x="30" y="40" width="20" height="60" fill="white"/>
<rect x="60" y="20" width="20" height="80" fill="white"/>
<rect x="90" y="0" width="20" height="100" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.
+12
View File
@@ -0,0 +1,12 @@
<svg width="200" height="450" viewBox="0 0 200 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="450" rx="100" fill="#0E7A7D"/>
<g transform="translate(42, 100) scale(1.2)">
<path d="M25 70 V0 H55 C80 0 80 35 55 35 H25 M55 35 L85 70 M53 67 L87 38"
stroke="white"
stroke-width="10"
stroke-linecap="butt"
stroke-linejoin="miter"
fill="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.
+323
View File
@@ -0,0 +1,323 @@
import { useMemo } from "react";
import { AbsoluteFill, Easing, Img, interpolate, spring, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
type PillProps = {
src: string;
x: number;
y: number;
rotation: number;
zIndex: number;
};
type PillTransform = {
x: number;
y: number;
rotation: number;
};
const PILL_WIDTH = 200;
const PILL_HEIGHT = 450;
const RISE_DURATION_FRAMES = 150;
const RISE_SETTLE_PAUSE_FRAMES = 0;
const GAP_AFTER_RISE_FRAMES = 30;
const FAN_DURATION_FRAMES = 236;
const FAN_START_FRAME = RISE_DURATION_FRAMES + RISE_SETTLE_PAUSE_FRAMES + GAP_AFTER_RISE_FRAMES;
const OVERLAY_BLEND_START_PROGRESS = 0.5;
const OVERLAY_BLEND_TRANSITION_FRAMES = 150;
const OVERLAP_BLEND_MAX_OPACITY = 0.2;
const BACKGROUND_COLOR = "#efefef";
const COLOR_CODE = "#E38B16";
const COLOR_RX = "#0E7A7D";
const COLOR_DATA = "#109E6C";
const Pill: React.FC<PillProps> = ({ src, x, y, rotation, zIndex }) => {
return (
<Img
src={src}
style={{
position: "absolute",
width: PILL_WIDTH,
height: PILL_HEIGHT,
left: "50%",
top: "50%",
transform: `translate(-50%, -50%) translate(${x}px, ${y}px) rotate(${rotation}deg)`,
transformOrigin: "50% 100%",
zIndex,
}}
/>
);
};
const withCapsuleTransform = ({
ctx,
transform,
videoWidth,
videoHeight,
draw,
}: {
ctx: CanvasRenderingContext2D;
transform: PillTransform;
videoWidth: number;
videoHeight: number;
draw: () => void;
}) => {
ctx.save();
// Match the CSS transform order and origin used for the visible pills.
ctx.translate(videoWidth / 2, videoHeight / 2);
ctx.translate(-PILL_WIDTH / 2, -PILL_HEIGHT / 2);
ctx.translate(transform.x, transform.y);
ctx.translate(PILL_WIDTH / 2, PILL_HEIGHT);
ctx.rotate((transform.rotation * Math.PI) / 180);
ctx.translate(-PILL_WIDTH / 2, -PILL_HEIGHT);
draw();
ctx.restore();
};
const drawCapsuleFill = ({
ctx,
transform,
fill,
videoWidth,
videoHeight,
}: {
ctx: CanvasRenderingContext2D;
transform: PillTransform;
fill: string;
videoWidth: number;
videoHeight: number;
}) => {
withCapsuleTransform({
ctx,
transform,
videoWidth,
videoHeight,
draw: () => {
ctx.fillStyle = fill;
ctx.beginPath();
ctx.roundRect(0, 0, PILL_WIDTH, PILL_HEIGHT, PILL_WIDTH / 2);
ctx.fill();
},
});
};
const buildPairBlendIntersection = ({
outputCtx,
pairCtx,
maskCtx,
colorA,
colorB,
transformA,
transformB,
videoWidth,
videoHeight,
}: {
outputCtx: CanvasRenderingContext2D;
pairCtx: CanvasRenderingContext2D;
maskCtx: CanvasRenderingContext2D;
colorA: string;
colorB: string;
transformA: PillTransform;
transformB: PillTransform;
videoWidth: number;
videoHeight: number;
}) => {
// Build blended color result for the two pills.
pairCtx.clearRect(0, 0, videoWidth, videoHeight);
pairCtx.globalCompositeOperation = "source-over";
drawCapsuleFill({ ctx: pairCtx, transform: transformA, fill: colorA, videoWidth, videoHeight });
pairCtx.globalCompositeOperation = "multiply";
drawCapsuleFill({ ctx: pairCtx, transform: transformB, fill: colorB, videoWidth, videoHeight });
pairCtx.globalCompositeOperation = "source-over";
// Build hard intersection mask for the same pair.
maskCtx.clearRect(0, 0, videoWidth, videoHeight);
maskCtx.globalCompositeOperation = "source-over";
drawCapsuleFill({ ctx: maskCtx, transform: transformA, fill: "#fff", videoWidth, videoHeight });
maskCtx.globalCompositeOperation = "source-in";
drawCapsuleFill({ ctx: maskCtx, transform: transformB, fill: "#fff", videoWidth, videoHeight });
maskCtx.globalCompositeOperation = "source-over";
// Keep only overlap area from blended result.
pairCtx.globalCompositeOperation = "destination-in";
pairCtx.drawImage(maskCtx.canvas, 0, 0);
pairCtx.globalCompositeOperation = "source-over";
// Accumulate pair overlap into final output.
outputCtx.drawImage(pairCtx.canvas, 0, 0);
};
export const MyComposition: React.FC = () => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const overlayBlendEndProgress = Math.min(
1,
OVERLAY_BLEND_START_PROGRESS + OVERLAY_BLEND_TRANSITION_FRAMES / FAN_DURATION_FRAMES,
);
const codeSrc = staticFile("logo/capsuleCode.svg");
const rxSrc = staticFile("logo/capsuleRx.svg");
const dataSrc = staticFile("logo/capsuleData.svg");
const rise = interpolate(frame, [0, RISE_DURATION_FRAMES], [0, 1], {
easing: Easing.out(Easing.cubic),
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const fan = spring({
frame: frame - FAN_START_FRAME,
fps,
durationInFrames: FAN_DURATION_FRAMES,
config: { damping: 200 },
});
const centerY = interpolate(rise, [0, 1], [560, 76], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const leftY = interpolate(fan, [0, 1], [centerY, 74], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const rightY = interpolate(fan, [0, 1], [centerY, 74], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const leftX = interpolate(fan, [0, 1], [0, -36], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const rightX = interpolate(fan, [0, 1], [0, 36], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const leftRotation = interpolate(fan, [0, 1], [0, -50], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const rightRotation = interpolate(fan, [0, 1], [0, 50], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const overlapBlendProgress =
overlayBlendEndProgress >= 1
? interpolate(fan, [0, OVERLAY_BLEND_START_PROGRESS, 1], [0, 0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
})
: interpolate(
fan,
[0, OVERLAY_BLEND_START_PROGRESS, overlayBlendEndProgress, 1],
[0, 0, 1, 1],
{
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
},
);
const overlapMaskSrc = useMemo(() => {
const centerTransform: PillTransform = { x: 0, y: centerY, rotation: 0 };
const leftTransform: PillTransform = { x: leftX, y: leftY, rotation: leftRotation };
const rightTransform: PillTransform = { x: rightX, y: rightY, rotation: rightRotation };
const outputCanvas = document.createElement("canvas");
outputCanvas.width = width;
outputCanvas.height = height;
const outputCtx = outputCanvas.getContext("2d");
const pairCanvas = document.createElement("canvas");
pairCanvas.width = width;
pairCanvas.height = height;
const pairCtx = pairCanvas.getContext("2d");
const maskCanvas = document.createElement("canvas");
maskCanvas.width = width;
maskCanvas.height = height;
const maskCtx = maskCanvas.getContext("2d");
if (!outputCtx || !pairCtx || !maskCtx) {
return null;
}
outputCtx.clearRect(0, 0, width, height);
buildPairBlendIntersection({
outputCtx,
pairCtx,
maskCtx,
colorA: COLOR_CODE,
colorB: COLOR_RX,
transformA: centerTransform,
transformB: leftTransform,
videoWidth: width,
videoHeight: height,
});
buildPairBlendIntersection({
outputCtx,
pairCtx,
maskCtx,
colorA: COLOR_CODE,
colorB: COLOR_DATA,
transformA: centerTransform,
transformB: rightTransform,
videoWidth: width,
videoHeight: height,
});
buildPairBlendIntersection({
outputCtx,
pairCtx,
maskCtx,
colorA: COLOR_RX,
colorB: COLOR_DATA,
transformA: leftTransform,
transformB: rightTransform,
videoWidth: width,
videoHeight: height,
});
const pixels = outputCtx.getImageData(0, 0, width, height).data;
let hasAlphaPixels = false;
for (let i = 3; i < pixels.length; i += 4) {
if (pixels[i] > 0) {
hasAlphaPixels = true;
break;
}
}
if (!hasAlphaPixels) {
return null;
}
return outputCanvas.toDataURL("image/png");
}, [centerY, height, leftRotation, leftX, leftY, rightRotation, rightX, rightY, width]);
return (
<AbsoluteFill style={{ backgroundColor: BACKGROUND_COLOR }}>
<Pill src={codeSrc} x={0} y={centerY} rotation={0} zIndex={3} />
<Pill src={rxSrc} x={leftX} y={leftY} rotation={leftRotation} zIndex={1} />
<Pill src={dataSrc} x={rightX} y={rightY} rotation={rightRotation} zIndex={2} />
{overlapMaskSrc ? (
<Img
src={overlapMaskSrc}
style={{
position: "absolute",
inset: 0,
opacity: OVERLAP_BLEND_MAX_OPACITY * overlapBlendProgress,
pointerEvents: "none",
zIndex: 20,
}}
/>
) : null}
</AbsoluteFill>
);
};
+18
View File
@@ -0,0 +1,18 @@
import "./index.css";
import { Composition } from "remotion";
import { MyComposition } from "./Composition";
export const RemotionRoot: React.FC = () => {
return (
<>
<Composition
id="MyComp"
component={MyComposition}
durationInFrames={380}
fps={60}
width={1280}
height={720}
/>
</>
);
};
+1
View File
@@ -0,0 +1 @@
@import "tailwindcss";
+4
View File
@@ -0,0 +1,4 @@
import { registerRoot } from "remotion";
import { RemotionRoot } from "./Root";
registerRoot(RemotionRoot);
+8
View File
@@ -0,0 +1,8 @@
<svg width="200" height="450" viewBox="0 0 200 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="450" rx="100" fill="#E38B16"/>
<g transform="translate(50, 100) scale(1.2)">
<path d="M10 0 L50 30 L10 60" stroke="white" stroke-width="10" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<line x1="55" y1="65" x2="85" y2="65" stroke="white" stroke-width="10" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 458 B

+10
View File
@@ -0,0 +1,10 @@
<svg width="200" height="450" viewBox="0 0 200 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="450" rx="100" fill="#109E6C"/>
<g transform="translate(45, 100) scale(1)">
<rect x="0" y="60" width="20" height="40" fill="white"/>
<rect x="30" y="40" width="20" height="60" fill="white"/>
<rect x="60" y="20" width="20" height="80" fill="white"/>
<rect x="90" y="0" width="20" height="100" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 477 B

+12
View File
@@ -0,0 +1,12 @@
<svg width="200" height="450" viewBox="0 0 200 450" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="200" height="450" rx="100" fill="#0E7A7D"/>
<g transform="translate(42, 100) scale(1.2)">
<path d="M25 70 V0 H55 C80 0 80 35 55 35 H25 M55 35 L85 70 M53 67 L87 38"
stroke="white"
stroke-width="10"
stroke-linecap="butt"
stroke-linejoin="miter"
fill="none"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 459 B

+96 -74
View File
@@ -8,103 +8,125 @@ interface CvmisLogoProps {
className?: string
}
// Pivot point: bottom-center of the pill stack (in viewBox coords)
const PX = 300
const PY = 275
// Build a CSS transform that rotates around (PX, PY) then offsets by dx
function fanTransform(rotation: number, dx: number): string {
return [
`translate(${dx}px, 0px)`,
`translate(${PX}px, ${PY}px)`,
`rotate(${rotation}deg)`,
`translate(${-PX}px, ${-PY}px)`,
].join(' ')
}
const IDENTITY_TRANSFORM = fanTransform(0, 0)
const FAN_EASING = 'cubic-bezier(0.34, 1.56, 0.64, 1)'
export function CvmisLogo({ size, cssHeight, animated = false, className }: CvmisLogoProps) {
const prefersReducedMotion = useReducedMotion()
const [animationPhase, setAnimationPhase] = useState<'rise' | 'fan' | 'done'>(
animated && !prefersReducedMotion ? 'rise' : 'done'
const [phase, setPhase] = useState<'rising' | 'fanning' | 'done'>(
animated && !prefersReducedMotion ? 'rising' : 'done'
)
useEffect(() => {
if (!animated || prefersReducedMotion) return
const riseTimer = setTimeout(() => setAnimationPhase('fan'), 500)
const fanTimer = setTimeout(() => setAnimationPhase('done'), 1000)
const fanTimer = setTimeout(() => setPhase('fanning'), 500)
const doneTimer = setTimeout(() => setPhase('done'), 1000)
return () => {
clearTimeout(riseTimer)
clearTimeout(fanTimer)
clearTimeout(doneTimer)
}
}, [animated, prefersReducedMotion])
const skipAnimation = !animated || prefersReducedMotion
const showAll = animationPhase === 'fan' || animationPhase === 'done'
const skip = !animated || prefersReducedMotion
const isFanned = phase === 'fanning' || phase === 'done'
const fanTarget = isFanned || skip
const leftTransform = fanTarget ? fanTransform(-50, -16) : IDENTITY_TRANSFORM
const rightTransform = fanTarget ? fanTransform(50, 16) : IDENTITY_TRANSFORM
const fanTransition = skip ? 'none' : `transform 0.6s ${FAN_EASING}`
const fanTransitionDelayed = skip ? 'none' : `transform 0.6s ${FAN_EASING} 0.03s`
// The original SVG viewBox is 600pt x 506pt with an internal transform of
// scale(0.05, -0.05) translate(0, 506). We keep the original coordinate
// system and let the outer viewBox handle scaling.
return (
<svg
viewBox="0 0 600 506"
viewBox="0 0 600 300"
height={cssHeight ? undefined : size}
className={className}
role="img"
aria-label="CVMIS logo"
style={{ overflow: 'visible', ...(cssHeight ? { height: cssHeight, width: 'auto' } : {}) }}
>
<g transform="translate(0,506) scale(0.05,-0.05)" stroke="none">
{/* Capsule: Rx (Pharmacy) - Left — teal, tilts left in fan */}
<motion.g
id="capsule-rx"
fill="#0b7979"
initial={skipAnimation ? false : { opacity: 0 }}
animate={
skipAnimation
? { opacity: 1, rotate: 0, x: 0, y: 0 }
: showAll
? { opacity: 1, rotate: 0, x: 0, y: 0 }
: { opacity: 0 }
}
transition={{ duration: 0.5, ease: 'easeOut' }}
style={{ transformOrigin: '2500px 3000px' }}
>
<path d="M2060 6850 c-914 -249 -1279 -1334 -697 -2071 47 -60 198 -225 336 -366 138 -141 256 -265 262 -275 6 -10 150 -160 320 -333 300 -306 1129 -1163 1490 -1542 549 -575 1246 -700 1772 -318 l86 62 -105 35 c-506 172 -872 557 -1036 1089 l-55 179 -11 1003 -12 1003 -550 567 c-780 805 -801 822 -1095 932 -172 65 -531 82 -705 35z m610 -1228 c80 -45 128 -177 97 -261 l-23 -59 99 -21 c147 -31 144 -33 131 87 -10 101 -7 111 54 170 86 83 87 82 102 -73 24 -254 8 -234 213 -270 99 -18 187 -38 194 -45 22 -23 -136 -153 -173 -143 -19 6 -71 16 -115 23 l-82 12 9 -122 c9 -117 6 -126 -57 -191 -80 -83 -99 -74 -99 47 0 52 -6 141 -13 198 l-12 104 -137 31 c-180 41 -244 39 -288 -9 -41 -45 -37 -52 98 -195 l81 -85 -66 -65 -66 -65 -189 200 c-105 110 -232 248 -283 307 l-93 106 159 150 c236 222 314 251 459 169z" />
<path d="M2395 5412 c-112 -103 -113 -108 -37 -180 65 -63 93 -57 185 41 73 77 81 140 26 196 -47 46 -70 39 -174 -57z" />
</motion.g>
{/* Capsule: Terminal (Code) - Centre — amber, stays upright */}
<motion.g
id="capsule-terminal"
fill="#d97706"
initial={skipAnimation ? false : { opacity: 0 }}
animate={
skipAnimation
? { opacity: 1, rotate: 0, x: 0, y: 0 }
: showAll
? { opacity: 1, rotate: 0, x: 0, y: 0 }
: { opacity: 0 }
}
transition={{ duration: 0.5, ease: 'easeOut', delay: skipAnimation ? 0 : 0.05 }}
style={{ transformOrigin: '5500px 5000px' }}
>
<path d="M5740 8362 c-476 -105 -891 -512 -1015 -997 -45 -173 -54 -3865 -11 -4070 50 -233 182 -483 355 -671 185 -201 701 -447 777 -371 11 11 -100 221 -119 267 -19 46 -18 106 -37 200 -66 317 -11 705 143 1010 120 237 111 226 917 1060 255 264 493 513 528 554 l65 74 -8 916 c-9 1115 -24 1196 -286 1542 -286 377 -851 587 -1309 486z m31 -1595 c115 -118 209 -223 209 -236 0 -12 -97 -118 -215 -236 l-215 -215 -55 48 c-75 64 -76 62 103 244 l159 162 -159 158 c-175 174 -176 176 -115 242 62 65 57 68 288 -167z m825 -613 l-6 -64 -295 -6 -295 -5 0 75 0 76 301 -6 301 -6 -6 -64z" />
</motion.g>
{/* Capsule: Data (Analytics) - Right — green, the "rising" capsule */}
<motion.g
id="capsule-data"
fill="#059669"
initial={
skipAnimation
? false
: { opacity: 0, scale: 0, y: 2000 }
}
animate={
skipAnimation
? { opacity: 1, scale: 1, y: 0, rotate: 0 }
: animationPhase === 'rise'
? { opacity: 1, scale: 1, y: 0 }
: { opacity: 1, scale: 1, y: 0, rotate: 0 }
}
transition={{
duration: 0.5,
ease: 'easeOut',
delay: skipAnimation ? 0 : (animationPhase === 'fan' ? 0.1 : 0),
style={{
overflow: 'visible',
...(cssHeight ? { height: cssHeight, width: 'auto' } : {}),
}}
style={{ transformOrigin: '9000px 3000px' }}
>
<path d="M9380 6850 c-351 -63 -390 -94 -1322 -1027 -1753 -1757 -1929 -1943 -2039 -2162 -455 -906 300 -1962 1305 -1822 381 53 567 178 1165 785 2249 2284 2186 2217 2302 2468 432 933 -380 1945 -1411 1758z m35 -1254 l83 -86 -325 -325 -325 -325 -89 91 -89 91 319 319 c369 370 320 342 426 235z m-502 -59 c88 -86 90 -81 -108 -280 l-175 -176 -86 85 -85 84 175 174 c201 201 192 197 279 113z m1036 -132 c11 -8 47 -44 79 -80 l60 -65 -409 -409 -409 -409 -86 84 -86 84 405 405 c223 224 410 406 416 406 5 0 19 -7 30 -16z m-460 -164 l79 -81 -254 -254 -254 -254 -81 79 c-99 97 -115 63 168 349 276 279 243 263 342 161z" />
</motion.g>
{/* Rise group — all pills rise together from below */}
<motion.g
initial={skip ? false : { y: 350, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{
y: { duration: 0.5, ease: [0.33, 1, 0.68, 1] },
opacity: { duration: 0.25 },
}}
>
{/* Rx pill — teal, fans left (bottom layer) */}
<g style={{ transform: leftTransform, transition: fanTransition }}>
<g transform="translate(250, 50)">
<rect width="100" height="225" rx="50" fill="#0E7A7D" />
<g transform="translate(21, 50) scale(0.6)">
<path
d="M25 70 V0 H55 C80 0 80 35 55 35 H25 M55 35 L85 70 M53 67 L87 38"
stroke="white"
strokeWidth="10"
strokeLinecap="butt"
strokeLinejoin="miter"
fill="none"
/>
</g>
</g>
</g>
{/* Data pill — green, fans right (middle layer) */}
<g style={{ transform: rightTransform, transition: fanTransitionDelayed }}>
<g transform="translate(250, 50)">
<rect width="100" height="225" rx="50" fill="#109E6C" />
<g transform="translate(22.5, 50) scale(0.5)">
<rect x="0" y="60" width="20" height="40" fill="white" />
<rect x="30" y="40" width="20" height="60" fill="white" />
<rect x="60" y="20" width="20" height="80" fill="white" />
<rect x="90" y="0" width="20" height="100" fill="white" />
</g>
</g>
</g>
{/* Code pill — amber, center (top layer, no fan) */}
<g transform="translate(250, 50)">
<rect width="100" height="225" rx="50" fill="#E38B16" />
<g transform="translate(25, 50) scale(0.6)">
<path
d="M10 0 L50 30 L10 60"
stroke="white"
strokeWidth="10"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
/>
<line
x1="55"
y1="65"
x2="85"
y2="65"
stroke="white"
strokeWidth="10"
strokeLinecap="round"
/>
</g>
</g>
</motion.g>
</svg>
)
}
+1 -1
View File
@@ -239,7 +239,7 @@ export function LoginScreen({ onComplete }: LoginScreenProps) {
>
<div style={{ marginBottom: '10px' }}>
<CvmisLogo
cssHeight="clamp(48px, 4vw, 64px)"
cssHeight="clamp(80px, 8vw, 120px)"
animated={true}
/>
</div>