docs(plan): add frontend homepage and CV page plan
Browse files- Bootstraps Vue 3 + Vite + Semantic UI (fomantic-ui-css) frontend at
src/frontend/ with responsive Home and CV pages
- Shared animated graph background sampled from the COINs 3- and 4-node
motif enumeration, with prefers-reduced-motion fallback
- Homepage integrates NELL Fact of the Day (sample-triples) and a
System Status widget aggregating /health, /, and /methods
- Backend adds subject_label/relation_label/object_label to
sample-triples using existing clean_entity_name/clean_relation_name
helpers; api.yaml, docs/postman, and backend README updated
- CV content sourced verbatim from docs/Profile.pdf (LinkedIn export)
- Dark mode, mobile sidebar, rabbit-emoji favicon, logos for EPFL,
Swisscom, Attercop, CERN, Data Masters, Endava, FCSE, Isomorphic
Labs, Google DeepMind
|
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Frontend Bootstrap: Homepage and CV Page
|
| 2 |
+
|
| 3 |
+
Bootstraps the Vue.js + Semantic UI frontend of the PhD website with two responsive pages (Homepage, CV), dark mode, a shared animated graph background sampled from the COINs motif enumeration, and the first live backend integrations: **NELL fact of the day** (sample-triples) plus a **system status** widget driven by the health endpoints.
|
| 4 |
+
|
| 5 |
+
## Context
|
| 6 |
+
|
| 7 |
+
The backend (Django REST API at `src/backend/`) is working and exposes the demo endpoints, a random-triple endpoint per KG dataset, and three health/discovery endpoints (`/api/v1/health`, `/api/v1/`, `/api/v1/methods`). No frontend exists yet β `src/frontend/` is empty. `CLAUDE.md` pins the stack to **Vue.js + Semantic UI ONLY** and prescribes a green-themed background of "drawings of graphs (nodes and edges) moving around like molecules". The demo pages (COINs, MultiProxAn, KG-anomaly) are out of scope here β only Homepage and CV ship now, with placeholder cards teasing the future demos.
|
| 8 |
+
|
| 9 |
+
**`docs/Profile.pdf` (LinkedIn export) is the single source of truth for all Work Experience and Education text on `/cv`** β every bullet, title, date, and location is transcribed verbatim from it into `src/frontend/src/data/cv.js`. The headshot at `docs/picture.jpg` is the avatar. Other sections (Profile summary, Skills, Languages, Projects, Publications, References, Honors & Awards) are also taken from `docs/Profile.pdf`. The COINs research code at `src/research/COINs-KGGeneration/graph_analysis/metrics.py` (`get_all_motifs`, lines 374β394) enumerates **13 directed 3-node motifs and 199 directed 4-node motifs** β these drive the animated background.
|
| 10 |
+
|
| 11 |
+
### Why Vite
|
| 12 |
+
|
| 13 |
+
**Vite** is a modern frontend build tool (dev server + production bundler). It serves the source over native ES modules in dev β no bundling β so the dev server starts in <1 s and HMR is near-instant regardless of project size. For production it bundles with Rollup (tree-shaking, code-splitting). It is the recommended stack for Vue 3 (replaces `vue-cli`/webpack, which the older `ti-dashboard-master` CERN project used).
|
| 14 |
+
|
| 15 |
+
Why we need a build tool at all: `.vue` Single-File Components are not native browser syntax β the `<template>` / `<script>` / `<style>` blocks must be compiled to JS. Without a build step we'd be limited to runtime template compilation, no SFCs, no path aliases, no env files, no asset pipeline. Vite gives us all that with effectively zero config.
|
| 16 |
+
|
| 17 |
+
### Inspiration from prior Vue projects
|
| 18 |
+
|
| 19 |
+
- **`ti-dashboard-master/frontend`** (CERN TI dashboard β Vue 2 + vue-cli + TypeScript + Semantic UI + Vuex + vue-router): clean `components/` (NavBar, DatePicker, EventsTable, OperatorInfo, UserSearch, Vistar, CallsChart) vs `views/` (Dashboard.vue); single Vuex store; router-driven layout. We mirror the small-prop-driven-component philosophy, on Vue 3 + Vite.
|
| 20 |
+
- **`github.com/Bani57/finkiScholar`** (FOSS coursework β Laravel + Vue 2 + Semantic UI + vue-router, bundled via laravel-mix). Components: App, NavBar, Home, Profile, Archive, Publish, Login, Register, DatePicker. Confirms the NavBar + view-component pattern used here, and is the canonical source for the UKIM/FINKI logo (`resources/assets/logo.png`).
|
| 21 |
+
|
| 22 |
+
## Assumptions and Constraints
|
| 23 |
+
|
| 24 |
+
- Stack is **Vue 3 + Vite + Vue Router 4 + Pinia + Semantic UI CSS (`fomantic-ui-css`) + axios**. No Tailwind, no d3, no Three.js. Semantic is consumed as CSS classes on plain Vue templates β no component-wrapper library.
|
| 25 |
+
- Backend dev URL: `http://localhost:8000`; prod URL: `https://bani57.pythonanywhere.com`. Switched via Vite env files (`.env.development`, `.env.production`) exposing `VITE_API_BASE_URL`.
|
| 26 |
+
- The motif list is **static** β generated once from Python and committed as JSON. No runtime call to the research code.
|
| 27 |
+
- **Responsive design is a hard requirement**: layouts must work cleanly from 320 px up to 4K. Use Semantic's responsive grid (`computer only`, `tablet only`, `mobile only` classes) and CSS `clamp()` for typography.
|
| 28 |
+
- **Dark mode is in scope**: theme toggle in NavBar, persisted in `localStorage`, defaulting to system preference (`prefers-color-scheme`). Implemented via a `data-theme="dark"` attribute on `<html>` + CSS custom properties, so Semantic's components remain styled by overriding background/text vars only.
|
| 29 |
+
- Animations use SVG + `requestAnimationFrame`, respect `prefers-reduced-motion`. No WebGL.
|
| 30 |
+
- Logos are downloaded once and committed to `src/frontend/public/logos/`. If a logo is not freely redistributable, we use a text fallback (acronym in a styled badge).
|
| 31 |
+
- Browser target: evergreen (Chrome/Edge/Firefox/Safari latest).
|
| 32 |
+
- **Docs hygiene for every backend change**: when an endpoint's request/response shape, behaviour, or error codes change (or a new endpoint is added), update all three in the same commit β `docs/api.yaml` (OpenAPI spec), `docs/postman/` (collection + environment), and `src/backend/README.md` (endpoint table).
|
| 33 |
+
|
| 34 |
+
## Scope
|
| 35 |
+
|
| 36 |
+
**In scope**
|
| 37 |
+
- `src/frontend/` Vite scaffold, build/dev scripts, frontend `README.md`.
|
| 38 |
+
- Shared layout: persistent navbar (Home, CV, dark-mode toggle); routed main content; shared `GraphBackground` behind all pages; rabbit-emoji favicon.
|
| 39 |
+
- Homepage (`/`): hero intro with avatar, education/work highlights, demo preview cards (COINs / MultiProxAn / KG-Anomaly β each "coming soon"), **NELL Fact of the Day** widget, **System Status** widget aggregating all three health/discovery endpoints.
|
| 40 |
+
- CV page (`/cv`): full mirror of the PDF using reusable components (SocialLink, WorkExperience, Education, Skill, Language, Publication, Reference, Project), plus the headshot.
|
| 41 |
+
- Dark mode + theme persistence.
|
| 42 |
+
- Mobile/tablet responsiveness for both pages.
|
| 43 |
+
- Motif JSON export script (one-shot, committed output).
|
| 44 |
+
- Logo assets for: EPFL, Swisscom, Attercop, **CERN**, **Data Masters**, **Endava**, Ss. Cyril & Methodius University (UKIM) / Faculty of Computer Science and Engineering (FCSE/FINKI), Isomorphic Labs, Google DeepMind.
|
| 45 |
+
|
| 46 |
+
**Out of scope**
|
| 47 |
+
- The three interactive demo pages (only teaser cards on the homepage).
|
| 48 |
+
- Authentication, analytics, i18n.
|
| 49 |
+
- Deployment config changes on PythonAnywhere (later plan).
|
| 50 |
+
- Unit/e2e tests β manual browser verification only for this first cut.
|
| 51 |
+
|
| 52 |
+
## Design
|
| 53 |
+
|
| 54 |
+
### Directory layout (`src/frontend/`)
|
| 55 |
+
|
| 56 |
+
```
|
| 57 |
+
src/frontend/
|
| 58 |
+
index.html # <link rel="icon" ...> rabbit-emoji SVG data URL
|
| 59 |
+
package.json
|
| 60 |
+
vite.config.js
|
| 61 |
+
.env.development # VITE_API_BASE_URL=http://localhost:8000
|
| 62 |
+
.env.production # VITE_API_BASE_URL=https://bani57.pythonanywhere.com
|
| 63 |
+
README.md
|
| 64 |
+
public/
|
| 65 |
+
logos/
|
| 66 |
+
epfl.svg
|
| 67 |
+
swisscom.svg
|
| 68 |
+
attercop.svg
|
| 69 |
+
cern.svg
|
| 70 |
+
data-masters.svg
|
| 71 |
+
endava.svg
|
| 72 |
+
fcse.svg
|
| 73 |
+
ukim.svg
|
| 74 |
+
isomorphic-labs.svg
|
| 75 |
+
google-deepmind.svg
|
| 76 |
+
README.md # source + licence per logo
|
| 77 |
+
avatar.jpg # copied from docs/picture.jpg at build/setup time
|
| 78 |
+
favicon.svg # inline rabbit emoji
|
| 79 |
+
src/
|
| 80 |
+
main.js # app + router + pinia + fomantic-ui-css
|
| 81 |
+
App.vue # <NavBar/> + <GraphBackground/> + <RouterView/>
|
| 82 |
+
router/index.js # routes: /, /cv, scrollBehavior to top
|
| 83 |
+
stores/
|
| 84 |
+
theme.js # Pinia: { mode: 'light'|'dark', toggle(), init() }
|
| 85 |
+
api/
|
| 86 |
+
client.js # axios instance; baseURL = VITE_API_BASE_URL + '/api/v1'
|
| 87 |
+
coins.js # sampleTriples(datasetId, count)
|
| 88 |
+
health.js # getHealth(), getApiRoot(), getMethods()
|
| 89 |
+
data/
|
| 90 |
+
motifs.json # { motifs3: [[[0,1],...]], motifs4: [...] }
|
| 91 |
+
cv.js # full CV content (single source of truth)
|
| 92 |
+
components/
|
| 93 |
+
layout/
|
| 94 |
+
NavBar.vue # nav links + theme toggle
|
| 95 |
+
PageSection.vue # Semantic `ui container segment` wrapper
|
| 96 |
+
ThemeToggle.vue # sun/moon icon button
|
| 97 |
+
background/
|
| 98 |
+
GraphBackground.vue # full-viewport SVG, spawns FloatingMotif instances
|
| 99 |
+
FloatingMotif.vue # one small SVG motif with motion state
|
| 100 |
+
cv/
|
| 101 |
+
SocialLink.vue
|
| 102 |
+
WorkExperience.vue # employer, role, dates, location, bullets, logo
|
| 103 |
+
Education.vue # institution, degree, dates, location, gpa, logo
|
| 104 |
+
Skill.vue # category + tag list
|
| 105 |
+
Language.vue # name + 1-5 proficiency dots
|
| 106 |
+
Publication.vue # title, venue, date, optional link
|
| 107 |
+
Reference.vue # name, title, org, email
|
| 108 |
+
ProjectCard.vue
|
| 109 |
+
home/
|
| 110 |
+
HeroIntro.vue # avatar + name + title + bio + CTAs
|
| 111 |
+
DemoPreviewCard.vue # icon + title + description + "coming soon" badge
|
| 112 |
+
FactOfTheDay.vue # subject -> relation -> object pills + reload
|
| 113 |
+
SystemStatus.vue # aggregates /health, /, /methods into one card
|
| 114 |
+
views/
|
| 115 |
+
HomeView.vue
|
| 116 |
+
CVView.vue
|
| 117 |
+
NotFoundView.vue
|
| 118 |
+
styles/
|
| 119 |
+
tokens.css # CSS custom properties for both themes
|
| 120 |
+
theme.css # Semantic overrides + dark-mode rules
|
| 121 |
+
responsive.css # breakpoint helpers
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
### Key existing files referenced
|
| 125 |
+
|
| 126 |
+
- `src/research/COINs-KGGeneration/graph_analysis/metrics.py:374` β `get_all_motifs(k)` for JSON export.
|
| 127 |
+
- `src/backend/api/utils.py` β `clean_entity_name` / `clean_relation_name`; called by the extended sample-triples view.
|
| 128 |
+
- `src/backend/api/views/coins.py`, `src/backend/api/views/health.py` (and `api/urls.py`) β confirm response shapes; `coins.py` gets the additive `*_label` fields.
|
| 129 |
+
- `docs/Profile.pdf` β verbatim source for every Work Experience, Education, and other narrative section in `cv.js`. `docs/cvAndrejJanchevski.pdf` is not used for CV text.
|
| 130 |
+
- `docs/picture.jpg` β copied to `src/frontend/public/avatar.jpg`.
|
| 131 |
+
|
| 132 |
+
### GraphBackground animation
|
| 133 |
+
|
| 134 |
+
- Full-viewport fixed SVG, `z-index: -1`, mounted once in `App.vue`.
|
| 135 |
+
- Spawns **N β 20** `FloatingMotif` instances (single tunable constant; reduced on mobile via `matchMedia('(max-width: 640px)')` to β 10 for performance).
|
| 136 |
+
- Each `FloatingMotif`: random pick from `motifs.json` (50/50 weight 3-node vs 4-node so 3-node motifs stay visible), circular layout (k points on a 20 px radius), SVG `<line>` edges with arrow markers + `<circle>` nodes, drift via `requestAnimationFrame` translating the `<g>` plus gentle rotation, wrap around viewport edges, re-randomise on wrap (motif, drift vector, scale 40β80 px).
|
| 137 |
+
- Color palette uses CSS variables so dark mode flips to brighter green-on-dark; light mode is translucent sea-green-on-cream.
|
| 138 |
+
- `prefers-reduced-motion: reduce` β motifs render static at fixed positions, no rAF loop.
|
| 139 |
+
|
| 140 |
+
### Motif data generation
|
| 141 |
+
|
| 142 |
+
- One-shot script `scripts/export_motifs.py` (new) imports `get_all_motifs(3)` and `get_all_motifs(4)` and writes `src/frontend/src/data/motifs.json` with two arrays of edge lists. Counts asserted (13 and 199). Output committed.
|
| 143 |
+
|
| 144 |
+
### NELL Fact of the Day β name prettification
|
| 145 |
+
|
| 146 |
+
The COINs `/sample-triples` endpoint returns raw IDs (e.g. `concept:person:john_doe`, `concept:agentcollaborateswithagent`). The backend already exposes `clean_entity_name(name, dataset_id)` and `clean_relation_name(name, dataset_id)` in `src/backend/api/utils.py` covering all three datasets (freebase strips `/m/`, wordnet drops the POS suffix, nell strips `concept:` prefixes and takes the tail segment, with a `new`-slug edge case). Approach:
|
| 147 |
+
|
| 148 |
+
1. **Backend**: extend the `/coins/datasets/{id}/sample-triples` response so each triple carries `subject_label`, `relation_label`, `object_label` produced by the existing `clean_entity_name` / `clean_relation_name` helpers. Keep the raw IDs too (for tooltips and downstream use). This is a small, additive change in `src/backend/api/views/coins.py` β no new Python logic, just a call to the existing utils. Updates `docs/api.yaml` accordingly.
|
| 149 |
+
2. **Frontend**: `FactOfTheDay.vue` calls `coins.sampleTriples('nell', 1)` and renders three Semantic `ui label` pills (subject β relation β object) using the `*_label` fields; raw ID shown as a tooltip on hover.
|
| 150 |
+
3. No frontend-side lookup table needed β the `nellLabels.json` export is dropped; `data/` loses that file. Falls back to the raw ID if a label field is absent (defensive, but shouldn't happen).
|
| 151 |
+
4. Loading state: Semantic `ui placeholder`. Error state: muted "Could not reach the server". Reload icon button re-fetches.
|
| 152 |
+
|
| 153 |
+
### System Status widget
|
| 154 |
+
|
| 155 |
+
Single card on the homepage that calls all three discovery endpoints in parallel on mount:
|
| 156 |
+
|
| 157 |
+
- `GET /api/v1/health` β green/yellow/red dot per model (`coins`, `multiproxan`, `kg_anomaly`); shows inference lock state.
|
| 158 |
+
- `GET /api/v1/` β API name + version (small caption).
|
| 159 |
+
- `GET /api/v1/methods` β list of the three research methods with thesis section labels (links to the future demo pages β currently inert).
|
| 160 |
+
|
| 161 |
+
Aggregation in `api/health.js`:
|
| 162 |
+
|
| 163 |
+
```js
|
| 164 |
+
export async function fetchSystemStatus() {
|
| 165 |
+
const [health, root, methods] = await Promise.all([
|
| 166 |
+
client.get('/health'), client.get('/'), client.get('/methods'),
|
| 167 |
+
]);
|
| 168 |
+
return { health: health.data, root: root.data, methods: methods.data };
|
| 169 |
+
}
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
Refresh button re-runs the parallel fetch. If any single call fails the others still render.
|
| 173 |
+
|
| 174 |
+
### Dark mode
|
| 175 |
+
|
| 176 |
+
- `src/styles/tokens.css` defines `--bg`, `--surface`, `--text`, `--muted`, `--primary`, `--primary-strong`, `--motif-stroke`, `--motif-fill` for both light and dark, scoped under `:root` and `:root[data-theme="dark"]`.
|
| 177 |
+
- `src/stores/theme.js` (Pinia) initialises from `localStorage('theme') || matchMedia('(prefers-color-scheme: dark)') ? 'dark' : 'light'` and writes `document.documentElement.dataset.theme` on changes.
|
| 178 |
+
- `ThemeToggle.vue` is a Semantic `ui icon button` (sun β moon).
|
| 179 |
+
- Light primary `#2e8b57` (sea green); dark primary `#3ddc97` (brighter for contrast on dark surfaces).
|
| 180 |
+
|
| 181 |
+
### Responsive layout
|
| 182 |
+
|
| 183 |
+
- All pages wrapped in `ui container` (Semantic switches widths automatically per breakpoint).
|
| 184 |
+
- Homepage hero: stacked on mobile, side-by-side (avatar + text) on tablet+. Demo cards: `ui three column stackable cards` (collapses to single column on mobile).
|
| 185 |
+
- CV page: header section uses `ui stackable grid`. WorkExperience/Education items: logo column hidden on `mobile only`, stacked above on `tablet only`, side-by-side on `computer only`.
|
| 186 |
+
- Typography uses `clamp(1rem, 2.5vw, 1.25rem)` for body, similar scaling for headings.
|
| 187 |
+
- NavBar collapses to a Semantic `ui sidebar` triggered by a hamburger icon below 768 px.
|
| 188 |
+
|
| 189 |
+
### CV content pipeline
|
| 190 |
+
|
| 191 |
+
- `src/frontend/src/data/cv.js` exports a structured object β single source of truth, transcribed from the PDF.
|
| 192 |
+
- Reusable components are dumb (props only). Optional `logo` prop on Work/Education items references `/logos/<slug>.svg`; missing file β acronym badge fallback.
|
| 193 |
+
- Full merged Work Experience list (reverse-chronological):
|
| 194 |
+
1. **Attercop** β Data Scientist, 08/2025βpresent, Remote / Brighton, UK.
|
| 195 |
+
2. **EPFL** β Machine Learning Researcher (PhD), 11/2021β10/2025, Lausanne, Switzerland.
|
| 196 |
+
3. **Data Masters** β Data Consultant, 03/2024β08/2024, Skopje, North Macedonia.
|
| 197 |
+
4. **Swisscom** β ML & Data Analysis Intern, 02/2021β08/2021, Lausanne, Switzerland.
|
| 198 |
+
5. **CERN** β Software Engineer intern (ISOLDE, Summer Student Programme), 06/2018β08/2018, Meyrin, Switzerland β PHP back-end for collider operator apps + TI dashboard; front-end migration from Angular to Vue.
|
| 199 |
+
6. **Faculty of Computer Science and Engineering (FCSE/FINKI), Skopje** β Student Assistant, 02/2018β06/2019, Skopje, North Macedonia β part-time lab TA across 5 courses, 12 h/week for 3 semesters.
|
| 200 |
+
7. **Endava** β Software Engineer intern, 07/2017β08/2017, Skopje, North Macedonia β 6-week training programme; built a three-part task-management app in AngularJS + Spring Boot + MySQL.
|
| 201 |
+
- Tagline: LinkedIn headline "Data Scientist & Researcher | Graph ML, Gen AI, Deep Learning" β used in `HeroIntro.vue` and the CV header.
|
| 202 |
+
- References section: **Andreas Loukas** (Isomorphic Labs), **Claudiu Musat** (Google DeepMind), **Volkan Cevher** (EPFL), plus **Martina Naumovska Necheska** (Data Masters β `martina.naumovska@datamasters.co`).
|
| 203 |
+
- Honors & Awards section (from LinkedIn, not in the short CV PDF): three scholarship/honorary-award items β worth rendering as a small list on `/cv`.
|
| 204 |
+
- Publications list on the website should add **"Scalable Methods for Knowledge Graph Reasoning and Generation"** (the PhD thesis itself, linked to `https://infoscience.epfl.ch/entities/publication/87acf391-feef-43a0-b665-7f2f0bc70b2c`).
|
| 205 |
+
|
| 206 |
+
### Theming + favicon
|
| 207 |
+
|
| 208 |
+
- `index.html` favicon is an inline SVG data URL of the rabbit emoji: `<text font-size="80" y="80">π</text>` wrapped in a 100Γ100 SVG β no binary asset.
|
| 209 |
+
- Page title set per-route via `router.afterEach` writing `document.title`.
|
| 210 |
+
|
| 211 |
+
### API client
|
| 212 |
+
|
| 213 |
+
- Single axios instance in `api/client.js` with `baseURL = ${VITE_API_BASE_URL}/api/v1`, 10 s timeout.
|
| 214 |
+
- Modules `coins.js` and `health.js` wrap individual calls. Errors expose `error.response?.data?.message` from the backend's error envelope.
|
| 215 |
+
|
| 216 |
+
## Implementation Steps
|
| 217 |
+
|
| 218 |
+
1. **Scaffold `src/frontend/`**: `npm create vite@latest frontend -- --template vue`; install `vue-router`, `pinia`, `axios`, `fomantic-ui-css`. Configure Vite, env files, npm scripts (`dev`, `build`, `preview`). Add rabbit-emoji favicon in `index.html`. Commit.
|
| 219 |
+
2. **Shared layout + theming**: `App.vue`, `NavBar.vue` (Home/CV links + `ThemeToggle`), `tokens.css`, `theme.css`, `responsive.css`, Pinia `theme` store with persistence, mobile sidebar. Router with `/`, `/cv`, `/:pathMatch(.*)*` (404). Verify `npm run dev`.
|
| 220 |
+
3. **Generate motif JSON**: `scripts/export_motifs.py`; run once; commit `src/frontend/src/data/motifs.json` (assert 13 + 199).
|
| 221 |
+
4. **Extend sample-triples response with labels**: in `src/backend/api/views/coins.py`, call the existing `clean_entity_name` / `clean_relation_name` helpers from `api/utils.py` to add `subject_label`, `relation_label`, `object_label` alongside the raw IDs. Update `docs/api.yaml`, `src/backend/README.md`, and the Postman collection/environment under `docs/postman/` (add example response fields so the team can smoke-test the change).
|
| 222 |
+
5. **Build `GraphBackground` + `FloatingMotif`**: SVG renderer, rAF drift, arrow markers, dark/light via CSS vars, mobile count reduction, reduced-motion fallback. Mount in `App.vue`.
|
| 223 |
+
6. **API client**: `client.js`, `coins.js`, `health.js`. Smoke-test against local backend.
|
| 224 |
+
7. **CV data + assets**: copy `docs/picture.jpg` β `src/frontend/public/avatar.jpg`; transcribe `docs/Profile.pdf` verbatim into `src/frontend/src/data/cv.js` (Work Experience, Education, Skills, Languages, Projects, Publications, References, Honors & Awards, Profile summary).
|
| 225 |
+
8. **Download logos**: EPFL, Swisscom, Attercop, **CERN**, **Endava**, UKIM/FINKI, Isomorphic Labs, Google DeepMind. Save as SVG where possible to `public/logos/`. Record source + licence in `public/logos/README.md`.
|
| 226 |
+
9. **Reusable CV components**: `SocialLink`, `WorkExperience`, `Education`, `Skill`, `Language`, `Publication`, `Reference`, `ProjectCard`. Prop-driven; Semantic styling; logo-fallback acronym badge.
|
| 227 |
+
10. **Homepage view**: `HomeView.vue` composing `HeroIntro` (avatar + bio from `cv.js`), education/work summary, three `DemoPreviewCard`s, `FactOfTheDay`, `SystemStatus`.
|
| 228 |
+
11. **CV view**: `CVView.vue` composing components into sections (Header with photo + contact + socials; Profile; Experience; Education; Skills; Languages; Projects; Publications; References) inside `PageSection` wrappers.
|
| 229 |
+
12. **Polish**: scroll-to-top on route change, page titles, error boundaries on widgets, mobile responsive QA at 320 / 375 / 768 / 1280 / 1920 px, dark-mode QA.
|
| 230 |
+
13. **Manual verification** (see below).
|
| 231 |
+
|
| 232 |
+
## Verification
|
| 233 |
+
|
| 234 |
+
Start backend (`python manage.py runserver` at `src/backend/`) and frontend (`npm run dev` at `src/frontend/`). In the browser:
|
| 235 |
+
|
| 236 |
+
- `/` renders: navbar (with theme toggle), animated graphs in the background, hero intro with the headshot, three demo teaser cards, **Fact of the Day** showing prettified NELL subject β relation β object pills (raw IDs visible on hover), and **System Status** card showing health dots for all three models, API version, and the methods list. All widgets reload via their refresh icons.
|
| 237 |
+
- `/cv` renders: full CV mirroring the PDF including CERN and Endava entries with logos; headshot in the header; every reusable component appears at least once.
|
| 238 |
+
- NavBar links route between `/` and `/cv` without a full reload; background animation persists across the route change. 404 route renders for unknown paths.
|
| 239 |
+
- Dark-mode toggle switches the entire UI (Semantic surfaces, motif background colors, text contrast). Refresh preserves the choice (`localStorage`). System dark-mode preference picked up on first load.
|
| 240 |
+
- Resize from 320 px β 1920 px: layout reflows cleanly, no horizontal scroll, NavBar collapses to a hamburger sidebar below 768 px, CV cards stack on mobile.
|
| 241 |
+
- DevTools Network tab confirms `GET /api/v1/coins/datasets/nell/sample-triples?count=1`, `GET /api/v1/health`, `GET /api/v1/`, `GET /api/v1/methods` all 200.
|
| 242 |
+
- Toggle OS "reduce motion" β motifs stop drifting but remain rendered.
|
| 243 |
+
- `npm run build && npm run preview` succeeds; rabbit-emoji favicon visible in the tab.
|
| 244 |
+
- Kill the backend β both widgets show muted error states; rest of the homepage and CV still render.
|
| 245 |
+
|
| 246 |
+
## Open Questions
|
| 247 |
+
|
| 248 |
+
(none β all employer/experience questions resolved via `docs/Profile.pdf`)
|