diff --git a/.cspell/words.txt b/.cspell/words.txt index 81c970b..48456ae 100644 --- a/.cspell/words.txt +++ b/.cspell/words.txt @@ -1,4 +1,6 @@ autoplay +bangumi +bilibili cachix callout calt @@ -9,6 +11,7 @@ elif endfor endif ensp +fediverse fontawesome fontsource giscus @@ -21,12 +24,15 @@ inlines jetbrains katex kwargs +listenbrainz lqip maple materialised menlo +metabrainz minijinja ndash +neodb netease neue nixfmt diff --git a/CLAUDE.md b/CLAUDE.md index 9016146..7fe45ee 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,6 +4,8 @@ IgnIt is a kiln theme built with Tailwind CSS v4, inspired by Hugo LoveIt. It provides MiniJinja templates, compiled CSS, and JS assets for [kiln](https://github.com/hakula139/kiln) sites. +User-facing reference lives in [`README.md`](./README.md) and [`docs/`](./docs/). This file is contributor-only context for working on the theme itself: file-tree navigation, build pipeline, conventions, and decisions that don't fit in commit history. + ### Theme Structure All assets live under a single `static/` tree. Files and directories whose names start with `_` are private build inputs — kiln's `copy_static` skips them when publishing the site. @@ -31,17 +33,21 @@ All assets live under a single `static/` tree. Files and directories whose names │ │ │ │ ├── embed/ │ │ │ │ │ └── apple-music.css # Apple Music embed light / dark toggle │ │ │ │ ├── layout/ +│ │ │ │ │ ├── error-page.css # .back-home-link (404 page back-to-home pill) │ │ │ │ │ ├── float-buttons.css # .float-btn shared base + .back-to-top, .jump-to-comments stack │ │ │ │ │ ├── glass-panel.css # .glass-panel, .glass-glow, [data-glow-target], .header-nav, .site-footer │ │ │ │ │ ├── header.css # .header-logo, .header-link, .header-icon, .header-mobile-*, menu animations │ │ │ │ │ ├── print.css # @media print overrides (hide chrome, clean typography) │ │ │ │ │ └── skip-link.css # .skip-link visually hidden, focus-promoted accessibility anchor │ │ │ │ ├── listing/ -│ │ │ │ │ ├── home-card.css # .home-card-*, .profile-avatar, .text-card-* +│ │ │ │ │ ├── home-card.css # .home-card-*, .profile-*, .text-card-* │ │ │ │ │ ├── listing.css # .year-heading, .tag-pill, .category-card, .post-entry-* │ │ │ │ │ └── pagination.css # .pagination-link, .pagination-ellipsis, .pagination-input -│ │ │ │ └── search/ -│ │ │ │ └── search.css # Pagefind trigger, modal, and result theming +│ │ │ │ ├── search/ +│ │ │ │ │ └── search.css # Pagefind trigger, modal, and result theming +│ │ │ │ └── shared/ +│ │ │ │ ├── fa-icon.css # @utility fa-icon — Font Awesome glyph setup for ::before / ::after +│ │ │ │ └── icon-button.css # @utility icon-tap-hover — circular tinted hover for icon surfaces │ │ │ ├── fonts.css # @font-face for Inter Variable + Maple Mono │ │ │ └── main.css # Entry: tokens, dark mode, partial imports │ │ └── style.css # Compiled Tailwind output (shipped) @@ -101,12 +107,7 @@ Source CSS lives in `static/css/_src/` using Tailwind CSS v4 conventions: ### Design Tokens -Defined in `@theme { ... }` in `main.css`. Custom properties follow these prefixes: - -- `--color-*` — colors (bg, text, link, border, card, selection) -- `--radius-*` — border radii -- `--shadow-*` — box shadows -- `--surface-*` — glass surface fills, borders, and overlays +Defined in `@theme { ... }` in `main.css`, with parallel dark-mode overrides under `[data-theme='dark']`. The public namespaces and override pattern are documented in [`docs/customization.md`](./docs/customization.md#visual-tokens). ### Component Classes vs. Inline Utilities @@ -140,16 +141,13 @@ The theme paints kiln's `lqip_uri` via the `` wrapper kiln em - **Body images**: auto-wrapped by kiln. No template work. - **Featured images** (`templates/post.html` banner, `templates/home.html` cards): templates emit the wrapper themselves, gated on `{% if featured_image.lqip_uri %}`. Per-context size overrides (`.post-banner-media .lqip`, `.home-card > .lqip`) live in `lqip.css`. -- **Body background** (`config.params.background`): hand-rolled in `base.html`. Sites supply `image` + `lqip_uri` (a pre-computed data URI); the wrapper pins fixed-fullscreen with `object-fit: cover`, and `position` / `position_mobile` drive `object-position` via `--bg-position*` CSS vars. +- **Body background** (`config.params.background`): hand-rolled in `base.html`. The wrapper pins fixed-fullscreen with `object-fit: cover`, and `position` / `position_mobile` drive `object-position` via `--bg-position*` CSS vars. The user-facing config schema is in [`docs/parameters.md`](./docs/parameters.md#paramsbackground). ## Internationalization -Translation tables live under `i18n/.toml` (`en`, `zh-Hans`); active language is set by `config.language` in the consuming site. Access: - -- **Templates** (MiniJinja): `t('key')`. -- **Client JS**: `data-i18n-*` attributes on the document root. +Translation tables live under `i18n/.toml` (`en`, `zh-Hans`); active language is set by `config.language` in the consuming site. Templates read keys via MiniJinja's `t('key')`; client JS reads `data-i18n-*` attributes on the document root and on per-element carriers. -Sites can layer overrides via a same-named TOML at their root's `i18n/.toml`, merged key-by-key. Per-key lookup falls back: **site override → theme translation → theme English**. +The site / theme / English fallback chain and the full key reference are in [`docs/i18n.md`](./docs/i18n.md). When adding a new template string, register the key in **both** `i18n/en.toml` and `i18n/zh-Hans.toml`, then add a row to the `docs/i18n.md` reference table so site authors can discover it. ## Webfonts diff --git a/README.md b/README.md index b106bf1..dc73c9e 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,14 @@ IgnIt is a [kiln](https://github.com/hakula139/kiln) theme built with Tailwind C - Keyboard-accessible focus states (`:focus-visible`), skip-to-content link, semantic landmark regions - `prefers-reduced-motion` honored across animations and smooth-scroll +## Documentation + +| Document | Description | +| -------------------------------------- | ----------------------------------------------------------------------------------- | +| [Customization](docs/customization.md) | Override visual tokens, templates, social icons, comments, fonts, and static assets | +| [Parameters](docs/parameters.md) | `[params]` schema reference — defaults, types, where each value is rendered | +| [i18n](docs/i18n.md) | Translatable string reference — keys, English defaults, override pattern | + ## Installation Add IgnIt to your kiln site as a Git submodule: @@ -61,115 +69,38 @@ theme = "IgnIt" `static/css/style.css` ships pre-built, so consuming sites don't need Node.js. -## Configuration - -IgnIt provides default parameters that can be overridden in your site's `config.toml`. +## Quick Start -### Theme Parameters +A minimal `config.toml` to get IgnIt running: ```toml -[params] -cdn = "https://cdn.jsdelivr.net/npm" # CDN base URL for dependencies -code_max_lines = 40 # Max visible lines before code blocks scroll -emojis = true # Enable emoji replacement -fontawesome = true # Enable Font Awesome icon loading -``` - -### Background Image - -Set a fixed background image with glassmorphism content panels: - -```toml -[params.background] -image = "/images/bg.webp" # Path to background image -lqip_uri = "data:image/webp;base64,..." # Pre-computed LQIP data URI (optional) -position = "right center" # CSS background-position (default: center) -position_mobile = "60% center" # Mobile background-position (default: position) -``` - -When unset, panels use solid backgrounds (the theme works without a background image). - -### Comments - -```toml -[params.comments] -enabled = true -provider = "twikoo" # Currently the only built-in provider - -[params.comments.twikoo] -api_url = "https://twikoo.example.com" -``` - -The dispatcher is provider-agnostic — additional providers (Giscus, Waline, etc.) drop in via a sibling partial under `templates/_partials/comments/`. The provider's CDN dep loads only when comments are enabled. - -### Footer - -```toml -[params.footer] -since = 2018 # Copyright start year -license = "CC BY-NC-SA 4.0" # License name -license_url = "https://creativecommons.org/licenses/by-nc-sa/4.0/" -powered_by = true # Show "Powered by kiln & IgnIt" -``` +theme = "IgnIt" +title = "My Site" +language = "en" -### Home Profile +[params] +fontawesome = true -```toml [params.home.profile] avatar = "/images/avatar.webp" title = "Site Title" -subtitle = "A short tagline" -``` - -### Navigation Menu +subtitle = "An optional tagline" -```toml [[menu.main]] name = "Posts" url = "/posts/" -icon = "fas fa-archive" # Font Awesome class (optional) -weight = 1 # Sort order (ascending) +icon = "fas fa-archive" +weight = 1 -[[menu.main]] +[[menu.social]] name = "GitHub" -url = "https://github.com/user" -icon = "fab fa-github" -weight = 10 -external = true # Opens in new tab -``` - -### Visual Effects (experimental) - -```toml -[params.effects] -cursor_glow = false # Cursor-tracking glow on glass panels +url = "https://github.com/example" +icon = "svg:github" +weight = 1 +external = true ``` -Off by default. The `will-change` layers it injects clash with `backdrop-filter` compositing on Chromium / WebKit: - -- **Panel dropout during scroll** (frequent) — the glass panel may briefly vanish for a moment while scrolling. -- **Phantom gap near `#comments`** (rare) — an in-page anchor jump close to the comments section can leave the article card with a much larger apparent gap than its real 32 px margin; the next manual scroll repaints. - -## Image Pipeline - -IgnIt paints kiln's `lqip_uri` (low-quality image placeholder, a base64-encoded WebP data URI) via the `` wrapper kiln emits around content images. The backdrop shows immediately on first paint; the inner image fades in once it decodes. - -- **Body images**: auto-wrapped by kiln. No site-level work. -- **Featured images** (post banner, home cards): templates emit the wrapper themselves, gated on `featured_image.lqip_uri`. -- **Body background** (`[params.background]`): supply a pre-computed `lqip_uri` alongside `image`. The wrapper pins fixed-fullscreen with `object-fit: cover`; `position` / `position_mobile` drive `object-position` via CSS variables. - -## Internationalization - -Translation tables live under `i18n/.toml` (`en`, `zh-Hans`); the active language follows `config.language` in the consuming site. Access: - -- **Templates** (MiniJinja): `t('key')`. -- **Client JS**: `data-i18n-*` attributes on the document root. - -Sites can layer overrides via a same-named TOML at their root's `i18n/.toml`, merged key-by-key. Per-key lookup falls back: **site override → theme translation → theme English**. - -## Webfonts - -Inter Variable (`--font-sans`) and Maple Mono Variable (`--font-mono`, with separate regular and italic faces) are self-hosted under `static/fonts/`. Both are fetched lazily on first use — Inter when any page text renders, Maple Mono when the first `` appears. CJK falls through to system fonts (Sarasa Gothic SC, PingFang SC, Noto Sans CJK SC, etc.) via the `--font-sans` cascade. +For the complete schema (`[params.background]`, `[params.comments]`, `[params.footer]`, `[params.section]`, `[params.effects]`) and the full menu field list, see [Parameters](docs/parameters.md). For visual customization, social icon overrides, comment provider wiring, and other extension patterns, see [Customization](docs/customization.md). ## Theme Development diff --git a/docs/customization.md b/docs/customization.md new file mode 100644 index 0000000..8bae5f2 --- /dev/null +++ b/docs/customization.md @@ -0,0 +1,167 @@ +# Customization + +IgnIt is built to be bent. Most surfaces — colors, typography, navigation, icons, copy, comment provider — can be customized from your site without forking the theme. This document is the how-to guide; for the schema reference behind the knobs, see [Parameters](parameters.md). For translatable strings, see [i18n](i18n.md). + +## Visual Tokens + +IgnIt's design tokens live in `@theme { ... }` inside [`static/css/_src/main.css`](../static/css/_src/main.css), with a parallel set of dark-mode overrides under `[data-theme='dark']`. The token namespaces are: + +| Prefix | Purpose | +| ------------- | ----------------------------------------------------------------------------- | +| `--color-*` | Page surfaces, text, links, borders, selection highlight, syntax theme | +| `--radius-*` | Border radii (`md`, `lg`, `xl`, `card`) | +| `--shadow-*` | Box shadows (`md`, `card`) + a `--drop-shadow-text` for over-image legibility | +| `--surface-*` | Glass panel fills, borders, hover states, scroll shadow, overlay backdrop | +| `--callout-*` | Per-callout-kind accent colors (12 variants) | +| `--syntax-*` | Code-block token colors | + +Sites override individual tokens by emitting their own CSS that re-declares the variable inside the appropriate scope. The declaration must hit the correct level: site-wide tokens go on `:root` or `[data-theme]`, dark-mode-only overrides go on `[data-theme='dark']`. The value is consumed everywhere the theme's compiled CSS reads it, so a single override propagates through every component that uses the token. + +```css +:root { + --color-link: #c84d72; /* light-mode link color */ +} + +[data-theme='dark'] { + --color-link: #ffaad0; /* dark-mode override */ +} +``` + +To get a site stylesheet onto every page, override `base.html` (see [Template overrides](#template-overrides)) and add a `` after the theme's ``. Anything you ship as a fork of the theme's `style.css` replaces it entirely and loses every token; cascade your file on top instead. + +For changes scoped to one post, kiln's [per-page CSS](https://github.com/hakula139/kiln/blob/main/docs/content.md) picks up a co-located `style.css` next to the post's `index.md`. Use it for one-off styling without touching site-wide files. + +## Template Overrides + +kiln's [override model](https://github.com/hakula139/kiln/blob/main/docs/themes.md#override-model) resolves templates from your site's `templates/` directory before falling back to the theme's. Drop a same-named file into your site to replace any IgnIt template — `base.html`, a partial under `_partials/`, a directive under `directives/`. + +```text +my-site/templates/ +├── base.html # Replaces theme/templates/base.html entirely +└── _partials/ + └── layout/ + └── footer.html # Replaces theme/templates/_partials/layout/footer.html +``` + +Common reasons to override: + +- **`base.html`** — add an analytics snippet, a custom stylesheet ``, or extra `` tags before ``. +- **`_partials/layout/footer.html`** — replace the default attribution with your own copyright text. +- **`_partials/comments/.html`** — wire a comments provider IgnIt doesn't bundle (see [Comments providers](#comments-providers)). + +Overriding wholesale loses future theme updates for that file. Prefer surgical overrides: copy the partial that contains the bit you need to change, edit just that, leave everything else inherited. + +## Social Icons + +Menu items configured under `[[menu.main]]` or `[[menu.social]]` carry an `icon` field. IgnIt's icon dispatcher accepts two forms: + +| Spec form | Renders as | Source | +| ----------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------- | +| `"fab fa-github"` | `` | Font Awesome glyph (any FA class, including `fas`, `far`, `fal`, custom). | +| `"svg:github"` | Inline `` from `_partials/icons/github.svg` | Theme-bundled SVG, or a same-name file in your site's `templates/_partials/icons/` (site wins). | + +The dispatcher picks based on the `svg:` prefix; everything else falls through to the Font Awesome path. + +### Bundled Registry + +The theme ships these `svg:` entries out of the box: + +| Slug | Source | +| -------------- | -------------------------------------------------------------------------------------------- | +| `bilibili` | [Simple Icons](https://simpleicons.org/?q=bilibili) | +| `douban` | [Simple Icons](https://simpleicons.org/?q=douban) | +| `fediverse` | [neodb-social/neodb](https://github.com/neodb-social/neodb) — used for NeoDB, Mastodon, etc. | +| `github` | [Simple Icons](https://simpleicons.org/?q=github) | +| `linkedin` | [Simple Icons](https://simpleicons.org/?q=linkedin) | +| `listenbrainz` | [metabrainz/design-system](https://github.com/metabrainz/design-system) | +| `mastodon` | [Simple Icons](https://simpleicons.org/?q=mastodon) | +| `qq` | [Simple Icons](https://simpleicons.org/?q=qq) | +| `steam` | [Simple Icons](https://simpleicons.org/?q=steam) | +| `telegram` | [Simple Icons](https://simpleicons.org/?q=telegram) | +| `zhihu` | [Simple Icons](https://simpleicons.org/?q=zhihu) | + +Bundled SVGs are stripped of `` and `role="img"` markers (the parent anchor carries `aria-label`, so the SVG itself is `aria-hidden`). Fill is unset so the path inherits `currentColor`, which lets a single icon scale across light / dark themes and adopt hover colors via the surrounding ``. + +### Adding a Custom Icon + +The dispatcher resolves SVG slugs through kiln's path loader, which checks the site's templates directory before the theme's. To add or replace an icon: + +1. Save your SVG at `/templates/_partials/icons/.svg`. The viewBox should be square (e.g. `0 0 24 24`); the path should have no explicit `fill`, so it picks up `currentColor` from the surrounding link. +2. Reference it from your menu config: + + ```toml + [[menu.social]] + name = "menu_social_bangumi" + url = "https://bgm.tv/user/example" + icon = "svg:bangumi" + weight = 9 + external = true + ``` + +A site icon at the same slug as a theme-bundled one transparently overrides the theme version, which is useful when you prefer a different visual style for, say, your `github` glyph. + +When mixing FA and SVG icons in the same menu group, the theme's CSS aligns both to the same horizontal slot (1.25em, the FA `fa-fw` value) so labels line up across rows regardless of icon source. + +## i18n Overrides + +Site translations layer on top of theme translations per [kiln's i18n model](https://github.com/hakula139/kiln/blob/main/docs/themes.md#internationalization). The full list of keys IgnIt ships is documented in [i18n](i18n.md). + +To translate the theme into a language IgnIt doesn't ship, place a complete `i18n/.toml` in your site. To override individual strings in a language IgnIt already supports, ship just the keys you want to change. Everything else falls through to the theme's translation. + +```toml +# my-site/i18n/en.toml +back_to_top = "Top" # tighter than IgnIt's default "Back to top" +``` + +## Comments Providers + +`[params.comments]` enables comments and selects a provider; provider-specific keys live under `[params.comments.]` (see [Parameters](parameters.md#paramscomments)). The provider name maps directly to a partial: `provider = "twikoo"` includes `_partials/comments/twikoo.html`. + +To wire a provider IgnIt doesn't ship, add the partial in your site: + +```text +my-site/templates/_partials/comments/giscus.html +``` + +The partial is responsible for emitting the provider's mount markup and CDN scripts. Theme defaults register CDN deps with SRI under `[params.deps.]` in `theme.toml`; new providers follow the same pattern (see [`CLAUDE.md`](../CLAUDE.md#dependencies) for the SRI regeneration flow). + +Once the partial is in place, set the provider in your site config: + +```toml +[params.comments] +enabled = true +provider = "giscus" + +[params.comments.giscus] +repo = "owner/repo" +# ...remaining provider-specific keys +``` + +The dispatcher emits the partial only when `enabled = true`, so disabling comments site-wide also drops the provider's CDN dep entirely. + +## Webfonts + +Inter Variable and Maple Mono Variable are self-hosted under [`static/fonts/`](../static/fonts/) and declared in [`static/css/_src/fonts.css`](../static/css/_src/fonts.css). They're consumed via the `--font-sans` and `--font-mono` design tokens, with CJK falling through to system fonts (Sarasa Gothic SC, PingFang SC, Noto Sans CJK SC) at the end of the cascade. + +To swap the body font, override the token in your site CSS: + +```css +:root { + --font-sans: 'Your Variable', system-ui, sans-serif; +} +``` + +Then ensure your face is loaded, either by linking a `@font-face` declaration from your override stylesheet, or by replacing `static/fonts/` outright via the [static-asset override](#template-overrides) mechanism (kiln serves `static/` from your site ahead of the theme's). + +## Static Asset Overrides + +Same precedence rule as templates: files in your site's `static/` directory shadow theme files at the same path. Common cases: + +| Asset | Path | Reason to override | +| ---------------- | ----------------------------- | --------------------------------------- | +| Favicon (32×32) | `static/favicon.ico` | Per-site brand mark | +| Apple touch icon | `static/apple-touch-icon.png` | Per-site brand mark | +| Web manifest | `static/manifest.webmanifest` | App name, theme color, install icon set | +| `robots.txt` | `static/robots.txt` | Crawler controls | + +Avoid replacing `static/css/style.css` outright, since you lose every theme component class. Cascade a site stylesheet on top instead, as described in [Visual Tokens](#visual-tokens). diff --git a/docs/i18n.md b/docs/i18n.md new file mode 100644 index 0000000..0facaf3 --- /dev/null +++ b/docs/i18n.md @@ -0,0 +1,116 @@ +# i18n Reference + +IgnIt ships translation tables for every literal string its templates render, so sites can stay in any language without forking templates. The mechanism (file layout, lookup order, missing-key behavior) is documented in [kiln's i18n model](https://github.com/hakula139/kiln/blob/main/docs/themes.md#internationalization) — this page is the reference for which keys IgnIt uses. + +## Bundled Languages + +| Language | File | +| ------------------ | ------------------------------------------- | +| English | [`i18n/en.toml`](../i18n/en.toml) | +| Simplified Chinese | [`i18n/zh-Hans.toml`](../i18n/zh-Hans.toml) | + +To support an additional language, ship `i18n/.toml` in your site (or the theme, via PR). Sites set the active language via `language` in `config.toml`. + +## Key Reference + +### Header and navigation + +| Key | English default | Where rendered | +| ----------------- | ---------------------- | ------------------------------------------------------ | +| `skip_to_content` | `Skip to content` | First focusable element on every page (a11y skip link) | +| `search` | `Search` | Search trigger button (desktop and mobile) | +| `open_menu` | `Open menu` | Mobile-menu toggle, closed state | +| `close_menu` | `Close menu` | Mobile-menu toggle, open state | +| `switch_to_dark` | `Switch to dark mode` | Theme toggle while light mode is active | +| `switch_to_light` | `Switch to light mode` | Theme toggle while dark mode is active | + +### Listing pages + +| Key | English default | Where rendered | +| -------------- | --------------- | ----------------------------------------------------- | +| `all_sections` | `All Sections` | `/sections/` overview page title | +| `all_tags` | `All Tags` | `/tags/` overview page title | +| `more` | `More` | "More posts" link on listings that truncate | +| `pagination` | `Pagination` | `aria-label` on numbered pagination nav | +| `jump_to_page` | `Jump to page` | Page-number input label inside the pagination control | +| `pinned` | `Pinned` | Badge on pinned posts in the home listing | + +### Content pages + +| Key | English default | Where rendered | +| ------------------- | ------------------- | ------------------------------------------------------- | +| `table_of_contents` | `Table of Contents` | TOC heading on post / page templates | +| `jump_to` | `Jump to` | Heading-anchor `aria-label` prefix injected by `toc.js` | +| `image_credit` | `Image credit` | Featured-image attribution label | +| `back_to_top` | `Back to top` | Back-to-top float button label / tooltip | + +### Comments + +| Key | English default | Where rendered | +| ------------------ | ------------------ | -------------------------------------------- | +| `comments` | `Comments` | Comments section heading | +| `jump_to_comments` | `Jump to comments` | Float button label when comments are enabled | + +### Directives + +| Key | English default | Where rendered | +| ---------------------- | -------------------------------- | ------------------------------------------------------------------------------------------ | +| `music_player_apple` | `Apple Music` | `:::music server="apple"` player chrome label | +| `music_player_netease` | `NetEase Music` | `:::music server="netease"` player chrome label | +| `music_player_tencent` | `QQ Music` | `:::music server="tencent"` player chrome label | +| `music_unknown_server` | `Unknown music server: {server}` | `:::music` rendering error for an unrecognized `server` value. `{server}` is interpolated. | + +### Footer + +| Key | English default | Where rendered | +| ------------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `powered_by` | `Powered by {kiln} & {ignit}` | Footer attribution line. `{kiln}` and `{ignit}` are interpolated as anchor tags pointing at the upstream projects. | + +### 404 page + +| Key | English default | Where rendered | +| ----------------- | -------------------------------------------- | ---------------------------- | +| `error_404_title` | `404` | 404 page large title | +| `page_not_found` | `The page you requested could not be found.` | 404 page body line | +| `back_to_home` | `Back to home` | 404 page primary action link | + +## Menu names + +Menu items in `[[menu.main]]` and `[[menu.social]]` carry a `name` field that IgnIt resolves through `t()`. The same field accepts either an i18n key (e.g., `name = "menu_posts"` looking up the active language's translation) or a plain literal label (`name = "Posts"`). + +This means sites are free to mix: + +```toml +[[menu.main]] +name = "menu_posts" # i18n key — translatable +url = "/posts/" +weight = 1 + +[[menu.main]] +name = "GitHub" # literal — same in every language +url = "https://github.com/example" +external = true +weight = 10 +``` + +Per [kiln's missing-key behavior](https://github.com/hakula139/kiln/blob/main/docs/themes.md#missing-key-behavior), an i18n key that doesn't resolve renders as the key itself, so genuine typos surface as visibly wrong text instead of empty strings. + +## Overriding a string + +A site overrides any IgnIt-shipped string by placing the key in its own `i18n/.toml`. Site values win over theme values per the resolution chain `site → theme → English`. Translate only what you want changed; keys you don't override fall through to the theme's translation. + +```toml +# my-site/i18n/zh-Hans.toml +back_to_top = "返回顶部" # site override +search = "搜寻" # site override + +# Everything else inherits IgnIt's zh-Hans translations. +``` + +The override file may also introduce new keys for site-specific menu names or directive content: + +```toml +# my-site/i18n/zh-Hans.toml +menu_posts = "文章" +menu_about_me = "关于我" +``` diff --git a/docs/parameters.md b/docs/parameters.md new file mode 100644 index 0000000..a981431 --- /dev/null +++ b/docs/parameters.md @@ -0,0 +1,111 @@ +# Parameter Reference + +IgnIt reads its configuration from the `[params]` table in your site's `config.toml`. Theme defaults come from `theme.toml`; site values override theme values per [kiln's parameter merging rules](https://github.com/hakula139/kiln/blob/main/docs/themes.md#parameter-merging). + +## `[params]` + +Top-level theme switches. + +| Field | Type | Default | Description | +| ---------------- | -------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `cdn` | `string` | `"https://cdn.jsdelivr.net/npm"` | CDN base URL for vendor dependencies (Font Awesome, KaTeX, Mermaid, Twikoo). Swap to a mirror or self-hosted base if needed. | +| `code_max_lines` | `int` | `40` | Maximum visible lines before fenced code blocks gain a vertical scrollbar. `0` disables the limit. | +| `emojis` | `bool` | `true` | Replace `:shortcode:` with the corresponding Unicode emoji during rendering. | +| `fontawesome` | `bool` | `true` | Load the Font Awesome stylesheet. Disable when no `fa-*` classes are referenced anywhere in templates or content. | + +## `[params.background]` + +Site-wide background image with optional LQIP backdrop. Omit the table to use solid panel backgrounds. + +| Field | Type | Default | Description | +| ----------------- | -------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | +| `image` | `string` | none | Site-relative path to the background image. Required when the table is present. | +| `lqip_uri` | `string` | none | Pre-computed `data:image/webp;base64,...` URI rendered as a blurred backdrop until the full image decodes. Optional but recommended. | +| `position` | `string` | `"center"` | Desktop CSS `object-position` value (e.g., `"top"`, `"70% 50%"`). | +| `position_mobile` | `string` | falls back to `position` | Mobile-only CSS `object-position`. Useful for crops that frame poorly on portrait viewports. | + +`lqip_uri` is a hand-rolled placeholder — kiln auto-generates LQIPs only for content `` tags, not CSS-background equivalents. See [kiln's image pipeline docs](https://github.com/hakula139/kiln/blob/main/docs/themes.md#image-rendering) for the data-URI format. + +## `[params.home]` / `[params.home.profile]` + +Home-page profile panel and pagination. + +| Field | Type | Default | Description | +| ---------- | ----- | ------- | ------------------------------------------------------------------------------- | +| `paginate` | `int` | `8` | Posts per page on the home listing. Falls back to `params.paginate` when unset. | + +```toml +[params.home.profile] +avatar = "/images/avatar.webp" # Site-relative or absolute URL +title = "Site Title" # Falls back to config.title when unset +subtitle = "An optional tagline" +``` + +| Field | Type | Default | Description | +| ---------- | -------- | -------------- | ----------------------------------------------------------------------- | +| `avatar` | `string` | none | Profile avatar image. Site-relative path or absolute URL. Omit to hide. | +| `title` | `string` | `config.title` | Heading shown above the subtitle. Override to display a different name. | +| `subtitle` | `string` | none | Tagline rendered below the title. Omit to hide. | + +The optional social row under the profile is populated from `[[menu.social]]` and rendered in `weight` order. See [Customization → Social icons](customization.md#social-icons) for the icon spec format. + +## `[params.section]` + +Section-archive pagination. + +| Field | Type | Default | Description | +| ---------- | ----- | ------- | -------------------------------------------------------------------------------- | +| `paginate` | `int` | `10` | Posts per page on `/posts/
/` archives. Falls back to `params.paginate`. | + +## `[params.comments]` + +Per-post comment system. + +| Field | Type | Default | Description | +| ---------- | -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `enabled` | `bool` | `false` | Master switch. The comments partial and its CDN dep load only when enabled. | +| `provider` | `string` | `"twikoo"` | Provider key matching a partial under `templates/_partials/comments/.html`. Currently only `"twikoo"` ships built in. | + +Provider-specific configuration lives under `[params.comments.]`: + +```toml +[params.comments.twikoo] +api_url = "https://twikoo.example.com" +``` + +Adding a provider is a [Customization](customization.md#comments-providers) topic. + +## `[params.footer]` + +Footer copyright line and license badge. + +```toml +[params.footer] +since = 2018 +license = "CC BY-NC-SA 4.0" +license_url = "https://creativecommons.org/licenses/by-nc-sa/4.0/" +powered_by = true +``` + +| Field | Type | Default | Description | +| ------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------- | +| `since` | `int` | none | Copyright start year. The footer renders `` when set, current year only otherwise. | +| `license` | `string` | none | License name shown after the copyright line. | +| `license_url` | `string` | none | URL the license name links to. | +| `powered_by` | `bool` | `true` | Show the "Powered by kiln & IgnIt" attribution line. | + +## `[params.effects]` + +Optional visual effects. Off by default. + +| Field | Type | Default | Description | +| ------------- | ------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `cursor_glow` | `bool` | `false` | Cursor-tracking glow on glass panels. Enabling injects `will-change` layers that interact poorly with `backdrop-filter` on Chromium / WebKit — see the rendering caveats in the README before turning on. | + +## `[params.deps.]` + +Pinned versions and SRI hashes for vendor CDN dependencies. Theme-internal — rebuild only when bumping a dep version. The schema is documented in [`CLAUDE.md`](../CLAUDE.md#dependencies); sites consuming defaults don't need to touch this table. + +## Menu groups + +`[[menu.main]]` populates the header navigation; `[[menu.social]]` populates the home profile social row. Both follow [kiln's menu schema](https://github.com/hakula139/kiln/blob/main/docs/themes.md#navigation-menus). IgnIt extends the `icon` field to accept either a Font Awesome class string (`"fab fa-github"`) or a `svg:` reference resolved against `templates/_partials/icons/.svg` — see [Customization → Social icons](customization.md#social-icons) for the bundled registry and the override pattern. diff --git a/static/css/_src/base.css b/static/css/_src/base.css index 8c7509d..6981ee8 100644 --- a/static/css/_src/base.css +++ b/static/css/_src/base.css @@ -1,19 +1,17 @@ -/* Temporarily enable color transitions during theme toggle (added / removed by JS). */ -@media (prefers-reduced-motion: no-preference) { - html.theme-transition, - html.theme-transition *, - html.theme-transition *::before, - html.theme-transition *::after { - @apply transition-[background-color,border-color]! duration-300!; - } -} - @layer base { @media (prefers-reduced-motion: no-preference) { html, .toc-sidebar-scroll { @apply scroll-smooth; } + + /* Temporarily enable color transitions during theme toggle (added / removed by JS). */ + html.theme-transition, + html.theme-transition *, + html.theme-transition *::before, + html.theme-transition *::after { + @apply transition-[background-color,border-color]! duration-300!; + } } /* Honor prefers-reduced-motion globally. 0.01ms (not 0) preserves diff --git a/static/css/_src/components/content/callout.css b/static/css/_src/components/content/callout.css index 2481036..e841f88 100644 --- a/static/css/_src/components/content/callout.css +++ b/static/css/_src/components/content/callout.css @@ -21,9 +21,7 @@ /* Variant icon (left); chevron (right) lives in collapse.css. */ .callout-title::before { - @apply shrink-0; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; + @apply fa-icon; content: var(--callout-icon, '\f05a'); } diff --git a/static/css/_src/components/content/collapse.css b/static/css/_src/components/content/collapse.css index 28760d8..98bd6b1 100644 --- a/static/css/_src/components/content/collapse.css +++ b/static/css/_src/components/content/collapse.css @@ -14,9 +14,7 @@ .code-header::before, .callout-title::after, .toc-trigger::after { - @apply shrink-0 text-xs transition-transform duration-200; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; + @apply fa-icon text-xs transition-transform duration-200; content: '\f078'; } diff --git a/static/css/_src/components/content/comments.css b/static/css/_src/components/content/comments.css index 693792c..db43bbd 100644 --- a/static/css/_src/components/content/comments.css +++ b/static/css/_src/components/content/comments.css @@ -1,3 +1,5 @@ +/* Unlayered — must override Twikoo's injected styles and prose.css defaults at full specificity. */ + /* Override prose.css's inline default for non-figure imgs. */ .prose .tk-avatar-img { @apply my-0 block; @@ -23,6 +25,5 @@ } .twikoo .OwO .OwO-body { - @apply overflow-hidden rounded-card text-text shadow-card glass-surface; - border: 1px solid var(--surface-glass-border); + @apply overflow-hidden rounded-card glass-surface text-text shadow-card; } diff --git a/static/css/_src/components/content/syntax.css b/static/css/_src/components/content/syntax.css index db54964..9c4c873 100644 --- a/static/css/_src/components/content/syntax.css +++ b/static/css/_src/components/content/syntax.css @@ -2,8 +2,8 @@ /* ── Syntax Highlighting (Material Light / Palenight) ── */ .highlight { - color: var(--syntax-default); @apply bg-bg-muted/60; + color: var(--syntax-default); } .highlight .source { diff --git a/static/css/_src/components/content/toc.css b/static/css/_src/components/content/toc.css index 4b41ddb..ae1a3f6 100644 --- a/static/css/_src/components/content/toc.css +++ b/static/css/_src/components/content/toc.css @@ -38,9 +38,7 @@ /* List-ul icon (left); chevron (right) lives in collapse.css. */ .toc-trigger::before { - @apply shrink-0 text-text-tertiary; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; + @apply fa-icon text-text-tertiary; content: '\f0ca'; } diff --git a/static/css/_src/components/embed/apple-music.css b/static/css/_src/components/embed/apple-music.css index 3b9ce43..00c9ab8 100644 --- a/static/css/_src/components/embed/apple-music.css +++ b/static/css/_src/components/embed/apple-music.css @@ -22,7 +22,6 @@ /* ── Dark Mode ── */ /* `display` set per-variant to avoid specificity conflict with `.apple-music-wrapper iframe`. */ - .apple-music-light { @apply block; diff --git a/static/css/_src/components/layout/error-page.css b/static/css/_src/components/layout/error-page.css new file mode 100644 index 0000000..cf5c10b --- /dev/null +++ b/static/css/_src/components/layout/error-page.css @@ -0,0 +1,9 @@ +@layer components { + .back-home-link { + @apply inline-flex items-center gap-2 rounded-md border-b-0 px-3 py-2 text-sm text-text-secondary no-underline transition-colors; + } + + .back-home-link:hover { + @apply icon-tap-hover; + } +} diff --git a/static/css/_src/components/layout/glass-panel.css b/static/css/_src/components/layout/glass-panel.css index 04c262f..f4d05af 100644 --- a/static/css/_src/components/layout/glass-panel.css +++ b/static/css/_src/components/layout/glass-panel.css @@ -1,6 +1,7 @@ @utility glass-surface { - @apply backdrop-blur-lg backdrop-saturate-125; + @apply border backdrop-blur-lg backdrop-saturate-125; background: var(--surface-glass-panel); + border-color: var(--surface-glass-border); } @layer components { @@ -11,19 +12,17 @@ --glow-color: transparent; --glow-border-size: 400px; --glow-border-color: transparent; - } - - .glass-glow { - @apply rounded-card; - } - @variant dark { - :where(.glass-glow, [data-glow-target]) { + @variant dark { --glow-color: rgba(255, 255, 255, 0.05); --glow-border-color: rgba(255, 255, 255, 0.7); } } + .glass-glow { + @apply rounded-card; + } + /* ── Glass Glow: Cursor-Tracking Layers ── */ .glow-ambient { @@ -58,8 +57,7 @@ /* No `position` — consumers (.post-banner, .home-card, .glass-glow) provide their own. */ .glass-panel { - @apply rounded-card shadow-card glass-surface backface-hidden; - border: 1px solid var(--surface-glass-border); + @apply rounded-card glass-surface shadow-card backface-hidden; } /* Always chained with `.glass-panel` in markup, so glass tokens come from there. */ diff --git a/static/css/_src/components/layout/header.css b/static/css/_src/components/layout/header.css index dc932ef..6b58e39 100644 --- a/static/css/_src/components/layout/header.css +++ b/static/css/_src/components/layout/header.css @@ -9,38 +9,40 @@ @apply text-link-hover; } - /* ── Desktop Nav Links (icon-only) ── */ + /* ── Desktop Nav (icon-only anchors and buttons) ── */ - .header-link { - @apply inline-flex aspect-square items-center justify-center rounded-md border-b-0 p-2 text-text-secondary transition-colors; + .header-link, + .header-icon { + @apply inline-flex aspect-square items-center justify-center rounded-md p-2 text-text-secondary transition-colors; } - .header-link:hover { - @apply bg-bg/50 text-link-hover; - - @variant dark { - @apply bg-white/10; - } + .header-link:hover, + .header-icon:hover { + @apply icon-tap-hover; } - /* ── Group Dividers ── */ + .header-link { + @apply border-b-0; + } - .header-divider { - @apply mx-1 h-5 w-px bg-border-overlay; + .header-icon { + @apply cursor-pointer; } - /* ── Icon Buttons ── */ + .header-link :is(i, svg), + .header-mobile-link :is(i, svg) { + @apply inline-block w-[1.25em] text-center; + } - .header-icon { - @apply inline-flex aspect-square cursor-pointer items-center justify-center rounded-md p-2 text-text-secondary transition-colors; + .header-link svg, + .header-mobile-link svg { + @apply h-[1em] fill-current align-middle; } - .header-icon:hover { - @apply bg-bg/50 text-link-hover; + /* ── Group Dividers ── */ - @variant dark { - @apply bg-white/10; - } + .header-divider { + @apply mx-1 h-5 w-px bg-border-overlay; } /* ── Mobile Backdrop ── */ @@ -53,8 +55,7 @@ /* ── Mobile Dropdown ── */ .header-mobile { - @apply fixed inset-x-0 top-14 z-40 max-h-[calc(100dvh-3.5rem)] overflow-y-auto border-b py-2 glass-surface md:hidden; - border-color: var(--surface-glass-border); + @apply fixed inset-x-0 top-14 z-40 max-h-[calc(100dvh-3.5rem)] overflow-y-auto glass-surface border-x-0 border-t-0 py-2 md:hidden; } .header-mobile-link { diff --git a/static/css/_src/components/layout/skip-link.css b/static/css/_src/components/layout/skip-link.css index ed579ce..5df31eb 100644 --- a/static/css/_src/components/layout/skip-link.css +++ b/static/css/_src/components/layout/skip-link.css @@ -7,8 +7,7 @@ .skip-link:focus, .skip-link:focus-visible { - @apply fixed top-3 left-3 z-100 m-0 inline-flex h-auto w-auto items-center overflow-visible rounded-card px-4 py-2 text-sm leading-none font-medium whitespace-normal text-text glass-surface; + @apply fixed top-3 left-3 z-100 m-0 inline-flex h-auto w-auto items-center overflow-visible rounded-card glass-surface px-4 py-2 text-sm leading-none font-medium whitespace-normal text-text; clip: auto; - border: 1px solid var(--surface-glass-border); } } diff --git a/static/css/_src/components/listing/home-card.css b/static/css/_src/components/listing/home-card.css index b2f8b6e..96aa383 100644 --- a/static/css/_src/components/listing/home-card.css +++ b/static/css/_src/components/listing/home-card.css @@ -137,4 +137,20 @@ .profile-avatar { @apply mx-auto mb-4 h-24 w-24 rounded-full shadow-md; } + + .profile-socials { + @apply mt-4 flex flex-wrap items-center justify-center gap-2; + } + + .profile-social { + @apply inline-flex aspect-square items-center justify-center rounded-full border-b-0 p-2 text-2xl text-text-secondary no-underline transition-colors; + } + + .profile-social svg { + @apply size-[1em] fill-current; + } + + .profile-social:hover { + @apply icon-tap-hover; + } } diff --git a/static/css/_src/components/search/search.css b/static/css/_src/components/search/search.css index c2b2b03..f0fb4ff 100644 --- a/static/css/_src/components/search/search.css +++ b/static/css/_src/components/search/search.css @@ -28,8 +28,7 @@ /* ── Modal Surface ── */ pagefind-modal > dialog.pf-modal { - @apply rounded-card! border! glass-surface!; - border-color: var(--surface-glass-border) !important; + @apply rounded-card! glass-surface!; } pagefind-modal > dialog.pf-modal::backdrop { diff --git a/static/css/_src/components/shared/fa-icon.css b/static/css/_src/components/shared/fa-icon.css new file mode 100644 index 0000000..b0acf99 --- /dev/null +++ b/static/css/_src/components/shared/fa-icon.css @@ -0,0 +1,6 @@ +/* Font Awesome glyph setup for ::before / ::after pseudo-elements (callout, collapse, toc). */ +@utility fa-icon { + @apply shrink-0; + font-family: 'Font Awesome 6 Free'; + font-weight: 900; +} diff --git a/static/css/_src/components/shared/icon-button.css b/static/css/_src/components/shared/icon-button.css new file mode 100644 index 0000000..9493c3f --- /dev/null +++ b/static/css/_src/components/shared/icon-button.css @@ -0,0 +1,7 @@ +@utility icon-tap-hover { + @apply bg-bg/50 text-link-hover; + + @variant dark { + @apply bg-white/10; + } +} diff --git a/static/css/_src/main.css b/static/css/_src/main.css index d219324..7a81678 100644 --- a/static/css/_src/main.css +++ b/static/css/_src/main.css @@ -60,14 +60,11 @@ --syntax-invalid: #e53935; /* Surfaces */ - --radius-sm: 0.25rem; --radius-md: 0.375rem; --radius-lg: 0.5rem; --radius-xl: 0.75rem; --radius-card: 1.25rem; - --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.1); --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.06), 0 4px 6px rgba(0, 0, 0, 0.1); - --shadow-lg: 0 4px 6px rgba(0, 0, 0, 0.05), 0 10px 15px rgba(0, 0, 0, 0.1); --shadow-card: 0 0.25rem 1rem rgba(0, 0, 0, 0.2); --drop-shadow-text: 0 1px 2px rgba(0, 0, 0, 0.3); --surface-glass-panel: rgba(255, 255, 255, 0.75); @@ -173,7 +170,12 @@ @import './base.css'; +/* Shared */ +@import './components/shared/fa-icon.css'; +@import './components/shared/icon-button.css'; + /* Layout */ +@import './components/layout/error-page.css'; @import './components/layout/float-buttons.css'; @import './components/layout/glass-panel.css'; @import './components/layout/header.css'; diff --git a/static/css/style.css b/static/css/style.css index a6f279c..6ed98bd 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -716,8 +716,8 @@ .hidden\! { display: none !important; } - .inline-block { - display: inline-block; + .inline { + display: inline; } .inline-flex { display: inline-flex; @@ -799,19 +799,12 @@ .overflow-hidden { overflow: hidden; } - .rounded-card { - border-radius: var(--radius-card); - } .rounded-lg { border-radius: var(--radius-lg); } .rounded-none { border-radius: 0; } - .border { - border-style: var(--tw-border-style); - border-width: 1px; - } .border-0 { border-style: var(--tw-border-style); border-width: 0px; @@ -820,12 +813,12 @@ border-bottom-style: var(--tw-border-style); border-bottom-width: 0px; } - .border-border { - border-color: var(--color-border); - } .p-6 { padding: calc(var(--spacing) * 6); } + .p-8 { + padding: calc(var(--spacing) * 8); + } .px-3 { padding-inline: calc(var(--spacing) * 3); } @@ -920,6 +913,11 @@ padding: calc(var(--spacing) * 8); } } + .sm\:p-16 { + @media (width >= 40rem) { + padding: calc(var(--spacing) * 16); + } + } .md\:flex { @media (width >= 48rem) { display: flex; @@ -1031,20 +1029,18 @@ --surface-overlay-backdrop: rgba(0, 0, 0, 0.55); } } -@media (prefers-reduced-motion: no-preference) { - html.theme-transition, html.theme-transition *, html.theme-transition *::before, html.theme-transition *::after { - transition-property: background-color,border-color !important; - transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)) !important; - transition-duration: var(--tw-duration, var(--default-transition-duration)) !important; - --tw-duration: 300ms !important; - transition-duration: 300ms !important; - } -} @layer base { @media (prefers-reduced-motion: no-preference) { html, .toc-sidebar-scroll { scroll-behavior: smooth; } + html.theme-transition, html.theme-transition *, html.theme-transition *::before, html.theme-transition *::after { + transition-property: background-color,border-color !important; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)) !important; + transition-duration: var(--tw-duration, var(--default-transition-duration)) !important; + --tw-duration: 300ms !important; + transition-duration: 300ms !important; + } } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { @@ -1086,6 +1082,38 @@ outline-color: var(--color-link); } } +@layer components { + .back-home-link { + display: inline-flex; + align-items: center; + gap: calc(var(--spacing) * 2); + border-radius: var(--radius-md); + border-bottom-style: var(--tw-border-style); + border-bottom-width: 0px; + padding-inline: calc(var(--spacing) * 3); + padding-block: calc(var(--spacing) * 2); + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + color: var(--color-text-secondary); + text-decoration-line: none; + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .back-home-link:hover { + background-color: color-mix(in srgb, #fff 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-bg) 50%, transparent); + } + color: var(--color-link-hover); + &:where([data-theme="dark"], [data-theme="dark"] *) { + background-color: color-mix(in srgb, #fff 10%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-white) 10%, transparent); + } + } + } +} @layer components { .float-btn { pointer-events: none; @@ -1132,16 +1160,14 @@ --glow-color: transparent; --glow-border-size: 400px; --glow-border-color: transparent; - } - .glass-glow { - border-radius: var(--radius-card); - } - &:where([data-theme="dark"], [data-theme="dark"] *) { - :where(.glass-glow, [data-glow-target]) { + &:where([data-theme="dark"], [data-theme="dark"] *) { --glow-color: rgba(255, 255, 255, 0.05); --glow-border-color: rgba(255, 255, 255, 0.7); } } + .glass-glow { + border-radius: var(--radius-card); + } .glow-ambient { pointer-events: none; position: absolute; @@ -1179,15 +1205,17 @@ } .glass-panel { border-radius: var(--radius-card); - --tw-shadow: 0 0.25rem 1rem var(--tw-shadow-color, rgba(0, 0, 0, 0.2)); - box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + border-style: var(--tw-border-style); + border-width: 1px; --tw-backdrop-blur: blur(var(--blur-lg)); --tw-backdrop-saturate: saturate(125%); -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); background: var(--surface-glass-panel); + border-color: var(--surface-glass-border); + --tw-shadow: 0 0.25rem 1rem var(--tw-shadow-color, rgba(0, 0, 0, 0.2)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); backface-visibility: hidden; - border: 1px solid var(--surface-glass-border); } .header-nav { display: flex; @@ -1227,21 +1255,19 @@ .header-logo:hover { color: var(--color-link-hover); } - .header-link { + .header-link, .header-icon { display: inline-flex; aspect-ratio: 1 / 1; align-items: center; justify-content: center; border-radius: var(--radius-md); - border-bottom-style: var(--tw-border-style); - border-bottom-width: 0px; padding: calc(var(--spacing) * 2); color: var(--color-text-secondary); transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); transition-duration: var(--tw-duration, var(--default-transition-duration)); } - .header-link:hover { + .header-link:hover, .header-icon:hover { background-color: color-mix(in srgb, #fff 50%, transparent); @supports (color: color-mix(in lab, red, red)) { background-color: color-mix(in oklab, var(--color-bg) 50%, transparent); @@ -1254,38 +1280,29 @@ } } } + .header-link { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 0px; + } + .header-icon { + cursor: pointer; + } + .header-link :is(i, svg), .header-mobile-link :is(i, svg) { + display: inline-block; + width: 1.25em; + text-align: center; + } + .header-link svg, .header-mobile-link svg { + height: 1em; + fill: currentcolor; + vertical-align: middle; + } .header-divider { margin-inline: calc(var(--spacing) * 1); height: calc(var(--spacing) * 5); width: 1px; background-color: var(--color-border-overlay); } - .header-icon { - display: inline-flex; - aspect-ratio: 1 / 1; - cursor: pointer; - align-items: center; - justify-content: center; - border-radius: var(--radius-md); - padding: calc(var(--spacing) * 2); - color: var(--color-text-secondary); - transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; - transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); - transition-duration: var(--tw-duration, var(--default-transition-duration)); - } - .header-icon:hover { - background-color: color-mix(in srgb, #fff 50%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-bg) 50%, transparent); - } - color: var(--color-link-hover); - &:where([data-theme="dark"], [data-theme="dark"] *) { - background-color: color-mix(in srgb, #fff 10%, transparent); - @supports (color: color-mix(in lab, red, red)) { - background-color: color-mix(in oklab, var(--color-white) 10%, transparent); - } - } - } .header-mobile-backdrop { position: fixed; inset: calc(var(--spacing) * 0); @@ -1306,18 +1323,22 @@ z-index: 40; max-height: calc(100dvh - 3.5rem); overflow-y: auto; - border-bottom-style: var(--tw-border-style); - border-bottom-width: 1px; - padding-block: calc(var(--spacing) * 2); + border-style: var(--tw-border-style); + border-width: 1px; --tw-backdrop-blur: blur(var(--blur-lg)); --tw-backdrop-saturate: saturate(125%); -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); background: var(--surface-glass-panel); + border-color: var(--surface-glass-border); + border-inline-style: var(--tw-border-style); + border-inline-width: 0px; + border-top-style: var(--tw-border-style); + border-top-width: 0px; + padding-block: calc(var(--spacing) * 2); @media (width >= 48rem) { display: none; } - border-color: var(--surface-glass-border); } .header-mobile-link { display: block; @@ -1457,6 +1478,14 @@ align-items: center; overflow: visible; border-radius: var(--radius-card); + border-style: var(--tw-border-style); + border-width: 1px; + --tw-backdrop-blur: blur(var(--blur-lg)); + --tw-backdrop-saturate: saturate(125%); + -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); + background: var(--surface-glass-panel); + border-color: var(--surface-glass-border); padding-inline: calc(var(--spacing) * 4); padding-block: calc(var(--spacing) * 2); font-size: var(--text-sm); @@ -1467,13 +1496,7 @@ font-weight: var(--font-weight-medium); white-space: normal; color: var(--color-text); - --tw-backdrop-blur: blur(var(--blur-lg)); - --tw-backdrop-saturate: saturate(125%); - -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); - backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); - background: var(--surface-glass-panel); clip: auto; - border: 1px solid var(--surface-glass-border); } } @layer components { @@ -1766,6 +1789,8 @@ } .code-header::before, .callout-title::after, .toc-trigger::after { flex-shrink: 0; + font-family: 'Font Awesome 6 Free'; + font-weight: 900; font-size: var(--text-xs); line-height: var(--tw-leading, var(--text-xs--line-height)); transition-property: transform, translate, scale, rotate; @@ -1773,8 +1798,6 @@ transition-duration: var(--tw-duration, var(--default-transition-duration)); --tw-duration: 200ms; transition-duration: 200ms; - font-family: 'Font Awesome 6 Free'; - font-weight: 900; content: '\f078'; } .callout-title::after, .toc-trigger::after { @@ -1820,15 +1843,17 @@ .twikoo .OwO .OwO-body { overflow: hidden; border-radius: var(--radius-card); - color: var(--color-text); - --tw-shadow: 0 0.25rem 1rem var(--tw-shadow-color, rgba(0, 0, 0, 0.2)); - box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + border-style: var(--tw-border-style); + border-width: 1px; --tw-backdrop-blur: blur(var(--blur-lg)); --tw-backdrop-saturate: saturate(125%); -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); background: var(--surface-glass-panel); - border: 1px solid var(--surface-glass-border); + border-color: var(--surface-glass-border); + color: var(--color-text); + --tw-shadow: 0 0.25rem 1rem var(--tw-shadow-color, rgba(0, 0, 0, 0.2)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } @layer components { .link-grid { @@ -2168,11 +2193,11 @@ } @layer components { .highlight { - color: var(--syntax-default); background-color: color-mix(in srgb, #f5f5f7 60%, transparent); @supports (color: color-mix(in lab, red, red)) { background-color: color-mix(in oklab, var(--color-bg-muted) 60%, transparent); } + color: var(--syntax-default); } .highlight .source { color: var(--syntax-default); @@ -2271,9 +2296,9 @@ } .toc-trigger::before { flex-shrink: 0; - color: var(--color-text-tertiary); font-family: 'Font Awesome 6 Free'; font-weight: 900; + color: var(--color-text-tertiary); content: '\f0ca'; } .toc-trigger::after { @@ -2633,6 +2658,49 @@ --tw-shadow: 0 2px 4px var(--tw-shadow-color, rgba(0, 0, 0, 0.06)), 0 4px 6px var(--tw-shadow-color, rgba(0, 0, 0, 0.1)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .profile-socials { + margin-top: calc(var(--spacing) * 4); + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + gap: calc(var(--spacing) * 2); + } + .profile-social { + display: inline-flex; + aspect-ratio: 1 / 1; + align-items: center; + justify-content: center; + border-radius: calc(infinity * 1px); + border-bottom-style: var(--tw-border-style); + border-bottom-width: 0px; + padding: calc(var(--spacing) * 2); + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + color: var(--color-text-secondary); + text-decoration-line: none; + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .profile-social svg { + width: 1em; + height: 1em; + fill: currentcolor; + } + .profile-social:hover { + background-color: color-mix(in srgb, #fff 50%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-bg) 50%, transparent); + } + color: var(--color-link-hover); + &:where([data-theme="dark"], [data-theme="dark"] *) { + background-color: color-mix(in srgb, #fff 10%, transparent); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-white) 10%, transparent); + } + } + } } @layer components { .year-heading { @@ -2897,106 +2965,106 @@ inherits: false; initial-value: solid; } -@property --tw-shadow { +@property --tw-backdrop-blur { syntax: "*"; inherits: false; - initial-value: 0 0 #0000; } -@property --tw-shadow-color { +@property --tw-backdrop-brightness { syntax: "*"; inherits: false; } -@property --tw-shadow-alpha { - syntax: ""; +@property --tw-backdrop-contrast { + syntax: "*"; inherits: false; - initial-value: 100%; } -@property --tw-inset-shadow { +@property --tw-backdrop-grayscale { syntax: "*"; inherits: false; - initial-value: 0 0 #0000; } -@property --tw-inset-shadow-color { +@property --tw-backdrop-hue-rotate { syntax: "*"; inherits: false; } -@property --tw-inset-shadow-alpha { - syntax: ""; +@property --tw-backdrop-invert { + syntax: "*"; inherits: false; - initial-value: 100%; } -@property --tw-ring-color { +@property --tw-backdrop-opacity { syntax: "*"; inherits: false; } -@property --tw-ring-shadow { +@property --tw-backdrop-saturate { syntax: "*"; inherits: false; - initial-value: 0 0 #0000; } -@property --tw-inset-ring-color { +@property --tw-backdrop-sepia { syntax: "*"; inherits: false; } -@property --tw-inset-ring-shadow { +@property --tw-shadow { syntax: "*"; inherits: false; initial-value: 0 0 #0000; } -@property --tw-ring-inset { +@property --tw-shadow-color { syntax: "*"; inherits: false; } -@property --tw-ring-offset-width { - syntax: ""; +@property --tw-shadow-alpha { + syntax: ""; inherits: false; - initial-value: 0px; + initial-value: 100%; } -@property --tw-ring-offset-color { +@property --tw-inset-shadow { syntax: "*"; inherits: false; - initial-value: #fff; + initial-value: 0 0 #0000; } -@property --tw-ring-offset-shadow { +@property --tw-inset-shadow-color { syntax: "*"; inherits: false; - initial-value: 0 0 #0000; } -@property --tw-backdrop-blur { - syntax: "*"; +@property --tw-inset-shadow-alpha { + syntax: ""; inherits: false; + initial-value: 100%; } -@property --tw-backdrop-brightness { +@property --tw-ring-color { syntax: "*"; inherits: false; } -@property --tw-backdrop-contrast { +@property --tw-ring-shadow { syntax: "*"; inherits: false; + initial-value: 0 0 #0000; } -@property --tw-backdrop-grayscale { +@property --tw-inset-ring-color { syntax: "*"; inherits: false; } -@property --tw-backdrop-hue-rotate { +@property --tw-inset-ring-shadow { syntax: "*"; inherits: false; + initial-value: 0 0 #0000; } -@property --tw-backdrop-invert { +@property --tw-ring-inset { syntax: "*"; inherits: false; } -@property --tw-backdrop-opacity { - syntax: "*"; +@property --tw-ring-offset-width { + syntax: ""; inherits: false; + initial-value: 0px; } -@property --tw-backdrop-saturate { +@property --tw-ring-offset-color { syntax: "*"; inherits: false; + initial-value: #fff; } -@property --tw-backdrop-sepia { +@property --tw-ring-offset-shadow { syntax: "*"; inherits: false; + initial-value: 0 0 #0000; } @property --tw-gradient-position { syntax: "*"; @@ -3180,6 +3248,15 @@ --tw-duration: initial; --tw-leading: initial; --tw-outline-style: solid; + --tw-backdrop-blur: initial; + --tw-backdrop-brightness: initial; + --tw-backdrop-contrast: initial; + --tw-backdrop-grayscale: initial; + --tw-backdrop-hue-rotate: initial; + --tw-backdrop-invert: initial; + --tw-backdrop-opacity: initial; + --tw-backdrop-saturate: initial; + --tw-backdrop-sepia: initial; --tw-shadow: 0 0 #0000; --tw-shadow-color: initial; --tw-shadow-alpha: 100%; @@ -3194,15 +3271,6 @@ --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 #0000; - --tw-backdrop-blur: initial; - --tw-backdrop-brightness: initial; - --tw-backdrop-contrast: initial; - --tw-backdrop-grayscale: initial; - --tw-backdrop-hue-rotate: initial; - --tw-backdrop-invert: initial; - --tw-backdrop-opacity: initial; - --tw-backdrop-saturate: initial; - --tw-backdrop-sepia: initial; --tw-gradient-position: initial; --tw-gradient-from: #0000; --tw-gradient-via: #0000; diff --git a/templates/404.html b/templates/404.html index 46b029f..b460519 100644 --- a/templates/404.html +++ b/templates/404.html @@ -5,15 +5,12 @@ {% block body %}
-
+ diff --git a/templates/_partials/icons/bilibili.svg b/templates/_partials/icons/bilibili.svg new file mode 100644 index 0000000..ee80dc2 --- /dev/null +++ b/templates/_partials/icons/bilibili.svg @@ -0,0 +1 @@ + diff --git a/templates/_partials/icons/douban.svg b/templates/_partials/icons/douban.svg new file mode 100644 index 0000000..c8eb0b8 --- /dev/null +++ b/templates/_partials/icons/douban.svg @@ -0,0 +1 @@ + diff --git a/templates/_partials/icons/fediverse.svg b/templates/_partials/icons/fediverse.svg new file mode 100644 index 0000000..4f0abe8 --- /dev/null +++ b/templates/_partials/icons/fediverse.svg @@ -0,0 +1,5 @@ + diff --git a/templates/_partials/icons/github.svg b/templates/_partials/icons/github.svg new file mode 100644 index 0000000..56379fa --- /dev/null +++ b/templates/_partials/icons/github.svg @@ -0,0 +1 @@ + diff --git a/templates/_partials/icons/linkedin.svg b/templates/_partials/icons/linkedin.svg new file mode 100644 index 0000000..b93e01f --- /dev/null +++ b/templates/_partials/icons/linkedin.svg @@ -0,0 +1 @@ + diff --git a/templates/_partials/icons/listenbrainz.svg b/templates/_partials/icons/listenbrainz.svg new file mode 100644 index 0000000..acabdda --- /dev/null +++ b/templates/_partials/icons/listenbrainz.svg @@ -0,0 +1 @@ + diff --git a/templates/_partials/icons/mastodon.svg b/templates/_partials/icons/mastodon.svg new file mode 100644 index 0000000..f960941 --- /dev/null +++ b/templates/_partials/icons/mastodon.svg @@ -0,0 +1 @@ + diff --git a/templates/_partials/icons/qq.svg b/templates/_partials/icons/qq.svg new file mode 100644 index 0000000..8f999ef --- /dev/null +++ b/templates/_partials/icons/qq.svg @@ -0,0 +1 @@ + diff --git a/templates/_partials/icons/steam.svg b/templates/_partials/icons/steam.svg new file mode 100644 index 0000000..aa7d51a --- /dev/null +++ b/templates/_partials/icons/steam.svg @@ -0,0 +1 @@ + diff --git a/templates/_partials/icons/telegram.svg b/templates/_partials/icons/telegram.svg new file mode 100644 index 0000000..75a5d39 --- /dev/null +++ b/templates/_partials/icons/telegram.svg @@ -0,0 +1 @@ + diff --git a/templates/_partials/icons/zhihu.svg b/templates/_partials/icons/zhihu.svg new file mode 100644 index 0000000..ac35400 --- /dev/null +++ b/templates/_partials/icons/zhihu.svg @@ -0,0 +1 @@ + diff --git a/templates/_partials/layout/head-deps.html b/templates/_partials/layout/head-deps.html index ccc88fd..e00c936 100644 --- a/templates/_partials/layout/head-deps.html +++ b/templates/_partials/layout/head-deps.html @@ -19,6 +19,13 @@ type="font/woff2" crossorigin > +
@@ -16,7 +17,7 @@ title="{{ item_label }}" aria-label="{{ item_label }}" > - {%- if item.icon %}{% endif -%} + {{- icons.render(item.icon) -}} {%- endfor %} @@ -30,7 +31,7 @@ title="{{ item_label }}" aria-label="{{ item_label }}" > - {%- if item.icon %}{% endif -%} + {{- icons.render(item.icon) -}} {%- endfor %} @@ -91,8 +92,7 @@ class="header-mobile-link" style="--item-index: {{ loop.index0 }}" > - {%- if item.icon %} {% endif -%} - {{ t(item.name) }} + {{- icons.render(item.icon) }} {{ t(item.name) }} {%- endfor %} {%- set extra_index = menu_items | length -%} diff --git a/templates/_partials/macros/icon.html b/templates/_partials/macros/icon.html new file mode 100644 index 0000000..a48546d --- /dev/null +++ b/templates/_partials/macros/icon.html @@ -0,0 +1,10 @@ +{# Two forms: "fab fa-github" → , "svg:" → inline _partials/icons/.svg. #} +{%- macro render(spec) %} + {%- if spec %} + {%- if spec[:4] == "svg:" -%} + {%- include "_partials/icons/" ~ spec[4:] ~ ".svg" -%} + {%- else -%} + + {%- endif %} + {%- endif %} +{%- endmacro %} diff --git a/templates/home.html b/templates/home.html index 63935bf..11f8368 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{%- import "_partials/macros/icon.html" as icons -%} {% block head %} {%- set og_type = "website" -%} @@ -7,6 +8,7 @@ {% block body %} {%- set profile = config.params.home.profile %} +{%- set socials = config.menu.social | default([]) %} {%- if profile %}
@@ -24,6 +26,22 @@

{{ profile.title }}

{%- if profile.subtitle %}

{{ profile.subtitle }}

{%- endif %} + {%- if socials %} +
+ {%- for item in socials %} + {%- set item_label = t(item.name) %} + + {{- icons.render(item.icon) -}} + + {%- endfor %} +
+ {%- endif %}
diff --git a/theme.toml b/theme.toml index a095ced..f52180f 100644 --- a/theme.toml +++ b/theme.toml @@ -1,7 +1,7 @@ name = "IgnIt" description = "A clean, feature-rich theme for kiln — inspired by Hugo LoveIt" license = "MIT" -min_kiln_version = "0.1.0" +min_kiln_version = "0.3.0-rc.1" [author] name = "Hakula"