You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
725 lines
20 KiB
725 lines
20 KiB
<template> |
|
<div id="SlideAlbum"> |
|
<div class="img-slide-wrapper"> |
|
<div class="img-slide-list" |
|
ref="wrapperEl" |
|
@touchstart.passive="touchStart" |
|
@touchmove="touchMove" |
|
@touchend="touchEnd"> |
|
<div class="img-slide-item" v-for="img in item.imgs"> |
|
<img :ref="e=>setItemRef(e,'itemRefs')" |
|
:src="img"> |
|
</div> |
|
</div> |
|
</div> |
|
<Icon icon="fluent:play-28-filled" class="pause-icon" v-if="state.status === SlideItemPlayStatus.Pause"/> |
|
|
|
<template v-if="state.operationStatus === SlideAlbumOperationStatus.Normal"> |
|
<ItemToolbar |
|
class="mb3r" |
|
v-model:item="state.localItem" |
|
:position="position" |
|
v-bind="$attrs" |
|
/> |
|
<ItemDesc |
|
class="mb3r" |
|
v-model:item="state.localItem" |
|
:position="position" |
|
/> |
|
</template> |
|
<!--不知为啥touch事件,在下部20px的空间内不触发,加上click事件不好了 --> |
|
<div class="progress-bar" |
|
v-if="!state.isPreview && state.operationStatus!== SlideAlbumOperationStatus.Zooming" |
|
@click="null" |
|
@touchstart="progressBarTouchStart" |
|
@touchmove="progressBarTouchMove" |
|
@touchend="progressBarTouchMEnd" |
|
> |
|
<div class="bar" v-for="(img,index) in item.imgs"> |
|
<div class="progress" |
|
:style="getWidth(index)"></div> |
|
</div> |
|
</div> |
|
<Teleport to="#home-index" v-if="state.isPreview"> |
|
<div class="preview"> |
|
<div class="preview-wrapper"> |
|
<img :src="img" |
|
:class="{'preview-img':index === state.localIndex}" |
|
v-for="(img,index) in props.item.imgs" |
|
:ref="e=>setItemRef(e,'previewImgs')" |
|
> |
|
</div> |
|
<div class="indicator"> |
|
<span class="index">{{ state.localIndex + 1 }}</span> / {{ props.item.imgs.length }} |
|
</div> |
|
</div> |
|
</Teleport> |
|
<Teleport to="#home-index" v-if="state.operationStatus !== SlideAlbumOperationStatus.Normal"> |
|
<div class="album-toolbar"> |
|
<div class="left"> |
|
<Icon icon="iconamoon:close" @click="state.operationStatus = SlideAlbumOperationStatus.Normal"/> |
|
</div> |
|
<div class="right"> |
|
<Icon icon="heroicons-outline:menu-alt-1" @click="Utils.$no"/> |
|
<Icon icon="fluent:play-28-filled" |
|
v-if="state.status === SlideItemPlayStatus.Pause" |
|
class="pause" |
|
@click="startPlay"/> |
|
<Icon icon="bi:pause-fill" |
|
v-else |
|
class="pause" |
|
@click="stopPlay"/> |
|
<Icon icon="system-uicons:push-down" @click="Utils.$no"/> |
|
</div> |
|
</div> |
|
</Teleport> |
|
</div> |
|
</template> |
|
|
|
<script setup lang="jsx"> |
|
import enums from "../../utils/enums"; |
|
import Utils from '../../utils' |
|
import {mat4} from 'gl-matrix' |
|
import {Icon} from "@iconify/vue"; |
|
import {onMounted, onBeforeUpdate, reactive, ref, watch, computed, provide, nextTick, onUnmounted} from "vue"; |
|
import { |
|
getSlideDistance, |
|
slideInit, |
|
slideReset, |
|
slideTouchEnd, |
|
slideTouchMove, |
|
slideTouchStart |
|
} from "./common"; |
|
import {SlideAlbumOperationStatus, SlideItemPlayStatus, SlideType} from "../../utils/const_var"; |
|
import ItemToolbar from "./ItemToolbar"; |
|
import ItemDesc from "./ItemDesc"; |
|
import GM from "../../utils"; |
|
import {cloneDeep} from "lodash"; |
|
import bus, {EVENT_KEY} from "../../utils/bus"; |
|
|
|
let out = new Float32Array([ |
|
0, 0, 0, 0, |
|
0, 0, 0, 0, |
|
0, 0, 0, 0, |
|
0, 0, 0, 0 |
|
]) |
|
let ov = new Float32Array([ |
|
1, 0, 0, 0, |
|
0, 1, 0, 0, |
|
0, 0, 1, 0, |
|
0, 0, 0, 1, |
|
]); |
|
let origin = cloneDeep(ov) |
|
const rectMap = new Map() |
|
|
|
// provide('isPlaying', computed(() => this.isPlaying)) |
|
provide('isPlaying', false) |
|
const props = defineProps({ |
|
item: { |
|
type: Object, |
|
default() { |
|
return { |
|
type: 'imgs', |
|
imgs: [ |
|
'https://cdn.seovx.com/ha/?mom=302', |
|
'https://cdn.seovx.com/?mom=302', |
|
'https://cdn.seovx.com/?mom=302', |
|
'https://cdn.seovx.com/?mom=302', |
|
'https://cdn.seovx.com/?mom=302', |
|
'https://cdn.seovx.com/?mom=302', |
|
'https://cdn.seovx.com/?mom=302', |
|
'https://cdn.seovx.com/?mom=302', |
|
], |
|
"id": "034ae83b-ca0a-401a-b7c6-cf78361bae7b", |
|
video: 'http://douyin.ttentau.top/0.mp4', |
|
"video_data_size": 26829508, |
|
"duration": 427780, |
|
"desc": "我不管我们宿舍第一好看", |
|
"allow_download": 0, |
|
"allow_duet": 0, |
|
"allow_react": 0, |
|
"allow_music": 1, |
|
"allow_douplus": 1, |
|
"allow_share": 1, |
|
"digg_count": 10480000, |
|
"comment_count": 79000, |
|
"download_count": 6, |
|
"play_count": 0, |
|
"share_count": 119000, |
|
"forward_count": 0, |
|
"collect_count": 3, |
|
"sort": 195, |
|
"is_top": 0, |
|
"city": "北京", |
|
address: '中央戏剧学院', |
|
"musicId": "2ee213c6-3e3f-4758-ba5a-7f1c955604a4", |
|
"create_time": "1630423555", |
|
"creator_id": "93864497380", |
|
"status": 1, |
|
"topics": [ |
|
{ |
|
"id": "85ceda30-898f-4b57-b891-0e58b3ab99a9", |
|
"name": "敬礼变装", |
|
"creator_id": "93864497380", |
|
"create_time": "1630423555", |
|
"status": 1 |
|
}, |
|
{ |
|
"id": "85ceda30-898f-4b57-b891-0e58b3ab99a9", |
|
"name": "宿舍", |
|
"creator_id": "93864497380", |
|
"create_time": "1630423555", |
|
"status": 1 |
|
} |
|
], |
|
"music": { |
|
"id": "cde50af2-628c-4d28-b9c6-67237a62518e", |
|
"cover": "https://p29.douyinpic.com/img/tos-cn-avt-0015/f4de202ff2e41b523838a4a767aebd16~c5_100x100.jpeg?from=116350172", |
|
"mp3": "https://sf3-cdn-tos.douyinstatic.com/obj/ies-music/1658584661080088.mp3", |
|
"title": "@穷电影创作的原声-小高快起来跳舞", |
|
"creator_id": "93864497380", |
|
"create_time": "1630423555", |
|
"status": 1 |
|
}, |
|
"author": { |
|
"id": "1", |
|
"unique_id_modify_time": "1630393144", |
|
"unique_id": "10040050", |
|
"favoriting_count": 143, |
|
"avatar": new URL('../../assets/img/icon/avatar/3.png', import.meta.url).href, |
|
school: { |
|
name: '中央戏剧学院', |
|
department: null, |
|
joinTime: null, |
|
education: null, |
|
displayType: enums.DISPLAY_TYPE.ALL, |
|
}, |
|
"city": "", |
|
"province": '北京', |
|
"country": "", |
|
"location": "", |
|
"birthday": "2002-01-01", |
|
"cover": "https://p3.douyinpic.com/obj/c8510002be9a3a61aad2?from=116350172", |
|
"following_count": 66, |
|
"follower_count": 235000, |
|
"aweme_count": 1796000, |
|
"nickname": "我是小睿耶", |
|
certification: '', |
|
"phone": "", |
|
"sex": "", |
|
"last_login_time": "1630423555", |
|
"create_time": "1630423555", |
|
"status": 1, |
|
"desc": `一个普普通通学表演的\n看到的人都能开开心心`, |
|
"is_private": 0 |
|
} |
|
} |
|
} |
|
}, |
|
position: { |
|
type: Object, |
|
default: () => { |
|
return { |
|
uniqueId: '', |
|
index: '', |
|
} |
|
} |
|
}, |
|
}) |
|
const judgeValue = 20 |
|
const wrapperEl = ref(null) |
|
|
|
//用于解决,touch事件触发startPlay,然后click事件又触发stopLoop的问题 |
|
let lockDatetime = 0 |
|
|
|
const state = reactive({ |
|
name: 'SlideHorizontal', |
|
localIndex: 0, |
|
needCheck: true, |
|
isPreview: false, |
|
isZoom: false, |
|
operationStatus: SlideAlbumOperationStatus.Normal, |
|
next: false, |
|
wrapper: {width: 0, height: 0, childrenLength: 0}, |
|
last: { |
|
point1: {x: 0, y: 0}, |
|
point2: {x: 0, y: 0}, |
|
}, |
|
start: { |
|
x: 0, y: 0, |
|
point1: {x: 0, y: 0}, |
|
point2: {x: 0, y: 0}, |
|
center: {x: 0, y: 0}, |
|
time: 0 |
|
}, |
|
move: {x: 0, y: 0}, |
|
itemRefs: [], |
|
previewImgs: [], |
|
cycleFn: -1, |
|
status: SlideItemPlayStatus.Play, |
|
isAutoPlay: true, |
|
localItem: props.item, |
|
}) |
|
|
|
function stopPlay() { |
|
state.status = SlideItemPlayStatus.Pause |
|
stopLoop() |
|
} |
|
|
|
function startPlay() { |
|
state.isAutoPlay = true |
|
state.status = SlideItemPlayStatus.Play |
|
startLoop() |
|
} |
|
|
|
function stopLoop() { |
|
clearInterval(state.cycleFn) |
|
state.cycleFn = -1 |
|
} |
|
|
|
function startLoop() { |
|
if (state.cycleFn !== -1) return |
|
if (!state.isAutoPlay) return |
|
state.cycleFn = setInterval(() => { |
|
if (state.localIndex < props.item.imgs.length - 1) { |
|
state.localIndex++ |
|
} else { |
|
state.localIndex = 0 |
|
} |
|
}, 1500) |
|
} |
|
|
|
onMounted(async () => { |
|
await nextTick(); |
|
slideInit(wrapperEl.value, state, SlideType.HORIZONTAL) |
|
startPlay() |
|
// setTimeout(() => { |
|
// state.operationStatus = SlideAlbumOperationStatus.Zooming |
|
// }, 1000) |
|
|
|
bus.on(EVENT_KEY.SINGLE_CLICK_BROADCAST, click) |
|
}) |
|
|
|
onUnmounted(() => { |
|
bus.off(EVENT_KEY.SINGLE_CLICK_BROADCAST, click) |
|
}) |
|
|
|
function click({uniqueId, index, type}) { |
|
// console.log('position,', type, Date.now() - lockDatetime) |
|
if (props.position.uniqueId === uniqueId && props.position.index === index) { |
|
// if (type === EVENT_KEY.ITEM_TOGGLE) { |
|
// if (state.status === SlideItemPlayStatus.Play) { |
|
// stopLoop() |
|
// } else { |
|
// state.isAutoPlay = true |
|
// startLoop() |
|
// } |
|
// } |
|
if (type === EVENT_KEY.ITEM_STOP) { |
|
stopPlay() |
|
setTimeout(() => { |
|
state.localIndex = 0 |
|
}, 500) |
|
} |
|
if (type === EVENT_KEY.ITEM_PLAY) { |
|
state.localIndex = 0 |
|
state.isAutoPlay = true |
|
state.status = SlideItemPlayStatus.Play |
|
startLoop() |
|
} |
|
} |
|
} |
|
|
|
// 确保在每次更新之前重置ref |
|
onBeforeUpdate(() => { |
|
state.itemRefs = [] |
|
state.previewImgs = [] |
|
}) |
|
|
|
watch( |
|
() => state.localIndex, |
|
(newVal) => { |
|
GM.$setCss(wrapperEl.value, 'transition-duration', `300ms`) |
|
GM.$setCss(wrapperEl.value, 'transform', `translate3d(${getSlideDistance(state, SlideType.HORIZONTAL)}px, 0, 0)`) |
|
} |
|
) |
|
|
|
watch( |
|
() => state.operationStatus, |
|
(newVal) => { |
|
if (newVal !== SlideAlbumOperationStatus.Normal) { |
|
bus.emit(EVENT_KEY.ENTER_FULLSCREEN) |
|
} else { |
|
bus.emit(EVENT_KEY.EXIT_FULLSCREEN) |
|
} |
|
} |
|
) |
|
|
|
function calcCurrentIndex(e) { |
|
state.isPreview = true |
|
let x = e.touches[0].pageX |
|
|
|
let current = -1 |
|
let length = state.previewImgs.length |
|
for (let i = length - 1; i >= 0; i--) { |
|
let rect = state.previewImgs[i].getBoundingClientRect() |
|
if (rect.x < x) { |
|
current = i |
|
break |
|
} |
|
} |
|
if (current > -1) { |
|
state.localIndex = current |
|
} |
|
} |
|
|
|
function progressBarTouchStart(e) { |
|
Utils.$stopPropagation(e) |
|
} |
|
|
|
function progressBarTouchMove(e) { |
|
Utils.$stopPropagation(e) |
|
calcCurrentIndex(e) |
|
} |
|
|
|
function progressBarTouchMEnd(e) { |
|
Utils.$stopPropagation(e) |
|
state.isPreview = false |
|
} |
|
|
|
function touchStart(e) { |
|
lockDatetime = Date.now() |
|
|
|
// Utils.$showNoticeDialog('start'+e.touches.length) |
|
console.log('start', e.touches.length) |
|
if (e.touches.length === 1) { |
|
slideTouchStart(e, wrapperEl.value, state) |
|
} else { |
|
state.last.point2 = state.start.point2 = {x: e.touches[1].pageX, y: e.touches[1].pageY}; |
|
if (state.operationStatus === SlideAlbumOperationStatus.Zooming) { |
|
state.start.center = Utils.getCenter(state.start.point1, state.start.point2) |
|
return |
|
} |
|
state.operationStatus = SlideAlbumOperationStatus.Zooming |
|
state.itemRefs[state.localIndex].style['transition-duration'] = '0ms'; |
|
state.last.point1 = state.start.point1 = {x: e.touches[0].pageX, y: e.touches[0].pageY}; |
|
// state.last.point2 = state.start.point2 = {x: e.touches[1].pageX, y: e.touches[1].pageY}; |
|
state.start.center = Utils.getCenter(state.start.point1, state.start.point2) |
|
} |
|
} |
|
|
|
function touchMove(e) { |
|
// Utils.$showNoticeDialog('move'+e.touches.length) |
|
console.log('move', e.touches.length,state.operationStatus ) |
|
let current1 = {x: e.touches[0].pageX, y: e.touches[0].pageY} |
|
stopLoop() |
|
|
|
//单手移动 |
|
if (e.touches.length === 1) { |
|
if (state.operationStatus === SlideAlbumOperationStatus.Zooming) { |
|
// console.log('m1') |
|
Utils.$stopPropagation(e) |
|
|
|
// console.log('单手移动',) |
|
let movementX = current1.x - state.last.point1.x |
|
let movementY = current1.y - state.last.point1.y |
|
// console.log(movementX, movementY) |
|
const t = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, movementX, movementY, 0, 1,]); |
|
ov = mat4.multiply(out, t, ov); |
|
state.itemRefs[state.localIndex].style.transform = `matrix3d(${ov.toString()})`; |
|
state.last.point1 = current1 |
|
} else { |
|
// console.log('m2') |
|
state.isAutoPlay = false |
|
slideTouchMove(e, wrapperEl.value, state, judgeValue, canNext, |
|
() => { |
|
}, SlideType.HORIZONTAL, |
|
() => { |
|
if (state.operationStatus === SlideAlbumOperationStatus.Detail) { |
|
Utils.$stopPropagation(e) |
|
} |
|
}) |
|
} |
|
} else { |
|
// console.log('m3') |
|
state.operationStatus = SlideAlbumOperationStatus.Zooming |
|
Utils.$stopPropagation(e) |
|
|
|
let rect = {x: 0, y: 0} |
|
if (rectMap.has(state.localIndex)) { |
|
rect = rectMap.get(state.localIndex) |
|
} else { |
|
//getBoundingClientRect在手机上获取不到值 |
|
let offset = $(state.itemRefs[state.localIndex]).offset() |
|
rect = {x: offset.left, y: offset.top} |
|
rectMap.set(state.localIndex, rect) |
|
} |
|
|
|
let current2 = {x: e.touches[1].pageX, y: e.touches[1].pageY} |
|
|
|
// 双指缩放比例,就是对应的放大倍数 |
|
let currentRatio = Utils.getDistance(current1, current2) / Utils.getDistance(state.start.point1, state.start.point2); |
|
let movementRatio = currentRatio - ov[0] |
|
// console.log('movementRatio',movementRatio) |
|
//如果本次比例和上次的不超过0.02。那么判定为平移 |
|
if (Math.abs(movementRatio) <= 0.02) { |
|
let movementX = current1.x - state.last.point1.x |
|
let movementY = current1.y - state.last.point1.y |
|
let movement2X = current2.x - state.last.point2.x |
|
let movement2Y = current2.y - state.last.point2.y |
|
|
|
let minX = Math.min(movementX, movement2X) |
|
let minY = Math.min(movementY, movement2Y) |
|
const t1 = new Float32Array([ |
|
1, 0, 0, 0, |
|
0, 1, 0, 0, |
|
0, 0, 1, 0, |
|
minX, minY, 0, 1 |
|
]); |
|
ov = mat4.multiply(out, t1, ov); |
|
} else { |
|
let center = Utils.getCenter(current1, current2) |
|
center.x -= rect.x |
|
center.y -= rect.y |
|
//用最新的放大倍数ratio除以之前的放大ov[0]倍数,算出本次要累加放大的倍数 |
|
let zoom = currentRatio / ov[0] |
|
const x = center.x * (1 - zoom); |
|
const y = center.y * (1 - zoom); |
|
const t = new Float32Array([ |
|
zoom, 0, 0, 0, |
|
0, zoom, 0, 0, |
|
0, 0, 1, 0, |
|
x, y, 0, 1 |
|
]); |
|
//如果zoom是每次都是最后放大倍数,第三个参数用原值(即,矩阵x乘时,都是乘以单位矩阵) |
|
//如果zoom是累加放大(比如每次都是0.15),第三个参数用ov。这里还是采用累加计算 |
|
ov = mat4.multiply(out, t, ov); |
|
} |
|
|
|
state.itemRefs[state.localIndex].style.transform = `matrix3d(${ov.toString()})`; |
|
state.last.point1 = current1 |
|
state.last.point2 = current2 |
|
} |
|
} |
|
|
|
function touchEnd(e) { |
|
console.log('Date.now() - lockDatetime', Date.now() - lockDatetime,) |
|
if (Date.now() - lockDatetime < 300 && state.move.x === 0 && state.move.y === 0) { |
|
if (state.status === SlideItemPlayStatus.Play) { |
|
stopPlay() |
|
} else { |
|
startPlay() |
|
} |
|
return |
|
} |
|
|
|
state.isPreview = false |
|
//这里,如果是双指触控的话,会触发两次事件,第一次touches长度为1,第二次为0 |
|
//如果是单指触控的话,触发一次事件,touches长度为0 |
|
console.log('end', e.touches.length) |
|
|
|
// e.touches.length === 1 说明,松开了第一只手指 |
|
if (e.touches.length === 1) { |
|
//双指缩放状态下,但只松开了一只手 |
|
if (state.operationStatus === SlideAlbumOperationStatus.Zooming) { |
|
Utils.$stopPropagation(e) |
|
state.last.point1 = {x: e.touches[0].pageX, y: e.touches[0].pageY} |
|
startLoop() |
|
} |
|
} else { |
|
if (state.operationStatus === SlideAlbumOperationStatus.Zooming) { |
|
Utils.$stopPropagation(e) |
|
ov = origin |
|
state.itemRefs[state.localIndex].style['transition-duration'] = '300ms'; |
|
state.itemRefs[state.localIndex].style.transform = `matrix3d(${origin.toString()})`; |
|
startLoop() |
|
state.operationStatus = SlideAlbumOperationStatus.Look |
|
} else { |
|
slideTouchEnd(e, state, canNext, |
|
() => { |
|
console.log('nextCb') |
|
}, |
|
() => { |
|
console.log('doNotNextCb') |
|
startLoop() |
|
} |
|
) |
|
slideReset(wrapperEl.value, state, SlideType.HORIZONTAL, null) |
|
} |
|
} |
|
} |
|
|
|
function getWidth(index) { |
|
if (state.localIndex >= index) return {width: '100%'} |
|
} |
|
|
|
function setItemRef(el, key) { |
|
el && state[key].push(el) |
|
} |
|
|
|
function canNext(isNext, e) { |
|
let res = !((state.localIndex === 0 && !isNext) || (state.localIndex === props.item.imgs.length - 1 && isNext)); |
|
if (!res && state.operationStatus === SlideAlbumOperationStatus.Detail && e) { |
|
Utils.$stopPropagation(e) |
|
} |
|
return res |
|
} |
|
|
|
|
|
</script> |
|
|
|
<style scoped lang="less"> |
|
@import "@/assets/less/index"; |
|
|
|
#SlideAlbum { |
|
transition: height .3s; |
|
position: relative; |
|
width: 100%; |
|
height: 100%; |
|
overflow: hidden; |
|
color: white; |
|
font-size: 14rem; |
|
|
|
.img-slide-wrapper { |
|
position: relative; |
|
height: 100%; |
|
width: 100%; |
|
|
|
.img-slide-list { |
|
height: 100%; |
|
width: 100%; |
|
display: flex; |
|
position: relative; |
|
|
|
.img-slide-item { |
|
height: 100%; |
|
width: 100%; |
|
flex-shrink: 0; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
|
|
img { |
|
transform-origin: 0 0; |
|
width: 100%; |
|
} |
|
} |
|
} |
|
} |
|
|
|
.progress-bar { |
|
position: absolute; |
|
width: 100%; |
|
bottom: 10rem; |
|
display: flex; |
|
box-sizing: border-box; |
|
padding: 0 5rem; |
|
@h: 4rem; |
|
//height: @h; |
|
height: 10rem; |
|
//background-color: red; |
|
align-items: flex-end; |
|
justify-content: space-between; |
|
|
|
.bar { |
|
border-radius: 10rem; |
|
flex: 1; |
|
margin: 0 2rem; |
|
height: @h; |
|
background: rgba(#000, .5); |
|
position: relative; |
|
overflow: hidden; |
|
|
|
.progress { |
|
border-radius: 10rem; |
|
position: absolute; |
|
left: 0; |
|
height: @h; |
|
background: white; |
|
} |
|
} |
|
} |
|
|
|
} |
|
</style> |
|
<style lang="less"> |
|
@import "@/assets/less/index"; |
|
|
|
.preview { |
|
transition: opacity .3s; |
|
position: fixed; |
|
bottom: 0; |
|
width: 100%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
flex-direction: column; |
|
|
|
.preview-wrapper { |
|
img { |
|
transition: width .3s; |
|
margin: 0 5rem; |
|
width: 30rem; |
|
height: 50rem; |
|
background-color: black; |
|
border-radius: 3rem; |
|
overflow: hidden; |
|
object-fit: cover; |
|
|
|
&.preview-img { |
|
width: 40rem; |
|
} |
|
} |
|
} |
|
|
|
.indicator { |
|
background: @footer-color; |
|
width: 100%; |
|
height: @footer-height; |
|
color: gray; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
|
|
.index { |
|
color: white; |
|
} |
|
} |
|
} |
|
|
|
.album-toolbar { |
|
position: absolute; |
|
bottom: 0; |
|
color: white; |
|
font-size: 24rem; |
|
background: @footer-color; |
|
width: 100%; |
|
box-sizing: border-box; |
|
height: @footer-height; |
|
display: flex; |
|
align-items: center; |
|
justify-content: space-between; |
|
padding: 0 10rem; |
|
|
|
@padding: 18rem; |
|
|
|
.left { |
|
height: 40rem; |
|
background-color: rgba(71, 71, 86, 0.53); |
|
border-radius: 10rem; |
|
padding: 0 @padding; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
} |
|
|
|
.right { |
|
.left; |
|
|
|
display: flex; |
|
align-items: center; |
|
gap: 20rem; |
|
} |
|
} |
|
|
|
</style>
|
|
|