| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 14 | 42 | 0% |
| Commands | 0 | 3 | 0 | 0% |
| Section tags | 4 | 2 | 4 | 40% |
What each file covers
Sections
0 shared · 14 only in A · 42 only in B- − Liant Portfolio - Project Guidelines
- − Brief overview
- − Site language policy
- − Communication style
- − Styling conventions
- − Input and form handling
- − State management
- − Commit message conventions
- − Library and dependency policy
- − Section visibility control
- − Build and deployment notes
- − Reusable component structure
- − Accessibility baseline
- − Internationalization note
- + Liant Portfolio - UI Standards & Component Patterns
- + 1. COLOR SYSTEM
- + 1.1 Brand Gold (Primary Accent)
- + 1.2 Theme Tokens (CSS Custom Properties)
- + 1.3 Tailwind Arbitrary Value Pattern
- + 2. SECTION LAYOUT PATTERN
- + Section IDs (used by header nav anchors)
- + 3. ANIMATION CONVENTIONS
- + 3.1 Scroll-Triggered Reveal (Framer Motion)
- + 3.2 Hover Animations
- + 3.3 Floating/Looping Animation (for decorative elements)
- + 4. TYPOGRAPHY CONVENTIONS
- + 5. SECTION HEADER PATTERN (STANDARDIZED)
- + Usage
- + Component API
- + Rendered Output
- + Example Usage by Section
- + 6. CARD STANDARDS
- + 6.1 Card Container
- + 6.2 Card Hover Border Accent
- + 6.3 Bullet/List Items
- + 6.4 Spec/Info Grid (2-column metadata)
- + 7. BUTTON PATTERNS
- + 7.1 Primary Button (Gold fill)
- + 7.2 Secondary/Outline Button
- + 7.3 Danger/Remove Button
- + 7.4 Legacy `.main-button` (used by Button.jsx component)
- + 8. SVG ICON PATTERN
- + 8.1 Inline SVGs (Recommended for new code)
- + 8.2 FontAwesome (Used in SocialMedia, Contact, header)
- + 8.3 React Icons (Installed but unused — avoid if possible)
- + 9. FORM BLUEPRINT (WhatsApp/Email Actions)
- + 10. IMAGE HANDLING (Vite)
- + 11. LANGUAGE / I18N PATTERN
- + 12. MODAL / OVERLAY PATTERNS
- + 12.1 ImageLightbox (`src/components/imageLightbox/ImageLightbox.jsx`)
- + 12.2 ProjectShowcase (`src/components/projectShowcase/ProjectShowcase.jsx`)
- + 13. THEME TOGGLE SYSTEM
- + 14. LOTTIE ANIMATION PATTERN
- + 15. FRAMER MOTION IMPORTS
- + 16. KNOWN INCONSISTENCIES & MIGRATION NOTES
- + 17. FILE ORGANIZATION SUMMARY
Commands
0 shared · 3 only in A · 0 only in B- − gh-pages
- − npm run start
- − npm run build
Section tags
4 shared · 2 only in A · 4 only in B- − build
- − git-pr
- + test
- + lint-format
- + database
- + api
- code-style
- architecture
- ui
- do-not
Line diff
BryaanF/LiantPortfolio · .clinerules/project-guidelines.md
@@ −1 @@
1# Liant Portfolio - Project Guidelines
2
3> **Compatibility Note:** This guideline file is designed to be tool-agnostic and readable by any AI coding assistant (Cline, Copilot, future tools). It supplements the existing [.github/copilot-instructions.md](../.github/copilot-instructions.md) with additional conventions and preferences not covered there.
4
5## Brief overview
6
7- **Project:** A data-driven, single-page portfolio website for Brilliant Fikri (Liant), built with React 18 + Vite + Tailwind CSS + SCSS.
8- **Nature:** Pure frontend — no backend, no database. All content lives in `src/portfolio.jsx` as a single configuration file.
9- **Deployment:** Static site deployed to GitHub Pages (`gh-pages` branch) via GitHub Actions from `master` branch.
10- **Domain:** [brilliantfikri.com](https://brilliantfikri.com)
11
12## Site language policy
13
14- **Primary language for all user-facing content is English.** The portfolio targets an international/professional audience.
15- **Pending revision:** `pricingSection` in `src/portfolio.jsx` is currently in Bahasa Indonesia and must be translated to English. This includes all `title`, `description`, `desc`, `longDesc`, `features`, `notIncluded`, `workflow` fields, and the WhatsApp checkout message template in `src/containers/pricing/Pricing.jsx` (`handleCheckout` function).
16- Comments in code may use either English or Bahasa Indonesia — both are acceptable.
17- Communication with the developer (AI or human) may use Bahasa Indonesia.
18
19## Communication style
20
21- **Concise and direct.** Avoid filler words and conversational fluff in code comments, commit messages, and documentation.
22- Code comments should explain the "why," not the "what" — the code itself should be self-documenting for what it does.
23- **SCSS comments** use `/* */` block style for section headers (see existing pattern in `variables.scss`).
24- **JSX comments:** `//` for inline, `/* */` for multi-line explanations.
25
26## Styling conventions
27
28- **Tailwind CSS is the primary styling approach** for all new components and sections. Use utility classes directly in JSX (reference `src/containers/pricing/Pricing.jsx` as the canonical example).
29- **SCSS is reserved for reusable custom components** that need encapsulated, complex styles not easily expressed with Tailwind utilities. When creating a new reusable component in `src/components/`, co-locate a `.scss` file in the same folder.
30- **Design tokens:** Always reference CSS custom properties for theme-switchable values:
31 - `var(--bg-body)`, `var(--bg-card)`, `var(--text-primary)`, `var(--text-secondary)`, `var(--btn-primary-bg)`, `var(--border-light)`
32 - These are defined in `src/variables.scss` for both `.light-mode` and `.dark-mode`.
33 - For Tailwind, use arbitrary value syntax when needed: `bg-[var(--bg-card)]`, `text-[var(--text-primary)]`, `border-[var(--border-light)]`.
34- **SCSS variables** (prefixed with `$`) are for non-theme values only (e.g., `$brand-gold`, `$buttonHover`, static colors). Do not use SCSS variables for colors that should respond to dark/light mode.
35- **Never hardcode colors** in component JSX or SCSS that should respect the theme. Always go through CSS custom properties.
36- **Brand color:** Gold `#a1902e` (`$brand-gold`). This is the primary accent across buttons, links, and highlights.
37- **IMPORTANT:** The brand-gold in `tailwind.config.js` must match `$brand-gold` in `variables.scss`. Currently both use `#a1902e`. If one changes, update the other.
38
39## Input and form handling
40
41- **This project has no backend.** There is no server to POST form data to.
42- All "contact" or "checkout" interactions must use **direct action links:**
43 - **WhatsApp:** `https://wa.me/6281331487753?text=...` with a pre-formatted, URL-encoded message.
44 - **Email:** `mailto:briliantfikri@gmail.com` with optional `?subject=...&body=...` parameters.
45- Do NOT create `<form>` elements with submit handlers — there is nothing to handle submissions.
46- Do NOT add any form validation libraries or backend integration code.
47
48## State management
49
50- **Context API** is the established pattern for global state (see `src/contexts/StyleContext.js` for theme).
51- **Local state** (`useState` / `useReducer`) is sufficient for component-specific state.
52- **Custom hooks** in `src/hooks/` for reusable state logic (e.g., `useLocalStorage` for persistence).
53- Do NOT introduce Redux, Zustand, or other external state management libraries — they are unnecessary for this project's complexity level.
54
55## Commit message conventions
56
57Use structured, prefixed commit messages. The format is: `<type>: <brief description>`
58
59| Prefix | Use when |
60| ----------- | ---------------------------------------------------------------- |
61| `feat:` | Adding a new section, component, or feature |
62| `fix:` | Fixing a bug, broken style, or incorrect behavior |
63| `refactor:` | Restructuring code without changing functionality |
64| `style:` | CSS/SCSS/Tailwind adjustments only (no logic changes) |
65| `docs:` | Updating README, comments, or documentation files |
66| `chore:` | Dependency updates, config changes, build tweaks |
67| `content:` | Updating portfolio data in `portfolio.jsx` (text, images, links) |
68
69Examples:
70
71- `feat: add pricing calculator with WhatsApp checkout`
72- `fix: dark mode card background not applying on mobile`
73- `content: translate pricing section to English`
74- `chore: update framer-motion to v12`
75
76## Library and dependency policy
77
78- **No hard restrictions** on library usage, but exercise caution:
79 - Avoid packages with known security vulnerabilities or a history of unmaintained releases with CVEs.
80 - Prefer well-maintained, widely-adopted libraries with active GitHub repositories and recent releases.
81 - Before adding a new dependency, ask: "Can this be done with what we already have?" The project already includes React 18, Framer Motion, Lottie React, FontAwesome, and react-icons — which cover most UI needs.
82- **Do not add backend frameworks, ORMs, or database drivers** — this is a static frontend only.
83
84## Section visibility control
85
86- Every major section in `src/portfolio.jsx` has a `display: true/false` flag.
87- Containers in `src/containers/` must check this flag and return `null` when `display` is false.
88- The header navigation in `src/components/header/Header.jsx` should also check these flags to show/hide nav links accordingly.
89- When adding a new section, follow the pattern: config object → container with display check → registration in `Main.jsx` → optional nav link.
90
91## Build and deployment notes
92
93- **Dev server:** `npm run start` → Vite on port 3000, auto-opens browser.
94- **Production build:** `npm run build` → outputs to `build/` directory.
95- **Deployment:** GitHub Actions workflow (`.github/workflows/deploy.yml`) triggers on push to `master`, builds, and deploys to `gh-pages` branch. Also runs weekly on Monday 12:00 UTC.
96- **CI variable:** `CI=false` is set in the workflow to prevent treating warnings as errors during build.
97- **Node version:** 18.x (locked in CI workflow).
98
99## Reusable component structure
100
101When creating a new reusable component in `src/components/`:
102
103```
104src/components/MyComponent/
105├── MyComponent.jsx # Component logic
106└── MyComponent.scss # Styles (only if Tailwind is insufficient)
107```
108
109- Component file uses PascalCase matching the folder name.
110- Accept `className` prop to allow parent styling overrides.
111- Use `export default function ComponentName(...)` pattern (consistent with existing codebase).
112
113## Accessibility baseline
114
115- Use semantic HTML elements (`<section>`, `<nav>`, `<button>`, `<h1>`-`<h6>`).
116- Ensure header navigation links correspond to section `id` attributes for anchor-based scrolling.
117- Images should include meaningful `alt` text.
118- Interactive elements must be keyboard-accessible (buttons, not divs with onClick).
119
120## Internationalization note
121
122- The website currently supports a single language (English, with some Indonesian pending revision).
123- No i18n framework is in use. If multi-language support is added in the future, use the `feat_lang` add-on structure already defined in `pricingSection.featuresList` as a starting point (ID/EN, 2 languages).
124
BryaanF/LiantPortfolio · .clinerules/ui-standards.md
@@ +1 @@
1# Liant Portfolio - UI Standards & Component Patterns
2
3> **Purpose:** Ensures all UI code (new and existing) follows consistent visual patterns, animation conventions, and component APIs. Read this before creating or modifying any UI code.
4
5---
6
7## 1. COLOR SYSTEM
8
9### 1.1 Brand Gold (Primary Accent)
10
11| Token | Hex | Usage |
12| ------------------------- | ----------------------- | ------------------------------------------------------------------------ |
13| `$brand-gold` / `#a1902e` | `#a1902e` | Buttons, links, highlights, active states, badges, decorative underlines |
14| `$brand-gold-hover` | Computed (10% lighter) | Button hover states |
15| `var(--btn-primary-bg)` | `#a1902e` (both themes) | CSS custom property for Tailwind arbitrary values |
16
17**Critical:** The gold in `tailwind.config.js` must always match `variables.scss` -> `$brand-gold`. Currently both use `#a1902e`.
18
19### 1.2 Theme Tokens (CSS Custom Properties)
20
21Always use these in JSX via Tailwind arbitrary values — never hardcode colors:
22
23```jsx
24// LIGHT MODE (.light-mode) — Neutral cool grey, no warm/pink tint
25--bg-body: #eaeaea
26--bg-card: #f5f5f5
27--bg-header: #f5f5f5
28--text-primary: #1a1a2e
29--text-secondary: #3a3a4e
30--border-light: rgba(0, 0, 0, 0.1)
31--shadow-card: 0 4px 20px rgba(0,0,0,0.05), 0 1px 6px rgba(0,0,0,0.07)
32
33// DARK MODE (.dark-mode, [data-theme='dark'])
34--bg-body: #0a0a0a
35--bg-card: #1f1f1f
36--bg-header: #161b22
37--text-primary: #ffffff
38--text-secondary: #b0b0b0
39--border-light: rgba(255, 255, 255, 0.1)
40--shadow-card: 0 4px 6px rgba(255, 255, 255, 0.05)
41```
42
43### 1.3 Tailwind Arbitrary Value Pattern
44
45```jsx
46bg-[var(--bg-body)] // Section backgrounds
47bg-[var(--bg-card)] // Card backgrounds
48text-[var(--text-primary)] // Main heading text
49text-[var(--text-secondary)] // Subtitle, metadata, description text
50border-[var(--border-light)] // Subtle borders and dividers
51text-[var(--btn-primary-bg)] // Brand accent text
52bg-[var(--btn-primary-bg)] // Primary button fill
53hover:text-[var(--btn-primary-bg)] // Link/button hover accent
54hover:border-[var(--btn-primary-bg)] // Card hover border accent
55```
56
57---
58
59## 2. SECTION LAYOUT PATTERN
60
61Every section follows this exact structure in JSX:
62
63```jsx
64import {motion} from "framer-motion";
65import LanguageContext from "../../contexts/LanguageContext";
66import {getTranslation} from "../../utils/translations";
67
68export default function MySection() {
69 const {lang} = useContext(LanguageContext);
70 const {isDark} = useContext(StyleContext); // if theme needed
71
72 if (!config.display) return null;
73
74 return (
75 <section
76 id="my-section"
77 className="relative py-16 md:py-24 overflow-hidden"
78 style={{backgroundColor: "var(--bg-body)"}}
79 >
80 <div className="max-w-7xl mx-auto px-4 md:px-8">
81 {/* Header: centered, animated */}
82 <motion.div
83 initial={{opacity: 0, y: 20}}
84 whileInView={{opacity: 1, y: 0}}
85 viewport={{once: true}}
86 transition={{duration: 0.6}}
87 className="text-center mb-12 md:mb-16"
88 >
89 <h2 className="text-3xl md:text-5xl font-black text-[var(--text-primary)] mb-4">
90 {getTranslation(config.title, lang)}
91 </h2>
92 <p
93 className="text-sm md:text-base uppercase tracking-[0.2em] font-semibold"
94 style={{color: "var(--text-secondary)"}}
95 >
96 {getTranslation(config.subtitle, lang)}
97 </p>
98 </motion.div>
99
100 {/* Content grid */}
101 <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
102 {/* ... cards / content ... */}
103 </div>
104 </div>
105 </section>
106 );
107}
108```
109
110### Section IDs (used by header nav anchors)
111
112| id | Container |
113| --------------- | ------------------ |
114| `#greeting` | Greeting.jsx |
115| `#intro-video` | IntroVideo.jsx |
116| `#skills` | Skills.jsx |
117| `#education` | Education.jsx |
118| `#experience` | WorkExperience.jsx |
119| `#projects` | BigProjects.jsx |
120| `#achievements` | Achievement.jsx |
121| `#pricing` | Pricing.jsx |
122| `#contact` | Contact.jsx |
123
124---
125
126## 3. ANIMATION CONVENTIONS
127
128### 3.1 Scroll-Triggered Reveal (Framer Motion)
129
130Use `whileInView` for all scroll-triggered animations (not `animate`, which runs on mount):
131
132```jsx
133// Single element
134<motion.div
135 initial={{opacity: 0, y: 20}}
136 whileInView={{opacity: 1, y: 0}}
137 viewport={{once: true, margin: "-100px"}}
138 transition={{duration: 0.6}}
139>
140 ...
141</motion.div>;
142
143// Staggered children
144const containerVariants = {
145 hidden: {opacity: 0},
146 visible: {
147 opacity: 1,
148 transition: {staggerChildren: 0.15, delayChildren: 0.2}
149 }
150};
151const itemVariants = {
152 hidden: {opacity: 0, y: 12},
153 visible: {opacity: 1, y: 0, transition: {duration: 0.4}}
154};
155
156<motion.div
157 variants={containerVariants}
158 initial="hidden"
159 whileInView="visible"
160 viewport={{once: true}}
161>
162 {items.map((item, i) => (
163 <motion.div key={i} variants={itemVariants}>
164 ...
165 </motion.div>
166 ))}
167</motion.div>;
168```
169
170### 3.2 Hover Animations
171
172```jsx
173// Card lift on hover
174<motion.div whileHover={{y: -5}}>...</motion.div>;
175
176// Button/link with CSS transition
177className = "transition-all duration-300 hover:-translate-y-1";
178```
179
180### 3.3 Floating/Looping Animation (for decorative elements)
181
182```jsx
183<motion.span
184 animate={{y: [0, -6, 0]}}
185 transition={{duration: 3, repeat: Infinity, ease: "easeInOut"}}
186>
187 🚀
188</motion.span>
189```
190
191---
192
193## 4. TYPOGRAPHY CONVENTIONS
194
195| Element | Tailwind classes | Style |
196| ---------------- | -------------------------------------------------------------------------------------------- | ------------------------------- |
197| Section heading | `text-3xl md:text-5xl font-black text-[var(--text-primary)]` | Black weight, responsive sizing |
198| Section subtitle | `text-sm md:text-base uppercase tracking-[0.2em] font-semibold text-[var(--text-secondary)]` | Uppercase, letter-spaced |
199| Card title | `text-lg md:text-xl font-bold text-[var(--text-primary)]` | Bold weight |
200| Card description | `text-sm leading-relaxed text-[var(--text-secondary)]` | Normal weight |
201| Pill/badge | `text-[9px] font-black px-2 py-1 rounded-full` | Tiny uppercase |
202| Duration/tags | `text-[10px] font-black tracking-[0.2em] uppercase` | Small uppercase |
203
204---
205
206## 5. SECTION HEADER PATTERN (STANDARDIZED)
207
208All sections must use the shared `SectionHeader` component located at `src/components/sectionHeader/SectionHeader.jsx`. This ensures visual harmony and consistency across the portfolio.
209
210### Usage
211
212```jsx
213import SectionHeader from "../../components/sectionHeader/SectionHeader";
214
215<SectionHeader
216 title="Section Title" // Required
217 subtitle="Optional subtitle" // Optional
218 emoji="🚀" // Optional emoji above title
219 align="center" // "center" (default) | "left"
220/>;
221```
222
223### Component API
224
225| Prop | Type | Default | Description |
226| ---------- | -------------------- | ---------- | ----------------------------------------- |
227| `title` | string | (required) | Section heading text |
228| `subtitle` | string | `""` | Subtitle shown below gold accent strip |
229| `emoji` | string | `""` | Optional emoji/icon displayed above title |
230| `align` | `"center"`\|`"left"` | `"center"` | Text alignment |
231
232### Rendered Output
233
234- **Title**: `text-3xl md:text-5xl font-bold text-[var(--text-primary)]`
235- **Emoji** (optional): `text-4xl md:text-5xl` above title
236- **Gold underline accent strip**: Animated `width: 0 → 80px` with `bg-[var(--btn-primary-bg)]`
237- **Subtitle** (optional): `text-sm md:text-base uppercase tracking-[0.2em] font-semibold text-[var(--text-secondary)]`
238
239### Example Usage by Section
240
241| Container | title | subtitle | emoji | align |
242| -------------- | -------------------------- | ----------------------------- | ----- | ------ |
243| Education | `educationInfo.title` | — | `🎓` | center |
244| Skills | `skillsSection.title` | `skillsSection.subTitle` | `💡` | center |
245| IntroVideo | `introVideo.title` | `introVideo.subtitle` | `🎬` | center |
246| WorkExperience | `workExperiences.title` | `workExperiences.subtitle` | `💼` | center |
247| BigProjects | `bigProjects.title` | `bigProjects.subtitle` | `🚀` | center |
248| Achievement | `achievementSection.title` | `achievementSection.subtitle` | `🏆` | center |
249| Pricing | `pricingSection.title` | `pricingSection.description` | `💰` | center |
250| Contact | `contactInfo.title` | `contactInfo.subtitle` | `✉️` | center |
251
252---
253
254## 6. CARD STANDARDS
255
256### 6.1 Card Container
257
258```jsx
259<div className="rounded-xl border border-[var(--border-light)] bg-[var(--bg-card)]
260 transition-all duration-300 hover:-translate-y-1 hover:shadow-xl">
261```
262
263### 6.2 Card Hover Border Accent
264
265```jsx
266// Add group class to parent and use:
267className =
268 "group border border-[var(--border-light)] hover:border-[var(--btn-primary-bg)]/50 transition-colors";
269```
270
271### 6.3 Bullet/List Items
272
273```jsx
274// Gold dot bullet
275<li className="flex gap-2">
276 <span className="mt-[6px] h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--btn-primary-bg)]" />
277 <span className="text-[var(--text-secondary)]">{text}</span>
278</li>
279
280// Gold triangle bullet
281<li className="flex items-start gap-3">
282 <span className="text-[var(--btn-primary-bg)] mt-1.5 shrink-0 text-[8px]">▶</span>
283 <span className="text-[var(--text-secondary)]">{text}</span>
284</li>
285```
286
287### 6.4 Spec/Info Grid (2-column metadata)
288
289```jsx
290<div
291 className="grid grid-cols-2 gap-2 text-[10px] p-3 rounded-lg border
292 border-[var(--border-light)]"
293 style={{
294 backgroundColor: isDark ? "rgba(255,255,255,0.02)" : "rgba(0,0,0,0.02)"
295 }}
296>
297 {/* Items */}
298</div>
299```
300
301---
302
303## 7. BUTTON PATTERNS
304
305### 7.1 Primary Button (Gold fill)
306
307```jsx
308<button
309 className="px-6 py-3 rounded-lg text-xs font-black uppercase tracking-widest
310 transition-all hover:brightness-110 active:scale-95"
311 style={{backgroundColor: "var(--btn-primary-bg)", color: "#fff"}}
312>
313 {text}
314</button>
315```
316
317### 7.2 Secondary/Outline Button
318
319```jsx
320<button
321 className="py-3 rounded-lg border border-[var(--border-light)] text-xs
322 font-bold transition-colors"
323 style={{color: "var(--text-primary)", backgroundColor: "transparent"}}
324>
325 {text}
326</button>
327```
328
329### 7.3 Danger/Remove Button
330
331```jsx
332<button
333 className="py-3 rounded-lg text-xs font-bold uppercase tracking-widest"
334 style={{
335 color: "#f87171",
336 backgroundColor: "rgba(239,68,68,0.08)",
337 border: "1px solid rgba(239,68,68,0.25)"
338 }}
339>
340 {text}
341</button>
342```
343
344### 7.4 Legacy `.main-button` (used by Button.jsx component)
345
346- Defined in `src/components/button/Button.scss`
347- Gold background, white text, rounded, hover lift effect
348- For NEW code, prefer primary button pattern above
349
350---
351
352## 8. SVG ICON PATTERN
353
354Use either approach consistently. For new code, prefer **inline SVG components** (portable, no external dependency).
355
356### 8.1 Inline SVGs (Recommended for new code)
357
358```jsx
359const MyIcon = () => (
360 <svg
361 width="14"
362 height="14"
363 viewBox="0 0 24 24"
364 fill="none"
365 stroke="currentColor"
366 strokeWidth="2"
367 >
368 <circle cx="12" cy="12" r="10" />
369 <path d="M12 6v6l4 2" />
370 </svg>
371);
372```
373
374Usage: `<MyIcon />` — color inherits from `currentColor`.
375
376### 8.2 FontAwesome (Used in SocialMedia, Contact, header)
377
378- Import: `import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"`
379- Brand icons: `import { faGithub, faLinkedin } from "@fortawesome/free-brands-svg-icons"`
380- Solid icons: `import { faEnvelope } from "@fortawesome/free-solid-svg-icons"`
381- Brand colors defined as SCSS variables in `variables.scss` (e.g., `$linkedin: #0e76a8`)
382
383### 8.3 React Icons (Installed but unused — avoid if possible)
384
385- The `react-icons` package is installed but not currently used in any component.
386- Prefer inline SVGs or FontAwesome instead.
387
388---
389
390## 9. FORM BLUEPRINT (WhatsApp/Email Actions)
391
392**Do NOT use `<form>` elements.** All interactions are direct links:
393
394```jsx
395// WhatsApp
396<a href={`https://wa.me/6281331487753?text=${encodeURIComponent(message)}`}
397 target="_blank" rel="noopener noreferrer">
398 Contact via WhatsApp
399</a>
400
401// Email
402<a href={`mailto:briliantfikri@gmail.com?subject=${subject}&body=${body}`}>
403 Send Email
404</a>
405```
406
407For the pricing checkout message, construct the message template inside a `handleCheckout` function and call `window.open()`.
408
409---
410
411## 10. IMAGE HANDLING (Vite)
412
413- **Always import images statically** (Vite convention):
414 ```jsx
415 import myImage from "../../assets/images/myImage.png";
416 // Usage: <img src={myImage} alt="..." />
417 ```
418- **Do NOT use `require()`** — that is a CRA/Webpack pattern. The Skills.jsx container is the only file still using `require()` and should be migrated.
419- For images from external URLs, use `src={url}` directly (no import needed).
420- Use `loading="lazy"` on below-the-fold images.
421
422---
423
424## 11. LANGUAGE / I18N PATTERN
425
426All user-facing text that supports bilingual display uses this pattern:
427
4281. In `portfolio.jsx`: define as `{ en: "...", id: "..." }` object.
4292. In component: import `LanguageContext`, get `lang`, call `getTranslation(configField, lang)`.
4303. The helper lives in `src/utils/translations.js`.
431
432```jsx
433import LanguageContext from "../../contexts/LanguageContext";
434import {getTranslation} from "../../utils/translations";
435
436const {lang} = useContext(LanguageContext);
437const title = getTranslation(config.title, lang); // returns string
438```
439
440**Rules:**
441
442- `getTranslation()` handles nested objects recursively (e.g., `workflow` steps).
443- Arrays of bilingual objects are resolved element-by-element.
444- Plain strings/numbers pass through unchanged.
445
446---
447
448## 12. MODAL / OVERLAY PATTERNS
449
450### 12.1 ImageLightbox (`src/components/imageLightbox/ImageLightbox.jsx`)
451
452```jsx
453<ImageLightbox src={url} alt={text} onClose={() => setState(null)} />
454```
455
456- Fixed overlay, dark backdrop, centered image, close on backdrop click or ✕ button.
457- Always wrap with `{show && <ImageLightbox ... />}`.
458
459### 12.2 ProjectShowcase (`src/components/projectShowcase/ProjectShowcase.jsx`)
460
461```jsx
462<ProjectShowcase
463 title={string}
464 description={string}
465 media={[{type: "image" | "video", url, caption, thumbnail}]}
466 externalUrl={
467 string
468 } /* Optional — shows "Visit Website" button in top-right header */
469 onClose={fn}
470/>
471```
472
473- Full-screen dark overlay with media gallery.
474- **"Visit Website"** button appears in top-right header ONLY when `externalUrl` is provided.
475- Used by AchievementCard and BigProject containers.
476
477---
478
479## 13. THEME TOGGLE SYSTEM
480
481- Global context: `src/contexts/StyleContext.js` provides `{ isDark, changeTheme }`.
482- Theme is persisted in `localStorage` via `useLocalStorage` hook.
483- Toggle switch: `src/components/ToggleSwitch/ToggleSwitch.jsx` (sun/moon emoji).
484- Theme class: `.light-mode` or `.dark-mode` on root `<div>` in `Main.jsx`.
485- CSS variables defined in `src/variables.scss` respond to these classes.
486
487```jsx
488import StyleContext from "../../contexts/StyleContext";
489const { isDark } = useContext(StyleContext);
490
491// Use in conditional styling:
492<div style={{ backgroundColor: isDark ? "rgba(0,0,0,0.4)" : "rgba(255,255,255,0.6)" }}>
493```
494
495---
496
497## 14. LOTTIE ANIMATION PATTERN
498
499```jsx
500import DisplayLottie from "../../components/displayLottie/DisplayLottie";
501import animationData from "../../assets/lottie/myAnimation";
502
503{
504 illustration.animated ? (
505 <DisplayLottie animationData={animationData} />
506 ) : (
507 <img src={fallbackImage} alt="..." />
508 );
509}
510```
511
512- All Lottie JSON files live in `src/assets/lottie/`.
513- The `DisplayLottie` component wraps `lottie-react` with `Suspense` + `<Loading>` fallback.
514
515---
516
517## 15. FRAMER MOTION IMPORTS
518
519Always import only what's needed from `framer-motion`:
520
521```jsx
522import {motion, AnimatePresence} from "framer-motion";
523```
524
525- `motion.div`, `motion.button`, `motion.a`, `motion.span`, `motion.img`, `motion.h1`-`h6` supported.
526- `AnimatePresence` for mounting/unmounting animations (modals, checkout bar).
527
528---
529
530## 16. KNOWN INCONSISTENCIES & MIGRATION NOTES
531
532These are NOT required to fix immediately, but be aware of them when touching related code:
533
5341. **`Skills.jsx` uses `require()` for static images** — should use `import` like all other containers (Vite pattern).
5352. **`Button.jsx` does not spread `className`** — it wraps in `<div className={className}>` instead of applying directly to `<a>`. New button code should use the inline primary button pattern instead.
5363. **`variables.scss` has legacy SCSS variables** that duplicate CSS custom properties (e.g., `$textColorDark` is `#ffffff` but already covered by `--text-primary` in dark mode). When refactoring, prefer CSS custom properties.
5374. **`Greeting.scss` and `Skills.scss` still contain legacy classes** that are partially overridden by Tailwind. When editing, prefer moving styles to Tailwind and removing SCSS.
5385. **`Header.scss` is partially refactored** — it has both legacy classes and a comment noting Tailwind removal was done. When editing, complete the migration.
5396. **`SplashScreen.jsx` uses `.css` instead of `.scss`** — minor inconsistency.
5407. **`ProjectShowcase.jsx` and `Pricing.jsx` both define duplicate inline SVG icons** (`Close`, `Image`) — consider extracting to `src/utils/icons.jsx` in the future.
541
542---
543
544## 17. FILE ORGANIZATION SUMMARY
545
546```
547src/
548├── portfolio.jsx # Single config file — all content data
549├── App.jsx # Root component
550├── Main.jsx # Container orchestrator, theme/language providers
551├── variables.scss # Design tokens (CSS vars + SCSS vars)
552├── contexts/
553│ ├── StyleContext.js # Theme state (isDark, changeTheme)
554│ └── LanguageContext.js # Language state (lang, changeLang)
555├── hooks/
556│ └── useLocalStorage.js # Persistent state hook
557├── utils/
558│ └── translations.js # getTranslation() helper
559├── components/ # Reusable UI components
560│ ├── header/Header.jsx
561│ ├── button/Button.jsx
562│ ├── footer/Footer.jsx
563│ ├── socialMedia/SocialMedia.jsx
564│ ├── ToggleSwitch/ToggleSwitch.jsx
565│ ├── LanguageToggle/LanguageToggle.jsx
566│ ├── displayLottie/DisplayLottie.jsx
567│ ├── imageLightbox/ImageLightbox.jsx
568│ ├── projectShowcase/ProjectShowcase.jsx
569│ ├── achievementCard/AchievementCard.jsx
570│ ├── educationCard/EducationCard.jsx
571│ ├── experienceCard/ExperienceCard.jsx
572│ ├── softwareSkills/SoftwareSkill.jsx
573│ └── ... (other legacy components)
574├── containers/ # Page sections (one per portfolio section)
575│ ├── greeting/Greeting.jsx
576│ ├── introVideo/IntroVideo.jsx
577│ ├── skills/Skills.jsx
578│ ├── education/Education.jsx
579│ ├── workExperience/WorkExperience.jsx
580│ ├── projects/Projects.jsx
581│ ├── BigProjects/BigProject.jsx
582│ ├── achievement/Achievement.jsx
583│ ├── pricing/Pricing.jsx
584│ ├── contact/Contact.jsx
585│ ├── splashScreen/SplashScreen.jsx
586│ └── ... (others)
587└── assets/
588 ├── images/ # Static images (imported in portfolio.jsx)
589 ├── lottie/ # Lottie JSON animations
590 └── fonts/ # Custom fonts
591```
592
@@ −1 +1 @@
1−# Liant Portfolio - Project Guidelines
1+# Liant Portfolio - UI Standards & Component Patterns
22
3−> **Compatibility Note:** This guideline file is designed to be tool-agnostic and readable by any AI coding assistant (Cline, Copilot, future tools). It supplements the existing [.github/copilot-instructions.md](../.github/copilot-instructions.md) with additional conventions and preferences not covered there.
3+> **Purpose:** Ensures all UI code (new and existing) follows consistent visual patterns, animation conventions, and component APIs. Read this before creating or modifying any UI code.
44
5−## Brief overview
5+---
66
7−- **Project:** A data-driven, single-page portfolio website for Brilliant Fikri (Liant), built with React 18 + Vite + Tailwind CSS + SCSS.
8−- **Nature:** Pure frontend — no backend, no database. All content lives in `src/portfolio.jsx` as a single configuration file.
9−- **Deployment:** Static site deployed to GitHub Pages (`gh-pages` branch) via GitHub Actions from `master` branch.
10−- **Domain:** [brilliantfikri.com](https://brilliantfikri.com)
7+## 1. COLOR SYSTEM
118
12−## Site language policy
9+### 1.1 Brand Gold (Primary Accent)
1310
14−- **Primary language for all user-facing content is English.** The portfolio targets an international/professional audience.
15−- **Pending revision:** `pricingSection` in `src/portfolio.jsx` is currently in Bahasa Indonesia and must be translated to English. This includes all `title`, `description`, `desc`, `longDesc`, `features`, `notIncluded`, `workflow` fields, and the WhatsApp checkout message template in `src/containers/pricing/Pricing.jsx` (`handleCheckout` function).
16−- Comments in code may use either English or Bahasa Indonesia — both are acceptable.
17−- Communication with the developer (AI or human) may use Bahasa Indonesia.
11+| Token | Hex | Usage |
12+| ------------------------- | ----------------------- | ------------------------------------------------------------------------ |
13+| `$brand-gold` / `#a1902e` | `#a1902e` | Buttons, links, highlights, active states, badges, decorative underlines |
14+| `$brand-gold-hover` | Computed (10% lighter) | Button hover states |
15+| `var(--btn-primary-bg)` | `#a1902e` (both themes) | CSS custom property for Tailwind arbitrary values |
1816
19−## Communication style
17+**Critical:** The gold in `tailwind.config.js` must always match `variables.scss` -> `$brand-gold`. Currently both use `#a1902e`.
2018
21−- **Concise and direct.** Avoid filler words and conversational fluff in code comments, commit messages, and documentation.
22−- Code comments should explain the "why," not the "what" — the code itself should be self-documenting for what it does.
23−- **SCSS comments** use `/* */` block style for section headers (see existing pattern in `variables.scss`).
24−- **JSX comments:** `//` for inline, `/* */` for multi-line explanations.
19+### 1.2 Theme Tokens (CSS Custom Properties)
2520
26−## Styling conventions
21+Always use these in JSX via Tailwind arbitrary values — never hardcode colors:
2722
28−- **Tailwind CSS is the primary styling approach** for all new components and sections. Use utility classes directly in JSX (reference `src/containers/pricing/Pricing.jsx` as the canonical example).
29−- **SCSS is reserved for reusable custom components** that need encapsulated, complex styles not easily expressed with Tailwind utilities. When creating a new reusable component in `src/components/`, co-locate a `.scss` file in the same folder.
30−- **Design tokens:** Always reference CSS custom properties for theme-switchable values:
31− - `var(--bg-body)`, `var(--bg-card)`, `var(--text-primary)`, `var(--text-secondary)`, `var(--btn-primary-bg)`, `var(--border-light)`
32− - These are defined in `src/variables.scss` for both `.light-mode` and `.dark-mode`.
33− - For Tailwind, use arbitrary value syntax when needed: `bg-[var(--bg-card)]`, `text-[var(--text-primary)]`, `border-[var(--border-light)]`.
34−- **SCSS variables** (prefixed with `$`) are for non-theme values only (e.g., `$brand-gold`, `$buttonHover`, static colors). Do not use SCSS variables for colors that should respond to dark/light mode.
35−- **Never hardcode colors** in component JSX or SCSS that should respect the theme. Always go through CSS custom properties.
36−- **Brand color:** Gold `#a1902e` (`$brand-gold`). This is the primary accent across buttons, links, and highlights.
37−- **IMPORTANT:** The brand-gold in `tailwind.config.js` must match `$brand-gold` in `variables.scss`. Currently both use `#a1902e`. If one changes, update the other.
23+```jsx
24+// LIGHT MODE (.light-mode) — Neutral cool grey, no warm/pink tint
25+--bg-body: #eaeaea
26+--bg-card: #f5f5f5
27+--bg-header: #f5f5f5
28+--text-primary: #1a1a2e
29+--text-secondary: #3a3a4e
30+--border-light: rgba(0, 0, 0, 0.1)
31+--shadow-card: 0 4px 20px rgba(0,0,0,0.05), 0 1px 6px rgba(0,0,0,0.07)
3832
39−## Input and form handling
33+// DARK MODE (.dark-mode, [data-theme='dark'])
34+--bg-body: #0a0a0a
35+--bg-card: #1f1f1f
36+--bg-header: #161b22
37+--text-primary: #ffffff
38+--text-secondary: #b0b0b0
39+--border-light: rgba(255, 255, 255, 0.1)
40+--shadow-card: 0 4px 6px rgba(255, 255, 255, 0.05)
41+```
4042
41−- **This project has no backend.** There is no server to POST form data to.
42−- All "contact" or "checkout" interactions must use **direct action links:**
43− - **WhatsApp:** `https://wa.me/6281331487753?text=...` with a pre-formatted, URL-encoded message.
44− - **Email:** `mailto:briliantfikri@gmail.com` with optional `?subject=...&body=...` parameters.
45−- Do NOT create `<form>` elements with submit handlers — there is nothing to handle submissions.
46−- Do NOT add any form validation libraries or backend integration code.
43+### 1.3 Tailwind Arbitrary Value Pattern
4744
48−## State management
45+```jsx
46+bg-[var(--bg-body)] // Section backgrounds
47+bg-[var(--bg-card)] // Card backgrounds
48+text-[var(--text-primary)] // Main heading text
49+text-[var(--text-secondary)] // Subtitle, metadata, description text
50+border-[var(--border-light)] // Subtle borders and dividers
51+text-[var(--btn-primary-bg)] // Brand accent text
52+bg-[var(--btn-primary-bg)] // Primary button fill
53+hover:text-[var(--btn-primary-bg)] // Link/button hover accent
54+hover:border-[var(--btn-primary-bg)] // Card hover border accent
55+```
4956
50−- **Context API** is the established pattern for global state (see `src/contexts/StyleContext.js` for theme).
51−- **Local state** (`useState` / `useReducer`) is sufficient for component-specific state.
52−- **Custom hooks** in `src/hooks/` for reusable state logic (e.g., `useLocalStorage` for persistence).
53−- Do NOT introduce Redux, Zustand, or other external state management libraries — they are unnecessary for this project's complexity level.
57+---
5458
55−## Commit message conventions
59+## 2. SECTION LAYOUT PATTERN
5660
57−Use structured, prefixed commit messages. The format is: `<type>: <brief description>`
61+Every section follows this exact structure in JSX:
5862
59−| Prefix | Use when |
60−| ----------- | ---------------------------------------------------------------- |
61−| `feat:` | Adding a new section, component, or feature |
62−| `fix:` | Fixing a bug, broken style, or incorrect behavior |
63−| `refactor:` | Restructuring code without changing functionality |
64−| `style:` | CSS/SCSS/Tailwind adjustments only (no logic changes) |
65−| `docs:` | Updating README, comments, or documentation files |
66−| `chore:` | Dependency updates, config changes, build tweaks |
67−| `content:` | Updating portfolio data in `portfolio.jsx` (text, images, links) |
63+```jsx
64+import {motion} from "framer-motion";
65+import LanguageContext from "../../contexts/LanguageContext";
66+import {getTranslation} from "../../utils/translations";
6867
69−Examples:
68+export default function MySection() {
69+ const {lang} = useContext(LanguageContext);
70+ const {isDark} = useContext(StyleContext); // if theme needed
7071
71−- `feat: add pricing calculator with WhatsApp checkout`
72−- `fix: dark mode card background not applying on mobile`
73−- `content: translate pricing section to English`
74−- `chore: update framer-motion to v12`
72+ if (!config.display) return null;
7573
76−## Library and dependency policy
74+ return (
75+ <section
76+ id="my-section"
77+ className="relative py-16 md:py-24 overflow-hidden"
78+ style={{backgroundColor: "var(--bg-body)"}}
79+ >
80+ <div className="max-w-7xl mx-auto px-4 md:px-8">
81+ {/* Header: centered, animated */}
82+ <motion.div
83+ initial={{opacity: 0, y: 20}}
84+ whileInView={{opacity: 1, y: 0}}
85+ viewport={{once: true}}
86+ transition={{duration: 0.6}}
87+ className="text-center mb-12 md:mb-16"
88+ >
89+ <h2 className="text-3xl md:text-5xl font-black text-[var(--text-primary)] mb-4">
90+ {getTranslation(config.title, lang)}
91+ </h2>
92+ <p
93+ className="text-sm md:text-base uppercase tracking-[0.2em] font-semibold"
94+ style={{color: "var(--text-secondary)"}}
95+ >
96+ {getTranslation(config.subtitle, lang)}
97+ </p>
98+ </motion.div>
7799
78−- **No hard restrictions** on library usage, but exercise caution:
79− - Avoid packages with known security vulnerabilities or a history of unmaintained releases with CVEs.
80− - Prefer well-maintained, widely-adopted libraries with active GitHub repositories and recent releases.
81− - Before adding a new dependency, ask: "Can this be done with what we already have?" The project already includes React 18, Framer Motion, Lottie React, FontAwesome, and react-icons — which cover most UI needs.
82−- **Do not add backend frameworks, ORMs, or database drivers** — this is a static frontend only.
100+ {/* Content grid */}
101+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
102+ {/* ... cards / content ... */}
103+ </div>
104+ </div>
105+ </section>
106+ );
107+}
108+```
83109
84−## Section visibility control
110+### Section IDs (used by header nav anchors)
85111
86−- Every major section in `src/portfolio.jsx` has a `display: true/false` flag.
87−- Containers in `src/containers/` must check this flag and return `null` when `display` is false.
88−- The header navigation in `src/components/header/Header.jsx` should also check these flags to show/hide nav links accordingly.
89−- When adding a new section, follow the pattern: config object → container with display check → registration in `Main.jsx` → optional nav link.
112+| id | Container |
113+| --------------- | ------------------ |
114+| `#greeting` | Greeting.jsx |
115+| `#intro-video` | IntroVideo.jsx |
116+| `#skills` | Skills.jsx |
117+| `#education` | Education.jsx |
118+| `#experience` | WorkExperience.jsx |
119+| `#projects` | BigProjects.jsx |
120+| `#achievements` | Achievement.jsx |
121+| `#pricing` | Pricing.jsx |
122+| `#contact` | Contact.jsx |
90123
91−## Build and deployment notes
124+---
92125
93−- **Dev server:** `npm run start` → Vite on port 3000, auto-opens browser.
94−- **Production build:** `npm run build` → outputs to `build/` directory.
95−- **Deployment:** GitHub Actions workflow (`.github/workflows/deploy.yml`) triggers on push to `master`, builds, and deploys to `gh-pages` branch. Also runs weekly on Monday 12:00 UTC.
96−- **CI variable:** `CI=false` is set in the workflow to prevent treating warnings as errors during build.
97−- **Node version:** 18.x (locked in CI workflow).
126+## 3. ANIMATION CONVENTIONS
98127
99−## Reusable component structure
128+### 3.1 Scroll-Triggered Reveal (Framer Motion)
100129
101−When creating a new reusable component in `src/components/`:
130+Use `whileInView` for all scroll-triggered animations (not `animate`, which runs on mount):
102131
132+```jsx
133+// Single element
134+<motion.div
135+ initial={{opacity: 0, y: 20}}
136+ whileInView={{opacity: 1, y: 0}}
137+ viewport={{once: true, margin: "-100px"}}
138+ transition={{duration: 0.6}}
139+>
140+ ...
141+</motion.div>;
142+
143+// Staggered children
144+const containerVariants = {
145+ hidden: {opacity: 0},
146+ visible: {
147+ opacity: 1,
148+ transition: {staggerChildren: 0.15, delayChildren: 0.2}
149+ }
150+};
151+const itemVariants = {
152+ hidden: {opacity: 0, y: 12},
153+ visible: {opacity: 1, y: 0, transition: {duration: 0.4}}
154+};
155+
156+<motion.div
157+ variants={containerVariants}
158+ initial="hidden"
159+ whileInView="visible"
160+ viewport={{once: true}}
161+>
162+ {items.map((item, i) => (
163+ <motion.div key={i} variants={itemVariants}>
164+ ...
165+ </motion.div>
166+ ))}
167+</motion.div>;
103168 ```
104−src/components/MyComponent/
105−├── MyComponent.jsx # Component logic
106−└── MyComponent.scss # Styles (only if Tailwind is insufficient)
169+
170+### 3.2 Hover Animations
171+
172+```jsx
173+// Card lift on hover
174+<motion.div whileHover={{y: -5}}>...</motion.div>;
175+
176+// Button/link with CSS transition
177+className = "transition-all duration-300 hover:-translate-y-1";
107178 ```
108179
109−- Component file uses PascalCase matching the folder name.
110−- Accept `className` prop to allow parent styling overrides.
111−- Use `export default function ComponentName(...)` pattern (consistent with existing codebase).
180+### 3.3 Floating/Looping Animation (for decorative elements)
112181
113−## Accessibility baseline
182+```jsx
183+<motion.span
184+ animate={{y: [0, -6, 0]}}
185+ transition={{duration: 3, repeat: Infinity, ease: "easeInOut"}}
186+>
187+ 🚀
188+</motion.span>
189+```
114190
115−- Use semantic HTML elements (`<section>`, `<nav>`, `<button>`, `<h1>`-`<h6>`).
116−- Ensure header navigation links correspond to section `id` attributes for anchor-based scrolling.
117−- Images should include meaningful `alt` text.
118−- Interactive elements must be keyboard-accessible (buttons, not divs with onClick).
191+---
119192
120−## Internationalization note
193+## 4. TYPOGRAPHY CONVENTIONS
121194
122−- The website currently supports a single language (English, with some Indonesian pending revision).
123−- No i18n framework is in use. If multi-language support is added in the future, use the `feat_lang` add-on structure already defined in `pricingSection.featuresList` as a starting point (ID/EN, 2 languages).
195+| Element | Tailwind classes | Style |
196+| ---------------- | -------------------------------------------------------------------------------------------- | ------------------------------- |
197+| Section heading | `text-3xl md:text-5xl font-black text-[var(--text-primary)]` | Black weight, responsive sizing |
198+| Section subtitle | `text-sm md:text-base uppercase tracking-[0.2em] font-semibold text-[var(--text-secondary)]` | Uppercase, letter-spaced |
199+| Card title | `text-lg md:text-xl font-bold text-[var(--text-primary)]` | Bold weight |
200+| Card description | `text-sm leading-relaxed text-[var(--text-secondary)]` | Normal weight |
201+| Pill/badge | `text-[9px] font-black px-2 py-1 rounded-full` | Tiny uppercase |
202+| Duration/tags | `text-[10px] font-black tracking-[0.2em] uppercase` | Small uppercase |
203+
204+---
205+
206+## 5. SECTION HEADER PATTERN (STANDARDIZED)
207+
208+All sections must use the shared `SectionHeader` component located at `src/components/sectionHeader/SectionHeader.jsx`. This ensures visual harmony and consistency across the portfolio.
209+
210+### Usage
211+
212+```jsx
213+import SectionHeader from "../../components/sectionHeader/SectionHeader";
214+
215+<SectionHeader
216+ title="Section Title" // Required
217+ subtitle="Optional subtitle" // Optional
218+ emoji="🚀" // Optional emoji above title
219+ align="center" // "center" (default) | "left"
220+/>;
221+```
222+
223+### Component API
224+
225+| Prop | Type | Default | Description |
226+| ---------- | -------------------- | ---------- | ----------------------------------------- |
227+| `title` | string | (required) | Section heading text |
228+| `subtitle` | string | `""` | Subtitle shown below gold accent strip |
229+| `emoji` | string | `""` | Optional emoji/icon displayed above title |
230+| `align` | `"center"`\|`"left"` | `"center"` | Text alignment |
231+
232+### Rendered Output
233+
234+- **Title**: `text-3xl md:text-5xl font-bold text-[var(--text-primary)]`
235+- **Emoji** (optional): `text-4xl md:text-5xl` above title
236+- **Gold underline accent strip**: Animated `width: 0 → 80px` with `bg-[var(--btn-primary-bg)]`
237+- **Subtitle** (optional): `text-sm md:text-base uppercase tracking-[0.2em] font-semibold text-[var(--text-secondary)]`
238+
239+### Example Usage by Section
240+
241+| Container | title | subtitle | emoji | align |
242+| -------------- | -------------------------- | ----------------------------- | ----- | ------ |
243+| Education | `educationInfo.title` | — | `🎓` | center |
244+| Skills | `skillsSection.title` | `skillsSection.subTitle` | `💡` | center |
245+| IntroVideo | `introVideo.title` | `introVideo.subtitle` | `🎬` | center |
246+| WorkExperience | `workExperiences.title` | `workExperiences.subtitle` | `💼` | center |
247+| BigProjects | `bigProjects.title` | `bigProjects.subtitle` | `🚀` | center |
248+| Achievement | `achievementSection.title` | `achievementSection.subtitle` | `🏆` | center |
249+| Pricing | `pricingSection.title` | `pricingSection.description` | `💰` | center |
250+| Contact | `contactInfo.title` | `contactInfo.subtitle` | `✉️` | center |
251+
252+---
253+
254+## 6. CARD STANDARDS
255+
256+### 6.1 Card Container
257+
258+```jsx
259+<div className="rounded-xl border border-[var(--border-light)] bg-[var(--bg-card)]
260+ transition-all duration-300 hover:-translate-y-1 hover:shadow-xl">
261+```
262+
263+### 6.2 Card Hover Border Accent
264+
265+```jsx
266+// Add group class to parent and use:
267+className =
268+ "group border border-[var(--border-light)] hover:border-[var(--btn-primary-bg)]/50 transition-colors";
269+```
270+
271+### 6.3 Bullet/List Items
272+
273+```jsx
274+// Gold dot bullet
275+<li className="flex gap-2">
276+ <span className="mt-[6px] h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--btn-primary-bg)]" />
277+ <span className="text-[var(--text-secondary)]">{text}</span>
278+</li>
279+
280+// Gold triangle bullet
281+<li className="flex items-start gap-3">
282+ <span className="text-[var(--btn-primary-bg)] mt-1.5 shrink-0 text-[8px]">▶</span>
283+ <span className="text-[var(--text-secondary)]">{text}</span>
284+</li>
285+```
286+
287+### 6.4 Spec/Info Grid (2-column metadata)
288+
289+```jsx
290+<div
291+ className="grid grid-cols-2 gap-2 text-[10px] p-3 rounded-lg border
292+ border-[var(--border-light)]"
293+ style={{
294+ backgroundColor: isDark ? "rgba(255,255,255,0.02)" : "rgba(0,0,0,0.02)"
295+ }}
296+>
297+ {/* Items */}
298+</div>
299+```
300+
301+---
302+
303+## 7. BUTTON PATTERNS
304+
305+### 7.1 Primary Button (Gold fill)
306+
307+```jsx
308+<button
309+ className="px-6 py-3 rounded-lg text-xs font-black uppercase tracking-widest
310+ transition-all hover:brightness-110 active:scale-95"
311+ style={{backgroundColor: "var(--btn-primary-bg)", color: "#fff"}}
312+>
313+ {text}
314+</button>
315+```
316+
317+### 7.2 Secondary/Outline Button
318+
319+```jsx
320+<button
321+ className="py-3 rounded-lg border border-[var(--border-light)] text-xs
322+ font-bold transition-colors"
323+ style={{color: "var(--text-primary)", backgroundColor: "transparent"}}
324+>
325+ {text}
326+</button>
327+```
328+
329+### 7.3 Danger/Remove Button
330+
331+```jsx
332+<button
333+ className="py-3 rounded-lg text-xs font-bold uppercase tracking-widest"
334+ style={{
335+ color: "#f87171",
336+ backgroundColor: "rgba(239,68,68,0.08)",
337+ border: "1px solid rgba(239,68,68,0.25)"
338+ }}
339+>
340+ {text}
341+</button>
342+```
343+
344+### 7.4 Legacy `.main-button` (used by Button.jsx component)
345+
346+- Defined in `src/components/button/Button.scss`
347+- Gold background, white text, rounded, hover lift effect
348+- For NEW code, prefer primary button pattern above
349+
350+---
351+
352+## 8. SVG ICON PATTERN
353+
354+Use either approach consistently. For new code, prefer **inline SVG components** (portable, no external dependency).
355+
356+### 8.1 Inline SVGs (Recommended for new code)
357+
358+```jsx
359+const MyIcon = () => (
360+ <svg
361+ width="14"
362+ height="14"
363+ viewBox="0 0 24 24"
364+ fill="none"
365+ stroke="currentColor"
366+ strokeWidth="2"
367+ >
368+ <circle cx="12" cy="12" r="10" />
369+ <path d="M12 6v6l4 2" />
370+ </svg>
371+);
372+```
373+
374+Usage: `<MyIcon />` — color inherits from `currentColor`.
375+
376+### 8.2 FontAwesome (Used in SocialMedia, Contact, header)
377+
378+- Import: `import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"`
379+- Brand icons: `import { faGithub, faLinkedin } from "@fortawesome/free-brands-svg-icons"`
380+- Solid icons: `import { faEnvelope } from "@fortawesome/free-solid-svg-icons"`
381+- Brand colors defined as SCSS variables in `variables.scss` (e.g., `$linkedin: #0e76a8`)
382+
383+### 8.3 React Icons (Installed but unused — avoid if possible)
384+
385+- The `react-icons` package is installed but not currently used in any component.
386+- Prefer inline SVGs or FontAwesome instead.
387+
388+---
389+
390+## 9. FORM BLUEPRINT (WhatsApp/Email Actions)
391+
392+**Do NOT use `<form>` elements.** All interactions are direct links:
393+
394+```jsx
395+// WhatsApp
396+<a href={`https://wa.me/6281331487753?text=${encodeURIComponent(message)}`}
397+ target="_blank" rel="noopener noreferrer">
398+ Contact via WhatsApp
399+</a>
400+
401+// Email
402+<a href={`mailto:briliantfikri@gmail.com?subject=${subject}&body=${body}`}>
403+ Send Email
404+</a>
405+```
406+
407+For the pricing checkout message, construct the message template inside a `handleCheckout` function and call `window.open()`.
408+
409+---
410+
411+## 10. IMAGE HANDLING (Vite)
412+
413+- **Always import images statically** (Vite convention):
414+ ```jsx
415+ import myImage from "../../assets/images/myImage.png";
416+ // Usage: <img src={myImage} alt="..." />
417+ ```
418+- **Do NOT use `require()`** — that is a CRA/Webpack pattern. The Skills.jsx container is the only file still using `require()` and should be migrated.
419+- For images from external URLs, use `src={url}` directly (no import needed).
420+- Use `loading="lazy"` on below-the-fold images.
421+
422+---
423+
424+## 11. LANGUAGE / I18N PATTERN
425+
426+All user-facing text that supports bilingual display uses this pattern:
427+
428+1. In `portfolio.jsx`: define as `{ en: "...", id: "..." }` object.
429+2. In component: import `LanguageContext`, get `lang`, call `getTranslation(configField, lang)`.
430+3. The helper lives in `src/utils/translations.js`.
431+
432+```jsx
433+import LanguageContext from "../../contexts/LanguageContext";
434+import {getTranslation} from "../../utils/translations";
435+
436+const {lang} = useContext(LanguageContext);
437+const title = getTranslation(config.title, lang); // returns string
438+```
439+
440+**Rules:**
441+
442+- `getTranslation()` handles nested objects recursively (e.g., `workflow` steps).
443+- Arrays of bilingual objects are resolved element-by-element.
444+- Plain strings/numbers pass through unchanged.
445+
446+---
447+
448+## 12. MODAL / OVERLAY PATTERNS
449+
450+### 12.1 ImageLightbox (`src/components/imageLightbox/ImageLightbox.jsx`)
451+
452+```jsx
453+<ImageLightbox src={url} alt={text} onClose={() => setState(null)} />
454+```
455+
456+- Fixed overlay, dark backdrop, centered image, close on backdrop click or ✕ button.
457+- Always wrap with `{show && <ImageLightbox ... />}`.
458+
459+### 12.2 ProjectShowcase (`src/components/projectShowcase/ProjectShowcase.jsx`)
460+
461+```jsx
462+<ProjectShowcase
463+ title={string}
464+ description={string}
465+ media={[{type: "image" | "video", url, caption, thumbnail}]}
466+ externalUrl={
467+ string
468+ } /* Optional — shows "Visit Website" button in top-right header */
469+ onClose={fn}
470+/>
471+```
472+
473+- Full-screen dark overlay with media gallery.
474+- **"Visit Website"** button appears in top-right header ONLY when `externalUrl` is provided.
475+- Used by AchievementCard and BigProject containers.
476+
477+---
478+
479+## 13. THEME TOGGLE SYSTEM
480+
481+- Global context: `src/contexts/StyleContext.js` provides `{ isDark, changeTheme }`.
482+- Theme is persisted in `localStorage` via `useLocalStorage` hook.
483+- Toggle switch: `src/components/ToggleSwitch/ToggleSwitch.jsx` (sun/moon emoji).
484+- Theme class: `.light-mode` or `.dark-mode` on root `<div>` in `Main.jsx`.
485+- CSS variables defined in `src/variables.scss` respond to these classes.
486+
487+```jsx
488+import StyleContext from "../../contexts/StyleContext";
489+const { isDark } = useContext(StyleContext);
490+
491+// Use in conditional styling:
492+<div style={{ backgroundColor: isDark ? "rgba(0,0,0,0.4)" : "rgba(255,255,255,0.6)" }}>
493+```
494+
495+---
496+
497+## 14. LOTTIE ANIMATION PATTERN
498+
499+```jsx
500+import DisplayLottie from "../../components/displayLottie/DisplayLottie";
501+import animationData from "../../assets/lottie/myAnimation";
502+
503+{
504+ illustration.animated ? (
505+ <DisplayLottie animationData={animationData} />
506+ ) : (
507+ <img src={fallbackImage} alt="..." />
508+ );
509+}
510+```
511+
512+- All Lottie JSON files live in `src/assets/lottie/`.
513+- The `DisplayLottie` component wraps `lottie-react` with `Suspense` + `<Loading>` fallback.
514+
515+---
516+
517+## 15. FRAMER MOTION IMPORTS
518+
519+Always import only what's needed from `framer-motion`:
520+
521+```jsx
522+import {motion, AnimatePresence} from "framer-motion";
523+```
524+
525+- `motion.div`, `motion.button`, `motion.a`, `motion.span`, `motion.img`, `motion.h1`-`h6` supported.
526+- `AnimatePresence` for mounting/unmounting animations (modals, checkout bar).
527+
528+---
529+
530+## 16. KNOWN INCONSISTENCIES & MIGRATION NOTES
531+
532+These are NOT required to fix immediately, but be aware of them when touching related code:
533+
534+1. **`Skills.jsx` uses `require()` for static images** — should use `import` like all other containers (Vite pattern).
535+2. **`Button.jsx` does not spread `className`** — it wraps in `<div className={className}>` instead of applying directly to `<a>`. New button code should use the inline primary button pattern instead.
536+3. **`variables.scss` has legacy SCSS variables** that duplicate CSS custom properties (e.g., `$textColorDark` is `#ffffff` but already covered by `--text-primary` in dark mode). When refactoring, prefer CSS custom properties.
537+4. **`Greeting.scss` and `Skills.scss` still contain legacy classes** that are partially overridden by Tailwind. When editing, prefer moving styles to Tailwind and removing SCSS.
538+5. **`Header.scss` is partially refactored** — it has both legacy classes and a comment noting Tailwind removal was done. When editing, complete the migration.
539+6. **`SplashScreen.jsx` uses `.css` instead of `.scss`** — minor inconsistency.
540+7. **`ProjectShowcase.jsx` and `Pricing.jsx` both define duplicate inline SVG icons** (`Close`, `Image`) — consider extracting to `src/utils/icons.jsx` in the future.
541+
542+---
543+
544+## 17. FILE ORGANIZATION SUMMARY
545+
546+```
547+src/
548+├── portfolio.jsx # Single config file — all content data
549+├── App.jsx # Root component
550+├── Main.jsx # Container orchestrator, theme/language providers
551+├── variables.scss # Design tokens (CSS vars + SCSS vars)
552+├── contexts/
553+│ ├── StyleContext.js # Theme state (isDark, changeTheme)
554+│ └── LanguageContext.js # Language state (lang, changeLang)
555+├── hooks/
556+│ └── useLocalStorage.js # Persistent state hook
557+├── utils/
558+│ └── translations.js # getTranslation() helper
559+├── components/ # Reusable UI components
560+│ ├── header/Header.jsx
561+│ ├── button/Button.jsx
562+│ ├── footer/Footer.jsx
563+│ ├── socialMedia/SocialMedia.jsx
564+│ ├── ToggleSwitch/ToggleSwitch.jsx
565+│ ├── LanguageToggle/LanguageToggle.jsx
566+│ ├── displayLottie/DisplayLottie.jsx
567+│ ├── imageLightbox/ImageLightbox.jsx
568+│ ├── projectShowcase/ProjectShowcase.jsx
569+│ ├── achievementCard/AchievementCard.jsx
570+│ ├── educationCard/EducationCard.jsx
571+│ ├── experienceCard/ExperienceCard.jsx
572+│ ├── softwareSkills/SoftwareSkill.jsx
573+│ └── ... (other legacy components)
574+├── containers/ # Page sections (one per portfolio section)
575+│ ├── greeting/Greeting.jsx
576+│ ├── introVideo/IntroVideo.jsx
577+│ ├── skills/Skills.jsx
578+│ ├── education/Education.jsx
579+│ ├── workExperience/WorkExperience.jsx
580+│ ├── projects/Projects.jsx
581+│ ├── BigProjects/BigProject.jsx
582+│ ├── achievement/Achievement.jsx
583+│ ├── pricing/Pricing.jsx
584+│ ├── contact/Contact.jsx
585+│ ├── splashScreen/SplashScreen.jsx
586+│ └── ... (others)
587+└── assets/
588+ ├── images/ # Static images (imported in portfolio.jsx)
589+ ├── lottie/ # Lottie JSON animations
590+ └── fonts/ # Custom fonts
591+```
124592
