From 3865c45642b401b7e91a6b601b69a9fa7ec1c9cc Mon Sep 17 00:00:00 2001 From: Felix Klement Date: Sun, 4 Oct 2026 19:12:42 +0200 Subject: [PATCH] mixc --- README.md | 29 +++++ src/components/home/Insights.astro | 49 ++++---- src/components/team/Research.astro | 49 ++------ src/components/verifier/ComingSoon.astro | 8 +- src/content.config.ts | 30 ++++- .../mentions/berlin-6g-conference-2026.md | 9 ++ .../xg-aloe-plugfest-2026-security-testing.md | 9 ++ .../mentions/xg-aloe-user-forum-2026-recap.md | 9 ++ .../xg-aloe-user-forum-2026-tutorial.md | 9 ++ src/data/insights.ts | 21 ++++ src/i18n/ui.ts | 24 ++++ src/pages/_InsightsIndex.astro | 109 +++++++++++++++--- src/pages/_Mention.astro | 109 ++++++++++++++++++ src/pages/de/insights/[slug].astro | 14 ++- src/pages/de/privacy.astro | 3 + src/pages/insights/[slug].astro | 14 ++- src/pages/privacy.astro | 3 + src/styles/base.css | 28 +++++ 18 files changed, 439 insertions(+), 87 deletions(-) create mode 100644 src/content/mentions/berlin-6g-conference-2026.md create mode 100644 src/content/mentions/xg-aloe-plugfest-2026-security-testing.md create mode 100644 src/content/mentions/xg-aloe-user-forum-2026-recap.md create mode 100644 src/content/mentions/xg-aloe-user-forum-2026-tutorial.md create mode 100644 src/data/insights.ts create mode 100644 src/pages/_Mention.astro diff --git a/README.md b/README.md index e11e548..84c4f33 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,35 @@ npm run check # Astro + TypeScript check - **Matomo.** The privacy page links to the existing opt-out on `cipher.uber.space`. The tracking snippet itself is not included; add it in `src/layouts/Base.astro` if analytics should continue. +## Insights filter + +The Insights index can be filtered by type: Essays (posts), LinkedIn (mentions with +`platform: 'LinkedIn'`) and Other (mentions from any other platform). A filter only appears once it +has an entry, and the bar only when there are at least two types. The choice is kept in +`?type=essay|linkedin|other`, so a filtered view can be linked. The types are in `src/data/insights.ts`. + +## Adding a mention + +A mention is a short pointer to a post elsewhere (LinkedIn) that CipherCell took part in. It appears +in the Insights list and on the home page, sorted by date with the essays, and has a short page at +`/insights/`: headline, summary, a link out, and the post embedded on request. The embed is +two-click: nothing loads from LinkedIn until the visitor presses the button (see the privacy page). +The embed URL is derived from a LinkedIn post link; set `embed` by hand only if that fails, and +`embedHeight` (pixels, default 720) if the post is cut off or leaves a gap. Text below the front +matter is shown as our own notes. A slug used by both an essay and a mention fails the build. Create `src/content/mentions/.md` with front matter only: + +```yaml +title: 'Component security testing at the xG-ALOE Plugfest 2026.' # our headline, sentence case +summary: 'One or two sentences in our words on what the post says.' +url: 'https://www.linkedin.com/posts/...' # paste the share link; query and fragment are stripped +source: 'xG-ALOE' # who posted it +platform: 'LinkedIn' # optional, defaults to LinkedIn +date: 2026-09-17 # the post's publication date +lang: en # language of the post itself +``` + +Summarise only what the post says. + ## Adding a post Create `src/content/insights/.md` with the front matter used in `xg-aloe-user-forum.md`. diff --git a/src/components/home/Insights.astro b/src/components/home/Insights.astro index 7796434..05cdab3 100644 --- a/src/components/home/Insights.astro +++ b/src/components/home/Insights.astro @@ -1,13 +1,12 @@ --- -import { getCollection } from 'astro:content'; +import { getFeed } from '../../data/insights'; import { Picture } from 'astro:assets'; import { localePath, t, type Lang } from '../../i18n/ui'; interface Props { lang: Lang } const { lang } = Astro.props; const ui = t(lang); -const posts = (await getCollection('insights')) - .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) - .slice(0, 2); +const feed = (await getFeed()).slice(0, 2); +const m = ui.insights.mention; ---
@@ -17,23 +16,31 @@ const posts = (await getCollection('insights'))
    - {posts.map((post) => ( + {feed.map((item) => (
  • - - -

    {post.data.category}

    -

    {post.data.title}

    -
    + {item.kind === 'post' ? ( + + +

    {item.entry.data.category}

    +

    {item.entry.data.title}

    +
    + ) : ( + + +

    {m.category(item.entry.data.platform)}

    +

    {item.entry.data.title}

    +
    + )}
  • ))}
  • @@ -70,7 +77,7 @@ const posts = (await getCollection('insights')) .insights__card:hover h3 { text-decoration: underline; text-decoration-thickness: var(--hairline); text-underline-offset: 0.12em; } .insights__img, :global(.insights__img) { width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; margin-bottom: var(--space-2); } .insights__card h3 { max-width: 18em; } - .insights__event-when { + .insights__event-when, .insights__panel { display: flex; align-items: flex-end; aspect-ratio: 3 / 2; diff --git a/src/components/team/Research.astro b/src/components/team/Research.astro index f2f8b8a..0c4be51 100644 --- a/src/components/team/Research.astro +++ b/src/components/team/Research.astro @@ -34,28 +34,28 @@ const fieldStyle = (paper: (typeof papers)[number]) =>
diff --git a/src/pages/de/insights/[slug].astro b/src/pages/de/insights/[slug].astro index 40fad1d..8ef57ba 100644 --- a/src/pages/de/insights/[slug].astro +++ b/src/pages/de/insights/[slug].astro @@ -1,11 +1,17 @@ --- import { getCollection } from 'astro:content'; import Post from '../../_Post.astro'; +import Mention from '../../_Mention.astro'; +/* Essays and mentions share /insights/; a slug used by both would be a defect. */ export async function getStaticPaths() { - const posts = await getCollection('insights'); - return posts.map((post) => ({ params: { slug: post.id }, props: { post } })); + const posts = (await getCollection('insights')).map((post) => ({ params: { slug: post.id }, props: { post } })); + const mentions = (await getCollection('mentions')).map((mention) => ({ params: { slug: mention.id }, props: { mention } })); + const taken = new Set(posts.map((p) => p.params.slug)); + const clash = mentions.find((m) => taken.has(m.params.slug)); + if (clash) throw new Error(`Insights slug used by an essay and a mention: ${clash.params.slug}`); + return [...posts, ...mentions]; } -const { post } = Astro.props; +const { post, mention } = Astro.props; --- - +{post ? : } diff --git a/src/pages/de/privacy.astro b/src/pages/de/privacy.astro index 59cedcd..6c5ea24 100644 --- a/src/pages/de/privacy.astro +++ b/src/pages/de/privacy.astro @@ -20,6 +20,9 @@ const ui = t('de');

Benachrichtigung zur CipherVerifier-Alpha

Wenn Sie sich für die Benachrichtigung zur Alpha von CipherVerifier eintragen, speichern wir Ihre E-Mail-Adresse und den Zeitpunkt Ihrer Einwilligung, um Ihnen mitzuteilen, wann die erste Alpha veröffentlicht ist. Wir nutzen sie für nichts anderes und geben sie nicht an Dritte weiter. Rechtsgrundlage ist Ihre Einwilligung nach Art. 6 Abs. 1 lit. a DSGVO. Sie können Ihre Einwilligung jederzeit per E-Mail an office@ciphercell.de widerrufen; wir löschen Ihre Adresse dann. Nach dem Versand der Benachrichtigung löschen wir sie ebenfalls.

+

Eingebettete LinkedIn-Beiträge

+

Einige Seiten unter Einblicke können einen Beitrag von LinkedIn (LinkedIn Ireland Unlimited Company, Wilton Place, Dublin 2, Irland) anzeigen. Beim Aufruf der Seite wird der Beitrag nicht geladen. Erst wenn Sie auf die Schaltfläche zum Laden klicken, verbindet sich Ihr Browser mit den Servern von LinkedIn; diese erhalten Ihre IP-Adresse und die aufgerufene Seite und können Cookies setzen. Sind Sie bei LinkedIn angemeldet, kann LinkedIn den Besuch Ihrem Konto zuordnen. Rechtsgrundlage ist Ihre Einwilligung nach Art. 6 Abs. 1 lit. a DSGVO, die Sie mit diesem Klick erteilen. Zur Verarbeitung durch LinkedIn siehe dessen Datenschutzrichtlinie unter linkedin.com/legal/privacy-policy.

+

Recht auf Auskunft, Berichtigung, Sperrung oder Löschung

Nach dem anwendbaren Datenschutzrecht haben Sie ein Recht auf unentgeltliche Auskunft über Ihre gespeicherten Daten sowie ggf. ein Recht auf Berichtigung, Sperrung oder Löschung dieser Daten. Wenden Sie sich bei weitergehenden Fragen zum Datenschutz und zur Verarbeitung Ihrer personenbezogenen Daten, bei Auskünften oder zur Ausübung Ihres Rechts auf Berichtigung, Sperrung oder Löschung oder für den Fall des Widerrufs einer Einwilligung bitte per E-Mail an office@ciphercell.de. Selbstverständlich können Sie auch die im Impressum angegebenen Kontaktdaten oder das Kontaktformular auf der Website nutzen. CipherCell verarbeitet und nutzt die von Ihnen mitgeteilten Informationen ausschließlich für die Bearbeitung Ihres Anliegens.

diff --git a/src/pages/insights/[slug].astro b/src/pages/insights/[slug].astro index 2e8f8c0..81f0494 100644 --- a/src/pages/insights/[slug].astro +++ b/src/pages/insights/[slug].astro @@ -1,11 +1,17 @@ --- import { getCollection } from 'astro:content'; import Post from '../_Post.astro'; +import Mention from '../_Mention.astro'; +/* Essays and mentions share /insights/; a slug used by both would be a defect. */ export async function getStaticPaths() { - const posts = await getCollection('insights'); - return posts.map((post) => ({ params: { slug: post.id }, props: { post } })); + const posts = (await getCollection('insights')).map((post) => ({ params: { slug: post.id }, props: { post } })); + const mentions = (await getCollection('mentions')).map((mention) => ({ params: { slug: mention.id }, props: { mention } })); + const taken = new Set(posts.map((p) => p.params.slug)); + const clash = mentions.find((m) => taken.has(m.params.slug)); + if (clash) throw new Error(`Insights slug used by an essay and a mention: ${clash.params.slug}`); + return [...posts, ...mentions]; } -const { post } = Astro.props; +const { post, mention } = Astro.props; --- - +{post ? : } diff --git a/src/pages/privacy.astro b/src/pages/privacy.astro index bacd1d6..d85fd22 100644 --- a/src/pages/privacy.astro +++ b/src/pages/privacy.astro @@ -20,6 +20,9 @@ const ui = t('en');

CipherVerifier alpha notification

If you register for the CipherVerifier alpha notification, we store your email address and the time of your consent in order to tell you when the first alpha is released. We use it for nothing else and do not pass it on to third parties. The legal basis is your consent under Art. 6 para. 1 lit. a GDPR. You can withdraw your consent at any time by email to office@ciphercell.de; we then delete your address. We also delete it once the notification has been sent.

+

Embedded LinkedIn posts

+

Some pages in Insights can show a post from LinkedIn (LinkedIn Ireland Unlimited Company, Wilton Place, Dublin 2, Ireland). The post is not loaded when you open the page. Only when you click the button to load it does your browser connect to LinkedIn's servers, which receive your IP address and the page you are on and may set cookies; if you are logged in to LinkedIn, it can link the visit to your account. The legal basis is your consent under Art. 6 para. 1 lit. a GDPR, given by that click. For LinkedIn's processing, see its privacy policy at linkedin.com/legal/privacy-policy.

+

Right to information, correction, blocking, or deletion

Under applicable data protection law, you have the right to obtain information about your stored data free of charge and, if necessary, the right to correct, block, or delete this data. If you have any further questions about data protection and the processing of your personal data, if you require information, or if you wish to exercise your right to correction, blocking, or deletion, or in the event of revocation of consent, please send an email to office@ciphercell.de. You can also use the contact details provided in the legal notice or the contact form on the website. CipherCell processes and uses the information you provide exclusively for the purpose of processing your request.

diff --git a/src/styles/base.css b/src/styles/base.css index 7661e6d..cc61232 100644 --- a/src/styles/base.css +++ b/src/styles/base.css @@ -201,6 +201,34 @@ p { overflow-wrap: break-word; } .night .button { background: var(--paper); color: var(--night); } .night .button:hover { background: var(--paper-deep); } +/* Segmented choice: a row of radio options in the menu bar's language, hairline cells and mono + caps, the checked one filled with ink. Used for the research filters and the Insights filter. + Each option draws its own frame and overlaps its neighbours by one line, so a wrapped row + never leaves a rule running past its last option. */ +.choice-group { border: 0; padding: 0; margin: 0; min-width: 0; display: grid; gap: var(--space-2); } +.choice-group__legend { padding: 0; color: var(--ink-muted); font-size: var(--step-small); } +.choices { display: flex; flex-wrap: wrap; padding: 0 var(--hairline) var(--hairline) 0; } +.choice { position: relative; display: flex; margin: 0 calc(-1 * var(--hairline)) calc(-1 * var(--hairline)) 0; } +.choice input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; } +.choice > span { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + border: var(--hairline) solid var(--line); + font-family: var(--font-mono); + font-size: var(--step-micro); + font-weight: var(--weight-medium); + letter-spacing: var(--track-caps); + text-transform: uppercase; + line-height: 1; + transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); +} +.choice:hover > span { background: color-mix(in srgb, var(--ink) var(--tint-hover), transparent); } +.choice input:checked + span { background: var(--ink); color: var(--paper); } +.choice input:focus-visible + span { outline: var(--focus-width) solid var(--ink); outline-offset: calc(-1 * var(--focus-width) - 2px); } +.choice__n { color: inherit; opacity: 0.6; } + /* Prose: long-form text */ .prose { max-width: var(--measure); } .prose > * + * { margin-top: var(--space-5); }