Add modern animations and remove admin services
UI/UX improvements: - Removed CI/CD and Container Management from public view (admin-only) - Added scroll-triggered fade-up animations for cards and tech items - Implemented parallax floating effect on hero background - Added animated gradient shift on title text - Created micro-interactions for all interactive elements Animation features: - Service cards: Subtle bounce on hover - Tech items: Scale + pulsing glow effect - Footer links: Animated underline slide-in - Hero badge: Floating + glowing pulse - Status badges: Subtle pulse animation - Section titles: Slide-in from top entrance Technical implementation: - Built IntersectionObserver in Rust for scroll animations - Added js-sys and DomTokenList web-sys features - CSS animations following 2024-2025 trends - Accessibility: Respects prefers-reduced-motion - Performance: GPU-accelerated transforms - Staggered delays for cascade effect Bundle size: ~25KB WASM + 10KB JS (still optimized)
This commit is contained in:
parent
15a18e45b4
commit
4d44e470cc
|
|
@ -9,6 +9,7 @@ crate-type = ["cdylib"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasm-bindgen = "0.2.104"
|
wasm-bindgen = "0.2.104"
|
||||||
wasm-bindgen-futures = "0.4.54"
|
wasm-bindgen-futures = "0.4.54"
|
||||||
|
js-sys = "0.3.81"
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3.81"
|
version = "0.3.81"
|
||||||
|
|
@ -20,6 +21,12 @@ features = [
|
||||||
"Node",
|
"Node",
|
||||||
"Window",
|
"Window",
|
||||||
"Location",
|
"Location",
|
||||||
|
"IntersectionObserver",
|
||||||
|
"IntersectionObserverEntry",
|
||||||
|
"IntersectionObserverInit",
|
||||||
|
"DomRect",
|
||||||
|
"NodeList",
|
||||||
|
"DomTokenList",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ export interface InitOutput {
|
||||||
readonly memory: WebAssembly.Memory;
|
readonly memory: WebAssembly.Memory;
|
||||||
readonly main: () => void;
|
readonly main: () => void;
|
||||||
readonly __wbindgen_export_0: (a: number) => void;
|
readonly __wbindgen_export_0: (a: number) => void;
|
||||||
|
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, c: number, d: number) => void;
|
||||||
readonly __wbindgen_start: () => void;
|
readonly __wbindgen_start: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,29 +6,6 @@ heap.push(undefined, null, true, false);
|
||||||
|
|
||||||
function getObject(idx) { return heap[idx]; }
|
function getObject(idx) { return heap[idx]; }
|
||||||
|
|
||||||
let heap_next = heap.length;
|
|
||||||
|
|
||||||
function addHeapObject(obj) {
|
|
||||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
|
||||||
const idx = heap_next;
|
|
||||||
heap_next = heap[idx];
|
|
||||||
|
|
||||||
heap[idx] = obj;
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleError(f, args) {
|
|
||||||
try {
|
|
||||||
return f.apply(this, args);
|
|
||||||
} catch (e) {
|
|
||||||
wasm.__wbindgen_export_0(addHeapObject(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isLikeNone(x) {
|
|
||||||
return x === undefined || x === null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cachedUint8ArrayMemory0 = null;
|
let cachedUint8ArrayMemory0 = null;
|
||||||
|
|
||||||
function getUint8ArrayMemory0() {
|
function getUint8ArrayMemory0() {
|
||||||
|
|
@ -59,6 +36,92 @@ function getStringFromWasm0(ptr, len) {
|
||||||
return decodeText(ptr, len);
|
return decodeText(ptr, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let heap_next = heap.length;
|
||||||
|
|
||||||
|
function addHeapObject(obj) {
|
||||||
|
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||||
|
const idx = heap_next;
|
||||||
|
heap_next = heap[idx];
|
||||||
|
|
||||||
|
heap[idx] = obj;
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(f, args) {
|
||||||
|
try {
|
||||||
|
return f.apply(this, args);
|
||||||
|
} catch (e) {
|
||||||
|
wasm.__wbindgen_export_0(addHeapObject(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLikeNone(x) {
|
||||||
|
return x === undefined || x === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let WASM_VECTOR_LEN = 0;
|
||||||
|
|
||||||
|
const cachedTextEncoder = new TextEncoder();
|
||||||
|
|
||||||
|
if (!('encodeInto' in cachedTextEncoder)) {
|
||||||
|
cachedTextEncoder.encodeInto = function (arg, view) {
|
||||||
|
const buf = cachedTextEncoder.encode(arg);
|
||||||
|
view.set(buf);
|
||||||
|
return {
|
||||||
|
read: arg.length,
|
||||||
|
written: buf.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function passStringToWasm0(arg, malloc, realloc) {
|
||||||
|
|
||||||
|
if (realloc === undefined) {
|
||||||
|
const buf = cachedTextEncoder.encode(arg);
|
||||||
|
const ptr = malloc(buf.length, 1) >>> 0;
|
||||||
|
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||||
|
WASM_VECTOR_LEN = buf.length;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let len = arg.length;
|
||||||
|
let ptr = malloc(len, 1) >>> 0;
|
||||||
|
|
||||||
|
const mem = getUint8ArrayMemory0();
|
||||||
|
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
for (; offset < len; offset++) {
|
||||||
|
const code = arg.charCodeAt(offset);
|
||||||
|
if (code > 0x7F) break;
|
||||||
|
mem[ptr + offset] = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset !== len) {
|
||||||
|
if (offset !== 0) {
|
||||||
|
arg = arg.slice(offset);
|
||||||
|
}
|
||||||
|
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
||||||
|
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
||||||
|
const ret = cachedTextEncoder.encodeInto(arg, view);
|
||||||
|
|
||||||
|
offset += ret.written;
|
||||||
|
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
WASM_VECTOR_LEN = offset;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedDataViewMemory0 = null;
|
||||||
|
|
||||||
|
function getDataViewMemory0() {
|
||||||
|
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
||||||
|
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachedDataViewMemory0;
|
||||||
|
}
|
||||||
|
|
||||||
function dropObject(idx) {
|
function dropObject(idx) {
|
||||||
if (idx < 132) return;
|
if (idx < 132) return;
|
||||||
heap[idx] = heap_next;
|
heap[idx] = heap_next;
|
||||||
|
|
@ -71,10 +134,48 @@ function takeObject(idx) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
|
||||||
|
? { register: () => {}, unregister: () => {} }
|
||||||
|
: new FinalizationRegistry(
|
||||||
|
state => {
|
||||||
|
wasm.__wbindgen_export_3.get(state.dtor)(state.a, state.b);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||||
|
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||||
|
const real = (...args) => {
|
||||||
|
|
||||||
|
// First up with a closure we increment the internal reference
|
||||||
|
// count. This ensures that the Rust closure environment won't
|
||||||
|
// be deallocated while we're invoking it.
|
||||||
|
state.cnt++;
|
||||||
|
const a = state.a;
|
||||||
|
state.a = 0;
|
||||||
|
try {
|
||||||
|
return f(a, state.b, ...args);
|
||||||
|
} finally {
|
||||||
|
if (--state.cnt === 0) {
|
||||||
|
wasm.__wbindgen_export_3.get(state.dtor)(a, state.b);
|
||||||
|
CLOSURE_DTORS.unregister(state);
|
||||||
|
} else {
|
||||||
|
state.a = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
real.original = state;
|
||||||
|
CLOSURE_DTORS.register(real, state, state);
|
||||||
|
return real;
|
||||||
|
}
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
wasm.main();
|
wasm.main();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function __wbg_adapter_4(arg0, arg1, arg2, arg3) {
|
||||||
|
wasm.__wbindgen_export_4(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
||||||
|
}
|
||||||
|
|
||||||
const EXPECTED_RESPONSE_TYPES = new Set(['basic', 'cors', 'default']);
|
const EXPECTED_RESPONSE_TYPES = new Set(['basic', 'cors', 'default']);
|
||||||
|
|
||||||
async function __wbg_load(module, imports) {
|
async function __wbg_load(module, imports) {
|
||||||
|
|
@ -113,6 +214,9 @@ async function __wbg_load(module, imports) {
|
||||||
function __wbg_get_imports() {
|
function __wbg_get_imports() {
|
||||||
const imports = {};
|
const imports = {};
|
||||||
imports.wbg = {};
|
imports.wbg = {};
|
||||||
|
imports.wbg.__wbg_add_4e0283c00f7ecabe = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
|
getObject(arg0).add(getStringFromWasm0(arg1, arg2));
|
||||||
|
}, arguments) };
|
||||||
imports.wbg.__wbg_appendChild_87a6cc0aeb132c06 = function() { return handleError(function (arg0, arg1) {
|
imports.wbg.__wbg_appendChild_87a6cc0aeb132c06 = function() { return handleError(function (arg0, arg1) {
|
||||||
const ret = getObject(arg0).appendChild(getObject(arg1));
|
const ret = getObject(arg0).appendChild(getObject(arg1));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
|
|
@ -125,6 +229,10 @@ function __wbg_get_imports() {
|
||||||
const ret = getObject(arg0).call(getObject(arg1));
|
const ret = getObject(arg0).call(getObject(arg1));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_classList_61149e0de7c668c5 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).classList;
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
imports.wbg.__wbg_createElement_4909dfa2011f2abe = function() { return handleError(function (arg0, arg1, arg2) {
|
imports.wbg.__wbg_createElement_4909dfa2011f2abe = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
const ret = getObject(arg0).createElement(getStringFromWasm0(arg1, arg2));
|
const ret = getObject(arg0).createElement(getStringFromWasm0(arg1, arg2));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
|
|
@ -133,6 +241,37 @@ function __wbg_get_imports() {
|
||||||
const ret = getObject(arg0).document;
|
const ret = getObject(arg0).document;
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbg_getAttribute_8bfaf67e99ed2ee3 = function(arg0, arg1, arg2, arg3) {
|
||||||
|
const ret = getObject(arg1).getAttribute(getStringFromWasm0(arg2, arg3));
|
||||||
|
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_export_1, wasm.__wbindgen_export_2);
|
||||||
|
var len1 = WASM_VECTOR_LEN;
|
||||||
|
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||||
|
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_get_0da715ceaecea5c8 = function(arg0, arg1) {
|
||||||
|
const ret = getObject(arg0)[arg1 >>> 0];
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_instanceof_Element_162e4334c7d6f450 = function(arg0) {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = getObject(arg0) instanceof Element;
|
||||||
|
} catch (_) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
const ret = result;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_instanceof_IntersectionObserverEntry_819e56422a481344 = function(arg0) {
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = getObject(arg0) instanceof IntersectionObserverEntry;
|
||||||
|
} catch (_) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
const ret = result;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
imports.wbg.__wbg_instanceof_Window_12d20d558ef92592 = function(arg0) {
|
imports.wbg.__wbg_instanceof_Window_12d20d558ef92592 = function(arg0) {
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
|
|
@ -143,13 +282,44 @@ function __wbg_get_imports() {
|
||||||
const ret = result;
|
const ret = result;
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbg_isIntersecting_31dfa252ee048a6f = function(arg0) {
|
||||||
|
const ret = getObject(arg0).isIntersecting;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_item_e5c3452334bca83f = function(arg0, arg1) {
|
||||||
|
const ret = getObject(arg0).item(arg1 >>> 0);
|
||||||
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_length_186546c51cd61acd = function(arg0) {
|
||||||
|
const ret = getObject(arg0).length;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_length_e7f4a6e30ea139e7 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).length;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
imports.wbg.__wbg_log_6c7b5f4f00b8ce3f = function(arg0) {
|
imports.wbg.__wbg_log_6c7b5f4f00b8ce3f = function(arg0) {
|
||||||
console.log(getObject(arg0));
|
console.log(getObject(arg0));
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbg_new_19c25a3f2fa63a02 = function() {
|
||||||
|
const ret = new Object();
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
imports.wbg.__wbg_newnoargs_254190557c45b4ec = function(arg0, arg1) {
|
imports.wbg.__wbg_newnoargs_254190557c45b4ec = function(arg0, arg1) {
|
||||||
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbg_newwithoptions_f6e0820321465a5c = function() { return handleError(function (arg0, arg1) {
|
||||||
|
const ret = new IntersectionObserver(getObject(arg0), getObject(arg1));
|
||||||
|
return addHeapObject(ret);
|
||||||
|
}, arguments) };
|
||||||
|
imports.wbg.__wbg_observe_d5620e0d99e20a09 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).observe(getObject(arg1));
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_querySelectorAll_71b924f0e83d096b = function() { return handleError(function (arg0, arg1, arg2) {
|
||||||
|
const ret = getObject(arg0).querySelectorAll(getStringFromWasm0(arg1, arg2));
|
||||||
|
return addHeapObject(ret);
|
||||||
|
}, arguments) };
|
||||||
imports.wbg.__wbg_setAttribute_d1baf9023ad5696f = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
imports.wbg.__wbg_setAttribute_d1baf9023ad5696f = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||||
getObject(arg0).setAttribute(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
getObject(arg0).setAttribute(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||||
}, arguments) };
|
}, arguments) };
|
||||||
|
|
@ -162,6 +332,9 @@ function __wbg_get_imports() {
|
||||||
imports.wbg.__wbg_settextContent_b55fe2f5f1399466 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbg_settextContent_b55fe2f5f1399466 = function(arg0, arg1, arg2) {
|
||||||
getObject(arg0).textContent = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2);
|
getObject(arg0).textContent = arg1 === 0 ? undefined : getStringFromWasm0(arg1, arg2);
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbg_setthreshold_7daca4126268ea47 = function(arg0, arg1) {
|
||||||
|
getObject(arg0).threshold = getObject(arg1);
|
||||||
|
};
|
||||||
imports.wbg.__wbg_static_accessor_GLOBAL_8921f820c2ce3f12 = function() {
|
imports.wbg.__wbg_static_accessor_GLOBAL_8921f820c2ce3f12 = function() {
|
||||||
const ret = typeof global === 'undefined' ? null : global;
|
const ret = typeof global === 'undefined' ? null : global;
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
|
|
@ -178,6 +351,19 @@ function __wbg_get_imports() {
|
||||||
const ret = typeof window === 'undefined' ? null : window;
|
const ret = typeof window === 'undefined' ? null : window;
|
||||||
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbg_target_161cb00cc3daf872 = function(arg0) {
|
||||||
|
const ret = getObject(arg0).target;
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbg_wbindgencbdrop_eb10308566512b88 = function(arg0) {
|
||||||
|
const obj = getObject(arg0).original;
|
||||||
|
if (obj.cnt-- == 1) {
|
||||||
|
obj.a = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const ret = false;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
imports.wbg.__wbg_wbindgenisundefined_c4b71d073b92f3c5 = function(arg0) {
|
imports.wbg.__wbg_wbindgenisundefined_c4b71d073b92f3c5 = function(arg0) {
|
||||||
const ret = getObject(arg0) === undefined;
|
const ret = getObject(arg0) === undefined;
|
||||||
return ret;
|
return ret;
|
||||||
|
|
@ -193,6 +379,16 @@ function __wbg_get_imports() {
|
||||||
const ret = getStringFromWasm0(arg0, arg1);
|
const ret = getStringFromWasm0(arg0, arg1);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
imports.wbg.__wbindgen_cast_d6cd19b81560fd6e = function(arg0) {
|
||||||
|
// Cast intrinsic for `F64 -> Externref`.
|
||||||
|
const ret = arg0;
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
|
imports.wbg.__wbindgen_cast_fe9164a03cdb0b6b = function(arg0, arg1) {
|
||||||
|
// Cast intrinsic for `Closure(Closure { dtor_idx: 1, function: Function { arguments: [NamedExternref("Array<any>"), NamedExternref("IntersectionObserver")], shim_idx: 2, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||||
|
const ret = makeMutClosure(arg0, arg1, 1, __wbg_adapter_4);
|
||||||
|
return addHeapObject(ret);
|
||||||
|
};
|
||||||
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
||||||
const ret = getObject(arg0);
|
const ret = getObject(arg0);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
|
|
@ -211,6 +407,7 @@ function __wbg_init_memory(imports, memory) {
|
||||||
function __wbg_finalize_init(instance, module) {
|
function __wbg_finalize_init(instance, module) {
|
||||||
wasm = instance.exports;
|
wasm = instance.exports;
|
||||||
__wbg_init.__wbindgen_wasm_module = module;
|
__wbg_init.__wbindgen_wasm_module = module;
|
||||||
|
cachedDataViewMemory0 = null;
|
||||||
cachedUint8ArrayMemory0 = null;
|
cachedUint8ArrayMemory0 = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -3,4 +3,8 @@
|
||||||
export const memory: WebAssembly.Memory;
|
export const memory: WebAssembly.Memory;
|
||||||
export const main: () => void;
|
export const main: () => void;
|
||||||
export const __wbindgen_export_0: (a: number) => void;
|
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, c: number, d: number) => void;
|
||||||
export const __wbindgen_start: () => void;
|
export const __wbindgen_start: () => void;
|
||||||
|
|
|
||||||
|
|
@ -159,6 +159,8 @@ section {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
display: block;
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-card:hover {
|
.service-card:hover {
|
||||||
|
|
@ -173,12 +175,15 @@ section {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-card h3 {
|
.service-card h3 {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
word-break: break-word;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
|
|
@ -191,6 +196,8 @@ section {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tech Section */
|
/* Tech Section */
|
||||||
|
|
@ -297,3 +304,140 @@ html {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scroll-triggered animations */
|
||||||
|
[data-animate] {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
62
src/lib.rs
62
src/lib.rs
|
|
@ -1,5 +1,6 @@
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use web_sys::{console, Document, Element, HtmlElement, Window};
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::{console, Document, Element, HtmlElement, Window, IntersectionObserver, IntersectionObserverEntry, IntersectionObserverInit};
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
#[wasm_bindgen(start)]
|
||||||
pub fn main() -> Result<(), JsValue> {
|
pub fn main() -> Result<(), JsValue> {
|
||||||
|
|
@ -12,6 +13,7 @@ pub fn main() -> Result<(), JsValue> {
|
||||||
if let Some(body) = document.body() {
|
if let Some(body) = document.body() {
|
||||||
body.set_inner_html("");
|
body.set_inner_html("");
|
||||||
build_page(&document, &body)?;
|
build_page(&document, &body)?;
|
||||||
|
setup_scroll_animations(&document)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
console::log_1(&"✅ Landing page ready!".into());
|
console::log_1(&"✅ Landing page ready!".into());
|
||||||
|
|
@ -83,12 +85,12 @@ fn create_services_section(document: &Document) -> Result<Element, JsValue> {
|
||||||
// Live services
|
// Live services
|
||||||
let services_live = vec![
|
let services_live = vec![
|
||||||
("Git Repository", "Gitea - Self-hosted Git server with web UI", "🔴", "https://git.dc.railwayka.ru"),
|
("Git Repository", "Gitea - Self-hosted Git server with web UI", "🔴", "https://git.dc.railwayka.ru"),
|
||||||
("CI/CD", "Woodpecker CI - Automated builds and deployments", "🔴", "https://cicd.dc.railwayka.ru"),
|
|
||||||
("Container Management", "Portainer - Docker management interface", "🔴", "https://port.dc.railwayka.ru"),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
for (name, desc, status, url) in services_live {
|
for (i, (name, desc, status, url)) in services_live.iter().enumerate() {
|
||||||
let card = create_service_card(document, name, desc, status, Some(url))?;
|
let card = create_service_card(document, name, desc, status, Some(url))?;
|
||||||
|
card.set_attribute("data-animate", "fade-up")?;
|
||||||
|
card.set_attribute("data-delay", &format!("{}", i * 100))?;
|
||||||
grid.append_child(&card)?;
|
grid.append_child(&card)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,8 +103,10 @@ fn create_services_section(document: &Document) -> Result<Element, JsValue> {
|
||||||
("Monitoring", "Grafana + Prometheus - Infrastructure monitoring", "🔵"),
|
("Monitoring", "Grafana + Prometheus - Infrastructure monitoring", "🔵"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (name, desc, status) in services_planned {
|
for (i, (name, desc, status)) in services_planned.iter().enumerate() {
|
||||||
let card = create_service_card(document, name, desc, status, None)?;
|
let card = create_service_card(document, name, desc, status, None)?;
|
||||||
|
card.set_attribute("data-animate", "fade-up")?;
|
||||||
|
card.set_attribute("data-delay", &format!("{}", (i + services_live.len()) * 100))?;
|
||||||
grid.append_child(&card)?;
|
grid.append_child(&card)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,8 +181,10 @@ fn create_tech_section(document: &Document) -> Result<Element, JsValue> {
|
||||||
("Rust + WASM", "High-performance web applications"),
|
("Rust + WASM", "High-performance web applications"),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (name, desc) in technologies {
|
for (i, (name, desc)) in technologies.iter().enumerate() {
|
||||||
let item = create_tech_item(document, name, desc)?;
|
let item = create_tech_item(document, name, desc)?;
|
||||||
|
item.set_attribute("data-animate", "fade-up")?;
|
||||||
|
item.set_attribute("data-delay", &format!("{}", i * 80))?;
|
||||||
grid.append_child(&item)?;
|
grid.append_child(&item)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,3 +244,47 @@ fn create_footer(document: &Document) -> Result<Element, JsValue> {
|
||||||
|
|
||||||
Ok(footer)
|
Ok(footer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_scroll_animations(document: &Document) -> Result<(), JsValue> {
|
||||||
|
// Create intersection observer callback
|
||||||
|
let callback = Closure::wrap(Box::new(move |entries: js_sys::Array, _observer: IntersectionObserver| {
|
||||||
|
for entry in entries.iter() {
|
||||||
|
if let Ok(entry) = entry.dyn_into::<IntersectionObserverEntry>() {
|
||||||
|
if entry.is_intersecting() {
|
||||||
|
if let Some(target) = entry.target().dyn_ref::<Element>() {
|
||||||
|
let delay = target.get_attribute("data-delay")
|
||||||
|
.and_then(|d| d.parse::<u32>().ok())
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
target.class_list().add_1("animate-in").ok();
|
||||||
|
target.set_attribute("style", &format!("animation-delay: {}ms", delay)).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) as Box<dyn FnMut(js_sys::Array, IntersectionObserver)>);
|
||||||
|
|
||||||
|
// Create observer with options
|
||||||
|
let mut options = IntersectionObserverInit::new();
|
||||||
|
options.set_threshold(&JsValue::from_f64(0.1));
|
||||||
|
|
||||||
|
let observer = IntersectionObserver::new_with_options(
|
||||||
|
callback.as_ref().unchecked_ref(),
|
||||||
|
&options,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Observe all elements with data-animate attribute
|
||||||
|
let elements = document.query_selector_all("[data-animate]")?;
|
||||||
|
for i in 0..elements.length() {
|
||||||
|
if let Some(node) = elements.item(i) {
|
||||||
|
if let Some(element) = node.dyn_ref::<Element>() {
|
||||||
|
observer.observe(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent callback from being dropped
|
||||||
|
callback.forget();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
144
style.css
144
style.css
|
|
@ -159,6 +159,8 @@ section {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
display: block;
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-card:hover {
|
.service-card:hover {
|
||||||
|
|
@ -173,12 +175,15 @@ section {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-card h3 {
|
.service-card h3 {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
word-break: break-word;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
|
|
@ -191,6 +196,8 @@ section {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tech Section */
|
/* Tech Section */
|
||||||
|
|
@ -297,3 +304,140 @@ html {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scroll-triggered animations */
|
||||||
|
[data-animate] {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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