Skip to content

Releases: EISSeuropa/netsec.github.io

v1.12.0 — A working directory and life after Stockholm

17 Jun 09:18
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

The post-conference release, gathering everything merged since v1.11.0 now that the Summer School and the European Security Studies Conference are over. The directory grows from a listing into a working tool: every member gets a shareable profile page at /people/<slug>, can offer to host an STSM visitor or to mentor through the join form, and carries a self-updating list of recent publications drawn from ORCID. An Events page gathers everything the Action runs in one place, and the conference pages settle into a fixed archive. Underneath sit consolidated renderers shared across the three locales and the Management Committee figures syncing themselves from the cost.eu roster. Four themes follow, then a single canonical index at the bottom.

A directory you can act on

Every member now has a permanent, shareable profile page at /people/<slug>, server-rendered with their own Open Graph card and Person structured data, so a link unfurls as that person and search engines can index it. Two new signals tie the directory to COST money and to the Action's mentoring goal: members can say whether their institution will host a Short-Term Scientific Mission, with the grants page linking straight to the pre-filtered list of hosts, and the mentorship facet grows from a pair of badges into a matching panel that splits offers from requests and scopes itself to the research theme or region in view. Cards also carry a "Recent publications" list drawn from each member's ORCID record, fetched lazily so it costs nothing at load. The directory opens in compact cards by default, three to a row, with the photo-and-bio view one click away.

A fuller front door

A dedicated Events page now gathers the conference, the training schools, the workshops, and the Management Committee meetings in one place, with a dated archive for past editions, and the Publications page renders from data so the first D6 policy brief in October is a data-entry task. The About page opens with an "In numbers" strip giving the size of the Action at a glance. The FAQ and Glossary now emit FAQPage and DefinedTerm structured data, so search engines can surface their questions and terms as rich results.

Life after Stockholm

With the Summer School and the conference over, the ESSC page becomes a fixed archive rendered from a frozen snapshot rather than the live Indico feed, its "Live programme" heading now reading "Full programme" and a hidden template parked ready for next year. During the conference itself the programme carried a "Now happening" banner that named the sessions in progress in Stockholm time and advanced on its own through the day. The Summer School page was redesigned to match the rest of the site, the home Events block now shows only what is still ahead, and the deliverables timeline reads "Delivered" where it once read "Shipped".

Steadier underneath

The 1,850-line directory script that was copied across the English, French, and German pages is now one shared module, and the member-card popover that the conference programme carried its own copy of became a single shared component. The Management Committee statistics on the About page and press kit rewrite themselves from the cost.eu roster each week (the first run caught Ukraine joining the Action), and Working-Group chips reconcile each member's own answer against the formal record instead of being overwritten wholesale. A blank-directory regression that reached the live site was fixed and the render smoke check now exercises all three locales, dozens of stray research keywords were mapped back under theme filters, and "MC" was spelled out as "Management Committee" across the reader-facing copy.

Index of changes

Added

  • An "In numbers" strip on the About page (all three locales), giving a visitor the size of the Action at a glance: Management Committee members across the countries they represent, Working Group members, and the people who have joined the Network so far. The Management Committee and country figures are the same cost.eu-synced literals used lower down the page, and the Working Group and Network figures fill at load from data/wg.json and data/bios.json, so none of the three drift. It sits above the deliverables timeline, which now carries a "Timeline & Deliverables" heading where it previously ran without a title. The Action's text also points to the four Working Groups, linking through to /working-groups.html. Stat labels, the section heading, and the linking sentence are hand-translated for FR and DE.
  • A dedicated Events page (/events.html, all three locales), reached from the main navigation, that gathers everything the Action runs in one place rather than only the next few entries on the home page. Events still ahead sit at the top, soonest first, and past editions fall into a dated archive below, grouped by year and most recent first, so the European Security Studies Conference, the training schools, the workshops, and the Management Committee meetings all keep a lasting home. A filter row narrows the list by type, every card carries its own calendar link, and the page renders from data/events.json through the same card builder the home block uses, so a new event stays a data entry task. The home Events block now ends with a link through to the full list. Hero copy and the type labels are hand-translated for FR and DE. (#927)
  • Real, shareable member profile pages at /people/<slug> (all three locales). Every member now has a permanent URL of their own to put in an email signature or share on LinkedIn, server-rendered from the directory data with their photo, role, affiliation, Working-Group chips, badges, bio, research interests, contact links, and recent publications. A shared link carries the member's own Open Graph metadata and a Person structured-data record, so it unfurls as that person and search engines can index it. The pages are generated by scripts/build-profile-pages.py, reuse the exact site chrome from the directory so they never drift, regenerate on every weekly bios sync, and a CI gate blocks a stale set from merging. Per-member social-card images, on-site search consolidation, and sitemap listing are tracked follow-ups. (#762)
  • An STSM-hosting signal on the directory (all three locales), tying the tool to COST mobility money. Members can say through the join form whether their institution can host Short-Term Scientific Mission visitors, and a quiet badge ("Can host STSM visitors", or "Open to hosting" for a conditional answer) then appears on their card, alongside a "STSM hosting" filter that narrows the directory to hosts. The grants page's STSM card links straight to that pre-filtered view, so an applicant looking for a host starts from the people who offered. Like the mentorship and region facets, the badge and filter stay invisible until at least one member opts in, and the answer is collected on the same form, parsed to a tri-state field, and shape-checked. Badge and filter copy hand-translated for FR and DE. (#760)
  • A mentorship matching view on the directory (all three locales). The mentorship facet shipped as data plumbing, with "Available to mentor" and "Seeking mentorship" badges and a filter, but nothing joined the two sides: a mentee had to run the filter and infer the matches. Selecting a mentorship filter now opens a panel above the grid that splits the matching members into "Offering" and "Seeking", carries a short note on how to approach a potential mentor, and, when only one side is showing, offers the other side in one click. The filter is now bookmarkable too, so a "Find a mentor" link can deep-link straight to the pre-filtered view (#mentorship=mentor). The panel copy is hand-translated for FR and DE. (#763)
  • A "Recent publications" list on directory cards (all three locales), drawn automatically from each member's ORCID record. A member who has added their ORCID iD now shows their three most recent works on their card, each linking to the paper by its DOI where one exists, with no extra effort on their part: the card becomes a living CV that updates itself as they publish. The works are gathered weekly by a new sync that fails soft per member and never wipes the list on a transient outage, and the directory fetches them lazily, once and only after the page has gone idle, so member cards carry none of the weight at load. The "Recent publications" heading is hand-translated for FR and DE. (#761)
  • A "Now happening" banner on the ESSC programme page (all three locales). While the conference is on, a banner above the day list names the session or sessions in progress right now, each linking to its card, with a live pulse. Liveness reads from the conference clock in Stockholm time, so it is correct from any visitor's timezone and stays right across daylight-saving, and it advances on its own as the programme moves through the day. It mirrors the same cue on the conference Indico, shows parallel tracks together, and hides itself whenever nothing is live. Built in the shared programme renderer, so next year's page inherits it. (#832)
  • A quiet "Is this you? Add your profile" link on the Working Groups page (/working-groups.html, all three locales) for every member shown without a directory bio. The card a person finds with their own name on it is the directory's highest-intent moment, so the link sits there and goes straight to the join form, with the form address read at runtime from the same directory data the page already loads. Members who already have a bio, and the named leads and co-leads, do not get the link. A comp...
Read more

v1.11.0 — Directory filters, mentorship, and a smoother mobile directory

08 Jun 13:40
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

This is the big pre-conference directory release, gathering everything merged since v1.10.0. Visitors can now narrow The Network by research theme, by research region and by mentorship, and the mobile filter experience that drives those choices was rebuilt so it behaves properly on a phone. Around it sit lighter photos, new ways to reach the Action, and the usual housekeeping. Three themes follow, then a single canonical index at the bottom.

Finding people by what they work on

The directory's filter used to turn on free-text keywords, which almost never matched between members, so the chip row rarely clustered anyone. Two curated facets replace that. Research themes group each member's keywords into a small set of broad areas (foreign policy and diplomacy, security and defence, economic security and geoeconomics, intelligence, information and influence, and so on, modelled on how peer bodies such as the EU Institute for Security Studies organise the field). Research regions add a second, geographic axis (Europe and its neighbourhoods, the Middle East and North Africa, Africa, Asia, the Americas, and global or cross-regional work). The two axes combine, so "cyber and Russia" is a single query. Member cards still show each person's own keyword pills. Clicking one selects its theme, and exact-keyword lookup stays in the search box.

A third facet, mentorship, lets a member flag in the join form that they are available to mentor early-career researchers or are looking for a mentor. It shows as a badge on the card and as a filter beside the themes and regions. All three facets stay hidden until at least one member opts in, so nothing appears before the form question is live. Theme and region names are hand-translated for FR and DE.

A mobile filter experience built to last

On phones the filters now open in a bottom sheet instead of filling the screen with controls before the member list. Getting that sheet to behave on iOS turned into a small saga, and this release settles it: the sheet is a native <dialog> opened with showModal(), which renders in the browser top layer and is therefore out of reach of the containing-block, scroll and soft-keyboard quirks that used to strand a position:fixed sheet in the middle of the page. It pins to the bottom on both iOS Safari and Chrome, keeps its Apply button above the Safari toolbar (dvh), dismisses on the first tap, and closes without the half-second lag that the backdrop blur and the scroll-lock used to cost on Safari. It also brings a native focus trap and Escape-to-close. Desktop is untouched: the same controls render inline in the toolbar.

Around the site

Beyond the directory, the footer now links the Action's LinkedIn and Bluesky, every shared page carries a hand-designed social card (the directory gets its own), and headshots serve a WebP first, so photos download at roughly half the bytes with no visible change. The directory wears an Early Access Preview banner inviting contributions, the About-page deliverables timeline shows what has shipped rather than only what is planned, and each Working Group now lists the COST deliverables it leads alongside its related publications. A new FAQ entry is honest about why the bio intake runs on a Google Form.

Index of changes

Added

  • New FAQ entry (all three locales) explaining why the directory intake runs on a Google Form rather than an open-source or European tool. It is honest about the trade-off (the tool is neither European nor open-source, and the sign-in asks something of researchers who would rather not hand Google a login) while laying out the practical reasons (spam barrier, an edit link, photo upload, and a spreadsheet the weekly sync reads with no server to run), and it points anyone who would rather not use the form to the contact form, noting the hand-entry delay.
  • The directory's research-interest filter now clusters members by broad research theme instead of by individual keyword, in all three locales. Free-text keywords fragment (almost every one is unique, so a per-keyword filter rarely groups anyone), so a curated set of eight themes (Foreign policy and diplomacy, Security and defence, Economic security and geoeconomics, Intelligence, information and influence, and so on, modelled on how peer bodies such as the EU Institute for Security Studies organise the field) groups related work so people in the same area surface together. Member cards still show each person's specific keyword pills. Clicking a pill now selects that keyword's theme, and exact-keyword lookup stays available through the search box. The theme chips, their counts, and the URL hash (#themes=…) all follow, and theme names are hand-translated for FR and DE.
  • The directory (/people.html, all three locales) gains a second, geographic filter axis: research regions. Alongside the topical themes, a visitor can now narrow to people who work on a region (Europe, Europe - Western Balkans, Europe - Eastern neighbours / Russia, Middle East and North Africa, Africa, Asia, The Americas, or Global and cross-regional), and the two axes combine, so "cyber and Russia" is one query. Member cards carry their regions as clickable pills, and selecting one filters the grid. The eight-region vocabulary follows the EU Institute for Security Studies' lean regional taxonomy, kept short on purpose so members cluster rather than fragment. Like the mentorship facet, the region row stays hidden until at least one member opts in through a new optional form question, so nothing appears while the question is still being added. Region names are hand-translated for FR and DE. (Closes #555.)
  • A second promotional poster on the press kit: a directory poster that invites scholars to add their bio, with the headline question, a QR code to the add-your-bio form, and the three reasons to join (be findable, connect across borders, mentor and be mentored). The press kit's poster section now holds both the general poster and the directory poster, each downloadable as a print file and a small preview, in all three locales. The directory poster ships as a print-ready PDF (vector) plus PNG renders.
  • Directory and ESSC headshots now serve a WebP first, with the original JPEG or PNG kept as the fallback for browsers that need it, so photos download at roughly half the bytes with no visible change. The sync writes a .webp next to each headshot (both committed to the repository), and every place a photo appears (the directory cards, the ESSC speaker popovers, the About and Working Group leadership avatars) now wraps it in a <picture> element so the browser picks the format it supports. (Closes #269.)
  • Shipped releases on the public roadmap now show how many tracked issues they closed, beside the release-notes link (a small "N tasks done" with a check mark). It reads the same per-milestone data the in-flight progress bars already use, and derives each card's version from its release-notes link, so every shipped release gets the tally with no per-card markup. The count is shown from v1.8.1 onward, the first release with full milestone tagging. Earlier releases used milestones too lightly for the figure to be representative, so they omit it.
  • The directory (/people.html, all three locales) can now surface mentorship. A member who offers mentoring shows a green Available to mentor badge on their card, and one looking for a mentor shows a Seeking mentorship badge. A new Mentorship filter sits alongside the Working Group and country filters, letting visitors narrow the directory to people offering or seeking mentorship. The badge and filter stay invisible until at least one member opts in through the form, so nothing appears while the question is still being added. (Tracked in #498.)
  • New hand-designed social cards (the image that appears when a page is shared on LinkedIn, Bluesky, or anywhere reading Open Graph tags). The directory (/people.html, all locales) gets its own card showing the searchable directory, and every other page uses a refreshed general card. inject-seo.py picks the right one per page, and the brand-asset script no longer regenerates the old auto-composed card over the top.
  • The site footer now links the Action's two social channels, in every page and locale: LinkedIn (costnetsec) and Bluesky (netsec-cost.eu), alongside the existing repository link. They sit as small circular icon buttons in the footer's social row, the same place the placeholder links lived, so they stay out of the way of page content. The unused X / Twitter placeholder is gone. Both channels are also declared as the organisation's sameAs profiles in the home-page structured data, which helps search engines connect the site to the accounts.
  • The directory (/people.html) now carries an Early Access Preview banner at the top, in all three locales, inviting visitors to contribute. The "adding your bio" link jumps straight to the join card at the foot of the page. The banner uses the same accent-gradient language as the What's New banner but sits in the page rather than as a dismissible top bar.
  • The About-page deliverables timeline now shows which deliverables have shipped, not only what is planned, in all three locales. A shipped milestone gains a check mark, and one delivered ahead of plan shows in green with a faded marker left at its originally planned month and a dotted lead-line between the two. The first to land is the NetSec directory (D1). Each status carries a screen-reader description, so the meaning never rests on colour alone.
  • Gave each Working Group on /working-groups.html two more sections, in all three locales: a Working towards list of the COST deliverables that group leads (with target months, each row linking to its entry in the About-page deliverables timeline) and a Related publications section. Publications are tagged by Working Group in a new data/publications.json and surface automatical...
Read more

v1.10.0 — Working Groups page and milestone-aware roadmap

31 May 10:11
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

The headline this cycle is a dedicated Working Groups page: each of the four groups gets its own section with an objective, its leadership, and a live membership grid drawn straight from the COST directory, and the Action's Memorandum-of-Understanding titles are adopted across the whole site. The public roadmap learns to read its own GitHub milestones, showing a progress bar on every in-flight release and marking the next one as in progress on its own. Around those two sit a wave of conference run-up for the European Security Conference and the usual directory and data housekeeping.

A home for the Working Groups

/working-groups.html (with hand-translated FR and DE) gives each Working Group an anchored section: the objective from the MoU, a row of focus areas, the lead and co-lead, and an expandable grid of every member with their country, all rendering at runtime from data/wg.json. That file is a new fourth surface the weekly cost.eu sync writes, so a leadership or membership change on the COST directory now flows through to the page, the home-page cards, the About leadership, and the directory filter with no hand-edit. The MoU titles (Building the Network, Transfer of Knowledge, Fostering the Next Generation of Scholars, and Inclusion, Representativeness & Ethics) replace the old working placeholders everywhere. Events and Working Groups cross-link both ways: the page lists the events a group runs, and each home-page Events card shows the groups its event serves.

A milestone-aware roadmap

The public roadmap now reads the repository's own GitHub milestones. Each in-flight card shows a progress bar of issues closed over total, refreshed automatically whenever an issue or milestone changes, and the next incoming release marks itself as in progress with a softly blinking dot. The Under-watch section gained a count of what sits parked there. The milestone discipline that has always driven release planning behind the scenes finally has a visible public payoff.

Conference run-up

With the European Security Conference approaching, the live ESSC programme and the FAQ both got attention. The programme now lists non-presenting co-authors alongside the speakers, carries a last-synced cue so a panelist who just edited Indico understands why the page is a day behind, and offers a one-click download of the official PDF (the browser print-to-PDF path had a chain of Chrome-specific truncation bugs, all now fixed). A new At a NetSec conference FAQ section, with matching signposts on the contact form and the programme page, answers the recurring questions about correcting your details, printing the programme, chairing a panel, and requesting a visa letter.

Directory and plumbing

The weekly member-spotlight engine and its home-page block both landed dormant, waking once ten members are eligible. Directory keywords now fold American spellings to British English so a submitted Defense reads as Defence, the Working-Group filter follows a leadership change on cost.eu on its own, and a long-standing imprecision in where anchor links land under the floating header is fixed.

Index of changes

Added

  • Related events on the Working Groups page. An event in data/events.json can now carry a workingGroups array, and the Working Groups page renders a compact Related events card for each event tagged with that group. The NetSec Early-Career Scholars Summer School is tagged WG2, WG3, and WG4, and the European Security Conference is tagged across all four groups, so each event appears under the groups it serves with no duplication in the data. assets/js/working-groups.js fetches events.json alongside the WG data and fails soft if it is absent (the leadership and member render is unaffected). Cards localise the title, date, and event type EN/FR/DE, and a group with no tagged events shows nothing. While here, a .prose-page a underline that was bleeding onto the member and leadership cards was scoped out, so those cards read as tiles.
  • Working-Group pills on the home Events cards. The reverse of that Related events block: each card in the home Events section now shows a small colour-coded pill for every Working Group the event serves, read from the same workingGroups tag, each linking to that group's section on the Working Groups page. So the Events ↔ Working Group relationship reads both ways (the Summer School shows WG2 / WG3 / WG4, the European Security Conference all four). Rendered by assets/js/home-events.js, EN/FR/DE, and an untagged event shows no pills.
  • Milestone progress bars on the public roadmap. Each in-flight card on /roadmap.html (+ FR + DE) now shows a progress bar fed by its GitHub milestone, the closed-over-total issue count as a percentage (for example 29% · 2 of 7 tasks done). scripts/sync-roadmap-progress.py writes data/roadmap-progress.json and a roadmap-progress.yml workflow refreshes it on every issue and milestone change (auto-PR, like the cost.eu and Indico syncs), so closing an issue moves the bar with no manual edit. assets/js/roadmap-progress.js renders the bar onto the card carrying the matching data-milestone, the fill follows the card's status colour, and the page fails soft when the JSON is absent. Shipped cards keep just their Shipped pill. The same renderer also marks the next incoming release (the first still-planned version card) as In progress automatically, with a slow-blinking status dot, so whichever release is next-up reads as active with no per-release edit (the static markup stays planned, so the release tooling is unaffected). The Under watch section heading also shows a small count of the items parked there. Localised EN/FR/DE and covered by scripts/test-roadmap-progress.py. This gives the milestone discipline of rule §10 a visible public payoff.
  • Dedicated Working Groups page (/working-groups.html + FR + DE). Each of the four Working Groups gets its own anchored section with its objective (lifted from the Memorandum of Understanding), a row of focus areas each carrying a line icon, its lead and co-lead, and an expandable grid of every member. The page opens with an overall stats strip (people across the four groups, countries represented, and the group count), computed client-side from data/wg.json so it tracks the weekly sync. Every member card shows the member's country (a directory bio adds a photo and a link to their card). Leadership and membership render at runtime from data/wg.json, so a change on cost.eu flows through with no hand-edit, and the page fails soft to a static fallback. The global nav Working Groups link now points here instead of the home-page overview section, the four home-page Working Group tiles link through to their section, and the page joins the sitemap (XML, the visual /sitemap.html, all three locales). Closes #378.
  • Working Groups data layer (data/wg.json), synced from cost.eu. The data half of #378. scripts/sync-cost.py writes a fourth surface: a per-WG dataset giving each of the four groups its lead and co-lead (read from each bio's wg_leadership) and every member of the group (name and country from cost.eu's Membership table, plus a directory slug for those who also have a bio). WG titles and the colour palette are config in the script, the file regenerates every weekly sync and is never hand-edited, so a leadership or membership change on cost.eu flows straight through. Covered by a new case in scripts/test-sync-cost.py.
  • ESSC programme now lists non-presenting co-authors alongside speakers. Indico keeps presenters and co-authors in separate lists, and the live programme grid on /essc-2026.html (+ FR + DE) used to show only presenters, so multi-author papers under-listed their co-authors versus the printed programme. scripts/sync-indico.py now emits a full author byline per contribution (a people array with a speaker flag), and the renderer prints every author, marking presenters with a small microphone icon, shown only on papers that actually mix speakers and co-authors, so single-author talks stay clean. The microphone carries a localised Speaker / Intervenant·e / Vortragende·r label for assistive tech, and member-card links still resolve for co-authors who are NetSec members.
  • FAQ now answers the recurring conference-participation questions. A new evergreen At a NetSec conference section on /faq.html (+ FR + DE) covers correcting your name, affiliation, or paper title on the programme; finding and printing the programme as a PDF; whether chairs can reorganise their own panel; and requesting a visa letter of invitation. The edit-my-details answer reflects how Indico actually works (it stores a per-event snapshot, so changing your personal profile does not update the programme; the paper title is self-editable on your contribution, while name and affiliation come to us or are fixed on the contribution's author entry). Recurring panelist requests can now self-serve or arrive complete and correctly addressed. docs/admin-guide.md gains the matching organiser-side setup (granting chairs session-coordinator rights each edition, and the document-generation route for invitation letters). First step of #332.
  • Self-service signposting on the contact form, the ESSC page, and the glossary. The home-page contact form (EN + FR + DE) gains a short pre-submit signpost pointing the recurring conference questions (correcting your details, the printable programme, visa letters) at the new FAQ section, plus an optional topic dropdown so enquiries arrive categorised for triage (the existing Formspree handler captures it with no code change). The ESSC programme page carries a matching Speakers & chairs practical block linking through to th...
Read more

v1.9.0 — Per-event calendar downloads and a news RSS feed

29 May 08:24
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

The pre-Stockholm cut, four days before the Summer School and European Security Conference open at Stockholm University. Visitors who land on the home page in the run-up to the conference get a calendar-savvy events block (per-card Add to calendar dropdown, every event downloadable as a single .ics), an RSS feed for the news block, and home-page cards that no longer drift between locales because they all derive from JSON. Three themes below; a single canonical index at the bottom.

Calendar plumbing

The website has shipped a single calendar.ics subscribable feed since v1.3.0. v1.9.0 keeps that, and adds one .ics per event under /calendar/<slug>.ics. Slugs derive from each event's UID (summer-school-2026@netsec-cost.eu/calendar/summer-school-2026.ics); scripts/build-calendar.py refuses non-conforming slugs at generation time so URLs stay predictable, removing an event from JSON auto-deletes its .ics, and the existing calendar-drift CI workflow now watches calendar/** too.

Every event card on the home page carries a new Add to calendar dropdown with four destinations: Google Calendar (prefilled template), Outlook web compose, Apple webcal:// subscription, and direct .ics download. The menu reparents itself to <body> on first open and pins with position: fixed against the trigger's bounding rect, so it escapes the stacking context that .event-card creates via backdrop-filter — otherwise the menu was occluded by the next card down. Same portal pattern as the ESSC member-preview popover; the decision is logged on the Wiki Decisions page.

Home page derives from data

Both the events block and the news block on index.html (+ FR + DE) now render at runtime from JSON. data/events.json and a new data/news.json are the source of truth; the renderers in assets/js/home-events.js and assets/js/home-news.js pick the locale from <html lang>, sort items, and rebuild the relevant <div>. The pre-existing hand-coded HTML survives as a fail-soft fallback that the renderer empties on success — so a fetch failure leaves visitors with a coherent page rather than an empty section. Card descriptions get a five-line clamp with a Read more / Lire la suite / Mehr anzeigen toggle that's only injected when the rendered text actually overflows; @media print drops the clamp.

This closes #249 and the drift class it tracked. The home-page event cards used to be hand-authored across three locales and could drift from the calendar feed; PR #248 had to paper over an "Applications now open" CTA that was already past its closing deadline. From v1.9.0 onwards, both lists share the same source of truth as the public /calendar.ics and /news.xml feeds. The Indico-write tooling continues to overwrite summary / start / end per indicoEventId; the renderer prefers cardTitle.{locale} over summary so localised home-page titles aren't affected.

News on the open web

/news.xml is a new RSS 2.0 feed exposing the home-page news cards to feed readers (Feedly, Inoreader, NetNewsWire, Reeder). scripts/build-news-rss.py generates it from data/news.json with <atom:link rel="self">, CDATA-wrapped descriptions, RFC 822 pubDate, and guid isPermaLink="false". A new news-drift CI workflow runs --check on every PR touching the data or the generator, mirroring the calendar-drift shape.

Every home page carries <link rel="alternate" type="application/rss+xml"> in <head> so feed readers auto-discover the feed without the visitor needing to know the URL; a visible Subscribe to NetSec news (RSS) affordance under the news block (mirroring the existing calendar-subscribe block) covers visitors who don't read <head>. Single-language EN by RSS convention — per-locale feeds (/news.fr.xml, /news.de.xml) are a deferred follow-up if reader demand surfaces.

Index of changes

Added

  • Per-event .ics downloads at /calendar/<slug>.ics. scripts/build-calendar.py now writes one .ics per event in data/events.json, in addition to the existing aggregate /calendar.ics subscribable feed. Slug derives from the event's UID (summer-school-2026@netsec-cost.eu/calendar/summer-school-2026.ics); the script refuses non-conforming slugs (^[a-z0-9-]+$) at generation time so URLs stay predictable. The per-event files carry the same VTIMEZONE block as the aggregate but no REFRESH-INTERVAL / X-PUBLISHED-TTL since they're one-shot import downloads, not subscribable feeds. Removing an event from JSON auto-deletes the matching /calendar/*.ics; the existing calendar-drift CI workflow now watches calendar/** too and fails the build if any output is stale. #257.
  • Home-page event cards now derive from data/events.json (closes #249 renderer half). New assets/js/home-events.js reads the events JSON on DOMContentLoaded, picks the locale from <html lang>, and rebuilds the #events .event-list cards from structured data. Schema extends each event with eventType, featured, displayDate, cardTitle, cardDescription, meta[], and cta — all with {en, fr, de} blocks where appropriate. The pre-existing hand-coded HTML survives as a fail-soft fallback. Card descriptions get a five-line clamp with a Read more / Lire la suite / Mehr anzeigen toggle injected only when the rendered text overflows; @media print drops the clamp. Each card carries an Add to calendar dropdown with four destinations: Google Calendar (prefilled template), Outlook web compose deep-link, Apple webcal:// subscription, and direct .ics download from the per-event files. Outside-click + Escape dismiss the menu. The renderer prefers cardTitle.{locale} over summary so the Indico sync (which overwrites summary via indicoEventId) doesn't bleed into the localised home-page titles. #259.
  • News RSS feed at /news.xml + structured news data layer. New data/news.json is the source of truth for both the home-page news cards and the public RSS feed. Schema mirrors data/events.json per item. scripts/build-news-rss.py generates news.xml in RSS 2.0 format with <atom:link rel="self">, per-item CDATA-wrapped descriptions, guid isPermaLink="false", and RFC 822 pubDate. Single-language EN per RSS convention; FR / DE feeds deferred. The matching news-drift.yml CI workflow mirrors calendar-drift. assets/js/home-news.js reads the JSON on DOMContentLoaded, picks the locale, sorts newest-first, and rebuilds #news .news-list; hand-coded HTML survives as fail-soft fallback. Every home page now carries <link rel="alternate" type="application/rss+xml"> in <head> for feed-reader auto-discovery. #261.
  • Visible Subscribe to NetSec news (RSS) affordance under the news block on the home page (EN / FR / DE), mirroring the existing Subscribe to NetSec events calendar affordance. Hint sentence names three popular readers (Feedly, Inoreader, NetNewsWire) so non-technical visitors recognise the use case. #263.
  • Visual sitemap entries for /news.xml and /calendar.ics in sitemap.html (+ FR + DE). The Home sub-tree now reads News & announcements · RSS feed and Events · calendar.ics. The machine-readable sitemap.xml is unchanged: RSS feeds and .ics files aren't pages and don't belong in <urlset>. #264.
  • Directory freshness line on The Network. /people.html (+ FR + DE) now shows a discreet Directory last updated line under the page lede, driven by data/bios.json's top-level generated_at stamp. The stamp only moves when scripts/sync-bios.py produces a substantive change, so the date stays honest across weeks with no new submissions. Locale-aware date formatting (en-GB / fr-FR / de-DE) and a manually translated label; docs/bios-setup.md now documents the field. Closes #271.

Fixed

  • Add-to-calendar URLs now resolve the event's real UTC offset year-round. assets/js/home-events.js hard-coded +02:00 when building the Google Calendar and Outlook compose URLs. That matches Stockholm summer time but lands an hour off for any event in winter (CET, +01:00) or in a different zone. The two URL builders now resolve the offset for each event's wall-clock time via Intl.DateTimeFormat, lifting the zone from data/events.json (top-level tzid, with an optional per-event tzid override). The .ics downloads were always correct (they carry a VTIMEZONE block), so only the inline URLs changed. Closes #260.
  • 404-page broken-star fragments now stream on a wind. The loose pieces of the emblem (the cluster of yellow circles where three stars are missing from the top of the ring) used to bob straight up and down on a quiet err-illu-drift loop. They now blow left to right: each fragment enters from down-and-left, lifts up and across the canvas, then fades, as if a wind is carrying the broken pieces off the page. Per-fragment animation delays spread the cluster across the cycle so the motion never stalls, and a new --frag-peak custom property (set inline on each circle, matched to its opacity attribute) carries the resting opacity through the keyframes so the depth of the original cloud survives. prefers-reduced-motion falls back to the static attribute opacity with no transform.
  • 404-page polish, sixth pass: the locale-row gap finally takes, map fills the card. The button-to-locale gap had been bumped four times (28 px through 80 px acr...
Read more

v1.8.1 — Founding contributors, release-infra hygiene, ribbon and voice polish

27 May 21:26
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

Index of changes

Added

  • Founding contributors section on /about.html (Item A of the founding-cohort brainstorm). New <section id="founding"> between Leadership and FAQ lists the 52 researchers across 21 countries who participated in COST Open Call OC-2024-1-27931 establishing this Action. Sourced from a new data/founding-proposers.json (the JSON also records membership status, MC-availability at founding, and the original source name where the affiliation needed light cleanup). Renders runtime via inline JS; reuses the existing country-grid / mc-collapse / mc-stats patterns from the MC country grid directly above so the founding cohort reads as a parallel "where we came from" narrative to the current MC composition. FR + DE variants land alongside, with locale-aware country names and the corresponding "Soumissionnaire" / "Antragsteller" badge for the Open Call Proposer (Dr Hugo Meijer). Privacy notice on /privacy.html (+ FR + DE) gains a new sub-section under §2 documenting the founding-listing as a separate processing activity with Article 6 (1)(f) legitimate-interest basis and a fourteen-day contact-form opt-out. Pairs with the Wiki-side directory-growth tracker (Item B) and a follow-up issue covering Items C (per-bio "founding contributor" badge) and D (founding-cohort stats refresh on press kit + PDF documentation pack). #242.
  • Three new entries in data/events.json from the official Action event ledger: the NetSec Policy Workshop (4 September 2026, format TBC), the NetSec ITC Conference (8–11 September 2026, ITC Conference Grant scheme), and the Inaugural Management Committee plenary (18 September 2026, the firm date previously listed as "before late September"). Each entry feeds the home-page event banner via data/events.json and the public webcal feed via calendar.ics; calendar.ics rebuilt accordingly (5 events). Public roadmap (/roadmap.html + FR + DE) firms up the MC-plenary card date from "Before late September 2026" / "Avant fin septembre 2026" / "Vor Ende September 2026" to 18 September / 18 septembre / 18. September. docs/roadmap-2026.md timeline gets matching rows for the Policy workshop and the ITC Conference between the existing Stockholm and MC plenary entries. Past Core Group JourFix dates (January, March, May 2026) and the September Core Group + MC back-to-back are seeded into the Wiki Meetings index (Wiki commit; minutes pending). #244.
  • Issue-lifecycle automation (#238 item C). Three new workflows under .github/workflows/ bound the open-issue backlog without manual sweeping: lock-closed-issues.yml (daily 13:00 UTC) locks issues 14+ days after closure to keep drive-by comments off settled threads, issue-lifecycle-comment.yml (on labeled event) auto-posts the standard message when a lifecycle label lands (needs-info, duplicate, wontfix, stale), and issue-sweep.yml (daily 14:00 UTC) labels open issues stale after 60 days of inactivity, closes stale issues after another 14 days, and closes needs-info issues if no human comment arrives within 14 days. Two new labels (needs-info, stale) join the existing bug, enhancement, documentation, duplicate, wontfix set. Thresholds tuned softer than the upstream anthropics/claude-code defaults (14 / 60 / 14 vs. their 7 / 30 / 7) because our backlog is small and the maintainer reads every notification by hand. Label vocabulary + workflow behaviour codified in CLAUDE.md §12. #240.
  • YAML-form GitHub issue templates (#238 item B). Three structured forms land under .github/ISSUE_TEMPLATE/: bug_report.yml (preflight checkboxes + required actual / expected / repro / environment), enhancement.yml (mirrors the maintainer's What's happening / Why it matters / Fix path / Target shape from CLAUDE.md §3), documentation.yml (typed dropdown picking which surface the issue affects: maintainer docs, Wiki, PDF pack, public copy, cross-cutting). A config.yml disables blank issues and routes routine questions to the public contact form, the FAQ, the Wiki onboarding page, and the ESSC 2026 member orientation. CLAUDE.md §3 updated to point external contributors at the forms while keeping the four-section maintainer-issue shape as the canonical body content for gh issue create paths. #239.
  • CLAUDE.md §12 Release-infrastructure hygiene (#238). Codifies the three conventions on .github/ introduced across items A, B, and C of the issue: SHA-pin third-party actions with tag-comment annotation, YAML-form issue templates rather than free-form markdown, and the lifecycle-label vocabulary table (needs-info, stale, duplicate, wontfix). Lands together so the next maintainer inherits the conventions rather than rederiving them from upstream.

Changed

  • Home-page event cards (index.html + FR + DE) refreshed against the official Action event ledger. Two new cards added between the European Security Conference and the Inaugural MC Plenary: the NetSec Policy Workshop (4 September 2026) and the NetSec ITC Conference (8–11 September 2026, ITC Conference Grant scheme; travel-grant support linked through to grants.html). The Inaugural Management Committee Plenary card firms up from "To be announced" to 18 September 2026 with updated body prose (first formal plenary, MC representatives + Working Group leads, restricted-access notice; the previous Kick-off Meeting event-type pill becomes MC Plenary). The Summer School card updates the Application deadline meta row to Applications closed on 1 March 2026. Selected participants will be contacted by the scientific coordinators; the CTA text shortens from Full details & how to apply to Full details since applications have closed. The event-list now matches the chronological order in data/events.json (Summer School → ESSC → Policy Workshop → ITC Conference → MC Plenary). The home-page cards are still hand-coded HTML rather than rendered from events.json; deriving them at build time is tracked in #249 for v1.9.0. #248.
  • .github/workflows/*.yml third-party actions SHA-pinned with tag-comment annotation (#238 item A). Every uses: line across the eleven touched workflow files now references a commit SHA instead of a floating version tag, with a trailing # vN (sha-pinned) comment for human readability. Closes the supply-chain exposure where a maintainer of any third-party action (or an attacker who compromises one) could push a malicious commit under the same tag and run with contents: write plus pull-requests: write on our next sync. Affected: actions/checkout@v4, actions/configure-pages@v5, actions/deploy-pages@v4, actions/setup-node@v4, actions/setup-python@v5, actions/upload-pages-artifact@v3, peter-evans/create-pull-request@v7. Widens the scope of #151 (which only covered Node 20 removal pinning) to the full third-party surface. Dependabot continues to surface updates via PR, updating the SHA explicitly each time. #239.
  • scripts/sync-cost.py now propagates per-bio WG memberships from cost.eu into data/bios.json (#236 Gap A). The weekly Monday sync (plus any manual workflow_dispatch) parses the Membership table on https://www.cost.eu/actions/CA24154/, looks each row up against bios.json.members[].name via the existing norm() helper, and overwrites the matched entry's wgs field with cost.eu's list. Entries not present on cost.eu (community members in the directory who aren't on the MC, or seed entries for leaders not yet on the Membership table) are left untouched. Before this change, the home-page WG chips (driven by WG_MAP in index.html) and the /people.html per-bio chips (driven by the Google Form submitter's answer) could drift indefinitely. cost.eu is now the authoritative source for formal WG membership on both surfaces; the Google Form remains the seed when a bio first lands. Rule documented in docs/bios-setup.md. Six new smoke tests in scripts/test-sync-cost.py cover the overwrite, idempotency, leave-unmatched-alone, salutation normalisation, missing-file, and leadership-suffix regression cases. Gaps B (statistics + country roster) and C (leadership-label regex holes) deferred to v1.10.0. #237.
  • Public roadmap (roadmap.html + FR + DE) and docs/roadmap-2026.md reshuffled around the Stockholm conference cadence. Five planned releases now interleave with the Action calendar: v1.8.1 (28 May, this release), v1.9.0 (5 June, pre-Stockholm calendar plumbing), v1.10.0 (late July, reactive post-conference patch with sync-cost Gaps B + C, Stockholm recap, FR / DE FAQ + Glossary native-speaker pass), v1.11.0 (mid September, three days ahead of the inaugural MC plenary; Outputs section refresh with D6 cards + schema.org/ScholarlyArticle, Phase 2 IA pass, founding-cohort follow-ups #245, PDF documentation pack section-level catch-up #229), v1.12.0 (late December, Year 1 retrospective + D11 + D12 ...
Read more

v1.8.0 — Brand launch, Indico writes, programme PDF, voice sweep

25 May 23:53
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

Pre-Stockholm release. The brand identity finally lands across the site, three Indico operational tools ship to make the ESSC 2027 prep cycle programmatic, the programme page exports a polished self-identifying PDF, and the CLAUDE.md §7 writing-voice rules get applied retroactively across the EN launch-era prose.

NetSec brand identity

The designer's new four-petal mark and lockup deploy across all 46 HTML pages, replacing the launch-era "NS" gradient-square placeholder. Three surfaces move at once. The header brand link ships two <img> lockups keyed off the site's .dark class (light and dark variants follow whatever theme the visitor explicitly picked rather than the OS prefers-color-scheme, which would desync against the rest of the page), with a 32×32 mark-only swap below 700 px to free up header real estate against the hamburger, language switcher, and theme toggle. The favicon family rasterises from the 595×599 mark into the per-size PNG chain (16 / 32 / 48 for browser tabs, 180 for Apple touch-icon, 192 and 512 for Android home-screen and PWA manifest) plus a multi-resolution favicon.ico for legacy clients; a new manifest.webmanifest at repo root carries those references along with theme_color and background_color so the OS shortcut UI matches the brand. A fresh 2400×1260 OG card composes the primary lockup over a soft brand-tinted canvas for LinkedIn, Mastodon, Bluesky, Slack, Twitter, and Facebook link previews; JSON-LD Organization.logo points at the new 512×512 mark so the Google Knowledge Panel renders correctly. Two reproducible build scripts ship alongside (build-brand-assets.py and update-brand-html.py) so the next designer refresh stays a one-command operation; the rationale and refresh workflow are written up in docs/brand-deployment.md. Out of scope for this cut: SVG masters (designer delivered PNG only, follow-up on #220) and the #003399 to brand #2B639C accent migration (the values are close but not identical, and the migration is cross-cutting across site.css, JSON-LD themeColor, manifest theme_color, and several inline <style> blocks).

Indico write-side automation

Two new operational scripts and one permission-model finding land together, the result of four probe rounds against the live EISS Indico instance through PRs #212 to #217. scripts/indico_patch.py is the write-side companion to the daily sync-indico.py: it reads a YAML "fix-plan" describing session renames, room changes, contribution session-moves, affiliation corrections, and block-time edits, resolves friendly Indico IDs to internal database IDs against the live read API, then dispatches the right write call against the management endpoints. Dry-run by default; --apply flips to live writes; resolved IDs cached in a gitignored sidecar JSON. scripts/indico_clean_duplicate.py handles the ESSC-N to ESSC-N+1 rollover: Indico's "duplicate event" feature copies the previous year's contributions and sessions along with the configuration we actually want to inherit (review workflow, custom fields, registration form, role assignments), so new submissions continue the old friendly-ID counter and your first ESSC 2027 abstract lands as #342 instead of #1. The clean-duplicate script enumerates inherited content via the read API and selectively DELETEs it via the management API, leaving configuration intact, with a hardcoded PROTECTED_EVENTS allow-list refusing to touch the live ESSC 2026 (event 22) unless --force is passed; dry-run by default, explicit --delete <category> required per content type. Smoke-tested against event 22 in dry-run: enumerated 105 contributions correctly via the read API and produced the right DELETE URLs without issuing any. The probe rounds resolved a permission-model misread that had blocked Phase 1: the 403-with-anonymous-session pattern on /event/<id>/manage/* that we first read as "Bearer auth ignored" turned out to be Indico's standard auth-then-permission flow, falling through to the anonymous render path when the user lacked management permission. The unlock is the admin flag on the bot account that owns INDICO_WRITE_TOKEN, not a scope or auth-mechanism change. That operational precondition is now documented across all three Indico scripts in docs/indico-patch.md. Tracks #210.

Programme page · self-identifying print-to-PDF

/essc-2026.html (plus FR and DE) now exports a self-identifying PDF when the visitor uses Print → Save as PDF. Page 1 carries a full title block (conference name, dates, venue, organisers) so the file makes sense when shared or archived separately from the URL; pages 2 onwards get a thin single-line locale-aware running header and a bottom-right page counter (Page 2 of 4 / sur / von). A4 portrait, 20 / 14 / 16 mm margins, tighter cards at 9.5 pt body with 0.6 pt borders and no shadows, contributions list force-open on print so the full paper line-up and abstracts make it onto paper. The export shrank from 17 stretched-card pages to a clean 6 once the leak from the external-link ::after decoration (width: 0.85em icon mask combined with word-break: break-all was wrapping URL characters one per line and inflating link headings to 565 px) was reset inside the print rules. Closes #208.

Maintainer signals and the launch-prose sweep

Two small but high-leverage pieces of cleanup. The weekly bios-sync and cost-sync workflows open auto-PRs on dedicated branches and auto-merge them when CI is green, which keeps main fresh but means churn lands silently. Adding reviewers: APB-LDN to both peter-evans/create-pull-request@v7 invocations turns each diff-producing run into an email and a mobile-push notification on the maintainer's GitHub account, without gating the auto-merge (#222). Separately, the CLAUDE.md §7 writing-voice rules (no em dashes, no rule-of-three, no synonym cycling) get applied retroactively across the EN public surface: 30+ HTML pages, the SEO injection script and its regenerated meta and JSON-LD output, the hand-authored ESSC 2026 OG / Twitter / JSON-LD blocks, and the events.json calendar copy. UI glyph em dashes (the empty-field "—" in quickfacts cells, the JS no-value defaults) are kept as-is; those are typography, not punctuation. FR and DE prose is left to its translators, who decide their own punctuation conventions (#223). The P1 documentation voice-sweep tracks separately for the next cycle.

Index of changes

Added

  • NetSec logo deployment across all 46 HTML pages. Header lockup (light / dark <img> pair keyed off .dark with a mark-only swap below 700 px), favicon family (16 / 32 / 48 / 180 / 192 / 512 PNG chain plus multi-resolution .ico), manifest.webmanifest at repo root, JSON-LD Organization.logo, and a fresh 2400×1260 OG social card. Reproducible via scripts/build-brand-assets.py and scripts/update-brand-html.py; refresh workflow at docs/brand-deployment.md. Closes #220.
  • scripts/indico_patch.py, the write-side companion to sync-indico.py. Reads a YAML fix-plan, resolves friendly Indico IDs against the live read API, dispatches session-rename, room-change, contribution-move, affiliation, and block-time edits against the management endpoints. Dry-run by default; --apply writes for real. Schema at data/indico-fix-plans/EXAMPLE.yaml; design rationale at docs/indico-patch.md. Tracks #210.
  • scripts/indico_clean_duplicate.py, for the ESSC-N to ESSC-N+1 rollover. Lists inherited content via the read API and DELETEs it via the management API, leaving configuration intact. A hardcoded PROTECTED_EVENTS allow-list refuses to touch ESSC 2026 (event 22) without --force. Smoke-tested against event 22 in dry-run: enumerated 105 contributions correctly and produced the right DELETE URLs without issuing any. Same admin-flag precondition as indico_patch.py.
  • Cover masthead, running header, and bottom-right page counter on the programme print-to-PDF (essc-2026.html plus FR and DE). A4 portrait, 20 / 14 / 16 mm margins, tighter cards at 9.5 pt body, contributions list forced open. Closes #208.
  • Sync workflow maintainer notifications. reviewers: APB-LDN added to both bios-sync and cost-sync peter-evans/create-pull-request@v7 steps; each diff-producing run now emails plus mobile-pushes the maintainer. Auto-merge still fires on green CI, so the line is a change-awareness signal, not a review gate (#222).

Changed

  • Voice rules applied retroactively to EN public HTML and shared SEO infrastructure (#223). 30+ pages swept: <title> em dashes to ·, <meta description> first em dash to colon and subsequent ones to comma, hand-authored ESSC 2026 OG / Twitter / JSON-LD blocks aligned to the new SEO constants in scripts/inject-seo.py. The inject-seo.py BEGIN seo:auto sentinel keeps its em dash for regex backward-compatibility with already-deployed pages. UI glyph em dashes (empty-...
Read more

v1.7.0 — Directory keyword filter, bios-sync hardening, release automation

24 May 17:43
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

Conference-prep release. The directory gets a research-interest filter chip row so visitors can drill in by topic across the membership; the bios-sync pipeline gets the robustness work to handle the volume the open form is about to deliver; and the release process itself gets the automation that will make every future release lighter than the last. Cut before the European Security Conference on 9–12 June so the new directory shape is what the incoming submissions land against.

Directory research-interest filter

Three phases shipped end-to-end across the three locales. Phase 1 renders a member's research keywords as outlined chips on the detailed bio card. Phase 2 normalises submissions through a curated data/keyword-aliases.json so near-duplicates collapse to a single canonical form and acronyms (UN, NATO, EU, UK, US, UNDP, …) survive the sentence-case pass; an aggregate count per canonical keyword falls out as a by-product. Phase 3 surfaces that aggregate as a multi-select toggle chip row above the directory grid: top eight by count, Show all expander, OR semantics, URL-hash persistence so filtered views are shareable (#keywords=ai-governance,foreign-policy-analysis), and clickable per-bio pills that feed into the same filter. The guided tour and the welcome strip on /people.html were updated in EN / FR / DE to introduce the new row.

Bios-sync robustness, before the firehose

The Google Form is about to open to ~50 incoming submissions. Three improvements harden the pipeline. The merge logic was already truthy-merge per field; that semantic guarantee is now pinned by a regression test and explained in docs/bios-setup.md so respondents who resubmit sparsely (the documented workaround for the Google Forms file-upload-edit bug, #183) don't lose their previously-stored optional links. Defensive PHOTOS_CHANGED tracking carries the lesson from sister-project EISSeuropa.github.io #105+#106: if photo_source_sha256 propagation ever regresses, the script screams loudly instead of silently producing an unexplained binary diff. And the auto-PR itself self-describes now: title becomes data: Dr Alex Petrova joined the network or data: 2 new bios + 3 updates; body opens with a structured What changed section listing new joiners with country + affiliation, updated members with the specific fields that moved, and the list of headshot files rewritten on disk.

Release-time automation

The maintainer-facing release process picks up two pieces of automation that close the between-releases drift gap CLAUDE.md §11 had deliberately left open. docs/roadmap-2026.md carries a machine-managed AUTOSTAMP block; a new workflow regenerates it on every push to main that touches CHANGELOG.md, auto-merging the PR. And scripts/release.sh now calls scripts/promote-roadmap.py before the release commit, which flips the matching <li class="rm-entry planned"> card across EN / FR / DE to shipped with locale-correct date formats (8 September 2026 / 8 septembre 2026 / 8. September 2026), inserts the localised release-notes link, and bumps the Last updated paragraph's two <time> attributes plus visible text. On minor / major releases the script also prints a structured PDF-cover reminder pointing at the four version stamps to update. First observation of the workflow firing caught an auto-merge gap on sync-roadmap.yml and sync-bios.yml; fixed in the same window.

Index of changes

Added

  • Research-interest keyword chips on directory cards (/people.html + FR + DE). Detailed view only. Sentence-case normalisation at render time so submissions like "International Security" and "international security" collapse to a single visual form. A curated acronym set (UN / NATO / EU / UK / US / UNDP / OSCE / ASEAN / IMF / WHO / IAEA / GDPR / IoT / R&D / CFSP / PESCO / BRICS / G7 / G20 / …) keeps those preserved through the normaliser so compound forms like "EU–NATO relations" render correctly rather than mangling to "Eu–nato relations". Distinct styling from the WG chips (subdued outlined pill vs. bright gradient pill) so visitors parse the two layers at a glance. Keywords already entered the directory search vector and now also enter the site-wide Pagefind index via rendered DOM.
  • Phase 2 keyword infrastructure for the directory. New data/keyword-aliases.json carries a curated acronym list + alias map. scripts/sync-bios.py resolves each bio's raw keywords through the alias map (with sentence-case + acronym preservation as the auto-normaliser), emits a canonical_keywords field per bio plus a top-level keyword_aggregate count, and logs Levenshtein / substring-close pairs as "possible alias candidate" hints so the maintainer can merge them by hand. The renderer (EN / FR / DE) prefers canonical_keywords, falling back to the inline normaliser for older data. Documented in docs/bios-setup.md. Phase 3 (dedicated filter chips above the grid) still tracked in #175.
  • Phase 3 research-interest filter chip row above the directory (/people.html + FR + DE). Reads keyword_aggregate from bios.json, renders the top eight canonical keywords as toggle pills with submission counts, and expands to the full list on demand. Multi-select with OR semantics: any bio carrying at least one selected interest passes. Per-bio keyword pills are now buttons too: tap one to add it to the active filter and scroll to the result. Selection persists in the URL hash (#keywords=…) so filtered views are shareable and survive back-forward navigation. Visible as soon as the directory has any canonical keywords; hidden cleanly otherwise.

Changed

  • Directory guided tour + welcome strip updated to introduce the research-interest filter row. A new tour step lands between Country and Card density, explaining the chip row, multi-select OR semantics, the clickable per-bio pills, and the URL-shareability of a filtered view. The welcome strip gains a matching bullet so the orientation is visible even to visitors who skip the tour. Mirrored to FR + DE. docs/bios-setup.md also gets a one-paragraph note that keyword_aggregate powers the filter automatically.
  • Bios-sync auto-PRs now self-describe. Title used to be the static data: sync member bios from Google Form; it's now dynamic and reflects what changed: data: Dr Alex Petrova joined the network, data: Bob Smith updated their headshot, data: 2 new bios + 3 updates, etc. The body gains a structured ## What changed section above the raw run log: per-member bullets list new joiners with country + affiliation, updated members with the specific fields that moved (bio, LinkedIn vs headshot replaced vs bio, keywords + headshot), removals, and the list of headshot files that were rewritten on disk. Driven by a pure classify_diff function in scripts/sync-bios.py covered by 20 new test assertions.
  • Roadmap-doc autostamp automated. docs/roadmap-2026.md now carries a machine-managed AUTOSTAMP block near the top that records the number of bullets in CHANGELOG.md [Unreleased] (per category) and the freshness date. A new workflow .github/workflows/sync-roadmap.yml regenerates the stamp on every push to main touching CHANGELOG.md (plus a weekly cron + manual dispatch) and opens an auto-merging PR if the count moved. Closes the gap CLAUDE.md §11 deliberately left open between releases: the staleness signal is automated; humans still write the prose synthesis on release-time §5 sweep. Driven by scripts/sync-roadmap.py, pinned by 21 test assertions in scripts/test-sync-roadmap.py.
  • Public-roadmap promotion + last-updated stamps automated at release time. scripts/release.sh now calls a new scripts/promote-roadmap.py before the release commit. The script finds the <li class="rm-entry planned"> card matching the release version across roadmap.html + FR + DE, flips the status pill to Shipped / Livrée / Veröffentlicht, formats the date per locale convention (8 September 2026 / 8 septembre 2026 / 8. September 2026), adds the Release notes / Notes de version / Release-Notizen link, and bumps both <time datetime="…"> attributes + the visible date text in the Last updated paragraph. Non-release planned milestones (Stockholm event, MC plenary) are protected by an rm-milestone class guard. Idempotent: re-runs no-op cleanly. Pinned by 28 test assertions in scripts/test-promote-roadmap.py. On minor / major releases (X.Y.0 / X.0.0), release.sh also prints a structured PDF-cover reminder with the current PDF version, the four stamps to update in docs/pdf/documentation.html, the ./docs/pdf/build.sh rebuild command, and the PDF bump-policy ladder. Patch releases skip the PDF reminder per CLAUDE.md §11.
  • Join-form Google Forms settings flipped to work around an upstream limitation: file uploads can't be replaced via the response-edit link, so photo updates need a fresh submission. Limit to 1 response is now off; Collect email addresses → Verified keeps sign-in mandatory and gives the sync a reliable dedup key; Allow response editing stays on for non-photo updates. A note on the form's Photo question points respondents at the workaround at the point of confusion. docs/bios-setup.md Step 1 + the Editing section rewritten accordingly. Tracked as #183; will revert if Google ever fixes the upstream bug.
  • Documented the truthy-merge semantics of scripts/sync-bios.py in the bios-setup guide. A respondent submitting a "minimum-viable" second response that fills only the required fields plus the new photo will NOT lose their previously-store...
Read more

v1.6.1 — Pre-ESSC polish, sync robustness, copy hygiene

24 May 14:00
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

Index of changes

Added

  • Inline-expand full abstract on programme contribution cards (/essc-2026.html + FR + DE). scripts/sync-indico.py now emits a fullAbstract field alongside the truncated teaser; clicking Read full abstract swaps the teaser for the full text in place, Show less swaps it back. Title still anchors to the Indico contribution page for the canonical record. #158.
  • Per-session room badge on the programme grid. Surfaces "D House, Lecture Hall 8" / "Lecture Hall 9" / "Floor 3" on session, contribution, and break cards via a small pin-icon chip. The sync exposes inheritRoom and inheritLoc flags from Indico for forward use. #156.
  • Practical information section on /essc-2026.html after the live programme. Two cards: Accommodation (five recommended Stockholm neighbourhoods with their nearest red-line metro stops as chips) and Getting around (T13 context + sl.se link). Quick-facts strip grows from 4 to 5 tiles with a new Practical info / Stockholm tips ↓ in-page anchor. Mirrored to FR + DE. #159.
  • indicoEventId link field on data/events.json. Entries that opt in get their summary, start, and end overwritten from the fresh Indico payload on every sync, closing the drift between the live programme and the home-page banner / calendar.ics. Allow-list is tight; location, description, url, categories stay hand-edited. Documented in docs/indico-sync.md. Refactor to fully-derived data tracked in #170. #171.
  • Per-PR [Unreleased] maintenance rule added to CLAUDE.md §4. Every PR that ships a user-visible change adds at least one bullet to [Unreleased] in the same PR; reconstructing the batch at release time loses nuance.

Changed

  • Parallel programme rows sorted by canonical room name so the same room consistently lands in the same column across the day. Indico orders parallel panels by convener id; without normalisation, Lecture Hall 8 jumped between left and right between time slots. A small _canonical_room helper strips cosmetic building prefixes so "Lecture Hall 8" and "D House, Lecture Hall 8" collapse to the same column key. #157.
  • sync-indico.yml opens a PR via peter-evans/create-pull-request@v7 instead of pushing directly to main. Branch protection on main had started rejecting the direct push with GH013. CodeQL still runs on the bot PR (separate workflow), all checks complete, auto-merge fires, daily cadence stays hands-free. PAT not required; GITHUB_TOKEN is enough. #160.
  • Sitewide footer attribution: em-dash → colon. COST Action NetSec — Networking European Security Knowledge becomes COST Action NetSec: Networking European Security Knowledge (and locale variants) across 45 page footers (15 EN + 15 FR + 15 DE). Voice-rule cleanup pass; rest of the em-dash audit tracked in #164. #166.

Fixed

  • Mobile home visual polish. The floating nav no longer ghosts high-contrast details-strip text through its backdrop-filter on iOS Safari: a fixed top-scrim covers the gap above the bubble and the nav itself takes a near-opaque background on small viewports. Details-strip ↔ event-banner vertical gap tightened from 24 + 24 px to 12 + 8 px at ≤ 720 px. The event-banner status pill is now wrapped in a subtle currentColor-tinted chip so the dot reads as part of the same pill rather than a floating speck. #154.
  • Beta-translation ribbon ghosting on FR / DE pages. The disclaimer ribbon used a ~5-15% alpha accent gradient over no base, so page content scrolled visibly through. Layered over var(--glass-bg-strong) + backdrop-filter: saturate(180%) blur(20px) on desktop, plus a near-opaque page-bg tint on mobile (≤ 720 px). #155.
  • Details-strip separator half-line on mobile home. The 2 × 2 grid at ≤ 1100 px left a stray border-bottom under the third tile only. Switched the strip rule from :nth-child(2n) + :last-child to :nth-last-child(-n+2) so the final row sheds the border regardless of total item count. #163.
  • Break-card title and room badge collision on /essc-2026.html. The pin icon sat right against the last word of the title; italic muted styling made the title vanish next to the badge. Now flex-laid with gap: 14 px, title in normal weight + ink-2 colour, middle-dot · separator before the badge. #168.

🤖 Authored with help from Claude Code.

v1.6.0 — Live ESSC programme and member previews

23 May 08:47
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

The live ESSC programme release. v1.6.0 turns netsec-cost.eu into the canonical entry point for the European Security Studies Conference: a daily-synced live programme page at /essc-2026.html, in-place bio previews for speakers who are NetSec members, a collapsible shipped-history on the public roadmap, and a CSS lint that catches the class-name collisions that bit the directory mid-cycle.

Live ESSC 2026 programme on netsec-cost.eu

The flagship outreach moment of the year now has its own page on the NetSec site rather than living only on Indico. scripts/sync-indico.py runs daily at 03:45 UTC, talks to indico.eiss-europa.com's API, scopes to category 1 (Annual Conferences), and writes the normalised programme to data/indico.json. The page at /essc-2026.html (+ .fr.html + .de.html) reads that file at render-time and lays out a programme grid with day-chip navigation, parallel-session rows, contributions, abstracts, livestream badges on plenaries and roundtables, and a pulse-dot beside the page-level "Live programme" cue. Chrome strings (chair / speakers / discussants / day labels / error messages) translate via an inline I18N table; programme content stays in whatever language the submitter wrote it in. The home-page Events block now deep-links to the live page; the sitemap and calendar.ics treat it as the canonical URL for the conference. Schema-compatible with EISS's existing programme generator so a future port to a build-time renderer drops in.

Member-aware previews on the programme

Hover a speaker name on the programme — if the speaker resolves to a NetSec member through data/bios.json, a glass-surfaced preview card opens via the native Popover API. The card carries photo, name, position, affiliation, country with flag, role / working-group chips, three-line bio excerpt, contact-icon row (email, website, ORCID, LinkedIn, X, Bluesky, Mastodon — only the ones the member has filled in), and a "View full profile →" link to /people.html#<slug> that scrolls to the matching directory card with a persistent spotlight. Matching uses a JS port of scripts/sync-bios.py's name_key(): NFKD-normalise, strip diacritics, drop honorifics, drop apostrophes, drop post-nominals, drop nobiliary particles, key on first + last surviving tokens. Members whose Indico spelling won't match the canonical bios.json name can declare an optional name_aliases: [] field to bridge the gap. Show / hide model: hover or focus opens; the popover stays open while the cursor is over either anchor or card; leaving both, scrolling the page, clicking outside, or pressing Esc all dismiss. Graceful degrade: feature-detects HTMLElement.showPopover; on browsers without it the anchor navigates straight to /people.html#<slug>.

Roadmap UX + CSS hygiene

The Shipped list on /roadmap.html now collapses behind a single toggle (default collapsed) so the in-progress and planned items stay above the fold as the shipped history grows. A new CSS class-collision lint (scripts/check-css-class-collisions.py) runs on every PR that touches assets/css/site.css and flags the kind of mistake that briefly broke /people.html mid-cycle — the popover originally used .member-card as its container class, which was already the directory's main card class. The lint walks the CSS, finds classes declared as the sole-compound selector of two or more rule blocks more than 200 lines apart, and reports them as cross-feature collisions. Inline /* css-collision-allow: .my-class */ markers handle legitimate cross-cutting cases.

Polish

The matcher gained a debug logger that lists unmatched speakers via console.debug during render; useful for spotting near-misses (typo, name-order flip, missing alias) without bothering readers. The /people.html deep-link spotlight is now persistent instead of auto-fading after 3.5 s — in detailed view, where every card shows its full bio, the old timer often expired before the visitor noticed the landing; the spotlight now clears on user-initiated action (typing in search, clicking a different card, changing a filter) and the hash strips with it. Linked speaker names on the programme now read as tappable at rest (visible accent-coloured dotted underline plus a soft accent tint to the text) so touch users — who never see a :hover reveal — can tell at a glance which names lead to a profile. The popover's glass background respects @supports (backdrop-filter) with a solid --bg-1 fallback. The roadmap's chevron animation respects prefers-reduced-motion.

Index of changes

Added

  • Live ESSC 2026 programme page at /essc-2026.html (+ FR + DE), sourced from a daily Indico sync (scripts/sync-indico.py + .github/workflows/sync-indico.yml, runs 03:45 UTC). Schema-compatible with EISS's programme generator. Home-page Events block, sitemap, and calendar.ics link to the live page rather than directly to Indico. New data/indico.json artefact. New docs/indico-sync.md documenting the pipeline.
  • Member preview popover on the ESSC programme. Tap or hover a member-linked speaker name and a glass card opens with photo, position, affiliation, country, role + WG chips, bio excerpt, contact icons, and a deep-link CTA. Position is computed in JS (viewport-flipped, edge-clamped). Class family is .essc-member-card* to avoid colliding with the directory's .member-card.
  • Member-aware speaker links on the programme. Names that match a bios.json record become dotted-underlined anchors to /people.html#<slug>. JS port of name_key() with diacritic / honorific / post-nominal / particle stripping. Optional name_aliases: [] field on bios records for hard-to-match cases — documented in docs/bios-setup.md.
  • Collapsible Shipped list on /roadmap.html (+ FR + DE). One toggle injected per <ol class="rm-timeline"> that has shipped entries. Locale-aware labels. JS-off graceful degrade leaves entries visible.
  • CSS class-collision lint (scripts/check-css-class-collisions.py + .github/workflows/css-class-collisions.yml). Catches same-class declarations >200 source lines apart and orphan BEM children. Inline suppression marker for legit cross-cutting patterns.
  • Particles drop in name_key() (Python + JS). 24 nobiliary / patronymic connectors (de, van, von, da, della, etc.) excluded from the key so "Jéssica da Costa Pereira" matches "Jéssica da Costa".
  • console.debug unmatched-speaker log on the programme render. Filtered to keyable names; surfaces near-misses during preview.
  • Three writing-voice rules in CLAUDE.md §6 + §7: no "source of truth" on public copy, no em dashes, no rule-of-three rhythm, no synonym cycling.

Changed

  • /people.html deep-link spotlight is now persistent. The 3.5 s auto-fade is gone; the spotlight clears when the visitor types in search, clicks a different card, focuses a filter, or changes the country select. Visual treatment strengthened: accent-2 outline + 6 px halo + 14 px drop shadow + subtle tinted background. Hash is stripped on dismissal.
  • Linked speaker names on the programme read as tappable at rest. Resting state: dotted underline at full accent-2 opacity + a 70 / 30 colour mix of accent-2 / --ink for the text. Hover / focus brings the text to full accent-2. Replaces the earlier 55 %-transparent dotted underline that was effectively invisible at rest on a touch device.
  • Roadmap retro-truth-up: v1.4.0 + v1.5.0 marked Shipped with their actual content; the "Official logos and social channels" milestone moved to Under watch with a clear external trigger.
  • Sitemap + calendar.ics updated to reflect the NetSec-hosted ESSC live programme as the canonical URL. ESSC entry in data/events.json URL flipped from indico.eiss-europa.com/event/22/ to netsec-cost.eu/essc-2026.html; Indico stays in the calendar DESCRIPTION as a registration link.

Fixed

  • Directory regression on /people.html. The ESSC popover's CSS used .member-card — the directory's own class since launch. The new rules (position: fixed; width: 360px; box-shadow; overflow: hidden) cascaded onto every directory card and stacked all 13 of them at the viewport's top-left. The reported symptom — "only Arthur Laudrain shows, his card is half blue" — was 13 cards stacked, with var(--bg-1) showing through where the width clamp narrowed them. Renamed the entire popover class family to .essc-member-card* across CSS + JS in all three locale files; directory's own rules untouched.
  • Popover light-dismiss and visibility in early popover drafts. The card was rendering with no background because var(--surface) / var(--border) / var(--surface-2) referenced design tokens that don't exist on this site (it uses --bg-1 / --line). Without visible chrome, clicks that the visitor thought were "outside the card" often landed inside the invisible bounds, and the Popover API correctly didn't dismiss. Fix: use the tokens the site actually defines, add glassmorphism (backdrop-filter: blur(18px)), add a scroll-dismiss listener.
  • Pulse-dot vertical alignment beside the "Live programme" heading on /essc-2026.html, pulled rightward from the heading column edge after multiple fine-tunes.
  • Search-overlay landing wrapped highlighted terms in a nested <mark>. Two highlight passes were running on the same hits — Pagefind's PagefindHighlight constructor calls this.highlight() itself, and our bootstrap then explicitly called ph.highlight() a second time. Screen readers announce the inner mark twice ("STSM, mark, STSM, mark"); visual rendering is unaffected. Dropped the explicit call, kept the constructor's. Resolves #118.
  • Skip-link target inconsistency: the home page's skip-link pointed at #top while every other page pointed at #main. Mechanically both work — they hit <main> — but the inconsistency was confusing. Renamed the ho...
Read more

v1.5.0 — Pre-launch polish and accessibility v1.2

22 May 18:33
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

The pre-launch quality pass. v1.5.0 closes the launch-QA loop before the public push — a swarm of polish fixes that surfaced from the user-journey sweep, an accessibility statement bumped to v1.2 with three new audit results, a hybrid release-notes format rolled out across the whole CHANGELOG so future releases read consistently, and the documentation pack caught up to v1.7.0.

Pre-launch polish

Six user journeys × four-viewport sweep (desktop + iPhone-emulated mobile) ran end-to-end in headless Chromium across the eight most-trafficked pages. Three findings shipped in this release:

  • The FR / DE beta-translation ribbon said "machine translation"; the translations are manual. Public-facing falsehood about how the site is built, directly contradicting the standing project constraint baked into the architecture doc and the documentation PDF. Corrected across 35 files: FR ribbon copy → "Traduction manuelle", DE ribbon copy → "Manuell übersetzt", EN top-of-file comments → "manually translated", the accessibility statement, and the longer privacy-page ribbon flavour.
  • The mobile hamburger menu's panel was transparent in dark mode — the floating-header bubble's own backdrop-filter and the panel's nested one didn't re-stack reliably, so the hero text bled through behind every nav item. Pinned the drawer to near-opaque (rgba(246,248,252,.97) / rgba(11,18,32,.97)) scoped to the mobile breakpoint, with a stronger elevation shadow.
  • /people.html#<slug> deep-links could fail to spotlight + expand the target card on cold load. The whole hash-handler was wrapped in requestAnimationFrame; when RAF deferred (headless Chromium, plausibly real browsers under heavy load), nothing fired. Pulled the spotlight + expand class-manipulations out of RAF — they're layout-safe — and kept only scrollIntoView behind it, with a setTimeout(50) belt-and-braces fallback.

Accessibility statement v1.2

Three new audits ran on top of the Phase 2 baseline from the earlier v1.1 statement: a programmatic structural assistive-technology audit of the four most-trafficked pages (landmarks, heading hierarchy, alt-text coverage, accessible names on every interactive element, label association on inputs — all clean); an Open Graph + Twitter Card metadata sweep on home / about / roadmap / press-kit with a render-check of the 2400×1260 shared og-image.png; and a dark-mode readability sweep across all sixteen public English pages, with both a per-element programmatic contrast probe and visual review. No new low-contrast findings surfaced beyond the manual-review item already documented on /accessibility.html. The statement at /accessibility.html (+ FR + DE) is updated to v1.2 with these results and the three corrections above explicitly referenced.

Release-notes hybrid format

Adopted across the whole CHANGELOG, with the structure-rule documented in three places (the CHANGELOG preamble itself, docs/admin-guide.md Cutting a release, and the header comment in scripts/release.sh). The shape: lede + 2-4 themed ### sub-sections + a canonical ### Index of changes block with #### Added / #### Changed / #### Deprecated / #### Removed / #### Fixed / #### Security sub-headings. Self-policing tier: patch releases skip the lede + themes and ship the index only; minor and major releases get the full hybrid. v1.0.0 → v1.4.0 were retrofitted in place and their GitHub Release bodies overwritten to match. A <!-- TEMPLATE --> block at the top of [Unreleased] shows the shape so the next maintainer doesn't reverse-engineer it. A separate rule was added afterwards explaining why CHANGELOG prose must not be hard-wrapped: GitHub Releases use the break-on-newline GFM variant and every soft \n becomes a <br>, so a hard-wrapped paragraph renders narrow on the Releases page even though it looks flowing on the github.com file view.

Launch-QA plan + automation

docs/launch-qa-2026.md lays out a three-phase audit (automation pre-flight → critical user journeys → a11y + cross-browser + perf) with explicit Go / No-Go criteria, a schedule, a tooling cheatsheet, and a findings log that survives past the launch as the audit trail. Two new scripts back it: scripts/check-links.sh (broken-link checker, Python-only, threads with per-host rate-limit-respecting concurrency, validates people.*.html#<slug> deep-links against data/bios.json, skips known auth-gated hosts) and scripts/check-a11y.sh (pa11y scan, aggregates per-page summary into tmp/a11y-report.md). New CI workflow launch-qa-link-check.yml runs the link checker on every HTML-touching PR and weekly on main. The findings log records the journey results, the I-1 / M-1 / J4-1 fixes, and the four "green" final-pass audits (VoiceOver-substitute, OG metadata, dark-mode sweep, structural AT).

Index of changes

Added

  • Release-notes hybrid format, applied across the whole CHANGELOG. v1.0.0 → v1.4.0 retrofitted in place and their GitHub Release bodies overwritten to match. Format rule documented in three places (CHANGELOG preamble, docs/admin-guide.md, scripts/release.sh header). Shape: lede + 2-4 themed ### sub-sections + canonical ### Index of changes. Self-policing tier: patch releases skip the lede + themes. Template block at the top of [Unreleased]. Companion rule against hard-wrapped prose (GitHub Releases renders soft \n as <br>).
  • Launch-QA plan + automation for the late-May 2026 public push. New docs/launch-qa-2026.md lays out the three-phase audit with Go / No-Go criteria, schedule, tooling cheatsheet, findings log. Two new scripts (scripts/check-links.sh, scripts/check-a11y.sh) and a new CI workflow (launch-qa-link-check.yml).
  • Documentation pack refreshed to v1.7.0 — cover stamp bumped, changelog appendix entry recording what the pack now reflects (site v1.4.0 → v1.5.0), and a section-level catch-up scheduled for v1.8.0.

Changed

  • Accessibility statement bumped to v1.2 on /accessibility.html (+ FR + DE). New paragraph in the audit narrative covering three additional final-pass checks (structural AT audit, OG metadata sweep, dark-mode sweep). Three new bullets in the methods list. Version footer updated; Last assessed stays at 22 May 2026 (same day).
  • scripts/check-a11y.sh switched from @axe-core/cli to pa11y. The original CLI requires a system Chrome that matches a system ChromeDriver — brittle. pa11y wraps the same axe-core engine behind a Puppeteer-bundled headless Chromium that doesn't depend on the system pair. Fixed a heredoc-vs-stdin race in the report generator while in there.

Fixed

  • Seven primary-CTA backgrounds failed WCAG AA contrast in dark mode. The dark-mode --accent is #6ea1ff (lighter blue, chosen so accent text reads against the dark page). Buttons using background: var(--accent); color: #fff directly collapsed to 2.56:1 — below the 4.5:1 AA floor. Affected: .event-card.featured .event-date, .event-subscribe, #for-members .members-actions .primary (home); .tour-btn-primary, .tour-trigger-cta (people); .deliverables-roadmap-link-cta (about); .rm-feedback-action.is-primary (roadmap). Fix: pin those CTAs to brand EU-blue #003399 in dark mode (10.86:1) plus a #0a4ed0 hover (11:1). Surfaced by Phase 2 of the launch-QA audit.
  • FR / DE beta-translation ribbon misclaimed "machine translation". The translations are manual. Public-facing falsehood about the build methodology, contradicting the standing project constraint. 35 files corrected: FR copy → "Traduction manuelle", DE copy → "Manuell übersetzt", EN HTML comments → "manually translated", accessibility statement updated.
  • Mobile hamburger menu drawer was transparent in dark mode — hero text bled through behind nav items because the nested backdrop-filter didn't re-stack inside the floating-header bubble. Pinned the drawer to ~97 % opacity (rgba(246,248,252,.97) / rgba(11,18,32,.97)) scoped to @media (max-width: 980px); bumped box-shadow so the drawer reads as elevated.
  • /people.html#<slug> hash-deep-link spotlight + expand could fail to fire on cold page load. Handler was wrapped end-to-end in requestAnimationFrame; if RAF deferred (headless reliably, real browsers under load plausibly), none of the spotlight / expand / scroll actions ran. Pulled the class-manipulations out of RAF (layout-safe); kept scrollIntoView behind RAF with a setTimeout(50) fallback. Applied identically across people.html / people.fr.html / people.de.html.
  • Nine broken internal anchors caught by the new link checker. faq.{en,fr,de}.html and licensing.{en,fr,de}.html still pointed at index.html#committee, #roadmap, #outputs — sections that the Phase 1 IA pass migrated to dedicated pages. Updated to about.X.html#leadership, roadmap.X.html, outputs.X.html.