{ "version": "https://jsonfeed.org/version/1", "title": "Blog von Thomas Puppe, Web Developer.", "home_page_url": "https://blog.thomaspuppe.de", "feed_url": "https://blog.thomaspuppe.de/feed/json", "author": { "name": "Thomas Puppe", "url": "https://www.thomaspuppe.de" }, "items": [ { "id": "https://blog.thomaspuppe.de/frontend-web-development-adventskalender-2024", "content_html": "

👉 https://advent2024.thomaspuppe.de/ 👈

\n

Ich habe dieses Jahr einen kleinen Webdev Adventskalender gebaut. Jeden Tag gibt es ein relativ unbekanntes Feature oder Detail aus der Vanilla-Welt von HTML, CSS und JavaScript. 🍬

\n
\n\n

Und dies sind Kalender, die ich selbst gerne lese oder empfehlen würde:

\n

Web Adventskalender zum Lesen

\n

HTML Hell

\n

https://htmhell.dev/adventcalendar/

\n

Ein Kalender von Manuel Matuzović mit kuratierten Blogposts zu moderner Webentwicklung, mit einem leichten Schwerpunkt auf Accessibility. Es gibt auch ein Archiv der letzten Jahre – eine Goldgrube!

\n

Perf Planet

\n

https://calendar.perfplanet.com/2024/

\n

Ein Kalender nur über Web Performance – großartig! Seit einigen (16!) Jahren schon kuratiert Stoyan Stefanov Posts zum Thema Website Performance – allgemeine ("Not every user owns an iPhone") und sehr nerdige "How does the React Compiler perform on real code".

\n

Self HTML

\n

https://forum.selfhtml.org/advent/2024

\n

Ich wusste nicht, dass es selfhtml noch gibt. Da kann ich ja meine Offlineversion löschen ;-). Wie auch immer, die Pioniere des Web Teaching haben einen schönen Kalender zu Accessibility-Themen.

\n

inclusive design 24

\n

https://inclusivedesign24.org/2024/

\n

Apropos Accessibility ... inclusive design 24 ist eine Art jährlich stattfindende Online-Konferenz. Auf dem zugehörigen id24 Youtube Channel findet man die die 24 einstündigen Videos davon. Zufällig also perfekt passend als Adventskalender.

\n

Web Adventskalender zum Mitmachen

\n

Sehr schön finde ich die Idee von Mitmach-Kalendern, bei denen hinter jedem Türchen eien Coding Challenge steckt. Leider hatte ich dazu noch die die Zeit oder Muße. Aber diese hier sehen gut aus:

\n

Der Klassiker ist der Advent of Code Kalender. Jeden Tag gibt es eine neue Coding-Aufgabe, die man in einer Sprache seiner Wahl erledigen kann.

\n

Advent of CSS und Advent of JS

\n

Amy Dutton hat Challenges in CSS und in JS vorbereitet. Jeden Tag gibt es eine Aufgabe

\n\n

und diese Aufgabe kann dann gelöst werden. Ich habe die Challenges noch nicht angeschaut, aber die Aufmachung der Landing Pages – auch die der letzten Jahre – ist vielversprechend!

\n

https://www.adventofjs.com/ und https://www.adventofcss.com/

\n", "url": "https://blog.thomaspuppe.de/frontend-web-development-adventskalender-2024", "title": "Frontend Web Development Adventskalender 2024", "summary": "Adventskalender mit Frontend Web Development Themen zum Lesen und Selbermachen", "date_modified": "2024-12-12T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/smashing-conference-2022-freiburg", "content_html": "

A summary of the conference in one quote per talk, and a few random notes. To get a better overview, you can visit the community coverage in the official Google Docs: smashed.by/kaese (Day 1) and smashed.by/wurst (Day 2).

\n

Nathan Curtis: Components: Big, Small, or Do 'em All?

\n
\n

It's not the driest thing. It's gonna smell.

\n
\n
\n\n

Michelle Barker: Modern CSS Layout

\n
\n

Designing for fixed breakpoints is hopefully long gone.

\n
\n
\n\n

Andy Bell: Be the Browser’s Mentor, not Its Micromanager

\n
\n

We build for everyone, not just for ourselves or our peer groups.

\n
\n
\n\n

Asim Hussain: You’ve set a Net-zero Climate Target, Now What?!?

\n
\n

There is just too much carbon in the athosphere. Reduce that — that's all.

\n
\n
\n\n

dina Amin: Making by Breaking

\n
\n

It confirmed the idea that I can actually do whatver I want.

\n
\n
\n\n

Harry Roberts: Get Your Head Straight

\n
\n

Who used a spacer gif in the last 5 years? I actually did.

\n
\n
\n\n

Rémi Parmentier: Shining the Light on HTML Email Dark Modes

\n
\n

What matters is what works now.

\n
\n
\n\n

Ben Callahan: You have a Design System. Now What?

\n
\n

... tries to serve everyone perfectly, serving nobody well.

\n
\n
\n\n

Tejas Kumar: Digging into Virtual Scrolling

\n
\n

Browsers have finite memory, compared to your infinite ability to scroll.

\n
\n
\n\n

Sophie Tahran: Designing with Words

\n
\n

Content design is a lot more than just filling text boxes.

\n
\n
\n\n

Manuel Matuzović: Lost in Translation

\n
\n

No matter the stack, most issues are caused by badly written HTML.

\n
\n", "url": "https://blog.thomaspuppe.de/smashing-conference-2022-freiburg", "title": "Quotes from SmashingConf 2022 in Freiburg", "summary": "A summary of the conference in one quote per talk", "date_modified": "2022-09-06T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/fronteers-conference-2019-amsterdam", "content_html": "

A summary of the conference in one quote per talk

\n

Charlie Owen: All constraints are beautiful

\n
\n

Facebooks technology choices are made for a giant unimportant app.

\n
\n
\n\n

Chen Hui Jing: A deep dive into images on the web

\n
\n

This is actually fascinating, at least to me. And I am on the stage. So you have to sit through this.

\n
\n
\n\n

Christophe Porteneuve: Fun & Games with ES Proxies

\n
\n

Make a proxy of its own! The proxy proxy. You have to get paid better if you do this. It's elite.

\n
\n
\n\n

Eva Lettner: Paint the Web - Drawing with CSS

\n
\n

Doing nonsensical stuff on the web is what we, I think, are supposed to do.

\n
\n
\n\n

Jad Joubran: Secrets of Native-like PWAs

\n
\n

Trust me on that one: the web does not need another pop-up.

\n
\n
\n\n

Rachel Andrew: Who Designed This? Where Web Platform features come from, and how to get involved

\n
\n

You don't need any permission or special qualification to get involved.

\n
\n
\n\n

Stephen Cook: 100% CSS Mario Kart

\n
\n

Why are we using CSS to make a game? … Why not?

\n
\n
\n\n

Paul Lewis: Custom Web Shadow Elements, or whatever...

\n
\n

The user is more important than our convenience, in every case.

\n
\n
\n\n

Jeremy Keith und Remy Sharp: How We Built the World Wide Web in Five Days

\n
\n

Defend simplicity and resilience! It's essential to the web.

\n
\n
\n\n

Diana Mounter: Component API design and the developer experience

\n
\n

Duplication is far cheaper than the wrong abstraction.

\n
\n
\n\n

Jack Franklin: Components on the Web

\n
\n

If you have a tree of components, it is much easier to dive in and find the code you want to change in isolation.

\n
\n
\n\n

Ashi Krishnan: Learning from Machines

\n
\n

Molecules are tiny but powerful – like you.

\n
\n
\n\n

Peter Müller: High Performance Web Fonts

\n
\n

There is a ton of font loading techniques. You don't just need a technique. You need a strategy.

\n
\n
\n\n

Raymond Camden: It Was the Best of Times, It Was the Worst of Times

\n
\n

If you add things to your website that browser vendors actively try to prevent, you're doing something wrong.

\n
\n
\n\n

Anjana Vakil: The universe in a single arrow

\n
\n

That's why I am really jazzed to bring you along to my adventure in lambda land.

\n
\n
\n\n

Lea Verou (@LeaVerou): The web design cheat code: Using SVG to bridge CSS’ gaps

\n
\n

We can have a style element in our SVG, which is a document inside our CSS.

\n
\n
\n\n

Mandy Michael (@): The Future of Web Typography with Variable Fonts

\n

Alex Russell (@): The Mobile Web: MIA

\n", "url": "https://blog.thomaspuppe.de/fronteers-conference-2019-amsterdam", "title": "Quotes from Fronteers 2019 in Amsterdam", "summary": "A summary of the conference in one quote per talk", "date_modified": "2019-10-04T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/templating-mit-js-template-literals", "content_html": "

Für meinen kleinen Static-Site Generator easto benutze ich keine Templating-Engine, sondern mache nur Suchen-und-Ersetzen in den Templates. Das sah bisher so aus:

\n

Gut genug: String-Replacement

\n
<div class=\"post__meta\">\n    <span class=\"post__category\">#{{ META_TAGS }}</span>\n    <time datetime=\"{{ META_DATE }}\">{{ META_DATELABEL }}</time>\n</div>\n<h1 class=\"post__title\">{{ META_TITLE }}</h1>\n{{ CONTENT }}\n
\n\n

Im JavaScript habe ich dann über eine Liste von Metadaten iteriert und diese im Template ersetzt:

\n
\ntemplate.replace('{{ CONTENT }}', fileContentHtml)\n\nfor (var key in metadata) {\n    const re = new RegExp('{{ META_' + key.toUpperCase() + ' }}', 'g')\n\n    if ( key === 'tags') {\n        const tagsString = metadata[key].join(', #')\n        template = template.replace(re, tagsString)\n    } else {\n        template = template.replace(re, metadata[key])\n    }\n}
\n\n

... im Prinzip. In echt war dann alles noch etwas umständlicher. Die Regex Nummer ist nötig, weil nur das erste Vorkommen ersetzt wird, wenn man einen String als Parameter verwendet. In Teasern benutze ich etwas andere Ausgaben als im Post oder den Feeds. Usw.

\n

Besser: Template Literals

\n

Bei einem anderen Projekt wurde ich wieder daran erinnert, dass man in modernem JavaScript Template Literals zur Verfügung hat. Wenn Strings in Back Ticks (ˋ) gefasst sind, werden enthaltene Ausdrücke ausgeführt.

\n
var kitty = \"Cheddar\";\nconsole.log(`Meine Katze heißt ${kitty}.`);\n=> Meine Katze heißt Cheddar.\n
\n\n

Meine Template-Dateien sehen nun etwas anders aus. Erstens werden sie komplett mit Back Ticks umschlossen. Und zweitens verwende ich Ausdrücke mit Dollarzeichen und geschweiften Klammern anstelle der selbst ausgedachten Marker fürs Suchen-und-Ersetzen.

\n
`<div class=\"post__meta\">\n    <span class=\"post__category\">#${ meta.tags.join(', #') }</span>\n    <time datetime=\"{{ META_DATE }}\">${ meta.datelabel }</time>\n</div>\n<h1 class=\"post__title\">${ meta.title }</h1>\n${ content }\n`
\n\n

Im JavaScript stecke ich ein Objekt zusammen das alles enthält, was das Template braucht.

\n
const output = eval_template(template, {\n    'blogmeta': CONFIG,\n    'meta': metadata,\n    'content': fileContentHtml\n})\n
\n\n

Damit das mit mehreren Variablen und Objekten funktioniert, habe ich im Netz eine magische Funktion gefunden (deren Quelle ich leider nicht mehr weiß).

\n
const eval_template = (s, params) => {\n    return Function(...Object.keys(params), \"return \" + s)\n    (...Object.values(params))\n}
\n\n\n

Voilá

\n

Diese Änderung hat mehrere Vorteile.

\n\n

Außerdem sieht das professioneller und sexier aus.

\n

Geht da noch mehr?

\n

Auf jeden Fall. Insbesondere Nesting Tempaltes und Tagged Templates sehen vielversprechend aus, um z.B. verschiedene Block-Typen zu rendern. (Im Content steht ein Objekt wie quote = {text:"Niemand hat das Recht zu gehorchen", name:"Hannah Arendt"}), und das Blog rendert eine schöne Blockquote.

\n", "url": "https://blog.thomaspuppe.de/templating-mit-js-template-literals", "title": "Templating mit JS Template Literals", "summary": "Wie natives JS Templating mein Suchen-und-Ersetzen im Blog-Generator ablöste", "date_modified": "2019-07-02T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/easto-static-site-generator", "content_html": "

Dieses Blog wird über einen Static-Site Generator erzeugt. Zuletzt hatte ich die Open Source Software Acrylamid im Einsatz, die leider nicht mehr maintained wird.

\n

Als Acrylamid Probleme hatte, und ich mich nach Alternativen umsah, begann der Aufstieg des JAM-Stacks in Meetups und Blogposts. Der Ansatz wird oft als schlank und schnell beschrieben, aber das sehe ich anders. Der JAM-Stack ist ein Zusammenkleben mehrerer SAAS oder FAAS aus der Cloud. Inhalte werden auf fremdem Speicher abgelegt, eine Serverless Generator-Software wird pro Sekunde fürs Rendern bezahlt, und ein weiterer Service übernimmt das Hosting. Und das alles nur um meine 20 HTML Seiten abzulegen -- sind die verrückt geworden? Und alle machen mit! Der nächste Trend wird garantiert "speed up your JAM Stack with a self-hosted hard disk".

\n

Rant 1 Ende.

\n

Ich möchte mein Blog also lokal auf meinem Rechner schreiben und erzeugen. Static-Site Generatoren gibt es wie Sand am Meer. Eigentlich fand ich Gatsby ganz interessant, weil (optional!) Möglichkeiten zur Integration in Richtung Contentful, Netlify, Algolia und co. Aber: das benutzt JS/React nicht nur zum Bauen der Seite, sondern müllt das auch alles in das Ergebnis. Und schon habe ich für ein "Hello World" vier JS-Requests mit 250 KB Download.

\n

Ich will nicht den JS-ist-böse Meckerkopf spielen. Und für viele Anwendungen ist es eine gute Idee, einmal das Paket zu laden und dann bei jedem Klick Bytes zu sparen die nicht mehr durch die Leitung müssen. Aber für eine statische Seite oder ein Blog mit zwei oder drei Page Views pro Besuch ist das halt keine gute Idee. Und bei Gatsby wird es wohl wieder schwierig, das JS zu rauszufrickeln.

\n

Rant 2 Ende.

\n

Easto: ein Static-Site Generator im Eigenbau

\n

Langes Meckern, kurzer Sinn: ich hatte mich entschieden, einen eigenen Generator zu schreiben. Den entscheidenden Impuls gab mir Denis Defreyne beim ersten Static-Site Meetup in Berlin, bei dem er dafür warb, es einmal selbst zu probieren.

\n

Erste Gehversuche hatte ich vor Monaten Jahren mit Ruby gemacht (https://github.com/thomaspuppe/easto-ruby). Da ich mich 2018 aber entschieden hatte, mich auf JavaScript zu konzentrieren, startete ich einen zweiten Anlauf.

\n

Diesen möchte ich nun beschreiben, und damit einen Fahrplan liefern, wie jeder mit 100 Zeilen Code seinen eigenen Static-Site Generator bauen kann.

\n

Schritt 1: Markdown-Dateien iterieren, transformieren, und speichern

\n

Das Grundprinzip eines Static-Site Generators lässt sich in wenigen Zeilen Code umsetzen. Lies Dateien (z.B. Blogposts) von der Festplatte aus, mach etwas mit ihnen, und speichere das Ergebnis wieder auf die Festplatte.

\n
const fs = require('fs')\nfs.readdirSync('content')\n  .forEach(sourceFilename => {\n    const sourcePath = `content/${sourceFilename}`\n    const content = fs.readFileSync(sourcePath, {encoding: 'utf-8'})\n    // TODO: Verarbeiten\n    const targetFilename = sourceFilename.replace('.md', '.html')\n    const targetPath = `output/` + targetFilename\n    fs.writeFileSync(targetPath, content)\n  })
\n\n

Nun habe alle Dateien aus einem Quellordner ausgelesen, und unter neuem Namen in einen Zielordner gespeichert. Das könnte ich auch kopieren, ohne auszulesen, aber ich möchte ja noch etwas damit anfangen:

\n
const fs = require('fs')\nconst marked = require('marked')\n\nfs.readdirSync('content')\n  .forEach(sourceFilename => {\n    const sourcePath = `content/${sourceFilename}`\n\n    const contentMarkdown = fs.readFileSync(sourcePath, {encoding: 'utf-8'})\n    const contentHtml = marked(contentMarkdown)\n\n    const targetFilename = sourceFilename.replace('.md', '.html')\n    const targetPath = `output/${targetFilename}`\n\n    fs.writeFileSync(targetPath, contentHtml)\n  })
\n\n

Im Befehl const contentHtml = marked(contentMarkdown) steckt die Magie. Texte, die in Markdown verfasst wurden, werden in HTML umgewandelt. Natürlich könnte ich auch gleich in HTML schreiben. Schöner ist aber beides! Mit dem npm Modul marked kann ich sowohl schlankes Markdown schreiben (für Blogposts aus Überschriften, Texten, und Links), als auch im selben Text HTML verwenden, wenn Markdown nicht mehr ausreicht (für Figures, Tabellen, JS und CSS). Die Quelldatei dieses Blog-Posts veranschaulicht die Mischung. Ein Artikel über moderne Browser-APIs enthält ausführbare Code-Beispiele aus CSS und JavaScript.

\n

Aber zurück zum Generator. Obiges Textbeispiel als JavaScript-Datei reicht schon aus, um ein kleines simples Blog zu erzeugen. Der Aufruf der JavaScript-Datei wird alle Markdown-Dateien im Content-Ordner als HTML im Output-Ordner ablegen.

\n
$ node index.js\n🚀 Easto: 170.456ms
\n\n

Wie in der modernen JavaScript-Welt üblich, musste ich vorher noch das "marked" Modul als Dependency mit dem aktuell coolen Paketmanager installieren.

\n

Die Laufzeit des Scripts messe ich übrigens mit

\n
console.time('🚀 Easto')\n...\nconsole.timeEnd('🚀 Easto')\n
\n\n

am Anfang und Ende des Scripts.

\n

2: Templating

\n

Nun ist der Output von Inhalt im HTML Format noch keine anständige Website. Alles um den eigentlichen Inhalt herum (Header, Navigation, Footer) möchte ich ja auch nicht auf jeder einzelnen Seite pflegen. Also: Templating to the rescue!

\n

Aber bevor ich irgendwelche Template-Engines lade, benutze ich simple String-Ersetzung. Das soll ja schließlich ein einfacher Seiten-Editor werden, und kein Dependency-Monster.

\n

Als erstes Template für mein Blog dient eine HTMl-Datei, deren Inhalt nur aus einem Platzhalter besteht.

\n
<!doctype html>\n<html lang=\"de\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>Mein Blog</title>\n    </head>\n    <body>\n        <h1>Willkommen auf Thomas Puppes Blog!</h1>\n        {{ CONTENT }}\n    </body>\n</html>
\n\n

In meinem Generator lese ich nun zusätzlich das Template aus (außerhalb der Schleife, denn das bleibt ja gleich) und benutze es zum Zusammenbauen jeder einzelnen Seite.

\n
// Module laden\n\nconst template = fs.readFileSync('template.html', {encoding: 'utf-8'})\n\nfs.readdirSync('content')\n  .forEach(sourceFilename => {\n    const sourcePath = `content/${sourceFilename}`\n\n    const contentMarkdown = fs.readFileSync(sourcePath, {encoding: 'utf-8'})\n    const contentHtml = marked(contentMarkdown)\n\n    let contentPage = template.replace('{{ CONTENT }}', contentHtml)\n\n    const targetFilename = sourceFilename.replace('.md', '.html')\n    const targetPath = `output/` + targetFilename\n\n    fs.writeFileSync(targetPath, contentPage)\n  })
\n\n

Und schon enthalten die generierten Dateien das HTML des Templates und alle Inhalte.

\n

Was sofort auffällt: der title ist gar nicht individuell. Den möchte ich aber gern setzen. Woraus eine zweite Frage folgt: woher bekomme ich den Titel, oder andere Metadaten?

\n

3: Metadaten (YAML/Frontmatter)

\n

"Frontmatter" ist ein gängiges Format oder Prinzip, um in Dateien mit Inhalten außerdem noch Meta-Informationen zu schreiben.

\n
---\ntitle: Perfekte Link-Unterstreichung\nlanguage: de\npermalink: link-unterstreichung\n---\nHier beginnt der Inhalt. Er ist beliebig lang...
\n\n

In einen Block am Anfang der Datei werden die Meta-Informationen geschrieben. Nach einem Trenner (---) kommt dann der Inhalt. Weil diese Struktur schön einfach und definiert ist, kommen selbst Computer damit klar — und deswegen gibt es auch ein npm Modul dafür: yaml-front-matter.

\n

Im Detail unterscheiden sich die Module (zum Beispiel brauchen manche den Trenner auch am Anfang der Datei), und es gibt kleine Fallstricke (wenn der Titel einen Doppelpunkt enthält, muss er in Anführungsstriche gesetzt werden, damit klar wird was Struktur und was Inhalt ist). Aber im Grunde parst das Modul die Datei, und gibt die Werte strukturiert zurück.

\n

Ich setze weitere Platzhalter in das Template, und ersetze diese beim Zusammenbauen der HTML-Seiten.

\n

Das Template sieht nun so aus:

\n
<!doctype html>\n<html lang=\"{{ META_LANGUAGE }}\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>{{ META_TITLE }}</title>\n    </head>\n    <body>\n        <h1>{{ META_TITLE }}</h1>\n        {{ CONTENT }}\n    </body>\n</html>
\n\n

Im JavaScript füge ich hinter das Transformieren von Markdown in HTML die Schleife zur Ersetzung aller Metadaten ein.

\n
const content = fs.readFileSync(sourcePath, {encoding: 'utf-8'})\n\n// parse die Datei mit Inhalt und Metadaten\nconst frontmatter = yaml.loadFront(content)\n\n// der Teil unter dem Trenner steht als \"__content\" zur Verfügung\nlet contentHtml = marked(frontmatter.__content)\nlet contentPage = template.replace('{{ CONTENT }}', contentHtml)\n\n// die Yaml-Teile über dem Trenner sind nun Felder im \"frontmatter\" Objekt\ncontentPage = contentPage.replace('{{ META_TITLE }}', frontmatter['title'])\ncontentPage = contentPage.replace('{{ META_LANGUAGE }}', frontmatter['language'])
\n\n

Das yaml-Modul habe ich am Anfang des Scripts via const yaml = require('yaml-front-matter') geladen, und vorher mit dem Paketmanager installiert.

\n

Die Werte aus dem Frontmatter kann ich natürlich nicht nur für den Inhalt der Seiten benutzen, sondern auch für den Dateinamen.

\n
const targetFilename = frontmatter['permalink'] + '.html`
\n\n

Wenn ich die Struktur häufiger erweitere, oder es etwas bequemer haben möchte, kann ich natürlich auch über alle Metadaten iterieren und diese im Template generisch ersetzen. Das ist dann schon etwas fortgeschrittener:

\n
for (var key in contentFrontmatter) {\n    const re = new RegExp('{{ META_' + key.toUpperCase() + ' }}', 'g')\n\n    // Frontmatter unterstützt auch Arrays im Format\n    // \"tags: [Webentwicklung, Magic, Internet]\"\n    if ( key === 'tags') {\n        const tagsString = contentFrontmatter[key].join(', #')\n        contentPage = contentPage.replace(re, tagsString)\n    } else {\n        contentPage = contentPage.replace(re, contentFrontmatter[key])\n    }\n}
\n\n

Bei der Entwicklung von diesen Algorithmus konnte ich wunderbar in kleinen Schritten vorgehen, weil ich binnen Milliskunden das Ergebnis meiner Bemühung im Browser betrachten konnte. Hoch lebe die handgestrickte Webentwicklung!

\n

4: Inhaltsverzeichnis

\n

Mit dem bisherigen Code wurden also allerlei Seiten oder Blog-Artikel generiert. Eine dieser Seiten war die Startseite, die ich anfangs händisch angelegt und die Links zu allen Artkeln eingepflegt habe. Ich möchte aber, dass die Startseite meines Blog austomatisch eine Liste aller Blogposts enthält und darauf verlinkt. Auch das habe ich easto beigebracht.

\n

Beim Iterieren über alle Inhalte baue ich nicht nur die aktuelle Seite zusammen, sondern jeweils auch einen Link zur Seite. Und die gesammelten Links werden am Ende als Index-Seite gespeichert.

\n
\nlet teaserList = [];\n\n.forEach(sourceFilename => {\n    ...\n    teaserList.push(`
  • ${frontmatter['title']}
  • `)\n}\n\nconst indexTemplate = fs.readFileSync('template_index.html', {encoding: 'utf-8'})\n\nlet indexContent = indexTemplate.replace(\n '{{ CONTENT_BODY }}',\n teaserList.join()\n)\n\nfs.writeFileSync('output/index.html', indexContent)
    \n\n

    Das lässt sich nun mit der bekannten Technik der Templates erweitern, damit aus der simplen Linkliste schöne Teaser-Blöcke werden.

    \n
    \nlet teaserList = [];\nconst teaserTemplate = fs.readFileSync(`templates/teaser.html`, {encoding: 'utf-8'})\n\n.forEach(sourceFilename => {\n    ...\n    // in der Schleife, wo auch Artikel-Metadaten ersetzt werden:\n    teaserContent = teaserTemplate.replace(re, fileContentFrontmatter[key])\n    teaserList.push(teaserContent)\n}
    \n\n

    Beim Ansehen der index-Seite fällt auf, dass die Quelldatien in "zufälliger" Reihenfolge von der Platte gelesen werden. Ich löse das, indem ich als Präfix für meine Dateinamen das Datum jedes Blogposts benutze (/content/2019-03-11_easto-static-site-generator.md), und zwischen Auslesen und Verarbeiten eine Sortierung setze:

    \n
    fs\n  .readdirSync('content')\n  .sort((a, b) => {\n    return b.localeCompare(a)\n  })\n  .forEach(sourceFilename => { ...
    \n\n\n

    5: Feeds generieren

    \n

    Wenn ich schon das chronologische Inhaltsverzeichnis erzeuge, kann ich im gleichen Zuge auch RSS-Feeds bauen, die im Grunde nichts anderes sind. Auch dafür gibt es ein Node-Package ("feed"), mit dem dies recht einfach gelingt.

    \n
    \n    // Vorbereitung: Metadaten zum Feed\n\n    const Feedbuilder = require('feed')\n    feed_config = {\n        \"title\": \"Blog von Thomas Puppe, Web Developer.\",\n        \"description\": \"This is my personal feed!\"\n    }\n    let feed = new Feedbuilder.Feed(feed_config)\n\n    // ... in der Schleife der Blogposts: \"Teaser\" sammeln\n\n    const feedItem = {\n        title: contentFrontmatter['title'],\n        description: contentFrontmatter['description'],\n        date: contentFrontmatter['date'],\n        link: `https://blog.thomaspuppe.de/${targetFilename}`\n    }\n    feed.addItem(feedItem)\n\n    // ... nach der Schleife: Speichern der Feeds in den Output-Ordner\n\n    fs.mkdirSync(`output/feed`)\n    fs.writeFileSync(`output/feed/rss`, feed.rss2())\n    fs.writeFileSync(`output/feed/atom`, feed.atom1())\n    fs.writeFileSync(`output/feed/json`, feed.json1())\n
    \n\n

    Die Domain des Blogs möchte ich am Ende lieber in einer Konfigurationsdatei haben als im Quellcode, aber das Beispiel zeigt, worauf ich hinaus will: mit wenigen Zeilen Code lassen sich Feeds in den gängigen Formaten RSS, Atom und JSON bauen.

    \n

    Eine XML Sitemap sollte ähnlich funktionieren sein, das habe ich aber noch nicht umgesetzt.

    \n

    6: Statische Dateien kopieren

    \n

    Zuletzt noch alles was den geschriebenen Inhalt anreichert: Assets (CSS, JS), Bilder und andere statische Dateien kopiere ich direkt aus einer Quelle in das Output-Verzeichis. Das Node-Modul ncpermöglicht das rekursiv.

    \n
    const ncp = require('ncp').ncp\nncp('static', 'output', err => {\n  if (err) return console.error(err)\n})\n
    \n\n\n

    Deployment

    \n

    Am Ende habe ich einen Output-Ordner, der alle Inhalte als HTML-Dateien enthält, und außerdem ein Inhaltsverzeichnis, Feeds, mein CSS, und Bilder.

    \n

    Das lässt sich am einfachsten per scp auf meinen Server kopieren. Optionen wären auch auch FTP, Github Pages, Netlify, whatever. Das ist einer der Vorteile statischer Websites :-)

    \n

    Struktur und Übersicht

    \n

    Okay, das war ein langer Blogpost. Rekapitulieren wir das Ganze:

    \n\n

    Die Markdown-Dateien bestehen aus zwei Teilen

    \n
      \n
    1. Metadaten wie Titel, Permalink, Sprache, Beschreibung oder Datum. Diese werden im Frontmatter-Format hinterlegt, und können unter ihrem Namen in den Templates genutzt werden.
    2. \n
    3. Inhalte, die in Markdown geschrieben werden. Ich kann auch HTMl-Code verwenden, oder beides vermischen.
    4. \n
    \n

    Der Algorithmus besteht aus drei Teilen

    \n
      \n
    1. Einsammeln von Templates, Konfiguration und Inhalten
    2. \n
    3. Iterieren über die Inhalte. Dabei werden Artikel gerendert und gespeichert. Teaser und Feed-Items werden zusammengetragen.
    4. \n
    5. Abspeichern der Homepage und Feeds, Kopieren von statischen Dateien (Bilder und Assets).
    6. \n
    \n

    Fazit und Ausblick

    \n

    Mit diesem Setup lassen sich Blogs in weniger als einer Sekunde generieren. Außerdem finde ich als Programmierer so ein Projekt sehr schön, weil ich mir neue Features direkt nach Bedarf selbt hinzufügen kann, und keinen Code habe den ich nicht benötige.

    \n

    Die nächsten Featues werden sein

    \n\n

    ... und die Abstrahierung von easto in ein Modul, das auch andere benutzen können. Easto ist Open Source und schon jetzt recht gut per Konfiguration steuerbar, aber bevor sie jeder benutzen kann und möchte, muss noch ein wenig Aufräumarbeit und Dokumentation geschehen.

    \n

    Ich freue mich über Feedback, am besten via Twitter an @thomaspuppe oder E-Mail.

    \n", "url": "https://blog.thomaspuppe.de/easto-static-site-generator", "title": "Easto: ein kleiner schneller Static-Site Generator", "summary": "Um mein kleines Blog zu betreiben, brauche ich keine fremde Software. Einfache Prinzipien und ein paar Node-Module genügen, um mir selbst einen Static-Site Generator zu schreiben. Eine Schritt-für-Schritt Anleitung.", "date_modified": "2019-03-11T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/dark-mode-css", "content_html": "

    Mit Version 10.14 Mojave von macOS hat Apple den "Dark Mode" eingeführt. Über seine Systemeinstellungen kann man das Farbschema des Betriebssystems in einen Modus versetzen, der alle Interfaces abdunkelt.

    \n

    Das funktioniert für native Software und die GUI des Browsers, aber schlägt natürlich nicht auf Websites durch. (Außer man benutzt des Reader-Modus im Safari.) Für seine Website kann man aber einen Dark Mode via CSS bauen.

    \n
    \n \"Screenshots\n
    www.thomaspuppe.de und die MacOS Systemeinstellung im Light Mode (links) und Dark Mode (rechts)
    \n
    \n\n

    Der aktivierte Dark Mode wird via Media Query an CSS und JS des Browsers exponiert, sodass man als Programmierer einer Website auf diesen bevorzugten Modus seiner Besucher eingehen kann. Menschen, die auf ihrem Computer helle Schrift auf dunklem Hintergrund bevorzugen, bekommen dies auch auf meiner Website. Die Umsetzung ist, wie bei anderen Media Queries auch, sehr einfach:

    \n
    body {\n    background: #fff;\n    color: #333;\n}\n@media (prefers-color-scheme: dark) {\n    body {\n        background: #333;\n        color: #fff;\n    }\n}
    \n\n

    Die Query nimmt drei Werte an: dark, light und no-preference. Eine normalerweise dunkle Website kann also auch ins Helle umschwenken, wenn gewünscht. Wobei in der Praxis ist im Safari ja immer hell, das GUI für Systemeinstellung im MacOS gibt kein "no preference" her.

    \n

    Der Teufel steckt im Detail

    \n

    Die obigen Zeilen sind das, was ich immer als "Toy Example" bezeichne. Zunächst sieht es sehr einfach aus. Aber natürlich ist es mit dem Umdrehen von Hintergrund- und Textfarbe in den meisten Fällen nicht getan.

    \n

    Je fancier das Design einer Website ist, desto mehr muss man auch für den Dark Mode hinterherräumen. Selbst auf meiner Homepage und in meinem Blog hat es nicht ausgereicht, zwei Farben zu invertieren.

    \n

    Meine spezielle Unterstreichung von Links erfordert einen Text-Shadow, der im Dark Mode schlecht aussieht:

    \n
    \n \"Screenshot:\n
    Die Umkehrung von Hintergrund- und Schriftfarbe ist nicht genug, wenn man farbige Schlagschatten am Text definiert hat.
    \n
    \n\n

    Und schon ist man dabei, seinen kleinen Hacks hinterherzuräumen.

    \n
    @media (prefers-color-scheme: dark) {\n    a {\n        color: currentColor;\n        background-image: none;\n        text-shadow: none;\n        text-decoration: underline;\n    }\n}
    \n\n

    Complexity is killing us.

    \n

    Auch sonst ist man gut beraten, seine Designs im Dark Mode ausführlich zu testen. Denn der Teufel steckt im Detail. Ein Artikel bei CSS-Tricks empfiehlt, helle Bilder per CSS abzudunkeln, weil sie sonst auf dunklem Hintergrund zu grell sind. Apple hat Beispiele für Schatten und Glow-Effekte an Icons.

    \n

    Alle Farben im Dark Mode muss man erneut auf ihren Kontrast überprüfen.

    \n

    Bei größeren Projekten helfen Sass-Variablen oder CSS Custom Properties bei der Organisation der Farben.

    \n

    Noch nicht in Browsern verfügbar

    \n

    In der Praxis ist die prefers-color-scheme Media Query noch nicht sinnvoll. Dafür ist die Unterstützung viel zu schlecht. Erstens stellt sich die Frage, wer den Dark Mode in seinem MacOS überhaupt aktiviert. Und dann muss man noch den Safari Browser nutzen ... in dem der Dark Mode bisher auch nur in der Developer Preview verfügbar ist.

    \n

    Sobald aber Chrome und Firefox das Feature unterstützen, könnte es sich lohnen in diesen Modus einzusteigen. Bei Firefox sucht das Feature gerade nach einem Assignee ;-)

    \n

    Ähnliche Queries

    \n

    Im W3C Draft für Media Queries Level 5 gibt es weitere Ideen, die Präferenzen von Usern zu berücksichtigen:

    \n\n

    Weiterführende Artikel:

    \n\n", "url": "https://blog.thomaspuppe.de/dark-mode-css", "title": "Dark Mode für Websites", "summary": "Bisher nur experimentell, aber ein schönes Bastelfeature: den MacOS Dark Mode via CSS unterstützen", "date_modified": "2019-01-26T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/notizen-januar-2019", "content_html": "

    Tim Kadlec: The Ethics of Web Performance

    \n

    Tim Kadlec räsoniert in seinem Artikel "The Ethics of Web Performance" darüber, dass imperformante Websites nicht nur ökonomisch sondern auch moralisch schlecht sind.

    \n
    \n

    Performance as exclusion. Performance as waste.

    \n
    \n

    Zum Thema Waste gibts dann eine schöne Milchmädchenrechnung: in einer Studie wurde errechnet, dass für jedes GB Daten im Internet 5 kWh an Energie benötigt werden (für die Übertragung, nehme ich an). Folglich sind das laut Kadlec 17.6 Millionen kWh pro Tag. Jetzt kann man als Hausaufgabe überschlagen wie viele Kernkraftwerke durch das Abschalten des Google Tag Managers eingespart würden ;-)

    \n

    Ein anderes Beispiel: beim Youtube Feather Projekt hat Chris Zacharias entdeckt, dass Nutzer in nicht-so-reichen Ländern die Videoplattform überhaupt erst nutzen können, wenn man sie radikal beschleunigt. Also ganze Nutzergruppen konnten Youtube vorher gar nicht benutzen, obwohl sie es wollten. Das beweist erneut "Performance is Accessibility".

    \n

    ... und die Ökonomie von Web Performance?

    \n

    Ich glaube ja, dass das gleiche für ... sagen wir ... News-Websites zutreffen würde. Die erste Seite die signifikant schneller ist als der Rest wird sicher viel mehr besucht. Erreichen könnte man das z.B. durch weniger nervige Werbung. Die Einkommensverluste pro Besucher/Klick würde man dann durch höhere Reichweite ausgleichen? Kann man nicht wissen, solange man es nicht probiert.

    \n

    Zumal: die große Verbreitung von Adblockern kommt ja nicht daher, dass die Menschen Werbung an sich blöd finden. Vielmehr stören sie sich an der schlechten Performance und am unkontrollierten Tracking. Wenn man durch ehrliches Ausliefern eigener guter Inhalte mit ganz normaler Werbung die Leute überzeugen kann, ihre Adblocker auszuschalten, kommen gleich mal 30% zählbare Nutzer hinzu. Damit überholt man locker seine nächsten beiden Mitbewerber.

    \n

    Aus den gleichen Motiven, aus denen Leser Adblocker benutzen, benutzen sie den Reader-Mode im Browser, oder auch Pocket. Dass (und wie) wir mit unseren Websites gegen die schöne text-only Darstellung der Browser konkurrieren, legt Eric Bailey dar in Reader Mode: The Button to Beat:

    \n
    \n

    And what if we’re on a slow, intermittent, and/or metered connection? Top-of-the-line MacBooks still have to use hotel wifi, just like everyone else.

    \n
    \n

    Zwei Tage später habe ich im Smashing Magazine den Artikel How Improving Website Performance Can Help Save The Planet entdeckt. Auch hier ist "save the Planet" aus meiner Sicht ein wenig übertrieben. Aber die Intention ist fein. Mach Sachen schlank und performant, dann hilfst du dem Planeten. Und wenn ein Berliner Webdev-Hipster sich dadurch überzeugen lässt, Preact statt React zu benutzen — auch gut.

    \n

    Weiterführende Links zum Thema:

    \n\n

    Usability/Findability Vergleich von Flat Design vs Traditional Design

    \n

    Ein Paper aus dem Jahr 2015 wurde bei Hacker News hochgespült. Forscher aus Russland haben "Flat Design vs Traditional Design" verglichen, in Bezug auf Findbarkeit von User Interface Elementen. Flat Design schneidet schlechter ab.

    \n
    \n

    The results show that a search in flat text mode (compared with the traditional mode) is associated with higher cognitive load. A search for flat icons takes twice as long as for realistic icons and is also characterized by higher cognitive load. Identifying clickable objects on flat web pages requires more time and is characterised by a significantly greater number of errors.

    \n
    \n

    The State of Web Browsers

    \n

    Eine schöne Analyse zum aktuellen Stand des Browser Wars. Warum hat Chrome gewonnen, was erwartet die anderen hersteller und den Nutzer in naher Zukunft? Ferdy Christant: The State of Web Browsers 2018

    \n
    \n

    It’s a better kind of dominance. Like a friendlier dictator. But still a dictator.

    \n
    \n

    Und weil der Artikel so dystopisch ist, gibt es noch ein follow-up zur Aufmunterung: Ferdy Christant: The State of Web Browsers 2019

    \n

    Mein Lieblingszitat:

    \n
    \n

    Be honest, it’s you that wants Web Components, not your users. I won’t judge, I love web tech too.

    \n
    \n

    LinkedIn spioniert die Browser-Extensions seiner Nutzer aus

    \n

    Apropos Dystopie und Microsoft und Dominanz: am Beispiel von Linkedin wird hier gezeigt, wie Websites herausfinden können welche Browser-Extensions ein Besucher installiert hat: Nefarious LinkedIn

    \n

    tldr: man prüft, welche lokalen Extension-Dateien der Browser von chrome://extensions lädt, oder welche Manipulationen auf berühmten Websites vorgenommen werden. Und gleicht das dann mit seiner Datenbank bekannter Extensions ab.

    \n

    Das erinnert mich an die gute alte Erkennung von visited Links via getComputedStyles(), um herauszufinden welche anderen Seiten deine Besucher noch besucht hat.

    \n

    Hiring Engineers

    \n

    Wer Software-Entwickler einstellt, ist heutzutage kein "Käufer" der sich den Entwickler aussucht, sondern er ist "Verkäufer" der vor allem die Firma gut darstellen muss. Die Firmen müssen sich bei den Entwicklern bewerben, nicht andersherum. Das ist alles etwas arrogant geschrieben, aber inhaltlich hat der Autor (gegenwärtig) recht. Welche Aspakte es da zu berücksichtigen gilt, und ein paar schöne Analogien, findet man bei Trouble hiring senior engineers? It's probably you.

    \n

    Exclusive Design

    \n

    https://exclusive-design.vasilis.nl/

    \n
    \n

    Roughly said, in the past 25 years we have been designing websites mostly for people who design websites.\n...\nI have designed and created tailor made websites, exclusively for individual people with disabilities.

    \n
    \n

    Sonstiges

    \n

    Die New York Times hat wegen der DSGVO in Europa auf targeted Ads verzichtet, und ist davon nciht pleite gegangen: After GDPR, The New York Times cut off ad exchanges in Europe — and kept growing ad revenue

    \n
    \n

    The desirability of a brand may be stronger than the targeting capabilities.

    \n
    \n

    Im Guardian erklärt Paul Dolan in einem Exzerpt seines neuen Buches, dass das Erfüllen von gesellschaftlichen Erwartungen nicht automatisch glücklich macht (social narrative vs personal experience): The money, job, marriage myth: are you happy yet?.

    \n
    \n

    To be happier we need to move from a culture of “more please” to one of “just enough”.

    \n
    \n", "url": "https://blog.thomaspuppe.de/notizen-januar-2019", "title": "Notizen Januar 2019", "summary": "Notizen und Lesetipps für Januar 2019: Performance, Privacy und das vorläufige Ende des Browser War", "date_modified": "2019-01-21T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/high-score-optimierung-w3c-validator", "content_html": "

    Valides HTML – eigentlich selbstverständlich für jeden seriösen Web-Entwickler, aber dennoch habe ich bisher kaum eine Seite gesehen die fehlerfrei validiert wird. Manchmal geht einem einfach etwas durch die Lappen. Manchmal gibt es Bedingungen die man nicht erfüllen will: target="_blank", anyone?

    \n

    Den Auftakt meiner Blogserie "High Score" bildet also der HTML Validator vom W3C. Ist mein eigenes Blog in validem HTML geschrieben? Wie viele Fehler gibt es in meinen simplen Templates? Und wie gut kann ich die Anforderungen des Validators erfüllen?

    \n

    Der Test

    \n

    Der Validator des W3C ist online verfügbar unter validator.w3.org. Er prüft das Markup einer Website auf Validität. Es gibt vom W3C auch weitere Tools, um CSS zu validieren oder defekte Links zu finden. Ich schaue mir zunächst nur das HTML der Startseite an.

    \n

    Das Ergebnis erschrickt mich zunächst: 32 Fehler! Allerdings ist das 32 mal derselbe Fehler.

    \n

    1) fehlerhaftes Datumsformat

    \n

    In jedem Blogpost-Teaser auf der Indexseite ist das Datum in einem ungültigen Format angegeben. Neben dem lesbaren Datum für Menschen wollte ich es auch maschinenlesbar machen...

    \n
    <time datetime=\"Fri Jul 31 2018 02:00:00 GMT+0200 (CEST)\">01. Juli 2018 – 31. Juli 2018</time>
    \n\n

    ... und eben jenes Fri Jul 20 2018 02:00:00 GMT+0200 (CEST) ist nicht valide. Der W3C-Validator verlinkt auch gleich auf die Spec des time-Elements, die verletzt wird. Dort sind viele Beispiele gültiger Datumsformate aufgelistet.

    \n

    Ich ändere also das Datumsformat und kürze gleich noch die unnütze Uhrzeit raus.

    \n
    <time datetime=\"2018-07-31\">01. Juli 2018 – 31. Juli 2018</time>
    \n\n

    Und schwupps: No errors or warnings to show!

    \n

    Das war zu einfach. Deshalb schaue ich mir noch die einzelnen Blogposts an.

    \n

    rekursives Testen aller Artikel

    \n

    Das habe ich mir einfacher vorgestellt. Leider hat der Validator keine Funktion für rekursives Testen (oder eine Liste von Links), und ein entsprechendes einfaches Tool habe ich auf die Schnelle auch nicht gefunden. Also prüfe ich einige Artikel von Hand.

    \n

    strike-Tag

    \n

    Im Artikel Mein Smarterphone benutze ich das <strike> Tag, um ein paar Worte durchzustreichen. Der Validator moniert, das sei obsolet und man solle lieber CSS benutzen. Die Erklärung ist erst einmal nicht zufriedenstellend, weil das eben kein optischer Effekt ist, sondern ich tatsächlich eine inhaltliche Aussage treffe: dieser Text wurde durchgestrichen.

    \n

    Wieder verlinkt der Validator die Specs, diesmal auf Whatwg zum Thema "Presentational Links and Attributes". Hier wird genauer erklärt: s und strike sind zu ersetzen durch del. Das korrigiert den Fehler.

    \n

    Durch die Erklärung, ich wolle ja mit der Durchstreichung etwas ausdrücken, ist mir eines aufgefallen: wenn man den entfernten Text semantisch kennzeichnet, was ist dann mit dem hinzugefügten Text? Der besagte Artikel zu Präsentationselementen hat die Antwort: das ins Tag!

    \n

    Der default-Stil des ins Tags ist übrigens eine einfache Unterstreichung — genau wie es auch Links in meinem Blog sind. Da ich meine Links nicht einfärbe und in der Textfarbe unterstreiche, sehen nun die eingefügten Texte aus wie Links. (Nebenbei: das wäre schon ein Grund, nicht an Link-Stilen herumzufummeln: weil man dann genau solche Probleme erzeugt.) Da ich meine Link-Stile aber nicht ändern möchte, style ich also die ins Tags um. Der erste Ansatz war, text-decoration: none zu setzen und dann mit einem border-bottom in der currentColor zu arbeiten. Was aber viel besser ist: es gibt das CSS-Property text-decoration-style, mit dem man eben diese Unterstreichung ändern kann, zum Beispiel auf dotted, dashed oder wavy.

    \n

    Bonuswissen: man kann ebenso die text-decoration-color und die text-decoration-line ändern. Damit kann man einen Text z.B. wellig rot über- unter- und durchstreichen — sogar gleichzeitig.

    \n
    text-decoration-style: wavy;\ntext-decoration-line: overline underline line-through;\ntext-decoration-color: red;
    \n\n

    Das aber nur am Rande. Im Blog-CSS habe ich mich für eine wellige Unterstreichung in etwas hellerem Ton entschieden:

    \n

    Hier die Demo mit Text im del-Tag und Text im ins-Tag.

    \n

    spitze Klammern in pre

    \n

    Innerhalb von pre Tags, die ich für Code-Beispiele im Blog benutze, sind keine spitzen Klammern erlaubt. Sie werden nämlich interpretiert, was mir an manchen Stellen im Browser nicht auffiel (das "Element" wurde versteckt, und der Teil des Codes nicht gezeigt). Dem Validator fiel es auf.

    \n

    Ob es besser wäre, anstelle von pre lieber code zu benutzen, prüfe ich ein andermal.

    \n

    Ergebnis der Tests mit dem W3C-validator

    \n

    Ich sage mal: fehlerfrei. Ich habe jetzt nicht jeden einzelnen Artikel getestet, aber auf den getesteten Seiten konnte ich alle Fehler korrigieren. Erwartet hatte ich eigentlich, dass ich mich über manche "zu strenge" Hinweise hinwegsetzen werde.

    \n

    Fazit zum W3C-Validator

    \n

    pro:

    \n\n

    contra:

    \n\n

    Meta-Fazit

    \n

    Der Auftakt hat mir gut gefallen. Ich habe zwar keine groben Schnitzer gefunden, die man unbedingt korrigieren müsste. Aber die Blog-Serie heißt ja auch "Mikrooptimierung", und ich möchte jeden kleinen Scheiß fixen.

    \n

    Das Schöne: man lernt viele Details hinzu. Wann im Arbeitsalltag stolpert man schon über das <ins> Tag, und wie man die text-decoration-line stylen kann?

    \n

    Ich bin heiß auf den nächsten Check!

    \n", "url": "https://blog.thomaspuppe.de/high-score-optimierung-w3c-validator", "title": "High Score – Mikrooptimierung mit ... W3C-Validator", "summary": "In dieser neuen Serie versuche ich, mit meinem Blog verschiedene Website-Testing-Tools zufriedenzustellen. Den Anfang macht der W3C-Validator.", "date_modified": "2018-12-19T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/notizen-juli-2018", "content_html": "

    Accessibility for Collapsible sections

    \n

    Um eine typische Liste von togglebaren Überschriften per Tastatus bedienbar zu machen, konnte ich endlich mal ein Inclusive Components Beispiel von Heydon Pickering benutzen: collapsible sections. Kurz zusammengefasst: man macht die Überschriften zu <button> Elementen, damit die ganze Tabindex/Focus/"click" Sache funktioniert. Und entfernt die Button-Stile via all: inherit; im CSS. Der Artikel von Heydon geht natürlich noch viel mehr in die Tiefe und behandelt JS und Screen Reader.

    \n

    TODONEXT

    \n

    Ein kleiner alter Trick, um am Morgen einen guten Kickstart beim Arbeiten hinzubekommen: zum Feierabend versuche ich, die aktuelle Aufgabe nicht abzuschließen. Sondern als letzte Tat schreibe ich einen failenden Test (wenn man denn welche hat) oder eine Funktion die den Build kaputtmacht oder so. Und daneben einen Kommentar in der Form // TODONEXT: consider negative input. So kommt man schnell wieder in die Arbeit, und muss sich morgens nicht erst orientieren.

    \n

    Leankoala

    \n

    Etwas Neues aus der Reihe "Tools für Qualitätssicherung": Leankoala ist ein sympathisches Hamburger Startup, bei dem man verschiedenste Tests gegen seine Website laufen lässt. Neben Klassikern wie dem Broken-Link-Checker und Ping gibt es auch Bufgets für Dateigrößen und Pagespeed-Score. Außerdem natürlich eigene Tests (prüfen ob ein CSS-Selektor/XPath auf der Seite enthalten ist, z.B.) und einen Smoke-Test, bei dem Cache-Header, gzip usw geprüft werden. Das ist alles ziemlich smart, und Wizards helfen bei der Einrichtung indem sie Dinge auf der Seite finden (hat man Analytics eingebunden?) und daraus Checks vorschlagen. Kann man 14 Tage kostenlos testen.

    \n

    Web Performance bei Wikipedia

    \n

    Wikimedia hat ein eigenes Performance Team (natürlich) und sie arbeiten öffentlich (natürlich). Eine super interessante Quelle für alle die sich für Web Performance interessieren. Auf der Performance Team Page sind viele Ressourcen verlinkt, z.B. Dashboards, das Blog des Teams, und ihr Ticket-Board.

    \n

    Lesetipps

    \n

    Das Buch Calm Technology habe ich in einem Tweet von Christoph Rauscher entdeckt und sofort bestellt. Leider noch nicht gelesen. Wenns soweit ist, komme ich hier drauf zurück.

    \n

    Wieder einmal was Geiles von Heydon Pickering: Generative Art.

    \n

    Der Freitag erklärt 5 Mythen über die Flüchtligskrise

    \n
    \n

    Menschen ohne Rechte, so warnte Hannah Arendt, sind „erste Anzeichen für eine mögliche Rückentwicklung der Zivilisation“.

    \n
    \n", "url": "https://blog.thomaspuppe.de/notizen-juli-2018", "title": "Notizen Juli 2018", "summary": "this-month-I-learned und Lesetipps für Juli 2018: Accessibility für Toggles, Leankoala Testing, und Calm Technology.", "date_modified": "2018-07-20T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/notizen-juni-2018", "content_html": "

    WM Ticker

    \n

    Ich habe mir zwei Tools geschrieben, um mir fix aktuelle WM-Spielstände anzuschauen. Ein winziges Tool für die Kommandozeile, und eine kleine progressive Web App. Beide greifen eine API ab und zeigen die heutigen Spiele mit Live-Spielstand. Alles kein Hexenwerk, aber eine kleine feine Freude. Bei Twitter gibts nen Screenshot.

    \n

    A11y Club Conference

    \n

    Im November wird die Accessibility Club Conference in Berlin stattfinden. Da freu ich mich drauf, Tickets kosten auch nur 80 Euro. Letztes Jahr gab es den Club schon einmal, und das war eine tolle Veranstaltung. Meine Lieblingszitate vom letzten Jahr:

    \n
    \n

    Your brand is not your color palette.
    \nYour brand is what people think of you.

    \nHeydon Pickering\n
    \n\n

    und

    \n
    \n

    Privacy: you dont send a \"private message\" to your friend. You send it to facebook, and they show it to your friend.

    \nLaura Kalbag\n
    \n\n\n

    Tooltipps

    \n

    GitStats generiert aus einem Git-Repo Statistiken: Commits pro Wochentag oder im Laufe der Zeit, Top-Committer usw. Was man GitHub im Interface eingebaut hat, kann man sich hier für beliebige Git-Repositories generieren lassen. Das Ergebnis sind lokale HTML-Dateien mit einigen Graphen. GitStats muss nicht installiert werden, erwartet aber gnuplot auf dem System.

    \n

    https://3rdparty.io/ zeigt für einzelne 3rd-Party-Scripts, wie sehr sie einer Website schaden. Also man analysiert nicht seine Seite mit allen Scripts (was die klassischen Webperf-Tools tun), sondern schaut sich ein spezifisches Script an und bekommt eine Auswertung, wie gut sich das benimmt. Ist es komprimiert? Blockiert es den Browser? Schreibt es globale JS Variablen oder Cookies? Lädt es ein eigenes jQuery nach? usw. Auf einer Übersichtsseite sind die Resultate für die üblichen Verdächtigen (Facebook Connect, Google Analytics) aufgelistst.

    \n

    Cookiepedia und Trackography zeigen Auswertungen zu Cookies und Trackern auf Websites, Trackography konzentriert sich dabei auf News.

    \n

    Lesetipps

    \n

    Die beiden Cookie-Tools fand ich in Tracking um jeden Preis: Das Stockholm-Syndrom der Presseverlage auf Netzpolitik. Ein Artikel über DSGVO und ePrivacy, die Haltung deutscher Verlage dazu, und wie ihre Websites tatsächlich tracken. Highlight:

    \n
    \n

    2016 konnte beispielsweise ein Recherche-Team des NDR von einem Databroker einen Datensatz mit angeblich anonymisierten Browserverläufen erwerben. Darin fanden sich hochsensible Informationen unter anderem über einen Polizisten, einen Richter und Bundestagsmitarbeiterinnen.

    \n
    \n

    Sven Wolfermann mit einer schönen umfassenden Website-Performance-Präsentation. Mein Highlight ist die Folie zu serve breakpoint specific JS:

    \n
    <link rel=\"preload\" as=\"script\" href=\"map.js\" media=\"(min-width: 601px)\">
    \n\n

    Generative Artistry: coole interaktive(!) Tutorials von Tim Holman.

    \n

    onym.co bietet massenhaft Theorie, Inspiration, und Quellen für Naming Things. gefunden von unserem Designer Nando.

    \n

    Die Direktive 2102 ist eine EU-Verodnung zu Accessibility im Web. Betrifft nur den öffentlichen Sektor, aber wird ähnlich wie die DSGVO zu Last-Minute-Panik führen, obwohl man alles schon jahrelang wusste. Marcus Herrmann erklärt das Ganze auf einer hübschen Website.

    \n

    Sehr schöne Longread von The Intercept zur NSA: The Wiretap Rooms.

    \n

    dev.tube listet Konferenztalks zu Entwicklerthemen, mit einer eigenen Rubrik für Performance. Es gibt keine Deeplinks auf Kategorien, aber der Code ist Open Source bei GitHub, da kann man also helfen ;-)

    \n", "url": "https://blog.thomaspuppe.de/notizen-juni-2018", "title": "Notizen Juni 2018", "summary": "this-month-I-learned und Lesetipps für Juni 2018: sauschneller WM Ticker, Git Statistiken, und Naming Things.", "date_modified": "2018-06-30T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/notizen-mai-2018", "content_html": "

    Bullshit Jobs by David Graeber

    \n

    Nachdem ich vor einer Weile das Essay On the Phenomenon of Bullshit Jobs: A Work Rant by David Graeber gelesen hatte, hab ich mich sehr auf das dazugehörige Buch gefreut (Wikipedia-Seite, Rezension im Guardian). Es war meine Urlaubslektüre, und ich kann es sehr empfehlen. Auf jeden Fall aber das Essay!

    \n
    \n

    The paradox of modern work:

    \n
      \n
    1. Most people's sense of dignity and self-worth is caught up in working for a living.
    2. \n
    3. Most people hate their jobs.
    4. \n
    \n
    \n

    Handy-Sync via Syncthing

    \n

    Syncthing ist eine praktische Software, um Inhalte vom Handy auf den Rechner zu backuppen. Funktioniert prima mit Ubuntu und MacOS. Zuvor hatte ich Resilio Sync benutzt, aber das war weniger intuitiv und ich hatte manchmal das Gefühl dass Daten fehlten.

    \n

    Gefunden habe ich Syncthing in Martin Pitts Artikel De-Googling my phone.

    \n", "url": "https://blog.thomaspuppe.de/notizen-mai-2018", "title": "Notizen Mai 2018", "summary": "this-month-I-learned und Lesetipps für Mai 2018: Bullshit Jobs", "date_modified": "2018-05-31T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/static-sites-from-markdown-with-caddy-server", "content_html": "

    Lately, I am experimenting with different forms of static-site generation. While investigating the server Caddy for serving pages, I found that it can also generate these pages from markdown.

    \n
    \n

    ⚠️ Update 2022: This article is from 2018 and refers to Caddy version 1. User camkego on HN provides links to the current documentation of Caddy V2 markdown usage:

    \n\n
    \n

    Render markdown as HTML

    \n

    Basically it is enough to activate markdown in your Caddyfile:

    \n
    localhost:8000\nmarkdown /
    \n\n

    Now start your server with caddy -conf ../path/to/Caddyfile and visit pages http://localhost:8000/one.md. You will see that pages written in markdown (which you have to create, of course) ...

    \n
    # one.md:\n---\ntitle: My First Post\n---\n\nWhat The Fuck. Cool!\n\n## This is h2\n\nLorem Ipsum youknow.
    \n\n

    ... are rendered as HTML:

    \n
    # http://localhost:8000/one.md\n<!DOCTYPE html>\n<html>\n    <head>\n        <title>My First Post</title>\n        <meta charset=\"utf-8\">\n    </head>\n    <body>\n        <p>What The Fuck. Cool!</p>\n        <h2>This is h2</h2>\n        <p>Lorem Ipsum youknow.</p>\n    </body>\n</html>
    \n\n

    Ta dah! A website with title-tag and content.

    \n

    Add CSS and JS

    \n

    For some cases like a programming journal this might be enough. But you might want at least some CSS with it. You can do this by simply adding the path to a CSS file into the markdown-block of your caddyfile:

    \n
    localhost:8000\nmarkdown / {\n    css /styles.css\n}
    \n\n

    which results into the line

    \n
    <link rel=\"stylesheet\" href=\"/styles.css\">
    \n\n

    in the head of your rendered HTML file. This also works for JavaScript (js /script.js), which is also inserted into the head of your HTML file.

    \n

    Templating

    \n

    But you want to load JS asyncronously and deferred at the end of the body-tag, don't you? And you want to add some more HTML, like a custom header or something. You can do that – by creating a template file and using that in the caddyfile:

    \n
    localhost:8000\nmarkdown / {\n    template ../template.html\n}
    \n\n

    Note: the template path is not relative to the caddyfile, but relative to the folder where you run your caddy -conf path/to/Caddyfile command.

    \n

    Note 2: You need to restart the caddy server after changing the caddyfile.

    \n

    And: we do not need to specify the CSS and JS here, because we can do it directly inside out HTML template file:

    \n
    <!DOCTYPE html>\n<html>\n    <head>\n        <title>{{.Doc.title}}</title>\n        <link rel=\"stylesheet\" media=\"screen\" href=\"/styles.css\">\n    </head>\n    <body>\n        <head>\n            <h1>{{.Doc.title}}</h1>\n        </head>\n        <article>\n            {{.Doc.body}}\n        </article>\n        <script async defer src=\"/script.js\"></script>\n    </body>\n</html>
    \n\n

    which renders this:

    \n
    <!DOCTYPE html>\n<html>\n    <head>\n        <title>My First Post</title>\n        <link rel=\"stylesheet\" media=\"screen\" href=\"/styles.css\">\n    </head>\n    <body>\n        <head>\n            <h1>My First Post</h1>\n        </head>\n        <article>\n            <p>What The Fuck. Cool!</p>\n            <h2>This is h2</h2>\n            <p>Lorem Ipsum youknow.</p>\n        </article>\n        <script async defer src=\"/script.js\"></script>\n    </body>\n</html>
    \n\n\n

    Template variables

    \n

    You can see that the variable {{.Doc.title}} holds the content of the line title: My First Post in your markdown file. That kind of declaration in markdown (an other) files is called "frontmatter" and can be written in YAML or JSON format.

    \n

    Even better: it can hold different contents ...

    \n
    ---\ntitle: My First Post\nauthor: Thomas Puppe\nauthor_url: https://www.thomaspuppe.de\ndate: April 10th 2018\n---
    \n\n

    ... which then can be used inside your template file:

    \n
    <a href=\"{{.Doc.author_url}}\">{{.Doc.author}}</a> on {{.Doc.date}}</footer>
    \n\n

    Caddy even offers some own template variables like {{.Cookie "cookiename"}} or {{.RandomString 100 10000}}, sanitizing functions and control statements. A complete list of template actions can be found in the Caddy docs.

    \n

    This is pretty cool and you can get very far with that.

    \n

    Clean URLs

    \n

    But one more thing: The URL http://localhost:8000/one.md is neither pretty nor common. Something like http://localhost:8000/my-first-post would be nicer. The slug "my-first-post" is simply the name of your file, of course. But what about the extension? You can get rid of that by making use of Caddys ext directive:

    \n
    localhost:8000\n\nmarkdown / {\n    ext .md .html\n    template ../template.html\n}
    \n\n

    This means that caddy tries to match every request, which is not served by an existing file, is tested against files with these extensions. So if you request http://localhost:8000/my-first-post, Caddy tries to find a my-first-post file, and if that does not exist, it tries to find my-first-port.md, which we just have created. .html would be another fallback, and you can define as many as you like.

    \n

    I also tried the other way around: I put markdown files without any extension into the folder, and wanted Caddy to render them as HTML, but that did not work.

    \n

    Special templates per file

    \n

    You might want render diffent posts inside different templates. Maybe there are special posts which have a sidebar, or you create some landing pages with a lot of beautiful custom HTML and only some copy text from markdown. You can introduce diffent templates to Caddy be listing them in the caddyfile.

    \n
    markdown / {\n    template ../template.html\n    template special ../special.html\n}
    \n\n

    In the frontmatter part of your your markdown file, you can specify the template just like this: template: index. Then, this page will use the special.html template for rendering. If no template is given in the markdown file, the default template.html, which is defined without a name in the Caddyfile, will be used.

    \n

    If your templates share a common set of HTML tags, you should be able to use the import directive:

    \n
    {{.Include \"path/to/file.html\"}}  // no arguments\n{{.Include \"path/to/file.html\" \"arg1\" 2 \"value 3\"}}  // with arguments
    \n\n

    (This is written in the template action docs, but did not work for me. Maybe because of a strange combination of relative paths?)

    \n

    File listings

    \n

    On index pages, it would be handy to hava an automatic listing of subpages: the home page of a blog, the members page of your sports club. Caddy can even do that:

    \n
    <ul>\n    {{.Files \"sub/folder\"}}\n        <li>\n            <a href=\"{{$.StripExt .Name}}\">{{$.StripExt .Name}}</a>\n        </li>\n    {{end}}\n</ul>
    \n\n

    You "just" have to filter files like ".DS_Store", asset folders, the index file itself and so on. But, for your convenience, Caddy at least provides control statements like {{if not .IsDir }} and you can strip away extensions ({{$.StripExt .Name}}).

    \n

    According to the docs, you can even list a subset of "tagged" files by, but I have not explored that.

    \n

    External data sources

    \n

    It would be cool to store the markdown files externally (think GitHub), and have Caddy fetch them. I think this should be possible, because Caddy can be used as a proxy server.

    \n

    I tried the proxy directive (proxy /test https://raw.githubusercontent.com/thomaspuppe/blog.thomaspuppe.de/), but got stuck with SSL handshake errors.

    \n

    Other shortcomings

    \n

    While Caddy gets you pretty far with its templating and markdown rendering functions, there are a few things which keep me from using it for this blog.

    \n\n

    Wrap-up

    \n

    Caddys built-in features for static-site generation do a good job for putting together a simple and fast static site. With templating, logical conditions and string manipulation you get pretty far with few lines of code. If you reach the limits of what Caddy can do, you won't have wasted much time. So it is worth a try.

    \n", "url": "https://blog.thomaspuppe.de/static-sites-from-markdown-with-caddy-server", "title": "Generate static sites from markdown files with the Caddy server", "summary": "Caddy can not only serve files fast and safely. It can also generate static files from markdown, so you dont need a generator.", "date_modified": "2018-04-10T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/notizen-april-2018", "content_html": "

    Magical Auto-Jump statt CD

    \n

    Autojump ist ein schönes kleines Tool für die Kommandozeile. Es merkt sich häufig benutzte Verzeichnisse, und weiß dann wo ich hinwill wenn ich irgendwo j a11 eingebe.

    \n
    brew install autojump\n\npuppe:~ $ j blog\n/Users/puppe/code/private/blog/blog.thomaspuppe.de\n\npuppe:~ $ j a11\n/Users/puppe/code/zon/a11y-dashboard
    \n\n\n

    A11y: Zwei Tools um Einschränkungen zu simulieren

    \n

    colorblindsim.comist eine offlinefähige Web-App fürs Spartphone, die auf dem Display anzeigt wie das Drumherum denn aussehen würde wenn man farbenblind wäre.

    \n

    Funkify ist eine Chrome-Extension, die beim Surfen Dinge wie Farbenblindheit, verschwommenes Sehen, oder Zittern simuliert.

    \n

    Request Map

    \n

    Die Request Map ist ein wunderbares Tool von Simon Hearne, das alle Requests die eine Seite auslöst, schön übersichtlich als Graph darstellt. Schön zum Gruseln und Argumentieren.

    \n
    \n \"Graphen-Darstellung\n
    Requests auf ZEIT Online
    \n
    \n\n\n

    Wordpress-Plugin Primer

    \n

    Ich habe mein erstes Wordpress-Plugin geschrieben ... und hoffentlich mein letztes. Die Erfahrung in ein paar Sätze gefasst:

    \n\n
    add_action( 'wp_head', 'my_meta_tags' );\nfunction my_meta_tags() {\n  echo '<meta name=\"generator\" content=\"Thomas\" />';\n}
    \n\n\n

    Gute Quellen, die mir geholfen haben:

    \n\n

    Buchtipp: White Hat UX

    \n
    \n

    Marketing, IT and sales people all over the world work hard every\nday to make deceptive user experiences. You are better than that.

    \n
    \n\n

    White Hat UX kostet als eBook 7,50 $.

    \n

    Webperf-Erkenntnisse aus einem Webinar zur DeltaV Conference

    \n

    Aus einem kurzen Werbe-Webinar zur DeltaV Konferenz habe ich zwei interessante Sachen mitgenommen:

    \n

    (1) Die BBC konzentriert sich bei ihrem Performance-Monitoring auf zwei wichtige Werte: time to Headline (also ab wann kann der Leser Infos erfassen) und time to Scroll (wann beginnt er, den Artikel zu lesen). Ob drumherum Sachen geladen werden oder so ist ja eigentlich auch egal, aus UX-Perspektive. Die sollen schnell ihre Infos kriegen, und darauf hin wird optimiert. (Ganz so plump wird es nicht sein in der Praxis, aber das ist ein interessanter Ansatz.)

    \n

    (2) Trivago nutzt die NetworkInformation API, um an Leute mit verschiedenen Netzwerkbedingungen verschiedene Sachen zu senden. Dabei haben sie die Erfahrung gemacht:

    \n\n

    Eine Aufzeichnung des Webinars gibt es bei Youtube: youtube.com/watch?v=VamKs0HKvQA.

    \n

    Die DeltaV Konferenz selbst hat ein mega geiles Lineup. Hoffentlich veröffentlichen sie im Nachgang die Videos.

    \n

    Stress-Testing third party scripts

    \n

    Harry Roberts schreibt über Möglichkeiten, Third-Party-Scripts (oder andere Inhalte) auf der eigenen Website stress-zu-testen.

    \n

    Mein Lieblingstrick, aber nicht der einzige im Artikel, ist das Blackhole von Webpagetest. Via /etc/hosts Datei lässt man Requests an bestimmte Domains in einen Timeout laufen.

    \n

    Mit der Browser-Extension "SPOF-o-Matic" lässt sich das auch machen.

    \n

    Mit beiden Tricks hatte ich mal deutsche News-Websites auf Single-Points-of-Failures getestet.

    \n

    Paste Clipboard into file on MacOS

    \n
    pbpaste > file.js
    \n\n\n

    Webfont Preloading

    \n

    Zwei interessante Sachen gelernt. Erstens: ich dachte immer, das Preloading kann man doch gar nicht realistisch machen, weil man im HTML ja gar nicht weiß ob der Browser woff2, woff, oder ein älteres Format anfordern wird. Auf der JSUnconf Konferenz habe ich dann gelernt: Alle Browser, die Preload können, unterstützen auch woff2. Womit es sich wunderbar einsetzen lässt, auch wenn man im CSS ältere Browser mit älteren Schriftformaten versorgt.

    \n

    Zweite Sache, die ich beim Benutzen schmerzhaft gelernt habe: Preload von Schriften braucht dringend das crossorigin Attribut, auch wenn die Schriften vom gleichen Host kommen! (Quellen: MDN, Smashing).

    \n

    Rendern von Unicode-Zeichen als Emoji unterdrücken

    \n

    Das mobile iOS (und vermutlich auch andere) stellen Unicode-Zeichen auf Websites als Emoji dar. Das kann ganz witzig sein – aber als Zeitung willst du das nicht in deinen Artikeln haben.

    \n

    Lösung: Das Anhängen von \\00FE0E unterdrückt das Rendern als Emoji.

    \n

    Reines Zeichen: &#x1F435 (&amp;#x1F435); . Explizit kein Emoji: 🐵︎ (&amp;#x1F435;&amp;#xFE0E;)

    \n

    Das blöde ist nur: es funktioniert nicht (mehr) im Chrome auf Android :-(.

    \n

    Lesetipps

    \n

    Rolf Dobelli über den Verzicht auf News: News is bad for you – and giving up reading it will make you happier.

    \n

    Sehr gute Analyse zur DSGVO: "What I'm not seeing, however, is any real change to the way publishers use data". Die Frage ist nicht "Wie kann ich meine unbedachte Datensammelwut legal rechtfertigen?" Sondern "Wozu brauche ich denn überhaupt welche Daten?"

    \n

    Sam Altman on Productivity. Yet another one. Bei diesen Posts lernt man ja nichts, was man nicht schon wüsste. Dennoch steh ich da drauf. Und auch bei diesem Artikel lautet einer der Tipps "don’t fall into the trap of productivity porn". Naja.

    \n

    Frank Chimero: The Good Room über den Umgang mit moderner Technologie: was ist daran gut, was nicht? Und wie kann man die Balance verbessern? Außerdem ein sehr schön gestalteter "Longread".

    \n

    Dear Developer, The Web Isn't About You.

    \n

    Aufholen beim Thema JavaScript: How I stopped worrying and learned to love the JavaScript ecosystem.

    \n
    \n

    JavaScript was changing but I was resistant.

    \nModern JavaScript for dinosaurs\n
    \n\n

    Randnotiz: Ich finde es fürchterlich, dass heute alle (auch Hackernoon) mit Medium bloggen. Und wehre mich dagegen. Mal sehen, ob sich das obige in ein paar Jahren auch für die Blogging-Plattform wiederholt.

    \n", "url": "https://blog.thomaspuppe.de/notizen-april-2018", "title": "Notizen April 2018", "summary": "this-month-I-learned und Lesetipps für April 2018", "date_modified": "2018-04-03T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/mein-smarterphone", "content_html": "

    Mein Kollege und Chef Nico Brünjes warf mir folgendes Interview als Blogstöckchen zu.

    \n

    …und ich, Name:

    \n

    Thomas Puppe

    \n

    verbringt seinen Tag…

    \n

    mit Frontend-Webentwicklung bei ZEIT Online

    \n

    nutzt ein:

    \n

    Samsung Galaxy S7, das mir die Firma zur Verfügung stellt. Eigentlich wollte ich mal ein iPhone ausprobieren, aber zugunsten der Vielfalt im Frontend-Team wurde es doch wieder ein Android.

    \n

    Wie würdest Du dein Verhältnis zu Deinem Smartphone bezeichnen?

    \n

    Eng, aber unemotional. Wie zu meiner Hose. Ich habe das Smartphone ständig dabei und nutze es viel, aber es ist austauschbar – und am Wochenende geht es auch mal ohne.

    \n

    Welche App/Funktion nutzt du am häufigsten (gerne in den Statistiken nachschauen oder aus dem Bauch schätzen)

    \n

    Twitter, Whatsapp, Browser, Evernote, den Wecker.

    \n

    Welche App/Funktion nutzt du gar nicht?

    \n

    Ich spiele nicht auf dem Handy. Ich mache kein Mobile Banking.

    \n

    Arbeit und Handy — wie regelst Du das?

    \n

    Rufbereitschaft braucht es im hochqualitativen Zero-Bug-Frontend von ZEIT Online nicht ;-). Und wenn, wäre ich auch nicht der Ansprechpartner. Daher ist alle arbeitsbezogene Handynutzung in der Freizeit freiwillig. Und beschränkt sich auf Sachen wie mal-Slack-checken.

    \n

    Andersherum nutze ich das "Diensthandy" auch privat. Auch im Büro. Während technische Recherche und Weiterbildung viel in der Freizeit passiert -- ich denke das gleicht sich aus.

    \n

    Welche Notification hast Du eingeschaltet?

    \n

    So wenig wie möglich. Ich schalte prinzipiell alles aus, und gewählte Apps wieder ein. Dazu gehören Twitter, Whatsapp, E-Mails, Pushover und die DHL. (Und ein paar kürzlich installierte, die ich noch nicht abgeklemmt habe ... gleich mal machen!)

    \n

    Warum?

    \n

    Weil die Scheiße nervt. Die meisten Apps richten die Dringlichkeit für Pushes nicht nach mir (etwas Schlimmes ist passiert, oder mein Bus verspätet sich), sondern nach sich (Generalsekretär wie geplant gewählt, Busticket-Sonderangebot).

    \n

    Hälst du dich an soziale Regeln bei der Smartphone-Nutzung? Wenn ja: Welche?

    \n

    Ich hab mein Handy niemals laut. Und versuche seit neuestem sogar, die Vibration aus zu lassen. In der Regel will ich den Griff zum Handy aus eigener Motivation tun, und nicht weil es sich meldet.

    \n

    Gibt es Regeln, die du wieder verworfen hast?

    \n

    Ich nehme mir immer wieder mal vor, das Handy vor dem Frühstück nicht zu benutzen, oder nicht im Schlafzimmer. Aber das klappt bisher nicht.

    \n

    Zum Abschluss: Was sollten mehr Menschen im Umgang mit Smartphones wissen?

    \n

    Wenn du nicht gerade Kinder hast, oder kranke Eltern, gibt es keinen vernünftigen Grund jederzeit erreichbar zu sein.

    \n

    Ich sende den Kettenbrief werfe das Stöckchen weiter zu Arne Seemann.

    \n", "url": "https://blog.thomaspuppe.de/mein-smarterphone", "title": "Mein Smarterphone", "summary": "Man nennt es Blogstöckchen: ein Interview zu meiner Smartphone-Nutzung", "date_modified": "2018-03-14T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/notizen-maerz-2018", "content_html": "

    Ich versuche mich mal am beliebten "this month I learned" Format. Weil ich in den letzten Monaten aber immer gesammelt und nie gepublished habe, versuche ich es so: zu Monatsbeginn schreibe ich einen Anfang, und dann ergänze ich im Laufe des Monats. Das gibt auch Raum für kleine Sachen, die es nie zu eigenen Blogposts schaffen. Mal schauen wie das wird.

    \n

    Default Parameters in Javascript ... nicht im IE 11

    \n

    Fangen wir gleich mal an mit dem Fail der Woche. Einmal ZEIT Online Production kaputtmachen für alte Browser. Wie? Durch den Gebrauch von default Parametern in JavaScript:

    \n
    function(myParam, optionalParam = true) { ... }
    \n\n

    Das ist ein Feature aus ES2015 funktioniert deshalb leider nicht für den Internet Explorer 11 abwärts. Also, wenn man nicht babelt, muss man das wieder einkassieren und in der Funktion selbst den Default-Wert definieren:

    \n
    function(myParam, optionalParam) {\n    optionalParam = (typeof optionalParam !== 'undefined') ?  optionalParam : true;\n}
    \n\n\n

    document.currentScript

    \n

    Das zweite JS Learning des Tages: man kann im JS Code dasjenige script-DOM-Element selektieren, in dem der Code läuft. Einfach via document.currentScript.

    \n

    Detaillierter Artikel zu currentScript von Dr. Axel Rauschmayer mit veralteten Angaben zur Browser-Unterstützung. Caniuse bestätigt eine gute Unterstützung: [https://www.caniuse.com/#search=currentScript](Browser-Unterstützung von document.currentScript bei caniuse.com).

    \n

    Allerdings wieder ohne den IE11. Und weil wir da ja keinen Bug haben wollen (siehe oben), gibt es einen schönen Polyfill:

    \n
    var currentScript = document.currentScript || (function() {\n    var scripts = document.getElementsByTagName('script');\n    return scripts[scripts.length - 1];\n})();
    \n\n

    Wozu braucht man das? Um z.B. ein Script-Tag wegzuräumen, nachdem es seine Arbeit getan hat.

    \n
    <script>\n    var currentScript = document.currentScript;\n    var parent = currentScript.parentNode;\n    var newDiv = document.createElement('div');\n    newDiv.setAttribute('class', 'wurstbrot');\n    parent.replaceChild(newDiv, currentScript);\n</script>
    \n\n

    Im Übrigen, unintuitiverweise:

    \n
    // String.replace Parameter Reihenfolge: search, replace\nstr.replace(regexp|substr, newSubStr|function);\n\n// Node.replaceChild Parameter Reihenfolge: replace, search\nparentNode.replaceChild(newChild, oldChild);\n
    \n\n

    Ich dachte das sei eine Spezialität von PHP.

    \n

    Newsletter

    \n

    Ich lese begeistert den Software Lead Weekly Newsletter von Oren Ellenbogen, der auch das lesenswerte Buch Leading Snowflakes geschrieben hat.

    \n

    Früher interessierte mich der Social Media Watchblog von Martin Giesler nicht so sehr. Für meinen Geschmack ging es zu sehr um "Plattform X hat jetzt Funktion Y" und "die 10 besten Tipps für Whatever". Ich habe ihn aber erneut abonniert, und freue mich dass es um mehr Meta-Theman geht. Mobile Payment via Wechat und Whatsapp, wie gehts bei der Google News Initiative weiter, Facebooks Rolle bei der Verfolgung der Rohinja oder eine Zusammenfassung zum Cambridge Analytics "Skandal". Themen wie Entlassungen von Mitarbeitern bei Snapchat überlese ich weiterhin.

    \n

    Accessibility Weekly von David A. Kennedy: schöner wöchentlicher Digest zu Accessibility.

    \n

    Accessibility Dashboard

    \n

    Bin letztens über das "pa11y Dashboard" gestolpert, was Acessibility-Test-Scores für verschiedene Seiten auf einem Dashboard aufmalt. Das ist aber gerade im Umbruch. Ein Rewrite namens "Sidekick" ist WIP und funktioniert noch nicht. Da sind mein Kollege Valentin und ich auf die Idee gekommen, dass wir ja schon ein Dashboard haben, in das man pa11y-Testergebnisse reinmalen kann. Und zwar ein Graphite/Grafana, das wir für Sitespeed Performance Monitoring benutzen.

    \n

    Also haben wir ein kleines CSript geschrieben, das pa11y-tests parallel laufen lässt und die Ergebnisse an eine Graphite Instanz sendet. Ergebnis ist ein kleines Repo: a11y-dashboard-connector: Es ist ein zaghafter Anfang, und noch hardcoded für unsere Zwecke, aber immerhin.

    \n

    CSS Keylogger und andere Schweinereien

    \n

    Keylogging via CSS: vor einigen Tagen ging ein GitHub Repo um, das zeigte, wie man CSS als Keylogger missbrauchen kann: CSS Keylogger.

    \n
    input[type=\"password\"][value$=\"p\"] {\n  background: url('/password?p');\n}
    \n\n

    In seinem Blogpost Third party CSS is not safe erfindet Jake Archibald weitere Möglichkeiten, wie 3rd Party CSS eine Seite manipulieren kann. Sehr spannend, aber nur nebenbei im Artikel erwähnt: wenn ein Angreifer die Cache Storage vollschreibt, kann er Clients dauerhaft kompromittieren, auch wenn das schadhafte Script längst von der Seite entfernt wurde. Fazit:

    \n
    \n

    Third party content is not safe\n– Jake Archibald

    \n
    \n

    Performance bei Wikimedia

    \n

    Wikimedia hat ein eigenes Performance Team (Traumjob!), und wie es sicht gehört arbeiten die transparent. Also kann man in deren Backlog verfolgen, alle Dashboards ansehen, und sie veröffentlichen auch Blog-Posts.

    \n

    Ethical Design and Development

    \n

    Zwei schöne Artikel dazu: Ethical Design: The Practical Getting-Started Guide im Smashing Magazine und Oaths, pledges and manifestos: a master list of ethical tech values auf Medium.

    \n

    Zu dem Thema ist mir das Buch "White hat UX" untergekommen, was ich mit gleich mal gekauft habe. Ich werde in diesem Blog berichten. Das eBook kostet nur $7.50, das kann man auch mal anonym an seine Produktentwicklung spenden.

    \n

    Next Gerenartion Clearfix

    \n

    Es gibt ein natives Clearfix in CSS. Chris Coyier schrieb schon 2016 darüber, Rachel Andrew Anfang 2017. Allerdings ist die Browser-Unterstützung recht mager: nur Firefox und Chrome unterstützen display: flow-root. Wenn ich jetzt den Draft-Revision-usw. Lebenszyklus der CSSWG verstehen würde...

    \n

    Hinter Docker herräumen

    \n

    Mein Kollege Wolfgang hat was schönes entdeckt: Docker-Container werden nicht automatisch gelöscht nachdem ein Job mit ihnen lief. Wir benutzen Docker für Sitespeed-Tests, die alle paar Minuten laufen. Das nimmt nicht viel Platz weg, aber summiert sich und verlangsamt auch einige Befehle. Man findet die Dinger via docker container ls --all, kann einmal durchwischen mit docker container prune und verhindert das Ansammeln, indem man den Job mit docker run --rm startet.

    \n

    Resilient CSS

    \n

    Jen Simmons https://hacks.mozilla.org/2018/03/how-to-write-css-that-works-in-every-browser-even-the-old-ones/

    \n

    The idea of the web is, to work for everyone, independent of the device. A good reminder by Jen Simmons what the idea of the web is https://www.youtube.com/watch?v=u00FY9vADfQ

    \n

    Lesetipps

    \n

    Lasagne Code ist eine schöne neue Metapher, die ich im Artikel Lasagna code - too many layers? gelernt habe. Der Autor plädiert nicht gegen Layer im Code, sondern für die richtige Dosis und Art. Ich finde das Gleichnis auch noch auf einer anderen Ebene (no pun intended) spannend: bei der Infrastruktur. Einmal Proxy-Cache ist cool. Mehrere Ebenen mit eigenen Regeln und Verzweigungen führt zu einer Situation wie bei Spaghetti Code, nur als Lasagne.

    \n

    "Die Sicherheitstechnik funktionierte wie vorgesehen - auf dem Niveau der 1960er Jahre, dem Tiefpunkt der Gesetzgebung in der Kerntechnik." – Sehr spannender postmortem Artikel zur Fukushima-Kernschmelze bei Golem.de.

    \n

    China setzt eine neue Marke im Bereich Geo-Engineering: China needs more water. So it's building a rain-making network three times the size of Spain

    \n", "url": "https://blog.thomaspuppe.de/notizen-maerz-2018", "title": "Notizen März 2018", "summary": "Eine Art this-month-I-learned meets Lesetipps meets Notizblock", "date_modified": "2018-03-23T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/front-trends-2017-warsaw", "content_html": "

    A list of talks from the Front Trends 2017 conference in Warsaw, and some remarks.

    \n

    There are other summaries available. First of all, the official schedule, which also contains descriptions of each talk. Nienke Dekker put her detailed notes on GitHub. Syb Wartna has a blog post about his highlights. These sources provide more detail. My list is more of a link collection.

    \n
    \n \"Venue\n
    Venue of the Front Trends 2017 conference in Warsaw
    \n
    \n\n

    The talks, in order of my personal preference

    \n

    The How, What and Why of migrating to ReactJS by Jack Franklin was not about React, but all about migrating complex software. His company Songkick switched from Angular (yesterdays hot code was "unmaintainable, feature-bloated, tricky, old developers gone") to ReactJS.

    \n

    They did it by incremental migration, instead of a big bang. That way, you can learn as you migrate, often release to the public, and prioritize based on pain-points in the old app. At first, they migrated unit tests and introduced acceptance tests.

    \n

    A mix of big and small tasks helped to keep momentum. People could choose freely from a big backlog in Trello. They aroused interest and morale with a variety of available tasks.

    \n

    Code-Reviews were split into feature branches with feature-part-branches and incremental pull requests. The knowlede base was inside the code repository, and maintained by the same pull requests that added/changed code.

    \n

    Metrics (e.g. the number of Angular and React files) were prominently communicated, so the team could see if it was heading in the right direction. And this is a good thing to show to the business department.

    \n

    The Power of CSS by Una Kravets was a great code-in-slides performance (in Codepen!!!) about CSS effects like filters and blend-modes. Special takeaway for me: youmightnotneedjs.com.

    \n

    I didn't know the browser could do that! by Sam Bellen gave a cool introduction into new Browser APIs like window.speechSynthesis, Speech Recognition in Chrome, Geolocation, Notification, Push, MediaRecorder, BatteryManager. The examples were embedded in Sams cool and cute cat assistant Poes which is available for everyone online.

    \n

    Field-tested interfaces for the next billion by Ally Long (slides) was about regions which are not as good connected as western countries, but will come online during the next years. Ally Long has worked for health projects in south-sahara Africa and shared valuable tips with us:

    \n

    The typical device is Android on cheap hardware. Opera Mini is really big in Africa. Connectivity is bad, data is expensive, latency is high. Electrical power is badly available. As a consequence, you should follow some rules: UI should not be blocked by network loading (eg fullscreen spinners). No gestures. No off-canvas navigation. Forms should be no longer than a screen — rather use multi-screen forms. Don't use selectboxes — rather use a list of radio buttons. Skeuomorphism is better than flat design. Be robust for people to click everywhere and often. Use consistent UI. Strip everything back to the essentials!

    \n

    Watch your back, Browser! You're being observed. by Stefan Judis showed APIs and Obervers which are available to use in modern browsers: IntersectionObserver (check if an element is inside the visible viewport), MutationObserver (changes in DOM, save to use today), Timing API (navigation, user, resource), PerformanceObserver, ResizeObserver, Observables in general, ... check the slides!

    \n

    Easy and affordable user-testing by Ida Aalen was a funny talk about how everyone can conduct cheap user tests. Colleagues, designers, developers, or user testing experts are not your users! You find real users among your friends and on the streets. And since only 5 real users will catch most of the problems in any thing, it is worth trying some low-cost low-effort methods. Watch the slides and an article about the tools mentioned in her talk to get some great ideas!

    \n

    Rendering performance inside out by Martin Splitt was about the insides of rendering inside the browser. The rendered page is composed of layers, and from a computational perspective, composing them is faster than repainting, because it can be done in parallel (pixels are independent from another). That's why videos or canvas, which are always inside their own layer, do not slow down the whole page. What I took away from this talk: do animations via transformation, and use the will-change property with absolute positioning. The slides are available online.

    \n

    Legends of the Ancient Web by Maciej Cegłowski talks about the fact that "there's nothing new under the sun" and that we have seen developments, which seem new in the web business, long before: in the radio broadcasting business. (Amateurs -> professionals -> business -> advertising -> copyright -> content created only to increase audience -> propaganda and hate speech.) This is an interesting analogy.

    \n
    \n

    You have a choice what you work for. If you work for mass surveillance. If you base your business model on evil. You are not only technicians. You are also citicens and human beings.

    \n

    — Maciej Cegłowski

    \n
    \n

    The First Meaningful Paint by Patrick Hamann (video on CSSConf) talks about ways to speed up the first meaningful paint on a website. Nice example: Google renders the header of its result page, even before the search request is sent to the server. Measures you can take are: inlining critital CSS. <link rel=preload> (which also works via HTTP header). And HTTP/2 server push, which could even be done by your proxy cache, before the actual backend server renders a response.

    \n

    Motion In Design Systems: Animation, Style Guides, and the Design Process by Val Head: Very cool presentation about designer-developer-collaboration with sketches, storyboards (anyone can do. quick tests with little commitment. trigger, what happens, how does it happen?), motion comps (HTML5 tool: Tumult Hype) and interactive prototypes (tool: atomic, codepen). Slides, Codepen.

    \n

    Experimenting your way to a better product by Zoe Mickley Gillenwater was about A/B-Tests. It's clear that experiments help to avoid biases and opinions. The goal is that users drive the product direction. Zoe shared her process (observe, hypotesis, test, measure, avaluate, repeat) and experience (create smallest change possible, iterate quickly, most tests fail, one out of ten tiny changes has a huge impact). Cool talk, spiced up with some cognitive biases (Texas sharpshooter fallacy, HARPing). Slides

    \n
    \n

    Avoid HIPPOs. (Highest Paid Persons Opinions)

    \n

    — Zoe Mickley Gillenwater

    \n
    \n

    Changing the layout game by Chris Wright presented some examples on how people hack CSS to get near the features they actually would like to have. Fox example we have hacked layouts with tables, floats, flexbox ... and now CSS grids are available. The talk contains a lot of nice examples on how to solve common problems in CSS. You should check the slides.

    \n

    Alternative Reality DevTools by Konrad Dzwinel started with the history of browser dev tools, which have all been similar, and even today have seen not much improvement. So, Konrad tries new concepts, inspired by AutoCAD, video editing, processor design, game design, graphic design, and so on. Highlights: Context aware inspectors (for meta tags: docs and specs, for SVG: SVG editor). Concentrate on a certain element (hide all the others). Timeline (go back while using your app, e.g. un-click a radio button). Inionite canvas (multiple devices on one big screen). Good news: everyone can contribute! Firefox devtools are open source and written in React.

    \n

    Components, patterns and sh*t it’s hard to deal with by Marco Cedaro: how do we manage and reuse patterns, without making them too rigid for day-to-day activities? Problems arise if you use BEM too dogmatic. Solutions: enable unbemmy/atomic CSS in some cases. Use open mixins where the author decides what to enforce, and what to expose to the user of the mixin. If you have a BEM-problem, step back and ask yourself: what problem are we actually trying to solve? Slides

    \n

    Monsters, mailboxes and other nonsense by Niels Leenheer was about fun wit IoT in his smart house Slides.

    \n

    On How Your Brain is Conspiring Against You Making Good Software by Jenna Zeigen (slides) applies cognitive biases to software development. The effects everyone knows (pair programming and breaks are good, debugging your own code is hard, people are bad at estimating the time to program something) are explained by the cognitive biases humans have.

    \n

    Let's save the internet: How to make browsers compatible with the web by Ola Gasidlo introduced us to the history of browser wars and led to the W3C and WHATWG specs. Slides. Takeaway: WHATWG specs are available on GitHub, you can contribute to prevent single companies from deciding on the webs future.

    \n

    Microservices - The FAAS and the Furious by Phil Hawksworth gave examples on how static sites can be enhanced with microservices (comments and search, for example). In the end, the setup was rather complex ;-) ... but the basic message is: start by outsourcing simple things.

    \n

    Demystifying Deep Neural Networks by Rosie Campbell gave an introduction to machine leaning. Conventional programs have explicit algorithms, deep learning is trained with data. The talk was based on an article which explains the principles behind neural networks.

    \n

    I'm in IoT: Vadim Makeev controlled lamps and drones on stage with his browser. His drone was highjacked by someone in the audience :-). Slides.

    \n

    Big Bang Redesign: Smashing Magazine’s 2017 Relaunch: Vitaly Friedman gave insights on why and how Smashing Magazine was rebuilt. Because of the rise of ad blockers, they shifted focus on membership and selling products. They used atomic design and a living styleguide (which is already outdated).

    \n

    A Cartoon Intro to React Fiber by Lin Clark was a deep dive into how React Fiber improves the perceived performance of react apps. I only understood that it prioritizes updates during rendering. You can watch the talk on youtube.

    \n

    The past and future of designing interfaces by Adam Morse was a bit abstract and about design systems (which are like Gutenbergs moveable type), spreadsheets (a study shows: almost all have errors) teams (if you have less or more than 3 people, you won't make any substantial progress). My highlight was the idea of a random content generator for design systems, which puts a stress test on styleguides by automatic permutation of content.

    \n

    Prevent Default — The future of authoring tools by Mihai Cernusca showed a lot of examples and principles of authoring tools from the past, present and future. Even complex 3D software uses a heap of generic UI elements (sliders, input, radio buttons). How can we enhance this? The tool "Hemingway" lints your text while writing. But apart from that, most text editors look like Word 1.0. And it gets harder if you want to edit layout instead of vertical one-column text flow, have history, drag&drop, selection, cursor navigation. Mihais Solutions: ContentEditable, Outliner, Workflowy, Notion.io.

    \n

    Building a Progressive Web App by Kirupa Chinnathambi was an intoductionto PWAs (Slides). It was new to me that PWAs are available and installable from the Windows 10 app store.

    \n

    Random facts that I learned:

    \n\n

    My catch-up list:

    \n\n", "url": "https://blog.thomaspuppe.de/front-trends-2017-warsaw", "title": "Front Trends 2017 in Warsaw", "summary": "A list of talks from the Front Trends 2017 conference in Warsaw, and some remarks.", "date_modified": "2017-07-08T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/browser-api-css-mediaqueries", "content_html": "

    In der Regel denkt man bei Media Queries an nicht viel mehr als min-width oder device-pixel-ratio und bei Browser APIs an navigator.userAgent. Moderne Browser können natürlich viel mehr.

    \n

    Kürzlich sah ich in einer interessanten Präsentation über Progressive Web Apps dies:

    \n
    @media (display-mode: standalone), (display-mode: fullscreen) {\n    .backButton {\n        display: block;\n    }\n}
    \n\n

    Mit dieser Media Query stellt man fest, ob die Website (bzw Web App) das Browser-GUI (und damit den Back-Button des Browsers ) zur Verfügung hat, oder nicht. Und kann dann einen eigenen Button einblenden.

    \n

    Das war für mich ein Anlass, mal zu schauen, welche Media/Feature Queries und Browser APIs heutzutage zur Verfügung stehen.

    \n

    (1) Browser APIs

    \n

    navigator.cookieEnabled

    \n

    Eigentlich ganz simpel, kannte ich aber bis heute noch nicht. Diese Abfrage ist natürlich der Methode, ein Cookie zu setzen und dann auszulesen, zu bevorzugen. Sehr gute Browser-Unterstützung.

    \n
    \n

    In diesem Browser:

    \n
    \n

    navigator.doNotTrack

    \n

    Zeigt an, ob im Browser "Do not track" gesetzt wurde -- und auf welchen Wert. Ist in vielen Browsern implementiert, aber der Rückgabewert ist unterschiedlich und muss geparsed werden.

    \n
    \n

    In diesem Browser:

    \n
    \n

    navigator.hardwareConcurrency

    \n

    Gibt die Zahl der verfügbaren Prozessoren zurück. Das ist ein grober Indikator dafür, ob der Browser auf einem ein starken neuen Gerät läuft, oder einer alten Möhre. (Vielleicht kann man auch auf die sinnvolle Anzahl an Service Workern oder Web Workern schließen? Gibt ein Browser-Tab, das in einem Prozess läuft, solche Möglichkeiten her?)

    \n
    \n

    In diesem Browser:

    \n
    \n

    navigator.geolocation

    \n

    Selbsterklärend. Neben der aktuellen Position kann man auch Positions (oder Genauigkeits-) Änderungen als Event-Handler empfangen. Was macht man mit der Position des Users? Man füttert sie in eine Routenplanung, oder nutzt sie, um Ort und Land des Besuchers herauszufinden.

    \n
    navigator.geolocation.getCurrentPosition(function(position) {\n  console.log(position.coords.latitude, position.coords.longitude);\n});
    \n\n
    \n

    In diesem Browser:

    \n
    \n\n\n

    Geolocation hat eine sehr gute Browser-Unterstützung, erfordert aber die Berechtigung durch den User und ist im Chrome Browser nur unter SSL verfügbar.

    \n

    navigator.onLine

    \n
    \n

    In diesem Browser:

    \n
    \n

    Selbsterklärend. Die Änderung des online-Status lässt sich über ein Event abfangen:

    \n
    window.addEventListener('online', function(e) {\n    console.log(\"You are online\");\n}, false);\n\nwindow.addEventListener('offline', function(e) {\n    console.log(\"You are offline\");\n}, false);
    \n\n\n

    navigator.connection

    \n

    Mehr Details zur Internetverbindung des Users (cellular, wifi).

    \n
    \n

    In diesem Browser:

    \n
    \n\n\n

    navigator.connection ist nur im Firefox Mobile und auf Android verfügbar.

    \n

    navigator.getBattery()

    \n

    Der Batteriestand des Devices kann abgefragt werden! Traurige Berühmtheit erlangte das Feature, als Uber denjenigen Kunden, deren Smartphone nur noch wenig Saft hatte, die Preise erhöht hat -- weil sie schnell das erstbeste Fahrzeug buchen würden.

    \n
    \n

    In diesem Browser: waiting...

    \n
    \n\n\n

    Gute Menschen setzen das ein, um bei Telefonen mit schwachem Akku auf aufwändige Animationen und allen überflüssigen Quatsch zu verzichten, der die CPU belastet.

    \n
    navigator.getBattery().then(function(battery) {\n   console.log(battery.level*100 + '%');\n   console.log(battery.chargingTime);\n   console.log(battery.dischargingTime);\n});
    \n\n

    Für die Änderung der Werte kann man auch wieder Event Handler registrieren, damit die Website/App informiert wird.

    \n

    Eine schöne Einführung findet man bei MDN. Das Feature wird unterstützt im Firefox (nur in "privilegiertem Code" -- diesen Term recherchiere ich jetzt nicht), Chrome und Opera.

    \n

    navigator.share()

    \n

    Mit dieser Funktion kann man per JS die Sharing-Funktion auslösen, die sonst aus dem Menü des Browsers getriggert wird. Der Vorteil gegenüber individuellen Sharing-Buttons: die Leute bekommen genau die Dienste angeboten, die sie zur Verfügung haben -- und nicht die der Seitenbetreiber für wichtig hielt.

    \n
    navigator.share({\n    title: document.title,\n    url: window.location.href\n})
    \n\n

    Alle Parameter müssen Strings sein. Ob diese aus JavaScript kommen oder hardcoded sind, ist egal. Test oder URL können weggelasssen werden, eines von beiden muss vorhanden sein.

    \n

    Zurückgegeben wird eine Promise. Man kann den User also fürs Sharen belohnen oder Abbruchraten tracken.

    \n

    Ein paar Einschränkungen hat die Technik: Sie ist nur auf HTTPS-Seiten verfügbar, und kann nur durch User-Interaktion getriggert werden (nicht etwa onLoad oder onScroll -- Sorry liebe "User Engager"). Navigator.share() ist derzeit nur im Chrome (ab Version 55) verfügbar. Wie bei allen aktuellen Features wird der Entwickler also progressively enhancen.

    \n\n\n
    \n

    In diesem Browser:

    \n
    \n

    navigator.sendBeacon

    \n

    Kann genutzt werden, um kleine Datenmengen asynchron an einen Server zu senden. Das Verfahren ist für Event Tracking und Monitoring gedacht. Vor allem das Rumeiern mit Requests bei window.unload soll erleichtert werden.

    \n
    navigator.sendBeacon(\"/log\", data);
    \n\n

    Das data können Form Objekte, Arrays oder normale JS Objekte sein.

    \n

    Der Browser-Support ist durchwachsen und das Verfahren ist als experimentell eingestuft. Auf GitHub liegt eine clevere kleine Testseite bereit.

    \n

    navigator.vibrate(500)

    \n

    Lässt das Gerät vibrieren, wenn das verfügbar ist. Als Parameter nimmt die Funktion einen Integer (einmalige Vibration für x Millisekunden) oder ein Array (Pattern von Vibration und Pause) entgegen. navigator.vibrate([100,200,300]) vibriert also 100 ms, pausiert 200 ms, vibriert 300 ms. Verfügbar in modernen mobilen Browsern.

    \n
    \n

    In diesem Browser:

    \n
    \n

    Die Funktion steht auch in nicht-vibrierfähigen Geräten zur Verfügung und man kann nicht prüfen, ob sie tatsächlich etwas tun wird. Zur Erkennung müsste man also über die Device-Detection gehen.

    \n

    (2) Media Queries

    \n

    Die am häufigsten eingesetzten Media Queries dürften (min/max-)(device-)width sein, die für responsive Layouts genutzt werden. Das device macht den wichtigen Unterschied zwischen der Größe des Gerätes und des eigentlichen Viewports (also Fensters oder Split Screens).

    \n

    Besonders auf großen Bildschirmen, wo man im Split Screen arbeitet (was mittlerweile auch auf Tablets kein Problem mehr ist), kann das Fenster komplett anders als das Gerät sein.

    \n

    Doch Media Queries können viel mehr:

    \n

    (device-)aspect-ratio

    \n

    ... ist das Seitenverhältnis des geräts (bzw Viewports). Damit kann man im simpelsten Fall quer- von Hochformat unterscheiden, oder beispielsweise Panorama-Bildschirmen spezielel Stile oder Inhalte ausliefern.

    \n
    @media screen and (device-aspect-ratio: 16/9), screen and (device-aspect-ratio: 16/10) { ... }
    \n\n\n

    orientation

    \n

    Eine noch simplere Variante der aspect-ratio. Nimmt die Werte landscape oder portrait an.

    \n
    @media all and (orientation: portrait) { ... }
    \n\n\n\n
    \n

    In diesem Browser: \n undefined\n portrait\n landscape\n

    \n
    \n

    resolution

    \n

    Erkennt die Pixeldichte auf einem Gerät, und wird vor Allem genutzt, um Retina-optimierte Bilder auszuliefern (min-resolution: 300dpi).

    \n

    display-mode

    \n

    Mit dieser Media Query stellt man fest, ob die Website (bzw Web App) das Browser-GUI (und damit den Back-Button des Browsers ) zur Verfügung hat. Mögliche Werte: fullscreen, standalone, minimal-ui und browser.

    \n

    light-level

    \n

    Beschreibt die Lichtverhältnisse in der Umgebung, und nimmt die Werte dim (gedämpft), normal und washed (sehr hell) an. Eigentlich praktisch für so etwas wie den Nacht-Lese-Modus. Andererseits sehe ich hier die Gefahr, dass man mit seinen Queries die Nutzereinstellungen oder automatische Helligkeitsanpassung des Smartphones überschreibt -- "das Gegenteil von gut ist gut gemeint". Unterstützt wird light-level nur im Edge und im Firefox für OS X.

    \n

    supports

    \n

    Erst diese Woche in einem Vortrag meines Kollegen Nico Brünjes gesehen. Außerhalb leider selten. Mit dieser Feature Query prüft man, ob der Browser bestimmte CSS-Eigenschaften unterstützt. Zum Beispiel @supports(blink) oder @supports (display: grid).

    \n

    In den meisten Fällen lässt sich die Abfrage bzw. das progressive Enhancement direkt in die CSS-Regeln einbauen. Zum Beispiel bei Schriftgrößen via font-size:16px; font-size: 1rem; für den IE8. Der ignoriert die zweite Angabe, mit der er nicht klarkommt, und nutzt die erste. Moderne Browser überschreiben die erste mit der zweiten.

    \n

    Die Query @supports sorgt aber für Klarheit, wenn man größere Blöcke umstylen will, sobald eine Technik verfügbar ist. Oder aber, um eine ganz andere CSS-Datei zu laden.

    \n
    <link rel=\"stylesheet\" media=\"all\" href=\"basic.css\" />\n<link rel=\"stylesheet\" media=\"screen and (min-width: 5in) and (display: flex)\" href=\"shiny.css\" />
    \n\n

    Der IE bis inklusive Version 11 unterstützt @supports nicht.

    \n

    Der Support von Features lässt sich übrigens auch via JavaScript über die CSS-API des Browsers abfragen:

    \n
    var canuiseCSSGrid = CSS.supports(\"(display: grid)\");
    \n\n\n

    Exotische Queries

    \n

    Der Vollständigkeit halber noch ein paar Media Features, die eher selten genutzt werden.

    \n

    scan unterscheidet in der Art des Bildaufbaus am Bildschirm (interlace vs progressive), und ist relevant bei schnellen Bewegungen.

    \n

    color und color-index geben an, wie viele Farben verfügbar sind und wie sie gespeichert werden, monochrome erkennt Geräte die nur mit Graustufen arbeiten (und mit wie vielen). grid spricht auf Monospace-Font Displays an, also alle die feste Plätze und Breiten für jedes Zeichen haben. Theoretisch ist das cool, um reduzierte Bilder (oder auf Kontrast optimiertes CSS) an Geräte wie den Kindle auszuliefern. Weil diese Mikrooptimierung aber kaum jemand betreibt, machen die Geräte selbst was Gutes aus dem CSS -- weshalb wiederum niemand diese Mikrooptimierung betreibt.

    \n

    Im Firefox kann man Windows-Versionen und Betriebssystem-Stile erkennen und mit seienm Styling darauf reagieren. Feature-Queries wie -moz-os-version:windows-win10, -moz-mac-graphite-themeund-moz-windows-theme:aero` regeln das.

    \n

    Media Queries Level 4

    \n

    Noch im Draft-Status sind Media Queries Level 4. Sie versprechen Features wie hover (kann ich über Elemente hovern?), pointer (wie genau kann ich Elemente treffen -- Mauszeiger vs Wurstfinger), update (Schnelligkeit beim Bildaufbau, quasi frames-per-second) und mehr.

    \n

    device-width, device-height, und damit auch device-aspect-ratio` sind deprecated.

    \n

    (3) Was mir fehlt

    \n\n
    <script media=\"screen and not (doNotTrack)\" src=\"https://google.com/analytics.js\" defer integrity=\"abc\">\n<script media=\"screen and (bandwidth:high)\" src=\"/immersiveEffects.js\" defer integrity=\"xyz\">\n
    \n\n", "url": "https://blog.thomaspuppe.de/browser-api-css-mediaqueries", "title": "Browser APIs und CSS Media Queries", "summary": "Der Einsatz von Media Queries und Browser APIs beschränkt sich häufig auf min-width und navigator.userAgent. Aber in modernen Browsern gibt es cooles Zeug.", "date_modified": "2017-04-08T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/open-source-in-deutschen-newsrooms", "content_html": "

    Vorab: bei Recherchen weiß man ja nie, was man übersehen hat. Daher bin ich dankbar für jeden Hinweis, Ergänzung oder Korrektur. Am besten per Twitter: @thomaspuppe.

    \n

    Ich folge seit einiger Zeit dem Twitter-Account @NewsNerdRepos. Er versendet jedes mal einen Tweet, wenn eine große Onlinezeitung ein GitHub-Repository veröffentlicht. Mir fiel auf, dass dort keine deutschen Medien vertreten sind -- obwohl diese laut Liste durchaus zum Teil beobachtet werden. Also habe ich selbst mal recherchiert, und einen Überblick über offenen Code und offene Blogs in deutschen Newsrooms erstellt.

    \n

    Große Zeitungen: große Vorbilder

    \n

    Die großen englischen Zeitungen erzeugen viel Open Source Code. Die Washington Post hostet bei GitHub Daten zu Police-Shootings und Cloud-Tools, und pflegt ein Developer-Blog. Die Financial Times hat einen Account mit Frontend-Repositories von der productiv-seite, sowie ein FT Labs Konto mit Experimenten und Tools. Auch New York Times hat verschiedene Accounts: einen für die Website, ein NYT Labs und eins für den NYT Newsroom. Außerdem gibt es ein Development-Blog und eine Artikel-Such-API. In Europa tut sich bekanntlich der Guardian hervor mit GitHub-Repositories -- unter anderem eines mit dem Frontend-Code von www.theguardian.com -- und einem empfehlenswerten Developer-Blog. Zudem gibt es eine Art Entwickler-Recruiting-Seite.

    \n

    Natürlich sind auch anderssprache Zeitungen vertreten, wie z.B. Le Monde oder Hürriyet. Aber es soll hier um deutsche Medien gehen. Also?

    \n

    Und in Deutschland?

    \n

    Ich habe mich auf die Suche gemacht nach offenen Repositories, Entwicklungs-Blogs, oder zumindest Twitter-Accounts, mit denen Newsrooms und Entwicklungsabteilungen Teile ihrer Arbeit veröffentlichen.

    \n

    Die großen 15:

    \n

    Zunächst zu den reichweitenstärksten Nachrichtenportalen in Deutschland laut Statista/IWV.

    \n

    Bild.de: unter der Marke Bild habe ich nichts gefunden. Der Axel Springer Verlag hat einen aktiven GitHub-Account samt vielversprechender github.io-Seite. Es gibt außerdem eine Ideen-und-Strategie-Abteilung mit eigenen Repositories sowie eine (offenbar eingestellte?) Abteilung namens "iPool".

    \n

    Spiegel Online: zweieinhalb ältere Projekte bei GitHub -- da gab es wohl mal einen Versuch.

    \n

    Zu Focus Online und n-tv.de habe ich nichts gefunden.

    \n

    upday pflegt einen GitHub-Account sowie ein Tech Blog.

    \n

    Die Welt hat einen sehr aktiven GitHub Account, der von AWS über Swift bis Frontend und Kantinen-Slackbot einige Projekte umfasst.

    \n

    Von ZEIT Online: gibt es einen GitHub-Account, in dem vor allem Module des selbst geschriebenen ZOPE-CMS veröffentlicht sind. Außerdem sind die Coding-Guidelines und der Sourcecode des anonymen Whistleblower-Briefkastens öffentlich. Ein Dev-Blog berichtet über technische Entwicklungen -- leider viel zu selten. Oh, und es gibt einen scheintoten Twitter-Account. (Disclaimer: ZEIT Online ist mein Arbeitgeber.)

    \n

    Sueddeutsche.de: Im GitHub-Account liegen Frontend-Experimente und Daten-Tools.

    \n

    Zu FAZ.net, Stern.de, Gruner + Jahr, Huffington Post, Funke Medien Gruppe, RP Online, Express Online und Handelsblatt.com habe ich keine Repositories oder Blogs gefunden.

    \n

    Kleinere Medien

    \n

    Das preisgekrönte Interaktiv-Team der Berliner Morgenpost stellt bei GitHub Rohdaten seiner Anwendungen zur Verfügung. Tools und Quellcode der Anwendungen sind zahlreich bei Webkid, den Codern hinter den Projekten, zu finden. Die bloggen auch.

    \n

    Beim Tagesspiegel gibt es einen fast leeren GitHub Account. Deren Data Scientist Sebastian Vollnhals ist aber sehr aktiv im eigenen Account und im ausgegründeten "Data Department" des Tagespiegel.

    \n

    Ähnlich verhält es sich mit Steffen Kühne vom BR Data Team. Apropos:

    \n

    Öffentlich-Rechtliche

    \n

    Der Norddeutsche Rundfunk veröffentlicht Tools auf GitHub. Vom ZDF ist das Projekt Lobbyradar (mittlerweile unter der Obhut von Netzpolitik.org) auf GitHub zu finden. Das "junge Angebot von ARD und ZDF" betreibt einen GitHub-Account mit aktuellen Repos.

    \n

    IT-Medien

    \n

    Der Heise-Verlag hat einen kaum noch aktiven GitHub-Account, Golem ein einziges, aber schönes Nerd-Repo. Von t3n gibt es ein verwaistes Repository, von Netzpolitik gar nichts.

    \n

    Fazit

    \n

    ... ist ernüchternd. Die großen internationalen Medien zeigen, wie es gehen kann. Natürlich haben die ein vielfaches der deutschen Budgets. Dennoch könnten hiesige Newsrooms und Entwicklungsabteilungen offener sein. Axel Springer ist vorn dabei, die Welt hat mit dem ansprechenden Relaunch auch viel Open Source veröffentlicht.

    \n

    Mit etwas Stolz sehe ich uns bei ZEIT Online mit vorn dabei, auch wenn die aktive Veröffentlichung von Inhalten oder das Neuveröfentlichen von Code-Repositories eingerostet ist.

    \n

    Die Berliner Morgenpost zeigt, dass man zumindest Datensätze veröffentlichen kann, um Recherchen transparenter zu machen und vielleicht neue Projekte zu inspirieren (was z.B. SRF Data sehr gut macht).

    \n

    Nicht zuletzt ist Open Source und öffentliches Tech-Bloggen ein wirkungsvolles Recruiting-Instrument -- hier könnte sich jeder hervorheben, der nur irgendwas tut. Der Guardian zeigt wie das im Idelfall aussehen könnte.

    \n", "url": "https://blog.thomaspuppe.de/open-source-in-deutschen-newsrooms", "title": "Open Source in deutschen Newsrooms", "summary": "Ein Überblick über Open Source, offene Daten und Development-Blogs aus deutschen Nachrichtenportalen.", "date_modified": "2017-03-14T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/twitter-bots-anne-will", "content_html": "

    Social Bots sind aktuell ein großes Thema. Die Medien berichten darüber, wie die bösen Roboter unsere Demokratie hacken werden. Sogar Verbote werden gefordert -- ich nehme an, der Quatsch soll Schlagzeilen bringen und ist nicht ernst gemeint. "Forscher" und "Experten" versuchen, die vom Untergang bedrohten Parteien zu beraten. Besonders aufgefallen ist mir kürzlich das Projekt Botswatch, das Bot-Reaktionen auf Talkshows beleuchtet -- mit der simplen Heuristik, dass User mit 50 oder mehr Tweets pro Tag Bots seien.

    \n

    Ich halte das alles für sinnlose Panikmache. War aber neugierig, wie denn die Zahlen zustande kommen, was sie bedeuten, und welche Techniken heute zum Einsatz kommen, um Social Bots zu identifizieren.

    \n

    Dazu habe ich am vergangenen Sonntag Tweets zur Sendung Anne Will im Ersten gespeichert, und diese etwas genauer angeschaut.

    \n

    Datensammlung

    \n\n

    Die Rohdaten habe ich bei GitHub veröffentlicht, einige Explorations-Grafiken sind in meinem Tableau Account zugänglich.

    \n
    \n \"Graph:\n
    Tweets mit #annewill pro Minute während der Sendung am 05.03.2017 Daten bei Tableau
    \n
    \n\n\n

    Insgesamt haben 1794 User (Re)Tweets gesendet. Die meisten wenige, manche viele. Die fleißigsten Kommentatoren waren @rot_pe (64 Tweets), @HorstNRW (53 Tweets), @darksideoftheeg (37 Tweets).

    \n

    888 User haben nur einen Tweet gesendet, 491 User zwei oder drei Tweets. Immerhin 115 User schrieben jeweils mehr als 10 Tweets.

    \n

    Die erfolgreichsten (im Sinne von Retweets während der Sendung) stammen von @krk979 (98 RT), @AliCologne (58 RT und 42 RT) und @Heinrich_Krug (42 RT).

    \n

    Man kann da jetzt alle möglichen Analysen drauf fahren (Hashtags, Dialoge, wer wird erwähnt), oder die erfolgreichsten Tweets zitieren ... aber hier soll es um Bots gehen. Also los: finden wir die Bots!

    \n

    Verfahren 1: Accounts mit mehr als 50 Tweets pro Tag sind Bots

    \n

    Das erste Verfahren, das mich auch auf das Thema gebracht hat, ist das von Botswatch. Alle Accounts, die mindestens 50 Tweets pro Tag senden oder mindestens 50 Favoriten markieren, sind Bots.

    \n

    Im Sample von #annewill wären das

    \n\n

    12 Prozent aller User sollen also Bots sein, die für ein Fünftel der Tweets verantwortlich sind. Das sind auch Zahlen, die Botswatch nennt (logisch, ich habe deren Verfahren angewendet) und 19% werden auch in einem ZEIT Online Artikel zu Social Bots genannt, oder im Handelsblatt. Beide zitieren Studien von Universitäten in den USA. Ich habe mir nicht die Mühe gemacht, die Primärquellen zu prüfen, und übernehme die Zahlen jetzt mal so.

    \n

    Verschiebt man die Grenze, ab der ein Account als Bot gilt, auf 40 oder 60 Tweets, bleibt der Anteil ähnlich: 16 bis 10 Prozent der Nutzer wären Bots, und 22 bis 15 Prozent der Nachrichten von ihnen geschrieben. Ein Histogramm dazu habe ich bei Tableau (Achtung: unterschiedliche Bucket-Größen!).

    \n
    \n \"Graph:\n
    Anzahl von Accounts des #annewill Samples, die eine bestimmte Menge von Tweets pro Tag versenden
    \n
    \n\n\n

    Was sind das für Bots, die "nach einer neuen Bundestags-Studie nicht nur unsere politische Kultur vergiften, sondern bei knappen Mehrheiten auch Wahlergebnisse beeinflussen können" (focus)?

    \n

    Ich habe mir die Accounts angeschaut, die im Annewill-Sample als Bot klassifiziert wurden, und besonders viel tweeten.

    \n

    Top-Bot meines Samples mit 855 Tweets pro Tag ist der @bot_huso ("Hurensohn Bot"). Ein automatisches Programm, das Tweets retweetet, die das Wort "Hurensohn" beinhalten.

    \n

    Es folgen eine kurdische Nachrichten-Suchmaschine (@Rojname_com), der @Demokratie_Bot, der offenbar alles mit #democracy retweetet, die @WorldTweetNews und etliche Trending-Topic-Bots (@TrendingTopicPK, @top_world_now). Alles Bots. Aber das offensichtlich, und vor Allem ungefährlich.

    \n

    Erst danach folgt im Ranking der User @hans_obermeier (Username "Old Fart") mit 439 Tweets pro Tag und 12 Beiträgen zur Anne Will Sendung. Ein echter AfD/Merkelmussweg Typ, der sehr viele Beiträge retweetet, und manche selber verfasst oder kommentiert. Nach der 50-Tweets-Regel wäre er ein Bot, nach meiner menschlichen Einschätzung nicht. Zu viele Follower, zu "breites" Themenspektrum, eigene Kommentare zwischen reinen Retweets.

    \n

    Dann folgen einige Spam-Bots.

    \n

    Nachrichten wie \"Check LINK Live Nude Streaming #Deutschland #BVBFCB #annewill #bpw16 #CDU www.ein-spam-link.com\" werden häufig verschickt. Sie stammen auch eindeutig von Bots. Aber sie gefähren nicht unsere Demokratie!

    \n\n

    Das Live-Nude-Streaming aus dem Bundestag wurde mit nur 14 Tweets beworben. Das ist nicht viel, aber treibt zusammen mit Trending-Topic-Tweets und anderem Spam die Zahlen der Bot-Hysteriker nach oben.

    \n

    Zwischen Trend-Bots und Spam gibt es natürlich viele Accounts, die tatsächlich Inhalte verbreiten. Auch, und häufig ausschließlich, politische. Ob das denn Bots sind, lässt sich aber nicht aus der Menge der Tweets schließen.

    \n

    Als nächstes habe ich mir das andere Ende des Spektrums angesehen: Accounts, die sehr wenige Tweets pro Tag absenden. (Kleiner Exkurs: die Tweets pro Tag errechnet man in der Regel aus dem Tag der Accounteröffnung und der Gesamtzahl der bisherigen Tweets. Schwankungen in der Aktivität werden dabei nicht berücksichtigt. Das ginge auch, aber mit erheblch mehr Aufwand.)

    \n

    Ab unteren Ende des #annewill-Bot-Rankings nach Tweets-pro-Tag sind auffällig viele Accounts, die seit Jahren bestehen, aber nur eine einstellige Anzahl an Tweets verfasst haben. Als Beispiel seien genannt: @Secret9191, @Eschenbach22145, @Coby18807372, @juewilu, @trueequalsfalse und @FredHeiss. Besucht man diese Accounts, stellt man fest, dass sie wenige oder keine Follower haben, und tatsächlich nur einen sehr aktuellen Tweet (der aber zum Zeitpunkt der Überprüfung, zwei Tage später, schon nicht mehr der Annewill-Tweet ist). Hier wird offensichtlich kurz nach dem Schreiben wieder gelöscht. Entweder das sind komische Kauze -- oder Bots, die nicht also solche (durch plumpe Heuristiken) erkennbar sein wollen. Besonders die Namen mit Zahlen deuten möglicherweise auf eine generische Erzeugung der Accounts hin. Beim Herumspielen mit den Zahlen habe ich allerdings kein Schema und keine Serie gefunden. Unter der 50-Tweet-Hürde rutschen die alle durch.

    \n

    Verfahren 2: BotOrNot

    \n

    Die University of Indiana bietet einen Service namens BotOrNot an, den man auch per API benutzen kann. Die Nutzer der Annewill-Tweets habe ich gegen diese API gesendet.

    \n

    Als Ergebnis erhält man eine Wahrscheinlichkeit, zu der ein Account als Bot angesehen wird. Da gibt es verschiedene Merkmale wie das Netzwerk, Account-Infos, Zeiträume zu denen geschrieben wird (24h Aktivität=Bot) und Inhaltsanalysen. Außerdem einen Gesamt-Score, den ich für alle Accounts, die sich an #annewill beteiligt haben, betrachtet habe:

    \n
    \n \"Graph:\n
    Anzahl von Accounts des #annewill Samples, die eine bestimmten Score bei BotOrNot erreichen
    \n
    \n\n

    Aus dem Sample werden 163 Accounts als Bots betrachtet, wenn man 50% Wahrscheinlichkeit als Bot-Grenze zieht. Möchte man zu 60% sicher sein, sind es nur noch 31 Accounts. Diese habe ich mir im Detail angesehen.

    \n

    Sehr hohe Werte haben die Accounts @Anti68er (97%), @FredHeiss (92%), Coby18807372 (79%). Das sind die oben genannten "leeren" Accounts, die ihre Tweets schnell wieder lsöchen.

    \n

    Auch vertreten sind @RMehberg (79%), @MarkusFelder2 (64%), @LisaSkytta (64%), die nicht sehr aktiv sind. Warum Accounts, die wenige Wochen alt sind und nicht viel schreiben, als Bots eingeordnet werden, erschließt sich mir nicht.

    \n

    Von den 31 Usern, die mit mehr als 60% als Bot klassifiziert werden, wurde nur ein einziger in der 50-Tweets-Methode erfasst: @top_world_now.

    \n

    Umgekehrt: der "Hurensohn"-Retweeter, die Trend-Bots, und die Spam-Schleudern sind von BotOrNot alle nicht als Bot erkannt worden. Zumindest nicht mit 60%er Sicherheit. BotOrNot vergubt ja nur den Score, und wo man dann seine Grenze zieht kann man selbst entscheiden. Aber: ich hätte die Bots, die eindeutig diesen Zweck haben, und sich selbst als Bots zu erkennen geben, auch mit hohen Punktwerten erwartet.

    \n

    Auch hier noch ein Blick auf das andere Ende des Spektrums: von den 58 Usern, die mit weniger als 20% Wahrscheinlichkeit als Bot eingeordnet wurden, wären drei von der 50-Tweet Klassifizierung erfasst worden. Ein manueller Check zeigt, das sind normale Nutzer, die vor Allem ein breites Themenspektrum abdecken. Also nicht politikfixiert sind, sondern eben Sonntagabend mal Alle Will kommentieren.

    \n

    Fazit 1: Vergleich der Bots

    \n

    Man sieht, dass die beiden Verfahren -- 50-Tweet-Heutistik vs. API der Universität -- unterschiedliche Accounts als Bots einordnen. Bei beiden findet man an beiden Enden des Spektrums sofort Fehlklassifizierungen. Die Zahlen, die man als Grenzwert benutzt, lasse sich beliebig verschieben und sorgen dann entweder allmählich für mehr Bots (Anzahl-der-Tweets Methode) oder rapide (BotOrNot Score). Welche Grenze "richtig" ist, lässt sich nicht sagen. Bot-Erkennung ist halt orakeln statt Wissenschaft.

    \n

    Fazit 2: Der Inhalt

    \n

    Im Sample habe ich jede Menge radikale Asichten gesehen. Meistens von rechts. Besonders von Vielschreibern. Und das durch die Bank, unabhängig von Methode und Einordnung als Bot.

    \n

    Was nicht bedeuten muss, dass die AfD und andere Nazis Bots einsetzen (wie gern irgendwo geschrieben wird), sondern auch bedeuten kann dass diejenigen besonders viel zu politischen Talkshows twittern, die besonders viel politisches Mitteilungsbedürfnis haben.

    \n

    In absoluten Zahlen ist das nach meiner Ansicht eh nicht relevant. Accounts wie @AnneWillTalk, @HeikoMaas, @berlinerzeitung oder @jungeunion haben eine große Reichweite, stehen aber nicht im Verdacht Bots zu sein.

    \n

    Der patriotische Vielschreiber @darksideoftheeg kommt auf 13.000 Follower, hat viele Beiträge und einen mittleren Score von 51% bei BotOrNot. Dass er exakt so viele Follower wie Friends hat, weist auf Strategie hin -- aber nicht zwingend auf einen Bot. Netzwerk und Inhalt sprechen laut BotOrNot-Daten für einen Bot. Die Aktivitäts-Zeiten dagegen. Ein Grenzfall. In dubio pro reo würde ich sagen, da ist jemand einfach aktiv und erfolgreich.

    \n

    Die deutlich als Bots erkannten Accounts haben hingegen keine großen Follower-Zahlen. Und damit keinen großen Einfluss. Außer, wie so oft bei Twitter, indirekt über die Medien, die das Thema Bots aufgreifen und die, als wirksamer Aufreger, extreme Ansichten weiterverbreiten.

    \n

    Was hilft denn? Wie erkennt man Bots?

    \n

    Ganz wichtig: Bots schreiben eh keine Inhalte. Sie dienen als Multiplikatoren (einer schreibt, zehn künstliche Accounts veröffentlichen), oder sie verbreiten bestimmte Inhalte weiter. Dabei müsste der Algorithmus aber auch erkennen, ob merkelmussweg nun ernst oder ironisch benutzt wurde. Was eines der großen Probleme in der Sprachverarbeitung ist. Und beim stumpfen verbreiten von Ausländerhass ist es auch egal, ob das ein identitärer Fanboy mit zu viel Zeit macht, oder ein automatisches Programm.

    \n

    Als konkrete Maßnahme für ein Thema, z.B. Polit-Talkshows, hilft nur die Einordnung der Tweets ins Thema. Passt das Geschriebene zur Sendung? Das kann dann nicht vorbereitet aus der Dose kommen. (Witze über die Vorhersagbarkeit von Polit-Talkshows überspringen wir.) Dann ist es wohl von Menschen geschrieben. So etwas ist aufwändig, und nur begrenzt automatisierbar. Da müsste man Mühe und Hirnschmalz reinstecken. Das ist nichts für simple Lösungen und plakative "jeder Fünfte ist kein Mensch" Schlagzeilen.

    \n

    Die Bot-Detektoren jedenfalls helfen nicht. Noch nicht. Noch nicht gut genug. Es gibt sicher weitere Ansätze, aber so etwas ist immer ein Wettrennen. Die Bot-Programmierer wissen ja auch, mit welchen Methoden die Detektoren arbeiten. Und haben sich, wie gesehen, daran angepasst. Ich erinnere an die Accounts, die sehr viel schreiben und gleich wieder löschen. Oder die automatischen Trend-Bots und Spam-Bots. Bei solch groben Unschärfen kann man nur raten. Eine Angabe im Nachkommabereich, dass nun 22,81% der Akteure Bots seien, täuscht eine Wissenschaftlichkeit vor, die nicht gegeben ist.

    \n

    P.S.

    \n

    Es macht wirklich keinen Spaß, sich durch politische Tweets durchzuarbeiten.

    \n
    \"Die Talkshow #annewill ist noch nicht mal als Trinkspiel zu gebrauchen. Sad /o\\\"
    — @NicolePunkt
    \n\n\n\n\n", "url": "https://blog.thomaspuppe.de/twitter-bots-anne-will", "title": "Twitter-Bot-Beobachtung zu #AnneWill", "summary": "Social Bots sind aktell ein großes Thema. Zu Recht? Ich habe am Sonntag", "date_modified": "2017-03-10T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/fast-brutalist-websites", "content_html": "\n\n

    Websites which are intentionally ugly are one of the hottest trends in webdesign in early 2017, according to the Washington Post, webdesignerdepot or the t3n magazine.

    \n

    After years of flash, wood patterns, skeuomorphism, webfonts, grunge handwriting, full screen images, ambient background videos, parallax bullshit and interactive crapcards, this is a trend that I actually like. Because: ugly is fast!

    \n

    Well, it can be fast. Scanning a collection of brutalist websites, I have seen everything from pages with 10 kilobytes to dozens of megabytes. In this article I want to explore and explain some tricks for building ugly websites simply and fast.

    \n

    1. Backgrounds

    \n

    Use small background images and either tile them, or show them in full size - even if the image is actually much smaller than the screen.

    \n
    \n
    \n\n

    I really like CSS backgrounds which repeat a pattern or symbol. There is a beautiful collection of CSS3 patterns by Lea Verou. You can even have moving color tiles. My absoute favourite are the fruits by Angela Valasquez.

    \n
    \n\n

    At this point I want to point out that I do not want to describe the sources or the techniques as "ugly". But they can help you to create effects that might be used in so-called brutalist webdesign. That being said, let me continue with CSS gradients:

    \n
    \n\n

    2. Fonts

    \n

    You can go absolutely crazy with CSS text effects. Just google it. Without even touching the font size or font face (aka webfont), you can make a lot of impact. The best: you do not need to load a huge web font file (maybe even from the Google servers), but can use these effects with any system font. Which viewer cares about rounded serifs and handcrafted ligatures, if you put these in their face?

    \n

    Text-Shadow and underline,\nCrazy Neon or fire effects.

    \n

    Take care that you do not get too classy by accident.

    \n

    You might want to use text which fills the screen. Which fills any screen, from smartphone to cinema display. There's a unit for that:

    \n
    Viewport sized typography
    \n\n

    More properties that you can play with: line-heights, alignment (center, right), vertical text, 3D glasses effect, star wars scrolling and hover animations (1, 2, 3)!

    \n

    3. Images

    \n

    Holy shit, there is a huge amount of CSS filters that you can apply on images in modern browsers. Plus: everything Photoshop.

    \n

    But we want a tiny fast brutal website, don't we? So we only use small images. Which can either be scaled, repeated (with varying effects) or heavily compressed.

    \n
    \n\n\n\n\n\n\n\n\n\n\n\n\n\n
    appy CSS filters
    \n(credits: @bennettfeely)
    \"Photo\n
    sepia(1) hue-rotate(200deg)\n
    \"Photo\n
    sepia(1)\n
    \"Photo\n
    saturate(4)\n
    \"Photo\n
    hue-rotate(90deg)\n
    \"Photo\n
    invert(.8)\n
    \"Photo\n
    contrast(3)\n
    \"Photo\n
    blur(7px)\n
    \n
    \n\n
    \n \n \n \n \n \n
    compress heavily\n \"Photo
    \n jpg 50%: 13kB\n
    \n \"Photo
    \n jpg 25%: 9kB\n
    \n \"Photo
    \n jpg 10%: 6kB\n
    \n
    \n\n
    \n \n \n \n \n
    reduce colors\n \"8-color-photo
    \n 8 colors png: 44kB\n
    \n \"2-color-photo
    \n 2 colors png: 15kB\n
    \n
    \n\n\n

    4. ASCII Art

    \n
    \n _______________________________________\n< Are you old enough to know ASCII art? >\n ---------------------------------------\n        \\   ^__^\n         \\  (oo)\\_______\n            (__)\\       )\\/\\\n                ||----w |\n                ||     ||
    \n\n\n

    5. Unicode Writing

    \n

    I can write ⓘⓝ ⓑⓤⓑⓑⓛⓔⓢ or s⃣   q⃣   u⃣   a⃣   r⃣   e⃣   s⃣ and even ą ʂէվӀҽ çąӀӀҽժ ҍҽղէ just using unicode characters.

    \n

    There are many more on the ➥ unicode table 😎.

    \n

    6. Oldschool HTML without styles

    \n

    This was more fun when <hr> had a dropshadow and tables had borders and table-padding and stuff. But some HTML elements look quite brutal when unstyled. fieldset elements are handy for creating boxes:

    \n
    \n form fieldset legend\n \n
    \n \n
    \n With HTML5 we even have new fany stuff like the progress element:\n progress\n
    \n\n\n

    7. Share this page! ❤

    \n

    Never never ever use Twitter and Facebook widgets! They are the worst! They force your users to connect to twitter/facebook servers and load tons of crap, even when they do not click.

    \n

    Instead, use simple links which you can style on your own behalf. It is really fast, it is safe, and it works. Just try it out:

    \n\n

    \n┊┊┊┊┊┊┊┊┊┊┊┊╭━━━━━╮\n┊┊┊┊╱▔▔╲┊┊╭━╯TWEET╰━╮\n┊┊┊▕┈┈▋▋▏╭┫THIS HOLY┃\n┊▂▂╱┈┈┈▕╲╯╰╮ SHIT! ┃\n┊▏▕▂▂╱┈▕▔┊┊╰━━━━━━━━╯\n┊╲▂▂▂▂▂╱┊┊┊┊┊┊┊┊┊┊┊┊┊┊┊┊┊┊┊┊┊┊\n━━━━┃━┃━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n

    \n\n\n

    Share on facebook

    \n







    \nCredits: Kermit photo, Kermit icon, Twitter Button, Facebook Button.

    \n", "url": "https://blog.thomaspuppe.de/fast-brutalist-websites", "title": "How to make your shiny new brutalist website brutally fast", "summary": "Intentionally ugly websites are one of the trends in 2017. What I like most about this: ugly is fast!", "date_modified": "2017-02-09T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/url-unshortening", "content_html": "

    Für ein aktuelles Feature bei Bundestwitter trage ich gerade zusammen, welche Links von Politikern via Twitter verbreitet wurden. Dabei stellt sich heraus, dass mehr als 20% der geposteten Links sogenannte Shortlinks sind. Also nicht nur die Kürzung von Twitter selbst via t.co (deren echte URLs werden über die APIs mitgeliefert), sondern es werden schon Short-URLs beim Schreiben der Tweets eingegeben.

    \n

    In Zahlen drückt sich das so aus (bei insgesamt 48.000 Links):

    \n\n

    Diese URLs möchte ich nun also auflösen. Im Internet findet man darüber Artikel, einige verschiedene Dienste und diverse Scripte, Bibliotheken und Snippets bei GitHub.

    \n

    Eigentlich ist es viel einfacher!

    \n

    Es stellt sich heraus, dass im Prinzip ein Curl-Aufruf ausreicht. Alle genannten Dienste antworten mit einem HTTP Status 301 und dem Location-Header. Das Internet ist also doch noch nicht so kaputt wie ich dachte.

    \n

    In Bash ist das ganz simpel: curl -I http://fb.me/16MEzokwA. Fertig. Wegen der Anbindung an meine Datenbank habe ich die Arbeit mit PHP erledigt, was auch mit Bordmitteln und ein paar Codezeilen funktioniert:

    \n
    function get_resp_from_url($url)\n{\n    $curlConnection = curl_init();\n    curl_setopt($curlConnection, CURLOPT_URL, $url);\n    curl_setopt($curlConnection, CURLOPT_NOBODY, true);\n\n    if (curl_exec($curlConnection) == false) {\n        print 'Curl-Error: ' . curl_error($ch);\n        curl_close($curlConnection);\n        return false;\n    } else {\n        $responseInfo = curl_getinfo($curlConnection);\n        curl_close($curlConnection);\n        return array(\n            'status' => $responseInfo['http_code'],\n            'location' => $responseInfo['redirect_url']\n            );\n    }\n}
    \n\n

    Einzig Facebook konnte damit nicht abgegrast werden. fb.me-Adressen liefern mir zuverlässig Status 301 bei Abfrage per Bash, und Status 200 mit Facebook-HTML im Body bei der Abfrage per PHP.

    \n

    Weder Google noch die curl-Referenz für PHP konnten helfen, also musste Python herhalten. Nach dem üblichen erfolglosen Herumirren auf mehreren Wegen (httplib und urllib2) funktioniert es dann auf diese Art:

    \n
    import requests\nr = requests.get(url, allow_redirects=False)\nif r.status_code == 301:\n    return r.headers.get('Location')
    \n\n

    Schöne Erkenntnis am Rande: selbst bei hunderten oder tausenden Abfragen im unter-Sekunden-Takt hat keiner der Dienste mit Rate Limiting oder anderer Abweisung reagiert.

    \n", "url": "https://blog.thomaspuppe.de/url-unshortening", "title": "URL-unshortening", "summary": "Auflösen von bit.ly, fb.me und co im großen Stil", "date_modified": "2017-01-06T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/berlin-anschlag-aufmacher", "content_html": "

    Die Aufmacher der großen Online-Medien am 21. Dezember 2016, zwei Tage nach dem Anschlag. Entspringt einer kleinen Übung zu Crawling und Scraping. Ohne Auswertung.

    \n\n\n

    Der SPIEGEL

    \n\n\n

    Die ZEIT

    \n\n

    FAZ

    \n\n

    Focus

    \n\n

    Süddeutsche Zeitung

    \n\n

    taz

    \n\n

    Berliner Morgenpost

    \n\n", "url": "https://blog.thomaspuppe.de/berlin-anschlag-aufmacher", "title": "Online-Aufmacher nach dem Anschlag in Berlin", "summary": "Die Aufmacher der großen Online-Medien zwei Tage nach dem Anschlag im Vergleich", "date_modified": "2017-01-05T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/das-anti-agile-manifest", "content_html": "

    Das Agile Manifest und seine agilen Prinzipien sind die Basis agiler Software-Entwicklung. An diesen Leitplanken (nicht ehernen Gesetzen) orientieren sich viele moderne Entwickler-Teams.

    \n

    Wie klingt es, wenn man das agile Manifest umkehrt?

    \n

    Das Anti-agile Manifest

    \n

    Wir haben feste Prozesse und Werkzeuge. Das wird nicht einfach umgeworfen für kurze Dienstwege und direkte Absprachen.

    \n

    Dokumentation sagt uns was die Software tut. Der super-clevere Quellcode ist eh nicht lesbar.

    \n

    Schriftliches geht über Kunden-Absprachen.

    \n

    Pläne werden befolgt und nicht einfach umgeworfen, nur weil es neue Fragen und Erkenntnisse gibt.

    \n

    Zwölf Prinzipien Anti-agiler Softwareentwicklung

    \n

    Wir sollten nicht ständig kleinteiligen Code releasen, sondern große Würfe in großen Release-Paketen abliefern.

    \n

    Während der Entwicklung möchten wir keine Änderungen an der Anforderung mehr sehen. Was einmal festgeschrieben wurde, soll dann auch gelten. Selbst Schuld, wenn nicht alles bedacht wurde.

    \n

    Ein Feature ist fertig, wenn der Code lokal läuft. Das geht dann auch auf produktiven Systemen. Falls nicht, merkt das jemand im Testsystem. Dafür ist genug Zeit, weil nicht so oft released wird (siehe auch "große Würfe").

    \n

    Everyone, please leave me alone. Die tägliche Zusammenarbeit stört. Alle wichtigen Infos sind in der User Story zusammengefasst. Nachfragen können so erst gar nicht entstehen. Falls doch, lässt sich das auch noch in der Review klären ("ich konnte nicht, weil...").

    \n

    Errichte Projekte rund um unmotivierte Gruppen. Wirf ihnen regelmäßig Aufgaben zu. Wenn die abgehakt sind, wirf neue nach. Wenn nicht, siehe "Leave me alone".

    \n

    Die effizienteste und effektivste Methode, Informationen an und innerhalb eines Entwicklungsteams zu übermitteln, ist schriftlich und allumfassend im Vorfeld (siehe auch "Leave me alone").

    \n

    Beschäftigtgewesensein ist das wichtigste Fortschrittsmaß.

    \n

    Agil sein bedeutet, wechselweise sprinten und spurten zu können. Wir springen flexibel zwischen dringenden Deadlines, akuten Notfällen und wichtigen Bugs.

    \n

    Lieber schnelles Mittelmaß ("reicht doch so") als sinnloser Perfektionismus. Denn Code wird nicht gelesen, kopiert oder erweitert. Falls doch, kann man sich dann die Zeit zum refactorieren nehmen (siehe auch "sprinten und spurten").

    \n

    Lines of Code und Features pro Sprint sind der beste Indikator für hochwertige Arbeit.

    \n

    Die besten Architekturen, Anforderungen und Entwürfe entstehen außerhalb des ausführenden Teams. Sie fallen dann in Stein gemeißelt auf das Team herab.

    \n

    Team-interne Meetings sind zu vermeiden (siehe auch "Leave me alone"). Wird dennoch hin und wieder eine Retrospektive erzwungen, lässt man die still über sich ergehen und macht weiter wie zuvor.

    \n", "url": "https://blog.thomaspuppe.de/das-anti-agile-manifest", "title": "Das Anti-agile Manifest", "summary": "Wie klingt es, wenn man das agile Manifest umkehrt?", "date_modified": "2016-12-13T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/us-wahl-aufmacher-auf-news-websites", "content_html": "

    Kürzlich habe ich mit dem Python-Framework Scrapy herumgespielt. Erstens wollte ich ausprobieren, wie gut es sich bedienen lässt. Zweitens war ich neugierig, wie die Aufmacher von News-Websies vor und bei der US-Präsidentschaftswahl bestückt werden. Beides lässt sich praktischerweise gut kombinieren :-)

    \n

    Das Vorgehen

    \n

    Zunächst habe ich die Homepages der News-Seiten mehrere Tage lang im 5-Minuten-Takt heruntergeladen.

    \n
    #!/bin/bash\ncurrenttime=`date '+%Y-%m-%d_%H-%M-%S'`;\nfoldername=\"/var/www/newscurl/$currenttime\";\nmkdir $foldername;\ncd $foldername;\n\ncurl -o spiegel.html http://www.spiegel.de/\ncurl -o zeit.html http://www.zeit.de/index\ncurl -o lr-online.html http://www.lr-online.de/\n
    \n\n

    Damit ist das HTML der Seite gesichert, und kann später immer wieder analysiert werden. Was mit Scrapy auch recht einfach gelingt:

    \n
    import os\nimport scrapy\n\nclass LocalSpider(scrapy.Spider):\n    name = 'localspider'\n    # provided via `python -m SimpleHTTPServer 9090`, since file:// url did not work\n    start_urls = ['http://localhost:9090/']\n\n    def parse_single_page(self, response):\n        yield {\n            'title': response.css('main article h2 a::attr(title)').extract_first(),\n            'url': response.css('main article h2 a::attr(href)').extract_first(),\n            'image': response.css('main article img::attr(src)').extract_first()\n        }\n\n    def parse(self, response):\n        # entry point: directory listing of localhost:9090\n        for link in response.css('a'):\n            link_url = link.css('::attr(href)').extract_first()\n\n            if link_url.endswith('zeit.html'):\n                yield scrapy.Request(response.urljoin(link_url), callback=self.parse_single_page)\n            else:\n                yield scrapy.Request(response.urljoin(link_url), callback=self.parse)
    \n\n

    Mit scrapy runspider localspider.py -o result.json --logfile=localspider.log erhalte ich so ein JSON-File mit allen Ergebnissen und ein umfangreiches Logfile des Scraping-Vorgangs.

    \n

    Zum Schluss robbe ich mittels PHP über das JSON-File und gebe die Inhalte als HTML-Tabelle aus. Das Ergebnis (weder für Mobilgeräte optimiert, noch für sonstwas) schaut so aus: http://lab.thomaspuppe.de/us-wahl-news-aufmacher/

    \n

    Fazit 1: Scrapy

    \n

    Die Bedienung ist sehr einfach. Scrapy war eines der ersten Python-Frameworks, das ich problemlos installieren und benutzen konnte. Das liegt vielleicht an meiner wachsenden Python-Erfahrung, wahrscheinlich war viel Glück dabei, und auf jeden Fall bietet die Scrapy-Website einen super Einstieg samt funktionierendem Hello-World.

    \n

    Die Selektoren entsprechen im Grunde CSS-Selektoren. Womit mans chon sehr weit kommt. Die asynchronen yield-Aufrufe sind ein schöner Weg zur Parallelisierung der Anfragen. Der Output im JSON-Format kommt mir entgegen. Andere Varianten habe ich nicht getestet.

    \n

    Insgesamt war mein Test-Case ja sehr einfach. Da sieht man nciht viel vom Framework. Aber mein erster Eindruck ist gut. Auch, weil man im Netz jede Menge Doku und Rat zu Scrapy findet. Und es eine "Scraping as a Servcie" Anbindung gib, mit der man seine Scripte in die Cloud schießt und der Dienst Scrapinghub sich um das regelmäßige Crawling kümmert.

    \n

    Fazit 2: Die Aufmacher

    \n

    Das Ergebnis kann man hier ansehen: Aufmacher auf deutschen News-Websites zur US-Wahl (Achtung: 4 MB groß). Die Legende zeigt die Farbcodes an. Per Mouseover sieht man den Titel. Die meisten Felder sind zum Artikel verlinkt. Zoomen funktioniert. Mobil sieht man was, aber Mouseover tut natürlich nicht.

    \n
    \n \"Screenshot\n
    Screenshot der Visualisierung
    \n
    \n\n

    Was sieht man?

    \n

    Themen: Am Freitag war die Türkei das dominante Aufmacherthema. Am Wochenende wechselte das über deutsche Politik auf die US-Wahl.

    \n

    Montag ging es um Clinton (das FBI und die E-Mails), wechselte dann zu allgemeiner/beidseitiger Berichterstattung.

    \n

    Dienstag hatten wir den ganzen Tag allgemeine (nicht auf einen kandidaten fokussierte) US-Wahl-Aufmacher, nur unterbrochen durch den "Prediger ohne Gesicht". Ab dem Abend dann kippte es schon in Richtung Donald Trump (klickte wohl besser).

    \n

    Im Laufe der Nacht gab es zunächst allgemeine Wahlticker, bis es am Morgen dann natürlich zu Trump wechselte. Um Hillary Clinton ging es erst am Abend wieder, als sie vor die Presse trat.

    \n

    Die lokalen Zeitungen aus Berlin und der Lausitz haben lokale Themen und nationale Themen als Aufmacher. Pünktlich am Mittwoch schwenkten sie zur US-Wahl. Die Morgenpost zunächst mit einer Clinton-Party in Berlin.

    \n

    So weit, so erwartbar.

    \n

    Verfügbarkeit: Alle betrachteten Angebote waren durchweg verfügbar. ZEIT Online hatte zur wichtigsten geplanten Nachrichtenlage des Jahres Aussetzer. Shame on us.

    \n

    Und sonst?

    \n\n

    Der Zieleinlauf

    \n

    In dieser Reihenfolge wurde der Sieg Trumps verkündet (konkrete Aussage, ohne "praktisch nicht mehr einholbar" uns so):

    \n\n", "url": "https://blog.thomaspuppe.de/us-wahl-aufmacher-auf-news-websites", "title": "News-Aufmacher zur Präsidentenwahl in den USA", "summary": "Was war der Aufmacher auf deutschen News-Websites rund um das Wahl-Wochenende? Und wer hat den Sieger als erster verkündet?", "date_modified": "2016-11-09T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/spof-auf-news-websites", "content_html": "

    Moderne Websites binden häufig Inhalte und Code von fremden Servern ein. Seien es Social-Media-Widgets, Tracking, oder Werbung. News-Websites verwenden all diese Dinge. Und damit ist die Verfügbarkeit und Geschwindigkeit der News-Website unter Umständen abhängig von der Verfügbarkeit und Geschwindigkeit der fremden Anbieter. Was diese Umstände sind, wie man sie erkennt, und ausschaltet, erkläre ich in diesem Artikel an einigen Beispielen.

    \n

    Was ist ein SPOF und was ist daran schlimm?

    \n

    Ein "Single Point of Failure" (SPOF) ist ein Bestandteil eines Systems, dessen Ausfall das gesamte System beeinträchtigt. Auf einer Website heißt das: ein Element der Website, das die gesamte Seite beeinträchtigt, wenn es einen Fehler hat, langsam ist, oder überhaupt nicht verfügbar ist. Was eine "Beeinträchtigung der Seite" ist, darüber kann man streiten. Im Kontext dieses Artikels ist eine News-Website beeinträchtigt, wenn man keine Inhalte lesen kann.

    \n

    Wie entsteht so etwas?

    \n

    Der SPOF kann auf einer Website an verschiedenen Stellen sitzen. Offensichtlich ist der Fall, dass der Server, der die HTML-Seiten ausliefert, nicht verfügbar ist. Dann gibt es schlicht keine Inhalte. Das ist der Worst Case, und es gibt Strategien dagegen, aber das soll hier nicht das Thema sein.

    \n

    Interessant sind die Situationen, in denen der eigentliche Inhalt — das HTML — sehr wohl verfügbar ist. Aber externe Scripte den schon geladenen Inhalt dann verzögern, beschädigen, oder blockieren. Ein nicht notwendiger Bestandteil der Seite blockiert also den wichtigen Teil. Das ist ärgerlich, weil unnötig. Diesen Fällen möchte ich mich hier widmen.

    \n

    Simulation von SPOF

    \n

    Die beschriebene Situation, dass ein externer Bestandteil — oder besser Zusatz — einer Website dieselbe lahmlegt, lässt sich künstlich simulieren.

    \n

    Steve Souders befasst sich in dem sehr guten Artikel "Frontend SPOF" mit SPOFs. Auch die Präsentation "Frontend SPOF" von Patrick Meenan beleuchtet das Problem und liefert einen tollen Trick, mit dem man einen beliebigen Server (genauer: Host) als kaputt simulieren kann. Und zwar über einen manipulierten DNS-Eintrag. Die Domain des zu testenden Hosts wird mittels der lokalen Hosts-Datei auf dem Testrechner ins Leere geführt. Man weist ihm einfach eine falsche IP-Adresse zu.

    \n

    Das kann entweder Localhost sein. In dem Fall würde der Request schnell fehlschlagen, meist mit Fehler 404. Das simuliert einen Third-Party-Server, der schnell mit einem Fehler antwortet. Oder man leitet den Request zum "Blackhole-Service" vom Webpagetest.org. Dieser Server antwortet einfach nicht auf Requests, und lässt diese somit in einen Timeout laufen. Damit simuliere ich, dass ein Third-Party-Server überlastet ist.

    \n

    Es gibt eine Extension für den Google-Chrome Browser, die auf SPOF hinweist und die jeweiligen Resourcen auch blockieren kann: SPOF-O-Matic. Damit lassen sich SPOF auch gut simulieren.

    \n

    Zur Sache: Single Points of Failure auf deutschen News-Websites

    \n

    Für meinen Test habe ich acht deutsche Nachrichten-Websites herangezogen. Grundlage für meine Auswahl waren die Zugriffszahlen laut Statista, plus die taz aus eigenem Interesse.

    \n

    Mein Vorgehen: im jeweiligen Quellcode der Seite habe ich nach den beiden Triggern für blockierendes JavaScript gesucht: document.write und <script src="" ohne async.

    \n

    Von der Liste der gefundenen Dateien habe ich diejenigen ausgenommen, die unter der Domain der Zeitung selbst laufen. Auch wenn Subdomains in der Art scripts.zeit.de oder code.bildstatic.de vielleicht schon Third-Party sind (CDN bzw Proxy-Server zu einem anderen Hoster), habe ich das als selbst gehostet gewertet. Im Sinne von "wenn das ausfällt, ist kein Fremder schuld sondern du selber".

    \n

    Die übrigen Hosts habe ich dann in meiner /etc/hosts-Datei blockiert, indem ich sie auf die IP des Webpagetest Blackhole schicke: 72.66.115.13\twww.googletagmanager.com. Das funktioniert auch in anderen Betriebssystemen.

    \n

    Test 1: IVW Zählung

    \n

    Es gibt eine Sache, die alle betrachteten Websites gemeinsam haben. Und zwar die "IVW-Zählung". Die IVW ist die "Informationsgemeinschaft zur Feststellung der Verbreitung von Werbeträgern". Sie erfasst die Reichweite vieler Medien, und dient damit nicht nur zur unabhängigen Zählung der Besucher, sondern auch zur Verteilung von Werbeerlösen.

    \n

    Da machen alle betrachteten Medien mit, und daher haben auch alle die Datei https://script.ioam.de/iam.js eingebunden. Was passiert also, wenn ich den Host script.ioam.de blockiere — oder er tatsächlich ausfällt bzw lahmt?

    \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    BildSpiegelFocusWeltSternSZtazZeit
    IVWblockiertblockiertblockiertokblockiertblockiertblockiertblockiert
    \n\n

    Alle Seiten bis auf welt.de bleiben weiß! Der Nutzer sieht keinen Inhalt, und nichts passiert solange das Zähl-Script der IWV geladen und ausgeführt wird. Wenn deren Server nicht antwortet, bleibt die News-Website also etliche Sekunden lang (bei meinen Tests im aktuellen Chrome 2 Minuten) weiß. Obwohl alle Inhalte eigentlich schon da sind.

    \n

    Auch unter besten Bedingungen hat das einen Performance-Impact. Bei meinen Tests benötigte das Script zwischen 35 und 180ms zum Laden. Jede Seite muss also immer warten. Meistens unmerklich. Erst wenn es Probleme beim Laden gibt, wird der Single Point of Failure zu einem Problem.

    \n

    Einzig die Welt lädt ihren Zählpixel asynchron und im Footer der Seite. Das heißt, auch wenn es beim Laden Probleme gibt, wird immerhin der gesamte Inhalt dargestellt.

    \n

    Test 2: Google Analytics und Google TagManager

    \n

    Als nächstes habe ich mir die Standardtools von Google angesehen. Auch sie werden von fast allen Medien genutzt.

    \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    BildSpiegelFocusWeltSternSZtazZeit
    Google Analytics-ok-okok---
    Google TagManager--ok--ok-ok
    \n\n

    Da Google die Einbindung grundsätzlich asynchron empfiehlt, stellen diese Tools keinen SPOF dar. Und folglich spielen auch alle Dinge, die über den TagManager nachgeladen werden (zum Beispiel Analytics, das dann auch nicht mit betrachtet wird), keine Rolle mehr für die Untersuchung. Dass sie die korrekt geladene Seite dennoch wieder langsam machen und bis zur Unkenntlichkeit manipulieren können, ist hier nicht das Thema.

    \n

    Test 3: Facebook und Twitter

    \n

    Einzig die Süddeutsche Zeitung bindet auf der Homepage direkt (ohne Umweg über den Google TagManager) Facebook und Twitter Krams ein. Beides aber nicht blockierend. Also kein SPOF.

    \n

    Test 4: Chartbeat

    \n

    Chartbeat ist ein Tool, mit dem man live die Besucherstöme auf einer Website anschauen kann, samt Herkunft und Interaktionen.

    \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    BildSpiegelFocusWeltSternSZtazZeit
    Chartbeat----okblockiert--
    \n\n

    Der Stern und die SZ binden Chartbeat auf ihrer Homepage ein. Und bieten ein schönes Beispiel dafür, dass der SPOF nicht zwingend durch den Drittanbieter verursacht wird, sondern man selbst durch die Art der Einbindung Einfluss darauf hat. Hat Chartbeat Probleme, reißt es die Süddeutsche mit rein. Der Stern lädt das Script asynchron, und ist damit nicht abhängig.

    \n

    Test 5: Werbung

    \n

    Die Einbindung der Werbung ist sehr unterschiedlich gelöst. Und häufig über die eigenen Server. So werden Scripte gern mal blockierend eingebunden, auch über document.write, aber über eigene Domains (Beispielsweise <script type="text/javascript" src="http://scripts.zeit.de/iqd/iqd_gzip_test.js.gz"></script>). Diese Fälle lasse ich hier außen vor.

    \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    BildSpiegelFocusWeltSternSZtazZeit
    Werbungblockiertblockiertokokokblockiertokok
    \n\n

    Wieder einmal macht es die Welt richtig. Sie benutzt den gleichen Werbeserver wie die Bild, aber bindet das Script asynchron ein (es gibt einen Feature-Toggle: offenbar werden synchron und asynchron verglichen).

    \n

    Auf verschiedene Wege verhindern auch andere Seiten den SPOF: Der Stern lädt das Werbescript asynchron und vom eigenen Server. Die Zeit blockierend vom eigenen Server. Die taz asynchron von fremden Servern.

    \n

    Bei Bild, Spiegel und Süddeutscher Zeitung leidet die Website, wenn das Werbenetzwerk langsam antwortet.

    \n

    Sonstige Beobachtungen

    \n\n

    Zusammenfassung

    \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    BildSpiegelFocusWeltSternSZtazZeit
    IVWblockiertblockiertblockiertokblockiertblockiertblockiertblockiert
    Google Analytics-ok-okok---
    Google TagManager--ok--ok-ok
    Chartbeat----okblockiert--
    Werbungblockiertblockiertokokokblockiertokok
    \n\n

    Mit ihrer Ansage, durch den Relaunch im September 2016 die schnellste News-Website in Deutschland zu werden, meinte die Welt es ernst. Best Practices wurden offenbar konsequent verfolgt, notfalls auch um den Preis von IVW-Zählungen (so meine Vermutung). Dass Lade- und Render-Zeiten von verschiedenen Punkten auf der Website mittels window.performance.mark gemessen werden, deutet auf eine genaue Beobachtung der Performance hin — was überhaupt erst einmal die Grundlage einer vernünftigen Optimierung ist.

    \n

    Die Bild hingegen verfolgt das Gegenteil. Hier gibt es viel Blocking, zum Beispiel vom Clickbait-Inhalte-Lieferanten Outbrain, und einen AdBlocker-Blocker, der bei Versagen des ThirdParty Servers zu unrecht anspringt.

    \n

    Wenn ohne Werbung auch kein Inhalt geliefert wird, kann das vielleicht verargumentiert werden — schließlich werden AdBlocker-Blocker genau mit dieser Begründung gerechtfertigt.

    \n

    Dass aber ein Tracking-Tool wie Chartbeat oder eine Bibliothek wie jQuery in der Lage ist, die Webseite lahmzulegen, ist ein leicht zu vermeidendes Desaster. Und kein theoretisches: jQuery, SoundCloud.

    \n

    One more thing

    \n

    Bitte nicht vergessen, die Tracking-Domains aus der Hosts-Datei zu entfernen. Wäre doch schade, wenn die Scripte nicht mehr geladen würden.

    \n
    # Tracker-Blocking\n127.0.0.1       www.googletagmanager.com\n127.0.0.1       www.google-analytics.com\n127.0.0.1       widgets.outbrain.com\n127.0.0.1       connect.facebook.net\n127.0.0.1       platform.twitter.com\n127.0.0.1       static.chartbeat.com\n
    \n", "url": "https://blog.thomaspuppe.de/spof-auf-news-websites", "title": "Single Point Of Failure auf News-Websites", "summary": "Kaputte Third-Party Scripts können Websites lahmlegen. Ich untersuche einige Nachrichten-Websites auf deren Robustheit gegen dieses Problem.", "date_modified": "2016-10-31T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/inodes-linux-festplatte-voll", "content_html": "

    Kürzlich fiel mein Bundestwitter-Server aus. Bei der Suche nach der Ursache habe ich einige neue Dinge über Linux gelernt, die ich hier dokumentieren möchte.

    \n

    Die Tweet-Sammel-Cronjobs auf meinem Linux (Ubuntu) Server meldeten sich, dass sie keine Daten mehr schreiben könnten. Ich kenne das Problem schon, wenn zu viele Logs und Backups herumliegen ohne aufgeräumt zu werden.

    \n

    Diesmal allerdings sagt df -h, es sei noch ordentlich Platz auf der Platte frei:

    \n
    \nSize    Used    Avail    Use%\n 77G     50G      28G    65%
    \n\n

    Nach kurzer Recherche stieß ich darauf, dass nicht nur der Platz auf der Festplatte begrenzt ist, sondern auch die maximale Zahl der Dateien. Wikipedia sagt:

    \n
    \n

    Die Anzahl der möglichen Inodes und somit der möglichen Dateien ist bei manchen Dateisystemen beschränkt; wird die Maximalanzahl erreicht, lassen sich keine weiteren Dateien anlegen.

    \n

    Wikipedia: Inode

    \n
    \n

    Also: df -i.

    \n
    \n Inodes      IUsed   IFree    IUse%\n1000000    1000000       0     100%
    \n\n

    Eine Lösung könnte darin bestehen, die Festplatte neu zu formatieren und die Zahl der möglichen Inodes höher festzulegen. Ob das bei meinem 1und1 VirtualServer so einfach möglich ist, ist fraglich. Zunächst möchte ich aber wissen, wo diese vielen Dateien liegen und woher sie kommen. Bundestwitter legt zwar viele Logfiles für Tweets an, aber diese werden regelmäßig gepackt, heruntergeladen und vom Server gelöscht. Sie erklären die volle Platte vermutlich nicht.

    \n

    Ich suche also einen Befehl, der mir zeigt, in welchen Verzeichnissen viele Inodes verbraucht werden. Google ist mein Freund:

    \n

    find /var/www/ -xdev -printf '%h\\n' | sort | uniq -c | sort -k 1 -n

    \n

    ... listet alle Folder und subfolder mit der Zahl ihrer enthaltenen Inodes auf. Was ggf. sehr lang wird — in meinem Fall knapp 2000 Einträge:

    \n
    \n1       /var/www/blog_thomaspuppe_de/assets\n[... 2000 weitere Einträge ...]\n2563    /var/www/bundestwitter/cache/www_bundestwitter_de/connectionStatistics
    \n\n

    Das hilft also nur bei überschaubarer Ordnertiefe. Besser ist dieser Befehl, der die Anzahl der Inodes in einem Verzeichnis (oder in jedem Unterverzeichnis) zeigt:

    \n

    for i in /var/www/*; do echo $i; find $i | wc -l | sort ; done (ohne "/*" für das einzelne Verzeichnis):

    \n
    \n104      /var/www/blog_thomaspuppe_de\n76266    /var/www/bundestwitter\n2        /var/www/html\n1371     /var/www/lab_thomaspuppe_de\n32748    /var/www/socialmediapolitik\n2        /var/www/thomas_works\n3        /var/www/www_meetingtimer_biz\n101      /var/www/www_thomaspuppe_de\n
    \n\n

    Es zeigt sich, dass Bundestwitter nicht der Verursacher ist. Aber mit dem genannten Befehl kann ich nun die Ordner des Root-Verzeichnisses auflisten ...

    \n
    sudo su #(um sich eine Menge \"Permission denied\" Meldungen zu sparen)\nfor i in /*; do echo $i; find $i | wc -l | sort ; done
    \n\n

    ... zeigt, dass die allermeisten Inodes in /var verbraucht werden. (Warum in proc so viel los ist, wird Gegenstand einer anderen Untersuchung sein.)

    \n
    \n16        /home\n22271     /proc\n29687     /usr\n953965    /var\n
    \n\n

    Von hier aus kann ich mich mit dem Befehl for i in /var/*; do echo $i; find $i | wc -l | sort ; done usw. fortbewegen.

    \n
    \n662       /var/log\n836952    /var/spool\n112120    /var/www\n
    \n\n

    In /var/spool liegt also sehr viel Zeugs. Dies ist das "Verzeichnis für abzuarbeitende Warteschlangen (Druckaufträge, E-Mail-Versandaufträge …)"\n(Wikipedia).

    \n

    Mit der bekannten Technik taste ich mich weiter voran und stoße auf ein Verzeichnis, in dem fast alle Dateien liegen:

    \n
    836836    /var/spool/postfix/maildrop
    \n\n

    Das sind also felgeschlagene Versuche des Systems, E-Mails zu versenden. Stichproben (cat /var/spool/postfix/maildrop/D7E6A90D4CA9) ergeben, dass meine Cronjobs versuchen, Mails zu versenden.

    \n

    Ich habe also meinen Übeltäter. Nun geht es ans Aufräumen.

    \n

    Die erste Maßnahme, für den Seelenfrieden, ist natürlich

    \n
    sudo rm -rf /var/spool/postfix/maildrop/*\nbash: /bin/rm: Argument list too long
    \n\n

    Eine schnelle Suche führt mich auf`sysadminslife.com, wo folgender Befehl vorgeschlagen wird:

    \n
    find -type f -print0 | xargs -0 rm
    \n\n

    An der Stelle möchte ich Explainshell erwähnen. Eine Website, die Shell-Befehle aufdröselt und die einzelnen Bestandteile erklärt. http://explainshell.com/explain?cmd=find+-type+f+-print0+%7C+xargs+-0+rm

    \n

    Ta dah:

    \n
    $ ls /var/spool/postfix/maildrop | wc -l\n14\n\n$ df -i\nInodes      IUsed     IFree    IUse%\n1000000    143491    856509      15%\n
    \n\n\n

    Es bleibt die Frage: Wie vermeide ich, dass die Ausführung der Cronjobs Mails erzeugt (die dann in der Spool Queue landen)?

    \n

    http://www.cyberciti.biz/faq/disable-the-mail-alert-by-crontab-command/:

    \n
    \n

    The crontab command is used to maintain crontab files for individual users. By default the output of a command or a script (if any produced), will be email to your local email account.

    \n
    \n

    Die einfachste Methode, dies zu verhindern, ist die Zeile MAILTO="" an den beginn der Crontab-Datei (crontab -e) zu schreiben. Damit werden alle Cron-Mails aus diesr Datei unterdrückt. Alternativ kann man auch pro Zeile den Versand von Mails verhintern durch den Output nach /dev/null: 0 1 5 10 * /path/to/script.sh > /dev/null.

    \n

    Kleines Extra: In Zukunft möchte ich eine Warnung erhalten, wenn der Plattenplatz (oder die Inodes) zur Neige gehen. Der Test dafür ist leicht geschrieben (siehe oben), und einen Mechanismus zur Benachrichtigung habe ich schon für andere Zwecke. Und zwar benutze ich den Service und die App von Pushover: Simple Notifications for Android, iOS, and Desktop und Dead Man's Snitch — A dead simple cron job monitoring tool.

    \n", "url": "https://blog.thomaspuppe.de/inodes-linux-festplatte-voll", "title": "Inodes im Linux: Volle Festplatte, obwohl noch Platz ist?", "summary": "Kürzlich fiel mein Bundestwitter-Server aus. Bei der Suche nach der Ursache habe ich einige neue Dinge über Linux gelernt, die ich hier dokumentieren möchte.", "date_modified": "2016-10-18T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/front-trends-2016-warsaw-day-three", "content_html": "

    Summarizing the third day presentations of the Front Trends 2016 conference in Warsaw, including the Nyan cat, pixel bonding, modern(izer) tools, CSS engineering principles, Origami, offline pages, real programmers and Trojan CVs.. A speakers list can also be found on 2016.front-trends.com/speakers/.

    \n
    \n \"Photograph\n
    If an interface is more human, it allow us to be more human.
    —Tammie Lister
    \n
    \n\n\n

    Opening by Staś Małolepszy

    \n

    Trigger a bit of interest, be available to help. And many cool things will happen.

    \n\n

    Exploring the Universal Library by Szymon Kalinski

    \n

    Szymon showed some work he had done. It looked really cool, but honestly I don't know what he wanted to tell us ;-)

    \n

    Pixel Bonding by Tammie Lister

    \n

    People long for connection with others. By creating emotional UX and positive experience, you create trust and users will come back.

    \n

    As a user, you recognize if an interface (the people behind it) care for you, or simply don't care. For example, friends don't let friends make mistakes.

    \n

    Even little UI eastereggs can be incredibly delightful. But don't get creepy. Nobody likes stalker interfaces.

    \n
    \n

    If an interface is more human, it allow us to be more human.

    \n
    \n\n

    Modern Websites for the Modern Web by Patrick Kettner

    \n

    Use of tools inside the new version of the Modernizr website and downloader: Service workers, clipboard.js, download binary blobs of service-worker-generated files, decent markup, custom Modernizr versions via npm install,

    \n
    \n

    Make cool stuff. Don't be afraid to use new shiny things.

    \n
    \n\n

    CSS for Software Engineers for CSS Developers by Harry Roberts

    \n

    Harry applies software engineering principles to CSS development: DRY, Single Source of Truth, Single Responsibility, Seperation of Concerns(!), Immutability, Open/Closed Principle,

    \n

    Good and funny talk with lots of code examples. One of the conference highlights!

    \n
    \n

    There are 6,442,450,944 possible combinations of s sandwich at Subways. And all taste the same.

    \n
    \n\n

    Can't you make it more like Bootstrap? by Alice Bartlett

    \n

    Subtitle: "Considerations for building front end systems". Alice leads a team whose aim is to unify and DRY frontend work at the Financial Times (project "Origami".

    \n

    On the different FT branded websites, there are a lot of things which have been implemented differently, even on atomic scale (4 implementations of a close button which looks the same, 3 different Twitter icons).

    \n

    First step was to use components to keep HTML, CSS, JS for one thing in one place (this seems to be a hot topic this year).

    \n

    The websites also have different tooling, which should be unified so all websites (implemented in different languages) can use the same components.

    \n

    You can move the JS to bower packages, for example. But for (customizable) HTML templates or CSS ths is more complicated. So they implemented a build service that "everyone" can use to request Origamis CSS, JS and HTML. People liked using it, but the documentation was not good. -> I wish it was more like bootstrap.

    \n

    And: they created a documentation styleguide(!), whcih includes a communication plan for new releases, incident reports, ...

    \n
    \n

    Documentation is not complicated. It is just hard.

    \n
    \n\n

    Building an Offline Page for The Guardian Oliver Joseph Ash

    \n

    The Guardian's Native app caches articles offline, to provide content even if the user (or the servers) are offline. The website is not available offline. Oliver built a prototype for an offline page in less than a day! And provided a detailes step-by-step walkthrough on how it works.

    \n\n

    The Myth of the "Real JavaScript Programmer" by Brenna O'Brian

    \n

    Great talk about what seems to be expected from "good programmers" today, and what really is.

    \n

    We don't need to know everything, finding a niche is cool. This is not a test. Real developers use resources wisely. Code has not to be perfect or even correct on the first try (or at all). Learn from mistakes. Show your work, not only your finished and polishes products. Encourage each other. We don't have to code all the day and everyday. Being a developer does not have to kill your hobbies (or parentship).

    \n
    \n

    My biggest professional development was admitting what I don't know.\n— Jessica Rose

    \n
    \n\n

    Don't hate the player, hate the game by Tim Holman

    \n
    \n

    please be less ridiculous

    \n
    \n\n
    \n

    Creativity is the best motivator.\nProjects are a journey, be fluid.\n— Tim Holman

    \n
    \n", "url": "https://blog.thomaspuppe.de/front-trends-2016-warsaw-day-three", "title": "Front Trends 2016 in Warsaw: Day 3", "summary": "Summarizing the third day presentations of the Front Trends 2016 conference in Warsaw, including the Nyan cat, pixel bonding, modern(izer) tools, CSS engineering principles, Origami, offline pages, real programmers and Trojan CVs.", "date_modified": "2016-05-20T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/front-trends-2016-warsaw-day-two", "content_html": "

    Summarizing the second day presentations of the Front Trends 2016 conference in Warsaw. Todays talks were about components, digital afterlife, tiny JS, typography, knitting and frontend tooling. A speakers list can also be found on 2016.front-trends.com/speakers/.

    \n
    \n \"beautiful\n
    beautiful Warsaw
    \n
    \n\n\n

    First Class Styles by Mark Dalgleish

    \n

    The first talk was very technical, but on an abstract level. Todays tooling handles HTML, Images, JS, CSS seperately (seperate tasks in Grunt or Gulp), and the connecteion between them has to be maintained by the developer (by using the same names e.g.). It would be better to have everything which belongs to one component in one place (including CSS which might be written in JS and served inline). Another hot thing: server-side rendering of the HTML. (sic!)

    \n

    My instant reaction: WTF? This is wrong on so many levels! But Mark presented good arguments (the strongest: it works in production on a big app) that I will take some time reading his blog posts.

    \n\n

    Our eternal digital afterlife by Alberta Soranzo

    \n

    Peoples social media profiles are still available when they pass away. Alberta Soranzo explained what tools and strategies (not yet dead) people and their relatives can do. Very moving and widespread talk.

    \n
    \n

    We need to design offboarding.

    \n
    \n\n

    Demo Reel & Tiny JavaSCript by Mathieu Henry

    \n

    The creative coder and code golfer did exactly this: vanilla JS coding of a cool visualization with sound in just a few lines. First some dirty tricks, then some jawdropping live coding.

    \n\n

    Syntax Highlight Everything by Kenneth Ormandy

    \n
    \n

    Syntax highlighting is designed.

    \n
    \n

    Starting with features of syntax highlighting and code fonts, Kenneth explains some typographic features (kerning, ligatures, case-sensitive fonts, small caps) and how to use them correctly in your CSS. The best part: you can just copy and use it from utility-opentype.kennethormandy.com.

    \n\n

    Lightning Talks

    \n

    Marciej Korsan: Frontend is an art (an ukulele song!!!!).

    \n

    Ramon Vicor: Redux principles in plain JavaScript. A talk about the principles of Redux. There is a blog post which covers the same topic: Tic-Tac-Toe.js: redux pattern in plain javascript.

    \n

    Keith: Tech Addiction in Aeroplane Mode: A coping strategy. tl;dr: Have fun with yourself and unconscious passengers!

    \n

    Michael Hans, Kamil Zasada: API First. Customer says "We need something. Dont know what. Can you show something this week?" RESTful API TODO. API contracts are created before any actual code is written. This way, QA can start running tests against it while DEV is programming. Also, FE can programm a SPA against dummy/mockup endpoints.

    \n

    Rachel Nabors: Creatures of the Deep, featuring great illustrations from the DevToolsChallenger.com !

    \n

    Computer Assisted Arts and Crafts by Mariko Kosaka

    \n

    After hacking her knitting mashine, Mariko Kosaka started experimenting with electric circuits. On stage, she showed cool demos with metallic tape on paper, pen lead as resistor, yarn as dimmer switch, ... and blowing bulbs :-)

    \n\n

    Taking over the web platform with Angular 2 by Todd Motto

    \n

    A walk through Angular 2 architecture, (web) components, dataflow.

    \n

    Panel Discussion: The State of Front-End Tooling

    \n

    It is hard to cover a discussion, so I just post a few questions to think about.

    \n\n

    ...and a few thoughts from the panel:

    \n\n
    \n

    Next read: Front Trends 2016 in Warsaw: Day 3

    \n", "url": "https://blog.thomaspuppe.de/front-trends-2016-warsaw-day-two", "title": "Front Trends 2016 in Warsaw: Day 2", "summary": "Summarizing the second day presentations of the Front Trends 2016 conference in Warsaw. Todays talks were about components, digital afterlife, tiny JS, typography, knitting and frontend tooling.", "date_modified": "2016-05-19T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/front-trends-2016-warsaw-day-one", "content_html": "

    Summarizing the first day presentations of the Front Trends 2016 conference in Warsaw. Covering pranks, animations, static sites, RxJS, performance, and leadership. A speakers list can also be found on 2016.front-trends.com/speakers/.

    \n
    \n \"The\n
    The venue: Warsaws Star Club
    \n
    \n\n

    Opening by Tim Holman

    \n\n

    Keynote: The Web in Motion by Rachel Nabors

    \n

    Rachel talked about Apps vs Websites, and how Apps often provide the same content, but look much nicer because transitions between states are animated. If there is only old state -> new state, the users brain needs to do heavy interpolation work. If the transition is animated, there is less cognitive workload on the user. So the goal is to eliminate sudden visual changes. Rachel takes two excurses to dark patterns/ads and technical history of animations (Flash, SMIL. CSS Animations) and closes with the Web Animation API.

    \n
    \n

    The difference between web and native starts to blur. The Web is moving forward.

    \n
    \n

    Lovely presentation slides with selfmade comic drawings!

    \n\n

    Static Sites go all Hollywood Phil Hawksworth

    \n

    Phil Hawksworth speaks about the advantages of Static Site Generators and the situations where they are a good choice. Embedded in Praise of simplicity and "Short Stack development". Key argument: SSG moves complexity and potential failures away from the user, because if things go wrong, it is in the building step and not while actually serving a user's request.

    \n
    \n

    Simplifying is not dumbing down. It is focussing on what matters.

    \n
    \n\n

    Working with the web and the future by Sally Jenkinson

    \n

    Sally Jenkinson had a rather abstract talk about how our decisions of today affect the future. In positive ways (visionary user interfaces) and negative ways (technical debt). I learned about the "White Elephant" analogy.

    \n
    \n

    The future is a spectrum, not a single point in time. Think of the next sprint, the next month, the next year, the next ten years – at the same time.

    \n
    \n\n

    Lightning Talks

    \n\n

    RxJS – Destroy the state machine by Stenver Jerkku

    \n

    A technical talk with lots of code samples. In short: RxJS is like "Promises on steroids", providing more possibilities and code which is easier to read. It also has brilliant testing tools and is available in various languages (Scala, Ruby, Java, ...).

    \n\n

    Untangle your code with yield by Staś Małolepszy

    \n

    Very technical talk, completely in code. This was fascinating, in the sense of Clarke's law. I will need to check and work-through the presentation. Buzzwords: ES5, function generators, yield, iterators.

    \n

    Staś did some live coding (live-uncommenting, actually) with split-screen of code and results. Not sure if this setting is also part of the demo repo.

    \n\n

    Once more with feeling by Tim Kadlec

    \n

    Tim Kadlec held yet another performance talk, making fun of the fact that this topic has been talked about a lot in the last years. ("There's a secret club of people who need to say 'The fastest Request is no Request' two times each year.")

    \n

    Measurable performance improvement (DNS-prefetch, preconnect, preload, prerender, async, defer, inline critical CSS) is important. If there is nothing left to speed up, you can increase the perceived speed (skeleton rendering, changing status text on progress bars). And sometimes you need to add an artificial delay, because people do not trust a credit card validation which runs too fast.

    \n
    \n

    Everything on your page should have a value, because everything has a cost.

    \n
    \n\n

    Leadership in an Ever-Changing Industry by Meri Williams

    \n

    Meri Williams cursed more than other speakers while explaining what makes a good geek manager. in short: giving/explaining/allowing people PURPOSE, AUTONOMY, MASTERY and INCLUSION. And keep developing your own mastery in TECH, TEAM and TOOLS. People need to say "yes" to these questions: "Am I expected? Am I respected? Can I be myself and be successfull here?"

    \n
    \n

    Be a bulldozer and a cheerleader. Get shit out of their way and tell them they're awesome.

    \n
    \n\n
    \n

    Next read: Front Trends 2016 in Warsaw: Day 2

    \n", "url": "https://blog.thomaspuppe.de/front-trends-2016-warsaw-day-one", "title": "Front Trends 2016 in Warsaw: Day 1", "summary": "Summarizing the first day presentations of the Front Trends 2016 conference in Warsaw. Covering pranks, animations, static sites, RxJS, performance, and leadership.", "date_modified": "2016-05-18T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/single-page-apps-minimieren-mit-gulp", "content_html": "

    Unter meetingtimer.biz habe ich eine kleine Web-App. CSS und JavaScript sind so gering, dass ich keine seperaten Requests starten möchte. Gleichzeitig sollen Less und JSLint genutzt werden. Ein wunderbares Einsatzgebiet für Gulp!

    \n

    Das Projekt

    \n

    Unter der Domain meetingtimer.biz betreibe ich eine kleine Website. Ein Nachmittagsprojekt, das während eines Meetings anhand der Teilnehmerzahl die Kosten des Meetings hochzählt und anzeigt.

    \n

    Die Seite ist einfach und benötigt nur wenig JavaScript und CSS. Dem Performance-Junkie in mir hat es gestört, dass für 2,8 KB CSS (nicht minimiert und unkomprimiert) extra ein Request gestartet werden muss. Weil ich außerdem mit Gulp experimentieren wollte, nahm ich diese Seite zum Anlass.

    \n

    Herausforderungen

    \n
      \n
    1. Möglichst wenige Requests im Live-betrieb ... es wäre schade, für 6 kB JavaScript und 3 kB CSS zwei Roundtrips zu starten.
    2. \n
    3. Beim Programmieren möchte ich dennoch nicht alles in einer großen HTML Datei horten.
    4. \n
    \n

    Lösung mittels Gulp

    \n

    Grundlage bei der Arbeit mit CSS und JS sind natürlich die klassischen Werkzeuge wie less, minify-css, slint, uglify und concat. Die sind in jedem Gulp-Tutorial enthalten und sollen hier nicht behandelt werden. Spannend für meine Zwecke sind zwei weitere Tasks:

    \n
    var replace = require('gulp-replace-task'),\n    cleanhtml = require('gulp-cleanhtml');
    \n\n

    Deren Benutzung ist simpel und komfortabel: Im HTML setzt man Platzhalter:

    \n
    ...\n<style type=\"text/css\">@@cssStyles</style>\n...</head>\n<body>...\n<script type=\"text/javascript\">@@jsScript</script>\n</body>
    \n\n

    Und lässt diese im Gulp Task durch den Inhalt der Dateien ersetzen:

    \n
    gulp.task('replace', function () {\n    return gulp.src('./index.html')\n        .pipe(replace({\n            patterns: [\n                {\n                    match: 'cssStyles',\n                    replacement: fs.readFileSync('./web/assets/css/style.css', 'utf8')\n                },\n                {\n                    match: 'jsScript',\n                    replacement: fs.readFileSync('./web/assets/js/all.min.js', 'utf8')\n                },\n                {\n                    match: 'timestamp',\n                    replacement: currentDatetime\n                }\n            ]\n        }))\n        .pipe(cleanhtml())\n        .pipe(gulp.dest('./web'));\n});
    \n\n

    Ich habe auch mit "gulp-inline-source" experimentiert, aber der hat den JavaScript Code zerstört. Replace macht dasselbe und kann außerdem banutzt werden um andere Platzhalter zu erstezen - z.B. den aktuellen Timestamp, eine Release-Nummer, Dateigrößen, Subtemplates, MD5-Hashes von Downloads oder was auch immer.

    \n

    Die Datei aus "src" wird gelesen und die Vorkommen des Suchpatterns ersetzt. Danach entfernt "cleanhtml" alle Whitespaces und "dest" schreibt die Datei in ihren Zielordner (das VErzeichnis was dann deployed wird). So einfach geht es.

    \n

    Das Ergebnis ist eine 4,4 kB große HTML Datei (die damit einen Bruchteil des 27,7 kB großen Touch Icons hat). Zu sehen unter meetingtimer.biz. Die Quellen, .gulpfile und das Ergebnis sind bei GitHub zu finden: https://github.com/thomaspuppe/meetingtimer.biz

    \n

    Potential

    \n\n

    Das kommt später. Das wäre auch ein guter Anreiz, die Minimierung auszureizen. Wieso sollte ein simpler Timer KB groß sein, wenn man Minecraft in 1024 bytes oder Wolfenstein 2D in 2K programmieren kann?

    \n", "url": "https://blog.thomaspuppe.de/single-page-apps-minimieren-mit-gulp", "title": "Eine Single-Page-App minimieren mit Gulp", "summary": "Unter meetingtimer.biz habe ich eine kleine Web-App. CSS und JavaScript sind so gering, dass ich keine seperaten Requests starten möchte. Gleichzeitig sollen Less und JSLint genutzt werden. Ein wunderbares Einsatzgebiet für Gulp!", "date_modified": "2015-01-23T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/twitterdaten-sammeln-mit-aws", "content_html": "

    Um Tweets zu einem aktuellen Thema zu sammeln, muss man die Twitter Streaming API mitschneiden. In 20 Minuten ist dafür ein kostenloses always-online System aufgesetzt.

    \n

    Bei kurzen Events (TV-Duell vor der Bundestagswahl) habe ich die Sammlung von Tweets einfach auf meinem lokalen Rechner gemacht. Für die anstehenden Feierlichkeiten zum #Mauerfall soll die Sammlung mehrere Tage dauern. Mein Rechner soll dabei aber nicht im Dauerbetrieb laufen. Daher habe ich einfach eine AWS Instanz hochgefahren und lasse die die Arbeit tun. Zum Mitschneiden des Streams dient diesmal auch nicht die PHP-Lib Phirehose, sondern das Python-Tool Tweepy.

    \n

    Dieser Artikel beschreibt, wie man mit wenigen Klicks, 9 Konsolenkommandos und 40 Zeilen Code einen automatischen und kostenlosen Twitter-Mitschnitt anfertigt.

    \n

    Out of scope:

    \n\n

    Schritt 1: AWS Instanz erstellen und hochfahren

    \n

    Für das Sammeln reicht die kleinste EC2 Instanz von AWS (t2.micro), die kostenlos ist. Der kostenlose Festplattenspeicher kann bis zu 30 GB reichen, je größer desto besser wird auch die IO Performance sein. Zugriff braucht man nur per SSH, daher lege ich kein eigenes VPC an.

    \n

    Mit meinem (oder einem neu generierten) Key melde ich mich via SSH auf der Instanz an.

    \n

    Schritt 2: Twitter-Collecor installieren und anwerfen

    \n
    // Update\n$ sudo apt-get update\n$ sudo apt-get upgrade\n\n// PIP (Python Paketmanager) und Tweepy (Twitter API Lib) installieren\n$ sudo apt-get install python-pip\n$ sudo pip install tweepy\n\n// Python-File erstellen\n$ mkdir tweets_mauerfall\n$ cd tweets_mauerfall/\n$ nano collect.py
    \n\n

    Die genaue Code-Quelle finde ich gerade nicht mehr. Eine Suche nach "tweepy save tweets to file" brignt aber mehrere Quellen zutage. Mein Code hatte ursprünglich eine MongoDB als Datenspeicher, das habe ich gegen die einfache Datei ausgetauscht.

    \n

    Der Quellcode von collect.py:

    \n
    import json\nimport tweepy\n\nconsumer_key = \"\"\nconsumer_secret = \"\"\naccess_key = \"\"\naccess_secret = \"\"\n\nauth = tweepy.OAuthHandler(consumer_key, consumer_secret)\nauth.set_access_token(access_key, access_secret)\napi = tweepy.API(auth)\n\n# initialize blank list to contain tweets\ntweets = []\n# file name that you want to open is the second argument\nsave_file = open('tweets.json', 'a')\n\nclass CustomStreamListener(tweepy.StreamListener):\n    def __init__(self, api):\n        self.api = api\n        super(tweepy.StreamListener, self).__init__()\n\n        self.save_file = tweets\n\n    def on_data(self, tweet):\n        self.save_file.append(json.loads(tweet))\n        print tweet\n        save_file.write(str(tweet))\n\n    def on_error(self, status_code):\n        return True # Don't kill the stream\n\n    def on_timeout(self):\n        return True # Don't kill the stream\n\nsapi = tweepy.streaming.Stream(auth, CustomStreamListener(api))\nsapi.filter(track=['mauerfall', 'fotw25', 'mauerspecht', 'berlinwall', 'fallofthewall25'])
    \n\n

    Und das Ganze muss dann nur noch gestartet werden:

    \n
    // Stumm im Hintergrund ausführen lassen\n$ nohup python collect.py > /dev/null &\n\n// anhand der ausgegebenen Prozessnummer (oder via top suchen) kann ich den Prozess später wieder killen.\n\n// Um die aktuelle Zahl der Tweets zu verfolgen, lasse ich mir (periodisch) die Anzahl der Zeilen in der json-Datei ausgeben:\n$ while true; do ( wc -l tweets.json ; sleep 5 ) done;
    \n\n\n

    Fertig. Ein Update zur Zuverlässigkeit und eine Auswertung der Tweets gibt es dann später hier im Blog.

    \n

    Update zur Zuverlässigkeit:

    \n

    Das lief nicht so toll. Die Sammlung ist mehrfach abgebrochen, ohne dass Infos im Error-Log gelandet sind. Ob der Server mit dem Speichern nicht nachgekommen ist, oder ob die Streaming API Aussetzer hatte, lässt sich nicht mehr feststellen. Lesson learned: während der Sammlung sollte ein externer "Dienst" (Cronjob, Daemon, whatever) prüfen ob das Programm noch läuft und falls nötig neu starten. Sicher lässt sich auch das

    \n

    Auswertung der Tweets:

    \n

    Insgesamt wurden 71180 Tweets erfasst. Da die Aufzeichnung ausgerechnet am Abend des 8.11. abbrach und ich bis Spätabends am 9.11. nicht online war, fehlen alle Tweets vom Tag des Mauerfalls. Aber immerhin gibt es die kleine Sammlung der Tage zuvor.

    \n

    Die Datei lässt sich via scp vom AWS Server herunterladen, dann kann man die Maschien stoppen oder terminieren.

    \n

    Die Tweets, die ein Foto enthalten und mit Geodaten versehen waren, habe ich auf einer Karte dargestellt: Karte, Blog-Beitrag.

    \n", "url": "https://blog.thomaspuppe.de/twitterdaten-sammeln-mit-aws", "title": "Twitterdaten sammeln mit AWS", "summary": "Um Tweets zu einem aktuellen Thema zu sammeln, muss man die Twitter Streaming API mitschneiden. In 20 Minuten ist dafür ein kostenloses always-online System aufgesetzt.", "date_modified": "2014-11-06T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/twitterdaten-mappen-mit-leaflet", "content_html": "

    Meine Sammlung von Tweets zum #Mauerfall via Amazon AWS hat trotz Ausfall über 70.000 Tweets ergeben. Nun sollen diejenigen mit Fotos und Geo-Daten auf einer Karte angezeigt werden.

    \n

    In der JSON Datei, die dabei angelegt wird, ist jeder Tweet in einer Zeile erfasst. Das macht das Zählen leicht, und auch das Aussortieren usw.

    \n
    //Alle Tweets in einer großen Datei:\n$ wc -l tweets.json\n// 71180 tweets.json\n\n// Filtern der Tweets, die Medien enthalten (was zurzeit nur Fotos sein können)\n$ grep media_url tweets.json > tweets_media.json\n$ wc -l tweets_media.json\n// 32330 tweets_media.json\n\n// Filtern der Tweets, die Koordinaten enthalten\n$ grep -v \"\\\"coordinates\\\":null\" tweets_media.json > tweets_media_coordinates.json\n$ wc -l tweets_media_coordinates.json\n// 249 tweets_media_coordinates.json
    \n\n

    Die Datei enthält nun (erstaunlich wenige) Tweets, die aber mit allen Daten. Da für die Visualisierung nicht alles benötigt wird, extrahiere ich nur die nötigen Daten. Dazu habe ich ein kleines Python-Script geschrieben. Die Code-Qualität sei mir verziehen, das waren jetzt meine ersten Zeilen Python überhaupt.

    \n
    import json\n\nreadFile = open('tweets_media_coordinates.json')\nlines = readFile.readlines()\nreadFile.close()\n\noutputArray = []\n\nfor lineString in lines:\n    try:\n        lineObject = json.loads(lineString)\n        outputObject = {}\n        outputObject['id_str'] = lineObject['id_str']\n        outputObject['text'] = lineObject['text']\n        outputObject['screen_name'] = lineObject['user']['screen_name']\n        outputObject['text'] = lineObject['text']\n\n        for entityKey in lineObject['entities'] :\n            if entityKey == 'media' :\n                for media in lineObject['entities']['media'] :\n                    if media['type'] == 'photo' :\n                        outputObject['media_url'] = media['media_url']\n\n        outputObject['coordinates'] = lineObject['coordinates']['coordinates']\n\n        outputArray.append(outputObject)\n    except:\n        pass\n\noutputString = json.dumps(outputArray, separators=(',',':'), indent=2)\n\nwriteFile = open('tweets_media_coordinates_short.json','w')\nwriteFile.write('{\"tweets\":' + outputString + '}')\nwriteFile.close()
    \n\n

    Das Ergebnis ist eine JSON Datei mit den Tweets, Foto-URLs und Koordinaten: http://www.thomaspuppe.de/lab/mauerfall-tweets/data/tweets_media_coordinates_short.json.

    \n

    Diese Datei ist die Daten-Grundlage für die Visualisierung. Auch dafür habe ich fix was aus dem Netz gezogen: den Leaflet-Generator von Moritz Klack. Via npm lädt man das halbe Internet herunter, hat aber dafür eine out-of-the-box Map Anwendung. Die eigentlich benötigten Dateien sollte man sich dann fürs nächste mal zurechtlegen.

    \n

    In die gegebene Karte wmüssen nur noch ein paar Zeilen JavaScript eingefügt werden, und schon sind die Punkte auf einer schönen Karte hinterlegt.

    \n
    var markerIcon = L.divIcon({className: 'my-div-icon'}), // stylen via CSS!\nmarkerOptions = {\n    'clickable': true,\n    'keyboard': false,\n    'icon': markerIcon\n};\n\ndocument.addEventListener('DOMContentLoaded', function() {\n    var httpRequest = new XMLHttpRequest()\n    httpRequest.onreadystatechange = function () {\n        if (httpRequest.readyState === 4) {\n            if (httpRequest.status === 200) {\n                var data = JSON.parse(httpRequest.responseText);\n                for (var i=0; i < data['tweets'].length; i++) {\n                    var currentData = data['tweets'][i];\n                    markerOptions['alt'] = currentData['media_url'];\n                    L.marker(\n                        [currentData['coordinates'][1], currentData['coordinates'][0]],\n                        markerOptions)\n                    .addTo(map);\n                }\n            }\n        }\n    }\n    httpRequest.open('GET', 'data/tweets_media_coordinates_short.json')\n    httpRequest.send()\n});
    \n\n

    Zu beachten: die Koordinaten bei Twitter sind als Lon,Lat gespeichert, leaflet benötigt aber Lat,Lon. Daher die Rochade beim Anlegen der Marker.

    \n

    Und hier das Ergebnis:

    \n
    \n \n \"Eine\n
    http://lab.thomaspuppe.de/mauerfall-tweets/
    \n
    \n
    \n", "url": "https://blog.thomaspuppe.de/twitterdaten-mappen-mit-leaflet", "title": "Twitterdaten mappen mit Leaflet", "summary": "Meine Sammlung von Tweets zum", "date_modified": "2014-11-19T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/link-unterstreichung", "content_html": "

    Link-Unterstreichung bei großen Schriftarten wirkt schnell klobig. Mit ein paar Zeilen CSS lässt sich die Seite optisch aufwerten und die Lesbarkeit erhöhen.

    \n

    Als Freund “großer” Schrift auf Websites stelle ich fest, dass die Unterstreichung von Links ab 20 Pixeln Schriftgröße (bei meinem Setup von font-family: Georgia, "Times New Roman", Times, serif;) recht breit ist. Das stört auch die Lesbarkeit des Textes — zumindest im Vergleich zu einer Variante mit schmalerer Unterstreichung. Hier im Blog kommt das nicht zum Tragen. Groß sind nur die Überschriften, die eine dicke Unterstreichung vertragen. Der Fließtext ist kleiner als 20px und hat daher moderate Unterstreichung. Auf meiner Website mit vielen Links störte die Unterstreichung schon mehr (siehe Screenshot):

    \n
    \n \"Abb.\n
    Abb. 1: Link-Unterstreichung nativ und trickreich
    \n
    \n\n

    Kürzlich bin ich auf einen Artikel von Marcin Wichary gestoßen, der dieses Problem löst: “Crafting link underlines on Medium”. In dem sehr lesenswerten Artikel wird auf pro und contra von Link-Unterstreichung im Allgemeinen und verschiedenen Techniken im Speziellen eingegangen. Am Ende schaut die Lösung so aus:

    \n
    a, a:link, a:visited {\n    color: #333;\n    text-decoration: none;\n    position: relative;\n    text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;\n    background-image: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 2px, #333333 2px, #333333 3px, rgba(0, 0, 0, 0) 3px);\n}\n\n@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) {\n    a {\n        background-image: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 2px, #333333 2px, #333333 2.5px, rgba(0, 0, 0, 0) 2.5px);\n    }\n}\n\na:hover, a:focus {\n    background-image: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 2px, #666 2px, #666 3px, rgba(0, 0, 0, 0) 3px);\n}\n\n@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) {\n    a:hover, a:focus {\n        background-image: linear-gradient(to top, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0) 2px, #666 2px, #666 2.5px, rgba(0, 0, 0, 0) 2.5px);\n    }\n}
    \n\n

    Trick 1: Links bekommen ein Hintergrundbild, das keine Datei oder DataURI nutzt, sondern Gradients.

    \n

    Trick 2: Ein Text-Shadow, unterbricht die Linie bei Buchstaben mit Unterlänge.

    \n

    Achtgeben muss man auf Links, die nicht nur Text enthalten. Wird z.B. ein Bild verlinkt, erhält dieses auch eine solche Unterstreichung. Das muss dann mittels “background-image:none;” unterbunden werden — und zwar am Link statt am Bild. Bei großen Seiten kann es hier haarig werden und der Nutzen rechtfertigt den Aufwand vielleicht nicht mehr. Dasselbe gilt für Links im Button-Stil usw.

    \n", "url": "https://blog.thomaspuppe.de/link-unterstreichung", "title": "Perfekte Link-Unterstreichung", "summary": "Link-Unterstreichung bei großen Schriftarten wirkt schnell klobig. Mit ein paar Zeilen CSS lässt sich die Seite optisch aufwerten und die Lesbarkeit erhöhen.", "date_modified": "2014-05-06T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/gregor-gysi-retweets", "content_html": "

    Der Fraktionsführer der Linken im Deutschen Bundestag, Gregor Gysi, führt in den letzten Wochen die Retweet-Charts von Bundestwitter an. Ist das systematische Unterstützung durch Wenige? Oder eine breite Basis?

    \n

    Grundlage der Analyse sind alle Tweets von Gregor Gysi aus dem Jahr 2014 (Stand 16.02.). Diese habe ich nach Retweet-Häufigkeit sortiert, und die Top 25 dann genauer untersucht. Von jedem Tweet wurden die Profildaten aller Retweeter geladen, und die Daten in einer Tabelle zusammengeführt (Link führt zur Tabelle am Fuß dieses Posts).

    \n

    Insgesamt haben 920 verschiedene Profile die Top-Tweets von Gregor Gysi weiterverbreitet. Manche Accounts retweeten fast jeden dieser Beiträge:

    \n
      \n
    1. Free Chelsea Manning (politlinkx) schickte 23 Retweets an 643 Follower.
    2. \n
    3. Demokratie Report (ProDemokratie) schickte 21 Retweets an 318 Follower.
    4. \n
    5. ✡ TS_Palm ✡ (TS_Palm) schickte 15 Retweets an 646 Follower.
    6. \n
    7. Monique (Queuey) schickte 13 Retweets an 58 Follower.
    8. \n
    9. NimA (Nima_1990) schickte 12 Retweets an 29 Follower.
    10. \n
    \n\n

    Mit @politlinkx und @ProDemokratie sind "Gruppen" (Begriff als Gegensatz zu einzelnen Privatpersonen) an der Spitze der Unterstützer. Die @linksfraktion ist mit 6 Retweets an Position 19. Mit jeweils 1 Retweet sind @katjakipping, Petra_Sitte_MdB, MWBirkwald und @DerRostocker von der Linksfraktion dabei. Andere MdB haben die untersuchten Tweets nicht weiterverbreitet.

    \n

    Die große Unterstützung kommt aber aus einer breiten Basis. 668 Personen haben 1 Retweet gemacht, 140 Personen 2 Retweets. Es folgen 45 Personen mit 3 Retweets, 38 Personen mit 4 Retweets und 20 Personen mit 5 Retweets. Mehr als 10 Retweets hatten nur die 5 oben genannten Accounts.

    \n

    1 Retweet: 668 Personen. 2 Retweets: 140 Personen. 3 Retweets: 45 Personen.4 Retweets: 38 Personen. 5 Retweets: 20 Personen. 6 Retweets: 4 Personen. 7 Retweets: 6 Personen. 8 Retweets: 8 Personen. 10 Retweets: 2 Personen. 12 Retweets: 1 Person. 13 Retweets: 1 Person. 15 Retweets: 1 Person. 21 Retweets: 1 Person. 23 Retweets: 1 Person.

    \n

    Wer sind die Unterstützer mit den meisten Followern?

    \n
      \n
    1. Selami İnce (selamiince) 27703 Follower, 1 Retweet.
      \n \"#Hitzlsperger Respekt! In allen Bereichen mit antiquiertem Männlichkeitsbild spielt hoffentlich bald keine Rolle mehr, wer mit wem wie lebt.\"
    2. \n
    3. Christopher Lauer (Schmidtlepp) 26669 Follower, 1 Retweet.
      \n \"(mi) #Gysi zu #Merkel: Unter Kohl war die Marktwirtschaft noch sozialer als heute. Hier das Video @linksfraktion http://t.co/SlH8NTMTRr\"
    4. \n
    5. Jörg Tauss (tauss) 15508 Follower, 1 Retweet.
      \n \"Durch ihre Enthaltung verhindert die Bundesregierung eine qualifizierte Mehrheit der EU-Agrarministern gegen die Einführung von #GenMais1507\"
    6. \n
    7. Katja Kipping (katjakipping) 13858 Follower, 1 Retweet.
      \n \"Durch ihre Enthaltung verhindert die Bundesregierung eine qualifizierte Mehrheit der EU-Agrarministern gegen die Einführung von #GenMais1507\"
    8. \n
    9. linksfraktion (Linksfraktion) 12378 Follower, 6 Retweets.
    10. \n
    \n\n\n

    Hier die Tabelle aller Accounts mit mehr als 2 Retweets:

    \n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n
                <tr>\n    <td>1</td>\n                            <td>Free Chelsea Manning (politlinkx)</td>\n                            <td>643</td>\n                            <td>23</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                    </tr>\n            <tr>\n    <td>2</td>\n                            <td>Demokratie Report (ProDemokratie)</td>\n                            <td>318</td>\n                            <td>21</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>3</td>\n                            <td>✡ TS_Palm ✡ (TS_Palm)</td>\n                            <td>646</td>\n                            <td>15</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                    </tr>\n            <tr>\n    <td>4</td>\n                            <td>Monique (Queuey)</td>\n                            <td>58</td>\n                            <td>13</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>5</td>\n                            <td>NimA (Nima_1990)</td>\n                            <td>29</td>\n                            <td>12</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>6</td>\n                            <td>MisterX™ (kritik2punkt0)</td>\n                            <td>706</td>\n                            <td>10</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                    </tr>\n            <tr>\n    <td>7</td>\n                            <td>1KETZER (7VAMPIR)</td>\n                            <td>747</td>\n                            <td>10</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>8</td>\n                            <td>Pirat Parzival (PParzival)</td>\n                            <td>1827</td>\n                            <td>8</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                    </tr>\n            <tr>\n    <td>9</td>\n                            <td>Sebastian Sebastian (Thebastian85DD)</td>\n                            <td>5</td>\n                            <td>8</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>10</td>\n                            <td>jolicoeur (jolicoeur11)</td>\n                            <td>758</td>\n                            <td>7</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>11</td>\n                            <td>Einsiedler (eddiotos)</td>\n                            <td>539</td>\n                            <td>7</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>12</td>\n                            <td>hausrockenweltretten (hausrocken)</td>\n                            <td>15</td>\n                            <td>7</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                    </tr>\n            <tr>\n    <td>13</td>\n                            <td>Mario Ćubelić (MarioCubelic94)</td>\n                            <td>17</td>\n                            <td>7</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>14</td>\n                            <td>Garion Coyote (GarionCoyote)</td>\n                            <td>514</td>\n                            <td>7</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>15</td>\n                            <td>Agnes de Berlimont (AgnesdeBerlimon)</td>\n                            <td>583</td>\n                            <td>7</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>16</td>\n                            <td>Eva Badenschier (badenschier)</td>\n                            <td>25</td>\n                            <td>6</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>17</td>\n                            <td>Box an (inribonax)</td>\n                            <td>378</td>\n                            <td>6</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>18</td>\n                            <td>Daniela Sedelke (DSedelke)</td>\n                            <td>67</td>\n                            <td>6</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>19</td>\n                            <td>linksfraktion (Linksfraktion)</td>\n                            <td>12378</td>\n                            <td>6</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>20</td>\n                            <td>olaf26  (olaf26)</td>\n                            <td>408</td>\n                            <td>5</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>21</td>\n                            <td>Rapic2012 (rapic2012)</td>\n                            <td>291</td>\n                            <td>5</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>22</td>\n                            <td>Sonya... (KuanQia)</td>\n                            <td>534</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                    </tr>\n            <tr>\n    <td>23</td>\n                            <td>TS-E (ts_e)</td>\n                            <td>23</td>\n                            <td>5</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>24</td>\n                            <td>nimmdenbus (nimmdenbus)</td>\n                            <td>279</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>25</td>\n                            <td>Sægge (Ap_Saegge)</td>\n                            <td>140</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>26</td>\n                            <td>Ingrid Buchwieser (ingridHH)</td>\n                            <td>622</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>27</td>\n                            <td>HLW-BLN (HLWBerlin)</td>\n                            <td>209</td>\n                            <td>5</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>28</td>\n                            <td>Team PetraPau (TeamPetraPau)</td>\n                            <td>355</td>\n                            <td>5</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>29</td>\n                            <td>viva la vida (Orwellsmith)</td>\n                            <td>838</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                    </tr>\n            <tr>\n    <td>30</td>\n                            <td>fetterChe (TemaLue)</td>\n                            <td>212</td>\n                            <td>5</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>31</td>\n                            <td>Stefan Springer (SpringerHerten)</td>\n                            <td>73</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>32</td>\n                            <td>Vatane Man (RuzByt)</td>\n                            <td>230</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>33</td>\n                            <td>Nick Knatterton (messagebouncer)</td>\n                            <td>66</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>34</td>\n                            <td>Marie (krippmarie)</td>\n                            <td>1386</td>\n                            <td>5</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>35</td>\n                            <td>Grandmaster Lansh (LennartVoss)</td>\n                            <td>1477</td>\n                            <td>5</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>36</td>\n                            <td>Uwe Shopinweb (shopinweb)</td>\n                            <td>3651</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>37</td>\n                            <td>Witch on Wheels (weiberkraft)</td>\n                            <td>70</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>38</td>\n                            <td>Markus Kuen (creation_2011)</td>\n                            <td>896</td>\n                            <td>5</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>39</td>\n                            <td>Alex (ultra_ri0t)</td>\n                            <td>304</td>\n                            <td>5</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>40</td>\n                            <td>NichtdieMama (Yrrgw)</td>\n                            <td>748</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>41</td>\n                            <td>Ivo kain Krieg (IvokainKrieg)</td>\n                            <td>250</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>42</td>\n                            <td>Simon  (simasview)</td>\n                            <td>120</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>43</td>\n                            <td>k.&k.Hoflieferant (Reichsvikar)</td>\n                            <td>1047</td>\n                            <td>4</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>44</td>\n                            <td>Linke Dithmarschen (LINKE_HEI)</td>\n                            <td>177</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>45</td>\n                            <td>Julia S (juliainfinland)</td>\n                            <td>933</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>46</td>\n                            <td>Jacob Something (j_kanev)</td>\n                            <td>130</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>47</td>\n                            <td>Janet McBunny (JanetMcBunny)</td>\n                            <td>4</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>48</td>\n                            <td>Mr. Mann (rokkr91)</td>\n                            <td>136</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>49</td>\n                            <td>Turan Altuner (Turamax)</td>\n                            <td>2777</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>50</td>\n                            <td>Schlotti (Fjunchclick)</td>\n                            <td>247</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>51</td>\n                            <td>Gert Schoen (Gert_Schoen)</td>\n                            <td>32</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>52</td>\n                            <td>MEE (KANAKEN)</td>\n                            <td>19</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>53</td>\n                            <td>Ⓣeegeflüster  (Noeh_A)</td>\n                            <td>589</td>\n                            <td>4</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>54</td>\n                            <td>Max Sensei (maxsensei)</td>\n                            <td>78</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>55</td>\n                            <td>le Cram (aquaero)</td>\n                            <td>152</td>\n                            <td>4</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>56</td>\n                            <td>★◄der☛DUTSCHI☚☮✰ (Der_Dutschi)</td>\n                            <td>2036</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>57</td>\n                            <td>Markus (MarkusDarmstadt)</td>\n                            <td>35</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>58</td>\n                            <td>Ries Ling (r0llinger)</td>\n                            <td>654</td>\n                            <td>4</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>59</td>\n                            <td>Johannes (bojome)</td>\n                            <td>40</td>\n                            <td>4</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>60</td>\n                            <td>Petra S. (Almwichterl)</td>\n                            <td>255</td>\n                            <td>4</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>61</td>\n                            <td>Frank (frhey)</td>\n                            <td>101</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>62</td>\n                            <td>frischer_fischer (frisch_fish)</td>\n                            <td>442</td>\n                            <td>4</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>63</td>\n                            <td>Beatrice Pietsch (BeatricePietsch)</td>\n                            <td>3</td>\n                            <td>4</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>64</td>\n                            <td>The Shape (Der_P82)</td>\n                            <td>76</td>\n                            <td>4</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>65</td>\n                            <td>papa chango (papachango9)</td>\n                            <td>536</td>\n                            <td>4</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>66</td>\n                            <td>René Füssel (ReneFSL)</td>\n                            <td>20</td>\n                            <td>4</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>67</td>\n                            <td>Konrad (KonradvonFell)</td>\n                            <td>5</td>\n                            <td>4</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>68</td>\n                            <td>Tenrevo (Tenrevo)</td>\n                            <td>60</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>69</td>\n                            <td>Wildkatze (joluchs)</td>\n                            <td>410</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>70</td>\n                            <td>White Fox Blue Eyes (DominikScheib)</td>\n                            <td>7</td>\n                            <td>3</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>71</td>\n                            <td>Stephan Forth (pingaffe)</td>\n                            <td>96</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>72</td>\n                            <td>Claudia Lohausen (ClaudiaLohausen)</td>\n                            <td>578</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>/td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>73</td>\n                            <td>rhabarbeer (rhabarbeer)</td>\n                            <td>246</td>\n                            <td>3</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>74</td>\n                            <td>Helma Dorn (HelmaDorn)</td>\n                            <td>882</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>75</td>\n                            <td> Detektei ManagerSOS (managersos)</td>\n                            <td>834</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>76</td>\n                            <td>LordCrash (CrashMjF)</td>\n                            <td>44</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>77</td>\n                            <td>Felix (NuKingOfErthing)</td>\n                            <td>46</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>78</td>\n                            <td>Michi  (mj123451)</td>\n                            <td>904</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>79</td>\n                            <td>Leikan Monsis (honduram)</td>\n                            <td>209</td>\n                            <td>3</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>80</td>\n                            <td>TapferesSchraderlein (jaffi_schrader)</td>\n                            <td>26</td>\n                            <td>3</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>81</td>\n                            <td>Twiidy (Twiidy)</td>\n                            <td>86</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>82</td>\n                            <td>Jan  (el_hausaffe)</td>\n                            <td>11</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>83</td>\n                            <td>Klotze Buck (Fatze_Buck)</td>\n                            <td>140</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                    </tr>\n            <tr>\n    <td>84</td>\n                            <td>bankziggy (bankziggy)</td>\n                            <td>9</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>85</td>\n                            <td>Na, geht doch! (NaGehtDoch)</td>\n                            <td>458</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>86</td>\n                            <td>MDChronik (MDChronik)</td>\n                            <td>112</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>87</td>\n                            <td>TJ (pgtj77)</td>\n                            <td>202</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>88</td>\n                            <td>Kirstin Wodrich (DerEngel007)</td>\n                            <td>18</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>89</td>\n                            <td>G-ritti (G_Jai_)</td>\n                            <td>105</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>90</td>\n                            <td>guerilla_ronny (guerillaronny)</td>\n                            <td>2</td>\n                            <td>3</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>91</td>\n                            <td>Radler Frank (dynamo1955)</td>\n                            <td>770</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>92</td>\n                            <td>lord.daywalker (monstropolis)</td>\n                            <td>355</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>93</td>\n                            <td>Nadine (Schaedelnoppen)</td>\n                            <td>211</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>94</td>\n                            <td>G. Shumway (Vigilius_)</td>\n                            <td>57</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>95</td>\n                    </tr>\n            <tr>\n    <td>96</td>\n                            <td>Die_Kehrseite (Die_Kehrseite)</td>\n                            <td>1371</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>97</td>\n                            <td>Laurin Huber (MrAchherrje)</td>\n                            <td>32</td>\n                            <td>3</td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>98</td>\n                            <td>Thomas (JeffreyGoines32)</td>\n                            <td>60</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>99</td>\n                            <td>Özlem Sirakaya (oezlem1011)</td>\n                            <td>7</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>100</td>\n                            <td>Linksfraktion Thl (Linke_Thl)</td>\n                            <td>1154</td>\n                            <td>3</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                    </tr>\n            <tr>\n    <td>101</td>\n                            <td>МИР/k.o. (wortkrieger)</td>\n                            <td>22</td>\n                            <td>3</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>102</td>\n                            <td>Der müde Joe (muederjoe)</td>\n                            <td>29</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>103</td>\n                            <td>Der Klugscheißer (Besserwissender)</td>\n                            <td>58</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>104</td>\n                            <td>--ERIK-- (2485fischer)</td>\n                            <td>76</td>\n                            <td>3</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>105</td>\n                            <td>FreakDrear (FreakDrear)</td>\n                            <td>17</td>\n                            <td>3</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>106</td>\n                            <td>Andreas  Heitmann (AndyGER)</td>\n                            <td>687</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>107</td>\n                            <td>mwastel1967 (mwastel1967)</td>\n                            <td>122</td>\n                            <td>3</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>108</td>\n                            <td>Lars (txted_)</td>\n                            <td>2</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>109</td>\n                            <td>Torsten Zimmermann (zimmi69)</td>\n                            <td>113</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>110</td>\n                            <td>knallerten (knallerten)</td>\n                            <td>80</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>111</td>\n                            <td>Þórr (wunderfitzig)</td>\n                            <td>1436</td>\n                            <td>3</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n            <tr>\n    <td>112</td>\n                            <td>Psychonaut (olliwaack)</td>\n                            <td>464</td>\n                            <td>3</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td></td>\n                            <td>X</td>\n                            <td></td>\n                            <td></td>\n                    </tr>\n
    \n
    RankNameFollowerRT\n \n 31.01 \n
    125 RT\n
    \n \n 24.01 \n
    102 RT\n
    \n \n 08.01 \n
    98 RT\n
    \n \n 11.02 \n
    86 RT\n
    \n \n 30.01 \n
    79 RT\n
    \n \n 11.02 \n
    77 RT\n
    \n \n 29.01 \n
    75 RT\n
    \n \n 09.01 \n
    72 RT\n
    \n \n 21.01 \n
    71 RT\n
    \n \n 22.01 \n
    70 RT\n
    \n \n 24.01 \n
    63 RT\n
    \n \n 13.01 \n
    61 RT\n
    \n \n 06.01 \n
    59 RT\n
    \n \n 30.01 \n
    56 RT\n
    \n \n 11.02 \n
    55 RT\n
    \n \n 09.01 \n
    53 RT\n
    \n \n 09.01 \n
    51 RT\n
    \n \n 11.02 \n
    51 RT\n
    \n \n 21.01 \n
    48 RT\n
    \n \n 18.01 \n
    46 RT\n
    \n \n 09.02 \n
    44 RT\n
    \n \n 22.01 \n
    40 RT\n
    \n \n 21.01 \n
    32 RT\n
    \n \n 17.01 \n
    31 RT\n
    \n \n 06.01 \n
    27 RT\n
    \n\n\n

    Hinweise:

    \n\n\n", "url": "https://blog.thomaspuppe.de/gregor-gysi-retweets", "title": "Die Unterstützer des @GregorGysi", "summary": "Gregor Gysi führt in den letzten Wochen die Retweet-Charts von Bundestwitter an. Ist das systematische Unterstützung durch Wenige? Oder eine breite Basis?", "date_modified": "2014-03-16T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/responsive-website-ein-praktisches-beispiel", "content_html": "

    Wie baut man eine einfache Website von nicht-responsive auf responsive um? Eine Schritt-für-Schritt Anleitung anhand dieses Blogs.

    \n

    Ziel ist nicht, dass der Blog auf allen Geräten gleich aussieht (was auch nicht der Sinn von RWD ist). Sondern dass er auf allen Geräten gut aussieht und vor Allem funktioniert (was der Sinn von RWD ist).

    \n

    Anhand des sehr einfachen Blog-Layouts öchte ich drei grundlegende Regeln von Responsive Websites erläutern.

    \n

    1. Anpassung des Viewport

    \n

    Mit der Einführung des iPhone wurde quasi eine neue Geräteklasse geschaffen, auf die das Web nicht vorbereitet war. Websites, die zuvor auf einem Bildschirm mit 1024 und mehr Pixeln dargestellt wurden, sollten nun auf einem 320 oder 480 Pixel breiten Display gezeigt werden. Vor dieser Herausforderung stand Apple. Eine Variante dafür wäre, nur die linke obere Ecke der Website zu zeigen. Ein anderer, die Website klein zu skalieren. Für die zweite Variante hatte sich Apple entschieden. Websites wurden auf eine feste Breite von 980 Pixel gerendert und dann auf die Breite des iPhones herunterskaliert. (Für Android-Browser gilt das gleiche, mit 800 Pixeln Breite.)

    \n
    \"Abb.
    Abb. 1: Darstellung ohne Viewport
    \n\n

    Wenn nun aber eine Website mit einer Breite von wenigr als 980 Pixeln gut zurechtkommt (also responsive ist), dann muss das dem Browser mitgeteilt werden. Dafür dient der Viewport:

    \n
    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
    \n\n

    Dieser Meta-Tag im Head-Bereich der Website sagt dem Browser, dass die Seite nicht mit fiktiven 980 Pixeln gerendert werden soll, sondern auf der nativen Breite des Geräts. Wie sich die Seite bei dieser Breite verhält, das ist dann Sache des CSS.

    \n
    \"Abb.
    Abb. 2: Darstellung mit Viewport (noch nicht schön, aber man sieht die automatische Anpassung der Breite).
    \n\n

    Auf Desktop-Browsern spielt der Viewport übrigens keine Rolle. Hier gilt die Fensterbreite wie sie ist, weil es ja keinen Grund zum Skalieren gibt.

    \n

    2. Flexible Seitenbreite und Raster

    \n

    Als nächstes muss die Seite flexibel auf die (nun auch auf kleinen Geräten erkannte) Breite reagieren. Statt einer festen Breite in Pixeln geben wir den Elementen auf der Seite eine flexible Breite in Prozent, die sich an der größe des Browserfensters (oder des mobilen Gerätes) orientiert.

    \n

    Feste oder minimale Breiten können im Responsive Web Design für kleine Elemente nützlich sein. Für große Elemente sind sie es meist nicht. Böser Fehler in der ersten Version dieses Blogs:

    \n
    .posts { width: 100%; min-width:710px; }
    \n\n

    Soeben haben wir mobile Geräte mittels Viewport dazu gezwungen, die Seite in Originalpixeln zu rendern. Hat das Gerät nur 480 Pixel Breite, ist die Seite natürlich zur Hälfte abgeschnitten. Indem man feste oder minmale Breiten aus dem CSS entfernt, passten sich Block-Elemente wie Divs an die verfügbare Breite an. In diesem Fall sind das 100% des Browsers - so soll es sein. Auf die Angabe von 100% kann man in dem Fall natürlich auch verzichten.

    \n

    100% Breite für Elemente sind aber nicht immer erwünscht. Gerade bei sehr großen Monitoren sind Flisßtexte schlecht lesbar, wenn sie den ganzen Bildschrimbreite einnehmen. Abhilfe schafft die Angabe einer maximalen Breite:

    \n
    .posts p, .posts h1, .posts h2 { max-width:600px; }
    \n\n

    Da die Artikel selbst noch breit laufen sollen (bunter Rand rechts), begrenzen wir nur Überschriften und Absätze in der Breite. Ḿan erkennt, dass die Inhalte der rechten Spalte sich dem Menü

    \n

    3. Breakpoints für unterschiedliche Styles

    \n

    Anhand von sogenannten Media Queries kann bestimmtes CSS für bestimmte Gerätetypen ausgeliefert werden. Für Responsive Websites richtet man sich meist nach der Breite des Bildschirms. Anhand von Grenzwerten (“Breakpoints”) werden unterschiedliche Regeln für unterschiedliche Bildschirm edefiniert. Für den Einsatz von Breakpoints gibt es zwei verschiedene Ansätze. Desktop-First funktioniert mit absteigenden max-width Werten:

    \n
    h1 {\nfont-size: 3.5em;\n}\n\n@media only screen and (max-width : 1200px) {\n    h1 {\n        font-size: 3em;\n    }\n}\n\n@media only screen and (max-width : 600px) {\n    h1 {\n        font-size: 2em;\n    }\n}
    \n\n

    Mobile-First arbeitet mit aufsteigenden min-width Werten:

    \n
    h1 {\nfont-size: 2em;\n}\n\n@media only screen and (min-width : 600px) {\n    h1 {\n        font-size: 3em;\n    }\n}\n\n@media only screen and (min-width : 1200px) {\n    h1 {\n        font-size: 3.5em;\n    }\n}
    \n\n

    Beachte: für alle Bildschirme über dem größten max-width-Wert (oder unter dem kleinsten min-width-Wert) muss ein Standardwert definiert werden.

    \n

    Da mobile (kleine) Geräte tendentiell schwächer sind als große, halte ich Mobile-First für einen guten Ansatz.

    \n

    Linktipp

    \n

    “This is responsive” von Brad Frost.

    \n", "url": "https://blog.thomaspuppe.de/responsive-website-ein-praktisches-beispiel", "title": "Umstellung auf Responsive Web Design - ein praktisches Beispiel", "summary": "Wie baut man eine einfache Website von nicht-responsive auf responsive um? Eine Schritt-für-Schritt Anleitung anhand dieses Blogs.", "date_modified": "2014-02-13T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/do-not-track-header-crossbrowser", "content_html": "

    Mit dem doNotTrack Befehl können User signalisieren, dass sie nicht von Websites getrackt werden möchten. Browser senden dafür einen HTTP-Header mit ihren Requests an den Webserver. Via JavaScript lässt sich diese Einstellung auch im Client auslesen. Dieses Auslesen funktioniert leider nicht einheitlich.

    \n

    Chrome und Safari haben eine andere Schreibweise für die doNotTrack Property als der Firefox Browser. Der IE 9/10 verwendet einen eigenen Namen. Und der IE 8 unterstützt diese Einstellung laut Microsoft auf sehr krude Art und Weise.

    \n

    Getestete Browser:

    \n\n \n \n \n\n
    <tbody>\n<tr><td>Chrome 30 (Win 7)</td><td>navigator.doNotTrack</td><td>"0", "1", null</td></tr>\n<tr><td>Chromium 30 (Ubuntu 13)</td><td>navigator.doNotTrack</td><td>"0", "1", null</td></tr>\n\n<tr><td>Safari 5 (Win 7)</td><td>navigator.doNotTrack</td><td>"0", "1", null</td></tr>\n\n<tr><td>Firefox 25 (Win 7)</td><td>navigator.doNotTrack</td><td>"no", "yes", "unspecified"</td></tr>\n<tr><td>Firefox 25 (Ubuntu 13)</td><td>navigator.doNotTrack</td><td>"no", "yes", "unspecified"</td></tr>\n\n<tr><td>Opera 12 (Win 7)</td><td>navigator.doNotTrack</td><td><del>"0"</del>, "1", null</td></tr>\n\n<tr><td>IE 10 (Win 7)</td><td>navigator.msDoNotTrack</td><td>"0", "1", TODO</td></tr>\n<tr><td>IE 10 (Win 7)</td><td>window.external.InPrivateFilteringEnabled()</td><td>false, true, undefined</td></tr>\n<tr><td>IE 9 (Win 7)</td><td>window.external.InPrivateFilteringEnabled()</td><td>false, true, undefined</td></tr>\n<tr><td>IE 8 (Win 7)</td><td>window.external.InPrivateFilteringEnabled()</td><td>false, true, undefined</td></tr>\n<tr><td>IE 7 (Win 7)</td><td colspan=2>nicht verfügbar</td></tr>\n\n<tr><td>Chrome Mobile (Android 4.3)</td><td>navigator.doNotTrack</td><td>"0", "1", null</td></tr>\n<tr><td>Safari (iOS 5 / iPad 1)</td><td colspan=2>nicht verfügbar</td></tr>\n<tr><td>Sony Tablet Browser (Android 4.0)</td><td colspan=2>nicht verfügbar</td></tr>\n</tbody>\n
    \n
    BrowserAbrufWert
    \n\n

    Die Tabelle wird nach und nach ergänzt. Input bitte an @thomaspuppe

    \n

    Live-Test in Ihrem Browser

    \n
    \n\n\n",
                "url": "https://blog.thomaspuppe.de/do-not-track-header-crossbrowser",
                "title": "doNotTrack Browserübergreifend erkennen",
                "summary": "Der doNotTrack Flag des Browsers signalisiert, dass ein Nutzer nicht getrackt werden möchte. Diese Information lässt sich per JS auslesen — jedoch je nach browser unterschiedlch.",
                "date_modified": "2013-11-07T00:00:00.000Z"
            },
            {
                "id": "https://blog.thomaspuppe.de/visualized-io-berlin-2013",
                "content_html": "

    Eine grobe Zusammenfassung der Themen auf der VISUALIZED.iO Konferenz am 05. Oktober 2013 in Berlin. Es ist wenig ausformuliert, sondern eher eine Liste an Stichpunkten, Links zu den vorgestellten Projekten und einige Zitate.

    \n
    \"Knowledge is the reason for change. Data Visualization helps spreading knowledge.\" — Moritz Stefaner
    \n\n

    Maral Pourkazemi (@maralllo) führte wunderbar durch die Konferenz.

    \n\n

    1) Cedrik Kiefer (@cedrickiefer), onformative

    \n

    \"Sometimes data us visualized to create an emotion, not to give information.\"

    \n

    \"Adding data does not necessarily adding meaning.\"

    \n

    \"Your data might be more boring than you think.\"

    \n\n\n

    2) Benedikt Groß (@bndktgrs): Reading and Writing Landscapes

    \n

    Benedikt Groß stellte sein Projekt \"The Atlas of LA Pools\" vor. Interessante Einblicke in die Mühen, aus den Rohdaten (Satelliten(?)fotos) verwertbare Daten zu machen. Wochenlange Handarbeit und Outsourcing nach Indien inbegriffen. http://benedikt-gross.de/log/2013/06/the-big-atlas-of-la-pools/

    \n

    Zum Abschluss gab es einen Überblick über sein Projekt zum \"Agricultural Printing\", der Gestaltung von Landschaft. http://benedikt-gross.de/log/2013/06/avena-test-bed_agricultural-printing-and-altered-landscapes/

    \n\n

    3) Christopher Warnow (@brainSteen): Sick Data - The weird cousin of Big Data.

    \n

    \"It's not the petabytes. It's the question.\"

    \n

    Christopher Warnow stellte einige Ergebnisse seiner Visualisierung von Lobbying, Geldflüssen und der Shareholders des DAX (viele Gewinne fließen ins Ausland) vor.

    \n\n

    4) Boris Müller

    \n

    Digitale Kuppelproduktion – Marc Tiedemann: Datenvisualisierung im Planetarium(!!!) Bei Vimeo gibt es ein schönes Video des Projekts.

    \n

    Vorstellung des GED VIZ und einiger Hintergründe beim UI Design.

    \n

    \"Only who is interested in the data, will be interested in the visualization.\"

    \n

    \"Provide a tool that allows people to tell stories. (Exploration and communication)\"

    \n\n

    5) Marian Doerk (@nrchtct): Taking Visualization to the streets

    \n

    \"The Flaneur\" als Metapher für den Informationssuchenden. Visualisierte Daten als Stadt. Und diese erkundet man nicht mit einer Karte aus Vogelperspektive, sondern durchs Spazierengehen in den Straßen. \"A navigational approach, where data elements serve as anchors for local views.\"

    \n

    Vorstellung zweier Projekte: Pivot Paths und Word Wanderer.

    \n

    \"They are prototypes. They are meant to break.\"

    \n\n

    6) Sandra Rendgen (@srendgen): Storytelling in DataViz

    \n

    Storytelling in movies evolved. First they've been showing whatever, because the technology was new and cool. Then they used narrative patterns from the theater. Later, own patterns evolved. The same is to be expected from storytelling via data visualization.

    \n\n

    7) Sebastian Meier & Marcus Paeschke (@letsMphasize): Insights from teaching DataViz

    \n

    Kurzweilige Einblicke in die Lehre von Datenvisualisierung an der Hochschule. Anfangs erwarten die Studenten IronMan-Interfaces. Doch zunächst ringen sie um Bar Charts.

    \n

    Vorstellung von Studentenprojekten: Visualizing Meteorites www.meteor-impact.com/, visualizing.org/visualizations/fallen-and-found-0, www.kimalbrecht.com/project/meteorites/) und von der weltweiten Schneedecke (spontan keine Links gefunden).

    \n\n

    8) Felix Hardmood Beck: Media in Space

    \n

    Vorstellung des Living Cartography Projekts und der Installation River is....

    \n\n

    9) Andreas Nicolas Fischer (@__anf)

    \n

    Andreas Nicolas Fischer erstellt Skulpturen aus Daten, Algorithmen und Musik. Einige davon stellte er vor.

    \n\n

    10) David Ikuye (@d4ve): Behaviour Change by Design

    \n

    \"the outcome of our work changes peoples behaviour.\"

    \n

    Vorstelleung einiger Beispiele, wie Design und Datenvisualisierung das vehalten von Menschen ändert: die schwedische Blitzer-Lotterie, Energieeinsparung durch Vergleiche des Haushaltsverbrauchs mit der Nachbarschaft, Run Together Aktion von Nike.

    \n

    Danach stellte David die App weCycle vor, die eigene Fahrradfahrten trackt und zu häufigerem Fahren ermuntern soll.

    \n\n

    11) Ulli Hendrik Streckenbach: Why I am Visualizing Information

    \n

    In einem tollen Votrag erzählte Ulli Streckenbach, wie er zur Visualisierung fand (wegen seiner Vortragsangst) und stellte zwei seiner Arbeiten vor: The overfishing of the oceans und Let's talk about soil

    \n

    \"People are busy lazy.\"

    \n\n

    12) Wesley Grubbs (@pitchinc): Drones

    \n

    In einem bewegenden Vortrag stellte Wesley Grubbs das Out of Sight Out of Mind Projekt vor, das alle bekannten Drohnenangriffe der Amerikaner auf Pakistan visualisiert.

    \n\n

    13) Martin Oberhäuser (@oberhaeuserinfo)

    \n

    \"Design is a filter for information.\"

    \n

    \"Do not only display data in another way, but rather become an author!\"

    \n

    Vorstellung der Diplomarbeit Stadtistik und der nextr iPhone App.

    \n\n

    14) NAND.io : Making Data Tangible

    \n

    You can only see the effects of big data, coming out of a black box. Transparency is important. Make people aware of what is happening.

    \n

    Zwei Projekte zur Unterstützung dieser Thesen: natürliche Zufallszahlen-Generatoren (Known Unknowns) und die Täuschung von \"Smart\" Devices (Unreliable Machinery).

    \n\n

    15) Moritz Stefaner (@moritz_stefaner): Mapping the real worlds

    \n

    Vorstellung von drei Projekten (resonet, Stadtbilder und Wahlland) und einige kluge Worte zum Zweck der Visualisierung (Data Visualization as a Macroscope. Data Visualization as a body extension and brain amplifier.)

    \n

    \"We are the new photojournalists.\"

    \n\n

    \"Knowledge is the reason for change. Data Visualization helps spreading knowledge.\"

    \n", "url": "https://blog.thomaspuppe.de/visualized-io-berlin-2013", "title": "VISUALIZED.iO Berlin 2013", "summary": "Eine Zusammenfassung der Themen auf der VISUALIZED.iO Konferenz 2013 in Berlin.", "date_modified": "2013-10-09T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/goodbye-google-analytics-goodbye-piwik", "content_html": "

    Warum ich mein Website-Tracking von Google Analytics über Piwik zu AWStats zurück entwickelte und trotzdem nichts vermisse.

    \n

    \"Die

    \n\n

    Im Juli 2013 wurde das Besuchertracking bei Bundestwitter umgestellt von Google Analytics (mit IP-Anonymisierung) auf Piwik. Das ist ein OpenSource Tracking-Dienst den man sich selbst hosten kann. Wenn der eigene Server geschont werden soll, kann man das auch auf seinem eigenen Cloud-Rechner installieren. Eine schöne Anleitung dazu gibt es hier und hier.

    \n

    Piwik war mir wichtig, um vom Platzhirschen Google Analytcis loszukommen. Kleiner paranoider Exkurs:

    \n
    Man muss sich das mal auf der Zunge zergehen lassen: ich zwinge den Besucher meiner Seite, Code vom Server des profitorientierten Unternehmen Google aus den USA herunterzuladen. Dieser Code wird im Browser meines Besuchers ausgeführt und sendet dann seine Erkenntnisse an seinen Herkunftsserver: Google in den USA.
    \n\n

    Mit der Nutzung von Google Analytics leistet man Google Beihilfe zur Erstellung von Nutzerprofilen. Mit Piwik werden die User-Daten auf dem eigenen Server gehostet. Damit klinkt man seine eigene Website aus dem Google-Netzwerk aus. (Das Gleiche gilt für die Nicht-Nutzung von Facebook- oder Twitter-Widgets.)

    \n

    Daher wollte ich herausfinden, ob man wirklich auf die Features von Google Analytics angewiesen ist. Sicher: das Tool ist das beste kostenlose Tracking-Tool auf dem Markt. Aber solang man auf die Integration mit Google AdWords verzichten kann, braucht man die Funktionen vielleicht nicht alle?

    \n

    Nachdem ich ein paar Monate völlig zufrieden war mit Piwik, entschloss ich mich, noch einen "Schritt zurück" zu gehen und die serverseitige Software AWStats zu testen. Die wertet die Apache Logfiles aus (welche standardmäßig sowieso angelegt werden), und stellt die Ergebnisse dar. AWStats ist bei vielen Hostings inklusive und von Anfang an installiert.

    \n

    Die Kernfunktionen, die ich nutze, bieten tatsächlich alle 3 Systeme:

    \n\n

    AWStats zeigt nicht den Anteil neuer bzw. wiederkehrender Besucher an. Google Analytics hat zudem eine "Trackback"-Übersicht mit Seiten, die auf Bundestwitter verlinken (auch ohne dass Besucher darüber kamen). Dafür gibt es aber auch Dienste, und die Google-Suche lässt sich entsprechend nutzen (link: "bundestwitter.de" -site:bundestwitter.de sucht Seiten, die auf bundestwitter.de verlinken und schließt dabei bundestwitter.de aus).

    \n

    Ein weiteres Alleinstellungsmerkmal von Google Analytics ist die Messung der Website Performance. Diese Funktion finde ich sehr stark, aber auch sie lässt sich ersetzen durch andere Dienste wie Pingdom RUM (die man auch temporär nutzen kann, und nicht ständig aktiv haben muss).

    \n

    Dafür bietet AWStats sogar Vorteile, die die anderen Tools nicht bieten:

    \n\n

    Interessant ist auch, dass die Anzahl der gezählten Besucher stark variiert. AWStats zeigt ca. doppelt so viele Besucher an wie Piwik (für Google Analytics habe ich keinen Vergleich, weil dies nicht parallel installiert war). Daher genieße ich die Zahlen aller Dienste mit Vorsicht, und achte eher auf Auf- und Abwärtstrends statt absolute Zahlen.

    \n

    Fazit: für den normalen Gebrauch bietet AWStats alles, was der Website-Betreiber begehrt. Wer auf clientseitiges Tracking von Google Analytics oder Piwik verzichtet, tut seinem Besucher etwas Gutes ohne auf Besucherstatistiken verzichten zu müssen. Mit der Liste von 404 Fehlern verfügt AWStats sogar über ein nützliches Alleinstellungsmerkmal.

    \n", "url": "https://blog.thomaspuppe.de/goodbye-google-analytics-goodbye-piwik", "title": "Good bye Google Analytics! Good bye Piwik! Hallo AWStats!", "summary": "Warum ich mein Website-Tracking von Google Analytics über Piwik zu AWStats zurück entwickelte und trotzdem nichts vermisse.", "date_modified": "2013-10-22T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/twitter-verhalten-mdb-und-nds", "content_html": "

    Wie viele Politiker sind auf Twitter aktiv? Wie häufig twittern sie? Was wird geschrieben? Erzeugt das Bürgernähe? Hilft es im Wahlkampf?

    Ein Überblick über das Twitter-Verhalten der MdB und der Wahlkämpfer der niedersächsischen Landtagswahl 2013.

    \n\n

    Dieser Artiel ist die schriftliche Zusammenfassung einer Präsentation, die ich im Januar 2013 an der HTW Berlin hielt.

    \n\n

    Wie viele Politiker twittern?

    \n

    Von 619 Abgeordneten twittern 230 aktiv (im letzten halben Jahr mindestens eine Nachricht gesendet).

    \n\n\n \n \n \n \n \n \n \n \n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n
    CDU/CSUSPDFDPDie LINKEBündnis 90/Die Grünen
    Sitze im Bundestag237146937668
    Twitter-Accounts6450483444
    Verhältnis17%34%52%45%65%
    \n\n

    Seit wann twittern die Politiker?

    \n

    Twitter wurde 2006 gegründet. Die Twitter-Anfänge der deutschen MdB scheinen vom US-Wahlkampf 2008 inspiriert zu sein. Der erste MdB des 17. Deutschen Bundestages mit Twitter-Account war Johannes Vogel von der FDP. Er eröffnete seinen Account am 28.07.2008 und twittert seitdem durchgehend aktiv (im Schnitt 1,6 Nachrichten pro Tag).

    \n

    Im September 2008 wurden dann viele Accounts der FDP-Fraktion angelegt (welche zum Teil aber nur zur Namenssicherung dienten und nie aktiv waren).

    \n
    \n\"neue
    \n\"neue
    \n\"neue
    \n\"neue
    \n\"neue
    \n
    MdB-Twitter-Account Neuzugänge in den Jahren 2008 bis 2012
    \n
    \n

    Die anderen Parteien begannen im August 2008 (Hubertus Heil, SPD), September 2008 (Volker Beck, Bündnis 90/Die Grünen), Dezember 2008 (Stefan Müller, CDU/CSU) und Januar 2009 (Halina Wawzyniak, Die LINKE). 2009 wurden dann auch 140 weitere Accounts aller Parteien eröffnet — bevor es zur Bundestagswahl im September 2009 einen Rückgang von Neuregistrierungen gab. Seit Herbst 2012 wurden keine neuen Twitter-Accounts registriert.

    \n\n

    Wie verteilen sich Accounts, Follower und Tweets auf die Parteien?

    \n

    Verglichen mit der Zahl der Sitze im Bundestag sind die kleinen/jungen Parteien aktiver bei Twitter als die großen. Die FDP sticht bei Follower-Zahlen hervor, während die Grünen am meisten schreiben.

    \n
    \n \"Graph:
    \n \"Graph:
    \n \"Graph:
    \n
    \n

    Natürlich gibt es jeweils eine große Bandbreite. Jede Partei hat sehr aktive und sehr inaktive Twitterer. Am Aktivsten sind Folgende Abgeordnete:

    \n
    \n\n
    (Stand: Januar 2013)
    \n
    \n\n\n

    Wer hat die meisten Follower?

    \n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    \"ProfilbildDr. Kristina Schröder (@schroeder_k)
    32.757 Follower
    \"ProfilbildPeter Altmaier (@peteraltmaier)
    31.478 Follower
    \"ProfilbildVolker Beck (@Volker_Beck)
    28.279 Follower
    \"ProfilbildFDP-Fraktion (@FDP_Fraktion)
    25.033 Follower
    \"ProfilbildSigmar Gabriel (@sigmargabriel)
    18.731 Follower
    \n

    (Stand: Januar 2013)

    \n\n

    Zum Vergleich: Barack Obama hat 25.000.000 Follower, David Cameron 200.000 und sein Amtsaccount als Prime Minister, 2.000.000 Follower.

    \n\n\n

    Welche Inhalte werden verschickt?

    \n

    Es findet – mit 7% der Nachrichten – relativ wenig Dialog statt. (Relativ zu meiner Erwartung. Ich kenne keine Twitter-weiten Zahlen hierzu.) Umso mehr werden Links verschickt (zu Pressemitteilungen, Artikeln, Fotos) und geretweetet (was eine sehr einfache Form der \"Inhaltsgenerierung\" ist).

    \n
    \n\"Graph:\n
    \n

    Wobei zu beachten ist: aus dieser Statistik entfallen direkte Tweets an Persionen via \"@Screenname\" am Anfang des Tweets, weil diese Nachrichten nicht in der öffentlichen Timeline erscheinen und somit nicht von Bundestwitter ausgewertet werden.

    \n\n

    Wahllkampf in Niedersachsen

    \n

    Im Landtagswahlkampf 2013 für Niedersachsen habe ich die Accounts der amtierenden Abgeordneten sowie aller twitternden Kandidaten beobachtet. Hier einige Impressionen (die ich noch vor der Wahl beispielhaft zusammengetragen habe).

    \n
    \n \"Tweets\n
    David McAllisters Team twitterte eher hölzern.
    \n
    \n
    \n \"Tweets\n
    Berichte über den Wahlkampf – auch in der Provinz oder übers Plakatekleben
    \n
    \n
    \n \"Tweets\n
    Das Team um Stephan Weil war etwas näher am Bürger.
    \n
    \n
    \n \"Tweets\n
    Typisch für die Piraten: der schon länger existirende private Account wird nun für Privates und Politik genutzt.
    \n
    \n
    \n \"Tweets\n
    Doris Schröder-Köpf ließ ihren Account automatisch von Facebook befüllen.
    \n
    \n\n\n

    Die Beispiele zeigen, dass das Twitter-Verhalten stark variiert. Jede Partei hat \"gute\" (authentisch, dialogfreudig) und \"schlechte\" (unpersönliches Broadcasting) Twitter-Accounts. In der Tendenz war mein Eindruck, dass die FDP und CDU Twitter eher zum Verbreiten von Statements und Meldungen benutzen. Die SPD war um mehr Bürgernähe bemüht, aber das war eher selten. Die Linke war in Niedersachsen wenig aktiv (mit Ausnahme von Victor Perli), aber authentisch und dialogfreudig. Die Grünen waren fleißig und authentisch, ebenso wie die Piraten, die natürlich in diesem Medium viel Erfahrung haben. Bei ihnen vermischen sich politische Inhalte aber stark mit privaten, sodass man beim Folgen den Privatpersonen näher kommt als den Politikern.

    \n

    \n

    In der Nachbetrachtung – wo man immer schlauer ist – kann man sagen: CDU twitterte zu professionall (= nicht authentisch). SPD hat es hinbekommen. FDP zu passiv. Piraten nicht professionell (im klassischen Politik-Sinn) genug. Grüne aktiv und authentisch. Die Freien Wähler waren auch auf Twitter aktiv, aber fielen nicht ins Gewicht. Die Kandidaten anderer Parteien hatten keine Twitter-Accounts.

    \n

    Natürlich haben auch die Medien sich die Onlinekompetenz im Wahlkampf angesehen. Ein Auszug von Beiträgen:\n

    \n

    \n", "url": "https://blog.thomaspuppe.de/twitter-verhalten-mdb-und-nds", "title": "Wie twittern Politiker?", "summary": "Ein Überblick über das Twitter-Verhalten der MdB und der Wahlkämpfer der niedersächsischen Landtagswahl 2013", "date_modified": "2013-01-22T00:00:00.000Z" }, { "id": "https://blog.thomaspuppe.de/eigene-domain-google-plus-umleiten", "content_html": "

    Ein Tipp für die Leute mit eigener Domain: Weiterleitung von thomaspuppe.de/+ auf das Google-Profil via .htaccess:

    \n
    RewriteEngine on\nRewriteCond %{REQUEST_URI} ^/\\+\nRewriteRule ^(.*)$ https://plus.google.com/u/0/109992889758306031081/ [R=permanent,L]
    \n", "url": "https://blog.thomaspuppe.de/eigene-domain-google-plus-umleiten", "title": "domain.de+ auf Google+ Profil umleiten", "summary": "Weiterleitung von thomaspuppe.de/+ auf das Google-Profil via .htaccess", "date_modified": "2013-01-17T00:00:00.000Z" } ] }