diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8039518f..7b309a48 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -22,10 +22,10 @@ This document defines the detailed coding standards for the FixIt theme project. - Use UnoCSS atomic classes for common utilities (`hidden`, `me-1`, `text-center`, etc.) - Theme colors: `bg-primary`, `text-success`, `text-danger`, etc. (mapped via `@theme` in `uno.config.ts`) -- Responsive: `sm:` (>=681px), `md:` (>=961px), `lg:` (>=1201px), `xl:` (>=1441px). Use `max-sm:` for xs (<681px) +- Responsive: `sm:` (>=680px), `md:` (>=960px), `lg:` (>=1200px), `xl:` (>=1440px). Use `max-sm:` for xs (<680px) - Wrap SVG elements with `` / `` to avoid false positives from `d` attribute values - Dynamically generated classes (e.g., `order-*`) must be added to `safelist` in `uno.config.ts` -- Run `pnpm css:build` after modifying `uno.config.ts` or templates that use UnoCSS classes +- Run `pnpm unocss` after modifying `uno.config.ts` or templates that use UnoCSS classes ## TypeScript Coding Standards diff --git a/CLAUDE.md b/CLAUDE.md index 273c401a..d997de5a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -90,7 +90,7 @@ Cross-module communication uses the shared `eventBus` singleton, not direct modu Hugo Pipes processes all assets. The key orchestration is in `_partials/base/assets.html`: -- **CSS**: `scss/main.scss` is the entry point importing `core/`, `pages/`, `widgets/`, `custom`. SCSS variables are configured via `hugo:vars` (user-configurable from `[params.appearance]`) and `hugo:vars/internal` (theme system config). UnoCSS utility classes are pre-built to `assets/css/unocss.css` (via `pnpm css:build`), loaded before the main SCSS bundle. +- **CSS**: `scss/main.scss` is the entry point importing `core/`, `pages/`, `widgets/`, `custom`. SCSS variables are configured via `hugo:vars` (user-configurable from `[params.appearance]`) and `hugo:vars/internal` (theme system config). UnoCSS utility classes are pre-built to `assets/css/unocss.css` (via `pnpm unocss`), loaded before the main SCSS bundle. - **JS**: `_partials/function/js-build.html` wraps `js.Build` with minify-in-production defaults. Hugo's `@params` injection passes config values into TypeScript at build time. - **Third-party libraries**: Stored in `assets/lib/` (vendored, not npm-managed). Tracked by `librarybot.yml` and updated weekly by the `hugo-fixit/librarybot` GitHub Action. Can be overridden via CDN config in `assets/data/cdn/jsdelivr.yml` or `unpkg.yml`. @@ -112,10 +112,10 @@ Hugo Pipes processes all assets. The key orchestration is in `_partials/base/ass ### UnoCSS (`assets/css/unocss.css`) -- Pre-built utility classes generated by `pnpm css:build` (config: `uno.config.ts`) +- Pre-built utility classes generated by `pnpm unocss` (config: `uno.config.ts`) - Use atomic classes for common utilities (`hidden`, `me-1`, `text-center`, etc.) - Theme colors mapped via `@theme`: `bg-primary`, `text-success`, `text-danger`, etc. -- Responsive breakpoints: `sm:` (>=681px), `md:` (>=961px), `lg:` (>=1201px), `xl:` (>=1441px). Use `max-sm:` for xs (<681px) +- Responsive breakpoints: `sm:` (>=680px), `md:` (>=960px), `lg:` (>=1200px), `xl:` (>=1440px). Use `max-sm:` for xs (<680px) - Wrap SVG elements with `` / `` to avoid false positives from `d` attribute values - `safelist` in `uno.config.ts` for dynamically generated classes (e.g., `order-*`, `sm:hidden`) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76f7e4b7..6a1dd5af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,9 +82,9 @@ pnpm gen:lexers pnpm preview # Build UnoCSS utility classes (pre-built, committed to repo) -pnpm css:build +pnpm unocss # Watch mode for UnoCSS (for theme developers) -pnpm css:dev +pnpm unocss:watch ``` > [!TIP] diff --git a/assets/css/unocss.css b/assets/css/unocss.css index 6f56fc6c..362ea078 100644 --- a/assets/css/unocss.css +++ b/assets/css/unocss.css @@ -82,9 +82,12 @@ .opacity-50{opacity:0.5;} .shadow{--un-shadow:var(--un-shadow-inset) 0 1px 3px 0 var(--un-shadow-color, rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 1px 2px -1px var(--un-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);} .blur{--un-blur:blur(8px);filter:var(--un-blur) var(--un-brightness) var(--un-contrast) var(--un-drop-shadow) var(--un-grayscale) var(--un-hue-rotate) var(--un-invert) var(--un-saturate) var(--un-sepia);} -@media (max-width: 680.9px){ +@media print{ +.print\:hidden{display:none;} +} +@media (max-width: 679.9px){ .max-sm\:hidden{display:none;} } -@media (min-width: 681px){ +@media (min-width: 680px){ .sm\:hidden{display:none;} } \ No newline at end of file diff --git a/assets/scss/core/mixins/_media.scss b/assets/scss/core/mixins/_media.scss index 7d2922c7..cdc425de 100644 --- a/assets/scss/core/mixins/_media.scss +++ b/assets/scss/core/mixins/_media.scss @@ -1,45 +1,48 @@ -// Responsive breakpoints matching the project's media query design -// ┌──────────────────────────────────────────────────────┐ -// │ xs │ sm │ md │ lg │ xl │ -// │ ≤680 │ 681–960 │ 961–1200 │ 1201–1440 │ ≥1441 │ -// └──────────────────────────────────────────────────────┘ +// FixIt Responsive breakpoints system design +// ┌──────┬───────────┬───────────┬──────────────┐ +// │ Name │ min-width │ max-width │ Direction │ +// ├──────┼───────────┼───────────┼──────────────┤ +// │ xs │ (null) │ 679.9px │ only │ +// │ sm │ 680px │ 959.9px │ only/up/down │ +// │ md │ 960px │ 1199.9px │ only/down │ +// │ lg │ 1200px │ 1439.9px │ only/up │ +// │ xl │ 1440px │ (null) │ only │ +// └──────┴───────────┴───────────┴──────────────┘ @use "sass:map"; +@use "sass:list"; $breakpoints: ( - 'xs': ( - 'min': null, - 'max': 680.9px, - ), - 'sm': ( - 'min': 681px, - 'max': 960.9px, - ), - 'md': ( - 'min': 961px, - 'max': 1200.9px, - ), - 'lg': ( - 'min': 1201px, - 'max': 1440.9px, - ), - 'xl': ( - 'min': 1441px, - 'max': null, - ), + 'xs': null, + 'sm': 680px, + 'md': 960px, + 'lg': 1200px, + 'xl': 1440px, ) !default; +/// Get the max-width for a breakpoint (0.1px below the next breakpoint). +@function _bp-max($name) { + // Ordered list of breakpoint names for range derivation + $keys: map.keys($breakpoints); + $idx: list.index($keys, $name); + @if $idx == null or $idx >= list.length($keys) { + @return null; + } + $next: list.nth($keys, $idx + 1); + @return map.get($breakpoints, $next) - 0.1px; +} + /// Apply styles within a named media target. /// @param {String} $target - Target name: xs | sm | md | lg | xl | print | reduce-motion /// @param {String} $direction - Range direction: only | up | down [default: only] /// /// @example -/// // xs: ≤ 680px +/// // xs: < 680px /// @include media('xs') { ... } -/// // sm: 681px – 960px +/// // sm: 680px – 959.9px /// @include media('sm') { ... } -/// // md and above: ≥ 961px +/// // md and above: ≥ 960px /// @include media('md', 'up') { ... } -/// // lg and below: ≤ 1440px +/// // lg and below: ≤ 1199.9px /// @include media('lg', 'down') { ... } /// // print media /// @include media('print') { ... } @@ -54,21 +57,13 @@ $breakpoints: ( @media screen and (prefers-reduced-motion: reduce) { @content; } - } @else { - $range: map.get($breakpoints, $target); - $min: null; - $max: null; + } @else if map.has-key($breakpoints, $target) { + $min: map.get($breakpoints, $target); + $max: _bp-max($target); - @if $range != null { - $min: map.get($range, 'min'); - $max: map.get($range, 'max'); - } - - @if $range == null { - @warn "Unknown media target `#{$target}`. Expected: xs, sm, md, lg, xl, print, reduce-motion."; - } @else if $direction == 'only' { + @if $direction == 'only' { @if $min == null { - // xs: max-width only, no lower bound + // xs: max-width only @media only screen and (max-width: $max) { @content; } @@ -84,7 +79,7 @@ $breakpoints: ( } } @else if $direction == 'up' { @if $min == null { - // xs-up: no lower bound, always applies + // xs-up: always applies @content; } @else { @media only screen and (min-width: $min) { @@ -93,7 +88,7 @@ $breakpoints: ( } } @else if $direction == 'down' { @if $max == null { - // xl-down: no upper bound, always applies + // xl-down: always applies @content; } @else { @media only screen and (max-width: $max) { @@ -103,5 +98,7 @@ $breakpoints: ( } @else { @warn "Unknown direction `#{$direction}`. Expected: only, up, down."; } + } @else { + @warn "Unknown media target `#{$target}`. Expected: xs, sm, md, lg, xl, print, reduce-motion."; } } diff --git a/layouts/_partials/init/index.html b/layouts/_partials/init/index.html index 6bd5f953..cb07bb0a 100644 --- a/layouts/_partials/init/index.html +++ b/layouts/_partials/init/index.html @@ -1,4 +1,4 @@ -{{- hugo.Store.Set "version" "v1.0.0-mqt0ysyp" -}} +{{- hugo.Store.Set "version" "v1.0.0-mqw3lkj1" -}} {{- .Store.Set "this" dict -}} {{- partial "init/detection-env.html" . -}} diff --git a/layouts/posts/single.html b/layouts/posts/single.html index 02677b2f..cd4da146 100644 --- a/layouts/posts/single.html +++ b/layouts/posts/single.html @@ -151,7 +151,7 @@ (dict "Process" "resize 1200x webp" "descriptor" "1200w") (dict "Process" "resize 1600x webp" "descriptor" "1600w") -}} - {{- dict "Src" $image "Title" $.Description "Resources" .Resources "Matches" $matches "Loading" "eager" "Sizes" "(max-width: 680px) 100vw, (max-width: 960px) 80vw, (max-width: 1440px) 56vw, 800px" "OptimConfig" $optim | partial "plugin/image.html" -}} + {{- dict "Src" $image "Title" $.Description "Resources" .Resources "Matches" $matches "Loading" "eager" "Sizes" "(max-width: 679.9px) 100vw, (max-width: 959.9px) 80vw, (max-width: 1439.9px) 56vw, 800px" "OptimConfig" $optim | partial "plugin/image.html" -}} {{- end -}} diff --git a/layouts/summary.html b/layouts/summary.html index ade2861d..55d8a31a 100644 --- a/layouts/summary.html +++ b/layouts/summary.html @@ -18,7 +18,7 @@ (dict "Process" "fill 1200x504 Center webp" "descriptor" "1200w") (dict "Process" "fill 1600x672 Center webp" "descriptor" "1600w") -}} - {{- dict "Src" $image "Title" $.Title "Resources" $.Resources "Matches" $matches "Loading" "eager" "Width" "800" "Height" "336" "Sizes" "(max-width: 680px) 100vw, (max-width: 960px) 80vw, (max-width: 1440px) 56vw, 800px" "OptimConfig" $optim "Alt" (printf "Featured image for %v" $.Title) | partial "plugin/image.html" -}} + {{- dict "Src" $image "Title" $.Title "Resources" $.Resources "Matches" $matches "Loading" "eager" "Width" "800" "Height" "336" "Sizes" "(max-width: 679.9px) 100vw, (max-width: 959.9px) 80vw, (max-width: 1439.9px) 56vw, 800px" "OptimConfig" $optim "Alt" (printf "Featured image for %v" $.Title) | partial "plugin/image.html" -}} {{- end -}} diff --git a/package.json b/package.json index 3e544858..32d1fc56 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,8 @@ "version": "pnpm -F versioning start prod", "changelog": "auto-changelog-plus --starting-date 2021-12-18", "gen:lexers": "pnpm -F chroma-lexers start", - "css:build": "unocss \"layouts/**/*.html\" \"assets/js/**/*.ts\" -o assets/css/unocss.css", - "css:dev": "pnpm css:build --watch", + "unocss": "unocss", + "unocss:watch": "unocss --watch", "prepare": "simple-git-hooks" }, "devDependencies": { diff --git a/uno.config.ts b/uno.config.ts index 548d9831..4569feb9 100644 --- a/uno.config.ts +++ b/uno.config.ts @@ -17,12 +17,12 @@ export default defineConfig({ ], theme: { // FixIt responsive breakpoints (min-width, mobile-first) - // xs (max-width: 680.9px) use max-sm: prefix in UnoCSS + // xs (< 680px) use max-sm: prefix in UnoCSS breakpoints: { - sm: '681px', - md: '961px', - lg: '1201px', - xl: '1441px', + sm: '680px', + md: '960px', + lg: '1200px', + xl: '1440px', }, colors: { primary: 'var(--fi-primary)', @@ -65,6 +65,7 @@ export default defineConfig({ // Responsive visibility 'sm:hidden', 'max-sm:hidden', + 'print:hidden', // Print page breaks 'break-before-page', 'break-after-page', @@ -76,4 +77,13 @@ export default defineConfig({ 'assets/js/**/*.ts', ], }, + // CLI entry for unocss / unocss:watch + cli: { + entry: [ + { + patterns: ['layouts/**/*.html', 'assets/js/**/*.ts'], + outFile: 'assets/css/unocss.css', + }, + ], + }, })