Skip to content

Commit be0295f

Browse files
fix: enforce viewport-based mobile certificate scale
Compute certificate preview scale from actual viewport width in the component so phone rendering cannot fall back to desktop zoom, ensuring the full certificate is visible. Made-with: Cursor
1 parent c895a1d commit be0295f

1 file changed

Lines changed: 29 additions & 7 deletions

File tree

src/components/CertificateCard.tsx

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@ interface Props {
1111
onReset: () => void
1212
}
1313

14+
const DESKTOP_CERT_UI_SCALE = 0.604
15+
const CERT_RENDER_WIDTH = 794
16+
const CERT_RENDER_HEIGHT = 1123
17+
18+
function clamp(value: number, min: number, max: number) {
19+
return Math.min(max, Math.max(min, value))
20+
}
21+
22+
function getCertificateUiScale(viewportWidth: number) {
23+
if (viewportWidth > 900) return DESKTOP_CERT_UI_SCALE
24+
return clamp((viewportWidth - 170) / CERT_RENDER_WIDTH, 0.18, 0.32)
25+
}
26+
1427
const SOCIAL_BG = '#E8E8E8'
1528
const SOCIAL_EXPORT_FORMATS = {
1629
instagramPortrait: { width: 1080, height: 1350, padding: 64, filename: 'instagram-portrait' },
@@ -66,6 +79,7 @@ export default function CertificateCard({ cert, onReset }: Props) {
6679
const visibleStampRef = useRef<HTMLDivElement>(null)
6780
const exportStampRef = useRef<HTMLDivElement>(null)
6881
const [visible, setVisible] = useState(false)
82+
const [uiScale, setUiScale] = useState(DESKTOP_CERT_UI_SCALE)
6983
const [isGeneratingShare, setIsGeneratingShare] = useState(false)
7084
const [showInlineShare, setShowInlineShare] = useState(false)
7185
const [copied, setCopied] = useState(false)
@@ -75,6 +89,14 @@ export default function CertificateCard({ cert, onReset }: Props) {
7589
return () => clearTimeout(t)
7690
}, [])
7791

92+
useEffect(() => {
93+
if (typeof window === 'undefined') return
94+
const applyScale = () => setUiScale(getCertificateUiScale(window.innerWidth))
95+
applyScale()
96+
window.addEventListener('resize', applyScale)
97+
return () => window.removeEventListener('resize', applyScale)
98+
}, [])
99+
78100
async function exportBlob(pixelRatio: number, watermark = false): Promise<Blob | null> {
79101
if (!exportCardRef.current) return null
80102
const wrapper = wrapperRef.current
@@ -84,8 +106,8 @@ export default function CertificateCard({ cert, onReset }: Props) {
84106
cacheBust: true,
85107
pixelRatio,
86108
backgroundColor: '#FAF6EF',
87-
width: 794,
88-
height: 1123,
109+
width: CERT_RENDER_WIDTH,
110+
height: CERT_RENDER_HEIGHT,
89111
})
90112
if (!watermark && exportStampRef.current) exportStampRef.current.style.visibility = ''
91113
if (wrapper) wrapper.style.zoom = ''
@@ -280,11 +302,11 @@ export default function CertificateCard({ cert, onReset }: Props) {
280302
<div
281303
ref={wrapperRef}
282304
style={{
283-
width: '794px',
305+
width: `${CERT_RENDER_WIDTH}px`,
284306
flexShrink: 0,
285307
transformOrigin: 'top center',
286-
transform: 'scale(var(--cert-ui-scale, 0.604))',
287-
marginBottom: 'calc((1123px * var(--cert-ui-scale, 0.604)) - 1123px)',
308+
transform: `scale(${uiScale})`,
309+
marginBottom: `calc((${CERT_RENDER_HEIGHT}px * ${uiScale}) - ${CERT_RENDER_HEIGHT}px)`,
288310
}}
289311
>
290312
<CertificateFixed
@@ -305,8 +327,8 @@ export default function CertificateCard({ cert, onReset }: Props) {
305327
position: 'fixed',
306328
left: '-10000px',
307329
top: 0,
308-
width: '794px',
309-
height: '1123px',
330+
width: `${CERT_RENDER_WIDTH}px`,
331+
height: `${CERT_RENDER_HEIGHT}px`,
310332
opacity: 0,
311333
pointerEvents: 'none',
312334
overflow: 'hidden',

0 commit comments

Comments
 (0)