From 097271b6241ec25516b018c489eb854683282b81 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Fri, 8 Nov 2024 11:04:55 +0100 Subject: [PATCH] feat: add the blog --- .env.example | 4 +- .../blog/[slug]/_styles/prism-twilight.css | 123 +++++++++++ .../[slug]/_styles/prism.twilight.min.css | 98 +++++++++ app/(root)/blog/[slug]/page.jsx | 205 ++++++++++++++++++ app/(root)/blog/page.jsx | 65 ++++++ app/(root)/blog/page/[id]/page.jsx | 74 +++++++ app/api/revalidate/route.js | 80 +++++++ app/rss.xml/route.js | 49 +++++ components/BlogCards.jsx | 151 +++++++++++++ components/PrismLoader.jsx | 26 +++ package-lock.json | 89 +++++++- package.json | 5 + public/blog-missing.png | Bin 0 -> 44632 bytes sanity/env.js | 1 + sanity/lib/client.js | 12 +- 15 files changed, 968 insertions(+), 14 deletions(-) create mode 100644 app/(root)/blog/[slug]/_styles/prism-twilight.css create mode 100644 app/(root)/blog/[slug]/_styles/prism.twilight.min.css create mode 100644 app/(root)/blog/[slug]/page.jsx create mode 100644 app/(root)/blog/page.jsx create mode 100644 app/(root)/blog/page/[id]/page.jsx create mode 100644 app/api/revalidate/route.js create mode 100644 app/rss.xml/route.js create mode 100644 components/BlogCards.jsx create mode 100644 components/PrismLoader.jsx create mode 100644 public/blog-missing.png diff --git a/.env.example b/.env.example index bac39f9..51609fd 100644 --- a/.env.example +++ b/.env.example @@ -12,4 +12,6 @@ NEXT_PUBLIC_HCAPTCHA_SITE_KEY= HCAPTCHA_SECRET= NEXT_PUBLIC_SANITY_PROJECT_ID= -NEXT_PUBLIC_SANITY_DATASET= \ No newline at end of file +SANITY_AUTH_TOKEN= +NEXT_PUBLIC_SANITY_DATASET= +SANITY_WEBHOOK_SECRET= \ No newline at end of file diff --git a/app/(root)/blog/[slug]/_styles/prism-twilight.css b/app/(root)/blog/[slug]/_styles/prism-twilight.css new file mode 100644 index 0000000..650904b --- /dev/null +++ b/app/(root)/blog/[slug]/_styles/prism-twilight.css @@ -0,0 +1,123 @@ +/** + * okaidia theme for JavaScript, CSS and HTML + * Loosely based on Monokai textmate theme by http://www.monokai.nl/ + * @author ocodia + */ + +code[class*="language-"], +pre[class*="language-"] { + color: #f8f8f2; + background: none; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; + border-radius: 0.3em; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #272822; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #8292a2; +} + +.token.punctuation { + color: #f8f8f2; +} + +.token.namespace { + opacity: 0.7; +} + +.token.property, +.token.tag, +.token.constant, +.token.symbol, +.token.deleted { + color: #f92672; +} + +.token.boolean, +.token.number { + color: #ae81ff; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #a6e22e; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: #f8f8f2; +} + +.token.atrule, +.token.attr-value, +.token.function, +.token.class-name { + color: #e6db74; +} + +.token.keyword { + color: #66d9ef; +} + +.token.regex, +.token.important { + color: #fd971f; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/app/(root)/blog/[slug]/_styles/prism.twilight.min.css b/app/(root)/blog/[slug]/_styles/prism.twilight.min.css new file mode 100644 index 0000000..6771ee8 --- /dev/null +++ b/app/(root)/blog/[slug]/_styles/prism.twilight.min.css @@ -0,0 +1,98 @@ +code[class*="language-"], +pre[class*="language-"] { + color: #f8f8f2; + background: 0 0; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} +pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; + border-radius: 0.3em; +} +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #272822; +} +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} +.token.cdata, +.token.comment, +.token.doctype, +.token.prolog { + color: #8292a2; +} +.token.punctuation { + color: #f8f8f2; +} +.token.namespace { + opacity: 0.7; +} +.token.constant, +.token.deleted, +.token.property, +.token.symbol, +.token.tag { + color: #f92672; +} +.token.boolean, +.token.number { + color: #ae81ff; +} +.token.attr-name, +.token.builtin, +.token.char, +.token.inserted, +.token.selector, +.token.string { + color: #a6e22e; +} +.language-css .token.string, +.style .token.string, +.token.entity, +.token.operator, +.token.url, +.token.variable { + color: #f8f8f2; +} +.token.atrule, +.token.attr-value, +.token.class-name, +.token.function { + color: #e6db74; +} +.token.keyword { + color: #66d9ef; +} +.token.important, +.token.regex { + color: #fd971f; +} +.token.bold, +.token.important { + font-weight: 700; +} +.token.italic { + font-style: italic; +} +.token.entity { + cursor: help; +} diff --git a/app/(root)/blog/[slug]/page.jsx b/app/(root)/blog/[slug]/page.jsx new file mode 100644 index 0000000..e2d8e84 --- /dev/null +++ b/app/(root)/blog/[slug]/page.jsx @@ -0,0 +1,205 @@ +import { client, urlFor } from "@/sanity/lib/client"; +import { PortableText } from "@portabletext/react"; +import Image from "next/image"; +import Link from "next/link"; +import { ArrowLeft, Rss } from "lucide-react"; +import { notFound } from "next/navigation"; +import { format } from "date-fns"; +import PrismLoader from "@/components/PrismLoader"; +import Prism from "prismjs"; +import "prismjs/components/prism-javascript"; +import "prismjs/components/prism-python"; +import "prismjs/components/prism-php"; +import "prismjs/components/prism-bash"; +import "prismjs/components/prism-sql"; +import "prismjs/components/prism-yaml"; +import "prismjs/components/prism-markdown"; +import "prismjs/components/prism-json"; +import "prismjs/components/prism-perl"; + +import "./_styles/prism-twilight.css"; +import "./_styles/prism.twilight.min.css"; + +async function getData(slug) { + const query = ` + *[_type == "blog" && slug.current == '${slug.replace(/'/g, "\\'")}'] { + "currentSlug": slug.current, + title, + content, + smallDescription, + titleImage, + _createdAt + }[0]`; + + const data = await client.fetch(query, {}, { cache: "no-store" }); + return data; +} + +export const dynamic = "force-static"; + +export async function generateMetadata(props) { + const params = await props.params; + const data = await getData(params.slug); + + if (!data) { + return { + title: "404 Not Found - MERNMail", + openGraph: { + title: "404 Not Found - MERNMail" + }, + twitter: { + title: "404 Not Found - MERNMail" + } + }; + } + + return { + title: `${data.title} - MERNMail`, + description: data.smallDescription, + openGraph: { + title: `${data.title} - MERNMail`, + description: data.smallDescription, + url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/blog/${data.currentSlug}`, + type: "website", + images: [ + { + url: data.titleImage + ? urlFor(data.titleImage).url() + : `${process.env.NEXT_PUBLIC_WEBSITE_URL}/blog-missing.png`, + width: 2560, + height: 1440, + alt: `${data.title} - MERNMail` + } + ] + }, + twitter: { + card: "summary_large_image", + site: "@MERNMail", + title: `${data.title} - MERNMail`, + description: data.smallDescription, + images: [ + data.titleImage + ? urlFor(data.titleImage).url() + : `${process.env.NEXT_PUBLIC_WEBSITE_URL}/blog-missing.png` + ], + creator: "@MERNMail" + } + }; +} + +const customPortableTextComponents = { + types: { + image: ({ value }) => { + return ( +
+ {value.alt + {value.caption && ( +

+ {value.caption} +

+ )} +
+ ); + }, + code: ({ value }) => { + const language = value.language || "none"; + const grammar = Prism.languages[language]; + + if (language != "none" && !grammar) { + console.error(`No grammar found for language: "${language}"`); + } + + return ( +
+
+            {value.code}
+          
+ {language == "none" ? "" : } +
+ ); + } + } +}; + +export default async function BlogSlugArticle(props) { + const params = await props.params; + const data = await getData(params.slug); + + if (!data) { + notFound(); + } + + const formattedDate = format(new Date(data._createdAt), "MMMM d, yyyy"); + + return ( + <> +
+
+ + + Back + + + + Subscribe to RSS + +
+
+
+

+ {data.title} +

+ {data.title} +

+ Published on: {formattedDate} +

+
+
+
+
+ +
+
+ + ); +} + +export async function generateStaticParams() { + const query = `*[_type == 'blog']{ + "slug": slug.current, + }`; + + const slugsRaw = await client.fetch(query); + + return slugsRaw; +} diff --git a/app/(root)/blog/page.jsx b/app/(root)/blog/page.jsx new file mode 100644 index 0000000..b19c7b7 --- /dev/null +++ b/app/(root)/blog/page.jsx @@ -0,0 +1,65 @@ +import { Rss } from "lucide-react"; +import Link from "next/link"; +import BlogCards from "@/components/BlogCards"; + +export const dynamic = "force-static"; + +export const metadata = { + title: "Blog - MERNMail", + description: + "Welcome to the MERNMail Blog! Explore our latest blog posts featuring email tips. Stay tuned for the latest MERNMail updates.", + openGraph: { + title: "Blog - MERNMail", + description: + "Welcome to the MERNMail Blog! Explore our latest blog posts featuring email tips. Stay tuned for the latest MERNMail updates.", + url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/blog`, + type: "website", + images: [ + { + url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png`, + width: 2560, + height: 1440, + alt: "Blog - MERNMail" + } + ] + }, + twitter: { + card: "summary_large_image", + site: "@MERNMail", + title: "Blog - MERNMail", + description: + "Welcome to the MERNMail Blog! Explore our latest blog posts featuring email tips. Stay tuned for the latest MERNMail updates.", + images: [ + `${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png` + ], + creator: "@MERNMail" + } +}; + +async function Blog() { + return ( +
+

+ MERNMail Blog +

+

+ Our blog has email-related tips and updates about MERNMail. + + + RSS feed + +

+ +
+ ); +} + +export default Blog; diff --git a/app/(root)/blog/page/[id]/page.jsx b/app/(root)/blog/page/[id]/page.jsx new file mode 100644 index 0000000..315db99 --- /dev/null +++ b/app/(root)/blog/page/[id]/page.jsx @@ -0,0 +1,74 @@ +import { Rss } from "lucide-react"; +import Link from "next/link"; +import BlogCards from "@/components/BlogCards"; + +export const dynamic = "force-static"; + +export async function generateMetadata({ params }) { + const obtainedParams = await params; + + return { + title: "Blog - MERNMail", + description: + "Welcome to the MERNMail Blog! Explore our latest blog posts featuring email tips. Stay tuned for the latest MERNMail updates.", + openGraph: { + title: "Blog - MERNMail", + description: + "Welcome to the MERNMail Blog! Explore our latest blog posts featuring email tips. Stay tuned for the latest MERNMail updates.", + url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/blog/page/${params.id}`, + type: "website", + images: [ + { + url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png`, + width: 2560, + height: 1440, + alt: "Blog - MERNMail" + } + ] + }, + twitter: { + card: "summary_large_image", + site: "@MERNMail", + title: "Blog - MERNMail", + description: + "Welcome to the MERNMail Blog! Explore our latest blog posts featuring email tips. Stay tuned for the latest MERNMail updates.", + images: [ + `${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png` + ], + creator: "@MERNMail" + } + }; +} + +async function Blog(props) { + const params = await props.params; + // Optionally, you can fetch some initial data here if needed. + let id = parseInt(params.id); + if (isNaN(id)) id = 1; + + return ( +
+

+ MERNMail Blog +

+

+ Our blog has email-related tips and updates about MERNMail. + + + RSS feed + +

+ +
+ ); +} + +export default Blog; diff --git a/app/api/revalidate/route.js b/app/api/revalidate/route.js new file mode 100644 index 0000000..c552be3 --- /dev/null +++ b/app/api/revalidate/route.js @@ -0,0 +1,80 @@ +/** + * This code is responsible for revalidating the cache when a post or author is updated. + * + * It is set up to receive a validated GROQ-powered Webhook from Sanity.io: + * https://www.sanity.io/docs/webhooks + * + * 1. Go to the API section of your Sanity project on sanity.io/manage or run `npx sanity hook create` + * 2. Click "Create webhook" + * 3. Set the URL to https://YOUR_NEXTJS_SITE_URL/api/revalidate + * 4. Dataset: Choose desired dataset or leave at default "all datasets" + * 5. Trigger on: "Create", "Update", and "Delete" + * 6. Filter: _type == "blog" + * 7. Projection: Leave empty + * 8. Status: Enable webhook + * 9. HTTP method: POST + * 10. HTTP Headers: Leave empty + * 11. API version: v2023-08-08 + * 12. Include drafts: No + * 13. Secret: Set to the same value as SANITY_REVALIDATE_SECRET (create a random secret if you haven't yet) + * 14. Save the cofiguration + */ + +import { isValidSignature, SIGNATURE_HEADER_NAME } from "@sanity/webhook"; +import { client } from "@/sanity/lib/client"; +import { revalidatePath } from "next/cache"; +import { NextResponse } from "next/server"; + +const secret = `${process.env.SANITY_WEBHOOK_SECRET}`; + +export async function POST(req) { + const body = await req.json(); + const rawBody = JSON.stringify(body); + + if ( + !(await isValidSignature( + rawBody, + req.headers.get(SIGNATURE_HEADER_NAME) ?? "", + secret.trim() + )) + ) { + return NextResponse.json({ message: "Invalid signature" }, { status: 401 }); + } + + try { + if (body._type == "blog") { + if (body.slug.current) { + revalidatePath(`/blog/${body.slug.current}`); + revalidatePath("/sitemap.xml"); + revalidatePath("/rss.xml"); + } + revalidatePath("/blog"); + + // Change in /blog/page/[id] route and in BlogCards component too! + const cardsPerPage = 6; + + const totalPostsQuery = `count(*[_type == 'blog'])`; + const totalPosts = await client.fetch( + totalPostsQuery, + {}, + { cache: "no-store" } + ); + + const totalPages = Math.ceil(totalPosts / cardsPerPage); + for (let i = 1; i <= totalPages + 1; i++) { + revalidatePath(`/blog/page/${i.toString()}`); + } + + return NextResponse.json({ + message: `Revalidated "${body._type}" with slug "${body.slug.current}"` + }); + } + + return NextResponse.json({ message: "No managed type" }); + } catch (err) { + return NextResponse.json( + { message: "Error revalidating" }, + { status: 500 } + ); + } +} diff --git a/app/rss.xml/route.js b/app/rss.xml/route.js new file mode 100644 index 0000000..3f4da22 --- /dev/null +++ b/app/rss.xml/route.js @@ -0,0 +1,49 @@ +import { NextResponse } from "next/server"; +import RSS from "rss"; +import { client, urlFor } from "@/sanity/lib/client"; +import { toHTML } from "@portabletext/to-html"; + +export const dynamic = "force-static"; + +export async function GET() { + const postsQuery = `*[_type == 'blog'] | order(_createdAt desc) { + title, + "slug": slug.current, + content, + titleImage, + _createdAt + }`; + + const posts = await client.fetch(postsQuery); + + const feed = new RSS({ + title: "MERNMail Blog", + description: "Explore the latest blog posts from MERNMail", + feed_url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/rss.xml`, + site_url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}`, + image_url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png`, + language: "en-US", + pubDate: new Date().toUTCString() + }); + + posts.forEach((post) => { + feed.item({ + title: post.title, + description: toHTML(post.content), + url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/blog/${post.slug}`, + date: new Date(post._createdAt).toUTCString(), + enclosure: { + url: post.titleImage + ? urlFor(post.titleImage).url() + : `${process.env.NEXT_PUBLIC_WEBSITE_URL}/blog-missing.png` + }, + author: "MERNMail" + }); + }); + + return new NextResponse(feed.xml({ indent: true }), { + headers: { + "Content-Type": "application/xml" + } + }); +} diff --git a/components/BlogCards.jsx b/components/BlogCards.jsx new file mode 100644 index 0000000..228ee3e --- /dev/null +++ b/components/BlogCards.jsx @@ -0,0 +1,151 @@ +import React from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { ChevronLeft, ChevronRight, ExternalLink } from "lucide-react"; +import { client, urlFor } from "@/sanity/lib/client"; +import { format } from "date-fns"; +import PropTypes from "prop-types"; + +const BlogCards = async (props) => { + "use server"; + + // Change in /blog/page/[id] route and in /api/revalidate route too! + const cardsPerPage = 6; + const currentPage = props.page; + + const query = `*[_type == 'blog'] | order(_createdAt desc) { + title, + smallDescription, + "currentSlug": slug.current, + titleImage, + _createdAt + }[${(currentPage - 1) * cardsPerPage}...${currentPage * cardsPerPage}]`; + + const posts = await client.fetch(query, {}, { cache: "no-store" }); + + const totalPostsQuery = `count(*[_type == 'blog'])`; + const totalPosts = await client.fetch( + totalPostsQuery, + {}, + { cache: "no-store" } + ); + + const totalPages = Math.ceil(totalPosts / cardsPerPage); + + let begPage = currentPage - 2; + let endPage = currentPage + 2; + if (endPage > totalPages) { + begPage -= endPage - totalPages; + endPage = totalPages; + } + if (begPage < 1) { + endPage += 1 - begPage; + begPage = 1; + } + + return ( + <> +
+ {posts.map((post, idx) => { + const formattedDate = format( + new Date(post._createdAt), + "MMMM d, yyyy" + ); + + const truncatedDescription = + post.smallDescription.length > 130 + ? post.smallDescription.substring(0, 130) + "..." + : post.smallDescription; + + return ( +
+
+ +
+ {post.title} +
+
+
+

+ {post.title} +

+
+ +
+
+

+ {truncatedDescription} +

+

+ Published on: {formattedDate} +

+
+ +
+
+ ); + })} +
+ { +
+ {totalPages > 1 && ( + + )} +
+ } + + ); +}; + +BlogCards.propTypes = { + page: PropTypes.number.isRequired +}; + +export default BlogCards; diff --git a/components/PrismLoader.jsx b/components/PrismLoader.jsx new file mode 100644 index 0000000..73382b6 --- /dev/null +++ b/components/PrismLoader.jsx @@ -0,0 +1,26 @@ +"use client"; +import { useEffect } from "react"; +import Prism from "prismjs"; +import "prismjs/themes/prism-okaidia.css"; +import "prismjs/components/prism-javascript"; +import "prismjs/components/prism-python"; +import "prismjs/components/prism-php"; +import "prismjs/components/prism-bash"; +import "prismjs/components/prism-sql"; +import "prismjs/components/prism-yaml"; +import "prismjs/components/prism-markdown"; +import "prismjs/components/prism-json"; +import "prismjs/components/prism-perl"; +import "prismjs/components/prism-markup"; +import "prismjs/components/prism-markup-templating"; +import "prismjs/components/prism-handlebars"; + +export default function PrismLoader() { + useEffect(() => { + if (Prism) { + Prism.highlightAll(); + } + }, []); + + return null; +} diff --git a/package-lock.json b/package-lock.json index 3d15abe..c39642d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,15 @@ "version": "0.1.0", "dependencies": { "@hcaptcha/react-hcaptcha": "^1.11.0", + "@portabletext/react": "^3.1.0", + "@portabletext/to-html": "^2.0.13", "@sanity/code-input": "^4.1.4", "@sanity/icons": "^3.4.0", "@sanity/image-url": "^1.1.0", "@sanity/vision": "^3.63.0", + "@sanity/webhook": "4.0.2-bc", "@tailwindcss/typography": "^0.5.15", + "date-fns": "^4.1.0", "globby": "^14.0.2", "lucide-react": "^0.454.0", "next": "^15.0.2", @@ -26,6 +30,7 @@ "react-dom": "^18.3.1", "react-markdown": "^9.0.1", "rehype-prism": "^2.3.3", + "rss": "^1.2.2", "sanity": "^3.63.0", "styled-components": "^6.1.13", "validator": "^13.12.0" @@ -3930,6 +3935,18 @@ "react": "^17 || ^18 || >=19.0.0-rc" } }, + "node_modules/@portabletext/to-html": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@portabletext/to-html/-/to-html-2.0.13.tgz", + "integrity": "sha512-T3zL+2RcPCPGCp7rRrGrNJnGAqkdlpiOZnb/wh4tjDYJevteGY+5hmA0/5idLXzLiPv6vT8Gld852Sc0aFXwUA==", + "dependencies": { + "@portabletext/toolkit": "^2.0.15", + "@portabletext/types": "^2.0.13" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, "node_modules/@portabletext/toolkit": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/@portabletext/toolkit/-/toolkit-2.0.16.tgz", @@ -4899,6 +4916,14 @@ "node": ">=6" } }, + "node_modules/@sanity/webhook": { + "version": "4.0.2-bc", + "resolved": "https://registry.npmjs.org/@sanity/webhook/-/webhook-4.0.2-bc.tgz", + "integrity": "sha512-I/Qq+ppPMkdZ2lQ3iHJ1HylBkEy+imn5qCOWEJefdVIyWdYPpNmTAH09exU6K6M1HRMM7Au4oOdijx3kruZEWA==", + "engines": { + "node": ">=18.17" + } + }, "node_modules/@sentry-internal/browser-utils": { "version": "8.37.1", "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.37.1.tgz", @@ -8096,18 +8121,12 @@ "integrity": "sha512-8YnDaaf7N3k/q5HnTJVuzSyLETjoZjVmHc4AeKAzOvKHEFQKcn64OKBfzHYtE9zGjctNM7V9I0MfnUVLpi7M5g==" }, "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" } }, "node_modules/date-now": { @@ -16563,6 +16582,34 @@ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==" }, + "node_modules/rss": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rss/-/rss-1.2.2.tgz", + "integrity": "sha512-xUhRTgslHeCBeHAqaWSbOYTydN2f0tAzNXvzh3stjz7QDhQMzdgHf3pfgNIngeytQflrFPfy6axHilTETr6gDg==", + "dependencies": { + "mime-types": "2.1.13", + "xml": "1.0.1" + } + }, + "node_modules/rss/node_modules/mime-db": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.25.0.tgz", + "integrity": "sha512-5k547tI4Cy+Lddr/hdjNbBEWBwSl8EBc5aSdKvedav8DReADgWJzcYiktaRIw3GtGC1jjwldXtTzvqJZmtvC7w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rss/node_modules/mime-types": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.13.tgz", + "integrity": "sha512-ryBDp1Z/6X90UvjUK3RksH0IBPM137T7cmg4OgD5wQBojlAiUwuok0QeELkim/72EtcYuNlmbkrcGuxj3Kl0YQ==", + "dependencies": { + "mime-db": "~1.25.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -16848,6 +16895,21 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/sanity/node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/sanity/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -19732,6 +19794,11 @@ "node": ">=8" } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==" + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", diff --git a/package.json b/package.json index 55dfc74..1837eab 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,15 @@ }, "dependencies": { "@hcaptcha/react-hcaptcha": "^1.11.0", + "@portabletext/react": "^3.1.0", + "@portabletext/to-html": "^2.0.13", "@sanity/code-input": "^4.1.4", "@sanity/icons": "^3.4.0", "@sanity/image-url": "^1.1.0", "@sanity/vision": "^3.63.0", + "@sanity/webhook": "4.0.2-bc", "@tailwindcss/typography": "^0.5.15", + "date-fns": "^4.1.0", "globby": "^14.0.2", "lucide-react": "^0.454.0", "next": "^15.0.2", @@ -30,6 +34,7 @@ "react-dom": "^18.3.1", "react-markdown": "^9.0.1", "rehype-prism": "^2.3.3", + "rss": "^1.2.2", "sanity": "^3.63.0", "styled-components": "^6.1.13", "validator": "^13.12.0" diff --git a/public/blog-missing.png b/public/blog-missing.png new file mode 100644 index 0000000000000000000000000000000000000000..bf2e891f632fa2d8c6500dfd590dda7d84c6fb08 GIT binary patch literal 44632 zcmeFZc{o<@`Zs(VLPDlw9&SQ1&twQUA#;)>^E}T}rjjybCLv@BNv4F%Ga*TmB=eMc z%FJ`#eE0Oe&vCrRevjw(*Yj(p1h6rk&PH;g(zM6m#BgnNj!b2$XMr zW-DGF;22}cEhVk7N#iU}&m$CVj{6plD)whZwB!r8+~%PofVIFVeh#mnhu zl;<|R=L#aA?4x!W8%AETxtW#{Q=;L8heedv@ccrM+--V3f#Zdu>}}czAWT)O`W0C(rqC{;YDj+KXXer zr0;c%2~uuf>HKlt$6=#0S>JZwd-`YVja z<34tLa3oYw@kdIHA&}EOp$>zH=s_r{dC~ovt6xWk>n(pA9wJ&KUD|iEL=6!LqPx~$ zj=HKU;--%F+{R{(Cg$8;_OMa#PeR(u$=KA^+>O!1+|t@X61h@ck7TqqlSJMYQsq^3 zk~6omR`hW(zv-i+{75N$MhO=)3vn%!!e2wcZ<0tWH#aA79v)9m zPi{{EZbuhO9zHQKF&n}HSPvdAV<#RyZeAXHd!B#4!qrXQ0|xo)f&RBw zxN3t}^Jtm7I=Z`^DWN2h==+PCIhtCViJyGr=eH0R5)&2UvJm4l;}SGAHRCciv49`KW_%VxLhxq` z^M4yk$-&jl*um5sJroSiZ4Kj?3Rnp63y289HOBm0f))Z6T*kZt@T-`Jh=rNCsR*wS z-@gr^?qUsAX>9lJPlX=J42I(86B9NT5)|d)7v(kO5)?2IQ}03WZAsEDwzh%nsi^hPJ+>6p8?f+eEIhGoB$4PL7|~b$^Utc_PG-h##wcSqa~PDDUr?Nn zPn=g&n~zsqKunxp=n60V`fukunps2#^mGcY#vbNoC(j9Eo&L$x%Gklu9HPfx=K7ECt^YTsAY@@?EFvVz z$0aCiZowsJE+D`qDk8$iWh%xmEGlMhDr77s_@AS@I$F4S8oQXwSi(BOT7h|CW3-|7AE{$0wyA2V4?zI zT%v-)VD082ycWiMqGD#IV*mNj|G%fm$9FQtQ=5P(p4h}+Qg2zz z>+j|IuU6o{I{9zw`aiZ@`2VUH%^d*fc|tviaZOwjYDpZE8`n{YWAuMs*5<~+H)ov` z@3CCbVMuZYdM%(Mh;>fT%&F*f)_~11JE*`e7+F2SqjU46seQ&=WAHVXf z)s_G8`ga=>g85f+*Te{cHBdY1}#Cq17g?Mx;)&QEtG%ERr^ z|9Dp)KRx|~V8q3sJpIUc_U})BuL0QJ-*)i#8bC<++YbI-19WEi+YbI-gTL(ny#jyR z!QX3eVg`T5#J_#v#1Q_@jep0)|I1@yCs3} zMBJCPHRrE3<&~9{At50OStPc$wrh(+W$X#cIM^p=T~p3UI5{;n zWqEwu(t^@17yt!sR? zI6n3E*3Ye6lqb1*^=e6dJwt->C5np|3(pgroZE2C^_&&Xr|D_)8vjFaqVwnLe-Ea< zc=0qMBI0>kny}gFfH<*;?uI#!>XPbe8pmHXyV>sk^y?j<54Tf8kQKh7$^euU~`Z6adm(|Fv5;CEsSUXZBgOG&aiAc&#DRB=1w?Xmw)Xo}50WR)zWe zs7Wi# z)UCG%9V)VP3R?HKR|uJpNSZx8JWK)tq|w{P&8^39x^IDZ$0uV0Z0S;mwVMm|2`!otEPE-qhqPtV!0!{klR9$bK( z4Jj-vtgNcyi@y8*-8*vd9EI!G2~RyTP~C#i$baCUL4fz^)i3`~lOi~CGGd_NG`^94 z-_XzCY1Q#>)^OLl&upl^{ zFal#I$>1FG{e^w?##SETQ21?d|Po z=Z6@;Cm~_?R}4KLeI<|~8vMUpkLy=`fFosSL}a7_>{e^5`~ueLJtcTCSFxPDy{U$W zhamtEPGWekPbt~i*|mKA8vobINf2W?nbaEaP)8`(IXEa34GKi#+87%%LHvX$Ms+&F zYcAgqYVeJGp-y-~!IfsOF(M+O%mJG_Cnif@naMuU{N()F@<_GU@sZz)^z`hiK8L)kH3VHVIneR(lgi#<~gOa|XVf&vy9)i?_&11KFy1QHU_I$ug zsL>D10{32AT5A3FP3Py&pT28@RCII^7kLa?*4Im~Jj6iY1R^BHAk?96@bl+Wv>Et5 z%gM>f)8^n47q>yXVYFgye`x*TlB2VW%lbgwOn0}+!O?d8wiG7fS~^)E?su?@gUx<* zXJ=<)O5c+4@p0X|cf%7B5-6#u`H5iwN+$*>-w=j?gS9e;?Sp)n!9?{+o$GBrM4%Ka z;`~e4eRPob^|hn)6`FPtgVpWs?L9FCg1qqX@SX7|44M7&KYu`CQkaMeI53s)Klt8& zf!K>iNL+)}SzKIfSzdM^4{mN-dY+OJzO}Ul!Ravu+#4$!^5J>u11C1m@GFU3Yfq#P z8kb(4UW$MOb9i_N_x`p!?O;HuoTF=C5UHF)%FN7+gO0seV?_anwh$q%4*&Z3Q{Q)c z#1rQ{;g$Er%#^7u=Yn0Cn@`I~ zd>gf~uwaGMTpM`s6rASz_3K#G$2MRsH7w+xm^``O;p(s?f1j_ z4*szu>eK;>Ox-~U6HulT^)dhLyT#Owl zh6Rx1xsaYacTP%iUEo)ZH`&T)J;V3IfIo{#K^?bC%FEpyhf`Bhs<#GnA)4La_Y6jK zOOJrNe0$Of`CKY(TU|w^WopVyMOC%+^XF(|Os4|uF)4}A??XeOkQH@j6YJK-o1b{? zuA779LW(b!TE%jKdqU=VtGIJMH#ZlHF5tnwCb_)6zJ3eDC0Yhp>5YtxjOWS8VM$3z zfZ`(Z^DjS`CAtUR4BiT%i4wGy9AfHZ{xftH`)N&>fo;G&!nhxRAu8J5k50a_TO zk6qmKsf8qU>~Z^zRzA6afWX@3d_O=gkE8Pqg~WmTsM9c7WLO^w0ShaGr(Ca4R7xrm zQXXW!+OpwxmwL$4`U$HH5XuNJc5EOX)ZE|Z{#ASbB7{pmetuF{S69BPS83GKr6R6J z(Nlflg73qFgM+=+r$UWC4}yiBd6u4ZmUz2v^YI|tj+?w@OJ}Fjix)2-ZDd`KGI4h1 ztJ|HS;iC*JQa~(YEqPr1z4c*>gbEMB?fZ(89oNj)hST~&tzCL7ZX%8@f{FdI!gjb- zF&qI-`h9dX3T6k-fyO=X99sh06o(LX*1-O%=@|U%0X)1Ocs)BG(2X4u#WY_>OGj6g zg5#S3nXtP01aE>Bqc6U3;|8Cwu!ShXmk(oy{@%TN5FDRvtx+lt4i2tu*B?tJ_gK6s zDnc7|2Fn&~tEjYeRC^Qyjl#iXL_+VK72L53_AsPgp$GnayAQyya6Xdi84cpkHN84MbWRX69cuk47ibS1vj_I<^DS zln6NbxzC62C1Iec%O%?w85!B>GCV>a9WmVBJ{(YpT_!vzcu{sumelBrs7aBe@%Arx z+G+tcQ(nqpY$jq^ylL9IE9dJ>=nyw#fB)7Ake=F_dcdnnjh!6Qz#8p?OFik;8s;!|mf; zWDeNa-1dH0<#}ZCQ~&y?jSW{Q3h-fVX|qU4`Z_2P6udHiyX%+07`z`owNPe_Yb>~3 zDS(HRYMUCX5Gw&G~xL|c0 z8}C_W9{bnF^D8iAc6M2md2Ri^01tHhDI~117$ITxLqOS|x0dg5wfL1{qkCtCMv10f zsCq2o&SR6jeEwnVjk=nkO*75K0ibC;Po`Yum zB?2L@-i$bydU{@j+Uw^_H597C1mUI3q9n_a(A1U46Qi8-oiSUf)r15pqsF5nDY!2U zYMPsyTTrqJAhm?O94hRYT5i?6lkATrVL)m_#M!#Lz6Sl}!uy9(L1QRGKI({KUCySJ zMmym$fe^D;<($>+(SR(2qB6dakU2d9gnHwk3{M&@;~qcz;X+Y;vQ&QDiBusCZ&f)ZSJZ1f{3z2K8kf@zigNE1x!dQPTL>n8|fgF*;x>c8-p&g%Kek z{tbv#Y`5xr5IoV_eek_TE)4_)r&<8gLsX$oGn5l0Wo1DMS&cZFNnKqxRB_1k3Mo9l zlE(Ztu_DFbnagi~l zrxd+t_*e~|ZvOs}P=O<36nTZ%`|uzK8Pxg&dr5qX6XN)?uq9a47o4jSqF4xv1eVeOMv!YOumS}a`Ln-utGEJPSTJch}B`4Ss#%frLdY{Xkk7J~hIdw*wj9808# zjHEB1{fXVf>fM7~fs0>5mU%@b#l&O^224!zLMYVrSL4r;0wbq91H_*K@ys-8BSL_@GP7uaGBqR~9POW2Rdm711(i7%V^)!k>y40@oIpmYdXoaxs zoSw5)`jOM7A^uHE9uS7*p4m$J?hw;QJ&91u=8aIm0*GRVheSr0BAx>U0yZwRQEi~F zU)&sOWijO<_988iy3}%;gY7a&T1@yP{PxIMz-N}v?tf38x-LNH6pV5@XCe=Qq|hqkjW&S zX6V$*OHGB(i2OqOc9WQ-@B5eeO_gsZaV$z?<(nZPki`}rMqEzsbuA;ltSp=&Uoo88 zIT``B^GYtaztNQnQJS#efbE@l&+s9k$0BHW>2a&cIFUxu zrjI0S798aGXW+9i{$-tl0ofd_2g6Hg`J)Be93={$o{o9mNLex|(KX_we6?OyJ;Dus z$fA^-%Cd3b8~stax%_ z0(0IinNT(?zSGQt8`0fgUp6^ZX1VGmVTTuS=kDERs1&{y^{;G0jQR|RLj`On`pDPT zzVK#l%Li(`kX934FNLos&h`}Q3}Kwh76bye7)ELD*pQhEa93cwj0ab?_iv}fmkzy| zQ2+i+`4Uv08TJY&)Vc}c`CCc?98Mk{X3|iVrLP1LP)Rp0Y&9)xAt}PgcXpm%zN_dA zNv-(92XTQd=KHm2cIP5k7{D&}Z~rB2Rnc%!m3J?Ked6r6&eyk-3WF55mafefn0pLP;m-B~jQ{LPzl z`7nOS?%9&wh{MHJ6K9JbYM;I?_}2%p+Om_87zt8?HSZDL9XI;||JIyArZlF-6CBQx1xzkvGw8aA( z$+1pkZuykO^WXV2+PbjC zZ*(mi146TZJp-K33L%C(<(Wj1N@-aeFm%xXD2Uy-qS~1kScmU$&@s=pk=Kv*I#ZF5 zj3hN0H9&Czlps#Q!0WvGS17`NK2ihAddg`10xAqZ?M8z9G?mHlAW&{UCzKJP&Sp;N z0!LQK=3DltE=@~b^OAVsOJe=BYr0ALXzjd&?~aD^o!e6q)*|5m*OGf!X&%14G&@tH zoWoCM{dE7Z?#+C96<<_H2I#wKA%+p50CfaJGqdC?3G4fZ zP@R~|eJ%$F!h8^DHyFoq7O>9N(zD)K^Se7`x|{;H(xXBY1OT0n`tK8RC*IK927etA z^W33WURfz)n)_iXx7<2WJH->p%;{nnQCV` zcIjWp@A{^d$Is&)`Wyan9^%Y?@NC1;I{H-#8#?WNA;7^B77>9+cEMS?Ec();kdVjC zh+>5yJ7Hj_iC-v>1;cwYQ8OOIf z6G+*S5dZOT0)+tfqp>fO1s>MkW^LpAnW}}Jgf8=2ugHKWfsRV)anCvcDZie{HTF!@ zlNa-=mO}ugMmIKlwhflGSC%~Pep~Bd8NRW+*&DQ|dysNrbQ^&EfXxM{6>_w|0*_Jx z@PjcwPOlHtWcAPUI;o>x8}la>S5~rb((Ar{6ry*>4m;LjiYdKef zTCYGbi|b@rAnLUfzTzt@z)^LRg$Q%wAmvi#3Ygt_n4)YDF@!$YLgADMIu~5HXTCN* zSYkw@GgTl@cpX^yw;wf0DhhuHA9&tkC;to_Fn0P**fWr9V8Q3FSNWsNcZ5{wLgf}A3u^`Q_N4% zCzc$oxXrQz_17dQpMlb;P{)=7Ad8+=xt?_P=-|8?I2EKnrz(B;AW-|cs=l5l`>0}c zr9BAu{LW`G!p3>GCMbLK7LM-M8k|>PZpJJV{`E$u;P&XSNr~vX^@_}Ar|;fSWCpiS zLZ*M*JU;g7;bmLl6tE_Cif}MExq7*}<-r9LT%e$((pO&da8Y!%tQ;O6)*rJiNcgV4 zJ%m^%bo_HwwMZt2T*>!xF95N&Yov#~t2C>-zNI*UG!Zz!UD%WoeqC_CbJ64;_eB*} zGT>%I=X_!mQC|fYg^{HLmKg7ev{E4{Q z=uuZw)3UoO23+UYnWHTqI$%K%b#}kQ6tZA;zPGz!B0_UJJCu-pLr_TIwJY&a(R<|K z@{qneKPbxnPTD!&anF%xCsykY7lu*Qu|GW5K){SFGs(Snz-8l3D)tXW7EPK|)cl_+ zppYc!b$FZ9H67h!af>W4Yd*3|pBv4By*cLw-XYX$$ekEhZjTm5328NZ)3Icl#bOdvZ zA|ovkGevmGrxaxXi~0rz6Pugym(%CJH8wT8JW6=c+tu272HFIv3P23_qBx=NW%c#viw3(w%M|<$YX@QZcYQmiT}tl-IN{$UqzRZ- zJ6_z_eM=|Zzq;}joC=mzm8*Q?oluHu%cL_D%gn@hjHIEy8~Vf~szDur&((4#^Q98z zxA&m}f-cf7YgTZqU#cIRX5{Rw*@1DPymQ(`R7y$*U(Y<^ z+l-Zz541UgwK>kD_de0(Y@0m9AFwHRa&~@T#(!BNf+By?ao^Uz-tZg32>^1wcC2L2 zcpez|nNT40#SKV zSTfNPUHOm-rn$ab^JehChbeZlw6ARsf+JX(K-?ZUGcy=!N)NiY^!DCO>6S#H;#gd6 zQV7Vrwk1gFu>h(;TT;4f+69&k!ho)x-X1H~IX_MCnW%PiE=`d)21QR`lhG7`on0R5 zB(q;5g4b^&^D;TUlPE20#PjrYOJ(9S(DVfW38plgi5(%Rc#zyX8|R)uvlEM>H;*v} ze8Zih*)1PqUdp#e0Y|E2eX9E0iK|L!dTIobnZrw%fzdY`pk#qMwJB(yMWKGQUT==n zd5T47NpXo z13q<@DztA0A&Q&FqYqcAHP~Mc*)`debG(oEGXL();5lqe=}nRm&bZF8xjCy>6qy09 zwDQLwC*nP61_(HvFNRD$OMtj?7Z%~@s4nyOzM5XG0h$6I+wv(XDOu}zEWy=jQMPli zTlSjY@6Wy;P>T><(5Tgv`NG&otdYF_dBzyUF*L)OtDbJL*PKNTVG^9Y$e_0A+c%1! zdcpTASmA(y{`6PY8mRa6LcVzBhq16PH3m^=L}7EHo6|vS*TnxH zF)d?w808Wzu(X+nGc7l@>6dytvg<9hw$UV1ZLOs0nSgo#oonmUH*UT7#Dv_cxmzyY z{xW6uGE@j{qXEZ-NkTg&B_V@FhRpO)y(&tf#LC!(O(o1g83Nut`<(vJ^t)1~}mK}WDR3RD)Dxt8zid$9q$ zYmy{FZ}^j*nhYxfu3?Rvf8xn=kveovI8*(14{(stnAAHfmyN0*&#J`>&FwqZg*Yeu zWV5>-(_>*?KeKJ^C&pdvUvuz#;EKE>ee}lu+S=rG-=l*&H^aZ81nH1i4Jx)p-OitZ z!~{LT+fKgq&d#$ykzLxixTYDf526c!l6>$nbY~~*&0Uq_R`So(dBAcBkbcm2 zhl+Dqns?G^lw!@!tKnikU{FiuJ0nPPxOkW63n#Zz038CS?gge=j#ep-KM-d?%%(|r zH$BmBHWeV5hGruNv?7rGi=DN2Gal6eig((bhbnq)kulehF-E4ovj+#XfGncuB_}S6 zBdJsJ5XBHHx~5HV+2`)lM70CO|9ba5ppEZ8el$TrWm588Bh_n*?DBj5I{}ZY}HFfpt_;u$2 zZ~+g6Gdiy`$Ym6oC&H#gy1pwhGdp`>K&sV5^N-+_!82ptUmk@xFz;QJUSJD+z>^zz zk%GcZy$hlVq(>40oZXkURz#~ksGnAk+9#=OrJOE8QwkUc9qnO28$;?u`iT!yCQFa& z*rfdrI&Riiz>`6nk_*a~agXXDMwH;U3U%!CUfkNWvoZ+)4~-oi)yZ-Vu2bsX zbBrGy?uT7fae)$<1R87UonjtP@I$y-JJ@vvuqf4m-z*{pFlz#ODLa!<(!`-Ch1bE* z3V^V@I-4F~;^-LV{xIN+4n%acL&Bxk4HliNN4ySp`23F#Js~#X1X$-i=-)bE!10qn!5;3M_*oz{!e|0Q6pwxH z?b1ee)xA)>zr9QsR4?~wrJ|w&8i+%bD8X0=X=bXJ#Bo%S{vfDWo9Us!32>yMsq@}Y z_^uGDbR|JqfaZV>y~{&G;|DqqC=su&2&WK&P-h3)prKF{$6!8%-a(QI4%#iNJy&4y z#U30<1Dt@qb`XlQC4v#lsJNxVVy& zqGsTlMSu>dlr}p3&G!c10(~c27u9K|<=iM5>!?5DB>E`6kHr1=+$*uTpSf2tA2gH) ztmx4v5j}?ffXi*Rw~kJ0O2Mp@{9jt!zWk zL$@nxyf-YeKYVOR4E`pQBJI25G$qWocjR5OyrPFn<+$W`aQ(3Q#iJBqXQ$8Vs0xx) zDWw}2Mg;?5iY&+}f2n|@?v)1$?dHH{>3S38U|*HT&Tph6QK*Lke8RQJ&r{oZ<{4-b$n>SBT zI&uBc-W8y4gx9Tss|yFRx5jsu6!t{m4zs1k=r+8~2)Cj0wu}V<*!ueV_uRLkZ(aLe zYPHCk=4ffQyxnoAxUz3;R%;Mo`Z*OV0Qn(eZu`|U%!YzQCt*p#<+!g!kwkbSW0Tt6hUk3@KH#jxF=DbM9T)9;MV68%u|f=rj1H(=2)D?7Vxog+OK>I|Nnp`dkRKM!X%WfdY%K9P9F& z@36*^koq3!qx!I|UVtmI3(gKsMWx6CKR)lKr+CjBsZ|I3IUwArADP19 zUlRR~p>IPo5eYd39qcAr&w=B#Mis?tSu*-h#x!?EF(-M;)w&iioSgA_PC}I?ZwnzJ!roDy(tQ>$DK@ zn{Ivyg&=sBmA3FqV8H+fx}Pe_wz#^gv9p~L5-&ysH2g&ud+-n^(QY+j!QA1Yp*}O} zF>9cE!qf}&IVr^I_N1Xtqra^U1>L;bqQ_NGyRG&H9Fc)&f|=(z$YIcJ-|t(`lT4OE8updqaeKir zF*R?Wn^ykr+9f4YbQmj{i$8i_xq1UN8n6KXm*vgldC`S(!6e6}qq?+{7Q5Q0Ahh|1E$Zc8Vk|`P`{GkVG~Cv1n0=@ z8$j!zwY9aQz8|_{K&sP%+GgBMe789IP2AHI=H(0Q;kN*U^UAU|g;TETAJD zEh)*p>t8YZ0(&e{hTb0zMXu>`J_ew}L}dd#=J72bOW~bQ&O=+!VvP>e1mk@6tWcNp zqX8DM$D=+!^lqHH8H&OL5oy5ln=Vjsfe7QS$V1W?kGf-kloZb#MrSves3S_5u|Y^g zDwUgZ3pO8Wo+jbtUsIc#+GXwmjxJtapsbY>esoR$&dW|I4FzVC5=sE=r9+%*YHA9} zxiw#0wLf`HJ4??V>?uS|U?t=a{XZiXp=f!!w0-0iGF|o69RMIg*gQ zHR9dr_oOH3b4O*rIsU=vsJDN|2;Dk^no{xFTc8hO6rqBM2*9SMP9*&3+CfL$GwlL> zZP_oi;fm-I)k}lM2Of<7O1@*eGR_EH^~o4(TFz$O7fwV z%ZXi`1{XDAS(sAv{h!&~y+X4Aq8sx4YS59uLRac@1F1$Cp#ZHDIG_^)eQw|l;ODzx zdu@&j@yeF~uC*R=13OW!r^2Ol(YTjMu^I&3L52%<6w|-5*Y_i8hV%0%~pPCsmGpWG9TyB+xr3 z@o!_)s4XsT_(7fhyz#R=pq1#CR;+O`k_gU;_|D!h1VsJUQQ4}HxpySHT@YT+zkY&# zG2pMJFSnLfRwkhtAyJZ%;hY2=3rPflZ)Y^p6Cs-38J1| z%_iN%*H*{nfJvTQTqFR3ikT4lHlW9V;~_LuzkKo1xN;6b^aKln0%#RwwQiA( z{1I_YN#}Z~dIMB0Kt;ZxxrBPbzqWrUeMskx`8=fVcEJF7iD=O@`DZL(nLMePf61RD zRFfWI!iX+l^xec$7sFctGJ~~$ixoota)!*4Tc1VIX0EJs_-1ec-TDN%F*LNU9!tb^ zx+Oe#qgOaFH>XJQV9ZS%1k1O+xwCx<)F}vt-%bLESsg_vs<^bYNsZ*CE+;ZU`SPD5 zPKvHuub8Swh*Ekymh=Fa%R!UB)}a1}1gnXf;M2)TQ=qgms6;?aa%;`^x0^T^qG+lb z6qyAL^^Hb>EK0ZU==|Oi2d)6H;%8S&E$&EJrH-llrlwf1*Jx6WKZ({G*DFKgT80MY z)g8T)wvz&pYyeQ~j6jSLjFVW)BQ_c|Nk(6<74F~rK?pOzBz z*BObUg?O(|w3#bpkr))+R{YEXR1igY=bvp6hy2uPN{gRjP#4ieJdS>UY#>V8R(pS& zm2{^z67n&)2#|Py%X!>tMpm|g4dN9MX-{PABLMyw7(f16cNC_LGrBlRspOtP&8HjO&<>qBmmbel*UXDmVM_F5n*E)K)Waxg7Y`a2rm`> zb4;JT_?mqkZeW}U5CwDIx0*sPRrPOn*~j1O9(SSyE-FGn?$7?>mJg^`TL7Ewwr9jR z-(e&sUMGUesq{?X#%@h?;7?v_UQ0jIUqeGlpBHEdaG+1)l&2aSM@;kt-K4LrETn-h(^11yofUYt$H1v~46A~_r=R1{iru0PK({a*+ zrw8!1tba}q5-N1zmpmpxbqMVxblJ8m=~_h#a$E`esKr4w7Sb?ZdP1D$TkjK`_m3C+ z4fXV7p`8zPdac1MLrs{IP;`s*qkp6_up)FSGcbc!TR{H>BLahWos*(k`4HMBAOsit z)=LMUZWc(Z2=;V->_z8nj4=p*YPkX~sYT9^**cE)#Zo_IsM)Py^O6uk2O1I)`-Fi- zt-*J83?mTDgAald48Lj+E`zSl@T<$D(%fVjIt6fa#)_+>?ht`sA+alG-uX_^AlV0v zh11^h(CinG|G~Q)z!{LfU*k{dk&R_}7LH7Q2>A?ji`I2?U@G4R2T4LvI~sUMCDMtt zN|R;a!p9N~{gXb9UxG3N@F6of{x}$M*bp9?{sCdM+#fODd~L0t8P7s>6l#-(O&=~o+9b{Ber4o%oRTq^+X%*PXWw{fZJ3Bi&tnXe@k_AE$4qnPf zBM#R&5~!~l9u78Ii(%eE#R+y-cTTQd11h?1Yyj${-dPg-;8#AW$~nlXuiuK#ZGm`- zWHsm4!lzG>CT;PVHZ5J|UqgR0-g}nUc?et}ggy0jTO)movLU0gp+`^ear5xx(j%7L z#G#8)dhGDvno-`^FlX;Vcxz^{ym<9Uq-Af~*%bBr67+B0zQw=MH3WT*eC%_QlIZ?#3GrjNSWbAtCbp!p8s}-J3;&W0Wfg#W+m`01DY;3r^a}hck^>Y zqH5Ik%4oXH3<+$ZE+;U}Q6nSw(r|Pz9Ax5+k?L8{u!8mSTAMh7mg#_?H)_lE6dJYR zAL(&YyeTXk2d<6nSz;p9!KGW&td-E*3QiS{nKXNwCvMq{qrOAPlnE68=&MzQ{6^Rx z_ZYpjn8cRRxqUl$#Ibrr$IgZ?d9jipuvoif-p%LtWGq;Z*`mOr>=>tGH`y)HR+ADV zSJ#_6zT(so_=J%V1M09ttunzVUT`R(NGyYJQs%~jo!lzxR)CR|LJ!VTo>V!P`Qsaf>-t{&KQ1Da?bKR$& z01B7G7!Jd3~FCs zUBWt$w+eO4Qv?W=CD2>wU{CU-FA4lPB1}xQn1k*krMc|!Jz`=X8+1mFXl}0 z+b7ZRkCAw6EYzZ{7>gt(=$$_I6DvXSg!FsdiO|k@MDXE>UlgFoY*lFb0)ZYzFlWt# zknlQ=q{h2*=MIU-Lz10X{5?Q8v<07bD!Ur35_n{g$sMAS6yb!m z<1eNF;<6slqjyQdi_tFa<=_GusvLmcx#^~HdU|>nX|#m2^zD`n&7Tj88pPN?FPmqcf|}3OO`%`SzMruPZ^cBHmBtej6DBqoMwwes`*M65qN zKAc(D@&PmfO%$fdQc#!YzaYJE>ls>7rvHZy09n!Ceh)3Qbh4nu+-G$_Oi7=65Bi+w z9$4P!U8pW73Gnj=K`7I=vR(DyNl2TICl@CtBXvZRMOldq`748>EL~3gS`Y_4Wm8Y< zc=l)az2h+`$Dp%u#jI>#aB>J``0L}v9_;SGKFO2s`-j~8Y8T!j^_FGIp7I7Q;QhJ5?*t954!QXBlVsH%J zV5kW00~i9?7D$*hiUu$E6+w$u;E>PA`GNLDb z7r_e(P?j2}90&4PSLIS6bYvCKbA|URvmp~;)Q92aq(1zgc>249EBtTLUWa@HG z0J-*C4geOgNPt|bY$FBDVZOQjXF)ofZ6GI=p6-RH)L_T6-l(do%HIM#!V36B8=-Mr z^5V9SBvcZi%7(~YR-if?vh&FP`ab)ub5f@n z)DSRi;-OU85GSMywmpFxIkC}vy?Ja%QEnRhBcQa5N_71dFTpD;7w)QEoALv{MM;-c zR(3QLg+a}ivEq@cmRsp^4h#CPvRDnkCztElb0kRv*YSR4r`$ThzN!EiZD=7DPo+NO zr51jW$i)`+kw4E;8yv|>5M-I;yRs}=$yHTi5CS^65fiMnoT}=#1^8POr(94P=x`cEiNw)4mjQ)1-ytbMgcax%XVkYt0~h6gF^xrYMu5#lUpSvH zEaVMmgV%hz7N>#QeL1tR(85%z6v6vKDCjv?> z_}j17It5S*cpi!U{QA1vQcatBPR5(4JhBV8ks5=d;%&^!7qKp(1aE-!9^Ultt zABC%s^?ZFbj`I1yf;%)whF1Ju+>6jRV?oA%RRBd&ALD7pO9`S?j!jQ7ad8OGRsFq2 z2;HRK7ocJ5pC@;zj}ztVTiANU8I6O90VGaj&m0Z-Yo}Q(p$tkOS1TXg2}Cz&t4Az? zI(T2DXj46N&YQ;0BVG3VatNHuqu%j$s>$^`1Hxz-orRZ%`1*C3qKnU7nUbD+l%u88 zQ7h2HNRl7gW6^xLzr7prA{v}J!#y5U#!$bnYzj~!be09!INX1+U^Yy<-0ASL{8_@; zZc$cn`q~NL-3WM+E2(Oe@K&B|siT!rRv+=6BuPS*dc^jymEdxX<{#V4e(7+vd`Y!Y z*R;#@XvN?rp=LRXwd6DVM0_Ynyg=Ut66Dt)a7OE7jfd#tWn;5Lk7~dPGvbD|IoxyO zYhOISl9(r7=2GQ5+p0>EYYGPUnUE@*6G~p7BfU0eB39+^B9UT1Z$VIozJCZ!$}7~= zc8g6peixAdcVgG28naK?Dz8x=D>uB#Bb-%P<7~8adil`{Ge-m4$Qq~gXW_jb7rb~` za(w7tFCD|Uo(I+S#ZRG%bXWnk^dOKj?r~8$%1(zm2K;01Djhw2tENi_ig~?Xd9_)CC4RIR`v+3#@~AYsH8*}#ZFGWFnU4rco00rgIEFegAecM zb)iB0Z&n=zW~e@)^K@kq{qhdzoUf~`^^i4ugr+cBCo@!Zmz})0rbgmh{oQt?jiVV8m!T0VKHVRgcyxlt@ zO;tq|+D0Mw)9Q@SZ-DrZc~k>Df)};TYnSh7oe9H305*`~L}gn6$B34`bs8oC0)PWr z0!)LB6~oX>Z~gf3V@L1V%ZWpFk3p9snd1U{Z^bZOfkkDA^AM2q>E>wB6DX^okqf*& z`Y6}CeGJldKzX5q_H4*6u?kt}suA9;E%hCs1L1jwb$NTwbGGCT#gLh`tQ1JsJ} z0wpgc0dTaw5AXmhlaSoILQ6Ecv!naXmWaZqjp}PyoM2AdE7R@34#??g~6`n~v%xU)nk9E?Jp1>KbO_y(v+ z#LW12s+ZivJ*6;sN>|*)Q*DBd8xrLdkTW=&&`Lz1+zKIt3}H1ic)15IY<*Y^;>+#N z|BXRO(jyEw5CAmqKnen8fkq!%e=rRndO-I9%4|@W7~@+_gLVf5824a_Wv zdVlh|<09*=ZAQN?K-UC25ckky(7R-=?)HW+v`B~RiGu?XECFO9Mba zOtB@|*oN=Cc;Dae`~HRRsN*>fJ=(*4U-xybb*^)*b*?}=>o-EjO-$2F@8p9`7ZK)M z33%HXEb^HR_k+M2Ehw+HG9jNg(GzJs0)Kjh=7K~GC=m8Pg~vK{cXi3+G8T2!F|0D0 zi|oePz|%*n8W$T?92L~Z4|xzkQq-z}egrz;F<0_Xy(I>M>U(y(@1awQN5VCj{R>sj zr<516Y@@|g@@F~D9qzF|nzsE5`Z@%HO?3t8m;?e_lQn>WP{kz-WW|Kc2<$o?au27e1b4h_J!V=EPtBm7_(|oQ zUpQO&m_S_vEuH>W2RXa_WyR4uG#-JhSPsept?DQ+bLQsukhPJg^a>Q~3k zkr7V6*-42N!Iwop-(A7={vt9+$fGagSVW`yfC5b0ttB?fBYDZ+mL4kMpGp7uOmKet zZ$w=rz1!|iU4YvX|4VO0E^XEjyI#^+gkJppC|z8-%WQ864yC&6p3<&g@x!LQk1Tez zI%og%qbC`h=+oSiTX766CCOBYrzn~P4uGq}j#lSY0^W$I=z0qZIPiEX=s29+pEtQi zc%CX!<8c8kwE0%QnagN`o3YbG1Hai1e}-5f}L4f7-lC^^?{G!lK;RP zzsv@Teft;7`Thl!b)tP{$%ejDg?NNu+R~SsV}lAW6E zhA5J&>M1CXK%L3yY3k&uL%j1QYTe+rtLJ-R%FXuQ@Y*SaQZ8jqPj3YRcaU`ND;DPp zH{QH?GrAR6^RJ>-z@zl$#LGJ8!r&Jzn@78MP_1Xk*Dp_Scp7k{^@;!09crSUu{*dV zoP&Z=&YV44s&!yB_Mo-zq-NvtJ`GXVu5NT|{9y@HM`HYdN?WU{MfJH5^w@)m9R|m%v|9%psK-B7t2h8@`S|A z;lf{HzBM%>9TVbQZ&o(Z-0T}WXFdweHGDDP*`vCoYgHcGhY7P z4>$*nIg>B9Q{$(CXZ9vt1id;&+qrWme#4t55T%uk27D~}+CTM(s6YkynHF`(K2nAS zRgULCDMFT$@rH=QyjOc#@AaZF_(_f7bD$bmN0pY%_0& zxKm{#ZR6u(S((`~G@f~47Bug5tzmX!f!sqP?ebD^r0H?u5wGQ{UZ770QYxP;MjCqD zNZH*4bd|s#Oo(uXe@7nt!5P-@x5qO>yEG48;MB9@yuCi5%$5R*o3tW1xGhmT9djAb zv1z!(F;jnA>?W?{FyLCBLpAOQAEFF&0?}vnic+ta=?_Y!_l#JtZQZFSp*?FNfofs1 zl_^KzSML0IlqGrTxE=a!i2)`wJ1Ry_F41^os`c<-U-rubl9hRHt=_lu9^_-qUTPM*c@a(8cAvs`vYmTSi5#0eHuu-rs})GjOn zp!kvV=c5D@Wsq4T4E~lYZ2AmH@}HKtf^61lnbUG0c0_MiRl4N^wty8OaS1(N*p5;ibXQ_PD`UqY6=`ai7LBS>RX;DT(f8lmVk-0VYxK*X^`ONj6H@ z=@oAehx??C*KFRDzjh+OjKKPXNKiK|D$3SV@7|e@HjAcT8e@zgoM5z(kA6Vg`gHK! zzjp`VtAN5nW@dbje5`3;SixLZrzCZIBa+`c`URFUUA|FmdiUtrTVgEd8+4=RBdLoe zo&MjPui`%hie4M8200jL92azw<7cCA{1RM|^wlvrZ%CtUPHalguH9rqgaAjy(}4R? z7yI_y??J=3HVz(otnCCPy;WFF$nmESiJaNK<~|cwZ78DW@V!ZwhaK*Uu|UzFv9ZzS z>eCuDpn}G*+qH|u+sr@r&70}RS5PBmd-dr}WH4abH)l4xGuWV#_Hg4?J~s3j{G8y5 zjf;C{{%}(2y4F7yQVm8f2p8T06ro%8(>@JZy)jc=r$3z)Yh{+AfWhdS2Z|HE&WNW| zFXRiM1MsAL%m%vEaITb(LY6&(?b@f8gwWIJB$%Va!Bdc$vA(y+gk_s@uy!t(U@&B; z!xI}*M!5~jQ50T0y$-K}M7(iVUaA8nG51Mzqr+q5Z2`^)cTU?g9xN;Q{X z7A!W!^E5j6&D`L-$e@YUo+!34T1`Rn5;21J`Jnq-(pjJeVQ3xC7o9b@iod6ImxpEa zZp3>4n4R3}6r>n`vaNKRZxu+l&bCVR`yG00nZuvx`1r^n^N77YSMz$^r24kb{O&`| zd*T2yGVRz?NDed2(F^CU0puNVb>%`=@LOxq&pX4ZYRvwA`rY?azd1k8y6cGcL62gK zCtu!cP)uJ+6b=4Sv|e)&OrhLA8bh37ry!M(9$Szj_3aFLEqa=T!uDjYHxANPMyv!P znA=wv&Hq`nfAs3XVxhC?XT!`?og`z?K##}kV;4{pJ#}R5o|Mq@Fn{RC7gVk(ZUt?Qj{<1!}?!&eyUG
MuGmE_uR4;XNs@v5}UvD?+GXC!kt+{&Q4N9h#`u+m;0N8P>@O zp;7}Nvne&Ex+i78b?f4P^Qz3!ztg!3-A?Ufxmv<)R`lhT`U8|YtQ!-8UBK2Rb#$mi zI>@zJHYn)MUOUdagCQkcJ9@dz_RypNbB8?Wx??^_h4yrA$$TOeznG2UbkqEc=BJ^1 z`{TRCkGqTwGF-B7|A9~YqQYG}a>+4Y2>8C3ie4x5lBDl8l;m7m8U4;vBw{Z^2a8SS z?K4N)8YFyMhO$nspXr_!JgzAf$?fHXIz4N@8KOz(UBSu4u4Wi*;m^=xPA?+tz9{9g!$paw|Bj?KvEXOk&9N(n&nqdQsYy3%RMZ_oNyyX1tq3{vpERxA zAonzUy#zE=^g+GHF}Pt=qNmHAvj+&|!3CebwNOe@q= zL{0#KDE3J0dB+V~jqc^;;)&stGrn+loiri`VL81a^VKVn3y!(KkIm0yZJeF;vz*vh z3HLmks~-EgX}PAJ91-D?A*qqjn({}IzMuw1q@T5th*+;w92V1Ji(#sv*qw}sIf zu)>kSbKYyE$r>V3>_o0U-2F)MmwuPsooq-3rk_$xHr#+d-}Ft!O0oLL$9jxFZZZ1i zU`k4g{@FD5&pA6nu$#}?ggJ?nfJsyzq@WP?>mwouq2BN!--=o~r`2uFm>+De|5axE zQG(~H7}Xd+n9E6*600D4!5h8zl6J}+?})vcS&C-}((ptHqTQ}w&z5sxN15DVpb)^_ z@zK>8kTBuzgDi$uzEt&zy^sBb!h$I1(!4vgdd*ca9pv%DMNy;ExsHw!l5@A0Z@PgV z(W)$Y4->G17k0))4P5Bhx9UKy%t&jG&s|;d$;1Ps`}SGO&?twi*3{N;?N@8HC37#X zL6^c^Lk~38O!(gjsyhvwer~U&&Ml$}^x;44)$oBU3`RMT7uQNH=tLBCUR1eeY;f*&Fxpe^xvOyN887y_2FP(5_waC zv<2a*MD<@nrtioJ)qZEvw=FN<$SJqtVXfr8B17QH&oy*!Whw12w9PI}c7HymcV}8? zMfeIV6XG?NLXNwK`QqlA`&TGdpglCi`5~~a;f8f%=d+ql%I>H~$|U4{Tb+Yk4vXcH zoZ^?RsMT&AN;L@9xHvhcv$cDfTV}t>^oUjNQ7- zvF&HP*9k2;u(oLy}wZqC+UXhKS2uJ=IRZ#(thhpZxi}@Q`)Jlsi`^| zWBCXMfr{Wj@QRo+B+bl zW}gtpvoGcBr7v|Bt(0g*1%R)1{*{;Ez?$|_l>H$ zpPK1u$f1Pq3aUcZ+Y@;p)GgRlTn9~8X}o+$96s7rLMp;#f4H{Yv#Ux7rjSUoEmuJ0 zS8)TshlfW_r^N%bXPm~p5e+ZYMZ(CmuHAS5!=VvVL%NHI5|NUZI;ORGIwG70G+tJ_ zHl55$9_ADi9SYG9g$*jELaX+Gsik(F0HVjxPd}Mh&B~d~CAemRj9PyPbiV z(pdeVpt+tUSbHnBT2@vTk5S?6_SRphds|wF!VTpFtJIB&yO0VWA0NNmesxX2q_$Y% zy`|Y2uEp@couIFdfjE6{Z91H#qr^WTFa=oD9*{~* zQ<2pA2Ur#@Y8$B5V;}THqb(P}OG)p?_`tS38BH3BvBq_F5m$O$wxicR==N=Hd1m%q zSzJD%HWsLEyMLiAAV@C?Dk$HaOQ1&ZZ1dDif$isiR)lAfx^zLo^=|JkN7YPC1tjYz?sjuY`KNuz=E}LF&EGu9Q3XRy2TRyC>m_x*=oVsD=@NTRJWN}BrWbNcY% zv!0&TP2=gG7ESkhczSkG{pK!UTUtyQ+`_X)K*|;GH=8?>9qxO0Jp+YzUHr$2q9WCj zK-OjzMw34-2@fAW9F`4_Q}*G9z-3fSQW?E}Xebm~8qlaTVe@V1=&&hy?>i&3n#!?!xh<5cQTZkC<4yS-H-*vV0smGCa%9X>2hkP;H71_#$D?Cn z9%g15txYkNioAv<64IAu)-Z#HWynDulGPC~#c?)CwJJjmr|n406rGMV{DaQ%D2~Fm z)*RP=a0{ttU!xKg5{VYH!Rp%DgWi9>ng|)Fe}`h`nPYGFLEFkPb87gEhX;`%C(?#! zbvx16v!l7C1p-Ao?kRt&tkj0?lf}gKY84cvY@9_+V(I%Bg)Ny6-$9_(dv1mnwPSo= zl7VI1ke;62F&T>Q1?#M|G%i$hfpAZJ@Mr8#?nDiahI2w`WuqJ4YIMb_qV#LIk)B<%s%BuAIe6uJ!a##A4ssH zEV3LYe*N^~a~!8mor=3^F*0MkD>c8cd&ui0D*I-ZSVn&Q_>pYR0{ZaliI(=+ySXE@ zp?&p#WOnT6=kTwQVdV2?H9_g_i4*I8(;=!bAg{!bQ=q7*DD&&?2m~CzgCbThWrp$( zk|0tA6W>{#EJkCdrlz0_0U4U==>o?&Z&yVnrNOKr+!YAPW32@~^u5EGWaDCw9kI`2 zw0jFR&Bzp~i8gOY(-zE54eAaxOioU|ZW@I!Hrem7v9Z)<%G~F?S2)ASIg(>V4maA^ zqMR8mu!MOt@H7yKhqUlYzom9JD183=awq$dJI>F~*QFkZ!klfILy`YRg<2lv{e{BA2ecy|@h)d;i5n?%aw&lyq z$HvBzHs!hzN&L875LL1|$Hm;ik>lER1^1u^wNAXq-ucSUkEt;##@M)~o3tJrAj;`T zWkHWc;PDR}_$bo~FGt4>NSO+F>~i^>v{;T3naE|%!p_FVCz&AC+}vz??p)3=9cfl4 z!31UnWo02e!eweD?Z4$duMiDQfF3OO;QCZZk^0T{`jw*>A@GHc1v)&5r0#o#_EPlD z5_3^(?XS{@En!8;7lZt3Ei}HQ>5yybf}A&wHGVvOP~4uqrNkaZ*g-K0r>7Vv!Vz*{ zua2kdU+^6$sHo0^(x zGGqQZ?B(T!yDo?Hvpp>>HT9#ax(oT$ zi5xcGf;O@8wl*s=Cjh{LKQVTPs&QyoSVR6kjdvkswykf@f61R*2a4Twu>Pg)%oX%) z-GCx7Dsltcp9ZS8=AMzj%kU}nK9kP}bt!B`Ne&3YkXfxsDcdG3%^9fvR+Yj13!E;h zKZzV}-f#sB8p?snDl3E3P8={ht*~dN=l3KZA72m<#&Nef7@iutLMBMf{(|tZ?$8&k z(|6b|>gH&Dx%Ul=Jeq1c7j)39o%Y~wN?on)# zhd#lRMBx%>T~T1LaMif$C62})X2{pK-8q()he(u!iQvaVsejK-*N3B5N zI#TA1xOu1`_YDjfEcN}(R~3zY!Let{K|jC3@n)6JO-=nfSuB)rk$DMU)nHE1n7rjS z_domxac|@@G#VaaC+D31{Uh@YwZ^UC!C+-mFXQ_U9~?88{l=N@EI8rgqAsrKjh{bq z2#Yv1lq4r7=TTw0p3}Wbz3)Lf2Kdp+?YqgbUu8Qvf~Dr(+zfQmHBQM!Es^|2{v2bd zj?1)2(G;}<7Zyk56zmeC{K${3D5l!-m0uT ztj0Rcfcsa5P%(6#N5w6^=Rp-9b6nHO>3P;3RdK&HZ8@$FVFI9O54g$vC?GRC`$A>K zX(3^=JdSt0ziV4soNT1buo~`5|FMNN;TBY&q`aG$$Waw%eAnaFn&-Z6t#XI&F9dXe z3r#L`WjVRCo6D6WtIMXRr!%eS+cW%B_KtMoyg*a_+{DNO;nWc&cx=*V;oAE_@MUF6 zZK(NOdk=V-^GB9cc5^&_QS-?Qy3PzIT%h(noH1c!Ht^wddwc4``jwVPoq+~kQUl<` z;bay0{GE>Csy0M~sN40;4l1Uy(vPoP7C3=6|A(-J4=QFM{jC!=^YUn5>|+ zqgX*Zox7^F_5M1`bZ@-_7q2z6vgxwF2umJ4x|?!aue zL2@bV5~F+xQ&FT*qqj2AxzG5h{CiJP=2!G^<5kxYmN-#v1`CV$GBJd@4Am5K6hj{x zB|mZ^EYl)1mtgB#fsu^NOP0L%&$XTORvvE6y&cReoBD2Ko;R< zB88JE{6DwZ`kj|Bj0vjBOT@x@VBfH*33R*?CkAz~@2JZrd)WW|)J)+DiXhIGFvt}x$p9w-VR=i;lxNgF-*(eZfWEG zs^tB3HrKZ|5T_Q-QhW9_yg31BYD;d_1MMLyCUa4?%x-JU`Gyaj8dH&lA15soUZRNC zt_24NPyEdI$nPfa0%HSGx=6O3c`cCy7ko)KSk^p2{^Wj4|^h{FAu9+2jo`xds%)NW} za^$O*WZ~IY2>Y#p{cyhb#=*``<4J%P{o4~JljoTK0`x#o8w#Uqo{6kxWMsqvxIMO; zNx2nq2Szn~J=H=9B}d(=z}t-GelGIvU0WNQqLUYyAszYsVVH7A;oJRk1EiKi+cWh*)0%~c%mczntY|nC5YucfJM=JunGb;gy7hCGd55%`O>HA_v zksS!L$qWrFsAm-uVzUbP=c5h|k|1|^U_Pt-df^LTiMcnFbDXcma)+&xjfu7 zdqi4ftWH~g0E_?NQ&i!?maRsdmGjUz$y6X0mrr3%v!7i56FHdVeS=TR z_!4kMcI#F$o5`KZ_ZyG`CwnwueLxo(8|~`ilE9}?%E1xM%*;&W-|;3KkUS~Y;{BQb z{#%M_O(3G&2i7YfC`E+y5qL7M)ZPnM3$qI-s_$iK==Z5a*j3`2lUKf_YB#Q&F(v_=MkJ{L%ZjYxhxM)dr!|Vk?vbr37 z9k(PrKBHFXD5fHnS5#!yec!)&&6+o(lt#UEpMuQI&27EC-wbnfqf?GN_C3Nrb_e*U zg;0PjRWLQMv8*E&8)hSi|KhVvzdZ!2fU>UKcdtWO1-DsSUvDoRAO^fpjtLcGG+ITd zfC5i9zg6TOEiGF-8jz?&qB!a#>-3)dSiR@oH<@N#5<2m*L^u@&qb=~G&oe<;ymb$C(Nm5derO5_=t$F|%n95q*H3kM)Ijj}+Jr}o4PLAi& zrAwF}u@Yf|Q{_4VM2%#X;pEu|;t4bapr;O>2Y`c!ng|(&gfNi=H$dj7umJem^z7@v z>)sUXY#E1# zJT?x>jf}75ZX)sB*|TzRAV*J~+6-NhySY-w5GKO~2;q$688EP)_iKmC0z(e?(C$-{ z)0Lg_9##hIg-Lc+k)I*i&C8c-!4D)|mg=|aqutHBpp^S5w_AJ|5I+r40I#v14R&V9}Y^1<)}z zhOQ4)X<@6oub)v&>AmH(crdtc%%~V0R{M>)8J4tUxNw*WWb$H?7ulh;5qkqo-<=Y3 zEqF0*sTOeo;t^_bboLyZUwv51BH=Y_N`L&Y2g_ZCIWm^7gON!iCbF%q4FewX_Skn% zpN&1QGh$uw*2LN;>7z?|EFu|<*@0}YeZgYxehU$@p>1u8YpOVq!NTFpa8?LJOxsW( zW#*#_{dlVeDGn2}NNl2wr#4s7V}%i6OSuccY`Gua{R)?yD|{b8LzF44KQU!9meQSl z1zwWk?vxLR4?ka1dP^2pZ8Bv*w1y)#@BVcZryg;b%mrO2zl`p^aTP}sy}?m0)bekH zzG&#FfFo$%<$$i;+*@9tBQy~5;wZB!ofcjQz;4mRp;G0ZOT}3)xOL+Q)ICg;Jx>3j z(db+ZcE%ulkawT9i*2l~;llO=?M5)wSq~2_V57*4iLdk&G8f3C02Z#JEt@K`h=xnK7ZsJ(wiFX}3ZG)+7nYceB$}P|MRB6<$hE!L{RkE@o-H4( zCQhf9Ql`@}H3O$|DtmvX`Ak(W&I!~dUpEcnF@Af3xEOoMZ}h~86I+i2(V#8EJ2z3G zlH&au(VHuMe8|b#&o7z1yJF}Z(nHVlx5Q8=Yoc{D)C`YaxN!Jjjx-Ph*q_AgY>XhH zP$rB3mvChLWCo$^TMqW-^+%wf*G+p=;sVFAG#%4Sy=f;-omx4*sAw_4mZ(kusk!xO z3<*n4ny~iy#V)y%@C%r+g1CL46L^%H!5;o{DPU(09Xf>FnP{{GU*$KIeeCez7-s_t zMMGm|kxfS3oY9hX4i*H3^*@O(9~f|iJ|C0JM>)Yn&;zyeTB&OQyf{KRMhIl?j%W|V3Ei)DVo{$isLRuVtjH`AJe zK${=MIhAS|9v=3Pp1DSu;P3zW^Ezst$p9^4Wx?*QT(=gx18x&`y;ZSr{=;q_#N`mH zgv%gv>2PdRCTTcH#1r6AveUl7GtUjtEmks&fUmWdMHv9*2qjubRlIw*7tM(OO8GSA zn!<>kk9@&NZ1JHo-baw?{1p?!DvD zY&BvKfOCQGuhAFPaEcz z&5lgxqWCMKzlT~sI3t_k-43e#8loi4{rmUZ4MPFGBH+@ScJRDz?nhh0j2ZD9=uGp~F9foS~gN4ex9; zl;j~;4$&RB(~ZO)QYdu?5fFS^)Bz)p)ckd<|3u%zutYQh~7Nw`Kr#U>sxcCw z;==GPWE}n&WP`Kd1ujm@%31~Pmb+aAsSoS9DU;j%z5$ebD#rluP_hG@XR=jF6)(i- zMkHkXZRhV_RSVLw6VtyxVyLOuAv#F;(538yiEbMe;Tsb1hd9kC2%e@skh5mz~tpl^Yp^2Zn6B;(f-W zR+Uo`l$J@vd#}I_D7Kzo69;K{M6CYGM16uN&z&<}>wC%JKI<18QoIU-`S>9!la`(R zL{jB5`dV<+TeolLLl~hp|NaCuJYG#}Yiq!#y<1Zbzy*1a)$UpLOX&qeO%h3`;NKyx z=n7A5MO8_+kXk@5>PidlLz@im^8QuEi-~`Ud?wvMZJ_8=ISY4FYLdY8gMp8L%HN6m z@z#aAUm~W(iO9;5U$U_K#Bk*j!~_K9EF#cMR`ww$RgF>;fgw*pI7E+l*UY6M_=i>s zp|?inS54#yVcLnled)RZ#G!cd`%V@}Fi4v?;?qcghfJ4_Z{dF&s-oh6*n(wmC@n5l zM}LCFS}8R+LBbD0CfN23d20&J33e^`dO2fcTu5Mb9ui(}ID47?Azhc@iVsIiuv+_G5vErZ{ zvOfCVqTH+bU)TF#%y$)v8j-t5OWTUhY4WF|oiN#$(2!{Se0{mF9Bzql(7za%itP3R zwUNng=H?-Az_~L5|Fb=rS?Aw8VFb)MpFSW34{xr*s~|NQ28N_;LUoaiO#j5hcKps3 zQ2;mn3yvd6Db&^;ahZR&Kg|sr8a++HsPr$JV8>Y{@hte05Lfc`%{Vs1jN*r7R{Q_G zH$V8#e*nd=5&uR~fe_wFYLQKbByjb4;Ae7gSy)VvL>aSA;WI`S*g5@o%fE z!AAYg7Sv&-oV@D-sES{B#sf0rb77a+^vm&ap8;YZTGIC|u^*O>3`DiF6T`7xG`~g^ zcIrab<2$w@37YW(F0kE)#L)|(01USZQ{HJ6yc;1uc7i5&RirTHx`~a1Ew2A{igS_8 zEhEHYc;19BadVRiDN9UT*n~Ib^m25J=YjOrz=dCRG=E~{UHGdA*CLyRzxit{^zCmA^;f?@{=B6#gCstiWHV`4=ku1xWLz;Q!~N zFq^td=_UckeBGyg!cyfHxC0w?$^s2Q(OqgYzXlY47KZ->anfu5i>Aeyq!<3N=<_n?Lv3@$5`Wh+f HHW&X7bdzs2 literal 0 HcmV?d00001 diff --git a/sanity/env.js b/sanity/env.js index 3fc02d5..cc00360 100644 --- a/sanity/env.js +++ b/sanity/env.js @@ -3,3 +3,4 @@ export const apiVersion = export const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET; export const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID; +export const token = process.env.SANITY_AUTH_TOKEN; diff --git a/sanity/lib/client.js b/sanity/lib/client.js index 05f5aa0..2dca996 100644 --- a/sanity/lib/client.js +++ b/sanity/lib/client.js @@ -1,10 +1,18 @@ import { createClient } from "next-sanity"; +import imageUrlBuilder from "@sanity/image-url"; -import { apiVersion, dataset, projectId } from "../env"; +import { apiVersion, dataset, projectId, token } from "../env"; export const client = createClient({ projectId, dataset, apiVersion, - useCdn: true // Set to false if statically generating pages, using ISR or tag-based revalidation + token, + useCdn: false // Set to false if statically generating pages, using ISR or tag-based revalidation }); + +const builder = imageUrlBuilder(client); + +export function urlFor(source) { + return builder.image(source); +}