layout + non looping video

This commit is contained in:
nicwands 2026-06-24 11:16:12 -04:00
parent dbb93ba802
commit 1894e37605
10 changed files with 111 additions and 21 deletions

2
src/components.d.ts vendored
View file

@ -24,8 +24,10 @@ declare module 'vue' {
SiteHeader: typeof import('./components/site/Header.vue')['default']
StrapiContent: typeof import('./components/strapi/Content.vue')['default']
StrapiCopy: typeof import('./components/strapi/Copy.vue')['default']
StrapiDiptych: typeof import('./components/strapi/Diptych.vue')['default']
StrapiLink: typeof import('./components/strapi/Link.vue')['default']
StrapiMap: typeof import('./components/strapi/Map.vue')['default']
StrapiMedia: typeof import('./components/strapi/Media.vue')['default']
SvgPlay: typeof import('./components/svg/Play.vue')['default']
}
}

View file

@ -14,10 +14,13 @@ const props = defineProps({ row: Object })
.dl-row {
grid-template-columns: desktop-vw(40px) 1fr;
display: grid;
align-items: center;
align-items: flex-start;
padding-top: desktop-vw(8px);
border-top: 1px dashed currentColor;
.label {
padding-top: 0.4em;
}
.content {
p,
a {

View file

@ -1,6 +1,6 @@
<template>
<div v-if="global" class="layout">
<site-header v-show="showHeader" :copy="headerCopy" :dl="headerDl" />
<div v-if="global" class="layout" :style="{ '--header-height': headerHeight + 'px' }">
<site-header v-show="showHeader" :copy="headerCopy" :dl="headerDl" ref="header" />
<client-only>
<mosaic-scroll :items="mosaicItems" />
@ -17,20 +17,26 @@
</template async>
<script setup>
import { computed} from 'vue';
import { computed, ref} from 'vue';
import { useSeoMeta } from '@unhead/vue'
import { useStrapiGlobal } from '@/composables/useStrapi'
import { useRoute } from 'vue-router';
import useBreakpoints from '@/composables/useBreakpoints';
import { useElementSize } from '@vueuse/core';
const header = ref()
const route = useRoute()
const { data: global } = await useStrapiGlobal()
const {isMobile} = useBreakpoints()
const { isMobile } = useBreakpoints()
const { height: headerHeight } = useElementSize(header)
const headerCopy = computed(() => global.value?.header_copy)
const headerDl = computed(() => global.value?.header_dl)
const mosaicItems = computed(() => global.value?.mosaic_items || [])
const showHeader = computed(() => !(isMobile && route.name === 'mosaic-item'))
const showHeader = computed(() => !(route.name === 'mosaic-item' && isMobile.value))
// SEO
const seo = computed(() => global.value?.seo)

View file

@ -47,7 +47,9 @@ const onClose = () => router.push('/')
padding-top: desktop-vw(27px);
.meta {
grid-column: 1 / span 2;
grid-column: 2 / span 2;
margin-top: var(--header-height);
padding-top: var(--layout-column-gap);
}
.window {
@ -78,7 +80,7 @@ const onClose = () => router.push('/')
}
}
&.content {
grid-column: 3 / -2;
grid-column: 4 / -2;
.lenis {
max-height: desktop-vh(950px);
@ -118,6 +120,8 @@ const onClose = () => router.push('/')
display: grid;
gap: var(--layout-column-gap);
align-items: flex-start;
margin: 0;
padding: 0;
}
.window {
border-radius: mobile-vw(10px);

View file

@ -14,7 +14,7 @@
</template>
<script setup>
import { ref, watch } from 'vue'
import { nextTick, ref, watch } from 'vue'
import _shuffle from 'lodash/shuffle'
import { useMosaicLayout } from '@/composables/useMosaicLayout'
import { useRoute } from 'vue-router'
@ -69,8 +69,11 @@ const generateMosaicContent = async (index) => {
// Start/stop scroll when page is open
watch(
() => route.name,
() => {
async () => {
if (route.name === 'mosaic-item') {
if (!scrollContainer.value) {
await nextTick()
}
scrollContainer.value?.stop?.()
} else {
scrollContainer.value?.start?.()

View file

@ -24,7 +24,7 @@ const props = defineProps({
left: 0;
right: 0;
pointer-events: none;
padding: desktop-vw(27px) var(--layout-margin);
padding: desktop-vw(27px) var(--layout-margin) 0;
align-items: flex-start;
> * {

View file

@ -0,0 +1,21 @@
<template>
<section class="strapi-diptych">
<strapi-media v-bind="props.left_media" />
<strapi-media v-bind="props.right_media" />
</section>
</template>
<script setup>
const props = defineProps({
left_media: Object,
right_media: Object,
})
</script>
<style lang="scss">
.strapi-diptych {
grid-template-columns: 1fr 1fr;
display: grid;
gap: var(--layout-column-gap);
}
</style>

View file

@ -12,11 +12,13 @@
<script setup>
import Copy from './Copy.vue'
import Diptych from './Diptych.vue'
import Media from './Media.vue'
const MAP = {
'global.copy': Copy,
'global.media': Media,
'global.dipytch': Diptych,
}
const props = defineProps({

View file

@ -1,7 +1,11 @@
<template>
<div
v-if="image"
:class="['strapi-media', { 'fill-space': fillSpace }, `fit-${fit}`]"
:class="[
'strapi-media',
{ 'fill-space': fillSpace, 'can-play': canPlay },
`fit-${fit}`,
]"
:style="styles"
>
<div class="image-sizer">
@ -16,13 +20,19 @@
decoding="async"
/>
<button v-if="!shouldLoop" class="play-button" @click="onPlayClick">
<svg-play />
</button>
<video
v-if="video?.url"
:src="video.url"
muted
autoplay
playsinline
loop
:muted="shouldLoop"
:autoplay="shouldLoop"
:playsinline="shouldLoop"
:loop="shouldLoop"
:controls="!shouldLoop"
ref="videoRef"
/>
</div>
</div>
@ -30,11 +40,15 @@
<script setup>
import { breakpoints } from '@/libs/theme'
import { computed } from 'vue'
import { computed, ref } from 'vue'
const props = defineProps({
image: Object,
video: Object,
looping: {
type: Boolean,
default: () => true,
},
aspect: {
type: [String, Number],
default: () => -1,
@ -57,6 +71,10 @@ const props = defineProps({
},
})
const videoRef = ref()
const canPlay = ref(props.looping !== false)
const shouldLoop = computed(() => props.looping !== false)
const formats = computed(() => props.image?.formats || {})
const ratio = computed(() => {
if (!props.image) return 1
@ -82,6 +100,11 @@ const sizes = computed(
() =>
`(max-width: ${breakpoints.mobile}) ${props.mobileSize}, ${props.desktopSize}`,
)
const onPlayClick = () => {
canPlay.value = true
videoRef.value?.play()
}
</script>
<style lang="scss">
@ -94,7 +117,8 @@ const sizes = computed(
overflow: hidden;
padding-bottom: var(--aspect);
& > * {
img,
video {
display: block;
position: absolute;
height: 100%;
@ -102,9 +126,17 @@ const sizes = computed(
left: 0;
top: 0;
}
.svg {
background-size: cover;
background-repeat: no-repeat;
.play-button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
svg {
width: 4rem;
height: auto;
}
}
}
@ -126,6 +158,11 @@ const sizes = computed(
}
}
&:not(.can-play) video {
opacity: 0;
pointer-events: none;
}
// fits
&.fit-cover .image-sizer > * {
object-fit: cover;

View file

@ -0,0 +1,12 @@
<template>
<svg
class="svg-play"
width="15"
height="16"
viewBox="0 0 15 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M0 16V0L14.607 8L0 16Z" fill="currentColor" />
</svg>
</template>