Skip to content
LisvindanuPublic

About

Portofolio 3 Reload — README-nya dungeon crawl Tartarus yang bisa dimainkan. Portfolio bertema Persona 3 Reload, Go + React 19, nol dependency UI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

anaphygon-persona — Quickshell, ported to the browser

Go React TypeScript Vite UI deps

P3R — Portofolio 3 Reload

Portfolio bergaya Persona 3 Reload — port dari Persona-Quickshell (QML) ke browser.

Tanpa router, tanpa animation library, tanpa CSS framework. Semua panel dibaca ulang dari QML sumbernya, bukan dijiplak dari screenshot.


▶   Buka portfolio-nya   ◀

Repo ini isinya cuma tulisan ini. Kodenya privat — kalau kamu perlu lihat, tinggal bilang.


⛧ TARTARUS

Dokumentasi teknis portfolio ini tidak ditulis sebagai daftar. Dia ditaruh di dalam menara.

Cara main: klik ▸ untuk masuk ruangan. Tiap lantai dijaga satu Shadow. Pilih satu serangan — kalau salah kamu kena Repel (dan tetap dapat penjelasan kenapa salah), kalau benar dapat ONE MORE! dan lantainya menjatuhkan dokumentasi aslinya sebagai loot.

Empat lantai bersih → The Reaper terbuka. Tidak mau main? Ada save point di bawah.

ENTRANCE ── 1F THEBEL ── 2F ARQA ── 3F YABBASHAH ── 4F ADAMAH ── ☠ MONAD


 🔵  1F · THEBEL — The Server Vault   guardian: Cachebound Table

Ruangan sempit, dinding penuh header HTTP yang membeku. Sesuatu berbentuk meja mengambang, halamannya berdetak seperti jantung.

Cachebound Table menghalangi jalan. Dia hanya bisa dijatuhkan oleh strategi cache yang benar.

HP ▓▓▓▓▓▓▓▓▓▓   |   Arcana: The Hermit   |   Weak: ?

Pilih serangan:

 🔥  Agi — semua respons pakai no-cache, aman

MISS. Aman, tapi mahal. Font, favicon, dan bundle ber-hash ikut divalidasi ulang tiap load — padahal tiga-tiganya tidak pernah berubah tanpa ganti nama file. Kamu buang round trip untuk byte yang sudah ada di disk user.

Cachebound Table masih berdiri. Coba lagi.

 ❄️  Bufu — semua di /assets/ dapat immutable

REPEL — kamu kena 40 damage sendiri.

Ini persis bug yang pernah hidup di repo ini. /assets/ tidak hanya berisi file ber-hash Vite; ada juga gambar dan audio yang di-copy apa adanya dari public/. Nama file mereka tidak berubah saat isinya diganti, jadi max-age=31536000, immutable mengunci versi lama di browser user selama setahun dan tidak ada cara memaksa refresh selain rename.

Cachebound Table menyeringai. Coba lagi.

 ⚡  Zio — immutable hanya untuk nama file ber-hash + folder font

⚡ WEAK!  →  ONE MORE!  →  ALL-OUT ATTACK

Cachebound Table hancur. Loot jatuh:

var hashedBundle = regexp.MustCompile(`-[A-Za-z0-9_-]{8,}\.[A-Za-z0-9]+$`)

// Nama file font sudah membawa family + subset-nya, jadi revisi berarti nama baru,
// bukan byte baru di bawah nama lama — jaminan yang sama dengan hash Vite.
func immutableAsset(urlPath string) bool {
	if strings.HasPrefix(urlPath, "/assets/fonts/") {
		return true
	}
	return strings.HasPrefix(urlPath, "/assets/") && hashedBundle.MatchString(path.Base(urlPath))
}

Jalur request (cmd/server/main.go, 116 baris):

flowchart LR
    R([request]) --> A{prefix /api/ ?}
    A -- ya --> API[API handler]
    A -- tidak --> D{DEV_PROXY diisi?}
    D -- ya --> V[proxy ke Vite :5173]
    D -- tidak --> F{file ada di DIST_DIR?}
    F -- ya --> C{immutableAsset ?}
    C -- ya --> I[max-age=31536000, immutable]
    C -- tidak --> N[no-cache]
    F -- tidak --> S[fallback index.html<br/>route client-side selamat saat hard refresh]
Loading

Environment:

Var Default Keterangan
ADDR :8080 Alamat listen
DIST_DIR web/dist Folder hasil build
DEV_PROXY — Kalau diisi, semua non-API di-proxy ke sini

1F THEBEL CLEARED ✓

 🌪  Garu — matikan caching, biar selalu fresh

NULL. Shadow tidak bergeming. "Selalu fresh" itu bukan strategi cache, itu menyerah — dan tetap tidak menjamin apa-apa, karena tanpa header eksplisit browser boleh menebak sendiri lewat heuristik.

Coba lagi.


 🟦  2F · ARQA — The Component Wing   guardian: Maya of Many Renders

Lorong bercabang jadi 21 pintu, tiap pintu satu komponen. Di tengahnya sesuatu terus me-mount dan un-mount dirinya sendiri, dan tiap kali itu terjadi musik di ruangan ini mengulang dari awal.

HP ▓▓▓▓▓▓▓▓▓▓   |   Arcana: The Magician   |   Weak: ?

Musiknya restart tiap kali pindah halaman. Kenapa?

 🔥  Agi — file audionya kekecilan, tinggal di-loop

MISS. Loop mengurus akhir track, bukan awalnya. Yang terjadi di sini adalah elemen audionya sendiri lenyap lalu dibuat ulang — posisi playback ikut hilang bersamanya.

Coba lagi.

 ⚡  Zio — elemen <audio> hidup di dalam Shell, dan Shell ikut unmount saat keluar desktop

⚡ WEAK!  →  ONE MORE!  →  ALL-OUT ATTACK

Benar. Kepemilikan state-nya dinaikkan ke App, di atas percabangan route:

/** Owned here rather than in Shell: leaving the desktop unmounts Shell, and with it
    the audio element and its graph, so the track would restart on every route. */
const TRACK_SRC = '/assets/audio/theme.m4a'

Maya of Many Renders pecah. Peta lantainya terbuka:

cmd/server/main.go          Go net/http — API, static, SPA fallback, cache policy
web/
  index.html                @font-face di-inline + preload scene/​font
  src/
    App.tsx                 route switch, hotkey, kepemilikan audio
    routes.ts               ROUTES + MENU (urutan 01–04 dikunci di sini)
    views/
      Shell.tsx             desktop shell
      MobileShell.tsx       layout mobile — desain sendiri, bukan desktop yang dikecilkan
      Page.tsx              kerangka halaman dalam
      content.tsx           isi Projects / Skills / About / Contact
    components/             21 komponen (tabel di bawah)
    hooks/                  useMediaQuery · usePersonaTime · useSystemInfo
                            useTrackSpectrum · useTransitionRoute
    lib/                    cava · rain · system · time
    styles/tokens.css       satu-satunya sumber warna, easing, tilt, type scale
    data/projects.ts        9 project + tanggal untuk timeline & kalender
Komponen Perannya
Wallpaper 5 layer scene — langit, hujan di canvas, bar bergerak, cava, kaca depan
BarWipe / SlabWipe / BubbleWipe Transisi antar route, port dari P3rTransition.qml
OptionsBars / OptionsPanel Kolom bar hitam + panel miring saat item dipilih
TiltMenu / Marquee / TextMarquee Menu miring dan teks berjalan
Clock / Capsule / CavaVisualizer Jam, kapsul musik, dan spektrum audio-nya
CalendarOverlay Tangga 7 hari, bisa digeser harian, lompat ke tanggal project
Timeline Mode tampilan kedua di /projects
ProjectDetail / Lightbox Halaman detail + galeri per project
CommandPalette ⌘K atau / — semua route dan link
EdgeDock / ResumeList / ResumePanel / SkewPanel Dock tepi dan panel-panel miring

2F ARQA CLEARED ✓

 ❄️  Bufu — React 19 memang begitu, tidak bisa diapa-apakan

NULL. Shadow tertawa. React tidak pernah menghancurkan apa pun tanpa alasan struktural — kalau sesuatu ikut hilang, dia memang dipasang di cabang pohon yang ikut dilepas. Naikkan kepemilikannya.

Coba lagi.


 🟨  3F · YABBASHAH — The Foundry   guardian: Blocking Wraith

Lantai ini terang tapi kosong: teks belum muncul, gambar belum turun. Semuanya sedang antre di belakang satu permintaan ke domain lain.

HP ▓▓▓▓▓▓▓▓▓▓   |   Arcana: The Hanged Man   |   Weak: ?

Font datang dari Google Fonts dan menahan render pertama. Serang:

 🌪  Garu — tambah preconnect ke dua origin Google

MISS — 12 damage. Ini memperbaiki gejalanya, bukan bentuknya. Rantainya tetap: DNS + TLS ke fonts.googleapis.com → CSS → DNS + TLS ke fonts.gstatic.com → woff2. preconnect cuma memotong bagian awalnya; jumlah hop-nya tidak berkurang.

Coba lagi.

 🔥  Agi — self-host woff2, inline @font-face ke index.html, preload font + scene

🔥 WEAK!  →  ONE MORE!  →  ALL-OUT ATTACK

Blocking Wraith menguap. Rantai lintas-origin-nya hilang seluruhnya — dua preconnect dan satu <link> stylesheet dihapus, diganti tiga woff2 lokal (±54 KB) dengan @font-face yang ditanam langsung di <head>.

Dua temuan yang mahal didapat, ditinggalkan di sini:

  • Montserrat itu variable font. Ketahuan karena CSS Google menyajikan URL woff2 latin yang sama persis untuk weight 300/400/600/700. Satu file menutup seluruh rentang: font-weight: 300 700. grep -a fvar pada woff2 tidak bisa dipakai untuk membuktikan ini — tabelnya dikompres Brotli, jadi hasilnya negatif palsu. Verifikasinya di runtime: lebar teks 529.36 / 535.17 / 558.63 px pada weight 300/400/700 membuktikan axis-nya hidup.
  • Gambar yang di-mount React tidak bisa mulai di-fetch sebelum bundle-nya di-parse. <link rel="preload" as="image"> di <head> memindahkan fetch-nya ke waktu parse HTML.

Hasil ukur (bukan estimasi):

Sebelum Sesudah
Request ke fonts.g* 3 + 2 preconnect 0
Font siap setelah rantai lintas-origin 46 / 51 / 60 ms
Scene mulai turun setelah bundle di-parse 12 ms
Cache font pihak ketiga immutable, 1 tahun

Ukuran bundle produksi:

Berkas Mentah Gzip
index.html 3.63 kB —
CSS 53.74 kB 11.52 kB
JS 246.52 kB 78.08 kB

Angka JS itu sudah termasuk React + React DOM. Sisanya — router, transisi, kalender, palette, visualizer — ditulis tangan, jadi tidak ada yang menumpang di atasnya.

3F YABBASHAH CLEARED ✓

 ⚡  Zio — pakai font-display: block biar tidak ada teks yang loncat

REPEL. Kamu menukar layout shift dengan halaman kosong. block menahan teks sampai font turun; yang dipakai di sini swap, supaya teks langsung terbaca dan fontnya menyusul. Lagipula ini masih tidak menyentuh akar masalahnya: request-nya masih ke domain orang lain.

Coba lagi.


 🔴  4F · ADAMAH — Chamber of Tokens   guardian: The Skewed Emperor

Ruangan terakhir sebelum Monad. Semua yang ada di sini miring −20°, dan huruf-huruf di dindingnya terpotong di ujung atas dan bawah.

HP ▓▓▓▓▓▓▓▓▓▓   |   Arcana: The Emperor   |   Weak: ?

Teks yang diputar −30° di dalam bar hitam huruf pertama dan terakhirnya tergunting. Kenapa?

 ❄️  Bufu — teksnya terlalu panjang, meluber ke kanan

MISS. Ini dugaan pertama yang salah waktu bug-nya benar-benar diburu. Probe geometri mengembalikan clippedPx negatif di semua lebar viewport — artinya tidak pernah ada overflow horizontal sama sekali. Arah potongannya bukan yang itu.

Coba lagi.

 🌪  Garu — rotate tidak mengubah kotak layout; jejak vertikal kata jadi ±1.8× font-size-nya

🌪 WEAK!  →  ONE MORE!  →  ALL-OUT ATTACK

Tepat. Kata yang diputar −30° menempati W·sin30 + H·cos30 secara vertikal ≈ 1.8× font-size-nya. Karena rotate itu transform, layout tidak tahu apa-apa — jadi overflow: hidden milik bar menggunting huruf pertama dan terakhir tanpa satu pun peringatan layout. Terukur: tinggi hasil rotasi 65px di dalam bar 52px, lewat 7px di atas dan 7px di bawah.

Perbaikannya bukan angka ajaib, tapi batas kedua terhadap tinggi bar yang jadi wadahnya:

font-size: min(
  clamp(26px, calc(var(--obar-w) * 0.087), 50px),
  calc(var(--obar-h) * 0.5)
);

The Skewed Emperor runtuh dan menjatuhkan tokens.css:

Token Nilai Asal
--c-base · --c-base-deep #0d1a3a · #08153f Data/Colors.qml (Pywal16)
--c-blue · --c-cyan #1a6aff · #7dd4fc idem
--c-yellow · --c-red #ffe700 · #c4001a idem
--e-out-back cubic-bezier(0.34, 1.56, 0.64, 1) Qt OutBack(1.7)
--e-out-back-hard cubic-bezier(0.34, 1.9, 0.64, 1) Qt OutBack(3.0)
--e-in-out-quart cubic-bezier(0.77, 0, 0.175, 1) Qt InOutQuart
--e-out-expo cubic-bezier(0.16, 1, 0.3, 1) Qt OutExpo
--t-wipe 350ms durasi satu slab P3rTransition.qml

Catatan port lain:

  • Panel miring -20deg dengan garis merah 8px di atas — Layers/OptionsList.qml.
  • Teks di dalam elemen miring selalu di-counter-rotate supaya tetap terbaca datar — Layers/Resume.qml.
  • BarWipe = 3 slab, delay 0/100/200ms, tiap slab 350ms masuk → tahan 200ms → 350ms keluar.
  • Di bawah prefers-reduced-motion: reduce, semua durasi kolaps ke 1ms.

4F ADAMAH CLEARED ✓

 🔥  Agi — hapus overflow: hidden dari bar-nya

REPEL — 25 damage. Sekarang hurufnya utuh, tapi bocor keluar bar dan menimpa potret karakter di sebelahnya. overflow: hidden itu bagian dari desainnya, bukan penyebabnya.

Coba lagi.


 ☠️  MONAD — The Reaper   hanya terbuka setelah 4 lantai bersih

Rantai terseret di lantai. Kamu mendengarnya sebelum melihatnya.

The Reaper tidak menanyakan apa pun. Dia cuma bertanya apakah build-mu jalan.

cd web && pnpm build          # → web/dist
go build -o persona-server ./cmd/server
./persona-server              # sajikan dist, fallback index.html

Tanpa DEV_PROXY, server menyajikan DIST_DIR dengan fallback index.html supaya route client-side tetap hidup saat hard refresh.

Status akhir:

TypeScript 3.331 baris, strict, tsc --noEmit bersih
CSS 3.826 baris, tanpa framework
Go 116 baris, hanya net/http
Dependency runtime react, react-dom — habis
Project di katalog 9

MONAD CLEARED ✓ — menara tamat.


🃏 THE PARTY

Empat entri menu shell. Urutannya dikunci di web/src/routes.ts, jadi kalender dan command palette tidak pernah berbeda urutan dari bar-nya.

# Route Role Sebagai
01 /projects WORK Product yang sudah rilis, side quest, dan yang selamat sampai produksi
02 /skills STACK Stack yang dipakai, dan sedalam apa masing-masing
03 /about SELF Asal-usul, alasan membangun, arah berikutnya
04 /contact LINK Mail, GitHub, LinkedIn

💾 SAVE POINT

Buat yang tidak mau masuk menara — versi tiga barisnya:

Portfolio pribadi bertema Persona 3 Reload, di-port dari shell desktop QML ke browser. Server Go 116 baris, frontend React 19 tanpa satu pun dependency UI — router, transisi, kalender, command palette, dan visualizer audionya ditulis tangan semua. Layout mobile-nya desain terpisah, bukan desktop yang dipaksa masuk media query.

Pintasan di dalam aplikasi: ⌘K / Ctrl+K / / membuka command palette.

Repo ini sengaja cuma berisi tulisan. Source-nya privat — kalau kamu butuh baca kodenya (recruiter, review, atau sekadar penasaran), kontak saya dan saya kasih akses.


⚖️ Lisensi aset

Kode portfolio-nya tulisan sendiri. Aset visualnya tidak semuanya.

  • Layout, komponen, transisi, dan tipografi dibangun ulang sebagai CSS — bukan gambar. Yang diambil dari QML sumber adalah nilainya (warna, easing, sudut, durasi), bukan berkasnya.
  • Potret karakter dan beberapa elemen scene turunan dari media Persona 3 Reload — © ATLUS / SEGA. Dipakai untuk portfolio pribadi non-komersial, tidak dilisensikan untuk didistribusikan ulang.
  • Repo sumber (Persona-Quickshell) menyebut MIT di README-nya, tapi tidak ada berkas LICENSE di tree-nya — jadi anggap kodenya MIT dan asetnya tidak.

Ringkasnya: silakan pinjam tekniknya, jangan pinjam gambarnya.


ANAPHYGON  ·  anaphygon.my.id

About

Portofolio 3 Reload — README-nya dungeon crawl Tartarus yang bisa dimainkan. Portfolio bertema Persona 3 Reload, Go + React 19, nol dependency UI.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors