diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..51cea79f --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +NUXT_PUBLIC_GHOST_KEY=ghost_pro_content_api_key +NUXT_WEBHOOK_SECRET=Webhook_secret \ No newline at end of file diff --git a/components/Blog/Card.vue b/components/Blog/Card.vue index 1ce19726..fefd0663 100644 --- a/components/Blog/Card.vue +++ b/components/Blog/Card.vue @@ -6,22 +6,24 @@ defineProps<{ item: Item showSnippet?: boolean }>() +// Change the path as required +const fallbackImage = '/img/fallback.png' +function handleImageError(event: Event) { + const target = event.target as HTMLImageElement + target.src = fallbackImage +} diff --git a/composables/useGhostClient.ts b/composables/useGhostClient.ts new file mode 100644 index 00000000..46c268e8 --- /dev/null +++ b/composables/useGhostClient.ts @@ -0,0 +1,15 @@ +import GhostContentAPI from '@tryghost/content-api' + +export function useGhostClient() { + const config = useRuntimeConfig() + const { url, key, version } = config.public.ghost + + // Create API instance with site credentials + const api = new GhostContentAPI({ + url, + key, + version, + }) + + return { api } +} \ No newline at end of file diff --git a/nuxt.config.ts b/nuxt.config.ts index 6ba01524..7c2439c6 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -8,6 +8,9 @@ export default defineNuxtConfig({ scrollBehaviorType: 'smooth', }, }, + routeRules: { + '/blog/**': { isr: 3600 }, // Regenerate every hour + }, css: [ '@unocss/reset/tailwind.css', @@ -20,7 +23,7 @@ export default defineNuxtConfig({ '@nuxtjs/fontaine', '@nuxtjs/plausible', '@nuxtjs/seo', - '@nuxtjs/robots', + '@nuxtjs/robots', '@nuxtjs/sitemap', '@nuxt/image', '@nuxt/scripts', @@ -28,7 +31,7 @@ export default defineNuxtConfig({ nitro: { output: { - dir: 'dist' + dir: 'dist' }, // Add CF Pages compatibility preset: 'cloudflare-pages', @@ -79,24 +82,24 @@ export default defineNuxtConfig({ // payloadExtraction: false, }, -// Replace your existing sitemap configuration with this: + // Replace your existing sitemap configuration with this: sitemap: { // Enable image discovery for better SEO discoverImages: true, - + // Exclude these routes from the sitemap exclude: [ '/__nuxt_island/**', '/api/**', // API routes '/_nuxt/**', // Build assets ], - + // Default settings for all pages (removed lastmod anti-pattern) defaults: { changefreq: 'monthly', priority: 0.7, }, - + // Custom configuration for specific routes routes: async () => { return [ @@ -106,7 +109,7 @@ export default defineNuxtConfig({ changefreq: 'weekly', priority: 1.0, }, - + // Main navigation pages - high priority for sitelinks { url: '/referrals', @@ -140,7 +143,7 @@ export default defineNuxtConfig({ }, ] }, - + // Split large sitemaps sitemapSize: 45000, // Max URLs per sitemap file }, @@ -176,10 +179,15 @@ export default defineNuxtConfig({ }, runtimeConfig: { + webhookSecret: '', // Maps to NUXT_WEBHOOK_SECRET // public runtime config public: { - // feed URL used for /api/blog - blogFeedUrl: 'https://medium.com/feed/@storacha', + // Ghost CMS settings used for /blog + ghost: { + url: 'https://storacha-network.ghost.io', + key: '', // Maps to NUXT_PUBLIC_GHOST_KEY + version: 'v6.0', + }, consoleUrl: import.meta.env.NUXT_PUBLIC_CONSOLE_URL || 'https://console.storacha.network', }, }, diff --git a/package.json b/package.json index 6a844122..49d137aa 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@nuxtjs/fontaine": "^0.5.0", "@nuxtjs/plausible": "^1.2.0", "@nuxtjs/seo": "^3.0.3", + "@tryghost/content-api": "^1.12.0", "@unhead/vue": "^2.0.10", "@unocss/nuxt": "^66.2.3", "@unocss/preset-icons": "^0.63.6", @@ -41,6 +42,7 @@ "devDependencies": { "@antfu/eslint-config": "^3.8.0", "@nuxt/eslint": "^1.4.1", + "@types/tryghost__content-api": "^1.3.17", "@unocss/eslint-config": "^0.63.6", "eslint": "^9.13.0", "nuxt": "^3.17.5", diff --git a/pages/blog.vue b/pages/blog.vue deleted file mode 100644 index 48998592..00000000 --- a/pages/blog.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - \ No newline at end of file diff --git a/pages/blog/[slug].vue b/pages/blog/[slug].vue new file mode 100644 index 00000000..7ee88ed5 --- /dev/null +++ b/pages/blog/[slug].vue @@ -0,0 +1,167 @@ + + + + + + diff --git a/pages/blog/index.vue b/pages/blog/index.vue new file mode 100644 index 00000000..efa42f08 --- /dev/null +++ b/pages/blog/index.vue @@ -0,0 +1,56 @@ + + + \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ad29351..f01f875a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@nuxtjs/seo': specifier: ^3.0.3 version: 3.0.3(@unhead/vue@2.0.10(vue@3.5.12(typescript@5.6.3)))(db0@0.3.2(better-sqlite3@11.10.0))(h3@1.15.3)(ioredis@5.6.1)(magicast@0.3.5)(rollup@4.44.0)(unstorage@1.16.0(db0@0.3.2(better-sqlite3@11.10.0))(ioredis@5.6.1))(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.2)(yaml@2.8.0))(vue@3.5.12(typescript@5.6.3))(webpack-sources@3.3.3) + '@tryghost/content-api': + specifier: ^1.12.0 + version: 1.12.0 '@unhead/vue': specifier: ^2.0.10 version: 2.0.10(vue@3.5.12(typescript@5.6.3)) @@ -69,6 +72,9 @@ importers: '@nuxt/eslint': specifier: ^1.4.1 version: 1.4.1(@typescript-eslint/utils@8.34.1(eslint@9.13.0(jiti@2.4.2))(typescript@5.6.3))(@vue/compiler-sfc@3.5.17)(eslint-import-resolver-node@0.3.9)(eslint@9.13.0(jiti@2.4.2))(magicast@0.3.5)(typescript@5.6.3)(vite@6.3.5(@types/node@24.0.3)(jiti@2.4.2)(terser@5.43.1)(tsx@4.19.2)(yaml@2.8.0)) + '@types/tryghost__content-api': + specifier: ^1.3.17 + version: 1.3.17 '@unocss/eslint-config': specifier: ^0.63.6 version: 0.63.6(eslint@9.13.0(jiti@2.4.2))(typescript@5.6.3) @@ -1838,6 +1844,9 @@ packages: peerDependencies: vue: ^2.7.0 || ^3.0.0 + '@tryghost/content-api@1.12.0': + resolution: {integrity: sha512-rU9yrBHlVIohOLSpC9PeH9evMeNCk4OKGvI0VEwNwuwWlsQIpBn3o79DzleEN0tbMynlObYAG0IJ/EGkIumtJA==} + '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -1891,6 +1900,9 @@ packages: '@types/triple-beam@1.3.5': resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/tryghost__content-api@1.3.17': + resolution: {integrity: sha512-4DASYoK0hP1+XDyLS/8IZevalQRJuPmyPmfxdT1hnYRjxnJkgusATeDc/7QXA2izMZ/+cWkgdZDeTN2cBW+EoA==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -2740,6 +2752,9 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.21: resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} engines: {node: ^10 || ^12 || >=14} @@ -2747,6 +2762,9 @@ packages: peerDependencies: postcss: ^8.1.0 + axios@1.12.2: + resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} + b4a@1.6.7: resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} @@ -3021,6 +3039,10 @@ packages: colorspace@1.1.4: resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} @@ -3331,6 +3353,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -3528,6 +3554,10 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.17.19: resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} engines: {node: '>=12'} @@ -4005,6 +4035,15 @@ packages: fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + fontaine@0.5.0: resolution: {integrity: sha512-vPDSWKhVAfTx4hRKT777+N6Szh2pAosAuzLpbppZ6O3UdD/1m6OlHjNcC3vIbgkRTIcLjzySLHXzPeLO2rE8cA==} @@ -4015,6 +4054,10 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -4204,6 +4247,10 @@ packages: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + hash-sum@2.0.0: resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} @@ -5657,6 +5704,9 @@ packages: protocols@2.0.1: resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -9089,6 +9139,12 @@ snapshots: '@tanstack/virtual-core': 3.10.8 vue: 3.5.12(typescript@5.6.3) + '@tryghost/content-api@1.12.0': + dependencies: + axios: 1.12.2 + transitivePeerDependencies: + - debug + '@trysound/sax@0.2.0': {} '@tybys/wasm-util@0.9.0': @@ -9144,6 +9200,8 @@ snapshots: '@types/triple-beam@1.3.5': {} + '@types/tryghost__content-api@1.3.17': {} + '@types/unist@3.0.3': {} '@types/web-bluetooth@0.0.20': {} @@ -10409,6 +10467,8 @@ snapshots: async@3.2.6: {} + asynckit@0.4.0: {} + autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.25.0 @@ -10419,6 +10479,14 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 + axios@1.12.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + b4a@1.6.7: {} balanced-match@1.0.2: {} @@ -10725,6 +10793,10 @@ snapshots: color: 3.2.1 text-hex: 1.0.0 + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@10.0.1: {} commander@12.1.0: {} @@ -10991,6 +11063,8 @@ snapshots: defu@6.1.4: {} + delayed-stream@1.0.0: {} + denque@2.1.0: {} depd@2.0.0: {} @@ -11167,6 +11241,13 @@ snapshots: dependencies: es-errors: 1.3.0 + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esbuild@0.17.19: optionalDependencies: '@esbuild/android-arm': 0.17.19 @@ -11842,6 +11923,8 @@ snapshots: fn.name@1.1.0: {} + follow-redirects@1.15.11: {} + fontaine@0.5.0(webpack-sources@3.3.3): dependencies: '@capsizecss/metrics': 2.2.0 @@ -11872,6 +11955,14 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -12080,6 +12171,10 @@ snapshots: has-symbols@1.1.0: {} + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hash-sum@2.0.0: {} hasown@2.0.2: @@ -13994,6 +14089,8 @@ snapshots: protocols@2.0.1: {} + proxy-from-env@1.1.0: {} + pump@3.0.2: dependencies: end-of-stream: 1.4.4 diff --git a/public/ghost-cards.css b/public/ghost-cards.css new file mode 100644 index 00000000..8a7b9da4 --- /dev/null +++ b/public/ghost-cards.css @@ -0,0 +1,2639 @@ +.kg-audio-card, +.kg-audio-card * { + box-sizing: border-box +} + +.kg-audio-card { + display: flex; + width: 100%; + min-height: 96px; + border-radius: 6px; + padding: 4px; + background: #fff; + color: #222; + box-shadow: inset 0 0 0 1px rgba(124, 139, 154, .25); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif +} + +.kg-audio-card+.kg-audio-card { + margin-top: 1em +} + +.kg-audio-thumbnail { + display: flex; + justify-content: center; + align-items: center; + width: 64px; + min-width: 80px; + margin: 6px; + background: 0 0; + object-fit: cover; + aspect-ratio: 1/1; + border-radius: 3px +} + +.kg-audio-thumbnail.placeholder { + background: var(--ghost-accent-color) +} + +.kg-audio-thumbnail.placeholder svg { + width: 24px; + height: 24px; + fill: #fff +} + +.kg-audio-player-container { + position: relative; + display: flex; + flex-direction: column; + justify-content: space-between; + width: 100%; + --seek-before-width: 0%; + --volume-before-width: 100%; + --buffered-width: 0% +} + +.kg-audio-title { + width: 100%; + margin: 6px 0 0; + padding: 8px 12px; + border: 0; + font-family: inherit; + font-size: 12px; + font-weight: 600; + line-height: 1.15em; + background: 0 0 +} + +.kg-audio-player { + display: flex; + flex-grow: 1; + align-items: center; + padding: 8px 12px +} + +.kg-audio-current-time, +.kg-audio-time { + font-family: inherit; + font-size: 12.5px; + font-weight: 500; + line-height: 1em; + white-space: nowrap +} + +.kg-audio-current-time { + min-width: 38px; + padding: 0 4px +} + +.kg-audio-time { + width: 56px; + color: #ababab +} + +.kg-audio-duration { + padding: 0 4px +} + +.kg-audio-pause-icon, +.kg-audio-play-icon { + position: relative; + bottom: 1px; + padding: 0 4px 0 0; + font-size: 0; + background: 0 0 +} + +.kg-audio-hide { + display: none !important +} + +.kg-audio-pause-icon svg, +.kg-audio-play-icon svg { + width: 14px; + height: 14px; + fill: currentColor +} + +.kg-audio-seek-slider { + flex-grow: 1; + margin: 0 4px +} + +@media (max-width:640px) { + .kg-audio-seek-slider { + display: none + } +} + +.kg-audio-playback-rate { + min-width: 37px; + padding: 0 4px; + font-family: inherit; + font-size: 12.5px; + font-weight: 600; + line-height: 1em; + text-align: left; + background: 0 0; + white-space: nowrap +} + +@media (max-width:640px) { + .kg-audio-playback-rate { + padding-left: 8px + } +} + +.kg-audio-mute-icon, +.kg-audio-unmute-icon { + position: relative; + bottom: -1px; + padding: 0 4px; + font-size: 0; + background: 0 0 +} + +@media (max-width:640px) { + + .kg-audio-mute-icon, + .kg-audio-unmute-icon { + margin-left: auto + } +} + +.kg-audio-mute-icon svg, +.kg-audio-unmute-icon svg { + width: 16px; + height: 16px; + fill: currentColor +} + +.kg-audio-volume-slider { + width: 80px +} + +@media (max-width:400px) { + .kg-audio-volume-slider { + display: none + } +} + +.kg-audio-seek-slider::before, +.kg-audio-volume-slider::before { + content: ""; + position: absolute; + left: 0; + width: var(--seek-before-width) !important; + height: 4px; + cursor: pointer; + background-color: currentColor; + border-radius: 2px +} + +.kg-audio-volume-slider::before { + width: var(--volume-before-width) !important +} + +.kg-audio-player-container input[type=range] { + position: relative; + -webkit-appearance: none; + background: 0 0; + height: auto; + padding: 0; + border: 0 +} + +.kg-audio-player-container input[type=range]:focus, +.kg-video-card input[type=range]:focus { + outline: 0 +} + +.kg-audio-player-container input[type=range]::-webkit-slider-thumb { + -webkit-appearance: none +} + +.kg-audio-player-container button, +.kg-video-card button { + display: flex; + align-items: center; + border: 0; + cursor: pointer +} + +.kg-audio-player-container input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 4px; + cursor: pointer; + background: rgba(124, 139, 154, .25); + border-radius: 2px +} + +.kg-audio-player-container input[type=range]::-webkit-slider-thumb { + position: relative; + box-sizing: content-box; + width: 13px; + height: 13px; + margin: -5px 0 0; + border: 0; + cursor: pointer; + background: #fff; + border-radius: 50%; + box-shadow: 0 0 0 1px rgba(0, 0, 0, .08), 0 1px 4px rgba(0, 0, 0, .24) +} + +.kg-audio-player-container input[type=range]:active::-webkit-slider-thumb { + transform: scale(1.2) +} + +.kg-audio-player-container input[type=range]::-moz-range-track { + width: 100%; + height: 4px; + cursor: pointer; + background: rgba(124, 139, 154, .25); + border-radius: 2px +} + +.kg-audio-player-container input[type=range]::-moz-range-progress { + background: currentColor; + border-radius: 2px +} + +.kg-audio-player-container input[type=range]::-moz-range-thumb { + box-sizing: content-box; + width: 13px; + height: 13px; + border: 0; + cursor: pointer; + background: #fff; + border-radius: 50%; + box-shadow: 0 0 0 1px rgba(0, 0, 0, .08), 0 1px 4px rgba(0, 0, 0, .24) +} + +.kg-audio-player-container input[type=range]:active::-moz-range-thumb { + transform: scale(1.2) +} + +.kg-audio-player-container input[type=range]::-ms-track { + width: 100%; + height: 3px; + border: solid transparent; + color: transparent; + cursor: pointer; + background: 0 0 +} + +.kg-audio-player-container input[type=range]::-ms-fill-lower { + background: #fff +} + +.kg-audio-player-container input[type=range]::-ms-fill-upper { + background: currentColor +} + +.kg-audio-player-container input[type=range]::-ms-thumb { + box-sizing: content-box; + width: 13px; + height: 13px; + border: 0; + cursor: pointer; + background: #fff; + border-radius: 50%; + box-shadow: 0 0 0 1px rgba(0, 0, 0, .08), 0 1px 4px rgba(0, 0, 0, .24) +} + +.kg-audio-player-container input[type=range]:active::-ms-thumb { + transform: scale(1.2) +} + +.kg-blockquote-alt { + font-size: 1.3em; + font-style: italic; + line-height: 1.7em; + text-align: center; + padding: 0 2.5em +} + +@media (max-width:800px) { + .kg-blockquote-alt { + font-size: 1.2em; + padding-left: 2em; + padding-right: 2em + } +} + +@media (max-width:600px) { + .kg-blockquote-alt { + font-size: 1.2em; + padding-left: 1.75em; + padding-right: 1.75em + } +} + +.kg-bookmark-card, +.kg-bookmark-card * { + box-sizing: border-box +} + +.kg-bookmark-card, +.kg-bookmark-publisher { + position: relative +} + +.kg-bookmark-card a.kg-bookmark-container, +.kg-bookmark-card a.kg-bookmark-container:hover { + display: flex; + background: #fff; + text-decoration: none; + border-radius: 6px; + border: 1px solid rgb(124 139 154/25%); + overflow: hidden; + color: #222 +} + +.kg-bookmark-content { + display: flex; + flex-direction: column; + flex-grow: 1; + flex-basis: 100%; + align-items: flex-start; + justify-content: flex-start; + padding: 16px; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif +} + +.kg-bookmark-title { + font-size: 13px; + line-height: 1.4em; + font-weight: 600 +} + +.kg-bookmark-description { + display: -webkit-box; + font-size: 12px; + line-height: 1.5em; + margin-top: 3px; + font-weight: 400; + overflow-y: hidden; + opacity: .7; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical +} + +.kg-bookmark-metadata { + display: flex; + align-items: center; + margin-top: 16px +; + width: 100%; + font-size: 12px; + font-weight: 500; + white-space: nowrap +} + +.kg-bookmark-metadata>:not(img) { + opacity: .7 +} + +.kg-bookmark-icon { + width: 20px; + height: 20px; + margin-right: 6px +} + +.kg-bookmark-author { + display: inline +} + +.kg-bookmark-publisher { + text-overflow: ellipsis; + overflow: hidden; + max-width: 240px; + white-space: nowrap; + display: block; + line-height: 1.65em +} + +.kg-bookmark-metadata>span:nth-of-type(2) { + font-weight: 400 +} + + +.kg-bookmark-metadata>span:last-of-type { + overflow: hidden; + text-overflow: ellipsis +} + +.kg-bookmark-thumbnail { + position: relative; + flex-grow: 1; + min-width: 33% +} + +.kg-bookmark-thumbnail img, +.kg-collection-card-img img { + position: absolute; + width: 100%; + height: 100%; + object-fit: cover +} + +.kg-bookmark-thumbnail img { + top: 0; + left: 0; + border-radius: 0 2px 2px 0 +} + +.kg-button-card, +.kg-button-card * { + box-sizing: border-box +} + +.kg-button-card, +.kg-button-card a.kg-btn { + display: flex; + position: static; + align-items: center +} + +.kg-button-card { + width: 100%; + justify-content: center +} + +.kg-button-card.kg-align-left { + justify-content: flex-start +} + +.kg-button-card a.kg-btn { + padding: 0 1.2em; + height: 2.4em; + line-height: 1em; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + font-size: .95em; + font-weight: 600; + text-decoration: none; + border-radius: 5px; + transition: opacity .2s ease-in-out +} + +.kg-button-card a.kg-btn:hover { + opacity: .85 +} + +.kg-button-card a.kg-btn-accent { + background-color: var(--ghost-accent-color); + color: #fff +} + +.kg-callout-card, +.kg-callout-card * { + box-sizing: border-box +} + +.kg-callout-card { + display: flex; + padding: 1em 1.6em; + border-radius: 8px +} + +.kg-callout-card-grey { + background: rgba(124, 139, 154, .13) +} + +.kg-callout-card-white { + background: 0 0; + box-shadow: inset 0 0 0 1px rgba(124, 139, 154, .2) +} + +.kg-callout-card-blue { + background: rgba(33, 172, 232, .12) +} + +.kg-callout-card-green { + background: rgba(52, 183, 67, .12) +} + +.kg-callout-card-yellow { + background: rgba(240, 165, 15, .13) +} + +.kg-callout-card-red { + background: rgba(209, 46, 46, .11) +} + +.kg-callout-card-pink { + background: rgba(225, 71, 174, .11) +} + +.kg-callout-card-purple { + background: rgba(135, 85, 236, .12) +} + +.kg-callout-card-accent { + background: var(--ghost-accent-color); + color: #fff +} + +.kg-callout-card.kg-callout-card-accent a { + color: #fff; + text-decoration: underline +} + +.kg-callout-card div.kg-callout-emoji { + padding-right: .8em; + line-height: 1.25em; + font-size: 1em +} + +.kg-callout-card div.kg-callout-text { + font-size: .95em; + line-height: 1.5em +} + +.kg-callout-card+.kg-callout-card { + margin-top: 1em +} + +.kg-collection-card { + width: 100%; + margin-top: 6vmin +} + +.kg-collection-card+* { + margin-top: 6vmin +} + +.kg-collection-card-title { + margin: .8rem 0 1.6rem; + font-size: 1.5rem; + font-weight: 700; + text-transform: uppercase +} + +a.kg-collection-card-post-wrapper { + text-decoration: none; + color: var(--text-color) +} + +a.kg-collection-card-post-wrapper:hover { + opacity: 1 +} + +.kg-collection-card-post { + display: flex; + gap: 3.2rem +} + +.kg-collection-card-img { + position: relative; + aspect-ratio: 3/2 +} + +.kg-collection-card-img img { + inset: 0 +} + +a.kg-collection-card-post-wrapper:hover img { + opacity: .92; + transition: all .2s ease +} + +.kg-collection-card-content { + display: flex; + flex-direction: column; + font-size: 1.6rem +} + +h2.kg-collection-card-post-title, +p.kg-collection-card-post-excerpt { + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2 +} + +h2.kg-collection-card-post-title { + margin: 0; + font-size: 2rem +} + +p.kg-collection-card-post-excerpt { + margin-top: 1.2rem; + line-height: 1.4 +} + +.kg-collection-card-post-meta { + display: flex; + opacity: .5; + margin-top: 1.2rem; + font-size: 1.3rem; + font-weight: 500 +} + +.kg-collection-card-list { + display: flex; + flex-direction: column; + gap: 3.2rem +} + +@media (max-width:767px) { + .kg-collection-card-list .kg-collection-card-post { + flex-direction: column + } +} + +.kg-collection-card-list .kg-collection-card-img { + flex: 0 0 30% +} + +.kg-collection-card-grid { + display: grid; + grid-template-columns: repeat(1, minmax(0, 1fr)); + gap: 2.4rem +} + +@media (min-width:640px) { + .kg-collection-card-grid:not(.columns-1) { + grid-template-columns: repeat(2, minmax(0, 1fr)) + } +} + +@media (min-width:1024px) { + .kg-collection-card-grid:not(.columns-1):not(.columns-2) { + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 3.2rem + } + + .kg-collection-card-grid.columns-1 { + gap: 4.8rem + } + + .kg-collection-card-grid.columns-2 { + gap: 4rem + } +} + +@media (min-width:1280px) { + .kg-collection-card-grid:not(.columns-1):not(.columns-2):not(.columns-3) { + grid-template-columns: repeat(4, minmax(0, 1fr)) + } +} + +.kg-collection-card-grid .kg-collection-card-post { + flex-direction: column; + gap: 1.2rem +} + +@media (min-width:1024px) { + .kg-collection-card-grid.columns-1 .kg-collection-card-post { + gap: 2rem + } + + .kg-collection-card-grid.columns-2 .kg-collection-card-post { + gap: 1.6rem + } +} + +.kg-collection-card-grid.columns-1 .kg-collection-card-img, +.kg-collection-card-grid.columns-2 .kg-collection-card-img { + aspect-ratio: 16/9 +} + +.kg-collection-card-grid .kg-collection-card-content { + font-size: 1.5rem +} + +@media (min-width:640px) { + .kg-collection-card-grid .kg-collection-card-content { + font-size: 1.6rem + } +} + +@media (min-width:1024px) { + .kg-collection-card-grid.columns-1 .kg-collection-card-content { + font-size: 1.8rem + } + + .kg-collection-card-grid.columns-3 .kg-collection-card-content, + .kg-collection-card-grid.columns-4 .kg-collection-card-content { + font-size: 1.5rem + } +} + +.kg-collection-card-grid h2.kg-collection-card-post-title { + font-size: 1.7rem +} + +.kg-collection-card-grid .kg-collection-card-post-meta { + font-size: 1.25rem +} + +@media (min-width:640px) { + .kg-collection-card-grid h2.kg-collection-card-post-title { + font-size: 1.9rem + } + + .kg-collection-card-grid.columns-1 h2.kg-collection-card-post-title { + font-size: 2rem + } + + .kg-collection-card-grid:not(.columns-3):not(.columns-4) .kg-collection-card-post-meta { + font-size: 1.3rem + } +} + +@media (min-width:1024px) { + .kg-collection-card-grid.columns-1 h2.kg-collection-card-post-title { + font-size: 3.6rem + } + + .kg-collection-card-grid.columns-2 h2.kg-collection-card-post-title { + font-size: 2rem + } + + .kg-collection-card-grid.columns-1 .kg-collection-card-post-meta { + font-size: 1.4rem + } +} + +@media (min-width:1280px) { + .kg-collection-card-grid.columns-4 h2.kg-collection-card-post-title { + font-size: 1.7rem + } +} + +.kg-cta-card, +.kg-cta-card * { + box-sizing: border-box +} + +.kg-cta-card { + display: flex; + flex-direction: column; + border-radius: 8px +} + +.kg-cta-bg-grey { + background: rgba(151, 163, 175, .14) +} + +.kg-cta-bg-white { + background: 0 0; + box-shadow: inset 0 0 0 1px rgba(124, 139, 154, .2) +} + +.kg-cta-bg-blue { + background: rgba(33, 172, 232, .12) +} + +.kg-cta-bg-green { + background: rgba(52, 183, 67, .12) +} + +.kg-cta-bg-yellow { + background: rgba(240, 165, 15, .13) +} + +.kg-cta-bg-red { + background: rgba(209, 46, 46, .11) +} + +.kg-cta-bg-pink { + background: rgba(225, 71, 174, .11) +} + +.kg-cta-bg-purple { + background: rgba(135, 85, 236, .12) +} + +.kg-cta-sponsor-label-wrapper { + margin: 0 1.5em; + padding: .7em 0; + border-bottom: 1px solid rgba(124, 139, 154, .2) +} + +@media (max-width:600px) { + .kg-cta-sponsor-label-wrapper { + margin: 0 1.25em; + padding: .5em 0 + } +} + +.kg-cta-bg-none .kg-cta-sponsor-label-wrapper { + margin: 0; + padding-top: 0 +} + +.kg-cta-bg-none.kg-cta-no-dividers .kg-cta-sponsor-label-wrapper, +.kg-cta-has-img .kg-cta-sponsor-label-wrapper:not(.kg-cta-bg-none .kg-cta-sponsor-label-wrapper):not(.kg-cta-minimal .kg-cta-sponsor-label-wrapper) { + border-bottom: 0 +} + +.kg-cta-sponsor-label { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + text-wrap: pretty +} + +.kg-cta-sponsor-label span:not(a span) { + color: color-mix(in srgb, currentColor 45%, transparent) +} + +.kg-cta-sponsor-label a, +.kg-cta-sponsor-label a span, +.kg-cta-text a { + color: currentColor; + transition: opacity .15s ease-in-out +} + +.kg-cta-sponsor-label a:hover, +.kg-cta-sponsor-label a:hover span { + color: currentColor; + opacity: .85 +} + +.kg-cta-link-accent .kg-cta-sponsor-label a { + color: var(--ghost-accent-color) +} + +.kg-cta-content { + display: flex; + padding: 1em; + gap: 1.5em +} + +@media (max-width:600px) { + .kg-cta-content { + padding: 1.25em; + gap: 1.25em + } +} + +.kg-cta-has-img .kg-cta-sponsor-label-wrapper+.kg-cta-content:not(.kg-cta-bg-none .kg-cta-content):not(.kg-cta-minimal .kg-cta-content) { + padding-top: 0 +} + +.kg-cta-bg-none .kg-cta-content { + padding: 1em 0; + border-bottom: 1px solid rgba(124, 139, 154, .2) +} + +.kg-cta-bg-none.kg-cta-no-dividers .kg-cta-content { + padding: 0; + border-bottom: none +} + +.kg-cta-bg-none:not(.kg-cta-no-dividers) .kg-cta-content:not(.kg-cta-sponsor-label-wrapper+.kg-cta-content) { + border-top: 1px solid rgba(124, 139, 154, .2) +} + +@media (max-width:600px) { + .kg-cta-bg-none .kg-cta-content { + padding: 1.25em 0 + } +} + +.kg-cta-minimal .kg-cta-content { + flex-direction: row +} + +@media (max-width:600px) { + .kg-cta-minimal .kg-cta-content { + flex-direction: column; + gap: 1.6rem + } +} + +.kg-cta-immersive .kg-cta-content { + flex-direction: column +} + +.kg-cta-content-inner { + display: flex; + flex-direction: column; + gap: 1.5em +} + +@media (max-width:600px) { + .kg-cta-content-inner { + gap: 1.25em + } +} + +.kg-cta-immersive.kg-cta-centered .kg-cta-content-inner, +a.kg-cta-button { + align-items: center +} + +.kg-cta-image-container { + flex-shrink: 0 +} + +.kg-cta-image-container img { + width: 100%; + height: auto; + margin: 0; + object-fit: cover; + border-radius: 6px +} + +.kg-cta-minimal .kg-cta-image-container img { + width: 64px; + height: 64px +} + +@media (max-width:600px) { + .kg-cta-minimal .kg-cta-image-container img { + width: 52px; + height: 52px + } +} + +.kg-cta-text p { + margin: 0; + line-height: 1.5em; + text-wrap: pretty +} + +.kg-cta-bg-none .kg-cta-text p { + line-height: unset +} + +.kg-cta-immersive.kg-cta-centered .kg-cta-text { + text-align: center +} + +.kg-cta-text p+p { + margin-top: 1.25em +} + +.kg-cta-text a:hover { + color: currentColor; + opacity: .85 +} + +.kg-cta-link-accent .kg-cta-text a, +.kg-file-card-icon svg, +.kg-header-card h2.kg-header-card-header a, +.kg-header-card h3.kg-header-card-subheader a { + color: var(--ghost-accent-color) +} + +a.kg-cta-button { + display: flex; + position: static; + justify-content: center; + padding: 0 1em; + height: 2.5em; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + font-size: .95em; + font-weight: 500; + line-height: 1.65; + text-decoration: none; + border-radius: 6px; + transition: opacity .15s ease-in-out; + width: max-content +} + +a.kg-cta-button:hover { + opacity: .85 +} + +a.kg-cta-button.kg-style-accent { + background-color: var(--ghost-accent-color) +} + +.kg-cta-immersive.kg-cta-has-img a.kg-cta-button { + width: 100% +} + +.kg-file-card, +.kg-file-card * { + box-sizing: border-box +} + +.kg-file-card { + display: flex +} + +.kg-file-card a.kg-file-card-container { + display: flex; + align-items: stretch; + justify-content: space-between; + padding: 12px; + min-height: 92px; + background: #fff; + color: #222; + border: 1px solid rgb(124 139 154/25%); + border-radius: 5px; + transition: all ease-in-out .35s; + text-decoration: none; + width: 100%; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif +} + +.kg-file-card a.kg-file-card-container:hover { + border: 1px solid rgb(124 139 154/35%) +} + +.kg-file-card-contents { + display: flex; + flex-direction: column; + justify-content: space-between; + margin: 4px 8px; + width: 100% +} + +.kg-file-card-title { + font-size: 12px; + font-weight: 600; + line-height: 1.3em +} + +.kg-file-card-caption { + font-size: 12px; + line-height: 1.3em; + opacity: .7 +} + +.kg-file-card-title+.kg-file-card-caption { + flex-grow: 1; + margin-top: 3px +} + +.kg-file-card-metadata { + display: inline; + font-size: 12px; + line-height: 1.3em; + margin-top: 5px +} + +.kg-file-card-filename { + display: inline; + font-weight: 500 +} + +.kg-file-card-filesize { + display: inline-block; + font-size: 12px; + opacity: .6 +} + +.kg-file-card-filesize:before { + display: inline-block; + content: "รขโ‚ฌยข"; + margin-left: 6px; + margin-right: 6px +} + +.kg-file-card-icon { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 80px; + min-width: 80px; + height: 100%; + min-height: 80px +} + +.kg-file-card-icon:before { + position: absolute; + display: block; + content: ""; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: currentColor; + opacity: .06; + transition: opacity ease-in-out .35s; + border-radius: 3px +} + +.kg-file-card a.kg-file-card-container:hover .kg-file-card-icon:before { + opacity: .08 +} + +.kg-file-card-icon svg { + width: 20px; + height: 20px +} + +.kg-file-card-medium a.kg-file-card-container { + min-height: 72px +} + +.kg-file-card-medium .kg-file-card-caption { + opacity: 1; + font-weight: 500 +} + +.kg-file-card-small a.kg-file-card-container { + align-items: center; + min-height: 52px +} + +.kg-file-card-small .kg-file-card-metadata { + font-size: 12px; + margin-top: 0 +} + +.kg-file-card-small .kg-file-card-icon svg { + width: 20px; + height: 20px +} + +.kg-file-card+.kg-file-card { + margin-top: 1em +} + +.kg-gallery-card, +.kg-gallery-card * { + box-sizing: border-box +} + +.kg-gallery-card, +.kg-image-card { + --gap: 1.2rem +} + +.kg-gallery-card:not(.kg-card-hascaption)+.kg-gallery-card, +.kg-gallery-card:not(.kg-card-hascaption)+.kg-image-card, +.kg-image-card:not(.kg-card-hascaption)+.kg-gallery-card, +.kg-image-card:not(.kg-card-hascaption)+.kg-image-card { + margin-top: var(--gap) +} + +.kg-gallery-container { + position: relative +} + +.kg-gallery-row { + display: flex; + flex-direction: row; + justify-content: center +} + +.kg-gallery-image img { + display: block; + margin: 0; + width: 100%; + height: 100% +} + +.kg-gallery-row:not(:first-of-type) { + margin: var(--gap)0 0 +} + +.kg-gallery-image:not(:first-of-type) { + margin: 0 0 0 var(--gap) +} + +@media (max-width:600px) { + + .kg-gallery-card, + .kg-image-card { + --gap: 0.6rem + } +} + +.kg-header-card, +.kg-header-card * { + box-sizing: border-box +} + +.kg-header-card { + padding: 12vmin 4em; + min-height: 60vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center +} + +.kg-header-card.kg-size-small { + padding-top: 14vmin; + padding-bottom: 14vmin; + min-height: 40vh +} + +.kg-header-card.kg-size-large { + padding-top: 18vmin; + padding-bottom: 18vmin; + min-height: 80vh +} + +.kg-header-card.kg-align-left { + text-align: left; + align-items: flex-start +} + +.kg-header-card.kg-style-dark { + background: #151515; + color: #fff +} + +.kg-header-card.kg-style-light { + background-color: #fafafa +} + +.kg-header-card.kg-style-accent, +.kg-header-card.kg-style-accent.kg-v2 { + background-color: var(--ghost-accent-color) +} + +.kg-header-card.kg-style-image { + position: relative; + background-color: #e7e7e7; + background-size: cover; + background-position: center +} + +.kg-header-card.kg-style-image::before { + position: absolute; + display: block; + content: ""; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: linear-gradient(0deg, transparent, rgba(0, 0, 0, .2)) +} + +.kg-header-card h2.kg-header-card-header { + font-size: 5em; + font-weight: 700; + line-height: 1.1em; + letter-spacing: -.01em; + margin: 0 +} + +.kg-header-card h2.kg-header-card-header strong { + font-weight: 800 +} + +.kg-header-card.kg-size-small h2.kg-header-card-header { + font-size: 4em +} + +.kg-header-card.kg-size-large h2.kg-header-card-header { + font-size: 6em +} + +.kg-header-card h3.kg-header-card-subheader { + font-size: 1.3em; + font-weight: 500; + line-height: 1.4em; + margin: 0; + max-width: 40em +} + +.kg-header-card h2+h3.kg-header-card-subheader { + margin: .35em 0 0 +} + +.kg-header-card .kg-header-card-subheading strong, +.kg-header-card h3.kg-header-card-subheader strong, +.kg-signup-card .kg-signup-card-subheading strong { + font-weight: 600 +} + +.kg-header-card.kg-size-small h3.kg-header-card-subheader { + font-size: 1.25em +} + +.kg-header-card.kg-size-large h3.kg-header-card-subheader { + font-size: 1.75em +} + +.kg-header-card:not(.kg-style-light) h2.kg-header-card-header, +.kg-header-card:not(.kg-style-light) h3.kg-header-card-subheader { + color: #fff +} + +.kg-header-card.kg-style-accent h3.kg-header-card-subheader, +.kg-header-card.kg-style-image h3.kg-header-card-subheader, +.kg-product-card-rating-active.kg-product-card-rating-star svg { + opacity: 1 +} + +.kg-header-card.kg-style-image a.kg-header-card-button, +.kg-header-card.kg-style-image h2.kg-header-card-header, +.kg-header-card.kg-style-image h3.kg-header-card-subheader { + z-index: 999 +} + +.kg-header-card.kg-style-accent h2.kg-header-card-header a, +.kg-header-card.kg-style-accent h3.kg-header-card-subheader a, +.kg-header-card.kg-style-image h2.kg-header-card-header a, +.kg-header-card.kg-style-image h3.kg-header-card-subheader a { + color: #fff +} + +.kg-header-card a.kg-header-card-button { + display: flex; + position: static; + align-items: center; + fill: #fff; + background: #fff; + border-radius: 3px; + outline: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + font-size: 1.05em; + font-weight: 600; + line-height: 1em; + text-align: center; + text-decoration: none; + letter-spacing: .2px; + white-space: nowrap; + text-overflow: ellipsis; + color: #151515; + height: 2.7em; + padding: 0 1.2em; + transition: opacity .2s ease +} + +.kg-header-card h2+a.kg-header-card-button, +.kg-header-card h3+a.kg-header-card-button { + margin: 1.75em 0 0 +} + +.kg-header-card a.kg-header-card-button:hover { + opacity: .85 +} + +.kg-header-card.kg-size-large a.kg-header-card-button { + font-size: 1.1em; + height: 2.9em +} + +.kg-header-card.kg-size-large h2+a.kg-header-card-button, +.kg-header-card.kg-size-large h3+a.kg-header-card-button { + margin-top: 2em +} + +.kg-header-card.kg-size-small a.kg-header-card-button { + height: 2.4em; + font-size: 1em +} + +.kg-header-card.kg-size-small h2+a.kg-header-card-button, +.kg-header-card.kg-size-small h3+a.kg-header-card-button { + margin-top: 1.5em +} + +.kg-header-card.kg-style-dark a.kg-header-card-button, +.kg-header-card.kg-style-image a.kg-header-card-button { + background: #fff; + color: #151515 +} + +.kg-header-card.kg-style-light a.kg-header-card-button { + background: var(--ghost-accent-color); + color: #fff +} + +.kg-header-card.kg-style-accent a.kg-header-card-button { + background: #fff; + color: #151515 +} + +@media (max-width:640px) { + .kg-header-card { + padding-left: 1em; + padding-right: 1em + } + + .kg-header-card h2.kg-header-card-header { + font-size: 3.5em + } + + .kg-header-card.kg-size-large h2.kg-header-card-header { + font-size: 4em + } + + .kg-header-card.kg-size-small h2.kg-header-card-header { + font-size: 3em + } + + .kg-header-card h3.kg-header-card-subheader { + font-size: 1.25em + } + + .kg-header-card.kg-size-large h3.kg-header-card-subheader { + font-size: 1.3em + } + + .kg-header-card.kg-size-small h3.kg-header-card-subheader { + font-size: 1em + } +} + +.kg-header-card.kg-v2 { + position: relative; + padding: 0; + min-height: initial; + text-align: initial; + box-sizing: border-box +} + +.kg-header-card.kg-v2 * { + box-sizing: border-box +} + +.kg-header-card.kg-v2 a, +.kg-header-card.kg-v2 a span, +.kg-signup-card a, +.kg-signup-card a span { + color: currentColor +} + +.kg-header-card-content { + width: 100% +} + +.kg-layout-split .kg-header-card-content { + display: grid; + grid-template-columns: 1fr 1fr +} + +.kg-header-card-text { + position: relative; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + height: 100%; + padding: min(6.4vmax, 120px) min(4vmax, 80px); + background-size: cover; + background-position: center; + text-align: left +} + +.kg-width-wide .kg-header-card-text { + padding: min(10vmax, 220px) min(6.4vmax, 140px) +} + +.kg-width-full .kg-header-card-text { + padding: min(12vmax, 260px)0 +} + +.kg-layout-split .kg-header-card-text { + padding: min(12vmax, 260px) min(4vmax, 80px) +} + +.kg-layout-split.kg-content-wide .kg-header-card-text { + padding: min(10vmax, 220px)0 min(10vmax, 220px) min(4vmax, 80px) +} + +.kg-layout-split.kg-content-wide.kg-swapped .kg-header-card-text { + padding: min(10vmax, 220px) min(4vmax, 80px) min(10vmax, 220px)0 +} + +.kg-swapped .kg-header-card-text { + grid-row: 1 +} + +.kg-header-card-text.kg-align-center { + align-items: center; + text-align: center +} + +.kg-header-card.kg-style-image .kg-header-card-subheading, +.kg-header-card.kg-style-image h2.kg-header-card-heading, +.kg-header-card.kg-style-image.kg-v2 .kg-header-card-button { + z-index: 999 +} + +.kg-header-card>picture>.kg-header-card-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + background-color: #fff; + pointer-events: none +} + +.kg-header-card-content .kg-header-card-image { + width: 100%; + height: 0; + min-height: 100%; + object-fit: cover; + object-position: center +} + +.kg-content-wide .kg-header-card-content .kg-header-card-image { + height: 100%; + padding: 5.6em 0; + object-fit: contain +} + +.kg-header-card h2.kg-header-card-heading, +.kg-signup-card h2.kg-signup-card-heading { + margin: 0; + font-size: clamp(1.7em, 4vw, 2.5em); + font-weight: 700; + line-height: 1.05em; + letter-spacing: -.01em +} + +.kg-header-card.kg-width-wide h2.kg-header-card-heading { + font-size: clamp(1.7em, 5vw, 3.3em) +} + +.kg-header-card.kg-width-full h2.kg-header-card-heading { + font-size: clamp(1.9em, 5.6vw, 4.2em) +} + +.kg-header-card.kg-width-full.kg-layout-split h2.kg-header-card-heading, +.kg-signup-card.kg-width-full.kg-layout-split h2.kg-signup-card-heading { + font-size: clamp(1.9em, 4vw, 3.3em) +} + +.kg-header-card-subheading { + margin: 0 0 2em +} + +.kg-header-card .kg-header-card-subheading { + max-width: 40em; + margin: 0; + font-size: clamp(1.05em, 2vw, 1.4em); + font-weight: 500; + line-height: 1.2em +} + +.kg-header-card h2+.kg-header-card-subheading { + margin: .6em 0 0 +} + +.kg-header-card.kg-width-full.kg-layout-split .kg-header-card-subheading, +.kg-header-card.kg-width-wide .kg-header-card-subheading { + font-size: clamp(1.05em, 2vw, 1.55em) +} + +.kg-header-card.kg-width-full .kg-header-card-subheading:not(.kg-layout-split .kg-header-card-subheading) { + max-width: min(65vmax, 1200px); + font-size: clamp(1.05em, 2vw, 1.7em) +} + +.kg-header-card.kg-v2 .kg-header-card-button { + display: flex; + position: relative; + align-items: center; + height: 2.9em; + min-height: 46px; + padding: 0 1.2em; + outline: 0; + border: 0; + font-size: 1em; + font-weight: 600; + line-height: 1em; + text-align: center; + text-decoration: none; + letter-spacing: .2px; + white-space: nowrap; + text-overflow: ellipsis; + border-radius: 3px; + transition: opacity .2s ease +} + +.kg-header-card.kg-v2 .kg-header-card-button.kg-style-accent { + background-color: var(--ghost-accent-color) +} + +.kg-header-card.kg-v2 h2+.kg-header-card-button, +.kg-header-card.kg-v2 p+.kg-header-card-button { + margin: 1.5em 0 0 +} + +.kg-header-card.kg-v2 .kg-header-card-button:hover { + opacity: .85 +} + +.kg-header-card.kg-v2.kg-width-wide .kg-header-card-button { + font-size: 1.05em +} + +.kg-header-card.kg-v2.kg-width-wide h2+.kg-header-card-button, +.kg-header-card.kg-v2.kg-width-wide p+.kg-header-card-button, +.kg-signup-card.kg-width-wide h2+.kg-signup-card-button, +.kg-signup-card.kg-width-wide p+.kg-signup-card-button { + margin-top: 1.75em +} + +.kg-header-card.kg-v2.kg-width-full .kg-header-card-button { + font-size: 1.1em +} + +.kg-header-card.kg-v2.kg-width-full h2+.kg-header-card-button, +.kg-header-card.kg-v2.kg-width-full p+.kg-header-card-button, +.kg-signup-card.kg-width-full h2+.kg-signup-card-button, +.kg-signup-card.kg-width-full p+.kg-signup-card-button { + margin-top: 2em +} + +@media (max-width:640px) { + .kg-layout-split .kg-header-card-content { + grid-template-columns: 1fr + } + + .kg-width-wide .kg-header-card-text { + padding: min(6.4vmax, 120px) min(4vmax, 80px) + } + + .kg-layout-split.kg-content-wide .kg-header-card-text, + .kg-layout-split.kg-content-wide.kg-swapped .kg-header-card-text { + padding: min(9.6vmax, 180px)0 + } + + .kg-header-card.kg-width-full .kg-header-card-subheading:not(.kg-layout-split .kg-header-card-subheading) { + max-width: unset + } + + .kg-header-card-content .kg-header-card-image:not(.kg-content-wide .kg-header-card-content .kg-header-card-image) { + height: auto; + min-height: unset; + aspect-ratio: 1/1 + } + + .kg-content-wide .kg-header-card-content .kg-header-card-image { + padding: 1.7em 0 0 + } + + .kg-content-wide.kg-swapped .kg-header-card-content .kg-header-card-image { + padding: 0 0 1.7em + } + + .kg-header-card.kg-v2 .kg-header-card-button { + height: 2.9em + } + + .kg-header-card.kg-v2.kg-width-full .kg-header-card-button, + .kg-header-card.kg-v2.kg-width-wide .kg-header-card-button { + font-size: 1em + } +} + +.kg-nft-card, +.kg-nft-card * { + box-sizing: border-box +} + +.kg-nft-card { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + margin-left: auto; + margin-right: auto +} + +.kg-nft-card a.kg-nft-card-container { + position: static; + display: flex; + flex: auto; + flex-direction: column; + text-decoration: none; + font-family: -apple-system, BlinkMacSystemFont, "avenir next", avenir, "helvetica neue", helvetica, ubuntu, roboto, noto, "segoe ui", arial, sans-serif; + font-size: 12px; + font-weight: 400; + box-shadow: 0 2px 6px -2px rgb(0 0 0/10%), 0 0 1px rgb(0 0 0/40%); + width: 100%; + max-width: 512px; + color: #222; + background: #fff; + border-radius: 5px; + transition: none +} + +.kg-nft-card * { + position: static +} + +.kg-nft-metadata { + padding: 16px; + width: 100% +} + +.kg-nft-image { + border-radius: 5px 5px 0 0; + width: 100% +} + +.kg-nft-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 20px +} + +.kg-nft-header h4.kg-nft-title { + font-family: inherit; + font-size: 16px; + font-weight: 700; + line-height: 1.3em; + min-width: unset; + max-width: unset; + margin: 0; + color: #222 +} + +.kg-nft-opensea-logo { + margin-top: 2px; + width: 100px; + object-fit: scale-down +} + +.kg-nft-card p.kg-nft-description, +.kg-nft-creator { + font-family: inherit; + line-height: 1.4em; + margin: 4px 0 0; + color: #ababab +} + +.kg-nft-creator span { + font-weight: 500; + color: #222 +} + +.kg-nft-card p.kg-nft-description { + font-size: 12px; + margin: 20px 0 0; + color: #222 +} + +.kg-product-card, +.kg-product-card * { + box-sizing: border-box +} + +.kg-product-card { + display: flex; + align-items: center; + flex-direction: column; + width: 100% +} + +.kg-product-card-container { + display: grid; + grid-template-columns: auto min-content; + align-items: center; + grid-row-gap: 16px; + background: 0 0; + max-width: 550px; + padding: 16px; + width: 100%; + background: #fff; + color: #222; + border-radius: 5px; + box-shadow: inset 0 0 0 1px rgb(124 139 154/25%); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif +} + +.kg-product-card-image { + grid-column: 1/3; + justify-self: center; + height: auto +} + +.kg-product-card-title-container { + grid-column: 1/2 +} + +.kg-product-card h4.kg-product-card-title { + text-decoration: none; + font-weight: 600; + font-size: 18px; + margin-top: 0; + margin-bottom: 0; + line-height: 1.15em +} + +.kg-product-card-description { + grid-column: 1/3 +} + +.kg-product-card .kg-product-card-description ol, +.kg-product-card .kg-product-card-description p, +.kg-product-card .kg-product-card-description ul { + font-size: 12px; + line-height: 1.5em; + opacity: .7; + margin-bottom: 0 +} + +.kg-product-card .kg-product-card-description p:first-of-type { + margin-top: -4px +} + +.kg-product-card .kg-product-card-description ol, +.kg-product-card .kg-product-card-description p:not(:first-of-type), +.kg-product-card .kg-product-card-description ul { + margin-top: .95em +} + +.kg-product-card .kg-product-card-description li+li, +.kg-toggle-card li+li { + margin-top: .5em +} + +.kg-product-card-rating { + display: flex; + align-items: center; + grid-column: 2/3; + align-self: start; + justify-self: end; + padding-left: 16px +} + +@media (max-width:400px) { + .kg-product-card-title-container { + grid-column: 1/3 + } + + .kg-product-card-rating { + grid-column: 1/3; + justify-self: start; + margin-top: -15px; + padding-left: 0 + } +} + +.kg-product-card-rating-star { + height: 20px; + width: 20px +} + +.kg-product-card-rating-star svg { + width: 16px; + height: 16px; + fill: currentColor; + opacity: .15 +} + +.kg-product-card a.kg-product-card-button { + justify-content: center; + grid-column: 1/3; + display: flex; + position: static; + align-items: center; + font-size: 12px; + font-weight: 600; + line-height: 1em; + text-decoration: none; + width: 100%; + height: 38px; + border-radius: 6px; + padding: 0 12px; + transition: opacity .2s ease-in-out +} + +.kg-product-card a.kg-product-card-btn-accent { + background-color: var(--ghost-accent-color); + color: #fff +} + +.kg-signup-card { + position: relative +} + +.kg-signup-card, +.kg-signup-card * { + box-sizing: border-box +} + +.kg-signup-card.kg-style-accent { + background-color: var(--ghost-accent-color) +} + +.kg-layout-split .kg-signup-card-content { + display: grid; + grid-template-columns: 1fr 1fr +} + +.kg-signup-card-text { + position: relative; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + height: 100%; + padding: min(4vmax, 80px); + background-size: cover; + background-position: center; + text-align: left +} + +.kg-width-wide .kg-signup-card-text { + padding: min(6.4vmax, 120px) +} + +.kg-width-full .kg-signup-card-text { + padding: min(12vmax, 260px)0 +} + +.kg-layout-split .kg-signup-card-text { + padding: min(12vmax, 260px) min(4vmax, 80px) +} + +.kg-layout-split.kg-content-wide .kg-signup-card-text { + padding: min(10vmax, 220px)0 min(10vmax, 220px) min(4vmax, 80px) +} + +.kg-layout-split.kg-content-wide.kg-swapped .kg-signup-card-text { + padding: min(10vmax, 220px) min(4vmax, 80px) min(10vmax, 220px)0 +} + +.kg-swapped .kg-signup-card-text { + grid-row: 1 +} + +.kg-signup-card-text.kg-align-center { + align-items: center; + text-align: center +} + +.kg-signup-card.kg-style-image .kg-signup-card-button, +.kg-signup-card.kg-style-image .kg-signup-card-subheading, +.kg-signup-card.kg-style-image h2.kg-signup-card-heading { + z-index: 999 +} + +.kg-signup-card>picture>.kg-signup-card-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + background-color: #fff; + pointer-events: none +} + +.kg-signup-card-content .kg-signup-card-image { + width: 100%; + height: 0; + min-height: 100%; + object-fit: cover; + object-position: center +} + +.kg-content-wide .kg-signup-card-content .kg-signup-card-image { + height: 100%; + padding: 5.6em 0; + object-fit: contain +} + +.kg-signup-card.kg-width-wide h2.kg-signup-card-heading { + font-size: clamp(1.7em, 5vw, 3.3em) +} + +.kg-signup-card.kg-width-full h2.kg-signup-card-heading { + font-size: clamp(1.9em, 5.6vw, 4.2em) +} + +.kg-signup-card-subheading { + margin: 0 0 2em +} + +.kg-signup-card .kg-signup-card-subheading { + max-width: 40em; + margin: 0; + font-size: clamp(1.05em, 2vw, 1.4em); + font-weight: 500; + line-height: 1.2em +} + +.kg-signup-card h2+.kg-signup-card-subheading { + margin: .6em 0 0 +} + +.kg-signup-card.kg-width-full.kg-layout-split .kg-signup-card-subheading, +.kg-signup-card.kg-width-wide .kg-signup-card-subheading { + font-size: clamp(1.05em, 2vw, 1.55em) +} + +.kg-signup-card.kg-width-full .kg-signup-card-subheading:not(.kg-layout-split .kg-signup-card-subheading) { + max-width: min(65vmax, 1200px); + font-size: clamp(1.05em, 2vw, 1.7em) +} + +.kg-signup-card-form { + position: relative; + display: flex; + flex-shrink: 0; + width: 100% +} + +.kg-align-center .kg-signup-card-form { + justify-content: center +} + +.kg-signup-card-heading+.kg-signup-card-form, +.kg-signup-card-subheading+.kg-signup-card-form { + margin: min(2.4vmax, 48px)0 0 +} + +.kg-width-wide .kg-signup-card-heading+.kg-signup-card-form, +.kg-width-wide .kg-signup-card-subheading+.kg-signup-card-form { + margin: min(3.2vmax, 64px)0 0 +} + +.kg-width-full .kg-signup-card-heading+.kg-signup-card-form, +.kg-width-full .kg-signup-card-subheading+.kg-signup-card-form { + margin: min(4vmax, 80px)0 0 +} + +.kg-signup-card-fields { + display: flex; + width: 100%; + padding: 3px; + background: #fff; + border: 1px solid #e6e6e6; + border-radius: 4px +} + +.kg-width-full .kg-signup-card-fields, +.kg-width-wide .kg-signup-card-fields { + width: 100%; + max-width: 500px +} + +.kg-signup-card-input { + width: 100%; + height: 2.9em; + min-height: 46px; + margin: 0 3px 0 0; + padding: 12px 16px; + border: 0; + background: #fff; + font-size: 1.1em +} + +.kg-signup-card-input:focus, +.kg-signup-card-input:focus-visible { + outline: 0 +} + +.kg-signup-card-button { + display: flex; + position: relative; + align-items: center; + min-height: 46px; + height: 100%; + padding: 0 1.2em; + outline: 0; + border: 0; + font-size: 1em; + font-weight: 600; + line-height: 1em; + text-align: center; + text-decoration: none; + letter-spacing: .2px; + white-space: nowrap; + text-overflow: ellipsis; + border-radius: 3px; + transition: opacity .2s ease; + cursor: pointer +} + +.kg-signup-card-button.kg-style-accent { + background-color: var(--ghost-accent-color) +} + +.kg-signup-card h2+.kg-signup-card-button, +.kg-signup-card p+.kg-signup-card-button { + margin: 1.5em 0 0 +} + +.kg-signup-card .kg-signup-card-button:hover { + opacity: .85 +} + +.kg-signup-card.kg-width-wide .kg-signup-card-button { + font-size: 1.05em +} + +.kg-signup-card.kg-width-full .kg-signup-card-button { + font-size: 1.1em +} + +.kg-signup-card-error, +.kg-signup-card-form.success .kg-signup-card-fields, +.kg-signup-card-success { + display: none +} + +.kg-signup-card-form.success .kg-signup-card-success { + display: flex; + align-items: center; + height: 3em; + font-size: 1.25em; + font-weight: 500; + line-height: 1.4em +} + +.kg-signup-card-form.error .kg-signup-card-fields { + border: 1px solid red; + box-shadow: inset 0 0 0 1px rgba(255, 0, 0, .2) +} + +.kg-signup-card-form.error .kg-signup-card-error { + position: absolute; + bottom: calc(-1rem - 1.6em); + display: block; + font-size: inherit +} + +.kg-signup-card-button-loading { + position: absolute; + inset: 0; + align-items: center; + justify-content: center; + display: none +} + +.kg-signup-card-form.loading .kg-signup-card-button-default { + color: transparent +} + +.kg-signup-card-form.loading .kg-signup-card-button-loading { + display: flex +} + +.kg-signup-card-disclaimer { + margin: 1rem 0 0 +} + +.kg-signup-card-form.error+.kg-signup-card-disclaimer, +.kg-signup-card-form.success+.kg-signup-card-disclaimer { + visibility: hidden +} + +@media (max-width:640px) { + .kg-layout-split .kg-signup-card-content { + grid-template-columns: 1fr + } + + .kg-width-wide .kg-signup-card-text { + padding: min(6.4vmax, 120px) min(4vmax, 80px) + } + + .kg-layout-split.kg-content-wide .kg-signup-card-text, + .kg-layout-split.kg-content-wide.kg-swapped .kg-signup-card-text { + padding: min(9.6vmax, 180px)0 + } + + .kg-signup-card.kg-width-full .kg-signup-card-subheading:not(.kg-layout-split .kg-signup-card-subheading) { + max-width: unset + } + + .kg-signup-card-content .kg-signup-card-image:not(.kg-content-wide .kg-signup-card-content .kg-signup-card-image) { + height: auto; + min-height: unset; + aspect-ratio: 1/1 + } + + .kg-content-wide .kg-signup-card-content .kg-signup-card-image { + padding: 1.7em 0 0 + } + + .kg-content-wide.kg-swapped .kg-signup-card-content .kg-signup-card-image { + padding: 0 0 1.7em + } + + .kg-signup-card-input { + height: 2.9em; + padding: 6px 12px; + font-size: 1em + } + + .kg-signup-card-button { + height: 2.5em + } + + .kg-signup-card.kg-width-full .kg-signup-card-button, + .kg-signup-card.kg-width-wide .kg-signup-card-button { + font-size: 1em + } +} + +.kg-toggle-card, +.kg-toggle-card * { + box-sizing: border-box +} + +.kg-toggle-card { + background: 0 0; + box-shadow: inset 0 0 0 1px rgba(124, 139, 154, .25); + border-radius: 4px; + padding: 1em +} + +.kg-toggle-card[data-kg-toggle-state=close] .kg-toggle-content { + height: 0; + overflow: hidden; + transition: opacity .5s ease, top .35s ease; + opacity: 0; + top: -.5em; + position: relative +} + +.kg-toggle-content { + height: auto; + opacity: 1; + transition: opacity 1s ease, top .35s ease; + top: 0; + position: relative +} + +.kg-toggle-card[data-kg-toggle-state=close] svg { + transform: unset +} + +.kg-toggle-heading { + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: flex-start +} + +.kg-toggle-card h4.kg-toggle-heading-text { + font-size: 1em; + font-weight: 700; + line-height: 1.3em; + margin-top: 0; + margin-bottom: 0 +} + +.kg-toggle-content p:first-of-type { + margin-top: .5em +} + +.kg-toggle-card .kg-toggle-content ol, +.kg-toggle-card .kg-toggle-content p, +.kg-toggle-card .kg-toggle-content ul { + font-size: .95em; + line-height: 1.5em; + margin-top: .95em; + margin-bottom: 0 +} + +.kg-toggle-card-icon { + height: 20px; + width: 20px; + display: flex; + justify-content: center; + align-items: center; + margin-left: 1em; + padding: 0; + background: 0 0; + border: 0; + cursor: pointer +} + +.kg-toggle-heading svg { + width: 14px; + color: rgba(124, 139, 154, .5); + transition: all .3s; + transform: rotate(-180deg) +} + +.kg-toggle-heading path { + fill: none; + stroke: currentcolor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 1.5; + fill-rule: evenodd +} + +.kg-toggle-card+.kg-toggle-card { + margin-top: 1em +} + +.kg-video-card, +.kg-video-card * { + box-sizing: border-box +} + +.kg-video-card { + position: relative; + --seek-before-width: 0%; + --volume-before-width: 100%; + --buffered-width: 0% +} + +.kg-video-card video { + display: block; + max-width: 100%; + height: auto +} + +.kg-video-container { + position: relative; + height: 0; + width: 100%; + overflow: hidden +} + +.kg-video-container video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover +} + +.kg-video-large-play-icon, +.kg-video-overlay { + display: flex; + justify-content: center; + align-items: center; + transition: opacity .2s ease-in-out +} + +.kg-video-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-image: linear-gradient(180deg, rgba(0, 0, 0, .3)0, transparent 70%, transparent 100%); + z-index: 999 +} + +.kg-video-large-play-icon { + width: 60px; + height: 60px; + padding: 0; + background: rgba(0, 0, 0, .5); + border-radius: 50% +} + +.kg-video-large-play-icon svg { + width: 20px; + height: auto; + margin-left: 2px; + fill: #fff +} + +.kg-video-player-container { + position: absolute; + bottom: -1px; + left: 0; + right: 0; + width: 100%; + height: 80px; + background: linear-gradient(transparent, rgba(0, 0, 0, .5)); + z-index: 999; + transition: opacity .2s ease-in-out +} + +.kg-video-player { + position: absolute; + bottom: 0; + display: flex; + align-items: center; + width: 100%; + z-index: 9999; + padding: 12px 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif +} + +.kg-video-current-time, +.kg-video-time { + font-family: inherit; + font-size: 12.5px; + font-weight: 500; + line-height: 1.4em; + white-space: nowrap +} + +.kg-video-current-time { + min-width: 38px; + padding: 0 4px; + color: #fff +} + +.kg-video-time { + color: rgba(255, 255, 255, .6) +} + +.kg-video-duration { + padding: 0 4px +} + +.kg-video-pause-icon, +.kg-video-play-icon { + position: relative; + padding: 0 4px 0 0; + font-size: 0; + background: 0 0 +} + +.kg-video-hide { + display: none !important +} + +.kg-video-hide-animated { + opacity: 0 !important; + transition: opacity .2s ease-in-out; + cursor: initial +} + +.kg-video-pause-icon svg, +.kg-video-play-icon svg { + width: 14px; + height: 14px; + fill: #fff +} + +.kg-video-seek-slider { + flex-grow: 1; + margin: 0 4px +} + +@media (max-width:520px) { + .kg-video-seek-slider { + display: none + } +} + +.kg-video-playback-rate { + min-width: 37px; + padding: 0 4px; + color: #fff; + font-family: inherit; + font-size: 12.5px; + font-weight: 600; + line-height: 1.4em; + text-align: left; + background: 0 0; + white-space: nowrap +} + +@media (max-width:520px) { + .kg-video-playback-rate { + padding-left: 8px + } +} + +.kg-video-mute-icon, +.kg-video-unmute-icon { + position: relative; + bottom: -1px; + padding: 0 4px; + font-size: 0; + background: 0 0 +} + +@media (max-width:520px) { + + .kg-video-mute-icon, + .kg-video-unmute-icon { + margin-left: auto + } +} + +.kg-video-mute-icon svg, +.kg-video-unmute-icon svg { + width: 16px; + height: 16px; + fill: #fff +} + +.kg-video-volume-slider { + width: 80px +} + +@media (max-width:300px) { + .kg-video-volume-slider { + display: none + } +} + +.kg-video-seek-slider::before, +.kg-video-volume-slider::before { + content: ""; + position: absolute; + left: 0; + width: var(--seek-before-width) !important; + height: 4px; + cursor: pointer; + background-color: #ebeef0; + border-radius: 2px +} + +.kg-video-volume-slider::before { + width: var(--volume-before-width) !important +} + +.kg-video-card input[type=range] { + position: relative; + -webkit-appearance: none; + background: 0 0; + height: auto; + padding: 0; + border: 0 +} + +.kg-video-card input[type=range]::-webkit-slider-thumb { + -webkit-appearance: none +} + +.kg-video-card input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 4px; + cursor: pointer; + background: rgba(255, 255, 255, .2); + border-radius: 2px +} + +.kg-video-card input[type=range]::-webkit-slider-thumb { + position: relative; + box-sizing: content-box; + width: 13px; + height: 13px; + margin: -5px 0 0; + border: 0; + cursor: pointer; + background: #fff; + border-radius: 50%; + box-shadow: 0 0 0 1px rgba(0, 0, 0, .08), 0 1px 4px rgba(0, 0, 0, .24) +} + +.kg-video-card input[type=range]:active::-webkit-slider-thumb { + transform: scale(1.2) +} + +.kg-video-card input[type=range]::-moz-range-track { + width: 100%; + height: 4px; + cursor: pointer; + background: rgba(255, 255, 255, .2); + border-radius: 2px +} + +.kg-video-card input[type=range]::-moz-range-progress { + background: #ebeef0; + border-radius: 2px +} + +.kg-video-card input[type=range]::-moz-range-thumb { + box-sizing: content-box; + width: 13px; + height: 13px; + border: 0; + cursor: pointer; + background: #fff; + border-radius: 50%; + box-shadow: 0 0 0 1px rgba(0, 0, 0, .08), 0 1px 4px rgba(0, 0, 0, .24) +} + +.kg-video-card input[type=range]:active::-moz-range-thumb { + transform: scale(1.2) +} + +.kg-video-card input[type=range]::-ms-track { + width: 100%; + height: 3px; + border: solid transparent; + color: transparent; + cursor: pointer; + background: 0 0 +} + +.kg-video-card input[type=range]::-ms-fill-lower { + background: #fff +} + +.kg-video-card input[type=range]::-ms-fill-upper { + background: #ebeef0 +} + +.kg-video-card input[type=range]::-ms-thumb { + box-sizing: content-box; + width: 13px; + height: 13px; + border: 0; + cursor: pointer; + background: #fff; + border-radius: 50%; + box-shadow: 0 0 0 1px rgba(0, 0, 0, .08), 0 1px 4px rgba(0, 0, 0, .24) +} + +.kg-video-card input[type=range]:active::-ms-thumb { + transform: scale(1.2) +} \ No newline at end of file diff --git a/public/ghost-screen.css b/public/ghost-screen.css new file mode 100644 index 00000000..0b12f5d5 --- /dev/null +++ b/public/ghost-screen.css @@ -0,0 +1,634 @@ +/* Ghost Content Styles - Scoped to .gh-content only */ + +.gh-content { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + font-size: 1.2rem; + line-height: 1.6em; + color: #15171A; + } + + .gh-content > * + * { + margin-top: max(2.4vmin, 16px); + margin-bottom: 0; + } + + .gh-content > [id] { + color: #15171A; + font-family: inherit; + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.01em; + margin: 0; + } + + .gh-content > [id]:not(:first-child) { + margin: 2em 0 0; + } + + .gh-content > [id] + * { + margin-top: 1.5rem !important; + } + + .gh-content h1 { font-size: 3.6rem; margin: 0 0 0.5em; } + .gh-content h2 { font-size: 2.8rem; margin: 1.5em 0 0.5em; } + .gh-content h3 { font-size: 2.2rem; margin: 1.5em 0 0.5em; } + .gh-content h4 { font-size: 2rem; margin: 1.5em 0 0.5em; } + .gh-content h5 { font-size: 1.8rem; } + .gh-content h6 { font-size: 1.6rem; } + + .gh-content p { margin: 0 0 1.5em; line-height: 1.6em; } + + .gh-content ul, + .gh-content ol { + padding-left: 1.9em; + margin: 1.5em 0; + } + + .gh-content li { + margin: 0.5em 0; + line-height: 1.6em; + } + + .gh-content a { + color: #0066CC; + text-decoration: underline; + word-break: break-word; + } + + .gh-content a:hover { + opacity: 0.8; + } + + .gh-content blockquote { + position: relative; + font-style: italic; + padding: 0; + margin: max(4.8vmin, 32px) 0; + } + + .gh-content blockquote:before { + content: ""; + position: absolute; + left: -1.5em; + top: 0; + bottom: 0; + width: 0.3rem; + background: var(--ghost-accent-color, #0066CC); + } + + .gh-content code { + padding: 0.15em 0.4em; + margin: 0; + font-size: 0.8em; + background: #f0f6f9; + border: 1px solid #e1eaef; + border-radius: 0.25em; + font-family: Menlo, Monaco, Courier, monospace; + } + + .gh-content pre { + overflow: auto; + padding: 10px 12px; + margin: 2em 0; + background: #15171A; + border-radius: 5px; + line-height: 1.5em; + font-size: 1rem; + } + + .gh-content pre code { + padding: 0; + background: transparent; + color: #fff; + border: none; + } + + .gh-content img { + max-width: 100%; + height: auto; + margin: 2em auto; + display: block; + } + + .gh-content figure { + margin: max(4.8vmin, 32px) 0; + } + + .gh-content figcaption { + margin: 1.5rem 1.5rem 0; + color: rgba(0, 0, 0, 0.5); + font-size: 1.3rem; + line-height: 1.4em; + text-align: center; + } + + .gh-content hr { + position: relative; + margin: max(4.8vmin, 32px) 0; + border: 0; + border-top: 1px solid #e5e7eb; + } + + /* Image Cards */ + .gh-content .kg-image-card { + margin: max(4.8vmin, 32px) auto; + max-width: 100%; + } + + .gh-content .kg-image { + width: 100%; + height: auto; + border-radius: 5px; + } + + .gh-content .kg-image-card.kg-width-wide { + max-width: 85vw; + } + + .gh-content .kg-image-card.kg-width-full { + max-width: 100vw; + } + + /* Gallery Cards */ + .gh-content .kg-gallery-container { + display: flex; + flex-direction: column; + margin: max(4.8vmin, 32px) auto; + max-width: 100%; + } + + .gh-content .kg-gallery-row { + display: flex; + flex-direction: row; + justify-content: center; + gap: 0.75em; + margin-bottom: 0.75em; + } + + .gh-content .kg-gallery-row:last-child { + margin-bottom: 0; + } + + .gh-content .kg-gallery-image { + flex: 1; + min-width: 0; + } + + .gh-content .kg-gallery-image img { + display: block; + margin: 0; + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 3px; + } + + /* Video Cards */ + .gh-content .kg-video-card { + margin: max(4.8vmin, 32px) 0; + width: 100%; + } + + .gh-content .kg-video-container { + position: relative; + width: 100%; + background: #000; + border-radius: 5px; + overflow: hidden; + } + + .gh-content .kg-video-container video { + width: 100%; + height: auto; + display: block; + } + + .gh-content .kg-video-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: flex; + justify-content: center; + align-items: center; + background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, transparent); + cursor: pointer; + } + + .gh-content .kg-video-large-play-icon { + width: 80px; + height: 80px; + background: rgba(255, 255, 255, 0.9); + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: all 0.2s ease; + } + + .gh-content .kg-video-large-play-icon:hover { + background: white; + transform: scale(1.1); + } + + .gh-content .kg-video-large-play-icon svg { + width: 32px; + height: 32px; + fill: #15171A; + margin-left: 4px; + } + + /* Audio Cards */ + .gh-content .kg-audio-card { + margin: max(4.8vmin, 32px) 0; + width: 100%; + } + + .gh-content .kg-audio-player-container { + display: flex; + align-items: center; + padding: 16px; + background: #F4F8FB; + border-radius: 5px; + } + + .gh-content .kg-audio-thumbnail { + width: 60px; + height: 60px; + margin-right: 16px; + border-radius: 5px; + object-fit: cover; + } + + .gh-content .kg-audio-player { + flex: 1; + } + + .gh-content .kg-audio-title { + font-size: 1.6rem; + font-weight: 600; + color: #15171A; + margin-bottom: 8px; + } + + .gh-content .kg-audio-card audio { + width: 100%; + outline: none; + } + + /* Bookmark Cards */ + .gh-content .kg-bookmark-card { + width: 100%; + margin: max(4.8vmin, 32px) 0; + } + + .gh-content .kg-bookmark-container { + display: flex; + text-decoration: none; + border-radius: 5px; + border: 1px solid #E6EBF0; + overflow: hidden; + color: inherit; + } + + .gh-content .kg-bookmark-container:hover { + border: 1px solid #0066CC; + text-decoration: none; + } + + .gh-content .kg-bookmark-content { + display: flex; + flex-direction: column; + flex-grow: 1; + padding: 1.2em; + min-width: 0; + } + + .gh-content .kg-bookmark-title { + font-size: 1.8rem; + line-height: 1.4; + font-weight: 600; + color: #15171A; + } + + .gh-content .kg-bookmark-description { + display: -webkit-box; + overflow-y: hidden; + margin-top: 0.5em; + font-size: 1.6rem; + line-height: 1.5; + color: #626D79; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } + + .gh-content .kg-bookmark-metadata { + display: flex; + align-items: center; + margin-top: 1em; + color: #626D79; + font-size: 1.4rem; + } + + .gh-content .kg-bookmark-icon { + width: 20px; + height: 20px; + margin-right: 0.5em; + } + + .gh-content .kg-bookmark-author:after { + content: "โ€ข"; + margin: 0 0.5em; + } + + .gh-content .kg-bookmark-thumbnail { + position: relative; + min-width: 33%; + max-height: 100%; + } + + .gh-content .kg-bookmark-thumbnail img { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + margin: 0; + border-radius: 0; + } + + /* Toggle Cards */ + .gh-content .kg-toggle-card { + margin: max(3.2vmin, 24px) 0; + border: 1px solid #E6EBF0; + border-radius: 5px; + overflow: hidden; + background: #fff; + } + + .gh-content .kg-toggle-heading { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.2em; + cursor: pointer; + user-select: none; + background: #F4F8FB; + transition: background 0.2s ease; + } + + .gh-content .kg-toggle-heading:hover { + background: #E6EBF0; + } + + .gh-content .kg-toggle-heading-text { + font-size: 2rem; + font-weight: 600; + color: #15171A; + } + + .gh-content .kg-toggle-card-icon { + width: 20px; + height: 24px; + transition: transform 0.3s ease; + } + + .gh-content .kg-toggle-card-open .kg-toggle-card-icon { + transform: rotate(180deg); + } + + .gh-content .kg-toggle-content { + display: none; + padding: 1.2em; + border-top: 1px solid #E6EBF0; + } + + .gh-content .kg-toggle-card-open .kg-toggle-content { + display: block; + } + + .gh-content .kg-toggle-content > * { + margin-top: 0; + } + + .gh-content .kg-toggle-content > * + * { + margin-top: 1em; + } + + .gh-content .kg-toggle-content p, + .gh-content .kg-toggle-content ul, + .gh-content .kg-toggle-content ol { + font-size: 1.9rem; + line-height: 1.6em; + } + + /* Callout Cards */ + .gh-content .kg-callout-card { + display: flex; + padding: 1.2em; + margin: max(4.8vmin, 32px) 0; + border-radius: 5px; + } + + .gh-content .kg-callout-card.kg-callout-card-blue { + background: rgba(0, 102, 204, 0.1); + border-left: 4px solid #0066CC; + } + + .gh-content .kg-callout-card.kg-callout-card-green { + background: rgba(30, 203, 132, 0.1); + border-left: 4px solid #1ECB84; + } + + .gh-content .kg-callout-card.kg-callout-card-yellow { + background: rgba(255, 176, 0, 0.1); + border-left: 4px solid #FFB000; + } + + .gh-content .kg-callout-card.kg-callout-card-red { + background: rgba(245, 56, 56, 0.1); + border-left: 4px solid #F53838; + } + + .gh-content .kg-callout-card.kg-callout-card-grey { + background: rgba(21, 23, 26, 0.05); + border-left: 4px solid #626D79; + } + + .gh-content .kg-callout-emoji { + font-size: 2.1rem; + line-height: 1.4em; + margin-right: 1em; + } + + .gh-content .kg-callout-text { + font-size: 1.9rem; + line-height: 1.6em; + } + + /* Button Cards */ + .gh-content .kg-button-card { + margin: max(4.8vmin, 32px) 0; + text-align: center; + } + + .gh-content .kg-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.9em 1.8em; + font-size: 1.8rem; + font-weight: 600; + text-decoration: none; + border-radius: 5px; + transition: all 0.2s ease; + } + + .gh-content .kg-btn-accent { + background: var(--ghost-accent-color, #0066CC); + color: #fff; + } + + .gh-content .kg-btn-accent:hover { + background: #0052A3; + color: #fff; + text-decoration: none; + } + + /* File Cards */ + .gh-content .kg-file-card { + margin: max(4.8vmin, 32px) 0; + } + + .gh-content .kg-file-card-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.2em; + border: 1px solid #E6EBF0; + border-radius: 5px; + } + + .gh-content .kg-file-card-contents { + flex: 1; + margin-right: 1em; + } + + .gh-content .kg-file-card-title { + font-size: 1.8rem; + font-weight: 600; + color: #15171A; + } + + .gh-content .kg-file-card-caption { + margin-top: 0.5em; + font-size: 1.4rem; + color: #626D79; + } + + .gh-content .kg-file-card-metadata { + display: inline; + margin-right: 1em; + font-size: 1.4rem; + color: #626D79; + } + + .gh-content .kg-file-card-filename { + display: inline; + font-weight: 500; + } + + .gh-content .kg-file-card-filesize { + display: inline; + } + + /* Product Cards */ + .gh-content .kg-product-card { + margin: max(4.8vmin, 32px) 0; + border: 1px solid #E6EBF0; + border-radius: 5px; + overflow: hidden; + } + + .gh-content .kg-product-card-image { + width: 100%; + height: auto; + } + + .gh-content .kg-product-card-content { + padding: 2em; + } + + .gh-content .kg-product-card-title { + font-size: 2.4rem; + font-weight: 700; + color: #15171A; + margin-bottom: 0.5em; + } + + .gh-content .kg-product-card-description { + font-size: 1.7rem; + line-height: 1.6em; + color: #626D79; + margin-bottom: 1.5em; + } + + /* Header Cards */ + .gh-content .kg-header-card { + padding: 6em 3em; + margin: max(4.8vmin, 32px) 0; + border-radius: 5px; + text-align: center; + } + + .gh-content .kg-header-card.kg-style-dark { + background: #15171A; + color: #fff; + } + + .gh-content .kg-header-card.kg-style-light { + background: #F4F8FB; + color: #15171A; + } + + .gh-content .kg-header-card h2 { + font-size: 4.4rem; + font-weight: 700; + line-height: 1.05; + margin: 0; + } + + .gh-content .kg-header-card h3 { + font-size: 2.4rem; + font-weight: 400; + line-height: 1.4; + margin: 1em 0 0; + } + + /* Responsive */ + @media (max-width: 768px) { + .gh-content { + font-size: 1.5rem; + } + + .gh-content h1 { font-size: 3.6rem; } + .gh-content h2 { font-size: 2.8rem; } + .gh-content h3 { font-size: 2.4rem; } + + .gh-content .kg-bookmark-container { + flex-direction: column; + } + + .gh-content .kg-bookmark-thumbnail { + order: 1; + min-width: 100%; + max-height: 200px; + } + + .gh-content .kg-bookmark-content { + order: 2; + } + } \ No newline at end of file diff --git a/public/img/fallback.png b/public/img/fallback.png new file mode 100644 index 00000000..a63d9f67 Binary files /dev/null and b/public/img/fallback.png differ diff --git a/server/api/blog.ts b/server/api/blog.ts deleted file mode 100644 index 6925f45d..00000000 --- a/server/api/blog.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { XMLParser } from 'fast-xml-parser' -import type { Feed } from '~/types/blog' - -async function getFeed(feedUrl: string) { - const rss = await $fetch(feedUrl) - return rss -} - -async function fetchPosts(url: string): Promise { - const rss = await getFeed(url) - - if (!rss || rss.trim().length === 0) { - throw new Error('Empty RSS feed received') - } - - const root = new XMLParser().parse(rss) - - // Add null checks for RSS structure - if (!root?.rss?.channel) { - throw new Error('Invalid RSS feed structure - missing channel') - } - - const { channel } = root.rss - - // Handle case where there are no items - if (!channel.item) { - return { items: [] } - } - - // Ensure channel.item is an array (sometimes it's a single object) - const items = Array.isArray(channel.item) ? channel.item : [channel.item] - - const regex = / { - try { - const content = post['content:encoded'] || '' - const images = Array.from(String(content) - .matchAll(regex)).map(match => match[1]).filter(Boolean) - let snippet = content.replace(/(<([^>]+)>)/g, '') - if (snippet.length > 200) { - snippet = `${snippet.slice(0, 200)}...` - } - return { - title: post.title || 'Untitled', - snippet, - pubDate: post.pubDate || new Date().toISOString(), - isoDate: post.isoDate || new Date().toISOString(), - link: post.link || '#', - images, - } - } catch (error) { - console.warn('Failed to process blog post:', post.title, error) - return null - } - }).filter(Boolean) // Remove null entries - } -} - -export default defineCachedEventHandler(async (event) => { - const config = useRuntimeConfig(event) - try { - const feedUrl = config.public.blogFeedUrl - if (!feedUrl) { - throw createError({ - statusCode: 500, - statusMessage: 'Blog feed URL not configured' - }) - } - - const posts = await fetchPosts(feedUrl) - return posts - } - catch (e: any) { - console.error('failed to get blog posts:', e) - - // Return empty data instead of throwing for client errors - if (e.message?.includes('fetch') || e.message?.includes('Empty RSS') || e.message?.includes('Invalid RSS')) { - console.warn('Blog feed issue, returning empty data:', e.message) - return { items: [] } - } - - // Only throw for server configuration errors - throw createError({ - statusCode: 500, - statusMessage: `Failed to fetch posts: ${e.message}` - }) - } -}, { maxAge: 60 * 60 }) \ No newline at end of file diff --git a/server/api/blog/[slug].get.ts b/server/api/blog/[slug].get.ts new file mode 100644 index 00000000..62ea4b76 --- /dev/null +++ b/server/api/blog/[slug].get.ts @@ -0,0 +1,35 @@ +import GhostContentAPI from '@tryghost/content-api' + +export default defineCachedEventHandler(async (event) => { + const slug = getRouterParam(event, 'slug') + + if (!slug) { + throw createError({ status: 400, message: 'Missing slug parameter' }) + } + + const config = useRuntimeConfig(event) + const { url, key, version } = config.public.ghost + + const api = new GhostContentAPI({ url, key, version }) + + try { + const post = await api.posts.read({ slug }, { formats: ['html'] }) + + return { + title: post.title, + content: post.html, + snippet: post.excerpt, + pubDate: post.published_at || new Date().toISOString(), + isoDate: post.published_at || new Date().toISOString(), + slug: post.slug, + images: post.feature_image ? [post.feature_image] : [], + + } + } + catch (error: any) { + throw createError({ + status: error.response?.status || 404, + message: 'Post not found', + }) + } +}) diff --git a/server/api/blog/index.get.ts b/server/api/blog/index.get.ts new file mode 100644 index 00000000..6d2940a7 --- /dev/null +++ b/server/api/blog/index.get.ts @@ -0,0 +1,38 @@ +import GhostContentAPI from '@tryghost/content-api' +import type { Feed, Item } from '~/types/blog' + +async function fetchGhostPosts(event: any): Promise { + const config = useRuntimeConfig(event) + const { url, key, version } = config.public.ghost + + const api = new GhostContentAPI({ url, key, version }) + + const posts = await api.posts.browse({ + limit: 'all', + fields: ['title', 'slug', 'feature_image', 'published_at', 'excerpt'], + }) + + if (!posts?.length) + return { items: [] } + + const items: Item[] = posts.map(post => ({ + title: post.title || '๐ŸŒ€ Uh-oh, the title went on a data adventure ๐ŸŒ. Please refresh the page.', + snippet: post.excerpt || '๐ŸŒ€ Uh-oh, the snippet went on a data adventure ๐ŸŒ. Please refresh the page.', + pubDate: post.published_at || new Date().toISOString(), + isoDate: post.published_at || new Date().toISOString(), + link: `/blog/${post.slug}`, + images: post.feature_image ? [post.feature_image] : [], + })) + + return { items } +} + +export default defineCachedEventHandler(async (event) => { + try { + return await fetchGhostPosts(event) + } + catch (e: any) { + console.error('Failed to get blog posts:', e) + return { items: [] } + } +}) diff --git a/server/api/revalidate.post.ts b/server/api/revalidate.post.ts new file mode 100644 index 00000000..19e3582c --- /dev/null +++ b/server/api/revalidate.post.ts @@ -0,0 +1,37 @@ +export default defineEventHandler(async (event) => { + const config = useRuntimeConfig(event) + const body = await readBody(event) + const query = getQuery(event) + const secret = query.secret as string + + // Validate secret + if (!config.webhookSecret) { + console.error('โŒ WEBHOOK_SECRET not configured!') + throw createError({ + statusCode: 500, + message: 'Webhook not configured' + }) + } + + if (secret !== config.webhookSecret) { + console.error('โŒ Invalid webhook secret provided') + throw createError({ + statusCode: 403, + message: 'Forbidden' + }) + } + + console.log('โœ… Ghost webhook received - clearing cache') + + // Clear all cache + const storage = useStorage('cache') + await storage.clear() + + const postTitle = body.post?.current?.title || body.post?.previous?.title || 'Unknown' + console.log(`๐Ÿ“ Cache cleared for: ${postTitle}`) + + return { + success: true, + time: new Date().toISOString() + } +}) \ No newline at end of file