├── .editorconfig ├── .env.development.example ├── .env.production.example ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── public ├── .htaccess ├── bootstrap.php ├── index.php └── media │ └── index.html ├── scripts └── ploi-deploy.sh ├── site ├── blueprints │ ├── files │ │ └── image.yml │ ├── pages │ │ └── default.yml │ ├── sections │ │ ├── blocks.yml │ │ ├── images.yml │ │ └── meta.yml │ └── site.yml ├── config │ ├── config.php │ └── meta.php ├── plugins │ └── .gitkeep ├── snippets │ ├── blocks │ │ ├── heading.php │ │ └── image.php │ ├── footer.php │ └── layouts │ │ └── default.php └── templates │ └── default.php ├── src ├── env.d.ts ├── main.ts ├── modules │ ├── README.md │ └── demo.ts ├── styles │ └── main.scss └── templates │ ├── README.md │ ├── default.ts │ └── home.ts ├── storage ├── accounts │ └── .gitkeep ├── cache │ └── .gitkeep ├── content │ ├── .gitkeep │ ├── error │ │ └── default.txt │ ├── home │ │ └── default.txt │ └── site.txt ├── logs │ └── .gitkeep └── sessions │ └── .gitkeep ├── tsconfig.json ├── unocss.config.ts └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.php] 12 | indent_size = 4 13 | 14 | [site/{snippets,templates}/**/*.php] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.env.development.example: -------------------------------------------------------------------------------- 1 | KIRBY_DEBUG=true 2 | KIRBY_PANEL_INSTALL=true 3 | KIRBY_CACHE=false 4 | -------------------------------------------------------------------------------- /.env.production.example: -------------------------------------------------------------------------------- 1 | KIRBY_DEBUG=false 2 | KIRBY_PANEL_INSTALL=false 3 | KIRBY_CACHE=false 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /vendor 3 | .DS_Store 4 | .env 5 | 6 | # Kirby 7 | .lock 8 | public/media/* 9 | !public/media/index.html 10 | storage/accounts/* 11 | !storage/accounts/.gitkeep 12 | storage/cache/* 13 | !storage/cache/.gitkeep 14 | # storage/content/* 15 | # !storage/content/.gitkeep 16 | storage/logs/* 17 | !storage/logs/.gitkeep 18 | storage/sessions/* 19 | !storage/sessions/.gitkeep 20 | site/config/.license 21 | 22 | # Plugins 23 | site/plugins/* 24 | !site/plugins/.gitkeep 25 | 26 | # Build assets 27 | public/assets/dev 28 | public/dist 29 | src/styles/uno.css 30 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "antfu.unocss", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Enable ESLint flat config support 3 | "eslint.useFlatConfig": true, 4 | 5 | // Use Prettier as the default formatter 6 | "editor.defaultFormatter": "esbenp.prettier-vscode", 7 | "editor.formatOnSave": true, 8 | 9 | // Auto-fix ESLint errors on save 10 | "editor.codeActionsOnSave": { 11 | "source.fixAll.eslint": "explicit", 12 | "source.organizeImports": "never" 13 | }, 14 | 15 | // Enable ESLint for all supported languages 16 | "eslint.validate": [ 17 | "javascript", 18 | "javascriptreact", 19 | "typescript", 20 | "typescriptreact", 21 | "vue", 22 | "html", 23 | "markdown", 24 | "json", 25 | "jsonc", 26 | "yaml" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-PRESENT Johann Schopplich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kirby Vite UnoCSS Kit 2 | 3 | A powerful and performant integration of [Vite](https://vitejs.dev), [UnoCSS](https://github.com/unocss/unocss) and [Kirby](https://getkirby.com). This project seeks to provide a best practice that combines these three solutions while focusing on the developer experience. 4 | 5 | ## Key Features 6 | 7 | - ⚡️ [Vite](https://vitejs.dev) – with all its bells and whistles 8 | - 🎨 [UnoCSS](https://github.com/unocss/unocss) – on-demand atomic CSS engine similar to Tailwind CSS 9 | - 📑 [Module system](./src/modules) inspired by Nuxt 10 | - 💡 [On-demand template loading](./src/templates) based on the current page template 11 | - 🪄 Page reload on PHP snippet/template changes 12 | - 🤹‍♀️ [Pure CSS icons](https://github.com/unocss/unocss/tree/main/packages/preset-icons/) – use any icon as a single class 13 | - 🔍 [SEO-friendly](https://github.com/johannschopplich/kirby-helpers/blob/main/docs/meta.md) defaults 14 | - 🦾 TypeScript, of course 15 | 16 | ## Why UnoCSS 17 | 18 | **tl;dr**: Write CSS like you always did, while getting the benefits of a utility-first framework. 19 | 20 | Let me cite [Anthony Fu](https://antfu.me), the author of UnoCSS: 21 | 22 | > UnoCSS is an atomic-CSS engine instead of a framework. Everything is designed with flexibility and performance in mind. In UnoCSS, there are no core utilities; all functionalities are provided via presets. 23 | > 24 | > By default, UnoCSS applies the default preset. Which provides a common superset of the popular utilities-first framework, including Tailwind CSS, Windi CSS, Bootstrap, Tachyons, etc. 25 | 26 | For example, for this demo website the following CSS is generated: 27 | 28 | ```css 29 | /* layer: preflights */ 30 | *, 31 | ::before, 32 | ::after { 33 | --un-rotate: 0; 34 | --un-rotate-x: 0; 35 | --un-rotate-y: 0; 36 | --un-rotate-z: 0; 37 | --un-scale-x: 1; 38 | --un-scale-y: 1; 39 | --un-scale-z: 1; 40 | --un-skew-x: 0; 41 | --un-skew-y: 0; 42 | --un-translate-x: 0; 43 | --un-translate-y: 0; 44 | --un-translate-z: 0; 45 | --un-pan-x: ; 46 | --un-pan-y: ; 47 | --un-pinch-zoom: ; 48 | --un-scroll-snap-strictness: proximity; 49 | --un-ordinal: ; 50 | --un-slashed-zero: ; 51 | --un-numeric-figure: ; 52 | --un-numeric-spacing: ; 53 | --un-numeric-fraction: ; 54 | --un-border-spacing-x: 0; 55 | --un-border-spacing-y: 0; 56 | --un-ring-offset-shadow: 0 0 rgba(0, 0, 0, 0); 57 | --un-ring-shadow: 0 0 rgba(0, 0, 0, 0); 58 | --un-shadow-inset: ; 59 | --un-shadow: 0 0 rgba(0, 0, 0, 0); 60 | --un-ring-inset: ; 61 | --un-ring-offset-width: 0px; 62 | --un-ring-offset-color: #fff; 63 | --un-ring-width: 0px; 64 | --un-ring-color: rgba(147, 197, 253, 0.5); 65 | --un-blur: ; 66 | --un-brightness: ; 67 | --un-contrast: ; 68 | --un-drop-shadow: ; 69 | --un-grayscale: ; 70 | --un-hue-rotate: ; 71 | --un-invert: ; 72 | --un-saturate: ; 73 | --un-sepia: ; 74 | --un-backdrop-blur: ; 75 | --un-backdrop-brightness: ; 76 | --un-backdrop-contrast: ; 77 | --un-backdrop-grayscale: ; 78 | --un-backdrop-hue-rotate: ; 79 | --un-backdrop-invert: ; 80 | --un-backdrop-opacity: ; 81 | --un-backdrop-saturate: ; 82 | --un-backdrop-sepia: ; 83 | } 84 | ::backdrop { 85 | --un-rotate: 0; 86 | --un-rotate-x: 0; 87 | --un-rotate-y: 0; 88 | --un-rotate-z: 0; 89 | --un-scale-x: 1; 90 | --un-scale-y: 1; 91 | --un-scale-z: 1; 92 | --un-skew-x: 0; 93 | --un-skew-y: 0; 94 | --un-translate-x: 0; 95 | --un-translate-y: 0; 96 | --un-translate-z: 0; 97 | --un-pan-x: ; 98 | --un-pan-y: ; 99 | --un-pinch-zoom: ; 100 | --un-scroll-snap-strictness: proximity; 101 | --un-ordinal: ; 102 | --un-slashed-zero: ; 103 | --un-numeric-figure: ; 104 | --un-numeric-spacing: ; 105 | --un-numeric-fraction: ; 106 | --un-border-spacing-x: 0; 107 | --un-border-spacing-y: 0; 108 | --un-ring-offset-shadow: 0 0 rgba(0, 0, 0, 0); 109 | --un-ring-shadow: 0 0 rgba(0, 0, 0, 0); 110 | --un-shadow-inset: ; 111 | --un-shadow: 0 0 rgba(0, 0, 0, 0); 112 | --un-ring-inset: ; 113 | --un-ring-offset-width: 0px; 114 | --un-ring-offset-color: #fff; 115 | --un-ring-width: 0px; 116 | --un-ring-color: rgba(147, 197, 253, 0.5); 117 | --un-blur: ; 118 | --un-brightness: ; 119 | --un-contrast: ; 120 | --un-drop-shadow: ; 121 | --un-grayscale: ; 122 | --un-hue-rotate: ; 123 | --un-invert: ; 124 | --un-saturate: ; 125 | --un-sepia: ; 126 | --un-backdrop-blur: ; 127 | --un-backdrop-brightness: ; 128 | --un-backdrop-contrast: ; 129 | --un-backdrop-grayscale: ; 130 | --un-backdrop-hue-rotate: ; 131 | --un-backdrop-invert: ; 132 | --un-backdrop-opacity: ; 133 | --un-backdrop-saturate: ; 134 | --un-backdrop-sepia: ; 135 | } 136 | /* layer: icons */ 137 | .hover\:i-carbon-moon:hover { 138 | --un-icon: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3Z'/%3E%3C/svg%3E"); 139 | -webkit-mask: var(--un-icon) no-repeat; 140 | mask: var(--un-icon) no-repeat; 141 | -webkit-mask-size: 100% 100%; 142 | mask-size: 100% 100%; 143 | background-color: currentColor; 144 | color: inherit; 145 | width: 1em; 146 | height: 1em; 147 | } 148 | .i-carbon-sun { 149 | --un-icon: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6ZM5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z'/%3E%3C/svg%3E"); 150 | -webkit-mask: var(--un-icon) no-repeat; 151 | mask: var(--un-icon) no-repeat; 152 | -webkit-mask-size: 100% 100%; 153 | mask-size: 100% 100%; 154 | background-color: currentColor; 155 | color: inherit; 156 | width: 1em; 157 | height: 1em; 158 | } 159 | .i-carbon-warning { 160 | --un-icon: url("data:image/svg+xml;utf8,%3Csvg viewBox='0 0 32 32' width='1em' height='1em' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath fill='currentColor' d='M16 2a14 14 0 1 0 14 14A14 14 0 0 0 16 2Zm0 26a12 12 0 1 1 12-12a12 12 0 0 1-12 12Z'/%3E%3Cpath fill='currentColor' d='M15 8h2v11h-2zm1 14a1.5 1.5 0 1 0 1.5 1.5A1.5 1.5 0 0 0 16 22z'/%3E%3C/svg%3E"); 161 | -webkit-mask: var(--un-icon) no-repeat; 162 | mask: var(--un-icon) no-repeat; 163 | -webkit-mask-size: 100% 100%; 164 | mask-size: 100% 100%; 165 | background-color: currentColor; 166 | color: inherit; 167 | width: 1em; 168 | height: 1em; 169 | } 170 | /* layer: typography */ 171 | .prose :where(h1, h2, h3, h4, h5, h6):not(:where(.not-prose, .not-prose *)) { 172 | color: var(--un-prose-headings); 173 | font-weight: 600; 174 | line-height: 1.25; 175 | } 176 | .prose :where(a):not(:where(.not-prose, .not-prose *)) { 177 | color: var(--un-prose-links); 178 | text-decoration: underline; 179 | font-weight: 500; 180 | } 181 | .prose :where(a code):not(:where(.not-prose, .not-prose *)) { 182 | color: var(--un-prose-links); 183 | } 184 | .prose :where(p, ul, ol, pre):not(:where(.not-prose, .not-prose *)) { 185 | margin: 1em 0; 186 | line-height: 1.75; 187 | } 188 | .prose :where(blockquote):not(:where(.not-prose, .not-prose *)) { 189 | margin: 1em 0; 190 | padding-left: 1em; 191 | font-style: italic; 192 | border-left: 0.25em solid var(--un-prose-borders); 193 | } 194 | .prose :where(h1):not(:where(.not-prose, .not-prose *)) { 195 | margin: 1rem 0; 196 | font-size: 2.25em; 197 | } 198 | .prose :where(h2):not(:where(.not-prose, .not-prose *)) { 199 | margin: 1.75em 0 0.5em; 200 | font-size: 1.75em; 201 | } 202 | .prose :where(h3):not(:where(.not-prose, .not-prose *)) { 203 | margin: 1.5em 0 0.5em; 204 | font-size: 1.375em; 205 | } 206 | .prose :where(h4):not(:where(.not-prose, .not-prose *)) { 207 | margin: 1em 0; 208 | font-size: 1.125em; 209 | } 210 | .prose :where(img, video):not(:where(.not-prose, .not-prose *)) { 211 | max-width: 100%; 212 | } 213 | .prose :where(figure, picture):not(:where(.not-prose, .not-prose *)) { 214 | margin: 1em 0; 215 | } 216 | .prose :where(figcaption):not(:where(.not-prose, .not-prose *)) { 217 | color: var(--un-prose-captions); 218 | font-size: 0.875em; 219 | } 220 | .prose :where(code):not(:where(.not-prose, .not-prose *)) { 221 | color: var(--un-prose-code); 222 | font-size: 0.875em; 223 | font-weight: 600; 224 | font-family: var(--un-prose-font-mono); 225 | } 226 | .prose :where(:not(pre) > code):not(:where(.not-prose, .not-prose *))::before, 227 | .prose :where(:not(pre) > code):not(:where(.not-prose, .not-prose *))::after { 228 | content: "`"; 229 | } 230 | .prose :where(pre):not(:where(.not-prose, .not-prose *)) { 231 | padding: 1.25rem 1.5rem; 232 | overflow-x: auto; 233 | border-radius: 0.375rem; 234 | } 235 | .prose :where(pre, code):not(:where(.not-prose, .not-prose *)) { 236 | white-space: pre; 237 | word-spacing: normal; 238 | word-break: normal; 239 | word-wrap: normal; 240 | -moz-tab-size: 4; 241 | -o-tab-size: 4; 242 | tab-size: 4; 243 | -webkit-hyphens: none; 244 | -moz-hyphens: none; 245 | hyphens: none; 246 | background: transparent; 247 | } 248 | .prose :where(pre code):not(:where(.not-prose, .not-prose *)) { 249 | font-weight: inherit; 250 | } 251 | .prose :where(ol, ul):not(:where(.not-prose, .not-prose *)) { 252 | padding-left: 1.25em; 253 | } 254 | .prose :where(ol):not(:where(.not-prose, .not-prose *)) { 255 | list-style-type: decimal; 256 | } 257 | .prose :where(ol[type="A"]):not(:where(.not-prose, .not-prose *)) { 258 | list-style-type: upper-alpha; 259 | } 260 | .prose :where(ol[type="a"]):not(:where(.not-prose, .not-prose *)) { 261 | list-style-type: lower-alpha; 262 | } 263 | .prose :where(ol[type="A" s]):not(:where(.not-prose, .not-prose *)) { 264 | list-style-type: upper-alpha; 265 | } 266 | .prose :where(ol[type="a" s]):not(:where(.not-prose, .not-prose *)) { 267 | list-style-type: lower-alpha; 268 | } 269 | .prose :where(ol[type="I"]):not(:where(.not-prose, .not-prose *)) { 270 | list-style-type: upper-roman; 271 | } 272 | .prose :where(ol[type="i"]):not(:where(.not-prose, .not-prose *)) { 273 | list-style-type: lower-roman; 274 | } 275 | .prose :where(ol[type="I" s]):not(:where(.not-prose, .not-prose *)) { 276 | list-style-type: upper-roman; 277 | } 278 | .prose :where(ol[type="i" s]):not(:where(.not-prose, .not-prose *)) { 279 | list-style-type: lower-roman; 280 | } 281 | .prose :where(ol[type="1"]):not(:where(.not-prose, .not-prose *)) { 282 | list-style-type: decimal; 283 | } 284 | .prose :where(ul):not(:where(.not-prose, .not-prose *)) { 285 | list-style-type: disc; 286 | } 287 | .prose :where(ol > li):not(:where(.not-prose, .not-prose *))::marker, 288 | .prose :where(ul > li):not(:where(.not-prose, .not-prose *))::marker, 289 | .prose :where(summary):not(:where(.not-prose, .not-prose *))::marker { 290 | color: var(--un-prose-lists); 291 | } 292 | .prose :where(hr):not(:where(.not-prose, .not-prose *)) { 293 | margin: 2em 0; 294 | border: 1px solid var(--un-prose-hr); 295 | } 296 | .prose :where(table):not(:where(.not-prose, .not-prose *)) { 297 | display: block; 298 | margin: 1em 0; 299 | border-collapse: collapse; 300 | overflow-x: auto; 301 | } 302 | .prose :where(tr):not(:where(.not-prose, .not-prose *)):nth-child(2n) { 303 | background: var(--un-prose-bg-soft); 304 | } 305 | .prose :where(td, th):not(:where(.not-prose, .not-prose *)) { 306 | border: 1px solid var(--un-prose-borders); 307 | padding: 0.625em 1em; 308 | } 309 | .prose :where(abbr):not(:where(.not-prose, .not-prose *)) { 310 | cursor: help; 311 | } 312 | .prose :where(kbd):not(:where(.not-prose, .not-prose *)) { 313 | color: var(--un-prose-code); 314 | border: 1px solid; 315 | padding: 0.25rem 0.5rem; 316 | font-size: 0.875em; 317 | border-radius: 0.25rem; 318 | } 319 | .prose :where(details):not(:where(.not-prose, .not-prose *)) { 320 | margin: 1em 0; 321 | padding: 1.25rem 1.5rem; 322 | background: var(--un-prose-bg-soft); 323 | } 324 | .prose :where(summary):not(:where(.not-prose, .not-prose *)) { 325 | cursor: pointer; 326 | font-weight: 600; 327 | } 328 | .prose { 329 | color: var(--un-prose-body); 330 | max-width: 65ch; 331 | } 332 | /* layer: shortcuts */ 333 | .box { 334 | margin-left: auto; 335 | margin-right: auto; 336 | max-width: 80rem; 337 | border-radius: 0.375rem; 338 | --un-bg-opacity: 1; 339 | background-color: rgba(243, 244, 246, var(--un-bg-opacity)); 340 | padding: 1rem; 341 | --un-shadow: var(--un-shadow-inset) 0 1px 2px 0 342 | var(--un-shadow-color, rgba(0, 0, 0, 0.05)); 343 | box-shadow: var(--un-ring-offset-shadow), var(--un-ring-shadow), 344 | var(--un-shadow); 345 | } 346 | /* layer: default */ 347 | .mx-auto { 348 | margin-left: auto; 349 | margin-right: auto; 350 | } 351 | .mb-4 { 352 | margin-bottom: 1rem; 353 | } 354 | .h2 { 355 | height: 0.5rem; 356 | } 357 | .max-w-screen-md { 358 | max-width: 768px; 359 | } 360 | .flex { 361 | display: flex; 362 | } 363 | .space-x-3 > :not([hidden]) ~ :not([hidden]) { 364 | --un-space-x-reverse: 0; 365 | margin-left: calc(0.75rem * calc(1 - var(--un-space-x-reverse))); 366 | margin-right: calc(0.75rem * var(--un-space-x-reverse)); 367 | } 368 | .px-4 { 369 | padding-left: 1rem; 370 | padding-right: 1rem; 371 | } 372 | .py-4 { 373 | padding-top: 1rem; 374 | padding-bottom: 1rem; 375 | } 376 | .text-4xl { 377 | font-size: 2.25rem; 378 | line-height: 2.5rem; 379 | } 380 | .text-base { 381 | font-size: 1rem; 382 | line-height: 1.5rem; 383 | } 384 | ``` 385 | 386 | For the story behind this project, see the blog post [Reimagine Atomic CSS](https://antfu.me/posts/reimagine-atomic-css). 387 | 388 | ## How It Works 389 | 390 | When running `npm run dev`: 391 | 392 | 1. The [@unocss/cli](https://github.com/unocss/unocss/tree/main/packages/cli) is given the glob pattern `site/{snippets,templates}/**/*` via the `uno` npm script. Once started, it scans each directory and collects all occurring utility class names. 393 | The CSS for these classes will be generated by UnoCSS and the output saved to `src/styles/uno.css`. 394 | Each time one of the PHP files changes, the UnoCSS will generate the latest `uno.css` accordingly. 395 | 396 | 2. At the same time, the Vite development server will be started. It builds the JavaScript assets, but also the [`main.scss`](./src/styles/main.scss), which itself uses the `uno.css` in the same directory. 397 | Vite will watch for JavaScript asset changes or changes in the `main.scss` (or consequently `uno.css`) and manages those with its HMR capabilities. 398 | 399 | 3. Vite will reload the page, if any of the PHP files get changed, so you won't have to reload the tab yourself when updating your layout. 400 | 401 | ## Installation 402 | 403 | 1. Duplicate the [`.env.development.example`](./.env.development.example) as `.env`: 404 | 405 | ```bash 406 | cp .env.development.example .env 407 | ``` 408 | 409 | 2. Install the required Node dependencies: 410 | 411 | ```bash 412 | npm install 413 | ``` 414 | 415 | 3. Install the required Composer dependencies: 416 | 417 | ```bash 418 | composer install 419 | ``` 420 | 421 | ## Configuration 422 | 423 | To add custom rules or shortcuts, edit the [`uno.config.ts`](./uno.config.ts). 424 | 425 | For example, this project already defines a `box` class via a shortcut: 426 | 427 | ``` 428 | shortcuts: { 429 | 'box': 'max-w-7xl mx-auto bg-gray-100 rounded-md shadow-sm p-4' 430 | } 431 | ``` 432 | 433 | ## Usage 434 | 435 | ### Development 436 | 437 | 1. Start the Vite development server and watch for file changes accordingly: 438 | 439 | ```bash 440 | npm run dev 441 | ``` 442 | 443 | 2. Run the built-in PHP web server or use a development web server of your choice (like [Laravel Valet](https://laravel.com/docs/10.x/valet)). 444 | 445 | ```bash 446 | composer start 447 | ``` 448 | 449 | ### Production 450 | 451 | Build the frontend assets for production: 452 | 453 | ```bash 454 | npm run build 455 | ``` 456 | 457 | If you have caching enabled, make sure to wipe the cache after each build: 458 | 459 | ```bash 460 | rm -rf storage/cache/ 461 | ``` 462 | 463 | ### Deployment 464 | 465 | > [!NOTE] 466 | > See [ploi-deploy.sh](./scripts/ploi-deploy.sh) for deployment instructions. 467 | 468 | > [!TIP] 469 | > For Apache web servers: Some hosting environments require uncommenting `RewriteBase /` in [`.htaccess`](./public/.htaccess) to make site links work. 470 | 471 | ## License 472 | 473 | [MIT](./LICENSE) License © 2021-PRESENT [Johann Schopplich](https://github.com/johannschopplich) 474 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "getkirby/cms": "^4", 4 | "johannschopplich/kirby-helpers": "^6" 5 | }, 6 | "scripts": { 7 | "start": [ 8 | "Composer\\Config::disableProcessTimeout", 9 | "@php -S localhost:8000 -t public vendor/getkirby/cms/router.php" 10 | ] 11 | }, 12 | "config": { 13 | "optimize-autoloader": true, 14 | "allow-plugins": { 15 | "getkirby/composer-installer": true 16 | } 17 | }, 18 | "extra": { 19 | "kirby-cms-path": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "e3edbaf4d77638551df8b51a3ec40621", 8 | "packages": [ 9 | { 10 | "name": "christian-riesen/base32", 11 | "version": "1.6.0", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/ChristianRiesen/base32.git", 15 | "reference": "2e82dab3baa008e24a505649b0d583c31d31e894" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/ChristianRiesen/base32/zipball/2e82dab3baa008e24a505649b0d583c31d31e894", 20 | "reference": "2e82dab3baa008e24a505649b0d583c31d31e894", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^7.2 || ^8.0" 25 | }, 26 | "require-dev": { 27 | "friendsofphp/php-cs-fixer": "^2.17", 28 | "phpstan/phpstan": "^0.12", 29 | "phpunit/phpunit": "^8.5.13 || ^9.5" 30 | }, 31 | "type": "library", 32 | "extra": { 33 | "branch-alias": { 34 | "dev-master": "1.x-dev" 35 | } 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Base32\\": "src/" 40 | } 41 | }, 42 | "notification-url": "https://packagist.org/downloads/", 43 | "license": [ 44 | "MIT" 45 | ], 46 | "authors": [ 47 | { 48 | "name": "Christian Riesen", 49 | "email": "chris.riesen@gmail.com", 50 | "homepage": "http://christianriesen.com", 51 | "role": "Developer" 52 | } 53 | ], 54 | "description": "Base32 encoder/decoder according to RFC 4648", 55 | "homepage": "https://github.com/ChristianRiesen/base32", 56 | "keywords": [ 57 | "base32", 58 | "decode", 59 | "encode", 60 | "rfc4648" 61 | ], 62 | "support": { 63 | "issues": "https://github.com/ChristianRiesen/base32/issues", 64 | "source": "https://github.com/ChristianRiesen/base32/tree/1.6.0" 65 | }, 66 | "time": "2021-02-26T10:19:33+00:00" 67 | }, 68 | { 69 | "name": "claviska/simpleimage", 70 | "version": "4.2.0", 71 | "source": { 72 | "type": "git", 73 | "url": "https://github.com/claviska/SimpleImage.git", 74 | "reference": "dfbe53c01dae8467468ef2b817c09b786a7839d2" 75 | }, 76 | "dist": { 77 | "type": "zip", 78 | "url": "https://api.github.com/repos/claviska/SimpleImage/zipball/dfbe53c01dae8467468ef2b817c09b786a7839d2", 79 | "reference": "dfbe53c01dae8467468ef2b817c09b786a7839d2", 80 | "shasum": "" 81 | }, 82 | "require": { 83 | "ext-gd": "*", 84 | "league/color-extractor": "0.4.*", 85 | "php": ">=8.0" 86 | }, 87 | "require-dev": { 88 | "laravel/pint": "^1.5", 89 | "phpstan/phpstan": "^1.10" 90 | }, 91 | "type": "library", 92 | "autoload": { 93 | "psr-0": { 94 | "claviska": "src/" 95 | } 96 | }, 97 | "notification-url": "https://packagist.org/downloads/", 98 | "license": [ 99 | "MIT" 100 | ], 101 | "authors": [ 102 | { 103 | "name": "Cory LaViska", 104 | "homepage": "http://www.abeautifulsite.net/", 105 | "role": "Developer" 106 | } 107 | ], 108 | "description": "A PHP class that makes working with images as simple as possible.", 109 | "support": { 110 | "issues": "https://github.com/claviska/SimpleImage/issues", 111 | "source": "https://github.com/claviska/SimpleImage/tree/4.2.0" 112 | }, 113 | "funding": [ 114 | { 115 | "url": "https://github.com/claviska", 116 | "type": "github" 117 | } 118 | ], 119 | "time": "2024-04-15T16:07:16+00:00" 120 | }, 121 | { 122 | "name": "composer/semver", 123 | "version": "3.4.3", 124 | "source": { 125 | "type": "git", 126 | "url": "https://github.com/composer/semver.git", 127 | "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" 128 | }, 129 | "dist": { 130 | "type": "zip", 131 | "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", 132 | "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", 133 | "shasum": "" 134 | }, 135 | "require": { 136 | "php": "^5.3.2 || ^7.0 || ^8.0" 137 | }, 138 | "require-dev": { 139 | "phpstan/phpstan": "^1.11", 140 | "symfony/phpunit-bridge": "^3 || ^7" 141 | }, 142 | "type": "library", 143 | "extra": { 144 | "branch-alias": { 145 | "dev-main": "3.x-dev" 146 | } 147 | }, 148 | "autoload": { 149 | "psr-4": { 150 | "Composer\\Semver\\": "src" 151 | } 152 | }, 153 | "notification-url": "https://packagist.org/downloads/", 154 | "license": [ 155 | "MIT" 156 | ], 157 | "authors": [ 158 | { 159 | "name": "Nils Adermann", 160 | "email": "naderman@naderman.de", 161 | "homepage": "http://www.naderman.de" 162 | }, 163 | { 164 | "name": "Jordi Boggiano", 165 | "email": "j.boggiano@seld.be", 166 | "homepage": "http://seld.be" 167 | }, 168 | { 169 | "name": "Rob Bast", 170 | "email": "rob.bast@gmail.com", 171 | "homepage": "http://robbast.nl" 172 | } 173 | ], 174 | "description": "Semver library that offers utilities, version constraint parsing and validation.", 175 | "keywords": [ 176 | "semantic", 177 | "semver", 178 | "validation", 179 | "versioning" 180 | ], 181 | "support": { 182 | "irc": "ircs://irc.libera.chat:6697/composer", 183 | "issues": "https://github.com/composer/semver/issues", 184 | "source": "https://github.com/composer/semver/tree/3.4.3" 185 | }, 186 | "funding": [ 187 | { 188 | "url": "https://packagist.com", 189 | "type": "custom" 190 | }, 191 | { 192 | "url": "https://github.com/composer", 193 | "type": "github" 194 | }, 195 | { 196 | "url": "https://tidelift.com/funding/github/packagist/composer/composer", 197 | "type": "tidelift" 198 | } 199 | ], 200 | "time": "2024-09-19T14:15:21+00:00" 201 | }, 202 | { 203 | "name": "filp/whoops", 204 | "version": "2.16.0", 205 | "source": { 206 | "type": "git", 207 | "url": "https://github.com/filp/whoops.git", 208 | "reference": "befcdc0e5dce67252aa6322d82424be928214fa2" 209 | }, 210 | "dist": { 211 | "type": "zip", 212 | "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2", 213 | "reference": "befcdc0e5dce67252aa6322d82424be928214fa2", 214 | "shasum": "" 215 | }, 216 | "require": { 217 | "php": "^7.1 || ^8.0", 218 | "psr/log": "^1.0.1 || ^2.0 || ^3.0" 219 | }, 220 | "require-dev": { 221 | "mockery/mockery": "^1.0", 222 | "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", 223 | "symfony/var-dumper": "^4.0 || ^5.0" 224 | }, 225 | "suggest": { 226 | "symfony/var-dumper": "Pretty print complex values better with var-dumper available", 227 | "whoops/soap": "Formats errors as SOAP responses" 228 | }, 229 | "type": "library", 230 | "extra": { 231 | "branch-alias": { 232 | "dev-master": "2.7-dev" 233 | } 234 | }, 235 | "autoload": { 236 | "psr-4": { 237 | "Whoops\\": "src/Whoops/" 238 | } 239 | }, 240 | "notification-url": "https://packagist.org/downloads/", 241 | "license": [ 242 | "MIT" 243 | ], 244 | "authors": [ 245 | { 246 | "name": "Filipe Dobreira", 247 | "homepage": "https://github.com/filp", 248 | "role": "Developer" 249 | } 250 | ], 251 | "description": "php error handling for cool kids", 252 | "homepage": "https://filp.github.io/whoops/", 253 | "keywords": [ 254 | "error", 255 | "exception", 256 | "handling", 257 | "library", 258 | "throwable", 259 | "whoops" 260 | ], 261 | "support": { 262 | "issues": "https://github.com/filp/whoops/issues", 263 | "source": "https://github.com/filp/whoops/tree/2.16.0" 264 | }, 265 | "funding": [ 266 | { 267 | "url": "https://github.com/denis-sokolov", 268 | "type": "github" 269 | } 270 | ], 271 | "time": "2024-09-25T12:00:00+00:00" 272 | }, 273 | { 274 | "name": "getkirby/cms", 275 | "version": "4.5.0", 276 | "source": { 277 | "type": "git", 278 | "url": "https://github.com/getkirby/kirby.git", 279 | "reference": "94cc37ee7c3004ebb4950a53f14e1329ed4d28d3" 280 | }, 281 | "dist": { 282 | "type": "zip", 283 | "url": "https://api.github.com/repos/getkirby/kirby/zipball/94cc37ee7c3004ebb4950a53f14e1329ed4d28d3", 284 | "reference": "94cc37ee7c3004ebb4950a53f14e1329ed4d28d3", 285 | "shasum": "" 286 | }, 287 | "require": { 288 | "christian-riesen/base32": "1.6.0", 289 | "claviska/simpleimage": "4.2.0", 290 | "composer/semver": "3.4.3", 291 | "ext-ctype": "*", 292 | "ext-curl": "*", 293 | "ext-dom": "*", 294 | "ext-filter": "*", 295 | "ext-hash": "*", 296 | "ext-iconv": "*", 297 | "ext-json": "*", 298 | "ext-libxml": "*", 299 | "ext-mbstring": "*", 300 | "ext-openssl": "*", 301 | "ext-simplexml": "*", 302 | "filp/whoops": "2.16.0", 303 | "getkirby/composer-installer": "^1.2.1", 304 | "laminas/laminas-escaper": "2.14.0", 305 | "michelf/php-smartypants": "1.8.1", 306 | "php": "~8.1.0 || ~8.2.0 || ~8.3.0", 307 | "phpmailer/phpmailer": "6.9.2", 308 | "symfony/polyfill-intl-idn": "1.31.0", 309 | "symfony/polyfill-mbstring": "1.31.0", 310 | "symfony/yaml": "6.4.13" 311 | }, 312 | "replace": { 313 | "symfony/polyfill-php72": "*" 314 | }, 315 | "suggest": { 316 | "ext-PDO": "Support for using databases", 317 | "ext-apcu": "Support for the Apcu cache driver", 318 | "ext-exif": "Support for exif information from images", 319 | "ext-fileinfo": "Improved mime type detection for files", 320 | "ext-intl": "Improved i18n number formatting", 321 | "ext-memcached": "Support for the Memcached cache driver", 322 | "ext-sodium": "Support for the crypto class and more robust session handling", 323 | "ext-zip": "Support for ZIP archive file functions", 324 | "ext-zlib": "Sanitization and validation for svgz files" 325 | }, 326 | "type": "kirby-cms", 327 | "extra": { 328 | "unused": [ 329 | "symfony/polyfill-intl-idn" 330 | ] 331 | }, 332 | "autoload": { 333 | "files": [ 334 | "config/setup.php", 335 | "config/helpers.php" 336 | ], 337 | "psr-4": { 338 | "Kirby\\": "src/" 339 | }, 340 | "classmap": [ 341 | "dependencies/" 342 | ] 343 | }, 344 | "notification-url": "https://packagist.org/downloads/", 345 | "license": [ 346 | "proprietary" 347 | ], 348 | "authors": [ 349 | { 350 | "name": "Kirby Team", 351 | "email": "support@getkirby.com", 352 | "homepage": "https://getkirby.com" 353 | } 354 | ], 355 | "description": "The Kirby core", 356 | "homepage": "https://getkirby.com", 357 | "keywords": [ 358 | "cms", 359 | "core", 360 | "kirby" 361 | ], 362 | "support": { 363 | "email": "support@getkirby.com", 364 | "forum": "https://forum.getkirby.com", 365 | "issues": "https://github.com/getkirby/kirby/issues", 366 | "source": "https://github.com/getkirby/kirby" 367 | }, 368 | "funding": [ 369 | { 370 | "url": "https://getkirby.com/buy", 371 | "type": "custom" 372 | } 373 | ], 374 | "time": "2024-11-28T10:10:23+00:00" 375 | }, 376 | { 377 | "name": "getkirby/composer-installer", 378 | "version": "1.2.1", 379 | "source": { 380 | "type": "git", 381 | "url": "https://github.com/getkirby/composer-installer.git", 382 | "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d" 383 | }, 384 | "dist": { 385 | "type": "zip", 386 | "url": "https://api.github.com/repos/getkirby/composer-installer/zipball/c98ece30bfba45be7ce457e1102d1b169d922f3d", 387 | "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d", 388 | "shasum": "" 389 | }, 390 | "require": { 391 | "composer-plugin-api": "^1.0 || ^2.0" 392 | }, 393 | "require-dev": { 394 | "composer/composer": "^1.8 || ^2.0" 395 | }, 396 | "type": "composer-plugin", 397 | "extra": { 398 | "class": "Kirby\\ComposerInstaller\\Plugin" 399 | }, 400 | "autoload": { 401 | "psr-4": { 402 | "Kirby\\": "src/" 403 | } 404 | }, 405 | "notification-url": "https://packagist.org/downloads/", 406 | "license": [ 407 | "MIT" 408 | ], 409 | "description": "Kirby's custom Composer installer for the Kirby CMS and for Kirby plugins", 410 | "homepage": "https://getkirby.com", 411 | "support": { 412 | "issues": "https://github.com/getkirby/composer-installer/issues", 413 | "source": "https://github.com/getkirby/composer-installer/tree/1.2.1" 414 | }, 415 | "funding": [ 416 | { 417 | "url": "https://getkirby.com/buy", 418 | "type": "custom" 419 | } 420 | ], 421 | "time": "2020-12-28T12:54:39+00:00" 422 | }, 423 | { 424 | "name": "graham-campbell/result-type", 425 | "version": "v1.1.3", 426 | "source": { 427 | "type": "git", 428 | "url": "https://github.com/GrahamCampbell/Result-Type.git", 429 | "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" 430 | }, 431 | "dist": { 432 | "type": "zip", 433 | "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", 434 | "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", 435 | "shasum": "" 436 | }, 437 | "require": { 438 | "php": "^7.2.5 || ^8.0", 439 | "phpoption/phpoption": "^1.9.3" 440 | }, 441 | "require-dev": { 442 | "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" 443 | }, 444 | "type": "library", 445 | "autoload": { 446 | "psr-4": { 447 | "GrahamCampbell\\ResultType\\": "src/" 448 | } 449 | }, 450 | "notification-url": "https://packagist.org/downloads/", 451 | "license": [ 452 | "MIT" 453 | ], 454 | "authors": [ 455 | { 456 | "name": "Graham Campbell", 457 | "email": "hello@gjcampbell.co.uk", 458 | "homepage": "https://github.com/GrahamCampbell" 459 | } 460 | ], 461 | "description": "An Implementation Of The Result Type", 462 | "keywords": [ 463 | "Graham Campbell", 464 | "GrahamCampbell", 465 | "Result Type", 466 | "Result-Type", 467 | "result" 468 | ], 469 | "support": { 470 | "issues": "https://github.com/GrahamCampbell/Result-Type/issues", 471 | "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" 472 | }, 473 | "funding": [ 474 | { 475 | "url": "https://github.com/GrahamCampbell", 476 | "type": "github" 477 | }, 478 | { 479 | "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", 480 | "type": "tidelift" 481 | } 482 | ], 483 | "time": "2024-07-20T21:45:45+00:00" 484 | }, 485 | { 486 | "name": "johannschopplich/kirby-helpers", 487 | "version": "6.2.0", 488 | "source": { 489 | "type": "git", 490 | "url": "https://github.com/johannschopplich/kirby-helpers.git", 491 | "reference": "46a8b6d60d05c55a3e3e01b1e92c176e5a34dc31" 492 | }, 493 | "dist": { 494 | "type": "zip", 495 | "url": "https://api.github.com/repos/johannschopplich/kirby-helpers/zipball/46a8b6d60d05c55a3e3e01b1e92c176e5a34dc31", 496 | "reference": "46a8b6d60d05c55a3e3e01b1e92c176e5a34dc31", 497 | "shasum": "" 498 | }, 499 | "require": { 500 | "getkirby/composer-installer": "^1.2", 501 | "vlucas/phpdotenv": "^5.6" 502 | }, 503 | "require-dev": { 504 | "getkirby/cms": "^4" 505 | }, 506 | "type": "kirby-plugin", 507 | "extra": { 508 | "kirby-cms-path": false 509 | }, 510 | "autoload": { 511 | "files": [ 512 | "helpers.php" 513 | ], 514 | "psr-4": { 515 | "JohannSchopplich\\": "classes/JohannSchopplich/" 516 | } 517 | }, 518 | "notification-url": "https://packagist.org/downloads/", 519 | "license": [ 520 | "MIT" 521 | ], 522 | "authors": [ 523 | { 524 | "name": "Johann Schopplich", 525 | "email": "pkg@johannschopplich.com", 526 | "homepage": "https://johannschopplich.com" 527 | } 528 | ], 529 | "description": "Dotenv support, meta tag generation and more for Kirby CMS", 530 | "homepage": "https://github.com/johannschopplich/kirby-helpers#readme", 531 | "keywords": [ 532 | "dotenv", 533 | "env", 534 | "getkirby", 535 | "kirby", 536 | "kirby-cms", 537 | "meta", 538 | "meta-tags", 539 | "seo", 540 | "vite" 541 | ], 542 | "support": { 543 | "issues": "https://github.com/johannschopplich/kirby-helpers/issues", 544 | "source": "https://github.com/johannschopplich/kirby-helpers/tree/v6.2.0" 545 | }, 546 | "time": "2024-12-30T09:33:49+00:00" 547 | }, 548 | { 549 | "name": "laminas/laminas-escaper", 550 | "version": "2.14.0", 551 | "source": { 552 | "type": "git", 553 | "url": "https://github.com/laminas/laminas-escaper.git", 554 | "reference": "0f7cb975f4443cf22f33408925c231225cfba8cb" 555 | }, 556 | "dist": { 557 | "type": "zip", 558 | "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/0f7cb975f4443cf22f33408925c231225cfba8cb", 559 | "reference": "0f7cb975f4443cf22f33408925c231225cfba8cb", 560 | "shasum": "" 561 | }, 562 | "require": { 563 | "ext-ctype": "*", 564 | "ext-mbstring": "*", 565 | "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" 566 | }, 567 | "conflict": { 568 | "zendframework/zend-escaper": "*" 569 | }, 570 | "require-dev": { 571 | "infection/infection": "^0.27.9", 572 | "laminas/laminas-coding-standard": "~3.0.0", 573 | "maglnet/composer-require-checker": "^3.8.0", 574 | "phpunit/phpunit": "^9.6.16", 575 | "psalm/plugin-phpunit": "^0.19.0", 576 | "vimeo/psalm": "^5.21.1" 577 | }, 578 | "type": "library", 579 | "autoload": { 580 | "psr-4": { 581 | "Laminas\\Escaper\\": "src/" 582 | } 583 | }, 584 | "notification-url": "https://packagist.org/downloads/", 585 | "license": [ 586 | "BSD-3-Clause" 587 | ], 588 | "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", 589 | "homepage": "https://laminas.dev", 590 | "keywords": [ 591 | "escaper", 592 | "laminas" 593 | ], 594 | "support": { 595 | "chat": "https://laminas.dev/chat", 596 | "docs": "https://docs.laminas.dev/laminas-escaper/", 597 | "forum": "https://discourse.laminas.dev", 598 | "issues": "https://github.com/laminas/laminas-escaper/issues", 599 | "rss": "https://github.com/laminas/laminas-escaper/releases.atom", 600 | "source": "https://github.com/laminas/laminas-escaper" 601 | }, 602 | "funding": [ 603 | { 604 | "url": "https://funding.communitybridge.org/projects/laminas-project", 605 | "type": "community_bridge" 606 | } 607 | ], 608 | "time": "2024-10-24T10:12:53+00:00" 609 | }, 610 | { 611 | "name": "league/color-extractor", 612 | "version": "0.4.0", 613 | "source": { 614 | "type": "git", 615 | "url": "https://github.com/thephpleague/color-extractor.git", 616 | "reference": "21fcac6249c5ef7d00eb83e128743ee6678fe505" 617 | }, 618 | "dist": { 619 | "type": "zip", 620 | "url": "https://api.github.com/repos/thephpleague/color-extractor/zipball/21fcac6249c5ef7d00eb83e128743ee6678fe505", 621 | "reference": "21fcac6249c5ef7d00eb83e128743ee6678fe505", 622 | "shasum": "" 623 | }, 624 | "require": { 625 | "ext-gd": "*", 626 | "php": "^7.3 || ^8.0" 627 | }, 628 | "replace": { 629 | "matthecat/colorextractor": "*" 630 | }, 631 | "require-dev": { 632 | "friendsofphp/php-cs-fixer": "~2", 633 | "phpunit/phpunit": "^9.5" 634 | }, 635 | "suggest": { 636 | "ext-curl": "To download images from remote URLs if allow_url_fopen is disabled for security reasons" 637 | }, 638 | "type": "library", 639 | "autoload": { 640 | "psr-4": { 641 | "League\\ColorExtractor\\": "src" 642 | } 643 | }, 644 | "notification-url": "https://packagist.org/downloads/", 645 | "license": [ 646 | "MIT" 647 | ], 648 | "authors": [ 649 | { 650 | "name": "Mathieu Lechat", 651 | "email": "math.lechat@gmail.com", 652 | "homepage": "http://matthecat.com", 653 | "role": "Developer" 654 | } 655 | ], 656 | "description": "Extract colors from an image as a human would do.", 657 | "homepage": "https://github.com/thephpleague/color-extractor", 658 | "keywords": [ 659 | "color", 660 | "extract", 661 | "human", 662 | "image", 663 | "palette" 664 | ], 665 | "support": { 666 | "issues": "https://github.com/thephpleague/color-extractor/issues", 667 | "source": "https://github.com/thephpleague/color-extractor/tree/0.4.0" 668 | }, 669 | "time": "2022-09-24T15:57:16+00:00" 670 | }, 671 | { 672 | "name": "michelf/php-smartypants", 673 | "version": "1.8.1", 674 | "source": { 675 | "type": "git", 676 | "url": "https://github.com/michelf/php-smartypants.git", 677 | "reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad" 678 | }, 679 | "dist": { 680 | "type": "zip", 681 | "url": "https://api.github.com/repos/michelf/php-smartypants/zipball/47d17c90a4dfd0ccf1f87e25c65e6c8012415aad", 682 | "reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad", 683 | "shasum": "" 684 | }, 685 | "require": { 686 | "php": ">=5.3.0" 687 | }, 688 | "type": "library", 689 | "autoload": { 690 | "psr-0": { 691 | "Michelf": "" 692 | } 693 | }, 694 | "notification-url": "https://packagist.org/downloads/", 695 | "license": [ 696 | "BSD-3-Clause" 697 | ], 698 | "authors": [ 699 | { 700 | "name": "Michel Fortin", 701 | "email": "michel.fortin@michelf.ca", 702 | "homepage": "https://michelf.ca/", 703 | "role": "Developer" 704 | }, 705 | { 706 | "name": "John Gruber", 707 | "homepage": "https://daringfireball.net/" 708 | } 709 | ], 710 | "description": "PHP SmartyPants", 711 | "homepage": "https://michelf.ca/projects/php-smartypants/", 712 | "keywords": [ 713 | "dashes", 714 | "quotes", 715 | "spaces", 716 | "typographer", 717 | "typography" 718 | ], 719 | "support": { 720 | "issues": "https://github.com/michelf/php-smartypants/issues", 721 | "source": "https://github.com/michelf/php-smartypants/tree/1.8.1" 722 | }, 723 | "time": "2016-12-13T01:01:17+00:00" 724 | }, 725 | { 726 | "name": "phpmailer/phpmailer", 727 | "version": "v6.9.2", 728 | "source": { 729 | "type": "git", 730 | "url": "https://github.com/PHPMailer/PHPMailer.git", 731 | "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c" 732 | }, 733 | "dist": { 734 | "type": "zip", 735 | "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a7b17b42fa4887c92146243f3d2f4ccb962af17c", 736 | "reference": "a7b17b42fa4887c92146243f3d2f4ccb962af17c", 737 | "shasum": "" 738 | }, 739 | "require": { 740 | "ext-ctype": "*", 741 | "ext-filter": "*", 742 | "ext-hash": "*", 743 | "php": ">=5.5.0" 744 | }, 745 | "require-dev": { 746 | "dealerdirect/phpcodesniffer-composer-installer": "^1.0", 747 | "doctrine/annotations": "^1.2.6 || ^1.13.3", 748 | "php-parallel-lint/php-console-highlighter": "^1.0.0", 749 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 750 | "phpcompatibility/php-compatibility": "^9.3.5", 751 | "roave/security-advisories": "dev-latest", 752 | "squizlabs/php_codesniffer": "^3.7.2", 753 | "yoast/phpunit-polyfills": "^1.0.4" 754 | }, 755 | "suggest": { 756 | "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", 757 | "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", 758 | "ext-openssl": "Needed for secure SMTP sending and DKIM signing", 759 | "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", 760 | "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", 761 | "league/oauth2-google": "Needed for Google XOAUTH2 authentication", 762 | "psr/log": "For optional PSR-3 debug logging", 763 | "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", 764 | "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" 765 | }, 766 | "type": "library", 767 | "autoload": { 768 | "psr-4": { 769 | "PHPMailer\\PHPMailer\\": "src/" 770 | } 771 | }, 772 | "notification-url": "https://packagist.org/downloads/", 773 | "license": [ 774 | "LGPL-2.1-only" 775 | ], 776 | "authors": [ 777 | { 778 | "name": "Marcus Bointon", 779 | "email": "phpmailer@synchromedia.co.uk" 780 | }, 781 | { 782 | "name": "Jim Jagielski", 783 | "email": "jimjag@gmail.com" 784 | }, 785 | { 786 | "name": "Andy Prevost", 787 | "email": "codeworxtech@users.sourceforge.net" 788 | }, 789 | { 790 | "name": "Brent R. Matzelle" 791 | } 792 | ], 793 | "description": "PHPMailer is a full-featured email creation and transfer class for PHP", 794 | "support": { 795 | "issues": "https://github.com/PHPMailer/PHPMailer/issues", 796 | "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.9.2" 797 | }, 798 | "funding": [ 799 | { 800 | "url": "https://github.com/Synchro", 801 | "type": "github" 802 | } 803 | ], 804 | "time": "2024-10-09T10:07:50+00:00" 805 | }, 806 | { 807 | "name": "phpoption/phpoption", 808 | "version": "1.9.3", 809 | "source": { 810 | "type": "git", 811 | "url": "https://github.com/schmittjoh/php-option.git", 812 | "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" 813 | }, 814 | "dist": { 815 | "type": "zip", 816 | "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", 817 | "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", 818 | "shasum": "" 819 | }, 820 | "require": { 821 | "php": "^7.2.5 || ^8.0" 822 | }, 823 | "require-dev": { 824 | "bamarni/composer-bin-plugin": "^1.8.2", 825 | "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" 826 | }, 827 | "type": "library", 828 | "extra": { 829 | "bamarni-bin": { 830 | "bin-links": true, 831 | "forward-command": false 832 | }, 833 | "branch-alias": { 834 | "dev-master": "1.9-dev" 835 | } 836 | }, 837 | "autoload": { 838 | "psr-4": { 839 | "PhpOption\\": "src/PhpOption/" 840 | } 841 | }, 842 | "notification-url": "https://packagist.org/downloads/", 843 | "license": [ 844 | "Apache-2.0" 845 | ], 846 | "authors": [ 847 | { 848 | "name": "Johannes M. Schmitt", 849 | "email": "schmittjoh@gmail.com", 850 | "homepage": "https://github.com/schmittjoh" 851 | }, 852 | { 853 | "name": "Graham Campbell", 854 | "email": "hello@gjcampbell.co.uk", 855 | "homepage": "https://github.com/GrahamCampbell" 856 | } 857 | ], 858 | "description": "Option Type for PHP", 859 | "keywords": [ 860 | "language", 861 | "option", 862 | "php", 863 | "type" 864 | ], 865 | "support": { 866 | "issues": "https://github.com/schmittjoh/php-option/issues", 867 | "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" 868 | }, 869 | "funding": [ 870 | { 871 | "url": "https://github.com/GrahamCampbell", 872 | "type": "github" 873 | }, 874 | { 875 | "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", 876 | "type": "tidelift" 877 | } 878 | ], 879 | "time": "2024-07-20T21:41:07+00:00" 880 | }, 881 | { 882 | "name": "psr/log", 883 | "version": "3.0.2", 884 | "source": { 885 | "type": "git", 886 | "url": "https://github.com/php-fig/log.git", 887 | "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" 888 | }, 889 | "dist": { 890 | "type": "zip", 891 | "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", 892 | "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", 893 | "shasum": "" 894 | }, 895 | "require": { 896 | "php": ">=8.0.0" 897 | }, 898 | "type": "library", 899 | "extra": { 900 | "branch-alias": { 901 | "dev-master": "3.x-dev" 902 | } 903 | }, 904 | "autoload": { 905 | "psr-4": { 906 | "Psr\\Log\\": "src" 907 | } 908 | }, 909 | "notification-url": "https://packagist.org/downloads/", 910 | "license": [ 911 | "MIT" 912 | ], 913 | "authors": [ 914 | { 915 | "name": "PHP-FIG", 916 | "homepage": "https://www.php-fig.org/" 917 | } 918 | ], 919 | "description": "Common interface for logging libraries", 920 | "homepage": "https://github.com/php-fig/log", 921 | "keywords": [ 922 | "log", 923 | "psr", 924 | "psr-3" 925 | ], 926 | "support": { 927 | "source": "https://github.com/php-fig/log/tree/3.0.2" 928 | }, 929 | "time": "2024-09-11T13:17:53+00:00" 930 | }, 931 | { 932 | "name": "symfony/deprecation-contracts", 933 | "version": "v3.5.1", 934 | "source": { 935 | "type": "git", 936 | "url": "https://github.com/symfony/deprecation-contracts.git", 937 | "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" 938 | }, 939 | "dist": { 940 | "type": "zip", 941 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", 942 | "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", 943 | "shasum": "" 944 | }, 945 | "require": { 946 | "php": ">=8.1" 947 | }, 948 | "type": "library", 949 | "extra": { 950 | "thanks": { 951 | "url": "https://github.com/symfony/contracts", 952 | "name": "symfony/contracts" 953 | }, 954 | "branch-alias": { 955 | "dev-main": "3.5-dev" 956 | } 957 | }, 958 | "autoload": { 959 | "files": [ 960 | "function.php" 961 | ] 962 | }, 963 | "notification-url": "https://packagist.org/downloads/", 964 | "license": [ 965 | "MIT" 966 | ], 967 | "authors": [ 968 | { 969 | "name": "Nicolas Grekas", 970 | "email": "p@tchwork.com" 971 | }, 972 | { 973 | "name": "Symfony Community", 974 | "homepage": "https://symfony.com/contributors" 975 | } 976 | ], 977 | "description": "A generic function and convention to trigger deprecation notices", 978 | "homepage": "https://symfony.com", 979 | "support": { 980 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" 981 | }, 982 | "funding": [ 983 | { 984 | "url": "https://symfony.com/sponsor", 985 | "type": "custom" 986 | }, 987 | { 988 | "url": "https://github.com/fabpot", 989 | "type": "github" 990 | }, 991 | { 992 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 993 | "type": "tidelift" 994 | } 995 | ], 996 | "time": "2024-09-25T14:20:29+00:00" 997 | }, 998 | { 999 | "name": "symfony/polyfill-ctype", 1000 | "version": "v1.31.0", 1001 | "source": { 1002 | "type": "git", 1003 | "url": "https://github.com/symfony/polyfill-ctype.git", 1004 | "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" 1005 | }, 1006 | "dist": { 1007 | "type": "zip", 1008 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", 1009 | "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", 1010 | "shasum": "" 1011 | }, 1012 | "require": { 1013 | "php": ">=7.2" 1014 | }, 1015 | "provide": { 1016 | "ext-ctype": "*" 1017 | }, 1018 | "suggest": { 1019 | "ext-ctype": "For best performance" 1020 | }, 1021 | "type": "library", 1022 | "extra": { 1023 | "thanks": { 1024 | "url": "https://github.com/symfony/polyfill", 1025 | "name": "symfony/polyfill" 1026 | } 1027 | }, 1028 | "autoload": { 1029 | "files": [ 1030 | "bootstrap.php" 1031 | ], 1032 | "psr-4": { 1033 | "Symfony\\Polyfill\\Ctype\\": "" 1034 | } 1035 | }, 1036 | "notification-url": "https://packagist.org/downloads/", 1037 | "license": [ 1038 | "MIT" 1039 | ], 1040 | "authors": [ 1041 | { 1042 | "name": "Gert de Pagter", 1043 | "email": "BackEndTea@gmail.com" 1044 | }, 1045 | { 1046 | "name": "Symfony Community", 1047 | "homepage": "https://symfony.com/contributors" 1048 | } 1049 | ], 1050 | "description": "Symfony polyfill for ctype functions", 1051 | "homepage": "https://symfony.com", 1052 | "keywords": [ 1053 | "compatibility", 1054 | "ctype", 1055 | "polyfill", 1056 | "portable" 1057 | ], 1058 | "support": { 1059 | "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" 1060 | }, 1061 | "funding": [ 1062 | { 1063 | "url": "https://symfony.com/sponsor", 1064 | "type": "custom" 1065 | }, 1066 | { 1067 | "url": "https://github.com/fabpot", 1068 | "type": "github" 1069 | }, 1070 | { 1071 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1072 | "type": "tidelift" 1073 | } 1074 | ], 1075 | "time": "2024-09-09T11:45:10+00:00" 1076 | }, 1077 | { 1078 | "name": "symfony/polyfill-intl-idn", 1079 | "version": "v1.31.0", 1080 | "source": { 1081 | "type": "git", 1082 | "url": "https://github.com/symfony/polyfill-intl-idn.git", 1083 | "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" 1084 | }, 1085 | "dist": { 1086 | "type": "zip", 1087 | "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", 1088 | "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", 1089 | "shasum": "" 1090 | }, 1091 | "require": { 1092 | "php": ">=7.2", 1093 | "symfony/polyfill-intl-normalizer": "^1.10" 1094 | }, 1095 | "suggest": { 1096 | "ext-intl": "For best performance" 1097 | }, 1098 | "type": "library", 1099 | "extra": { 1100 | "thanks": { 1101 | "url": "https://github.com/symfony/polyfill", 1102 | "name": "symfony/polyfill" 1103 | } 1104 | }, 1105 | "autoload": { 1106 | "files": [ 1107 | "bootstrap.php" 1108 | ], 1109 | "psr-4": { 1110 | "Symfony\\Polyfill\\Intl\\Idn\\": "" 1111 | } 1112 | }, 1113 | "notification-url": "https://packagist.org/downloads/", 1114 | "license": [ 1115 | "MIT" 1116 | ], 1117 | "authors": [ 1118 | { 1119 | "name": "Laurent Bassin", 1120 | "email": "laurent@bassin.info" 1121 | }, 1122 | { 1123 | "name": "Trevor Rowbotham", 1124 | "email": "trevor.rowbotham@pm.me" 1125 | }, 1126 | { 1127 | "name": "Symfony Community", 1128 | "homepage": "https://symfony.com/contributors" 1129 | } 1130 | ], 1131 | "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", 1132 | "homepage": "https://symfony.com", 1133 | "keywords": [ 1134 | "compatibility", 1135 | "idn", 1136 | "intl", 1137 | "polyfill", 1138 | "portable", 1139 | "shim" 1140 | ], 1141 | "support": { 1142 | "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" 1143 | }, 1144 | "funding": [ 1145 | { 1146 | "url": "https://symfony.com/sponsor", 1147 | "type": "custom" 1148 | }, 1149 | { 1150 | "url": "https://github.com/fabpot", 1151 | "type": "github" 1152 | }, 1153 | { 1154 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1155 | "type": "tidelift" 1156 | } 1157 | ], 1158 | "time": "2024-09-09T11:45:10+00:00" 1159 | }, 1160 | { 1161 | "name": "symfony/polyfill-intl-normalizer", 1162 | "version": "v1.31.0", 1163 | "source": { 1164 | "type": "git", 1165 | "url": "https://github.com/symfony/polyfill-intl-normalizer.git", 1166 | "reference": "3833d7255cc303546435cb650316bff708a1c75c" 1167 | }, 1168 | "dist": { 1169 | "type": "zip", 1170 | "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", 1171 | "reference": "3833d7255cc303546435cb650316bff708a1c75c", 1172 | "shasum": "" 1173 | }, 1174 | "require": { 1175 | "php": ">=7.2" 1176 | }, 1177 | "suggest": { 1178 | "ext-intl": "For best performance" 1179 | }, 1180 | "type": "library", 1181 | "extra": { 1182 | "thanks": { 1183 | "url": "https://github.com/symfony/polyfill", 1184 | "name": "symfony/polyfill" 1185 | } 1186 | }, 1187 | "autoload": { 1188 | "files": [ 1189 | "bootstrap.php" 1190 | ], 1191 | "psr-4": { 1192 | "Symfony\\Polyfill\\Intl\\Normalizer\\": "" 1193 | }, 1194 | "classmap": [ 1195 | "Resources/stubs" 1196 | ] 1197 | }, 1198 | "notification-url": "https://packagist.org/downloads/", 1199 | "license": [ 1200 | "MIT" 1201 | ], 1202 | "authors": [ 1203 | { 1204 | "name": "Nicolas Grekas", 1205 | "email": "p@tchwork.com" 1206 | }, 1207 | { 1208 | "name": "Symfony Community", 1209 | "homepage": "https://symfony.com/contributors" 1210 | } 1211 | ], 1212 | "description": "Symfony polyfill for intl's Normalizer class and related functions", 1213 | "homepage": "https://symfony.com", 1214 | "keywords": [ 1215 | "compatibility", 1216 | "intl", 1217 | "normalizer", 1218 | "polyfill", 1219 | "portable", 1220 | "shim" 1221 | ], 1222 | "support": { 1223 | "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" 1224 | }, 1225 | "funding": [ 1226 | { 1227 | "url": "https://symfony.com/sponsor", 1228 | "type": "custom" 1229 | }, 1230 | { 1231 | "url": "https://github.com/fabpot", 1232 | "type": "github" 1233 | }, 1234 | { 1235 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1236 | "type": "tidelift" 1237 | } 1238 | ], 1239 | "time": "2024-09-09T11:45:10+00:00" 1240 | }, 1241 | { 1242 | "name": "symfony/polyfill-mbstring", 1243 | "version": "v1.31.0", 1244 | "source": { 1245 | "type": "git", 1246 | "url": "https://github.com/symfony/polyfill-mbstring.git", 1247 | "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" 1248 | }, 1249 | "dist": { 1250 | "type": "zip", 1251 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", 1252 | "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", 1253 | "shasum": "" 1254 | }, 1255 | "require": { 1256 | "php": ">=7.2" 1257 | }, 1258 | "provide": { 1259 | "ext-mbstring": "*" 1260 | }, 1261 | "suggest": { 1262 | "ext-mbstring": "For best performance" 1263 | }, 1264 | "type": "library", 1265 | "extra": { 1266 | "thanks": { 1267 | "url": "https://github.com/symfony/polyfill", 1268 | "name": "symfony/polyfill" 1269 | } 1270 | }, 1271 | "autoload": { 1272 | "files": [ 1273 | "bootstrap.php" 1274 | ], 1275 | "psr-4": { 1276 | "Symfony\\Polyfill\\Mbstring\\": "" 1277 | } 1278 | }, 1279 | "notification-url": "https://packagist.org/downloads/", 1280 | "license": [ 1281 | "MIT" 1282 | ], 1283 | "authors": [ 1284 | { 1285 | "name": "Nicolas Grekas", 1286 | "email": "p@tchwork.com" 1287 | }, 1288 | { 1289 | "name": "Symfony Community", 1290 | "homepage": "https://symfony.com/contributors" 1291 | } 1292 | ], 1293 | "description": "Symfony polyfill for the Mbstring extension", 1294 | "homepage": "https://symfony.com", 1295 | "keywords": [ 1296 | "compatibility", 1297 | "mbstring", 1298 | "polyfill", 1299 | "portable", 1300 | "shim" 1301 | ], 1302 | "support": { 1303 | "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" 1304 | }, 1305 | "funding": [ 1306 | { 1307 | "url": "https://symfony.com/sponsor", 1308 | "type": "custom" 1309 | }, 1310 | { 1311 | "url": "https://github.com/fabpot", 1312 | "type": "github" 1313 | }, 1314 | { 1315 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1316 | "type": "tidelift" 1317 | } 1318 | ], 1319 | "time": "2024-09-09T11:45:10+00:00" 1320 | }, 1321 | { 1322 | "name": "symfony/polyfill-php80", 1323 | "version": "v1.31.0", 1324 | "source": { 1325 | "type": "git", 1326 | "url": "https://github.com/symfony/polyfill-php80.git", 1327 | "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" 1328 | }, 1329 | "dist": { 1330 | "type": "zip", 1331 | "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", 1332 | "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", 1333 | "shasum": "" 1334 | }, 1335 | "require": { 1336 | "php": ">=7.2" 1337 | }, 1338 | "type": "library", 1339 | "extra": { 1340 | "thanks": { 1341 | "url": "https://github.com/symfony/polyfill", 1342 | "name": "symfony/polyfill" 1343 | } 1344 | }, 1345 | "autoload": { 1346 | "files": [ 1347 | "bootstrap.php" 1348 | ], 1349 | "psr-4": { 1350 | "Symfony\\Polyfill\\Php80\\": "" 1351 | }, 1352 | "classmap": [ 1353 | "Resources/stubs" 1354 | ] 1355 | }, 1356 | "notification-url": "https://packagist.org/downloads/", 1357 | "license": [ 1358 | "MIT" 1359 | ], 1360 | "authors": [ 1361 | { 1362 | "name": "Ion Bazan", 1363 | "email": "ion.bazan@gmail.com" 1364 | }, 1365 | { 1366 | "name": "Nicolas Grekas", 1367 | "email": "p@tchwork.com" 1368 | }, 1369 | { 1370 | "name": "Symfony Community", 1371 | "homepage": "https://symfony.com/contributors" 1372 | } 1373 | ], 1374 | "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", 1375 | "homepage": "https://symfony.com", 1376 | "keywords": [ 1377 | "compatibility", 1378 | "polyfill", 1379 | "portable", 1380 | "shim" 1381 | ], 1382 | "support": { 1383 | "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" 1384 | }, 1385 | "funding": [ 1386 | { 1387 | "url": "https://symfony.com/sponsor", 1388 | "type": "custom" 1389 | }, 1390 | { 1391 | "url": "https://github.com/fabpot", 1392 | "type": "github" 1393 | }, 1394 | { 1395 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1396 | "type": "tidelift" 1397 | } 1398 | ], 1399 | "time": "2024-09-09T11:45:10+00:00" 1400 | }, 1401 | { 1402 | "name": "symfony/yaml", 1403 | "version": "v6.4.13", 1404 | "source": { 1405 | "type": "git", 1406 | "url": "https://github.com/symfony/yaml.git", 1407 | "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9" 1408 | }, 1409 | "dist": { 1410 | "type": "zip", 1411 | "url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", 1412 | "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", 1413 | "shasum": "" 1414 | }, 1415 | "require": { 1416 | "php": ">=8.1", 1417 | "symfony/deprecation-contracts": "^2.5|^3", 1418 | "symfony/polyfill-ctype": "^1.8" 1419 | }, 1420 | "conflict": { 1421 | "symfony/console": "<5.4" 1422 | }, 1423 | "require-dev": { 1424 | "symfony/console": "^5.4|^6.0|^7.0" 1425 | }, 1426 | "bin": [ 1427 | "Resources/bin/yaml-lint" 1428 | ], 1429 | "type": "library", 1430 | "autoload": { 1431 | "psr-4": { 1432 | "Symfony\\Component\\Yaml\\": "" 1433 | }, 1434 | "exclude-from-classmap": [ 1435 | "/Tests/" 1436 | ] 1437 | }, 1438 | "notification-url": "https://packagist.org/downloads/", 1439 | "license": [ 1440 | "MIT" 1441 | ], 1442 | "authors": [ 1443 | { 1444 | "name": "Fabien Potencier", 1445 | "email": "fabien@symfony.com" 1446 | }, 1447 | { 1448 | "name": "Symfony Community", 1449 | "homepage": "https://symfony.com/contributors" 1450 | } 1451 | ], 1452 | "description": "Loads and dumps YAML files", 1453 | "homepage": "https://symfony.com", 1454 | "support": { 1455 | "source": "https://github.com/symfony/yaml/tree/v6.4.13" 1456 | }, 1457 | "funding": [ 1458 | { 1459 | "url": "https://symfony.com/sponsor", 1460 | "type": "custom" 1461 | }, 1462 | { 1463 | "url": "https://github.com/fabpot", 1464 | "type": "github" 1465 | }, 1466 | { 1467 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1468 | "type": "tidelift" 1469 | } 1470 | ], 1471 | "time": "2024-09-25T14:18:03+00:00" 1472 | }, 1473 | { 1474 | "name": "vlucas/phpdotenv", 1475 | "version": "v5.6.1", 1476 | "source": { 1477 | "type": "git", 1478 | "url": "https://github.com/vlucas/phpdotenv.git", 1479 | "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" 1480 | }, 1481 | "dist": { 1482 | "type": "zip", 1483 | "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", 1484 | "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", 1485 | "shasum": "" 1486 | }, 1487 | "require": { 1488 | "ext-pcre": "*", 1489 | "graham-campbell/result-type": "^1.1.3", 1490 | "php": "^7.2.5 || ^8.0", 1491 | "phpoption/phpoption": "^1.9.3", 1492 | "symfony/polyfill-ctype": "^1.24", 1493 | "symfony/polyfill-mbstring": "^1.24", 1494 | "symfony/polyfill-php80": "^1.24" 1495 | }, 1496 | "require-dev": { 1497 | "bamarni/composer-bin-plugin": "^1.8.2", 1498 | "ext-filter": "*", 1499 | "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" 1500 | }, 1501 | "suggest": { 1502 | "ext-filter": "Required to use the boolean validator." 1503 | }, 1504 | "type": "library", 1505 | "extra": { 1506 | "bamarni-bin": { 1507 | "bin-links": true, 1508 | "forward-command": false 1509 | }, 1510 | "branch-alias": { 1511 | "dev-master": "5.6-dev" 1512 | } 1513 | }, 1514 | "autoload": { 1515 | "psr-4": { 1516 | "Dotenv\\": "src/" 1517 | } 1518 | }, 1519 | "notification-url": "https://packagist.org/downloads/", 1520 | "license": [ 1521 | "BSD-3-Clause" 1522 | ], 1523 | "authors": [ 1524 | { 1525 | "name": "Graham Campbell", 1526 | "email": "hello@gjcampbell.co.uk", 1527 | "homepage": "https://github.com/GrahamCampbell" 1528 | }, 1529 | { 1530 | "name": "Vance Lucas", 1531 | "email": "vance@vancelucas.com", 1532 | "homepage": "https://github.com/vlucas" 1533 | } 1534 | ], 1535 | "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", 1536 | "keywords": [ 1537 | "dotenv", 1538 | "env", 1539 | "environment" 1540 | ], 1541 | "support": { 1542 | "issues": "https://github.com/vlucas/phpdotenv/issues", 1543 | "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" 1544 | }, 1545 | "funding": [ 1546 | { 1547 | "url": "https://github.com/GrahamCampbell", 1548 | "type": "github" 1549 | }, 1550 | { 1551 | "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", 1552 | "type": "tidelift" 1553 | } 1554 | ], 1555 | "time": "2024-07-20T21:52:34+00:00" 1556 | } 1557 | ], 1558 | "packages-dev": [], 1559 | "aliases": [], 1560 | "minimum-stability": "stable", 1561 | "stability-flags": {}, 1562 | "prefer-stable": false, 1563 | "prefer-lowest": false, 1564 | "platform": {}, 1565 | "platform-dev": {}, 1566 | "plugin-api-version": "2.6.0" 1567 | } 1568 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import antfu from "@antfu/eslint-config"; 3 | 4 | export default antfu({ 5 | stylistic: false, 6 | unocss: true, 7 | ignores: ["**/site/plugins/*/index.js", "**/vendor/**"], 8 | }); 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "private": true, 4 | "scripts": { 5 | "dev": "rm -rf public/dist && concurrently \"unocss --watch\" \"vite\"", 6 | "build": "unocss && vite build", 7 | "lint": "eslint .", 8 | "lint:fix": "eslint . --fix", 9 | "test:types": "tsc --noEmit", 10 | "format": "prettier \"src/**/*\" --write" 11 | }, 12 | "devDependencies": { 13 | "@antfu/eslint-config": "^3.16.0", 14 | "@iconify-json/carbon": "^1.2.5", 15 | "@types/node": "^22.10.10", 16 | "@unocss/cli": "^65.4.3", 17 | "@unocss/eslint-plugin": "^65.4.3", 18 | "@unocss/preset-mini": "^65.4.3", 19 | "@unocss/preset-wind": "^65.4.3", 20 | "@unocss/reset": "^65.4.3", 21 | "concurrently": "^9.1.2", 22 | "eslint": "^9.18.0", 23 | "prettier": "^3.4.2", 24 | "sass": "^1.83.4", 25 | "unocss": "^65.4.3", 26 | "vite": "^6.0.11", 27 | "vite-plugin-full-reload": "^1.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | # Kirby .htaccess 2 | 3 | # Core rewrite rules 4 | 5 | 6 | RewriteEngine on 7 | 8 | # Make sure to set the RewriteBase correctly if you are running 9 | # the site in a subfolder otherwise links or the entire site will break. 10 | RewriteBase / 11 | 12 | # Block files and folders beginning with a dot, such as `.git`, except for 13 | # the `.well-known` folder, which is used for Let's Encrypt and `security.txt`. 14 | RewriteRule (^|/)\.(?!well-known\/) index.php [L] 15 | 16 | # Make site links work. 17 | RewriteCond %{REQUEST_FILENAME} !-f 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteRule ^(.*) index.php [L] 20 | 21 | 22 | 23 | # Pass the Authorization header to PHP. 24 | SetEnvIf Authorization "(.+)" HTTP_AUTHORIZATION=$1 25 | 26 | # Compression for better web performance 27 | 28 | 29 | # Force compression for mangled `Accept-Encoding` request headers. 30 | 31 | 32 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 33 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 34 | 35 | 36 | 37 | # Compress all output labeled with one of the following media types. 38 | 39 | AddOutputFilterByType DEFLATE "application/atom+xml" \ 40 | "application/javascript" \ 41 | "application/json" \ 42 | "application/ld+json" \ 43 | "application/manifest+json" \ 44 | "application/rdf+xml" \ 45 | "application/rss+xml" \ 46 | "application/schema+json" \ 47 | "application/geo+json" \ 48 | "application/wasm" \ 49 | "application/x-font-ttf" \ 50 | "application/x-javascript" \ 51 | "application/x-web-app-manifest+json" \ 52 | "application/xhtml+xml" \ 53 | "application/xml" \ 54 | "font/opentype" \ 55 | "font/otf" \ 56 | "image/bmp" \ 57 | "image/svg+xml" \ 58 | "text/cache-manifest" \ 59 | "text/calendar" \ 60 | "text/css" \ 61 | "text/html" \ 62 | "text/javascript" \ 63 | "text/plain" \ 64 | "text/markdown" \ 65 | "text/vcard" \ 66 | "text/vtt" \ 67 | "text/x-component" \ 68 | "text/x-cross-domain-policy" \ 69 | "text/xml" 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /public/bootstrap.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'index' => __DIR__, 11 | 'base' => $base, 12 | 'site' => $base . '/site', 13 | 'storage' => $storage = $base . '/storage', 14 | 'content' => $storage . '/content', 15 | 'accounts' => $storage . '/accounts', 16 | 'cache' => $storage . '/cache', 17 | 'logs' => $storage . '/logs', 18 | 'sessions' => $storage . '/sessions' 19 | ] 20 | ]); 21 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | render(); 6 | -------------------------------------------------------------------------------- /public/media/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannschopplich/kirby-vite-unocss-kit/f61ceca485d36b0c85dbd75d6d4eec26c903f730/public/media/index.html -------------------------------------------------------------------------------- /scripts/ploi-deploy.sh: -------------------------------------------------------------------------------- 1 | cd {SITE_DIRECTORY} 2 | 3 | # Create .env if not present 4 | if [ ! -f .env ] && [ -f .env.production.example ]; then 5 | cp .env.production.example .env 6 | fi 7 | 8 | # Pull changes 9 | git pull origin main 10 | 11 | # Install composer dependencies 12 | composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev 13 | 14 | {RELOAD_PHP_FPM} 15 | 16 | # Build frontend assets 17 | if [ -f package-lock.json ]; then 18 | npm ci && npm run build 19 | elif [ -f pnpm-lock.yaml ]; then 20 | npx pnpm i && npx pnpm run build 21 | fi 22 | 23 | # Clean cache 24 | rm -rf storage/cache/{SITE_DOMAIN} 25 | 26 | echo "🚀 Application deployed!" 27 | -------------------------------------------------------------------------------- /site/blueprints/files/image.yml: -------------------------------------------------------------------------------- 1 | title: 2 | de: Bild 3 | en: Image 4 | 5 | accept: 6 | type: image 7 | 8 | fields: 9 | alt: 10 | label: 11 | de: Alternativtext 12 | en: Alternative Text 13 | type: text 14 | icon: title 15 | caption: 16 | label: 17 | de: Beschriftung 18 | en: Caption 19 | type: writer 20 | icon: text 21 | inline: true 22 | -------------------------------------------------------------------------------- /site/blueprints/pages/default.yml: -------------------------------------------------------------------------------- 1 | title: 2 | de: Seite 3 | en: Page 4 | 5 | columns: 6 | - width: 2/3 7 | sections: 8 | main: sections/blocks 9 | 10 | - width: 1/3 11 | sections: 12 | meta: sections/meta 13 | images: sections/images 14 | -------------------------------------------------------------------------------- /site/blueprints/sections/blocks.yml: -------------------------------------------------------------------------------- 1 | type: fields 2 | fields: 3 | text: 4 | label: Text 5 | type: blocks 6 | fieldsets: 7 | text: 8 | label: Text 9 | type: group 10 | fieldsets: 11 | - heading 12 | - text 13 | - list 14 | - quote 15 | - line 16 | media: 17 | label: 18 | de: Medien 19 | en: Media 20 | type: group 21 | fieldsets: 22 | - image 23 | - gallery 24 | - video 25 | code: 26 | label: Code 27 | type: group 28 | fieldsets: 29 | - code 30 | - markdown 31 | -------------------------------------------------------------------------------- /site/blueprints/sections/images.yml: -------------------------------------------------------------------------------- 1 | type: files 2 | label: 3 | de: Bilder 4 | en: Images 5 | template: image 6 | sortBy: filename desc 7 | empty: 8 | de: Noch keine Bilder 9 | en: No images yet 10 | -------------------------------------------------------------------------------- /site/blueprints/sections/meta.yml: -------------------------------------------------------------------------------- 1 | type: fields 2 | fields: 3 | customTitle: 4 | label: 5 | de: Titel (Überschreiben) 6 | en: Title (Override) 7 | type: text 8 | icon: title 9 | placeholder: "{{ page.title }} – {{ site.title }}" 10 | help: 11 | de: Der Seitentitel, so wie er in Suchmaschinen erscheinen soll. Bleibt das Feld leer, wird stattdessen der Seitentitel ausgegeben. 12 | en: The page title as it should appear in search engines. If left blank, the page title will be used. 13 | description: 14 | label: 15 | de: Beschreibung (Überschreiben) 16 | en: Description (Override) 17 | type: textarea 18 | buttons: false 19 | help: 20 | de: Eine kurze Beschreibung der Seite, die von Suchmaschinen unter dem Titel angezeigt wird. 21 | en: A short description of the page that will be displayed underneath the title by search engines. 22 | thumbnail: 23 | label: 24 | de: Vorschaubild 25 | en: Thumbnail 26 | type: files 27 | query: page.files.template('image') 28 | uploads: 29 | template: image 30 | multiple: false 31 | translate: false 32 | -------------------------------------------------------------------------------- /site/blueprints/site.yml: -------------------------------------------------------------------------------- 1 | title: 2 | de: Seite 3 | en: Site 4 | 5 | tabs: 6 | dashboard: 7 | label: 8 | de: Übersicht 9 | en: Dashboard 10 | icon: dashboard 11 | columns: 12 | - width: 1/1 13 | sections: 14 | pages: 15 | type: pages 16 | headline: 17 | de: Seiten 18 | en: Pages 19 | status: all 20 | create: default 21 | info: "/{{ page.slug }}" 22 | image: page.thumbnail.toFile 23 | 24 | settings: 25 | label: 26 | de: Einstellungen 27 | en: Settings 28 | icon: settings 29 | columns: 30 | - width: 1/1 31 | sections: 32 | meta: 33 | extends: sections/meta 34 | type: fields 35 | fields: 36 | thumbnail: 37 | query: kirby.site.files.template('image') 38 | uploads: 39 | parent: kirby.site 40 | template: image 41 | -------------------------------------------------------------------------------- /site/config/config.php: -------------------------------------------------------------------------------- 1 | env('KIRBY_MODE') === 'development' || env('KIRBY_DEBUG', false), 6 | 7 | 'panel' => [ 8 | 'install' => env('KIRBY_PANEL_INSTALL', false), 9 | 'slug' => env('KIRBY_PANEL_SLUG', 'panel') 10 | ], 11 | 12 | 'yaml' => [ 13 | 'handler' => 'symfony' 14 | ], 15 | 16 | 'date' => [ 17 | 'handler' => 'intl' 18 | ], 19 | 20 | 'cache' => [ 21 | 'pages' => [ 22 | 'active' => env('KIRBY_CACHE', false), 23 | 'ignore' => fn (\Kirby\Cms\Page $page) => $page->kirby()->user() !== null 24 | ] 25 | ], 26 | 27 | 'thumbs' => [ 28 | 'format' => 'webp', 29 | 'quality' => '80', 30 | 'presets' => [ 31 | 'default' => ['format' => 'webp', 'quality' => 80], 32 | ], 33 | 'srcsets' => [ 34 | 'default' => [360, 720, 1024, 1280, 1536] 35 | ] 36 | ], 37 | 38 | 'johannschopplich.helpers' => [ 39 | 'meta' => [ 40 | 'defaults' => require __DIR__ . '/meta.php' 41 | ] 42 | ] 43 | 44 | ]; 45 | -------------------------------------------------------------------------------- /site/config/meta.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'jsonld' => [ 5 | 'WebSite' => [ 6 | 'name' => $site->title()->value(), 7 | 'description' => $site->description()->value(), 8 | 'url' => url() 9 | ] 10 | ] 11 | ]; 12 | -------------------------------------------------------------------------------- /site/plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannschopplich/kirby-vite-unocss-kit/f61ceca485d36b0c85dbd75d6d4eec26c903f730/site/plugins/.gitkeep -------------------------------------------------------------------------------- /site/snippets/blocks/heading.php: -------------------------------------------------------------------------------- 1 | level()->or('h2'); 8 | $id = Str::slug(Str::unhtml($block->text())); 9 | 10 | echo Html::tag($level, [$block->text()], ['id' => $id]); 11 | -------------------------------------------------------------------------------- /site/snippets/blocks/image.php: -------------------------------------------------------------------------------- 1 | alt(); 7 | $caption = $block->caption(); 8 | $link = $block->link(); 9 | $ratio = $block->ratio(); 10 | $img = null; 11 | 12 | if ($block->location() === 'web') { 13 | $img = Html::img($block->src(), ['alt' => $alt]); 14 | } elseif ($image = $block->image()->toFile()) { 15 | if ($alt->isEmpty()) $alt = $image->alt(); 16 | if ($caption->isEmpty()) $caption = $image->caption(); 17 | 18 | $img = Html::img( 19 | $image->url(), 20 | [ 21 | 'loading' => 'lazy', 22 | 'data-srcset' => $image->srcset(), 23 | 'data-sizes' => 'auto', 24 | 'width' => $image->width(), 25 | 'height' => $image->height(), 26 | 'alt' => $alt 27 | ] 28 | ); 29 | } else { 30 | return; 31 | } 32 | 33 | ?> 34 | $ratio], ' ') ?>> 35 | isNotEmpty()) : ?> 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | isNotEmpty()) : ?> 44 |
45 | 46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /site/snippets/footer.php: -------------------------------------------------------------------------------- 1 |
2 |

3 | Copyright Trustworthy Website 4 |

5 |
6 | -------------------------------------------------------------------------------- /site/snippets/layouts/default.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <?= $page->customTitle()->or($page->title() . ' – ' . $site->title()) ?> 14 | 15 | meta() ?> 16 | robots() ?> 17 | jsonld() ?> 18 | social() ?> 19 | 20 | 21 | 22 | js('main.ts') ?> 23 | css('main.ts') ?> 24 | 25 | isDev()): ?> 26 | 'vite-dev-css']) ?> 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 36 | 37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /site/templates/default.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |
9 |
10 | text()->toBlocks() ?> 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 19 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import "./styles/main.scss"; 2 | 3 | // Remove temporary stylesheet (to prevent FOUC) in development mode 4 | if (import.meta.env.DEV) { 5 | for (const el of document.querySelectorAll(`[id*="vite-dev"]`)) { 6 | el.remove(); 7 | } 8 | } 9 | 10 | // Auto-load modules 11 | for (const m of Object.values( 12 | import.meta.glob<{ install?: () => void | Promise }>("./modules/*.ts", { 13 | eager: true, 14 | }), 15 | )) { 16 | m.install?.(); 17 | } 18 | 19 | // Auto-load templates 20 | const templates = Object.fromEntries( 21 | Object.entries( 22 | import.meta.glob<{ default?: () => void | Promise }>( 23 | "./templates/*.ts", 24 | ), 25 | ).map(([key, value]) => [key.slice(12, -3), value]), 26 | ); 27 | 28 | const { template = "default" } = document.body.dataset; 29 | templates[template]?.().then((m) => m.default?.()); 30 | -------------------------------------------------------------------------------- /src/modules/README.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | A custom user module system. Place a `.ts` file with the following template, it will be installed automatically. 4 | 5 | ```ts 6 | export const install = () => { 7 | // Do something 8 | }; 9 | ``` 10 | -------------------------------------------------------------------------------- /src/modules/demo.ts: -------------------------------------------------------------------------------- 1 | export function install() { 2 | // eslint-disable-next-line no-console 3 | console.log("This module will run on every page"); 4 | } 5 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | // https://unocss.dev/guide/style-reset#tailwind-compat 2 | @use "@unocss/reset/tailwind-compat"; 3 | 4 | @use "uno"; 5 | -------------------------------------------------------------------------------- /src/templates/README.md: -------------------------------------------------------------------------------- 1 | # Templates 2 | 3 | Modules in this directory with the following syntax will be chunked and loaded on-demand for corresponding Kirby templates. 4 | 5 | ```ts 6 | export default () => { 7 | // Do something 8 | }; 9 | ``` 10 | 11 | ## How? 12 | 13 | We simply add the current template name to the body tag and then lazily import the asset chunk which name equals the template name. 14 | 15 | ```php 16 | 17 | ``` 18 | -------------------------------------------------------------------------------- /src/templates/default.ts: -------------------------------------------------------------------------------- 1 | export default async function () { 2 | // eslint-disable-next-line no-console 3 | console.log("Hello from the default template!"); 4 | } 5 | -------------------------------------------------------------------------------- /src/templates/home.ts: -------------------------------------------------------------------------------- 1 | export default async function () { 2 | // eslint-disable-next-line no-console 3 | console.log("Hello from the homepage"); 4 | } 5 | -------------------------------------------------------------------------------- /storage/accounts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannschopplich/kirby-vite-unocss-kit/f61ceca485d36b0c85dbd75d6d4eec26c903f730/storage/accounts/.gitkeep -------------------------------------------------------------------------------- /storage/cache/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannschopplich/kirby-vite-unocss-kit/f61ceca485d36b0c85dbd75d6d4eec26c903f730/storage/cache/.gitkeep -------------------------------------------------------------------------------- /storage/content/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannschopplich/kirby-vite-unocss-kit/f61ceca485d36b0c85dbd75d6d4eec26c903f730/storage/content/.gitkeep -------------------------------------------------------------------------------- /storage/content/error/default.txt: -------------------------------------------------------------------------------- 1 | Title: Error 2 | 3 | ---- 4 | 5 | Text: 6 | 7 | ---- 8 | 9 | Customtitle: 10 | 11 | ---- 12 | 13 | Description: 14 | 15 | ---- 16 | 17 | Thumbnail: [] 18 | 19 | ---- 20 | 21 | Uuid: e3cNttCmieqcniPR -------------------------------------------------------------------------------- /storage/content/home/default.txt: -------------------------------------------------------------------------------- 1 | Title: Home 2 | 3 | ---- 4 | 5 | Text: [{"content":{"level":"h1","text":"Kirby Vite UnoCSS Kit"},"id":"12a02ece-083a-4c87-997b-137d86594134","isHidden":false,"type":"heading"},{"content":{"text":"

These blocks are styled entirely by UnoCSS utilities. For the box surrounding this text block, a shortcut was added. UnoCSS shortcuts works similar to Tailwind/UnoCSS @apply:

"},"id":"dd9ba371-9113-467d-9d39-0d94900ee24d","isHidden":false,"type":"text"},{"content":{"code":"shortcuts: [\n { box: \"max-w-7xl mx-auto bg-gray-100 rounded-md shadow-sm p-4\" },\n]","language":"js"},"id":"94e69d50-0c0d-4ad9-ac8a-285e2de0ee0c","isHidden":false,"type":"code"},{"content":{"text":"

With @unocss/preset-icons, you can use any icons from any free icon library thanks to Iconify, which unified 100+ icon sets with 10,000+ icons into the consistent JSON format.

"},"id":"24f89682-ea11-4ce1-be24-7ba5da4580a8","isHidden":false,"type":"text"},{"content":{"code":"
\n
\n
","language":"html"},"id":"890cc8a3-cfc4-44db-b4c0-3138ae618a9a","isHidden":false,"type":"code"}] 6 | 7 | ---- 8 | 9 | Customtitle: 10 | 11 | ---- 12 | 13 | Description: 14 | 15 | ---- 16 | 17 | Thumbnail: [] 18 | 19 | ---- 20 | 21 | Uuid: WHChjLFsJAHjbtkT -------------------------------------------------------------------------------- /storage/content/site.txt: -------------------------------------------------------------------------------- 1 | Title: Kirby Vite UnoCSS Kit 2 | 3 | ---- 4 | 5 | Customtitle: 6 | 7 | ---- 8 | 9 | Description: 10 | 11 | ---- 12 | 13 | Thumbnail: [] -------------------------------------------------------------------------------- /storage/logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannschopplich/kirby-vite-unocss-kit/f61ceca485d36b0c85dbd75d6d4eec26c903f730/storage/logs/.gitkeep -------------------------------------------------------------------------------- /storage/sessions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johannschopplich/kirby-vite-unocss-kit/f61ceca485d36b0c85dbd75d6d4eec26c903f730/storage/sessions/.gitkeep -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler", 7 | "resolveJsonModule": true, 8 | "strict": true, 9 | "noEmit": true, 10 | "isolatedModules": true, 11 | "verbatimModuleSyntax": true, 12 | "skipLibCheck": true 13 | }, 14 | "include": ["src"] 15 | } 16 | -------------------------------------------------------------------------------- /unocss.config.ts: -------------------------------------------------------------------------------- 1 | import type { Theme } from "@unocss/preset-wind"; 2 | import { 3 | defineConfig, 4 | presetIcons, 5 | presetTypography, 6 | presetWind, 7 | } from "unocss"; 8 | 9 | export default defineConfig({ 10 | cli: { 11 | entry: { 12 | patterns: ["site/{snippets,templates}/**/*"], 13 | outFile: "src/styles/uno.css", 14 | }, 15 | }, 16 | shortcuts: [ 17 | { box: "max-w-7xl mx-auto bg-gray-100 rounded-md shadow-sm p-4" }, 18 | ], 19 | presets: [presetWind(), presetIcons(), presetTypography()], 20 | }); 21 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin as PostCSSPlugin } from "postcss"; 2 | import * as fs from "node:fs"; 3 | import * as path from "node:path"; 4 | import * as url from "node:url"; 5 | import { defineConfig } from "vite"; 6 | import FullReload from "vite-plugin-full-reload"; 7 | 8 | const currentDir = url.fileURLToPath(new URL(".", import.meta.url)); 9 | 10 | export default defineConfig(({ mode }) => { 11 | const isProd = mode === "production"; 12 | 13 | return { 14 | root: "src", 15 | base: isProd ? "/dist/" : "/", 16 | 17 | build: { 18 | outDir: path.resolve(currentDir, "public/dist"), 19 | emptyOutDir: true, 20 | manifest: true, 21 | rollupOptions: { 22 | input: path.resolve(currentDir, "src/main.ts"), 23 | }, 24 | }, 25 | 26 | css: { 27 | postcss: { 28 | ...(!isProd && { plugins: [exportDevStyles()] }), 29 | }, 30 | }, 31 | 32 | plugins: [FullReload("site/{snippets,templates}/**/*")], 33 | 34 | server: { 35 | cors: true, 36 | }, 37 | }; 38 | }); 39 | 40 | /** 41 | * Prevent FOUC in development mode before Vite 42 | * injects the CSS into the page 43 | */ 44 | function exportDevStyles(): PostCSSPlugin { 45 | return { 46 | postcssPlugin: "postcss-vite-dev-css", 47 | OnceExit(root) { 48 | const outDir = path.resolve(currentDir, "public/assets/dev"); 49 | fs.mkdirSync(outDir, { recursive: true }); 50 | fs.writeFileSync(path.resolve(outDir, "index.css"), root.toResult().css); 51 | }, 52 | }; 53 | } 54 | --------------------------------------------------------------------------------