diff --git a/.github/workflows/zola-zolarwind.yml b/.github/workflows/zola-zolarwind.yml new file mode 100644 index 00000000..9d464c16 --- /dev/null +++ b/.github/workflows/zola-zolarwind.yml @@ -0,0 +1,35 @@ +name: Zolarwind integration +on: + pull_request: + paths: ['packages/zola-zolarwind/**', 'verification/zola-zolarwind/**', 'test/zola-zolarwind.test.js', '.github/workflows/zola-zolarwind.yml'] +permissions: + contents: read +jobs: + live-site: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: actions/checkout@v4 + with: + repository: firekylin/firekylin + ref: ae71b610ed2dcb6e992366bc06080ec51805042c + path: .verification-core + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + - run: pnpm install --frozen-lockfile + working-directory: .verification-core + - run: pnpm install --frozen-lockfile --ignore-scripts + - name: Verify real SQLite site and installed ZIP + env: + FIREKYLIN_CHECKOUT: ${{ github.workspace }}/.verification-core + PLAYWRIGHT_MODULE: ${{ github.workspace }}/node_modules/playwright-core + CHROMIUM_PATH: /usr/bin/google-chrome + run: node verification/zola-zolarwind/live.cjs + - uses: actions/upload-artifact@v4 + if: always() + with: + name: zola-zolarwind-live-evidence + path: verification/zola-zolarwind/ diff --git a/packages/zola-zolarwind/LICENSE b/packages/zola-zolarwind/LICENSE new file mode 100644 index 00000000..4105b7ba --- /dev/null +++ b/packages/zola-zolarwind/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright 2023 Thomas Weitzel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/zola-zolarwind/NOTICE.md b/packages/zola-zolarwind/NOTICE.md new file mode 100644 index 00000000..80fce5b0 --- /dev/null +++ b/packages/zola-zolarwind/NOTICE.md @@ -0,0 +1,9 @@ +# Source and attribution + +Adapted from [Zolarwind](https://github.com/thomasweitzel/zolarwind/tree/b793bc2bff6d31da193fcc8f337729db7e399a76) by Thomas Weitzel, pinned at `b793bc2bff6d31da193fcc8f337729db7e399a76`. The complete original license is in LICENSE. + +Native Firekylin Eta integration by Firekylin contributors. No demo content, remote fonts, analytics or third-party service credentials included. + +Original Tailwind 4.3.3 generated CSS and its source CSS are retained. Full Tailwind MIT notice is included. Upstream font stack uses system fonts. No default banner, sample photography, icons, math/diagram engines, analytics or comment-service code is bundled. + +Four unused embedded callout icon data URLs are removed from source and generated CSS; native prose does not use those Zola shortcode icons. diff --git a/packages/zola-zolarwind/README.md b/packages/zola-zolarwind/README.md new file mode 100644 index 00000000..c79a9258 --- /dev/null +++ b/packages/zola-zolarwind/README.md @@ -0,0 +1,7 @@ +# Zolarwind for Firekylin + +Thomas Weitzel’s MIT Zolarwind blog layout is adapted using its original Tailwind 4.3.3 generated CSS, warm-neutral light/dark palette, blue links, sticky desktop header, 3/2/1-column image cards, 192px article banners and centered footer. Use a post’s featuredImage for covers; missing or unsafe image URLs omit the image instead of bundling demo photography. Current public-result IDs bound tag lookups. Images and title links remain separate from tag controls. + +Native posts/pages, archive/taxonomy/search, pagination, protected content, comments and attachments are supported. A persistent light/dark toggle and safe DOM-generated collapsible article TOC are included. Navigation wraps and stays visible without JavaScript; no-JS uses light mode. Footer text/author are configurable. + +Native blog routes replace Zola sections. Series navigation, localized resources, reading-time counts, syntax engines, math/diagram shortcodes, Artalk integration and feeds are not implemented. No remote fonts, default banner, icons or service scripts are bundled. No live upstream pixel comparison or remote comment submission is claimed. diff --git a/packages/zola-zolarwind/VISUAL-COMPARISON.md b/packages/zola-zolarwind/VISUAL-COMPARISON.md new file mode 100644 index 00000000..1b31d02d --- /dev/null +++ b/packages/zola-zolarwind/VISUAL-COMPARISON.md @@ -0,0 +1,3 @@ +# Visual adaptation + +Pinned list/list-element/blog-post/header templates and generated Tailwind CSS are the reference: 3/2/1-column cards with 192px covers, warm-neutral palette, blue links, metadata under summaries, sticky desktop header and full-width article banner. Native links replace SVG icons; mobile navigation stays expanded and no default photo is bundled. Installed screenshots and reproducible tests are in verification/zola-zolarwind; no live upstream pixel comparison was performed. diff --git a/packages/zola-zolarwind/archive.eta b/packages/zola-zolarwind/archive.eta new file mode 100644 index 00000000..7ba15893 --- /dev/null +++ b/packages/zola-zolarwind/archive.eta @@ -0,0 +1,6 @@ +<% layout('./layout'); const site = await firekylin.widget('Widget_Options'); const archive = await firekylin.widget('Widget_Archive', {type:'archive'}); %> +<% block('title', () => { %>归档 - <%= site.title() %><% }) %> +<% block('content', () => { %>

归档

+<% const groups = Object.entries(archive.grouped); if (!groups.length) { %>

暂无文章。

<% } %> +<% for (const [month, posts] of groups) { %>

<%= month %>

<% } %> +
<% }) %> diff --git a/packages/zola-zolarwind/attachment.eta b/packages/zola-zolarwind/attachment.eta new file mode 100644 index 00000000..56f7ecff --- /dev/null +++ b/packages/zola-zolarwind/attachment.eta @@ -0,0 +1 @@ +<% layout('./layout');const site=await firekylin.widget('Widget_Options');const archive=await firekylin.widget('Widget_Archive',{type:'attachment'});const found=archive.next();const cover=String(archive.row?.options?.featuredImage||''); %><% block('title',()=>{ %><%= found?archive.title()+' · ':'' %><%= site.title() %><% }) %><% block('content',()=>{ %>
<% if(found){ %>

<%= archive.title() %>

<% const media=archive.row?.options?.attachment||{};const mediaUrl=String(media.url||'');const mediaValid=/^(https?:\/\/|\/(?!\/))/.test(mediaUrl);const mime=String(media.mime||''); %><% if(mediaValid){ %>
<% if(mime.startsWith('image/')){ %><%= archive.title() %><% }else if(mime.startsWith('audio/')){ %><% }else if(mime.startsWith('video/')){ %><% } %>

<%= media.name||archive.title() %>

<% } %><%~ archive.content() %>
<%~ firekylin.comment({title:'评论'}) %><% }else{ %>

暂无内容。

<% } %>
<% }) %> \ No newline at end of file diff --git a/packages/zola-zolarwind/cate.eta b/packages/zola-zolarwind/cate.eta new file mode 100644 index 00000000..7eacd415 --- /dev/null +++ b/packages/zola-zolarwind/cate.eta @@ -0,0 +1,6 @@ +<% layout('./layout'); const site = await firekylin.widget('Widget_Options'); const metas = await firekylin.widget('Widget_Metas_Category_List'); %> +<% block('title', () => { %>分类 - <%= site.title() %><% }) %> +<% block('content', () => { %>

分类

<% if (!metas.have()) { %>

暂无分类。

<% } %>
<% }) %> diff --git a/packages/zola-zolarwind/error/404.html b/packages/zola-zolarwind/error/404.html new file mode 100644 index 00000000..5be24639 --- /dev/null +++ b/packages/zola-zolarwind/error/404.html @@ -0,0 +1 @@ +404

404

Home \ No newline at end of file diff --git a/packages/zola-zolarwind/error/500.html b/packages/zola-zolarwind/error/500.html new file mode 100644 index 00000000..3e278337 --- /dev/null +++ b/packages/zola-zolarwind/error/500.html @@ -0,0 +1 @@ +500

500

Home \ No newline at end of file diff --git a/packages/zola-zolarwind/inc/list.eta b/packages/zola-zolarwind/inc/list.eta new file mode 100644 index 00000000..8b8d8ec1 --- /dev/null +++ b/packages/zola-zolarwind/inc/list.eta @@ -0,0 +1 @@ +<% const archive=firekylin.archive;let count=0; %>
<% while(archive.next()){count++;const meta=firekylin.metaByPost?.get(archive.row.id)||archive.row;const image=String(archive.row.options?.featuredImage||''); %>
<% if(/^(https?:\/\/|\/(?!\/))/i.test(image)){ %><% } %>

<%= archive.title() %>

<%= archive.excerpt(260) %>

<% } %>
<% if(!count){ %>

暂无文章。

<% } %> \ No newline at end of file diff --git a/packages/zola-zolarwind/inc/pagination.eta b/packages/zola-zolarwind/inc/pagination.eta new file mode 100644 index 00000000..8536cfdd --- /dev/null +++ b/packages/zola-zolarwind/inc/pagination.eta @@ -0,0 +1 @@ +<% const archive=firekylin.archive; const p=archive.pagination; %><% if(p && p.totalPages>1){ %><% } %> diff --git a/packages/zola-zolarwind/index.eta b/packages/zola-zolarwind/index.eta new file mode 100644 index 00000000..49e8ab67 --- /dev/null +++ b/packages/zola-zolarwind/index.eta @@ -0,0 +1 @@ +<% layout('./layout');const site=await firekylin.widget('Widget_Options');const archive=await firekylin.widget('Widget_Archive',{type:'index'});const metaByPost=new Map();const ids=archive.stack.map(p=>p.id).filter(Boolean);if(ids.length&&typeof archive.model==='function'){const related=await archive.model('post').where({id:['IN',ids]}).field('id').setRelation('cate,tag').select();related.forEach(p=>metaByPost.set(p.id,p));} %><% block('title',()=>{ %><%= archive.archiveTitle||site.title() %><% }) %><% block('content',()=>{ %>

<%= archive.archiveTitle||site.title() %>

<%~ include('./inc/list',{archive,metaByPost}) %><%~ include('./inc/pagination',{archive}) %><% }) %> \ No newline at end of file diff --git a/packages/zola-zolarwind/layout.eta b/packages/zola-zolarwind/layout.eta new file mode 100644 index 00000000..22635c59 --- /dev/null +++ b/packages/zola-zolarwind/layout.eta @@ -0,0 +1 @@ +<% const site=await firekylin.widget('Widget_Options');const cfg=site.themeConfig; %><%~ block('title') %>
<%= site.title() %>
<%~ block('content') %>
\ No newline at end of file diff --git a/packages/zola-zolarwind/package.json b/packages/zola-zolarwind/package.json new file mode 100644 index 00000000..8d63f45c --- /dev/null +++ b/packages/zola-zolarwind/package.json @@ -0,0 +1,36 @@ +{ + "name": "zola-zolarwind", + "title": "Zolarwind", + "version": "0.1.0", + "private": true, + "description": "Zolarwind’s original Tailwind 4 palettes, responsive image-card grid, sticky navigation and full-width article banners.", + "license": "MIT", + "author": "Thomas Weitzel; Firekylin contributors", + "homepage": "https://github.com/thomasweitzel/zolarwind", + "repository": { + "type": "git", + "url": "https://github.com/firekylin/themes.git", + "directory": "packages/zola-zolarwind" + }, + "firekylin": { + "type": "theme", + "id": "zola-zolarwind" + }, + "engines": { + "firekylin": ">=2.17.1" + }, + "configElements": [ + { + "type": "text", + "name": "authorName", + "label": "页脚作者", + "help": "留空使用站点标题。" + }, + { + "type": "text", + "name": "footerText", + "label": "页脚介绍", + "help": "留空使用站点描述。" + } + ] +} diff --git a/packages/zola-zolarwind/page.eta b/packages/zola-zolarwind/page.eta new file mode 100644 index 00000000..491a4b33 --- /dev/null +++ b/packages/zola-zolarwind/page.eta @@ -0,0 +1 @@ +<% layout('./layout');const site=await firekylin.widget('Widget_Options');const archive=await firekylin.widget('Widget_Archive',{type:'page'});const found=archive.next();const cover=String(archive.row?.options?.featuredImage||''); %><% block('title',()=>{ %><%= found?archive.title()+' · ':'' %><%= site.title() %><% }) %><% block('content',()=>{ %>
<% if(found){ %>

<%= archive.title() %>

<%~ archive.content() %>
<%~ firekylin.comment({title:'评论'}) %><% }else{ %>

暂无内容。

<% } %>
<% }) %> \ No newline at end of file diff --git a/packages/zola-zolarwind/post.eta b/packages/zola-zolarwind/post.eta new file mode 100644 index 00000000..0b9404f5 --- /dev/null +++ b/packages/zola-zolarwind/post.eta @@ -0,0 +1 @@ +<% layout('./layout');const site=await firekylin.widget('Widget_Options');const archive=await firekylin.widget('Widget_Archive',{type:'post'});const found=archive.next();const cover=String(archive.row?.options?.featuredImage||''); %><% block('title',()=>{ %><%= found?archive.title()+' · ':'' %><%= site.title() %><% }) %><% block('content',()=>{ %>
<% if(found){ %><% if(/^(https?:\/\/|\/(?!\/))/i.test(cover)){ %><% } %>

<%= archive.title() %>

<%~ archive.content() %>
<%~ firekylin.comment({title:'评论'}) %><% }else{ %>

暂无内容。

<% } %>
<% }) %> \ No newline at end of file diff --git a/packages/zola-zolarwind/res/css/source.css b/packages/zola-zolarwind/res/css/source.css new file mode 100644 index 00000000..ed45aa96 --- /dev/null +++ b/packages/zola-zolarwind/res/css/source.css @@ -0,0 +1,408 @@ +@import "tailwindcss"; + +@theme { + --color-*: initial; + /* Light-BEGIN */ + --color-neutral-50: oklch(0.147 0.004 49.250); + --color-neutral-100: oklch(0.216 0.006 56.043); + --color-neutral-200: oklch(0.268 0.007 34.298); + --color-neutral-300: oklch(0.374 0.010 67.558); + --color-neutral-400: oklch(0.444 0.011 73.639); + --color-neutral-500: oklch(0.553 0.013 58.071); + --color-neutral-600: oklch(0.709 0.010 56.259); + --color-neutral-700: oklch(0.869 0.005 56.366); + --color-neutral-800: oklch(0.923 0.003 48.717); + --color-neutral-900: oklch(0.970 0.001 106.424); + --color-neutral-950: oklch(0.985 0.001 106.423); + --color-primary-50: oklch(0.293 0.066 243.157); + --color-primary-100: oklch(0.391 0.090 240.876); + --color-primary-200: oklch(0.443 0.110 240.790); + --color-primary-300: oklch(0.500 0.134 242.749); + --color-primary-400: oklch(0.588 0.158 241.966); + --color-primary-500: oklch(0.685 0.169 237.323); + --color-primary-600: oklch(0.746 0.160 232.661); + --color-primary-700: oklch(0.828 0.111 230.318); + --color-primary-800: oklch(0.901 0.058 230.902); + --color-primary-900: oklch(0.951 0.026 236.824); + --color-primary-950: oklch(0.977 0.013 236.620); + --color-ok-50: oklch(0.266 0.065 152.934); + --color-ok-100: oklch(0.393 0.095 152.535); + --color-ok-200: oklch(0.448 0.119 151.328); + --color-ok-300: oklch(0.527 0.154 150.069); + --color-ok-400: oklch(0.627 0.194 149.214); + --color-ok-500: oklch(0.723 0.219 149.579); + --color-ok-600: oklch(0.792 0.209 151.711); + --color-ok-700: oklch(0.871 0.150 154.449); + --color-ok-800: oklch(0.925 0.084 155.995); + --color-ok-900: oklch(0.962 0.044 156.743); + --color-ok-950: oklch(0.982 0.018 155.826); + --color-warn-50: oklch(0.286 0.066 53.813); + --color-warn-100: oklch(0.421 0.095 57.708); + --color-warn-200: oklch(0.476 0.114 61.907); + --color-warn-300: oklch(0.554 0.135 66.442); + --color-warn-400: oklch(0.681 0.162 75.834); + --color-warn-500: oklch(0.795 0.184 86.047); + --color-warn-600: oklch(0.852 0.199 91.936); + --color-warn-700: oklch(0.905 0.182 98.111); + --color-warn-800: oklch(0.945 0.129 101.540); + --color-warn-900: oklch(0.973 0.071 103.193); + --color-warn-950: oklch(0.987 0.026 102.212); + --color-fail-50: oklch(0.258 0.092 26.042); + --color-fail-100: oklch(0.396 0.141 25.723); + --color-fail-200: oklch(0.444 0.177 26.899); + --color-fail-300: oklch(0.505 0.213 27.518); + --color-fail-400: oklch(0.577 0.245 27.325); + --color-fail-500: oklch(0.637 0.237 25.331); + --color-fail-600: oklch(0.704 0.191 22.216); + --color-fail-700: oklch(0.808 0.114 19.571); + --color-fail-800: oklch(0.885 0.062 18.334); + --color-fail-900: oklch(0.936 0.032 17.717); + --color-fail-950: oklch(0.971 0.013 17.380); + /* Light-END */ +} + +@layer base { + [data-theme="dark"] { + /* Dark-BEGIN */ + --color-neutral-50: oklch(0.985 0.001 106.423); + --color-neutral-100: oklch(0.970 0.001 106.424); + --color-neutral-200: oklch(0.923 0.003 48.717); + --color-neutral-300: oklch(0.869 0.005 56.366); + --color-neutral-400: oklch(0.709 0.010 56.259); + --color-neutral-500: oklch(0.553 0.013 58.071); + --color-neutral-600: oklch(0.444 0.011 73.639); + --color-neutral-700: oklch(0.374 0.010 67.558); + --color-neutral-800: oklch(0.268 0.007 34.298); + --color-neutral-900: oklch(0.216 0.006 56.043); + --color-neutral-950: oklch(0.147 0.004 49.250); + --color-primary-50: oklch(0.977 0.013 236.620); + --color-primary-100: oklch(0.951 0.026 236.824); + --color-primary-200: oklch(0.901 0.058 230.902); + --color-primary-300: oklch(0.828 0.111 230.318); + --color-primary-400: oklch(0.746 0.160 232.661); + --color-primary-500: oklch(0.685 0.169 237.323); + --color-primary-600: oklch(0.588 0.158 241.966); + --color-primary-700: oklch(0.500 0.134 242.749); + --color-primary-800: oklch(0.443 0.110 240.790); + --color-primary-900: oklch(0.391 0.090 240.876); + --color-primary-950: oklch(0.293 0.066 243.157); + --color-ok-50: oklch(0.982 0.018 155.826); + --color-ok-100: oklch(0.962 0.044 156.743); + --color-ok-200: oklch(0.925 0.084 155.995); + --color-ok-300: oklch(0.871 0.150 154.449); + --color-ok-400: oklch(0.792 0.209 151.711); + --color-ok-500: oklch(0.723 0.219 149.579); + --color-ok-600: oklch(0.627 0.194 149.214); + --color-ok-700: oklch(0.527 0.154 150.069); + --color-ok-800: oklch(0.448 0.119 151.328); + --color-ok-900: oklch(0.393 0.095 152.535); + --color-ok-950: oklch(0.266 0.065 152.934); + --color-warn-50: oklch(0.987 0.026 102.212); + --color-warn-100: oklch(0.973 0.071 103.193); + --color-warn-200: oklch(0.945 0.129 101.540); + --color-warn-300: oklch(0.905 0.182 98.111); + --color-warn-400: oklch(0.852 0.199 91.936); + --color-warn-500: oklch(0.795 0.184 86.047); + --color-warn-600: oklch(0.681 0.162 75.834); + --color-warn-700: oklch(0.554 0.135 66.442); + --color-warn-800: oklch(0.476 0.114 61.907); + --color-warn-900: oklch(0.421 0.095 57.708); + --color-warn-950: oklch(0.286 0.066 53.813); + --color-fail-50: oklch(0.971 0.013 17.380); + --color-fail-100: oklch(0.936 0.032 17.717); + --color-fail-200: oklch(0.885 0.062 18.334); + --color-fail-300: oklch(0.808 0.114 19.571); + --color-fail-400: oklch(0.704 0.191 22.216); + --color-fail-500: oklch(0.637 0.237 25.331); + --color-fail-600: oklch(0.577 0.245 27.325); + --color-fail-700: oklch(0.505 0.213 27.518); + --color-fail-800: oklch(0.444 0.177 26.899); + --color-fail-900: oklch(0.396 0.141 25.723); + --color-fail-950: oklch(0.258 0.092 26.042); + /* Dark-END */ + } +} + +@layer utilities { + .badge { + @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-sm font-medium leading-4; + } + .badge-neutral { + @apply bg-neutral-800 text-neutral-100; + } + .badge-fail { + @apply bg-fail-800 text-fail-100; + } + .badge-warn { + @apply bg-warn-800 text-warn-100; + } + .badge-ok { + @apply bg-ok-800 text-ok-100; + } + .badge-primary { + @apply bg-primary-800 text-primary-100; + } + + .prose { + @apply leading-normal; + } + .prose > * + * { + @apply mt-4; + } + .prose strong { + @apply text-neutral-50 font-bold; + } + .prose strong a { + @apply font-bold; + } + .prose h1 { + @apply text-2xl font-bold mt-8; + } + .prose h2 { + @apply text-xl font-bold mt-8; + } + .prose h3 { + @apply text-lg font-bold mt-8; + } + .prose h4 { + @apply text-base font-bold mt-4; + } + .prose p { + @apply mt-4; + } + .prose blockquote { + @apply border-l-4 border-neutral-500 pl-4 italic; + } + .prose blockquote.attention, .prose blockquote.hippo { + @apply relative ml-20 mt-4; + } + .prose blockquote.attention::before { + background-image: none; + @apply absolute content-[''] w-16 -left-20 top-0 bottom-0 border-neutral-300 bg-no-repeat; + } + [data-theme="dark"] .prose blockquote.attention::before { + background-image: none; + @apply absolute content-[''] w-16 -left-20 top-0 bottom-0 border-neutral-300 bg-no-repeat; + } + .prose blockquote.hippo::before { + background-image: none; + @apply absolute content-[''] w-16 -left-20 top-0 bottom-0 border-neutral-300 bg-no-repeat; + } + [data-theme="dark"] .prose blockquote.hippo::before { + background-image: none; + @apply absolute content-[''] w-16 -left-20 top-0 bottom-0 border-neutral-300 bg-no-repeat; + } + .prose kbd, + .prose p code, + .prose ol code, + .prose ul code, + .prose blockquote code, + .prose h1 code, + .prose h2 code, + .prose h3 code, + .prose h4 code { + @apply mx-0.5 px-1 py-0.5 font-mono bg-neutral-700 rounded shadow-sm ring-1 ring-neutral-100/25; + } + .prose pre.mermaid { + @apply block my-4 font-mono text-sm overflow-x-auto; + } + .prose pre:not(.mermaid) { + @apply block my-4 p-4 rounded-md font-mono text-sm shadow-md overflow-x-auto; + } + .prose ul { + @apply pl-4 list-disc; + @apply sm:pl-8; + } + .prose .tags-list { + @apply pl-0 list-none; + } + .prose ol { + @apply pl-4 list-decimal; + @apply sm:pl-8; + } + .prose td, .prose th { + @apply px-2 py-1 border border-neutral-700; + } + .prose tbody tr:nth-child(odd) { + @apply bg-neutral-800; + } + .prose table { + @apply my-4; + } + .prose img { + @apply my-4 rounded-md; + } + .prose img.image-shortcode { + @apply my-0; + } + .prose details { + @apply my-4 rounded-md border border-neutral-700 bg-neutral-900/40; + } + .prose summary { + @apply cursor-pointer select-none px-3 py-2 font-medium; + } + .prose details > *:not(summary) { + @apply px-3 pb-3; + } + .prose mark { + @apply mx-0.5 px-1 py-0.5 bg-primary-800 text-primary-100 rounded shadow-sm ring-1 ring-primary-800; + } + .prose del { + @apply text-neutral-500; + } + .prose dl { + @apply my-4; + } + .prose dt { + @apply font-semibold text-neutral-100; + } + .prose dd { + @apply ml-4 text-neutral-300; + } + .prose sup { + @apply text-xs align-text-top; + } + .prose sub { + @apply text-xs align-text-bottom; + } + .prose hr { + @apply border-0 border-t border-neutral-700 my-4 h-0 p-0; + } + .prose .footnote-definition:first-of-type { + @apply mt-4 border-0 pt-0; + } + .prose hr + .footnote-definition { + @apply mt-0; + } + .prose .footnote-definition { + @apply mt-2 text-sm text-neutral-300 grid grid-cols-[max-content_1fr] gap-x-2 items-start; + } + .prose .footnote-definition-label { + @apply text-xs col-start-1 text-primary-300; + } + .prose .footnote-definition p { + @apply m-0 col-start-2; + } + .prose .footnote-definition p + p { + @apply mt-2; + } + .prose .footnote-reference { + @apply text-xs align-text-top; + } + .prose p.inline-block img { + @apply inline-block; + } + .toc ul { + @apply list-disc ml-4; + } + .toc ol { + @apply list-decimal ml-4; + } + .toc li + li { + @apply mt-1; + } + .toc h2 { + @apply mt-0; + } + /* Since Zola v0.22.0: needed for `extra_grammars` and/or `extra_themes` */ + .prose .giallo-l { + @apply inline-block w-full min-h-[1lh]; + } + .prose .giallo-ln { + @apply inline-block select-none text-right opacity-80 mr-[0.4em] p-[0.4em] min-w-[3ch]; + } + + body { + @apply text-neutral-200 bg-neutral-950 antialiased; + } + h1 { + @apply text-2xl font-bold; + } + h2 { + @apply text-xl font-bold; + } + h3 { + @apply text-lg font-bold; + } + h4 { + @apply text-base font-bold; + } + a { + @apply text-primary-300 no-underline; + } + a:hover { + @apply text-primary-200; + } +} + +:root { + background-color: var(--color-neutral-950); +} + +html { + --scrollbar-bg: var(--color-neutral-950); + --scrollbar-thumb: var(--color-neutral-700); + --scrollbar-thumb-hover: var(--color-neutral-500); + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-bg); + scroll-padding-top: 4rem; +} + +input[type="search"] { + -webkit-appearance: none; + appearance: none; +} + +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; + appearance: none; + display: none; +} + + +/* See https://github.com/mermaid-js/mermaid/issues/2644#issuecomment-2313201033 */ +[data-theme='dark'] .mermaid { + filter: invert(0.85) hue-rotate(45deg); +} + +/* WebKit */ +::-webkit-scrollbar { + width: 12px; + height: 12px; +} + +::-webkit-scrollbar-track { + background: var(--scrollbar-bg); +} + +::-webkit-scrollbar-thumb { + background-color: var(--scrollbar-thumb); + border-radius: 6px; + border: 3px solid var(--scrollbar-bg); +} + +::-webkit-scrollbar-thumb:hover { + background-color: var(--scrollbar-thumb-hover); +} + +/* Used for light-dark-image, default: show light image, hide dark image; light images comes first */ +.light-dark-image img:nth-child(1) { + display: block; +} + +.light-dark-image img:nth-child(2) { + display: none; +} + +/* Dark mode: Show dark image, hide light image */ +html[data-theme="dark"] .light-dark-image img:nth-child(1) { + display: none; +} + +html[data-theme="dark"] .light-dark-image img:nth-child(2) { + display: block; +} + +/* Artalk */ +#Comments.atk-hide-copyright .atk-copyright { + display: none; +} diff --git a/packages/zola-zolarwind/res/css/theme.css b/packages/zola-zolarwind/res/css/theme.css new file mode 100644 index 00000000..0824751b --- /dev/null +++ b/packages/zola-zolarwind/res/css/theme.css @@ -0,0 +1,9 @@ +@import url('upstream.css'); + +/* Native Firekylin integration: controls, media and code containment. */ +body{overflow-wrap:anywhere}.skip{position:absolute;top:-100px}.skip:focus{top:0;z-index:99}.native-nav{display:flex;flex-wrap:wrap;gap:10px 16px;margin:18px 0}.prose pre,.prose table{display:block;max-width:100%;overflow:auto}.prose img,.attachment-media img{max-width:100%;height:auto;margin:1rem 0}.attachment-media video,.attachment-media audio,.prose iframe{max-width:100%}.prose ul{list-style:disc}.prose ol{list-style:decimal}.prose li{display:list-item}.pagination{display:flex;justify-content:space-between;gap:12px;margin:32px 0}.post-actions,.tags{display:flex;gap:12px;flex-wrap:wrap;margin:24px 0}input,button{font:inherit;max-width:100%;box-sizing:border-box}form{display:flex;flex-wrap:wrap;gap:10px}input{min-width:0}:focus-visible{outline:2px solid currentColor;outline-offset:3px} + +/* Native post/page media may declare dimensions wider than the reading column. */ +.prose video,.prose audio{max-width:100%;box-sizing:border-box}.prose video{height:auto} + +html{color-scheme:light}html[data-theme=dark]{color-scheme:dark}.native-header{position:sticky;top:0;background:var(--color-neutral-950);z-index:10}.header-inner,.content-inner,.site-footer{max-width:1280px;margin:auto;padding:1rem 2rem}.header-inner{display:flex;align-items:center;gap:1.5rem;flex-wrap:wrap;border-bottom:1px solid var(--color-neutral-700)}.native-nav{margin:0}.site-name{font-weight:700}.theme-toggle{margin-left:auto}.theme-toggle[hidden]{display:none}.content-inner{padding-top:2rem;padding-bottom:2rem}.listing-heading{margin-bottom:2rem}.post-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}.entry{display:flex;flex-direction:column;border:1px solid var(--color-neutral-900);border-radius:.375rem;overflow:hidden;background:var(--color-neutral-900);box-shadow:0 4px 6px -1px var(--color-neutral-800)}.entry-cover{width:100%;height:12rem;object-fit:cover}.entry-body{padding:1.5rem;display:flex;flex:1;flex-direction:column}.entry h2{font-size:1.25rem;line-height:1.4;margin:.5rem 0}.entry h2 a{color:var(--color-neutral-100)}.entry-excerpt{margin-top:.75rem;color:var(--color-neutral-400);flex:1}.entry-date{margin-top:1.5rem;color:var(--color-neutral-400);font-size:.875rem}.entry-tags{display:flex;flex-wrap:wrap;gap:.5rem;font-size:.875rem}.pagination a{padding:.5rem 1rem;border-radius:.375rem;background:var(--color-primary-300);color:var(--color-neutral-950)}.post-title{font-size:2rem;margin:1.5rem 0}.post-date{font-size:.875rem;margin-bottom:1rem}.article-banner{width:100%;height:12rem;object-fit:cover;border-radius:.375rem}.prose pre{box-sizing:border-box;white-space:pre}.prose pre code{display:block;white-space:pre;width:max-content;min-width:100%}.prose table{display:table;width:100%;table-layout:fixed}.prose td,.prose th{padding:.5rem;overflow-wrap:anywhere;border:1px solid var(--color-neutral-700)}.prose audio{height:54px}.prose h2,.prose h3,.prose h4{margin:2rem 0 1rem}.prose p{margin:1rem 0}.site-footer{text-align:center;color:var(--color-neutral-400)}.site-footer p{margin:.75rem 0}.skip:focus{z-index:30;background:var(--color-neutral-950);padding:.5rem}input,button{background:var(--color-neutral-900);color:var(--color-neutral-100);padding:.4rem .6rem;border:1px solid var(--color-neutral-700);border-radius:.375rem}.article-toc{border:1px solid var(--color-neutral-700);border-radius:.375rem;padding:1rem;margin:1rem 0}.toc-list{padding-left:1.2rem}.toc-list .nested{margin-left:1rem}@media(max-width:1023px){.post-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:767px){.post-grid{grid-template-columns:1fr}.header-inner,.content-inner,.site-footer{padding:1rem}.native-header{position:static}.header-inner{gap:.8rem}.theme-toggle{margin-left:0}.post-title{font-size:1.65rem}}@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}} diff --git a/packages/zola-zolarwind/res/css/upstream.css b/packages/zola-zolarwind/res/css/upstream.css new file mode 100644 index 00000000..683412ea --- /dev/null +++ b/packages/zola-zolarwind/res/css/upstream.css @@ -0,0 +1,2 @@ +/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */ +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:""}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--container-md:28rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--leading-normal:1.5;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-2xl:1rem;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-neutral-50:oklch(14.7% .004 49.25);--color-neutral-100:oklch(21.6% .006 56.043);--color-neutral-200:oklch(26.8% .007 34.298);--color-neutral-300:oklch(37.4% .01 67.558);--color-neutral-400:oklch(44.4% .011 73.639);--color-neutral-500:oklch(55.3% .013 58.071);--color-neutral-700:oklch(86.9% .005 56.366);--color-neutral-800:oklch(92.3% .003 48.717);--color-neutral-900:oklch(97% .001 106.424);--color-neutral-950:oklch(98.5% .001 106.423);--color-primary-100:oklch(39.1% .09 240.876);--color-primary-200:oklch(44.3% .11 240.79);--color-primary-300:oklch(50% .134 242.749);--color-primary-400:oklch(58.8% .158 241.966);--color-primary-500:oklch(68.5% .169 237.323);--color-primary-600:oklch(74.6% .16 232.661);--color-primary-700:oklch(82.8% .111 230.318);--color-primary-800:oklch(90.1% .058 230.902);--color-primary-950:oklch(97.7% .013 236.62);--color-ok-100:oklch(39.3% .095 152.535);--color-ok-600:oklch(79.2% .209 151.711);--color-ok-800:oklch(92.5% .084 155.995);--color-warn-100:oklch(42.1% .095 57.708);--color-warn-600:oklch(85.2% .199 91.936);--color-warn-700:oklch(90.5% .182 98.111);--color-warn-800:oklch(94.5% .129 101.54);--color-fail-100:oklch(39.6% .141 25.723);--color-fail-200:oklch(44.4% .177 26.899);--color-fail-300:oklch(50.5% .213 27.518);--color-fail-600:oklch(70.4% .191 22.216);--color-fail-800:oklch(88.5% .062 18.334)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}[data-theme=dark]{--color-neutral-50:oklch(98.5% .001 106.423);--color-neutral-100:oklch(97% .001 106.424);--color-neutral-200:oklch(92.3% .003 48.717);--color-neutral-300:oklch(86.9% .005 56.366);--color-neutral-400:oklch(70.9% .01 56.259);--color-neutral-500:oklch(55.3% .013 58.071);--color-neutral-600:oklch(44.4% .011 73.639);--color-neutral-700:oklch(37.4% .01 67.558);--color-neutral-800:oklch(26.8% .007 34.298);--color-neutral-900:oklch(21.6% .006 56.043);--color-neutral-950:oklch(14.7% .004 49.25);--color-primary-50:oklch(97.7% .013 236.62);--color-primary-100:oklch(95.1% .026 236.824);--color-primary-200:oklch(90.1% .058 230.902);--color-primary-300:oklch(82.8% .111 230.318);--color-primary-400:oklch(74.6% .16 232.661);--color-primary-500:oklch(68.5% .169 237.323);--color-primary-600:oklch(58.8% .158 241.966);--color-primary-700:oklch(50% .134 242.749);--color-primary-800:oklch(44.3% .11 240.79);--color-primary-900:oklch(39.1% .09 240.876);--color-primary-950:oklch(29.3% .066 243.157);--color-ok-50:oklch(98.2% .018 155.826);--color-ok-100:oklch(96.2% .044 156.743);--color-ok-200:oklch(92.5% .084 155.995);--color-ok-300:oklch(87.1% .15 154.449);--color-ok-400:oklch(79.2% .209 151.711);--color-ok-500:oklch(72.3% .219 149.579);--color-ok-600:oklch(62.7% .194 149.214);--color-ok-700:oklch(52.7% .154 150.069);--color-ok-800:oklch(44.8% .119 151.328);--color-ok-900:oklch(39.3% .095 152.535);--color-ok-950:oklch(26.6% .065 152.934);--color-warn-50:oklch(98.7% .026 102.212);--color-warn-100:oklch(97.3% .071 103.193);--color-warn-200:oklch(94.5% .129 101.54);--color-warn-300:oklch(90.5% .182 98.111);--color-warn-400:oklch(85.2% .199 91.936);--color-warn-500:oklch(79.5% .184 86.047);--color-warn-600:oklch(68.1% .162 75.834);--color-warn-700:oklch(55.4% .135 66.442);--color-warn-800:oklch(47.6% .114 61.907);--color-warn-900:oklch(42.1% .095 57.708);--color-warn-950:oklch(28.6% .066 53.813);--color-fail-50:oklch(97.1% .013 17.38);--color-fail-100:oklch(93.6% .032 17.717);--color-fail-200:oklch(88.5% .062 18.334);--color-fail-300:oklch(80.8% .114 19.571);--color-fail-400:oklch(70.4% .191 22.216);--color-fail-500:oklch(63.7% .237 25.331);--color-fail-600:oklch(57.7% .245 27.325);--color-fail-700:oklch(50.5% .213 27.518);--color-fail-800:oklch(44.4% .177 26.899);--color-fail-900:oklch(39.6% .141 25.723);--color-fail-950:oklch(25.8% .092 26.042)}}@layer components;@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-0{top:0}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing) * 2)}.right-2{right:calc(var(--spacing) * 2)}.right-3{right:calc(var(--spacing) * 3)}.z-10{z-index:10}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.m-0{margin:0}.m-4{margin:calc(var(--spacing) * 4)}.mx-1{margin-inline:var(--spacing)}.mx-auto{margin-inline:auto}.my-6{margin-block:calc(var(--spacing) * 6)}.mt-0{margin-top:0}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-10{margin-top:calc(var(--spacing) * 10)}.-mr-2{margin-right:calc(var(--spacing) * -2)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-8{margin-left:calc(var(--spacing) * 8)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.table-cell{display:table-cell}.size-1{width:var(--spacing);height:var(--spacing)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.h-2{height:calc(var(--spacing) * 2)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-8{height:calc(var(--spacing) * 8)}.h-16{height:calc(var(--spacing) * 16)}.h-40{height:calc(var(--spacing) * 40)}.h-48{height:calc(var(--spacing) * 48)}.h-64{height:calc(var(--spacing) * 64)}.h-84{height:calc(var(--spacing) * 84)}.h-120{height:calc(var(--spacing) * 120)}.h-full{height:100%}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-8{width:calc(var(--spacing) * 8)}.w-16{width:calc(var(--spacing) * 16)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.max-w-md{max-width:var(--container-md)}.flex-1{flex:1}.flex-auto{flex:auto}.flex-none{flex:none}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.grow{flex-grow:1}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.transform\!{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)!important}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-none{list-style-type:none}.appearance-none{appearance:none}.grid-flow-row{grid-auto-flow:row}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-rows-4{grid-template-rows:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:var(--spacing)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(var(--spacing) * var(--tw-space-x-reverse));margin-inline-end:calc(var(--spacing) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 8) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-x-reverse)))}.overflow-hidden{overflow:hidden}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border,.border-1{border-style:var(--tw-border-style);border-width:1px}.border-b-1{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-neutral-700{border-color:var(--color-neutral-700)}.border-neutral-800{border-color:var(--color-neutral-800)}.border-neutral-900{border-color:var(--color-neutral-900)}.border-primary-700{border-color:var(--color-primary-700)}.border-transparent{border-color:#0000}.border-warn-600{border-color:var(--color-warn-600)}.bg-neutral-800{background-color:var(--color-neutral-800)}.bg-neutral-900{background-color:var(--color-neutral-900)}.bg-neutral-900\/40{background-color:#f5f5f466}@supports (color:color-mix(in lab, red, red)){.bg-neutral-900\/40{background-color:color-mix(in oklab, var(--color-neutral-900) 40%, transparent)}}.bg-neutral-950{background-color:var(--color-neutral-950)}.bg-primary-300{background-color:var(--color-primary-300)}.bg-primary-400{background-color:var(--color-primary-400)}.bg-primary-500{background-color:var(--color-primary-500)}.bg-primary-600{background-color:var(--color-primary-600)}.bg-primary-700{background-color:var(--color-primary-700)}.bg-primary-800{background-color:var(--color-primary-800)}.bg-primary-950{background-color:var(--color-primary-950)}.bg-warn-700{background-color:var(--color-warn-700)}.bg-gradient-to-tr{--tw-gradient-position:to top right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-ok-600{--tw-gradient-from:var(--color-ok-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.from-primary-600{--tw-gradient-from:var(--color-primary-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-primary-800{--tw-gradient-to:var(--color-primary-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-warn-800{--tw-gradient-to:var(--color-warn-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.fill-current{fill:currentColor}.object-cover{object-fit:cover}.p-0{padding:0}.p-2{padding:calc(var(--spacing) * 2)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-8{padding-top:calc(var(--spacing) * 8)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-10{padding-right:calc(var(--spacing) * 10)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-8{padding-bottom:calc(var(--spacing) * 8)}.pl-2{padding-left:calc(var(--spacing) * 2)}.text-center{text-align:center}.align-middle{vertical-align:middle}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.leading-7{--tw-leading:calc(var(--spacing) * 7);line-height:calc(var(--spacing) * 7)}.leading-none{--tw-leading:1;line-height:1}.leading-normal{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.text-wrap{text-wrap:wrap}.text-fail-200{color:var(--color-fail-200)}.text-fail-300{color:var(--color-fail-300)}.text-neutral-100{color:var(--color-neutral-100)}.text-neutral-200{color:var(--color-neutral-200)}.text-neutral-300{color:var(--color-neutral-300)}.text-neutral-400{color:var(--color-neutral-400)}.text-neutral-500{color:var(--color-neutral-500)}.text-neutral-950{color:var(--color-neutral-950)}.text-primary-600{color:var(--color-primary-600)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.italic{font-style:italic}.ordinal{--tw-ordinal:ordinal;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.placeholder-neutral-500::placeholder{color:var(--color-neutral-500)}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring,.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-neutral-800{--tw-shadow-color:oklch(92.3% .003 48.717)}@supports (color:color-mix(in lab, red, red)){.shadow-neutral-800{--tw-shadow-color:color-mix(in oklab, var(--color-neutral-800) var(--tw-shadow-alpha), transparent)}}.ring-primary-300{--tw-ring-color:var(--color-primary-300)}.blur\!{--tw-blur:blur(8px)!important;filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)!important}.drop-shadow{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#0000001a)) drop-shadow(0 1px 1px var(--tw-drop-shadow-color,#0000000f));--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a) drop-shadow(0 1px 1px #0000000f);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-250{--tw-duration:.25s;transition-duration:.25s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.select-none{-webkit-user-select:none;user-select:none}@media (hover:hover){.hover\:bg-fail-600:hover{background-color:var(--color-fail-600)}.hover\:bg-neutral-700:hover{background-color:var(--color-neutral-700)}.hover\:bg-primary-500:hover{background-color:var(--color-primary-500)}.hover\:text-fail-600:hover{color:var(--color-fail-600)}.hover\:text-neutral-50:hover{color:var(--color-neutral-50)}.hover\:text-neutral-200:hover{color:var(--color-neutral-200)}.hover\:text-neutral-300:hover{color:var(--color-neutral-300)}.hover\:text-neutral-400:hover{color:var(--color-neutral-400)}}.focus\:bg-neutral-200:focus{background-color:var(--color-neutral-200)}.focus\:bg-neutral-300:focus{background-color:var(--color-neutral-300)}.focus\:text-neutral-700:focus{color:var(--color-neutral-700)}.focus\:text-neutral-800:focus{color:var(--color-neutral-800)}.focus\:text-neutral-950:focus{color:var(--color-neutral-950)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-primary-300:focus{--tw-ring-color:var(--color-primary-300)}.focus\:ring-primary-500:focus{--tw-ring-color:var(--color-primary-500)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-neutral-950:focus{--tw-ring-offset-color:var(--color-neutral-950)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:bg-primary-600:active{background-color:var(--color-primary-600)}@media (min-width:40rem){.sm\:px-3{padding-inline:calc(var(--spacing) * 3)}.sm\:px-6{padding-inline:calc(var(--spacing) * 6)}}@media (min-width:48rem){.md\:block{display:block}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:px-8{padding-inline:calc(var(--spacing) * 8)}.lg\:text-left{text-align:left}}@media (min-width:80rem){.xl\:rounded-md{border-radius:var(--radius-md)}.xl\:border-2{border-style:var(--tw-border-style);border-width:2px}.xl\:border-primary-600{border-color:var(--color-primary-600)}}@media (prefers-color-scheme:dark){.dark\:bg-neutral-200{background-color:var(--color-neutral-200)}}.badge{padding-inline:calc(var(--spacing) * 2.5);padding-block:calc(var(--spacing) * .5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:calc(var(--spacing) * 4);line-height:calc(var(--spacing) * 4);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-radius:3.40282e38px;align-items:center;display:inline-flex}.badge-neutral{background-color:var(--color-neutral-800);color:var(--color-neutral-100)}.badge-fail{background-color:var(--color-fail-800);color:var(--color-fail-100)}.badge-warn{background-color:var(--color-warn-800);color:var(--color-warn-100)}.badge-ok{background-color:var(--color-ok-800);color:var(--color-ok-100)}.badge-primary{background-color:var(--color-primary-800);color:var(--color-primary-100)}.prose{--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.prose>*+*{margin-top:calc(var(--spacing) * 4)}.prose strong{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);color:var(--color-neutral-50)}.prose strong a{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.prose h1{margin-top:calc(var(--spacing) * 8);font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.prose h2{margin-top:calc(var(--spacing) * 8);font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.prose h3{margin-top:calc(var(--spacing) * 8);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.prose h4{margin-top:calc(var(--spacing) * 4);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.prose p{margin-top:calc(var(--spacing) * 4)}.prose blockquote{border-left-style:var(--tw-border-style);border-left-width:4px;border-color:var(--color-neutral-500);padding-left:calc(var(--spacing) * 4);font-style:italic}.prose blockquote.attention,.prose blockquote.hippo{margin-top:calc(var(--spacing) * 4);margin-left:calc(var(--spacing) * 20);position:relative}.prose blockquote.attention:before{top:0;bottom:0;left:calc(var(--spacing) * -20);width:calc(var(--spacing) * 16);border-color:var(--color-neutral-300);--tw-content:"";content:var(--tw-content);background-image:none;background-repeat:no-repeat;position:absolute}[data-theme=dark] .prose blockquote.attention:before{top:0;bottom:0;left:calc(var(--spacing) * -20);width:calc(var(--spacing) * 16);border-color:var(--color-neutral-300);--tw-content:"";content:var(--tw-content);background-image:none;background-repeat:no-repeat;position:absolute}.prose blockquote.hippo:before{top:0;bottom:0;left:calc(var(--spacing) * -20);width:calc(var(--spacing) * 16);border-color:var(--color-neutral-300);--tw-content:"";content:var(--tw-content);background-image:none;background-repeat:no-repeat;position:absolute}[data-theme=dark] .prose blockquote.hippo:before{top:0;bottom:0;left:calc(var(--spacing) * -20);width:calc(var(--spacing) * 16);border-color:var(--color-neutral-300);--tw-content:"";content:var(--tw-content);background-image:none;background-repeat:no-repeat;position:absolute}.prose kbd,.prose p code,.prose ol code,.prose ul code,.prose blockquote code,.prose h1 code,.prose h2 code,.prose h3 code,.prose h4 code{margin-inline:calc(var(--spacing) * .5);background-color:var(--color-neutral-700);padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);font-family:var(--font-mono);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#1c191740;border-radius:.25rem}@supports (color:color-mix(in lab, red, red)){.prose kbd,.prose p code,.prose ol code,.prose ul code,.prose blockquote code,.prose h1 code,.prose h2 code,.prose h3 code,.prose h4 code{--tw-ring-color:color-mix(in oklab, var(--color-neutral-100) 25%, transparent)}}.prose pre.mermaid{margin-block:calc(var(--spacing) * 4);font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));display:block;overflow-x:auto}.prose pre:not(.mermaid){margin-block:calc(var(--spacing) * 4);border-radius:var(--radius-md);padding:calc(var(--spacing) * 4);font-family:var(--font-mono);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);display:block;overflow-x:auto}.prose ul{padding-left:calc(var(--spacing) * 4);list-style-type:disc}@media (min-width:40rem){.prose ul{padding-left:calc(var(--spacing) * 8)}}.prose .tags-list{padding-left:0;list-style-type:none}.prose ol{padding-left:calc(var(--spacing) * 4);list-style-type:decimal}@media (min-width:40rem){.prose ol{padding-left:calc(var(--spacing) * 8)}}.prose td,.prose th{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-neutral-700);padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing)}.prose tbody tr:nth-child(odd){background-color:var(--color-neutral-800)}.prose table{margin-block:calc(var(--spacing) * 4)}.prose img{margin-block:calc(var(--spacing) * 4);border-radius:var(--radius-md)}.prose img.image-shortcode{margin-block:0}.prose details{margin-block:calc(var(--spacing) * 4);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-neutral-700);background-color:#f5f5f466}@supports (color:color-mix(in lab, red, red)){.prose details{background-color:color-mix(in oklab, var(--color-neutral-900) 40%, transparent)}}.prose summary{cursor:pointer;padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);-webkit-user-select:none;user-select:none}.prose details>:not(summary){padding-inline:calc(var(--spacing) * 3);padding-bottom:calc(var(--spacing) * 3)}.prose mark{margin-inline:calc(var(--spacing) * .5);background-color:var(--color-primary-800);padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);color:var(--color-primary-100);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:var(--color-primary-800);border-radius:.25rem}.prose del{color:var(--color-neutral-500)}.prose dl{margin-block:calc(var(--spacing) * 4)}.prose dt{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-neutral-100)}.prose dd{margin-left:calc(var(--spacing) * 4);color:var(--color-neutral-300)}.prose sup{vertical-align:text-top;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.prose sub{vertical-align:text-bottom;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.prose hr{margin-block:calc(var(--spacing) * 4);border-style:var(--tw-border-style);border-width:0;border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-neutral-700);height:0;padding:0}.prose .footnote-definition:first-of-type{margin-top:calc(var(--spacing) * 4);border-style:var(--tw-border-style);border-width:0;padding-top:0}.prose hr+.footnote-definition{margin-top:0}.prose .footnote-definition{margin-top:calc(var(--spacing) * 2);align-items:flex-start;column-gap:calc(var(--spacing) * 2);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--color-neutral-300);grid-template-columns:max-content 1fr;display:grid}.prose .footnote-definition-label{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:var(--color-primary-300);grid-column-start:1}.prose .footnote-definition p{grid-column-start:2;margin:0}.prose .footnote-definition p+p{margin-top:calc(var(--spacing) * 2)}.prose .footnote-reference{vertical-align:text-top;font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.prose p.inline-block img{display:inline-block}.toc ul{margin-left:calc(var(--spacing) * 4);list-style-type:disc}.toc ol{margin-left:calc(var(--spacing) * 4);list-style-type:decimal}.toc li+li{margin-top:var(--spacing)}.toc h2{margin-top:0}.prose .giallo-l{width:100%;min-height:1lh;display:inline-block}.prose .giallo-ln{text-align:right;opacity:.8;-webkit-user-select:none;user-select:none;min-width:3ch;margin-right:.4em;padding:.4em;display:inline-block}body{background-color:var(--color-neutral-950);color:var(--color-neutral-200);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}h2{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}h3{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}h4{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}a{color:var(--color-primary-300);text-decoration-line:none}a:hover{color:var(--color-primary-200)}}:root{background-color:var(--color-neutral-950)}html{--scrollbar-bg:var(--color-neutral-950);--scrollbar-thumb:var(--color-neutral-700);--scrollbar-thumb-hover:var(--color-neutral-500);scrollbar-color:var(--scrollbar-thumb) var(--scrollbar-bg);scroll-padding-top:4rem}input[type=search]{appearance:none}input[type=search]::-webkit-search-cancel-button{appearance:none;display:none}[data-theme=dark] .mermaid{filter:invert(.85)hue-rotate(45deg)}::-webkit-scrollbar{width:12px;height:12px}::-webkit-scrollbar-track{background:var(--scrollbar-bg)}::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb);border:3px solid var(--scrollbar-bg);border-radius:6px}::-webkit-scrollbar-thumb:hover{background-color:var(--scrollbar-thumb-hover)}.light-dark-image img:first-child{display:block}.light-dark-image img:nth-child(2),html[data-theme=dark] .light-dark-image img:first-child{display:none}html[data-theme=dark] .light-dark-image img:nth-child(2){display:block}#Comments.atk-hide-copyright .atk-copyright{display:none}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""} \ No newline at end of file diff --git a/packages/zola-zolarwind/res/js/init.js b/packages/zola-zolarwind/res/js/init.js new file mode 100644 index 00000000..145f535d --- /dev/null +++ b/packages/zola-zolarwind/res/js/init.js @@ -0,0 +1 @@ +(()=>{try{const t=localStorage.getItem('zolarwind-theme');document.documentElement.dataset.theme=t==='light'||t==='dark'?t:matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light'}catch(e){}})(); \ No newline at end of file diff --git a/packages/zola-zolarwind/res/js/theme.js b/packages/zola-zolarwind/res/js/theme.js new file mode 100644 index 00000000..3b1ed1a5 --- /dev/null +++ b/packages/zola-zolarwind/res/js/theme.js @@ -0,0 +1 @@ +(()=>{const b=document.querySelector('#dark-mode-toggle');if(b){b.hidden=false;b.onclick=()=>{const t=document.documentElement.dataset.theme==='dark'?'light':'dark';document.documentElement.dataset.theme=t;try{localStorage.setItem('zolarwind-theme',t)}catch(e){}}}const content=document.querySelector('.prose'),toc=document.querySelector('#TableOfContents');if(!content||!toc)return;const hs=[...content.querySelectorAll('h2,h3')];if(!hs.length)return;const used=new Set([...document.querySelectorAll('[id]')].map(e=>e.id)),list=document.createElement('ul');list.className='toc-list';hs.forEach((h,i)=>{if(!h.id){let id='zolarwind-heading-'+i;while(used.has(id))id+='-';h.id=id;used.add(id)}const li=document.createElement('li'),a=document.createElement('a');if(h.tagName==='H3')li.className='nested';a.href='#'+encodeURIComponent(h.id);a.textContent=h.textContent;li.append(a);list.append(li)});toc.append(list);toc.hidden=false})(); \ No newline at end of file diff --git a/packages/zola-zolarwind/res/licenses/Tailwind-MIT.txt b/packages/zola-zolarwind/res/licenses/Tailwind-MIT.txt new file mode 100644 index 00000000..d6a82290 --- /dev/null +++ b/packages/zola-zolarwind/res/licenses/Tailwind-MIT.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Tailwind Labs, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/zola-zolarwind/screenshot.png b/packages/zola-zolarwind/screenshot.png new file mode 100644 index 00000000..3bf89fa7 Binary files /dev/null and b/packages/zola-zolarwind/screenshot.png differ diff --git a/packages/zola-zolarwind/search.eta b/packages/zola-zolarwind/search.eta new file mode 100644 index 00000000..94945d6b --- /dev/null +++ b/packages/zola-zolarwind/search.eta @@ -0,0 +1 @@ +<% layout('./layout');const site=await firekylin.widget('Widget_Options');const archive=await firekylin.widget('Widget_Archive',{type:'search'}); %><% block('title',()=>{ %>搜索 - <%= site.title() %><% }) %><% block('content',()=>{ %>

搜索

<%~ include('./inc/list',{archive}) %><%~ include('./inc/pagination',{archive}) %><% }) %> \ No newline at end of file diff --git a/packages/zola-zolarwind/tag.eta b/packages/zola-zolarwind/tag.eta new file mode 100644 index 00000000..5d453c17 --- /dev/null +++ b/packages/zola-zolarwind/tag.eta @@ -0,0 +1,6 @@ +<% layout('./layout'); const site = await firekylin.widget('Widget_Options'); const metas = await firekylin.widget('Widget_Metas_Tag_Cloud'); %> +<% block('title', () => { %>标签 - <%= site.title() %><% }) %> +<% block('content', () => { %>

标签

    +<% while (metas.next()) { %>
  • <%= metas.row.name %> (<%= metas.row.count %>) +<% if (metas.row.children && metas.row.children.length) { %>
      <% for (const child of metas.row.children) { %>
    • <%= child.name %> (<%= child.count %>)
    • <% } %>
    <% } %> +
  • <% } %>
<% if (!metas.have()) { %>

暂无标签。

<% } %>
<% }) %> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 722e6d86..61562f71 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -127,8 +127,11 @@ importers: packages/zola-arabesque: {} + packages/zola-zolarwind: {} + packages/zola-zolanight: {} + packages/academic-cv: {} packages/solo: {} diff --git a/test/zola-zolarwind.test.js b/test/zola-zolarwind.test.js new file mode 100644 index 00000000..c7825469 --- /dev/null +++ b/test/zola-zolarwind.test.js @@ -0,0 +1,10 @@ +'use strict'; +const test=require('node:test'),assert=require('node:assert/strict'); +const {render,pages}=require('./port-fixture'); +for(const page of pages)test('Zolarwind '+page+' renders populated and empty Widgets',async()=>{for(const empty of [false,true]){const html=await render('zola-zolarwind',page,{empty});assert.match(html,/Zolarwind/);assert(!html.includes('undefined'));}}); +test('Zolarwind escapes titles, rejects active cover URLs, preserves protected body',async()=>{const html=await render('zola-zolarwind','index',{hostile:true,config:{profileName:'',backgroundImage:'javascript:alert(1)'}});assert(!html.includes('{const {context}=require('./port-fixture');const {Eta}=require('eta');const path=require('node:path');for(const [mime,element] of [['image/png','img'],['audio/mpeg','audio'],['video/mp4','video'],['application/pdf',null]]){const ctx=context('zola-zolarwind','attachment');const archive=await ctx.widget('Widget_Archive');archive.stack[0].options={attachment:{mime,url:'/uploads/file',name:'Download file'}};const html=await new Eta({views:path.join(__dirname,'../packages/zola-zolarwind'),varName:'firekylin'}).renderAsync('attachment',ctx);assert.match(html,/href="\/uploads\/file" download/);if(element)assert(html.includes('<'+element+' '));}const ctx=context('zola-zolarwind','attachment');(await ctx.widget('Widget_Archive')).stack[0].options={attachment:{mime:'image/png',url:'javascript:alert(1)'}};const html=await new Eta({views:path.join(__dirname,'../packages/zola-zolarwind'),varName:'firekylin'}).renderAsync('attachment',ctx);assert(!html.includes('javascript:'));}); + +test('Zolarwind identifies taxonomy',async()=>{for(const [kind,label] of [['cate','随笔'],['tag','生活']]){assert.match(await render('zola-zolarwind','index',{kind}),new RegExp(']*>'+label+'')); }}); diff --git a/verification/zola-zolarwind/home-1440.png b/verification/zola-zolarwind/home-1440.png new file mode 100644 index 00000000..3bf89fa7 Binary files /dev/null and b/verification/zola-zolarwind/home-1440.png differ diff --git a/verification/zola-zolarwind/home-390.png b/verification/zola-zolarwind/home-390.png new file mode 100644 index 00000000..2f5f86ef Binary files /dev/null and b/verification/zola-zolarwind/home-390.png differ diff --git a/verification/zola-zolarwind/home-dark.png b/verification/zola-zolarwind/home-dark.png new file mode 100644 index 00000000..a4aa0fbf Binary files /dev/null and b/verification/zola-zolarwind/home-dark.png differ diff --git a/verification/zola-zolarwind/live.cjs b/verification/zola-zolarwind/live.cjs new file mode 100644 index 00000000..0d36d18e --- /dev/null +++ b/verification/zola-zolarwind/live.cjs @@ -0,0 +1,56 @@ +'use strict'; +// Creates a disposable local SQLite site; never points at a user-provided database. +const fs=require('node:fs'),os=require('node:os'),path=require('node:path'),crypto=require('node:crypto'); +const {spawn,execFileSync}=require('node:child_process');const {createRequire}=require('node:module');const assert=require('node:assert/strict'); +const core=process.env.FIREKYLIN_CHECKOUT;if(!core)throw Error('Set FIREKYLIN_CHECKOUT'); +const coreRequire=createRequire(path.join(core,'package.json'));const adapterRequire=createRequire(coreRequire.resolve('think-model-sqlite'));const Database=adapterRequire('better-sqlite3'); +const {chromium}=require(process.env.PLAYWRIGHT_MODULE||'playwright'); +const repository=path.resolve(__dirname,'../..');const site=fs.mkdtempSync(path.join(os.tmpdir(),'zola-zolarwind-site-'));const port=Number(process.env.ZOLARWIND_TEST_PORT)||8913;const origin='http://127.0.0.1:'+port; +let server,browser,db;const delay=ms=>new Promise(r=>setTimeout(r,ms)); +(async()=>{ + await require(path.join(core,'lib/cli')).run(['new',site,'--db-type','sqlite','--db-path','data/firekylin.sqlite','--site-title','ZOLARWIND 测试博客','--site-url',origin,'--admin-user','fixture-admin','--admin-password',crypto.randomBytes(24).toString('hex'),'--admin-email','fixture@example.invalid','--non-interactive','--skip-install']); + const configPath=path.join(site,'firekylin.config.js');const config=require(configPath);config.server={host:'127.0.0.1',port,proxy:false};fs.writeFileSync(configPath,'module.exports='+JSON.stringify(config)); + execFileSync('python3',['-c',`import importlib.util,pathlib\nr=pathlib.Path(${JSON.stringify(repository)})\ns=importlib.util.spec_from_file_location('builder',r/'scripts/build-registry.py')\nm=importlib.util.module_from_spec(s);s.loader.exec_module(m)\nm.build_theme(r/'packages/zola-zolarwind',r,m.theme_files(r/'packages/zola-zolarwind'))`]); + const {ThemeInstaller}=require(path.join(core,'src/service/theme-installer'));const installer=new ThemeInstaller({themesPath:path.join(site,'themes'),backupPath:path.join(site,'backups'),firekylinVersion:coreRequire('./package.json').version});await installer.install(fs.readFileSync(path.join(repository,'dist/zola-zolarwind-'+JSON.parse(fs.readFileSync(path.join(repository,'packages/zola-zolarwind/package.json'))).version+'.zip'))); + db=new Database(path.join(site,'data/firekylin.sqlite')); + const set=(k,v)=>db.prepare('INSERT INTO fk_options (key,value) VALUES (?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value').run(k,v); + set('theme','zola-zolarwind');set('description','一生想做自由极客');set('navigation',JSON.stringify([{label:'About',url:'/page/about.html'}])); + db.prepare("UPDATE fk_post SET title='ZOLARWIND article',pathname='hello',summary='

Public summary

',content='

Bold and Italic.

  • First
    • Nested
  1. One
  2. Two
Header
LongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCellLongTableCell
long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_long_code_without_spaces_

First chapter

Public content

Second chapter

Fixture

',allow_comment=0").run(); + const seed=db.prepare('INSERT INTO fk_post (user_id,type,status,title,pathname,summary,markdown_content,content,allow_comment,create_time,update_time,is_public,comment_num) VALUES (1,?,?,?,?,?,?,?,0,?,?,?,0)'); + const now='2026-10-28 00:00:00';seed.run(0,3,'PRIVATE_MARKER','private-marker','PRIVATE_MARKER','private','PRIVATE_MARKER',now,now,0);seed.run(0,0,'DRAFT_MARKER','draft-marker','DRAFT_MARKER','draft','DRAFT_MARKER',now,now,1);seed.run(1,3,'About','about','About','About','

About page

',now,now,1); + for(let i=0;i<12;i++)seed.run(0,3,'ZOLARWIND public '+i,'public-'+i,'public '+i,'public','

Public

',now,now,1); + db.prepare('UPDATE fk_post SET options=? WHERE is_public=1 AND status=3').run(JSON.stringify({featuredImage:'/themes/zola-zolarwind/res/fixture.svg'})); + db.prepare('UPDATE fk_post SET options=? WHERE pathname=?').run('{}','hello'); + db.prepare('UPDATE fk_post SET options=? WHERE pathname=?').run(JSON.stringify({featuredImage:'javascript:invalid'}),'public-11'); + seed.run(2,3,'Native attachment','native-attachment','','','',now,now,1);db.prepare('UPDATE fk_post SET options=? WHERE pathname=?').run(JSON.stringify({attachment:{url:'/themes/zola-zolarwind/res/fixture.svg',mime:'image/svg+xml',name:'Fixture image'}}),'native-attachment'); + db.prepare("INSERT INTO fk_cate(name,pathname) VALUES ('随笔','notes')").run(); + db.prepare("INSERT INTO fk_post_cate(post_id,cate_id) SELECT id,(SELECT id FROM fk_cate WHERE pathname='notes') FROM fk_post WHERE is_public=1 AND status=3").run(); + db.prepare("INSERT INTO fk_tag(name,pathname) VALUES ('生活','life')").run(); + db.prepare("INSERT INTO fk_post_tag(post_id,tag_id) SELECT id,(SELECT id FROM fk_tag WHERE pathname='life') FROM fk_post WHERE is_public=1 AND status=3").run(); + set('themeConfig',JSON.stringify({footerText:'阅读与探索本站内容。',authorName:'Zolarwind Fixture Author'})); + fs.writeFileSync(path.join(site,'themes/zola-zolarwind/res/fixture.svg'),''); + db.prepare("UPDATE fk_post SET content=content||? WHERE pathname IN ('hello','about')").run("

Embedded media regression

"); + server=spawn(process.execPath,[path.join(core,'bin/firekylin.js')],{cwd:site,stdio:['ignore','ignore','pipe']});let serverErrors='';server.stderr.on('data',c=>serverErrors+=c);for(let i=0;i<150;i++){try{if((await fetch(origin)).ok)break;}catch{}if(i===149)throw Error('Site startup failed: '+serverErrors);await delay(200);} + browser=await chromium.launch({executablePath:process.env.CHROMIUM_PATH||'/usr/bin/chromium',args:['--no-sandbox']}); + const context=await browser.newContext();const page=await context.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));page.on('response',r=>{if(r.url().startsWith(origin+'/themes/')&&r.status()>=400)errors.push(r.status()+' '+r.url())}); + await page.route('**/*',route=>route.request().url().startsWith(origin)?route.continue():route.abort()); + const evidence=path.join(repository,'verification/zola-zolarwind');fs.mkdirSync(evidence,{recursive:true});const checks=[]; + for(const width of [1440,390]){ + await page.setViewportSize({width,height:1000}); + for(const route of ['/','/?page=2','/post/hello.html','/page/about.html','/attachment/native-attachment.html','/archives','/categories','/tags','/search.html?keyword=ZOLARWIND']){ + const response=await page.goto(origin+route);assert.equal(response.status(),200,route); + const html=await page.content();assert(!html.includes('undefined'),route+' contains undefined');assert(!html.includes('MMM D'),'unsupported date format');assert(!html.includes('PRIVATE_MARKER')&&!html.includes('DRAFT_MARKER'),route+' leaked content'); + if(route.includes('/attachment/')){assert.equal(await page.locator('.attachment-media a[download]').count(),1);assert(await page.locator('.attachment-media img').evaluate(i=>i.complete&&i.naturalWidth>0));}assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),route+' overflow');if(route.includes('/post/hello.html')||route.includes('/page/about.html')){const media=await page.locator('.prose').evaluate(e=>{const p=e.getBoundingClientRect(),v=e.querySelector('video').getBoundingClientRect(),a=e.querySelector('audio').getBoundingClientRect();return {column:p.width,video:v.width,audio:a.width,audioHeight:a.height,ratio:v.width/v.height}});assert(media.audioHeight>=40,'audio visible');assert(media.video<=media.column+1&&media.audio<=media.column+1,route+' embedded media fits');assert(Math.abs(media.ratio-1280/720)<.02,route+' video aspect ratio');}checks.push({width,route,status:response.status()}); + } + await page.goto(origin);await page.locator('.native-nav a[href="/archives"]').click();await page.waitForURL('**/archives');await page.goto(origin);assert.equal(await page.locator('.entry').count(),10);assert(await page.locator('#main').evaluate(e=>e.getBoundingClientRect().width>280),'usable main width');await page.evaluate(()=>Promise.all(document.getAnimations().filter(a=>a.effect.getTiming().iterations!==Infinity).map(a=>a.finished.catch(()=>{}))));await page.screenshot({path:path.join(evidence,'home-'+width+'.png'),fullPage:true}); + await page.getByRole('link',{name:'下一页',exact:true}).click();await page.waitForURL('**/?page=2'); + await page.goto(origin+'/post/hello.html');for(const sel of ['.prose h3','.prose h4','.prose pre','.prose table','.prose img'])assert(await page.locator(sel).first().isVisible(),sel+' visible');assert(await page.locator('.prose ul').first().evaluate(e=>getComputedStyle(e).listStyleType!=='none'),'list markers');await page.screenshot({path:path.join(evidence,'post-'+width+'.png'),fullPage:true});assert(await page.locator('.nav-items a[href="/archives"]').isVisible()); + + } + await page.setViewportSize({width:1440,height:1000});await page.goto(origin);assert.equal(await page.locator('.post-grid').evaluate(e=>getComputedStyle(e).gridTemplateColumns.split(' ').length),3);assert.equal(await page.locator('.entry-cover').count(),8);assert.equal(await page.locator('.entry-tags a').first().textContent(),'#生活');assert.equal(await page.locator('.footer-author').textContent(),'Zolarwind Fixture Author');await page.setViewportSize({width:800,height:1000});assert.equal(await page.locator('.post-grid').evaluate(e=>getComputedStyle(e).gridTemplateColumns.split(' ').length),2);await page.setViewportSize({width:390,height:1000});assert.equal(await page.locator('.post-grid').evaluate(e=>getComputedStyle(e).gridTemplateColumns.split(' ').length),1);await page.locator('#dark-mode-toggle').click();assert.equal(await page.locator('html').getAttribute('data-theme'),'dark');await page.reload();assert.equal(await page.locator('html').getAttribute('data-theme'),'dark');await page.setViewportSize({width:1440,height:1000});await page.screenshot({path:path.join(evidence,'home-dark.png'),fullPage:true});await page.locator('#dark-mode-toggle').click();await page.goto(origin+'/post/hello.html');assert(await page.locator('#TableOfContents').isVisible());await page.locator('#TableOfContents summary').click();assert(await page.locator('#TableOfContents a').count()>0);for(const [route,label] of [['/cate/notes','随笔'],['/tag/life','生活']]){await page.goto(origin+route);assert.equal(await page.locator('#main h1').textContent(),label);} + for(const pathname of ['private-marker' ,'draft-marker']){const denied=await fetch(origin+'/post/'+pathname+'.html',{redirect:'manual'});assert.equal(denied.status,302);assert.equal(denied.headers.get('location'),'/');} + db.prepare('UPDATE fk_post SET allow_comment=1 WHERE pathname=?').run('hello');let html=await(await fetch(origin+'/post/hello.html')).text();assert(html.includes('data-comment-config'));assert.equal((html.match(/id="comments"/g)||[]).length,1); + db.prepare('UPDATE fk_post SET allow_comment=0 WHERE pathname=?').run('hello');assert(!(await(await fetch(origin+'/post/hello.html')).text()).includes('data-comment-config')); + const nojs=await browser.newContext({javaScriptEnabled:false});const np=await nojs.newPage();await np.setViewportSize({width:390,height:1000});await np.goto(origin);await np.locator('.nav-items a[href="/search.html"]').click();await np.locator('#keyword').fill('ZOLARWIND');await np.locator('#main').getByRole('button',{name:'搜索',exact:true}).click();await np.waitForURL('**/search.html?keyword=ZOLARWIND');await nojs.close(); + assert.deepEqual(errors,[]);const report={core:coreRequire('./package.json').version,coreRevision:execFileSync('git',['-C',core,'rev-parse','HEAD'],{encoding:'utf8'}).trim(),browser:browser.version(),checks,passed:['three/two/one column image cards','native tag relations and safe images','persistent palette toggle and article TOC','taxonomy headings and footer settings','actual ZIP installation','18 real SQLite routes','native attachment image/download','long native code containment','native article pagination','visible wrapping navigation without JavaScript','responsive header navigation','search without JavaScript','private/draft exclusion','comment gating','1280px video/audio in posts and pages with preserved video aspect ratio'],limits:['Compared pinned upstream CSS and Zola templates; no live upstream demo comparison.','Waline remote client login/submission not tested.']};fs.writeFileSync(path.join(evidence,'report.json'),JSON.stringify(report,null,2)+'\n');fs.copyFileSync(path.join(evidence,'home-1440.png'),path.join(repository,'packages/zola-zolarwind/screenshot.png'));console.log(JSON.stringify(report)); +})().catch(e=>{console.error(e);process.exitCode=1}).finally(async()=>{if(browser)await browser.close();if(server)server.kill('SIGTERM');if(db)db.close();console.log('Disposable test site:',site)}); diff --git a/verification/zola-zolarwind/post-1440.png b/verification/zola-zolarwind/post-1440.png new file mode 100644 index 00000000..5bba5e7b Binary files /dev/null and b/verification/zola-zolarwind/post-1440.png differ diff --git a/verification/zola-zolarwind/post-390.png b/verification/zola-zolarwind/post-390.png new file mode 100644 index 00000000..cdf95aca Binary files /dev/null and b/verification/zola-zolarwind/post-390.png differ diff --git a/verification/zola-zolarwind/report.json b/verification/zola-zolarwind/report.json new file mode 100644 index 00000000..ae1257b1 --- /dev/null +++ b/verification/zola-zolarwind/report.json @@ -0,0 +1,118 @@ +{ + "core": "2.17.1", + "coreRevision": "ae71b610ed2dcb6e992366bc06080ec51805042c", + "browser": "154.0.8037.57", + "checks": [ + { + "width": 1440, + "route": "/", + "status": 200 + }, + { + "width": 1440, + "route": "/?page=2", + "status": 200 + }, + { + "width": 1440, + "route": "/post/hello.html", + "status": 200 + }, + { + "width": 1440, + "route": "/page/about.html", + "status": 200 + }, + { + "width": 1440, + "route": "/attachment/native-attachment.html", + "status": 200 + }, + { + "width": 1440, + "route": "/archives", + "status": 200 + }, + { + "width": 1440, + "route": "/categories", + "status": 200 + }, + { + "width": 1440, + "route": "/tags", + "status": 200 + }, + { + "width": 1440, + "route": "/search.html?keyword=ZOLARWIND", + "status": 200 + }, + { + "width": 390, + "route": "/", + "status": 200 + }, + { + "width": 390, + "route": "/?page=2", + "status": 200 + }, + { + "width": 390, + "route": "/post/hello.html", + "status": 200 + }, + { + "width": 390, + "route": "/page/about.html", + "status": 200 + }, + { + "width": 390, + "route": "/attachment/native-attachment.html", + "status": 200 + }, + { + "width": 390, + "route": "/archives", + "status": 200 + }, + { + "width": 390, + "route": "/categories", + "status": 200 + }, + { + "width": 390, + "route": "/tags", + "status": 200 + }, + { + "width": 390, + "route": "/search.html?keyword=ZOLARWIND", + "status": 200 + } + ], + "passed": [ + "three/two/one column image cards", + "native tag relations and safe images", + "persistent palette toggle and article TOC", + "taxonomy headings and footer settings", + "actual ZIP installation", + "18 real SQLite routes", + "native attachment image/download", + "long native code containment", + "native article pagination", + "visible wrapping navigation without JavaScript", + "responsive header navigation", + "search without JavaScript", + "private/draft exclusion", + "comment gating", + "1280px video/audio in posts and pages with preserved video aspect ratio" + ], + "limits": [ + "Compared pinned upstream CSS and Zola templates; no live upstream demo comparison.", + "Waline remote client login/submission not tested." + ] +}