diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml new file mode 100644 index 0000000..cdd882d --- /dev/null +++ b/.github/workflows/ci-pr.yml @@ -0,0 +1,47 @@ +# PR-triggered CI on PRs targeting `main`. +# Runs `astro check` (TypeScript + Astro templates) and a full build. +# The `build-pr` job name is referenced as a required status check on +# the `protect-main` ruleset — see docs/TECH_STACK.md §8.4. +# +# `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24` opts into Node 24 for JS-based +# actions, ahead of GitHub's runtime default flip on 2026-06-02. + +name: CI — PR + +on: + pull_request: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-pr-${{ github.head_ref }} + cancel-in-progress: true + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + +jobs: + build-pr: + name: build-pr + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Astro check + run: npm run check + + - name: Build + run: npm run build diff --git a/CLAUDE.md b/CLAUDE.md index 33c6745..4b42681 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,6 +11,23 @@ Read these in order: 3. `docs/BASELINE_COPY.md` — editorial content for every page. Use verbatim. 4. `design/handoff-bundle/README.md` — the Claude Design export, if present. +## Workspace bootstrap + +After the docs above, orient against the working state: + +1. `plans/BOOTSTRAP.md` — current project state, in-flight topics, recent + decisions. The slow-moving entry point. +2. `plans/INDEX.md` — table of contents for the workspace. Claude-maintained + via propose-then-validate; never hand-edited. +3. `plans/HANDOFFS/` — if a file dated within the last week exists, read the + most recent one before acting; it carries the previous session's tail + state. + +Working artefacts (plans, sub-agent reports, drafts, decision logs) live +under `plans/active//`; closed work lives under `plans/_archive/`. +Naming, write-path, and INDEX-maintenance conventions: `plans/meta/conventions.md`. +Sub-agents dispatch on Opus by default; Sonnet only for mechanical tasks (justification in prompt frontmatter). + ## Core principles - This is a **content website**, not an application. Prioritize performance, @@ -44,6 +61,44 @@ feel. **Pass 2 — remaining pages.** After Pass 1 is validated in real deployment, implement Products (with empty state), Contact, Impressum, Datenschutz, and 404. +## Gate-based handoff pattern + +Pass 1 shipped a working production system on the first production deploy +despite three failed staging attempts. The discipline below is what made that +possible; Pass 2 should follow it. + +- For implementation work involving multiple decision points (token + conversions, multi-step deploys, significant architecture changes), produce + a plan document first and surface it for review before starting. Don't + begin implementation until explicit approval. +- For each significant commit or deploy, pause at named gates (e.g., after + `wrangler.jsonc` is written, before first deploy) and wait for confirmation. +- For diagnostic work, report findings before making changes. Don't edit + based on an assumed fix until the diagnostic has been reviewed. +- Surface uncertainty as explicit open questions in plans, not resolved + silently. + +## Process rules from Pass 1 + +Specific technical lessons worth codifying: + +- **Token conversion verification.** When translating design tokens between + framework versions, diff against **both** the source preset **and** any + companion CSS-custom-property file in the handoff bundle. Presets may omit + aliases that the companion file declares for readability. The Pass 1 + missing-`--color-bg` bug slipped through because verification compared the + converted file against the preset only. +- **Wrangler environment inheritance.** `wrangler.jsonc` top-level config does + **not** inherit into named environments for several keys: `observability`, + `vars`, `routes`, `name`, `main`, `compatibility_flags` (and more). Declare + these inside each `env.*` block when using `--env` in `wrangler deploy`. +- **Primary-source verification for API / namespace questions.** When a task + depends on version-specific API behavior (framework routing, build-tool + namespaces, CLI permissions), verify against primary sources — the + framework's own source code, official docs, maintainer statements in + issues/discussions — before assuming. A targeted sub-agent with web search + is a valid escalation pattern and was used three times in Pass 1. + ## Skills When writing or modifying UI components, invoke the `frontend-design` skill. diff --git a/astro.config.mjs b/astro.config.mjs index 204d64f..54a4fd4 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -13,6 +13,13 @@ export default defineConfig({ prefixDefaultLocale: false, }, }, + // Disable smartypants so Markdown body content preserves the ASCII + // apostrophes ('), straight quotes, and `--` from BASELINE_COPY.md + // verbatim. The default (enabled) silently rewrites them to typographic + // forms, violating the "use copy verbatim" project rule. + markdown: { + smartypants: false, + }, vite: { plugins: [tailwindcss()], }, diff --git a/docs/BASELINE_COPY.md b/docs/BASELINE_COPY.md index abe2475..f74eba9 100644 --- a/docs/BASELINE_COPY.md +++ b/docs/BASELINE_COPY.md @@ -169,12 +169,12 @@ Kleinunternehmer gemäß § 19 UStG. Es wird keine Umsatzsteuer ausgewiesen. USt-IdNr. gemäß § 27a UStG DE461658750 -Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV -Lars Weiser (Anschrift wie oben) - Haftungsausschluss Die Inhalte dieser Website wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte kann jedoch keine Gewähr übernommen werden. Als Diensteanbieter bin ich gemäß § 7 Abs. 1 DDG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 DDG bin ich als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen. +Haftung für Links +Diese Website enthält Links zu externen Webseiten Dritter, auf deren Inhalte ich keinen Einfluss habe. Für diese fremden Inhalte kann ich keine Gewähr übernehmen; verantwortlich ist stets der jeweilige Anbieter oder Betreiber der verlinkten Seiten. Die verlinkten Seiten wurden zum Zeitpunkt der Verlinkung auf mögliche Rechtsverstöße geprüft; rechtswidrige Inhalte waren zu diesem Zeitpunkt nicht erkennbar. Eine dauerhafte inhaltliche Kontrolle der verlinkten Seiten ist ohne konkrete Anhaltspunkte einer Rechtsverletzung nach §§ 8 bis 10 DDG nicht zumutbar. Bei Bekanntwerden von Rechtsverletzungen werde ich derartige Links umgehend entfernen. + Urheberrecht Die durch den Seitenbetreiber erstellten Inhalte und Werke auf dieser Website unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechts bedürfen der schriftlichen Zustimmung des jeweiligen Autors. ``` @@ -205,12 +205,12 @@ Small-business exemption under § 19 UStG. No VAT is charged. VAT Identification Number per § 27a UStG DE461658750 -Responsible for content per § 18 (2) MStV -Lars Weiser (address as above) - Liability for Content The contents of this website have been created with the greatest care. However, no guarantee can be given for the accuracy, completeness, or currency of the content. As a service provider, I am responsible for my own content on these pages pursuant to § 7 (1) DDG. According to §§ 8 to 10 DDG, as a service provider I am not obliged to monitor transmitted or stored third-party information or to investigate circumstances indicating illegal activity. +Liability for Links +This website contains links to external third-party websites whose content is outside my control. No guarantee can be given for this third-party content; responsibility lies in each case with the respective provider or operator of the linked sites. The linked sites were checked for possible legal violations at the time of linking; no unlawful content was apparent at that time. Continuous monitoring of the content of linked sites is not reasonable without concrete indications of a legal violation, pursuant to §§ 8 to 10 DDG. Should I become aware of any legal violations, I will remove such links promptly. + Copyright The content and works on this website created by the site operator are subject to German copyright law. Duplication, processing, distribution, and any form of exploitation outside the limits of copyright law require the written consent of the respective author. ``` @@ -219,7 +219,7 @@ The content and works on this website created by the site operator are subject t ## 9. Datenschutzerklärung (DE — primary legal version) -> **Disclaimer for the owner:** This is a baseline drafted for the specific stack profile of this website — static content, no forms, no accounts, no third-party trackers, cookieless analytics. It covers the GDPR Art. 13 disclosure requirements for these narrow circumstances. It is **not legal advice**. Before public launch, consider a one-off review: IHK Hamburg offers affordable legal consultation for Kleinunternehmer; services like e-recht24 generate tailored policies for a small fee; or a specialist data-protection lawyer for full assurance. The baseline below is legally complete for the stack described. +> **Disclaimer for the owner:** This is a baseline drafted for the specific stack profile of this website — static content, no forms, no accounts, no third-party trackers, cookieless analytics. It covers the GDPR Art. 13 disclosure requirements for these narrow circumstances. It is **not legal advice**. Before public launch, consider a one-off review: IHK zu Lübeck offers affordable legal consultation for Kleinunternehmer; services like e-recht24 generate tailored policies for a small fee; or a specialist data-protection lawyer for full assurance. The baseline below is legally complete for the stack described. ``` Datenschutzerklärung @@ -255,21 +255,23 @@ Diese Website wird auf der Infrastruktur von Cloudflare, Inc. gehostet (101 Town Diese Verarbeitung erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse am sicheren und stabilen Betrieb der Website) und ist für die Auslieferung der Website technisch erforderlich. Cloudflare speichert diese Daten in Form von Server-Logs. Die Speicherdauer wird auf ein technisch notwendiges Minimum begrenzt. -Cloudflare ist nach dem EU-US Data Privacy Framework zertifiziert, sodass für Datenübermittlungen in die USA ein angemessenes Datenschutzniveau nach Art. 45 DSGVO sichergestellt ist. Zusätzlich besteht zwischen dem Verantwortlichen und Cloudflare ein Auftragsverarbeitungsvertrag (Data Processing Addendum) nach Art. 28 DSGVO. +Cloudflare ist nach dem EU-US Data Privacy Framework zertifiziert, sodass für Datenübermittlungen in die USA ein angemessenes Datenschutzniveau nach Art. 45 DSGVO sichergestellt ist. Diese Zertifizierung wird regelmäßig anhand der offiziellen Teilnehmerliste des EU-US Data Privacy Frameworks geprüft (zuletzt geprüft am {{ cloudflare_facts_verified_date }}). Zusätzlich besteht zwischen dem Verantwortlichen und Cloudflare ein Auftragsverarbeitungsvertrag (Data Processing Addendum) nach Art. 28 DSGVO. Weitere Informationen zum Datenschutz bei Cloudflare: https://www.cloudflare.com/privacypolicy/ 4. Analyse: Cloudflare Web Analytics -Zur Reichweitenmessung wird Cloudflare Web Analytics eingesetzt. Dieser Dienst arbeitet cookiefrei und ohne Fingerprinting. Es werden ausschließlich aggregierte Nutzungsstatistiken erhoben (Seitenaufrufe, Referrer, ungefähre geografische Herkunft auf Länderebene). Eine Identifizierung einzelner Besucher ist weder vorgesehen noch möglich. +Zur Reichweitenmessung wird Cloudflare Web Analytics eingesetzt. Hierzu wird beim Aufruf der Website ein Skript (Beacon) vom Cloudflare-Host static.cloudflareinsights.com nachgeladen. Dieser Dienst arbeitet cookiefrei und ohne Fingerprinting. Es werden ausschließlich aggregierte Nutzungsstatistiken erhoben (Seitenaufrufe, Referrer, ungefähre geografische Herkunft auf Länderebene). Eine Identifizierung einzelner Besucher ist weder vorgesehen noch möglich. Aggregierte Auswertungsdaten werden für {{ cwa_retention_aggregates_months }} Monate gespeichert; einzelne Rohereignisse werden innerhalb von {{ cwa_retention_raw_hours }} Stunden aggregiert und anschließend gelöscht. Diese Angaben werden regelmäßig anhand der öffentlichen Dokumentation von Cloudflare geprüft (zuletzt geprüft am {{ cloudflare_facts_verified_date }}). Rechtsgrundlage: Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse an einer datenschutzfreundlichen Reichweitenmessung). 5. E-Mail-Kommunikation -Wenn Sie mich per E-Mail kontaktieren (z. B. über die auf der Kontaktseite angegebene Adresse), wird Ihre Nachricht zur Bearbeitung gespeichert. Das zugehörige E-Mail-Postfach wird bei der IONOS SE (Elgendorfer Straße 57, 56410 Montabaur, Deutschland) betrieben. +Wenn Sie mich per E-Mail kontaktieren (z. B. über die auf der Kontaktseite angegebene Adresse), wird Ihre Nachricht zur Bearbeitung gespeichert. Das zugehörige E-Mail-Postfach wird bei der IONOS SE (Elgendorfer Straße 57, 56410 Montabaur, Deutschland) betrieben. Zwischen dem Verantwortlichen und IONOS besteht ein Auftragsverarbeitungsvertrag nach Art. 28 DSGVO. + +Nachrichten an Adressen unter der Domain blackbrowedlabs.de werden über Cloudflare Email Routing entgegengenommen und an das oben genannte IONOS-Postfach weitergeleitet. Es wird keine E-Mail-Adresse unter blackbrowedlabs.de aktiv veröffentlicht; die Catch-all-Weiterleitung dient als Auffangfunktion für versehentlich an diese Domain gerichtete Nachrichten. Die in Abschnitt 3 beschriebenen Maßnahmen für Datenübermittlungen in die USA (EU-US Data Privacy Framework nach Art. 45 DSGVO sowie Auftragsverarbeitungsvertrag nach Art. 28 DSGVO) gelten entsprechend für die E-Mail-Weiterleitung über Cloudflare Email Routing. -Die Verarbeitung erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO (vorvertragliche Maßnahmen) bzw. Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse an der Beantwortung Ihrer Anfrage). Die E-Mails werden so lange gespeichert, wie es für die Bearbeitung erforderlich ist, und anschließend gelöscht, sofern keine gesetzlichen Aufbewahrungspflichten entgegenstehen. +Die Verarbeitung erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO (vorvertragliche Maßnahmen) bzw. Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse an der Beantwortung Ihrer Anfrage). E-Mails ohne geschäftlichen oder steuerlichen Bezug werden nach abgeschlossener Bearbeitung gelöscht. Soweit E-Mails als Handels- und Geschäftsbriefe einzuordnen sind, werden sie nach § 147 Abs. 1 Nr. 2 und 3 AO sechs Jahre aufbewahrt; soweit sie Buchungsbelege darstellen, beträgt die Aufbewahrungsfrist nach § 147 Abs. 1 Nr. 4 AO zehn Jahre. Weitere Informationen zum Datenschutz bei IONOS: https://www.ionos.de/terms-gtc/terms-privacy @@ -277,13 +279,13 @@ Weitere Informationen zum Datenschutz bei IONOS: https://www.ionos.de/terms-gtc/ Eine Weitergabe Ihrer personenbezogenen Daten an Dritte findet — über die oben genannten Auftragsverarbeiter (Cloudflare, IONOS) hinaus — nicht statt. -7. Keine Cookies außerhalb technischer Notwendigkeit +7. Keine Cookies oder vergleichbaren Speichertechnologien außerhalb technischer Notwendigkeit -Diese Website setzt keine Cookies zu Analyse-, Marketing- oder Tracking-Zwecken. Sollten technisch notwendige Cookies (z. B. durch den Schutzmechanismus von Cloudflare) zum Einsatz kommen, erfolgt dies auf Grundlage von § 25 Abs. 2 Nr. 2 TDDDG. +Diese Website setzt keine Cookies oder vergleichbaren Speichertechnologien (z. B. Web Storage) zu Analyse-, Marketing- oder Tracking-Zwecken. Sollten technisch notwendige Cookies (z. B. durch den Schutzmechanismus von Cloudflare) zum Einsatz kommen, erfolgt dies auf Grundlage von § 25 Abs. 2 Nr. 2 TDDDG. Auf derselben Grundlage wird zur Speicherung Ihrer Designeinstellung (Hell, Dunkel, System) ein Eintrag im Web Storage Ihres Browsers (Schlüssel: bbl-theme) angelegt. Dieser Eintrag wird ausschließlich gesetzt, wenn Sie die Designauswahl aktiv ändern; er enthält keine personenbezogenen Daten und wird nicht zur Wiedererkennung über Websites hinweg verwendet. 8. Keine Social-Media-Plugins oder externen Einbettungen -Diese Website bindet keine Inhalte von Drittanbietern ein (keine YouTube-Videos, keine Twitter-/X-Einbettungen, keine Google Fonts, keine externen Skripte). Alle Ressourcen werden vom eigenen Server ausgeliefert. +Mit Ausnahme des in Abschnitt 4 beschriebenen Cloudflare-Web-Analytics-Beacons (geladen von static.cloudflareinsights.com) bindet diese Website keine Inhalte von Drittanbietern ein (keine YouTube-Videos, keine Twitter-/X-Einbettungen, keine Google Fonts, keine weiteren externen Skripte). Alle übrigen Ressourcen werden vom eigenen Server ausgeliefert. 9. Ihre Rechte @@ -354,21 +356,23 @@ This website is hosted on Cloudflare, Inc.'s infrastructure (101 Townsend St, Sa This processing is based on Art. 6 (1) (f) GDPR (legitimate interest in secure and stable operation of the site) and is technically required for delivering the website. Cloudflare stores this data in server logs. Retention is limited to what is technically necessary. -Cloudflare is certified under the EU-US Data Privacy Framework, ensuring an adequate level of data protection for transfers to the United States per Art. 45 GDPR. A Data Processing Addendum under Art. 28 GDPR is in place between the controller and Cloudflare. +Cloudflare is certified under the EU-US Data Privacy Framework, ensuring an adequate level of data protection for transfers to the United States per Art. 45 GDPR. This certification is verified periodically against the official EU-US Data Privacy Framework participant list (last verified on {{ cloudflare_facts_verified_date }}). A Data Processing Addendum under Art. 28 GDPR is in place between the controller and Cloudflare. Cloudflare's privacy policy: https://www.cloudflare.com/privacypolicy/ 4. Analytics: Cloudflare Web Analytics -Cloudflare Web Analytics is used to measure site usage. This service operates without cookies and without fingerprinting. Only aggregate usage statistics are collected (page views, referrer, approximate country-level geolocation). Identification of individual visitors is neither intended nor possible. +Cloudflare Web Analytics is used to measure site usage. To do this, a script (beacon) is loaded from the Cloudflare host static.cloudflareinsights.com when a page is opened. This service operates without cookies and without fingerprinting. Only aggregate usage statistics are collected (page views, referrer, approximate country-level geolocation). Identification of individual visitors is neither intended nor possible. Aggregated analytics data is retained for {{ cwa_retention_aggregates_months }} months; individual raw events are aggregated and then deleted within {{ cwa_retention_raw_hours }} hours. These figures are verified periodically against Cloudflare's public documentation (last verified on {{ cloudflare_facts_verified_date }}). Legal basis: Art. 6 (1) (f) GDPR (legitimate interest in privacy-friendly analytics). 5. Email Communication -If you contact me by email (e.g. using the address on the contact page), your message is stored for processing. The mailbox is operated by IONOS SE (Elgendorfer Straße 57, 56410 Montabaur, Germany). +If you contact me by email (e.g. using the address on the contact page), your message is stored for processing. The mailbox is operated by IONOS SE (Elgendorfer Straße 57, 56410 Montabaur, Germany). A Data Processing Addendum under Art. 28 GDPR is in place between the controller and IONOS. + +Inbound messages to addresses on the domain blackbrowedlabs.de are received via Cloudflare Email Routing and forwarded to the IONOS mailbox named above. No email address on blackbrowedlabs.de is publicly advertised; the catch-all forwarding serves as a fallback for messages mistakenly sent to this domain. The measures described in Section 3 for data transfers to the United States (EU-US Data Privacy Framework per Art. 45 GDPR; Data Processing Addendum per Art. 28 GDPR) apply accordingly to email forwarding via Cloudflare Email Routing. -Legal basis: Art. 6 (1) (b) GDPR (pre-contractual measures) or Art. 6 (1) (f) GDPR (legitimate interest in responding to your inquiry). Emails are retained as long as necessary to process the inquiry and then deleted, unless statutory retention periods apply. +Legal basis: Art. 6 (1) (b) GDPR (pre-contractual measures) or Art. 6 (1) (f) GDPR (legitimate interest in responding to your inquiry). Emails without a business or tax-relevant context are deleted once processing is complete. Where emails qualify as commercial and business correspondence (Handels- und Geschäftsbriefe), they are retained for six years pursuant to § 147 Abs. 1 Nr. 2 und 3 AO; where they constitute accounting records (Buchungsbelege), the retention period is ten years pursuant to § 147 Abs. 1 Nr. 4 AO. IONOS privacy information: https://www.ionos.com/terms-gtc/terms-privacy @@ -376,13 +380,13 @@ IONOS privacy information: https://www.ionos.com/terms-gtc/terms-privacy Your personal data is not disclosed to third parties beyond the processors listed above (Cloudflare, IONOS). -7. No Non-Essential Cookies +7. No Non-Essential Cookies or Comparable Storage Technologies -This website does not set cookies for analytics, marketing, or tracking. Technically necessary cookies (e.g. from Cloudflare's security layer) are set on the basis of § 25 (2) no. 2 TDDDG. +This website does not set cookies or comparable storage technologies (e.g. Web Storage) for analytics, marketing, or tracking. Technically necessary cookies (e.g. from Cloudflare's security layer) are set on the basis of § 25 (2) no. 2 TDDDG. On the same basis, an entry is written to your browser's Web Storage (key: bbl-theme) to remember your theme preference (Light, Dark, System). This entry is only set when you actively change the theme selection; it contains no personal data and is not used for cross-site recognition. 8. No Social Media Plugins or External Embeds -This website does not embed third-party content (no YouTube videos, no Twitter/X embeds, no Google Fonts, no external scripts). All resources are served from the site's own origin. +Apart from the Cloudflare Web Analytics beacon described in Section 4 (loaded from static.cloudflareinsights.com), this website embeds no third-party content (no YouTube videos, no Twitter/X embeds, no Google Fonts, no other external scripts). All other resources are served from the site's own origin. 9. Your Rights diff --git a/docs/TECH_STACK.md b/docs/TECH_STACK.md index 904ca46..b380091 100644 --- a/docs/TECH_STACK.md +++ b/docs/TECH_STACK.md @@ -131,6 +131,15 @@ One scheduled trigger on the production Worker, daily at 03:00 UTC, dispatches a - **Observability** is enabled on both Worker environments (`observability.enabled: true` in `wrangler.jsonc`) so runtime logs and Trace Events are captured from first deploy. No additional cost. - Custom Domains on the Worker, not CNAME records. `blackbrowedlabs.com` bound to production, `dev.blackbrowedlabs.com` bound to staging. +### 3.6 Cloudflare managed robots.txt (zone-level) + +Cloudflare's zone-level "Manage robots.txt" is deliberately enabled on the `blackbrowedlabs.com` zone. When enabled, Cloudflare prepends a managed block to the `robots.txt` our Worker serves. + +- **Production:** the managed block names known AI-training crawlers (the current list is visible in the Cloudflare dashboard and updated over time) with `Disallow: /` lines plus Cloudflare's Content Signals Policy preamble. Cloudflare maintains the list over time, so new crawlers enter the block without manual maintenance. +- **Staging:** the managed block prepends `User-agent: *` / `Allow: /` ahead of the Worker-served `Disallow: /`, which a crawler may read as an override. The additional `X-Robots-Tag` response header and the `` tag injected on staging (§3.3) remain the authoritative noindex signals — any crawler that respects either will not index staging regardless of the merged `robots.txt`. + +Toggle off at Cloudflare dashboard → Bots / Scrape Shield → Manage robots.txt if the managed content ever becomes undesirable. + --- ## 4. Framework & Styling — Astro + Tailwind @@ -645,7 +654,7 @@ Content: Full draft provided in `BASELINE_COPY.md` §9. Covers: controller details, Cloudflare hosting + server logs, IONOS email, Cloudflare Web Analytics (cookieless), no third-party services, data subject rights, supervisory authority. -**Disclaimer:** the Datenschutz draft is a baseline for a strictly-necessary, no-forms, no-tracking static site. It is not legal advice. For public launch, consider a one-off review from IHK Hamburg, a service like e-recht24, or a specialist data-protection lawyer. +**Disclaimer:** the Datenschutz draft is a baseline for a strictly-necessary, no-forms, no-tracking static site. It is not legal advice. For public launch, consider a one-off review from IHK zu Lübeck, a service like e-recht24, or a specialist data-protection lawyer. ### 11.3 Cookie / consent banner diff --git a/package-lock.json b/package-lock.json index 3a572e0..2df7b55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,10 @@ "name": "blackbrowedlabs-com", "version": "0.1.0", "dependencies": { - "astro": "^5.2.0" + "astro": "^6.1.9" }, "devDependencies": { - "@astrojs/check": "^0.9.4", + "@astrojs/check": "^0.9.8", "@tailwindcss/vite": "^4.0.0", "playwright": "^1.59.1", "tailwindcss": "^4.0.0", @@ -45,13 +45,17 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.13.1.tgz", "integrity": "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg==", + "dev": true, "license": "MIT" }, "node_modules/@astrojs/internal-helpers": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.6.tgz", - "integrity": "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q==", - "license": "MIT" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.9.0.tgz", + "integrity": "sha512-GdYkzR26re8izmyYlBqf4z2s7zNngmWLFuxw0UKiPNqHraZGS6GKWIwSHgS22RDlu2ePFJ8bzmpBcUszut/SDg==", + "license": "MIT", + "dependencies": { + "picomatch": "^4.0.4" + } }, "node_modules/@astrojs/language-server": { "version": "2.16.6", @@ -96,17 +100,16 @@ } }, "node_modules/@astrojs/markdown-remark": { - "version": "6.3.11", - "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.11.tgz", - "integrity": "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.1.1.tgz", + "integrity": "sha512-C6e9BnLGlbdv6bV8MYGeHpHxsUHrCrB4OuRLqi5LI7oiBVcBcqfUN06zpwFQdHgV48QCCrMmLpyqBr7VqC+swA==", "license": "MIT", "dependencies": { - "@astrojs/internal-helpers": "0.7.6", - "@astrojs/prism": "3.3.0", + "@astrojs/internal-helpers": "0.9.0", + "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", - "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", @@ -115,39 +118,39 @@ "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", - "shiki": "^3.21.0", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", - "unist-util-visit": "^5.0.0", + "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "node_modules/@astrojs/prism": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", - "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.1.tgz", + "integrity": "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ==", "license": "MIT", "dependencies": { "prismjs": "^1.30.0" }, "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0" + "node": ">=22.12.0" } }, "node_modules/@astrojs/telemetry": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", - "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.1.tgz", + "integrity": "sha512-7fcIxXS9J4ls5tr8b3ww9rbAIz2+HrhNJYZdkAhhB4za/I5IZ/60g+Bs8q7zwG0tOIZfNB4JWhVJ1Qkl/OrNCw==", "license": "MIT", "dependencies": { - "ci-info": "^4.2.0", - "debug": "^4.4.0", + "ci-info": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", - "is-docker": "^3.0.0", - "is-wsl": "^3.1.0", + "is-docker": "^4.0.0", + "is-wsl": "^3.1.1", "which-pm-runs": "^1.1.0" }, "engines": { @@ -222,6 +225,28 @@ "node": ">=18" } }, + "node_modules/@clack/core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.2.0.tgz", + "integrity": "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.1.3", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.2.0.tgz", + "integrity": "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.2.0", + "fast-string-width": "^1.1.0", + "fast-wrap-ansi": "^0.1.3", + "sisteransi": "^1.0.5" + } + }, "node_modules/@cloudflare/kv-asset-handler": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", @@ -423,6 +448,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -444,6 +470,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1433,6 +1460,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1453,17 +1481,6 @@ "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", "license": "MIT" }, - "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", - "devOptional": true, - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, "node_modules/@poppinss/colors": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", @@ -1493,289 +1510,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", - "cpu": [ - "ppc64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", - "cpu": [ - "s390x" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", - "devOptional": true, - "license": "MIT", - "peer": true - }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", @@ -2169,64 +1903,97 @@ ] }, "node_modules/@shikijs/core": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", - "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", + "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0", + "@shikijs/primitive": "4.0.2", + "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/engine-javascript": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", - "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", + "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0", + "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", - "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", + "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0", + "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/langs": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", - "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", - "license": "MIT", - "dependencies": { - "@shikijs/types": "3.23.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", - "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz", + "integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.23.0" + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" } }, - "node_modules/@shikijs/types": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", - "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "node_modules/@shikijs/primitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz", + "integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==", "license": "MIT", "dependencies": { + "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz", + "integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", + "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/@shikijs/vscode-textmate": { @@ -2543,6 +2310,7 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2707,18 +2475,6 @@ "dev": true, "license": "MIT" }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ajv": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", @@ -2751,80 +2507,6 @@ } } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2876,80 +2558,71 @@ } }, "node_modules/astro": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.18.1.tgz", - "integrity": "sha512-m4VWilWZ+Xt6NPoYzC4CgGZim/zQUO7WFL0RHCH0AiEavF1153iC3+me2atDvXpf/yX4PyGUeD8wZLq1cirT3g==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/astro/-/astro-6.1.9.tgz", + "integrity": "sha512-NsAHzMzpznB281g2aM5qnBt2QjfH6ttKiZ3hSZw52If8JJ+62kbnBKbyKhR2glQcJLl7Jfe4GSl0DihFZ36rRQ==", "license": "MIT", "dependencies": { - "@astrojs/compiler": "^2.13.0", - "@astrojs/internal-helpers": "0.7.6", - "@astrojs/markdown-remark": "6.3.11", - "@astrojs/telemetry": "3.3.0", + "@astrojs/compiler": "^3.0.1", + "@astrojs/internal-helpers": "0.9.0", + "@astrojs/markdown-remark": "7.1.1", + "@astrojs/telemetry": "3.3.1", "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", - "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", - "boxen": "8.0.1", - "ci-info": "^4.3.1", + "ci-info": "^4.4.0", "clsx": "^2.1.1", - "common-ancestor-path": "^1.0.1", + "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", - "cssesc": "^3.0.0", - "debug": "^4.4.3", - "deterministic-object-hash": "^2.0.2", - "devalue": "^5.6.2", + "devalue": "^5.6.3", "diff": "^8.0.3", - "dlv": "^1.1.3", "dset": "^3.1.4", - "es-module-lexer": "^1.7.0", + "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", - "estree-walker": "^3.0.3", "flattie": "^1.1.1", - "fontace": "~0.4.0", + "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", - "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", - "magicast": "^0.5.1", + "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", - "p-limit": "^6.2.0", - "p-queue": "^8.1.1", + "obug": "^2.1.1", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", - "picomatch": "^4.0.3", - "prompts": "^2.4.2", + "picomatch": "^4.0.4", "rehype": "^13.0.2", - "semver": "^7.7.3", - "shiki": "^3.21.0", + "semver": "^7.7.4", + "shiki": "^4.0.2", "smol-toml": "^1.6.0", - "svgo": "^4.0.0", - "tinyexec": "^1.0.2", + "svgo": "^4.0.1", + "tinyclip": "^0.1.12", + "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", - "unifont": "~0.7.3", - "unist-util-visit": "^5.0.0", - "unstorage": "^1.17.4", + "unifont": "~0.7.4", + "unist-util-visit": "^5.1.0", + "unstorage": "^1.17.5", "vfile": "^6.0.3", - "vite": "^6.4.1", - "vitefu": "^1.1.1", + "vite": "^7.3.2", + "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", - "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.3", - "zod": "^3.25.76", - "zod-to-json-schema": "^3.25.1", - "zod-to-ts": "^1.2.0" + "yargs-parser": "^22.0.0", + "zod": "^4.3.6" }, "bin": { - "astro": "astro.js" + "astro": "bin/astro.mjs" }, "engines": { - "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "node": ">=22.12.0", "npm": ">=9.6.5", "pnpm": ">=7.1.0" }, @@ -2961,535 +2634,19 @@ "sharp": "^0.34.0" } }, - "node_modules/astro/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/astro/node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } + "node_modules/astro/node_modules/@astrojs/compiler": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-3.0.1.tgz", + "integrity": "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA==", + "license": "MIT" }, - "node_modules/astro/node_modules/vite/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, + "node_modules/astro/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/axobject-query": { @@ -3511,12 +2668,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/base-64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", - "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", - "license": "MIT" - }, "node_modules/blake3-wasm": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", @@ -3530,40 +2681,6 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, - "node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -3574,18 +2691,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -3647,18 +2752,6 @@ "node": ">=8" } }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3802,10 +2895,13 @@ } }, "node_modules/common-ancestor-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", - "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", - "license": "ISC" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } }, "node_modules/cookie": { "version": "1.1.1", @@ -3876,18 +2972,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/csso": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", @@ -3982,18 +3066,6 @@ "node": ">=8" } }, - "node_modules/deterministic-object-hash": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", - "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", - "license": "MIT", - "dependencies": { - "base-64": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/devalue": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.7.1.tgz", @@ -4121,12 +3193,6 @@ "@emmetio/css-abbreviation": "^2.1.8" } }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT" - }, "node_modules/enhanced-resolve": { "version": "5.20.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", @@ -4164,9 +3230,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "license": "MIT" }, "node_modules/esbuild": { @@ -4232,15 +3298,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/eventemitter3": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", @@ -4260,6 +3317,21 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-string-truncated-width": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-1.2.1.tgz", + "integrity": "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-1.1.0.tgz", + "integrity": "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^1.2.0" + } + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -4277,6 +3349,15 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-wrap-ansi": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.1.6.tgz", + "integrity": "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^1.1.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -4348,18 +3429,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", @@ -4589,16 +3658,6 @@ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, - "node_modules/import-meta-resolve": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/iron-webcrypto": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", @@ -4609,15 +3668,15 @@ } }, "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", "license": "MIT", "bin": { "is-docker": "cli.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4627,6 +3686,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4650,6 +3710,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -5944,6 +5019,16 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/ofetch": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", @@ -5979,43 +5064,43 @@ } }, "node_modules/p-limit": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", - "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", + "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", "license": "MIT", "dependencies": { - "yocto-queue": "^1.1.1" + "yocto-queue": "^1.2.1" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-queue": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.1.tgz", - "integrity": "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.2.0.tgz", + "integrity": "sha512-dWgLE8AH0HjQ9fe74pUkKkvzzYT18Inp4zra3lKHnnwqGvcfcUBrvF2EAVX+envufDNBOzpPq/IBUONDbI7+3g==", "license": "MIT", "dependencies": { - "eventemitter3": "^5.0.1", - "p-timeout": "^6.1.2" + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-timeout": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", - "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", "license": "MIT", "engines": { - "node": ">=14.16" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6202,28 +5287,6 @@ "node": ">=6" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prompts/node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -6508,41 +5571,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" - } - }, "node_modules/rollup": { "version": "4.60.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", @@ -6654,19 +5682,22 @@ } }, "node_modules/shiki": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", - "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz", + "integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==", "license": "MIT", "dependencies": { - "@shikijs/core": "3.23.0", - "@shikijs/engine-javascript": "3.23.0", - "@shikijs/engine-oniguruma": "3.23.0", - "@shikijs/langs": "3.23.0", - "@shikijs/themes": "3.23.0", - "@shikijs/types": "3.23.0", + "@shikijs/core": "4.0.2", + "@shikijs/engine-javascript": "4.0.2", + "@shikijs/engine-oniguruma": "4.0.2", + "@shikijs/langs": "4.0.2", + "@shikijs/themes": "4.0.2", + "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" } }, "node_modules/sisteransi": { @@ -6706,23 +5737,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -6737,21 +5751,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/supports-color": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", @@ -6817,6 +5816,15 @@ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "license": "MIT" }, + "node_modules/tinyclip": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.12.tgz", + "integrity": "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, "node_modules/tinyexec": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", @@ -6889,18 +5897,6 @@ "license": "0BSD", "optional": true }, - "node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typesafe-path": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/typesafe-path/-/typesafe-path-0.2.2.tgz", @@ -6912,6 +5908,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -7289,18 +6286,17 @@ } }, "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", - "devOptional": true, + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "license": "MIT", - "peer": true, "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", - "tinyglobby": "^0.2.16" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" @@ -7316,10 +6312,9 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", + "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", @@ -7332,18 +6327,15 @@ "@types/node": { "optional": true }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, "jiti": { "optional": true }, "less": { "optional": true }, + "lightningcss": { + "optional": true + }, "sass": { "optional": true }, @@ -7660,21 +6652,6 @@ "node": ">=4" } }, - "node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/workerd": { "version": "1.20260421.1", "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260421.1.tgz", @@ -8215,23 +7192,6 @@ "@esbuild/win32-x64": "0.27.3" } }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -8352,6 +7312,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -8414,33 +7375,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yocto-spinner": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", - "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", - "license": "MIT", - "dependencies": { - "yoctocolors": "^2.1.1" - }, - "engines": { - "node": ">=18.19" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/youch": { "version": "4.1.0-beta.10", "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", @@ -8467,32 +7401,14 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zod-to-json-schema": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", - "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", - "license": "ISC", - "peerDependencies": { - "zod": "^3.25.28 || ^4" - } - }, - "node_modules/zod-to-ts": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", - "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", - "peerDependencies": { - "typescript": "^4.9.4 || ^5.0.2", - "zod": "^3" - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index d342961..dae392e 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,10 @@ "check": "astro check" }, "dependencies": { - "astro": "^5.2.0" + "astro": "^6.1.9" }, "devDependencies": { - "@astrojs/check": "^0.9.4", + "@astrojs/check": "^0.9.8", "@tailwindcss/vite": "^4.0.0", "playwright": "^1.59.1", "tailwindcss": "^4.0.0", @@ -25,5 +25,8 @@ }, "engines": { "node": ">=24" + }, + "overrides": { + "vite": "^7" } } diff --git a/src/components/SiteHeader.astro b/src/components/SiteHeader.astro index 546e9fe..8b3a30b 100644 --- a/src/components/SiteHeader.astro +++ b/src/components/SiteHeader.astro @@ -65,13 +65,17 @@ const aboutActive = path === aboutHref || path === aboutHref + '/'; const trigger = document.querySelector('[data-menu-trigger]'); const nav = document.getElementById('primary-nav'); if (!(trigger instanceof HTMLElement) || !(nav instanceof HTMLElement)) return; + // Re-bind to typed locals so the L67 instanceof narrowing propagates into + // every inner-function closure below. Type-only; same DOM references. + const triggerEl: HTMLElement = trigger; + const navEl: HTMLElement = nav; // Mirrors the mobile breakpoint used by the CSS: hamburger + nav panel // live below 768px; desktop nav is always visible. const mobileMql = window.matchMedia('(max-width: 767px)'); function isOpen() { - return trigger.getAttribute('aria-expanded') === 'true'; + return triggerEl.getAttribute('aria-expanded') === 'true'; } // Focusable elements currently VISIBLE inside the nav panel. Used for @@ -80,13 +84,13 @@ const aboutActive = path === aboutHref || path === aboutHref + '/'; // theme-toggle options), so those are filtered out automatically. function getFocusables() { const selector = 'a[href], button:not(:disabled), [tabindex]:not([tabindex="-1"])'; - return Array.from(nav.querySelectorAll(selector)) + return Array.from(navEl.querySelectorAll(selector)) .filter(function (el) { return el.offsetWidth > 0 || el.offsetHeight > 0; }); } function open() { - trigger.setAttribute('aria-expanded', 'true'); - nav.dataset.open = 'true'; + triggerEl.setAttribute('aria-expanded', 'true'); + navEl.dataset.open = 'true'; document.body.classList.add('menu-open'); // Move focus into the panel for keyboard + screen-reader continuity. const first = getFocusables()[0]; @@ -96,14 +100,14 @@ const aboutActive = path === aboutHref || path === aboutHref + '/'; // restoreFocus=true returns focus to the hamburger (Escape / tap-outside); // =false leaves focus alone (hamburger click — trigger already focused; // desktop-resize — hamburger is hidden at ≥768px anyway). - function close(restoreFocus) { - trigger.setAttribute('aria-expanded', 'false'); - delete nav.dataset.open; + function close(restoreFocus: boolean) { + triggerEl.setAttribute('aria-expanded', 'false'); + delete navEl.dataset.open; document.body.classList.remove('menu-open'); - if (restoreFocus) trigger.focus({ preventScroll: true }); + if (restoreFocus) triggerEl.focus({ preventScroll: true }); } - trigger.addEventListener('click', function () { + triggerEl.addEventListener('click', function () { if (isOpen()) close(false); else open(); }); @@ -113,7 +117,7 @@ const aboutActive = path === aboutHref || path === aboutHref + '/'; if (!isOpen()) return; const t = e.target; if (!(t instanceof Element)) return; - if (nav.contains(t) || trigger.contains(t)) return; + if (navEl.contains(t) || triggerEl.contains(t)) return; close(true); }); @@ -135,7 +139,7 @@ const aboutActive = path === aboutHref || path === aboutHref + '/'; const active = document.activeElement; if (e.shiftKey) { - if (active === first || !nav.contains(active)) { + if (active === first || !navEl.contains(active)) { e.preventDefault(); last.focus({ preventScroll: true }); } diff --git a/src/components/ThemeToggle.astro b/src/components/ThemeToggle.astro index 32b6dcc..8f03b20 100644 --- a/src/components/ThemeToggle.astro +++ b/src/components/ThemeToggle.astro @@ -75,7 +75,9 @@ const t = getUiStrings(locale).themeToggle; (function () { const STORAGE_KEY = 'bbl-theme'; - function readChoice() { + type ThemeChoice = 'light' | 'dark' | 'system'; + + function readChoice(): ThemeChoice { const stored = localStorage.getItem(STORAGE_KEY); if (stored === 'light' || stored === 'dark' || stored === 'system') { return stored; @@ -83,7 +85,7 @@ const t = getUiStrings(locale).themeToggle; return 'system'; } - function resolve(choice) { + function resolve(choice: ThemeChoice): 'light' | 'dark' { if (choice === 'system') { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' @@ -92,13 +94,13 @@ const t = getUiStrings(locale).themeToggle; return choice; } - function apply(choice) { + function apply(choice: ThemeChoice) { const resolved = resolve(choice); document.documentElement.dataset.theme = resolved; document.documentElement.dataset.themeChoice = choice; } - function persist(choice) { + function persist(choice: ThemeChoice) { if (choice === 'system') { localStorage.removeItem(STORAGE_KEY); } else { @@ -106,9 +108,9 @@ const t = getUiStrings(locale).themeToggle; } } - const triggers = document.querySelectorAll('[data-theme-toggle-trigger]'); - const menus = document.querySelectorAll('[data-theme-toggle-menu]'); - const options = document.querySelectorAll('[data-theme-value]'); + const triggers = document.querySelectorAll('[data-theme-toggle-trigger]'); + const menus = document.querySelectorAll('[data-theme-toggle-menu]'); + const options = document.querySelectorAll('[data-theme-value]'); function updateOptionsState() { const choice = readChoice(); @@ -140,7 +142,10 @@ const t = getUiStrings(locale).themeToggle; options.forEach(function (opt) { opt.addEventListener('click', function () { const value = opt.getAttribute('data-theme-value'); - if (!value) return; + // Boundary validation: data-theme-value markup is hard-coded to the + // three valid values, but we narrow defensively so persist/apply + // receive a typed ThemeChoice without a cast. + if (value !== 'light' && value !== 'dark' && value !== 'system') return; persist(value); apply(value); updateOptionsState(); diff --git a/src/content.config.ts b/src/content.config.ts new file mode 100644 index 0000000..f64468a --- /dev/null +++ b/src/content.config.ts @@ -0,0 +1,90 @@ +/** + * Astro content collections. + * + * Three collections per Pass 2 plan A.3: + * - editorial — Markdown for Home + About + Contact (frequently-edited + * copy that benefits from Markdown + mobile GitHub editing). + * - products — Markdown per product per language, keyed by slug + lang. + * - releases — remote, populated by a custom loader at build time + * (stub now; real GitHub-API fetch lands in Phase D per OQ-6). + * + * Legal pages (Impressum, Datenschutz), 404, and the Products index + * are intentionally NOT in `editorial` — they live as `.astro` files + * with inline copy because they're rarely-edited or template-shaped + * (per OQ-1/D16). + */ + +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; +import { z } from 'zod'; + +// Glob's default `generateId` strips dots from the filename, so +// `about.de.md` becomes `aboutde`. Override to preserve the dotted form +// (`about.de`), which matches the natural slug.lang convention used by +// editorial pages and read sites like `getEntry('editorial', 'about.de')`. +const editorial = defineCollection({ + loader: glob({ + pattern: '**/*.md', + base: './src/content/editorial', + generateId: ({ entry }) => entry.replace(/\.md$/, ''), + }), + schema: z.object({ + lang: z.enum(['de', 'en']), + title: z.string().optional(), + tagline: z.string().optional(), + heroLead: z.string().optional(), + intro: z.array(z.string()).optional(), + secondaryLink: z + .object({ + label: z.string(), + href: z.string(), + }) + .optional(), + description: z.string().optional(), + closing: z + .object({ + aphorism: z.string(), + signature: z.string(), + }) + .optional(), + }), +}); + +const products = defineCollection({ + loader: glob({ + pattern: '**/*.md', + base: './src/content/products', + generateId: ({ entry }) => entry.replace(/\.md$/, ''), + }), + schema: z.object({ + name: z.string(), + slug: z.string(), + lang: z.enum(['de', 'en']), + tagline: z.string(), + description: z.string(), + externalUrl: z.url(), + repo: z.string(), + logo: z.string().optional(), + order: z.number().optional(), + draft: z.boolean().optional(), + }), +}); + +// Stub loader — returns no entries. Phase D replaces with a real GitHub-API +// fetch (TECH_STACK §5.3). Landing the stub now means a product Markdown +// added between phases doesn't break the build. +const releases = defineCollection({ + loader: async () => [], + schema: z.object({ + productSlug: z.string(), + tagName: z.string(), + name: z.string(), + publishedAt: z.string(), + bodyMarkdown: z.string(), + isPrerelease: z.boolean(), + isDraft: z.boolean(), + htmlUrl: z.url(), + }), +}); + +export const collections = { editorial, products, releases }; diff --git a/src/content/editorial/about.de.md b/src/content/editorial/about.de.md new file mode 100644 index 0000000..2848edc --- /dev/null +++ b/src/content/editorial/about.de.md @@ -0,0 +1,18 @@ +--- +lang: de +title: Über +description: Blackbrowed Labs ist ein kleines Softwarestudio in Reinbek bei Hamburg. Ich entwickle Werkzeuge für die Klassenführung. +intro: + - "Blackbrowed Labs ist ein kleines Softwarestudio in Reinbek bei Hamburg. Ich entwickle Werkzeuge für die Klassenführung — Software, die Lehrkräften im Schulalltag Arbeit abnimmt." + - "Ich bin selbst kein Lehrer. Aber ich bin von Lehrkräften umgeben — Freunde, Familie, langjährige Gespräche über das, was im Unterricht funktioniert und was nicht. Die Software entsteht aus diesen Gesprächen, nicht aus allgemeinen Annahmen darüber, was Schulen brauchen." + - "Aktuell entstehen zwei Werkzeuge: eine iPad-App für die Klassenführung — Noten, Schülerleistungen, Beobachtungen, ohne Papierkram — und ein Claude-Cowork-Plugin, das bei der Planung von Unterrichtseinheiten hilft. Beide bekommen eine eigene Website, sobald sie so weit sind." +closing: + aphorism: Was funktioniert, bleibt. Was nicht, fliegt raus. + signature: — Lars Weiser +--- + +## Woher der Name kommt + +Der Name **Blackbrowed Labs** stammt vom Schwarzbrauenalbatros — einem Seevogel, der tausende Kilometer mit minimalem Aufwand zurücklegt, getragen vom Wind statt von Muskelkraft. Das markante dunkle Augenband gibt dem Vogel seinen Namen und dem Studio seinen Charakter: ein kleiner, präziser Strich als Erkennungszeichen. + +Der Anspruch an die Software ist derselbe wie der Flug des Albatros: lange tragen, wenig verbrauchen, klar bleiben. diff --git a/src/content/editorial/about.en.md b/src/content/editorial/about.en.md new file mode 100644 index 0000000..4429fa2 --- /dev/null +++ b/src/content/editorial/about.en.md @@ -0,0 +1,18 @@ +--- +lang: en +title: About +description: Blackbrowed Labs is a small software studio in Reinbek, near Hamburg. I build tools for classroom management. +intro: + - "Blackbrowed Labs is a small software studio in Reinbek, near Hamburg. I build tools for classroom management — software that takes work off teachers' plates." + - "I'm not a teacher myself. But I'm surrounded by them — friends, family, long conversations with working educators about what actually helps in the classroom and what doesn't. The software grows out of those conversations, not out of generic assumptions about what schools need." + - "Two tools are in development right now: an iPad app for classroom management — grades, student work, observations, without the paperwork — and a Claude Cowork plugin that helps plan teaching units. Each will get its own website when it's ready." +closing: + aphorism: What works, stays. What doesn't, goes. + signature: — Lars Weiser +--- + +## Where the name comes from + +The name **Blackbrowed Labs** comes from the Black-browed Albatross — a seabird that travels thousands of kilometres on the strength of the wind, barely flapping. Its dark eye-stripe gives the bird its name and the studio its character: a small, precise mark as the defining feature. + +The aim for the software is the same as the albatross's flight: lasts long, uses little, stays clear. diff --git a/src/content/editorial/home.de.md b/src/content/editorial/home.de.md new file mode 100644 index 0000000..9ab1e17 --- /dev/null +++ b/src/content/editorial/home.de.md @@ -0,0 +1,11 @@ +--- +lang: de +title: Blackbrowed Labs — Präzise Werkzeuge für den Unterricht +tagline: Präzise Werkzeuge für den Unterricht. +heroLead: Blackbrowed Labs ist ein kleines Softwarestudio in Reinbek bei Hamburg. Ich entwickle Werkzeuge für die Klassenführung — Software, die Lehrkräften im Schulalltag Arbeit abnimmt. +description: Kleines Softwarestudio in Reinbek bei Hamburg. Werkzeuge für die Klassenführung, aus Gesprächen mit Lehrkräften entstanden. +--- + +## Nah an der Praxis + +

Die Software hier entsteht aus Gesprächen mit Lehrkräften — was im Unterricht wirklich Zeit kostet, was funktioniert, was nicht. Kein Feature-Katalog, keine großen Versprechen, nur Werkzeuge, die das tun, was sie sollen.

diff --git a/src/content/editorial/home.en.md b/src/content/editorial/home.en.md new file mode 100644 index 0000000..21bbbb3 --- /dev/null +++ b/src/content/editorial/home.en.md @@ -0,0 +1,11 @@ +--- +lang: en +title: Blackbrowed Labs — Precise tools for teaching +tagline: Precise tools for teaching. +heroLead: Blackbrowed Labs is a small software studio in Reinbek, near Hamburg. I build tools for classroom management — software that takes work off teachers' plates. +description: A small software studio in Reinbek, near Hamburg. Tools for classroom management, built from conversations with teachers. +--- + +## Close to the work + +

The software here grows out of conversations with teachers — what really costs time in the classroom, what works, what doesn't. No feature catalogue, no big promises, just tools that do what they're meant to do.

diff --git a/src/content/products/.gitkeep b/src/content/products/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/i18n/de.ts b/src/i18n/de.ts index c9149f3..f901e79 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -1,10 +1,9 @@ /** * German (primary) UI strings for the Blackbrowed Labs site shell. * - * Scope is Pass 1: Home + About only. Nav does not contain a "Home" - * entry — the logo is the home link (see plan §7). Further nav entries - * (Products, Contact, Impressum, Datenschutz) arrive in Pass 2 - * alongside the pages themselves. + * Nav does not contain a "Home" entry — the logo is the home link + * (see plan §7). Legal pages (Impressum, Datenschutz) live in the + * footer only, not in the primary nav. */ export const de = { @@ -19,6 +18,8 @@ export const de = { nav: { ariaLabel: 'Hauptnavigation', about: 'Über', + products: 'Produkte', + contact: 'Kontakt', }, languageSwitcher: { @@ -41,7 +42,20 @@ export const de = { about: 'Über', github: 'GitHub', githubAriaLabel: 'Blackbrowed Labs auf GitHub', + navAriaLabel: 'Footer-Navigation', + products: 'Produkte', + contact: 'Kontakt', + legal: 'Impressum', + privacy: 'Datenschutz', }, } as const; -export type UiStrings = typeof de; +type WidenStrings = { + [K in keyof T]: T[K] extends string + ? string + : T[K] extends object + ? WidenStrings + : T[K]; +}; + +export type UiStrings = WidenStrings; diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 6332739..4c5749b 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -1,6 +1,6 @@ /** * English (secondary) UI strings for the Blackbrowed Labs site shell. - * Mirror of de.ts — same structure, translated copy. Scope: Pass 1. + * Mirror of de.ts — same structure, translated copy. */ import type { UiStrings } from './de'; @@ -17,6 +17,8 @@ export const en: UiStrings = { nav: { ariaLabel: 'Main navigation', about: 'About', + products: 'Products', + contact: 'Contact', }, languageSwitcher: { @@ -39,5 +41,10 @@ export const en: UiStrings = { about: 'About', github: 'GitHub', githubAriaLabel: 'Blackbrowed Labs on GitHub', + navAriaLabel: 'Footer navigation', + products: 'Products', + contact: 'Contact', + legal: 'Legal', + privacy: 'Privacy', }, }; diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts index 3b1d1f8..5227476 100644 --- a/src/lib/i18n.ts +++ b/src/lib/i18n.ts @@ -3,8 +3,7 @@ * * Counterpart URLs are resolved via an explicit hand-maintained map * because German slugs are not mechanical translations of English slugs - * (see docs/BASELINE_COPY.md §12). Pass 1 covers Home + About only; - * Pass 2 extends the map when the other pages land. + * (see docs/BASELINE_COPY.md §12). */ import type { Locale } from '../i18n'; @@ -28,6 +27,14 @@ const counterparts: Record = { '/en': '/', '/ueber': '/en/about', '/en/about': '/ueber', + '/kontakt': '/en/contact', + '/en/contact': '/kontakt', + '/impressum': '/en/legal', + '/en/legal': '/impressum', + '/datenschutz': '/en/privacy', + '/en/privacy': '/datenschutz', + '/produkte': '/en/products', + '/en/products': '/produkte', }; export function getLocaleFromPath(path: string): Locale { diff --git a/src/pages/en/about.astro b/src/pages/en/about.astro index fbb09f0..0fccb97 100644 --- a/src/pages/en/about.astro +++ b/src/pages/en/about.astro @@ -2,22 +2,33 @@ /** * About page (EN — secondary) — "About Blackbrowed Labs". * Matches design/handoff-bundle/design/pages/04-about.html (EN artboard). - * Copy verbatim from docs/BASELINE_COPY.md §4. + * Copy migrates from inline literals to the `editorial` collection + * in Pass 2 / A.4. Three regions map to dedicated frontmatter fields + * (title → H1; intro[] → .about__text paragraphs; closing → footer) + * to preserve the asymmetric `.about__top` grid + closing styling; + * body Markdown carries only the "Where the name comes from" H2 section. */ +import { getEntry, render } from 'astro:content'; import BaseLayout from '../../layouts/BaseLayout.astro'; import BrandLogo from '../../components/BrandLogo.astro'; + +const entry = await getEntry('editorial', 'about.en'); +if (!entry) throw new Error('Missing editorial entry: about.en'); +const { Content } = await render(entry); +const { title, description, intro, closing } = entry.data; +// Schema fields are collection-wide optional but about.en must populate them. +if (!title || !description) { + throw new Error('about.en editorial entry missing required fields'); +} --- - + @@ -25,25 +36,8 @@ import BrandLogo from '../../components/BrandLogo.astro';
-

About

-

- Blackbrowed Labs is a small software studio in Reinbek, near - Hamburg. I build tools for classroom management — software that - takes work off teachers' plates. -

-

- I'm not a teacher myself. But I'm surrounded by them — friends, - family, long conversations with working educators about what - actually helps in the classroom and what doesn't. The software - grows out of those conversations, not out of generic assumptions - about what schools need. -

-

- Two tools are in development right now: an iPad app for classroom - management — grades, student work, observations, without the - paperwork — and a Claude Cowork plugin that helps plan teaching - units. Each will get its own website when it's ready. -

+

{title}

+ {intro?.map((paragraph) =>

{paragraph}

)}
diff --git a/src/pages/en/index.astro b/src/pages/en/index.astro index 73acead..166cb6f 100644 --- a/src/pages/en/index.astro +++ b/src/pages/en/index.astro @@ -2,44 +2,44 @@ /** * Homepage (EN — secondary). * Matches design/handoff-bundle/design/pages/02-homepage-en.html. - * Copy is verbatim from docs/BASELINE_COPY.md §2 and §3. + * Copy migrates from inline literals to the `editorial` collection + * in Pass 2 / A.4. The H2 + lead-paragraph body lives in the + * Markdown file; the lead paragraph is authored as inline HTML to + * preserve the global `.lead` utility class binding. * * The "What's being built →" secondary link is omitted in Pass 1 - * (no /en/products route yet). Pass 2 restores it in the commit that - * adds the Products page. See plans/pass-1-implementation-plan.md §7. + * (no /en/products route yet). Phase C.3 restores it in the commit + * that adds the Products page. */ +import { getEntry, render } from 'astro:content'; import BaseLayout from '../../layouts/BaseLayout.astro'; import BrandLogo from '../../components/BrandLogo.astro'; + +const entry = await getEntry('editorial', 'home.en'); +if (!entry) throw new Error('Missing editorial entry: home.en'); +const { Content } = await render(entry); +const { title, tagline, heroLead, description } = entry.data; +// Schema fields are collection-wide optional but home.en must populate them. +if (!title || !tagline || !heroLead || !description) { + throw new Error('home.en editorial entry missing required fields'); +} --- - +
-

Precise tools for teaching.

-

- Blackbrowed Labs is a small software studio in Reinbek, near Hamburg. - I build tools for classroom management — software that takes work off - teachers' plates. -

+

{tagline}

+

{heroLead}

-

Close to the work

-

- The software here grows out of conversations with teachers — what - really costs time in the classroom, what works, what doesn't. No - feature catalogue, no big promises, just tools that do what they're - meant to do. -

+
diff --git a/src/pages/index.astro b/src/pages/index.astro index aa2275f..5a70176 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -2,44 +2,44 @@ /** * Homepage (DE — primary). * Matches design/handoff-bundle/design/pages/01-homepage-de.html. - * Copy is verbatim from docs/BASELINE_COPY.md §2 and §3. + * Copy migrates from inline literals to the `editorial` collection + * in Pass 2 / A.4. The H2 + lead-paragraph body lives in the + * Markdown file; the lead paragraph is authored as inline HTML to + * preserve the global `.lead` utility class binding. * * The "Was hier gerade entsteht →" secondary link is omitted in Pass 1 - * (no /produkte route yet). Pass 2 restores it in the commit that adds - * the Products page. See docs/pass-1-implementation-plan.md §7. + * (no /produkte route yet). Phase C.3 restores it in the commit that + * adds the Products page. */ +import { getEntry, render } from 'astro:content'; import BaseLayout from '../layouts/BaseLayout.astro'; import BrandLogo from '../components/BrandLogo.astro'; + +const entry = await getEntry('editorial', 'home.de'); +if (!entry) throw new Error('Missing editorial entry: home.de'); +const { Content } = await render(entry); +const { title, tagline, heroLead, description } = entry.data; +// Schema fields are collection-wide optional but home.de must populate them. +if (!title || !tagline || !heroLead || !description) { + throw new Error('home.de editorial entry missing required fields'); +} --- - +
-

Präzise Werkzeuge für den Unterricht.

-

- Blackbrowed Labs ist ein kleines Softwarestudio in Reinbek bei Hamburg. - Ich entwickle Werkzeuge für die Klassenführung — Software, die - Lehrkräften im Schulalltag Arbeit abnimmt. -

+

{tagline}

+

{heroLead}

-

Nah an der Praxis

-

- Die Software hier entsteht aus Gesprächen mit Lehrkräften — was im - Unterricht wirklich Zeit kostet, was funktioniert, was nicht. Kein - Feature-Katalog, keine großen Versprechen, nur Werkzeuge, die das tun, - was sie sollen. -

+
diff --git a/src/pages/ueber.astro b/src/pages/ueber.astro index b6bdfba..3dd87ad 100644 --- a/src/pages/ueber.astro +++ b/src/pages/ueber.astro @@ -2,27 +2,33 @@ /** * About page (DE — primary) — "Über Blackbrowed Labs". * Matches design/handoff-bundle/design/pages/04-about.html (DE artboard). - * Copy verbatim from docs/BASELINE_COPY.md §4. - * - * Layout: asymmetric reading grid. Left column has the prose; right - * column carries the arc mark at display scale as a wordless signature - * (per handoff README "the arc carries its own weight here; do not - * re-add the caption that was removed in revision"). + * Copy migrates from inline literals to the `editorial` collection + * in Pass 2 / A.4. Three regions map to dedicated frontmatter fields + * (title → H1; intro[] → .about__text paragraphs; closing → footer) + * to preserve the asymmetric `.about__top` grid + closing styling; + * body Markdown carries only the "Woher der Name kommt" H2 section. */ +import { getEntry, render } from 'astro:content'; import BaseLayout from '../layouts/BaseLayout.astro'; import BrandLogo from '../components/BrandLogo.astro'; + +const entry = await getEntry('editorial', 'about.de'); +if (!entry) throw new Error('Missing editorial entry: about.de'); +const { Content } = await render(entry); +const { title, description, intro, closing } = entry.data; +// Schema fields are collection-wide optional but about.de must populate them. +if (!title || !description) { + throw new Error('about.de editorial entry missing required fields'); +} --- - + @@ -30,26 +36,8 @@ import BrandLogo from '../components/BrandLogo.astro';
-

Über

-

- Blackbrowed Labs ist ein kleines Softwarestudio in Reinbek bei - Hamburg. Ich entwickle Werkzeuge für die Klassenführung — Software, - die Lehrkräften im Schulalltag Arbeit abnimmt. -

-

- Ich bin selbst kein Lehrer. Aber ich bin von Lehrkräften umgeben — - Freunde, Familie, langjährige Gespräche über das, was im Unterricht - funktioniert und was nicht. Die Software entsteht aus diesen - Gesprächen, nicht aus allgemeinen Annahmen darüber, was Schulen - brauchen. -

-

- Aktuell entstehen zwei Werkzeuge: eine iPad-App für die - Klassenführung — Noten, Schülerleistungen, Beobachtungen, ohne - Papierkram — und ein Claude-Cowork-Plugin, das bei der Planung von - Unterrichtseinheiten hilft. Beide bekommen eine eigene Website, - sobald sie so weit sind. -

+

{title}

+ {intro?.map((paragraph) =>

{paragraph}

)}