Init
This commit is contained in:
BIN
assets/test_character.glb
Normal file
BIN
assets/test_character.glb
Normal file
Binary file not shown.
38
index.html
38
index.html
@@ -11,7 +11,11 @@
|
||||
|
||||
<!-- Debug UI -->
|
||||
<div id="debug-ui">
|
||||
<div class="debug-header">
|
||||
<h3>Debug Controls</h3>
|
||||
<button id="minimize-btn">−</button>
|
||||
</div>
|
||||
<div id="debug-content">
|
||||
<div class="debug-item">
|
||||
<label>
|
||||
<input type="checkbox" id="orbit-controls-toggle">
|
||||
@@ -25,14 +29,35 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="debug-item">
|
||||
<label for="file-input" class="file-label">Load GLB Model:</label>
|
||||
<input type="file" id="file-input" accept=".glb,.gltf">
|
||||
<label>
|
||||
<input type="checkbox" id="show-skeleton-toggle">
|
||||
Show Skeleton
|
||||
</label>
|
||||
</div>
|
||||
<div class="debug-item">
|
||||
<label for="animation-select" class="asset-label">Select Animation:</label>
|
||||
<select id="animation-select">
|
||||
<option value="">-- No Animation --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="debug-item">
|
||||
<label>
|
||||
<input type="checkbox" id="play-animation-toggle">
|
||||
Play Animation
|
||||
</label>
|
||||
</div>
|
||||
<div class="debug-item">
|
||||
<label for="asset-select" class="asset-label">Select Model:</label>
|
||||
<select id="asset-select">
|
||||
<option value="">-- No Model --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="debug-item">
|
||||
<span class="debug-label">Model Status:</span>
|
||||
<span id="model-status">No model loaded</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Three.js CDN -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
@@ -43,6 +68,15 @@
|
||||
<!-- GLTFLoader -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js"></script>
|
||||
|
||||
<!-- Manager Classes -->
|
||||
<script src="src/sceneManager.js"></script>
|
||||
<script src="src/cameraManager.js"></script>
|
||||
<script src="src/rendererManager.js"></script>
|
||||
<script src="src/controlsManager.js"></script>
|
||||
|
||||
<!-- Debug UI Controller -->
|
||||
<script src="src/debugUI.js"></script>
|
||||
|
||||
<!-- Your main script -->
|
||||
<script src="src/main.js"></script>
|
||||
</body>
|
||||
|
||||
26
src/cameraManager.js
Normal file
26
src/cameraManager.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// Camera Setup
|
||||
class CameraManager {
|
||||
constructor() {
|
||||
this.camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
window.innerWidth / window.innerHeight,
|
||||
0.1,
|
||||
1000
|
||||
);
|
||||
this.camera.position.set(5, 5, 5);
|
||||
this.camera.lookAt(0, 0, 0);
|
||||
|
||||
this.setupResizeHandler();
|
||||
}
|
||||
|
||||
setupResizeHandler() {
|
||||
window.addEventListener('resize', () => {
|
||||
this.camera.aspect = window.innerWidth / window.innerHeight;
|
||||
this.camera.updateProjectionMatrix();
|
||||
});
|
||||
}
|
||||
|
||||
getCamera() {
|
||||
return this.camera;
|
||||
}
|
||||
}
|
||||
19
src/controlsManager.js
Normal file
19
src/controlsManager.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// Orbit Controls Setup
|
||||
class ControlsManager {
|
||||
constructor(camera, renderer) {
|
||||
this.controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||
this.controls.enabled = false;
|
||||
this.controls.enableDamping = true;
|
||||
this.controls.dampingFactor = 0.05;
|
||||
}
|
||||
|
||||
getControls() {
|
||||
return this.controls;
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.controls.enabled) {
|
||||
this.controls.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
279
src/debugUI.js
Normal file
279
src/debugUI.js
Normal file
@@ -0,0 +1,279 @@
|
||||
// Debug UI Controller
|
||||
class DebugUI {
|
||||
constructor(scene, camera, controls, gridHelper, loader) {
|
||||
this.scene = scene;
|
||||
this.camera = camera;
|
||||
this.controls = controls;
|
||||
this.gridHelper = gridHelper;
|
||||
this.loader = loader;
|
||||
this.loadedModel = null;
|
||||
this.skeletonHelper = null;
|
||||
this.animationMixer = null;
|
||||
this.animations = [];
|
||||
this.currentAction = null;
|
||||
this.isMinimized = false;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.setupMinimize();
|
||||
this.setupOrbitControls();
|
||||
this.setupGridToggle();
|
||||
this.setupSkeletonToggle();
|
||||
this.setupAnimationControls();
|
||||
this.setupAssetSelector();
|
||||
}
|
||||
|
||||
setupMinimize() {
|
||||
const debugUI = document.getElementById('debug-ui');
|
||||
const minimizeBtn = document.getElementById('minimize-btn');
|
||||
|
||||
minimizeBtn.addEventListener('click', () => {
|
||||
this.isMinimized = !this.isMinimized;
|
||||
debugUI.classList.toggle('minimized', this.isMinimized);
|
||||
minimizeBtn.textContent = this.isMinimized ? '+' : '−';
|
||||
});
|
||||
}
|
||||
|
||||
setupOrbitControls() {
|
||||
document.getElementById('orbit-controls-toggle').addEventListener('change', (event) => {
|
||||
this.controls.enabled = event.target.checked;
|
||||
});
|
||||
}
|
||||
|
||||
setupGridToggle() {
|
||||
document.getElementById('show-grid-toggle').addEventListener('change', (event) => {
|
||||
this.gridHelper.visible = event.target.checked;
|
||||
});
|
||||
}
|
||||
|
||||
setupSkeletonToggle() {
|
||||
document.getElementById('show-skeleton-toggle').addEventListener('change', (event) => {
|
||||
if (event.target.checked) {
|
||||
this.showSkeleton();
|
||||
} else {
|
||||
this.hideSkeleton();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showSkeleton() {
|
||||
if (!this.loadedModel) return;
|
||||
|
||||
this.loadedModel.traverse((child) => {
|
||||
if (child.isSkinnedMesh && child.skeleton) {
|
||||
if (!this.skeletonHelper) {
|
||||
this.skeletonHelper = new THREE.SkeletonHelper(this.loadedModel);
|
||||
this.skeletonHelper.material.linewidth = 2;
|
||||
this.scene.add(this.skeletonHelper);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hideSkeleton() {
|
||||
if (this.skeletonHelper) {
|
||||
this.scene.remove(this.skeletonHelper);
|
||||
this.skeletonHelper = null;
|
||||
}
|
||||
}
|
||||
|
||||
setupAnimationControls() {
|
||||
// Animation selector
|
||||
document.getElementById('animation-select').addEventListener('change', (event) => {
|
||||
this.selectAnimation(event.target.value);
|
||||
});
|
||||
|
||||
// Play animation toggle
|
||||
document.getElementById('play-animation-toggle').addEventListener('change', (event) => {
|
||||
if (event.target.checked) {
|
||||
this.playAnimation();
|
||||
} else {
|
||||
this.pauseAnimation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
selectAnimation(animationName) {
|
||||
if (!this.animationMixer || !animationName) {
|
||||
this.pauseAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop current action
|
||||
if (this.currentAction) {
|
||||
this.currentAction.stop();
|
||||
}
|
||||
|
||||
// Find and play selected animation
|
||||
const clip = this.animations.find(anim => anim.name === animationName);
|
||||
if (clip) {
|
||||
this.currentAction = this.animationMixer.clipAction(clip);
|
||||
|
||||
// Auto-play if checkbox is checked
|
||||
if (document.getElementById('play-animation-toggle').checked) {
|
||||
this.currentAction.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playAnimation() {
|
||||
if (this.currentAction) {
|
||||
this.currentAction.play();
|
||||
}
|
||||
}
|
||||
|
||||
pauseAnimation() {
|
||||
if (this.currentAction) {
|
||||
this.currentAction.paused = true;
|
||||
}
|
||||
}
|
||||
|
||||
updateAnimationMixer(delta) {
|
||||
if (this.animationMixer) {
|
||||
this.animationMixer.update(delta);
|
||||
}
|
||||
}
|
||||
|
||||
// Program custom animations here
|
||||
createCustomAnimations() {
|
||||
// Example: Create a custom rotation animation
|
||||
const times = [0, 2];
|
||||
const values = [0, Math.PI*2];
|
||||
const track = new THREE.NumberKeyframeTrack('.rotation[y]', times, values);
|
||||
const clip = new THREE.AnimationClip('RotateY', 2, [track]);
|
||||
return [clip];
|
||||
|
||||
return []; // Return array of custom AnimationClip objects
|
||||
}
|
||||
|
||||
setupAssetSelector() {
|
||||
// Available assets in the assets folder
|
||||
const assets = [
|
||||
'assets/test_character.glb'
|
||||
// Add more assets here as needed
|
||||
];
|
||||
|
||||
// Populate asset dropdown
|
||||
const assetSelect = document.getElementById('asset-select');
|
||||
assets.forEach(assetPath => {
|
||||
const option = document.createElement('option');
|
||||
option.value = assetPath;
|
||||
option.textContent = assetPath.split('/').pop(); // Show only filename
|
||||
assetSelect.appendChild(option);
|
||||
});
|
||||
|
||||
// Asset selection handler
|
||||
assetSelect.addEventListener('change', (event) => {
|
||||
this.loadAsset(event.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
loadAsset(assetPath) {
|
||||
if (!assetPath) {
|
||||
// Remove model if "No Model" is selected
|
||||
if (this.loadedModel) {
|
||||
this.scene.remove(this.loadedModel);
|
||||
this.loadedModel = null;
|
||||
}
|
||||
this.updateModelStatus('No model loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load selected asset
|
||||
this.updateModelStatus('Loading...');
|
||||
|
||||
this.loader.load(
|
||||
assetPath,
|
||||
(gltf) => {
|
||||
// Remove previous model if exists
|
||||
if (this.loadedModel) {
|
||||
this.scene.remove(this.loadedModel);
|
||||
}
|
||||
|
||||
// Remove previous skeleton helper if exists
|
||||
this.hideSkeleton();
|
||||
|
||||
// Uncheck skeleton toggle
|
||||
document.getElementById('show-skeleton-toggle').checked = false;
|
||||
|
||||
this.loadedModel = gltf.scene;
|
||||
|
||||
// Get bounding box
|
||||
const box = new THREE.Box3().setFromObject(this.loadedModel);
|
||||
const size = box.getSize(new THREE.Vector3());
|
||||
const center = box.getCenter(new THREE.Vector3());
|
||||
|
||||
// Calculate max dimension
|
||||
const maxDim = Math.max(size.x, size.y, size.z);
|
||||
|
||||
// Scale model to fit in a 4 unit cube (fits nicely with the grid)
|
||||
const targetSize = 4;
|
||||
const scale = targetSize / maxDim;
|
||||
this.loadedModel.scale.setScalar(scale);
|
||||
|
||||
// Recalculate box after scaling
|
||||
box.setFromObject(this.loadedModel);
|
||||
box.getCenter(center);
|
||||
|
||||
// Center the model at origin on X and Z, but place bottom at y=0
|
||||
this.loadedModel.position.x = -center.x;
|
||||
this.loadedModel.position.y = -box.min.y;
|
||||
this.loadedModel.position.z = -center.z;
|
||||
|
||||
this.scene.add(this.loadedModel);
|
||||
|
||||
// Setup animations
|
||||
this.animations = gltf.animations || [];
|
||||
|
||||
// Add custom animations
|
||||
const customAnimations = this.createCustomAnimations();
|
||||
this.animations = [...this.animations, ...customAnimations];
|
||||
|
||||
if (this.animations.length > 0) {
|
||||
this.animationMixer = new THREE.AnimationMixer(this.loadedModel);
|
||||
|
||||
// Populate animation dropdown
|
||||
const animSelect = document.getElementById('animation-select');
|
||||
animSelect.innerHTML = '<option value="">-- No Animation --</option>';
|
||||
|
||||
this.animations.forEach(clip => {
|
||||
const option = document.createElement('option');
|
||||
option.value = clip.name;
|
||||
option.textContent = clip.name;
|
||||
animSelect.appendChild(option);
|
||||
});
|
||||
} else {
|
||||
this.animationMixer = null;
|
||||
document.getElementById('animation-select').innerHTML = '<option value="">-- No Animation --</option>';
|
||||
}
|
||||
|
||||
// Reset animation controls
|
||||
document.getElementById('animation-select').value = '';
|
||||
document.getElementById('play-animation-toggle').checked = false;
|
||||
|
||||
// Check for skeleton
|
||||
let hasSkeleton = false;
|
||||
this.loadedModel.traverse((child) => {
|
||||
if (child.isSkinnedMesh) {
|
||||
hasSkeleton = true;
|
||||
console.log('Skeleton found:', child.skeleton);
|
||||
}
|
||||
});
|
||||
|
||||
const statusText = hasSkeleton ? 'Model loaded & fitted (with skeleton)' : 'Model loaded & fitted';
|
||||
this.updateModelStatus(statusText);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
console.error('Error loading model:', error);
|
||||
this.updateModelStatus('Error loading model');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
updateModelStatus(status) {
|
||||
document.getElementById('model-status').textContent = status;
|
||||
}
|
||||
}
|
||||
135
src/main.js
135
src/main.js
@@ -1,125 +1,42 @@
|
||||
// Scene setup
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0xffffff);
|
||||
|
||||
// Camera setup
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
window.innerWidth / window.innerHeight,
|
||||
0.1,
|
||||
1000
|
||||
// Initialize all managers
|
||||
const sceneManager = new SceneManager();
|
||||
const cameraManager = new CameraManager();
|
||||
const rendererManager = new RendererManager();
|
||||
const controlsManager = new ControlsManager(
|
||||
cameraManager.getCamera(),
|
||||
rendererManager.getRenderer()
|
||||
);
|
||||
camera.position.set(5, 5, 5);
|
||||
camera.lookAt(0, 0, 0);
|
||||
|
||||
// Renderer setup
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
document.getElementById('canvas-container').appendChild(renderer.domElement);
|
||||
|
||||
// Orbit Controls (initially disabled)
|
||||
const controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||
controls.enabled = false;
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.05;
|
||||
|
||||
// Lighting
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
||||
directionalLight.position.set(5, 10, 5);
|
||||
scene.add(directionalLight);
|
||||
|
||||
// Grid
|
||||
const gridHelper = new THREE.GridHelper(20, 20, 0x000000, 0x888888);
|
||||
scene.add(gridHelper);
|
||||
|
||||
// Model container
|
||||
let loadedModel = null;
|
||||
// Get instances
|
||||
const scene = sceneManager.getScene();
|
||||
const camera = cameraManager.getCamera();
|
||||
const renderer = rendererManager.getRenderer();
|
||||
const controls = controlsManager.getControls();
|
||||
const gridHelper = sceneManager.getGridHelper();
|
||||
|
||||
// GLB Loader
|
||||
const loader = new THREE.GLTFLoader();
|
||||
|
||||
// File input handler
|
||||
document.getElementById('file-input').addEventListener('change', (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
const arrayBuffer = e.target.result;
|
||||
|
||||
// Remove previous model if exists
|
||||
if (loadedModel) {
|
||||
scene.remove(loadedModel);
|
||||
}
|
||||
|
||||
// Load the GLB
|
||||
loader.parse(arrayBuffer, '', (gltf) => {
|
||||
loadedModel = gltf.scene;
|
||||
|
||||
// Get bounding box
|
||||
const box = new THREE.Box3().setFromObject(loadedModel);
|
||||
const size = box.getSize(new THREE.Vector3());
|
||||
const center = box.getCenter(new THREE.Vector3());
|
||||
|
||||
// Calculate max dimension
|
||||
const maxDim = Math.max(size.x, size.y, size.z);
|
||||
|
||||
// Scale model to fit in a 4 unit cube (fits nicely with the grid)
|
||||
const targetSize = 4;
|
||||
const scale = targetSize / maxDim;
|
||||
loadedModel.scale.setScalar(scale);
|
||||
|
||||
// Recalculate box after scaling
|
||||
box.setFromObject(loadedModel);
|
||||
box.getCenter(center);
|
||||
|
||||
// Center the model at origin on X and Z, but place bottom at y=0
|
||||
loadedModel.position.x = -center.x;
|
||||
loadedModel.position.y = -box.min.y;
|
||||
loadedModel.position.z = -center.z;
|
||||
|
||||
scene.add(loadedModel);
|
||||
document.getElementById('model-status').textContent = 'Model loaded & fitted';
|
||||
}, (error) => {
|
||||
console.error('Error loading model:', error);
|
||||
document.getElementById('model-status').textContent = 'Error loading model';
|
||||
});
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
document.getElementById('model-status').textContent = 'Loading...';
|
||||
}
|
||||
});
|
||||
|
||||
// Orbit controls toggle
|
||||
document.getElementById('orbit-controls-toggle').addEventListener('change', (event) => {
|
||||
controls.enabled = event.target.checked;
|
||||
});
|
||||
|
||||
// Grid toggle
|
||||
document.getElementById('show-grid-toggle').addEventListener('change', (event) => {
|
||||
gridHelper.visible = event.target.checked;
|
||||
});
|
||||
// Initialize Debug UI
|
||||
const debugUI = new DebugUI(scene, camera, controls, gridHelper, loader);
|
||||
|
||||
// Animation loop
|
||||
const clock = new THREE.Clock();
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
if (controls.enabled) {
|
||||
controls.update();
|
||||
}
|
||||
const delta = clock.getDelta();
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
// Update controls
|
||||
controlsManager.update();
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
});
|
||||
// Update animations
|
||||
debugUI.updateAnimationMixer(delta);
|
||||
|
||||
// Render scene
|
||||
rendererManager.render(scene, camera);
|
||||
}
|
||||
|
||||
// Start animation
|
||||
animate();
|
||||
|
||||
25
src/rendererManager.js
Normal file
25
src/rendererManager.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// Renderer Setup
|
||||
class RendererManager {
|
||||
constructor() {
|
||||
this.renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||
document.getElementById('canvas-container').appendChild(this.renderer.domElement);
|
||||
|
||||
this.setupResizeHandler();
|
||||
}
|
||||
|
||||
setupResizeHandler() {
|
||||
window.addEventListener('resize', () => {
|
||||
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
});
|
||||
}
|
||||
|
||||
getRenderer() {
|
||||
return this.renderer;
|
||||
}
|
||||
|
||||
render(scene, camera) {
|
||||
this.renderer.render(scene, camera);
|
||||
}
|
||||
}
|
||||
32
src/sceneManager.js
Normal file
32
src/sceneManager.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// Scene Setup
|
||||
class SceneManager {
|
||||
constructor() {
|
||||
this.scene = new THREE.Scene();
|
||||
this.scene.background = new THREE.Color(0xffffff);
|
||||
|
||||
this.setupLighting();
|
||||
this.setupGrid();
|
||||
}
|
||||
|
||||
setupLighting() {
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
||||
this.scene.add(ambientLight);
|
||||
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
||||
directionalLight.position.set(5, 10, 5);
|
||||
this.scene.add(directionalLight);
|
||||
}
|
||||
|
||||
setupGrid() {
|
||||
this.gridHelper = new THREE.GridHelper(20, 20, 0x000000, 0x888888);
|
||||
this.scene.add(this.gridHelper);
|
||||
}
|
||||
|
||||
getScene() {
|
||||
return this.scene;
|
||||
}
|
||||
|
||||
getGridHelper() {
|
||||
return this.gridHelper;
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,56 @@ canvas {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.debug-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.debug-header h3 {
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#minimize-btn {
|
||||
background: #333;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#minimize-btn:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
#debug-content {
|
||||
transition: max-height 0.3s ease, opacity 0.3s ease;
|
||||
max-height: 500px;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#debug-ui.minimized #debug-content {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.debug-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@@ -73,7 +119,19 @@ canvas {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.file-label {
|
||||
.debug-item select {
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
margin-top: 5px;
|
||||
font-size: 13px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.file-label,
|
||||
.asset-label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
|
||||
Reference in New Issue
Block a user