feat: implement customizable Bitcoin logo with 3D rotation and interactive controls
All checks were successful
Deploy to Production / deploy (push) Successful in 4s
All checks were successful
Deploy to Production / deploy (push) Successful in 4s
This commit is contained in:
317
src/App.vue
Normal file
317
src/App.vue
Normal file
@@ -0,0 +1,317 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import BitcoinLogo from './components/BitcoinLogo.vue'
|
||||
|
||||
// Modes: 'auto', 'interactive', 'controlled'
|
||||
const activeMode = ref('interactive');
|
||||
const activeEasing = ref('linear');
|
||||
const activeDuration = ref(4);
|
||||
const activeFriction = ref(0.98);
|
||||
const activeSymbolColor = ref('#ffffff');
|
||||
const activeSize = ref(250);
|
||||
const activeStrokeWidth = ref(5);
|
||||
const externalRotation = ref(0);
|
||||
|
||||
const easingOptions = [
|
||||
'linear',
|
||||
'ease',
|
||||
'ease-in',
|
||||
'ease-out',
|
||||
'ease-in-out',
|
||||
'cubic-bezier(0.68, -0.55, 0.265, 1.55)' // bouncy
|
||||
];
|
||||
|
||||
const handleRotationUpdate = (val) => {
|
||||
if (activeMode.value !== 'controlled') {
|
||||
externalRotation.value = val;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="glass-card">
|
||||
<header>
|
||||
<h1>Bitcoin 3D</h1>
|
||||
<p>Premium 3D Bitcoin logo with multiple rotation modes.</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<BitcoinLogo
|
||||
:mode="activeMode"
|
||||
:easing="activeEasing"
|
||||
:duration="activeDuration"
|
||||
:friction="activeFriction"
|
||||
:symbolColor="activeSymbolColor"
|
||||
:size="activeSize"
|
||||
:strokeWidth="activeStrokeWidth"
|
||||
v-model:rotation="externalRotation"
|
||||
@update:rotation="handleRotationUpdate"
|
||||
/>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="global-controls">
|
||||
<div class="color-control">
|
||||
<span class="label">SYMBOL COLOR</span>
|
||||
<input type="color" v-model="activeSymbolColor">
|
||||
</div>
|
||||
<div class="size-control">
|
||||
<span class="label">SIZE</span>
|
||||
<input type="range" v-model.number="activeSize" min="50" max="400" step="1">
|
||||
<span class="value">{{ activeSize }}px</span>
|
||||
</div>
|
||||
<div class="size-control">
|
||||
<span class="label">STROKE</span>
|
||||
<input type="range" v-model.number="activeStrokeWidth" min="0" max="20" step="0.5">
|
||||
<span class="value">{{ activeStrokeWidth }}px</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mode-selector">
|
||||
<label
|
||||
v-for="mode in ['auto', 'interactive', 'controlled']"
|
||||
:key="mode"
|
||||
:class="{ active: activeMode === mode }"
|
||||
>
|
||||
<input type="radio" :value="mode" v-model="activeMode">
|
||||
{{ mode.toUpperCase() }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="activeMode === 'auto'" class="control-panel">
|
||||
<div class="duration-control">
|
||||
<span class="label">SPEED</span>
|
||||
<input type="range" v-model.number="activeDuration" min="0.5" max="10" step="0.1">
|
||||
<span class="value">{{ activeDuration }}s</span>
|
||||
</div>
|
||||
<select v-model="activeEasing" class="easing-select">
|
||||
<option v-for="option in easingOptions" :key="option" :value="option">
|
||||
{{ option === 'cubic-bezier(0.68, -0.55, 0.265, 1.55)' ? 'BOUNCY' : option.toUpperCase() }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div v-if="activeMode === 'interactive'" class="control-panel">
|
||||
<div class="duration-control">
|
||||
<span class="label">INERTIA</span>
|
||||
<input type="range" v-model.number="activeFriction" min="0.8" max="0.999" step="0.001">
|
||||
<span class="value">{{ (activeFriction * 100).toFixed(1) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeMode === 'controlled'" class="control-panel">
|
||||
<input type="range" v-model.number="externalRotation" min="0" max="360" step="1">
|
||||
<span class="value">{{ externalRotation }}°</span>
|
||||
</div>
|
||||
|
||||
<div class="badges">
|
||||
<div class="badge">Vue 3</div>
|
||||
<div class="badge">SVG 3D</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 2rem 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.global-controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.color-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.color-control .label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 800;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.color-control input[type="color"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.color-control input[type="color"]::-webkit-color-swatch {
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.size-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.size-control .label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 800;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.size-control input[type="range"] {
|
||||
flex: 1;
|
||||
accent-color: #f7931a;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.size-control .value {
|
||||
font-family: monospace;
|
||||
min-width: 45px;
|
||||
font-size: 0.85rem;
|
||||
color: #f7931a;
|
||||
}
|
||||
|
||||
.mode-selector {
|
||||
display: flex;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 4px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.mode-selector label {
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.mode-selector label.active {
|
||||
background: #f7931a;
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(247, 147, 26, 0.3);
|
||||
}
|
||||
|
||||
.mode-selector input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.duration-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.duration-control .label {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 800;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.control-panel input[type="range"] {
|
||||
flex: 1;
|
||||
accent-color: #f7931a;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.control-panel .value {
|
||||
font-family: monospace;
|
||||
min-width: 45px;
|
||||
font-size: 0.85rem;
|
||||
color: #f7931a;
|
||||
}
|
||||
|
||||
.easing-select {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: white;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
appearance: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.easing-select:focus {
|
||||
outline: none;
|
||||
border-color: #f7931a;
|
||||
}
|
||||
|
||||
.badges {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 99px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: #fbbf24;
|
||||
border: 1px solid rgba(251, 191, 36, 0.2);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
h1 { font-size: 2.5rem; }
|
||||
.glass-card { padding: 2rem; margin: 1rem; }
|
||||
}
|
||||
</style>
|
||||
221
src/components/BitcoinLogo.vue
Normal file
221
src/components/BitcoinLogo.vue
Normal file
@@ -0,0 +1,221 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
|
||||
const FIX_SHOWING_THROUGH = '0.5px';
|
||||
|
||||
const props = defineProps({
|
||||
// Modes: 'auto', 'interactive', 'controlled'
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'interactive'
|
||||
},
|
||||
easing: {
|
||||
type: String,
|
||||
default: 'linear'
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 4
|
||||
},
|
||||
friction: {
|
||||
type: Number,
|
||||
default: 0.98
|
||||
},
|
||||
symbolColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
strokeWidth: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
// Used only in 'controlled' mode
|
||||
rotation: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:rotation']);
|
||||
|
||||
// Internal state for 'interactive' mode
|
||||
const internalRotation = ref(0);
|
||||
const velocity = ref(2);
|
||||
const isDragging = ref(false);
|
||||
const lastMouseX = ref(0);
|
||||
|
||||
// Final rotation value used in CSS
|
||||
const displayRotation = computed(() => {
|
||||
if (props.mode === 'controlled') return props.rotation;
|
||||
return internalRotation.value;
|
||||
});
|
||||
|
||||
const rotationStyle = computed(() => `${displayRotation.value}deg`);
|
||||
|
||||
// Drag handlers
|
||||
const handleStart = (e) => {
|
||||
if (props.mode !== 'interactive') return;
|
||||
isDragging.value = true;
|
||||
lastMouseX.value = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
|
||||
};
|
||||
|
||||
const handleMove = (e) => {
|
||||
if (!isDragging.value || props.mode !== 'interactive') return;
|
||||
const currentX = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
|
||||
const deltaX = currentX - lastMouseX.value;
|
||||
|
||||
internalRotation.value += deltaX * 0.5;
|
||||
velocity.value = deltaX * 0.5;
|
||||
lastMouseX.value = currentX;
|
||||
|
||||
emit('update:rotation', internalRotation.value);
|
||||
};
|
||||
|
||||
const handleEnd = () => {
|
||||
isDragging.value = false;
|
||||
};
|
||||
|
||||
// Animation loop
|
||||
let rafId = null;
|
||||
const animate = () => {
|
||||
if (props.mode === 'interactive' && !isDragging.value) {
|
||||
internalRotation.value += velocity.value;
|
||||
velocity.value *= props.friction;
|
||||
emit('update:rotation', internalRotation.value);
|
||||
}
|
||||
rafId = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
rafId = requestAnimationFrame(animate);
|
||||
window.addEventListener('mousemove', handleMove);
|
||||
window.addEventListener('mouseup', handleEnd);
|
||||
window.addEventListener('touchmove', handleMove, { passive: false });
|
||||
window.addEventListener('touchend', handleEnd);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
cancelAnimationFrame(rafId);
|
||||
window.removeEventListener('mousemove', handleMove);
|
||||
window.removeEventListener('mouseup', handleEnd);
|
||||
window.removeEventListener('touchmove', handleMove);
|
||||
window.removeEventListener('touchend', handleEnd);
|
||||
});
|
||||
|
||||
// Sync internal rotation if external control changes
|
||||
watch(() => props.rotation, (newVal) => {
|
||||
if (props.mode === 'controlled') {
|
||||
internalRotation.value = newVal;
|
||||
}
|
||||
});
|
||||
|
||||
const coinShape = 'M98.4946664 62.0964188c-6.6779856,26.7864844 -33.808340799999996,43.0882088 -60.5980024,36.4085124 -26.778419200000002,-6.6779856 -43.0801436,-33.8100516 -36.3992252,-60.594336399999996 6.6750528000000005,-26.789417200000003 33.805408,-43.0923636 60.58676,-36.414378 26.787706399999998,6.6779856 43.0884532,33.8129844 36.4097344,60.6006908l0.0004888 -0.0004888z';
|
||||
const symbolShape = 'M72.0434988 42.8770472c0.9951968,-6.6540344 -4.0707264,-10.2308284 -10.998,-12.6169056l2.247258 -9.013472 -5.4867799999999995 -1.3671735999999999 -2.1876244000000002 8.7761596c-1.4424488,-0.3597568 -2.9237572,-0.6987396 -4.3960228,-1.0347896l2.2035104 -8.8340824 -5.4833584 -1.3671735999999999 -2.24848 9.0105392c-1.1936496,-0.2717728 -2.3660364,-0.5403684 -3.5034739999999998,-0.8233836l0.0063544000000000005 -0.028350399999999998 -7.566379599999999 -1.8894564 -1.4595567999999999 5.8602232c0,0 4.0707264,0.9331192 3.984942,0.9905532 2.2218404,0.5545436 2.623634,2.0253428 2.5569128,3.1911308l-2.5598456 10.268466c0.1529944,0.0388596 0.3514472,0.0950716 0.5704296,0.1830556 -0.1830556,-0.0454584 -0.3778424,-0.0950716 -0.5799612,-0.1434628l-3.5880364 14.384650800000001c-0.2715284,0.6750328 -0.9607364,1.6880707999999998 -2.5141428,1.3033852 0.05499,0.07967439999999999 -3.9878747999999997,-0.9951968 -3.9878747999999997,-0.9951968l-2.7240824 6.280591200000001 7.140146 1.7799652c1.328314,0.3331172 2.6299884,0.6816316 3.9118664,1.009372l-2.270476 9.1168532 5.4804256 1.3671735999999999 2.24848 -9.0200708c1.4971944,0.4064372 2.9501524,0.7813468 4.3725604,1.1347492l-2.2409035999999998 8.9775452 5.4870244 1.3671735999999999 2.2702316 -9.0997452c9.3561208,1.770678 16.391174799999998,1.0567856 19.3523252,-7.4058088 2.3860772,-6.8133832 -0.1187784,-10.743335199999999 -5.0409944,-13.306113600000002 3.5851036,-0.8268051999999999 6.2854792,-3.1847764 7.0054815999999995,-8.0556684l-0.0017108000000000002 -0.001222zm-12.536009199999999 17.5787144c-1.6956471999999998,6.8133832 -13.1672944,3.1302752000000003 -16.886573600000002,2.2066876l3.0129632 -12.078248c3.7190347999999998,0.9284756000000001 15.645754799999999,2.7658748 13.873854799999998,9.8715604zm1.6968692 -17.677452c-1.5468076,6.1974952000000005 -11.0947824,3.04889 -14.192063600000001,2.2768303999999997l2.7316588 -10.9542524c3.0972812000000003,0.7720596 13.071734000000001,2.2130419999999997 11.4608936,8.677422l-0.0004888 0z';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="logo-container"
|
||||
@mousedown="handleStart"
|
||||
@touchstart="handleStart"
|
||||
>
|
||||
<div
|
||||
class="coin"
|
||||
:class="{
|
||||
'auto-rotate': props.mode === 'auto',
|
||||
'dragging': isDragging
|
||||
}"
|
||||
>
|
||||
<div class="side heads">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 100 100">
|
||||
<path fill="#F7931A" fill-rule="nonzero" :stroke="symbolColor" :stroke-width="strokeWidth" :d="coinShape"></path>
|
||||
<path :fill="symbolColor" fill-rule="nonzero" :d="symbolShape"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="side tails">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="svg_back" width="100%" height="100%" viewBox="0 0 100 100">
|
||||
<path fill="#F7931A" fill-rule="nonzero" :stroke="symbolColor" :stroke-width="strokeWidth" :d="coinShape"></path>
|
||||
<path :fill="symbolColor" fill-rule="nonzero" :d="symbolShape"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.logo-container {
|
||||
font-size: v-bind("props.size + 'px'");
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* background: rgba(255, 0, 0, 0.2); Bounding rect debug */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: v-bind("props.mode === 'interactive' ? (isDragging ? 'grabbing' : 'grab') : 'default'");
|
||||
user-select: none;
|
||||
touch-action: v-bind("props.mode === 'interactive' ? 'none' : 'auto'");
|
||||
}
|
||||
|
||||
.coin {
|
||||
width: 0.1em;
|
||||
height: 1em;
|
||||
background: linear-gradient(#faa504, #141001);
|
||||
transform: rotateY(v-bind(rotationStyle));
|
||||
transform-style: preserve-3d;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.coin.auto-rotate {
|
||||
animation: rotate v-bind("props.duration + 's'") infinite v-bind("props.easing");
|
||||
}
|
||||
|
||||
.coin .side, .coin:before, .coin:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
right: calc(-0.4em + v-bind(FIX_SHOWING_THROUGH));
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
transform: rotateY(-90deg);
|
||||
-moz-backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.coin .tails, .coin:after {
|
||||
left: calc(-0.4em + v-bind(FIX_SHOWING_THROUGH));
|
||||
transform: rotateY(90deg);
|
||||
}
|
||||
|
||||
.coin:before, .coin:after {
|
||||
background: linear-gradient(#faa504, #141001);
|
||||
backface-visibility: hidden;
|
||||
transform: rotateY(90deg);
|
||||
}
|
||||
|
||||
.coin:after {
|
||||
transform: rotateY(-90deg);
|
||||
}
|
||||
|
||||
.svg_back {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% { transform: rotateY(90deg); }
|
||||
100% { transform: rotateY(450deg); }
|
||||
}
|
||||
</style>
|
||||
5
src/main.js
Normal file
5
src/main.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
65
src/style.css
Normal file
65
src/style.css
Normal file
@@ -0,0 +1,65 @@
|
||||
:root {
|
||||
font-family: 'Inter', system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: dark;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
background-color: #0f172a;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
/* Center vertically if content is small */
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
background: radial-gradient(circle at center, #1e293b 0%, #0f172a 100%);
|
||||
overflow-y: auto;
|
||||
/* Ensure vertical scroll is allowed */
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4rem;
|
||||
line-height: 1;
|
||||
font-weight: 800;
|
||||
margin-bottom: 0.5rem;
|
||||
background: linear-gradient(to right, #f59e0b, #fbbf24);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
filter: drop-shadow(0 0 10px rgba(245, 158, 11, 0.3));
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.25rem;
|
||||
color: #94a3b8;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 3rem;
|
||||
}
|
||||
|
||||
.glass-card {
|
||||
background: rgba(30, 41, 59, 0.5);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 24px;
|
||||
padding: 3rem;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||
display: inline-block;
|
||||
}
|
||||
Reference in New Issue
Block a user