1
1
"use client"
2
2
3
- import { useState } from "react"
3
+ import { useEffect , useState } from "react"
4
4
import Image from "next/image"
5
5
import { useTranslations } from "next-intl"
6
6
@@ -13,6 +13,17 @@ import heroImage from "@/public/images/home/hero.png"
13
13
export default function HeroQualityDemo ( ) {
14
14
const t = useTranslations ( "page-index" )
15
15
const [ selectedQualities , setSelectedQualities ] = useState < number [ ] > ( [ 100 , 5 ] )
16
+ const [ customHeight , setCustomHeight ] = useState ( 300 ) // Default height in px
17
+ const [ isMobile , setIsMobile ] = useState ( false )
18
+ const [ useCustomHeight , setUseCustomHeight ] = useState ( false ) // Toggle for custom height
19
+
20
+ // Check if mobile on mount
21
+ useEffect ( ( ) => {
22
+ const checkMobile = ( ) => setIsMobile ( window . innerWidth < 768 )
23
+ checkMobile ( )
24
+ window . addEventListener ( "resize" , checkMobile )
25
+ return ( ) => window . removeEventListener ( "resize" , checkMobile )
26
+ } , [ ] )
16
27
17
28
const alt = t ( "page-index-hero-image-alt" )
18
29
@@ -28,9 +39,82 @@ export default function HeroQualityDemo() {
28
39
)
29
40
}
30
41
42
+ const handleMouseResize = (
43
+ e : React . MouseEvent ,
44
+ direction : "top" | "bottom"
45
+ ) => {
46
+ if ( isMobile ) return
47
+
48
+ const startY = e . clientY
49
+ const startHeight = customHeight
50
+
51
+ const handleMouseMove = ( moveEvent : MouseEvent ) => {
52
+ const deltaY =
53
+ direction === "top"
54
+ ? startY - moveEvent . clientY
55
+ : moveEvent . clientY - startY
56
+ const newHeight = Math . max ( 100 , Math . min ( 600 , startHeight + deltaY ) )
57
+ setCustomHeight ( newHeight )
58
+ }
59
+
60
+ const handleMouseUp = ( ) => {
61
+ document . removeEventListener ( "mousemove" , handleMouseMove )
62
+ document . removeEventListener ( "mouseup" , handleMouseUp )
63
+ document . body . style . cursor = "default"
64
+ document . body . style . userSelect = "auto"
65
+ }
66
+
67
+ document . body . style . cursor = direction === "top" ? "n-resize" : "s-resize"
68
+ document . body . style . userSelect = "none"
69
+ document . addEventListener ( "mousemove" , handleMouseMove )
70
+ document . addEventListener ( "mouseup" , handleMouseUp )
71
+ }
72
+
73
+ const ImageContainer = ( {
74
+ quality,
75
+ isComparison = false ,
76
+ } : {
77
+ quality : number
78
+ isComparison ?: boolean
79
+ } ) => (
80
+ < div className = "relative mx-auto w-full max-w-[1536px]" >
81
+ { ! isMobile && useCustomHeight && (
82
+ < >
83
+ < div
84
+ className = "absolute left-0 right-0 top-0 z-10 h-2 cursor-n-resize hover:bg-primary/20"
85
+ onMouseDown = { ( e ) => handleMouseResize ( e , "top" ) }
86
+ />
87
+ < div
88
+ className = "absolute bottom-0 left-0 right-0 z-10 h-2 cursor-s-resize hover:bg-primary/20"
89
+ onMouseDown = { ( e ) => handleMouseResize ( e , "bottom" ) }
90
+ />
91
+ </ >
92
+ ) }
93
+ < div
94
+ className = {
95
+ useCustomHeight
96
+ ? "overflow-hidden"
97
+ : "h-[240px] overflow-hidden md:h-[380px] lg:h-[480px]"
98
+ }
99
+ style = { useCustomHeight ? { height : `${ customHeight } px` } : undefined }
100
+ >
101
+ < Image
102
+ src = { heroImage }
103
+ alt = { `${ alt } (Quality: ${ quality } %)` }
104
+ width = { 1536 }
105
+ height = { 480 }
106
+ quality = { quality }
107
+ sizes = { `(max-width: ${ breakpointAsNumber [ "2xl" ] } px) 100vw, ${ breakpointAsNumber [ "2xl" ] } px` }
108
+ className = "h-full w-full object-cover"
109
+ priority = { ! isComparison && quality >= 80 }
110
+ />
111
+ </ div >
112
+ </ div >
113
+ )
114
+
31
115
return (
32
- < div className = "min-h-screen bg-background" >
33
- < div className = "mb-8 p-8 text-center" >
116
+ < div className = "min-h-screen bg-background pb-20 " >
117
+ < div className = "p-8 text-center" >
34
118
< h1 className = "mb-4 text-3xl font-bold" >
35
119
Hero Image Quality Comparison
36
120
</ h1 >
@@ -40,6 +124,54 @@ export default function HeroQualityDemo() {
40
124
qualities side-by-side.
41
125
</ p >
42
126
</ div >
127
+ < div className = "sticky inset-x-auto top-20 z-sticky mx-auto mb-8 flex w-fit flex-col items-center rounded-2xl border bg-background/90 px-3 py-2" >
128
+ { /* Custom Height Toggle */ }
129
+ < div className = "mb-4 flex items-center justify-center gap-4" >
130
+ < label
131
+ htmlFor = "custom-height-toggle"
132
+ className = "flex cursor-pointer items-center gap-3"
133
+ >
134
+ < span className = "block text-sm font-medium" > Prod</ span >
135
+ < div className = "relative" >
136
+ < input
137
+ id = "custom-height-toggle"
138
+ type = "checkbox"
139
+ checked = { useCustomHeight }
140
+ onChange = { ( e ) => setUseCustomHeight ( e . target . checked ) }
141
+ className = "sr-only"
142
+ />
143
+ < div
144
+ className = { `h-6 w-11 rounded-full transition-colors ${ useCustomHeight ? "bg-primary" : "bg-gray-300" } ` }
145
+ >
146
+ < div
147
+ className = { `h-5 w-5 transform rounded-full bg-white shadow-md transition-transform ${ useCustomHeight ? "translate-x-5" : "translate-x-0" } ml-0.5 mt-0.5` }
148
+ > </ div >
149
+ </ div >
150
+ </ div >
151
+ < span className = "block text-sm font-medium" > Play with height</ span >
152
+ </ label >
153
+ </ div >
154
+
155
+ { useCustomHeight && (
156
+ < div className = "flex items-center justify-center gap-4 text-center" >
157
+ < span className = "text-sm text-body-medium" >
158
+ Height: { customHeight } px
159
+ </ span >
160
+ { ! isMobile && (
161
+ < span className = "text-xs text-body-medium" >
162
+ (Hover top/bottom edges to resize)
163
+ </ span >
164
+ ) }
165
+ </ div >
166
+ ) }
167
+
168
+ { ! useCustomHeight && (
169
+ < div className = "text-center text-sm text-body-medium" >
170
+ Using responsive breakpoint heights: 240px (mobile) • 380px (tablet)
171
+ • 480px (desktop)
172
+ </ div >
173
+ ) }
174
+ </ div >
43
175
44
176
{ /* Comparison Section */ }
45
177
{ selectedQualities . length > 0 && (
@@ -50,23 +182,13 @@ export default function HeroQualityDemo() {
50
182
< div className = "space-y-6" >
51
183
{ selectedQualities . map ( ( quality ) => (
52
184
< div key = { `comparison-${ quality } ` } className = "w-full" >
53
- < div className = "relative mx-auto w-full max-w-[1536px] " >
54
- < div className = "absolute start-4 top-4 text-center" >
185
+ < div className = "relative" >
186
+ < div className = "absolute start-4 top-4 z-20 text-center" >
55
187
< span className = "inline-block rounded bg-primary px-3 py-1 text-sm font-semibold text-primary-high-contrast" >
56
188
Quality: { quality } %
57
189
</ span >
58
190
</ div >
59
- < div className = "h-[240px] overflow-hidden md:h-[380px] lg:h-[480px]" >
60
- < Image
61
- src = { heroImage }
62
- alt = { `${ alt } (Quality: ${ quality } %)` }
63
- width = { 1536 }
64
- height = { 480 }
65
- quality = { quality }
66
- sizes = { `(max-width: ${ breakpointAsNumber [ "2xl" ] } px) 100vw, ${ breakpointAsNumber [ "2xl" ] } px` }
67
- className = "h-full w-full object-cover"
68
- />
69
- </ div >
191
+ < ImageContainer quality = { quality } isComparison = { true } />
70
192
</ div >
71
193
</ div >
72
194
) ) }
@@ -95,24 +217,37 @@ export default function HeroQualityDemo() {
95
217
</ Heading2 >
96
218
</ div >
97
219
</ div >
98
- < div className = "mx-auto w-full max-w-[1536px]" >
99
- < div className = "h-[240px] overflow-hidden md:h-[380px] lg:h-[480px]" >
100
- < Image
101
- src = { heroImage }
102
- alt = { `${ alt } (Quality: ${ quality } %)` }
103
- width = { 1536 }
104
- height = { 480 }
105
- quality = { quality }
106
- sizes = { `(max-width: ${ breakpointAsNumber [ "2xl" ] } px) 100vw, ${ breakpointAsNumber [ "2xl" ] } px` }
107
- className = "h-full w-full object-cover"
108
- priority = { quality >= 80 } // Only prioritize the highest quality images
109
- />
110
- </ div >
111
- </ div >
220
+ < ImageContainer quality = { quality } />
112
221
</ div >
113
222
) ) }
114
223
</ div >
115
224
225
+ { /* Mobile Height Slider */ }
226
+ { isMobile && useCustomHeight && (
227
+ < div className = "fixed bottom-4 left-4 right-4 z-30 rounded-lg border border-border bg-background p-4 shadow-lg" >
228
+ < div className = "flex items-center gap-4" >
229
+ < label
230
+ htmlFor = "height-slider"
231
+ className = "flex-shrink-0 text-sm font-medium"
232
+ >
233
+ Height:
234
+ </ label >
235
+ < input
236
+ id = "height-slider"
237
+ type = "range"
238
+ min = "100"
239
+ max = "600"
240
+ value = { customHeight }
241
+ onChange = { ( e ) => setCustomHeight ( Number ( e . target . value ) ) }
242
+ className = "flex-1"
243
+ />
244
+ < span className = "w-12 flex-shrink-0 text-sm text-body-medium" >
245
+ { customHeight } px
246
+ </ span >
247
+ </ div >
248
+ </ div >
249
+ ) }
250
+
116
251
< div className = "mx-auto mt-12 max-w-4xl px-8" >
117
252
< div className = "rounded-lg bg-background-highlight p-6" >
118
253
< h3 className = "mb-4 text-lg font-semibold" > Usage Notes:</ h3 >
@@ -132,6 +267,7 @@ export default function HeroQualityDemo() {
132
267
• The optimal quality depends on your performance vs visual
133
268
quality requirements
134
269
</ li >
270
+ < li > • Use the height controls to test different viewport sizes</ li >
135
271
</ ul >
136
272
</ div >
137
273
</ div >
0 commit comments