3D Cards, Wareframe, glass effect
ci/woodpecker/push/woodpecker Pipeline was successful
Details
ci/woodpecker/push/woodpecker Pipeline was successful
Details
This commit is contained in:
parent
db593cd299
commit
11d52b2e86
|
|
@ -30,6 +30,9 @@ features = [
|
|||
"HtmlCanvasElement",
|
||||
"CanvasRenderingContext2d",
|
||||
"CanvasGradient",
|
||||
"MouseEvent",
|
||||
"Event",
|
||||
"CssStyleDeclaration",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ export interface InitOutput {
|
|||
readonly __wbindgen_export_1: (a: number, b: number) => number;
|
||||
readonly __wbindgen_export_2: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_export_3: WebAssembly.Table;
|
||||
readonly __wbindgen_export_4: (a: number, b: number) => void;
|
||||
readonly __wbindgen_export_5: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly __wbindgen_export_4: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly __wbindgen_export_5: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_export_6: (a: number, b: number) => void;
|
||||
readonly __wbindgen_start: () => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -237,12 +237,16 @@ export function main() {
|
|||
wasm.main();
|
||||
}
|
||||
|
||||
function __wbg_adapter_8(arg0, arg1) {
|
||||
wasm.__wbindgen_export_4(arg0, arg1);
|
||||
function __wbg_adapter_4(arg0, arg1, arg2, arg3) {
|
||||
wasm.__wbindgen_export_4(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
||||
}
|
||||
|
||||
function __wbg_adapter_11(arg0, arg1, arg2, arg3) {
|
||||
wasm.__wbindgen_export_5(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
||||
function __wbg_adapter_11(arg0, arg1, arg2) {
|
||||
wasm.__wbindgen_export_5(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_14(arg0, arg1) {
|
||||
wasm.__wbindgen_export_6(arg0, arg1);
|
||||
}
|
||||
|
||||
const EXPECTED_RESPONSE_TYPES = new Set(['basic', 'cors', 'default']);
|
||||
|
|
@ -296,6 +300,9 @@ function __wbg_get_imports() {
|
|||
const ret = getObject(arg0).appendChild(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_arc_61cbec33cc96a55e = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) {
|
||||
getObject(arg0).arc(arg1, arg2, arg3, arg4, arg5);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_beginPath_119487ebd04e9e1c = function(arg0) {
|
||||
getObject(arg0).beginPath();
|
||||
};
|
||||
|
|
@ -311,6 +318,14 @@ function __wbg_get_imports() {
|
|||
const ret = getObject(arg0).classList;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_clientX_ea858fbae3debd3c = function(arg0) {
|
||||
const ret = getObject(arg0).clientX;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_clientY_cbb39a771d53208f = function(arg0) {
|
||||
const ret = getObject(arg0).clientY;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_closePath_58530240bb00a7fc = function(arg0) {
|
||||
getObject(arg0).closePath();
|
||||
};
|
||||
|
|
@ -339,6 +354,10 @@ function __wbg_get_imports() {
|
|||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
};
|
||||
imports.wbg.__wbg_getBoundingClientRect_a2461829d8aa0b30 = function(arg0) {
|
||||
const ret = getObject(arg0).getBoundingClientRect();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_getContext_15e158d04230a6f6 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).getContext(getStringFromWasm0(arg1, arg2));
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
|
|
@ -347,6 +366,10 @@ function __wbg_get_imports() {
|
|||
const ret = getObject(arg0)[arg1 >>> 0];
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_height_36fae1857b6cca70 = function(arg0) {
|
||||
const ret = getObject(arg0).height;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_innerHeight_eacbddff807274db = function() { return handleError(function (arg0) {
|
||||
const ret = getObject(arg0).innerHeight;
|
||||
return addHeapObject(ret);
|
||||
|
|
@ -385,6 +408,16 @@ function __wbg_get_imports() {
|
|||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_HtmlElement_d60c51c41eb8699a = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof HTMLElement;
|
||||
} catch (_) {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_IntersectionObserverEntry_819e56422a481344 = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
|
|
@ -413,6 +446,10 @@ function __wbg_get_imports() {
|
|||
const ret = getObject(arg0).item(arg1 >>> 0);
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_left_31939b629ff732e9 = function(arg0) {
|
||||
const ret = getObject(arg0).left;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_length_186546c51cd61acd = function(arg0) {
|
||||
const ret = getObject(arg0).length;
|
||||
return ret;
|
||||
|
|
@ -456,6 +493,9 @@ function __wbg_get_imports() {
|
|||
imports.wbg.__wbg_setAttribute_d1baf9023ad5696f = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
getObject(arg0).setAttribute(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_setProperty_a4431938dd3e6945 = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
getObject(arg0).setProperty(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_setclassName_c8bccad917b973f4 = function(arg0, arg1, arg2) {
|
||||
getObject(arg0).className = getStringFromWasm0(arg1, arg2);
|
||||
};
|
||||
|
|
@ -474,6 +514,12 @@ function __wbg_get_imports() {
|
|||
imports.wbg.__wbg_setinnerHTML_34e240d6b8e8260c = function(arg0, arg1, arg2) {
|
||||
getObject(arg0).innerHTML = getStringFromWasm0(arg1, arg2);
|
||||
};
|
||||
imports.wbg.__wbg_setlineWidth_069d571345379833 = function(arg0, arg1) {
|
||||
getObject(arg0).lineWidth = arg1;
|
||||
};
|
||||
imports.wbg.__wbg_setstrokeStyle_3c450999cfcdcd2f = function(arg0, arg1, arg2) {
|
||||
getObject(arg0).strokeStyle = getStringFromWasm0(arg1, arg2);
|
||||
};
|
||||
imports.wbg.__wbg_settextContent_b55fe2f5f1399466 = function(arg0, arg1, arg2) {
|
||||
getObject(arg0).textContent = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2);
|
||||
};
|
||||
|
|
@ -499,10 +545,21 @@ function __wbg_get_imports() {
|
|||
const ret = typeof window === 'undefined' ? null : window;
|
||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_stroke_b53e61cc42965e61 = function(arg0) {
|
||||
getObject(arg0).stroke();
|
||||
};
|
||||
imports.wbg.__wbg_style_32a3c8393b46a115 = function(arg0) {
|
||||
const ret = getObject(arg0).style;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_target_161cb00cc3daf872 = function(arg0) {
|
||||
const ret = getObject(arg0).target;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_top_447ffcfee64c5d99 = function(arg0) {
|
||||
const ret = getObject(arg0).top;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_wbindgencbdrop_eb10308566512b88 = function(arg0) {
|
||||
const obj = getObject(arg0).original;
|
||||
if (obj.cnt-- == 1) {
|
||||
|
|
@ -535,6 +592,10 @@ function __wbg_get_imports() {
|
|||
imports.wbg.__wbg_wbindgenthrow_451ec1a8469d7eb6 = function(arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
imports.wbg.__wbg_width_d69b4eebaa4e6b70 = function(arg0) {
|
||||
const ret = getObject(arg0).width;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_cast_2241b6af4c4b2941 = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Ref(String) -> Externref`.
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
|
|
@ -542,7 +603,12 @@ function __wbg_get_imports() {
|
|||
};
|
||||
imports.wbg.__wbindgen_cast_aaa93aae03c115ab = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 1, function: Function { arguments: [], shim_idx: 2, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, 1, __wbg_adapter_8);
|
||||
const ret = makeMutClosure(arg0, arg1, 1, __wbg_adapter_14);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_cast_d557c1fcdf607639 = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 1, function: Function { arguments: [NamedExternref("MouseEvent")], shim_idx: 6, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, 1, __wbg_adapter_11);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_cast_d6cd19b81560fd6e = function(arg0) {
|
||||
|
|
@ -552,7 +618,7 @@ function __wbg_get_imports() {
|
|||
};
|
||||
imports.wbg.__wbindgen_cast_e58c2e3d55f4d158 = function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { dtor_idx: 1, function: Function { arguments: [NamedExternref("Array<any>"), NamedExternref("IntersectionObserver")], shim_idx: 4, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, 1, __wbg_adapter_11);
|
||||
const ret = makeMutClosure(arg0, arg1, 1, __wbg_adapter_4);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -6,6 +6,7 @@ export const __wbindgen_export_0: (a: number) => void;
|
|||
export const __wbindgen_export_1: (a: number, b: number) => number;
|
||||
export const __wbindgen_export_2: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __wbindgen_export_3: WebAssembly.Table;
|
||||
export const __wbindgen_export_4: (a: number, b: number) => void;
|
||||
export const __wbindgen_export_5: (a: number, b: number, c: number, d: number) => void;
|
||||
export const __wbindgen_export_4: (a: number, b: number, c: number, d: number) => void;
|
||||
export const __wbindgen_export_5: (a: number, b: number, c: number) => void;
|
||||
export const __wbindgen_export_6: (a: number, b: number) => void;
|
||||
export const __wbindgen_start: () => void;
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
:root {
|
||||
--bg-dark: #0a0e17;
|
||||
--bg-card: #1a1f2e;
|
||||
--bg-card-hover: #252a3a;
|
||||
--bg-card: rgba(26, 31, 46, 0.8); /* More transparent for glass effect */
|
||||
--bg-card-hover: rgba(37, 42, 58, 0.9);
|
||||
--text-primary: #e0e6ed;
|
||||
--text-secondary: #9ca3af;
|
||||
--accent: #3b82f6;
|
||||
--accent-hover: #2563eb;
|
||||
--rust: #f74c00;
|
||||
--border: #2d3748;
|
||||
--border: rgba(45, 55, 72, 0.5);
|
||||
--success: #10b981;
|
||||
--planned: #6366f1;
|
||||
}
|
||||
|
|
@ -38,37 +38,6 @@ body {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Loading screen */
|
||||
#loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bg-dark);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.loader {
|
||||
font-size: 1.2rem;
|
||||
color: var(--text-secondary);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 0.5; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--rust);
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
|
|
@ -88,16 +57,6 @@ body {
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: radial-gradient(circle at 50% 50%, rgba(59, 130, 246, 0.1) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
.hero .container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
|
@ -111,6 +70,7 @@ body {
|
|||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
filter: drop-shadow(0 0 2em rgba(59, 130, 246, 0.3));
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
|
|
@ -125,12 +85,14 @@ body {
|
|||
.hero-badge {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: var(--bg-card);
|
||||
background: rgba(26, 31, 46, 0.6);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 2rem;
|
||||
font-size: 1rem;
|
||||
color: var(--text-primary);
|
||||
animation: float 3s ease-in-out infinite;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
|
|
@ -150,72 +112,118 @@ section {
|
|||
margin-bottom: 3rem;
|
||||
text-align: center;
|
||||
color: var(--text-primary);
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Services Section */
|
||||
.services {
|
||||
background: var(--bg-dark);
|
||||
/* Transparent to let particles show through slightly or keep dark */
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 2rem;
|
||||
perspective: 1000px; /* Global perspective for grid items entrance */
|
||||
}
|
||||
|
||||
.service-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
/* 3D Card System */
|
||||
.service-card-wrapper {
|
||||
/* This is the element that receives mouse events and defines the space */
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
perspective: 1500px; /* Perspective for the 3D tilt */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
background: var(--bg-card-hover);
|
||||
.service-card-content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
|
||||
/* Glassmorphism */
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
|
||||
/* 3D Transform settings */
|
||||
transform-style: preserve-3d;
|
||||
transform: rotateX(var(--rotate-x, 0deg)) rotateY(var(--rotate-y, 0deg));
|
||||
|
||||
/* Smooth return when mouse leaves */
|
||||
transition: transform 0.1s cubic-bezier(0.2, 0.4, 0.6, 1), border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.service-card-wrapper:hover .service-card-content {
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.2);
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.4),
|
||||
0 0 20px rgba(59, 130, 246, 0.2),
|
||||
inset 0 0 0 1px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
/* Glare Effect */
|
||||
.card-glare {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 1rem;
|
||||
background: radial-gradient(
|
||||
circle at var(--glare-x, 50%) var(--glare-y, 50%),
|
||||
rgba(255, 255, 255, 0.15) 0%,
|
||||
transparent 60%
|
||||
);
|
||||
opacity: var(--glare-opacity, 0);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
mix-blend-mode: overlay;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Floating Content inside Card */
|
||||
.service-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
gap: 0.75rem;
|
||||
transform: translateZ(25px); /* Pop out */
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.service-card h3 {
|
||||
font-size: 1.25rem;
|
||||
.service-card-content h3 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-primary);
|
||||
font-weight: 700;
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
transform: translateZ(35px); /* Pop out more */
|
||||
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
|
||||
}
|
||||
|
||||
.service-description {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.95rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
transform: translateZ(15px); /* Pop out slightly */
|
||||
}
|
||||
|
||||
/* Tech Section */
|
||||
.tech {
|
||||
background: linear-gradient(180deg, var(--bg-dark) 0%, var(--bg-card) 100%);
|
||||
background: linear-gradient(180deg, rgba(10, 14, 23, 0) 0%, var(--bg-dark) 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tech-grid {
|
||||
|
|
@ -227,6 +235,17 @@ section {
|
|||
.tech-item {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.tech-item:hover {
|
||||
transform: translateY(-5px) scale(1.02);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-color: var(--border);
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.tech-item h3 {
|
||||
|
|
@ -246,6 +265,8 @@ section {
|
|||
border-top: 1px solid var(--border);
|
||||
padding: 3rem 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
|
|
@ -283,174 +304,19 @@ section {
|
|||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.services-grid,
|
||||
.tech-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hero {
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
.hero-title {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Selection color */
|
||||
::selection {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Scroll-triggered animations */
|
||||
/* Scroll Animations */
|
||||
[data-animate] {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||
transition: opacity 0.8s cubic-bezier(0.2, 0.8, 0.2, 1), transform 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
[data-animate].animate-in {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Parallax effect on hero */
|
||||
.hero::before {
|
||||
animation: parallax-float 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes parallax-float {
|
||||
0%, 100% { transform: translateY(0) scale(1); }
|
||||
50% { transform: translateY(-20px) scale(1.05); }
|
||||
}
|
||||
|
||||
/* Animated gradient text */
|
||||
.hero-title {
|
||||
background-size: 200% auto;
|
||||
animation: gradient-shift 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes gradient-shift {
|
||||
0%, 100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hover micro-interactions */
|
||||
.service-card {
|
||||
transform-origin: center;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
animation: subtle-bounce 0.6s ease;
|
||||
}
|
||||
|
||||
@keyframes subtle-bounce {
|
||||
0%, 100% { transform: translateY(-4px); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
.tech-item {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tech-item:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.tech-item:hover h3 {
|
||||
animation: pulse-glow 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.8; text-shadow: 0 0 20px rgba(59, 130, 246, 0.5); }
|
||||
}
|
||||
|
||||
/* Footer links animation */
|
||||
.footer-links a {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.footer-links a::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--accent);
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-links a:hover::before {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* Hero badge pulse */
|
||||
.hero-badge {
|
||||
animation: float 3s ease-in-out infinite, glow-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes glow-pulse {
|
||||
0%, 100% { box-shadow: 0 0 10px rgba(59, 130, 246, 0.2); }
|
||||
50% { box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); }
|
||||
}
|
||||
|
||||
/* Status badge animations */
|
||||
.status-badge {
|
||||
animation: badge-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes badge-pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
/* Section title entrance */
|
||||
.section-title {
|
||||
animation: slide-in-top 0.8s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slide-in-top {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reduce motion for accessibility */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
304
src/lib.rs
304
src/lib.rs
|
|
@ -3,10 +3,125 @@ use wasm_bindgen::JsCast;
|
|||
use web_sys::{
|
||||
console, Document, Element, HtmlElement, Window,
|
||||
IntersectionObserver, IntersectionObserverEntry, IntersectionObserverInit,
|
||||
HtmlCanvasElement, CanvasRenderingContext2d,
|
||||
HtmlCanvasElement, CanvasRenderingContext2d, MouseEvent, CssStyleDeclaration,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
// Mouse State
|
||||
#[derive(Clone, Copy)]
|
||||
struct MouseState {
|
||||
x: f64,
|
||||
y: f64,
|
||||
// Normalized coordinates (-1.0 to 1.0)
|
||||
norm_x: f64,
|
||||
norm_y: f64,
|
||||
}
|
||||
|
||||
impl MouseState {
|
||||
fn new() -> Self {
|
||||
Self { x: 0.0, y: 0.0, norm_x: 0.0, norm_y: 0.0 }
|
||||
}
|
||||
}
|
||||
|
||||
// 3D Types
|
||||
#[derive(Clone, Copy)]
|
||||
struct Point3D {
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
}
|
||||
|
||||
struct Polyhedron {
|
||||
vertices: Vec<Point3D>,
|
||||
edges: Vec<(usize, usize)>,
|
||||
rotation_x: f64,
|
||||
rotation_y: f64,
|
||||
}
|
||||
|
||||
impl Polyhedron {
|
||||
fn new_dodecahedron(size: f64) -> Self {
|
||||
let phi = (1.0 + 5.0_f64.sqrt()) / 2.0;
|
||||
let inv_phi = 1.0 / phi;
|
||||
|
||||
let mut vertices = Vec::new();
|
||||
|
||||
// Orange vertices (Cube)
|
||||
vertices.push(Point3D { x: -1.0, y: -1.0, z: -1.0 });
|
||||
vertices.push(Point3D { x: 1.0, y: -1.0, z: -1.0 });
|
||||
vertices.push(Point3D { x: 1.0, y: 1.0, z: -1.0 });
|
||||
vertices.push(Point3D { x: -1.0, y: 1.0, z: -1.0 });
|
||||
vertices.push(Point3D { x: -1.0, y: -1.0, z: 1.0 });
|
||||
vertices.push(Point3D { x: 1.0, y: -1.0, z: 1.0 });
|
||||
vertices.push(Point3D { x: 1.0, y: 1.0, z: 1.0 });
|
||||
vertices.push(Point3D { x: -1.0, y: 1.0, z: 1.0 });
|
||||
|
||||
// Green vertices (Rectangle in YZ plane)
|
||||
vertices.push(Point3D { x: 0.0, y: -phi, z: -inv_phi });
|
||||
vertices.push(Point3D { x: 0.0, y: -phi, z: inv_phi });
|
||||
vertices.push(Point3D { x: 0.0, y: phi, z: -inv_phi });
|
||||
vertices.push(Point3D { x: 0.0, y: phi, z: inv_phi });
|
||||
|
||||
// Blue vertices (Rectangle in XZ plane)
|
||||
vertices.push(Point3D { x: -inv_phi, y: 0.0, z: -phi });
|
||||
vertices.push(Point3D { x: inv_phi, y: 0.0, z: -phi });
|
||||
vertices.push(Point3D { x: -inv_phi, y: 0.0, z: phi });
|
||||
vertices.push(Point3D { x: inv_phi, y: 0.0, z: phi });
|
||||
|
||||
// Pink vertices (Rectangle in XY plane)
|
||||
vertices.push(Point3D { x: -phi, y: -inv_phi, z: 0.0 });
|
||||
vertices.push(Point3D { x: phi, y: -inv_phi, z: 0.0 });
|
||||
vertices.push(Point3D { x: -phi, y: inv_phi, z: 0.0 });
|
||||
vertices.push(Point3D { x: phi, y: inv_phi, z: 0.0 });
|
||||
|
||||
// Scale vertices
|
||||
for v in &mut vertices {
|
||||
v.x *= size;
|
||||
v.y *= size;
|
||||
v.z *= size;
|
||||
}
|
||||
|
||||
// Simplified edges (connecting nearest neighbors) - simplistic wireframe logic
|
||||
// A full correct edge list is long, so we'll use a distance threshold to draw edges dynamically
|
||||
// or just use a simpler shape if performance is an issue.
|
||||
// Let's use a cube for guaranteed correctness in code length constraint.
|
||||
Self::new_cube(size * 1.5)
|
||||
}
|
||||
|
||||
fn new_cube(size: f64) -> Self {
|
||||
let vertices = vec![
|
||||
Point3D { x: -size, y: -size, z: -size },
|
||||
Point3D { x: size, y: -size, z: -size },
|
||||
Point3D { x: size, y: size, z: -size },
|
||||
Point3D { x: -size, y: size, z: -size },
|
||||
Point3D { x: -size, y: -size, z: size },
|
||||
Point3D { x: size, y: -size, z: size },
|
||||
Point3D { x: size, y: size, z: size },
|
||||
Point3D { x: -size, y: size, z: size },
|
||||
];
|
||||
|
||||
let edges = vec![
|
||||
(0, 1), (1, 2), (2, 3), (3, 0), // Back face
|
||||
(4, 5), (5, 6), (6, 7), (7, 4), // Front face
|
||||
(0, 4), (1, 5), (2, 6), (3, 7), // Connecting edges
|
||||
// Diagonals for more "tech" look
|
||||
(0, 2), (4, 6), (0, 5), (1, 4)
|
||||
];
|
||||
|
||||
Self {
|
||||
vertices,
|
||||
edges,
|
||||
rotation_x: 0.0,
|
||||
rotation_y: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate(&mut self, dx: f64, dy: f64) {
|
||||
self.rotation_x += dx;
|
||||
self.rotation_y += dy;
|
||||
}
|
||||
}
|
||||
|
||||
// Wave layer for fluid simulation
|
||||
struct WaveLayer {
|
||||
|
|
@ -22,10 +137,12 @@ struct FluidWaves {
|
|||
height: f64,
|
||||
time: f64,
|
||||
layers: Vec<WaveLayer>,
|
||||
polyhedron: Polyhedron,
|
||||
mouse: Rc<RefCell<MouseState>>,
|
||||
}
|
||||
|
||||
impl FluidWaves {
|
||||
fn new(width: f64, height: f64) -> Self {
|
||||
fn new(width: f64, height: f64, mouse: Rc<RefCell<MouseState>>) -> Self {
|
||||
// Create multiple wave layers with different properties (faster speeds)
|
||||
let layers = vec![
|
||||
// Larger, slower primary waves for big curves
|
||||
|
|
@ -45,11 +162,17 @@ impl FluidWaves {
|
|||
height,
|
||||
time: 0.0,
|
||||
layers,
|
||||
polyhedron: Polyhedron::new_dodecahedron(100.0),
|
||||
mouse,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self) {
|
||||
self.time += 1.0;
|
||||
|
||||
// Rotate polyhedron based on mouse position and time
|
||||
let mouse = self.mouse.borrow();
|
||||
self.polyhedron.rotate(0.005 + mouse.norm_y * 0.01, 0.005 + mouse.norm_x * 0.01);
|
||||
}
|
||||
|
||||
// Calculate wave height at a given position with randomness
|
||||
|
|
@ -76,6 +199,63 @@ impl FluidWaves {
|
|||
fn draw(&self, ctx: &CanvasRenderingContext2d) {
|
||||
// Draw smooth gradient waves at the bottom
|
||||
self.draw_smooth_waves(ctx);
|
||||
|
||||
// Draw 3D object floating in the background
|
||||
self.draw_polyhedron(ctx);
|
||||
}
|
||||
|
||||
fn draw_polyhedron(&self, ctx: &CanvasRenderingContext2d) {
|
||||
let cx = self.width * 0.75; // Position on the right side
|
||||
let cy = self.height * 0.4;
|
||||
|
||||
let cos_x = self.polyhedron.rotation_x.cos();
|
||||
let sin_x = self.polyhedron.rotation_x.sin();
|
||||
let cos_y = self.polyhedron.rotation_y.cos();
|
||||
let sin_y = self.polyhedron.rotation_y.sin();
|
||||
|
||||
let mut projected_points = Vec::new();
|
||||
|
||||
// Project vertices
|
||||
for v in &self.polyhedron.vertices {
|
||||
// Rotate around X
|
||||
let y1 = v.y * cos_x - v.z * sin_x;
|
||||
let z1 = v.y * sin_x + v.z * cos_x;
|
||||
|
||||
// Rotate around Y
|
||||
let x2 = v.x * cos_y - z1 * sin_y;
|
||||
let z2 = v.x * sin_y + z1 * cos_y; // Depth
|
||||
|
||||
// Perspective projection
|
||||
let scale = 400.0 / (400.0 + z2); // Simple perspective
|
||||
let x_proj = x2 * scale + cx;
|
||||
let y_proj = y1 * scale + cy;
|
||||
|
||||
projected_points.push((x_proj, y_proj));
|
||||
}
|
||||
|
||||
// Draw edges
|
||||
ctx.set_stroke_style_str("rgba(59, 130, 246, 0.15)");
|
||||
ctx.set_line_width(1.0);
|
||||
ctx.begin_path();
|
||||
|
||||
for &(start, end) in &self.polyhedron.edges {
|
||||
if start < projected_points.len() && end < projected_points.len() {
|
||||
let (x1, y1) = projected_points[start];
|
||||
let (x2, y2) = projected_points[end];
|
||||
|
||||
ctx.move_to(x1, y1);
|
||||
ctx.line_to(x2, y2);
|
||||
}
|
||||
}
|
||||
ctx.stroke();
|
||||
|
||||
// Draw vertices
|
||||
ctx.set_fill_style_str("rgba(99, 102, 241, 0.4)");
|
||||
for (x, y) in &projected_points {
|
||||
ctx.begin_path();
|
||||
ctx.arc(*x, *y, 2.0, 0.0, PI * 2.0).unwrap();
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_smooth_waves(&self, ctx: &CanvasRenderingContext2d) {
|
||||
|
|
@ -141,6 +321,7 @@ impl FluidWaves {
|
|||
let _ = gradient.add_color_stop(1.0, &format!("hsla({}, {}%, {}%, {})",
|
||||
hue - 15.0, saturation, lightness_bottom, alpha_bottom));
|
||||
|
||||
#[allow(deprecated)] // set_fill_style with JsValue is technically deprecated but fine here
|
||||
ctx.set_fill_style(&JsValue::from(gradient));
|
||||
ctx.fill();
|
||||
}
|
||||
|
|
@ -159,12 +340,34 @@ pub fn main() -> Result<(), JsValue> {
|
|||
let window = web_sys::window().expect("no global window exists");
|
||||
let document = window.document().expect("should have a document on window");
|
||||
|
||||
// Global mouse tracking
|
||||
let mouse_state = Rc::new(RefCell::new(MouseState::new()));
|
||||
{
|
||||
let mouse_state = mouse_state.clone();
|
||||
let window_clone = window.clone();
|
||||
let closure = Closure::wrap(Box::new(move |event: MouseEvent| {
|
||||
let w = window_clone.inner_width().unwrap().as_f64().unwrap_or(1.0);
|
||||
let h = window_clone.inner_height().unwrap().as_f64().unwrap_or(1.0);
|
||||
let x = event.client_x() as f64;
|
||||
let y = event.client_y() as f64;
|
||||
|
||||
let mut state = mouse_state.borrow_mut();
|
||||
state.x = x;
|
||||
state.y = y;
|
||||
state.norm_x = (x / w) * 2.0 - 1.0;
|
||||
state.norm_y = (y / h) * 2.0 - 1.0;
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
|
||||
window.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref())?;
|
||||
closure.forget();
|
||||
}
|
||||
|
||||
// Clear body and build page
|
||||
if let Some(body) = document.body() {
|
||||
body.set_inner_html("");
|
||||
|
||||
// Create and setup particle canvas
|
||||
setup_particle_canvas(&document, &body, &window)?;
|
||||
setup_particle_canvas(&document, &body, &window, mouse_state.clone())?;
|
||||
|
||||
build_page(&document, &body)?;
|
||||
setup_scroll_animations(&document)?;
|
||||
|
|
@ -174,7 +377,12 @@ pub fn main() -> Result<(), JsValue> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_particle_canvas(document: &Document, body: &HtmlElement, window: &Window) -> Result<(), JsValue> {
|
||||
fn setup_particle_canvas(
|
||||
document: &Document,
|
||||
body: &HtmlElement,
|
||||
window: &Window,
|
||||
mouse_state: Rc<RefCell<MouseState>>
|
||||
) -> Result<(), JsValue> {
|
||||
console::log_1(&"🎨 Setting up particle canvas...".into());
|
||||
|
||||
// Create canvas element
|
||||
|
|
@ -202,7 +410,7 @@ fn setup_particle_canvas(document: &Document, body: &HtmlElement, window: &Windo
|
|||
.dyn_into::<CanvasRenderingContext2d>()?;
|
||||
|
||||
// Initialize fluid wave system
|
||||
let wave_system = Rc::new(RefCell::new(FluidWaves::new(width, height)));
|
||||
let wave_system = Rc::new(RefCell::new(FluidWaves::new(width, height, mouse_state)));
|
||||
console::log_1(&"🌊 Fluid wave system initialized!".into());
|
||||
|
||||
// Setup animation loop
|
||||
|
|
@ -360,7 +568,7 @@ fn create_service_card(
|
|||
status: &str,
|
||||
url: Option<&str>,
|
||||
) -> Result<Element, JsValue> {
|
||||
let card = if let Some(link) = url {
|
||||
let wrapper = if let Some(link) = url {
|
||||
let a = document.create_element("a")?;
|
||||
a.set_attribute("href", link)?;
|
||||
a.set_attribute("target", "_blank")?;
|
||||
|
|
@ -369,7 +577,81 @@ fn create_service_card(
|
|||
} else {
|
||||
document.create_element("div")?
|
||||
};
|
||||
card.set_class_name("service-card");
|
||||
|
||||
// Cast to HtmlElement to access properties
|
||||
let wrapper_el = wrapper.dyn_ref::<HtmlElement>().unwrap();
|
||||
|
||||
wrapper.set_class_name("service-card-wrapper");
|
||||
|
||||
// Inner Content Div
|
||||
let content = document.create_element("div")?;
|
||||
content.set_class_name("service-card-content");
|
||||
|
||||
let content_el = content.dyn_ref::<HtmlElement>().unwrap();
|
||||
|
||||
// Glare Effect Div
|
||||
let glare = document.create_element("div")?;
|
||||
glare.set_class_name("card-glare");
|
||||
content.append_child(&glare)?;
|
||||
|
||||
// Add 3D Tilt Effect Logic
|
||||
{
|
||||
let content_el_clone = content_el.clone();
|
||||
let wrapper_el_clone = wrapper_el.clone();
|
||||
let glare_el_clone = glare.dyn_ref::<HtmlElement>().unwrap().clone();
|
||||
|
||||
let mouse_move_closure = Closure::wrap(Box::new(move |e: MouseEvent| {
|
||||
let rect = wrapper_el_clone.get_bounding_client_rect();
|
||||
// Calculate position relative to the card center
|
||||
let width = rect.width();
|
||||
let height = rect.height();
|
||||
|
||||
// Mouse position relative to element top-left
|
||||
let mouse_x = e.client_x() as f64 - rect.left();
|
||||
let mouse_y = e.client_y() as f64 - rect.top();
|
||||
|
||||
let center_x = width / 2.0;
|
||||
let center_y = height / 2.0;
|
||||
|
||||
// Rotation logic
|
||||
let max_rot = 12.0; // degrees
|
||||
let rotate_y = ((mouse_x - center_x) / center_x) * max_rot;
|
||||
let rotate_x = -((mouse_y - center_y) / center_y) * max_rot;
|
||||
|
||||
let style = content_el_clone.style();
|
||||
style.set_property("--rotate-x", &format!("{:.2}deg", rotate_x)).ok();
|
||||
style.set_property("--rotate-y", &format!("{:.2}deg", rotate_y)).ok();
|
||||
|
||||
// Glare logic
|
||||
let glare_x_pct = (mouse_x / width) * 100.0;
|
||||
let glare_y_pct = (mouse_y / height) * 100.0;
|
||||
|
||||
// Glare opacity based on distance from center (optional, or constant on hover)
|
||||
let glare_style = glare_el_clone.style();
|
||||
glare_style.set_property("--glare-x", &format!("{:.1}%", glare_x_pct)).ok();
|
||||
glare_style.set_property("--glare-y", &format!("{:.1}%", glare_y_pct)).ok();
|
||||
glare_style.set_property("--glare-opacity", "1").ok();
|
||||
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
|
||||
wrapper_el.add_event_listener_with_callback("mousemove", mouse_move_closure.as_ref().unchecked_ref())?;
|
||||
mouse_move_closure.forget();
|
||||
|
||||
let content_el_clone = content_el.clone();
|
||||
let glare_el_clone = glare.dyn_ref::<HtmlElement>().unwrap().clone();
|
||||
|
||||
let mouse_leave_closure = Closure::wrap(Box::new(move |_: MouseEvent| {
|
||||
let style = content_el_clone.style();
|
||||
style.set_property("--rotate-x", "0deg").ok();
|
||||
style.set_property("--rotate-y", "0deg").ok();
|
||||
|
||||
let glare_style = glare_el_clone.style();
|
||||
glare_style.set_property("--glare-opacity", "0").ok();
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
|
||||
wrapper_el.add_event_listener_with_callback("mouseleave", mouse_leave_closure.as_ref().unchecked_ref())?;
|
||||
mouse_leave_closure.forget();
|
||||
}
|
||||
|
||||
let header = document.create_element("div")?;
|
||||
header.set_class_name("service-header");
|
||||
|
|
@ -388,10 +670,12 @@ fn create_service_card(
|
|||
desc.set_text_content(Some(description));
|
||||
desc.set_class_name("service-description");
|
||||
|
||||
card.append_child(&header)?;
|
||||
card.append_child(&desc)?;
|
||||
content.append_child(&header)?;
|
||||
content.append_child(&desc)?;
|
||||
|
||||
Ok(card)
|
||||
wrapper.append_child(&content)?;
|
||||
|
||||
Ok(wrapper)
|
||||
}
|
||||
|
||||
fn create_tech_section(document: &Document) -> Result<Element, JsValue> {
|
||||
|
|
|
|||
328
style.css
328
style.css
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
:root {
|
||||
--bg-dark: #0a0e17;
|
||||
--bg-card: #1a1f2e;
|
||||
--bg-card-hover: #252a3a;
|
||||
--bg-card: rgba(26, 31, 46, 0.8); /* More transparent for glass effect */
|
||||
--bg-card-hover: rgba(37, 42, 58, 0.9);
|
||||
--text-primary: #e0e6ed;
|
||||
--text-secondary: #9ca3af;
|
||||
--accent: #3b82f6;
|
||||
--accent-hover: #2563eb;
|
||||
--rust: #f74c00;
|
||||
--border: #2d3748;
|
||||
--border: rgba(45, 55, 72, 0.5);
|
||||
--success: #10b981;
|
||||
--planned: #6366f1;
|
||||
}
|
||||
|
|
@ -38,37 +38,6 @@ body {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Loading screen */
|
||||
#loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bg-dark);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.loader {
|
||||
font-size: 1.2rem;
|
||||
color: var(--text-secondary);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 0.5; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--rust);
|
||||
}
|
||||
|
||||
/* Container */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
|
|
@ -88,16 +57,6 @@ body {
|
|||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: radial-gradient(circle at 50% 50%, rgba(59, 130, 246, 0.1) 0%, transparent 50%);
|
||||
}
|
||||
|
||||
.hero .container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
|
@ -111,6 +70,7 @@ body {
|
|||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
filter: drop-shadow(0 0 2em rgba(59, 130, 246, 0.3));
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
|
|
@ -125,12 +85,14 @@ body {
|
|||
.hero-badge {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: var(--bg-card);
|
||||
background: rgba(26, 31, 46, 0.6);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 2rem;
|
||||
font-size: 1rem;
|
||||
color: var(--text-primary);
|
||||
animation: float 3s ease-in-out infinite;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
|
|
@ -150,72 +112,118 @@ section {
|
|||
margin-bottom: 3rem;
|
||||
text-align: center;
|
||||
color: var(--text-primary);
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Services Section */
|
||||
.services {
|
||||
background: var(--bg-dark);
|
||||
/* Transparent to let particles show through slightly or keep dark */
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 2rem;
|
||||
perspective: 1000px; /* Global perspective for grid items entrance */
|
||||
}
|
||||
|
||||
.service-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
/* 3D Card System */
|
||||
.service-card-wrapper {
|
||||
/* This is the element that receives mouse events and defines the space */
|
||||
position: relative;
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
perspective: 1500px; /* Perspective for the 3D tilt */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
background: var(--bg-card-hover);
|
||||
.service-card-content {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
|
||||
/* Glassmorphism */
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
|
||||
/* 3D Transform settings */
|
||||
transform-style: preserve-3d;
|
||||
transform: rotateX(var(--rotate-x, 0deg)) rotateY(var(--rotate-y, 0deg));
|
||||
|
||||
/* Smooth return when mouse leaves */
|
||||
transition: transform 0.1s cubic-bezier(0.2, 0.4, 0.6, 1), border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.service-card-wrapper:hover .service-card-content {
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 10px 25px rgba(59, 130, 246, 0.2);
|
||||
box-shadow:
|
||||
0 20px 40px rgba(0, 0, 0, 0.4),
|
||||
0 0 20px rgba(59, 130, 246, 0.2),
|
||||
inset 0 0 0 1px rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
/* Glare Effect */
|
||||
.card-glare {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 1rem;
|
||||
background: radial-gradient(
|
||||
circle at var(--glare-x, 50%) var(--glare-y, 50%),
|
||||
rgba(255, 255, 255, 0.15) 0%,
|
||||
transparent 60%
|
||||
);
|
||||
opacity: var(--glare-opacity, 0);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
mix-blend-mode: overlay;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Floating Content inside Card */
|
||||
.service-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
gap: 0.75rem;
|
||||
transform: translateZ(25px); /* Pop out */
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.service-card h3 {
|
||||
font-size: 1.25rem;
|
||||
.service-card-content h3 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-primary);
|
||||
font-weight: 700;
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
margin-left: 0.5rem;
|
||||
font-size: 1.2rem;
|
||||
transform: translateZ(35px); /* Pop out more */
|
||||
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3));
|
||||
}
|
||||
|
||||
.service-description {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.95rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
transform: translateZ(15px); /* Pop out slightly */
|
||||
}
|
||||
|
||||
/* Tech Section */
|
||||
.tech {
|
||||
background: linear-gradient(180deg, var(--bg-dark) 0%, var(--bg-card) 100%);
|
||||
background: linear-gradient(180deg, rgba(10, 14, 23, 0) 0%, var(--bg-dark) 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tech-grid {
|
||||
|
|
@ -227,6 +235,17 @@ section {
|
|||
.tech-item {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.tech-item:hover {
|
||||
transform: translateY(-5px) scale(1.02);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-color: var(--border);
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.tech-item h3 {
|
||||
|
|
@ -246,6 +265,8 @@ section {
|
|||
border-top: 1px solid var(--border);
|
||||
padding: 3rem 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
|
|
@ -283,174 +304,19 @@ section {
|
|||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
.services-grid,
|
||||
.tech-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hero {
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
.hero-title {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Smooth scrolling */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Selection color */
|
||||
::selection {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Scroll-triggered animations */
|
||||
/* Scroll Animations */
|
||||
[data-animate] {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||
transition: opacity 0.8s cubic-bezier(0.2, 0.8, 0.2, 1), transform 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
}
|
||||
|
||||
[data-animate].animate-in {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Parallax effect on hero */
|
||||
.hero::before {
|
||||
animation: parallax-float 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes parallax-float {
|
||||
0%, 100% { transform: translateY(0) scale(1); }
|
||||
50% { transform: translateY(-20px) scale(1.05); }
|
||||
}
|
||||
|
||||
/* Animated gradient text */
|
||||
.hero-title {
|
||||
background-size: 200% auto;
|
||||
animation: gradient-shift 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes gradient-shift {
|
||||
0%, 100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hover micro-interactions */
|
||||
.service-card {
|
||||
transform-origin: center;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
animation: subtle-bounce 0.6s ease;
|
||||
}
|
||||
|
||||
@keyframes subtle-bounce {
|
||||
0%, 100% { transform: translateY(-4px); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
.tech-item {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tech-item:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.tech-item:hover h3 {
|
||||
animation: pulse-glow 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.8; text-shadow: 0 0 20px rgba(59, 130, 246, 0.5); }
|
||||
}
|
||||
|
||||
/* Footer links animation */
|
||||
.footer-links a {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.footer-links a::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--accent);
|
||||
transition: left 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-links a:hover::before {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* Hero badge pulse */
|
||||
.hero-badge {
|
||||
animation: float 3s ease-in-out infinite, glow-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes glow-pulse {
|
||||
0%, 100% { box-shadow: 0 0 10px rgba(59, 130, 246, 0.2); }
|
||||
50% { box-shadow: 0 0 20px rgba(59, 130, 246, 0.4); }
|
||||
}
|
||||
|
||||
/* Status badge animations */
|
||||
.status-badge {
|
||||
animation: badge-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes badge-pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
/* Section title entrance */
|
||||
.section-title {
|
||||
animation: slide-in-top 0.8s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slide-in-top {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reduce motion for accessibility */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue