forked from 77media/video-flow
调用接口
This commit is contained in:
parent
0269ec3038
commit
2b4208fb41
211
api/common.ts
Normal file
211
api/common.ts
Normal file
@ -0,0 +1,211 @@
|
||||
// Common API 相关接口
|
||||
import { BASE_URL } from './constants'
|
||||
|
||||
export interface ApiResponse<T = any> {
|
||||
code: number
|
||||
successful: boolean
|
||||
msg: string
|
||||
data: T
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
code: number
|
||||
message: string
|
||||
data: {
|
||||
token: string
|
||||
}
|
||||
successfull: boolean
|
||||
}
|
||||
|
||||
export interface TokenWithDomainResponse {
|
||||
code: number
|
||||
message: string
|
||||
data: {
|
||||
token: string
|
||||
domain: string
|
||||
}
|
||||
successfull: boolean
|
||||
}
|
||||
|
||||
export interface QiniuUploadResponse {
|
||||
hash: string
|
||||
key: string
|
||||
url?: string
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 获取七牛云上传token
|
||||
export const getUploadToken = async (timeoutMs: number = 10000): Promise<string> => {
|
||||
// 添加超时控制
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => {
|
||||
console.log(`请求超时(${timeoutMs / 1000}秒),正在中断请求...`)
|
||||
controller.abort()
|
||||
}, timeoutMs)
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/common/get-upload-token`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
signal: controller.signal,
|
||||
mode: "cors",
|
||||
})
|
||||
|
||||
// 请求完成后清除超时
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
console.error("获取token响应错误:", response.status, errorText)
|
||||
throw new Error(`获取token失败: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const result: TokenResponse = await response.json()
|
||||
console.log("获取token响应:", result)
|
||||
|
||||
if (result.code === 0 && result.successfull && result.data.token) {
|
||||
console.log("成功获取token")
|
||||
return result.data.token
|
||||
} else {
|
||||
throw new Error(result.message || "获取token失败,服务器未返回有效token")
|
||||
}
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId)
|
||||
console.error("获取上传token失败:", error)
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.name === "AbortError") {
|
||||
throw new Error("请求超时,请检查网络连接")
|
||||
} else if (error.message.includes("Failed to fetch")) {
|
||||
throw new Error("网络连接失败,可能是CORS策略限制或服务器不可达")
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 获取七牛云上传token(包含domain)
|
||||
export const getUploadTokenWithDomain = async (timeoutMs: number = 10000): Promise<{ token: string, domain: string }> => {
|
||||
// 添加超时控制
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => {
|
||||
console.log(`请求超时(${timeoutMs / 1000}秒),正在中断请求...`)
|
||||
controller.abort()
|
||||
}, timeoutMs)
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/common/get-upload-token`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
signal: controller.signal,
|
||||
mode: "cors",
|
||||
})
|
||||
|
||||
// 请求完成后清除超时
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
console.error("获取token响应错误:", response.status, errorText)
|
||||
throw new Error(`获取token失败: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const result: TokenWithDomainResponse | TokenResponse = await response.json()
|
||||
console.log("获取token响应:", result)
|
||||
|
||||
if (result.code === 0 && result.successfull && result.data.token) {
|
||||
console.log("成功获取token")
|
||||
// Support both old and new API response formats
|
||||
const domain = 'domain' in result.data ? result.data.domain : 'cdn.qikongjian.com'
|
||||
return {
|
||||
token: result.data.token,
|
||||
domain: domain
|
||||
}
|
||||
} else {
|
||||
throw new Error(result.message || "获取token失败,服务器未返回有效token")
|
||||
}
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId)
|
||||
console.error("获取上传token失败:", error)
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.name === "AbortError") {
|
||||
throw new Error("请求超时,请检查网络连接")
|
||||
} else if (error.message.includes("Failed to fetch")) {
|
||||
throw new Error("网络连接失败,可能是CORS策略限制或服务器不可达")
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 生成唯一文件名
|
||||
export const generateUniqueFileName = (originalName: string): string => {
|
||||
const timestamp = Date.now()
|
||||
const randomStr = Math.random().toString(36).substring(2, 8)
|
||||
const extension = originalName.split(".").pop()
|
||||
return `videos/${timestamp}_${randomStr}.${extension}`
|
||||
}
|
||||
|
||||
// 七牛云上传
|
||||
export const uploadToQiniu = async (
|
||||
file: File,
|
||||
token: string,
|
||||
onProgress?: (progress: number) => void
|
||||
): Promise<string> => {
|
||||
const uniqueFileName = generateUniqueFileName(file.name)
|
||||
|
||||
const formData = new FormData()
|
||||
formData.append("token", token)
|
||||
formData.append("key", uniqueFileName)
|
||||
formData.append("file", file)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
|
||||
xhr.upload.addEventListener("progress", (event) => {
|
||||
if (event.lengthComputable && onProgress) {
|
||||
const progress = Math.round((event.loaded / event.total) * 100)
|
||||
onProgress(progress)
|
||||
}
|
||||
})
|
||||
|
||||
xhr.addEventListener("load", () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try {
|
||||
const response: QiniuUploadResponse = JSON.parse(xhr.responseText)
|
||||
const qiniuUrl = `https://cdn.qikongjian.com/${response.key || uniqueFileName}`
|
||||
console.log("七牛云上传成功:", response)
|
||||
resolve(qiniuUrl)
|
||||
} catch (error) {
|
||||
console.error("解析响应失败:", error, "原始响应:", xhr.responseText)
|
||||
reject(new Error(`解析上传响应失败: ${xhr.responseText}`))
|
||||
}
|
||||
} else {
|
||||
console.error("七牛云上传失败:", xhr.status, xhr.statusText, xhr.responseText)
|
||||
reject(new Error(`上传失败: ${xhr.status} ${xhr.statusText}`))
|
||||
}
|
||||
})
|
||||
|
||||
xhr.addEventListener("error", (e) => {
|
||||
console.error("上传网络错误:", e)
|
||||
reject(new Error("网络错误,上传失败"))
|
||||
})
|
||||
|
||||
xhr.addEventListener("abort", () => {
|
||||
reject(new Error("上传被取消"))
|
||||
})
|
||||
|
||||
xhr.open("POST", "https://up-z2.qiniup.com")
|
||||
xhr.send(formData)
|
||||
})
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import { post } from './request';
|
||||
import { ApiResponse } from './common';
|
||||
|
||||
// 创建剧集的数据类型
|
||||
export interface CreateScriptEpisodeRequest {
|
||||
@ -33,15 +34,7 @@ export interface ScriptEpisode {
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// 创建剧集响应格式
|
||||
export interface EpisodeApiResponse<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data: T;
|
||||
successfull: boolean;
|
||||
}
|
||||
|
||||
// 创建剧集
|
||||
export const createScriptEpisode = async (data: CreateScriptEpisodeRequest): Promise<EpisodeApiResponse<ScriptEpisode>> => {
|
||||
return post<EpisodeApiResponse<ScriptEpisode>>('/script_episode/create', data);
|
||||
export const createScriptEpisode = async (data: CreateScriptEpisodeRequest): Promise<ApiResponse<ScriptEpisode>> => {
|
||||
return post<ApiResponse<ScriptEpisode>>('/script_episode/create', data);
|
||||
};
|
||||
@ -1,4 +1,5 @@
|
||||
import { post } from './request';
|
||||
import { ApiResponse } from './common';
|
||||
|
||||
// 创建剧本项目的数据类型
|
||||
export interface CreateScriptProjectRequest {
|
||||
@ -17,13 +18,6 @@ export interface CreateScriptProjectRequest {
|
||||
resolution?: number;
|
||||
}
|
||||
|
||||
// API响应类型
|
||||
export interface ApiResponse<T> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
// 创建剧本项目响应数据类型
|
||||
export interface ScriptProject {
|
||||
id: number;
|
||||
|
||||
69
api/video_flow.ts
Normal file
69
api/video_flow.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { post } from './request';
|
||||
import { ProjectTypeEnum } from './enums';
|
||||
import { ApiResponse } from './common';
|
||||
|
||||
// 剧本转分镜头请求接口
|
||||
export interface ScriptToSceneRequest {
|
||||
script: string;
|
||||
project_type: ProjectTypeEnum.SCRIPT_TO_VIDEO;
|
||||
}
|
||||
|
||||
// 视频转分镜头请求接口
|
||||
export interface VideoToSceneRequest {
|
||||
video_url: string;
|
||||
project_type: ProjectTypeEnum.VIDEO_TO_VIDEO;
|
||||
}
|
||||
|
||||
// 转换分镜头请求类型
|
||||
export type ConvertScenePromptRequest = ScriptToSceneRequest | VideoToSceneRequest;
|
||||
|
||||
// 转换分镜头响应数据接口
|
||||
export interface ConvertScenePromptData {
|
||||
task_id: string;
|
||||
status: string;
|
||||
shots_count?: number; // 剧本模式返回的分镜头数量
|
||||
video_url?: string; // 视频模式返回的视频链接
|
||||
estimated_time: number; // 预估处理时间(秒)
|
||||
}
|
||||
|
||||
// 转换分镜头响应接口
|
||||
export type ConvertScenePromptResponse = ApiResponse<ConvertScenePromptData>;
|
||||
|
||||
/**
|
||||
* 将剧本或视频转换为分镜头提示词
|
||||
* @param request - 请求参数,根据 project_type 自动判断是剧本还是视频模式
|
||||
* @returns Promise<ConvertScenePromptResponse>
|
||||
*/
|
||||
export const convertScenePrompt = async (
|
||||
request: ConvertScenePromptRequest
|
||||
): Promise<ConvertScenePromptResponse> => {
|
||||
return post<ConvertScenePromptResponse>('/video_flow/convert-scene-prompt', request);
|
||||
};
|
||||
|
||||
/**
|
||||
* 剧本转分镜头提示词
|
||||
* @param script - 剧本内容
|
||||
* @returns Promise<ConvertScenePromptResponse>
|
||||
*/
|
||||
export const convertScriptToScene = async (
|
||||
script: string
|
||||
): Promise<ConvertScenePromptResponse> => {
|
||||
return convertScenePrompt({
|
||||
script,
|
||||
project_type: ProjectTypeEnum.SCRIPT_TO_VIDEO
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 视频转分镜头提示词
|
||||
* @param video_url - 视频链接
|
||||
* @returns Promise<ConvertScenePromptResponse>
|
||||
*/
|
||||
export const convertVideoToScene = async (
|
||||
video_url: string
|
||||
): Promise<ConvertScenePromptResponse> => {
|
||||
return convertScenePrompt({
|
||||
video_url,
|
||||
project_type: ProjectTypeEnum.VIDEO_TO_VIDEO
|
||||
});
|
||||
};
|
||||
@ -1,6 +1,5 @@
|
||||
import { DashboardLayout } from '@/components/layout/dashboard-layout';
|
||||
// import { HomePage } from '@/components/pages/home-page';
|
||||
import { HomePage2 } from '@/components/pages/homepage';
|
||||
import { HomePage2 } from '@/components/pages/home-page2';
|
||||
import OAuthCallbackHandler from '@/components/ui/oauth-callback-handler';
|
||||
|
||||
export default function Home() {
|
||||
|
||||
@ -8,7 +8,7 @@ import { useRouter } from 'next/navigation';
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, SheetClose } from "@/components/ui/sheet";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import './style/create-to-video.css';
|
||||
import './style/create-to-video2.css';
|
||||
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import LiquidGlass from '@/plugins/liquid-glass/index'
|
||||
@ -19,11 +19,15 @@ import dynamic from 'next/dynamic';
|
||||
import { ProjectTypeEnum, ModeEnum, ResolutionEnum } from "@/api/enums";
|
||||
import { createScriptProject, CreateScriptProjectRequest } from "@/api/script_project";
|
||||
import { createScriptEpisode, CreateScriptEpisodeRequest } from "@/api/script_episode";
|
||||
import { getUploadTokenWithDomain, uploadToQiniu } from "@/api/common";
|
||||
|
||||
const JoyrideNoSSR = dynamic(() => import('react-joyride'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// 导入Step类型
|
||||
import type { Step } from 'react-joyride';
|
||||
|
||||
// 添加自定义滚动条样式
|
||||
const scrollbarStyles = `
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
@ -55,6 +59,7 @@ export function CreateToVideo2() {
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [videoUrl, setVideoUrl] = useState('');
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [activeTab, setActiveTab] = useState('script');
|
||||
const [isFocus, setIsFocus] = useState(false);
|
||||
@ -64,16 +69,33 @@ export function CreateToVideo2() {
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
const [runTour, setRunTour] = useState(true);
|
||||
|
||||
const handleUploadVideo = () => {
|
||||
const handleUploadVideo = async () => {
|
||||
console.log('upload video');
|
||||
// 打开文件选择器
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'video/*';
|
||||
input.onchange = (e) => {
|
||||
input.onchange = async (e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (file) {
|
||||
setVideoUrl(URL.createObjectURL(file));
|
||||
try {
|
||||
setIsUploading(true);
|
||||
|
||||
// 获取上传token
|
||||
const { token } = await getUploadTokenWithDomain();
|
||||
|
||||
// 上传到七牛云
|
||||
const videoUrl = await uploadToQiniu(file, token);
|
||||
|
||||
// 上传成功,设置视频URL
|
||||
setVideoUrl(videoUrl);
|
||||
console.log('视频上传成功:', videoUrl);
|
||||
} catch (error) {
|
||||
console.error('上传错误:', error);
|
||||
alert('上传失败,请稍后重试');
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
input.click();
|
||||
@ -111,7 +133,7 @@ export function CreateToVideo2() {
|
||||
alert(`创建剧集失败: ${episodeResponse.msg}`);
|
||||
}
|
||||
} else {
|
||||
alert(`创建项目失败: ${projectResponse.message}`);
|
||||
alert(`创建项目失败: ${projectResponse.msg}`);
|
||||
}
|
||||
} catch (error) {
|
||||
alert("创建项目时发生错误,请稍后重试");
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import { Plus, Table, AlignHorizontalSpaceAround, Loader2 } from "lucide-react";
|
||||
import "./style/home-page.css";
|
||||
import "./style/home-page2.css";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { VideoScreenLayout } from '@/components/video-screen-layout';
|
||||
import { VideoGridLayout } from '@/components/video-grid-layout';
|
||||
132
components/pages/style/create-to-video2.css
Normal file
132
components/pages/style/create-to-video2.css
Normal file
@ -0,0 +1,132 @@
|
||||
.video-tool-component {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
bottom: 1rem;
|
||||
--tw-translate-x: calc(-50% + 34.5px);
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.video-storyboard-tools {
|
||||
border: 1px solid rgba(255, 255, 255, .2);
|
||||
box-shadow: 0 4px 20px #0009;
|
||||
}
|
||||
|
||||
.video-storyboard-tools .tool-submit-button {
|
||||
display: flex;
|
||||
height: 36px;
|
||||
width: 120px;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
border-radius: 10px;
|
||||
font-size: .875rem;
|
||||
line-height: 1.25rem;
|
||||
font-weight: 600;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(29 33 41 / var(--tw-text-opacity));
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.video-storyboard-tools .tool-submit-button.disabled {
|
||||
background-color: #fff;
|
||||
opacity: .3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.storyboard-tools-tab {
|
||||
border-radius: 16px 16px 0 0;
|
||||
background: #ffffff0d;
|
||||
}
|
||||
|
||||
.storyboard-tools-tab .tab-item {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.storyboard-tools-tab .tab-item.active,
|
||||
.storyboard-tools-tab .tab-item.active span {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.storyboard-tools-tab .tab-item.active:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
left: 50%;
|
||||
width: 30px;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
background-color: #fff;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
.video-prompt-editor .editor-content {
|
||||
line-height: 26px;
|
||||
outline: none;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.video-prompt-editor .editor-content[contenteditable] {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.video-prompt-editor.focus {
|
||||
background-color: #ffffff0d;
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.tool-scroll-box-content {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
.tool-operation-button {
|
||||
display: flex;
|
||||
height: 36px;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
border-radius: 20px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
font-size: .875rem;
|
||||
line-height: 1.25rem;
|
||||
background-color: #ffffff0d;
|
||||
}
|
||||
|
||||
.tool-operation-button:hover {
|
||||
background-color: #ffffff1a;
|
||||
}
|
||||
|
||||
/* 下拉菜单样式 */
|
||||
.mode-dropdown.ant-dropdown .ant-dropdown-menu {
|
||||
background: rgba(25, 27, 30, 0.95);
|
||||
backdrop-filter: blur(15px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 8px;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
.mode-dropdown.ant-dropdown .ant-dropdown-menu-item {
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.mode-dropdown.ant-dropdown .ant-dropdown-menu-item:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.mode-dropdown.ant-dropdown .ant-dropdown-menu-item-selected {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
@ -1,55 +1,55 @@
|
||||
|
||||
.header {
|
||||
pointer-events: none;
|
||||
transition: color .2s;
|
||||
}
|
||||
@keyframes btnAnimation2 {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
pointer-events: none;
|
||||
transition: color .2s;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(0, 100%);
|
||||
@keyframes btnAnimation2 {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(0, 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (pointer: fine) {
|
||||
.roll.on .translate {
|
||||
animation-name: btnAnimation2;
|
||||
animation-play-state: running;
|
||||
animation-iteration-count: 1;
|
||||
animation-duration: .4s;
|
||||
animation-timing-function: cubic-bezier(.16, .03, .08, 1.55);
|
||||
|
||||
@media (pointer: fine) {
|
||||
.roll.on .translate {
|
||||
animation-name: btnAnimation2;
|
||||
animation-play-state: running;
|
||||
animation-iteration-count: 1;
|
||||
animation-duration: .4s;
|
||||
animation-timing-function: cubic-bezier(.16, .03, .08, 1.55);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.roll .translate>span:last-child {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.header-wrapper .logo,
|
||||
.roll2>span .logo,
|
||||
.roll3>span .logo {
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.roll .translate {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header-wrapper,
|
||||
.header .link-logo {
|
||||
pointer-events: initial;
|
||||
overflow: hidden;
|
||||
}
|
||||
.header button {
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
.roll .translate>span:last-child {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.header-wrapper .logo,
|
||||
.roll2>span .logo,
|
||||
.roll3>span .logo {
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.roll .translate {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header-wrapper,
|
||||
.header .link-logo {
|
||||
pointer-events: initial;
|
||||
overflow: hidden;
|
||||
}
|
||||
.header button {
|
||||
pointer-events: initial;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user