Skip to content // StreakField — signal simulation
const GRID = 128; // flow potential lattice
let phase = 0; // A→B morph, ~14s per epoch
const curl = (x, y) => {
const dx = sample(x + h, y) - sample(x - h, y);
const dy = sample(x, y + h) - sample(x, y - h);
return [dy * FLOW, -dx * FLOW]; // divergence-free: pure swirl
};
for (let i = 0; i < P; i++) {
const [u, v] = curl(px[i], py[i]);
px[i] += (u + vortex.x + scatter.x) * dt * rush;
py[i] += (v + vortex.y + scatter.y) * dt * rush;
// cursor crush: flash teal, kick outward, spring home
if (d2(ptr, i) < R && ptr.speed > MIN) {
flash[i] = 1;
kick(i, ptr, (1 - d2 / R) * ptr.speed * IMPULSE);
}
charge[i] = Math.max(flash[i] *= COOL, trailGlow(px[i], py[i]));
}
// trails: fade pass 4.5% → filaments persist ~25 frames
fade(fbo, 0.045); draw(points, ADDITIVE); composite(fbo, screen);
// StreakField — signal simulation
const GRID = 128; // flow potential lattice
let phase = 0; // A→B morph, ~14s per epoch
const curl = (x, y) => {
const dx = sample(x + h, y) - sample(x - h, y);
const dy = sample(x, y + h) - sample(x, y - h);
return [dy * FLOW, -dx * FLOW]; // divergence-free: pure swirl
};
for (let i = 0; i < P; i++) {
const [u, v] = curl(px[i], py[i]);
px[i] += (u + vortex.x + scatter.x) * dt * rush;
py[i] += (v + vortex.y + scatter.y) * dt * rush;
// cursor crush: flash teal, kick outward, spring home
if (d2(ptr, i) < R && ptr.speed > MIN) {
flash[i] = 1;
kick(i, ptr, (1 - d2 / R) * ptr.speed * IMPULSE);
}
charge[i] = Math.max(flash[i] *= COOL, trailGlow(px[i], py[i]));
}
// trails: fade pass 4.5% → filaments persist ~25 frames
fade(fbo, 0.045); draw(points, ADDITIVE); composite(fbo, screen);
// StreakField — signal simulation
const GRID = 128; // flow potential lattice
let phase = 0; // A→B morph, ~14s per epoch
const curl = (x, y) => {
const dx = sample(x + h, y) - sample(x - h, y);
const dy = sample(x, y + h) - sample(x, y - h);
return [dy * FLOW, -dx * FLOW]; // divergence-free: pure swirl
};
for (let i = 0; i < P; i++) {
const [u, v] = curl(px[i], py[i]);
px[i] += (u + vortex.x + scatter.x) * dt * rush;
py[i] += (v + vortex.y + scatter.y) * dt * rush;
// cursor crush: flash teal, kick outward, spring home
if (d2(ptr, i) < R && ptr.speed > MIN) {
flash[i] = 1;
kick(i, ptr, (1 - d2 / R) * ptr.speed * IMPULSE);
}
charge[i] = Math.max(flash[i] *= COOL, trailGlow(px[i], py[i]));
}
// trails: fade pass 4.5% → filaments persist ~25 frames
fade(fbo, 0.045); draw(points, ADDITIVE); composite(fbo, screen);
// StreakField — signal simulation
const GRID = 128; // flow potential lattice
let phase = 0; // A→B morph, ~14s per epoch
const curl = (x, y) => {
const dx = sample(x + h, y) - sample(x - h, y);
const dy = sample(x, y + h) - sample(x, y - h);
return [dy * FLOW, -dx * FLOW]; // divergence-free: pure swirl
};
for (let i = 0; i < P; i++) {
const [u, v] = curl(px[i], py[i]);
px[i] += (u + vortex.x + scatter.x) * dt * rush;
py[i] += (v + vortex.y + scatter.y) * dt * rush;
// cursor crush: flash teal, kick outward, spring home
if (d2(ptr, i) < R && ptr.speed > MIN) {
flash[i] = 1;
kick(i, ptr, (1 - d2 / R) * ptr.speed * IMPULSE);
}
charge[i] = Math.max(flash[i] *= COOL, trailGlow(px[i], py[i]));
}
// trails: fade pass 4.5% → filaments persist ~25 frames
fade(fbo, 0.045); draw(points, ADDITIVE); composite(fbo, screen);
// StreakField — signal simulation
const GRID = 128; // flow potential lattice
let phase = 0; // A→B morph, ~14s per epoch
const curl = (x, y) => {
const dx = sample(x + h, y) - sample(x - h, y);
const dy = sample(x, y + h) - sample(x, y - h);
return [dy * FLOW, -dx * FLOW]; // divergence-free: pure swirl
};
for (let i = 0; i < P; i++) {
const [u, v] = curl(px[i], py[i]);
px[i] += (u + vortex.x + scatter.x) * dt * rush;
py[i] += (v + vortex.y + scatter.y) * dt * rush;
// cursor crush: flash teal, kick outward, spring home
if (d2(ptr, i) < R && ptr.speed > MIN) {
flash[i] = 1;
kick(i, ptr, (1 - d2 / R) * ptr.speed * IMPULSE);
}
charge[i] = Math.max(flash[i] *= COOL, trailGlow(px[i], py[i]));
}
// trails: fade pass 4.5% → filaments persist ~25 frames
fade(fbo, 0.045); draw(points, ADDITIVE); composite(fbo, screen);
// StreakField — signal simulation
const GRID = 128; // flow potential lattice
let phase = 0; // A→B morph, ~14s per epoch
const curl = (x, y) => {
const dx = sample(x + h, y) - sample(x - h, y);
const dy = sample(x, y + h) - sample(x, y - h);
return [dy * FLOW, -dx * FLOW]; // divergence-free: pure swirl
};
for (let i = 0; i < P; i++) {
const [u, v] = curl(px[i], py[i]);
px[i] += (u + vortex.x + scatter.x) * dt * rush;
py[i] += (v + vortex.y + scatter.y) * dt * rush;
// cursor crush: flash teal, kick outward, spring home
if (d2(ptr, i) < R && ptr.speed > MIN) {
flash[i] = 1;
kick(i, ptr, (1 - d2 / R) * ptr.speed * IMPULSE);
}
charge[i] = Math.max(flash[i] *= COOL, trailGlow(px[i], py[i]));
}
// trails: fade pass 4.5% → filaments persist ~25 frames
fade(fbo, 0.045); draw(points, ADDITIVE); composite(fbo, screen);
// StreakField — signal simulation
const GRID = 128; // flow potential lattice
let phase = 0; // A→B morph, ~14s per epoch
const curl = (x, y) => {
const dx = sample(x + h, y) - sample(x - h, y);
const dy = sample(x, y + h) - sample(x, y - h);
return [dy * FLOW, -dx * FLOW]; // divergence-free: pure swirl
};
for (let i = 0; i < P; i++) {
const [u, v] = curl(px[i], py[i]);
px[i] += (u + vortex.x + scatter.x) * dt * rush;
py[i] += (v + vortex.y + scatter.y) * dt * rush;
// cursor crush: flash teal, kick outward, spring home
if (d2(ptr, i) < R && ptr.speed > MIN) {
flash[i] = 1;
kick(i, ptr, (1 - d2 / R) * ptr.speed * IMPULSE);
}
charge[i] = Math.max(flash[i] *= COOL, trailGlow(px[i], py[i]));
}
// trails: fade pass 4.5% → filaments persist ~25 frames
fade(fbo, 0.045); draw(points, ADDITIVE); composite(fbo, screen);
// StreakField — signal simulation
const GRID = 128; // flow potential lattice
let phase = 0; // A→B morph, ~14s per epoch
const curl = (x, y) => {
const dx = sample(x + h, y) - sample(x - h, y);
const dy = sample(x, y + h) - sample(x, y - h);
return [dy * FLOW, -dx * FLOW]; // divergence-free: pure swirl
};
for (let i = 0; i < P; i++) {
const [u, v] = curl(px[i], py[i]);
px[i] += (u + vortex.x + scatter.x) * dt * rush;
py[i] += (v + vortex.y + scatter.y) * dt * rush;
// cursor crush: flash teal, kick outward, spring home
if (d2(ptr, i) < R && ptr.speed > MIN) {
flash[i] = 1;
kick(i, ptr, (1 - d2 / R) * ptr.speed * IMPULSE);
}
charge[i] = Math.max(flash[i] *= COOL, trailGlow(px[i], py[i]));
}
// trails: fade pass 4.5% → filaments persist ~25 frames
fade(fbo, 0.045); draw(points, ADDITIVE); composite(fbo, screen);
// StreakField — signal simulation
const GRID = 128; // flow potential lattice
let phase = 0; // A→B morph, ~14s per epoch
const curl = (x, y) => {
const dx = sample(x + h, y) - sample(x - h, y);
const dy = sample(x, y + h) - sample(x, y - h);
return [dy * FLOW, -dx * FLOW]; // divergence-free: pure swirl
};
for (let i = 0; i < P; i++) {
const [u, v] = curl(px[i], py[i]);
px[i] += (u + vortex.x + scatter.x) * dt * rush;
py[i] += (v + vortex.y + scatter.y) * dt * rush;
// cursor crush: flash teal, kick outward, spring home
if (d2(ptr, i) < R && ptr.speed > MIN) {
flash[i] = 1;
kick(i, ptr, (1 - d2 / R) * ptr.speed * IMPULSE);
}
charge[i] = Math.max(flash[i] *= COOL, trailGlow(px[i], py[i]));
}
// trails: fade pass 4.5% → filaments persist ~25 frames
fade(fbo, 0.045); draw(points, ADDITIVE); composite(fbo, screen);
VM
2026
CineVote CineVote je kolaborativna platforma za ocjenjivanje i preporučivanje filmova koju sam razvio da pomognem ljubiteljima filma otkriti sadržaj putem glasanja vođenog zajednicom. Osmislio sam i izgradio cijelo korisničko iskustvo od početne ideje do finalne implementacije.
Zadatak
Ljubitelji filmova često se bore da pronađu pouzdane preporuke filmova izvan mainstream algoritama, a postojeće platforme nedostaju značajnog angažmana zajednice oko otkrivanja filmova. Identificirao sam potrebu za demokratičnijim pristupom gdje korisnici mogu aktivno sudjelovati u kuriranju i ocjenjivanju sadržaja za svoje kolege.
01
Filmski večeri ne bi trebali biti bitke. Ljubitelji filmova često se bore da pronađu pouzdane preporuke filmova izvan mainstream algoritama, a postojeće platforme nedostaju značajnog angažmana zajednice oko otkrivanja filmova. Identificirao sam potrebu za demokratičnijim pristupom gdje korisnici mogu aktivno sudjelovati u kuriranju i ocjenjivanju sadržaja za svoje kolege.
02
Demokracija susreće vaš sljedeći binge. Osmislio sam sustav temeljen na glasanju koji prioritizira input zajednice nad algoritmskim preporukama, implementirajući značajke za glasanje u stvarnom vremenu, detaljne rasprave o filmovima i personalizirane popise za gledanje. Fokusirao sam se na stvaranje intuitivnog sučelja koje potiče aktivno sudjelovanje dok održava čista, pristupačna dizajnerska načela kroz cijelo korisničko putovanje.
03
Od wireframe-ova do watch party-ja. Počeo sam s korisničkim istraživanjem i konkurentskom analizom da razumijem probleme u postojećim platformama, zatim sam stvorio wireframe-ove i prototipove da testiram osnovne mehanike glasanja. Razvio sam aplikaciju koristeći moderne web tehnologije, implementirajući responzivne dizajnerske uzorke i optimizirajući za desktop i mobilna iskustva dok sam provodio iterativno korisničko testiranje kroz razvoj.
04
Nula svađa, maksimalna zabava. Platforma uspješno demonstrira kako kuriranje vođeno zajednicom može poboljšati otkrivanje filmova, s glasačkim sustavom koji stvara angažiranije korisničke interakcije od tradicionalnih metoda ocjenjivanja. Stekao sam vrijedno iskustvo u izgradnji kolaborativnih značajki i naučio važne lekcije o balansiranju korisničke autonomije s vođenim iskustvima otkrivanja.
Do Not Touch