generated from polymech/site-template
latest
This commit is contained in:
parent
f82172797f
commit
48127f4534
@ -23,7 +23,7 @@
|
||||
"format": "unix-time"
|
||||
}
|
||||
],
|
||||
"default": "2025-03-17T18:23:55.481Z"
|
||||
"default": "2025-03-19T21:59:23.180Z"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -43,8 +43,7 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@components': '/src/components',
|
||||
'@layouts': '/src/layouts',
|
||||
"base": "../polymech-site/src"
|
||||
'@layouts': '/src/layouts'
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
|
||||
33
package-lock.json
generated
33
package-lock.json
generated
@ -15,8 +15,8 @@
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@jsdevtools/rehype-toc": "^3.0.2",
|
||||
"@plastichub/astro-site-template": "file:../polymech-site",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@polymech/astro-base": "file:../astro-components/packages/polymech",
|
||||
"@polymech/cache": "file:../polymech-mono/packages/cache",
|
||||
"@polymech/cad": "file:../polymech-mono/packages/cad",
|
||||
"@polymech/commons": "file:../polymech-mono/packages/commons",
|
||||
@ -83,6 +83,18 @@
|
||||
"sass-embedded": "^1.83.4"
|
||||
}
|
||||
},
|
||||
"../astro-components/packages/polymech": {
|
||||
"name": "@polymech/astro-base",
|
||||
"version": "0.5.6",
|
||||
"dependencies": {
|
||||
"@polymech/cad": "file:../../../polymech-mono/packages/cad",
|
||||
"@polymech/fs": "file:../../../polymech-mono/packages/fs",
|
||||
"@polymech/i18n": "file:../../../polymech-mono/packages/i18n",
|
||||
"@polymech/kbot-d": "file:../../../polymech-mono/packages/kbot",
|
||||
"@polymech/log": "file:../../../polymech-mono/packages/log",
|
||||
"react-jsx-parser": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"../polymech-mono/packages/cache": {
|
||||
"name": "@polymech/cache",
|
||||
"version": "0.4.8",
|
||||
@ -264,8 +276,9 @@
|
||||
"marked": "14.1.4",
|
||||
"marked-terminal": "7.2.1",
|
||||
"mime-types": "2.1.35",
|
||||
"openai": "4.85.3",
|
||||
"openai": "4.87.4",
|
||||
"p-map": "7.0.3",
|
||||
"ts-retry": "6.0.0",
|
||||
"tslog": "^4.9.3",
|
||||
"yargs": "17.7.2",
|
||||
"zod": "3.24.2"
|
||||
@ -316,6 +329,7 @@
|
||||
"../polymech-site": {
|
||||
"name": "@plastichub/astro-site-template",
|
||||
"version": "0.0.1",
|
||||
"extraneous": true,
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^2.10.4",
|
||||
"@astrojs/mdx": "^4.1.0",
|
||||
@ -325,6 +339,8 @@
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@jsdevtools/rehype-toc": "^3.0.2",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@polymech/astro-base": "file:../astro-components/packages/polymech",
|
||||
"@polymech/astro-components": "file:../astro-components/packages/astro-components",
|
||||
"@polymech/cache": "file:../polymech-mono/packages/cache",
|
||||
"@polymech/cad": "file:../polymech-mono/packages/cad",
|
||||
"@polymech/commons": "file:../polymech-mono/packages/commons",
|
||||
@ -349,7 +365,7 @@
|
||||
"github-slugger": "^2.0.0",
|
||||
"glob": "^11.0.1",
|
||||
"got": "^14.4.6",
|
||||
"imagetools": "file:packages/imagetools",
|
||||
"imagetools": "file:../astro-components/packages/imagetools",
|
||||
"lighthouse": "^12.3.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"marked": "^15.0.7",
|
||||
@ -363,7 +379,7 @@
|
||||
"p-map": "^7.0.3",
|
||||
"picomatch": "^4.0.2",
|
||||
"potrace": "^2.1.8",
|
||||
"react-jsx-parser": "^2.3.0",
|
||||
"react-jsx-parser": "^2.4.0",
|
||||
"reading-time": "^1.5.0",
|
||||
"rehype-accessible-emojis": "^0.3.2",
|
||||
"rehype-stringify": "^10.0.1",
|
||||
@ -376,6 +392,7 @@
|
||||
"sharp": "^0.29.3",
|
||||
"showdown": "^2.1.0",
|
||||
"tailwindcss": "^4.0.7",
|
||||
"ts-retry": "^6.0.0",
|
||||
"type-fest": "^4.34.1",
|
||||
"vite": "^6.1.1",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
@ -2395,10 +2412,6 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@plastichub/astro-site-template": {
|
||||
"resolved": "../polymech-site",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz",
|
||||
@ -2414,6 +2427,10 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@polymech/astro-base": {
|
||||
"resolved": "../astro-components/packages/polymech",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@polymech/cache": {
|
||||
"resolved": "../polymech-mono/packages/cache",
|
||||
"link": true
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@jsdevtools/rehype-toc": "^3.0.2",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@polymech/astro-base": "file:../astro-components/packages/polymech",
|
||||
"@polymech/cache": "file:../polymech-mono/packages/cache",
|
||||
"@polymech/cad": "file:../polymech-mono/packages/cad",
|
||||
"@polymech/commons": "file:../polymech-mono/packages/commons",
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
I18N_SOURCE_LANGUAGE,
|
||||
PRODUCT_SPECS,
|
||||
RETAIL_LOG_LEVEL_I18N_PRODUCT_ASSETS
|
||||
} from '@/app/config.js'
|
||||
} from 'config/config.js'
|
||||
|
||||
import { translateXLS } from '@polymech/i18n/translate_xls'
|
||||
import { I18N_STORE, OSR_ROOT } from 'config/config.js'
|
||||
@ -32,7 +32,7 @@ export const translate = async (text: string, srcLanguage = 'en', targetLanguage
|
||||
})
|
||||
return translation
|
||||
} catch (e) {
|
||||
logger.error(`Failed to translate text: ${text} from ${srcLanguage} to ${targetLanguage} : ${e.message}`, e)
|
||||
logger.error(`Failed to translate text: ${text} from ${srcLanguage} to ${targetLanguage} : ${e.message}`)
|
||||
}
|
||||
return text
|
||||
}
|
||||
@ -67,6 +67,6 @@ export const translateSheets = async (product, language) => {
|
||||
try {
|
||||
return await translateXLS(path.resolve(src), dst, i18nOptions)
|
||||
} catch (e) {
|
||||
logger.error(`Failed to translate assets ${src} to ${language}`, e.message, e)
|
||||
logger.error(`Failed to translate assets ${src} to ${language}`, e.message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,18 +8,14 @@ import { renderTemplate, unescapeHTML } from "astro/runtime/server/index.js";
|
||||
import { findUp } from 'find-up'
|
||||
import { createLogger } from '@polymech/log'
|
||||
import { parse, IProfile } from '@polymech/commons/profile'
|
||||
import { renderMarkup } from "@/model/component.js";
|
||||
|
||||
import {
|
||||
LOGGING_NAMESPACE,
|
||||
OSRL_ENV,
|
||||
OSRL_PRODUCT_PROFILE,
|
||||
PRODUCT_ROOT,
|
||||
I18N_SOURCE_LANGUAGE
|
||||
PRODUCT_ROOT
|
||||
} from 'config/config.js'
|
||||
|
||||
import { translate } from "@/base/i18n.js"
|
||||
|
||||
export const logger = createLogger(LOGGING_NAMESPACE)
|
||||
export const boot = () => logger.info('Astro is booting up')
|
||||
export const env = (item_rel: string = ""): IProfile => {
|
||||
@ -38,22 +34,12 @@ export const render = async (string) => {
|
||||
const html = `${unescapeHTML(string)}`
|
||||
return createComponent(() => renderTemplate(html as any, []))
|
||||
}
|
||||
export const component = async (str: string = "", locale, data = {}) => {
|
||||
const content = await translate(
|
||||
(str || "").trim(),
|
||||
I18N_SOURCE_LANGUAGE,
|
||||
locale
|
||||
)
|
||||
const markup = ((await renderMarkup(content, data)) as any) || { html: "" }
|
||||
return await render(markup.html)
|
||||
}
|
||||
export const item_defaults = async (itemDir) => {
|
||||
return await findUp('defaults.json', {
|
||||
stopAt: PRODUCT_ROOT(),
|
||||
cwd: itemDir
|
||||
})
|
||||
}
|
||||
|
||||
export async function markdownToHtml(markdown: string): Promise<string> {
|
||||
const result = await unified()
|
||||
.use(remarkParse)
|
||||
@ -63,11 +49,9 @@ export async function markdownToHtml(markdown: string): Promise<string> {
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
export const createMarkdownComponent = async (markdown: string) => {
|
||||
const html = unescapeHTML(await markdownToHtml(markdown));
|
||||
return createComponent(() => renderTemplate(html as any, []));
|
||||
}
|
||||
|
||||
export const createHTMLComponent = async (html: string) =>
|
||||
createComponent(() => renderTemplate(unescapeHTML(html) as any, []))
|
||||
@ -3,25 +3,32 @@ import pMap from 'p-map'
|
||||
import { GlobOptions } from 'glob'
|
||||
import { sanitizeUri } from 'micromark-util-sanitize-uri'
|
||||
import ExifReader from 'exifreader'
|
||||
|
||||
import { loadImage } from "imagetools/api"
|
||||
|
||||
import { sanitizeFilename } from '@polymech/fs/utils'
|
||||
import { resolve } from '@polymech/commons'
|
||||
import { files } from '@polymech/commons'
|
||||
import { sync as exists } from '@polymech/fs/exists'
|
||||
import { sync as read } from '@polymech/fs/read'
|
||||
import { logger } from '@/base/index.js'
|
||||
|
||||
import { removeArrayValues, removeArrays, removeBufferValues, removeEmptyObjects } from '@/base/objects.js'
|
||||
import { ITEM_ASSET_URL, PRODUCT_CONFIG, PRODUCT_ROOT, DEFAULT_IMAGE_URL } from '../app/config.js'
|
||||
import { env } from './index.js'
|
||||
import { GalleryImage, MetaJSON } from './images.js'
|
||||
|
||||
import { env } from './index.js'
|
||||
import {
|
||||
removeArrayValues,
|
||||
removeArrays,
|
||||
removeBufferValues,
|
||||
removeEmptyObjects
|
||||
} from '@/base/objects.js'
|
||||
|
||||
const IMAGES_GLOB = '*.+(JPG|jpg|png|PNG|gif)'
|
||||
import {
|
||||
ITEM_ASSET_URL, PRODUCT_CONFIG, PRODUCT_ROOT,
|
||||
DEFAULT_IMAGE_URL, ASSETS_LOCAL,
|
||||
ASSETS_GLOB
|
||||
} from 'config/config.js'
|
||||
|
||||
export const default_sort = (files: string[]): string[] =>
|
||||
{
|
||||
export const default_sanitizer = (files:string[]) => files.map((f) => sanitizeFilename(f))
|
||||
|
||||
export const default_sort = (files: string[]): string[] => {
|
||||
const getSortableParts = (filename: string) => {
|
||||
const baseName = path.parse(filename).name;
|
||||
const match = baseName.match(/^(\d+)_?(.*)$/); // Match leading numbers
|
||||
@ -29,8 +36,7 @@ export const default_sort = (files: string[]): string[] =>
|
||||
const textPart = match ? match[2] : baseName; // Extract text part
|
||||
|
||||
return { numPart, textPart };
|
||||
};
|
||||
|
||||
}
|
||||
return files.sort((a, b) => {
|
||||
const { numPart: aNum, textPart: aText } = getSortableParts(a)
|
||||
const { numPart: bNum, textPart: bText } = getSortableParts(b)
|
||||
@ -38,10 +44,29 @@ export const default_sort = (files: string[]): string[] =>
|
||||
return aNum - bNum || aText.localeCompare(bText, undefined, { numeric: true, sensitivity: 'base' })
|
||||
}
|
||||
return aText.localeCompare(bText, undefined, { numeric: true, sensitivity: 'base' })
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export const default_filter = async (url: string) => {
|
||||
try {
|
||||
const response = await fetch(url, { method: 'HEAD' })
|
||||
if (!response.ok) {
|
||||
logger.warn(`Image URL not found ${url}`)
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`Image URL not found ${url} : ${error.message}`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const default_filter_locale = async (url: string) => {
|
||||
return url && exists(url)
|
||||
}
|
||||
|
||||
export const image_url = async (src, fallback = DEFAULT_IMAGE_URL) => {
|
||||
if (exists(src)) return src
|
||||
let safeSrc = src
|
||||
try {
|
||||
const response = await fetch(src, { method: 'HEAD' })
|
||||
@ -55,25 +80,23 @@ export const image_url = async (src, fallback = DEFAULT_IMAGE_URL) => {
|
||||
return safeSrc
|
||||
}
|
||||
|
||||
export const gallery = async (
|
||||
assetPath,
|
||||
product): Promise<GalleryImage[] | undefined> => {
|
||||
product = '' + product
|
||||
export const gallery = async ( assetPath, item): Promise<GalleryImage[]> => {
|
||||
|
||||
const root = resolve(PRODUCT_ROOT())
|
||||
const profile = env()
|
||||
const assetSlug = path.parse(assetPath).name
|
||||
|
||||
const productConfig: any = read(PRODUCT_CONFIG(product), "json")
|
||||
if (!productConfig) {
|
||||
logger.warn(`item gallery : item ${product} config not found !`)
|
||||
return
|
||||
}
|
||||
const mediaPath = `${root}/${product}/${assetPath}/`
|
||||
if (!exists(mediaPath)) {
|
||||
logger.warn(`item gallery : item ${product} media path not found ${mediaPath}!`)
|
||||
const itemConfig: any = read(PRODUCT_CONFIG(item), "json")
|
||||
if (!itemConfig) {
|
||||
logger.warn(`item gallery : item ${item} config not found !`)
|
||||
return []
|
||||
}
|
||||
const galleryGlob = (productConfig.gallery || {})[assetSlug]?.glob || IMAGES_GLOB
|
||||
const mediaPath = `${root}/${item}/${assetPath}/`
|
||||
if (!exists(mediaPath)) {
|
||||
logger.warn(`item gallery : item ${item} media path not found ${mediaPath}!`)
|
||||
return []
|
||||
}
|
||||
const galleryGlob = (itemConfig.gallery || {})[assetSlug]?.glob || ASSETS_GLOB
|
||||
let galleryFiles: any[] = files(mediaPath, galleryGlob, {
|
||||
cwd: mediaPath,
|
||||
absolute: false,
|
||||
@ -85,10 +108,28 @@ export const gallery = async (
|
||||
}
|
||||
|
||||
if (!galleryFiles) {
|
||||
logger.warn(`ProductGallery : Product ${product} media files not found ! ${mediaPath}`)
|
||||
return
|
||||
logger.warn(`gallery : ${item} media files not found ! ${mediaPath}`)
|
||||
return []
|
||||
}
|
||||
const assetUrl = (filePath) => {
|
||||
return sanitizeUri(ITEM_ASSET_URL(
|
||||
{
|
||||
assetPath,
|
||||
filePath,
|
||||
ITEM_REL: item,
|
||||
...profile.variables
|
||||
}
|
||||
))
|
||||
}
|
||||
if (!ASSETS_LOCAL) {
|
||||
galleryFiles = await pMap(galleryFiles, async (f) => (await default_filter(assetUrl(f))) ? f : null, { concurrency: 5 })
|
||||
galleryFiles = galleryFiles.filter((f) => f !== null)
|
||||
galleryFiles = default_sort(galleryFiles)
|
||||
} else {
|
||||
galleryFiles = galleryFiles.filter(default_filter_locale)
|
||||
}
|
||||
galleryFiles = default_sort(galleryFiles)
|
||||
|
||||
return await pMap(galleryFiles, async (file: string) => {
|
||||
const parts = path.parse(file)
|
||||
const filePath = path.join(mediaPath, file)
|
||||
@ -123,21 +164,12 @@ export const gallery = async (
|
||||
delete imageMeta.exif.icc
|
||||
delete imageMeta.exif.xmp
|
||||
delete imageMeta.exif.iptc
|
||||
const assetUrl = (filePath) => {
|
||||
return sanitizeUri(ITEM_ASSET_URL(
|
||||
{
|
||||
assetPath,
|
||||
filePath,
|
||||
ITEM_REL: product,
|
||||
...profile.variables
|
||||
}
|
||||
))
|
||||
}
|
||||
const src = ASSETS_LOCAL ? filePath : await image_url(assetUrl(file))
|
||||
const ret: GalleryImage =
|
||||
{
|
||||
name: path.parse(file).name,
|
||||
url: await image_url(assetUrl(file)),
|
||||
src: await image_url(assetUrl(file)),
|
||||
src: src,
|
||||
url: src,
|
||||
meta: {
|
||||
format: imageMeta.format,
|
||||
width: imageMeta.width,
|
||||
@ -187,7 +219,7 @@ export const toJsonLd = async (images: GalleryImage[], lang: string, contentUrl:
|
||||
"@context": "https://schema.org",
|
||||
"@type": "ImageObject",
|
||||
"inLanguage": lang,
|
||||
"contentUrl": contentUrl || image.src,
|
||||
"contentUrl": contentUrl || image.src,
|
||||
"url": contentUrl || image.src,
|
||||
"name": image.title || image.name,
|
||||
"description": image.description || image.alt || "",
|
||||
@ -196,7 +228,7 @@ export const toJsonLd = async (images: GalleryImage[], lang: string, contentUrl:
|
||||
"identifier": image.name,
|
||||
"keywords": Array.isArray(image.keywords) ? image.keywords.join(", ") : image.keywords || ""
|
||||
}
|
||||
|
||||
|
||||
// Add GPS coordinates if available
|
||||
if (image.gps && image.gps.lat && image.gps.lon) {
|
||||
jsonLd["contentLocation"] = {
|
||||
|
||||
@ -6,7 +6,7 @@ import { I18N_SOURCE_LANGUAGE } from 'config/config.js'
|
||||
import { translate } from '@/base/i18n.js'
|
||||
import { item_defaults } from '@/base/index.js'
|
||||
|
||||
import config from "../app/config.json" with { "type": "json" }
|
||||
import config from "../config/config.json" with { "type": "json" }
|
||||
|
||||
const keywords = (keywords: string) => keywords.split(',').map(k => k.trim()).filter(Boolean);
|
||||
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
---
|
||||
import "../styles/flowbite.css"
|
||||
import "../styles/global.css"
|
||||
import "../styles/custom.scss"
|
||||
|
||||
import { LANGUAGES_PROD } from "config/config.js"
|
||||
|
||||
import config from "config/config.json"
|
||||
import { plainify } from "../base/strings.js"
|
||||
import config from "@/config/config.json"
|
||||
import { plainify } from "@/base/strings.js"
|
||||
|
||||
import "../styles/global.css"
|
||||
import "../styles/custom.scss"
|
||||
|
||||
import { AstroSeo } from "@astrolib/seo"
|
||||
import { item_keywords } from '@/base/seo.js'
|
||||
@ -55,7 +55,7 @@ const description = item?.description as string || config.metadata.description
|
||||
|
||||
const keywords = await item_keywords(item, Astro.currentLocale)
|
||||
|
||||
const tracking = config?.tracking?.googleAnalytics
|
||||
const tracking = config?.tracking?.googleAnalytics || 'G-RW6Q6EG3J0'
|
||||
---
|
||||
|
||||
<AstroSeo
|
||||
@ -146,21 +146,10 @@ const tracking = config?.tracking?.googleAnalytics
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date())
|
||||
gtag('config', {tracking})
|
||||
gtag('config', "G-RW6Q6EG3J0")
|
||||
</script>
|
||||
)}
|
||||
|
||||
{ REDIRECT && <script>
|
||||
const currentPath = window.location.pathname;
|
||||
if (!/^\/[a-z]{2}(\/|$)/i.test(currentPath)) {
|
||||
let language = navigator.language
|
||||
language = language.split('-')[0]
|
||||
language = "en"
|
||||
window.location.href = `/${language}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
---
|
||||
const { title, url, author, pubDate, description, image } = Astro.props;
|
||||
import { Img } from "imagetools/components"
|
||||
const { title, url, pubDate, description, image } = Astro.props;
|
||||
import { DEFAULT_IMAGE_URL } from "@/config/config";
|
||||
import { Img } from "imagetools/components";
|
||||
---
|
||||
|
||||
<div
|
||||
@ -8,15 +9,16 @@ import { Img } from "imagetools/components"
|
||||
>
|
||||
<dt>
|
||||
<h2 class="uppercase group-hover:text-orange-500 text-balance">
|
||||
<div
|
||||
class="z-0 scale-95 transition group-hover:scale-100"
|
||||
>
|
||||
</div>
|
||||
<div class="z-0 scale-95 transition group-hover:scale-100"></div>
|
||||
<a href={url} title={title}>
|
||||
<span class="absolute -inset-x-4 -inset-y-6 z-20 sm:-inset-x-6"></span><span class="relative z-10">{title}</span><br/><span class="text-sm">{pubDate}</span></a>
|
||||
<span class="absolute -inset-x-4 -inset-y-6 z-20 sm:-inset-x-6"
|
||||
></span><span class="relative z-10">{title}</span><br /><span
|
||||
class="text-sm">{pubDate}</span
|
||||
></a
|
||||
>
|
||||
<div class="">
|
||||
<Img
|
||||
src={image.url}
|
||||
src={image.src || DEFAULT_IMAGE_URL}
|
||||
alt={image.alt}
|
||||
sizes={`
|
||||
(min-width: 1024px) 33vw,
|
||||
@ -24,9 +26,9 @@ import { Img } from "imagetools/components"
|
||||
100vw
|
||||
`}
|
||||
attributes={{
|
||||
img:{
|
||||
class: "w-full p-2 rounded-2xl relative object-contain lg:mt-2"
|
||||
}
|
||||
img: {
|
||||
class: "w-full p-2 rounded-2xl relative object-contain lg:mt-2",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1,24 +1,9 @@
|
||||
---
|
||||
import { Img } from "imagetools/components"
|
||||
import config from "@/app/config.json"
|
||||
---
|
||||
<section>
|
||||
<div class="py-2">
|
||||
<div
|
||||
class="flex flex-col text-center bg-white rounded-xl overflow-hidden relative shadow-md">
|
||||
<Img
|
||||
src={config.pages.home.hero}
|
||||
alt=""
|
||||
format={['avif']}
|
||||
objectFit="contain"
|
||||
breakpoints={[100,500,800,1200,1600]}
|
||||
objectPosition="50% 50%",
|
||||
attributes={{
|
||||
img:{
|
||||
class:"md:-bottom-32"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div class="max-w-xl mx-auto relative">
|
||||
<h1
|
||||
class="text-lg text-neutral-600 font-mono tracking-tight text-balance uppercase">
|
||||
|
||||
@ -1,25 +1,10 @@
|
||||
---
|
||||
import { Img } from "imagetools/components"
|
||||
|
||||
---
|
||||
<section>
|
||||
<div class="py-2">
|
||||
<div
|
||||
class="flex flex-col p-4 pb-32 bg-white rounded-xl overflow-hidden relative">
|
||||
<Img
|
||||
class="w-full absolute md:-bottom-96 -left-96 -ml-48"
|
||||
src="/products/5.jpeg"
|
||||
alt=""
|
||||
format="png"
|
||||
breakpoints={[800, 1600]}
|
||||
attributes={{
|
||||
img: {
|
||||
class: `w-full absolute md:-bottom-96 -left-96 -ml-48`,
|
||||
},
|
||||
link: {
|
||||
fetchpriority: "high",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div class="max-w-xl relative ml-auto">
|
||||
<h1
|
||||
class="text-lg text-neutral-600 font-mono tracking-tight text-balance uppercase">
|
||||
|
||||
@ -1,131 +0,0 @@
|
||||
---
|
||||
interface Props extends Omit<astroHTML.JSX.IframeHTMLAttributes, 'src' | 'srcdoc'> {
|
||||
/**
|
||||
* Pass `true` to embed using `www.youtube.com` instead of `www.youtube-nocookie.com`
|
||||
*/
|
||||
cookie?: boolean
|
||||
/**
|
||||
* YouTube IFrame Player API parameters
|
||||
*
|
||||
* Defaults to `{autoplay: 1}`, additional parameters will be merged into the defaults
|
||||
* @see https://developers.google.com/youtube/player_parameters#Parameters
|
||||
*/
|
||||
embedParams?: EmbedParams
|
||||
/**
|
||||
* `loading` attribute for the thumbnail `<img>`
|
||||
*
|
||||
* Defaults to `"lazy"`
|
||||
*/
|
||||
loading?: 'eager' | 'lazy'
|
||||
/**
|
||||
* Pass `true` to omit the "Watch on YouTube" link - saves ~3.86 KB
|
||||
*/
|
||||
noLink?: boolean
|
||||
/**
|
||||
* Thumbnail image to use in the static embed
|
||||
*
|
||||
* Defaults to `"default"`, pass 1, 2 or 3 to use a screenshot from the video instead.
|
||||
*/
|
||||
thumbnail?: Thumbnail
|
||||
/**
|
||||
* Thumbnail resolution
|
||||
*
|
||||
* Defaults to `"sd"` (640x480)
|
||||
*/
|
||||
thumbnailRes?: ThumbnailRes
|
||||
/**
|
||||
* Title for the static embed
|
||||
*/
|
||||
title: string
|
||||
/**
|
||||
* 11-digit YouTube video id
|
||||
*/
|
||||
videoId: string
|
||||
}
|
||||
|
||||
interface EmbedParams {
|
||||
autoplay?: ToggleParam
|
||||
cc_lang_pref?: string
|
||||
cc_load_policy?: ToggleParam
|
||||
color?: 'red' | 'white'
|
||||
controls?: ToggleParam
|
||||
disablekb?: ToggleParam
|
||||
enablejsapi?: ToggleParam
|
||||
end?: number
|
||||
fs?: ToggleParam
|
||||
hl?: string
|
||||
iv_load_policy?: 1 | '1' | 3 | '3'
|
||||
list?: string
|
||||
listType?: 'playlist' | 'user_uploads'
|
||||
loop?: ToggleParam
|
||||
/** @deprecated has no effect, deprecated by YouTube on August 15 2023 */
|
||||
modestbranding?: ToggleParam
|
||||
origin?: string
|
||||
playlist?: string
|
||||
playslinline?: ToggleParam
|
||||
rel?: ToggleParam
|
||||
start?: number
|
||||
widget_referrer?: string
|
||||
}
|
||||
|
||||
type ToggleParam = 0 | '0' | 1 | '1'
|
||||
|
||||
type Thumbnail = 'default' | 1 | '1' | 2 | '2' | 3 | '3' | string
|
||||
|
||||
type ThumbnailRes = 120 | '120' | 'default' | 320 | '320' | 'medium' | 'mq' | 480 | '480' | 'high' | 'hq' | 640 | '640' | 'standard' | 'sd' | 1280 | '1280' | 'maxres'
|
||||
|
||||
const QUALITY_PREFIXES = {
|
||||
120: '',
|
||||
default: '',
|
||||
320: 'mq',
|
||||
medium: 'mq',
|
||||
mq: 'mq',
|
||||
480: 'hq',
|
||||
high: 'hq',
|
||||
hq: 'hq',
|
||||
640: 'sd',
|
||||
standard: 'sd',
|
||||
sd: 'sd',
|
||||
1280: 'maxres',
|
||||
maxres: 'maxres',
|
||||
}
|
||||
|
||||
let {
|
||||
cookie = false,
|
||||
embedParams,
|
||||
loading = 'lazy',
|
||||
noLink = false,
|
||||
thumbnail = 'default',
|
||||
thumbnailRes = 'sd',
|
||||
title,
|
||||
videoId,
|
||||
...iframeAttributes
|
||||
} = Astro.props as Props
|
||||
|
||||
let params: EmbedParams = {autoplay: 1, ...embedParams}
|
||||
|
||||
let embedQuery = Object.keys(params).map(key => `${key}=${params[key]}`).join('&')
|
||||
let embedUrl = `https://www.youtube${cookie ? '' : '-nocookie'}.com/embed${videoId ? `/${videoId}` : ''}${embedQuery ? `?${embedQuery}` : ''}`
|
||||
let thumbnailUrl = !/^(default|1|2|3)$/.test(String(thumbnail)) ? thumbnail : `https://i.ytimg.com/vi/${videoId}/${QUALITY_PREFIXES[thumbnailRes]}${thumbnail}.jpg`
|
||||
|
||||
let linkSvg = `<svg viewBox="0 0 110 26"><use href="#a" style="stroke-width:2px;stroke:#000;stroke-opacity:.15;stroke-linejoin:round"/><path id="a" fill="#fff" d="M16.68.99c-3.13.04-9.66.17-11.69.69-1.49.4-2.59 1.6-2.99 3-.69 2.7-.68 8.31-.68 8.31S1.31 18.6 2 21.3c.39 1.5 1.59 2.6 2.99 3 2.69.7 13.4.68 13.4.68s10.7.01 13.4-.68c1.5-.4 2.59-1.6 2.99-3 .69-2.7.68-8.31.68-8.31s.11-5.61-.68-8.31c-.4-1.5-1.59-2.6-2.99-3C29.11.98 18.4.99 18.4.99s-.67-.01-1.71 0zm72.21.9v21.28h2.78l.31-1.37h.09c.3.5.71.88 1.21 1.18.5.3 1.08.4 1.68.4 1.1 0 1.99-.49 2.49-1.59.5-1.1.81-2.7.81-4.9v-2.4c0-1.6-.11-2.9-.31-3.9-.2-.89-.5-1.59-1-2.09-.5-.4-1.1-.59-1.9-.59-.59 0-1.18.19-1.68.49-.49.3-1.01.8-1.21 1.4V1.9h-3.28zm-49.99.78 3.9 13.9.18 6.71h3.31v-6.71l3.87-13.9h-3.37l-1.4 6.31c-.4 1.89-.71 3.19-.81 3.99h-.09c-.2-1.1-.51-2.4-.81-3.99l-1.37-6.31h-3.4zm29.59 0v2.71h3.4v17.9h3.28V5.38h3.4s0-2.71-.09-2.71h-9.99zM15 7.79l8.9 5.18-8.9 5.09V7.78zm89.4.09c-1.7 0-2.89.59-3.59 1.59-.69.99-.99 2.6-.99 4.9v2.59c0 2.2.3 3.9.99 4.9.7 1.1 1.8 1.59 3.5 1.59 1.4 0 2.38-.3 3.18-1 .7-.7 1.09-1.69 1.09-3.09v-.5l-2.9-.21c0 1-.08 1.6-.28 2-.1.4-.5.62-1 .62-.3 0-.61-.11-.81-.31-.2-.3-.3-.59-.4-1.09-.1-.5-.09-1.21-.09-2.21v-.78l5.71-.09v-2.62c0-1.6-.1-2.78-.4-3.68-.2-.89-.71-1.59-1.31-1.99-.7-.4-1.48-.59-2.68-.59zm-50.49.09c-1.09 0-2.01.18-2.71.68-.7.4-1.2 1.12-1.49 2.12-.3 1-.5 2.27-.5 3.87v2.21c0 1.5.1 2.78.4 3.78.2.9.7 1.62 1.4 2.12.69.5 1.71.68 2.81.78 1.19 0 2.08-.28 2.78-.68.69-.4 1.09-1.09 1.49-2.09.39-1 .49-2.3.49-3.9v-2.21c0-1.6-.2-2.87-.49-3.87-.3-.89-.8-1.62-1.49-2.12-.7-.5-1.58-.68-2.68-.68zm12.18.09v11.9c-.1.3-.29.48-.59.68-.2.2-.51.31-.81.31-.3 0-.58-.1-.68-.4-.1-.3-.18-.7-.18-1.4V8.16h-3.4v11.21c0 1.4.18 2.39.68 3.09.49.7 1.21 1 2.21 1 1.4 0 2.48-.69 3.18-2.09h.09l.31 1.78h2.59V8.16s-3.4 0-3.4-.09zm17.31 0v11.9c-.1.3-.29.48-.59.68-.2.2-.51.31-.81.31-.3 0-.58-.1-.68-.4-.1-.3-.21-.7-.21-1.4V8.16h-3.4v11.21c0 1.4.21 2.39.71 3.09.5.7 1.18 1 2.18 1 1.39 0 2.51-.69 3.21-2.09h.09l.28 1.78h2.62V8.16s-3.4 0-3.4-.09zm20.9 2.09c.4 0 .58.11.78.31.2.3.3.59.4 1.09.1.5.09 1.21.09 2.21v1.09h-2.5v-1.09c0-1 0-1.71.09-2.21 0-.4.11-.8.31-1 .2-.3.51-.4.81-.4zm-50.49.12c.5 0 .8.18 1 .68.19.5.28 1.3.28 2.4v4.68c0 1.1-.08 1.9-.28 2.4s-.5.68-1 .68-.79-.18-.99-.68c-.2-.5-.31-1.3-.31-2.4v-4.68c0-1.1.11-1.9.31-2.4s.49-.68.99-.68zm39.68.09c.3 0 .61.1.81.4.2.3.27.67.37 1.37.1.6.12 1.51.12 2.71l.09 1.9c0 1.1 0 1.99-.09 2.59-.1.6-.19 1.08-.49 1.28-.2.3-.5.4-.9.4-.3 0-.51-.08-.81-.18-.2-.1-.39-.29-.59-.59v-8.5c.1-.4.29-.7.59-1 .3-.3.6-.4.9-.4z"/></svg>`
|
||||
let playButtonSvg = `<svg viewBox="0 0 68 48"><path d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z" fill="#f00"></path><path d="M 45,24 27,14 27,34" fill="#fff"></path></svg>`
|
||||
|
||||
let gradientStyle = `.gradient{width:100%;height:49px;padding-bottom:50px;position:absolute;top:0;background-repeat:repeat-x;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);pointer-events:none}`
|
||||
let linkStyle = noLink ? '' : '.woyt{z-index:2;background:rgba(23,23,23,.8);border-bottom-right-radius:2px;border-top-right-radius:2px;bottom:5px;height:47px;position:absolute}.woyt-text{color:#fff;float:left;font:500 16px/16px "YouTube Noto",Roboto,Arial,Helvetica,sans-serif;margin-left:12px;margin-top:16px}.woyt-logo{float:right;height:16px;margin-left:9px;margin-right:12px;margin-top:16px;width:72px}'
|
||||
let style = `<style>*{padding:0;margin:0;overflow:hidden}html,body{height:100%;background:#000}img{position:absolute;width:100%;top:0;bottom:0;margin:auto}.button{position:absolute;left:50%;top:50%;width:68px;height:48px;margin-left:-34px;margin-top:-24px}.top{position:absolute;top:18px;left:18px;right:18px;display:flex;flex-wrap:nowrap}.title{color:#fff;font-size:18px;white-space:nowrap;word-wrap:normal;text-shadow:0 0 2px rgba(0,0,0,.5);font-family:"YouTube Noto",Roboto,Arial,Helvetica,sans-serif;line-height:1.3;text-overflow:ellipsis;overflow:hidden}${gradientStyle}${linkStyle}</style>`
|
||||
|
||||
let link = noLink ? '' : `<a href="https://www.youtube.com/watch?v=${videoId}${params.start ? `&t=${params.start}s` : ''}" target="_blank" aria-label="Watch on YouTube" class="woyt"><div aria-hidden="true"><div class="woyt-text">Watch on</div><div class="woyt-logo">${linkSvg}</div></div></a>`
|
||||
let srcdoc = `${style}${link}<a href="${embedUrl}"><img src="${thumbnailUrl}" alt="${title}" loading="${loading}"><div class="gradient"></div><div class="top"><div class="title">${title}</div></div><div class="button">${playButtonSvg}</div></a>`
|
||||
---
|
||||
<iframe
|
||||
loading={loading}
|
||||
src={embedUrl}
|
||||
srcdoc={srcdoc}
|
||||
title={title}
|
||||
{...iframeAttributes}
|
||||
allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture"
|
||||
allowfullscreen
|
||||
frameborder="0"
|
||||
style="width:100%;aspect-ratio:16/9"
|
||||
/>
|
||||
@ -1,140 +0,0 @@
|
||||
<section>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 gap-2">
|
||||
<div class="flex flex-col h-full">
|
||||
<h1
|
||||
class="text-lg text-neutral-600 font-mono tracking-tight text-balance">
|
||||
Contact us
|
||||
</h1>
|
||||
<p class="text-sm text-balance text-neutral-500">
|
||||
Reach out to us for any inquiries or assistance with your product or
|
||||
service, or to get in touch with us for any other inquiries.
|
||||
</p>
|
||||
</div>
|
||||
<form class="col-span-2 bg-white p-4 rounded-xl">
|
||||
<div class="grid gap-2 grid-cols-1 sm:grid-cols-2">
|
||||
<div>
|
||||
<label
|
||||
for="first-name"
|
||||
class="sr-only"
|
||||
>First name</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="first-name"
|
||||
name="first-name"
|
||||
autocomplete="given-name"
|
||||
placeholder="Your name"
|
||||
aria-label="First name"
|
||||
class="flex-auto rounded-xl font-mono border-0 h-14 text-xs 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 bg-neutral-100 w-full"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
for="last-name"
|
||||
class="sr-only"
|
||||
>Last name</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="last-name"
|
||||
name="last-name"
|
||||
autocomplete="family-name"
|
||||
placeholder="Your last name"
|
||||
aria-label="Last name"
|
||||
class="flex-auto rounded-xl font-mono border-0 h-14 text-xs 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 bg-neutral-100 w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label
|
||||
for="company"
|
||||
class="sr-only"
|
||||
>Company</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="company"
|
||||
name="company"
|
||||
autocomplete="organization"
|
||||
placeholder="Company name"
|
||||
aria-label="Company"
|
||||
class="flex-auto rounded-xl font-mono border-0 h-14 text-xs 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 bg-neutral-100 w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label
|
||||
for="email"
|
||||
class="sr-only"
|
||||
>Email</label
|
||||
>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
autocomplete="email"
|
||||
placeholder="Your best email!"
|
||||
aria-label="Email"
|
||||
class="flex-auto rounded-xl font-mono border-0 h-14 text-xs 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 bg-neutral-100 w-full"
|
||||
/>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<label
|
||||
for="message"
|
||||
class="sr-only"
|
||||
>Message</label
|
||||
>
|
||||
<textarea
|
||||
rows="12"
|
||||
id="message"
|
||||
name="message"
|
||||
placeholder="Your message goes here..."
|
||||
aria-label="Message"
|
||||
class="flex-auto rounded-xl font-mono border-0 text-xs 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 bg-neutral-100 w-full"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-right">
|
||||
<button
|
||||
type="submit"
|
||||
title="link to your page"
|
||||
aria-label="your label"
|
||||
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">Submit</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>
|
||||
</section>
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
import Wrapper from "@/components/containers/Wrapper.astro"
|
||||
import { footer_left, footer_right } from "@/app/navigation.js"
|
||||
import { footer_left, footer_right } from "@/config/navigation.js"
|
||||
import { ISO_LANGUAGE_LABELS } from "@polymech/i18n"
|
||||
import {
|
||||
LANGUAGES_PROD,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
import Wrapper from "@/components/containers/Wrapper.astro";
|
||||
import { I18N_SOURCE_LANGUAGE } from "@/app/config";
|
||||
import { items } from "config/navigation.js";
|
||||
import { I18N_SOURCE_LANGUAGE } from "@/config/config";
|
||||
import { items } from "@/config/navigation.js";
|
||||
const locale = Astro.currentLocale || I18N_SOURCE_LANGUAGE;
|
||||
const navItems = await items({ locale });
|
||||
---
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
---
|
||||
import config from "@/app/config.json"
|
||||
import config from "@/config/config.json"
|
||||
import { get } from "@/model/registry.js"
|
||||
import { default_image } from "@/app/config.js"
|
||||
import { default_image } from "config/config.js"
|
||||
|
||||
const { frontmatter } = Astro.props
|
||||
const title = frontmatter?.title || config.site.title
|
||||
@ -39,5 +39,4 @@ let data = itemData || {
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
<script type="application/ld+json" set:html={JSON.stringify(data, null, 2)} />
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
<section>
|
||||
<div class="flex flex-col h-full ">
|
||||
<h1
|
||||
class="text-lg text-neutral-600 font-mono tracking-tight uppercase text-balance">
|
||||
Williamsburg, one - your next ecommerce theme for all your products.
|
||||
</h1>
|
||||
<p class="text-sm text-pretty text-neutral-500 mt-2">
|
||||
Your next minimalist ecommerce theme for all your digital products.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@ -1,13 +0,0 @@
|
||||
<section>
|
||||
<div class="flex flex-col p-4 text-center py-20">
|
||||
<div class="max-w-xl mx-auto">
|
||||
<h1
|
||||
class="text-lg text-neutral-600 font-mono tracking-tight text-balance uppercase">
|
||||
Williamsburg, your next ecommerce theme for all your products.
|
||||
</h1>
|
||||
<p class="text-sm text-balance text-neutral-500">
|
||||
Your next minimalist ecommerce theme for all your digital products.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -1,13 +0,0 @@
|
||||
<section>
|
||||
<div class="flex flex-col p-4 text-center py-20">
|
||||
<div class="max-w-xl mx-auto">
|
||||
<h1
|
||||
class="text-lg text-neutral-600 font-mono tracking-tight text-balance uppercase">
|
||||
Williamsburg, your next ecommerce theme for all your products.
|
||||
</h1>
|
||||
<p class="text-sm text-balance text-neutral-500">
|
||||
Your next minimalist ecommerce theme for all your digital products.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -2,7 +2,6 @@
|
||||
import { Img } from "imagetools/components";
|
||||
import Translate from "@/components/polymech/i18n.astro"
|
||||
import { translate } from "@/base/i18n";
|
||||
import { createMarkdownComponent, markdownToHtml } from "@/base/index.js";
|
||||
|
||||
import { I18N_SOURCE_LANGUAGE, IMAGE_SETTINGS } from "config/config.js"
|
||||
interface Image {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
import { DEFAULT_IMAGE_URL } from '@/app/config.js'
|
||||
import { DEFAULT_IMAGE_URL } from 'config/config.js'
|
||||
import { Picture } from "imagetools/components"
|
||||
import { image_url } from "@/base/media.js"
|
||||
|
||||
@ -19,4 +19,4 @@ const {
|
||||
|
||||
const srcSafe = await image_url(src, fallback)
|
||||
---
|
||||
<Picture class="" src={srcSafe} alt={alt} {...rest} />
|
||||
<Picture src={srcSafe} alt={alt} {...rest} />
|
||||
|
||||
@ -2,11 +2,10 @@
|
||||
import JSX from "./jsx.astro"
|
||||
import { sync as read } from "@polymech/fs/read"
|
||||
import { sync as exists } from "@polymech/fs/exists"
|
||||
import {} from "@polymech/cache"
|
||||
|
||||
import { run, OptionsSchema, IKBotTask } from "@polymech/kbot-d";
|
||||
import { render } from "@/base";
|
||||
import { renderMarkup } from "@/model/component.js";
|
||||
|
||||
import { createMarkdownComponent } from "@/base/index.js";
|
||||
|
||||
const { ...rest } = Astro.props;
|
||||
const promptContent = ((await Astro.slots.render("default")) as string) || "";
|
||||
@ -73,9 +72,7 @@ const renderers = {
|
||||
jsx: async (content: string) => {},
|
||||
html: async (content: string) => {},
|
||||
md: async (str: string) => {
|
||||
const markup = (await renderMarkup(str, options, "test.md")) || { html: "failed md" }
|
||||
const ret = await render(markup.html)
|
||||
return ret
|
||||
return createMarkdownComponent(str)
|
||||
},
|
||||
astro: async (content: string) => {
|
||||
// const compiled = await compile(content)
|
||||
|
||||
@ -1,17 +1,16 @@
|
||||
---
|
||||
import { renderMarkup } from "@/model/component.js";
|
||||
import { render, component } from "@/base/index.js";
|
||||
import { translate } from "@/base/i18n.js";
|
||||
import { I18N_SOURCE_LANGUAGE, ASSET_URL } from "config/config.js";
|
||||
import { createMarkdownComponent } from "@/base/index.js";
|
||||
import { } from "@/base/i18n.js";
|
||||
import { ASSET_URL } from "config/config.js";
|
||||
import { fromMarkdown } from "mdast-util-from-markdown";
|
||||
import { toString } from "mdast-util-to-string";
|
||||
|
||||
import { toMarkdown } from "mdast-util-to-markdown";
|
||||
import { visit } from "unist-util-visit";
|
||||
import { Root, Image } from "mdast";
|
||||
|
||||
// https://github.com/syntax-tree/mdast-util-mdxjs-esm?tab=readme-ov-file#when-to-use-this
|
||||
// https://github.com/syntax-tree/mdast-util-to-markdown?tab=readme-ov-file#list-of-extensions
|
||||
// https://chatgpt.com/c/67ba55c7-4c04-8001-b26b-bfa3a89aafb1
|
||||
import { toMarkdown } from "mdast-util-to-markdown";
|
||||
import { visit } from "unist-util-visit";
|
||||
import { Root, RootContent, Heading, Image } from "mdast";
|
||||
import { Img } from "imagetools/components";
|
||||
|
||||
interface Props {
|
||||
markdown: string;
|
||||
@ -40,10 +39,8 @@ const processImageUrls = (content: string, data: Record<string, string>) => {
|
||||
return markup;
|
||||
}
|
||||
|
||||
const ReadmeContent = await component(
|
||||
const ReadmeContent = await createMarkdownComponent(
|
||||
processImageUrls(markdown, data),
|
||||
Astro.currentLocale,
|
||||
{},
|
||||
)
|
||||
---
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ const title_i18n = await translate(title, I18N_SOURCE_LANGUAGE, locale);
|
||||
href={id}
|
||||
class="inline-block p-4 border-b-2
|
||||
border-transparent rounded-t-lg
|
||||
text-xs
|
||||
hover:text-gray-600
|
||||
hover:border-gray-300
|
||||
dark:hover:text-gray-300"
|
||||
|
||||
@ -1,187 +0,0 @@
|
||||
---
|
||||
const pricingPlans = [
|
||||
{
|
||||
name: "Individual",
|
||||
monthlyPrice: "149",
|
||||
annualPrice: "100",
|
||||
description: "For freelancers and independent designers",
|
||||
features: [
|
||||
"Unlimited access to all design assets",
|
||||
"Good plan for a freelancer and solo designer",
|
||||
"Support for basic web development",
|
||||
"Work on up to 3 product design projects",
|
||||
"Only one user per account",
|
||||
"Commercial use",
|
||||
],
|
||||
unavailableFeatures: [
|
||||
"Licensed for teams, startups, agencies and corporates",
|
||||
"Several users per account",
|
||||
"Commercial use",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Startup",
|
||||
monthlyPrice: "249",
|
||||
annualPrice: "300",
|
||||
description: " Best choice for any size team or agency",
|
||||
features: [
|
||||
"Everything included in the Individual plan",
|
||||
"Licensed for teams, startups, agencies and corporates",
|
||||
"Several users per account",
|
||||
"Commercial use",
|
||||
],
|
||||
unavailableFeatures: [
|
||||
"Missing advanced 3D design tools",
|
||||
"Without premium system collaboration",
|
||||
"Standard support",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Company",
|
||||
monthlyPrice: "50",
|
||||
annualPrice: "600",
|
||||
description: "For businesses aiming to lead in innovation.",
|
||||
features: [
|
||||
"Everything included in the Individual plan",
|
||||
"Everything included in the Startup plan",
|
||||
"Unlimited licensed for teams, startups, agencies and corporates",
|
||||
"Unlimited users per account",
|
||||
"Commercial use",
|
||||
],
|
||||
unavailableFeatures: [],
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<section x-data="{annual: false}">
|
||||
<div
|
||||
class="flex flex-col gap-12 h-full justify-between p-4 text-center pt-20">
|
||||
<div class="max-w-xl mx-auto">
|
||||
<h1
|
||||
class="text-lg text-neutral-600 font-mono tracking-tight text-balance">
|
||||
Become a member today and unlock unlimited access.
|
||||
</h1>
|
||||
<p class="text-sm text-balance text-neutral-500">
|
||||
Enroll in our membership program today at a discounted rate and enjoy
|
||||
full access to our incredible lineup of products.
|
||||
</p>
|
||||
<div class="max-w-sm mx-auto">
|
||||
<div
|
||||
class="overflow-hidden inline-flex mt-6 z-0 h-14 rounded-lg p-0.5 w-full bg-white">
|
||||
<button
|
||||
class="text-xs text-black w-full block font-medium px-8 py-2 transition rounded-lg border border-transparent"
|
||||
@click="annual = false"
|
||||
:class="annual == false ? 'bg-neutral-100 border text-black ' : ''"
|
||||
type="button"
|
||||
>Monthly</button
|
||||
>
|
||||
<button
|
||||
class="text-xs text-black w-full block font-medium px-8 py-2 transition rounded-lg border border-transparent"
|
||||
@click="annual = true"
|
||||
:class="annual == true ? 'bg-neutral-100 border text-black' : ''"
|
||||
type="button"
|
||||
>Annual ( save 25% )</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid lg:grid-cols-3 gap-2 py-2">
|
||||
{
|
||||
pricingPlans.map((plan) => (
|
||||
<>
|
||||
<div class="flex flex-col h-full bg-white p-4 rounded-xl">
|
||||
<div class=" h-full flex flex-col ">
|
||||
<div>
|
||||
<div>
|
||||
<div class="flex justify-between items-center">
|
||||
<h3
|
||||
id="tier-essential"
|
||||
class="text-lg text-neutral-600 font-mono tracking-tight uppercase">
|
||||
{plan.name}
|
||||
</h3>
|
||||
<p class="flex items-baseline gap-x-1 text-lg text-neutral-600 font-mono tracking-tight">
|
||||
<span>
|
||||
<span x-show="!annual">${plan.monthlyPrice}</span>
|
||||
<span x-show="annual">${plan.annualPrice}</span>
|
||||
</span>
|
||||
<span class="text-xs">
|
||||
/m
|
||||
<span
|
||||
x-show="annual"
|
||||
style="display: none;">
|
||||
(billed annually)
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-neutral-500">
|
||||
{plan.description}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-8">
|
||||
<button
|
||||
type="button"
|
||||
title="link to your page"
|
||||
aria-label="your label"
|
||||
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">
|
||||
Add to cart
|
||||
</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>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="mt-6 text-xs space-y-1 font-mono uppercase text-neutral-500">
|
||||
{plan.features.map((feature) => (
|
||||
<li class="inline-flex items-start gap-3 ">
|
||||
<span>+</span>
|
||||
{feature}
|
||||
</li>
|
||||
))}
|
||||
{plan.unavailableFeatures.map((feature) => (
|
||||
<li class="inline-flex items-start gap-3 opacity-60">
|
||||
<span>−</span> {feature}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
@ -1,65 +0,0 @@
|
||||
import { translate } from "@/base/i18n.js"
|
||||
import config from "config/config.json"
|
||||
}
|
||||
|
||||
/**
|
||||
* Process and combine keywords from multiple sources, translate them, and trigger an optional callback
|
||||
*
|
||||
* @param options - Configuration for processing keywords
|
||||
* @param options.itemConfig - The frontmatter or item configuration object
|
||||
* @param options.sourceLanguage - The source language for translation
|
||||
* @param options.targetLocale - The target locale for translation
|
||||
* @param options.additionalKeywords - Extra keywords to add to the processed list
|
||||
* @param options.onKeywordsProcessed - Optional callback that receives the processed keywords array
|
||||
* @returns Object containing the processed keywords as both an array and comma-separated string
|
||||
*/
|
||||
export async function processKeywords({
|
||||
itemConfig = {},
|
||||
sourceLanguage,
|
||||
targetLocale,
|
||||
additionalKeywords = [],
|
||||
onKeywordsProcessed
|
||||
}: {
|
||||
itemConfig?: Record<string, any>;
|
||||
sourceLanguage: string;
|
||||
targetLocale: string;
|
||||
additionalKeywords?: string[];
|
||||
onKeywordsProcessed?: (keywords: string[]) => void;
|
||||
}) {
|
||||
let systemKeywords = "";
|
||||
|
||||
if (itemConfig.PRODUCT_ROOT) {
|
||||
const defaultsJson = await item_defaults(itemConfig.PRODUCT_ROOT);
|
||||
const defaults: Record<string, string> = await import('@polymech/fs/read').then(m => m.sync(defaultsJson, 'json')) || {};
|
||||
|
||||
// Extract keywords from different sources
|
||||
const defaultsKeywords = (defaults.keywords || "").split(',').map(k => k.trim()).filter(Boolean);
|
||||
const configKeywords = (config.metadata?.keywords || "").split(',').map(k => k.trim()).filter(Boolean);
|
||||
const itemKeywords = (itemConfig.keywords || "").split(',').map(k => k.trim()).filter(Boolean);
|
||||
|
||||
// Combine and deduplicate all keywords
|
||||
const allKeywords = Array.from(new Set([
|
||||
...defaultsKeywords,
|
||||
...configKeywords,
|
||||
...itemKeywords,
|
||||
...additionalKeywords
|
||||
])).join(',');
|
||||
|
||||
// Translate the keywords
|
||||
systemKeywords = await translate(allKeywords, sourceLanguage, targetLocale);
|
||||
}
|
||||
|
||||
// Final processing and deduplication
|
||||
const keywordsArray = [...new Set([itemConfig.name, ...systemKeywords.split(','), ...additionalKeywords])].filter(Boolean);
|
||||
const keywords = keywordsArray.join(',');
|
||||
|
||||
// Call the callback with the processed keywords if it exists
|
||||
if (typeof onKeywordsProcessed === 'function') {
|
||||
onKeywordsProcessed(keywordsArray);
|
||||
}
|
||||
|
||||
return {
|
||||
keywordsArray,
|
||||
keywords
|
||||
};
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import { processKeywords } from '@/base/index.js'
|
||||
|
||||
const item_config = frontmatter as any || {}
|
||||
|
||||
// Process keywords using the extracted function
|
||||
import { item_defaults, processKeywords } from '@/base/index.js'
|
||||
itemConfig: item_config,
|
||||
sourceLanguage: I18N_SOURCE_LANGUAGE,
|
||||
targetLocale: locale,
|
||||
additionalKeywords,
|
||||
onKeywordsProcessed
|
||||
})
|
||||
// Process keywords using the extracted function
|
||||
const item_config = frontmatter as any || {}
|
||||
const { keywords, keywordsArray } = await processKeywords({
|
||||
itemConfig: item_config,
|
||||
sourceLanguage: I18N_SOURCE_LANGUAGE,
|
||||
targetLocale: locale,
|
||||
additionalKeywords,
|
||||
onKeywordsProcessed
|
||||
})
|
||||
@ -1,15 +1,16 @@
|
||||
---
|
||||
import { default_image } from "@/app/config.js";
|
||||
import Img from "@/components/polymech/image.astro";
|
||||
import { default_image } from "config/config.js";
|
||||
import Translate from "@/components/polymech/i18n.astro";
|
||||
|
||||
import { Img } from "imagetools/components"
|
||||
const { title, url, price, model, selected = false } = Astro.props;
|
||||
const thumbnail = model?.assets?.renderings[0]?.url || default_image();
|
||||
const classes = `group relative bg-white overflow-hidden group rounded-xl ${selected ? "ring-2 ring-orange-500" : ""}`
|
||||
|
||||
const thumbnail = model?.assets?.renderings[0]?.src || default_image();
|
||||
const classes = `group relative bg-white overflow-hidden group rounded-xl ${selected ? "ring-2 ring-orange-500" : ""}`;
|
||||
---
|
||||
|
||||
<div class={classes}>
|
||||
<div class="p-4 overflow-hidden group-hover:opacity-75 duration-300 transition-all">
|
||||
<div
|
||||
class="p-4 overflow-hidden group-hover:opacity-75 duration-300 transition-all"
|
||||
>
|
||||
<a href={url} title={title} aria-label={title}>
|
||||
<Img
|
||||
src={thumbnail}
|
||||
@ -30,7 +31,7 @@ const classes = `group relative bg-white overflow-hidden group rounded-xl ${sele
|
||||
<Translate>{title}</Translate>
|
||||
</a>
|
||||
</h3>
|
||||
<p class="absolute top-4 right-4">{price}</p>
|
||||
<p class="top-4 right-4"></p>
|
||||
<p class="mt-1"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,260 +0,0 @@
|
||||
<section>
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 gap-2">
|
||||
<div class="flex flex-col">
|
||||
<h1
|
||||
class="text-lg text-neutral-600 font-mono tracking-tight text-balance">
|
||||
Style guide
|
||||
</h1>
|
||||
<p class="text-sm text-balance text-neutral-500 mt-2">
|
||||
Follow the style guide to create a consistent and professional look for
|
||||
your website
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-2">
|
||||
<p class="text-lg text-neutral-600 font-mono tracking-tight text-balance">
|
||||
Typography / Inter · IBM PLex Mono
|
||||
</p>
|
||||
|
||||
<div class="mt-2">
|
||||
<div>
|
||||
<div class="space-y-2 py-3">
|
||||
<div class="grid grid-cols-1 items-start lg:grid-cols-2">
|
||||
<div><span class="text-black">Headers</span></div>
|
||||
<div>
|
||||
<span class="text-lg text-neutral-600 font-mono tracking-tight"
|
||||
>Headers</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 items-start lg:grid-cols-2">
|
||||
<div><span class="text-black">Link</span></div>
|
||||
<div>
|
||||
<a
|
||||
href="#_"
|
||||
class="text-sm hover:text-orange-600 text-neutral-500"
|
||||
>Link text</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 items-start lg:grid-cols-2">
|
||||
<div>
|
||||
<span class="text-sm text-neutral-500">Paragraph</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm text-neutral-600"
|
||||
>I am so happy, my dear friend, so absorbed in the exquisite
|
||||
sense of mere tranquil existence, that I neglect my talents.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 items-start lg:grid-cols-2">
|
||||
<div><span class="text-black">Caption</span></div>
|
||||
<div>
|
||||
<span class="text-netral-500 text-xs"
|
||||
>Picture about somethign</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 items-start lg:grid-cols-2">
|
||||
<div><span class="prose-styles">Code</span></div>
|
||||
<div>
|
||||
<code class="font-mono">tailwind.config.js</code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 items-start lg:grid-cols-2">
|
||||
<div><span class="text-black">List</span></div>
|
||||
<div>
|
||||
<ul
|
||||
class="text-neutral-600 list-inside space-y-3 list-disc"
|
||||
role="list">
|
||||
<ul
|
||||
class="text-xs space-y-1 font-mono uppercase text-neutral-500"
|
||||
role="list">
|
||||
<li>Access to premium posts</li>
|
||||
<li>Weekly newsletters</li>
|
||||
<li>Simple, secure card payment</li>
|
||||
<li>No Advertising</li>
|
||||
<li>Special discounts</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p
|
||||
class="text-lg mt-12 text-neutral-600 font-mono tracking-tight text-balance">
|
||||
Color palette
|
||||
</p>
|
||||
|
||||
<div class="grid gap-2 grid-cols-2 lg:grid-cols-3 mt-2">
|
||||
<div class="space-y-2">
|
||||
<div class="lg:justify-center p-6 h-14 bg-white rounded-lg"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="lg:justify-center p-6 h-14 rounded-t-lg bg-orange-50">
|
||||
</div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-orange-100"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-orange-200"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-orange-300"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-orange-400"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-orange-500"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-orange-600"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-orange-700"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-orange-800"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-orange-900"></div>
|
||||
<div class="lg:justify-center p-6 h-14 rounded-b-lg bg-orange-950">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="lg:justify-center p-6 h-14 rounded-t-lg bg-neutral-50">
|
||||
</div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-neutral-100"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-neutral-200"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-neutral-300"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-neutral-400"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-neutral-500"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-neutral-600"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-neutral-700"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-neutral-800"></div>
|
||||
<div class="lg:justify-center p-6 h-14 bg-neutral-900"></div>
|
||||
<div class="lg:justify-center p-6 h-14 rounded-b-lg bg-neutral-950">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
class="text-lg mt-12 text-neutral-600 font-mono tracking-tight text-balance">
|
||||
Buttons
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2 mt-6">
|
||||
<button
|
||||
type="submit"
|
||||
title="link to your page"
|
||||
aria-label="your label"
|
||||
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 justify-between">
|
||||
<span class="relative uppercase text-xs text-white">Primary</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>
|
||||
<button
|
||||
type="submit"
|
||||
title="link to your page"
|
||||
aria-label="your label"
|
||||
class="relative group overflow-hidden pl-4 justify-between text-xs text-white font-mono h-14 flex space-x-6 items-center bg-black hover:bg-neutral-200 hover:text-orange-600 duration-300 rounded-xl">
|
||||
<span class="relative uppercase text-xs">Secondary</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>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
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">Secondary</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>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -1,13 +1,8 @@
|
||||
---
|
||||
import BaseLayout from "@/layouts/BaseLayout.astro"
|
||||
import Wrapper from "@/components/containers/Wrapper.astro"
|
||||
import { getCollection } from "astro:content"
|
||||
import KBot from "@/components/polymech/kbot.astro"
|
||||
const locale = Astro.currentLocale
|
||||
const store = `${locale}/store/`
|
||||
|
||||
import Map from "@/components/polymech/map.astro"
|
||||
|
||||
const mapOptions = {
|
||||
zoom: 9,
|
||||
api_key: "AIzaSyCHsscCXksisHKMnUihOxl2X1mKny-qrqk"
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"site/*": ["src/*"],
|
||||
"config/*": ["src/site/*"]
|
||||
"config/*": ["src/config/*"]
|
||||
}
|
||||
},
|
||||
"include": [".astro/types.d.ts", "**/*.ts", "**/*.tsx", "**/*.astro"],
|
||||
|
||||
Loading…
Reference in New Issue
Block a user