# Ontwerpgids — Terraforming Mars Legacy Alles wat we ontwerpen is **data + template**. Jij beschrijft een kaart als een paar regels tekst, het systeem maakt er een kaart van die er gegarandeerd hetzelfde uitziet als alle andere. Zo blijft alles consistent, ook over verschillende chats heen. --- ## 1. Hoe het in elkaar zit ``` server/app.py ← serveert de site EN de opslag-API server/db.py ← SQLite: één tabel, JSON per onderdeel db/studio.db ← hier staat al het werk van iedereen index.html ← het overzicht: alles wat er op de server staat, per categorie kaart.html ← kaarteneditor werknemer.html ← werknemerskaarten onderdeel.html ← tegels, fiches, panelen en bordonderdelen bord.html ← bordeditor importeren.html ← bestaand werk eenmalig overzetten print/vel.html ← printvellen, met de juiste maten print/kalibratie.html ← eenmalig: klopt wat je printer doet? css/tokens.css ← ALLE maten en kleuren. Hier pas je dingen centraal aan. css/components.css ← iconen, tags, grondstof-badges, productiekader css/cards.css ← kaartsjablonen css/tiles.css ← tegels, hexrasters, fiches, panelen css/print.css ← papier, snijlijnen, dubbelzijdig js/icons.js ← de icoonset (SVG) js/templates.js ← data → HTML, één functie per onderdeeltype js/app.js ← overzicht, filters, printvellen data/*.js ← alleen nog voorbeeldmateriaal om te importeren; de echte inhoud staat in de database data/collections.js ← register van categorieën ``` **Werkwijze:** je maakt onderdelen in de editors; ze gaan meteen naar de server en verschijnen bij iedereen in het overzicht en op de printvellen. Grote hoeveelheden tegelijk aanleveren kan nog steeds: dan zet ik ze om en importeer ik ze via de API. --- ## 2. Velden per categorie ### Projectkaart (`data/project-cards.js`) | veld | betekenis | |---|---| | `id` | uniek, bv. `TML-014`. Verschijnt klein onderaan de kaart. | | `name` | kaartnaam | | `subtitle` | optionele regel onder de naam | | `type` | `green` (automatisch), `blue` (blijvend/actie), `red` (event), `prelude`, `legacy` | | `cost` | getal, of leeg weglaten voor geen kosten | | `requirement` | `'5 % {oxygen}'` of `{kind:'max', text:'…'}` — `min` = groen, `max` = rood | | `tags` | `['space','building']` — zie taglijst hieronder | | `effect` | de regeltekst | | `action` | alleen bij blauw: de actie-regel (krijgt een eigen kader) | | `vp` | getal → zeshoek met cijfer; tekst → breed VP-vakje ("1 VP per 3 {microbe}") | | `flavor` | sfeerzin, cursief onderaan | | `art` | illustratie-preset (zie §4) | | `artIcon` | icoon dat als watermerk in de illustratie staat | | `image` | pad naar een echte afbeelding, vervangt de preset | | `set`, `era`, `status` | ordening: welke set, welk legacy-hoofdstuk, hoe af is het | `status`: `idee` · `concept` · `test` · `definitief` — wordt als label getoond en je kunt erop filteren. ### Werknemer (`data/employees.js`) Een persoon in je bedrijf, met twee bonussen op één kaart. Paars, en verder in dezelfde stijl als een projectkaart. | veld | betekenis | |---|---| | `name` / `role` | naam en functie in de middenstrook | | `tags` | de ronde tags rechts in de strook | | `photo` | pad naar een echte foto | | `portrait` / `portraitIcon` | verloop met icoon als er geen foto is | | `ceo` | de bonus in de **bovenste** helft | | `employee` | de bonus in de **onderste** helft | | `cost` / `vp` | allebei optioneel | Beide helften gaan door hetzelfde blok-template als een projectkaart, dus ze nemen `gain`, `effect`, `action` en `note` — los of gecombineerd: ```js ceo: { gain: 'prod:mc:2', effect: { when:'science', then:'card:1' }, note: 'Effect: telkens als je een sciencetag speelt.' } ``` **De middenstrook staat op elke kaart op exact dezelfde hoogte.** Dat is geen toeval maar de opzet: de strook staat absoluut op `top:50%` met een verschuiving van de halve eigen hoogte, en de helften worden berekend als `calc(50% - strook/2)`. Procenten gaan over de binnenmaat van de kaart, dus de randdikte hoeft nergens in de som voor te komen. Nagemeten op een printvel: 36,5 mm vanaf de bovenkant, op elke kaart gelijk. Inhoud in de ene helft kan de strook dus nooit verschuiven. Bewerken doe je in **`werknemer.html`**: per helft staan open vakken waar je met één klik een voordeel, effect of actie toevoegt, en met het kruisje weer weghaalt. ### Corporatie (`data/corporations.js`) Zelfde velden, plus `start` (de startbonus) en `startLabel`. ### Tegel (`data/tiles.js`) `type` (`city` `greenery` `ocean` `special` `industry` `legacy` `land` `reserved` `empty`), `icon`, `name` (label onderin), `top` (labeltje bovenin), `bonus` (`['steel:2']`), `color` (eigen kleurenpaar: `['#aabbcc','#112233']`). Twee manieren om een tegel een gezicht te geven: - `official:'city'` — de échte tegelillustratie uit het spel. Beschikbaar: `city`, `greenery`, `ocean`, `special`, `colony`, `trade`, `empty`, `greenery_no_o2`. Laat `name` dan meestal weg; de illustratie zegt het al. - `image:'assets/img/mijntegel.png'` — je eigen plaatje. Zonder allebei krijg je de CSS-tegel: kleurverloop uit `type` (of je eigen `color`) met het icoon uit `icon`. Dat blijft de weg voor legacy-tegels die niet bestaan in het echte spel. ### Bordonderdeel (`data/boards.js`) ```js rows:[ {indent:2, cells:[ L(['steel:2']), L(), O(), null ]}, … ] ``` `indent` telt in **halve hexbreedtes** — daarmee maak je de schuine rijen van een hexraster. `null` = leeg vak. `L()` en `O()` zijn hulpjes bovenaan het bestand; je mag ook gewoon `{type:'city', icon:'city', bonus:['mc:3']}` schrijven. ### Borddeel (`data/board-parts.js`) De zeven delen van het bord uit *Frame 1.pdf*. Deze gebruiken **pointy-top** hexagons (punt boven en onder) van 34,4 x 40,0 mm — dezelfde stand als de officiele TM-tegels. | veld | betekenis | |---|---| | `cells` | `[{col,row,...}]` — kolom en rij. **Oneven rijen** liggen een halve kolom naar rechts. | | `texture` | achtergrond voor het hele deel: `board` of `vulcano` | | `texture` | achtergrond voor het hele deel: `board` of `vulcano` | Per vakje kun je toevoegen: | veld | doet | |---|---| | `name` | naam in het vakje | | `num` | klein label linksonder — vrije tekst, dus ook `A-79` | | `numColor` | kleur van dat label | | `lava` | groot gloeiend getal rechtsonder, gespiegeld tegenover `num` | | `reserved` | gereserveerd gebied: `ocean` / `city` / `greenery` / `legacy` | | `bonus` | plaatsingsbonus, bv. `['steel:2','prod:heat:1']` — zie hieronder | | `border` | eigen dikke rand, elke kleur: `'#fe2b00'` | | `borderW` | dikte daarvan (5,8 / 8,7 / 13 / 17,3 = 1,0 tot 3,0 mm) | | `overlay` | doorzichtige kleurwaas over het hele vakje: `'#2f7ab8'` | | `overlayOpacity` | dekking daarvan, `0` tot `1` | **Notatie van een plaatsingsbonus.** Dezelfde codes werken op borddelen en op losse tegels: | code | geeft | |---|---| | `steel:2` | grondstof met aantal | | `plant:-1` | negatief: hetzelfde fiche met een rode rand | | `ocean` | zonder getal | | `tag:plant` | forceer de **ronde tag** | | `res:plant` | forceer de **grondstof** | | `prod:heat:1` | **productie omhoog**: het fiche in het bruine productiekader | | `prod:energy:-1` | **productie omlaag**: kader plus rode rand | | `any:tag:plant` | geldt ook bij **andere spelers**: dikke rood-oranje ring eromheen | De prefixen mogen in elke volgorde. Staan er meerdere `prod:`-codes **naast elkaar** in dezelfde lijst, dan komen ze samen in één bruin kader in plaats van elk in een eigen kadertje. Wil je ze los, zet er dan iets tussen. ### Tag of grondstof? De plek bepaalt het Namen als `microbe`, `plant`, `animal` en `science` bestaan als tag én als grondstof. Een kale naam wordt daarom uitgelegd op basis van **waar hij staat**: | plek | een kale naam is | |---|---| | de tags in de kopregel | tag | | effect · **voorwaarde** | **tag** | | effect · gevolg | grondstof | | actie · kosten én opbrengst | grondstof | | voordeel, VP, vrije tekst, vereiste, bordbonussen | grondstof | Dat is alleen de standaard. Met `tag:` of `res:` zet je het per fiche om, in beide richtingen — dus `{tag:microbe}` in een VP-tekst of `res:plant` in een effect-voorwaarde kan gewoon. Bij een **vereiste** komen allebei voor, dus schrijf daar `{tag:space}` als je de ronde tag wilt. Een naam die nergens bekend is blijft in vrije tekst gewoon als tekst staan, zodat een typefout zichtbaar wordt in plaats van stilletjes te verdwijnen. Doe dat in de **bordeditor** (`bord.html`): klik een vakje aan, vul in, en klik daarna op *Opslaan & exporteren* om de nieuwe inhoud van `data/board-parts.js` te krijgen. Onder het bonusveld staat een opbouwertje — grondstof, aantal (mag negatief) en een vinkje *productie* — maar het tekstveld blijft de bron, dus je kunt de codes ook gewoon typen. Staan er te veel bonussen op één vakje, dan breekt de rij vanzelf af. **Bewerken en printen.** Je werk uit de editor staat in je browser en wordt ook door het overzicht en het printvel gebruikt, zodat je kunt printen wat je net hebt gemaakt. Op het printvel staat dan een melding dat het nog niet in `data/board-parts.js` staat, met een link om terug te vallen op het bestand. Rand en overlay hebben allebei een vrije kleurkiezer met een hexveld ernaast, zodat je ook een exacte code kunt plakken. De overlay ligt boven de textuur maar onder de omtrek en de tekst, zodat namen en bonussen leesbaar blijven. **Nummeren.** `num` zet een klein label in de linkeronderhoek van een vakje. Het is vrije tekst, dus `12`, `A-79` en `Tharsis-3` kunnen allemaal. Het staat bewust buiten het gecentreerde tekstblok, op de hoogte waar de zeshoek het breedst is, en net buiten de stippellijn van een gereserveerd gebied. Het label staat op een donker vlakje met afgeronde hoeken. Dat vlakje levert het contrast, waardoor het op elke ondergrond rustig leesbaar blijft — ook op een drukke fototextuur of boven de golflijnen van een oceaanvakje. Het blijft klein en ingetogen, maar je hoeft er niet meer naar te turen. | token | doet | |---|---| | `--num-size` | tekstgrootte (standaard `3,4 mm`) | | `--num-pad-x` / `--num-pad-y` | ruimte tussen tekst en het donkere vlakje | | `--num-opacity` | hoe zichtbaar het is (standaard `.72`) | **Het lavagetal.** `lava` zet een groot, opvallend getal in de rechteronderhoek, precies tegenover het kleine nummer. De hoogte wordt zo berekend dat de middens van beide getallen op één lijn liggen, ondanks het formaatverschil — het vlakje om het codelabel telt daarin mee. Grootte via `--lava-size` (standaard `9 mm`). **Indeling van een vakje.** De **bonus staat in het midden** — dat is het anker. De **titel hangt daarboven**, uit de flow gehaald met `position:absolute`. Zou de titel gewoon in de flow staan, dan wordt het blok als geheel gecentreerd en zakt de bonus onder het midden weg. Heeft een vakje alleen een titel en geen bonus, dan staat die titel gewoon in het midden. Onderin staan het codelabel (links) en het lavagetal (rechts). Het tekstblok schuift daarvoor iets omhoog: | situatie | uitwijking | reden | |---|---|---| | code | 0,5 mm | de titel staat inmiddels boven de bonus, dus het knelpunt is al weg | | lavagetal | 4 mm | dat getal is groot; zonder ruimte valt een plaatsingsbonus er half achter weg | Die twee tellen niet bij elkaar op — staan ze er allebei, dan wint de ruimste. De 0,5 mm bij een code houdt de bonus vrijwel exact in het midden. Let op bij vakjes met véél bonussen: breekt de rij in tweeën, dan komt de onderste regel dicht bij het codelabel. Eén of twee bonussen is ruim voldoende bediend. Het is opgebouwd uit **twee lagen over elkaar**, en dat is geen omweg maar noodzaak: een achtergrond wordt geschilderd vóór de tekstschaduwen van hetzelfde element. Een verloop dat je met `background-clip:text` in de letters knipt, verdwijnt dus onder je eigen korst en gloed. Het in een `::after` zetten werkt ook niet — daar knipt `background-clip:text` niets zichtbaars. Met twee echte elementen gaat het wel: de onderste (`.lava-glow`) draagt de donkere korst en de hitte, de bovenste (`.lava-fill`) ligt er absoluut overheen met het verloop van witheet naar afgekoeld rood. Wijzig je hier iets, houd die volgorde dan aan. **Gereserveerd voor een oceaan.** `reserved:'ocean'` geeft het vakje een koele waterwaas met golflijnen. Dat is getekend als vector, niet als foto: scherp op elke printresolutie en volledig van onszelf. De kleur trekt bewust naar cyaan, want een neutraal blauw wordt grijs boven het roodbruine terrein. Donkere randen suggereren diepte, en elke golf heeft een donkere lijn onder de lichte zodat er reliëf in zit. Twee knoppen in `css/tokens.css`: | token | doet | |---|---| | `--water-tint` | sterkte van de blauwe waas (standaard `.48`) | | `--water-wave` | sterkte van de golflijnen (standaard `.30`) | Lager zetten maakt het subtieler; op `.3` en `.2` is het nog net zichtbaar. **Randen.** Elk vakje krijgt een halftransparante witte omtrek, zodat je de achtergrond er doorheen ziet. Die wordt getekend als een SVG-zeshoek en niet als CSS-rand: een `box-shadow` volgt de rechthoek van het element en verdwijnt daarna onder de clip-path, waardoor de schuine randen onzichtbaar blijven. De lijn ligt midden op de rand en de buitenste helft wordt weggeknipt, dus twee buurvakjes vormen samen precies één lijn met overal dezelfde doorzichtigheid. Instelbaar in `css/tokens.css`: | token | doet | |---|---| | `--bhex-line` | dikte van de rasterlijn (~0,76 mm bij de standaardmaat) | | `--bhex-line-col` | kleur en doorzichtigheid van de rasterlijn | | `--bhex-edge-line` | standaarddikte van een gekleurde rand (~2,25 mm) | Deze staan in honderdsten van de vakjebreedte, dus ze schalen mee met `--hex-r`. **Achtergrond.** Eén afbeelding wordt over het hele borddeel gespannen en **gecentreerd op het midden van het deel**; elk vakje toont zijn eigen stukje, zodat het landschap over de vakjesgrenzen heen doorloopt. Bij de vulkaan valt het midden van de afbeelding daardoor precies in het middelste vakje. Let op bij het uitrekenen van de breedte van een deel: een vakje in een **oneven rij** ligt een halve kolom naar rechts, dus de rand van een deel volgt niet het kolomraster maar de echte posities van de vakjes. Reken je met het raster, dan blijft er aan één kant een halve kolom lege ruimte staan en ligt de achtergrond een kwart vakje scheef. De zwarte scheidingslijnen per vakje laten het raster zien. De draaiing en spiegeling gelden voor het **hele deel** en liggen vast per `id` — dezelfde invoer geeft altijd hetzelfde beeld, zodat de print gelijk is aan wat je op het scherm ziet. De vulkaan blijft bewust rechtop staan. **Maten.** Alles schaalt mee met `--bhex-w` in `css/tokens.css`. De hoogte en de rijafstand volgen uit de meetkunde van een zeshoek en horen niet los ingesteld te worden. ### Fiche (`data/tokens.js`) `icon` óf `text`, `color`, `small:true` voor de kleine maat. ### Paneel (`data/panels.js`) `w`/`h` in millimeter, `body` met rijke tekst. Voor spelershulpen en tracks. --- ## 3. Rijke tekst — de `{tokens}` In `effect`, `action`, `start`, `requirement` en `body` mag je dit gebruiken: | je typt | je krijgt | |---|---| | `{steel}` | grondstof-icoon | | `{steel:2}` | icoon met getal | | `{plant:-2}` | idem, met rode rand (verlies/kosten) | | `{space}` | ronde tag (bij tagnamen zonder getal) | | `[prod]{heat:2}[/prod]` | bruin **productiekader** eromheen | | `->` | pijl (kosten → opbrengst) | | `**vet**` | vet | | lege regel | nieuwe alinea | Beschikbare namen: `mc steel titanium plant energy heat card ocean oxygen temperature venus tr vp city greenery microbe animal science floater data colony` en de tags: `building space science energy jovian earth plant microbe animal city event venus moon wild`. Tekst die niet past wordt automatisch iets verkleind — maar als je 's nachts een roman op een kaart zet, ziet dat er niet uit. Kort en strak werkt het beste. --- ## 3b. Twee icoonsets Bovenin het overzicht en het printvel staat een keuze **Iconen**: - **officieel (TM)** — de echte spelsymbolen uit de submodule `assets/upstream`. Standaard aan. Zie [assets/HERKOMST.md](../assets/HERKOMST.md) voor waar ze vandaan komen en waarom ze alleen voor eigen gebruik zijn. - **eigen SVG-set** — de zelfgetekende vectorset in `js/icons.js`. Volledig van onszelf, oneindig scherp, en de enige set die je zou gebruiken als je ooit iets publiceert. De keuze wordt onthouden in je browser en geldt overal tegelijk. **Belangrijk:** een naam die niet in de officiële set zit, valt automatisch terug op de SVG-versie. Er gaat dus nooit iets stuk. Zo zijn `moon`, `vp`, `rocket`, `dome`, `factory`, `lock`, `skull`, `gear`, `flag`, `crystal`, `sun` en `mars` altijd SVG. Het lettertype **Prototype** (uit dezelfde map) wordt gebruikt voor titels en getallen — dat is het lettertype waarmee het echte spel is gezet. Alle Nederlandse leestekens werken (ë é ï ö ü à € °); alleen `²` ontbreekt erin, dus schrijf in een *titel* liever "O2" of gebruik `{oxygen}`. In gewone kaarttekst is `²` geen probleem. ## 3c. Kaarteneditor `kaart.html` is de klikbare editor voor projectkaarten: links een voorbeeld op ware grootte dat meteen meeverandert, rechts alle velden. Je kunt kaarten **toevoegen, dupliceren en verwijderen**, en met de fiche-opbouwer onderin klik je codes in het juiste veld (voordeel, effect-voorwaarde, effect-gevolg, actie-kosten, actie-opbrengst) met vinkjes voor *productie*, *als tag* en *andere spelers*. Je werk staat in je browser en wordt ook door het overzicht en het printvel gebruikt. Klik op *Opslaan & exporteren* en plak de tekst over `data/project-cards.js` om het vast te leggen. ## 3d. Kaartblokken: voordeel, effect en actie Alles wat een gespeelde kaart *doet* gaat door één template heen, met dezelfde codes als de plaatsingsbonussen op het bord. Elk onderdeel mag weg; wat je invult verschijnt in deze volgorde. ```js { gain: 'prod:steel:1, prod:heat:1, plant:2', effect: { when:'any:tag:plant', then:'plant:1' }, action: { cost:'mc:2', gain:'plant:1' }, note: 'Ook als een tegenstander een planttag speelt.' } ``` | veld | betekenis | |---|---| | `gain` | het **voordeel**: wat je meteen krijgt | | `effect` | `{when, then}` — voorwaarde en gevolg, met een **dubbele punt** ertussen | | `action` | `{cost, gain}` — kosten en opbrengst, met de **rode pijl** ertussen | | `note` | jouw eigen uitleg, schuin en tussen haakjes eronder | - **Voorwaarde weglaten mag.** Bij een effect zonder `when` vervalt de dubbele punt. Bij een actie zonder `cost` blijft de pijl wél staan — die maakt het verschil met een gewoon voordeel zichtbaar. - **Labels.** Effect en actie krijgen een klein kopje. Geef `label:'…'` mee voor een andere tekst, of `label:''` om het weg te laten. - `effect` en `action` mogen ook gewoon **tekst** zijn; dan worden ze als vrije regels gezet, zoals voorheen. Bestaande kaarten blijven dus werken. Het productiekader is nu overal hetzelfde: bruin vlak met een grijze verlooprand en een schaduw naar binnen, in `em` opgemaakt zodat het meeschaalt met de tekst eromheen. Op een bordvakje wordt het vanzelf groter dan op een kaart. ## 3e. Opmaak van een projectkaart De kaart heeft een **witte buitenrand** met daarbinnen een dunne lijn in de typekleur. Die rand valt buiten het snijvlak, dus een kleine snijafwijking valt niet op. Daarbinnen zit de kopstrook in een **grijs metaalverloop** met de kosten, de vereiste en de tags, en daaronder de titelband met afgeronde uiteinden in de typekleur. Die metaallook is opgebouwd uit CSS-verlopen (`--metal-dark` en `--metal-light` in `css/tokens.css`), niet uit een fototextuur: oneindig scherp op print en van onszelf. **Volgorde van de vlakken.** Effect en actie staan **boven** de illustratie, een direct voordeel **eronder**. De illustratie zit ertussen en rekt mee: hoe minder blokken, hoe groter de plaat. Bij de drukste kaart blijft er 22 mm illustratie over, bij de rustigste ruim 50 mm. **Gebogen panelen.** De vlakken zijn geen gestapelde rechthoeken. Ze hebben een `border-radius` waarvan de horizontale straal veel groter is dan de verticale, zodat de twee hoeken in het midden in elkaar overlopen tot één vloeiende boog. Met een negatieve marge vallen ze een stukje over de illustratie heen, en een lichte binnenrand langs de boog laat het paneel opbollen. **Automatisch verkleinen.** Staan er twee blokken in één paneel, dan schaalt de icoonrij een stap terug (via `:has()`), zodat de illustratie niet wordt platgedrukt. De icoonrijen van voordeel, effect en actie staan **gecentreerd** en groot (`--fs`-onafhankelijk, ingesteld in `.cb__row`), met ruime tussenruimte. De kleine kopjes zijn standaard uit, omdat je uitleg eronder al benoemt wat het is; met `label:'Actie'` zet je er alsnog een boven. ## 3d. Het overzicht Het overzicht is de plek waar je met meerdere mensen bijhoudt wat er ligt. - **Klik op elk voorbeeld** om het te bewerken. Je komt in de juiste editor terecht met dat onderdeel al geselecteerd (`?id=…`). Welke editor dat is staat per categorie in `data/collections.js` bij `editor`. - **De status pas je direct aan** met het knopje onder elk voorbeeld. Dat gaat meteen naar de server; je hoeft de editor niet in. - **Onder elk onderdeel staat wie er het laatst aan werkte en wanneer.** Zo zie je in één oogopslag wat een ander heeft gedaan. - Filteren kan op **set**, **status** en **persoon**, en sorteren op **laatst gewijzigd** (standaard) of op naam. Laatst gewijzigd bovenaan is het handigst als je met meer mensen werkt: dan zie je meteen wat er nieuw is. Nieuwe categorie toevoegen? Zet er in `data/collections.js` een `editor` bij, dan wordt hij vanzelf klikbaar vanaf het overzicht. ## 4. Illustratie-presets (`art`) `mars dust space deepspace ocean ice forest city industry lab energy heat jovian earth venus moon legacy danger gold neutral` Elke preset is een verloop met een groot, half doorzichtig icoon erin. Dat geeft een consistente look zonder dat we honderd illustraties nodig hebben. Wil je later echte illustraties? Zet ze in `assets/img/` en gebruik `image:'assets/img/naam.png'`. --- ## 5. Nieuwe dingen toevoegen **Nieuwe kaart:** regel toevoegen in het juiste `data/…js`-bestand. Klaar. **Nieuw icoon:** in `js/icons.js` een SVG-pad toevoegen onder een nieuwe naam (viewBox `0 0 24 24`). Werkt daarna overal, ook als `{naam}` in tekst. **Nieuwe categorie:** `data/mijncategorie.js` met `window.DATA_MIJN = [...]`, toevoegen aan `data/collections.js`, en het `