components:map

This commit is contained in:
lovebird 2025-03-07 19:27:03 +01:00
parent 7de1df59fd
commit 5555c04387
17 changed files with 326 additions and 534 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,12 @@
export default {
"environment": "dev",
"environment": "build",
"isSsrBuild": false,
"projectBase": "",
"publicDir": "C:\\Users\\zx\\Desktop\\polymech\\polymech-site\\public\\",
"rootDir": "C:\\Users\\zx\\Desktop\\polymech\\polymech-site\\",
"mode": "dev",
"outDir": "dist",
"assetsDir": "/_astro",
"mode": "production",
"outDir": "C:\\Users\\zx\\Desktop\\polymech\\polymech-site\\dist\\",
"assetsDir": "_astro",
"sourcemap": false,
"assetFileNames": "/_astro/[name]@[width].[hash][extname]"
}

View File

@ -0,0 +1,70 @@
# map.astro Component Documentation
## Overview
`map.astro` is an Astro component that renders a Google Maps static map displaying multiple locations with markers. The component centers the map based on the average coordinates of all locations.
## Component Structure
- **File Location**: `C:\Users\zx\Desktop\polymech\polymech-site\src\components\polymech\map.astro`
- **Dependencies**: Imports types from `map-types.ts`
## Props
The component accepts the following props:
### `locations` (optional)
An array of location objects with the following structure:
```typescript
interface Location {
geo: {
lat: number; // Latitude
lon: number; // Longitude
};
title: string; // Location title/name
}
```
If not provided, it defaults to sample locations in Lamu, Kenya.
### `options` (optional)
Configuration options for the map:
```typescript
interface Options {
zoom?: number; // Map zoom level (default: 9)
api_key?: string; // Google Maps API key (required for map to display)
}
```
## Features
1. **Automatic Map Centering**: Calculates the average of all location coordinates to center the map
2. **Labeled Markers**: Adds alphabetical labels (A, B, C, etc.) to each marker
3. **Location Legend**: Displays a list of locations with corresponding marker labels
4. **Error Handling**: Shows an error message when no API key is provided
## Example Usage
```astro
---
import Map from '../components/polymech/map.astro';
---
<Map
locations={[
{
geo: { lat: 37.7749, lon: -122.4194 },
title: 'San Francisco'
},
{
geo: { lat: 34.0522, lon: -118.2437 },
title: 'Los Angeles'
}
]}
options={{
zoom: 7,
api_key: 'YOUR_GOOGLE_MAPS_API_KEY'
}}
/>
```
## Notes
- **API Key Required**: A Google Maps API key is necessary for the map to function
- **Responsive Design**: The map container uses responsive classes for proper display on various screen sizes
- **Style Customization**: The component includes a style tag for additional CSS customization

View File

@ -6,9 +6,13 @@ export interface GeoPos {
export interface Location {
geo: GeoPos;
title: string;
group?: string;
website?: string;
}
export interface Options {
zoom?: number;
api_key?: string;
width?: number;
height?: number;
}

View File

@ -1,36 +1,200 @@
---
import { Location, Options } from './map-types'
import { Img } from "imagetools/components"
interface Props {
locations?: Location[];
options?: Options;
}
const { locations = [
// Historic Sites
{
geo: { lat: -2.269456, lon: 40.902096 },
title: 'Lamu Old Town'
title: 'Lamu Old Town',
group: 'historic_sites',
website: 'https://whc.unesco.org/en/list/1055/'
},
{
geo: { lat: -2.267922, lon: 40.901592 },
title: 'Lamu Fort',
group: 'historic_sites'
},
{
geo: { lat: -2.268811, lon: 40.902344 },
title: 'Lamu Museum',
group: 'historic_sites',
website: 'https://museums.or.ke/lamu-museum/'
},
{
geo: { lat: -2.267500, lon: 40.901850 },
title: 'Riyadha Mosque',
group: 'religious_sites',
website: 'https://www.visitkenya.com/lamu/riyadha-mosque'
},
{
geo: { lat: -2.268200, lon: 40.901100 },
title: 'German Post Office Museum',
group: 'historic_sites'
},
// Beaches
{
geo: { lat: -2.272012, lon: 40.890586 },
title: 'Shela Beach'
title: 'Shela Beach',
group: 'beaches',
website: 'https://www.visitlamu.org/shela-beach'
},
{
geo: { lat: -1.075012, lon: 40.890586 },
title: 'Shela Beach2'
geo: { lat: -2.336103, lon: 40.902733 },
title: 'Manda Beach',
group: 'beaches'
},
{
geo: { lat: -2.408333, lon: 40.866667 },
title: 'Kipungani Beach',
group: 'beaches'
},
{
geo: { lat: -2.243333, lon: 40.913333 },
title: 'Matondoni Beach',
group: 'beaches'
},
// Hotels & Accommodations
{
geo: { lat: -2.274578, lon: 40.889988 },
title: 'The Majlis Resort',
group: 'hotels',
website: 'https://themajlisresorts.com'
},
{
geo: { lat: -2.267825, lon: 40.908644 },
title: 'Lamu House Hotel',
group: 'hotels'
},
{
geo: { lat: -2.273205, lon: 40.889925 },
title: 'Peponi Hotel',
group: 'hotels',
website: 'https://www.peponihotel.com'
},
{
geo: { lat: -2.269872, lon: 40.904167 },
title: 'Jannat House',
group: 'hotels'
},
// Restaurants
{
geo: { lat: -2.269925, lon: 40.901678 },
title: 'Whispers Café',
group: 'restaurants',
website: 'https://www.tripadvisor.com/Restaurant_Review-Whispers-Cafe-Lamu'
},
{
geo: { lat: -2.271564, lon: 40.902122 },
title: 'Petley\'s Inn Restaurant',
group: 'restaurants'
},
{
geo: { lat: -2.273275, lon: 40.889950 },
title: 'Peponi Restaurant',
group: 'restaurants'
},
{
geo: { lat: -2.270115, lon: 40.901988 },
title: 'Seafront Café',
group: 'restaurants'
},
// Local Attractions
{
geo: { lat: -2.228056, lon: 40.839722 },
title: 'Takwa Ruins',
group: 'attractions',
website: 'https://www.nationalmuseums.or.ke/takwa-ruins'
},
{
geo: { lat: -2.303333, lon: 40.810000 },
title: 'Dodori National Reserve',
group: 'attractions'
},
{
geo: { lat: -2.267778, lon: 40.902222 },
title: 'Lamu Donkey Sanctuary',
group: 'attractions',
website: 'https://www.lamuconservationtrust.org/donkey-sanctuary'
},
// Markets
{
geo: { lat: -2.269205, lon: 40.902015 },
title: 'Lamu Market',
group: 'markets',
website: 'https://www.visitlamu.org/market'
},
{
geo: { lat: -2.270250, lon: 40.901750 },
title: 'Lamu Fish Market',
group: 'markets'
}
], options = {} } = Astro.props;
const { zoom = 9, api_key = '' } = options;
// Generate markers query param
const markersParam = locations.map((loc, index) => `markers=color:red%7Clabel:${String.fromCharCode(65 + index)}%7C${loc.geo.lat},${loc.geo.lon}`).join('&');
const { zoom = 12, api_key = '', width = 1200, height = 600 } = options;
// Group locations
const groupedLocations = locations.reduce((acc, loc) => {
const group = loc.group || 'default';
if (!acc[group]) {
acc[group] = [];
}
acc[group].push(loc);
return acc;
}, {} as Record<string, Location[]>);
// Define colors for different groups
const groupColors: Record<string, string> = {
default: 'red',
// Add more predefined colors for groups
};
// Get a color for a group
const getColorForGroup = (group: string): string => {
if (groupColors[group]) {
return groupColors[group];
}
// Generate colors for groups that don't have predefined colors
const colors = ['blue', 'green', 'purple', 'orange', 'yellow', 'pink', 'brown'];
const index = Object.keys(groupColors).length % colors.length;
groupColors[group] = colors[index];
return groupColors[group];
};
// Flatten grouped locations with indices for marker labels
let allLocationsWithIndices: Array<{ location: Location, index: number }> = [];
let currentIndex = 0;
Object.entries(groupedLocations).forEach(([group, locs]) => {
locs.forEach(loc => {
allLocationsWithIndices.push({
location: loc,
index: currentIndex++
});
});
});
// Generate markers query param with different colors for each group
const markersParam = Object.entries(groupedLocations).map(([group, locs]) => {
const color = getColorForGroup(group);
return locs.map((loc, groupIndex) => {
const index = allLocationsWithIndices.findIndex(item => item.location === loc);
return `markers=color:${color}%7Clabel:${String.fromCharCode(65 + index)}%7C${loc.geo.lat},${loc.geo.lon}`;
}).join('&');
}).join('&');
const centerLat = locations.reduce((sum, loc) => sum + loc.geo.lat, 0) / locations.length;
const centerLon = locations.reduce((sum, loc) => sum + loc.geo.lon, 0) / locations.length;
const googleMapsUrl = api_key ?
`https://maps.googleapis.com/maps/api/staticmap?center=${centerLat},${centerLon}&size=1200x600&${markersParam}&key=${api_key}` : null;
`https://maps.googleapis.com/maps/api/staticmap?center=${centerLat},${centerLon}&zoom=${zoom}&size=${width}x${height}&${markersParam}&key=${api_key}` : null;
const hasError = !api_key;
---
@ -41,20 +205,55 @@ const hasError = !api_key;
<img
src={googleMapsUrl}
alt="Map showing locations"
class="w-full h-auto" width="600"
height="400"
class="w-full h-auto" width={width}
height={height}
/>
<div class="bottom-0 w-full bg-white/[70%] p-4 backdrop-blur-sm">
<ul class="space-y-1">
{locations.map((loc, index) => (
<li key={index} class="flex items-center gap-2">
<span class="flex w-6 h-6 rounded-full bg-red-500 text-white items-center justify-center text-sm font-bold">
{String.fromCharCode(65 + index)}
</span>
<span class="font-medium">{loc.title}</span>
</li>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
{Object.entries(groupedLocations).map(([group, locs]) => (
<div key={group} class="space-y-1">
{group !== 'default' && (
<h3 class="font-bold text-gray-700 mb-2">{group.toUpperCase()}</h3>
)}
<ul class="space-y-1">
{locs.map((loc) => {
const index = allLocationsWithIndices.findIndex(item => item.location === loc);
const color = getColorForGroup(group);
return (
<li key={index} class="flex items-center gap-2">
<span
class={`flex w-6 h-6 rounded-full bg-${color}-500 text-white items-center justify-center text-sm font-bold`}
style={`background-color: ${color}`}
>
{String.fromCharCode(65 + index)}
</span>
<span class="font-medium">
<a
href={`https://www.google.com/maps?q=${loc.geo.lat},${loc.geo.lon}&z=12`}
target="_blank"
rel="noopener noreferrer"
class="text-blue-600 hover:underline"
>
{loc.title}
</a>
{loc.website && (
<a
href={loc.website}
target="_blank"
rel="noopener noreferrer"
class="ml-2 text-sm text-gray-600 hover:underline"
>
🌐
</a>
)}
</span>
</li>
);
})}
</ul>
</div>
))}
</ul>
</div>
</div>
</div>
) : (

View File

@ -47,11 +47,7 @@ const pages = [
{
name: "Blog",
links: [
{ href: "/blog/home", text: "Blog home" },
{ href: "/blog/posts/1", text: "Blog post" },
{ href: "/blog/tags", text: "Tag index" },
{ href: "/blog/tags/astro", text: "Tag category" },
{ href: "/rss.xml", text: "RSS" },
],
},
{

View File

@ -2,15 +2,3 @@
page: About
pubDate: 2024-01-01
---
Welcome to Williamsburg, your one-stop destination for all your shopping needs. Discover a wide range of high-quality products, from fashion and electronics to home decor and more. With competitive prices and a user-friendly shopping experience, we make it easy for you to find exactly what youre looking for.
At Williamsburg, we're committed to providing you with exceptional service and an unparalleled shopping experience. Our team is dedicated to curating the latest trends and top brands, ensuring that you have access to the best products on the market.
Whether you're shopping for yourself or searching for the perfect gift, Williamsburg has everything you need to elevate your shopping experience. Browse our extensive selection, enjoy hassle-free checkout, and experience the convenience of online shopping with Williamsburg.
Join us on our journey to redefine retail and discover the difference with Williamsburg. Start exploring now and elevate your shopping experience with us.
Welcome to Williamsburg where shopping meets satisfaction.

View File

@ -17,12 +17,6 @@ tags: ["community", "blogging", "c++"]
- [Article:Every Type of Plastic Used By LEGO](https://bricknerd.com/home/every-type-of-plastic-used-by-lego-5-20-22)
#### Products
- [Product Catalog - TheFlipFlopi @ Lamu - Africa](https://drive.google.com/file/d/1ulOZ1px8AxCHh-ksepDZE2mXQS0UoVU2/view)
- [Product Catalog - Ecollabo8](https://drive.google.com/drive/folders/1M-tvdT6jNTQHsh6UexctHvidKCav-1oF)
#### Finance & Economy
- [Panic At The Job Market](https://matt.sh/panic-at-the-job-market)
@ -127,10 +121,6 @@ tags: ["community", "blogging", "c++"]
- https://283bc.com/product.html
- https://www.instagram.com/wedoo_workshop_bali
- https://bazar.preciousplastic.com/mecanizados-ingenieria-y-diseno/
- https://www.instagram.com/insta_ventu/
- https://community.preciousplastic.com/research/hfg-jaipur-knee-prosthesis-10#update_Rzirih7uOfaK402429VD
- https://ko-fi.com/sotop_recycling (https://www.instagram.com/sotop_recycling/)
- https://www.youtube.com/c/AndysMachines

View File

@ -1,23 +0,0 @@
---
import BaseLayout from "./BaseLayout.astro";
const { frontmatter } = Astro.props;
---
<BaseLayout>
<section>
<div
class="grid grid-cols-1 md:grid-cols-1">
<div class="flex flex-col ">
<div class="prose-styles lg:col-span-2">
<img
class="w-full aspect-[4/2] rounded-2xl relative object-cover lg:-mt-2"
src={frontmatter.image.url}
alt={frontmatter.image.alt}
/>
<slot />
</div>
</div>
</section>
</BaseLayout>

View File

@ -11,7 +11,6 @@ import Resources from "@/components/polymech/resources.astro"
import Specs from "@/components/polymech/specs.astro"
import TabButton from "@/components/polymech/tab-button.astro"
import TabContent from "@/components/polymech/tab-content.astro"
import Map from "@/components/polymech/map.astro"
import "flowbite"

View File

@ -1,21 +1,19 @@
---
import BaseLayout from "@/layouts/BaseLayout.astro";
import config from "config/config.json";
import Wrapper from "@/components/containers/Wrapper.astro";
import { LANGUAGES_PROD } from "config/config.js";
import { getCollection } from "astro:content";
import LatestEntries from "@/components/blog/LatestEntries.astro";
import StoreEntries from "@/components/store/StoreEntries.astro";
import CtaOne from "@/components/cta/CtaOne.astro";
const allPosts = await getCollection("posts");
const allProducts = await getCollection("store");
const locale = Astro.currentLocale
import { translate } from "@/base/i18n.js"
import { I18N_SOURCE_LANGUAGE } from "config/config.js"
const store = `/${locale}/store/`;
const blog = `/blog/posts/`;
import { slugify } from "@/base/strings.js"
const allProducts = await getCollection("store")
const locale = Astro.currentLocale
const store = `/${locale}/store/`;
export function getStaticPaths() {
const all: unknown[] = [];
LANGUAGES_PROD.forEach((lang) => {
@ -45,79 +43,13 @@ const group = async (items) => {
const items = await group(allProducts)
---
<BaseLayout>
<Wrapper variant="standard" class="py-4">
<CtaOne />
<section>
<div class="py-2 space-y-2">
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
{
config.pages.home.blog &&
allPosts
.slice(0, 3)
.map((post) => (
<LatestEntries
url={blog + post.slug}
title={post.data.title}
description={post.data.description}
alt={post.data.title}
pubDate={post.data.pubDate.toString().slice(0, 10)}
author={post.data.author}
image={post.data.image.url}
/>
))
}
</div>
{
config.pages.home.blog && (
<a
href="/blog/home"
title="link to your page"
aria-label="your label"
class="relative group overflow-hidden pl-4 justify-between text-xs text-orange-600 font-mono h-14 flex space-x-6 items-center bg-white hover:bg-neutral-200 hover:text-orange-600 duration-300 rounded-xl"
>
<span class="relative uppercase text-xs">Read all articles</span>
<div
aria-hidden="true"
class="w-12 transition duration-300 -translate-y-7 group-hover:translate-y-7"
>
<div class="h-14 flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 m-auto fill-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"
/>
</svg>
</div>
<div class="h-14 flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 m-auto fill-white"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"
/>
</svg>
</div>
</div>
</a>
)
}
</div>
</section>
{

View File

@ -1,109 +0,0 @@
---
import { getCollection } from "astro:content";
import BaseLayout from "@/layouts/BaseLayout.astro";
import EntriesOne from "@/components/blog/EntriesOne.astro";
const allPosts = await getCollection("resources");
const tags = [...new Set(allPosts.map((post) => post.data.tags).flat())];
---
<BaseLayout>
<section>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 gap-2 py-2">
<div class="flex flex-col gap-12 h-full justify-between ">
<div>
<h1
class="text-lg text-neutral-600 font-mono tracking-tight text-balance">
Read articles about our work, projects, and more.
</h1>
<div
class="relative flex flex-wrap mt-4 justify-start gap-2 max-w-sm">
{
tags.map((tag) => (
<a
class="text-xs text-neutral-500 font-medium hover:text-orange-600"
href={`/blog/tags/${tag}`}>
{tag}
</a>
))
}
</div>
</div>
<form class="mt-12">
<div class="flex flex-col gap-2">
<label
for="email-address"
class="sr-only"
>Email address</label
>
<input
id="email-address"
name="email"
type="email"
autocomplete="email"
required
class="min-w-0 flex-auto rounded-xl font-mono border-0 h-14 text-sm uppercase duration-300 px-3.5 py-2 text-neutral-500 ring-1 ring-inset ring-white placeholder:text-neutral-400 focus:ring-2 focus:ring-inset focus:ring-orange-600"
placeholder="Enter your email"
/>
<button
type="submit"
class="relative group overflow-hidden pl-4 font-mono h-14 flex space-x-6 items-center bg-orange-500 hover:bg-black duration-300 rounded-xl w-full justify-between">
<span class="relative uppercase text-xs text-white"
>Subscribe</span
>
<div
aria-hidden="true"
class="w-12 text-white transition duration-300 -translate-y-7 group-hover:translate-y-7">
<div class="h-14 flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 m-auto fill-white">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"
></path>
</svg>
</div>
<div class="h-14 flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 m-auto fill-white">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"
></path>
</svg>
</div>
</div>
</button>
</div>
</form>
</div>
{
allPosts.slice().map((post) => (
<EntriesOne
url={"/blog/posts/" + post.slug}
title={post.data.title}
description={post.data.description}
alt={post.data.title}
pubDate={post.data.pubDate.toString().slice(0, 10)}
author={post.data.author}
image={post.data.image.url}
/>
))
}
</div>
</section>
</BaseLayout>

View File

@ -1,92 +1,30 @@
---
import BaseLayout from "@/layouts/BaseLayout.astro"
import { getCollection } from "astro:content"
import LatestEntries from "@/components/blog/LatestEntries.astro"
import StoreEntries from "@/components/store/StoreEntries.astro"
import Hero from "@/components/landing/Hero.astro"
import CtaOne from "@/components/cta/CtaOne.astro"
import CtaThree from "@/components/cta/CtaThree.astro"
import CtaTwo from "@/components/cta/CtaTwo.astro"
import Locale from "./[locale].astro"
const allPosts = await getCollection("posts")
const allProducts = await getCollection("store")
const locale = Astro.currentLocale || "en"
---
<BaseLayout>
<CtaTwo />
<section>
<div class="py-2 space-y-2">
<div class="grid md:grid-cols-2 lg:grid-cols-2 gap-2 ">
{
allPosts.slice(0, 4).map((post) => (
<LatestEntries
url={locale + "/blog/posts/" + post.slug}
title={post.data.title}
description={post.data.description}
alt={post.data.title}
pubDate={post.data.pubDate.toString().slice(0, 10)}
author={post.data.author}
image={post.data.image.url}
/>
))
}
</div>
<a
href="/blog/home"
title="link to your page"
aria-label="your label"
class="relative group overflow-hidden pl-4 justify-between text-xs text-orange-600 font-mono h-14 flex space-x-6 items-center bg-white hover:bg-neutral-200 hover:text-orange-600 duration-300 rounded-xl">
<span class="relative uppercase text-xs">Read all articles</span>
<div
aria-hidden="true"
class="w-12 transition duration-300 -translate-y-7 group-hover:translate-y-7">
<div class="h-14 flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 m-auto fill-white">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"
></path>
</svg>
</div>
<div class="h-14 flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 m-auto fill-white">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"
></path>
</svg>
</div>
</div>
</a>
</div>
</section>
<section>
<div class="grid sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-2 gap-2">
{
allProducts.map((post) => (
allProducts.map((item) => (
<StoreEntries
url={ locale + "/store/" + post.id}
title={post.data.title}
price={post.data.price}
type={post.data.type}
alt={post.data.title}
model={post.data}
url={ locale + "/store/" + item.id}
title={item.data.title}
price={item.data.price}
type={item.data.type}
alt={item.data.title}
model={item.data}
/>
))
}

View File

@ -3,14 +3,16 @@ import Wrapper from "@/components/containers/Wrapper.astro";
import { getCollection } from "astro:content";
import BaseLayout from "@/layouts/BaseLayout.astro";
import EntriesOne from "@/components/blog/EntriesOne.astro";
const allPosts = await getCollection("resources")
const all = await getCollection("resources")
---
<BaseLayout>
<Wrapper>
<section>
<div class="grid sm:grid-cols-1 lg:grid-cols-3 xl:grid-cols-3 gap-2 py-2">
{
allPosts
all
.slice()
.map((post) => (
<EntriesOne

View File

@ -1,111 +0,0 @@
---
import BaseLayout from "@/layouts/BaseLayout.astro";
import EntriesOne from "@/components/blog/EntriesOne.astro";
import { getCollection } from "astro:content";
export async function getStaticPaths() {
const allPosts = await getCollection("posts");
const uniqueTags = [
...new Set(allPosts.map((post) => post.data.tags).flat()),
];
return uniqueTags.map((tag) => {
const filteredPosts = allPosts.filter((post) =>
post.data.tags.includes(tag)
);
return {
params: { tag },
props: { posts: filteredPosts },
};
});
}
const { tag } = Astro.params;
const { posts } = Astro.props;
---
<BaseLayout pageTitle={tag}>
<section>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 gap-2 py-2">
<div class="flex flex-col gap-12 h-full justify-between ">
<div>
<h1
class="text-lg text-neutral-600 font-mono tracking-tight text-balance uppercase">
Blog posts tagged with {tag}
</h1>
</div>
<form class="mt-12">
<div class="flex flex-col gap-2">
<label
for="email-address"
class="sr-only"
>Email address</label
>
<input
id="email-address"
name="email"
type="email"
autocomplete="email"
required
class="min-w-0 flex-auto rounded-xl font-mono border-0 h-14 text-sm uppercase duration-300 px-3.5 py-2 text-neutral-500 ring-1 ring-inset ring-white placeholder:text-neutral-400 focus:ring-2 focus:ring-inset focus:ring-orange-600"
placeholder="Enter your email"
/>
<button
type="submit"
class="relative group overflow-hidden pl-4 font-mono h-14 flex space-x-6 items-center bg-orange-500 hover:bg-black duration-300 rounded-xl w-full justify-between">
<span class="relative uppercase text-xs text-white"
>Subscribe</span
>
<div
aria-hidden="true"
class="w-12 text-white transition duration-300 -translate-y-7 group-hover:translate-y-7">
<div class="h-14 flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 m-auto fill-white">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"
></path>
</svg>
</div>
<div class="h-14 flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 m-auto fill-white">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"
></path>
</svg>
</div>
</div>
</button>
</div>
</form>
</div>
{
posts.map((post) => (
<EntriesOne
url={"/blog/posts/" + post.slug}
title={post.data.title}
description={post.data.description}
alt={post.data.title}
pubDate={post.data.pubDate.toString().slice(0, 10)}
author={post.data.author}
image={post.data.image.url}
/>
))
}
</div>
</section>
</BaseLayout>

View File

@ -1,82 +0,0 @@
---
import { getCollection } from "astro:content";
import BaseLayout from "@/layouts/BaseLayout.astro";
const allPosts = await getCollection("posts");
const tags = [...new Set(allPosts.map((post) => post.data.tags).flat())];
const pageTitle = "Tag Index";
---
<BaseLayout pageTitle={pageTitle}>
<section>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 gap-2">
<div>
<h1
class="text-lg text-neutral-600 font-mono tracking-tight text-balance uppercase">
All tags
</h1>
<p class="text-sm text-balance text-neutral-500">
All tags from our blog.
</p>
</div>
<div class="items-center col-span-2">
<div class="grid items-end gap-4 lg:grid-cols-3">
<div class="col-span-full flex flex-col gap-2">
{
tags.sort().map((tag) => (
<>
<a
href="/blog/home"
class="relative group overflow-hidden pl-4 font-mono h-14 flex space-x-6 items-center bg-orange-500 hover:bg-black duration-300 rounded-xl w-full justify-between">
<span class="relative uppercase text-xs text-white">
{tag}
</span>
<div
aria-hidden="true"
class="w-12 text-white transition duration-300 -translate-y-7 group-hover:translate-y-7">
<div class="h-14 flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 m-auto fill-white">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"
/>
</svg>
</div>
<div class="h-14 flex">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-6 m-auto fill-white">
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M17.25 8.25 21 12m0 0-3.75 3.75M21 12H3"
/>
</svg>
</div>
</div>
</a>
<a
class="font-mono uppercase text-neutral-500 hover:text-orange-500 sr-only"
href={`/blog/tags/${tag}`}>
{tag}
</a>
</>
))
}
</div>
</div>
</div>
</div>
</section>
</BaseLayout>

View File

@ -5,7 +5,6 @@ import { getCollection } from "astro:content"
import KBot from "@/components/polymech/kbot.astro"
const locale = Astro.currentLocale
const store = `${locale}/store/`
const blog = `/blog/posts/`
import Map from "@/components/polymech/map.astro"
@ -19,8 +18,8 @@ const mapOptions = {
<BaseLayout>
<Wrapper variant="standard" class="py-4">
<section>
<Map options={mapOptions} />
<div class="py-2 space-y-2">
<Map options={mapOptions} />
<div class="grid md:grid-cols-1 lg:grid-cols-1 gap-4" >
<KBot router="openai" model="gpt-4.5-preview" mode="completion" template="code_simple" disabled={true}>
</KBot>