|
|
|
@ -22,6 +22,10 @@ |
|
|
|
<view class="radio-title">电台陪伴中</view> |
|
|
|
<view class="radio-sub">你们已经安静陪伴了 {{ silentMinutes }} 分钟</view> |
|
|
|
</view> |
|
|
|
<view class="safe-actions"> |
|
|
|
<view class="safe-btn warn" @tap="block">加入黑名单</view> |
|
|
|
<view class="safe-btn" @tap="openReportPanel">举报</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
<view class="timer-card"> |
|
|
|
@ -51,6 +55,16 @@ |
|
|
|
<view class="bubble image-bubble" v-if="item.messageType === 2" :class="{ muted: item.pending, blocked: item.blocked }"> |
|
|
|
<image class="chat-image" :src="item.content" mode="aspectFill" @tap="previewImage(item.content)"></image> |
|
|
|
</view> |
|
|
|
<view class="bubble video-bubble" v-else-if="item.messageType === 5" :class="{ muted: item.pending, blocked: item.blocked }"> |
|
|
|
<view class="video-card" @tap="previewVideo(item.content)"> |
|
|
|
<image class="video-poster" v-if="item.poster" :src="item.poster" mode="aspectFill"></image> |
|
|
|
<view class="video-placeholder" v-else> |
|
|
|
<view class="video-play">▶</view> |
|
|
|
<view class="video-text">点击播放视频</view> |
|
|
|
</view> |
|
|
|
<view class="video-meta" v-if="item.mediaDuration">{{ item.mediaDuration }}s</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
<view class="bubble emoji-bubble" v-else-if="item.messageType === 3" :class="{ muted: item.pending, blocked: item.blocked }">{{ item.content }}</view> |
|
|
|
<view class="bubble voice-bubble" v-else-if="item.messageType === 4" :class="{ muted: item.pending, blocked: item.blocked, playing: playingVoiceUrl === item.content }" |
|
|
|
@tap="playVoice(item)"> |
|
|
|
@ -74,26 +88,42 @@ |
|
|
|
|
|
|
|
<view class="input-bar" :style="{ bottom: inputBarBottom }"> |
|
|
|
<view class="tool-btn" @tap="toggleEmoji">☺</view> |
|
|
|
<view class="tool-btn" @tap="voiceMode = !voiceMode">{{ voiceMode ? '键' : '语' }}</view> |
|
|
|
<input class="input" v-if="!voiceMode" v-model="draft" placeholder="轻轻说一句,或保持安静" |
|
|
|
<view class="tool-btn" @tap="toggleVoiceMode">{{ voiceMode ? '键' : '语' }}</view> |
|
|
|
<input class="input" v-if="!voiceMode" v-model="draft" :disabled="isBlocked" :placeholder="isBlocked ? '已加入黑名单,无法继续发送' : '轻轻说一句,或保持安静'" |
|
|
|
:adjust-position="false" :cursor-spacing="20" @focus="handleInputFocus" @blur="handleInputBlur" /> |
|
|
|
<view class="voice-hold" v-else :class="{ recording: recording, cancelling: voiceCancelling }" |
|
|
|
@touchstart.prevent="startRecord" @touchmove.prevent="moveRecord" @touchend="stopRecord" @touchcancel="cancelRecord"> |
|
|
|
{{ voiceHoldText }} |
|
|
|
</view> |
|
|
|
<view class="tool-btn" @tap="chooseImage">+</view> |
|
|
|
<view class="send" v-if="!voiceMode" @tap="sendMessage">发送</view> |
|
|
|
<view class="tool-btn" @tap="chooseMedia">+</view> |
|
|
|
<view class="send" v-if="!voiceMode" :class="{ disabled: isBlocked }" @tap="sendMessage">发送</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
<view class="safety-mask" v-if="showSafety" @tap="showSafety = false"> |
|
|
|
<view class="safety-panel" @tap.stop> |
|
|
|
<view class="safety-title">安全与退出</view> |
|
|
|
<view class="safety-item" @tap="report">举报不适内容</view> |
|
|
|
<view class="safety-item" @tap="openReportPanel">举报不适内容</view> |
|
|
|
<view class="safety-item" @tap="block">拉黑这个对象</view> |
|
|
|
<view class="safety-item quiet" @tap="finishRoom">提前结束陪伴</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
<view class="safety-mask" v-if="showReportPanel" @tap="showReportPanel = false"> |
|
|
|
<view class="safety-panel" @tap.stop> |
|
|
|
<view class="safety-title">举报这个对象</view> |
|
|
|
<view class="report-reason" v-for="item in reportReasons" :key="item.key" |
|
|
|
:class="{ active: reportForm.reasonType === item.key }" @tap="reportForm.reasonType = item.key"> |
|
|
|
{{ item.label }} |
|
|
|
</view> |
|
|
|
<textarea class="report-textarea" v-model="reportForm.reasonText" maxlength="120" |
|
|
|
placeholder="可以补充说明发生了什么(选填)"></textarea> |
|
|
|
<view class="report-actions"> |
|
|
|
<view class="report-cancel" @tap="showReportPanel = false">取消</view> |
|
|
|
<view class="report-submit" @tap="submitReport">提交举报</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
<view class="profile-mask" v-if="showTargetProfile" @tap="showTargetProfile = false"> |
|
|
|
<view class="profile-panel" @tap.stop> |
|
|
|
<image class="profile-avatar-img" v-if="targetProfile.avatarUrl" :src="targetProfile.avatarUrl" mode="aspectFill"></image> |
|
|
|
@ -141,6 +171,8 @@ |
|
|
|
pollTimer: null, |
|
|
|
draft: '', |
|
|
|
showSafety: false, |
|
|
|
showReportPanel: false, |
|
|
|
isBlocked: false, |
|
|
|
showTargetProfile: false, |
|
|
|
targetProfile: {}, |
|
|
|
showEmoji: false, |
|
|
|
@ -169,6 +201,16 @@ |
|
|
|
sendingMessage: false, |
|
|
|
emojis: ['🙂', '😄', '🥲', '😭', '😴', '🙌', '🌙', '☁️', '🍃', '✨', '💛', '🫶'], |
|
|
|
presenceActions: ['我在', '听着呢', '慢慢说', '抱一下空气'], |
|
|
|
reportReasons: [ |
|
|
|
{ key: 'harassment', label: '骚扰不适' }, |
|
|
|
{ key: 'abuse', label: '攻击辱骂' }, |
|
|
|
{ key: 'fraud', label: '疑似欺诈' }, |
|
|
|
{ key: 'other', label: '其他问题' } |
|
|
|
], |
|
|
|
reportForm: { |
|
|
|
reasonType: 'other', |
|
|
|
reasonText: '' |
|
|
|
}, |
|
|
|
silentModes: ['一起听歌', '一起倒计时', '一起自习', '一起失眠'], |
|
|
|
companions: { |
|
|
|
i: { |
|
|
|
@ -314,6 +356,7 @@ |
|
|
|
mediaDuration: item.mediaDuration, |
|
|
|
mediaSize: item.mediaSize, |
|
|
|
mediaFormat: item.mediaFormat, |
|
|
|
poster: item.poster || item.thumbTempFilePath || '', |
|
|
|
mine, |
|
|
|
pending: false, |
|
|
|
blocked: item.isBlocked === 1 |
|
|
|
@ -400,9 +443,20 @@ |
|
|
|
this.sendChatContent(text, 1) |
|
|
|
}, |
|
|
|
toggleEmoji() { |
|
|
|
if (this.isBlocked) { |
|
|
|
uni.showToast({ title: '已加入黑名单,无法继续发送', icon: 'none' }) |
|
|
|
return |
|
|
|
} |
|
|
|
this.voiceMode = false |
|
|
|
this.showEmoji = !this.showEmoji |
|
|
|
}, |
|
|
|
toggleVoiceMode() { |
|
|
|
if (this.isBlocked) { |
|
|
|
uni.showToast({ title: '已加入黑名单,无法继续发送', icon: 'none' }) |
|
|
|
return |
|
|
|
} |
|
|
|
this.voiceMode = !this.voiceMode |
|
|
|
}, |
|
|
|
sendEmoji(emoji) { |
|
|
|
this.showEmoji = false |
|
|
|
this.sendChatContent(emoji, 3) |
|
|
|
@ -425,6 +479,10 @@ |
|
|
|
uni.showToast({ title: '房间信息缺失', icon: 'none' }) |
|
|
|
return |
|
|
|
} |
|
|
|
if (this.isBlocked) { |
|
|
|
uni.showToast({ title: '已加入黑名单,无法继续发送', icon: 'none' }) |
|
|
|
return |
|
|
|
} |
|
|
|
if (this.needWaitReply()) { |
|
|
|
uni.showToast({ title: '先等等对方回复吧,首次破冰最多发送 3 条', icon: 'none' }) |
|
|
|
return |
|
|
|
@ -438,6 +496,7 @@ |
|
|
|
mediaDuration: media.mediaDuration, |
|
|
|
mediaSize: media.mediaSize, |
|
|
|
mediaFormat: media.mediaFormat, |
|
|
|
poster: media.poster || '', |
|
|
|
mine: true, |
|
|
|
pending: true |
|
|
|
}) |
|
|
|
@ -471,11 +530,13 @@ |
|
|
|
msg.content = '发送失败,请稍后再试' |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
const message = (e && e.message) || '' |
|
|
|
if (message.indexOf('黑名单') >= 0) this.isBlocked = true |
|
|
|
const msg = this.messages.find(item => item.clientMsgId === payload.clientMsgId) |
|
|
|
if (msg) { |
|
|
|
msg.pending = false |
|
|
|
msg.blocked = true |
|
|
|
msg.content = (e && e.message) || '发送失败,请稍后再试' |
|
|
|
msg.content = message || '发送失败,请稍后再试' |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
@ -491,8 +552,33 @@ |
|
|
|
msg.blocked = ack.isBlocked === 1 |
|
|
|
msg.content = msg.blocked ? '这句话没有送出,请换一种更温柔的说法。' : (ack.content || msg.content) |
|
|
|
}, |
|
|
|
chooseImage() { |
|
|
|
chooseMedia() { |
|
|
|
if (this.isBlocked) { |
|
|
|
uni.showToast({ title: '已加入黑名单,无法继续发送', icon: 'none' }) |
|
|
|
return |
|
|
|
} |
|
|
|
if (this.uploadingImage) return |
|
|
|
if (!uni.chooseMedia) { |
|
|
|
this.chooseImageCompat() |
|
|
|
return |
|
|
|
} |
|
|
|
uni.chooseMedia({ |
|
|
|
count: 1, |
|
|
|
mediaType: ['image', 'video'], |
|
|
|
sourceType: ['album', 'camera'], |
|
|
|
camera: 'back', |
|
|
|
success: async (res) => { |
|
|
|
const file = res.tempFiles && res.tempFiles[0] |
|
|
|
if (!file || !file.tempFilePath) return |
|
|
|
if (res.type === 'video' || file.fileType === 'video') { |
|
|
|
await this.uploadAndSendVideo(file) |
|
|
|
return |
|
|
|
} |
|
|
|
await this.uploadAndSendImage(file.tempFilePath, file.size) |
|
|
|
} |
|
|
|
}) |
|
|
|
}, |
|
|
|
chooseImageCompat() { |
|
|
|
uni.chooseImage({ |
|
|
|
count: 1, |
|
|
|
sizeType: ['compressed'], |
|
|
|
@ -504,7 +590,7 @@ |
|
|
|
} |
|
|
|
}) |
|
|
|
}, |
|
|
|
async uploadAndSendImage(filePath) { |
|
|
|
async uploadAndSendImage(filePath, fileSize) { |
|
|
|
this.uploadingImage = true |
|
|
|
try { |
|
|
|
const result = await tui.uploadFile('/upload/file', filePath) |
|
|
|
@ -514,7 +600,30 @@ |
|
|
|
return |
|
|
|
} |
|
|
|
this.showEmoji = false |
|
|
|
this.sendChatContent(imageUrl, 2) |
|
|
|
this.sendChatContent(imageUrl, 2, { |
|
|
|
mediaSize: fileSize, |
|
|
|
mediaFormat: 'image' |
|
|
|
}) |
|
|
|
} finally { |
|
|
|
this.uploadingImage = false |
|
|
|
} |
|
|
|
}, |
|
|
|
async uploadAndSendVideo(file) { |
|
|
|
this.uploadingImage = true |
|
|
|
try { |
|
|
|
const result = await tui.uploadFile('/upload/file', file.tempFilePath) |
|
|
|
const videoUrl = typeof result === 'string' ? result : (result.url || result.fileUrl || result.path || result.fullPath || result) |
|
|
|
if (!videoUrl || typeof videoUrl !== 'string') { |
|
|
|
uni.showToast({ title: '视频上传失败', icon: 'none' }) |
|
|
|
return |
|
|
|
} |
|
|
|
this.showEmoji = false |
|
|
|
this.sendChatContent(videoUrl, 5, { |
|
|
|
mediaDuration: file.duration ? Math.round(file.duration) : undefined, |
|
|
|
mediaSize: file.size, |
|
|
|
mediaFormat: 'video', |
|
|
|
poster: file.thumbTempFilePath || '' |
|
|
|
}) |
|
|
|
} finally { |
|
|
|
this.uploadingImage = false |
|
|
|
} |
|
|
|
@ -523,6 +632,12 @@ |
|
|
|
if (!url) return |
|
|
|
uni.previewImage({ urls: [url], current: url }) |
|
|
|
}, |
|
|
|
previewVideo(url) { |
|
|
|
if (!url) return |
|
|
|
uni.navigateTo({ |
|
|
|
url: '/package1/ieBrowser/videoPreview?url=' + encodeURIComponent(url) |
|
|
|
}) |
|
|
|
}, |
|
|
|
initRecorder() { |
|
|
|
if (!uni.getRecorderManager) return |
|
|
|
this.recorderManager = uni.getRecorderManager() |
|
|
|
@ -564,6 +679,10 @@ |
|
|
|
return touch ? touch.clientY || touch.pageY || 0 : 0 |
|
|
|
}, |
|
|
|
startRecord(event) { |
|
|
|
if (this.isBlocked) { |
|
|
|
uni.showToast({ title: '已加入黑名单,无法继续发送', icon: 'none' }) |
|
|
|
return |
|
|
|
} |
|
|
|
if (!this.recorderManager || this.uploadingVoice) return |
|
|
|
this.showEmoji = false |
|
|
|
this.recording = true |
|
|
|
@ -681,15 +800,45 @@ |
|
|
|
prompt: profile.intro || this.companion.prompt |
|
|
|
} |
|
|
|
}, |
|
|
|
async report() { |
|
|
|
openReportPanel() { |
|
|
|
this.showSafety = false |
|
|
|
await reportIeRoom(this.roomId, { reportedUserId: this.targetUserId, reasonType: 'other', reasonText: '聊天内容不适' }) |
|
|
|
this.reportForm.reasonType = 'other' |
|
|
|
this.reportForm.reasonText = '' |
|
|
|
this.showReportPanel = true |
|
|
|
}, |
|
|
|
async submitReport() { |
|
|
|
if (!this.roomId || !this.targetUserId) { |
|
|
|
uni.showToast({ title: '举报对象缺失', icon: 'none' }) |
|
|
|
return |
|
|
|
} |
|
|
|
await reportIeRoom(this.roomId, { |
|
|
|
reportedUserId: this.targetUserId, |
|
|
|
reasonType: this.reportForm.reasonType, |
|
|
|
reasonText: this.reportForm.reasonText || '聊天内容不适' |
|
|
|
}) |
|
|
|
this.showReportPanel = false |
|
|
|
uni.showToast({ title: '已收到举报', icon: 'none' }) |
|
|
|
}, |
|
|
|
async block() { |
|
|
|
this.showSafety = false |
|
|
|
if (this.targetUserId) await blockIeUser(this.targetUserId, '聊天中拉黑') |
|
|
|
uni.showToast({ title: '已拉黑', icon: 'none' }) |
|
|
|
if (!this.targetUserId) { |
|
|
|
uni.showToast({ title: '拉黑对象缺失', icon: 'none' }) |
|
|
|
return |
|
|
|
} |
|
|
|
uni.showModal({ |
|
|
|
title: '加入黑名单?', |
|
|
|
content: '加入后你们不能继续互相发送消息,也不会再次随机匹配到对方。', |
|
|
|
confirmText: '加入', |
|
|
|
confirmColor: '#e85d75', |
|
|
|
success: async (res) => { |
|
|
|
if (!res.confirm) return |
|
|
|
await blockIeUser(this.targetUserId, '聊天中拉黑') |
|
|
|
this.isBlocked = true |
|
|
|
this.showEmoji = false |
|
|
|
this.voiceMode = false |
|
|
|
uni.showToast({ title: '已加入黑名单', icon: 'none' }) |
|
|
|
} |
|
|
|
}) |
|
|
|
}, |
|
|
|
async finishRoom() { |
|
|
|
if (this.finishing) return |
|
|
|
@ -925,6 +1074,8 @@ |
|
|
|
.radio-copy { |
|
|
|
position: relative; |
|
|
|
z-index: 1; |
|
|
|
flex: 1; |
|
|
|
min-width: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.radio-title { |
|
|
|
@ -938,6 +1089,31 @@ |
|
|
|
font-size: 22rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.safe-actions { |
|
|
|
position: relative; |
|
|
|
z-index: 1; |
|
|
|
display: flex; |
|
|
|
gap: 12rpx; |
|
|
|
margin-left: 18rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.safe-btn { |
|
|
|
height: 52rpx; |
|
|
|
line-height: 52rpx; |
|
|
|
padding: 0 18rpx; |
|
|
|
border-radius: 999rpx; |
|
|
|
color: #6c69d8; |
|
|
|
background: rgba(139,124,255,.1); |
|
|
|
font-size: 21rpx; |
|
|
|
font-weight: 800; |
|
|
|
white-space: nowrap; |
|
|
|
} |
|
|
|
|
|
|
|
.safe-btn.warn { |
|
|
|
color: #e85d75; |
|
|
|
background: rgba(232,93,117,.1); |
|
|
|
} |
|
|
|
|
|
|
|
.silent-modes { |
|
|
|
position: relative; |
|
|
|
z-index: 1; |
|
|
|
@ -1098,6 +1274,13 @@ |
|
|
|
background: rgba(255, 255, 255, 0.72); |
|
|
|
} |
|
|
|
|
|
|
|
.video-bubble { |
|
|
|
padding: 0; |
|
|
|
border: none; |
|
|
|
background: transparent; |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
|
|
|
|
.chat-image { |
|
|
|
display: block; |
|
|
|
width: 260rpx; |
|
|
|
@ -1106,6 +1289,71 @@ |
|
|
|
background: rgba(22, 27, 46, 0.06); |
|
|
|
} |
|
|
|
|
|
|
|
.video-card { |
|
|
|
position: relative; |
|
|
|
width: 300rpx; |
|
|
|
height: 390rpx; |
|
|
|
border-radius: 28rpx; |
|
|
|
overflow: hidden; |
|
|
|
background: |
|
|
|
radial-gradient(circle at 22% 16%, rgba(169,255,231,.36), transparent 130rpx), |
|
|
|
radial-gradient(circle at 78% 80%, rgba(139,124,255,.32), transparent 150rpx), |
|
|
|
linear-gradient(145deg, #151a2d, #2c3350); |
|
|
|
box-shadow: 0 18rpx 44rpx rgba(96,112,160,.18); |
|
|
|
} |
|
|
|
|
|
|
|
.mine .video-card { |
|
|
|
box-shadow: 0 18rpx 44rpx rgba(169,255,231,.24); |
|
|
|
} |
|
|
|
|
|
|
|
.video-poster, |
|
|
|
.video-placeholder { |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
} |
|
|
|
|
|
|
|
.video-placeholder { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
color: rgba(255,255,255,.92); |
|
|
|
} |
|
|
|
|
|
|
|
.video-play { |
|
|
|
width: 86rpx; |
|
|
|
height: 86rpx; |
|
|
|
border-radius: 50%; |
|
|
|
text-align: center; |
|
|
|
line-height: 86rpx; |
|
|
|
color: #151a2d; |
|
|
|
background: rgba(255,255,255,.9); |
|
|
|
box-shadow: 0 12rpx 32rpx rgba(0,0,0,.18); |
|
|
|
font-size: 34rpx; |
|
|
|
font-weight: 900; |
|
|
|
} |
|
|
|
|
|
|
|
.video-text { |
|
|
|
margin-top: 18rpx; |
|
|
|
color: rgba(255,255,255,.78); |
|
|
|
font-size: 24rpx; |
|
|
|
font-weight: 700; |
|
|
|
} |
|
|
|
|
|
|
|
.video-meta { |
|
|
|
position: absolute; |
|
|
|
right: 16rpx; |
|
|
|
bottom: 16rpx; |
|
|
|
height: 42rpx; |
|
|
|
line-height: 42rpx; |
|
|
|
padding: 0 16rpx; |
|
|
|
border-radius: 999rpx; |
|
|
|
color: #fff; |
|
|
|
background: rgba(0,0,0,.38); |
|
|
|
font-size: 21rpx; |
|
|
|
font-weight: 800; |
|
|
|
} |
|
|
|
|
|
|
|
.emoji-bubble { |
|
|
|
min-width: 72rpx; |
|
|
|
text-align: center; |
|
|
|
@ -1254,6 +1502,10 @@ |
|
|
|
font-weight: 800; |
|
|
|
} |
|
|
|
|
|
|
|
.send.disabled { |
|
|
|
opacity: .46; |
|
|
|
} |
|
|
|
|
|
|
|
.safety-mask { |
|
|
|
position: fixed; |
|
|
|
left: 0; |
|
|
|
@ -1295,6 +1547,62 @@ |
|
|
|
color: #6c69d8; |
|
|
|
} |
|
|
|
|
|
|
|
.report-reason { |
|
|
|
height: 76rpx; |
|
|
|
line-height: 76rpx; |
|
|
|
margin-top: 12rpx; |
|
|
|
padding: 0 24rpx; |
|
|
|
border-radius: 24rpx; |
|
|
|
color: rgba(22,27,46,.66); |
|
|
|
background: rgba(238,244,255,.72); |
|
|
|
font-size: 25rpx; |
|
|
|
font-weight: 800; |
|
|
|
} |
|
|
|
|
|
|
|
.report-reason.active { |
|
|
|
color: #151a2d; |
|
|
|
background: #a9ffe7; |
|
|
|
} |
|
|
|
|
|
|
|
.report-textarea { |
|
|
|
width: 100%; |
|
|
|
height: 150rpx; |
|
|
|
margin-top: 18rpx; |
|
|
|
padding: 22rpx; |
|
|
|
border-radius: 24rpx; |
|
|
|
box-sizing: border-box; |
|
|
|
color: #151a2d; |
|
|
|
background: rgba(238,244,255,.72); |
|
|
|
font-size: 24rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.report-actions { |
|
|
|
display: flex; |
|
|
|
gap: 16rpx; |
|
|
|
margin-top: 20rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.report-cancel, |
|
|
|
.report-submit { |
|
|
|
flex: 1; |
|
|
|
height: 78rpx; |
|
|
|
line-height: 78rpx; |
|
|
|
border-radius: 999rpx; |
|
|
|
text-align: center; |
|
|
|
font-size: 25rpx; |
|
|
|
font-weight: 800; |
|
|
|
} |
|
|
|
|
|
|
|
.report-cancel { |
|
|
|
color: rgba(22,27,46,.62); |
|
|
|
background: rgba(22,27,46,.06); |
|
|
|
} |
|
|
|
|
|
|
|
.report-submit { |
|
|
|
color: #11162a; |
|
|
|
background: #a9ffe7; |
|
|
|
} |
|
|
|
|
|
|
|
.profile-mask { |
|
|
|
position: fixed; |
|
|
|
left: 0; |
|
|
|
|