Initial Fleckenhilfe website

This commit is contained in:
2026-04-20 06:22:50 +00:00
commit 4f3abb2dd7
25 changed files with 6258 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/
+4
View File
@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}
+11
View File
@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}
+30
View File
@@ -0,0 +1,30 @@
# Fleckenhilfe
Fleckenhilfe ist eine Astro-Website für eine praktische, ad-taugliche SEO-Nische: Flecken auf Kleidung, Sofa, Teppich und anderen Oberflächen entfernen.
## Warum diese Nische
- wiederkehrendes Alltagsproblem
- keine einzelne offizielle Quelle macht die Idee sinnlos
- viele Long-Tail-Seiten möglich
- Inhalte lassen sich urheberrechtlich sauber originär erstellen
## Kernrouten
- `/`
- `/tools/flecken-entferner`
- `/flecken/[slug]`
- `/materialien/[slug]`
- `/ratgeber/[slug]`
- `/privacy-policy`
- `/legal-notice`
## Lokal starten
```bash
npm install
npm run dev
```
## Preview
```bash
npm run build
npm run preview -- --host 0.0.0.0 --port 4488
```
+6
View File
@@ -0,0 +1,6 @@
// @ts-check
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://fleckenhilfe.de',
});
+4736
View File
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
{
"name": "fleckenhilfe",
"type": "module",
"version": "0.1.0",
"engines": {
"node": ">=22.12.0"
},
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"astro": "^6.1.8"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
<rect width="64" height="64" rx="14" fill="#146EF5"/>
<path d="M18 46c9-10 18-18 28-28" stroke="white" stroke-width="6" stroke-linecap="round"/>
<circle cx="22" cy="42" r="6" fill="#FFAE13"/>
<circle cx="42" cy="22" r="6" fill="#ED52CB"/>
</svg>

After

Width:  |  Height:  |  Size: 327 B

+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://fleckenhilfe.de/sitemap-index.xml
+29
View File
@@ -0,0 +1,29 @@
---
interface Props {
slot?: string;
label?: string;
className?: string;
}
const { slot, label = 'Anzeige', className = '' } = Astro.props;
const adsenseClient = import.meta.env.PUBLIC_ADSENSE_CLIENT;
---
<div class={`ad-shell ${className}`.trim()}>
<div class="ad-shell__label">{label}</div>
{
adsenseClient ? (
<ins
class="adsbygoogle"
style="display:block"
data-ad-client={adsenseClient}
data-ad-slot={slot}
data-ad-format="auto"
data-full-width-responsive="true"></ins>
) : (
<div class="ad-shell__placeholder">Platz für eine spätere Anzeige oder einen gesponserten Hinweis.</div>
)
}
</div>
{adsenseClient && <script is:inline>(adsbygoogle = window.adsbygoogle || []).push({});</script>}
+95
View File
@@ -0,0 +1,95 @@
---
import type { StainItem } from '../data/content';
interface Props {
stains: StainItem[];
compact?: boolean;
}
const { stains, compact = false } = Astro.props;
const visibleStains = compact ? stains.slice(0, 8) : stains;
const payload = JSON.stringify(visibleStains);
---
<section class={`finder ${compact ? 'finder--compact' : ''}`} data-stains={payload}>
<div class="finder__controls">
<label class="field">
<span>Fleck oder Problem</span>
<input type="search" placeholder="z. B. Rotwein, Kaffee, Öl, Blut" data-search />
</label>
<label class="field">
<span>Kategorie</span>
<select data-category>
<option value="alle">Alle</option>
<option value="Getränke">Getränke</option>
<option value="Fett & Küche">Fett & Küche</option>
<option value="Outdoor & Alltag">Outdoor & Alltag</option>
<option value="Kosmetik">Kosmetik</option>
<option value="Schwierige Flecken">Schwierige Flecken</option>
</select>
</label>
</div>
<div class="finder__results" data-results>
{visibleStains.map((stain) => (
<article class="result-card">
<div class="result-card__top">
<span class="result-icon">{stain.icon}</span>
<span class="result-category">{stain.category}</span>
</div>
<h3>{stain.name}</h3>
<p>{stain.short}</p>
<a class="button-link" href={`/flecken/${stain.slug}`}>Zur Anleitung</a>
</article>
))}
</div>
<p class="finder__empty" data-empty hidden>Keine Treffer. Versuch es mit Kaffee, Rotwein, Deo oder Blut.</p>
{compact && (
<div class="finder__footer">
<a class="cta-outline" href="/tools/flecken-entferner">Alle Flecken öffnen</a>
</div>
)}
</section>
<script is:inline>
const root = document.currentScript?.previousElementSibling;
if (root && root.matches('.finder')) {
const stains = JSON.parse(root.getAttribute('data-stains') || '[]');
const search = root.querySelector('[data-search]');
const category = root.querySelector('[data-category]');
const results = root.querySelector('[data-results]');
const empty = root.querySelector('[data-empty]');
const render = () => {
const query = (search?.value || '').trim().toLowerCase();
const categoryValue = category?.value || 'alle';
const filtered = stains.filter((stain) => {
const haystack = [stain.name, stain.short, stain.category, stain.summary, ...stain.bestFor, ...stain.firstAid]
.join(' ')
.toLowerCase();
const queryMatch = !query || haystack.includes(query);
const categoryMatch = categoryValue === 'alle' || stain.category === categoryValue;
return queryMatch && categoryMatch;
});
results.innerHTML = filtered.map((stain) => `
<article class="result-card">
<div class="result-card__top">
<span class="result-icon">${stain.icon}</span>
<span class="result-category">${stain.category}</span>
</div>
<h3>${stain.name}</h3>
<p>${stain.short}</p>
<a class="button-link" href="/flecken/${stain.slug}">Zur Anleitung</a>
</article>
`).join('');
empty.hidden = filtered.length > 0;
};
search?.addEventListener('input', render);
category?.addEventListener('change', render);
}
</script>
+341
View File
@@ -0,0 +1,341 @@
export type StainItem = {
slug: string;
name: string;
icon: string;
category: 'Getränke' | 'Fett & Küche' | 'Outdoor & Alltag' | 'Kosmetik' | 'Schwierige Flecken';
short: string;
summary: string;
bestFor: string[];
firstAid: string[];
method: string[];
avoid: string[];
faqs: { question: string; answer: string }[];
relatedMaterials: string[];
};
export type MaterialGuide = {
slug: string;
name: string;
short: string;
sensitivity: string;
safeMoves: string[];
avoid: string[];
goodFor: string[];
};
export type GuideItem = {
slug: string;
title: string;
short: string;
intro: string;
steps: string[];
tips: string[];
};
export const stains: StainItem[] = [
{
slug: 'rotwein',
name: 'Rotweinfleck entfernen',
icon: '🍷',
category: 'Getränke',
short: 'Schnell abtupfen, nicht verreiben, dann materialgerecht vorgehen.',
summary: 'Rotwein wirkt dramatisch, wird aber oft erst dann richtig schwierig, wenn er eingerieben oder zu spät behandelt wird.',
bestFor: ['Baumwolle', 'Tischdecken', 'Sofa mit robustem Bezug', 'Teppich mit kurzer Faser'],
firstAid: ['Überschüssige Flüssigkeit sofort abtupfen.', 'Von außen nach innen arbeiten.', 'Mit kaltem oder lauwarmem Wasser testen, wenn das Material es verträgt.'],
method: ['Fleck sanft abtupfen.', 'Milde Reinigungslösung lokal auftragen.', 'Kurz einwirken lassen.', 'Mit sauberem Tuch wieder aufnehmen.', 'Vor dem Trocknen prüfen, ob noch Farbe sichtbar ist.'],
avoid: ['Nicht rubbeln.', 'Keine Hitze am frischen Fleck.', 'Nicht sofort in den Trockner oder unter heiße Luft geben.'],
faqs: [
{ question: 'Hilft Salz immer?', answer: 'Nicht zuverlässig. Es kann kurzfristig Flüssigkeit ziehen, ersetzt aber keine saubere Fleckbehandlung.' },
{ question: 'Warum ist Trocknen so riskant?', answer: 'Weil Restpigmente sich mit Hitze oft stärker festsetzen.' }
],
relatedMaterials: ['baumwolle', 'sofa', 'teppich', 'matratze']
},
{
slug: 'kaffee',
name: 'Kaffeefleck entfernen',
icon: '☕',
category: 'Getränke',
short: 'Kaffee lässt sich oft gut lösen, solange der Fleck nicht eintrocknet.',
summary: 'Bei Kaffee ist Zeit wichtig. Frische Flecken sind deutlich leichter zu behandeln als eingetrocknete Ränder.',
bestFor: ['Hemden', 'T-Shirts', 'Sofabezug', 'Teppich'],
firstAid: ['Flüssigkeit abtupfen.', 'Mit etwas kaltem Wasser verdünnen.', 'Fleck nicht tiefer in die Faser drücken.'],
method: ['Mildes Reinigungsmittel auftragen.', 'Kurz warten.', 'Mit sauberem Tuch aufnehmen.', 'Bei Bedarf wiederholen.', 'Danach normal waschen oder trocknen lassen und prüfen.'],
avoid: ['Keine aggressive Seife auf empfindlichen Stoffen.', 'Nicht heiß ausspülen, wenn der Fleck frisch ist.'],
faqs: [
{ question: 'Ist Milchkaffee schwieriger?', answer: 'Ja, weil Fett und Eiweiß den Fleck etwas komplexer machen können.' },
{ question: 'Kann ich Mineralwasser nehmen?', answer: 'Für den ersten Schritt ja, solange du nur tupfst und nicht einreibst.' }
],
relatedMaterials: ['baumwolle', 'wolle', 'sofa', 'teppich']
},
{
slug: 'blut',
name: 'Blutfleck entfernen',
icon: '🩸',
category: 'Schwierige Flecken',
short: 'Kalt behandeln, ruhig arbeiten und Hitze konsequent vermeiden.',
summary: 'Blut reagiert empfindlich auf Wärme. Deshalb ist kaltes Vorgehen fast immer der sichere Start.',
bestFor: ['Kleidung', 'Bettwäsche', 'Matratzenbezug'],
firstAid: ['Immer mit kaltem Wasser starten.', 'Frisches Blut vorsichtig herausspülen oder abtupfen.', 'Nicht mit heißem Wasser testen.'],
method: ['Kalten Vorlauf nutzen.', 'Mild vorbehandeln.', 'Kurz einwirken lassen.', 'Erneut kalt ausspülen.', 'Vor dem Trocknen kontrollieren.'],
avoid: ['Keine Hitze.', 'Nicht föhnen.', 'Nicht sofort bügeln oder in den Trockner geben.'],
faqs: [
{ question: 'Warum kein warmes Wasser?', answer: 'Weil Eiweißbestandteile sich dadurch stärker festsetzen können.' },
{ question: 'Ist eingetrocknetes Blut aussichtslos?', answer: 'Nein, aber es braucht meist mehr Geduld und mehrere sanfte Durchgänge.' }
],
relatedMaterials: ['baumwolle', 'matratze', 'wolle']
},
{
slug: 'oel-fett',
name: 'Öl- und Fettfleck entfernen',
icon: '🫒',
category: 'Fett & Küche',
short: 'Öl sofort abnehmen und nicht durch Wärme fixieren.',
summary: 'Fettflecken wirken klein, verteilen sich aber leicht tief in Stoff oder Polster, wenn zu stark gerieben wird.',
bestFor: ['Baumwolle', 'Arbeitsplattenbezüge', 'Sofa', 'Tischtextilien'],
firstAid: ['Überschuss vorsichtig abnehmen.', 'Nicht verreiben.', 'Material prüfen, bevor du Reiniger nutzt.'],
method: ['Lokale Vorbehandlung.', 'Sanft abtupfen.', 'Bei Textilien anschließend passend waschen.', 'Bei Polstern mit wenig Feuchtigkeit arbeiten.'],
avoid: ['Keine Hitze vor dem Prüfen.', 'Nicht mit viel Wasser in Polster drücken.'],
faqs: [
{ question: 'Warum sieht der Fleck nach dem Trocknen manchmal größer aus?', answer: 'Weil sich Fett beim falschen Reinigen weiter ausbreiten kann.' },
{ question: 'Hilft Spülmittel immer?', answer: 'Oft ja, aber nicht auf jedem Material und nicht in beliebiger Menge.' }
],
relatedMaterials: ['baumwolle', 'sofa', 'teppich']
},
{
slug: 'grass',
name: 'Grasfleck entfernen',
icon: '🌿',
category: 'Outdoor & Alltag',
short: 'Grünpigmente sind hartnäckig, aber frühe Vorbehandlung hilft viel.',
summary: 'Grasflecken bestehen nicht nur aus Schmutz, sondern oft auch aus starken Pflanzenfarbstoffen.',
bestFor: ['Kinderkleidung', 'Jeans', 'Sporttextilien'],
firstAid: ['Erst losen Schmutz trocknen lassen oder vorsichtig entfernen.', 'Dann gezielt vorbehandeln.', 'Nicht sofort wild rubbeln.'],
method: ['Fleck lokal anlösen.', 'Ein paar Minuten arbeiten lassen.', 'Sanft ausreiben oder abtupfen.', 'Danach passend waschen.'],
avoid: ['Keine aggressive Bürste auf feinen Stoffen.', 'Nicht mit heißen Methoden starten.'],
faqs: [
{ question: 'Warum bleibt oft ein grüner Schatten?', answer: 'Weil Pflanzenpigmente tief in die Faser ziehen können.' },
{ question: 'Geht das aus weißer Kleidung besser raus?', answer: 'Nicht automatisch. Weiße Stoffe zeigen Restschatten oft sogar stärker.' }
],
relatedMaterials: ['baumwolle', 'polyester']
},
{
slug: 'schokolade',
name: 'Schokoladenfleck entfernen',
icon: '🍫',
category: 'Fett & Küche',
short: 'Erst Reste abnehmen, dann Fett- und Farbbestandteile vorsichtig lösen.',
summary: 'Schokolade ist eine Mischung aus Fett, Zucker und Farbe. Deshalb hilft ein ruhiger Mehr-Schritt-Ansatz.',
bestFor: ['Kleidung', 'Kindertextilien', 'Sofa'],
firstAid: ['Feste Reste abheben.', 'Nicht tiefer in die Faser drücken.', 'Fleck erst dann mit Feuchtigkeit behandeln.'],
method: ['Reste entfernen.', 'Mild entfetten.', 'Sanft abtupfen.', 'Bei Textilien waschbar nachbehandeln.'],
avoid: ['Nicht mit heißem Wasser starten.', 'Nichts verschmieren, solange Reste noch aufliegen.'],
faqs: [
{ question: 'Warum ist Schokolade nicht nur ein normaler Fleck?', answer: 'Weil Fett und Farbstoff zusammen oft mehr als einen Reinigungsschritt brauchen.' },
{ question: 'Kann ich sofort in die Wäsche geben?', answer: 'Besser vorher lokal behandeln, damit der Fleck nicht nur mitwandert.' }
],
relatedMaterials: ['baumwolle', 'sofa', 'teppich']
},
{
slug: 'tomatensauce',
name: 'Tomatensaucenfleck entfernen',
icon: '🍅',
category: 'Fett & Küche',
short: 'Tomate färbt stark, besonders wenn Öl und Gewürze dabei sind.',
summary: 'Tomatensauce wirkt harmlos, ist aber wegen Farbe, Öl und Gewürzen oft hartnäckiger als gedacht.',
bestFor: ['Baumwolle', 'Tischdecke', 'Küchentextilien'],
firstAid: ['Feste Reste abheben.', 'Flüssigkeit abtupfen.', 'Nicht einreiben.'],
method: ['Fleck kühlen oder mit kaltem Wasser anlösen.', 'Mild vorbehandeln.', 'Kurz einwirken lassen.', 'Dann materialgerecht reinigen.'],
avoid: ['Keine direkte Hitze.', 'Keine übereilte Trocknung.'],
faqs: [
{ question: 'Warum bleiben orange Schatten?', answer: 'Weil Farbstoffe und Fett zusammen tief in die Faser ziehen können.' },
{ question: 'Ist das bei Kunstfaser anders?', answer: 'Ja, je nach Faser kann der Farbstoff unterschiedlich haften.' }
],
relatedMaterials: ['baumwolle', 'polyester']
},
{
slug: 'make-up',
name: 'Make-up-Fleck entfernen',
icon: '💄',
category: 'Kosmetik',
short: 'Kosmetikflecken brauchen oft eine Mischung aus Fett- und Farblösung.',
summary: 'Foundation, Concealer oder Lippenprodukt sitzen gern an Kragen, Ärmeln oder Kissen und verhalten sich je nach Produkt anders.',
bestFor: ['Kragen', 'Blusen', 'Kissenbezüge', 'helle Kleidung'],
firstAid: ['Überschuss aufnehmen.', 'Nicht verstreichen.', 'Empfindliche Stoffe zuerst an unauffälliger Stelle testen.'],
method: ['Fleck lokal lösen.', 'Sanft von außen nach innen arbeiten.', 'Danach normal nachreinigen.'],
avoid: ['Keine starken Lösemittel ohne Test.', 'Nicht auf Seide oder Wolle experimentieren.'],
faqs: [
{ question: 'Warum sind helle Kragen so schwierig?', answer: 'Weil dort oft mehrere Produktreste gleichzeitig sitzen.' },
{ question: 'Hilft Abschminkprodukt?', answer: 'Nicht pauschal. Manche Produkte hinterlassen selbst Rückstände.' }
],
relatedMaterials: ['baumwolle', 'wolle', 'matratze']
},
{
slug: 'deo',
name: 'Deofleck entfernen',
icon: '🧴',
category: 'Kosmetik',
short: 'Deospuren bauen sich oft langsam auf und brauchen eher Geduld als Aggression.',
summary: 'Deoflecken sind häufig keine einmaligen Flecken, sondern Rückstände, die sich mit der Zeit sichtbar aufbauen.',
bestFor: ['T-Shirts', 'Hemden', 'Sportkleidung'],
firstAid: ['Bereich anfeuchten.', 'Nicht zu stark reiben.', 'Farbe des Stoffes mitdenken.'],
method: ['Milde lokale Vorbehandlung.', 'Kurz einwirken lassen.', 'Normal waschen und vor dem Trocknen prüfen.'],
avoid: ['Keine aggressive Behandlung auf dunklen Stoffen.', 'Keine Hitze vor der Kontrolle.'],
faqs: [
{ question: 'Warum kommen Deoflecken immer wieder?', answer: 'Weil Produktreste, Schweiß und Waschverhalten zusammenspielen.' },
{ question: 'Hilft mehr Waschmittel?', answer: 'Nicht unbedingt. Oft ist gezielte Vorbehandlung wichtiger.' }
],
relatedMaterials: ['baumwolle', 'polyester']
},
{
slug: 'tinte',
name: 'Tintenfleck entfernen',
icon: '🖊️',
category: 'Schwierige Flecken',
short: 'Tinte schnell begrenzen, sonst wandert sie leicht weiter.',
summary: 'Tinte verbreitet sich gern und reagiert je nach Stift oder Untergrund unterschiedlich.',
bestFor: ['Hemden', 'Schulkleidung', 'Arbeitsflächenbezüge'],
firstAid: ['Nicht verreiben.', 'Überschüssige Farbe aufnehmen.', 'Behandlung an unauffälliger Stelle testen.'],
method: ['Fleck lokalisieren.', 'Mit passender milder Lösung arbeiten.', 'Aufnehmen statt drücken.', 'Bei Bedarf wiederholen.'],
avoid: ['Nicht unter fließendem Wasser verschmieren.', 'Nicht mit zu vielen Mitteln gleichzeitig arbeiten.'],
faqs: [
{ question: 'Warum wird der Fleck manchmal größer?', answer: 'Weil flüssige Farbstoffe beim falschen Tupfen weiterziehen.' },
{ question: 'Ist Kugelschreiber anders als Füller?', answer: 'Ja, Zusammensetzung und Verhalten können unterschiedlich sein.' }
],
relatedMaterials: ['baumwolle', 'sofa']
},
{
slug: 'kerzenwachs',
name: 'Kerzenwachs entfernen',
icon: '🕯️',
category: 'Schwierige Flecken',
short: 'Erst aushärten lassen, dann mechanisch und danach rückstandsarm weiterarbeiten.',
summary: 'Wachs ist kein klassischer Nassfleck. Erst die feste Schicht, dann eventuelle Farbreste oder Fettspuren behandeln.',
bestFor: ['Tischdecke', 'Kleidung', 'Teppich mit Vorsicht'],
firstAid: ['Wachs fest werden lassen.', 'Vorsichtig abheben.', 'Material vor weiterer Behandlung prüfen.'],
method: ['Feste Reste lösen.', 'Restspuren lokal behandeln.', 'Danach Oberfläche oder Stoff kontrollieren.'],
avoid: ['Nicht frisches Wachs verschmieren.', 'Keine unkontrollierte Hitze auf empfindlichen Materialien.'],
faqs: [
{ question: 'Warum nicht sofort wischen?', answer: 'Weil du flüssiges Wachs damit meist nur größer verteilst.' },
{ question: 'Bleiben oft Farbreste zurück?', answer: 'Ja, besonders bei farbigen Kerzen.' }
],
relatedMaterials: ['baumwolle', 'teppich', 'holz']
},
{
slug: 'rost',
name: 'Rostfleck entfernen',
icon: '🧲',
category: 'Schwierige Flecken',
short: 'Rost ist heikel und sollte nicht mit beliebigen Hausmitteln behandelt werden.',
summary: 'Rostflecken sehen klein aus, reagieren aber auf falsche Mittel oft mit noch stärkeren Verfärbungen.',
bestFor: ['helle Textilien', 'robustere Stoffe'],
firstAid: ['Trockenen Fleck erst prüfen.', 'Materialempfindlichkeit beachten.', 'Gezielte Behandlung statt Experimente.'],
method: ['Geeignetes Mittel sparsam nutzen.', 'Lokal arbeiten.', 'Danach gründlich prüfen und eventuell nachspülen.'],
avoid: ['Nicht mit Chlorprodukten experimentieren.', 'Nicht wild kombinieren.'],
faqs: [
{ question: 'Warum ist Rost so speziell?', answer: 'Weil es keine normale Alltagsverschmutzung, sondern eine chemische Verfärbung ist.' },
{ question: 'Kann ich einfach stark schrubben?', answer: 'Nein, damit beschädigst du eher das Material als den Fleck.' }
],
relatedMaterials: ['baumwolle', 'leinen']
}
];
export const materials: MaterialGuide[] = [
{
slug: 'baumwolle',
name: 'Baumwolle',
short: 'Robust und oft gut behandelbar, solange Hitze und aggressive Mittel nicht unnötig früh kommen.',
sensitivity: 'niedrig bis mittel',
safeMoves: ['Erst tupfen, dann lokal behandeln.', 'Vor dem Trocknen immer prüfen.', 'Bei starken Flecken lieber zwei sanfte Durchgänge.'],
avoid: ['Nicht mit Hitze starten.', 'Nicht alles sofort maximal behandeln.'],
goodFor: ['Rotwein', 'Kaffee', 'Blut', 'Tomatensauce']
},
{
slug: 'wolle',
name: 'Wolle',
short: 'Empfindlich gegen Reibung, Temperaturwechsel und zu viel Wasser.',
sensitivity: 'hoch',
safeMoves: ['So wenig Bewegung wie möglich.', 'Kalt oder lauwarm denken.', 'Nur lokal und sehr ruhig arbeiten.'],
avoid: ['Nicht rubbeln.', 'Nicht heiß behandeln.', 'Nicht stark durchfeuchten.'],
goodFor: ['Kaffee', 'Make-up', 'Blut']
},
{
slug: 'polyester',
name: 'Polyester',
short: 'Pflegeleicht, aber fettige oder farbige Flecken können je nach Oberfläche hartnäckig sein.',
sensitivity: 'mittel',
safeMoves: ['Lokal behandeln.', 'Nicht unnötig heiß werden lassen.', 'Sporttextilien zügig nachbehandeln.'],
avoid: ['Keine starke Hitze.', 'Keine ungetesteten Lösemittel.'],
goodFor: ['Gras', 'Deo', 'Tomatensauce']
},
{
slug: 'sofa',
name: 'Sofabezug',
short: 'Polster brauchen wenig Feuchtigkeit und sauberes, kontrolliertes Tupfen.',
sensitivity: 'mittel bis hoch',
safeMoves: ['Immer wenig Flüssigkeit.', 'Von außen nach innen arbeiten.', 'Schnell trocknen lassen.'],
avoid: ['Nichts tief einarbeiten.', 'Nicht zu nass reinigen.'],
goodFor: ['Rotwein', 'Kaffee', 'Öl', 'Schokolade']
},
{
slug: 'teppich',
name: 'Teppich',
short: 'Flecken sitzen oft tiefer als auf glatten Flächen und dürfen nicht großflächig verteilt werden.',
sensitivity: 'mittel',
safeMoves: ['Tupfen statt reiben.', 'Nur punktuell reinigen.', 'Trocknungsphase ernst nehmen.'],
avoid: ['Keine nassen Kreise ziehen.', 'Nicht mit Bürsten eskalieren.'],
goodFor: ['Rotwein', 'Kaffee', 'Kerzenwachs']
},
{
slug: 'matratze',
name: 'Matratze',
short: 'Hier zählt vor allem kontrollierte Feuchtigkeit und gründliches Nachtrocknen.',
sensitivity: 'hoch',
safeMoves: ['Sehr sparsam arbeiten.', 'Feuchtigkeit aufnehmen.', 'Lüftung und Trocknung einplanen.'],
avoid: ['Keine starke Durchnässung.', 'Nicht sofort wieder abdecken.'],
goodFor: ['Blut', 'Rotwein', 'Make-up']
},
{
slug: 'holz',
name: 'Holzoberfläche',
short: 'Holz reagiert schnell auf Feuchtigkeit, Hitze und aggressive Reiniger.',
sensitivity: 'mittel bis hoch',
safeMoves: ['Sofort abnehmen.', 'Sehr wenig Feuchtigkeit.', 'Finish und Versiegelung mitdenken.'],
avoid: ['Nichts stehen lassen.', 'Keine scheuernden Mittel.'],
goodFor: ['Kerzenwachs', 'Kaffee']
}
];
export const guides: GuideItem[] = [
{
slug: 'frischer-fleck-erste-hilfe',
title: 'Erste Hilfe bei frischen Flecken',
short: 'Was fast immer hilft, bevor du irgendetwas verschlimmerst.',
intro: 'Der größte Fehler bei Flecken passiert oft in den ersten zwei Minuten: zu viel Reibung, zu viel Wasser oder zu viel Eile.',
steps: ['Fleck identifizieren.', 'Überschuss abtupfen oder abheben.', 'Material prüfen.', 'Lokal und ruhig arbeiten.', 'Vor Hitze immer kontrollieren.'],
tips: ['Ein sauberer heller Lappen ist oft besser als Küchenrolle mit Farbe.', 'Mehr Mittel ist nicht automatisch besser.', 'Wenn du unsicher bist, erst an einer unauffälligen Stelle testen.']
},
{
slug: 'hitze-vermeiden',
title: 'Warum Hitze viele Flecken schlimmer macht',
short: 'Trockner, Föhn und Bügeleisen sind oft zu früh im Spiel.',
intro: 'Viele Flecken werden erst dann richtig hartnäckig, wenn sie zu früh getrocknet oder erhitzt werden.',
steps: ['Fleck nach Behandlung im Licht prüfen.', 'Nur trocknen, wenn kaum noch Rückstand sichtbar ist.', 'Lieber noch einmal sanft nacharbeiten als zu früh Hitze nutzen.'],
tips: ['Das gilt besonders für Blut, Rotwein, Tomate und Fettspuren.', 'Auch warme Raumluft kann Restschatten sichtbarer machen.']
},
{
slug: 'polster-richtig-reinigen',
title: 'Polster reinigen, ohne Wasserränder zu erzeugen',
short: 'Wenig Feuchtigkeit, klare Kanten und sauberes Nachtrocknen.',
intro: 'Bei Sofa und Matratze ist nicht nur der Fleck das Problem, sondern oft auch der Rand danach.',
steps: ['Nur wenig Lösung verwenden.', 'Immer von außen nach innen arbeiten.', 'Mit trockenem Tuch nachgehen.', 'Gut nachtrocknen lassen.'],
tips: ['Zu viel Druck verteilt den Fleck oft tiefer.', 'Große Flächen möglichst gleichmäßig behandeln.']
},
{
slug: 'hausmittel-mit-vorsicht',
title: 'Hausmittel bei Flecken: was oft hilft und was riskant ist',
short: 'Nicht jedes virale Hausmittel ist eine gute Idee.',
intro: 'Viele Hausmittel funktionieren manchmal, aber oft nur auf bestimmten Materialien oder bei bestimmten Flecken.',
steps: ['Immer erst Material prüfen.', 'Nicht mehrere Mittel gleichzeitig mischen.', 'Nur lokal testen.', 'Ergebnis beobachten statt blind nachkippen.'],
tips: ['Je empfindlicher der Stoff, desto weniger experimentieren.', 'Ein gutes Ergebnis braucht oft eher Ruhe als Härte.']
}
];
+10
View File
@@ -0,0 +1,10 @@
export const privacyHighlights = [
'Für das Lesen der Inhalte ist kein Konto nötig.',
'Die Hinweise dienen der allgemeinen Information und ersetzen nicht die Pflegehinweise von Herstellern.',
'Später eingebundene Werbung kann Cookies oder ähnliche Technologien verwenden.',
];
export const copyrightSafetyNote = {
title: 'Hinweis zu Inhalten und Grafiken',
text: 'Die Inhalte, Tabellen und vereinfachten Icons auf dieser Website sind originär erstellt. Es werden keine offiziellen Fremdgrafiken oder kopierten Herstellertexte als Kernelement der Website verwendet.'
};
+22
View File
@@ -0,0 +1,22 @@
export const site = {
name: 'Fleckenhilfe',
domain: 'https://fleckenhilfe.de',
language: 'de',
title: 'Fleckenhilfe | Flecken entfernen ohne Material zu ruinieren',
description:
'Fleckenhilfe zeigt dir einfache erste Schritte gegen Rotwein, Öl, Kaffee, Blut, Make-up und andere Alltagsflecken. Klar erklärt, materialschonend und schnell nutzbar.',
tagline: 'Klare Hilfe bei Flecken auf Kleidung, Sofa, Teppich und mehr.',
heroTitle: 'Flecken entfernen, ohne gleich alles schlimmer zu machen.',
heroText:
'Fleckenhilfe erklärt, was du bei typischen Flecken zuerst tun solltest, was du besser lässt und welche Methode zu Material und Oberfläche passt.',
};
export const company = {
name: 'Fleckenhilfe',
owner: 'Justin Michael Eckenweber',
email: 'hey@unverpackt-finder.de',
phone: '028160031948',
addressLine1: 'Rühlestraße 5',
addressLine2: '45147 Essen',
addressLine3: 'Deutschland',
};
+65
View File
@@ -0,0 +1,65 @@
---
import '../styles/global.css';
import { company, site } from '../data/site';
interface Props {
title?: string;
description?: string;
}
const { title = site.title, description = site.description } = Astro.props;
const canonical = new URL(Astro.url.pathname, site.domain).toString();
const adsenseClient = import.meta.env.PUBLIC_ADSENSE_CLIENT;
---
<html lang={site.language}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="generator" content={Astro.generator} />
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" href="/favicon.ico" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content={site.name} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonical} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<title>{title}</title>
{adsenseClient && (
<script async src={`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${adsenseClient}`} crossorigin="anonymous"></script>
)}
</head>
<body>
<header class="site-header">
<div class="container nav">
<a class="brand" href="/">{site.name}</a>
<nav class="nav-links" aria-label="Hauptnavigation">
<a href="/#finder">Finder</a>
<a href="/#flecken">Flecken</a>
<a href="/#materialien">Materialien</a>
<a href="/#ratgeber">Ratgeber</a>
</nav>
</div>
</header>
<slot />
<footer class="site-footer">
<div class="container footer-grid">
<div>
<strong>{site.name}</strong>
<p>{site.tagline}</p>
</div>
<div class="footer-links">
<a href="/">Start</a>
<a href="/tools/flecken-entferner">Fleck-Finder</a>
<a href="/privacy-policy">Datenschutz</a>
<a href="/legal-notice">Impressum</a>
</div>
</div>
</footer>
</body>
</html>
+87
View File
@@ -0,0 +1,87 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import AdSlot from '../../components/AdSlot.astro';
import { stains, materials } from '../../data/content';
export function getStaticPaths() {
return stains.map((stain) => ({ params: { slug: stain.slug }, props: { stain } }));
}
const { stain } = Astro.props;
const relatedMaterials = materials.filter((material) => stain.relatedMaterials.includes(material.slug));
---
<BaseLayout title={`${stain.name} | Fleckenhilfe`} description={stain.short}>
<main class="section">
<div class="container detail-layout">
<article class="article-shell">
<div class="eyebrow-row">
<span class="eyebrow">{stain.category}</span>
<span class="chip">schnelle Hilfe</span>
</div>
<h1>{stain.name}</h1>
<p class="lead-copy">{stain.summary}</p>
<div class="hero-tip-box">
<span class="result-icon result-icon--large">{stain.icon}</span>
<div>
<h2>Ersthilfe in kurz</h2>
<p>{stain.short}</p>
</div>
</div>
<section class="content-panel">
<h2>Dafür ist diese Anleitung besonders geeignet</h2>
<div class="tag-row">
{stain.bestFor.map((item) => <span class="chip">{item}</span>)}
</div>
</section>
<section class="content-panel">
<h2>Was du sofort tun solltest</h2>
<ol class="number-list">
{stain.firstAid.map((step) => <li>{step}</li>)}
</ol>
</section>
<section class="content-panel">
<h2>So gehst du weiter vor</h2>
<ol class="number-list">
{stain.method.map((step) => <li>{step}</li>)}
</ol>
</section>
<section class="content-panel content-panel--warn">
<h2>Lieber vermeiden</h2>
<ul class="simple-list">
{stain.avoid.map((item) => <li>{item}</li>)}
</ul>
</section>
<section class="content-panel">
<h2>Häufige Fragen</h2>
<div class="faq-stack">
{stain.faqs.map((faq) => (
<article class="faq-card">
<h3>{faq.question}</h3>
<p>{faq.answer}</p>
</article>
))}
</div>
</section>
</article>
<aside class="sidebar-shell">
<div class="sidebar-card">
<h2>Material zuerst prüfen</h2>
<ul class="simple-list">
{relatedMaterials.map((material) => (
<li><a class="button-link" href={`/materialien/${material.slug}`}>{material.name}</a></li>
))}
</ul>
</div>
<AdSlot className="sidebar-ad" />
</aside>
</div>
</main>
</BaseLayout>
+134
View File
@@ -0,0 +1,134 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import StainFinder from '../components/StainFinder.astro';
import { site } from '../data/site';
import { stains, materials, guides } from '../data/content';
import { copyrightSafetyNote } from '../data/legal';
---
<BaseLayout>
<main>
<section class="hero-block">
<div class="container hero-grid hero-grid--single-focus">
<div>
<div class="eyebrow-row">
<span class="eyebrow">Haushaltshilfe statt Hersteller-Blabla</span>
<span class="chip chip--accent">neu gedacht</span>
</div>
<h1>{site.heroTitle}</h1>
<p class="hero-copy">{site.heroText}</p>
<div class="hero-actions">
<a class="cta-primary" href="#finder">Fleck suchen</a>
<a class="cta-secondary" href="#materialien">Material zuerst prüfen</a>
</div>
<div class="hero-stats">
<div><strong>{stains.length}</strong><span>Fleck-Anleitungen</span></div>
<div><strong>{materials.length}</strong><span>Material-Guides</span></div>
<div><strong>{guides.length}</strong><span>praktische Ratgeber</span></div>
</div>
<div class="hero-inline-note">
<strong>Sinnvoller als Herstellerseiten:</strong> erst der sichere erste Schritt, dann die passende Methode für Material und Oberfläche.
</div>
</div>
</div>
</section>
<section id="finder" class="section section--overlap">
<div class="container feature-shell feature-shell--primary">
<div class="section-head section-head--tool">
<div>
<span class="eyebrow">Fleck-Finder</span>
<h2>Wähle Fleckart und geh direkt zur passenden Ersthilfe</h2>
</div>
<p>
Für den Alltag gebaut: erst suchen, dann ruhig und materialgerecht handeln.
</p>
</div>
<StainFinder stains={stains} compact={true} />
</div>
</section>
<section class="section">
<div class="container note-box">
<strong>{copyrightSafetyNote.title}</strong>
<p>{copyrightSafetyNote.text}</p>
</div>
</section>
<section id="materialien" class="section section--tinted">
<div class="container">
<div class="section-head">
<div>
<span class="eyebrow">Material zuerst mitdenken</span>
<h2>Was auf Baumwolle funktioniert, ist auf Wolle oft schon zu viel</h2>
</div>
</div>
<div class="material-list">
{materials.map((material) => (
<article class="material-row">
<div>
<h3>{material.name}</h3>
<p>{material.short}</p>
</div>
<div class="material-meta">
<span class="chip">Empfindlichkeit: {material.sensitivity}</span>
<a class="button-link" href={`/materialien/${material.slug}`}>Guide öffnen</a>
</div>
</article>
))}
</div>
</div>
</section>
<section id="flecken" class="section">
<div class="container">
<div class="section-head">
<div>
<span class="eyebrow">Beliebte Flecken</span>
<h2>Die häufigsten Problemfälle auf einen Blick</h2>
</div>
</div>
<div class="quick-link-grid">
{stains.slice(0, 6).map((stain) => (
<a class="quick-link-card" href={`/flecken/${stain.slug}`}>
<span class="result-icon">{stain.icon}</span>
<div>
<strong>{stain.name}</strong>
<p>{stain.short}</p>
</div>
</a>
))}
</div>
</div>
</section>
<section id="ratgeber" class="section">
<div class="container two-column-feature">
<div>
<span class="eyebrow">Ratgeber</span>
<h2>Die Fehler passieren meistens vor dem eigentlichen Reinigen</h2>
<p>
Darum deckt Fleckenhilfe nicht nur Fleckarten ab, sondern auch die typischen Fehlgriffe: Hitze zu früh,
Hausmittel ohne Test oder zu viel Wasser auf Polstern.
</p>
</div>
<div class="guide-stack">
{guides.map((guide) => (
<article class="guide-card">
<h3>{guide.title}</h3>
<p>{guide.short}</p>
<a class="button-link" href={`/ratgeber/${guide.slug}`}>Weiterlesen</a>
</article>
))}
</div>
</div>
</section>
<section class="section">
<div class="container note-box">
<strong>{copyrightSafetyNote.title}</strong>
<p>{copyrightSafetyNote.text}</p>
</div>
</section>
</main>
</BaseLayout>
+26
View File
@@ -0,0 +1,26 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import { company } from '../data/site';
---
<BaseLayout title="Impressum | Fleckenhilfe" description="Impressum von Fleckenhilfe.">
<main class="section legal-page">
<div class="container legal-shell">
<span class="eyebrow">Rechtliches</span>
<h1>Impressum</h1>
<p>Angaben gemäß § 5 TMG</p>
<p>
{company.owner}<br />
{company.addressLine1}<br />
{company.addressLine2}<br />
{company.addressLine3}
</p>
<p>
Kontakt<br />
E-Mail: {company.email}<br />
Telefon: {company.phone}
</p>
<p>Inhaltlich verantwortlich: {company.owner}</p>
</div>
</main>
</BaseLayout>
+53
View File
@@ -0,0 +1,53 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import AdSlot from '../../components/AdSlot.astro';
import { materials, stains } from '../../data/content';
export function getStaticPaths() {
return materials.map((material) => ({ params: { slug: material.slug }, props: { material } }));
}
const { material } = Astro.props;
const fittingStains = stains.filter((stain) => material.goodFor.some((name) => stain.name.toLowerCase().includes(name.toLowerCase()) || stain.slug.includes(name.toLowerCase()))).slice(0, 5);
---
<BaseLayout title={`${material.name} bei Flecken | Fleckenhilfe`} description={material.short}>
<main class="section">
<div class="container detail-layout">
<article class="article-shell">
<span class="eyebrow">Material-Guide</span>
<h1>{material.name} bei Flecken richtig behandeln</h1>
<p class="lead-copy">{material.short}</p>
<section class="content-panel">
<h2>Empfindlichkeit</h2>
<p>{material.sensitivity}</p>
</section>
<section class="content-panel">
<h2>Sichere Züge</h2>
<ul class="simple-list">
{material.safeMoves.map((item) => <li>{item}</li>)}
</ul>
</section>
<section class="content-panel content-panel--warn">
<h2>Darauf lieber verzichten</h2>
<ul class="simple-list">
{material.avoid.map((item) => <li>{item}</li>)}
</ul>
</section>
</article>
<aside class="sidebar-shell">
<div class="sidebar-card">
<h2>Typische Flecken für dieses Material</h2>
<ul class="simple-list">
{material.goodFor.map((item) => <li>{item}</li>)}
</ul>
</div>
<AdSlot className="sidebar-ad" />
</aside>
</div>
</main>
</BaseLayout>
+38
View File
@@ -0,0 +1,38 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import { company } from '../data/site';
import { privacyHighlights, copyrightSafetyNote } from '../data/legal';
---
<BaseLayout title="Datenschutz | Fleckenhilfe" description="Datenschutzhinweise für Fleckenhilfe.">
<main class="section legal-page">
<div class="container legal-shell">
<span class="eyebrow">Rechtliches</span>
<h1>Datenschutz</h1>
<p>Diese Website bietet allgemeine Hinweise zum Entfernen von Flecken auf unterschiedlichen Materialien und Oberflächen.</p>
<section class="content-panel">
<h2>Verantwortlich</h2>
<p>
{company.owner}<br />
{company.addressLine1}<br />
{company.addressLine2}<br />
{company.addressLine3}
</p>
<p>E-Mail: {company.email}</p>
</section>
<section class="content-panel">
<h2>Wichtige Hinweise</h2>
<ul class="simple-list">
{privacyHighlights.map((item) => <li>{item}</li>)}
</ul>
</section>
<section class="content-panel">
<h2>{copyrightSafetyNote.title}</h2>
<p>{copyrightSafetyNote.text}</p>
</section>
</div>
</main>
</BaseLayout>
+45
View File
@@ -0,0 +1,45 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import AdSlot from '../../components/AdSlot.astro';
import { guides } from '../../data/content';
export function getStaticPaths() {
return guides.map((guide) => ({ params: { slug: guide.slug }, props: { guide } }));
}
const { guide } = Astro.props;
---
<BaseLayout title={`${guide.title} | Fleckenhilfe`} description={guide.short}>
<main class="section">
<div class="container detail-layout">
<article class="article-shell">
<span class="eyebrow">Ratgeber</span>
<h1>{guide.title}</h1>
<p class="lead-copy">{guide.intro}</p>
<section class="content-panel">
<h2>Praktische Schritte</h2>
<ol class="number-list">
{guide.steps.map((step) => <li>{step}</li>)}
</ol>
</section>
<section class="content-panel">
<h2>Wichtige Hinweise</h2>
<ul class="simple-list">
{guide.tips.map((tip) => <li>{tip}</li>)}
</ul>
</section>
</article>
<aside class="sidebar-shell">
<div class="sidebar-card">
<h2>Warum das nützlich ist</h2>
<p>{guide.short}</p>
</div>
<AdSlot className="sidebar-ad" />
</aside>
</div>
</main>
</BaseLayout>
+24
View File
@@ -0,0 +1,24 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import StainFinder from '../../components/StainFinder.astro';
import AdSlot from '../../components/AdSlot.astro';
import { stains } from '../../data/content';
---
<BaseLayout title="Fleck-Finder | Fleckenhilfe" description="Finde schnell die passende Anleitung für Rotwein, Kaffee, Fett, Blut, Deo und andere Alltagsflecken.">
<main class="section">
<div class="container page-intro">
<span class="eyebrow">Tool</span>
<h1>Fleck-Finder</h1>
<p>Suche nach Fleckart oder filtere nach Kategorie. Die Ergebnisse führen direkt zu einer klaren Anleitung.</p>
</div>
<div class="container feature-shell">
<StainFinder stains={stains} />
</div>
<div class="container section">
<AdSlot label="Gesponserter Bereich" />
</div>
</main>
</BaseLayout>
+446
View File
@@ -0,0 +1,446 @@
body {
margin: 0;
font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(180deg, #f7f9fc 0%, #ffffff 24%);
color: #080808;
}
* { box-sizing: border-box; }
a { color: inherit; text-decoration: none; }
.container {
width: min(1180px, calc(100% - 32px));
margin: 0 auto;
}
.site-header {
position: sticky;
top: 0;
z-index: 30;
backdrop-filter: blur(10px);
background: rgba(247, 249, 252, 0.88);
border-bottom: 1px solid #d8d8d8;
}
.nav {
min-height: 74px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
}
.brand {
font-size: 1.1rem;
font-weight: 700;
color: #080808;
}
.nav-links,
.footer-links {
display: flex;
flex-wrap: wrap;
gap: 18px;
color: #363636;
font-size: 1rem;
}
.hero-block {
padding: 88px 0 32px;
}
.hero-grid,
.detail-layout,
.two-column-feature,
.footer-grid {
display: grid;
grid-template-columns: 1.15fr 0.85fr;
gap: 28px;
}
.hero-grid--single-focus {
grid-template-columns: minmax(0, 920px);
}
.hero-grid h1,
h1 {
font-size: clamp(3rem, 6vw, 5.2rem);
line-height: 1.02;
letter-spacing: -0.06em;
margin: 0 0 16px;
}
h2 {
font-size: clamp(1.6rem, 3vw, 2.6rem);
line-height: 1.08;
margin: 0 0 12px;
}
h3 {
font-size: 1.15rem;
margin: 0 0 10px;
}
p, li {
color: #222222;
font-size: 1.04rem;
line-height: 1.72;
}
.section {
padding: 38px 0 64px;
}
.section--overlap { padding-top: 8px; }
.section--tinted { background: linear-gradient(180deg, rgba(20,110,245,0.05), rgba(122,61,255,0.03)); }
.eyebrow {
display: inline-block;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 1.3px;
color: #146ef5;
font-weight: 700;
}
.eyebrow-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
margin-bottom: 18px;
}
.chip {
display: inline-flex;
align-items: center;
min-height: 30px;
padding: 0 12px;
border-radius: 999px;
border: 1px solid #d8d8d8;
background: #fff;
color: #222;
font-size: 0.86rem;
font-weight: 600;
}
.chip--accent {
border-color: rgba(20,110,245,0.2);
background: rgba(20,110,245,0.1);
color: #146ef5;
}
.hero-copy { max-width: 720px; font-size: 1.08rem; }
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 22px 0 24px;
}
.cta-primary,
.cta-secondary,
.cta-outline,
.button-link {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 48px;
padding: 0 18px;
border-radius: 8px;
font-weight: 600;
transition: transform 160ms ease, border-color 160ms ease, background 160ms ease;
}
.cta-primary {
background: #146ef5;
color: #fff;
box-shadow: rgba(0,0,0,0) 0px 84px 24px, rgba(0,0,0,0.01) 0px 54px 22px, rgba(0,0,0,0.04) 0px 30px 18px, rgba(0,0,0,0.08) 0px 13px 13px, rgba(0,0,0,0.09) 0px 3px 7px;
}
.cta-secondary,
.cta-outline,
.button-link {
background: #fff;
color: #080808;
border: 1px solid #d8d8d8;
}
.cta-primary:hover,
.cta-secondary:hover,
.cta-outline:hover,
.button-link:hover { transform: translateY(-2px); border-color: #898989; }
.hero-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.hero-stats div,
.hero-panel,
.feature-shell,
.teaser-card,
.result-card,
.material-row,
.guide-card,
.content-panel,
.sidebar-card,
.note-box,
.ad-shell,
.faq-card,
.legal-shell {
background: #fff;
border: 1px solid #d8d8d8;
border-radius: 8px;
box-shadow: rgba(0,0,0,0) 0px 84px 24px, rgba(0,0,0,0.01) 0px 54px 22px, rgba(0,0,0,0.04) 0px 30px 18px, rgba(0,0,0,0.08) 0px 13px 13px, rgba(0,0,0,0.09) 0px 3px 7px;
}
.hero-stats div {
padding: 16px;
}
.hero-inline-note {
margin-top: 16px;
padding: 14px 16px;
border-left: 4px solid #146ef5;
background: rgba(20,110,245,0.06);
border-radius: 8px;
max-width: 760px;
}
.hero-stats strong {
display: block;
font-size: 1.55rem;
margin-bottom: 6px;
}
.hero-stats span { color: #5a5a5a; font-size: 0.92rem; }
.feature-shell {
padding: 24px;
background: linear-gradient(180deg, #ffffff, #fbfdff);
}
.feature-shell--primary {
border-color: rgba(20,110,245,0.24);
}
.section-head {
display: grid;
grid-template-columns: 1fr minmax(240px, 340px);
gap: 18px;
align-items: end;
margin-bottom: 22px;
}
.section-head--tool {
margin-bottom: 18px;
}
.field { display: grid; gap: 8px; }
.field span { font-size: 0.9rem; font-weight: 600; color: #222; }
.finder__controls {
display: grid;
grid-template-columns: minmax(0, 1.7fr) minmax(200px, 0.7fr);
gap: 14px;
margin-bottom: 18px;
}
.field input,
.field select {
min-height: 50px;
border: 1px solid #d8d8d8;
border-radius: 8px;
padding: 0 14px;
font: inherit;
background: #fff;
}
.finder__results,
.card-grid,
.guide-stack,
.faq-stack {
display: grid;
gap: 18px;
}
.finder__results,
.card-grid--three {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.result-card,
.teaser-card,
.guide-card,
.faq-card { padding: 18px; }
.result-card__top,
.teaser-card__top {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 14px;
}
.result-icon {
width: 52px;
height: 52px;
display: inline-grid;
place-items: center;
border-radius: 8px;
background: linear-gradient(135deg, rgba(20,110,245,0.14), rgba(122,61,255,0.14));
font-size: 1.45rem;
}
.result-icon--large {
width: 74px;
height: 74px;
font-size: 2rem;
}
.result-category {
color: #146ef5;
font-size: 0.82rem;
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 700;
}
.finder__footer { margin-top: 18px; }
.finder__empty { margin-top: 18px; }
.material-list { display: grid; gap: 14px; }
.quick-link-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.quick-link-card {
display: grid;
grid-template-columns: auto 1fr;
gap: 14px;
align-items: start;
padding: 18px;
background: #fff;
border: 1px solid #d8d8d8;
border-radius: 8px;
box-shadow: rgba(0,0,0,0) 0px 84px 24px, rgba(0,0,0,0.01) 0px 54px 22px, rgba(0,0,0,0.04) 0px 30px 18px, rgba(0,0,0,0.08) 0px 13px 13px, rgba(0,0,0,0.09) 0px 3px 7px;
}
.quick-link-card strong {
display: block;
margin-bottom: 6px;
}
.quick-link-card p {
margin: 0;
}
.material-row {
padding: 18px;
display: grid;
grid-template-columns: 1fr auto;
gap: 16px;
align-items: center;
}
.material-meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
justify-content: flex-end;
}
.two-column-feature { align-items: start; }
.guide-stack { grid-template-columns: 1fr; }
.note-box, .legal-shell, .article-shell { padding: 24px; }
.note-box strong { display: block; margin-bottom: 10px; }
.detail-layout { align-items: start; }
.article-shell { display: grid; gap: 18px; }
.sidebar-shell { display: grid; gap: 18px; }
.sidebar-card { padding: 20px; position: sticky; top: 92px; }
.sidebar-ad { position: sticky; top: 320px; }
.hero-tip-box {
display: grid;
grid-template-columns: auto 1fr;
gap: 16px;
padding: 20px;
border: 1px solid #d8d8d8;
border-radius: 8px;
background: linear-gradient(180deg, rgba(20,110,245,0.06), rgba(122,61,255,0.05));
}
.lead-copy { font-size: 1.08rem; }
.tag-row { display: flex; flex-wrap: wrap; gap: 10px; }
.number-list { padding-left: 20px; }
.simple-list { padding-left: 18px; }
.content-panel { padding: 20px; }
.content-panel--warn { background: linear-gradient(180deg, rgba(238,29,54,0.04), #fff); }
.ad-shell { padding: 18px; }
.ad-shell__label {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 1.4px;
color: #7a3dff;
margin-bottom: 10px;
font-weight: 700;
}
.ad-shell__placeholder {
min-height: 96px;
display: grid;
place-items: center;
border: 1px dashed #ababab;
border-radius: 8px;
color: #5a5a5a;
text-align: center;
padding: 20px;
background: linear-gradient(180deg, #fff, #f7f9fc);
}
.page-intro { margin-bottom: 22px; }
.legal-shell { width: min(820px, 100%); }
.site-footer {
border-top: 1px solid #d8d8d8;
background: #080808;
color: #fff;
margin-top: 48px;
}
.site-footer p, .site-footer a { color: rgba(255,255,255,0.76); }
.footer-grid {
padding: 28px 0 40px;
align-items: start;
}
@media (max-width: 980px) {
.hero-grid,
.detail-layout,
.two-column-feature,
.footer-grid,
.section-head,
.finder__results,
.card-grid--three,
.hero-stats,
.material-row,
.quick-link-grid {
grid-template-columns: 1fr;
}
.material-meta { justify-content: flex-start; }
.sidebar-card, .sidebar-ad { position: static; }
}
@media (max-width: 640px) {
.finder__controls { grid-template-columns: 1fr; }
.hero-block { padding-top: 56px; }
}
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}