fix(vibe-stories): finalize laptop viewport sizing
This commit is contained in:
@@ -72,39 +72,50 @@ const tStories = computed(() => [
|
||||
}
|
||||
])
|
||||
|
||||
const defaultScreenViewport = Object.freeze({
|
||||
left: 19,
|
||||
top: 1.75,
|
||||
width: 62.75,
|
||||
height: 71.5,
|
||||
radius: 12
|
||||
})
|
||||
|
||||
const currentIndex = ref(0)
|
||||
let autoplayTimer = null
|
||||
const isPaginating = ref(false)
|
||||
const containerRef = ref(null)
|
||||
const laptopRef = ref(null)
|
||||
let wheelHandler = null
|
||||
let resizeObserver = null
|
||||
|
||||
const screenRegion = ref({
|
||||
centerX: 50,
|
||||
centerY: 45.75,
|
||||
width: 80,
|
||||
height: 90,
|
||||
radius: 4
|
||||
})
|
||||
const LAPTOP_ASPECT_RATIO = 2675 / 4608
|
||||
const laptopHeightPx = ref(null)
|
||||
|
||||
// Visible image container geometry relative to `.laptop-container`.
|
||||
// Adjust these five values directly to control the screen viewport.
|
||||
const screenViewport = ref({ ...defaultScreenViewport })
|
||||
|
||||
const formatPercent = (value) => `${value}%`
|
||||
const formatPixels = (value) => `${value}px`
|
||||
|
||||
const screenContentStyle = computed(() => ({
|
||||
top: formatPercent(screenRegion.value.centerY - screenRegion.value.height / 2),
|
||||
left: formatPercent(screenRegion.value.centerX - screenRegion.value.width / 2),
|
||||
width: formatPercent(screenRegion.value.width),
|
||||
height: formatPercent(screenRegion.value.height),
|
||||
borderRadius: formatPixels(screenRegion.value.radius)
|
||||
// The percentages below are always resolved against `.laptop-container`.
|
||||
const screenViewportStyle = computed(() => ({
|
||||
'--screen-left': formatPercent(screenViewport.value.left),
|
||||
'--screen-top': formatPercent(screenViewport.value.top),
|
||||
'--screen-width': formatPercent(screenViewport.value.width),
|
||||
'--screen-height': formatPercent(screenViewport.value.height),
|
||||
'--screen-radius': formatPixels(screenViewport.value.radius)
|
||||
}))
|
||||
|
||||
const currentStory = computed(() => tStories.value[currentIndex.value] ?? tStories.value[0])
|
||||
|
||||
// Keep crop settings explicit in source so they survive reload/HMR.
|
||||
const currentImageStyle = computed(() => ({
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center center',
|
||||
...(currentStory.value?.imageStyle || {})
|
||||
}))
|
||||
const currentImageStyle = computed(() => currentStory.value?.imageStyle || {})
|
||||
|
||||
const laptopContainerStyle = computed(() => (
|
||||
laptopHeightPx.value
|
||||
? { height: `${laptopHeightPx.value}px` }
|
||||
: {}
|
||||
))
|
||||
|
||||
const transitionName = ref('slide-left')
|
||||
|
||||
@@ -149,9 +160,18 @@ const stopAutoplay = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const updateLaptopHeight = () => {
|
||||
const laptop = laptopRef.value
|
||||
if (!laptop) return
|
||||
|
||||
const nextHeight = laptop.clientWidth * LAPTOP_ASPECT_RATIO
|
||||
laptopHeightPx.value = nextHeight > 0 ? nextHeight : null
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
startAutoplay()
|
||||
const container = containerRef.value
|
||||
const laptop = laptopRef.value
|
||||
if (!container) return
|
||||
|
||||
wheelHandler = (e) => {
|
||||
@@ -166,6 +186,17 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
container.addEventListener('wheel', wheelHandler, { passive: false })
|
||||
|
||||
updateLaptopHeight()
|
||||
|
||||
if (typeof ResizeObserver !== 'undefined' && laptop) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
updateLaptopHeight()
|
||||
})
|
||||
resizeObserver.observe(laptop)
|
||||
} else if (typeof window !== 'undefined') {
|
||||
window.addEventListener('resize', updateLaptopHeight)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -174,6 +205,13 @@ onUnmounted(() => {
|
||||
if (container && wheelHandler) {
|
||||
container.removeEventListener('wheel', wheelHandler)
|
||||
}
|
||||
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
resizeObserver = null
|
||||
} else if (typeof window !== 'undefined') {
|
||||
window.removeEventListener('resize', updateLaptopHeight)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -185,7 +223,7 @@ onUnmounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="laptop-wrapper" @mouseenter="stopAutoplay" @mouseleave="startAutoplay">
|
||||
<div class="laptop-container">
|
||||
<div ref="laptopRef" class="laptop-container" :style="laptopContainerStyle">
|
||||
<!-- Navigation Controls -->
|
||||
<button class="nav-btn prev" :aria-label="t.stories?.ui?.prevLabel || 'Previous story'" @click="prev">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6" /></svg>
|
||||
@@ -194,7 +232,7 @@ onUnmounted(() => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6" /></svg>
|
||||
</button>
|
||||
|
||||
<div class="screen-content" :style="screenContentStyle">
|
||||
<div class="screen-content" :style="screenViewportStyle">
|
||||
<a :href="withBase(currentStory.link)" class="screen-link">
|
||||
<transition :name="transitionName">
|
||||
<div :key="currentStory.id" class="screen-image-wrapper">
|
||||
@@ -304,13 +342,15 @@ onUnmounted(() => {
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
margin: 0 auto;
|
||||
aspect-ratio: 4608 / 2675;
|
||||
}
|
||||
|
||||
.laptop-frame {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
pointer-events: none;
|
||||
filter: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15));
|
||||
}
|
||||
@@ -322,6 +362,11 @@ onUnmounted(() => {
|
||||
.screen-content {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: var(--screen-top);
|
||||
left: var(--screen-left);
|
||||
width: var(--screen-width);
|
||||
height: var(--screen-height);
|
||||
border-radius: var(--screen-radius);
|
||||
background: #0b0b0f;
|
||||
overflow: hidden;
|
||||
perspective: 1000px;
|
||||
|
||||
Reference in New Issue
Block a user