Tugas Personal Website

This commit is contained in:
Gray
2026-11-06 21:21:29 +07:00
commit bbcd07213c
26 changed files with 1877 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
import type { Metadata } from "next";
import Script from "next/script";
import { NavExternal } from "../../components/nav";
import HtmlComment from "../../components/html-comment";
import { profile, education, skills, quote } from "../../lib/data";
export const metadata: Metadata = { title: "Tentang Saya | Personal Website" };
// css internal: satu blok <style> (di-hoist react ke <head>) dengan selector id dan class
const internalCss = `
#about-page {
min-height: 100vh;
background: #eaf3f4;
font-family: Georgia, "Times New Roman", serif;
}
.quote {
margin: 0;
padding: 16px 20px;
border-left: 4px solid #9aa9ad;
color: #55666b;
font-style: italic;
}
`;
export default function AboutPage() {
return (
<div id="about-page">
{/* external css: file public/styles.css dimuat lewat link, dipakai untuk nav dan wrapper main */}
<link rel="stylesheet" href="/styles.css" precedence="default" />
{/* internal css: blok style untuk latar halaman dan font (selector #about-page dan .quote) */}
<style href="about-internal" precedence="default">{internalCss}</style>
{/* tailwind css: script cdn, dipakai untuk komponen badge di bagian biodata */}
<Script src="https://cdn.tailwindcss.com" />
<HtmlComment text="about.html memakai 4 metode css sekaligus, tiap metode diberi label komentar di elemennya" />
<header className="site-header">
<p className="brand">{profile.nickname}</p>
<HtmlComment text="external css: kelas site-nav di styles.css mengatur layout navigasi" />
<NavExternal current="/about" />
</header>
<HtmlComment text="external css: kelas container di styles.css mengatur lebar dan padding wrapper main" />
<main className="container">
<section className="panel">
<h1>Tentang Saya</h1>
<p>
Saya {profile.name}, biasa dipanggil {profile.nickname}. Saya tinggal di {profile.city} dan sedang
menempuh pendidikan di Program Studi Informatika.
</p>
<HtmlComment text="tailwind css: kelas utilitas membentuk badge berbentuk pil" />
<p>
<span className="inline-block rounded-full bg-teal-800 px-3 py-1 text-sm font-semibold text-white">
Informatika
</span>{" "}
<span className="inline-block rounded-full bg-amber-500 px-3 py-1 text-sm font-semibold text-white">
Semester 3
</span>{" "}
<span className="inline-block rounded-full bg-slate-600 px-3 py-1 text-sm font-semibold text-white">
{profile.city}
</span>
</p>
</section>
<section className="panel">
<h2>Pendidikan</h2>
<HtmlComment text="ol dipakai karena riwayat pendidikan berurutan dari yang paling awal" />
<ol>
{education.map((item) => (
<li key={item}>{item}</li>
))}
</ol>
</section>
<section className="panel">
<h2>Keahlian</h2>
<table className="data-table">
<caption className="sr-only">Daftar keahlian saya</caption>
<thead>
<tr>
<th scope="col">Keahlian</th>
<th scope="col">Level</th>
<th scope="col">Catatan</th>
</tr>
</thead>
<tbody>
{skills.map((row) => (
<tr key={row.skill}>
<td>{row.skill}</td>
<td>{row.level}</td>
<td>{row.note}</td>
</tr>
))}
</tbody>
</table>
</section>
<section className="panel">
<h2>Kutipan favorit</h2>
<HtmlComment text="inline css: atribut style pada blockquote menimpa warna dan border dari css internal (.quote)" />
<blockquote
className="quote"
style={{ color: "#0f4c5c", borderLeft: "6px solid #e09f3e", background: "#fff7e6", fontSize: "1.2rem" }}
>
<p>&ldquo;{quote.text}&rdquo;</p>
<footer>&mdash; {quote.author}</footer>
</blockquote>
</section>
</main>
<footer className="site-footer">
<p>
&copy; {profile.year} {profile.name}
</p>
</footer>
</div>
);
}
+56
View File
@@ -0,0 +1,56 @@
import type { Metadata } from "next";
import { NavExternal } from "../../components/nav";
import HtmlComment from "../../components/html-comment";
import { profile, contacts } from "../../lib/data";
export const metadata: Metadata = { title: "Kontak | Personal Website" };
export default function ContactPage() {
return (
<>
{/* pilihan css: external. alasannya, halaman ini memakai ulang kelas panel, container, dan nav
yang sudah ada di styles.css. inline akan mengulang gaya, tailwind butuh script cdn tambahan
padahal halaman ini sederhana dan tidak perlu komponen khusus */}
<link rel="stylesheet" href="/styles.css" precedence="default" />
<HtmlComment text="contact.html memakai external css karena gayanya sama dengan halaman lain dan cukup satu stylesheet bersama" />
<header className="site-header">
<p className="brand">{profile.nickname}</p>
<NavExternal current="/contact" />
</header>
<main className="container">
<h1>Kontak</h1>
<section className="panel">
<h2>Hubungi saya</h2>
<p>Silakan hubungi saya lewat salah satu kanal berikut. Tidak ada formulir, langsung saja kirim pesan.</p>
<ul className="contact-list">
{contacts.map((item) => (
<li key={item.label}>
<span className="contact-label">{item.label}</span>
<a
href={item.href}
{...(item.external ? { target: "_blank", rel: "noopener noreferrer" } : {})}
>
{item.text}
</a>
</li>
))}
</ul>
</section>
<section className="panel">
<h2>Lokasi</h2>
<p>Saya berdomisili di {profile.city} dan kuliah di Universitas Katolik Widya Mandala Surabaya.</p>
</section>
</main>
<footer className="site-footer">
<p>
&copy; {profile.year} {profile.name}
</p>
</footer>
</>
);
}
+53
View File
@@ -0,0 +1,53 @@
import type { Metadata } from "next";
import Script from "next/script";
import { NavTailwind } from "../../components/nav";
import HtmlComment from "../../components/html-comment";
import { profile, gallery } from "../../lib/data";
export const metadata: Metadata = { title: "Galeri | Personal Website" };
export default function GalleryPage() {
return (
<div className="min-h-screen bg-slate-50 font-sans text-slate-800">
{/* halaman ini WAJIB tailwind css saja: script cdn dimuat di sini, semua gaya berupa kelas utilitas */}
<Script src="https://cdn.tailwindcss.com" />
<header className="mx-auto max-w-5xl px-6 pt-4">
<p className="m-0 font-serif text-xl font-bold text-teal-800">{profile.nickname}</p>
<NavTailwind current="/gallery" />
</header>
<main className="mx-auto max-w-5xl px-6 pb-6">
<section>
<h1 className="mb-2 mt-6 font-serif text-4xl font-bold leading-tight text-teal-800">Galeri Foto</h1>
<p className="mb-6 max-w-prose leading-relaxed">Beberapa potret dari keseharian saya.</p>
</section>
<section>
<h2 className="mb-4 font-serif text-2xl font-bold text-teal-800">Koleksi terbaru</h2>
<HtmlComment text="grid responsif: 1 kolom di ponsel, 2 di tablet, 3 di layar lebar" />
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{gallery.map((photo) => (
<figure key={photo.src} className="m-0 overflow-hidden rounded-xl bg-white shadow">
<img
src={photo.src}
alt={photo.alt}
width="400"
height="300"
className="aspect-[4/3] w-full object-cover"
/>
<figcaption className="px-4 py-3 text-sm font-semibold text-slate-700">{photo.caption}</figcaption>
</figure>
))}
</div>
</section>
</main>
<footer className="mx-auto max-w-5xl border-t border-slate-300 px-6 pb-8 pt-4 text-sm text-slate-600">
<p className="m-0">
&copy; {profile.year} {profile.name}
</p>
</footer>
</div>
);
}
+59
View File
@@ -0,0 +1,59 @@
import type { Metadata } from "next";
import { NavExternal } from "../../components/nav";
import HtmlComment from "../../components/html-comment";
import { profile, hobbies } from "../../lib/data";
export const metadata: Metadata = { title: "Hobi | Personal Website" };
export default function HobbiesPage() {
return (
<>
{/* halaman ini WAJIB external css saja: semua gaya ada di public/styles.css */}
<link rel="stylesheet" href="/styles.css" precedence="default" />
<header className="site-header">
<p className="brand">{profile.nickname}</p>
<NavExternal current="/hobbies" />
</header>
<main className="container">
<h1>Hobi Saya</h1>
<p>Tiga hal yang biasa saya lakukan di waktu luang.</p>
<HtmlComment text="tiap hobi dibuat sebagai section sendiri agar h2 dan h3 tetap berurutan" />
{hobbies.map((hobby) => (
<section className="hobby" key={hobby.title}>
<img src={hobby.photo} alt={hobby.alt} width="260" height="195" />
<div className="hobby-text">
<h2>{hobby.title}</h2>
<p>{hobby.description}</p>
<h3>Yang biasa saya lakukan</h3>
<ul>
{hobby.details.map((detail) => (
<li key={detail}>{detail}</li>
))}
</ul>
</div>
</section>
))}
<section className="panel">
<h2>Ingin mencoba juga?</h2>
<p>
Untuk pemula yang mau belajar ngegym silahkan{" "}
<a href="https://www.tiktok.com/@daddygeeeeee?is_from_webapp=1&sender_device=pc" target="_blank" rel="noopener noreferrer">
tonton dia ini
</a>
.
</p>
</section>
</main>
<footer className="site-footer">
<p>
&copy; {profile.year} {profile.name}
</p>
</footer>
</>
);
}
+17
View File
@@ -0,0 +1,17 @@
// root layout sengaja polos: tidak ada css global di sini
// setiap halaman membawa metode css-nya sendiri sesuai ketentuan tugas
import type { Metadata } from "next";
import type { ReactNode } from "react";
export const metadata: Metadata = {
title: "Personal Website",
description: "Situs personal multipage untuk tugas Pemrograman Web",
};
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="id">
<body>{children}</body>
</html>
);
}
+55
View File
@@ -0,0 +1,55 @@
import type { CSSProperties } from "react";
import type { Metadata } from "next";
import { NavInline } from "../components/nav";
import HtmlComment from "../components/html-comment";
import { profile } from "../lib/data";
export const metadata: Metadata = { title: "Beranda | Personal Website" };
// halaman ini WAJIB inline css saja: semua gaya memakai atribut style
const s: Record<string, CSSProperties> = {
page: { maxWidth: "960px", margin: "0 auto", padding: "24px", fontFamily: "system-ui, -apple-system, 'Segoe UI', sans-serif", color: "#1c2b33", background: "#f3f7f7", borderRadius: "20px", minHeight: "90vh" },
brand: { margin: 0, fontFamily: "Georgia, 'Times New Roman', serif", fontSize: "1.25rem", fontWeight: 700, color: "#0f4c5c" },
hero: { display: "flex", flexWrap: "wrap", alignItems: "center", gap: "32px", padding: "32px 0" },
photo: { width: "280px", maxWidth: "100%", height: "auto", borderRadius: "16px", border: "6px solid #e09f3e" },
heroText: { flex: "1 1 300px" },
h1: { margin: "0 0 12px", fontFamily: "Georgia, 'Times New Roman', serif", fontSize: "2.6rem", lineHeight: 1.15, color: "#0f4c5c" },
tagline: { margin: "0 0 16px", fontSize: "1.2rem", lineHeight: 1.5 },
section: { padding: "24px", background: "#ffffff", borderRadius: "12px", borderLeft: "6px solid #0f4c5c" },
h2: { margin: "0 0 8px", fontFamily: "Georgia, 'Times New Roman', serif", fontSize: "1.5rem", color: "#0f4c5c" },
p: { margin: 0, lineHeight: 1.7, maxWidth: "65ch" },
footer: { marginTop: "32px", paddingTop: "16px", borderTop: "1px solid #c9d8db", fontSize: "0.9rem", color: "#4a5d66" },
};
export default function HomePage() {
return (
<div style={s.page}>
<HtmlComment text="inline css: seluruh halaman ini distyle lewat atribut style sesuai aturan index.html" />
<header>
<p style={s.brand}>{profile.nickname}</p>
<NavInline current="/" />
</header>
<main>
<section style={s.hero}>
<img src={profile.photo} alt={profile.photoAlt} width="280" height="210" style={s.photo} />
<div style={s.heroText}>
<h1 style={s.h1}>Halo, saya {profile.name}</h1>
<p style={s.tagline}>{profile.tagline}</p>
</div>
</section>
<section style={s.section}>
<h2 style={s.h2}>Sekilas tentang saya</h2>
<p style={s.p}>{profile.intro}</p>
</section>
</main>
<footer style={s.footer}>
<p style={{ margin: 0 }}>
&copy; {profile.year} {profile.name}. Dibuat di {profile.city}.
</p>
</footer>
</div>
);
}