Initial deploy setup
deploy / deploy (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-21 14:00:00 +02:00
commit b8091e59bd
285 changed files with 27854 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
# Toegankelijkheid
Doel: WCAG 2.2 AA voor de kernflows.
## Semantiek
- één `h1` per pagina, hiërarchische headings;
- navigatie in `<nav>` met duidelijke accessible name;
- lijsten/tabellen voor werkelijke lijst-/tabulaire data;
- formulieren met expliciete `<label>`;
- statusmetadata als definition list waar passend;
- externe links herkenbaar in tekst/accessible label.
## Toetsenbord
Alle functies moeten zonder muis werken. Focusvolgorde volgt visuele volgorde. `:focus-visible` heeft een contrastvolle ring. Geen keyboard traps. Na server-POST landt focus logisch via heading/message; toekomstige modals herstellen focus.
## Kleur en contrast
- bodytekst minimaal 4,5:1;
- grote tekst minimaal 3:1;
- focus en componentgrenzen minimaal 3:1 waar vereist;
- status nooit alleen via kleur;
- light/dark afzonderlijk toetsen.
## Zoom en reflow
- bruikbaar op 320 CSS px breed;
- 200% zoom zonder verlies van acties/tekst;
- geen vaste hoogte voor tekstkaarten;
- lange URL's en woorden breken;
- horizontale scroll alleen voor intrinsiek brede data met alternatief.
## Forms en fouten
- foutsummary bovenaan bij meerdere fouten;
- inline fout bij veld;
- `aria-invalid` en `aria-describedby`;
- voorbeeld en vereiste syntax als hint;
- geen placeholder als enige label;
- validatie niet alleen on blur.
## Dynamiek
- server-rendering heeft geen verborgen loading state;
- async updates gebruiken live regions spaarzaam;
- successmessages niet alleen tijdelijk visueel;
- motion respecteert reduced motion;
- tijdslimieten bestaan niet in gebruikersflows.
## Vacaturetekst
Gesanitized HTML behoudt headings, paragraphs, lists en links waar veilig. Verwijder scripts, forms, iframes, eventhandlers en gevaarlijke URL-schema's. Voeg waar nodig `rel="noopener noreferrer"` toe aan externe links.
## Testen
Per zichtbare taak:
1. keyboard-only;
2. browser zoom/reflow;
3. automatische HTML/accessibilityscan waar beschikbaar;
4. screenreader smoke test voor dashboard, vacaturedetail en profiel;
5. contrastcontrole;
6. test met lange/lege/unknown content.
+121
View File
@@ -0,0 +1,121 @@
# Design system
## 1. Merkgevoel
Rustig, betrouwbaar, helder en licht technisch. De interface moet focus geven, niet gamificeren. Geen confetti, eindeloze badges of agressieve notificatiekleuren.
## 2. Visuele tokens
De implementatie staat in `static/css/app.css`. Gebruik semantische custom properties in plaats van losse kleuren in componenten.
Aanbevolen tokenrollen:
```css
--surface-page
--surface-card
--surface-raised
--text-primary
--text-secondary
--text-muted
--border-subtle
--accent
--accent-strong
--success
--warning
--danger
--focus-ring
--shadow-card
--radius-sm
--radius-md
--radius-lg
--space-1 ... --space-8
```
Donkere modus volgt `prefers-color-scheme` en behoudt semantische contrastrollen.
## 3. Typografie
- systeemfontstack, geen externe fontrequest;
- basis 16px;
- body line-height ongeveer 1,55;
- compacte metadata 0,80,9rem maar nooit onder bruikbare leesgrootte;
- titels met duidelijke schaal, niet alleen gewicht;
- cijfers voor scores mogen tabular nums gebruiken.
## 4. Spacing en layout
- 4/8px-gebaseerde schaal;
- contentcontainer ongeveer 11801280px;
- kaarten met consistente padding 2024px desktop, 16px mobiel;
- minimaal 44×44px touch targets voor primaire interactie;
- grids breken onder 900px en 640px logisch af;
- vaste/sticky elementen mogen content niet bedekken.
## 5. Componenten
### Button
Varianten:
- primary — één primaire actie per context;
- secondary — normale navigatie/opslaan;
- ghost — lage nadruk;
- danger — destructief met bevestiging;
- compact icon+label — vacaturefeedback.
States: hover, focus-visible, active, disabled, busy. Disabled blijft leesbaar en verklaarbaar.
### Badge/status pill
Bevat altijd tekst. Rollen: strong, possible, weak, hidden, active, review, deny, quarantined, expired. Gebruik niet alleen rood/groen.
### Card
Semantische header/body/footer. Hele kaart niet klikbaar wanneer er meerdere interne acties zijn; titel/link is de primaire link.
### Score
Score = getal/100 + recommendationtekst. Confidence wordt als “Datakwaliteit hoog/middel/laag” of exact percentage in detail getoond. Geen cirkeldiagram nodig voor één waarde.
### Form
- label boven veld;
- hint vóór fout;
- fout gekoppeld via `aria-describedby`;
- required expliciet;
- JSON-achtige lijstvelden uiteindelijk vervangen door chips/multi-select;
- opslaan bevestigt, maar verliest scroll/focus niet onnodig.
### Alert
Info/success/warning/error met icoon, titel en tekst. Alerts zijn `role=status` of `role=alert` afhankelijk van urgentie.
### Table/list
Kolomkoppen semantisch; op mobiel cards of stacked definition list. Actiekolom niet te breed.
## 6. Motion
- alleen functionele transities 120200ms;
- respecteer `prefers-reduced-motion`;
- geen automatische carrousels, pulserende scores of decoratieve parallax.
## 7. Iconen
Gebruik eenvoudige inline SVG's met `aria-hidden=true` naast zichtbare tekst. Geen externe iconfont. Een icon-only button vereist accessible label/tooltips.
## 8. Datavisualisatie
Gebruik balken voor scorecomponenten met numerieke tekst. Kleur ondersteunt, maar label/waarde draagt betekenis. Grafieken zijn niet nodig zolang data laag-volume is.
## 9. Designreviewchecklist
- primaire taak binnen één scherm duidelijk;
- geen ruwe technische identifiers in dagelijkse UI;
- empty/error/loading states aanwezig;
- focusvolgorde logisch;
- contrast en zoom 200% gecontroleerd;
- mobiel 320px breed bruikbaar;
- lange titels/werkgevers/URL's breken veilig;
- Nederlands consistent;
- scoreverklaring niet verstopt achter kleur.
+165
View File
@@ -0,0 +1,165 @@
# UX-specificatie
## 1. Ervaringsprincipe
VacatureRadar voelt niet als een zoekmachine maar als een rustige persoonlijke inbox. De gebruiker ziet eerst beslisbare informatie en pas daarna technische details. Bronbeheer en parserproblemen blijven beschikbaar, maar domineren de dagelijkse flow niet.
## 2. Informatiearchitectuur
Hoofdnavigatie:
1. **Vandaag** — beste matches en kernstatus;
2. **Vacatures** — volledige doorzoekbare lijst;
3. **Sollicitaties** — dossiers en opvolging;
4. **Profiel** — voorkeuren en drempels;
5. **Bronnen** — source health en retries;
6. **Systeem** — health, versies en beheercontext.
Op mobiel wordt dit een compacte uitklapbare navigatie; primaire vacatureacties blijven binnen duimbereik.
## 3. Dashboard “Vandaag”
### Doel
Binnen 30 seconden weten of er iets relevants is.
### Volgorde
1. paginatitel + korte samenvatting;
2. KPI's: nieuwe vandaag, actieve vacatures, open dossiers, ongezonde bronnen;
3. kaarten, descending score;
4. lege toestand met concrete uitleg;
5. subtiele link naar alle vacatures/bronnen.
### Matchkaart
Boven de vouw:
- recommendationlabel;
- score als `86/100` en confidence apart;
- functietitel;
- werkgever;
- locatie + werkvorm;
- maximaal twee kernpluspunten;
- maximaal één belangrijkste concern;
- acties: bekijken, interessant, bewaren, verbergen, gesolliciteerd.
Regels:
- geen score zonder label/uitleg;
- onbekende data als “Niet vermeld” of “Nog niet berekend”;
- hard uitgesloten jobs niet op Vandaag;
- dubbele aliassen niet als aparte kaart.
## 4. Vacaturelijst
Filters:
- vrije tekst;
- status;
- recommendation/scoreband;
- werkvorm;
- regio;
- werkgeverstype;
- feedbackstatus.
Desktop gebruikt een compacte filterbalk; mobiel een disclosurepanel. Filters blijven in querystring zodat bookmarks en back navigation werken.
Tabel of kaarten kiezen op breedte. Geen horizontale scroll voor kernvelden.
## 5. Vacaturedetail
Secties:
1. titel, werkgever, status, canonical link;
2. score-overzicht met componenten;
3. “Waarom aanbevolen” en aandachtspunten;
4. harde uitsluitingen indien via directe link geopend;
5. vacaturetekst (gesanitized);
6. voorwaarden/skills/locatie;
7. bronnen en provenance;
8. wijzigingshistorie;
9. sollicitatiestatus/notities.
Bronbewijs wordt technisch maar scanbaar getoond: veld, methode, confidence, excerpt en bron.
## 6. Profielwizard/editor
De eerste onboarding wordt in maximaal vijf logische stappen opgezet:
1. rollen en ongewenste titels;
2. skills en ongewenste verplichte skills;
3. regio, afstand en werkvorm;
4. contract en employerpreferences;
5. digest en samenvatting.
De bestaande editor mag één pagina blijven voor MVP, maar groepeert velden visueel en geeft voorbeeldsyntax. JSON mag in de interne modelvorm bestaan; de eindgebruiker ziet uiteindelijk chips/checkboxes, geen ruwe JSON.
Opslaan toont:
- welke harde regels actief zijn;
- scoregewichten in begrijpelijke labels;
- profielversie;
- mogelijkheid om learning uit/reset te zetten.
## 7. Bronnen
Bronkaart/-rij toont:
- naam en domein;
- type;
- policy en operationele status apart;
- laatste succes/fout;
- volgende run;
- parser/confidence;
- laatste tellingen;
- retryknop indien policy niet deny.
Kleur is nooit de enige indicator. `deny`, `review`, `quarantined` hebben tekst en icoon/patroon.
## 8. Sollicitaties
Kanban is niet noodzakelijk. Een duidelijke lijst met status, werkgever, functie, applied date, follow-up date en eerstvolgende actie is beter voor laag volume.
Dossierdetail/editor bevat:
- immutable vacaturesnapshot;
- huidige bronlink/status;
- contact;
- notities;
- documentenmanifest;
- tijdlijn van statuswijzigingen (backlog);
- opvolgdatum.
## 9. Meldingen
Digestcopy:
```text
2 sterke matches
4 mogelijke matches
11 automatisch uitgesloten
1 opgeslagen vacature sluit binnenkort
```
Elke vacature heeft titel, werkgever, locatie, score, twee redenen, één aandachtspunt en originele link. Geen volledige vacaturetekst in mail.
## 10. States
Iedere pagina specificeert:
- loading: alleen nodig bij async enhancement; serverrender toont normale response;
- empty: leg uit waarom en geef één relevante actie;
- error: concrete fout + veilige retry, geen stacktrace;
- partial: bron/resultaten beschikbaar met warning;
- stale: toon laatste succesvolle tijd en dat vernieuwing faalde;
- unauthorized: login en geen informatielek.
## 11. Copyrichtlijnen
- Nederlands, direct en niet-oordelend;
- “Mogelijke match” in plaats van “Je bent geschikt”;
- “Niet teruggevonden” in plaats van “Je mist”;
- “Rechtstreekse werkgeversbron” in plaats van absolute garantie;
- percentages alleen wanneer betekenis duidelijk is;
- technische bronfout in beheer, eenvoudige impacttekst op dashboard.
+113
View File
@@ -0,0 +1,113 @@
# Low-fidelity wireframes
## Dashboard — desktop
```text
┌──────────────────────────────────────────────────────────────────────────────┐
│ VacatureRadar Vandaag Vacatures Sollicitaties Profiel Bronnen │
├──────────────────────────────────────────────────────────────────────────────┤
│ Vandaag Laatste update 07:31│
│ Alleen vacatures die aandacht verdienen. │
│ │
│ [ 3 nieuwe ] [ 128 actief ] [ 2 open dossiers ] [ 1 bron vraagt aandacht ] │
│ │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ STERKE MATCH 88/100 Infrastructure Engineer │ │
│ │ Example IT · Hasselt · Hybride · Voltijds │ │
│ │ + Rechtstreekse werkgeversbron │ │
│ │ + Microsoft 365 en VMware teruggevonden │ │
│ │ ! Salaris niet vermeld │ │
│ │ [Bekijken] [Interessant] [Bewaren] [Verbergen] [Gesolliciteerd] │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ MOGELIJKE MATCH 72/100 ... │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
```
## Dashboard — mobiel
```text
┌───────────────────────────┐
│ VacatureRadar ☰ │
├───────────────────────────┤
│ Vandaag │
│ 3 nieuwe · 2 sterke │
│ │
│ ┌───────────────────────┐ │
│ │ STERKE MATCH │ │
│ │ 88/100 │ │
│ │ Infrastructure │ │
│ │ Engineer │ │
│ │ Example IT │ │
│ │ Hasselt · Hybride │ │
│ │ + M365 en VMware │ │
│ │ ! Salaris onbekend │ │
│ │ [Bekijken] │ │
│ │ [Interessant] [Meer] │ │
│ └───────────────────────┘ │
└───────────────────────────┘
```
## Vacaturedetail
```text
Titel + werkgever + status [Open originele vacature]
Locatie · contract · werkvorm · data
Score 88/100 Datakwaliteit 84%
[Content ████████] [Skills ███████] [Locatie ████████] ...
Waarom dit past Aandachtspunten
- titel sluit goed aan - salaris niet vermeld
- gewenste skills gevonden - 3 jaar ervaring gevraagd
- directe werkgever
[Interessant] [Bewaren] [Verbergen] [Gesolliciteerd]
Vacaturetekst
...
Bronnen en bewijs
- jobs.example.org · JSON-LD · 95%
- vacaturemail · alias · 65%
Wijzigingshistorie | Sollicitatiedossier
```
## Profiel
```text
Zoekprofiel: Primair versie 4 · actief
Gewenste rollen Uitgesloten titels
[Infrastructure Engineer] [Sales]
[Systeembeheerder] [Recruiter]
Skills Uitgesloten verplichte skills
[Microsoft 365] [VMware] [Apple-only]
Locatie
Postcode [....] Gemeente [....] Max afstand [45 km]
Voorkeursregio's [Limburg] [Vlaams-Brabant]
Werkvorm [x] Hybride [x] Op locatie [ ] Remote
Contract [x] Voltijds [x] Vast
Digest [07:30] Leren van feedback [aan]
Harde regels (samenvatting) | Scoregewichten
[Opslaan]
```
## Bronstatus
```text
Naam/domain | Type | Policy | Status | Laatste succes | Volgende run | Resultaat | Actie
Example IT | ATS | allow | active | 06:42 | 18:42 | 12 jobs | Retry
Platform X | mail | allow | active | 07:01 | 07:11 | 4 links | —
Unknown | web | review | candidate| — | — | — | Review
```
Een visueel voorbeeld staat in `dashboard-wireframe.svg`.
+43
View File
@@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="900" viewBox="0 0 1440 900" role="img" aria-labelledby="title desc">
<title id="title">VacatureRadar dashboard wireframe</title>
<desc id="desc">Licht dashboard met navigatie, statistieken en twee vacaturekaarten.</desc>
<rect width="1440" height="900" fill="#f4f6f8"/>
<rect x="0" y="0" width="1440" height="84" fill="#ffffff"/>
<text x="72" y="52" font-family="system-ui, sans-serif" font-size="27" font-weight="700" fill="#17202a">VacatureRadar</text>
<g font-family="system-ui, sans-serif" font-size="16" fill="#52606d">
<text x="700" y="50">Vandaag</text><text x="790" y="50">Vacatures</text><text x="895" y="50">Sollicitaties</text><text x="1010" y="50">Profiel</text><text x="1085" y="50">Bronnen</text><text x="1175" y="50">Systeem</text>
</g>
<rect x="695" y="62" width="67" height="3" rx="1.5" fill="#2b6cb0"/>
<text x="100" y="150" font-family="system-ui, sans-serif" font-size="38" font-weight="750" fill="#17202a">Vandaag</text>
<text x="100" y="184" font-family="system-ui, sans-serif" font-size="18" fill="#66788a">Alleen vacatures die aandacht verdienen.</text>
<text x="1160" y="160" font-family="system-ui, sans-serif" font-size="14" fill="#7b8794">Laatste update 07:31</text>
<g font-family="system-ui, sans-serif">
<g transform="translate(100 225)"><rect width="275" height="100" rx="14" fill="#fff" stroke="#dde3e9"/><text x="22" y="38" font-size="14" fill="#6b7785">NIEUW VANDAAG</text><text x="22" y="75" font-size="30" font-weight="700" fill="#17202a">3</text></g>
<g transform="translate(395 225)"><rect width="275" height="100" rx="14" fill="#fff" stroke="#dde3e9"/><text x="22" y="38" font-size="14" fill="#6b7785">ACTIEVE VACATURES</text><text x="22" y="75" font-size="30" font-weight="700" fill="#17202a">128</text></g>
<g transform="translate(690 225)"><rect width="275" height="100" rx="14" fill="#fff" stroke="#dde3e9"/><text x="22" y="38" font-size="14" fill="#6b7785">OPEN DOSSIERS</text><text x="22" y="75" font-size="30" font-weight="700" fill="#17202a">2</text></g>
<g transform="translate(985 225)"><rect width="275" height="100" rx="14" fill="#fff" stroke="#dde3e9"/><text x="22" y="38" font-size="14" fill="#6b7785">BRONNEN MET AANDACHT</text><text x="22" y="75" font-size="30" font-weight="700" fill="#9c4221">1</text></g>
</g>
<g transform="translate(100 365)" font-family="system-ui, sans-serif">
<rect width="1160" height="230" rx="18" fill="#ffffff" stroke="#d9e1e8"/>
<rect x="24" y="24" width="128" height="30" rx="15" fill="#e8f5ec"/>
<text x="40" y="44" font-size="13" font-weight="700" fill="#276749">STERKE MATCH</text>
<text x="1020" y="50" font-size="28" font-weight="750" fill="#17202a">88/100</text>
<text x="24" y="92" font-size="27" font-weight="750" fill="#17202a">Infrastructure Engineer</text>
<text x="24" y="122" font-size="16" fill="#5e6c7a">Example IT · Hasselt · Hybride · Voltijds</text>
<text x="24" y="158" font-size="15" fill="#2f855a">+ Rechtstreekse werkgeversbron</text>
<text x="24" y="184" font-size="15" fill="#2f855a">+ Microsoft 365 en VMware teruggevonden</text>
<text x="430" y="158" font-size="15" fill="#9c6b1b">! Salaris of barema niet vermeld</text>
<rect x="800" y="170" width="120" height="38" rx="8" fill="#2b6cb0"/><text x="825" y="195" font-size="15" font-weight="650" fill="#fff">Bekijken</text>
<rect x="934" y="170" width="100" height="38" rx="8" fill="#edf2f7"/><text x="950" y="195" font-size="15" fill="#2d3748">Bewaren</text>
<rect x="1048" y="170" width="88" height="38" rx="8" fill="#edf2f7"/><text x="1064" y="195" font-size="15" fill="#2d3748">Verberg</text>
</g>
<g transform="translate(100 625)" font-family="system-ui, sans-serif">
<rect width="1160" height="180" rx="18" fill="#ffffff" stroke="#d9e1e8"/>
<rect x="24" y="24" width="145" height="30" rx="15" fill="#fff5df"/>
<text x="40" y="44" font-size="13" font-weight="700" fill="#8a5b12">MOGELIJKE MATCH</text>
<text x="1020" y="50" font-size="28" font-weight="750" fill="#17202a">72/100</text>
<text x="24" y="92" font-size="25" font-weight="750" fill="#17202a">ICT Systeembeheerder</text>
<text x="24" y="122" font-size="16" fill="#5e6c7a">Publieke organisatie · Genk · Op locatie</text>
<text x="24" y="153" font-size="15" fill="#2f855a">+ Publieke sector en passende regio</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB