Initial commit: Tagesbyte prototype

Flutter app that collects daily tech news from German and English RSS/Atom
feeds, with a reader view, AI article summaries and daily briefings (Claude
API or n8n webhook), paywall/sponsored filtering and ad/consent blocking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
CedricandClaude Opus 5.5 committed 2026-10-16 14:53:37 +02:00
commit f12fb1dabb
159 files changed
+8738

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" width="1000" height="1000">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#5873FF"/>
<stop offset="1" stop-color="#2231A6"/>
</linearGradient>
</defs>
<rect width="1000" height="1000" fill="url(#bg)"/></svg>

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+25
View File
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" width="1000" height="1000">
<defs>
<mask id="bite" maskUnits="userSpaceOnUse" x="0" y="0" width="1000" height="1000">
<rect width="1000" height="1000" fill="#fff"/>
<circle cx="692.0188835160362" cy="274.5048250192058" r="88" fill="#000"/><circle cx="665.4951749807942" cy="247.98111648396377" r="88" fill="#000"/>
</mask>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="16"/>
</filter>
</defs>
<g transform="translate(500 500) scale(0.9) translate(-500 -500)">
<!-- Blur after cutting the bite so the shadow follows the bitten edge. -->
<g transform="translate(0 24)" filter="url(#shadow)" opacity="0.35">
<g mask="url(#bite)"><rect x="290" y="230" width="420" height="540" rx="52" fill="#0B1460" /></g>
</g>
<g mask="url(#bite)"><rect x="290" y="230" width="420" height="540" rx="52" fill="#FFFFFF" /></g>
<rect x="345" y="335" width="215" height="50" rx="25" fill="#3355DD"/>
<rect x="345" y="420" width="155" height="50" rx="25" fill="#3355DD"/>
<rect x="345" y="525" width="310" height="28" rx="14" fill="#C9D3F7"/>
<rect x="345" y="588" width="310" height="28" rx="14" fill="#C9D3F7"/>
<rect x="345" y="651" width="200" height="28" rx="14" fill="#C9D3F7"/>
<path d="M615,619 C620.52,659.48 620.52,659.48 661,665 C620.52,670.52 620.52,670.52 615,711 C609.48,670.52 609.48,670.52 569,665 C609.48,659.48 609.48,659.48 615,619 Z" fill="#FFB52E"/>
</g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

+32
View File
@@ -0,0 +1,32 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" width="1000" height="1000">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#5873FF"/>
<stop offset="1" stop-color="#2231A6"/>
</linearGradient>
</defs>
<rect width="1000" height="1000" fill="url(#bg)"/>
<defs>
<mask id="bite" maskUnits="userSpaceOnUse" x="0" y="0" width="1000" height="1000">
<rect width="1000" height="1000" fill="#fff"/>
<circle cx="692.0188835160362" cy="274.5048250192058" r="88" fill="#000"/><circle cx="665.4951749807942" cy="247.98111648396377" r="88" fill="#000"/>
</mask>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="16"/>
</filter>
</defs>
<g transform="translate(500 500) scale(1.1) translate(-500 -500)">
<!-- Blur after cutting the bite so the shadow follows the bitten edge. -->
<g transform="translate(0 24)" filter="url(#shadow)" opacity="0.35">
<g mask="url(#bite)"><rect x="290" y="230" width="420" height="540" rx="52" fill="#0B1460" /></g>
</g>
<g mask="url(#bite)"><rect x="290" y="230" width="420" height="540" rx="52" fill="#FFFFFF" /></g>
<rect x="345" y="335" width="215" height="50" rx="25" fill="#3355DD"/>
<rect x="345" y="420" width="155" height="50" rx="25" fill="#3355DD"/>
<rect x="345" y="525" width="310" height="28" rx="14" fill="#C9D3F7"/>
<rect x="345" y="588" width="310" height="28" rx="14" fill="#C9D3F7"/>
<rect x="345" y="651" width="200" height="28" rx="14" fill="#C9D3F7"/>
<path d="M615,619 C620.52,659.48 620.52,659.48 661,665 C620.52,670.52 620.52,670.52 615,711 C609.48,670.52 609.48,670.52 569,665 C609.48,659.48 609.48,659.48 615,619 Z" fill="#FFB52E"/>
</g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+123
View File
@@ -0,0 +1,123 @@
// Generates the Tagesbyte app icon sources (SVG) and PNG renders.
// Usage: npm install @resvg/resvg-js@2.6.2 && node make_icons.js assets/icon
// then: dart run flutter_launcher_icons
const fs = require('fs');
const path = require('path');
const { Resvg } = require('@resvg/resvg-js');
const out = process.argv[2];
fs.mkdirSync(out, { recursive: true });
const BLUE = '#3355DD';
const LINE = '#C9D3F7';
const SPARK = '#FFB52E';
// Design space 1000x1000. A news page with a bite out of its top-right corner.
const page = { x: 290, y: 230, w: 420, h: 540, r: 52 };
const corner = { x: page.x + page.w, y: page.y };
// Two overlapping circles: together they remove the corner and leave a small
// cusp between them, which reads as a bite mark.
const biteCircles = [112, 158].map((deg) => {
const a = (deg * Math.PI) / 180;
return { cx: corner.x + 48 * Math.cos(a), cy: corner.y + 48 * Math.sin(a), r: 88 };
});
const sparkle = (cx, cy, s) =>
`M${cx},${cy - s} C${cx + s * 0.12},${cy - s * 0.12} ${cx + s * 0.12},${cy - s * 0.12} ${cx + s},${cy} ` +
`C${cx + s * 0.12},${cy + s * 0.12} ${cx + s * 0.12},${cy + s * 0.12} ${cx},${cy + s} ` +
`C${cx - s * 0.12},${cy + s * 0.12} ${cx - s * 0.12},${cy + s * 0.12} ${cx - s},${cy} ` +
`C${cx - s * 0.12},${cy - s * 0.12} ${cx - s * 0.12},${cy - s * 0.12} ${cx},${cy - s} Z`;
const pageRect = (fill, extra = '') =>
`<rect x="${page.x}" y="${page.y}" width="${page.w}" height="${page.h}" rx="${page.r}" fill="${fill}" ${extra}/>`;
const biteMask = (id) => `
<mask id="${id}" maskUnits="userSpaceOnUse" x="0" y="0" width="1000" height="1000">
<rect width="1000" height="1000" fill="#fff"/>
${biteCircles.map((c) => `<circle cx="${c.cx}" cy="${c.cy}" r="${c.r}" fill="#000"/>`).join('')}
</mask>`;
// Headline bar, text lines and the AI sparkle on the page.
const contentShapes = (headline, line, spark) => `
<rect x="345" y="335" width="215" height="50" rx="25" fill="${headline}"/>
<rect x="345" y="420" width="155" height="50" rx="25" fill="${headline}"/>
<rect x="345" y="525" width="310" height="28" rx="14" fill="${line}"/>
<rect x="345" y="588" width="310" height="28" rx="14" fill="${line}"/>
<rect x="345" y="651" width="200" height="28" rx="14" fill="${line}"/>
<path d="${sparkle(615, 665, 46)}" fill="${spark}"/>`;
function foreground(scale) {
const t = `translate(500 500) scale(${scale}) translate(-500 -500)`;
return `
<defs>
${biteMask('bite')}
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="16"/>
</filter>
</defs>
<g transform="${t}">
<!-- Blur after cutting the bite so the shadow follows the bitten edge. -->
<g transform="translate(0 24)" filter="url(#shadow)" opacity="0.35">
<g mask="url(#bite)">${pageRect('#0B1460')}</g>
</g>
<g mask="url(#bite)">${pageRect('#FFFFFF')}</g>
${contentShapes(BLUE, LINE, SPARK)}
</g>`;
}
const background = `
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#5873FF"/>
<stop offset="1" stop-color="#2231A6"/>
</linearGradient>
</defs>
<rect width="1000" height="1000" fill="url(#bg)"/>`;
const svg = (body) =>
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" width="1000" height="1000">${body}</svg>`;
// Monochrome (Android 13 themed icons): page silhouette, content cut out.
const monochrome = svg(`
<defs>
${biteMask('bite')}
<mask id="content" maskUnits="userSpaceOnUse" x="0" y="0" width="1000" height="1000">
<rect width="1000" height="1000" fill="#fff"/>
${contentShapes('#000', '#000', '#000')}
</mask>
</defs>
<g transform="translate(500 500) scale(0.9) translate(-500 -500)">
<g mask="url(#bite)"><g mask="url(#content)">${pageRect('#FFFFFF')}</g></g>
</g>`);
const files = {
// Full-bleed icon for iOS, web and Windows (the OS rounds the corners).
'icon.svg': svg(background + foreground(1.1)),
// Android adaptive icon layers: content kept inside the 66dp safe zone.
'foreground.svg': svg(foreground(0.9)),
'background.svg': svg(background),
'monochrome.svg': monochrome,
};
for (const [name, content] of Object.entries(files)) {
fs.writeFileSync(path.join(out, name), content);
const png = new Resvg(content, { fitTo: { mode: 'width', value: 1024 } }).render().asPng();
fs.writeFileSync(path.join(out, name.replace('.svg', '.png')), png);
console.log('wrote', name, 'and', name.replace('.svg', '.png'));
}
// Preview: the full icon at launcher size plus a circle-masked adaptive icon.
const preview = svg(`
<defs><clipPath id="c"><circle cx="500" cy="500" r="500"/></clipPath></defs>
<g clip-path="url(#c)">${background}${foreground(0.9)}</g>`);
fs.writeFileSync(path.join(out, '_preview_adaptive.png'),
new Resvg(preview, { fitTo: { mode: 'width', value: 512 } }).render().asPng());
// Monochrome preview on a dark background (the real file is transparent), and
// the full icon at a real launcher size to check it still reads when small.
const inner = (s) => s.replace(/^<svg[^>]*>/, '').replace(/<\/svg>$/, '');
fs.writeFileSync(path.join(out, '_preview_monochrome.png'),
new Resvg(svg('<rect width="1000" height="1000" fill="#333"/>' + inner(monochrome)),
{ fitTo: { mode: 'width', value: 256 } }).render().asPng());
fs.writeFileSync(path.join(out, '_preview_small.png'),
new Resvg(files['icon.svg'], { fitTo: { mode: 'width', value: 96 } }).render().asPng());
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+21
View File
@@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" width="1000" height="1000">
<defs>
<mask id="bite" maskUnits="userSpaceOnUse" x="0" y="0" width="1000" height="1000">
<rect width="1000" height="1000" fill="#fff"/>
<circle cx="692.0188835160362" cy="274.5048250192058" r="88" fill="#000"/><circle cx="665.4951749807942" cy="247.98111648396377" r="88" fill="#000"/>
</mask>
<mask id="content" maskUnits="userSpaceOnUse" x="0" y="0" width="1000" height="1000">
<rect width="1000" height="1000" fill="#fff"/>
<rect x="345" y="335" width="215" height="50" rx="25" fill="#000"/>
<rect x="345" y="420" width="155" height="50" rx="25" fill="#000"/>
<rect x="345" y="525" width="310" height="28" rx="14" fill="#000"/>
<rect x="345" y="588" width="310" height="28" rx="14" fill="#000"/>
<rect x="345" y="651" width="200" height="28" rx="14" fill="#000"/>
<path d="M615,619 C620.52,659.48 620.52,659.48 661,665 C620.52,670.52 620.52,670.52 615,711 C609.48,670.52 609.48,670.52 569,665 C609.48,659.48 609.48,659.48 615,619 Z" fill="#000"/>
</mask>
</defs>
<g transform="translate(500 500) scale(0.9) translate(-500 -500)">
<g mask="url(#bite)"><g mask="url(#content)"><rect x="290" y="230" width="420" height="540" rx="52" fill="#FFFFFF" /></g></g>
</g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB